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.
Sign up to get posts via email,
or grab the RSS feed.