This post originated from an RSS feed registered with Ruby Buzz
by Jeremy Voorhis.
Original Post: First-Class Relationships, or, It's All Just CRUD
Feed Title: JVoorhis
Feed URL: http://feeds.feedburner.com/jvoorhis
Feed Description: JVoorhis is a Rubyist in northeast Ohio. He rambles about Ruby on Rails, development practices, other frameworks such as Django, and on other days he is just full of snark.
One classic yet braindead way of creating an application’s domain model has been to read your requirements document and underline the nouns. Sadly, some domain modelers never reach any further than this. Other savvy developers, however, may begin a project this way in order to drive a stake in the ground so work may progress and a feedback loop may be established between customer and developer. The important distinction here is that for the savvy developer, the domain is incomplete. Creating a rich domain model is, in fact, an emergent process.
One barrier for many inexperienced domain modelers is that they think each model must be a tangible thing. When describing a graph of domain object, however, we often have as much to say about the edges of that graph as we do about the nodes. It is at these times that we should think about promoting those edges to being their own full-fledged nodes.
For example, let’s start with a simple Rails app – a news site where readers may subscribe or unsubscribe to news categories and receive news stories from those categories in their personalized Atom feed. A naive domain model may allow a direct association between a User and a NewsCategory.
class User < ActiveRecord::Base
has_and_belongs_to_many :news_categories
end
class NewsCategory < ActiveRecord::Base
has_and_belongs_to_many :users
end
That could work, but what if we want to record additional metadata about when that User subscribed or unsubscribed? The direct association provides no place for us to store that. By promoting that association to a full-fledged model named NewsSubscription, we may store that metadata as attributes of the new model. Furthermore, we don’t necessarily introduce any new impedence by directing the relationship through this new model – ActiveRecord’s has_many :through association allows us to skip over a user’s subscriptions directly into the targeted news categories.
class User < ActiveRecord::Base
has_many :subscriptions
has_many :news_categories, :through => :subscriptions
end
class NewsCategory < ActiveRecord::Base
has_many :subscriptions
has_many :users, :through => :subscriptions
end
class Subscription < ActiveRecord::Base
belongs_to :user
belongs_to :news_category
end
DHH’s talk at Railsconf and Jim Greer’s blog post reminded me of this principle and suggested a new heuristic for a missing relationship model: entangled call sites in the form of controller actions. When we have a rich domain model which treats vital relationships as first-class citizens, complex operations within our domain can be mapped to simple CRUD operations on those relationship models. I look forward to exploring this in the following months, as well as diving back into Eric Evans’ remarkable Domain-Driven Design.