Building blocks

This section explains the ubiquitous language used and why it is required, the different patterns to be used in model-driven design and the importance of multilayered architecture.

Ubiquitous language

As we have seen, designing a model is the collective effort of software designers, domain experts, and developers and, therefore, it requires a common language to communicate. DDD makes it necessary to use common language and the use of ubiquitous language. Domain models use ubiquitous language in their diagrams, descriptions, presentations, speeches, and meetings. It removes the misunderstanding, misinterpretation and communication gap among them.

Unified Model Language (UML) is widely used and very popular when creating models. It also carries few limitations, for example when you have thousands of classes drawn of a paper, it's difficult to represent class relationships and also understand their abstraction while taking a meaning out of it. Also UML diagrams do not represent the concepts of a model and what objects are supposed to do.

There are other ways to communicate the domain model such as – documents, code, and so on.

Multilayered architecture

Multilayered architecture is a common solution for DDD. It contains four layers:

  1. Presentation layer or User Interface (UI).
  2. Application layer.
  3. Domain layer.
  4. Infrastructure layer.
    Multilayered architecture

    Layered architecture

You can see here that only the domain layer is responsible for the domain model and others are related to other components such as UI, app logic and so on. This layered architecture is very important. It keeps domain-related code separate from other layers.

In this multilayered architecture, each layer contains its respective code and it helps to achieve loose coupling and avoid mixing code from different layers. It also help the product/service's long term maintainability and the ease of enhancements as the change of one layer code does not impact on other components if the change is intended for the respective layer only. Each layer can be switched with another implementation easily with multitier architecture.

Presentation layer

This layer represents the UI and provides the user interface for the interaction and information display. This layer could be a web application, mobile app or a third-party application consuming your services.

Application layer

This layer is responsible for application logic. It maintains and coordinates the overall flow of the product/service. It does not contain business logic or UI. It may hold the state of application objects like tasks in progress. For example, your product REST services would be the part of this application layer.

Domain layer

The domain layer is a very important layer as it contains the domain information and business logic. It holds the state of the business object. It persists the state of the business objects, and communicates these persisted states to the infrastructure layer.

Infrastructure layer

This layer provides support to all the other layers and is responsible for communication among the other layers. It contains the supporting libraries that are used by the other layers. It also implements the persistence of business objects.

To understand the interaction of the different layers, let us take an example of table booking at a restaurant. The end user places a request for a table booking using UI. UI passes the request to the application layer. The application layer fetches the domain objects such as the restaurant, the table with a date and so on from the domain layer. The domain layers fetch these existing persisted objects from the infrastructure and invoke relevant methods to make the booking and persists them back to infrastructure layer. Once, domain objects are persisted, application layer shows the booking confirmation to end user.

Artifacts of domain-driven design

There are different artifacts used in DDD to express, create, and retrieve domain models.

Entities

There are certain categories of objects that are identifiable and remain same throughout the states of the product/services. These objects are NOT defined by its attributes, but by its identity and thread of continuity. These are known as entities.

It sounds pretty simple but carries complexity. You need to understand how we can define the entities. Let's take an example for table booking system, if we have a restaurant class with attributes such as restaurant name, address, phone number, establishment data, and so on. We can take two instances of the restaurant class that are not identifiable using the restaurant name, as there could be other restaurants with the same name. Similarly, if we go by any other single attributes we will not find any attributes that can singularly identify a unique restaurant. If two restaurants have all the same attribute values, these are the same and are interchangeable with each other. Still, these are not the same entities as both have different references (memory addresses).

Conversely, let's take a class of US citizen. Each citizen has his own social security number. This number is not only unique but remains unchanged throughout the life of the citizen and assures continuity. This citizen object would exist in the memory, would be serialized, and would be removed from the memory and stored in the database. It even exists after the person is dead. It will be kept in the system as long as system exists. A citizen's social security number remains the same irrespective of its representation.

Therefore, creating entities in a product means creating identity. So, now give an identity to any restaurant in the previous example, then either use a combination of attributes such as restaurant name, establishment date and street, or add an identifier such as restaurant_id to identify it. This is the basic rule that two identifiers cannot be same. Therefore, when we introduce an identifier for any entity we need to be sure of it.

There are different ways to create a unique identity for objects described as follows:

  • Using the primary key in a table.
  • Using an automated generated ID by a domain module. A domain program generates the identifier and assigns it to objects that are being persisted among different layers.
  • A few real life objects carry user-defined identifiers themselves. For example each country has its own country codes for dialing ISD calls.
  • An attribute or combination of attributes can also be used for creating an identifier as explained for the preceding restaurant object.

Entities are very important for domain models, therefore, they should be defined from the initial stage of the modeling process. When an object can be identified by its identifier and not by its attributes, a class representing these objects should have a simple definition and care should be taken with the life cycle continuity and identity. It's imperative to say that it is a requirement of identifying objects in this class that have the same attribute values. A defined system should return a unique result for each object if queried. Designers should take care that the model must define what it means to be the same thing.

Value objects

Entities have traits such as, identity, a thread of continuity, and attributes that do not define their identity. Value objects (VOs) just have attributes and no conceptual identity. A best practice is to keep value Objects as immutable objects. If possible, you should even keep entity objects immutable too.

Entity concepts may bias you to keep all objects as entities, a uniquely identifiable object in the memory or database with life cycle continuity, but there has to be one instance for each object. Now, let's say you are creating customers as entity objects. Each customer object would represent the restaurant guest and this cannot be used for booking orders for other guests. This may create millions of customer entity objects in the memory if millions of customers are using the system. There are not only millions of uniquely identifiable objects that exist in the system, but each object is being tracked. Both tracking and creating identity is complex. A highly credible system is required to create and track these objects, which is not only very complex but also resource heavy. It may result in system performance degradation. Therefore, it is important to use value objects instead of using entities. The reasons are explained in the next few paragraphs.

Applications don't always needs to have an identifiable customer object and be trackable. There are cases when you just need to have some or all attributes of the domain element. These are the cases when value objects can be used by the application. It makes things simple and improves the performance.

Value objects can be created and destroyed easily, owing to the absence of identity. This simplifies the design – it makes value objects available for garbage collection if no other object has referenced them.

Let's discuss the value object's immutability. Value objects should be designed and coded as immutable. Once they are created they should never be modified during their life-cycle. If you need a different value of the VO or any of its objects, then simply create a new value object, but don't modify the original value object. Here, immutability carries all the significance from object-oriented programming (OOP). A value object can be shared and used without impacting on its integrity if and only if it is immutable.

Frequently asked questions

  1. Can a value object contain another value object?

    Yes, it can

  2. Can a value object refer to another value object or entity?

    Yes, it can

  3. Can I create a value object using the attributes of different value objects or entities?

    Yes, you can

Services

While creating the domain model you may encounter various situations, where behavior may not be related to any object specifically. These behaviors can be accommodated in service objects.

Ubiquitous language helps you to identify different objects, identities or value objects with different attributes and behaviors during the process of domain modeling. During the course of creating the domain model, you may find different behaviors or methods that do not belong to any specific object. Such behaviors are important so cannot be neglected. You can also not add them to entities or value objects. It would spoil the object to add behavior that does not belong to it. Keep in mind, that behavior may impact on various objects. The use of object-oriented programming makes it possible to attach to some objects; this is known as a service.

Services are common in technical frameworks. These are also used in domain layers in DDD. A service object does not have any internal state, the only purpose of it is to provide a behavior to the domain. Service objects provides behaviors that cannot be related with specific entities or value objects. Service objects may provide one or more related behaviors to one or more entities or value objects. It is a practice to define the services explicitly in the domain model.

While creating the services, you need to tick all the following points:

  • Service objects' behavior performs on entities and value objects but it does not belong to entities or value objects
  • Service objects' behavior state is not maintained and hence they are stateless
  • Services are part of the domain model

Services may exist in other layers also. It is very important to keep domain layer services isolated. It removes the complexities and keeps the design decoupled.

Lets take an example where a restaurant owner wants to see the report of his monthly table booking. In this case, he will log in as an admin and click the Display Report button after providing the required input fields such as duration.

Application layers pass the request to the domain layer that owns the report and templates objects, with some parameters such as report ID and so on. Reports get created using the template and data is fetched from either the database or other sources. Then the application layer passes through all the parameters including the report ID to business layer. Here, a template needs to be fetched from the database or other source to generate the report based on the ID. This operation does not belong to either the report object or the template object. Therefore a service object is used that performs this operation to retrieve the required template from the DB.

Aggregates

Aggregate domain pattern is related to the object's life cycle and defines ownership and boundaries.

When, you reserve a table in your favorite restaurant online, using any app, you don't need to worry about the internal system and process that takes places to book your reservation such as searching the available restaurants, then the available tables during the given date, time, and so on and so forth. Therefore, you can say that a reservation app is an aggregate of several other objects and works as a root for all the other objects for a table reservation system.

This root should be an entity that binds collections of objects together. It is also called the aggregate root. This root object does not pass any reference of inside objects to external worlds and protects the changes performed in internal objects.

We need to understand why aggregators are required. A domain model can contains large numbers of domain objects. The bigger the application functionalities and size and the more complex its design, the greater number of objects will be there. A relationship exists among these objects. Some may have a many-to-many relationship, a few may have a one-to-many relationship and others may have a one-to-one relationship. These relationships are enforced by the model implementation in the code or in the database that ensures that these relationships among the objects are kept intact. Relationships are not just unidirectional, they can also be bi-directional. They can also increase in complexity.

The designer's job is to simplify these relationships in the model. Some relationships may exist in a real domain, but may not be required in the domain model. Designers need to ensure that such relationships do not exist in the domain model. Similarly, multiplicity can be reduced by these constraints. One constraint may do the job where many objects satisfy the relationship. It is also possible that a bidirectional relationship could be converted into a unidirectional relationship.

No matter how much simplification you input, you may still end up with relationships in the model. These relationships need to be maintained in the code. When one object is removed, the code should remove all the references of this object from other places. For example, a record removal from one table needs to be addressed wherever it has references in the form of foreign keys and such to keep the data consistent and maintain its integrity. Also invariants (rules) need to be forced and maintained whenever data changes.

Relationships, constraints, and invariants bring a complexity that requires an efficient handling in code. We find the solution by using the aggregate represented by the single entity known as the root that is associated with the group of objects that maintains consistency with respect to data changes.

This root is the only object that is accessible from outside, so this root element works as a boundary gate that separates the internal objects from the external world. Roots can refer to one or more inside objects and these inside objects can have references to other inside objects that may or may not have relationships with the root. However, outside objects can also refer to the root and not to any inside objects.

An aggregate ensures data integrity and enforces the invariant. Outside objects cannot make any change to inside objects they can only change the root. However, they can use the root to make a change inside the object by calling exposed operations. The root should pass the value of inside objects to outside objects if required.

If an aggregate object is stored in the database then the query should only return the aggregate object. Traversal associations should be used to return the object when it is internally linked to the aggregate root. These internal objects may also have references to other aggregates.

An aggregate root entity holds its global identity and hold local identities inside their entities.

An easy example of an aggregate in the table booking system is the customer. Customers can be exposed to external objects and their root object contains their internal object address and contact information. When requested, the value object of internal objects like address can be passed to external objects:

Aggregates

The customer as an aggregate

Repository

In a domain model, at a given point in time, many domain objects may exist. Each object may have its own life cycle from the creation of objects to their removal or persistence. Whenever any domain operation needs a domain object, it should retrieve the reference of the requested object efficiently. It would be very difficult if you didn't maintain all the available domain objects in a central object that carries the references of all the objects and is responsible for returning the requested object reference. This central object is known as the repository.

The repository is a point that interacts with infrastructures such as the database or file system. A repository object is the part of the domain model that interacts with storage such as database, external sources, and so on to retrieve the persisted objects. When a request is received by the repository for an object's reference, it returns the existing object's reference. If the requested object does not exist in the repository then it retrieves the object from storage. For example, if you need a customer, you would query the repository object to provide the customer with ID 31. The repository would provide the requested customer object if it is already available within the repository, and if not would query the persisted stores such as the database, fetch it and provide its reference.

The main advantage of using the repository is having a consistent way to retrieve objects where the requestor does not need to interact directly with the storage such as the database.

A repository may query objects from various storage types such as one or more databases, filesystems or factory repositories and so on. In such cases, a repository may have strategies that also point to different sources for different object types or categories:

Repository

Repository object flow

As shown in the repository object flow diagram, the repository interacts with the infrastructure layer and this interface is part of the domain layer. The requestor may belong to a domain layer or an application layer. The repository helps the system to manage the life cycle of domain objects.

Factory

The factory is required when a simple constructor is not enough to create the object. It helps to create complex objects or an aggregate that involves the creation of other related objects.

A factory is also a part of the life cycle of domain objects as it is responsible for creating them. Factories and repositories are in some way related to each other as both refer to domain objects. The factory refers to newly created objects whereas the repository returns the already existing objects either from in the memory or from external storages.

Let us see how control flows using a user creation process app. Let's say that a user signs up with a username user1. This user creation first interacts with the factory, which creates the name user1 and then caches it in the domain using the repository which also stores it in the storage for persistence.

When the same user logs in again, the call moves to the repository for a reference. This uses the storage to load the reference and pass it to the requestor.

The requestor may then use this user1 object to book the table in a specified restaurant and at a specified time. These values are passed as parameters and a table booking record is created in storage using the repository:

Factory

Repository object flow

The factory may use one of the object oriented programming patterns such as the factory or abstract factory pattern for object creation.

Modules

Modules are the best way of separating related business objects. These are best suited to large projects where the size of domain objects is bigger. For the end user, it makes sense to divide the domain model into modules and set the relationship between these modules. Once you understand the modules and their relationship, you start to see the bigger picture of the domain model, and it is easier to drill down further and understand the model.

Modules also help in code that is highly cohesive or that maintains low coupling. Ubiquitous language can be used to name these modules. For the table booking system, we could have different modules such as user-management, restaurants and tables, analytics and reports, and reviews, and so on.

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

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