Handling duplication of domain logic using DDD and

2019-03-10 13:39发布

问题:

I'm experimenting with DDD + CQRS and I can not understand how to handle this domain logic duplication problems:

First, about duplication across domains:

Scenario 1: Let's say I have some application which handles office employees. I have 3 bounded contexts: Programmer department, QA department and Audit Department. Each BC has it's own AR: "Programmer", "Tester", "Worker". They are 99% different, with different logic in each, however, each of those have "Name", "Surname" and a simple method "getFullName" which concatinates those two.

Question 1: How do I (and should I?) make that the common method is not duplicated in each AR?

Easiest answer is probably to make some shared "Human" class, and make those 3 ARs derive from it, but that is against the idea of DDD, as "QA Department" could never need the "getFullName" method but need some other "shared" method. Therefore this solution will make the domain spammed with unused methods.

Now about CQRS code duplication:

Scenario 2: Database contains invoices. Each invoice has fields "sum" and "tax". In "show invoice" page I need to show invoice sum with tax. Therefore in my read model I will need to do "total = sum + tax" to show it to the end user. However, user can press "approve" button, which should, let's say, register the invoice sum in some other database (accounting or something). So, in my write model I will once again need to do "total = sum + tax".

Question 2: How do I (and should I?) remove this kind of duplication?

Sure, this is a simple scenario, but after analyzing some of my real life applications I see that using CQRS would require lots of heavy duplication in different places, as there are lots of places where the end result is calculated from the data stored in database, and that is done both on query and command operations.

Any ideas? Am I missing something?

回答1:

Scenario 1

  1. Copy & paste the code into each of the three bounded contexts.
  2. Create a Value Object for names contained within a shared library that encapsulates the logic of getting a full name.
  3. Create an Employee bounded context responsible for managing employee details. Any additional bounded context can then use this for looking up an employee's details. Events would be published to ensure consistency between bounded contexts (e.g. EmployeeJoinedCompanyEvent containing their full name).

Scenario 2

Any calculations should be part of your domain model. Contained within the Entities, Value Objects or Domain Services.

The result of any calculations - total in this example - are then included in the Events that are published from the domain. Both read and write data stores can be updated from the values contained within the published events. They should not be doing any calculation themselves.

As an example, if the domain publishes an InvoiceApprovedEvent it would contain all data necessary for the read model. Including the sum tax and total amounts.

Events are also the primary means of integration between bounded contexts. So if you need to update an Accounting bounded context or external system, you would subscribe to the relevant events from the Invoicing bounded context and handle the events as they are received.

References

A couple of resources that I can highly recommend for implementing DDD and CQRS (assuming you're already familiar with Eric Evan's DDD book).

  • Microsoft Patterns & Practices - Exploring CQRS and Event Sourcing (free eBook)
  • Implementing Domain-Driven Design by Vaughn Vernon (paid book)


回答2:

Scenario 1

Quite often something like Master Data Management would be a bounded context of its own. This is where an employee's name, date of birth, etc. would belong. Other bounded contexts as well as the various read models could retrieve their data (directly or indirectly) from there.

Scenario 2

If you're merely creating simple sums I'd not mind having some duplication across contexts. Once you have more complex means of calculation they should be clearly associated with their respective bounded context. In your example there could be an Invoicing bounded context, being the natural place where invoicing-related algorithms, calculations and services belong. The creation and approval of invoices would then be propagated from there to populate the affected read models.