
Aprende DDD Domain-Driven Design parte 2

Te doy la bienvenida a esta segunda parte del tutorial, en ella veremos los dos últimos conceptos que me quedaron sin explicar y veremos las distintas capas a la hora de organizar el código con DDD.
Si te perdiste el primer tutorial, aquí tienes el enlace para que puedas seguirlo :)
https://cosasdedevs.com/posts/aprende-ddd-domain-driven-design-parte-1
📌 8. Contextos Acotados en DDD
En Domain-Driven Design (DDD), los Contextos Acotados (Bounded Contexts) son una forma de dividir un sistema grande en partes más pequeñas y manejables. Cada contexto tiene su propio modelo de dominio, sus propias reglas de negocio y su propia terminología.
Piensa en un Contexto Acotado como una "zona independiente" dentro de una aplicación, donde los términos y reglas tienen un significado específico.
🛒 Ejemplo: Tienda Online
Siguiendo nuestro ejemplo de tienda en línea. Tu aplicación puede tener varios módulos o contextos acotados, como:
1️⃣ Catálogo de Productos 🏷️
- Maneja los productos, precios y descripciones.
- Contiene el Producto con atributos como nombre, precio y stock.
2️⃣ Carrito de Compras 🛍️
- Se encarga de añadir productos al carrito y calcular el total.
- Contiene
ProductoDelCarrito
, que guarda productos con la cantidad seleccionada.
3️⃣ Pedidos y Pagos 💳
- Procesa órdenes y pagos.
- Contiene Pedido y Pagos, con estados de pago.
4️⃣ Usuarios y Autenticación 👤
- Gestiona clientes, direcciones y métodos de pago.
- Contiene User, con sus atributos.
Cada contexto acotado tiene sus propias entidades, servicios y repositorios, y aunque pueden compartir información, se comunican entre sí de forma controlada.
🎯 Ejemplo de Separación de Contextos
Si tenemos un producto en el Catálogo de Productos, su estructura puede verse así:
<?php
namespace App\Domain\Product;
class Product
{
private string $name;
private float $price;
private int $stock;
}
Pero en el Carrito de Compras, un producto no es lo mismo. Aquí se representa con un CartItem
, porque lo que importa es la cantidad y el subtotal:
<?php
namespace App\Domain\Cart;
class CartItem
{
private Product $product;
private int $quantity;
public function getSubtotal(): float
{
return $this->product->getPrice() * $this->quantity;
}
}
💡 Diferencia clave:
- En Catálogo, el producto es algo general con stock.
- En Carrito, el producto es un ítem con una cantidad específica.
Si mezcláramos estos dos modelos en una sola clase, el código se volvería un caos y difícil de mantener.
📌 9. Mapeo de Contextos
El Mapeo de Contextos (Context Mapping) en Domain-Driven Design (DDD) es el proceso de definir cómo se relacionan los Contextos Acotados dentro de un sistema. Esto es clave porque, aunque cada contexto es independiente, muchas veces necesitan comunicarse y compartir datos.
El objetivo del mapeo de contextos es definir claramente las relaciones entre los diferentes contextos y establecer reglas para su interacción.
🛒 Ejemplo: Tienda Online
Siguiendo nuestro ejemplo de tienda en línea, tenemos estos Contextos Acotados:
1️⃣ Catálogo de Productos 🏷️
- Maneja la información de los productos.
- No le interesa quién compra los productos ni cómo se pagan.
2️⃣ Carrito de Compras 🛍️
- Permite a los usuarios añadir productos y calcular precios.
- Necesita información del Catálogo (nombre, precio, stock).
3️⃣ Pedidos y Pagos 💳
- Procesa compras y pagos.
- Necesita saber qué productos fueron comprados (pero no todos los detalles del catálogo).
4️⃣ Usuarios y Autenticación 👤
- Gestiona clientes, direcciones y métodos de pago.
- Se comunica con Pedidos para saber quién hizo la compra.
🔗 Tipos de Relaciones entre Contextos
Dependiendo de cómo interactúan estos contextos, podemos tener distintos tipos de relaciones en el mapeo de contextos:
🔹 1. Relación Cliente-Proveedor (Upstream-Downstream)
Un contexto (Proveedor / Upstream) controla los datos y otro (Cliente / Downstream) depende de ellos.
Ejemplo:
- El Catálogo de Productos es el Proveedor de información.
- El Carrito de Compras es el Cliente, ya que necesita obtener datos del catálogo.
- Si cambia algo en el Catálogo, el Carrito debe adaptarse.
🔹 2. Relación Compartida (Conformista)
Un contexto se adapta a otro sin poder modificarlo, aceptando su estructura.
Ejemplo:
- Pedidos y Pagos necesita los productos del Catálogo.
- En lugar de definir su propio formato, usa el formato del Catálogo sin cambios.
🔹 3. Relación Anticorrupción (Anti-Corruption Layer)
Cuando un contexto no quiere depender directamente de otro, usa una capa intermedia que traduce los datos.
Ejemplo:
- El Carrito de Compras obtiene productos del Catálogo, pero quiere evitar acoplarse a su modelo.
- Implementa una Capa Anticorrupción que transforma los datos de productos en su propio formato.
📌 10. Capas de la metodología DDD

En DDD (Domain-Driven Design), organizamos el código en cuatro capas para que sea más fácil de manejar y mantener. Cada capa tiene una responsabilidad clara y evita que el código esté mezclado.
🏗 1. Capa de Dominio (Domain) – 🧠 El Corazón del Negocio
💡 ¿Qué es?
Básicamente, todo lo que hemos visto hasta ahora, pero haré un pequeño resumen con código. Aquí están las reglas y conceptos principales de la tienda. Es el centro del negocio, sin depender de bases de datos, frameworks o APIs externas.
💻 Ejemplo en la tienda online:
Un producto no puede tener stock negativo y debe tener un precio mayor a 0.
namespace App\Domain\Product;
class Product
{
private int $id;
private string $name;
private float $price;
private int $stock;
public function __construct(string $name, float $price, int $stock)
{
if ($price <= 0) {
throw new \DomainException("El precio debe ser mayor a 0.");
}
if ($stock < 0) {
throw new \DomainException("El stock no puede ser negativo.");
}
$this->name = $name;
$this->price = $price;
$this->stock = $stock;
}
public function getName(): string { return $this->name; }
public function getPrice(): float { return $this->price; }
public function getStock(): int { return $this->stock; }
}
🎯 Clave: Aquí definimos qué es un Producto sin preocuparnos de cómo se guarda o muestra.
🚀 2. Capa de Aplicación (Application) – 📢 Coordina Acciones
💡 ¿Qué es?
Aquí tenemos la lógica de casos de uso como insertar productos, actualizarlos, listarlos, etc. No contiene reglas de negocio, solo coordina lo que debe pasar en el sistema.
💻 Ejemplo: Un servicio que permite crear productos y obtenerlos.
namespace App\Application\Product;
use App\Domain\Product\Product;
use App\Domain\Product\ProductRepositoryInterface;
class ProductService
{
private ProductRepositoryInterface $productRepository;
public function __construct(ProductRepositoryInterface $productRepository)
{
$this->productRepository = $productRepository;
}
public function createProduct(string $name, float $price, int $stock): Product
{
$product = new Product($name, $price, $stock);
$this->productRepository->save($product);
return $product;
}
public function getAllProducts(): array
{
return $this->productRepository->findAll();
}
}
🎯 Clave: ProductService
usa el dominio, pero no se preocupa de la base de datos ni de cómo se muestra la información.
🔌 3. Capa de Infraestructura (Infrastructure) – 💾 Conexión con el Mundo Real
💡 ¿Qué es?
Aquí manejamos la base de datos, APIs externas y todo lo que conecta nuestro sistema con el exterior.
💻 Ejemplo: Un repositorio que usa Doctrine para guardar productos en la base de datos.
namespace App\Infrastructure\Persistence;
use App\Domain\Product\Product;
use App\Domain\Product\ProductRepositoryInterface;
use Doctrine\ORM\EntityManagerInterface;
class DoctrineProductRepository implements ProductRepositoryInterface
{
private EntityManagerInterface $entityManager;
public function __construct(EntityManagerInterface $entityManager)
{
$this->entityManager = $entityManager;
}
public function save(Product $product): void
{
$this->entityManager->persist($product);
$this->entityManager->flush();
}
public function findAll(): array
{
return $this->entityManager->getRepository(Product::class)->findAll();
}
}
🎯 Clave: Esta capa habla con la base de datos o con APIs externas, pero la aplicación no depende de esto directamente.
🌍 4. Capa de Interfaz de Usuario (Interface) – 🎨 La Puerta de Entrada
💡 ¿Qué es?
Aquí tenemos controladores, endpoints y vistas. Es la parte que el usuario ve o con la que interactúa.
💻 Ejemplo: Un controlador que expone una API para obtener productos.
namespace App\Interface\Http\Controller;
use App\Application\Product\ProductService;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\Routing\Annotation\Route;
class ProductController extends AbstractController
{
private ProductService $productService;
public function __construct(ProductService $productService)
{
$this->productService = $productService;
}
#[Route('/products', methods: ['GET'])]
public function getProducts(): JsonResponse
{
$products = $this->productService->getAllProducts();
return $this->json($products);
}
}
🎯 Clave: ProductController
muestra los productos, pero no contiene lógica de negocio.
Con esto, podemos dar por finalizada toda la parte teórica del tutorial. En próximos tutoriales veremos como aplicar esta metodología en un proyecto con Symfony 7 así que estate atento a mis redes sociales si no quieres perdértelo 💪.
Espero que este post te ayude y como siempre, te recomiendo seguirme en Twitter para estar al tanto de los nuevo contenido. Ahora también puedes seguirme en Instagram donde estoy subiendo tips, tutoriales en vídeo e información sobre herramientas para developers.
Por último os dejo mi guía para aprender a trabajar con APIs donde explico todo el funcionamiento de una API, el protocolo HTTP y veremos como construir una API con arquitectura REST.
Nos leemos 👋.