Domain Services | Domain Model | Domain Layer | Domain Driven Design
Domain Services
Creating Domain Services
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:
is derived from the ubiquitous language
is stateless
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.
To view a full listing of the tutorials in this series,
Domain Services
- Domain services encapsulate domain logic and concepts that are not naturally modeled as value objects or entities in your model.
- Domain services represent domain concepts; they are part of the UL.
- Domain services have no identity or state; their responsibility is to orchestrate business logic using entities and value objects.
- Too many domain services can lead to an anemic domain model that does not align well with the problem domain.
- Too few domain services can lead to logic being incorrectly located on entities or value objects. This causes distinct concepts to be mixed up, which reduces clarity.
- Domain services are also used as contracts, where the interface lives in the domain model, but the implementation does not.
Domain Service
When you use ubiquitous language, its nouns are always displayed as objects, while verbs have influence on behavior of these objects. However, often there are verbs or any other actions that can’t be attributed to any specific entity or value object. If there is a similar operation in the domain, it will be declared as domain service, which is quite different from application service that acts as a client. Services have the following features:
- A service-performed operation relates to the domain, which doesn’t belong to any of the existing entities
- The operation is carried on with specific objects of the domain’s model
- The operation doesn’t have a state.
You shouldn’t use services too often, though. If you rely on them frequently, it can result in anemic application domain model. The business logic should be divided between entities and values. Only if you can’t do it within ubiquitous language, you can use domain service. The important thing is that it needs to derive from ubiquitous language.
To demonstrate it, let’s have a look at a money transfer operation from one account to the other. Basically, you don’t know in which object you can store the transfer operation itself. Therefore, you can use a service. Like this:
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:
is derived from the ubiquitous language
is stateless
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.
To view a full listing of the tutorials in this series,
Services
We can define services as processes that perform certain tasks. Employees and evolved from Service Oriented Architecture or Remote Procedure Call . Generic tasks or actions that are not associated with a single determined single object instance, so the most common tendency is to create static methods on the entity or aggregate . This practice is not considered optimal because it does not follow the principles of development and greatly complicates testing, and it is considered bad practice to access repositories within the aggregates or entitiesin the domain model. The need to include static methods in the domain model is a good indicator to create a service .
Application Services
- Direct client of Domain Services and domain model.
- Use cases of the application that coordinates and orchestrates the requests to the business logic and repositories.
- Coordinates the responsibilities of the domain model and domain services.
- Hosted in Application Layer .
Domain Services
- Contains the logic / business rules .
- Transform one domain object to another.
- Calculate the value by entering objects from the domain model.
- They can access repositories.
- Hosted in Domain Layer .
In order to follow the development principles , we will declare interfaces for each Service.
The services are not a silver bullet , if we use them in excess extracting all the logic of application or domain in services, we can cause an Anemic Domain Model . It must be determined and correctly decided if an entity / aggregate method should be included in the domain model or create a service following Single Responsibility Principle at all times .
The services are not a silver bullet , if we use them in excess extracting all the logic of application or domain in services, we can cause an Anemic Domain Model . It must be determined and correctly decided if an entity / aggregate method should be included in the domain model or create a service following Single Responsibility Principle at all times .
In general, the implementations of the services are hosted in the infrastructure layer , although sometimes when we have a single implementation they can be hosted in the same layer where the interface is declared.
Sometimes, it just isn’t a thing.
Some concepts from the domain aren’t natural to model as objects. Forcing the required domain functionality to be the responsibility of an ENTITYor VALUE either distorts the definition of a model-based object or adds meaningless artificial objects.
Therefore:
When a significant process or transformation in the domain is not a natural responsibility of an ENTITY or VALUE OBJECT, add an operation to the model as a standalone interface declared as a SERVICE. Define the interface in terms of the language of the model and make sure the operation name is part of the UBIQUITOUS LANGUAGE. Make the SERVICE stateless.
No comments:
Post a Comment