This content originally appeared on DEV Community and was authored by Salah Elhossiny
Object to Object Mapping
Automatic object to object mapping is a useful approach to copy values from one object to another when two objects have same or similar properties.
DTO and Entity classes generally have same/similar properties and you typically need to create DTO objects from Entities.
ABP's object to object mapping system with AutoMapper integration makes these operations much easier comparing to manual mapping.
Use auto object mapping only for Entity to output DTO mappings.
Do not use auto object mapping for input DTO to Entity mappings.
There are some reasons why you should not use input DTO to Entity auto mapping;
An Entity class typically has a constructor that takes parameters and ensures valid object creation. Auto object mapping operation generally requires an empty constructor.
Most of the entity properties will have private setters and you should use methods to change these properties in a controlled way.
You typically need to carefully validate and process the user/client input rather than blindly mapping to the entity properties.
Example Use Cases
This section will demonstrate some example use cases and discuss alternative scenarios.
Entity Creation
Creating an object from an Entity / Aggregate Root class is the first step of the lifecycle of that entity.
The Aggregate / Aggregate Root Rules & Best Practices section suggests to create a primary constructor for the Entity class that guarantees to create a valid entity.
So, whenever we need to create an instance of that entity, we should always use that constructor.
See the Issue Aggregate Root class below:
Let's see an Application Service method that is used to create an issue:
CreateAsync method;
Uses the Issue constructor to create a valid issue. It passes the Id using the IGuidGenerator service. It doesn't use auto object mapping here.
If the client wants to assign this issue to a user on object creation, it uses the IssueManager to do it by allowing the IssueManager to perform the necessary checks before this assignment.
Saves the entity to the database
Finally uses the IObjectMapper to return an IssueDto that is automatically created by mapping from the new Issue entity.
Applying Domain Rules on Entity Creation
The example Issue entity has no business rule on entity creation, except some formal validations in the constructor.
However, there maybe scenarios where entity creation should check some extra business rules.
For example, assume that you don't want to allow to create an issue if there is already an issue with exactly the same Title.
Where to implement this rule? It is not proper to implement this rule in the Application Service, because it is a core business (domain) rule that should always be checked.
This rule should be implemented in a Domain Service, IssueManager in this case.
So, we need to force the Application Layer always to use the IssueManager to create a new Issue.
First, we can make the Issue constructor internal, instead of public:
This prevents Application Services to directly use the
constructor, so they will use the IssueManager.
Then we can add a CreateAsync method to the IssueManager:
CreateAsync method checks if there is already an issue with the same title and throws a business exception in this case.
If there is no duplication, it creates and returns a new Issue.
The IssueAppService is changed as shown below in order to use the IssueManager's CreateAsync method:
Updating / Manipulating An Entity
Once an entity is created, it is updated/manipulated by the use cases until it is deleted from the system.
There can be different types of the use cases directly or indirectly changes an entity.
In this section, we will discuss a typical update operation that changes multiple properties of an Issue.
This time, beginning from the Update DTO:
By comparing to IssueCreationDto, you see no RepositoryId.
Because, our system doesn't allow to move issues across repositories (think as GitHub repositories).
Only Title is required and the other properties are optional.
Let's see the Update implementation in the IssueAppService:
Domain Logic & Application Logic
Business Logic in the Domain Driven Design is split into two parts (layers): Domain Logic and Application Logic:
Domain Logic consists of the Core Domain Rules of the system while Application Logic implements application specific Use Cases.
While the definition is clear, the implementation may not be
easy. You may be undecided which code should stand in the
Application Layer, which code should be in the Domain Layer.
Multiple Application Layers
DDD helps to deal with complexity when your system is large. Especially, if there are multiple applications are being developed in a single domain, then the Domain Logic vs Application Logic separation becomes much more important.
Assume that you are building a system that has multiple
applications;
A Public Web Site Application, built with ASP.NET Core MVC, to show your products to users. Such a web site doesn't require authentication to see the products. The users login to the web site, only if they are performing some actions (like adding a product to the basket).
A Back Office Application, built with Angular UI (that uses REST APIs). This application used by office workers of the company to manage the system (like editing product descriptions).
A Mobile Application that has much simpler UI compared to the Public Web Site. It may communicate to the server via REST APIs or another technology (like TCP sockets).
Every application will have different requirements, different use cases (Application Service methods), different DTOs, different validation and authorization rules... etc.
Mixing all these logics into a single application layer makes your services contain too many if conditions with complicated
business logic makes your code harder to develop, maintain and test and leads to potential bugs.
If you've multiple applications with a single domain;
Create separate application layers for each application/client type and implement application specific business logic in these separate layers.
Use a single domain layer to share the core domain logic.
Such a design makes it even more important to distinguish between Domain logic and Application Logic.
To be more clear about the implementation, you can create different projects (.csproj) for each application types. For example:
IssueTracker.Admin.Application IssueTracker.Admin.Application.Contracts projects for the Back Office (admin) Application.
IssueTracker.Public.Application &
IssueTracker.Public.Application.Contracts projects for the Public Web Application.IssueTracker.Mobile.Application & IssueTracker.Mobile.Application.Contracts projects for the
Mobile Application
Example: Creating a new Organization in a Domain Service
Let's see the CreateAsync method step by step to discuss if the code part should be in the Domain Service, or not:
CORRECT: It first checks for duplicate organization name and and throws exception in this case. This is something related to core domain rule and we never allow duplicated names.
WRONG: Domain Services should not perform authorization. Authorization should be done in the Application Layer.
WRONG: It logs a message with including the Current User's UserName. Domain service should not be depend on the Current User. Domain Services should be usable even if there is no user in the system. Current User (Session) should be a Presentation/Application Layer related concept.
WRONG: It sends an email about this new organization creation. We think this is also a use case specific business logic. You may want to create different type of emails in different use cases or don't need to send emails in some cases.
Example: Creating a new Organization in an Application Service
Let's see the CreateAsync method step by step to discuss if the code part should be in the Application Service, or not:
CORRECT: Application Service methods should be unit of work (transactional). ABP's Unit Of Work system makes this automatic (even without need to add [UnitOfWork] attribute for the Application Services).
CORRECT: Authorization should be done in the application layer. Here, it is done by using the [Authorize] attribute.
CORRECT: Payment (an infrastructure service) is called to charge money for this operation (Creating an Organization is a paid service in our business).
CORRECT: Application Service method is responsible to save changes to the database.
CORRECT: We can send email as a notification to the system admin.
WRONG: Do not return entities from the Application Services. Return a DTO instead.
Example: CRUD Operations
This Application Service does nothing itself and delegates all the work to the Domain Service. It even passes the DTOs to the IssueManager.
Do not create Domain Service methods just for simple CRUD operations without any domain logic.
Never pass DTOs to or return DTOs from the Domain Services.
Application Services can directly work with repositories to query, create, update or delete data unless there are some domain logics should be performed during these operations.
In such cases, create Domain Service methods, but only for those really necessary.
Congratulations, thank you for reading all the series parts.
This content originally appeared on DEV Community and was authored by Salah Elhossiny
Salah Elhossiny | Sciencx (2021-08-16T09:27:44+00:00) Implementing Domain Driven Design: Part IV. Retrieved from https://www.scien.cx/2021/08/16/implementing-domain-driven-design-part-iv/
Please log in to upload a file.
There are no updates yet.
Click the Upload button above to add an update.