0

In most domains, there is an app that "prepares" data, and another app that "serves" the prepared data. The data, however, is from the same Core Domain, which causes some confusion in modeling.

Some examples:

  1. In a shipping/cargo app, there will be one UI for management: staff members will add/update/delete cargos, voyages, and customers. Then in another UI or part of the application, someone will leverage this prepared data to do the heavy lifting of matching cargos to voyages, depending on requests from customers, perhaps exposing it as an API.
  2. In an advertising application, there will be a UI where staff or advertisers can add/update/delete ads, which will include bids, restrictions (geo, devices, etc). Another app will take this information and use it to "serve" the prepared data.

There are tons of other examples but they share the same pattern: preparation of data, and serving the same data. Both of these share the same information, such as ad restrictions and payouts in the advertising domain, but use them in different contexts. "Preparing" can commonly be CRUD-based, whereas "serving" more involved.

How does one model these? Should there be a single bounded context related to core domain where the same entities are used? Or are they to be considered entirely separate contexts, where a shared kernel may include entity IDs, and perhaps a shared underlying database?

I have been looking into the CQRS pattern which might be helpful. To use the advertising domain, "preparing" the data could use both commands and queries (sort of a CRUD pattern), while the "serving" could use dedicated queries (which would obviously be very different.)

Still, I cannot shake the feeling that separating these entirely would create a lot of duplication, plus high coupling, e.g. if a new restriction is added on an ad (perhaps "operating system name") then it would simultaneously need to be added to the "preparation" and "usage" stages.

1 Answer 1

1

Aside from the terminology, you’ve described CQRS very closely. The piece you seem to be missing is the read model.

Creates, updates, and deletes all come in from the UI and are translated into commands. Commands make all changes to the domain model to execute the users desire. Keep in mind the domain model is what most people think of when they imagine a CRUD system – typically 3rd normal form with appropriate relationships, etc.

After the command is done making necessary changes, it will update all the read models that are affected (usually this is done via messaging or event streams to enable async processing, but let’s pretend we have an infinitely powerful machine so performance is of no concern). The read model is made up of a set of tables that are custom designed for serving to the user, and ONLY for serving for read. They will definitely duplicate data – 3rd normal form has no place in these tables. If a persons last name is needed on three different screens, there will be 3 different read model tables with a last name column. The idea is that updates are far rarer than reads, so let’s make the reads as fast as possible even if it costs a little extra during the update phase. Reading data from a read model should ideally be a simple select from the proper table with no joins, just a where clause to filter to the rows.

The read model tables will be tightly coupled to the view models, not necessarily to the domain model. If a new restriction is added on an ad like “op sys name”, then it obviously needs to be added to the domain model. It only needs to be added to the read models, though, if it is to be displayed on screen somewhere. In other words, changes to the domain model don’t drive changes to the read model, changing user requirements drive changes to the read model and the code that updates those models must keep up with changes to both sides.

On your comment about CQRS in the advertising domain and “preparing” the data using commands and queries, it appears you have the proper idea but terminology is tripping you up. CQRS Queries are never used on the command side. Commands often must retrieve data (I’m trying to avoid the q-word) and update that data before persisting it back, but should never retrieve data from the read model. CQRS Queries, on the other hand, are only allowed to retrieve data from the read model, not the domain model.

BTW, I have some experience with the shipping industry and event stream processing would be worth investigating. The complexity of the calculations warrants extra effort to make them async and scalable, that can be made much easier with stream processing (think Kafka, AWS Kinesis, or Azure Event hub).

7
  • Thanks, Brad. Some follow-up questions. 1. Are the query and command sides considered a part of the same bounded context? If so, what exactly do they share? 2. How are complex "data retrievals" done on the command (CRUD) side? Repositories are only for basic aggregate fetching, so how about complex reads for the UI? E.g. a collection of certain aggregates with certain required relations, for display on a UI index page?
    – timetofly
    Jul 10, 2018 at 14:03
  • The query and command sides are in the same bounded context, but don’t share much of anything. The command side queries data for aggregates in the normal fashion using repositories and such, so CQRS doesn’t introduce any special circumstances for complex queries. The command then will send an async event for another thread to update the read models.
    – Brad Irby
    Jul 10, 2018 at 15:41
  • On the read side remember that the read model tables are custom designed to make it easy to query for populating the viewmodel. Complex reads to create viewmodels should not happen – if they do, you need to examine the structure of the read side tables. The process that receives the message sent from the command side requesting a read model update is where that complex logic will go. That way it runs while it is not time sensitive instead of while the user is waiting for their screen to refresh.
    – Brad Irby
    Jul 10, 2018 at 15:41
  • I am still confused. You seem to address a basic "CRUD" like separation, where the "read" is separated. In my question, I refer to the CRUD application (which has both reads and writes), as well as a "usage" application (which only has reads.) They are different UIs, different use-cases. Perhaps I'm not understanding something, but both of the "reads" in each UI are completely different and may have separate requirements. The "CRUD" side of the application is more basic and might not need a separate read model.
    – timetofly
    Jul 10, 2018 at 17:40
  • If the CRUD side is really that simple then maybe CQRS is not the proper architecture. Standard client-server is still a valid option. The defining part of CQRS is that Queries take a different path than Commands. Create, Update, and Delete should take a path where a command is created, Aggregates retrieved, incoming data is validated, and all the changes are stored with the proper transactional isolation. The Read side takes a more direct route by just querying tables and returning results.
    – Brad Irby
    Jul 11, 2018 at 4:20

Your Answer

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

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