
Introduction to Entity Design, Object Creation Problems
When modeling such a concept of subject-oriented design as an entity, some difficulties may arise due to business requirements or the technical part. In particular, sometimes it becomes difficult to create an entity object.
This article describes two such problems and discusses how to solve them. This article is also suitable as an introduction to entity design. To understand the material, you will need a basic understanding of subject-oriented design.
So, we studied the subject area, formed a single language, identified limited contexts and decided on the requirements [2]. All this is beyond the scope of this article, here we will try to solve specific narrow problems:
We have a client that should be modeled as an entity (Entity) [2]. From a business point of view, each client must have:
And also there can be all kinds of additional information.
In the simple case, a class that implements a simulated entity can look like this.
We omitted a number of properties; to simplify the example, the real class can be much larger.
The resulting model is anemic, overloaded with meaningless setters instead of methods describing the behavior corresponding to business requirements. At any time, with this approach, you can create an object in an inconsistent state or break the business logic by setting one of the parameters, for example like this.
First, let's try to solve the problem of consistency. To do this, we remove the setters from the class, and we will request all the required parameters in the constructor [4]. Thus, the object will always be valid, can be used immediately after creation, and full encapsulation is provided preventing the possibility of bringing the client to an inconsistent state. Now our class is as follows.
We solved the problem, but it did not work out very elegantly. The constructor has 8 parameters, and this is not the limit. Of course, not every entity requires so many required parameters, this is perhaps not a very ordinary situation, but also not unusual.
What can be done about this? A simple and obvious solution is to group logically related parameters in Value Objects [3].
It looks much better, but there are still quite a lot of parameters, especially it is not convenient if some of them are scalar. The solution is the Builder template [5].
Thus, the modeled entity is always in a consistent state and can be flexibly and clearly constructed, regardless of the complexity of the created object and the number of parameters.
The designed class must have a unique identifier, as the main distinguishing feature of entities is individuality. An object can change significantly over time, so that none of its properties will be equal to what it was at the beginning. At the same time, all or most of the properties of an object may coincide with the properties of another object, but they will be different objects. It is a unique identifier that makes it possible to distinguish each object regardless of its current state [1].
There are various ways of generating identifiers, such as user input, generation in the application by any algorithm, can be generated by the database.
To generate, for example, a UUID or to query the database for the next sequence value is not difficult. But sometimes it becomes necessary to work with an existing database structure in which a table storing the corresponding data has an auto-increment field for the identifier.
In this case, it is extremely difficult and unreliable to obtain the next unique identifier without saving the entity, which means it is impossible to create an object in a completely consistent state.
And again, the Builder template will help us in this, which we can implement as follows.
Thus, we first add the corresponding record to the database, after which we get its identifier and create an object. The client does not need to know about this, and when changing the method of storing or generating identifiers, you only need to replace the implementation of the Builder in your dependency container.
Thank you for attention!
Please do not judge too harshly, "Chukchi is not a writer, Chukchi is a reader." For more experienced developers, the described things may seem commonplace, but I didn’t come to the described immediately, but when I applied the described approach, it proved to be excellent.
My development experience is relatively small - four years, DDD used so far on only one project.
I would be grateful for the feedback and constructive comments.
This article describes two such problems and discusses how to solve them. This article is also suitable as an introduction to entity design. To understand the material, you will need a basic understanding of subject-oriented design.
So, we studied the subject area, formed a single language, identified limited contexts and decided on the requirements [2]. All this is beyond the scope of this article, here we will try to solve specific narrow problems:
- Creation and maintenance of consistency of complex entity objects.
- Creation of entity objects with the generation of an identifier by an auto-increment field of a database.
Introduction
We have a client that should be modeled as an entity (Entity) [2]. From a business point of view, each client must have:
- name or title;
- organizational form (individual entity, individual entrepreneur, LLC, joint-stock company, etc.);
- the main manager (one of the managers assigned to the client);
- information about the actual address;
- information about the legal address.
And also there can be all kinds of additional information.
In the simple case, a class that implements a simulated entity can look like this.
namespace Domain;
final class Client
{
public function getId(): int;
public function setId($id): void;
public function setCorporateForm($corporateForm): void;
public function setName($name): void;
public function setGeneralManager(Manager $manager): void;
public function setCountry($country): void;
public function setCity($city): void;
public function setStreet($street): void;
public function setSubway($subway): void;
}
We omitted a number of properties; to simplify the example, the real class can be much larger.
The resulting model is anemic, overloaded with meaningless setters instead of methods describing the behavior corresponding to business requirements. At any time, with this approach, you can create an object in an inconsistent state or break the business logic by setting one of the parameters, for example like this.
$client = new Client();
// В данный момент клиент у нас уже находится в не консистентном состоянии
// Если мы хотим запросить его идентификатор, то получим ошибку, т.к. он ещё не установлен
$client->getId();
// Или мы можем сохранить (попытаться) не валидного клиента, у которого не установлены обязательные свойства
$repository->save($client);
Creation and maintenance of consistency of complex entity objects.
First, let's try to solve the problem of consistency. To do this, we remove the setters from the class, and we will request all the required parameters in the constructor [4]. Thus, the object will always be valid, can be used immediately after creation, and full encapsulation is provided preventing the possibility of bringing the client to an inconsistent state. Now our class is as follows.
namespace Domain;
final class Client
{
public function __construct(
$id,
$corporateForm,
$name,
$generalManager,
$country,
$city,
$street,
$subway = null
);
public function getId(): int;
}
We solved the problem, but it did not work out very elegantly. The constructor has 8 parameters, and this is not the limit. Of course, not every entity requires so many required parameters, this is perhaps not a very ordinary situation, but also not unusual.
What can be done about this? A simple and obvious solution is to group logically related parameters in Value Objects [3].
namespace Domain;
final class Address
{
public function __construct($country, $city, $street, $subway = null);
}
namespace Domain;
final class Client
{
public function __construct(
int $id,
string $name,
Enum $corporateForm,
Manager $generalManager,
Address $address
);
public function getId(): int;
}
It looks much better, but there are still quite a lot of parameters, especially it is not convenient if some of them are scalar. The solution is the Builder template [5].
namespace Application;
interface ClientBuilder
{
public function buildClient(): Client;
public function setId($id): ClientBuilder;
public function setCorporateForm($corporateForm): ClientBuilder;
public function setName($name): ClientBuilder;
public function setGeneralManager(Manager $generalManager): ClientBuilder;
public function setAddress(Address $address): ClientBuilder;
}
$client = $builder->setId($id)
->setName($name)
->setGeneralManagerId($generalManager)
->setCorporateForm($corporateForm)
->setAddress($address)
->buildClient();
Thus, the modeled entity is always in a consistent state and can be flexibly and clearly constructed, regardless of the complexity of the created object and the number of parameters.
Creation of entity objects with the generation of an identifier by an auto-increment field of a database.
The designed class must have a unique identifier, as the main distinguishing feature of entities is individuality. An object can change significantly over time, so that none of its properties will be equal to what it was at the beginning. At the same time, all or most of the properties of an object may coincide with the properties of another object, but they will be different objects. It is a unique identifier that makes it possible to distinguish each object regardless of its current state [1].
There are various ways of generating identifiers, such as user input, generation in the application by any algorithm, can be generated by the database.
To generate, for example, a UUID or to query the database for the next sequence value is not difficult. But sometimes it becomes necessary to work with an existing database structure in which a table storing the corresponding data has an auto-increment field for the identifier.
In this case, it is extremely difficult and unreliable to obtain the next unique identifier without saving the entity, which means it is impossible to create an object in a completely consistent state.
And again, the Builder template will help us in this, which we can implement as follows.
namespace Infrastructure;
final class MySqlClientBuilder implements ClientBuilder
{
private $connection;
public function __construct(Connection $connection);
public function buildClient()
{
$this->connection
->insert('clients_table', [
$this->name,
$this->corporateForm,
$this->generalManager->getId(),
$this->address
]);
$id = $this->connection->lastInsertId();
return new Client(
$id,
$this->name,
$this->corporateForm,
$this->generalManager,
$this->address
);
}
}
Thus, we first add the corresponding record to the database, after which we get its identifier and create an object. The client does not need to know about this, and when changing the method of storing or generating identifiers, you only need to replace the implementation of the Builder in your dependency container.
$builder = $container->get(ClientBuilder::class);
$client = $builder->setId($id)
->setName($name)
->setGeneralManagerId($generalManager)
->setCorporateForm($corporateForm)
->setAddress($address)
->buildClient();
$repository->save($client);
$client->getId();
Thank you for attention!
PS:
Please do not judge too harshly, "Chukchi is not a writer, Chukchi is a reader." For more experienced developers, the described things may seem commonplace, but I didn’t come to the described immediately, but when I applied the described approach, it proved to be excellent.
My development experience is relatively small - four years, DDD used so far on only one project.
I would be grateful for the feedback and constructive comments.
References:
- Evans E., “Subject Oriented Design (DDD). Structuring complex software systems. ”
- Vernon V., “Implementation of subject-oriented design methods.”
- M. Fowler, Value Object
- M. Fowler, Constructor Initialization
- E. Gamma, R. Helm, R. Johnson, D. Vlissids, “Methods of object-oriented design. Design patterns. ”