For package design, I first divide by layer, then by some other functionality.
There are some additional rules:
- layers are stacked from most general (bottom) to most specific (top)
- each layer has a public interface (abstraction)
- a layer can only depend on the public interface of another layer (encapsulation)
- a layer can only depend on more general layers (dependencies from top to bottom)
- a layer preferably depends on the layer directly below it
So, for a web application for example, you could have the following layers in your application tier (from top to bottom):
- presentation layer: generates the UI that will be shown in the client tier
- application layer: contains logic that is specific to an application, stateful
- service layer: groups functionality by domain, stateless
- integration layer: provides access to the backend tier (db, jms, email, ...)
For the resulting package layout, these are some additional rules:
- the root of every package name is
<prefix.company>.<appname>.<layer>
- the interface of a layer is further split up by functionality:
<root>.<logic>
- the private implementation of a layer is prefixed with private:
<root>.private
Here is an example layout.
The presentation layer is divided by view technology, and optionally by (groups of) applications.
com.company.appname.presentation.internal
com.company.appname.presentation.springmvc.product
com.company.appname.presentation.servlet
...
The application layer is divided into use cases.
com.company.appname.application.lookupproduct
com.company.appname.application.internal.lookupproduct
com.company.appname.application.editclient
com.company.appname.application.internal.editclient
...
The service layer is divided into business domains, influenced by the domain logic in a backend tier.
com.company.appname.service.clientservice
com.company.appname.service.internal.jmsclientservice
com.company.appname.service.internal.xmlclientservice
com.company.appname.service.productservice
...
The integration layer is divided into 'technologies' and access objects.
com.company.appname.integration.jmsgateway
com.company.appname.integration.internal.mqjmsgateway
com.company.appname.integration.productdao
com.company.appname.integration.internal.dbproductdao
com.company.appname.integration.internal.mockproductdao
...
Advantages of separating packages like this is that it is easier to manage complexity, and it increases testability and reusability. While it seems like a lot of overhead, in my experience it actually comes very natural and everyone working on this structure (or similar) picks it up in a matter of days.
Why do I think the vertical approach is not so good?
In the layered model, several different high-level modules can use the same lower-level module. For example: you can build multiple views for the same application, multiple applications can use the same service, multiple services can use the same gateway. The trick here is that when moving through the layers, the level of functionality changes. Modules in more specific layers don't map 1-1 on modules from the more general layer, because the levels of functionality they express don't map 1-1.
When you use the vertical approach for package design, i.e. you divide by functionality first, then you force all building blocks with different levels of functionality into the same 'functionality jacket'. You might design your general modules for the more specific one. But this violates the important principle that the more general layer should not know about more specific layers. The service layer for example shouldn't be modeled after concepts from the application layer.