CHAPTER 3

image

Domain-Driven Design

We don't write code; we solve business problems.

Introducing Domain-Driven Design

How do you measure the success of a software project? Do you rely solely on deploying the solution on schedule? Does the quantity and quality of design documents, such as a vision and scope document, package diagrams, component diagrams, key feature class diagrams, and other various Unified Modeling Language (UML) diagrams provide an indicator of a project’s success or failure? Have you seen the documentation created in a waterfall design–based software development project? I think trying to keep all of these documents in sync with the constantly changing requirements of a system is a nightmare. No thanks; I’d rather sleep at night.

I believe a software project is considered a success when the resulting application provides the value requested by the user to improve business processes. Oftentimes it makes life much easier for users by streamlining their workflow and allowing them to be more productive (being on schedule and on budget doesn’t hurt either).

Over the past ten years, there has been an evolution in application development. Object-oriented design is mature and here to stay. The industry has welcomed the idea of design patterns with open arms. Of all of the new paradigms, however, none has put more into practice than has Domain-Driven Design (DDD).

Image Caution  Domain-Driven Design is a vast collection of rules, terms, guidelines, and patterns. In fact, in order to cover the paradigm completely, I would need to write a book on the subject. This book contains only an overview of DDD. This book provides enough coverage to enable you to understand the architecture of the sample line-of-business applications that you are going to develop. Nonetheless, I strongly recommend that you pick up a copy of Domain-Driven Design: Tackling Complexity in the Heart of Software by Eric Evans (Prentice Hall, 2003). This was the first book on DDD and is commonly referred to as the “blue book.” It is the book for learning every aspect of Domain-Driven Design, and it contains the first official introduction to the collective concepts that represent DDD.

What Is Domain-Driven Design?

Simply put, Domain-Driven Design is a framework for designing and developing software solutions in which the design is built around the entities that make up the business domain. DDD provides a set of guidelines that help align your software design and implementation with the expectations of the customer.

Once you’ve built a domain model based on the core entities that make up the business domain, you then work to add support for the relationships between the entities and how they are supposed to interact with each other and the rest of the system. You work closely with a business analyst and, if you are lucky, the people who will actually use the finished product in order to make sure that you deliver what the user expects. Feedback is an excellent way to determine what the client wants, so you should schedule receiving feedback from the client as often as possible.

If you follow agile methodologies like Scrum, as you will do throughout the book, then the purpose of each sprint is to deliver a scheduled set of features derived from user stories. At the end of each sprint, you should have a working piece of software that delivers at least one feature that the end user should be able to test and on which they should be able to provide feedback. If you deploy the features delivered in each sprint and receive direct feedback regarding development, you will know right away if you start to stray from alignment with the client’s needs regarding the solution that you are developing.

For you to develop software for a specific business domain, you must take time to learn the current processes, rules, constraints, and terminology of the domain. This requires the team to have access to a business analyst or, even better, a user of the proposed system who can explain how things work, specifically in terms relating to the business and not to software development.

Before Domain-Driven Design

Before DDD, business analysts would gather requirements for the application to be developed. The business analyst would then work with an architect to create a verbose set of software design documents and UML diagrams. They would design every aspect of the application before writing a single line of code. The design documentation would serve as a way to create the project schedule, generate the plan for the required resources, and fashion the project budget.

As time went on, it became clear that this approach wasn’t very effective. In light of all of the time spent on creating design documents, these projects would rarely even consider how to handle a change of requirements. Developers would rely on a “requirements freeze,” which meant that, after a certain point, requirements would not change. The problem with this approach is that not only can requirements change, but they usually do!

The practice of designing a software solution from beginning to end before writing any code can be compared to the process of designing and constructing a house. When you build a house, you work with a contractor and make all of the decisions regarding the details of the design of the house. Once the computer-aided design (CAD) drawings are created and the client has signed off on the design plans, the contractor and construction team will then begin to build the house. Following along with this example, imagine that the contractor is 40 percent finished with the house and the client requests a modification that requires a change to the foundation’s layout. Indeed, I would not want to be the contractor facing this dilemma!

This is where it becomes clear that older software design and development paradigms aren’t really optimal design styles because requirements are likely to change throughout the development process. Application requirements don’t experience change only during design and development. Technology is constantly changing. It’s possible that you may need to change virtually any aspect of a software solution at any given moment in response to a change in technology, such as the release of a new NoSQL database storage engine, a cloud-based file system, or perhaps an update to the implementations of external system dependencies.

Oftentimes, a user will think they need a specific feature. However, when they receive the final application, only then are the design flaws brought to light. In these instances, the client will have to learn to work around the bad design decisions that were made when the requirements were gathered at the beginning of the project. For this reason, Domain-Driven Design advocates use agile development practices to interact with the user throughout the development life cycle in order to obtain feedback as the software is developed.

The point is that change is part of software development, and Domain-Driven Design (along with agile methodologies) introduces a set of guidelines that promotes developing software that is flexible and extensible in order to minimize the effects of changes to the requirements.

UML Hell and Stale “Requirements Documents”

Unified Modeling Language provides standards to communicate software development design artifacts in a predefined graphical format. I’ll admit it, up until this point, I have not painted the best picture of UML. The truth is, I like UML. It’s when UML is overused that I have a problem with it. When UML began to gain traction with object-oriented software design and analysis methodologies, many people considered UML to be the “magic bullet” to good design. As a result, there was a lot of time wasted making diagrams that were never looked at after their initial release.

Changes to requirements, along with large volumes of useless design diagrams, create several problems when developing software solutions, one of which is stale documentation.

You are more likely to update design documents if they are essential to the project. Domain-Driven Design promotes combining the model with the implementation. Your code should express the design details of your model clearly.

Business User? Who’s That?

Who are the business users? They are some of the most important people involved in the process of creating a line-of-business application. They are a group of people who have to deal with the issues associated with the business problem that your software solution is designed to solve. I can safely say that for projects on which I’ve worked, the direct involvement of a potential user to whom I was able to direct questions freely and receive constant and consistent feedback were easily the best and most rewarding. The resulting solutions were tailor-made for the business user’s needs. A happy client is the best indicator regarding the success of a software development project.

The fact of the matter is that you don’t always have the advantage of working directly with a business user. Generally, you work with a business analyst instead. If the analyst has a clear picture of the business domain, then you can still achieve great success in your development projects. The key point is that with Domain-Driven Design, the entire team needs to be on the same page regarding the entities, the processes, the external systems, and, most importantly, the ubiquitous language regarding the business’s terminology as it relates to the context of the business domain problem space.

Ubiquitous Language

When working on a business problem, you can ask five different users, with five different roles, to define a particular domain entity, and you may receive five completely different answers. That’s because certain terms and definitions when used in the context of one user’s role may vary completely when used in the context of a different user’s role.

This concept applies to the development team as well. When working as a team to learn and understand the ins and outs of a business domain, it’s important to understand that there will be gaps in the ways that each individual relates to a particular business concept and therefore how they understand it.

The solution involves working as a team to create a ubiquitous language that covers the gaps in various interpretations of business terms. A ubiquitous language is a dictionary full of business terms that are related to the domain in which you are working to create a domain model. This will form a standard, unified language to be used when referring to domain terminology, which will prevent confusion and promote consistency in the domain model.

The Domain Model in Domain-Driven Design

What is a domain model? A domain model is a loosely coupled component that embodies all entities, value objects, aggregate roots, and domain services, which are used to describe the business domain in which you are creating a software solution. The domain model is the core of the Domain-Driven Design paradigm. The domain model is source code that uses business terminology from your ubiquitous language to “model” the business domain in code.

The domain model should encapsulate only the constructs that are relevant to the business domain. This separation of concerns is extremely important. When the domain is modeled purely from business entities, it can easily be changed based upon user requests. When you ensure that your domain model is loosely coupled with other parts of the system, the model can easily be reused in future software solutions created to solve new business problems.

The Source Code Is the Design Documentation

In Domain-Driven Design, the source code represents the domain model, and therefore it serves as the documentation. There are various other documents that you can create as part of your process; however, the code should be clear and based on the business domain such that the design speaks for itself. This is another instance in which unit testing comes into play when writing an application. For instance, a new developer can look at unit tests to see how any class is meant to be used—any constraints, argument value ranges, expected exceptions, and so on.

Domain Entities

The domain entity sits at the core of Domain-Driven Design. During requirements-gathering sessions, you and your team should work with the business analyst or user to discuss the business processes related to the task at hand. As you do this, you will identify nouns that will later represent terms in your ubiquitous language and eventually a class in your system.

Domain entities have specific rules regarding their design. An entity should represent something in the business domain that has a unique identity. For instance, if you were working on an order placement system, you would have an entity called Order. The class should provide only the properties and methods for which it is responsible.

A domain entity should be ignorant regarding how and when it is persisted to the data store. This is a guideline that you often need to ignore when creating entities. For instance, if you plan to save and retrieve an entity from the data source, then you will generally encapsulate an ID property of some sort that relates to the database. This ID property has nothing to do with the business; however, it is a convenience when saving and retrieving an object. Remember, just like any other design methodologies, these are guidelines, not laws. You have to decide what works best for you.

Domain entities should be cohesive units of code that represent unique entities in the domain. An entity should know how to validate its state as well as be aware of the way that it interacts with any collaborating entities or value objects. The less your entities know about other parts of the system, the better. This cohesion assists in creating domain entities that can be used in other applications.

As you can see in Listing 3-1, the Employee entity is as simple as it needs to be. We’ve included a unique identifier and the relevant attributes that describe the employee. Always try to keep your entities as simple as possible, adding complexity only when necessary.

Domain Aggregate Roots

One of the tougher aspects of modeling the domain in source code is modeling references effectively to other entities. In most domains, you will find an entity that contains a private field that represents a collection of child entities or value objects. How can you model such references so that a modification to the state of any referenced entity is correctly propagated throughout the entire chain of references? This is where aggregate roots come into play.

An aggregate in the context of Domain-Driven Design is a collection of associated objects that are bound together for the purposes of modeling data modifications. Every aggregate has a root and a boundary. The root entity is the only object in the aggregate that can be referenced by objects outside of the aggregate boundary. Of course, inside the aggregate boundary, entities can reference each other. Otherwise, there would be no way to model complex associations.

Take, for instance, the Order entity mentioned earlier. This entity may have a collection of OrderItem entity objects. Each OrderItem entity could have an associated Product entity, and so on. In this example, the Order entity serves as the aggregate root. The Order entity is the only entity in the aggregate boundary in which an object outside of the boundary may hold a reference, such as a property or field.

The aggregate root entity may transiently pass a reference to a child entity to be used in a single operation, in which the calling object may not hold on to the reference. The aggregate root’s identity must be global while the entities throughout the boundary should have local identities. The root entity is responsible for checking all invariants or constraints throughout the aggregate’s boundary.

The aggregate roots should be the only entities that can be retrieved by a database query. All other entities of the aggregate must be loaded by following the associations of the aggregate. Finally, if any modifications are made to an object within the aggregate and the modifications are committed, all of the invariants or constraints must be satisfied. As long as the aggregate root controls all access to objects in the aggregate boundary, then there is one point of change for the data, and you can be sure that all invariants are enforced.

Here in Listing 3-2, you have an Order aggregate root entity that holds a reference to a collection of LineItem entities. The only way to access any information regarding the collection of LineItem objects is through the aggregate root Order entity object.

Domain Value Objects

A value object in the domain model is an object similar to an entity; the difference is that an entity has a unique identity and a value object does not. A well-known example of a value object would be a class that represents a customer’s address. The Customer object is clearly an entity because it has an identity and life cycle, which is tracked in the domain. The Address object, however, may describe the address of any customer, person, or business. An Address value object would have properties for street, house number, postal or ZIP code, state, country, and so on. When it comes to modeling value objects, you care about the attributes. Value objects should be immutable in order to make sharing references and different instances of the value safe.

When you make a value object immutable, it means you don’t change the reference to it. If modifications need to be made, you must replace the reference with a new instance of the value object. Some other great examples of value objects are lists that are used in your application, such as a drop-down list that is bound to a collection of State value objects, which allow the user to select a state. The collection of State value objects can be shared with any part of the system that needs to present a list of states. A drop-down box that allows the user to pick a color is another good example of where you would want to create a value object (in this case, the class would be Color).

Domain Services

Oftentimes, there are operations that belong in the domain; however, they don’t match up with any specific entity. You create these operations as methods that belong to classes called domain services. Domain services are often used to orchestrate operations that involve one or more entities. These service objects contain methods that represent actions that work with multiple domain objects to accomplish an activity specified in the model.

When it comes to domain services, you must be careful that you don’t get lazy. For example, instead of identifying an activity as a behavior or responsibility of an entity or value object, you simply create a service. At the same time, services assist you by providing a place to implement operations that belong in the domain but that don’t belong to any specific entity or value object. If you were to add a method to an entity simply because you couldn’t find anywhere else to put the functionality, you would complicate the entity and the responsibilities of the entity would become cloudy. It’s much better to model this type of functionality as a domain service to protect the integrity of the domain model.

Domain services must be stateless. It shouldn’t matter how an instance of a domain service has been used prior to using the instance of the service. The service will use data from the entities in which it operates; however, it does not hold any state-related properties itself. This reduces complexity by removing the need to worry about when an instance of the service was created or which operations have been executed and in what order.

The methods of domain services should be named using the ubiquitous language of the domain model. We usually name our domain services with the suffix of Service. A couple of examples of domain service names are PurchasingService and ReportService.

In Listing 3-3, you see OrderService, which is a domain service that works with an Order aggregate root Order entity and an IOrderRepository instance to save the Order and generate an Invoice entity. (We’ll discuss the Repository pattern in the next chapter.)

Domain Events

Domain events are domain objects that represent the occurrence of an event that is important to the domain. Domain events are objects that contain all of the attributes required to express the event that has occurred.

Domain events are powerful because they provide a way of notifying any part of the system when an event occurs about which the system needs to be aware. Moreover, depending on your event dispatching and subscription systems, events can transcend logical layers, physical tiers, and so forth. Events also provide assistance when adhering to the rules of aggregate root persistence. We will discuss specific design patterns for event publication and subscription in the next chapter.

CQRS: Command Query Responsibility Segregation

Command Query Responsibility Segregation (CQRS) is a design philosophy in which operations are divided into two types: commands that change the domain entity data and query-related operations that read data from the domain. The idea is to model your classes based on these two fundamental types of operations. You end up with services that contain only commands and with services that contain only queries. Essentially, what you are left with is an abstraction of all commands and an abstraction of all queries. In some instances, a sharp definition can improve the clarity of the domain model. We will not use CQRS in this book; however, it is important to be aware of the concept for your reference.

Summary

Domain-Driven Design is not a “magic bullet.” Still, once you invest the time required to use DDD effectively, you and your team will create software solutions that are well aligned with the requests of users. You will create loosely coupled components, which can be used throughout the enterprise when new applications are created.

For all of the advantages to using DDD, there are also some valid concerns, including the following:

  • The need to climb a steep learning curve.
  • An absolute commitment to DDD among the team members.
  • A willingness of team members to invest the time necessary to learn the principles of DDD.
  • To use DDD effectively, the entire team must share an understanding of the business domain. As design progresses, there will be questions raised and modifications required based on the answers to those questions. The team must have access to someone with deep knowledge of the business domain in order to validate the assumptions made throughout the entire project life cycle.

As you can see, there is a lot to learn with DDD. We’re confident that this chapter has provided enough information to whet your appetite to continue to learn and practice this awesome design methodology. Nevertheless, like most other things in life, the only way for you to learn DDD is to practice it. We hope that we’ve provided enough background for you to follow along with the reasons behind the design decisions that you will make throughout the rest of this book.

Next up, we will cover the design patterns used in modern applications that work hand-in-hand with Domain-Driven Design.

..................Content has been hidden....................

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