Dominio y Subdominios
No toda la lógica de negocio vale lo mismo: invierte donde el dominio es core.
El dominio es el área de conocimiento y actividad que el software pretende automatizar o apoyar. No es la base de datos, no es la API, no es el framework: es el problema de negocio en sí mismo, con todas sus reglas, restricciones y excepciones. Comprender el dominio con precisión es el trabajo principal del equipo de desarrollo en DDD, y esa comprensión debe estar capturada en el modelo.
Los dominios complejos no son monolíticos. Se dividen en subdominios: áreas más pequeñas y cohesionadas, cada una con su propio lenguaje, sus propias reglas y sus propios expertos. DDD clasifica los subdominios en tres categorías según su valor estratégico: core, supporting y generic. Esta clasificación no es técnica sino de negocio, y determina dónde el equipo debe concentrar su energía de diseño.
El subdominio core (o núcleo) es la razón de existir de la organización: la diferenciación competitiva, el conocimiento único que no tiene ningún competidor. Aquí es donde DDD, el diseño cuidadoso y los mejores desarrolladores del equipo deben invertirse. El código del subdominio core debe ser el más expresivo, el más testado y el más fácil de cambiar. Nunca se reemplaza por un paquete de terceros porque no hay paquete que codifique esa ventaja competitiva.
El subdominio supporting (o de soporte) es necesario para que el core funcione, pero no es diferenciador por sí solo. Tiene reglas de negocio propias y merece diseño cuidadoso, pero no al mismo nivel que el core. A menudo puede construirse con patrones más simples o con menor inversión en DDD táctico. Si se puede externalizar sin perder ventaja competitiva, es una señal de que es supporting o generic.
El subdominio generic es el que muchas organizaciones necesitan de la misma manera: autenticación, notificaciones, gestión de archivos, pagos estándar. No hay ventaja competitiva en construirlo desde cero. La decisión correcta suele ser comprar un producto existente, usar un servicio en la nube o adoptar una librería establecida. Invertir tiempo de ingeniería de primer nivel en un subdominio generic es un desperdicio estratégico.
La clasificación de subdominios no es permanente. Un subdominio puede comenzar como generic y volverse core si la organización encuentra diferenciación en él, o puede comenzar como core y volverse commodity con el tiempo. El equipo debe revisar periódicamente esta clasificación y ajustar la inversión de diseño en consecuencia.
# Project layout reflecting subdomain classification # Each subdomain maps to a Bounded Context (or more). # src/<project>/contexts/ # ├── <core_context>/ CORE — full DDD tactical design # │ ├── domain/ # │ │ ├── model/ # │ │ │ ├── <aggregate>.py # Rich model, invariants # │ │ │ └── <value_object>.py # Immutable, self-validating # │ │ ├── events/ # │ │ │ └── <something_happened>.py # Domain Events # │ │ └── repositories/ # │ │ └── <aggregate>_repository.py # Port (ABC) # │ └── application/ # │ └── commands/<do_something>/ # │ ├── command.py # │ └── handler.py # │ # ├── <supporting_context>/ SUPPORTING — simpler design ok # │ ├── domain/ # │ │ └── model/ # │ │ └── <supporting_entity>.py # │ └── application/ # │ # └── <generic_context>/ GENERIC — use existing solution # └── infrastructure/ # └── <third_party>_adapter.py # Wrap the external service # Example: identifying subdomain type # Ask: 'If a competitor copied this exact logic, would they gain advantage?' # YES → CORE (build, invest, protect) # MAYBE→ SUPPORTING (build simply or buy) # NO → GENERIC (buy / use open-source)
Debugging lab
Detecta y corrige el error o la violación de diseño.
- 1.3.5.1
# Treating a generic subdomain with full DDD tactical design class EmailNotification: def __init__(self, recipient: EmailAddress, template: NotificationTemplate): ... def send(self) -> None: self._validate_deliverability() self._events.append(EmailSent(recipient=self.recipient))
- 1.3.5.2
# Core business logic placed in a 'utils' module shared across all contexts from shared.utils import calculate_<core_metric> class <CoreAggregate>: def evaluate(self) -> <Metric>: return calculate_<core_metric>(self._data)
- 1.3.5.3
# Supporting subdomain modeled as a full aggregate with events from contexts.reporting.domain.model import Report report = Report.create(<data>) report.generate() report.publish() events = report.pull_events() # ReportCreated, ReportGenerated, ReportPublished
- 1.3.5.4
# Core subdomain replaced by a third-party library from thirdparty_rules_engine import evaluate_policy class <CoreAggregate>: def apply_rules(self) -> bool: return evaluate_policy(self._data, rules=EXTERNAL_RULES)
- 1.3.5.5
# Subdomain classification ignored: same design depth for everything from contexts.auth.domain.model import AuthUser # Generic from contexts.core_process.domain.model import <CoreEntity> # Core # Both modeled with identical DDD tactical depth