Nothing Special   »   [go: up one dir, main page]

Dev
Domain driven boldness

Domain driven boldness

How to create a good domain model is the subject of many books, but here’s a lesson I learned at 37signals: don’t be aseptic, double down on boldness.

One of the first things I did when I started working at 37signals almost three years ago was cloning the git repo for Basecamp. I poked around and ended up at this method:

module Person::Tombstonable
  
  def decease
    case
    when deceasable?
      erect_tombstone
      remove_administratorships
      remove_accesses_later
      self
    when deceased?
      nil
    else
      raise ArgumentError, "an account owner cannot be removed. You must transfer ownership first"
    end
  end
end

A person in Basecamp has a delegate type attribute that represents its specific kind (e.g., User or Client). When you remove a person from a given account, Basecamp replaces it with a placeholder so that its associated data remains untouched and functional.

I was well-versed in Domain-Driven Design and the importance of code reflecting domain concepts, but I had never seen that idea put into practice so intentionally before. I would have expected something like replacing a person with a placeholder when removing it, but erecting a tombstone when deceasing a person was so much better.

It was more eloquent, clear, and concise on the objective side. On the subjective side: it had a boldness component, like personality or soul. Can code have those? It can, and, when done right, that can be a considerable enhancement. For me, this was an aha moment.

Let me show another example, the HEY screening system. Internally, the system looks like this: users examine clearance petitions requested by contacts who send emails.

Again, I found that boldness component. A petition is different from a request because it implies formality. Screening in HEY is formal by design: people can’t get emails in your inbox without your permission. A clearance petition by a petitioner that an examiner has to approve is a crystal-clear way of explaining to another human what the system is doing, and that’s exactly what the code reflects:

class Contact < ApplicationRecord
  include Petitioner
   
end

module Contact::Petitioner
  extend ActiveSupport::Concern

  included do
    has_many :clearance_petitions, foreign_key: "petitioner_id", class_name: "Clearance", dependent: :destroy
  end
   
end

class User < ApplicationRecord
  include Examiner
end

module User::Examiner
  extend ActiveSupport::Concern

  included do
    has_many :clearances, foreign_key: "examiner_id", class_name: "Clearance", dependent: :destroy
  end

  def approve(contacts)
    
  end

  
end

This usage of concerns reminded me of roles in the DCI architectural pattern. DCI is one of those proposals full of interesting ideas that often don’t translate so well into code. This usage of concerns is a pretty pragmatic implementation of roles.

My favorite tool when building a non-trivial model is writing a plain text description. When I worked on improving the email analysis system for HEY, I wrote a note for myself about how the new domain model could look. Below you can see both this note (left) and the description I included in the Pull Request once the system was built (right). The note contents and its accuracy aren’t relevant — that was writing as a thinking tool for myself — but plain text is a wonderful starting point when thinking about a complex system. A dictionary is a great companion for doing this.

Both HEY and Basecamp bet hard on Domain-Driven design since the first commit. Of course, that doesn’t mean every single corner is shiny and perfect, but, in general, it is a pleasure to read through their codebases. How to create a good domain model is the subject of many books, but here’s a lesson I learned here: don’t be aseptic; double down on boldness.