Field-driven design: The Way to cope with software core complexity (III)
Each object has its lifecycle. After an object is created, it may go through different states and eventually die out. Of course, many objects are simple temporary objects. We just call their constructor to create them, use them in some calculation, and then throw them into the garbage collector.
How to manage these objects poses a challenge to us. If we do not handle them well, it is easy to make our work deviate from the model-driven design direction.
There are two challenges:
Maintain object integrity during the lifecycle;
This prevents the model from getting into trouble due to the complexity of the Management lifecycle.
Pass3To solve these problems.
Aggregate (Aggregate) The model is made more compact by defining clear ownership and boundaries to avoid the occurrence of an object (Link) network with a root node. This mode is critical for maintaining integrity at all stages of the lifecycle.
Factory (Factory) To create and reorganize complex objects and aggregates, and maintain good encapsulation of its internal structure.
Warehousing (RepositoryIs used to process the intermediate and ending parts of the lifecycle, provide us with methods to find and extract persistent objects, and encapsulate complex infrastructure related to lifecycle management.
Although factories and warehouses themselves are not produced in the field, their role in the design of the field cannot be ignored. These constructs provide us with methods to access and control model objects, and improve model-driven design.
Building an aggregated model and adding the factory and warehouse to the design allows us to systematically manipulate the model objects and make the lifecycles of these objects a meaningful unit. Aggregates a range. In this range, no matter which stage of the object is in the lifecycle, it should be immutable. The factory and warehouse objects are combined to encapsulate the complexity of specific lifecycle changes.
In models that contain complex associations, it is very difficult to ensure the consistency of object modifications. We must ensure that closely related object groups can also be immutable, not just discrete objects. If the locking policy is too cautious, it will lead to unnecessary mutual interference among multiple users, making the system unusable.
An aggregation is a family of associated objects. For the purpose of data changes, we regard these objects as a unit. Each aggregation has a root (Root) And a boundary. The boundary defines what is contained in an aggregation; the root is a single specific entity contained in the aggregation. The root is the only element in the aggregation that is allowed to be referenced by external objects. However, within the aggregation boundary, objects can reference each other. Objects outside the root have local identifiers, but they only need to be distinguished within the aggregation, because external objects outside the root object context do not see them.
Aggregates objects and value objects into aggregation. Each aggregation defines a boundary. Select an object for each aggregation as its root, and control all access to objects within the boundary through the root. External objects can only hold root references. Temporary references to internal elements can only be used in a single operation. Because the root controls access, we cannot bypass it to modify internal elements. This arrangement ensures that, in any state change, the constants of the aggregation itself (as a whole) and the constants of objects in the aggregation can be satisfied.
The aggregation circle defines a range. In this range, no matter which stage of the life cycle is generated, it must satisfy the invariant. The following factory and warehouse modes are used to perform aggregation operations and encapsulate the complexity of specific lifecycle changes.
The factory provides encapsulation when creating an object or the entire aggregation logic becomes very complex, or too many internal structures are exposed.
"Create" can be used as a major operation of the object itself. However, it is inappropriate to combine the object creation responsibilities with complex assembly operations. This will make the design very clumsy and hard to understand. The customer's direct construction of objects makes the customer's design very messy, destroys the encapsulated or aggregated object, and completely associates the implementation of the customer with the created object.
Complex Object creation is the responsibility of the domain layer. However, creation is not an object that expresses the model.
Object creation and Assembly usually have no meaning in the field; they are just implementation needs.
Every object-oriented language provides an object creation mechanism, but we still need a structure that is more abstract and decoupled from other objects. Responsible for creating other objectsProgramAn element is called a factory.
Separate the responsibilities of creating complex objects or aggregate instances into a single object. This object may not have any responsibilities in the domain model, but is still part of the domain design. It provides an interface that encapsulates all the complex components, so that the customer does not need to reference the specific class of the object to be instantiated. Use the factory to create an aggregate as a whole and ensure that its invariant is satisfied.
The factory encapsulates the lifecycle changes of objects during creation and reconstruction. There is another kind of change. Its technical complexity will also lead to chaotic design in the field, that is, bidirectional conversion between objects and storage. This transformation is another field design structure-the responsibility of warehousing.
The customer needs a feasible method to obtain the reference of existing domain objects. If the infrastructure allows the customer's developers to easily join the association, they will join more navigation associations to mess up the model. Another possibility is that developers use queries to extract the extra data they need, or extract some special objects, which should be accessed through the aggregation root. Domain logic goes to queryCodeAnd the customer code, and the entity and value object becomes a pure data container. The technical complexity of most of the database access infrastructure quickly confusions the Customer Code. In the end, developers had to leave the domain layer and turn the model into a decoration.
Some persistent objects must be globally accessed by querying by object attributes. This access method is required for the aggregated root that is not accessible through navigation. These objects are usually entities and sometimes contain value objects with complex internal structures, and sometimes enumeration values. Providing such access for other objects will blur some important differences. Unrestricted database queries will actually destroy the encapsulation of domain objects and aggregation. Exposing the technical infrastructure and database access mechanism will complicate the customer and mask the model-driven design.
But even with these technologies, we still need to pay attention to what has been lost. We no longer consider concepts in the domain model. Our code does not describe business-related things anymore; it is only using data retrieval technology. The warehousing model is a simple conceptual framework used to encapsulate the above solutions and retrieve the focus of the model.
A warehouse describes all objects of a certain type as a conceptual set. Its behavior is similar to that of a set, but it contains more precise query capabilities.
Warehousing can be used to add or delete objects of the appropriate type, and they can be inserted to or deleted from the database through the mechanism behind the warehousing.
Warehousing has a series of closely related responsibilities, providing us with access to the aggregation root from the beginning of generation to the end of its lifecycle.
Create an object for each type of object that requires global access. This object provides images for all objects of this type in the memory. Use a well-known global interface to set up an access portal. Provides methods for adding and deleting objects to encapsulate the actual insertion and deletion of data storage. Provides a method to filter objects based on a certain standard. It returns the fully instantiated object or object set whose property values comply with the standard, and encapsulates the actual storage and query technologies. Only provide warehousing for the aggregated root that really needs to be accessed directly. Let the customer focus on the model and entrust all the object storage and access work to the warehouse for completion.
Warehousing has many advantages, including:
They provide customers with a simple model to obtain persistent objects and manage their lifecycles;
They decouple applications and domain designs from persistent technologies, multi-database strategies, or even multiple data sources;
They convey the design decisions for Object Access
They can be easily replaced with dummy implementations for testing purposes (typically using a set in memory)
The factory processes the beginning of the object lifecycle, while the warehouse helps to manage the middle and end parts of the lifecycle.
In this field-driven design perspective, factories and warehousing have completely different responsibilities. The factory creates new objects, while the warehouse searches for old objects.
The warehousing must make the customer feel that those objects are in the memory. Objects may have to be rebuilt (yes, a new instance must be created), but they are the same conceptual object and still in its lifecycle.
Clear area creation and reconstruction helps to separate all persistence-related responsibilities from the factory. Factory work is to instantiate a very complex object based on data.
If the product is a new object, the customer will know this and add it to the warehouse. The work of saving the object to the database is encapsulated by the warehouse.