0

This is my first shot at trying to develop an application using some of the ideas behind DDD. It's a Rails monolith application (at-least for now). I know i'm not being strict with DDD, but I'm trying to focus on the separation of my domains which seems like the most important concept.

I think this is probably quite a rudimentary question and I'm sure I'll use the wrong DDD terminology, but here we go...

I have two domains:

  1. CRM (contacts, companies, branches, people, etc..)
  2. Quoting (quotes, quote items, currency, etc..)

In the Quoting domain, my Quote model links to a contact_id and company_id in the CRM domain.

On a page in the CRM domain I want to list all the Quotes that belong to a company.

The code is something like this:

  module CRM
    class QuotesController
      def index 
        @company = Company.find params[:company_id]
        @quotes = Quoting::Quote.for_company(@company.id)
      end

On this page I want to show who created each quote and I'm not sure what the best way is. The contact_id column in Quote links to the CRM::Contact model, and the CRM::Contact model links to the CRM::Person model which includes the name.

Without thinking of DDD, I would've done something like this (this is simplified) (yes, I know I'm not using associations here):

  module Quoting
    class Quote < ApplicationRecord
      belongs_to :company, class_name: "CRM::Company"
      belongs_to :contact, class_name: "CRM::Contact"

      class << self
        def for_company(company_id) 
          Quote.where(company_id: company_id)
               .includes(contact: :person)    <--- feels like it shouldn't be here
        end 
      end
    end 
  end

In the view:

@quotes.each do |quote|
  %tr  
    %td= quote.contact.person.name   (or quote.contact_name)

My issue with that is the line .includes(contact: :person) which includes knowledge about another domain, i.e. it knows that the name is within the person record. If my internal implementation of Contacts/Person changes then obviously this Quoting domain will fail too.

How could this be done in a more DDD style?

Would I have a new Quote model in my CRM domain that would run 2 queries and collate them into a new model?

Thanks!

  • The person needs to referenced outside its bounded context, thus needs to be an aggregate root. In DDD, an aggregate root publishes its primary key so others can rely on that. So, reference it from outside using its primary key, whatever that is. If you need the person's name (i.e. for a readable report) (and the name is not the primary key) then you should fetch it on demand (from the authoritative bounded context for Persons) rather than caching it in the other bounded context. – Erik Eidt Jul 9 '18 at 15:22
3

I’m not a fan of querying across domains in real time because it can cause cascading failures. If the Quoting domain is down and the CRM domain needs information on a quote, the CRM domain can fail through no fault of its own. Imagine the same scenario where the Quoting domain is not down but just slow – the CRM domain gets user complaints because it seems to be the slow one.

The best way to deal with this is to cache supporting information in the domains that need it. For example, the CRM domain could cache just the QuoteID and Name from the Quoting domain. Such caches are prone to stale data unless you also implement a messaging system allowing the source domain to tell all listeners that particular data has changed and they should update their caches. Each time a quote is added or updated, it posts a message for all subscribers with the now-current data. Subscribers cache just the fields they care about locally and use that when necessary.

With the cacheing approach, if the Quoting domain is down the CRM can still function. The CRM function is also free to change how much of the Quoting information it caches, without changing any requirements for the CRM Domain. It also scales well since the Quoting domain does not have to scale up just to satisfy the needs of the unexpectedly successful CRM domain.

  • Most of the objects in my system will link to Contact in the CRM domain to indicate who created the object. Adding a cache and messag for every domain object would seem excessive, there could be around 50+ different tables that link to Contact. Is there a simpler alternative? – chris-richards Jul 10 '18 at 8:16
  • Each domain would need its own cache, but inside the domains each of the 50 tables would hold only the ContactID. Each domain would have one additional table that caches the ContactID and name from the CRM system. You would need a message bus for the domains to communicate (like rabbitMQ or Kafka) and each time the CRM changes contact info it places a message on the queue with the new data. Each domain listens to those messages and updates its own cache. The only alternative is real time requests which have the problems stated above. – Brad Irby Jul 10 '18 at 10:43
  • Creating a cache/messages/listeners/code for everywhere where I need to cache supporting information like this seems like a hell of lot of work. Although I could see how the resulting system could be easier to modify. I can also see how you could create a monster if you get it wrong – chris-richards Jul 10 '18 at 10:57
  • That's true, cache listeners can propagate, but they are dead simple. Read the incoming event and update the cache table using the primary key. There's not much that can go wrong. If you have so many domains that code maintenance is a problem, you can put them into a module (i.e. NuGet if you're in the .NET world) but that will couple all the domains together so I would avoid that if possible. – Brad Irby Jul 10 '18 at 12:04

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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