Sunday, September 15, 2019

Factories

Shift the responsibility of creating instances of complex objects and AGGREGATES to a separate object, which may itself have no responsibility in the domain model but is still part of the domain design. Provide an interface that encapsulates all complex assembly and that does not require the client to reference the concrete classes of the objects being instantiated. Create entire AGGREGATES as a piece, enforcing their invariants.

When a constructor is all you need

The trade-offs favor a bare, public constructor in the following circunstances:

  • The class is the type. It is not part of any interesting hierarchy, and it isn't used polymorphically by implementing an interface.
  • The client cares about the implementation, perhaps as a way of choosing a STRATEGY.
  • All of the attributes of the object are available to the client, so that no object creation gets nested inside the constructor exposed to the client.
  • The construction is not complicated.
  • A public constructor must follow the same rules as a FACTORY: It must be an atomic operation that satisfies all invariants of the created object.

Avoid calling constructors within constructors of other classes. Constructors should be dead simple. Complex assemblies, especially of AGGREGATES, call for FACTORIES. The threshold for choosing to use a little FACTORY METHOD isn't high.

Eric Evans, "The Life Cycle of a Domain Object", in Domain-Driven Design: Tackling Complexity in the Heart of Software, 129.

Aggregates

Cluster the ENTITIES and VALUE OBJECTS into AGGREGATES and define boundaries around each. Choose one ENTITY to be the root of each AGGREGATE, and control all access to the objects inside the boundary through the root. Allow external objects to hold references to the root only. Transient references to internal members can be passed out for use within a single operation only. Because the root controls access, it cannot be blindsided by changes to the internals. This arrangement makes it practical to enforce all invariants for objects in the AGGREGATE and for the AGGREGATE as whole in any state change.

Eric Evans, "The Life Cycle of a Domain Object", in Domain-Driven Design: Tackling Complexity in the Heart of Software, 129.

Modeling paradigms

The four patterns [ENTITIES, VALUE OBJECTS, SERVICES and MODULES] in this chapter provide the building blocks for an object model. But MODEL-DRIVEN DESIGN does not necessarily mean forcing everything into an object mold. There are also other model paradigms supported by tools, such as rules engines. Projects have to make pragmatic trade-offs between them. These other tools and techniques are means to the end of a MODEL-DRIVEN DESIGN, not alternatives to it.

Here are four rules of thumb for mixing nonobject elements into a predominantly object-oriented system:

  • Don't fight the implementation paradigm. There's always another way to think about a domain. Find model concepts that fit the paradigm.
  • Lean on the ubiquitous language. Even when there is no rigorous connection between tools, very consistent use of language can keep parts of the design from diverging.
  • Don't get hung up on UML. Sometimes the fixation on a tool, such as UML diagramming, lead people to distort the model to make it fit what can easily be drawn. For example, UML does have some features for representing constraints, but they are not always sufficient. Some other style of drawing (perhaps conventional for the other paradigm), or simple English descriptions, are better than tortuous adaptation of a drawing style intended for a certain view of objects.
  • Be skeptical. Is the tool really pulling its weight? Just because you have some rules, that doesn't necessarily mean you need the overhead of a rules engine. Rules can be expressed as objects, perhaps a little less neatly; multiple paradigms complicate matters enormously.
Eric Evans, "A Model Expressed in Software", in Domain-Driven Design: Tackling Complexity in the Heart of Software, 116, 122.

The pitfalls of infrastructure-driven packaging

Elaborate technically driven packaging schemes impose two costs.

  • If the framework's partitioning conventions pull apart the elements implementing the conceptual objects, the code no longer reveals the model.
  • There is only so much partitioning a mind can stitch back togheter, and if the framework uses it all up, the domain developers lose their ability to chunck the model into meaningful pieces.

It is best to keep things simple. Choose a minimum of technical partitioning rules that are essential to the technical environment or actually aid development. For example, decoupling complicated data persistence code from the behavioral aspects of the objects may make refactoring easier.

Unless there is a real intention to distribute code on different servers, keep all the code that implements a single conceptual object in the same MODULE, if not the same object.

Use packaging to separate the domain layer from other code. Otherwise, leave as much freedom as possible to the domain developers to package the domain objects in ways that support their model and design choices.

Eric Evans, "A Model Expressed in Software", in Domain-Driven Design: Tackling Complexity in the Heart of Software, 114-115.