-
Notifications
You must be signed in to change notification settings - Fork 129
Modular Architecture
Applying the Layered Architecture for structuring a large software system into maintainable parts might not be enough. The Modular Architecture can solve this issue. It extends the layering by introducing another dimension to separate the software system.
Separation dimensions
- Layers separate the software artifacts into technical groups (e.g. Presentation layer contains UI related code).
- Modules separate the software artifacts into distinct parts which are often specific domain aspects (e.g. an email client might separate the email management implementation and the address book implementation into separate modules).
Figure 1: Shows the two separation dimensions of layers and modules.
Applying the Modular Architecture gives us the following advantages:
- Provides a simple and clean software structure.
- Prevents that unrelated classes depend on each other.
- Changing the implementation of a module doesn’t affect other modules.
- Reduces the efforts to maintain and extend the software system. This comes because of fewer dependencies in the software system.
- Helps to scale the software development to more development teams. A Modular Architecture requires good interfaces between the modules. These interfaces can be used to separate the responsibilities of the development teams.
- Developers can concentrate on their own modules and don’t need to understand the implementation of the other modules. They just work with the “documented” interfaces of the other modules.
- Simplifies unit testing because dependent modules can be replaced by mock implementations.
Note: These advantages are not for free! They are seen only if the development teams follow the Software Architecture rules. And that requires a severe discipline of all involved developers.
The three types participating in a Modular Architecture are: Module; Interface; Application Framework.
Figure 2: Shows the participants of a Modular Architecture.
A module contains all software artifacts for a distinct part of the software system. This distinct part represents often a specific domain/business aspect.
Characteristics
- A module contains all the types that are necessary to implement the distinct part of the software system.
- A module is responsible for all technical aspects. This can include the user interface or the persistency. Therefore, a module consists of various layers.
- A module must not have a direct dependency to another module.
- Communication between modules must be decoupled by an Interface.
- A module can be replaced by another module without the need to recompile the software system.
- A module consists of one or more .NET Assemblies.
Common Issues
- A module has an assembly reference to another module. As a result, these modules aren’t decoupled of each other.
- Avoid creating too much modules in a software system. The overall complexity will rise because the communication between the modules must be decoupled.
- A module does not focus on the specific part for which it is responsible for. This results in a negative impact on the understandability and manageability of the code.
- Avoid that multiple modules work on the same data source (e.g. data base). A module should not be able to influence other modules in any way except through the interfaces.
The interface exposes the public functionality of a module. Modules can communicate via these interfaces with each other.
Characteristics
- An interface contains the types that are necessary to expose the public functionality of one module.
- Other modules might use this public functionality. Thus, they have a direct dependency to the interface.
- The types used by an interface can be:
- Interfaces
- Data Transfer Objects (DTO). These are simple immutable objects without any logic.
- EventArgs. These classes are similar to DTOs.
- The number of types in the interface assembly should be low. This ensures low coupling between the modules.
- An interface consists of one or more .NET Assemblies. In most scenarios only one .NET Assembly is used.
Common Issues
- If the interface contains a lot of types then this can be a sign that the module boundaries are not chosen well. Bloating interfaces tighten the coupling between the modules and this lowers the advantages of the Modular Architecture (see 2. Motivation).
- Every module that exposes public functionality must provide it’s on interface assemblies. An interface is implemented by only one module but it can be used by multiple modules.
- Avoid exposing the module’s domain objects via the interface. Instead, create a DTO object which contains the required data.
Quality
Changing the interfaces has a huge impact on the whole software system. Therefore, the quality of the interfaces is very important. Here are some recommendations to improve the quality of the interface assemblies:
- Apply code reviews.
- Activate Code Analysis with “Microsoft All Rules”.
- Enable XML Comments. Use them to document the interface members.
- Write a document about the usage of the module’s public functionality. Consider to add important design decisions regarding the interface to this document.
The application framework is the foundation for the software system. It contains all the types that should be shared between multiple modules.
Characteristics
- The application framework might provide reusable types of various layers. Examples:
- Presentation layer: Custom controls, global ResourceDictionaries
- Domain layer: Custom base class for domain objects
- The WPF Application Framework (WAF) can also be seen as an application framework. It provides types that can be used by all modules (e.g. ViewModel).
- The number of application frameworks used by a software system is not limited. But I would keep it low for better maintainability.
Common Issues
- Avoid using the application framework to overcome the effort for decoupling the module communication.
- Do not implement types that are used by only one module. These types should reside within the responsible module.
Quality
Changing the application framework has a major impact on the whole software system. All modules might have a dependency to this framework. For that reason, the quality is very important. Here are some recommendations to improve the quality of the application framework:
- Apply code reviews.
- Activate Code Analysis with “Microsoft All Rules”.
- Enable XML Comments. Use them to document the public types and members.
- Write a document about the usage and architecture of the application framework.
Sometimes it is not appropriate to separate a part of the system into two modules because they would have to work tightly together. Consequently, this would create a bloating interface for the communication.
In that case the usage of an extension might be the better choice. An extension is part of the module and thus, it is able to access the internal implementation.
Figure 3: Shows the dependency between the core implementation and the extension.
Example
The BookLibrary sample application comes with a reporting extension. This extension allows the user to create reports about the books in the library. The extension needs full access to the Book and Person entities (domain objects).
Characteristics
- The extension can easily be replaced with another implementation (e.g. a mock implementation to simplify the unit testing of the module).
- The module might be implemented in a way that the availability of the extension is optional.
- An extension is part of one module. It cannot be reused by other modules.