Services :
when there are operations that need to be represented, but Entities and Value Objects aren't the best place, you should consider modeling these operations as Services. In Domain-Driven Design, there are typically three different types of Services you'll encounter:
Application Services: Operate on scalar types, transforming them into Domain types. A scalar type can be considered any type that's unknown to the Domain Model. This includes primitive types and types that don't belong to the Domain.
Infrastructure Services: Are operations that fulfill infrastructure concerns, such as sending emails and logging meaningful data. In terms of Hexagonal Architecture, they live outside the Domain boundary.
Application
Services
Application Services are the middleware between the outside world and the Domain logic. The purpose of such a mechanism is to transform commands from the outside world into meaningful Domain instructions.
Let's consider the User signs up to our platform use case. Starting with an outside-in approach: from the delivery mechanism, we need to compose the input request for our Domain operation. Using a framework like Symfony as the delivery mechanism, the code would look something like this:
class
SignUpController extends Controller
{
public function signUpAction(Request
$request)
{
$signUpService = new
SignUpUserService(
$this->get('user_repository')
);
try {
$response =
$signUpService->execute(new SignUpUserRequest(
$request->request->get('email'),
$request->request->get('password')
));
} catch (UserAlreadyExistsException
$e) {
return
$this->render('error.html.twig', $response);
}
return
$this->render('success.html.twig', $response);
}
}
As you can see, we create a new instance of our Application Services, passing all dependencies needed — in this case, a UserRepository. UserRepository is an interface that can be implemented with any specific technology (Example: MySQL, Redis, Elasticsearch). Then, we build a request object for our Application Service in order to abstract the delivery mechanism — in this example, a web request — from the business logic. Last, we execute the Application Service, get the response, and use that response for rendering the result. On the Domain side, let's check a possible implementation for the Application Service that coordinates the logic that fulfills the User signs up use case:
class
SignUpUserService
{
private
$userRepository;
public function
__construct(UserRepository $userRepository)
{
$this->userRepository
= $userRepository;
}
public function
execute(SignUpUserRequest $request)
{
$user =
$this->userRepository->userOfEmail($request->email);
if ($user) {
throw new
UserAlreadyExistsException();
}
$user = new User(
$this->userRepository->nextIdentity(),
$request->email,
$request->password
);
$this->userRepository->add($user);
return new
SignUpUserResponse($user);
}
}
Everything in the code is about the Domain problem we want to solve, and not about the specific technology we're using to solve it. With this approach, we can decouple the high level policies from the low-level implementation details. The communication between the delivery mechanism and the Domain is carried by data structures called DTOs.
Domain
Services
Throughout conversations with Domain Experts, you'll come across concepts in the Ubiquitous Language that can't be neatly represented as either an Entity or a Value Object, such as: Users being able to sign into systems by themselves A shopping cart being able to become an order by itself The preceding example are two concrete concepts, neither of which can naturally be bound to either an Entity or a Value Object. Further highlighting this oddity, we can attempt to model the behavior as follows:
class User
{
public function
signUp($aUsername, $aPassword)
{
// ...
}
}
class Cart
{
public function
createOrder()
{
// ...
}
}
In the case of the first implementation, we're not able to know that the given username and password relate to the invoked-upon user instance. Clearly, this operation doesn't suit this Entity; instead, it should be extracted out into a separate class, making its intention explicit. With this in mind, we could create a Domain Service with the sole responsibility of authenticating users:
class SignUp
{
public function
execute($aUsername, $aPassword)
{
// ...
}
}
Similarly, as in the case of the second example, we could create a Domain Service specialized in creating orders from a supplied cart:
class
CreateOrderFromCart
{
public function
execute(Cart $aCart)
{
// ...
}
}
A Domain Service can be defined as an operation that fulfills a Domain task and naturally doesn't fit into either an Entity or a Value Object. As concepts that represent operations in the Domain, Domain Services should be used by clients regardless of their run history.
Domain Services don't hold any kind of state by themselves, so Domain Services are stateless operations.
Domain
Services and Infrastructure Services
It's common to encounter infrastructural dependencies when modeling a Domain Service
— for example, in the case where an authentication mechanism that handles password hashing is required. In this instance, you could use a Separated Interface, which allows for multiple hashing mechanisms to be defined. Using this pattern still provides you with a clear Separation of Concerns between the Domain and the Infrastructure:
namespace
Ddd\Auth\Domain\Model;
interface SignUp
{
public function
execute($aUsername, $aPassword);
}
Using the preceding interface found in the Domain, we could create an implementation in the Infrastructure layer, like the following:
namespace
Ddd\Auth\Infrastructure\Authentication;
class
DefaultHashingSignUp implements Ddd\Auth\Domain\Model\SignUp
{
private
$userRepository;
public function
__construct(UserRepository $userRepository)
{
$this->userRepository
= $userRepository;
}
public function
execute($aUsername, $aPassword)
{
if
(!$this->userRepository->has($aUsername)) {
throw
UserDoesNotExistException::fromUsername($aUsername);
}
$aUser =
$this->userRepository->byUsername($aUsername);
if
(!$this->isPasswordValidForUser($aUser, $aPassword)) {
throw new
BadCredentialsException($aUser, $aPassword);
}
return $aUser;
}
private function
isPasswordValidForUser(
User $aUser,
$anUnencryptedPassword
) {
}
}
Here
is another implementation based instead on the MD5 algorithm:
namespace
Ddd\Auth\Infrastructure\Authentication;
use
Ddd\Auth\Domain\Model\SignUp
class
Md5HashingSignUp implements SignUp
{
const SALT =
'S0m3S4lT' ;
private
$userRepository;
public function
__construct(UserRepository $userRepository)
{
$this->userRepository
= $userRepository;
}
public function
execute($aUsername, $aPassword)
{
if
(!$this->userRepository->has($aUsername)) {
throw new
InvalidArgumentException(
sprintf('The user
"%s" does not exist.', $aUsername)
);
}
$aUser =
$this->userRepository->byUsername($aUsername);
if
($this->isPasswordInvalidFor($aUser, $aPassword)) {
throw new
BadCredentialsException($aUser, $aPassword);
}
return $aUser;
}
private function
salt()
{
return
md5(self::SALT);
}
private function
isPasswordInvalidFor(
User $aUser,
$anUnencryptedPassword
) {
$encryptedPassword =
md5(
$anUnencryptedPassword . '_'
.$this->salt()
);
return
$aUser->hash() !== $encryptedPassword;
}
}
Opting for this choice allows us to have multiple implementations of the Domain Service interface at the Infrastructure layer. In other words, we end up with several Infrastructure Domain Services. Each Infrastructure service will be responsible for handling a different hash mechanism. Depending on the implementation, the use can easily be managed through a Dependency Injection container — for example, through Symfony's Dependency Injection component:
<?xml
version="1.0"?>
<container
xmlns="http://symfony.com/schema/dic/services"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
http://symfony.com/schema/dic/services
http://symfony.com/schema/dic/services/services-1.0.xsd">
<services>
<service
id="sign_in" alias="sign_in.default" />
<service
id="sign_in.default"
class="Ddd\Auth\Infrastructure\Authentication
\DefaultHashingSignUp">
<argument
type="service" id="user_repository"/>
</service>
<service
id="sign_in.md5"
class="Ddd\Auth\Infrastructure\Authentication
\Md5HashingSignUp">
<argument
type="service" id="user_repository"/>
</service>
</services>
</container>
0 Comments:
Post a Comment
If you have any doubts . Please let me know.