Even if the Aggregate Services pattern is not a magic problem-solving pattern, it is a viable alternative to injecting too many dependencies into another class. Its goal is to aggregate many dependencies in a class to reduce the number of injected services in other classes, grouping dependencies together. The way to manage aggregates would be to group them by concern or responsibility. Putting a bunch of services in another service just for the sake of it is rarely the way to go; aim for cohesion.
Creating one or more aggregation services that expose other services can be a way to implement service discovery in a project. Like always, analyze if the problem is not elsewhere first. Loading a service that exposes other services can be handy. However, this may create issues, so don’t put everything into an aggregate firsthand either.
Here is an example of a mapping aggregate to reduce the number of dependencies of a Create-Read-Update-Delete (CRUD) controller that allows the creation, updating, deletion, and reading of one, many, or all products. Here’s the aggregate service code and a usage example:
public interface IProductMappers
{
IMapper<Product, ProductDetails> EntityToDto { get; }
IMapper<InsertProduct, Product> InsertDtoToEntity { get; }
IMapper<UpdateProduct, Product> UpdateDtoToEntity { get; }
}
public class ProductMappers : IProductMappers
{
public ProductMappers(IMapper<Product, ProductDetails> entityToDto, IMapper<InsertProduct, Product> insertDtoToEntity, IMapper<UpdateProduct, Product> updateDtoToEntity)
{
EntityToDto = entityToDto ??
throw new ArgumentNullException(nameof(entityToDto));
InsertDtoToEntity = insertDtoToEntity ??
throw new ArgumentNullException(nameof(insertDtoToEntity));
UpdateDtoToEntity = updateDtoToEntity ??
throw new ArgumentNullException(nameof(updateDtoToEntity));
}
public IMapper<Product, ProductDetails> EntityToDto { get; }
public IMapper<InsertProduct, Product> InsertDtoToEntity { get; }
public IMapper<UpdateProduct, Product> UpdateDtoToEntity { get; }
}
public class ProductsController : ControllerBase
{
private readonly IProductMappers _mapper;
// Constructor injection, other methods, routing attributes, …
public ProductDetails GetProductById(int id)
{
Product product = …; // Fetch a product by id
ProductDetails dto = _mapper.EntityToDto.Map(product);
return dto;
}
}
The IProductMappers aggregate could be logical in this example as it groups the mappers used by the ProductsController class under its umbrella. It is responsible for mapping ProductsController-related domain objects to DTOs and vice versa while the controller gives up this responsibility.You can create aggregate services with anything, not just mappers. That’s a fairly common pattern in DI-heavy applications (which can also point to some design flaws).Now that we’ve explored the Aggregate Services pattern, let’s explore how to make a mapping façade instead.
Leave a Reply