Package Design Checklist, General

1.1.1.    Favor placing API and implementation into separate packages

Rationale:

Simplicity, Consistency, Sefety and Evolution. Java only supports public and package scoped classes. You should obviously never mix public implementation classes with APIs (see checklist item). You can only place package scoped classes into API packages if you are certain they will be never needed in any other (implementation) package. Otherwise developers may inadvertently change their access to public, breaking the encapsulation of the API.

More importantly, Java module systems like OSGi use package boundaries for additional class loader isolation, dependency management and versioning. You won’t be able to take advantage of it if you combine API and implementation into one package.

Do this:

package com.company.product;

public class ApiClass {
   private ImplementationClass m;
}

package com.company.product.internal;

public class ImplementationClass {...}

Don’t do this:

package com.company.product;

public class ApiClass {

   private ImplementationClass m;
}

class ImplementationClass {...} //package scoped

Exceptions:

Very rarely, a small number of package scoped classes are useful when a separate implementation package adds no benefits, only complexity.

1.1.2.    Favor placing APIs into high-level packages and implementation into lower-level packages

Rationale:

Simplicity and Consistency. Java packages are organized into a hierarchy (tree). By convention, packages close to the base of this hierarchy are generic or more frequently used (ex. java.util) while deeper nested packages are more specialized or less frequently used (ex. java.util.concurrent.locks). API packages typically being the only public part of a component, service, or application are expected to be in the root namespace or as close as possible to the root namespace (package) reserved for the said component, service or application. Implementation packages should be at a lower level, preferably under the API package. Developers scanning the package structure for the API should be able to locate it quickly, without the need to scan through uninteresting implementation packages.

Do this:

package com.company.product.service;

public class ApiClass {...}

package com.company.product.service.internal;

public class ImplementationClass {...}

Don’t do this:

package com.company.product.service.this.that.other.api;

public class ApiClass {...}

package com.company.product.service.this.that;

public class ImplementationClass {...}

Exceptions:

The package structure of existing applications may be organized using different patterns. For consistency, follow the convention(s) already in place.

API and implementation may reside in distinct branches of the package tree, especially if multiple implementations are expected or supported. For example, ActiveMQ in the package org.apache.activemq implements the JMS API defined in the package javax.jms.

If a large API is split into multiple packages, these packages can be placed below a common root namespace. For example, a streaming API may have two packages: com.company.streaming.audio and com.company.streaming.video.

1.1.3.    Consider breaking up large APIs into several packages

Rationale:

Simplicity. Packages containing many classes can be difficult to understand and use. If some of the classes are used infrequently or only in specific scenarios, you should move them into their own package. When you do this, you are helping users determine which classes they need for which use cases. They will find the API easier to use if they do not need to understand every single class before they can start working.

Exceptions:

Breaking up API packages is only useful if the majority of use cases can be implemented by importing classes from one or two packages. For example, if the API has two packages, most use cases should require classes from one or the other. Remember that importing from two or more packages instead of one makes the use case slightly more complex. If you have strong dependencies between classes, you should leave them in the same package.

You should never break up APIs into packages based on design or implementation considerations, only along usage scenarios. Remember to consider the perspective of the caller.

1.1.4.    Consider putting API and implementation packages into separate Java archives

Rationale:

Safety and Evolution. This separation helps differentiate between build time and run time dependencies. Only the API classes are needed to build the client . Both API and implementation classes are needed to run it. By not requiring Java archives containing public implementation classes on the classpath at build time, you can further reduce (but cannot eliminate) the risk of clients inadvertently accessing implementation classes. This may cause unexpected errors and may break clients when the implementation changes.

Exceptions:

This defensive practice can significantly increase the number of Java archives. Not recommended for small modules.

If you use a Java module system like OSGi, you can package API and implementation together and the system ensures that only the API is visible to clients.

1.1.5.    Avoid (minimize) internal dependencies on implementation classes in APIs

Rationale:

Evolution. Strong internal dependencies will make it more difficult to change the implementation without making non-backwards compatible changes to either API signature or its behavior. For example, if you tightly couple a simple generic storage API with its implementation using JDBC and RDBMS, it may be prove challenging to re-implement the same API later on top of a file system or an LDAP directory service. Ideally, an API should be a separate layer on top of its implementation, not simply the public part of the implementation.

Imagine an API package which defines just Java interfaces and a few static factory methods to return implementations of these interfaces. All the implementation classes reside in separate packages. With this design, it is trivial to swap implementations (even at runtime) by changing the classes the factory methods return. Although we do not advocate this interface-only design approach to APIs (there are many other design considerations), the number of explicit dependencies between API and implementation packages (coupling) is a good predictor of how difficult it will be to evolve APIs independently from their implementation.

Exceptions:

Minimizing implementation dependencies requires more code and the use of appropriate design patterns. You need to weigh these costs against the benefits.

1.1.6.    Avoid unnecessary API fragmentation

Rationale:

Simplicity. Uses cases which import classes from multiple packages are more complex and harder to understand. Ideally, all API classes needed should come from a single package. Never break up API packages based on implementation needs.

Do this:

package com.company.product.service;

public class MyClass {...}
public class MyException extends Exception {...}

Don’t do this:

package com.company.product.service.core;

public class MyClass {...}

package com.company.product.service.common.exceptions.

public class MyException extends Exception {...}

Exceptions:

Large APIs should be breaken up into smaller packages based on usage patterns. See checklist item.

1.1.7.    Do not place public implementation classes in the API package

Rationale:

Simplicity, Consistency, Safety and Evolution. Users will have difficulty identifying which classes are part of the API and there is a big risk that they will inadvertently call public methods on implementation classes leading to unexpected errors. The client code may also stop working if implementation changes in later releases.

1.1.8.    Do not create dependencies between callers and implementation classes

Rationale:

Simplicity, Safety and Evolution. It defeats the purpose of separating API and implementation if callers are either forced or coaxed into importing implementation classes into their code. It is not enough to have implementation classes in separate packages. You cannot use them as the type of public fields, parameters, return values or exceptions either. Even if you put a “Do not use” or “Implementation use only” warning on such fields and methods, callers will still use them. It is much better not to have them at all.

Don’t do this:

public com.company.product.service;

public class ApiClass {

//IMPLEMENTATION ONLY!! DO NOT USE!!
public ApiClass(ImplementationClass impl) {...}
public void doSomething() throws ImplementationException {...}
}

public com.company.product.service.internal;

public class ImplementationClass {...}
public class ImplementationException extends Exception {...}

Exceptions:

None. No matter how challenging it feels to avoid implementation types in public API signatures, it is always possible with the correct combination of Java language features and design patterns.

1.1.9.    Do not place unrelated APIs into the same package

Rationale:

Simplicity and Consistency. Each API deserves its own package, so that developers can focus on the features they need, without any distracting clutter. The most frequently heard argument for putting several APIs into the same package is their small size. This argument fails to consider that APIs evolve, and what is a small package today can easily become a very large package containing a hodge-podge of unrelated features over time. The java.util package is the best know example.

1.1.10.  Do not place API and SPI into the same package

Rationale:

Simplicity, Consistency and Evolution. APIs and SPIs serve different purpose, are used differently, and evolve differently. APIs expose functionality to use. SPIs define functionality to implement and may offer certain facilities to help implement it. A related API and SPI pair may share some of the simpler public support types leading you to believe they belong in the same package. They don’t. Put the shared support types into the API package (making it the self-contained package) and make the SPI package depend on it (making it a bit more complex to use). This is in line with the expectations of most developers, that APIs are easier to use than SPIs.

1.1.11.  Do not move or rename the package of an already released public API

Rationale:

Evolution. Changing the fully qualified name of the API package breaks both binary and source backwards compatibility with existing clients.

Exceptions:

None

 

Creative Commons Licence
This work is licensed under a Creative Commons Attribution-ShareAlike 2.5 Canada License.

Advertisements

2 Responses to Package Design Checklist, General

  1. pik says:

    Hi, nice page, thanks.

    I have a question. Imagine you have a service interface, one or more implementation(s) and a wrapper around the java service loader to access a registered implementation (let’s forget how is selected), how would you structure the packages ? Suppose a Dictionary service, I use this

    com.example.dictionary.Dictionary
    com.example.dictionary.impl1.DictionaryImpl1
    com.example.dictionary.impl2.DictionaryImpl2
    com.example.dictionary.service.DictionaryProvider

    Then my clients use

    Dictionary d = DictionaryProvider.getDictonary();

    I have doubts about whether the [service] package should be named [provider], [loader] or something else..I think that [DictionaryProvider] and [Dictionary] cannot stay in the same package because if you depend on one class of a package you actually depend on all classes of that package, while i want as little as possible classes to know about the [DictionaryProvider]

    Also I am not sure how to name, in general, SPI interfaces packages, imagine I build application [xpic] that deals with images, the API main packages are in [org.example.xpic], imagine I abstract a way to let other people read different image formats, implementing a [ImageReader] interface, where should I put this interface, the default implementations and the service loader ? My idea is this:

    org.example.xpic – custom api of XPIC
    org.example.xpic.imagereader – pluggable service
    org.example.xpic.imagereader.impl – default implementation(s)
    org.example.xpic.imagereader.provider – accessor for the opportune service

    what do you think ?

    Thanks

    • I think you should put DirectoryProvider into the same package as Directory. If the caller needs access to DirectoryProvider directly, it is an API class. I might not fully understand your concerns about dependencies. If you do not want others to inadvertently call or subclass methods from this DirectoryProvider class, make these methods private and the class itself final. Then put the actual implementation class into another package. Make DirectoryProvider act like a simple facade, redirecting calls to this other class. It is also possible that your issue is that DirectoryProvider is also a “helper” class in your SPI, as explained below. The same method, splitting it into two, a regular API class and a helper API class for service providers works in this case as well.

      As for the best name for the class, a more concrete word is better. DirectoryProvider does not tell me much about what the class actually does. If the class is used to select from a list of available dictionaries, for example, this behaviour is better illustrated by the naming:

      Dictionary d = AvailableDictionaries.selectDictionary(“German”).

      SPIs are indeed a bit more complicated. As you have already discovered, classes and interfaces in a SPI serve different functions. At the core there are the pluggable interfaces service providers need to implement. These may depend on some classes from the end user API. In addition there may be many other classes offered to help service providers with this implementation task, like in your example. You can consider these classes as a separate, helper API. Finally, the helper API itself may have some internal implementation details, which, being irrelevant to the SPI user, you may want to put away into a separate package. So your package structure looks reasonable to me, based on what what little information you could share here.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: