8
Application Architecture

WHAT’S IN THIS CHAPTER?

  • Application architecture patterns that protect the integrity of your domain model
  • The difference between application and bounded context architectures
  • The role and responsibilities of application services
  • How to support various application clients

Wrox.com Code Downloads for This Chapter

The wrox.com code downloads for this chapter are found at www.wrox.com/go/domaindrivendesign on the Download Code tab. The code is in the Chapter 8 download and individually named according to the names throughout the chapter.

Domain-Driven Design (DDD) focuses on managing the challenges of building applications with complex domain logic by isolating the business complexities from the technical concerns. Up until now, this book has only looked at techniques to enable teams to model a useful conceptual abstraction of the problem domain. This chapter, however, looks at patterns that enable the domain model to be utilized in the context of an application, taking into consideration persistence, presentation, and other technical requirements.

Application Architecture

Developing software while following the principles of DDD does not require you to use any particular application architecture style. But one thing that your architecture must support is the isolation of your domain logic.

Separating the Concerns of Your Application

To avoid turning your codebase into a Big Ball of Mud (BBoM) and thus weakening the integrity and ultimately the usefulness of a domain model, it is vital that the structure of an application supports the separation of technical complexities from the complexities of the domain. Presentation, persistence, and domain logic concerns of an application will change at different rates and for different reasons; an architecture that separates these concerns can accommodate change without causing an undesired effect to unrelated areas of the codebase.

Abstraction from the Complexities of the Domain

In addition to a separation of concerns, an application architecture must abstract away from the intricacies of a complex domain by exposing a coarse-grained set of use cases that encapsulate and hide the low-level domain details. Abstracting at a higher level prevents changes in domain logic from affecting the presentation layer and vice versa because the clients of the application communicate through application services acting as use cases rather than directly with domain objects.

A Layered Architecture

To support the separation of concerns, you can layer the different responsibilities of an application, as shown in Figure 8.1. Fowler catalogued the ubiquitous layered architecture in his book Patterns of Enterprise Application Architecture. However, many other architectures support the separation of concerns by dividing an application into areas that change together, such as Uncle Bob’s Clean Architecture, the Hexagonal Architecture (also known as the Ports and Adapters Architecture), and the Onion Architecture.

images

FIGURE 8.1 A layered architecture.

Unlike typical views of a layered architecture, Figure 8.1 shows that at the heart of the architecture is the domain layer containing all the logic pertaining to the business. Surrounding the domain layer is an application layer that abstracts the low-level details of the domain behind a coarse-grained application programming interface (API) representing the business use cases of the application. The domain logic and application layers are isolated and protected from the accidental complexities of any clients, frameworks, and infrastructural concerns.

Dependency Inversion

To enforce a separation of concerns, the domain layer and application layers at the center of the architecture should not depend on any other layers. All dependencies face inward, in that the domain layer, at the heart of the application, is dependent on nothing else, enabling it to focus distraction-free on domain concerns. The application layer is dependent only on the domain layer; it orchestrates the handling of the use cases by delegating to the domain layer.

Of course, the state of domain objects needs to be saved to some kind of persistence store. To achieve this without coupling the domain layer to technical code, the application layer defines an interface that enables domain objects to be hydrated and persisted. This interface is written from the perspective of the application layer and in a language and style it understands free from specific frameworks or technical jargon. The infrastructural layers then implement and adapt to these interfaces, thus giving the dependency that the lower layers need without coupling. Transaction management along with cross-cutting concerns such as security and logging are provided in the same manner. Figure 8.2 shows the direction of dependencies and the direction of interfaces that describe the relationship between the application layer and technical layers.

images

FIGURE 8.2 Dependency inversion within a layered architecture.

The Domain Layer

As discussed in Chapter 4 a domain model represents a conceptual abstract view of the problem domain created to fulfill the needs of the business use cases. The domain layer containing the abstract model does not depend on anything else and is agnostic to the technicalities of the clients it serves and data stores that persist the domain objects.

The Application Service Layer

The application service layer represents the use cases and behavior of the application. Use cases are implemented as application services that contain application logic to coordinate the fulfillment of a use case by delegating to the domain and infrastructural layers. Application services operate at a higher level of abstraction than the domain objects, exposing a coarse-grained set of services while hiding the details of the domain layer—what the system does, but not how it does it. By hiding the complexities of the domain behind a façade, you can evolve the domain model while ensuring that changes do not affect clients.

The client of the domain layer is the application service layer; however, to perform its work, it requires dependencies on external layers. These dependencies are inverted because the application layer exposes the contracts to the interfaces it requires. The external resources must then adapt to the interfaces to ensure the application layer is not tightly coupled to a specific technology.

Coordinating the retrieval of domain objects from a data store, delegating work to them, and then saving the updated state is the responsibility of the application service layer. Application service layers are also responsible for coordinating notifications to other systems when significant events occur within the domain. All these interfaces with external resources are defined within the application service layer but are implemented in the infrastructural layer.

The application service layer enables the support of disparate clients without compromising the domain layer’s integrity. New clients must adapt to the input defined by the application’s contract—its API. They must also transform the output of the application service into a format that is suitable for them. In this way, the application layer can be thought of as an anticorruption layer, ensuring that the domain layer stays pure and unaffected by external technical details.

The Infrastructural Layers

The infrastructural layers of an application are the technical details that enable it to function. Whereas the application and domain layers are focused on modeling behavior and business logic, respectively, the infrastructural layers are concerned with purely technical capabilities, such as enabling the application to be consumed, whether by humans via a user interface or by applications via a set of web service or message endpoints. The infrastructural layers are also responsible for the technical implementation of storing information on the state of domain objects.

In addition, the infrastructural layer can provide capabilities for logging, security, notification, and integration with other bounded contexts and applications. These are all external details—technical concerns that should not directly affect the use case exposed and the domain logic of an application.

Communication Across Layers

When communicating across layers, to prevent exposing the details of the domain model to the outside world, you don’t pass domain objects across boundaries. For the same reasons, you don’t send raw unsolicited data or user input straight into the domain layer. Instead, you use simple data transfer objects (DTOs), presentation models, and application event objects to communicate changes or actions in the domain.

To avoid tight coupling of layers, higher layers must communicate with lower layers by adapting to their message types. This again keeps the lower layers isolated and loosely coupled to any external layers. Figure 8.3 shows the communication across the layers and how data is transformed to protect the integrity of the domain model.

images

FIGURE 8.3 Domain objects are hidden from clients of the application.

Testing in Isolation

Separating the different concerns in your application and ensuring your domain logic is not dependent on any technically focused code such as presentation or data persistence frameworks enables you to test domain and application logic in isolation, independent of any infrastructural frameworks.

As shown in Figure 8.4, you can use unit tests to confirm the logic within the domain layer. You can use mocks and stubs to give the application layer the fake implementations it requires to confirm the correctness of business task coordination with the domain layer and external resources.

images

FIGURE 8.4 Testing layers in isolation.

Don’t Share Data Schema between Bounded Contexts

In addition to separating the concerns within the codebase of an application, an architecture must include the separation of the persistence of the domain object state from other applications’ data requirements. Figure 8.5 shows applications integrated via a shared database and shared schema.

images

FIGURE 8.5 Bounded contexts integrating via a shared data schema.

Although this is an easy integration method, it can complicate and blur the lines of a model by acting as the catalyst to your codebase growing into a BBoM. Sharing data makes it easy for client code to bypass the protection of a bounded context and interact with a domain object state without the protection of domain logic. It is also easy to interpret logic and schema incorrectly, resulting in changes to the state that invalidate invariants.

As shown in Figure 8.6, you should favor application or bounded context databases over integration databases. Just as you apply context boundaries within the domain model, you must do the same for the persistence model. This helps to force clients to integrate through the well-defined application service layer, protecting the integrity of your model and ensuring invariants are met.

images

FIGURE 8.6 Bounded contexts with their own data schema.

Application Architectures versus Architectures for Bounded Contexts

Applications can be composed of more than one bounded context. Architectures apply to bounded contexts and applications in different ways. An application that is composed of two or more bounded contexts may have an architectural style for the user interfaces and different architectures for each of the bounded contexts. Figure 8.7 shows an application composed of three bounded contexts; here the presentation layer contains its own application layer to facilitate the coordination with the bounded contexts.

images

FIGURE 8.7 Bounded contexts integrating via a separate application layer.

However, some people believe that the boundary of a bounded context should extend to the presentation layer. Udi Dahan’s business component gives the bounded context the responsibility for owning specific regions of the user interface. This architecture can be seen in Figure 8.8.

images

FIGURE 8.8 Presentation layer composed of bounded contexts.

In this architecture style, the infrastructure takes care of ensuring communication and the sharing of correlation IDs.

There does not need to be consistency in architectural styles or data stores across bounded contexts, but within a single bounded context, you should strive to follow one method of representing domain logic.

Application Services

The application service layer, cataloged as the service layer in Fowler’s Patterns of Enterprise Application Architecture book, can be used to define the boundary of your domain model and can also be thought of as the implementation of the bounded context concept, isolating and protecting the integrity of your domain model.

As mentioned earlier in this chapter the responsibility of the application service layer is to expose the capabilities and operations available to the application while abstracting the low-level complexities of the domain model. Capabilities are defined by the business use cases that the system must satisfy. The application services fulfill the use cases by coordinating the execution of domain logic. They deal with technical concerns such as handling input and shaping reporting information on the state of the domain, as well as transactional, logging, and persistence concerns.

Application services contain application logic only. This logic covers security, transaction management, and communication with other technical capabilities such as e-mail and web services. They are the clients of the domain layer and delegate all work to that layer. No domain logic should be found within the application services; instead, the application services should be procedural in style and thin. The application layer is not dependent on any frameworks or technology that consumes the application service, such as UI or service frameworks. It does, however, define interfaces that it depends on to hydrate domain objects and manage nondomain tasks.

Application Logic versus Domain Logic

Application logic contains the workflow steps required to fulfill a business use case. Steps can include the hydrating of domain objects from a database, the mapping of user input to objects that the domain layer understands, and ultimately the delegating to domain objects or a collection of them to make a business decision. Other steps may include delegating to infrastructural services, such as notifying other systems of changes in domain state via messaging systems or web calls, authorization, and logging.

Application logic is all about coordination and orchestration through delegation to domain and infrastructural services. The application services don’t do any work, but they understand who to talk to to complete the task. Domain logic, on the other hand, is focused only on domain rules, concepts, information, and work flows. Domain logic is free from technical details, including persistence.

As an example, consider Figure 8.9, which models the use case of applying a promotion coupon to an e-commerce basket. The ASP.NET MVC framework presentation layer transforms the Hypertext Transport Protocol (HTTP) request into a form that the application service layer expects and calls the service method. The application service delegates to the persistence layer to retrieve the coupon object. It then checks whether the coupon is still valid. If it is not, it responds with an appropriate result. If it is valid, it again delegates to the persistence layer to retrieve the basket and passes the basket to the coupon to generate a discount. The changes to the discount on the basket domain object are persisted, and an event is published to notify that the coupon was redeemed.

images

FIGURE 8.9 Application logic versus domain logic.

Defining and Exposing Capabilities

Because the application services are exposing capabilities of the system, they should not have to adapt to new clients. Instead, new clients, such as presentation layers, should adapt to the contracts exposed by the services. In other words, the capabilities of the system should not change for clients. Rather, they should change only when the business use case changes. The use cases exposed by the application services change at a different rate and for different reasons than the domain logic that is used to fulfill them. This enables clients consuming the services to be protected from frequent changes to domain logic.

Take, for example, the business use case of risk assessing an order for fraud. The system exposes the capability to take details of an order and return a score based on domain logic. Over time, the domain logic may change, but the application service that is the implementation of the use case to score an order for risk will largely remain constant, changing only to alter its contract to provide additional information.

The stakeholders may not know the complexities of the domain layer; however, the business tasks that the application layer is responsible for are meaningful to the business. Even if they are not domain experts, the stakeholders should understand them.

Business Use Case Coordination

Whereas the domain model is object-oriented in its nature, the application services are procedural, as they are focused on task orchestration as opposed to modeling domain logic and concepts. Application services are somewhat similar to ASP.NET MVC controller actions. Controller actions contain logic to control the user interface interactions in the same manner that application services contain logic that represents business tasks or use cases that coordinate communication with services and objects within the domain layer. Both controller actions and application services are stateless and procedural in nature. An exception is that both controller actions and application services can store state, but the state should only be to store the status of the customer journey or the progress of the business task.

Application services also share more coordination logic with controller actions in the form of transforming and mapping input and output. Controller actions map HTTP post variables to objects that application services require and map application services query responses to view models for presentation needs. Application services, in the same way, map requests into structures that domain objects understand, and respond with presentation models that hide the real form of the domain objects and that are specific to user interface views.

Application Services Represent Use Cases, Not Create, Read, Update, and Delete

Behavior-Driven Design (BDD) helps you understand the behaviors of an application. With the behaviors you capture using BDD, you can use the language expressed in the BDD specifications as the name for you application services use/cases. This is similar to the way you use the ubiquitous language (UL) of the domain within the code of the domain layer. Application services are not simply create, read, update, and delete (CRUD) methods; they should reveal user intent and communicate the capabilities of a system. Examples of this can be seen in Chapter 25, “Commands: Application Service Patterns for Processing Business Use Cases,” along with patterns on how to implement the application service layer.

Domain Layer As an Implementation Detail

Application services are powerful and can be helpful for any application complexity, be it a core subdomain with rich logic or a generic subdomain that is merely a façade for access to the data store. Having the application services decouple clients from the logic enables the domain layer to evolve cleanly without having a ripple effect across layers.

Your application service methods can reveal whether a domain model is required at all. If you find that all your business use cases are simply updating, adding, or deleting data, then it’s a good bet that the domain is lacking any real logic and can be kept simple by employing a transaction script or data wrapper pattern, as discussed in Chapter 2, “Distilling the Problem Domain,” instead of a full-blown rich domain model. However, if the application services and behaviors of your system are rich in language, this may suggest the need for a domain model pattern in your domain logic layer.

Domain Reporting

Besides coordinating business tasks, the application service layer needs to provide information on the state of domain objects in the form of reports. You don’t want to expose the inner workings of your domain model to the outside world, so the application services transform domain objects into presentation models that give specific views of domain state without revealing the structure of the domain model. You can see this transformation in Figure 8.10.

images

FIGURE 8.10 A view model mapping to many domain objects.

Read Models versus Transactional Models

Sometimes a user interface requires information that spans across many domain objects. It would be inefficient and costly for the application service to hydrate all the rich domain objects to simply provide a subset of information for a view. In these cases, it is preferable for the application service layer to provide a specific view of domain state directly from the data source, as shown in Figure 8.11. This way, you can construct views in an efficient manner without having to construct large object graphs of the domain objects and expose details within them.

images

FIGURE 8.11 View models queried directly from the data source.

There is, however, a drawback to providing read and write capabilities from the same conceptual model, albeit the data model. The transactional model stores logic in domain objects and simple state in the data store. To support both reporting and transactional needs, the views might require extra information that will affect the structure of domain objects. To prevent the model from having to change because of presentation needs, you can store the view data separately in a data schema that is best suited to querying. To achieve this, you can store changes that occur within the domain model and use these as the basis for reporting requirements.

Figure 8.12 shows how the transactional model handles a write request from a client and then raises events that are stored for querying. You can store these events and their data in the same database or a completely different storage mechanism. This pattern is called Command Query Responsibility Segregation (CQRS) and is covered in greater detail in Chapter 24, “CQRS: An Architecture of a Bounded Context.” Further patterns for reporting on a domain model are presented in Chapter 26, “Queries: Domain Reporting.”

images

FIGURE 8.12 View store separated from transactional storage.

Application Clients

The role of the clients of the application service layer is to expose the capabilities of the system. Many applications have some form of presentation or user interface that will give users access to the system behaviors. Other applications instead expose their functionality via RESTful or web services. Regardless of the type of client application, a service should be ignorant to what consumes its functionality. Application services should not bend to meet the needs of a client but instead should expose use cases of an application and force a client to adapt to its API.

It is entirely possible to build a system without an application service layer, relying on the clients to perform all the tasks that the application service layer is responsible for. However, by creating a specific set of services, you are modeling use cases explicitly and keeping them separate from presentation requirements. These application services help to focus on the behaviors of the systems and enable you to separate domain logic from the other concerns of your application.

Figure 8.13 shows how multiple clients can consume the behaviors of an application via the application service layer. Also shown is how the application service layer can itself consume external contexts and third party services.

images

FIGURE 8.13 :Various clients of an application.

Bounded contexts can form large systems by communicating via technical infrastructure. Figure 8.14 shows various clients working together to define a larger system. The methods of integrating bounded contexts feature in Part II of this book, with the user interface needs being covered in Part IV.

images

FIGURE 8.14 A system composed of multiple bounded contexts.

Sometimes business processes span multiple bounded contexts. For these cases, you employ the use of a process manager to coordinate business tasks. Figure 8.15 shows a process manager that contains business task logic to coordinate larger processes. Similar to the application services, the process managers will be stateless apart from the state used to track task progression, and will delegate back to applications to carry out any work. This pattern is explored in Chapter 25, “Commands: Application Service Patterns for Processing Business Use Cases.”

images

FIGURE 8.15 A process manager.

The Salient Points

  • DDD does not require a specific architecture—only one that can separate the technical concerns from the business concerns.
  • Separate the concerns of your application, and isolate business complexity from technical complexity by layering your application.
  • Outer layers depend on inner layers. Inner layers expose interfaces that outer layers must adapt to and implement. This form of dependency inversion protects the integrity of the domain and application layers.
  • The domain layer is at the heart of your application. It is isolated from technical complexities by the application layer.
  • Application services expose the capabilities of a system by abstracting the domain logic to a higher level.
  • Application services are based around business use cases; they are the clients of the domain layer. They delegate to the domain layer to fulfill the use cases.
  • Application services should remain ignorant to the clients that consume them. Clients should adapt to the API of the application, which enables the support of discrepant clients.
  • The application service layer is the concrete implementation of the bounded context boundary.
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.188.200.46