14

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?

bezmax
  • 25,562
  • 10
  • 53
  • 84

2 Answers2

6

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).

Ben Smith
  • 2,369
  • 20
  • 17
  • 2
    Talking about scenario 2: Isn't whole idea of read model that it does not use your domain layer at all? Essentially, it should be as close to database as possible to skip all the overhead of domain layers and query the data you need in the way you need. – bezmax Jul 29 '14 at 14:00
  • As about scenario 1: seems logical, and as Adrian mentioned in his comment, those look similar but are probably different in nature. – bezmax Jul 29 '14 at 14:01
  • 1
    Scenario 2 doesn't look like pure read-model stuff to me. Writing the sum to an accounting database is not an innocent action -- you chose to label it "approve", which probably means it has a place of its own in your ubiquitous language. Accounting software could make decisions based on the value you approved, with unapproved changes occurring in the invoice in the mean time. If you want a single source of truth to track what happened, you'd better have an `InvoiceTotalApproved` domain event that contains the sum. – guillaume31 Jul 29 '14 at 15:07
3

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.

Dennis Traub
  • 50,557
  • 7
  • 93
  • 108
  • It seems you are right about scenario 1, that seems most logical approach. As about scenario 2, I'm still not quite sure exactly how should it be implemented. Let's say I want a table which lists all Invoices over the lifetime of application sorted by that sum and paginated. Controller takes the `pageNum` param and `sortBy` param and... passes it where? Directly to some repository of `Invoicing` BC? – bezmax Jul 30 '14 at 05:50
  • There might for instance be a Reporting BC that aggregates and provides this very information. – Dennis Traub Jul 30 '14 at 06:00
  • 1
    But wouldn't that mean that I need a separate BC for every form which needs some sort of calculation done on querying? For example there could be global invoice list with 3 columns, some "last month invoices" with 6 columns, and in every client "invoice history" with 12 columns. Would that require 3 separate BC, with different Invoices populated with different data? Then again we return to Scenario 1, as we have 3 different BC which calculate some of their columns using same rules and some of their own columns by their own rules. Any ideas? – bezmax Jul 30 '14 at 10:20