Entities and Value Objects: Diving Deep into Domain-Driven Design

In the realm of Domain-Driven Design (DDD), understanding core concepts such as entities, value objects, aggregates, and aggregate roots is essential. As we’ve previously explored the intricate nature of aggregates and aggregate roots, today’s discussion is centered on two foundational blocks: entities and value objects. Remember that we are using eShopOnContainers’ Order Service as our DDD example. To see domain-related code, check Order.Domain. For a sample entity, view OrderItem.cs, and for a value object, see Address.cs.”

A Closer Look at Entities

An entity, in DDD, is a domain object which possesses a distinct identity. This identity remains consistent through the various states and times the entity may go through. Thus, irrespective of its attributes or behaviors, an entity can always be distinctly identified.

The eShopOnContainers ordering service exemplifies the concept of entities perfectly. Here, an Order entity is distinct and identifiable by its Id. If you look at the foundational Entity class from the codebase:

public abstract class Entity
{
    int _Id;
    public virtual int Id
    {
        get
        {
            return _Id;
        }
        protected set
        {
            _Id = value;
        }
    }
    // ... rest of the Entity class
}

Every class that inherits from the Entity base class, such as OrderItem, gains this distinguishing feature of identity:

public class OrderItem : Entity
{
    // ... other properties and methods
}

Value Objects: A Descriptive Pillar

While entities underscore the essence of identity, value objects encapsulate immutability and lack of a distinct identity. They describe certain aspects of the domain. Typically, they are immutable because their value doesn’t change once they are created.

For instance, in the eShopOnContainers, the Address class functions as a value object:

public class Address : ValueObject
{
    public string Street { get; private set; }
    // ... other properties and methods
}

Entities vs Value Objects

To better grasp the distinction between entities and value objects, envision two people with the same name, “Daniel”. These are two unique individuals, each distinguished by a unique ID, say a social security number. This unique ID defines them as entities. On the other hand, an address, where any “Daniel” might reside, remains constant irrespective of its association. Such data, which remains invariant over associations, is embodied as a value object.

  • Identity: While each entity carries a unique identity, value objects don’t have a standalone identity. The identity of an entity remains constant throughout its lifecycle. For value objects, equality is determined based on their attribute values (ex. same city, street, and number).
  • Immutability: Value objects are inherently immutable. Once created, their state doesn’t change. Entities can have mutable states, but their identity remains unchanged.
  • Life-cycle: Entities typically have a longer life-cycle and persist across states and changes. Value objects have a limited life-cycle, often scoped to the life-cycle of an entity or a transaction.
  • Usage: Entities are often used where identity and continuity across states is essential, like User, Product, etc. Value objects are employed for representing domain concepts that don’t require distinct identity such as Address, Money, or Color.

Storing Value Objects with IDs in Databases: How Do They Differ from Entities?

In relational databases, tables usually contain a primary key for uniqueness and efficient lookups. While an Address might be stored with a unique ID, this is more about database management than domain identity.

  • Domain Identity (Entities): Represents distinct elements in the domain. Two entities with differing identities are unique even if their other attributes are identical.
  • Storage Identity (Database ID for Value Objects): Merely a technical distinction. Two addresses with matching fields but different database IDs are considered identical in the domain.

Practically, an Address might be stored in its table to reduce redundancy, especially if shared across entities. Yet, in domain modeling, its characterization as a value object remains. The crux is: storage strategies might differ from domain representations. Separating these concerns ensures a robust design. It’s best to decide storage methods after designing the domain, ensuring it doesn’t influence or appear in the domain itself.

Practical Tips for Implementing Entities and Value Objects

In your journey through DDD, here are some hands-on tips when working with entities and value objects:

Ubiquitous Language and Its Reflection in Code

One of the foundational principles of DDD is the establishment of a “Ubiquitous Language”. This language is a shared and consistent vocabulary between developers and domain experts. It ensures that both parties understand and interpret the domain in the same way, eliminating ambiguities.

How it Relates to Entities and Value Objects:

Naming Conventions: The names of entities and value objects should come directly from this ubiquitous language. For instance, if the domain experts refer to a customer’s contact details as “Profile”, the corresponding entity or value object in the code shouldn’t be named “UserInfo” or “AccountDetails”. It should be named “Profile”.

Domain Logic Preservation: Methods and operations within entities and value objects should also reflect domain actions and rules. If there’s a business operation referred to as “FinalizeOrder” in discussions, a method with the same name should exist in the Order entity, ensuring that the domain logic and operations are apparent and directly mapped.

Consistency Across Layers: The ubiquitous language isn’t just limited to the domain layer. It should be consistent across all application layers, from the UI to the database. This ensures that any team member, whether they’re working on the frontend or backend, can understand and align with the domain’s core concepts.

In essence, the codebase becomes a direct reflection of the domain model. By closely aligning the code with the ubiquitous language, it not only becomes more readable but also more maintainable, as everyone involved has a shared understanding of its structure and functionality.

There is a good read in details about Ubiquitous Language: Implementing a Common Vocabulary in Your Code (external link)

Defining Entities

  • Consistent Identifier: Always ensure that your entities have a consistent identifier (like Id in our Order example). This identifier should be immutable and unique throughout the entity’s lifecycle.
  • Encapsulation: Protect the state of the entity by using access modifiers effectively. Internal state changes should be performed by methods that enforce the domain’s business rules.
  • Domain Logic: It’s tempting to put business logic into service classes or other architectural layers. However, for a rich domain model, ensure that the business logic pertinent to the entity resides within the entity itself.

Designing Value Objects

  • Immutability First: Whenever you create a value object, ensure that it remains immutable after its creation. This means all properties should be readonly or have private setters, and any method that modifies the value should return a new instance of the value object.
  • Equality: Overload equality operators (== and !=) and override Equals() and GetHashCode() methods for value objects. The equality of value objects should be based on the values they carry, not their memory references.
  • Domain-specific Methods: Embed methods that represent domain-specific actions. For example, if you have a Money value object, consider methods like Add(), Subtract(), etc., that produce new instances of the value object with the updated value.

Real-world Scenarios

Let’s create a new example to understand entities and value objects better. Think about an “Employee” and their “Salary”. Decide which is an entity and which is a value object before continuing.

An “Employee” is an entity because each one has a unique ID (like an Employee ID).

A “Salary”, however, can be a value object. If two salaries are the same amount, they’re seen as equal, no matter which employee they’re for.

public class Employee : Entity
{
    public string Name { get; private set; }
    public Salary Salary { get; private set; }

    //... Other properties and methods
}

public class Salary : ValueObject
{
    public decimal Amount { get; private set; }
    //... Other properties and methods
}

Implementation Details: Diving into eShopOnContainers’ Order Service Base Classes

When dissecting the eShopOnContainers’ order service, the two foundational classes, Abstract Entity (Entity.cs) and ValueObject (ValueObject.cs), play pivotal roles in structuring the domain. Let’s delve deeper into their roles, characteristics, and functionalities. (make sure you take a look at the code before you continue reading to understand better )

The Base Class – Abstract Entity

This class acts as the bedrock for constructing other entities, furnishing them with essential features:

  • Unique Identifier: Central to an entity is its distinct identity. This base class ensures every entity inherits this characteristic.
  • Domain Events Handling: It provides methods to manage domain events, offering mechanisms to add, remove, or clear these events. These methods include:
  • AddDomainEvent()
  • RemoveDomainEvent()
  • ClearDomainEvents()
  • Transient Check: This helps in ascertaining if an entity has undergone persistence.
  • Equality and Hash Code Mechanisms:
  • Equals(): Dictates the conditions under which two entities are deemed identical.
  • GetHashCode(): Crafts a customized hash code for entities, primarily hinging on their unique identifiers.

The Base Class – ValueObject

This abstract class lays the groundwork for the crafting of value objects within our domain. It emphasizes:

  • Uniform Behavior: An integral feature, ensuring that all child value objects adhere to a consistent equality determination procedure.
  • Cloning Feature: With GetCopy(), it’s effortless to create a replica (shallow copy) of a value object instance.
  • Overriding Equality Mechanisms:
  • Equals(): Establishes that two value objects are identical if all their attributes concur.
  • GetHashCode(): Ingeniously computes a hash code, taking into account the hash codes of the individual attributes.

This deeper exploration into the base classes of the eShopOnContainers’ order service illuminates the thoughtful architecture underlying the service, emphasizing best practices and meticulous design.

Key Takeaways

Entities and value objects are fundamental to achieving a rich and expressive domain model. When modeled correctly:

  • They help in encapsulating the domain logic appropriately.
  • They make the codebase more maintainable and expressive.
  • They reduce complexities by ensuring that domain rules and constraints are honored.

It’s important to remember and apply these differences to use Domain-Driven Design effectively.

Whether you are just stepping into the world of DDD or are an expert, revisiting these basic building blocks helps in constructing a robust domain model, thereby leading to better software design and architecture.

Remember, the ultimate aim of DDD is to create a ubiquitous language between the domain experts and the software developers. Entities and value objects play an indispensable role in achieving this, making them central to DDD’s philosophy.

As you continue to explore and implement DDD in your projects, keep revisiting these foundational blocks. They not only guide the correct design but also ensure that the domain remains at the heart of your software solutions. Happy coding!


Posted

in

by

Tags:

Comments

Leave a Reply

Your email address will not be published. Required fields are marked *