Bloques de construcción de DDD: entidades y objetos de valor
Modele el dominio con entidades enriquecidas y objetos de valor inmutables.
Bloques de construcción de DDD: entidades y objetos de valor es una lección gratuita de PHP Academy en CoddyKit. Esta es la lección 1 de 4. Puedes leer la lección completa abajo gratuitamente — luego la practicas en el navegador con un editor de código integrado y un tutor de IA 24/7. Forma parte de la ruta de aprendizaje de PHP Academy, y tu progreso se sincroniza en la web y la app de CoddyKit. El curso de PHP Academy incluye 4 lecciones en total.
El núcleo táctico
Los patrones tácticos de Domain-Driven Design le proporcionan un vocabulario para modelar conceptos empresariales en código. Los dos bloques de construcción fundamentales son las Entidades (objetos definidos por su identidad a lo largo del tiempo) y los Objetos de valor (objetos definidos por sus atributos y tratados como inmutables). Comprender correctamente esta distinción determina todo lo demás en la capa de dominio.
Identidad frente a valor
Una Entidad tiene una identidad estable que perdura incluso cuando cambian sus atributos: un Customer sigue siendo el mismo cliente después de mudarse. Un Objeto de valor no tiene identidad; dos objetos de valor con atributos iguales son intercambiables, como dos billetes de 5 $. Pregúntese: "si cambian todos los campos, ¿sigue siendo lo mismo?" Si la respuesta es afirmativa, es una entidad.
Un objeto de valor
Los objetos de valor son inmutables y autovalidables. Las propiedades readonly de PHP 8.1 imponen la inmutabilidad; el constructor protege las invariantes para que nunca pueda existir una instancia no válida.
<?php
final class Email {
public function __construct(public readonly string $value) {
if (!filter_var($value, FILTER_VALIDATE_EMAIL)) {
throw new InvalidArgumentException("Invalid email: $value");
}
}
public function equals(Email $other): bool {
return strtolower($this->value) === strtolower($other->value);
}
}
$a = new Email('Jane@Example.com');
var_dump($a->equals(new Email('jane@example.com'))); // true
Igualdad por valor
Los objetos de valor se comparan mediante igualdad estructural, no por referencia. Proporcione un método explícito equals() en lugar de depender de ==, porque la comparación flexible de PHP tiene reglas sorprendentes. Un objeto de valor Money debe tener en cuenta tanto el importe como la moneda.
<?php
final class Money {
public function __construct(
public readonly int $cents,
public readonly string $currency
) {}
public function equals(Money $o): bool {
return $this->cents === $o->cents && $this->currency === $o->currency;
}
public function add(Money $o): self {
if ($this->currency !== $o->currency) {
throw new DomainException('Currency mismatch');
}
return new self($this->cents + $o->cents, $this->currency);
}
}
$sum = (new Money(500, 'USD'))->add(new Money(250, 'USD'));
echo $sum->cents, PHP_EOL; // 750
Inmutabilidad y métodos «with»
Como los objetos de valor son inmutables, «cambiar» uno significa devolver una nueva instancia. Métodos como add() o withAmount() nunca mutan el objeto; producen un valor nuevo. Esto elimina los errores de aliasing y permite compartir objetos de valor de forma segura por todo el dominio.
<?php
final class DateRange {
public function __construct(
public readonly DateTimeImmutable $start,
public readonly DateTimeImmutable $end
) {
if ($end < $start) { throw new InvalidArgumentException('end before start'); }
}
public function withEnd(DateTimeImmutable $end): self {
return new self($this->start, $end);
}
}
$r = new DateRange(new DateTimeImmutable('2026-01-01'), new DateTimeImmutable('2026-01-10'));
$r2 = $r->withEnd(new DateTimeImmutable('2026-02-01'));
echo $r->end->format('Y-m-d'), ' / ', $r2->end->format('Y-m-d'), PHP_EOL;
Una entidad
Una Entidad se define por su identidad, normalmente un id generado por el dominio, no por un autoincremento de la base de datos. La igualdad compara los ids. Sus atributos pueden cambiar mediante métodos ricos en comportamiento que protegen las invariantes.
<?php
final class Customer {
private Email $email;
public function __construct(
public readonly string $id,
Email $email
) { $this->email = $email; }
public function changeEmail(Email $new): void { $this->email = $new; }
public function email(): Email { return $this->email; }
public function sameIdentityAs(Customer $o): bool { return $this->id === $o->id; }
}
$c = new Customer('cus_1', new Email('a@b.com'));
$c->changeEmail(new Email('c@d.com'));
echo $c->email()->value, PHP_EOL; // c@d.com
Generación de identidad
Prefiera generar la identidad en el dominio (por ejemplo, un UUID) antes de la persistencia, en lugar de esperar a la base de datos. Esto permite construir en memoria una entidad completamente válida, referenciarla desde distintos agregados y realizar pruebas sin una base de datos. El id forma parte del modelo, no es un artefacto de almacenamiento.
Comportamiento rico, no datos anémicos
Un antipatrón habitual es el modelo de dominio anémico: las entidades son conjuntos de getters y setters públicos, mientras toda la lógica reside en clases de «servicio». DDD introduce el comportamiento en la entidad. $order->cancel() encapsula las reglas de cancelación en lugar de permitir que quienes llaman cambien directamente un campo de estado.
<?php
final class Order {
private string $status = 'open';
public function __construct(public readonly string $id) {}
public function cancel(): void {
if ($this->status === 'shipped') {
throw new DomainException('Cannot cancel a shipped order');
}
$this->status = 'cancelled';
}
public function status(): string { return $this->status; }
}
$o = new Order('o1');
$o->cancel();
echo $o->status(), PHP_EOL; // cancelled
Objetos de valor en todas partes
Sustituya la obsesión por los tipos primitivos con objetos de valor. En lugar de pasar string $email, int $cents y string $currency por todo el código, encapsúlelos en Email y Money. Ventajas:
- La validación se realiza una sola vez, al construir el objeto.
- Las reglas de dominio (la coincidencia de monedas) permanecen junto a los datos.
- Las firmas de tipos documentan la intención y evitan confusiones.
Nota sobre el mapeo de persistencia
Los objetos de valor suelen mapearse a columnas embebidas en lugar de tener sus propias tablas (Doctrine #[Embeddable]). Las entidades se mapean a filas identificadas por su identidad. Mantenga las preocupaciones de persistencia fuera de los propios objetos de dominio: el modelo no debería saber que está almacenado en MySQL. Un ORM o un mapeador escrito a mano traduce entre el dominio rico y la base de datos.
Objetos de valor compuestos
Los objetos de valor se pueden componer. Un Address agrupa varias primitivas en un concepto cohesionado, con su propia igualdad y formato. Así, la entidad contiene un único tipo rico en lugar de cinco cadenas sueltas, y las reglas de la dirección permanecen en un solo lugar.
<?php
final class Address {
public function __construct(
public readonly string $street,
public readonly string $city,
public readonly string $postcode
) {
if ($postcode === '') { throw new InvalidArgumentException('postcode required'); }
}
public function equals(Address $o): bool {
return $this->street === $o->street
&& $this->city === $o->city
&& $this->postcode === $o->postcode;
}
public function oneLine(): string {
return "{$this->street}, {$this->city} {$this->postcode}";
}
}
echo (new Address('1 Main St', 'Ankara', '06000'))->oneLine(), PHP_EOL;
Comprobación rápida
¿Entidad u objeto de valor?
Resumen
Ha aprendido los dos bloques de construcción fundamentales de DDD. Las Entidades se definen por una identidad estable, contienen un comportamiento rico y protegen sus invariantes mediante métodos. Los Objetos de valor son inmutables, autovalidables y se comparan por igualdad de atributos, lo que permite sustituir la obsesión por los tipos primitivos. Modelarlos correctamente, con la lógica dentro de los objetos en lugar de conjuntos de datos anémicos, es la base de los agregados, los repositorios y el resto de la capa de dominio.
Preguntas frecuentes
¿La lección «Bloques de construcción de DDD: entidades y objetos de valor» es gratis?
Sí — el texto completo de «Bloques de construcción de DDD: entidades y objetos de valor» es gratis para leer aquí en la web. Para practicarla de forma interactiva (editor de código integrado y tutor de IA 24/7) y desbloquear el resto del curso de PHP Academy, actualiza a CoddyKit PRO. El curso de PHP Academy incluye 4 lecciones en total.
¿Qué aprenderé en «Bloques de construcción de DDD: entidades y objetos de valor»?
Modele el dominio con entidades enriquecidas y objetos de valor inmutables. Practicas PHP Academy con código real que ejecutas directamente en el navegador, y un tutor de IA 24/7 responde tus preguntas mientras trabajas en la lección.
¿Necesito experiencia previa para empezar PHP Academy?
No se requiere experiencia previa. PHP Academy en CoddyKit está estructurado para principiantes hasta estudiantes avanzados, así que puedes empezar aquí o desde el inicio y avanzar a tu ritmo. Esta es la lección 1 de 4.
¿Cuánto tiempo toma la lección «Bloques de construcción de DDD: entidades y objetos de valor»?
La mayoría de las lecciones de CoddyKit toman alrededor de 5–10 minutos. Cada una es compacta e interactiva, así que avanzas constantemente y retomas exactamente por donde dejaste en la web y la app.
¿Puedo escribir y ejecutar código en esta lección de PHP Academy?
Sí. Cada lección de PHP Academy incluye un editor de código integrado, así que escribes y ejecutas código real directamente en tu navegador y obtienes retroalimentación instantánea de IA — sin configuración local necesaria.
Todas las lecciones de este curso
- Bloques de construcción de DDD: entidades y objetos de valor
- Agregados, repositorios y fábricas
- Eventos y servicios de dominio
- Contextos delimitados y mapeo de contextos