advanced · temario
7.7tema 7 de 7

DDD y Microservicios

Bounded context como unidad de despliegue, Context Map patterns y anti-patrones.

El alineamiento natural entre DDD y microservicios: un Bounded Context define una frontera lingüística y de modelo. Un microservicio es una unidad desplegable con su propia base de datos y equipo. Cuando ambos coinciden, cada servicio posee su modelo de dominio, su persistencia y su equipo — el modelo 'tú lo construyes, tú lo operas'. Esta alineación es el ideal, pero alcanzarla requiere conocer bien las fronteras del dominio antes de desplegar.

Los patrones del Context Map se traducen directamente en estrategias de integración entre microservicios. El Anticorruption Layer aísla el modelo propio de contratos externos. El Published Language define integration events o APIs estables como vocabulario compartido entre servicios. El Customer-Supplier establece una relación upstream/downstream con contratos de API explícitos. El Shared Kernel comparte código entre dos servicios relacionados — úsalo con moderación porque aumenta el acoplamiento.

La comunicación entre servicios sigue dos patrones: síncrono (REST/gRPC) para consultas que necesitan respuesta inmediata, y asíncrono (eventos vía broker) para cambios de estado entre contextos. La preferencia por eventos reduce el acoplamiento temporal — los servicios no necesitan estar disponibles simultáneamente — y permite que el sistema tolere fallos parciales mejor que las cadenas síncronas.

El anti-patrón más costoso es dividir demasiado pronto o demasiado finamente. Antes de extraer microservicios, construye un Modular Monolith bien estructurado con fronteras internas claras usando DDD. Un módulo bien delimitado dentro de un monolito puede extraerse a un microservicio cuando el equipo, el despliegue o la escala lo demanden — las fronteras ya están definidas. Hacerlo al revés — microservicios primero, boundaries después — es extremadamente costoso.

Cuándo NO usar microservicios: equipos pequeños, productos en etapa temprana con fronteras de dominio aún inciertas, o cuando el coste operacional supera el beneficio. Un microservicio por entidad es un anti-patrón — crea servicios que nunca son autónomos y que siempre necesitan coordinarse. DDD primero para descubrir las fronteras reales; microservicios después, como consecuencia de esas fronteras.

structure.txt
# <service_a>/src/<service_a>/contexts/<bc_a>/  — Bounded Context A (its own service)
# <service_b>/src/<service_b>/contexts/<bc_b>/  — Bounded Context B (its own service)

# Service A: publishes integration event after domain action
class <AggregateARepository>:
    def save(self, aggregate: <AggregateA>) -> None:
        self._session.merge(<AggregateAORM>.from_domain(aggregate))
        for event in aggregate.pull_events():
            self._outbox.write(event)
        self._session.commit()

# Service B: consumes integration event from Service A
class <SomethingHappened>Consumer:
    def __init__(self, acl: <AnticorruptionLayer>, service: <ServiceBApplicationService>) -> None:
        self._acl = acl
        self._service = service

    def handle(self, raw_event: dict) -> None:
        # ACL translates external contract to Service B's own domain concept
        internal_cmd = self._acl.translate(raw_event)
        self._service.execute(internal_cmd)

# Anticorruption Layer: isolates Service B's model from Service A's vocabulary
class <AnticorruptionLayer>:
    def translate(self, event: dict) -> <InternalCommand>:
        return <InternalCommand>(
            <bc_b_id>=<BcBId>(event['<bc_a_field>']),
            <bc_b_value>=<BcBValue>(event['<some_field>']),
        )

Debugging lab

Detecta y corrige el error o la violación de diseño.

0/5 tests passing0%
  1. 7.7.5.1

    # Bug: Service B directly importing Service A's domain model classes # In Service B's codebase: from service_a.contexts.catalog.domain.model.item import Item # Cross-service import! from service_a.contexts.catalog.domain.model.price import Price # Cross-service import! class ServiceBApplicationService: def handle_item_published(self, event: dict) -> None: # Constructing Service A's domain types in Service B item = Item(id=event["item_id"], price=Price(event["amount"], event["currency"])) self._local_repo.sync_from_external(item)

  2. 7.7.5.2

    # Bug: Service B querying Service A's database table directly via shared SQLAlchemy session class ServiceBQueryService: def get_current_state(self, entity_id: str) -> ServiceBView: # Bug: Service B using Service A's database session and ORM models row = self._service_a_session.query(ServiceAEntity).filter_by(id=entity_id).first() return ServiceBView(id=row.id, value=row.internal_field, status=row.internal_status)

  3. 7.7.5.3

    # Bug: microservice split created for each Entity — UserService, AddressService, PhoneService # UserService: manages User core fields # AddressService: manages user addresses — calls UserService to validate user exists # PhoneService: manages user phones — calls both UserService and AddressService class PhoneService: def add_phone(self, cmd: AddPhoneCommand) -> None: user = self._user_client.get_user(cmd.user_id) # Sync call to UserService address = self._address_client.get_primary_address(cmd.user_id) # Sync call phone = Phone(user_id=cmd.user_id, number=cmd.number) self._phone_repo.save(phone)

  4. 7.7.5.4

    # Bug: two services using a Shared Kernel with a mutable shared domain model that both teams modify # shared_kernel/domain/model/entity.py — owned by both Team A and Team B class SharedEntity: def __init__(self, id: str, value: str, extra_field_team_a: str = None) -> None: self.id = id self.value = value self.extra_field_team_a = extra_field_team_a # Team A added this # Team B wants to add extra_field_team_b — merge conflict inevitable

  5. 7.7.5.5

    # Bug: microservices architecture introduced from day one for a startup MVP with 2 developers # Day 1: 8 microservices, Kubernetes, service mesh, distributed tracing # Contexts are not yet understood — services are split by technical layer # service-api/ — HTTP handlers only # service-business/ — "business logic" # service-users/ — anything user-related # service-data/ — database access layer # service-notifications/ — sends emails # service-files/ — file storage # service-search/ — search # service-scheduler/ — scheduled jobs