Domain-Driven Design ( DDD ) is an approach to software development for complex requirements where the implementation is linked to an evolving model. Domain-driven design is not just a technique or method. It is rather a way of thinking and prioritizing to increase the productivity of software projects in the environment of complex technical contexts. Domain-driven design is based on the following two assumptions:
- The main focus of software design is on technicality and technical logic.
- The design of complex domain-oriented contexts should be based on a model of the application domain, the domain model.
Domain-driven design is not bound to a specific software development process, but is oriented towards agile software development. In particular, it requires iterative software development and a close cooperation between developers and experts.
The purpose of any software is to support the tasks of a specific application domain. In order to do this successfully, the software must harmonize with the domain for which it is intended. Domain-driven design enables this by modeling basic concepts and elements of the application domain and their relationships.
Domain Expert: The domain expert is the person who has the knowledge of the system or domain for which the software has to be developed .They would require a product that will help them work on their projects better and hence they require Software Developers
Developer: The developer is responsible to take in requirements from the Domain Expert and model a software according to their needs.
Domain experts and the developers together are equally responsible towards a final product when using the Domain Driven Design ideology
The domain not necessarily means anything related to software and hardware , it can also mean a business as shown by an example towards the end of this presentation
Modeling a domain
A model driven design is a code or software built around a set of domain concepts extracted from the domain experts.
Hence right from the start the implementation and domain are dependent on each other thus creating a feedback loop which unlike the waterfall model will be very useful .
- Model Change -> Code Change
- Code Change -> Model Change
These are the concepts which used in the domain driven design:
- Context: The setting in which a word or statement appears that determines its meaning;
- Domain: A sphere of knowledge (ontology), influence, or activity. The subject area to which the user applies a program is the domain of the software;
- Model: A system of abstractions that describes selected aspects of a domain and can be used to solve problems related to that domain;
- Ubiquitous language: A language structured around the domain model and used by all team members to connect all the activities of the team with the software.
Ingredients of Effective Modeling
Binding the Model and Implementation: Forming the link between the model and the code being implemented at an early state
Cultivating a language based on the model : Understanding the terms used by the domain experts and developers and trying to have an organized set of terms which can be unambiguously understood by both participants
Developing a knowledge-rich model: The components of the model built should follow certain rules , have behaviors , constraints which will be important to solve a complex problem related to the domain or the software
Distilling the model: During the course of future meetings the model will grow in code and features . Concepts may be added , removed, remodeled as deemed appropriate
Brainstorming and Experimenting: The language ,model and brainstorming attitude will go on for testing and judging of variations suggested and lead towards a final satisfactory product .
Multiple models are in play on any large project. Yet when code based on distinct models is combined, software becomes buggy, unreliable, and difficult to understand. Communication among team members becomes confusing. It is often unclear in what context a model should not be applied.
Best Practice: Explicitly define the context within which a model applies. Explicitly set boundaries in terms of team organization, usage within specific parts of the application, and physical manifestations such as code bases and database schemas. Keep the model strictly consistent within these bounds, but don't be distracted or confused by issues outside and inside.
When a number of people are working in the same bounded context, there is a strong tendency for the model to fragment. The bigger the team, the bigger the problem, but as few as three or four people can encounter serious problems. Yet breaking down the system into ever-smaller contexts eventually loses a valuable level of integration and coherency.
Best Practice: Institute a process of merging all code and other implementation artifacts frequently, with automated tests to flag fragmentation quickly. Relentlessly exercise the ubiquitous language to hammer out a shared view of the model as the concepts evolve in different people's heads.
An individual bounded context leaves some problems in the absence of a global view. The context of other models may still be vague and in flux. People on other teams won't be very aware of the context bounds and will unknowingly make changes that blur the edges or complicate the interconnections. When connections must be made between different contexts, they tend to bleed into each other.
Best Practice: Identify each model in play on the project and define its bounded context. This includes the implicit models of non-object-oriented subsystems. Name each bounded context, and make the names part of the ubiquitous language. Describe the points of contact between the models, outlining explicit translation for any communication and highlighting any sharing. Map the existing terrain.
In DDD, there are artifacts to express, create, and retrieve domain models:
An object that is not defined by its attributes, but rather by a thread of continuity and its identity.
Example: Most airlines distinguish each seat uniquely on every flight. Each seat is an entity in this context. However, Southwest Airlines, EasyJet and Ryanair do not distinguish between every seat; all seats are the same. In this context, a seat is actually a value object.
- Unique identity (string ,integer or any other single attribute ) for an Entity (object)
- Define an operation that is guaranteed to produce a unique identity result for each object.
- Unique identification can be irrespective of form , state , history and other attributes.
- Or it may be a combination of certain attributes and still guaranteed to be unique .
- The identification element can come form outside or may be created within the object.
- Once identity is symbol is created it is designated immutable.
- Sometimes an Entity can be generated automatically by the system . Most of the 3GL and 4GL’s usually provide this mechanism
- Sometimes certain objects may need the same identity and hence be the same . In such cases it has to be done manually in code and not the generated way .
When only the attributes of an object are important considerations then such an object is classified as a Value Object. Value objects are instantiated to represent elements of a design that we care about only for what they are and not who they are. Value objects are passed as parameters in messages between other objects (usually Entities). Value objects can be used as attributes within an entity too .
Example: When people exchange business cards, they generally do not distinguish between each unique card; they are only concerned about the information printed on the card. In this context, business cards are value objects.
Designing Value Objects
Value objects have to be immutable .If a value has to change within an entity it has to be fully replaced (should not have a pointer to another instance).
Some concepts from domain are not capable to be modeled as entity or Value objects and hence called Services. Services tend to be named for what they can do (for a client). A service has to be offered as an interface that is defined as a part of the model. Its parameters and results should be domain objects.
The 3 characteristics for good service are:
- The operation relates to a domain concept that is not a natural part of an entity or value object
- The interface is defined in terms of other elements of the domain model
- The operation is stateless (client can use any instance of a service irrespective of its previous history (state))
Two or more elements of a Model constitute a module. This classification is usually done to reduce cognitive overloading from an individuals perspective.
Modules provide 2 views on a model ,namely
- An Individual Module characteristic
- Idea about relations between two modules, neglecting the interior details.
High cohesion and low coupling are the unsung factors of modules .Nomenclature of modules should find itself in the ubiquitous language being used by the team.
A problem is that the change in modules may imply extensive change of code. It can be solved by reorganizing modules when a potential issue is spotted rather than waiting and increasing inertia.
An Aggregate is a cluster of Entities and Value objects. Each aggregate is treated as one single unit. It has boundary defined and maintains a strict idea of what belongs to the aggregate and what does not. One entity within each aggregate is chosen as the root , which helps control all access to the objects inside the predefined boundary. The aggregate root guarantees the consistency of changes being made within the aggregate by forbidding external objects from holding references to its members.
Example: When you drive a car, you do not have to worry about moving the wheels forward, making the engine combust with spark and fuel, etc.; you are simply driving the car. In this context, the car is an aggregate of several other objects and serves as the aggregate root to all of the other systems.
Methods for creating domain objects should delegate to a specialized Factory object such that alternative implementations may be easily interchanged.
Although, constructors and destructors are available to use in object oriented programs , there is a need for more abstract construction mechanisms that are decoupled from other objects in the domain and hence a program element named factory is required.
The basic requirements for a good factory are:
- Each creation method is atomic .It should not be affected by other threads using similar resources until all the invariants are satisfied
- In case of interruption or the request for an object is not possible there should be an exception thrown using the try catch or similar method.
- Factory method should return abstract and not concreted objects . (I.e. : the abstract superclass)
- The factory will be coupled to its arguments
Factory methods should be positioned according to the situation .
- In case of adding elements in a pre-existing aggregate , the root is the best place
- It can also be placed in an object that is closely responsible for spawning another object
- Standalone Factory: It can produce an entire object handing out reference to the root . (Used generally when an interior object of an aggregate requires a factory and the root is not responsible for it)
There are times that a simple constructor can be the best solution instead:
- The class is the Type
- Client cares abt Implementation (ex: strategy)
- All attributes of the required objects are already available to the client
- The construction is very simple
- For construction of Value objects However , Atomicity is still essential even in the case of a public constructor .
Methods for retrieving domain objects should delegate to a specialized Repository object such that alternative storage implementations may be easily interchanged.
What is a Repository?
- This will represent all the objects of a certain type as a conceptual set (Usually emulated).
- Acts as a collection except with more elaborate querying capability .
- Addition ,removal of objects are possible with the help of the machinery behind the REPOSITORY which inserts or deletes them from the database.
- Access to the root of the aggregates is possible which obviously is not possible easily by plain traversal.
Advantages of Repository
- Present client with a simple model for obtaining persistent objects and managing their life cycle.
- Decouple application and Domain design form the persistence technology
- Communicate design decisions about object access
- Allow dummy implementation that can be used in testing
A domain object that defines an event (something that happens). A domain event is an event that domain experts care about.
Model the following system (define Entities, Value Objects, Aggregate Boundaries and Repositories):
- Domain: Cargo shipping.
- Track handling of customer cargo.
- Book cargo in advance.
- Send invoices and updates to customer.
- Model of shipping domain classes
- Customer , cargo, delivery history, delivery specification , handling event, location, carrier movement
Isolating the design
Coordinator classes (Application Layer)
- Tracking query -> access past and present handling of cargo
- Booking application-> new cargo registration
- Incident logging application -> Record each event of the cargo , providing tracking query information.
These may have automatically generated identities or, manually allotted ones
- Handling event
- Carrier Movement
- Delivery History
These cannot be shared by 2 cargos even if they are going to the same destination.
- Delivery Specification
Identifying and designing Associations.
- Making association traversable only from Handling event to Carrier Movement .
- Circular reference : Cargo -> Delivery History -> handling event -> Cargo
- Customer location ,Carrier Movement have own identity ,shared by many cargos. Hence they must be routes of their aggregates.
- Delivery History /Specification follow suit
- 5 aggregate roots
- Decision of which of these elements should have a repository depends on application requirements
- Repositories needed are
- Customer Repository
- Location Repository
- Carrier Movement Repository
- Cargo Repository
Walking through the Scenarios
Check for a use case for each Aggregate and possible Scenarios
- Delivery History -> review , add , delete
- Customer roles -> copy keyed referenced Maps
- Tracking id -> Creating a new tracking id for a cargo delivery .