Value Object
Value Objects are one of the fundamental building blocks of Domain-Driven Design (DDD).
They encapsulate primitive values and enforce related invariants, ensuring data integrity within your domain.
In the DDD book, they are defined as follows:
"An object that represents a descriptive aspect of the domain with no conceptual identity is called a Value Object. Value Objects are instantiated to represent elements of the design that we care about only for what they are, not who or which they are."
-- Eric Evans
Unlike entities, value objects do not have a concept of identity; they only encapsulate and wrap primitive types.
There are three main qualities of Value Objects:
- Immutability: Once created, a value object cannot be modified.
- No Identity: Value objects are identified only by their attributes, not by a unique ID.
- Structural Equality: Two value objects are considered equal if their attributes are the same.
Value Object Implementation
An example of a value object can be found at: Task/src/shared/domain/valueObjects/TitleValueObject.ts
import { ValueObject, ValidationError, OverwriteProtectionBody, shallowEqualObject } from "@codebricks/codebricks-framework";
import { isType } from "is-what";
export class TitleValueObject implements ValueObject {
constructor(readonly _value: string) {
this.validate(_value);
}
@OverwriteProtectionBody(false)
validate(value: string): void {
if (!isType(value, String) || !value.length) {
throw new ValidationError(`TitleValueObject: ${value} is invalid`);
}
}
@OverwriteProtectionBody(false)
equals(other: ValueObject): boolean {
return other && isType(other, TitleValueObject) && shallowEqualObject(this, other);
}
get value(): string {
return this._value;
}
}
Detailed Implementation
_value
Property
The _value
property stores the encapsulated primitive value. It is marked as readonly
to enforce immutability.
validate
Function
The validate
function ensures that the value meets the required invariants. If the value does not meet these criteria, a ValidationError
is thrown.
@OverwriteProtectionBody(false)
validate(value: string): void {
if (!isType(value, String) || !value.length) {
throw new ValidationError(`TitleValueObject: ${value} is invalid`);
}
}
equals
Function
The equals
function checks if another value object is equal to the current one by comparing their values.
@OverwriteProtectionBody(false)
equals(other: ValueObject): boolean {
return other && isType(other, TitleValueObject) && shallowEqualObject(this, other);
}
Comparison to Entities
Value objects differ from entities in that they lack a unique identity and are defined by their attributes. Entities, on the other hand, have a unique identity and their equality is based on this identity.
Best Practices
- Immutability: Ensure value objects are immutable to maintain integrity.
- Validation: Always validate the value within the constructor to enforce invariants.
- Fine Granularity: Stay with your business granularity just because two value objects share a similar pattern does not mean it does not evolve differently.