36.1. Package Organization Guidelines
Guideline: Package Functionally Cohesive Vertical and Horizontal Slices
The basic "intuitive" principle is modularization based on functional cohesiontypes (classes and interfaces) are grouped together that are strongly related in terms of their participation in a common purpose, service, collaborations, policy, and function. For example, all the types in the NextGen Pricing package are related to product pricing. The layers and packages in the NextGen design are organized by functional groups.In addition to the usually sufficient informal guesswork on grouping by function ("I think class SalesLineItem belongs in Sales ") another clue to functional grouping is a cluster of types with strong internal coupling and weaker extra-cluster coupling. For example, Register has a strong coupling to Sale , which has a strong coupling to SalesLineItem .Internal package coupling, or relational cohesion , can be quantified, although such formal analysis is rarely of practical necessity. For the curious, one measure is:Where NumberOfInternalRelations includes attribute and parameter relations, inheritance, and interface implementations between types in the package.A package of 6 types with 12 internal relations has RC=2. A package of 6 types with 3 intra-type relations has RC=0.5. Higher numbers suggest more cohesion or relatedness for the package.Note that this measure is less applicable to packages of mostly interfaces; it is most useful for packages that contain some implementation classes.A very low RC value suggests either:
- The package contains unrelated things and is not factored well.
- The package contains unrelated things and the designer deliberately does not care. This is common with utility packages of disparate services (e.g., java.util ), where high or low RC is not important.
- It contains one or more subset clusters with high RC, but overall does not.
Guideline: Package a Family of Interfaces
Place a family of functionally related interfaces in a separate packageseparate from implementation classes. The Java technologies EJB package javax.ejb is an example: It is a package of at least twelve interfaces; implementations are in separate packages.
Guideline: Package by Work and by Clusters of Unstable Classes
The context for this discussion is that packages are usually the basic unit of development work and of release. It is less common to work on and release just one class. Unless a package is massive or very complex, a developer is often responsible for all the types within it.Suppose 1) there is an existing large package P1 with thirty classes, and 2) there is a work trend that a particular subset of ten classes (C1 through C10) is regularly modified and re-released.In this case, refactor P1 into P1-a and P1-b, where P1-b contains the ten frequently worked on classes.Thus, the package has been refactored into more stable and less stable subsets, or more generally, into groups related to work. That is, if most types in a package are worked on together, then it is a useful grouping.Ideally, fewer developers have a dependency on P1-b than on P1-a, and by factoring out this unstable part to a separate package, not as many developers are affected by new releases of P1-b as by re-releasing the larger original package P1.Note that this refactoring is in reaction to an emerging work trend. It is difficult to speculatively identify a good package structure in very early iterations. It incrementally evolves over the elaboration iterations, and it should be a goal of the elaboration phase (because it is architecturally significant) to have the majority of the package structure stabilized by elaboration completion.This guideline illustrates the basic strategy: Reduce widespread dependency on unstable packages .
Guideline: Most Responsible Are Most Stable
If the most responsible (depended-on) packages are unstable, there is a greater chance of widespread change dependency impact. As an extreme case, if a widely used utility package such as com.foo.util changed frequently, many things could break. Therefore, Figure 36.1 illustrates an appropriate dependency structure.
Figure 36.1. More responsible packages should be more stable.
[View full size image]
- It contains only or mostly interfaces and abstract classes.
- For example, java.sql contains eight interfaces and six classes, and the classes are mostly simple, stable types such as Time and Date .
- It has no dependencies on other packages (it is independent), or it depends on other very stable packages, or it encapsulates its dependencies such that dependents are not affected.
- For example, com.foo.nextgen.domain.posruleengine hides its rule engine implementation behind a single facade object. Even if the implementation changes, dependent packages are not affected.
- It contains relatively stable code because it was well-exercised and refined before release.
- For example, java.util .
- It is mandated to have a slow change schedule.
- For example, java.lang , the core package in the Java libraries, is simply not allowed to change frequently.
Guideline: Factor out Independent Types
Organize types that can be used independently or in different contexts into separate packages. Without careful consideration, grouping by common functionality may not provide the right level of granularity in the factoring of packages.For example, suppose that a subsystem for persistence services has been defined in one package com.foo.service.persistence . In this package are two very general utility/helper classes JDBCUtililities and SQLCommand . If these are general utilities for working with JDBC (Java's services for relational database access), then they can be used independently of the persistence subsystem, for any occasion when the developer is using JDBC. Therefore, it is better to migrate these types into a separate package, such as com.foo.util.jdbc . Figure 36.2 illustrates.
Figure 36.2. Factoring out independent types.
Guideline: Use Factories to Reduce Dependency on Concrete Packages
One way to increase package stability is to reduce its dependency on concrete classes in other packages. Figure 36.3 illustrates the "before" situation.
Figure 36.3. Direct coupling to concrete package due to creation.
Figure 36.4. Reduced coupling to a concrete package by using a factory object.
[View full size image]
Domain Object Factory Pattern
The use of domain object factories with interfaces for the creation of all domain objects is a common design idiom. I have seen it mentioned informally in design literature as the Domain Object Factory pattern, but don't know of a published reference.
Guideline: No Cycles in Packages
If a group of packages have cyclic dependency, then they may need to be treated as one larger package in terms of a release unit. This is undesirable because releasing larger packages (or package aggregates) increases the likelihood of affecting something.There are two solutions:
- Factor out the types participating in the cycle into a new smaller package.
- Break the cycle with an interface.
1. | Redefine the depended-on classes in one of the packages to implement new interfaces. |
2. | Define the new interfaces in a new package. |
3. | Redefine the dependent types to depend on the interfaces in the new package, rather than the original classes. |
Figure 36.5 illustrates this strategy.