Mateusz Babski
Mateusz Babski

Reputation: 11

Retrieving part of aggregate root from database - DDD approach C#, .NET, Entity Framework

At first I'd like to describe my "problem". Its more like concern than a problem cause I saw also articles that its recommended to retrieve all aggregate root's data from db and then show to user using dtos.

Although there are bunch of articles that are concerned about optimization queries etc. And one of those points focus on retrieving only data that you need to use later/show to user.

Using a DDD approach, my aggregate root which is called Shop has internal constructor (and second private for EF purposes). Entity creation is done by static factory method.

One of controller methods returns list of shops - the only fields I need are id, shopname and address, but using conventional data retrieving method, I fetch the whole entity and then I map it to dto with only those required fields.

Repository interface (IShopRepository) is placed in the domain layer that has no access to any other around that module.

Implementation of that interface is of course in infrastructure layer and DTOs with handler in application layer.

My question is how to fetch only those 3 fields from the database without breaking architecture and DDD approach? I do not want make another layer of abstraction by creating in domain layer something like ShopPartialDto class, I do not also move interface to the application layer.

It's also impossible in the repository to use

Select/SelectMany(x => new Shop(x.Id, x.ShopName, x.Address).ToList() 

due to

This is how my code looks like:

Shop.cs:

public class Shop : Entity, IAggregateRoot
{
    public ShopId Id { get; private set; }
    public Email Email { get; private set; }
    public PasswordHash PasswordHash { get; private set; }
    public Name OwnerName { get; private set; }
    public LastName OwnerLastName { get; private set; }
    public ShopName ShopName { get; private set; }
    public Address ShopAddress { get; private set; }
    public TaxNumber TaxNumber { get; private set; }
    public TelephoneNumber ContactNumber { get; private set; }
    public Roles Role { get; private set; } = Roles.shop;

    private Shop() { }

    internal Shop(Email email,
                  PasswordHash passwordHash,
                  Name ownerName,
                  LastName ownerLastName,
                  ShopName shopName,
                  Address shopAddress,
                  TaxNumber taxNumber,
                  TelephoneNumber contactNumber)
    {
        Id = new ShopId(Guid.NewGuid());
        Email = email;
        PasswordHash = passwordHash;
        OwnerName = ownerName;
        OwnerLastName = ownerLastName;
        ShopName = shopName;
        ShopAddress = shopAddress;
        TaxNumber = taxNumber;
        ContactNumber = contactNumber;
        Role = Roles.shop;
    }

    public static Shop Create(Email email,
                              PasswordHash passwordHash,
                              Name ownerName,
                              LastName ownerLastName,
                              ShopName shopName,
                              Address shopAddress,
                              TaxNumber taxNumber,
                              TelephoneNumber contactNumber)
    {
        var shop = new Shop(email,
                        passwordHash,
                        ownerName,
                        ownerLastName,
                        shopName,
                        shopAddress,
                        taxNumber,
                        contactNumber);
        shop.AddDomainEvent(new ShopCreatedDomainEvent(shop));

        return shop;
    }
}

Repository

public async Task<IEnumerable<Shop>> GetAllShops()
{
    return await _dbContext.Shops.ToListAsync();
}

ShopDto

public record ShopDto
{
    public Guid ShopId { get; init; }
    public string ShopName { get; init; }
    public Address ShopAddress { get; init; }

    internal static IEnumerable<ShopDto> CreateDtoFromObject(List<Shop> shops)
    {
        var shopList = new List<ShopDto>();

        foreach(var shop in shops)
        {
            var shopDto = new ShopDto()
            {
                ShopId = shop.Id,
                ShopName = shop.ShopName,
                ShopAddress = shop.ShopAddress
            };

            shopList.Add(shopDto);
        }

        return shopList;
    }
}

Is there any good way that can optimize this query without breaking the architecture?

Upvotes: 0

Views: 744

Answers (2)

Ashish Kumar Jaryal
Ashish Kumar Jaryal

Reputation: 822

One way to solve this to an extent might be use of an anonymous type in repository select statement i.e., Projection. I would love to hear more alternatives or thoughts on it.

We may define one more method(GetShopsNameWithAddress) in the IShopRepository to satisfy specific use case.

Task<IEnumerable<dynamic>> GetShopsNameWithAddress(...); 

Implement GetShopsNameWithAddress in the ShopRepository:

public async Task<IEnumerable<dynamic>> GetShopsNameWithAddress()
{
      /* This code sample is only for reference.
         Return type will match to the ShopDto with its own risk of future maintenance
         If feasible, we may replace select type anonymous & return type dynamic 
         with more specific type ShopDto!! 
         If not feasible, then dynamic type will do the job! */
     return await _dbContext.Shops.AsNoTracking()
         .Select/SelectMany(x => new (x.Id, x.ShopName, 
            x.Address)).ToListAsync();
}

Upvotes: 0

Louis
Louis

Reputation: 1241

You need to implement Contexts as well. If you try to apply DDD and use Aggregate Root, you will run into the problem of "over querying". There are two ways that this can be solved:

  1. Focus on the Ubiquitous Language (UL) in the specific feature you are building.

    In your specific example, what you are trying to display is actually the Shops Address (that's its own AggregateRoot). You would pull this Address using a Shop Address Repository. The "Shops" pulled by this repository is different from the Shops object that you would pull if you needed, for example, to list all the items in a Shop. So, you simply need to be careful when you define your Entities and Aggregates. You are defining the Shop Aggregate too broadly. Use your UL to "focus" it.

  2. Bounded Contexts

    This is similar to #1 above, but it's on a larger scale. For example, you application might need to account for Shop Customers and Shop Employees. There'll be a slew of functionality for each entity (for example: Employees can add Shop Products while Customers can buy). You don't want the same Shop to be used in all of those functionalities (that'll be a terrible idea). What you want instead is to define a bounded context around the Features. For example, A shop in an OrderContext will contain Products and Orders in the Shop (this Aggregate might wrap Customers). A Shop in a ManagementContext will have employees, size, and other properties necessary for managing the Store (this aggregate might wrap Employees). https://martinfowler.com/bliki/BoundedContext.html

Upvotes: 1

Related Questions