1

I have created a small ERP application and the architecture I chose included the Repository pattern and the anemic Domain model and Business logic layer. I was reading Microsoft's guide about microservices here:

https://learn.microsoft.com/en-us/dotnet/architecture/microservices/microservice-ddd-cqrs-patterns/net-core-microservice-domain-model

and I saw some examples like this

https://github.com/dotnet-architecture/eShopOnContainers/blob/main/src/Services/Ordering/Ordering.API/Application/Commands/ShipOrderCommandHandler.cs

and others on various sites. My understanding is that anemic model and BusinessLayer in large systems is an antipattern and logic should reside in the Domain model. Though the model should not (and cannot) query the Database but rather all needed information should be provider as parameters and queried at the CommandHandler (if I m not mistaken). But in some cases the data I need to fetch from the database is needed conditionally. Take this example for instance

public void CalculateDiscount(CommercialDocumentEntry enrty,CommercialDocumentEntryLine line)
{
  var customer = RepositoryProvider.CustomerRepositrory.FindbyId(enrty.BillingTraderId);
  if (customer.ZoneId.HasValue)
  {
    var zone = RepositoryProvider.ZoneRepositrory.FindbyId(customer.ZoneId.Value);
    line.TDiscountItemPercent = zone.Discount;
  }
  else if (line.Item.PriceListId.HasValue)
  {
    var priceList = RepositoryProvider.PriceListRepositror.FindbyId(PriceListId.Value);
    line.TDiscountItemPercent = priceList.Discount;
  }
  else
  {
    var finData= RepositoryProvider.TraderFinancialDataRepository.FindbyId(enrty.BillingTraderId);
    if (finData.TurnOver > 1000)
    {
      var globalDicount = RepositoryProvider.GlobalDiscoutntsRepository.GetGlobalDiscountbyTurnOver(finData.TurnOver);
      line.TDiscountItemPercent = globalDicount.Value;

    }
    else if (enrty.BTotalValue>1000)
    {
      var valueDicount = RepositoryProvider.ValueDiscoutntsRepository.GetValueDiscountbyTotal(enrty.BTotalValue);
      line.TDiscountItemPercent= customer.IsLocal? valueDicount.LocalDiscount:valueDicount.ForeignDiscount;
    }

  }
}

If I decide to refactor my code and

  1. Place CalculateDiscount on the CommercialDocumentEntry aggregate
  2. Discard CommercialDocumentEntryBusinessLogic and call CalculateDiscount method from a DiacountCommandHandler
  3. Make all the calls to the database before calling the CalculateDiscount method from the DiacountCommandHandler and pass the results as parameters the CalculateDiscount method

Then I will have made 5 calls to the database in stead of 2. This will eventually deteriorate the performance of my application.

How do I tackle this problem with DDD and CQRS?

Thank you very much for your answers.

4
  • DDD and CQRS are not solutions to this problem. Write code that does what you need it to do. Nov 25, 2022 at 19:58
  • ADM is the right way to go. martin fowler said it was an anti pattern once, but he was talking about OOP monoliths
    – Ewan
    Nov 25, 2022 at 20:27
  • I have rewritten my answer, let me know if this is what you are after.
    – Doc Brown
    Nov 26, 2022 at 8:38

1 Answer 1

2

In short: introduce another level of indirection.

I would recommend to create a "helper service" (a.k.a service facade) which encapsulates the repo access and "inject" this into the entity's method. In your example above, one may create a service like DiscountProvider with methods GetZoneDiscount, GetGlobalDiscount, GetLocalDiscount and GetPricelistDiscount. These methods can internally do the repo access at the time when they are called (or in advance, or cache the result, this becomes an implementation detail). The service can be passed into CommercialDocumentEntry.CalculateDiscount method as a parameter. This way, the business logic is in your entity, but your entity is not really aware if it accesses a database to get the required data.

Note also just because you let some methods stay inside your service layer (and outside your entities), you don't automatically get an anemic domain model. If you have only a few methods with the issue you mentioned, I would probably spare the whole service facade overhead and let the method stay directly in some service, as shown in your post. On the other hand, maybe you own suggestion for refactoring, which involves making always five queries instead of two is quick enough? Did you measure it? Performance problems should be only solved when they have a measurable impact on your program.

Let me finally say, stay pragmatic here, write the code which needs to be written, and take DDD as a guideline, not as a law.

1
  • Ι have not measured the specific case.But my code is in abundance of cases like the one I mentioned.Even worst in some cases I dont know in advnace the data I have to fetch from the db.Also with a few users the performance may not be an issue.But as the user base will grow I m expecting gradually the performance to be an issue.So I had in mind a similar solution like the one you proposed but it was compound with the repository.But your idea of having another separate distinct layer called providers in parallel with the repositories makes the architecture cleaner.So I will give it a try.Thanks. Nov 26, 2022 at 14:11

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.