0Pricing
PHP Academy · Lección

Agregados, repositorios y fábricas

Proteja las invariantes mediante agregados y persístalos correctamente.

Agregados, repositorios y fábricas es una lección gratuita de PHP Academy en CoddyKit. Esta es la lección 2 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.

Protección de invariantes

Una vez que tiene entidades y objetos de valor, necesita patrones para mantener consistentes los grupos que forman y para persistirlos correctamente. DDD responde con tres patrones tácticos: el Agregado (una frontera de consistencia), el Repositorio (una abstracción de persistencia similar a una colección) y la Fábrica (para la construcción compleja). Esta lección muestra cómo encajan en PHP.

Qué es un agregado

Un Agregado es un grupo de entidades y objetos de valor que se trata como una única unidad para los cambios de datos. Una entidad es la Raíz del agregado: el único miembro del que el código externo puede conservar una referencia. Todas las modificaciones pasan por la raíz, que hace cumplir las invariantes del agregado. El agregado también es la frontera transaccional: se carga y se guarda de forma atómica.

La raíz protege el conjunto

Quienes llaman desde fuera nunca acceden directamente a los miembros internos. Para añadir una línea de pedido, se llama a un método de la raíz, que valida y mantiene la coherencia (totales, límites). Así, las invariantes permanecen en un solo lugar.

<?php
final class LineItem {
    public function __construct(
        public readonly string $sku,
        public readonly int $qty,
        public readonly int $unitCents
    ) {}
    public function subtotal(): int { return $this->qty * $this->unitCents; }
}

final class Order { // Aggregate Root
    /** @var LineItem[] */
    private array $items = [];
    public function __construct(public readonly string $id) {}

    public function addItem(string $sku, int $qty, int $unitCents): void {
        if ($qty < 1) { throw new DomainException('qty must be >= 1'); }
        $this->items[] = new LineItem($sku, $qty, $unitCents);
    }
    public function total(): int {
        return array_sum(array_map(fn(LineItem $i) => $i->subtotal(), $this->items));
    }
}
$o = new Order('o1');
$o->addItem('A', 2, 500);
$o->addItem('B', 1, 300);
echo $o->total(), PHP_EOL; // 1300

Diseño de agregados pequeños

Un error frecuente es hacer los agregados demasiado grandes (un Order que también posee todo el grafo de Customer). Reglas generales:

  • Mantenga los agregados pequeños; referencie otros agregados por id, en lugar de conservar el objeto.
  • Una transacción debería modificar un solo agregado; coordine varios agregados mediante eventos de dominio.
  • Las invariantes que deben cumplirse siempre definen la frontera.

Referencia por id

El pedido almacena un objeto de valor customerId, no una entidad Customer. Esto mantiene estrecha la frontera de consistencia y evita cargar grafos de objetos enormes. La consistencia entre agregados pasa a ser eventual y se gestiona mediante eventos, en lugar de una única transacción gigantesca.

<?php
final class CustomerId {
    public function __construct(public readonly string $value) {}
}
final class Order {
    public function __construct(
        public readonly string $id,
        public readonly CustomerId $customerId // reference, not object
    ) {}
}
$order = new Order('o1', new CustomerId('cus_99'));
echo $order->customerId->value, PHP_EOL;

El contrato del repositorio

Un Repositorio ofrece la ilusión de una colección en memoria de raíces de agregado. El dominio depende únicamente de la interfaz; la implementación (Doctrine, PDO o memoria) reside en la capa de infraestructura. Los repositorios trabajan con agregados completos, nunca con filas parciales.

<?php
interface OrderRepository {
    public function ofId(string $id): ?Order;
    public function save(Order $order): void;
    public function nextIdentity(): string;
}

Una implementación en memoria

Un repositorio en memoria es muy valioso para realizar pruebas unitarias rápidas sin base de datos. Como el dominio depende de la interfaz, puede intercambiar las implementaciones libremente (la inversión de dependencias en acción).

<?php
interface OrderRepository {
    public function ofId(string $id): ?object;
    public function save(object $order): void;
    public function nextIdentity(): string;
}
final class Order { public function __construct(public readonly string $id) {} }

final class InMemoryOrderRepository implements OrderRepository {
    private array $store = [];
    public function ofId(string $id): ?object { return $this->store[$id] ?? null; }
    public function save(object $order): void { $this->store[$order->id] = $order; }
    public function nextIdentity(): string { return 'o_' . bin2hex(random_bytes(4)); }
}
$repo = new InMemoryOrderRepository();
$repo->save(new Order('o1'));
var_dump($repo->ofId('o1') !== null);

Un repositorio no es un DAO

Un repositorio no es un DAO CRUD genérico. Expone consultas con significado para el dominio (findOverdueOrders()) y reconstruye agregados completos con sus invariantes intactas. Oculta deliberadamente los detalles de SQL y del ORM para que el dominio sea independiente de la persistencia. Evite filtrar generadores de consultas o métodos genéricos como save($anyEntity) al dominio.

Fábricas para creaciones complejas

Cuando construir un agregado implica lógica real —generar la identidad, ensamblar objetos de valor y hacer cumplir las invariantes de creación—, trasládela a una Fábrica (una clase dedicada o un constructor estático con nombre). Así, el constructor de la entidad se mantiene sencillo y las reglas de creación válida se centralizan.

<?php
final class Order {
    private function __construct(
        public readonly string $id,
        public readonly string $customerId
    ) {}
    public static function place(string $customerId): self {
        if ($customerId === '') { throw new DomainException('customer required'); }
        return new self('o_' . bin2hex(random_bytes(4)), $customerId);
    }
}
$order = Order::place('cus_1');
echo $order->id, PHP_EOL;

Cómo colaboran

El flujo habitual en un servicio de aplicación:

  • Una Fábrica (o un constructor con nombre) crea un agregado válido.
  • Los métodos de la raíz del agregado hacen cumplir las invariantes durante su uso.
  • Un Repositorio persiste y posteriormente reconstruye el agregado completo.

El servicio de aplicación coordina estas operaciones dentro de una transacción por agregado y depende únicamente de interfaces.

Cómo hacer cumplir una invariante de todo el agregado

El verdadero valor de la raíz está en hacer cumplir las invariantes que abarcan a sus miembros. En este caso, el pedido rechaza una línea si esta hiciera que el total superase un límite de crédito, una regla que ninguna LineItem podría hacer cumplir por sí sola. Como todos los cambios pasan por la raíz, la regla nunca se puede eludir.

<?php
final class Order {
    private array $items = [];
    public function __construct(
        public readonly string $id,
        private int $creditLimitCents
    ) {}
    public function addItem(int $cents): void {
        if ($this->total() + $cents > $this->creditLimitCents) {
            throw new DomainException('Exceeds credit limit');
        }
        $this->items[] = $cents;
    }
    public function total(): int { return array_sum($this->items); }
}
$o = new Order('o1', 1000);
$o->addItem(600);
try { $o->addItem(600); } catch (DomainException $e) { echo $e->getMessage(), PHP_EOL; }
echo $o->total(), PHP_EOL; // 600

Comprobación rápida

Diseño de agregados.

Resumen

Ha aprendido a proteger las invariantes y a persistir correctamente. Los Agregados forman una frontera de consistencia y transaccional, se modifican únicamente a través de su raíz y se mantienen pequeños al referenciar otros agregados por id. Los Repositorios presentan las raíces de agregado como una colección detrás de una interfaz de dominio, ocultan el ORM y SQL y permiten usar dobles de prueba en memoria. Las Fábricas centralizan la creación compleja que hace cumplir las invariantes. En conjunto, mantienen coherente el modelo de dominio e independiente de la persistencia.

Preguntas frecuentes

¿La lección «Agregados, repositorios y fábricas» es gratis?

Sí — el texto completo de «Agregados, repositorios y fábricas» 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 «Agregados, repositorios y fábricas»?

Proteja las invariantes mediante agregados y persístalos correctamente. 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 2 de 4.

¿Cuánto tiempo toma la lección «Agregados, repositorios y fábricas»?

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

  1. Bloques de construcción de DDD: entidades y objetos de valor
  2. Agregados, repositorios y fábricas
  3. Eventos y servicios de dominio
  4. Contextos delimitados y mapeo de contextos
← Volver a PHP Academy