Modularity is designing a system that is divided into a set of functional units (named modules) that can be composed into a larger application. A module represents a set of related concerns. It can include a collection of related components, such as features, views, or business logic, and pieces of infrastructure, such as services for logging or authenticating users. Modules are independent of one another but can communicate with each other in a loosely coupled fashion.
A composite application exhibits modularity. For example, consider an online banking program. The user can access a variety of functions, such as transferring money between accounts, paying bills, and updating personal information from a single user interface (UI). However, behind the scenes, each of these functions is a discrete module. These modules communicate with each other and with back-end systems such as database servers. Application services integrate components within the different modules and handle the communication with the user. The user sees an integrated view that looks like a single application.
Figure 1 illustrates a design of a composite application with multiple modules.
Why Choose a Modular Design?
The following scenarios describe why you might want to choose a modular design for your application:
- Simplified modules. Properly defined modules have a high internal cohesion and loose coupling between modules. The coupling between the modules should be through well-defined interfaces.
Developing and/or deploying modules independently. Modules can be developed, tested, and/or deployed on independent schedules when modules are developed in a loosely coupled way. By doing this, you can do the following:
- You can independently version modules.
- You can develop and test modules in isolation.
- You can have modules developed by different teams.
- Loading modules from different locations. A Windows Presentation Foundation (WPF) application might retrieve modules from the Web, from the file system and/or from a database. A Silverlight application might load modules from different XAP files. However, most of the time, the modules come from one location; for example, there is a specific folder that contains the modules or they are in the same XAP file.
- Minimizing download time. When the application is not on the user's local computer, you want to minimize the time required to download the modules. To minimize the download time, only download modules that are required to start-up the application. The rest are loaded and initialized in the background or when they are required.
- Minimizing application start-up time. To get part of the application running as fast as possible, only load and initialize the module(s) that are required to start the application.
- Loading modules based on rules. This allows you to only load modules that are applicable for a specific role. An application might retrieve from a service the list of modules to load.
Designing a Modular System
When you develop in a modularized fashion, you structure the application into separate modules that can be individually developed, tested, and deployed by different teams. Modules can enforce separation of concerns by vertically partitioning the system and keeping a clean separation between the UI and business functionality. Not having modularity makes it difficult for the team to introduce new features and makes the system difficult to test and to deploy.
The following are general guidelines for developing a modular system:
- Modules should be opaque to the rest of the system and initialized through a well-known interface.
- Modules should not directly reference one another or the application that loaded them.
- Modules should use loosely coupled techniques, such as shared services, to communicate with the application or with other modules, instead of communicating directly.
- Modules should not be responsible for managing their dependencies. These dependencies should be provided externally, for example, through dependency injection.
- Modules should not rely on static methods that can inhibit testability.
- Modules should support being added and removed from the system in a pluggable fashion.
When you design a modular system, consider the following steps:
- Define the goals for the modular design. As described earlier, there are several reasons why you might decide to implement a modular design for your application.
- Decide how you are going to partition your modules and define your module's responsibilities. Each module should have a distinct set of responsibilities. The most common approach is to partition your application so that each module has its own functional area, such as Customers or Orders. In this case, each module will consist of its own presentation layer, a business or domain layer, and a resource access layer. Figure 2 illustrates an example where a module that addresses a specific functional area contains all these logical layers.
There are other ways to approach the design of modules. For example, if you want to make it easier to replace the UI of your application, it might make sense to place the presentation layer in one or more easy-to-replace modules. If you want your application to support different resource-access strategies, another example might be to put your resource access layer in a separate module. By replacing the resource access layer module, you can have the application access a local database or a remote Web service.
- Define the communication patterns between the modules.
Even though modules should have low coupling between each other, it is common for modules to communicate with each other. There are several loosely coupled communication patterns, each with their own strengths. Typically, combinations of these patterns are used to create the resulting solution. The following are some of these patterns:
- Loosely coupled events. A module can broadcast that a certain event has occurred. Other modules can subscribe to those events so they will be notified when the event occurs. Loosely coupled events are a lightweight manner of setting up communication between two modules; therefore, they are easily implemented. However, a design that relies too heavily on events can become hard to maintain, especially if many events have to be orchestrated together to fulfill a single task. In that case, it might be better to consider a shared service.
- Shared services. A shared service is a class that can be accessed through a common interface. Typically, shared services are found in a shared assembly and provide system-wide services, such as authentication, logging, or configuration.
- Shared resources. If you do not want modules to directly communicate with each other, you can also have them communicate indirectly through a shared resource, such as a database or a set of Web services.
Team Development Using Modules
Modules have explicit boundaries, typically by subsystem or feature. Having these boundaries makes it easier for separate teams to develop modules. On large applications, teams may be organized by cross-cutting capabilities in addition to being organized by a specific subsystem or feature. For example, there may be a team assigned to shared components of the application, such as the shell or the common infrastructure module.
The following are the different teams developing the Stock Trader Reference Implementation, as illustrated in Figure 3:
- Infrastructure team. This team develops the core cross-cutting services used in the application and cross-module types and interfaces.
- Positions team. This team maintains the Positions and Buy/Sell functionality.
- News team. This team handles aggregating and displaying news.
- UI team. This team manages the shell and contains a set of graphic designers who set the overall appearance of the application. They work across each of the development teams.
- Operations team. This team is responsible for managing and deploying modules to staging and production. They also manage the master module configuration files.
Figure 3 illustrates an example of a composite application's modules and associated teams.
For background information about concepts that are important to understanding the Composite Application Guidance, see the following topics: