I had prepared some notes, when I was reading through the DDD book from InfoQ - "Domain Driven Design Quickly
" which is just summarizes the essence of what DDD is, drawing mostly Eric Evans' book.
I thought, this note might be helpful to somebody who want to get the idea very quickly. This is not a complete description of what DDD is. Infact DDD is a very big topic that mostly comes into your grasp as you start practicing it. That is why probably books were written (and are written even now!) on it.
So here it goes...
1, 2, 3...
- DDD introduces nothing new, DDD is all about bringing Object Oriented paradigms to the Enterprise development
- Focus on business domain instead of technology hype
What is DDD
- Software exist to solve real world problems - automate processes. Software is deeply entangled to the domain it is built for.
- To create a good software, one must deeply understand the domain for which the software is built.
- The knowledge of the domain is known to the people inside it - the domain experts. The software should always start from this domain knowledge.
- Software should be a reflection of the domain.
- One even without a knowledge in the domain should be able to learn about it, by just reading the code.
- Software that does not reflect the domain cannot react well to the changes over time.
Building Domain Knowledge
- Software development process starts with understanding the domain. This starts with discussions between Software Designers/Developers and the Domain Experts.
- Knowledge about the domain from the domain experts may be disorganized and unpolished. Their organization of knowledge is very specific for their usage and not always suited to building a software system. We should extract the right information from them by asking the right questions towards transforming it to a useful form - the domain model.
- Early prototyping may be required for clarifying on the understanding between the two. However, the domain model is the place where the expertise of the two meet.
- Sometimes the discussions brings out some of the unearthed concepts from the domain to picture.
- There may be too many rounds of two and fro between domain expert, designer, and the developer.
- The process may seem to be time consuming - it is, and it should because, at the end, the software's purpose is to solve business problems in a real life domain.
Building the Domain Model
- Software process starts with Software Design - by talking to the domain experts.
- Knowledge from the domain experts is organized, systematized, divided into logical modules and abstracted as the Domain Model.
- Domain Model is very necessary throughout the design and development process as we will make lot of references to it later. We will need it to communicate with domain-experts, fellow designers, and developers.
- Domain model is an essential part of Software Design. Domain model is the domain knowledge abstracted for the software. All the thinking about the domain is synthesized into this model.
- Domain model can be expressed as a diagram (UML, DFD, FreeMind etc), in writing or a mix of both.
- As a part of Software Design we have the Domain model expressed. Now we can start doing the Code Design.
- Code design is different from Software Design. Software Design is like creating the architecture of a house, while Code Design is working on its details - deciding the location of a painting.
- A code design mistake is usually more easily corrected, while software design errors are a lot more costly to repair. Its easy moving the paining from one wall to the other, but a different thing to tear down one side of the house.
- Code design is where the design patterns comes to play. Good coding help create clean , maintainable code.
The Ubiquitous Language - The need for a common vocabulary.
- The Software process begins with building a domain model that happens by communication between the Domain expert and the Software Designer/Developer. Better Software needs better communication, that needs a common vocabulary between the parties - expert, designer and the developer.
- The project faces serious problem for lack of a common vocabulary to discuss the domain. There will be serious communication gap.
- Developers talk about classes, methods, algorithms, design patterns, databases, about which the domain expert hardly knows anything. But since we are solving the problems at the domain with the software, we should use the vocabulary that domain expert will understand, and normally uses.
- Domain experts on the other hand use their own jargon that is not easy to understand for designers and developers. We bridge this gap of common vocabulary by creating the domain model, where all involved in the team arrive at a common vocabulary mainly driven from the actual domain. This is where the software meets the domain.
- The vocabulary developed with the domain model must be used consistently in all communications without any ambiguity.
- The language should evolve with the consent from both the parties. Domain experts should object to awkward terms, while developers should watch for terms that may cause any problems at their side.
Model Driven Development
- Correctly expressing the model does not necessarily mean that the model will be correctly translated to a proper code. A good model producing a bad code model is not good anyway.
- Any domain can be expressed with many models, and any model can be expressed in various ways in code. For each particular problem there can be more than one solution. It is then important to choose a model which can be easily and accurately put into code. Again, the code should be able to communicate the business domain.
- The model chosen should allow the code to be accurately mapped to the model. This mapping should not be lost as the code and/or the domain evolves over time.
- The transition from model to code should not lose any information, and even worse, should not convey the wrong meanings. This happens primarily when the model chosen is modeled without taking the coding aspect.
- Analyst and domain expert meeting should not be closed room meetings. It would be good and productive to include the developers in the analyst meetings, so that they have a clear and complete view of the domain and the model before they start designing the code. The domain model should be constructed with an eye towards the software and design consideration, while not heavily relying on the code design.
- Everyone on the team must be aware of the model as well as the code aspect of the system. A refractoring in code should not break away from the model, similarly, a model constructed should be practical to program.
- Mapping between the model and code should be very simple and obvious. Complex mapping is a warning indicator. Revisit the model and modify it to be implemented more naturally in software, even as you seek to make it reflect deeper insight into the domain. Demand a single model that serves both purposes well, in addition to supporting a fluent Ubiquitous Language.
- Code and the model are two aspects of the project. A change in one aspect should ripple across the other aspect as well. This should be a conscious decision.
- Object-oriented programming is suitable for model implementation. They make it possible to create direct mappings between model objects with their relationships, and their programming counterparts.
Building blocks of Model Driven Development
- We should plan for layered architecture to allow different concerns - UI, Application, Domain, Infrastructure
- When we are interested about, which object it is - eg a bank account
- When we are more concerned about the attributes of the object rather than its identity. e.g - the points in a line.
- It is recommended to select as entities only those objects which conform to the entity definition. And make the rest of the objects Value Objects.
- Having no identity, Value Objects can be easily created and discarded. Nobody cares about creating an identity, and the garbage collector takes care of the object when is no longer referenced by any other object. This simplifies the design a lot.
- Value objects should be immutable so that it can be shared. Make copies of it and share it with other objects.
- Value objects may contain - attributes, other value objects, reference to entities.
- Service encapsulate the behavior of the domain. The behavior does not seem to belong to any of the objects- entity or value objects. Service can group related functionality which serves the Entities and the Value Objects.
- Such domain behaviors are attached to the Service objects. Such an object does not have an internal state, and its purpose is to simply provide functionality for the domain.
- Services act as interfaces which provide operations. A Service usually becomes a point of connection for many objects. This is one of the reasons why behavior which naturally belongs to a Service should not be included into domain objects.
- Services promote loose coupling between domain objects
- A Service should not replace the operation which normally belongs on domain objects. We should not create a Service for every operation needed. But when such an operation stands out as an important concept in the domain, a Service should be created for it.
- There are three characteristics of a Service:
1. The operation performed by the Service refers to a domain concept which does not naturally belong to an Entity or Value Object.
2. The operation performed refers to other objects in the domain.
3. The operation is stateless.
- Services can belong to 1. domain layer, 2. Application layer, 3. Infrastructure. These services should not be confused, and be kept separate:
- Both application and domain Services are usually built on top of domain Entities and Values providing required functionality directly related to those objects.
- If the operation performed conceptually belongs to the application layer, then the Service should be placed there. If the operation is about domain objects, and is strictly related to the domain, serving a domain need, then it should belong to the domain layer.
- The application layer is a thin layer which stands between the user interface, the domain and the infrastructure. (application layer = JSF actions written inside beans ?)
- Application layer does not talk to the domain object, but rather only to the service object, that in-turn work with domain objects.
- Modules allow to split application into logically related items, and call them as modules. It reduces complexity and cohesion between modules. Modules should be made up of elements which functionally or logically belong together assuring cohesion.
- Modules should have well defined interfaces which are accessed by other modules. Instead of calling three objects of a module, it is better to access one interface, because it reduces coupling. Low coupling reduces complexity, and increases maintainability.
- Give the Modules names that become part of the Ubiquitous Language. Modules and their names should reflect insight into the domain.
- We have he module concept naturally in place with OSGi's bundles. Every bundle (pair) forms a module for the system.
- Well defined interfaces to the Core bundles are provided by the OSGi service interfaces that we write.
- About refractoring modules: It is true that module refractoring may be more expensive than a class refractoring, but when a module design mistake is found, it is better to address it by changing the module then by finding ways around it.
Domain Design Patterns
Managing the life cycle of a domain object constitutes a challenge in itself, and if it is not done properly, it may have a negative impact on the domain model. Domain design patterns will help us deal with this.
- Aggregate is a domain pattern used to define object ownership and boundaries.
- Aggregates come to play when domain objects are associated with one another, creating a complex net of relationships. Associations can be one-to-one, one-to-many, and many-to-many.
- Many-to-many, bi-directional associations increases complexity. Reducing the complexity is very important. Tips:
- associations which are not essential for the model should be removed. They may exist in the domain, but they are not necessary in our model, so take them out.
- multiplicity can be reduced by adding a constraint. If many objects satisfy a relationship, it is possible that only one will do it if the right constraint is imposed on the relationship
- many times bidirectional associations can be transformed in unidirectional ones.
- An Aggregate is a group of associated objects which are considered as one unit with regard to data changes.
- Each Aggregate has one root. The root is an Entity, and it is the only object accessible from outside. The root can hold references to any of the aggregate objects, and the other objects can hold references to each other, but an outside object can hold references only to the root object. External objects cannot hold direct references to the objects in the aggregate for it to be directly changed, thus enforcing integrity.
- It is possible for the root to pass transient references of internal objects to external ones, with the condition that the external objects do not hold the reference after the operation is finished. The external objects should not be able to modify the original aggregate objects.
- One simple way to do that is to pass copies of the Value Objects to external objects.
- Pass on interface references that does not have object modifiers.
- If objects of an Aggregate are stored in a database, only the root should be obtainable through queries. The other objects should be obtained through traversal associations.
- Objects inside an Aggregate should be allowed to hold references to roots of other Aggregates.
- Factories gain importance when:
- creation of object is a major operation in itself.
- Knowledge needed to create an object needs to be encapsulated from the client objects that need them.
- Especially useful to create aggregates. When the root of the Aggregate is created, all the objects contained by the Aggregate are created along with it, and all the invariants are enforced.
- Good to have separate factories for each kind of objects. But related objects should be created in one factory.
- Object creation process needs to be atomic. Object creation should enforce all its invariants (rules), and should only produce valid objects (its states). If an object cannot be created properly, an exception should be raised, making sure that an invalid value is not returned.
- Factory: Therefore, shift the responsibility for 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 unit, enforcing their invariants.
- Patterns: e.g,. Abstract Factory, Factory Method
- Warning!:When creating a Factory, we are forced to violate an object's encapsulation, which must be done carefully. Whenever something changes in the object that has an impact on construction rules or on some of the invariants, we need to make sure the Factory is updated to support the new condition.
- Types:Entity Factories and Value Object Factories needs to be differentiated.
- Values objects are usually immutable, and all the necessary attributes need to be produced at the time of creation. When the object is created, it has to be valid and final. It won't change. Value objects don't need identity.
- Entities are not immutable. They can be changed later, by setting some of the attributes with their invariants respected. Entities need identity.
- Use Constructor instead of factory when:
- The construction is not complicated.
- The creation of an object does not involve the creation of others, and all the attributes needed are passed via the constructor.
- The client is interested in the implementation, perhaps wants to choose the Strategy used.
- The class is the type. There is no hierarchy involved, so no need to choose between a list of concrete implementations.
- When client objects need references to pre-existing domain objects, it can obtained by accessing the database, where they can be stored. But directly accessing it from the database meas muddling with the infrastructure, and the such access logic scattered throughout the entire domain, compromising the domain model.
- If infrastructure is simple to pull out data from the database (Hibernate), domain logic moves into queries and client code, and the Entities and Value Objects become mere data containers. The overall effect is that the domain focus is lost and the design is compromised.
- Encapsulate all the logic needed to obtain object references.
- The domain objects won't have to deal with the infrastructure to get the needed references to other objects of the domain. They will just get them from the Repository and the model is regaining its clarity and focus.
- Encapsulates the persistence mechanism.
- Repositories may:
- Cache domain object references
- use a strategy to switch between various persistence storage mechanism.
- on request from client, reconstitute the domain object from - cache, various repositories, or from a factory.
- Repository should have a well known interface that will provide access to add, remove and searching objects based on criteria. This will encapsulate actual add, remove, and querying techniques. This will keep the client focused on the model, delegating all object storage and access to the Repositories.
- An Entity can be easily specified by passing its identity. Other selection criteria can be made up of a set of object attributes.
- The implementation of a repository can be closely liked to the infrastructure, but that the repository interface will be pure domain model.
Factory vs Repository
- They are both patterns of the model-driven design, and they both help us to manage the life cycle of domain objects.
- While the Factory is concerned with the creation of new objects, the Repository takes care of already existing objects.
- When a new object is to be added to the Repository, it should be created first using the Factory, and then it should be given to the Repository which will store it like in the example below.
Refractoring Towards Deeper Insight
- Re-factoring the code is necessary and should be a continuous process.
- Re-factoring is the process of redesigning the code to make it better without changing application behavior.
- Re-factoring is usually done in small, controllable steps, with great care so we don't break functionality or introduce some bugs. Automated tests are of great help to ensure that we haven't broken anything.
- Apart from code re-factoring for technical reasons, there should be emphasis on re-factoring for domain model.
- Sometimes there is new insight into the domain, something becomes clearer, or a relationship between two elements is discovered. All that should be included in the design through re-factoring.
- Usually initial models from specification are shallow model, without much clarity on the domain. But the design should be flexible enough to incorporate changes as more clarity on the domain is obtained.
Bring Key Concepts to Light
- When re-factoring, try to bring the implicit concepts as explicit ones.
- Looking for implicit concepts: The language we are using during modeling and design contains a lot of information about the domain. At the beginning it may not be so much, or some of the information may not be correctly used. Some of the concepts may not be fully understood, or even completely misunderstood. This is all part of learning a new domain. But as we build our Ubiquitous Language, the key concepts make their way into it. That is where we should start looking for implicit concepts.
- Watch out for hidden concepts and contradictions in the concepts when doing re-factoring.
Explicit Constraints, Processes, and Specifications
- A Constraint is a simple way to express an invariant (business rule). This should be put as an explicit method so that its clearly visible. There is also room for growth adding more logic to the methods if the constraint becomes more complex.
- Processes are usually expressed in code with procedures. The best way to implement processes is to use a Service. If there are different ways to carry out the process, then we can encapsulate the algorithm in an object and use a Strategy. Make only those process as explicit that are mentioned in the Ubiquitous language.
- Specification is used to test an object to see if it satisfies a certain criteria. These are again, business rules, that operate against the domain data (from entity or value objects)
Preserving Model Integrity
- Splitting the domain into multiple models, each having its own context, and each model having a well defined contract with other models.
- Good for large teams working on a project. Instead of one stepping on others toe, bring in the models such that independent teams can work on their own model, thereby splitting the team to work independently on the modules.
- Still, everyone should have the big picture in mind.
- Even when a team works in a Bounded Context there is room for error.
- If one does not understand the relationships between objects, they may modify the code in such a way that comes in contradiction with the original intent. It is easy to make such a mistake when we do not keep 100% focus on the purity of the model.
- One member of the team might add code which duplicates existing code without knowing it, or they might add duplicate code instead of changing the current code, afraid of breaking existing functionality.
- A model is not fully defined from the beginning. It is created, then it evolves continuously based on new insight in the domain and feedback from the development process. That means that new concepts may enter the model, and new elements are added to the code. This is why we need continuous integration.
- We need a process of integration to make sure that all the new elements which are added fit harmoniously into the rest of the model, and are implemented correctly in code.
- The sooner we merge the code the better. For a single small team, daily merges are recommended.
- We also need to have a build process in place. The merged code needs to be automatically built so it can be tested.
- Another necessary requirement is to perform automated tests. If the team has a test tool, and has created a test suite, the test can be run upon each build, and any errors are signaled. The code can be easily changed to fix the reported errors, because they are caught early, and the merge, build, and test process is started again.
Anti Corruption Layer
- A layer between two separate but related systems. The two systems depend on each other closely. This layer will ensure that the two systems evolve independently of each other, while the ACL layer will manage the complexities that may come when the two systems evolve.
- Sort of a two way adapter?
Separate Ways Pattern
- The Separate Ways pattern addresses the case when an enterprise application can be made up of several smaller applications which have little or nothing in common from a modeling perspective.