ADR-0011 · Descomposición de servicios V1 (10 bounded contexts)¶
Revisión 2026-05-13:
connectors-socialsuma la 5ª strategy V1notifications-internaltras detectarse el canal en el legacy productivo (verdocs/discovery/08-legacy-delta.md§5 y §6.1, y ADR-0007). No cambia el conteo de servicios (sigue siendo 10 V1 + 1 stub V2);notifications-internales una strategy adicional dentro del mismo Lambdaconnectors-social.
- Status: Accepted
- Date: 2026-04-30
- Deciders: Cristian Fernández (Zerviz Group)
- Related: ADR-0001..0010,
docs/REPO_MAP.md.
Context¶
El monolito legacy concentra 30+ rutas en una sola Lambda. ADR-001 heredado y playbook §1.3 piden 6–10 servicios para V1 con bounded contexts DDD claros. Este ADR consolida la decomposición.
Decision¶
10 servicios V1, 1 stub V2 placeholder. Cada uno = un bounded context, una carpeta services/<name>/, su propia API GW HTTP API si expone HTTP, su propio role IAM y sus propios datastores (asignados en ADR-0003).
Servicios¶
| # | Servicio | Contexto DDD | Aggregate(s) | Owns | Eventos publicados | Eventos consumidos |
|---|---|---|---|---|---|---|
| 1 | tenant-mgmt |
Plataforma | Tenant, ConnectorRegistration, FeatureFlag | RDS ze_control_plane, DDB ze-tenants |
TenantCreated, TenantSuspended, ConnectorEnabled, ConnectorDisabled |
— |
| 2 | auth-bff |
Plataforma | Session | Cognito triggers | UserAuthenticated, UserSignedOut |
TenantCreated |
| 3 | inbound-router |
Conversation | InboundMessage | DDB ze-dedup |
Conversation.MessageReceived |
— |
| 4 | flow-engine |
Conversation | ConversationFlow, FlowStep | DDB ze-flow-state (+ Streams) |
Conversation.FlowStepCompleted, Conversation.FlowCompleted, Conversation.HandoffRequested |
Conversation.MessageReceived |
| 5 | connector-orchestrator |
Channel | OutboundDispatch | SSM (CB), DDB ze-circuit-breaker, DDB ze-outbound-idempotency |
Channel.OutboundDispatched, Channel.OutboundFailed |
Conversation.HandoffRequested, Conversation.FlowStepCompleted, Campaign.MessageReady |
| 6 | connectors-social |
Channel | (strategies: whatsapp-meta-cloud, whatsapp-360dialog, webchat-internal, notifications-internal) |
Secrets Manager (per-tenant) + DDB zen-{env}-notification-flows (para strategy notifications) |
(publica via orchestrator) | (invocado por orchestrator) |
| 7 | connectors-cc |
Channel | Five9Session | DDB ze-five9-sessions |
Channel.AgentAssigned, Channel.ConversationClosed |
Conversation.HandoffRequested |
| 8 | outbound-dispatcher |
Campaign | Campaign, CampaignRun, Template | RDS ze_app.campaigns, DDB ze-outbound-runs, Step Functions Express |
Campaign.Started, Campaign.Completed, Campaign.Failed, Campaign.MessageReady |
Channel.OutboundDispatched, Channel.OutboundFailed |
| 9 | reporting-api |
Reporting | (read-model) | RDS ze_app (read-only) + DDB ze-read-models |
— | (todos los eventos, materializa read-models) |
| 10 | encuesta-service |
Survey | Survey, SurveyResponse | DDB ze-encuesta-state (TTL 30 d), RDS ze_app.survey_responses |
Survey.Started, Survey.Completed, Survey.Abandoned |
Channel.ConversationClosed |
| (stub V2) | voice-bridge |
Voice | — | — | — | — |
Boundaries duros¶
- Sólo
tenant-mgmtescribe enze_control_plane. - Sólo
flow-engineescribe enze-flow-state. - Sólo
connector-orchestratorinvoca a strategies (connectors-social,connectors-cc). - Sólo
outbound-dispatcherorquesta campañas;connector-orchestratorejecuta cada mensaje. reporting-apies read-only sobreze_app. Mutaciones via eventos consumidos para materializar read-models en DDBze-read-models.
Comunicación inter-servicios¶
- Asíncrona por defecto: EventBridge → SQS → consumer (ADR-0004).
- Síncrona sólo cuando hay request-response semántico:
auth-bff→tenant-mgmt(lookup tenant en pre-token-generation).flow-engine→connector-orchestrator(request-reply para ejecutar paso bot que requiere envío inmediato).- SPA →
tenant-mgmt,flow-engine(admin),reporting-api(queries). - Nunca un servicio lee directo la base de datos de otro.
Convención de paths HTTP por servicio¶
tenant-mgmt:/tenants/*,/connectors/*,/feature-flags/*(admin).auth-bff:/auth/login,/auth/refresh,/auth/logout,/auth/me.inbound-router:/webhook/{provider}(sin auth JWT, ver ADR-0005).flow-engine:/flows/*,/flows/{flowId}/runs/*.connector-orchestrator:/orchestrator/dispatch(interno SigV4).outbound-dispatcher:/campaigns/*,/templates/*.reporting-api:/reports/*.encuesta-service:/surveys/*,/surveys/{surveyId}/responses/*.
Consequences¶
Positive¶
- Bounded contexts claros = ownership clara, blast radius acotado.
- 10 servicios cabe en el ADR-001 heredado y el playbook §1.3.
- Cada servicio puede evolucionar (deprecar, dividir) sin romper a los demás siempre que respete el contrato de eventos.
Negative¶
- 10 servicios > N microservicios para un equipo pequeño. Mitigado con
services/_templatey subagentes (node-service-builder,connector-strategist, etc.). - Latencia conversacional crece con saltos asíncronos. Mitigación:
flow-engine ↔ connector-orchestratorpuede ser sync request-reply.
Alternatives considered¶
- 5 servicios bigger: rechazado, repite el problema del monolito a menor escala.
- 20 servicios fine-grained: rechazado, ops overhead.
- Modelo monolito modular en una Lambda grande: descartado por ADR-0001.
Tenant-isolation impact¶
- Cada servicio aplica
packages/tenant-contextmiddleware. tenant-mgmtvalida y emite lostenant_idque los demás respetan.
Blast radius¶
- Caída de un servicio no detiene a los demás (eventos quedan en SQS hasta procesar).
- Excepción:
auth-bffcaído ⇒ no hay logins nuevos; tokens vigentes siguen funcionando hasta expirar.
Cost note¶
- 10 Lambdas + 8 HTTP API + ~12 SQS + ~5 DDB tablas + 1 RDS shared. Dev: < USD 30/mes.
ISO 27001 controls touched¶
- A.6.1.2 (segregation of duties): bounded contexts con ownership.
- A.14.2.5 (secure system engineering): hexagonal por servicio.
Sources¶
- ADRs 0001..0010.
ZEngine/ADR-001-ZEngine-Architecture.md(legacy ADR heredado, validación Bounded Contexts).docs/discovery/04-as-is-report.md(pain points del monolito).- Playbook §1.3.