Jim Majure's Blog
Packaging for Flexibility
A packaging strategy is a critical component to a flexible and adaptable code base. In many systems, however, there is no explicitly stated approach to the way system resources are separated into packages. This entry will discuss some possible strategies for a typical enterprise Java application.
Before we start talking about specific issues around packaging, we should probably describe the environment in which we are working and the goals we would like to achieve. First of all, we are talking about enterprise-grade applications. These are applications that are mission critical and will be maintained and evolved into the future. Some characteristics of our system environment include:
- Our set of requirements is imperfect, at best, and will certainly change as we develop and implement the system in a production environment
- The business environment in which the system operates is continuously evolving
- The business users will expect the application to be able to evolve along with the business environment, and do so quickly
About the only thing that we are certain about in this type of environment is that the understanding of the problem space will continue to change for both the business users and the development staff. As things change, we need to have our system structured so that it can be adapted to the changes as quickly as possible.
One of the most important elements to maintaining an adaptable and extensible code base is strong static dependency management. The packaging strategy that we choose is one of the key aspects to controlling and managing dependencies.
Given this environment, here are some of the things I think a packaging strategy should achieve:
- Separate logical areas of functionality (i.e., componentize)
- Separate the core logic of our applications from the frameworks that we use to support that logic
- Clearly manage the dependencies different components, and between components and frameworks
- Clearly identify the components' public interfaces
In the Beginning...
In Java, the traditional way to establish a namespace for a system is using the following pattern:
{com_or_org}.{company_name}.{application_name}
For Source Allies, Inc., our name spaces start with com.sourceallies. And we'll use an application called "Hit" as an example, so the name space will be: com.sourceallies.hit.
To Layer or not to Layer...
One common approach to packaging is to begin by introducing layers. A common 3 layer approach includes:
- Application Layer - contains packages and classes that directly support the software usage scenarios
- Domain Layer - contains packages and classes that represent core domain concepts and persistent data
- Infrastructure Layer - Contains utility classes, third party frameworks, and other resources that are completely abstracted away from the domain area of the application being implemented
In a layered system, dependencies always move from higher layers to lower layers. Classes in the application layer can depend on classes in the domain and infrastructure layers, but classes in the domain or infrastructure layer can never have static dependencies on the classes in the application layer.
Implicit vs. Explicit Layers...
In an explicit layering approach, the layer becomes part of the package structure. For example,
com.sourcallies.hit.app.*
com.sourcallies.hit.domain.*
com.sourcallies.hit.infra.*
At a package level, the hit.app name space can depend on the hit.domain namespace, but not vice-versa. As development proceeds, if there is a resource in the application layer that can be used by the domain layer, then it probably needs to be refactored into a more abstract resource and moved to the infrastructure layer. This is shown in the following diagram.
Often layers are used implicitly (sometimes by accident). In this case, a dependency analysis shows a clear flow of dependencies between packages without the introduction of large circular dependencies between high-level packages.
Layers are not Tiers...
Although there is correlation, a layer is not the same concept as a tier. For example, if there is some form of data that is required to support the user interface, and that data needs to be persisted to the database, then all of the classes required to do this can live in the application layer. In other words, classes corresponding to all tiers can be located in a single layer.
Regardless of whether an implicit or explicit layering scheme is used, the principles of layering should always be followed. Dependencies should managed at the name space level, and they should move from packages that directly support the application usage scenarios, through packages that contain more general domain concepts, and finally to packages that are completely abstracted from the application domain.
Layering begins to help us achieve a couple two goals of a packaging strategy - it is our first level of explicit dependency management.
Packaging for Componentization
One of our other goals is to separate our code base into functional areas, or components. When looking at this, let's work from an existing packaging approach to see how we can improve it to support components.
First, let's think about the classes that are required to implement a typical domain concept. For a "Contract" domain concept, we would have the classes shown in the following diagram:
Contract- a "model" object, that represents a contract instance and contains the persistent dataIContractService- a service interface that provides the API to save and retrieveContracts from the database, and to apply business logicContractException- an exception class that may be thrown by the service APIContractServiceImpl- an implementation of the service interface, that is still platform independentIContractDaoService- an interface that defines the API used to persist the data to the databaseHibernateContractDaoServiceImpl- a Hibernate-specific implementation class for the Dao interface
Now, let's think about where we put these classes. The Hit application uses a common practice, which is to package based on the archtype of the object class. So, we put all "model" classes in one package (regardless of the domain area of the classes), and we put all "service" objects in another package.
Consider the "Contract" concept, shown above, and a "System Variable" concept. Based on the patterns we use in Hit, we would have the following classes:
hit.model.Contract
hit.model.SystemVariable
hit.service.exception.ContractException
hit.service.exception.SystemVariableException
hit.service.IContractService
hit.service.ISystemVariableService
hit.service.impl.ContractServiceImpl
hit.service.impl.SystemVariableServiceImpl
hit.service.dao.IContractDaoService
hit.service.dao.ISystemVariableDaoService
hit.service.dao.hibernate.HibernateContractDaoServiceImpl
hit.service.dao.hibernate.HibernateSystemVariableDaoServiceImpl
The package dependcy relationships are shown in the following diagram:
From this diagram, we can see that the "model" package is not dependent on the logic or other implementation details. We can also see that our logic (in .service.impl) is not dependent on our Hibernate-specific implementation (in .service.dao.hibernate). These are good things.
However, we have no sense of whether "Contract" depends on "System Variable" or vice-versa or if the two have no dependencies on one another. Also, "SystemVariable" is clearly an application-independent concept. What if we want to use it in another application?
So we could try something like this:
hit.domain.contract.Contract
hit.domain.contract.ContractException
hit.domain.contract.IContractService
hit.domain.contract.ContractServiceImpl
hit.domain.contract.IContractDaoService
hit.domain.contract.HibernateContractDaoServiceImpl
hit.domain.systemvariable.SystemVariable
hit.domain.systemvariable.SystemVariableException
hit.domain.systemvariable.ISystemVariableService
hit.domain.systemvariable.SystemVariableServiceImpl
hit.domain.systemvariable.ISystemVariableDaoService
hit.domain.systemvariable.HibernateSystemVariableDaoServiceImpl
Now let's look at the package dependcy relationships:
This solution clearly separates the classes based on functionality. We can see from this diagram, that the "Contract" component depends on the "System Variable" component.
This solution doesn't, however, accomplish a couple of the other goals, one of which is to clearly identify the component's public API. The public API for a component should include the model objects, the service interface, and any exceptions thrown. In this case, a client of the "System Variable" component needs to "know" about these classes:
hit.domain.systemvariable.SystemVariable
hit.domain.systemvariable.SystemVariableException
hit.domain.systemvariable.ISystemVariableService
but not these, which represent the internal implementation details:
hit.domain.systemvariable.SystemVariableServiceImpl
hit.domain.systemvariable.ISystemVariableDaoService
hit.domain.systemvariable.HibernateSystemVariableDaoServiceImpl
An additional goal is to clearly separate our core logic from the external frameworks we use. In this case, the HibernateSystemVariableDaoServiceImpl class depends on the Hibernate API. So let's consider one more change:
hit.domain.systemvariable.core.SystemVariable
hit.domain.systemvariable.core.SystemVariableException
hit.domain.systemvariable.core.ISystemVariableService
hit.domain.systemvariable.impl.SystemVariableServiceImpl
hit.domain.systemvariable.impl.ISystemVariableDaoService
hit.domain.systemvariable.db.HibernateSystemVariableDaoServiceImpl
Now the package dependcy relationships look like this:
The core subpackage represents the component's public API. Clients of this component should be able to depend on core only. In this case, "Contract" depends on the core subpackage of the "System Variable" component, but doesn't depend on its internal implementation. The core package is also independent of its peer packages (which contain implementation specifics) both impl and db will depend on core, but not vice-versa.
So this strategy separates by component, and also controls dependencies with supporting frameworks.
Posted at 09:03AM Apr 30, 2007 by jim in General | Comments[0]