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
- Eric Evans, Domain-Driven Design: Tackling Complexity in the Heart of Software
- Vaughn Vernon, Implementing Domain-Driven Design
- Martin Fowler, EvansClassification