Mastering Entity Design in Domain‑Driven Development: IDs, Stability, and Best Practices
This article explains why entities are essential in domain‑driven design, how to define unique identifiers, ensure their stability, model entities in various states, and implement robust constructors with Java code examples and practical guidelines.
In domain‑driven design (DDD), an entity is a core domain object that represents a concept with a unique identity that persists over time, unlike simple data‑centric models that focus on database columns and foreign keys.
Why Use Entities
Entities are introduced when a concept needs to be distinguished by its individuality rather than just its attributes. Their uniqueness and mutability make them suitable for modeling objects that evolve throughout the software lifecycle.
Unique Identifier
An entity must have a unique identifier that distinguishes it from other instances. The identifier should be stable, readable when appropriate, and used for entity lookup rather than relying on mutable attributes such as a name.
Design entities by focusing on their identity and lookup behavior first; only add attributes and methods when they serve the entity’s essential characteristics.
Strategies for Creating Entity IDs
Consider generation time, database reference, and ORM behavior when choosing an ID strategy.
Reference: "DDD领域驱动设计实战 - 创建实体身份标识的常用策略".
Stability of Identifiers
In most scenarios an entity’s identifier should never change during its lifecycle. Hide the setter for the ID and add guard logic to prevent reassignment.
Example: the username field of a User entity can be set only once; subsequent attempts raise an exception.
Various Forms of Entities
4.1 Business Form
During strategic design, entities act as carriers of multiple attributes, operations, and behaviors, forming aggregates with closely related value objects.
4.2 Code Form
In code, entities are represented by rich domain classes (often using the “anemic vs. rich” model debate). All business logic related to the entity resides in its methods, while cross‑entity logic lives in domain services.
4.3 Runtime Form
At runtime, each entity instance has a unique ID and may undergo many state changes, yet it retains the same identity, e.g., a product’s ID remains constant even as its price changes.
4.4 Database Form
Mapping entities to persistence can be one‑to‑one, one‑to‑many, or many‑to‑one. Most entities map to a single table, but some may be transient or aggregate data from multiple tables.
One‑to‑many example: a User and Role each have separate tables but together form a permission entity.
Many‑to‑one example: Customer and Account data stored in a single table, generating two entity objects from one record.
Teams often fall into the trap of creating “entity‑relationship” models that result in fat, getter/setter‑only classes. Proper DDD encourages focusing on behavior and using a ubiquitous language to keep models expressive and aligned with business concepts.
Creating Entities
When constructing a new entity, the constructor should accept the unique identifier and any other required immutable data, ensuring the entity is in a valid state from the start.
public class User extends Entity {
// Each User must have tenantId, username, password, and person.
protected User(TenantId tenantId, String username, String password, Person person) {
this();
this.setPassword(password);
this.setPerson(person);
this.setTenantId(tenantId);
this.setUsername(username);
this.initialize();
}
protected void setPassword(String password) {
if (password == null) {
throw new IllegalArgumentException("The password may not be set to null.");
}
this.password = password;
}
protected void setPerson(Person person) {
if (person == null) {
throw new IllegalArgumentException("The person may not be set to null.");
}
this.person = person;
}
protected void setTenantId(TenantId tenantId) {
if (tenantId == null) {
throw new IllegalArgumentException("The tenantId may not be set to null.");
}
this.tenantId = tenantId;
}
protected void setUsername(String username) {
if (this.username != null) {
throw new IllegalStateException("The username may not be changed.");
}
if (username == null) {
throw new IllegalArgumentException("The username may not be set to null.");
}
this.username = username;
}
}The constructor delegates to setter methods, which act as guards to enforce non‑null constraints and protect the entity’s invariants.
public class Tenant extends Entity {
// Factory method to create a User and ensure consistency.
public User registerUser(String username, String password, Person person) {
person.setTenantId(this.tenantId());
User user = new User(this.tenantId(), username, password, person);
return user;
}
}References:
https://tech.meituan.com/2017/12/22/ddd-in-practice.html
《实现领域驱动设计》
实体和值对象:从领域模型的基础单元看系统设计
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
JavaEdge
First‑line development experience at multiple leading tech firms; now a software architect at a Shanghai state‑owned enterprise and founder of Programming Yanxuan. Nearly 300k followers online; expertise in distributed system design, AIGC application development, and quantitative finance investing.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
