In my previous article about DDD for Rails Developers, I talked about using the layered architecture for tackling domain complexity. I showed a few typical violations of the layered architecture and gave some advice on how to fix them.
The Building Blocks of Domain Driven Design
This time I’d like to start talking about the building blocks of Domain Driven Design, and how they can be used for modeling.
Entities and Values
In Domain Driven Design, an important distinction is drawn between Entities and Value Objects.
“An Entity is an object defined not by its attributes, but by a thread of continuity and identity.” An example of an Entity would be a bank account. Many bank accounts can exist in our system at the same time. Some of them can be assigned to the same branch or have the same owner, but it’s important for our system to treat them as different accounts as long as they have different identities. In case of a Rails application, an identity of an Entity is usually represented by an auto-generated primary key.
“A Value Object is an object that describes some characteristic or attribute but carries no concept of identity.” As there is no identity, two Value Objects are equal when all their attributes are equal. An example of a Value Object would be Money.
More on Entities
In the Rails community we have a good understanding of what Entities are. Practically, almost every object extending ActiveRecord::Base is an Entity.
Entities have the following characteristics:
- Entities care about their identity. The identity is usually represented by an auto-generated primary key, which is used to compare two Entities.
- They are mutable. The only field that cannot be changed is the primary key.
- They have long lives. Most Entities are never deleted from the database.
- Since Entities are mutable and long-lived, they usually have a complex life cycle:
* An Entity Object is created.
* It is saved in the database.
* It is read from the database.
* It is updated.
* It is deleted (or marked as deleted).
Due to mutability and a complex life cycle, dealing with Entities is complicated. Therefore, every time you define an Entity, think over how you are going to persist it, what attributes you have to make mutable, what Aggregate (more on Aggregates in the next post) should contain it, etc.
More on Values
Value Objects, on the other hand, are underused in the Rails community. As a result, most Rails applications suffer from Primitive Obsession:
- Primitive values such as integers and strings are used to represent important concepts of the domain.
Firstly, as the logic of dealing with a group of attributes is spread out over dozens of classes, Primitive Obsession is usually a source of code duplication. Secondly, using primitives instead of domain specific abstractions clutters high-level services with unnecessary details and makes the intent of your code unclear. Value Objects offer a good remedy for Primitive Obsession.
Value Objects have the following characteristics:
- Value Objects have no identity.
- They are immutable. For instance, adding 3 to 5 does not change any of these values. A new value is returned instead. Ideally, working with Value Objects should feel like working with primitives.
- Value Objects do not have a complex life cycle.
Creating Value Objects in Rails
There are many ways of creating and managing Value Objects in Rails, and I’d like to show three of them.
Imagine, we are writing another blog application. We’ve decided in favor of this model:
- Blog has many Posts.
- Every Post has many Comments.
- Posts and Comments have location attributes associated with them.
We can create Posts and Comments this way:
We can also search them by their location:
In addition to this, we have a presenter to display the location attributes:
As you can see, we always use the
city attributes together. Even when I was explaining the application behavior I wrote: ‘by their location’. Apart from having duplication we’ve missed an important part of our domain. There is a notion of a location that our model (our code) does not reflect. Let’s fix it.
Let’s start with defining a class that will encapsulate the location attributes:
Now we need to configure Post to wrap
location_city into an instance of Location:
This is the result of our refactoring:
The biggest gain after this refactoring is making an important concept of our domain explicit in the source code. Also, extracting a Value Object helped us to raise the level of abstraction, which leads to more readable code:
Value Objects Extending ActiveRecord::Base
Some people say that everything extending ActiveRecord::Base is an Entity. I disagree with this opinion. In my view, it doesn’t really matter how you implement your Value Objects as long as they have neither state nor identity.
Let’s define the Location class:
Using Location stays pretty much the same:
Such requirements as supervising the list of all possible locations dynamically or attaching some additional information to every object (for example, a link to a wikipedia article) may push your decision in favor of this approach.
Plain Old Ruby Objects
Those developers who learned Ruby via Rails tend to solve all their problems using Rails building blocks. Need to persist something? It’s only ActiveRecord. Need a Value Object? Use composed_of. Everything that does not use Rails feels dirty for them. For example, all models not extending ActiveRecord::Base go to the lib folder. Even though it may work for small applications, building a complex model will require using Factories, Services, Value Objects, etc. Therefore, don’t be afraid of implementing a Value Object without Rails at all.
To sum up, Entities and Value Objects are extremely important. They are the core elements of object models. Thus, software developers should have a solid understanding of differences between them.
Victor Savkin is a developer interested in Domain Driven Design, Enterprise Architecture, and Domain Specific Languages. He works on large enterprise applications written in Rails. Being a language nerd he spends a lot of his time playing with Smalltalk, Groovy, Scala, Clojure, Ruby, and Ioke.
Jump Start Git, 2nd Edition
Visual Studio Code: End-to-End Editing and Debugging Tools for Web Developers
Form Design Patterns