DEV Community

Nacho Colomina Torregrosa
Nacho Colomina Torregrosa

Posted on • Edited on

Creating focused domain applications. A Symfony approach (Saving the entity)

Introduction

In this third post of this series, we are going to create an entity ready to be persisted to the database from the DTO we created in the first article of the series.

Transforming the UserInputDTO into an entity

To start with this section, let's assume we are using doctrine to communicate with the database and our User entity looks like this:

#[ORM\Entity(repositoryClass: UserRepository::class)]
class User
{
    #[ORM\Id]
    #[ORM\GeneratedValue]
    #[ORM\Column]
    private ?int $id = null;

    #[ORM\Column(length: 150)]
    private string $firstname;

    #[ORM\Column(length: 255)]
    private string $lastname;

    #[ORM\Column(length: 25)]
    private string $dob;

    #[ORM\Column]
    private \DateTimeImmutable $createdAt;

    #[ORM\Column]
    private string $token;

    // getters and setters
}
Enter fullscreen mode Exit fullscreen mode

The fields email, firstname, lastname and dob will be filled with the UserInputDTO values and the createdAt and token fields will be filled following the next rules:

  • The createdAt field will hold the current date.
  • The token field will hold a string of 50 alphanumeric characters.

As we have decided how we are going to fill the entity fields, this part of the code will belong to the domain since it contains domain business rules. So, we need a service domain to do the stuff. Let's code it.

class UserEntityBuilder {

    public function buildEntity(UserInputDTO $userInputDto): User
    {
        $user = new User();
        $user->setEmail($userInputDto->email);
        $user->setFirstname($userInputDto->firstname);
        $user->setLastname($userInputDto->lastname);
        $user->setDob($userInputDto->dob);
        $user->setToken(bin2hex(random_bytes(50)));
        $user->setCreatedAt(new \DateTimeImmutable());

        return $user;
    }
}
Enter fullscreen mode Exit fullscreen mode

As you can see, the UserEntityBuilder buildEntity method creates the User entity following the pre-established rules and returns the entity.

An application service to create and save the entity

Now, we need a service which will be in charge of coordinating the processes involved in saving the entity:

  • Creating the User entity by using our recently created UserEntityBuilder domain service.
  • Using the doctrine EntityManager service to persist the entity on the database. The EntityManager service should be considered as an infrastructure service since it is provided by the Symfony framework through its symfony/orm-pack component.

Let's code it.

class UserCreator {

    public function __construct(
        private readonly UserEntityBuilder $userEntityBuilder,
        private readonly EntityManagerInterface $em,
    ){}

    public function createUser(UserInputDTO $userInputDto): object
    {
        $user = $this->userEntityBuilder->buildEntity($userInputDto);
        $this->em->persist($user);
        $this->em->flush();

        return '.....'; // Return a DTO ready to be used by the presentation layer
    }
}
Enter fullscreen mode Exit fullscreen mode

As you can see in the code above, the UserCreator application layer service uses first the UserEntityBuilder to create the entity and then uses the Doctrine entity manager to save it to the database.

You may have noticed that the "return" line is not complete. ¿ What should we return here?. We will see it in the next and last article of this series :)

What about the entities. Do they belong to the domain ?

This is a good question. In my opinion, they would belong to our domain since, although they represent a mapping of database tables to objects in our application, they encapsulate our decisions regarding the project's data model.
Now, the fact that they can belong to our domain does not mean that they can be used as DTO's. They should be isolated and only be used for saving data to the database and receiving data from there.

Conclusion

In this third article, we have created a domain service to create a User entity ready to be persisted to the database and also have created an application service which saves the entity to the database by using the domain service to create the user and the doctrine entity manager to save it.
In the next and last article, we will learn hot to create an output DTO with the saved user information ready to be returned to the presentation layer.

If you like my content and enjoy reading it and you are interested in learning more about PHP, you can read my ebook about how to create an operation-oriented API using PHP and the Symfony Framework. You can find it here: Building an Operation-Oriented Api using PHP and the Symfony Framework: A step-by-step guide

Top comments (3)

Collapse
 
icolomina profile image
Nacho Colomina Torregrosa

Hey David, I don't entirely agree.
It is true that having the entity as part of your domain may cause more coupling, but the user entity is, in fact, only an object with getters and setters if you remove the doctrine attributes. So, if you would change your persistence layer and would decide to use SQL queries for example, you could continue using the user entity as part of your domain.
Then, in the application service you would remove the doctrine entity manager and you would inject your new service related to your new way of persisting data. This service could receive the user object as a parameter so that it can use its data to perform the insert.

Collapse
 
xwero profile image
david duymelinck • Edited

You don't connect the harddisk to the CPU. You connect both to the motherboard. And the motherboard lets both things communicate with each other. The CPU in this analogy is the domain. The entity is the storage manager of the harddisk.
If you change the harddisk to an SSD there is going to be another storage manager. But you don't need to change the CPU, because the motherboard is in charge of the communication.
In code the communication is done by DTO's.

Is there a flaw in the way I see the parts of a domain driven application?

I didn't want to react to the code example, because it adds no value to the conversation.

Collapse
 
xwero profile image
david duymelinck

I think it is a mistake to add an ORM entity to your domain.

What if you change the ORM package or you decide to use sql queries, or use a different database or even a different data provider?

I think most domain code should not be aware if the data needs to be saved or not. What is the benefit for domain code to control the final outcome of the data?

I think the code in this post is not needed to make an application domain driven. You just need code in your application to transform the data it has to the format that is required by the domain code. Do that the way it makes most sense in your application.
This prevents the problem of needing to refactor ORM/database/... code in your domain code. And it is also less likely that ORM/database/... specific language is going to pollute the domain language.