Levelize Modules
by kirk knoernschild
Statement
Module relationships should be levelized.
Description
Levelized modules demands that module relationships be acyclic. Any cycles in module relationships therefore prevents levelization. There is a close relationship between levelized modules and physical layers, though the two are not the same. Physical layers aims to create one or more modules that are functionally equivalent to the typical layers composing an application. Levelized modules are more fine-grained than physical layers. While a typical software system may have only three or four layers, within each layer might exist multiple levels.
To levelize modules relationships, do the following:
- External modules are assigned level zero.
- Modules dependent only on level zero modules are assigned level one.
- Modules dependent on level one are assigned level two.
- Modules dependent on level n are assigned level n + 1.
Figure 1 shows levelized modules. Even though the topclient.jar module is dependent on client.jar, a Level 1 module, topclient.jar is also dependent on the midlevel.jar module, which is a Level 2 module. Because of this, topclient.jar module is a Level 3 module. More generally, if a module is dependent on an n – 1 leve module, the module is an n level module, even if it’s also dependent on modules lower in the level hierarchy.

Figure 1: Levelized Modules
Implementation Variations
Levels and layers, what’s the difference? Layers have much more do with responsibility, whereas levels have much more to do with understanding the system structure, especially the dependencies. Levels are more granular than layers. Within a single layer might exist multiple levels, but multiple layers can never exist at a single level.
The Levelized Modules pattern sits squarely between Physical Layers and Acyclic Relationships (not just in the book, but in concept, as well). Levelized Modules demands that the module structure be acyclic. If it weren’t, the cycle would make it impossible to clearly identify the levels. And as we discussed above, levels tend to be a bit more granular than layers.
In fact, there aren’t many implementation variations of Levelized Modules, which brings into question it’s value as a pattern to begin with. But I’ve decided to include it because it can be valuable to levelize modules as it provides an extra mechanism to more easily understand module relationships within the conceptual layers. Either a system’s modules are levelized, or they aren’t! But if the modules are levelized, than it’s a guarantee that the modules also exhibit acyclic relationships, though levelization does not guarantee layers because, again, levelization emphasizes dependencies between modules and layers emphasizes responsibilities. There are a few important things to think about when levelizing modules, though.
Coarse grained modules at lower levels of the system have a significant impact on the ability to easily reuse these modules. In general, lower level modules should have fewer outgoing dependencies and more incoming dependencies. In other words, modules in lower level layers should be lower level modules, and these modules should be finer grained and lighter weight than higher level modules. Likewise, modules in higher level layers will likely be higher level modules, though it’s perfectly acceptable that a Level 1 module exist in a higher level layer. For instance, a UI Utility module may have no outgoing dependencies, and therefore be a Level 1 module, but exist in the UI layer because of it’s behavior. At this point, it’s easy to question why this is valuable. We’ll see shortly!
An especially important piece of criteria to consider when levelizing modules is whether you’ll allow module levels to span layers. The decision to enforce a strict levelization scheme over allowing a relaxed scheme will have a significant architectural impact. In theory, a strict scheme might seem better, but in practice a relaxed scheme is easier. A mixture of the two is likely most pragmatic, and it must be a conscious decision each time you decide to allow a levelized module to span multiple layers.
Consequences
Levelization can provide insight to the complexity of dependencies within various layers. For instance, a level 5 module found in the data access layer indicates a rather complex set of dependencies for a module that is very likely used heavily throughout the system. Excessive dependencies in lower level modules have the greatest capacity to increase overall cost of maintaining a system. A good candidate for refactoring, perhaps!
Levelized Modules also offer the opportunity to perform a Levelized Build. If a team cannot easily identify the levels within the system, the only opportunity to perform a Levelized Build exists across the Physical Layers, which may not offer the flexibility necessary. For very large and complex systems, a Levelized Build might be necessary to keep build time fast, which is especially important for teams practicing continuous integration.
Levelization also help improve the module testability. Test Modules can be created for each module, and the levelization will provide guidance on the module test strategy. Lower level modules are inherently easier to test that higher level modules because they lack the complex dependency structure. For instance, modules in the UI layer can be perceived as more difficult to test independently because we have to create mocks and stubs for all of their dependencies. But a Level 1 module in the UI layer indicates no outgoing dependencies, and therefore provides insight to how easy the module will be to bring under test.
Levelization improves ability to understand the system structure. The team has the ability to extract and understand the module relationships in a large software system, allowing us to efficiently and accurately verify throughout the development lifecycle that these dependencies are consistent with our overall architectural vision. Levelization also provides insight to how reusable modules are. For example, reusing a level 3 module comes with the implicit knowledge that one or more level 2 and one or more level 1 modules must also be included in the application.
Levelization can serve as the foundation for an enterprise reuse strategy. Look at it this way. If modules are levelized, lower level modules are inherently easier to reuse because they have no outgoing dependencies. Because they have no outgoing dependencies, we can build these modules independent of the rest of the system. This separate build means that the module can evolve independently. Development of the module can exist outside the context of the application, in parallel, and each time the module is built, it can be included as part of the application (picture would really help here illustrating what I’m talking about – separate IDE project, VC project, build project, binary pulled into IDE, not source).
Sample
As mentioned, levelizing modules is slightly different than physical layers. Figure 2 illustrates this difference. As can be seen here, the levels don’t necessarily coincide with layers. At least, a single layer isn’t necessarily confined to contain a specific set of levels. The billpayutils.jar module is in the control layer based on the set of responsibilities it provides. However, it’s lack of dependence on any level 3 or level 4 modules in the domain layer Even though the billpayutils.jar module is in the control layer make it a level 2 component because it’s only dependency is on the level 1 dbutils.jar module.
Because the modules are levelized, we now have the opportunity to perform a levelized build, and we can more clearly understand build order. The Level 1 dbutils.jar module must be built first. Once finished, possibly as part of a completely separate build, the the billdata.jar and findata.jar modules can be built. Yet so too can the billpayutils.jar module, even though it’s in the Control Layer. Unless we understand module levelization, the opportunity to maximize the efficiency of the build may be lost. So too might we lose the opportunity to realize many of the other advantages of levelization.

Figure 2: Levelized Modules and Physical Layers
Wrapping Up
Levelize Modules sits squarely between Acyclic Modules and Physical Layers. If the module structure is acyclic, then module relationships can be levelized. Levelization helps understand the dependency structure of the application, and it’s benefits include build optimization, reuse, ease of testing, and a clear understanding of system structure.
Comments
I’m not sure I agree with this one (see comments on Levelized Build). Modularity forms a tree, not a strict set of levels. In fact, one might argue that the levelization here is a special case of a tree where there is only one branch, but I don’t see that everything needs to be funnelled in this way.
Actually I think this is a valuable and otherwise underrepresented topic although I also think it needs a lot more work.
Modularity will in fact form a directed acyclic graph, not just a tree. If your module dependency graph is a directed acyclic graph then you can apply a topological sort. While there can be multiple such sorts for any DAG it does offer an automated way to get you to a “levelized” view of your application.
In fact this is exactly the approach many testing and build tools take to get a valid order for execution.
In fact we used exactly this approach to generate the start order for the bundles in a rather complex OSGi application. We then took the basic sorting tool to generate graphs to give us insight into our architecture and to find exactly the kinds of issues that are discussed in this pattern: complexity at different levels, size of modules, inbound and outbound dependencies, etc.