cult3

Creating Domain Services

Sep 29, 2014

Table of contents:

  1. What is a Service?
  2. Different types of Service
  3. Avoid the Anaemic Model problem
  4. Deciding on a Domain Service
  5. Does the Domain Service belong in the Domain Model?
  6. Writing the Hashing Service interface
  7. Writing the Hashing Service implementation
  8. Tests
  9. Conclusion

An important concept in Domain Driven Design is the modelling of domain logic in Entities and Value Objects.

As we’ve seen over the last couple of weeks, we can use Value Objects and Entities to model and protect the business logic of our applications.

However not every action or piece of functionality will fit neatly into this theoretical workflow. Inevitably we will have a requirement that doesn’t quite belong as a method on any of the existing Entities or Value Objects.

This is where Domain Services come in. A Domain Service is taken directly from the Ubiquitous Language, but doesn’t naturally fit on any existing Domain Object.

In today’s article we are going to be looking at exactly what Domain Services are, when to use them, when not to use them, and how to implement them in your applications.

What is a Service?

Service is a very loaded term in computer programming that has all sorts of different meanings and connotations depending on the context you are using it in.

A Service in Domain Driven Design is simply a stateless object that performs an action.

For example, an AuthenticationService would have the sole responsibility for authenticating users into your application.

An important characteristic of a Service is that it should not have state. So the AuthenticationService should be used to perform an action such as authenticate() but it should not hold any references to stateful data such as the current authenticated users.

Different types of Service

The architecture of a Domain Driven Design project is split into three different layers.

The Application layer is how the outside world communicates with the model. This could be through HTTP requests, an API or through an automated messaging service.

The Infrastructure layer is how the actions of the model are executed. This could include persisting data to a database, queuing jobs, or sending email notifications.

And finally the Domain layer is where the business logic of the application resides. As we’ve seen over the last couple of weeks, this is where you have Domain Objects such as your User Entity or Email and Username Value Objects.

Each of these different layers also require Service objects and so we have three different types of services for the Application, Infrastructure and Domain layers.

An Application Service is typically used to orchestrate how the outside world interacts with your application. For example AuthenticationService would be an Application Service that co-ordinates how a user should be authenticated.

An Infrastructure Service is used for dealing with the technical details of the infrastructure. For example you might have a MailGunMessenger service that enables you to send email notifications using the MailGun API.

And finally a Domain Service is used for encapsulating domain logic that does not naturally fit on any existing Domain Object. For example you might have a RegisterUserService that co-ordinates how a user is registered within your application.

Whilst the differences between these three types of service seems pretty straightforward, it’s extremely important that the division of responsibility does not bleed into the wrong area of concern.

Avoid the Anaemic Model problem

Domain Services are a great way to model a particular aspect of your business logic. However over-use of Domain Services leaves you vulnerable to the Anaemic Model problem.

The Anaemic Model problem is where Domain Objects are void of domain logic because it has been abstracted to a service class.

For example, you might have a User entity that is basically just an empty class with getters and setters and a RegisterUser service class that actually registers the user within the application.

The domain logic of registering a user has been robbed from the natural home of the User entity and put into a service.

In essence the problem with anaemic domain models is that they incur all of the costs of a domain model, without yielding any of the benefits.

It is therefore extremely important that when modelling a particular bit of domain logic, you scrutinise whether it belongs on a Domain Object or as a separate Domain Service.

More often than not, you probably don’t need a Domain Service. You only need a Domain Service if the piece of domain logic you are modelling does not naturally fit on any Domain Object. A common scenario for this is when the responsibility of the action does not naturally fit on any particular object or if it requires multiple Domain Objects in order to co-ordinate the action.

Deciding on a Domain Service

An important bit of functionality that we are going to need whilst registering users is the ability to hash passwords. Storing plaintext passwords in the database would be a massive security concern, and so our business logic requires us to ensure that passwords are hashed before they are persisted.

Password hashing is a classic example of a Domain Service, but we will walk through the steps to determine why this is so.

Firstly, our domain explicitly states that passwords should be hashed before they are persisted to the database. Our business rules care about the security of passwords, and so “hashing passwords” comes directly from our ubiquitous language.

Secondly, it’s clear that the functionality of hashing a password is stateless. If you think about hashing as an input / output operation, no matter what string you put in, you will always get a hashed password out. The service itself does not store a copy of those passwords and so it is stateless.

Thirdly, a User entity requires a password to be hashed, but it is not the responsibility of the User to hash the password. The User entity only cares about accepting a hashed password, it is not concerned about how the password is actually hashed.

We therefore have a piece of business logic that:

  1. is derived from the ubiquitous language
  2. is stateless
  3. is not the responsibility of any existing domain object

I would say that this is therefore an excellent candidate for a Domain Service.

Does the Domain Service belong in the Domain Model?

We’ve established that hashing a password is indeed a Domain Service, but does the actual implementation of hashing a password belong in the Domain Model?

A Domain Service is derived from the Ubiquitous Language and it encapsulates business logic, so there is no doubting that it belongs within the Domain Model.

However, Domain Services often use functionality that really does not belong in the Domain.

In the case of hashing a password, our business logic dictates that this is important, but how the password is actually hashed is not the concern of the Domain.

Therefore we should not have the actual implementation of hashing a password inside of the Domain.

So should this service be in the Domain or not?

To model this functionality correctly we need to define a HashingService interface within the Domain Model and an implementation that sits within the Infrastructure layer of our application.

This provides the clean separation of the domain requirement from the specific implementation, and it allows us to easily switch the hashing algorithm we use by simply using a different implementation.

Writing the Hashing Service interface

The first thing we need to do is to write the HashingService interface that is going to sit within our Domain Model:

<?php namespace Cribbb\Domain\Services\Identity;

interface HashingService
{
    /**
     * Create a new hashed password
     *
     * @param Password $password
     * @return HashedPassword
     */
    public function hash(Password $password);
}

Note: I’ve renamed the Users namespace to the more descriptive Identity namespace.

I’ve placed this class in a new Services namespace that sits under the Domain namespace. This is simply for code organisation.

So as you can see the HashingService has a single hash() method that accepts an instance of the Password Value Object.

You will also notice that the return value should be an instance of HashedPassword. This is another Value Object that will allow me to type hint to ensure that we only accept hashed passwords. I won’t cover the implementation of the HashedPassword class because it is basically exactly the same code that we already covered in Encapsulating your application’s business rules.

Writing the Hashing Service implementation

Next we can write the concrete class that will implement the HashingService interface.

Create a new file under Infrastructure/Services called BcryptHashingService.php and copy the following code:

<?php namespace Cribbb\Infrastructure\Services;

use Illuminate\Hashing\BcryptHasher;
use Cribbb\Domain\Model\Identity\Password;
use Cribbb\Domain\Model\Identity\HashedPassword;
use Cribbb\Domain\Service\Identity\HashingService;

class BcryptHashingService implements HashingService
{
    /**
     * @var Illuminate\Hashing\BcryptHasher
     */
    private $hasher;

    /**
     * Create a new BcryptHashingService
     *
     * @param BcryptHasher $hasher
     * @return void
     */
    public function __construct(BcryptHasher $hasher)
    {
        $this->hasher = $hasher;
    }

    /**
     * Create a new HashedPassword
     *
     * @param Password $password
     * @return HashedPassword
     */
    public function hash(Password $password)
    {
        return new HashedPassword($this->hasher->make((string) $password));
    }
}

As you can see I’m injecting an instance of Illuminate\Hashing\BcryptHasher as the hashing library. I’m also implementing the hash() method to accept an instance of Password and return an instance of HashedPassword.

You could very easily replace this class with a different implementation that used a different hashing algorithm.

Tests

The test for this service is very simple because we only need to ensure that an instance of HashedPassword is returned:

<?php namespace Cribbb\Infrastructure\Services;

use Illuminate\Hashing\BcryptHasher;
use Cribbb\Domain\Model\Identity\Password;

class BcryptHashingServiceTest extends \PHPUnit_Framework_TestCase
{
    /** @test */
    public function should_make_new_hashed_password_instance()
    {
        $service = new BcryptHashingService(new BcryptHasher());

        $hashed = $service->hash(new Password("my_super_secret_password"));

        $this->assertInstanceof(
            "Cribbb\Domain\Model\Identity\HashedPassword",
            $hashed
        );
    }
}

Conclusion

Domain Services are a very important component of Domain Driven Design as they allow you to co-ordinate Domain Objects and protect Entities and Value Objects from assuming too much responsibility.

However it is extremely important that you refrain from over-using Domain Services. A Domain Service seems like a neat solution to a problem, but you can very quickly find yourself in a situation where you have robbed the domain logic from the very objects that need it most.

Services as a whole are also very important to Domain Driven Design. As I touched upon in this article, we have services for each layer of our application. In the coming weeks we will be taking a deeper look into writing more of these services to build out the functionality of Cribbb.

This is a series of posts on building an entire Open Source application called Cribbb. All of the tutorials will be free to web, and all of the code is available on GitHub.

Philip Brown

@philipbrown

© Yellow Flag Ltd 2024.