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.