Usually, applications present similar functionalities, being the definition of the domain one of the most complex tasks to develop. In this methodology, domain-driven design is recommended to perform this activity. Domain-driven design is an evolution of object-oriented domain modeling in which a set of design patterns and best practices are defined in order to facilitate the maintainability of the software and the test process. Domain-driven design consists of three principles:
- First, get a good (simple) domain model.
- Second, use model domain as communication language (called ubiquitous language).
- And third, keep the domain model up to date.
Simple domain model
A domain model represents a part of the real world with which the users of the software want to interact. A domain model consists of a set of fundamental entities and the relationships between them. It is just a mechanism to build software and all the details don't have to be modeled.
But model isn't just a data schema, it is the key to solve a complex problem and patterns and recommendations exposed in domain-driven design approach have to be taken into account to obtain a simple model that, at the same time, collects all the information required to implement the application. Model will be evolving during the development of the application, but along with the agile principles, simplicity is a priority, and this has to be kept until the end.
In the definition of the model, users and developers have to work together in order to get the best solution, studying different opinions and use scenarios.
An ubiquitous language
The obtained model establishes a common language. Its aim is to improve the communication between developers and users. Building a glossary of the terms based on the model can help to use it. Also, it facilitates the management of the heterogeneity among different paradigms.
Although object-oriented design is the most used paradigm, in some cases it is necessary to work with different paradigms such as relational paradigms to use relational databases. Because databases manage the objects persistence, they are most related to the object model than other components, being necessary to put special attention on the database paradigm.
Updating the model
The domain model will be used in all methodology phases: plan, design and implement. In each iteration there will be changes on the model and therefore the communication language will evolve.
In the plan phase, new tasks to develop will be determined and these will be reflected on the model on the design phase. The model has to keep up to date and simple, so in each iteration, in the design phase, a review will be done with this purpose. In addition, in the implementation phase there may be modifications on the model and these have to be included.
Code evolves during the development process, because new functionalities are implemented or improvements are carried out. Changing the code to make it better while keeping its behaviour is called refactoring. The aim of refactoring can be to improve the code quality, which is usually reached with the use of patterns and automatic tools that apply these patterns, or to improve the readability and understandability of the code, because the definition of the domain model becomes clearer.
Sometimes big changes are made in the code, which are known as breakthroughs. A breakthrough happen when an implicit concept becomes explicit. These concepts can be hidden in the model description and become necessary at a certain point at the development of the system, because their functionality increases and if they do not appear, another one has to implement their behaviour, with a decrease in software quality.
Another concept that can become explicit are constraints, processes and specifications. Its implementation in a separated way can help to discover them easily. A constraint is a restriction about something, it becomes explicit when it is implemented in a specific method. A process has to be explicit when it is referenced in the ubiquitous language. In that case, the best option is to implement it as a service. A specification determines if an object fulfils a set of criteria. It is composed of a set of rules or operations with a boolean result that usually are in the object related but which can be grouped to become explicit.
The design phase consists of the activities shown in the figure.
D.1 – Initial design
In this activity, an initial design is made. Based on user's indications an initial class diagram that represents all the elements required to develop the task on progress is built. Several tools can be used to represent this diagram, but in any case, users must be able to understand it and use the terms in the model to facilitate the communication with the developers in next iterations.
D.2 – Design refactoring
In this activity, the initial design is refactored After obtaining an initial draft, DDD patterns and best practices have to be applied to improve the domain model, keeping the original functionality. Next, a set of steps are exposed in order to refactor the domain model.
1. Analysing associations: An association establishes a relationship among two or more objects. The model has to be as simple as possible and avoiding complicate associations is a good mechanism to achieve this. Some ideas to simplify associations are: imposing a direction; adding a qualifier, reducing multiplicity; and eliminating non-essential associations.
For example, in a blog application there are posts and comments. Each post have several comments and each comment belongs to a particular post. We can represent this association in this way or we can simplify it by imposing a direction from posts to comments and a multiplicity, as long as one post can have zero or more comments.
2. Entities, value objects and services: The model has to be clear, distinguishing between entities and value objects. An entity is a fundamental concept: it requires an unique attribute to identify it because its state can change during the software execution. Meanwhile, a value object only describes a characteristic of the domain. A value object has no identity and can be shared. In some cases, making it immutable improves implementation features.
In the blog application, posts are entities, because each post must be unique. While comments are value objects, these have no identity and the same comment can appear in two posts.
Finally, in a domain model there are services. These represent processes that are not responsible of any entity or value object in particular. But sometimes services are overused and functions that correspond to the business logic of an object are implemented as a service. A good practice to avoid this situation is to use a verb to name the service.
3. Using aggregations: An aggregation is made up of a set of associated objects, but only one of those (the root) can be referenced by objects that are outside the aggregation. The objects involved in an aggregation acts as a unit, facilitating the management of complex associations between objects.
In the domain of a blog application, posts and comments form an aggregation in which posts are the root and control the access to comments.
4. Selecting repositories: Another pattern to get a good domain model is the use of repositories. A repository manages the storage of a concrete type of objects. It implies the implementation of typical operations over a database like adding, editing and removing elements and querying facilities.
Repositories are domain objects associated with an aggregate that manage the data storage and retrieval, abstracting persistence mechanisms required to perform these operations. In the blog domain, for example, we will use a repository to obtain a post or a list of posts stored in the database.
Usually each web development framework has a way to implement persistence, but it is necessary to analyse if it is the most suitable to the application or if it is required to develop a specific solution.
5. Using factories to create objects: Another issue to consider is the object creation and the possibility of using factories for this purpose. A factory defines the creation process of an object. Using a factory is useful only when the object to create is complex. In the rest of cases its use can complicate the process and it is better to use a simple constructor.
For example, creating a post is an easy process and we use a constructor to do it. But if we were to verify that a post does not exist before creating it in several remote sites, we would have to use a factory to hide this operation.
A factory for a value object is not the same as a factory for an entity. In the first case, the factory has to completely define the process of creation, because value objects are immutable and they have to be fully described since their creation. Meanwhile, the state of an entity can change during the software execution, and its factory only has to define some characteristics of it. Especially, we cannot forget its identifying attributes.
6. Validating the model: At this point, using several scenarios to confirm that the model fits the requirements is a good practice to go ahead with security. We must ensure that tasks to develop in this iteration can be implemented.
7. Dividing the model into modules: In the development of an enterprise application, the collaboration of many people that work in parallel is required, being necessary to divide the domain model into a set of modules.
To help to maintain the integrity of the system, each module has to be defined with a bounded context. There has to be a continuous integration and it is also advisable to represent the relationships among the models involved in the system in a map context. To facilitate to determine these associations, there are several patterns that cover different possibilities:
- The so-called Shared Kernel pattern is used when a part of the model is shared among two or more developers team. This part can be changed, so there has to be a fluid communication among developers to report every action that modifies the system functionality.
- Another pattern in which there is a closer relationship between models is Customer Supplier. In this case, one model (customer) depends on the other (supplier) and, therefore, there has to be a well defined interface to facilitate the information exchange.
- When the supplier does not worry about the requirements of the customer, the pattern to apply is called Conformist. In this case, the customer adopts the model defined in the supplier to simplify the integration between them.
If a system has to be integrated with others which have their own models, an Anticorruption Layer is a good pattern to apply. This implies to use a layer that understands models and manages the data exchange, allowing to take advantage of the information related to the systems' model to explain data.
- A Separated Ways pattern is applied when there are several systems with different independent models, so that they can evolve in a separated way and only a mutual GUI is implemented to enable one access point to them.
When a system has to be integrated with many others and each system needs to access the original in a different way, an Open Host Service pattern is advisable to be used. This way, the system is seen as a service provider and thus implementing a specific solution for each case is avoided.
- Sometimes it is advisable to distinguish the core of the model, in which the main concepts of the domain are modelled, from the rest of the model. This originates a set of subdomains that can be implemented using different methods: off-the-shelf solution, outsourcing, existing model or in-house implementation.
0 Ficheros adjuntos