Domain Services¶
- Do create an domain service for each aggregate root.
Domain Service Interface¶
- Do define an
interface
for each aggregate in the domain layer. - Do inherit from the
IDomainService
interface. - Do use the
Manager
postfix for the interface name (ex:IProjectManager
). - Do order methods according to C / R / U / D
- Do create or return entities.
- Do not avoid to generate domain services for only create, read, update and delete operations.
public interface IProjectManager : IDomainService
{
Task<Project> CreateAsync(
string name,
string description,
Guid baseModelId,
Priority priority,
DateTime startDate
);
}
Domain Service Implementation¶
The core business logic is generally implemented in aggregates (entities) and the Domain Services. Creating a Domain Service is especially needed when;
-
You implement a core domain logic that depends on some services (like repositories or other external services).
-
The logic you need to implement is related to more than one aggregate/entity, so it doesn't properly fit in any of the aggregates.
-
Do use
virtual
access modifier business rules in domain layer.
Creating A New Entity¶
- Do use the
CreateAsync
method name. - Do not have id parameter at
CreateAsync
method.- Exception: If it is desired to establish a correlation with an external entity as per the business rule, id can be added as a parameter.
- Do get and return the domain objects(entities, value objects)
- Do use domain services at application services
- Do not inject base services like ILogger and IGuidGenerator.
- Do not implement the use cases of the application (user interactions in a typical web application).
public class ProjectManager : DomainService, IProjectManager
{
protected IProjectRepository ProjectRepository { get; }
protected IBaseModelRepository BaseModelRepository { get; }
public ProjectManager(
IProjectRepository projectRepository,
IBaseModelRepository baseModelRepository)
{
ProjectRepository = projectRepository;
BaseModelRepository = baseModelRepository;
}
public virtual async Task<Project> CreateAsync(
string name,
string description,
Guid baseModelId, // This is required value while creating the project object.
Priority priority,
DateTime startDate)
{
ProjectValidator.CheckEmptyGuid(baseModelId);
ProjectValidator.CheckEdges(edges);
var baseModel = await BaseModelRepository.FindAsync( // related with another aggregate named `BaseModel`
id: baseModelId,
includeDetails: false
);
if (baseModel == null)
{
throw new BaseModelDoesNotExistException();
}
var project = new Project(
id: GuidGenerator.Create(),
name: name,
description: description,
baseModelId: baseModelId,
priority: priority,
startDate: startDate,
tenantId: CurrentTenant.Id
);
return project;
}
- Do use the
Check{EntityName}Async
method name for existance checks.
protected virtual async Task CheckBaseModelAsync(
Guid modelId,
[NotNull] string name,
[NotNull] string providerName)
{
using (DataFilter.Disable<IRequireOrganizationUnitAccessControl>())
{
if (await BaseModelRepository.ExistsAsync(modelId: modelId, name: name, providerName: providerName))
{
throw new BaseModelExistException();
}
}
}
Updating An Existing Entity¶
- Do not created dedicated the
UpdateAsync
method, most of the cases this should be handled at application layer.
Business Methods¶
- Do use entity always as first parameter.
- Do make only the described change in method name
Example method:
public virtual async Task<Project> LinkPredecessorAsync(
[NotNull] Project project,
List<Guid> predecessorIds)
{
foreach (var predecessorId in predecessorIds)
{
// Predecessor project should be part of existing the project
if (!(project.SuccessorIds.Contains(predecessorId) & (project.PredecessorIds.Contains(predecessorId))))
{
var predecessorProject = await GetProjectInternalAsync(predecessorId);
if (predecessorProject.BaseModelId == project.BaseModelId)
{
project.AddPredecessorRecord(predecessorId); // Rich entity has logic to handle add operation of predecessorId
}
}
}
return project;
}
Deleting An Existing Entity¶
- Do not create dedicated
DeleteAsync
method, most of the cases this should be handled at application layer.
Distributed Caching¶
- Do use the
CacheItem
postfix for the class name (ex:ProjectCacheItem
). - Do inherit from the
ExtensibleObject
class if entity is derived fromAggregateRoot
. - Do decorate with [Serializable] attribute.
[Serializable]
public class ProjectCacheItem : ExtensibleObject
{
public string Name { get; set; }
public string Description { get; set; }
public Guid BaseModelId { get; set; }
public Priority Priority { get; set; }
public string CurrentState { get; set; }
public DateTime StartDate { get; set; }
}