📜 ⬆️ ⬇️

Domain-Driven Design: tactical design. Part 2



Hello, dear users! In a previous article, we looked at strategic modeling using the DDD approach. It showed how to highlight conceptual boundaries within which individual tasks of the subject domain are solved — .

To implement a specific , a number of lower-level tactical patterns are used that are of a technical nature, that is, these patterns are used to solve technical problems. These templates are: , - , , , , , and . That they will be discussed in this article.
')
It is important to understand that with proper design, a is expressed through these templates in an explicit . The software model must fully demonstrate the richness of a in . If the concept is not expressed using a , then it should not be represented in the model. If the design is carried out using tactical templates, not paying attention to a , this means that a lightweight DDD approach is used.

Traditionally, starting with the book of E. Evans, the DDD pattern is first considered - the .

Entity


If a notion of a domain is unique and different from all other objects in the system, then an used to model it. Such - can differ greatly in their form over the entire life cycle, however, they can always be uniquely identified and found upon request. For this purpose, unique identifiers are used, the creation of which must be considered first of all when designing an .

There are several strategies for creating identifiers:

User input of a unique value

This approach should be used if the identifiers in the application should be readable. However, it is necessary to ensure that the identifier is checked for correctness and uniqueness in the application itself. Often, the cost of identifier changes is high, and users do not have to change them. Therefore, you need to use methods that will guarantee the quality of this identifier.

ID generated by the application

There are fast and highly reliable generators that can be used in applications to automatically create a unique identifier. For example, in the Java environment, there is a java.util.UUID class that allows you to generate a so-called universally unique identifier in four different ways (time-based, DCE security, name-based, randomly generated UUIDs).

An example of such a UUID is: 046b6c7f-0b8a-43b9-b35d-6489e6daee91 . That is 36 byte string.

Such large identifiers are not profitable to store due to memory overload. Depending on the level of confidence in the uniqueness of individual segments of the hexadecimal text of the UUID identifier, one or more segments of the whole identifier can be used. The shortened identifier is better protected if it is used as a local identifier of the at the boundary of the .

For example, consider the following identifier: APM-P-08-14-2016-046B6C7F .

Here: PM is a separate design management context; P - project; 08-14-2016 - the date of creation; 046B6C7F- is the first segment of the UUID. When such identifiers come across in various , developers immediately see where they came from.

ID is generated by persistent storage.

To create an identifier you can refer to the database. This way you can be sure that the unique value will definitely return. However, it will be quite short and it can also be used for a composite identifier.

The disadvantage of this approach is performance. Accessing a database for each value can take much longer than generating identifiers in an application.

Identifiers that are assigned to other restricted contexts.

It is possible that in order to obtain an identifier it is necessary to integrate various (for example, using , as shown in the previous article).

To find a specific identifier in another , for example, you can specify a number of attributes (e-mail, account number), which will enable you to define a unique identifier of an external that can be used as a local identifier. You can also copy some additional state (property) from the external to the local .

For an entity, usually, besides the main identifier of the subject area, a surrogate identifier is used. The first submits to the requirements of the subject area, and the second is already intended for the ORM tool itself (as Hibernate). To create a surrogate key, an attribute of the type long or int is usually created, a unique identifier is created in the database, and it is used as the primary key. Then the display of this key in the attribute is enabled using ORM tools. Such a surrogate identifier is usually hidden from the outside world, since it is not part of the domain model.

It is also important to say that in order to preserve uniqueness throughout the existence of an object, its identifier must be protected from modification. This is done mainly by hiding identifier setters, or by creating state change checks in setter methods to prevent such changes. For example, consider the same PFM system as in the previous article.

First you need to select in the subject area. It is quite obvious that the BankingAccount exists, and to identify it, the use of the account number AccountNumber arises. However, this number is unique only in a separate bank and may be repeated in other banks. (You can use an IBAN number, but this number is mainly used only in banks of the European Union.) That is, in addition to the account number, you can also use the UUID segment. Then our identifier will consist of PFM-A-424214343245-046b6c7f , where:



The identifier can be specified as - . It is necessary to examine in detail this very important DDD pattern.

Object Value (Value Object)


If individuality is not important for an object, if it is completely defined by its attributes, it should be considered an - . To find out if a concept is a , you need to find out if it has the most of the following characteristics:


It must be said that such objects should be encountered much more often than it seems at first glance. They are easier to create, test, and maintain, so you should try to use - instead of where possible.

Very rarely - are made mutable. To deny access to fields, the setters methods are usually made private, and the object's constructor is made public, into which all objects that are attribute are passed. Creating a - must be an atomic operation.

For a - it is very important to define an equality check operation. In order for two - be equal, it is necessary that all types and values ​​of attributes be equal.

It is also very important to say that all methods - must be . Since they should not violate the property of immutability, they can return objects, but cannot change the state of an object.

Consider the classic example of an object of value money (in many examples on the Internet this class is found):

 public class Money implements Serializable { private BigDecimal amount; private String currency; public Money (BigDecimal anAmount, String aCurrency) { this.setAmount(anAmount); this.setCurrency(aCurrency); } … } 

The setters methods here are made hidden, creating a value object is an atomic operation. An example of a specific value is {50 000 } . Individually, these attributes either describe something else or do not mean anything concrete. This is particularly the case for 50,000 and to some extent for dollars. That is, these attributes form a conceptually that describes the amount of money. Such integrity of the concept in the domain plays a very important role. It is important to understand that the types of and the themselves are named according to a in their .

Let's move on to the next important tactical simulation pattern.

Domain Service (Domain Service)


Using a , the nouns of this language are reflected in objects, and the verbs are reflected in the behavior of these objects. Very often there are verbs or some actions that cannot be attributed to an or an - . If there is such an operation in the domain, it is declared as (it differs from the , which is the client). There are three characteristics of :


No need to abuse the use of . This leads to the creation of an . Business logic should be distributed over and . Only if this cannot be done by following a , then you need to use . The main thing is that its interface accurately reflects a .

For example, you can take the money transfer from one payer account to the recipient. It is completely unclear in which object to store the translation method, therefore the used:


Event (Domain Event)


Studying the subject area, there are facts that are of particular importance to experts in the subject area. For example, from experts you can hear the following key phrases:


That is, if something should happen as a result of another separate action, it is likely that modeling of a specific necessary.
When modeling it is necessary to take into account that the is what happened in the past tense. Therefore, the name of the reflects the past time, and the name itself must be assigned in accordance with a in a .

very often projected immutable, as well as - , their functions are . modeled as an object whose interface expresses its purpose, and the properties reflect its reason.

For example, consider the event FundsDeposited:


occuredOn is the timestamp. Next, you must specify the important properties that carry information about what happened. An important property is the identifier of the or in which the generated (accountId). It is also possible for subscribers to be important some parameters of the transition from one state to another.

In this case, an event is modeled that occurs when funds are credited to a particular account. As a result, you can send an SMS about enrollment, send mail or perform another operation.

In order for be published, and so that they can be processed, you can use the template, or the - .

If an event is processed within the same , then you can not use different infrastructure components (they should not exist within the scope of the domain model), but you can simply add the implementation of the pattern to the model.

Thus, it is enough to create a DomainEventPublisher object that will store, register all subscribers and publish the . In this case, the publication for subscribers will go synchronously in a separate cycle and in a single transaction. And each subscriber will be able to work out the separately.

It is important to emphasize that is a concept of the scale of the , and not of a separate . Therefore, it is possible to perform asynchronous forwarding to external using the messaging infrastructure.

There are many message passing components that belong to the middleware class (for example, RabbitMQ, NServiceBus). You can also do messaging using REST resources, where autonomous systems access the publishing system, requiring notification of events that have not yet been processed.

The RESTful approach to publishing notifications is the opposite of publishing using a typical messaging infrastructure. “Publisher” does not support a number of registered “subscribers”, because nothing is sent to interested parties. Instead, the approach requires that REST clients themselves request notifications using a URI resource.

It is important to understand that it is necessary to achieve consistency between what the messaging infrastructure publishes and between the current state in the domain model. It is necessary to guarantee the delivery of the , and that this reflect the true situation in the model in which it was published.

There are various ways to achieve such consistency. The most commonly used method is to use within a . This repository is used by the domain model and is also used by an external component that publishes unpublished using a message passing mechanism. But with this approach, customers need to de-duplicate incoming messages so that when they resend the same event, clients correctly process it.

In both cases — when subscribers use middleware for messaging, or when notification clients use REST — it is important that tracking of the identity of the processed messages is recorded along with any changes in the local state of the domain model.

Let's look at the following DDD pattern.

Module


inside the model are named containers for a certain group of domain objects that are closely related to each other. Their goal is to weaken the links between classes that are in different . Since the in the DDD approach are informal or generalized sections, they should be correctly named. The choice of their names is a function of a .

It is necessary to design loosely coupled , which facilitates support and refactoring of modeling concepts. If connectivity is necessary, then you need to fight for acyclic dependencies between peer modules (peers are that are located on the same level or that have the same weight in the project). better not to make static model concepts, since they should change depending on the objects they organize.

There are certain rules for naming modules. Module names (in many programming languages) display their hierarchical form of organization. The name hierarchy usually begins with the domain name of the organization that develops the module (to avoid conflicts). For example:

 com.bankingsystems 

The next segment of the module name identifies the . It is desirable that the name of this segment be based on the name of the :

 com.bankingsystes.pfm 

The following is the qualifier that identifies the module of the subject area:

 com.bankingsystems.pfm.domain 

All model can be placed in this section domain. Like this:

 com.bankingsystems.pfm.domain.account <<Entity>>BankingAccount <<ValueObject>>AccountId 

In a well-known naming would be:

om.bankingsystems.resources
om.bankingsystems.resources.view - (view storage)

om.bankingsystems.application
om.bankingsystems.application.account - (submodule of )

are used to aggregate related domain objects and are separated from objects that are not connected or are loosely connected. often span multiple modules, because they usually combine all the concepts in one model first, if there are no clear boundaries of contexts.

Aggregate


is the most difficult of all tactical tools DDD. is a cluster of objects or . That is, these objects are considered as a whole from the point of view of changing data.

Everyone has a root (Aggregate Root) and a border within which invariants must always be satisfied.

All calls must be made through it , which is a globally unique identifier. All internal objects have only local identity, they can refer to each other as they please. External objects can only store a link to , and not to internal objects.

An invariant is a business rule that always maintains its consistency. This phenomenon is called transactional consistency, which is atomic. There is also a final consistency. In the case of invariants, we are talking about transactional consistency. , , , .

, , , .

, . , . , . , - , . As mentioned above, they are easier to maintain, test, etc.

Everyone can keep the link as the roots of others . Nor does it put this within the boundaries of the consistency of the first . Link does not generate holistic .

Within one transaction only one change should occur .

Links are best done by global identifiers , rather than storing direct links as objects (or pointers). Thus: the memory of objects decreases; they load faster; they are easier to scale.

If the client's request affects several , then the principle of total consistency should be used. Final consistency can be achieved through publications. . , , , , .

:


. ustomerId . ustomer – , (, , ). , , – .

, , , – . , , . . , . , all invariants must be satisfied.

Inquiry- a specific request for a credit report from other organizations. is . He has a global identity. If you need to reference this , you can only use the identifier .

When deleting a report, everything must be deleted - history and query records.

Factory (Factory)


, .

. . ( : , , , .) , . , . ( , ).

. . – , .

. , ( ).

, . . ( - ) . .

- -: , . , .

(Repository)


, . - . . , , . , . , .

, , . - .

:

  1. ;
  2. .

, , , . , , , , DDD.

HashMap<ObjectId,Object> . . , , .

, . :

  1. (implicity copy-on-read) ( );
  2. ( -).

, Hibernate, , .

, - .

, , , save() . For example:


. , .

, . save() put() . .

. .

Conclusion


DDD. . - . - . . , , , , .

, , , .

, DDD! , . . .

: greebn9k ( ), wa1one ( ), silmarilion ( ).

Source: https://habr.com/ru/post/316890/


All Articles