29
.
04
.
2024
16
.
08
.
2022
Ruby on Rails
Domain-Driven Design
Backend
Tutorial

Entity - DDD in Ruby on Rails

Paweł Strzałkowski
Chief Technology Officer

Entities are omnipresent. They are the backbone and the substance of most applications. We will take a deep dive into the concept of an entity from the Domain-Driven Design's perspective.

"An object defined primarily by its identity is called an ENTITY."

— Eric Evans, Domain-Driven Design: Tackling Complexity in the Heart of Software

Identity

The fundamental characteristic of an entity is its identity. The primary cause of its existence is to provide an identity and the ability to track a lifecycle.

Most applications orbit around identifiable objects. Those may be customers, products, operations, etc. For an e-commerce, it is crucial to track who has purchased what. For a bank, it is essential to distinguish every transfer (each deposit is a separate entity, even if their amounts match).

It is up to an application to define the level of identity recognition. For some, it may be enough to identify people using phone numbers when others cannot operate without a social security number-type value. Identity basis may be (among others) user-driven or application-generated. It all depends on use cases. It is important to choose the approach which aligns with your domain.

Once an entity, not always an entity

The decision to model a concept as an entity is not always obvious.

When designing a reservation system for a concert, we may assign a ticket to each seat. That seat would be modeled as an entity. It would also have characteristics such as location or type. The seat entity would come into existence at a point in time and go through changes (maybe a hard bench would be upgraded to a velour chair). Our reservation system would assign an attendee to the seat to book a concert ticket. On the other hand, there are events where seats are not assigned per ticket. What matters is the number of guests vs the total number of available seats. In such a situation, a seat would not be an entity but would rather become a value object representing the volume of seats.

Another example is money - the crowning example of a value object concept. Money amount is an attribute. But imagine numismatists who value each coin and each bill. They would put them in catalogs and carefully identify each item. Since the items have identities, we immediately recognize them as entities.

Identity assignment strategies

Vaughn Vernon, in Implementing Domain-Driven Design, describes four common strategies of identity assignment:

  • user provides a unique value (ie. email for a user account)
  • application generates an identifier (ie. invoice number or a UUID)
  • persistence store generates a unique identity (ie. DB auto-increment)
  • another bounded context has already defined an unique identity (when our entity is based on a remote one)

It is important to weight pros and cons of each approach before making a decision. Most entities in Ruby on Rails have their identity generated by a persistence store. Still, there may be a user-facing attribute (ie. email, phone or social security number) which can be used for authentication or for populating entities of the same identity in other contexts. DB-generated identifiers should remain local to the bounded context where they originate, as they have no meaning outside of it.

Most times, identity remains immutable after an entity is created. There are cases where an email address / username may be changed. It is a simple enough operation in a monolithic system. However, in a distributed one it may cause severe difficulties to propagate such a change.

Indispensable behavior

Entities not only are, but they also do. While doing, they go through their lifecycles of changes. A customer may change_phone_number(), an invoice may be voided by calling void(), an account may be activated with activate() method and so on.

Think what is the behavior of your entities. A list of operations is usually given to you by your client. It's important to realize that during the conversations and to be able to put down a list of methods similar to the ones listed above. They should express entity's behavior, not framework-related gimmicks like create_associations().

It's easy to fall into a trap of creating data bags, where every attribute has a setter and a getter. Such an entity is just a sack. It is moved around and operated on using a procedural approach. This way, you don't model your domain but rather elevate database tables to the level of objects. The result may only be described as an ORM-driven development. Whereas the nature of Object Oriented programming is encapsulation and representation of behavior.

How to design an Entity

"Rather than focusing on the attributes or even the behavior, strip the ENTITY object's definition down to the most intrinsic characteristics, particularly those that identify it or are commonly used to find or match it."

— Eric Evans, Domain-Driven Design: Tackling Complexity in the Heart of Software

Entities are characterized by multiple attributes but few of them are used for matching with other entities. The characteristics are valuable and drive the essential features of applications. However, what defines an entity is the identity which has to be matched or found. Therefore:

  • start by thinking what it represents and what are its relations with other elements. Don't jump right into listing all the attributes.
  • move on to the needed behavior
  • think carefully on naming - from now on, you will be using this language in every conversation
  • add the needed attributes and characteristics.

Please check out the follow-up post with a broad discussion of how to design an entity in Ruby on Rails.

Summary

ORM-centric frameworks, like Ruby on Rails, have always leaned towards treating entities as data wrappers. It is high time to put entities where they belong. They make the code explicit, readable and testable. They express identity, behavior and purpose. Entities reveal intention. Let us treat them the way, objected oriented paradigm demands and model their behavior with intention and purpose. Entities are the backbone of any well-designed domain model.

Resources

Articles in this series

Paweł Strzałkowski
Chief Technology Officer

Check my Twitter

Check my Linkedin

Did you like it? 

Sign up To VIsuality newsletter

READ ALSO

Vector Search in Ruby - Paweł Strzałkowski

Vector Search in Ruby

17
.
03
.
2024
Paweł Strzałkowski
ChatGPT
Embeddings
Postgresql
Ruby
Ruby on Rails
LLM Embeddings in Ruby - Paweł Strzałkowski

LLM Embeddings in Ruby

17
.
03
.
2024
Paweł Strzałkowski
Ruby
LLM
Embeddings
ChatGPT
Ollama
Handling Errors in Concurrent Ruby, Michał Łęcicki

Handling Errors in Concurrent Ruby

14
.
11
.
2023
Michał Łęcicki
Ruby
Ruby on Rails
Tutorial
Recap of Friendly.rb 2024 conference

Insights and Inspiration from Friendly.rb: A Ruby Conference Recap

02
.
10
.
2024
Kaja Witek
Conferences
Ruby on Rails

Covering indexes - Postgres Stories

14
.
11
.
2023
Jarosław Kowalewski
Ruby on Rails
Postgresql
Backend
Ula Sołogub - SQL Injection in Ruby on Rails

The Deadly Sins in RoR security - SQL Injection

14
.
11
.
2023
Urszula Sołogub
Backend
Ruby on Rails
Software
Michal - Highlights from Ruby Unconf 2024

Highlights from Ruby Unconf 2024

14
.
11
.
2023
Michał Łęcicki
Conferences
Visuality
Cezary Kłos - Optimizing Cloud Infrastructure by $40 000 Annually

Optimizing Cloud Infrastructure by $40 000 Annually

14
.
11
.
2023
Cezary Kłos
Backend
Ruby on Rails

Smooth Concurrent Updates with Hotwire Stimulus

14
.
11
.
2023
Michał Łęcicki
Hotwire
Ruby on Rails
Software
Tutorial

Freelancers vs Software house

02
.
10
.
2024
Michał Krochecki
Visuality
Business

Table partitioning in Rails, part 2 - Postgres Stories

14
.
11
.
2023
Jarosław Kowalewski
Backend
Postgresql
Ruby on Rails

N+1 in Ruby on Rails

14
.
11
.
2023
Katarzyna Melon-Markowska
Ruby on Rails
Ruby
Backend

Turbo Streams and current user

29
.
11
.
2023
Mateusz Bilski
Hotwire
Ruby on Rails
Backend
Frontend

Showing progress of background jobs with Turbo

14
.
11
.
2023
Michał Łęcicki
Ruby on Rails
Ruby
Hotwire
Frontend
Backend

Table partitioning in Rails, part 1 - Postgres Stories

14
.
11
.
2023
Jarosław Kowalewski
Postgresql
Backend
Ruby on Rails

Table partitioning types - Postgres Stories

14
.
11
.
2023
Jarosław Kowalewski
Postgresql
Backend

Indexing partitioned table - Postgres Stories

14
.
11
.
2023
Jarosław Kowalewski
Backend
Postgresql
SQL Views in Ruby on Rails

SQL views in Ruby on Rails

14
.
11
.
2023
Jan Grela
Backend
Ruby
Ruby on Rails
Postgresql
Design your bathroom in React

Design your bathroom in React

14
.
11
.
2023
Bartosz Bazański
Frontend
React
Lazy Attributes in Ruby - Krzysztof Wawer

Lazy attributes in Ruby

14
.
11
.
2023
Krzysztof Wawer
Ruby
Software

Exporting CSV files using COPY - Postgres Stories

14
.
11
.
2023
Jarosław Kowalewski
Postgresql
Ruby
Ruby on Rails
Michał Łęcicki - From Celluloid to Concurrent Ruby

From Celluloid to Concurrent Ruby: Practical Examples Of Multithreading Calls

14
.
11
.
2023
Michał Łęcicki
Backend
Ruby
Ruby on Rails
Software

Super Slide Me - Game Written in React

14
.
11
.
2023
Antoni Smoliński
Frontend
React