Cinco niveles de inmersión en un sistema complejo

¿Cómo entiendes la estructura de un sistema cuando abres un proyecto nuevo por primera vez — o vuelves a tu propio código unos meses después?

En ambos casos, un desarrollador tiene que construir el mismo mapa interno: averiguar dónde se divide la responsabilidad, qué capacidades existen y cómo se conectan con la implementación concreta. La experiencia ayuda a encontrar más rápido los principales puntos de anclaje, pero cuando no están expresados en la propia arquitectura — o cambian de proyecto a proyecto y de módulo a módulo — hay que reinventar la vía de entrada cada vez.

AOA saca esa vía de entrada de la cabeza de un desarrollador experimentado y la lleva a la estructura misma del sistema. De proyecto a proyecto mantiene los mismos cinco pilares: Domain, Action, Contract, Pipeline y Code. Cada pilar se convierte en su propio nivel de inmersión y, juntos, trazan un recorrido secuencial desde la visión general hasta el comportamiento concreto.

Un desarrollador avanza por estos niveles como si hiciera zoom sobre un mapa paso a paso: primero comprende el sistema como un todo y luego añade detalle. Cada nivel da una respuesta completa a su propia pregunta y no requiere saber de antemano lo que hay más profundo.

Documentación ↗

Cuando todos los niveles de contexto se mezclan

¿Qué te impide ver la visión general cuando cada detalle está disponible a la vez?

Un repositorio ordinario no separa la información por escala: áreas de negocio, operaciones, modelos de datos, orden de llamadas, infraestructura y líneas de código individuales se presentan todas a la vez. La memoria de trabajo tiene que retener hechos de distintos niveles y reconstruir por sí sola las relaciones entre ellos. La atención cambia constantemente entre la estructura del sistema y los detalles locales, de modo que la comprensión se desmorona rápidamente tras una pausa.

Cómo afecta al trabajo la falta de una visión general

Abajo, el mismo conjunto de información se muestra en dos estados: a la izquierda está mezclado, a la derecha está organizado en cinco preguntas secuenciales. Empecemos por la primera: ¿dónde vive la responsabilidad en el sistema?

base de código plano · nuevos hechos sin marco
orders.pyBillingServiceapi.pycreate_orderreserve_stockOrderModelsend_mailutils.pydb.py
cinco niveles · el contexto se construye en secuencia
01Domaindonde vive la responsabilidad
02Actionqué puede hacer el sistema
03Contractlo que la operación requiere y promete
04Pipelinecómo se desarrolla el escenario
05Codedonde vive el comportamiento concreto
Documentación completa ↗

Primero, un mapa de responsabilidades

¿Dónde vive la responsabilidad?

El primer pilar es un mapa de responsabilidad. Reúne controladores, servicios, modelos de datos, adaptadores de infraestructura y cadenas de llamadas en torno a las grandes áreas del sistema — un armazón al que después se pueden vincular operaciones, datos y código.

Los dominios dividen el sistema por áreas estables de responsabilidad, no por capas técnicas: pedidos, pagos, comunicación y analítica. En lugar de cientos de archivos aparentemente equivalentes aparece una primera topología comprensible.

Qué aporta el mapa de dominios

En el diagrama, cuatro dominios forman el nivel superior de la aplicación. A partir de aquí desplegaremos StoreDomain en secuencia: primero sus Actions, luego el contrato de una operación, su escenario y su implementación concreta.

4 × ActionStoreDomain

Posee el carrito, el pedido y el traspaso a la entrega.

3 × ActionBillingDomain

Posee pagos, facturas y reembolsos.

3 × ActionMessagingDomain

Posee comunicación con el cliente y webhooks.

3 × ActionAnalyticsDomain

Posee eventos, mercados e informes comerciales.

Documentación completa ↗

El mapa se convierte en un catálogo de capacidades.

¿Qué puede hacer el sistema?

El mapa de dominios muestra dónde reside la responsabilidad, pero aún no dice qué puede hacer el sistema. Para responder a eso, cada dominio se abre en un catálogo de Action, capacidades con nombre que proporciona al resto del sistema.

Un Action es una capacidad pública y nombrada de un dominio. El catálogo no muestra funciones internas, sino las operaciones que un dominio ofrece al resto del sistema, junto con las relaciones dirigidas entre ellas. Cada Action se convierte en un único punto de invocación: quien lo invoca elige por significado la capacidad que necesita, en lugar de ensamblar la operación a partir de métodos separados.

Qué aporta el catálogo de Actions

El diagrama destaca CreateOrderAction, el Action que inicia la creación de un pedido. A su alrededor aparecen los demás Actions de StoreDomain y las relaciones entre ellos. Una operación puede depender de otra, pero las dependencias tienen dirección y nunca se cierran en un ciclo. En este nivel solo vemos los nombres de los Actions y sus relaciones; la entrada, el resultado, el escenario y el código se revelarán después.

StoreDomaincapacidades ejecutables con nombre
CreateOrderAction

Crear un pedido: validar, reservar y cargar

→ ChargePaymentAction

GetOrderAction

Leer el estado actual de un pedido

CancelOrderAction

Cancelar un pedido e iniciar un reembolso

→ RefundPaymentAction

ShipOrderAction

Enviar un pedido confirmado a reparto

Documentación completa ↗

Fijamos la frontera de la operación

¿Qué requiere y promete la operación?

La capacidad ya tiene un nombre y un lugar en el sistema. Para usarlo como caja negra, necesitamos saber exactamente qué datos acepta y qué resultado promete devolver.

Un Contract hace que el límite Action sea tipificado y no sea ambiguo. Params describe todo lo que acepta la operación; Result describe todo lo que debe devolver. El código de llamada depende de esa promesa, no de la forma interna del Action. La implementación puede cambiar siempre que la entrada, la salida y el comportamiento observable sigan siendo compatibles.

Qué aporta el contrato del Action

El familiar CreateOrderAction ahora tiene una firma completa: CreateOrderParams a la izquierda y CreateOrderResult a la derecha. Los campos ya no existen como modelos de datos abstractos; pertenecen a una operación específica con un lugar y propósito conocidos. El interior del Action todavía no es necesario para poder utilizarlo.

CreateOrderParams
customer_id: str
items: list[Item]
currency: str
CreateOrderAction

Coordinar la creación de orden detrás de un límite declarado

Params → Result
CreateOrderResult
order_id: str
payment_id: str
status: OrderStatus
Documentación completa ↗

Desplegamos la operación como escenario

¿Cómo se desarrolla el escenario?

El contrato fija el principio y el fin de una operación, pero no muestra cómo uno se convierte en el otro. Para ver eso, desplegamos el flujo interno del escenario empresarial.

Un Pipeline hace que el escenario principal del negocio sea lineal y visible: validar la entrada, reservar inventario, cobrar el pago y ensamblar el resultado. Eso no quiere decir que no existan errores, ramificaciones y compensaciones; reciben un lugar explícito en relación con la línea principal. Cada paso también tiene un contrato local: declara lo que lee de Params, State y Context, y lo que agrega a State para etapas posteriores.

Qué aporta el escenario de la operación

El dominio le dio un lugar al escenario, al Action un nombre y al Contract un comienzo y un resultado prometido. El Pipeline agrega orden causal y límites internos. El paso reserve_inventory se aísla como un cambio de estado distinto: recibe datos ya validados y deja reservation_id para que continúe el escenario.

regularvalidatevalidated_items
regularreserve_inventoryreservation_id
regularcharge_paymentpayment_id
summarycreate_resultOrderResult
Documentación completa ↗

Sólo ahora, el comportamiento concreto

¿Dónde vive el comportamiento concreto?

El Pipeline localiza el comportamiento requerido con precisión y reduce drásticamente el área de búsqueda. Ahora podemos abrir no todo el repositorio o incluso todo el Action, sino un paso cuyo propósito, entrada y salida ya conocemos.

Code es el quinto nivel no porque los detalles no sean importantes, sino porque ahora están rodeados de significado. Sabemos qué dominio posee el comportamiento, qué capacidad implementa Action, qué promete Contract y dónde se ubica reserve_inventory en Pipeline. La implementación se lee como una respuesta local a una tarea concreta en lugar de una entrada a una investigación interminable del repositorio.

Qué aporta aislar el comportamiento en un solo paso

Cuando abrimos reserve_inventory, ya conocemos su intención, entradas y ubicación en el escenario. El principio sigue siendo el mismo: los efectos externos deben pasar a través de los Resource declarados y los Action públicos. La gramática arquitectónica hace que las desviaciones sean visibles y verificables sin pretender que el texto descriptivo por sí solo haga que cualquier derivación sea físicamente imposible.

store/actions/create_order.py
@regular_aspect("Reserve inventory")@result_string("reservation_id", required=True)@context_requires("user.tenant_id", "env.inventory_region")async def reserve_inventory_aspect(    self, params, state, box, connections, ctx):    inventory = box.resolve(InventoryResource)    reservation_id = await inventory.reserve(        tenant_id=ctx.get("user.tenant_id"),        region=ctx.get("env.inventory_region"),        items=state["validated_items"],    )    return {"reservation_id": reservation_id}
$ uv run python store/actions/create_order.py
salida
Documentación completa ↗

Los cinco niveles no ocultan el código ni reducen el sistema a un diagrama. Conservan el recorrido desde el modelo general hasta un detalle concreto. Quien acaba de llegar construye el contexto de forma progresiva; el arquitecto gestiona la gramática y las fronteras en lugar de cada archivo; y el agente de IA realiza trabajo local dentro de reglas declaradas: las personas diseñan el lenguaje del sistema y la máquina actúa en ese lenguaje.

Cinco niveles de inmersión en un sistema complejo · aoa.run