Cinco níveis de imersão em um sistema complexo

Como entender a estrutura de um sistema quando você abre um projeto novo pela primeira vez — ou volta ao seu próprio código alguns meses depois?

Nos dois casos, um desenvolvedor precisa construir o mesmo mapa interno: descobrir onde a responsabilidade é dividida, quais capacidades existem e como elas se conectam à implementação concreta. A experiência ajuda a encontrar mais rápido os principais pontos de referência, mas, quando eles não são expressos na própria arquitetura — ou mudam de projeto para projeto e de módulo para módulo —, o caminho de entrada precisa ser reinventado toda vez.

O AOA tira esse caminho de entrada da cabeça de um desenvolvedor experiente e o coloca na própria estrutura do sistema. De projeto para projeto, ele mantém os mesmos cinco pilares: Domain, Action, Contract, Pipeline e Code. Cada pilar se torna um nível de imersão próprio e, juntos, traçam um caminho sequencial da visão geral até o comportamento concreto.

Um desenvolvedor percorre esses níveis como se estivesse dando zoom em um mapa passo a passo: primeiro compreende o sistema como um todo, depois acrescenta detalhes. Cada nível dá uma resposta completa à sua própria pergunta e não exige saber de antemão o que há mais adiante.

Documentação ↗

Quando todos os níveis de contexto se misturam

O que impede você de ver a visão geral quando todos os detalhes estão disponíveis de uma só vez?

Um repositório comum não separa a informação por escala: apresenta de uma só vez áreas de negócios, operações, modelos de dados, ordem de chamada, infraestrutura e linhas de código individuais. A memória de trabalho precisa reter fatos de níveis diferentes e reconstruir sozinha as relações entre eles. A atenção fica alternando entre a estrutura do sistema e os detalhes locais, de modo que a compreensão se desfaz rapidamente depois de uma pausa.

Como a falta de uma visão geral afeta o trabalho

Abaixo, o mesmo conjunto de informações é mostrado em dois estados: à esquerda ele aparece misturado; à direita, organizado em cinco perguntas sequenciais. Vamos começar pela primeira: onde mora a responsabilidade no sistema?

base de código plana · novos fatos sem moldura
orders.pyBillingServiceapi.pycreate_orderreserve_stockOrderModelsend_mailutils.pydb.py
cinco níveis · o contexto é construído em sequência
01Domainonde mora a responsabilidade
02Actiono que o sistema pode fazer
03Contracto que a operação exige e promete
04Pipelinecomo o cenário se desenrola
05Codeonde mora o comportamento concreto
Documentação completa ↗

Primeiro, um mapa de responsabilidades

Onde mora a responsabilidade?

O primeiro pilar é um mapa de responsabilidades. Ele reúne controllers, serviços, modelos de dados, adaptadores de infraestrutura e cadeias de chamadas em torno das grandes áreas do sistema — uma moldura à qual operações, dados e código podem então ser ligados.

Os domínios dividem o sistema por áreas estáveis de responsabilidade, não por camadas técnicas: pedidos, pagamentos, comunicação e analytics. No lugar de centenas de arquivos aparentemente equivalentes surge uma primeira topologia compreensível.

O que o mapa de domínios oferece

No diagrama, quatro domínios formam o nível superior do aplicativo. A partir daqui desdobraremos StoreDomain em sequência: primeiro seus Actions, depois um contrato de operação, seu cenário e sua implementação concreta.

4 × ActionStoreDomain

Possui carrinho, pedido e entrega para entrega

3 × ActionBillingDomain

Possui pagamentos, faturas e reembolsos

3 × ActionMessagingDomain

Possui comunicação com o cliente e webhooks

3 × ActionAnalyticsDomain

Possui eventos, mercados e relatórios de negócios

Documentação completa ↗

O mapa se torna um catálogo de capacidades

O que o sistema pode fazer?

O mapa de domínios mostra onde reside a responsabilidade, mas ainda não diz o que o sistema pode fazer. Para responder a isso, cada domínio se abre em um catálogo de Actions – recursos nomeados que ele fornece ao restante do sistema.

Um Action é uma capacidade pública e nomeada de um domínio. O catálogo não mostra funções internas, e sim as operações que um domínio fornece ao restante do sistema, junto com as relações direcionadas entre elas. Cada Action se torna um único ponto de invocação: quem chama escolhe a capacidade de que precisa pelo significado, em vez de montar a operação a partir de métodos separados.

O que o catálogo de Actions oferece

O diagrama destaca CreateOrderAction, o Action que inicia a criação de um pedido. Ao redor dele aparecem os demais Actions de StoreDomain e as relações entre eles. Uma operação pode depender de outra, mas as dependências têm direção e nunca se fecham em um ciclo. Neste nível vemos apenas os nomes dos Actions e suas relações; entrada, resultado, cenário e código serão revelados depois.

StoreDomaincapacidades executáveis ​​nomeadas
CreateOrderAction

Crie um pedido: valide, reserve e carregue

→ ChargePaymentAction

GetOrderAction

Ler o estado atual de um pedido

CancelOrderAction

Cancelar um pedido e iniciar um reembolso

→ RefundPaymentAction

ShipOrderAction

Enviar um pedido confirmado para entrega

Documentação completa ↗

Fixamos a fronteira da operação

O que a operação exige e promete?

A capacidade já tem um nome e um lugar no sistema. Para usá-lo como caixa preta, precisamos saber exatamente quais dados ele aceita e qual resultado promete retornar.

Um Contract torna o limite Action digitado e inequívoco. Params descreve tudo o que a operação aceita; Result descreve tudo que deve retornar. A chamada do código depende dessa promessa, não do formato interno do Action. A implementação pode mudar desde que a entrada, a saída e o comportamento observável permaneçam compatíveis.

O que o contrato do Action oferece

O familiar CreateOrderAction agora tem uma assinatura completa: CreateOrderParams à esquerda e CreateOrderResult à direita. Os campos não existem mais como modelos de dados abstratos; pertencem a uma operação específica com local e finalidade conhecidos. O interior do Action ainda não é necessário para usá-lo.

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

Coordenar a criação de pedidos atrás de um limite declarado

Params → Result
CreateOrderResult
order_id: str
payment_id: str
status: OrderStatus
Documentação completa ↗

Desdobramos a operação em um cenário

Como o cenário se desenrola?

O contrato fixa o início e o fim de uma operação, mas não mostra como um se transforma no outro. Para perceber isso, desdobramos o fluxo interno do cenário de negócios.

Um Pipeline torna linear e visível o cenário principal do negócio: validar o insumo, reservar estoque, cobrar o pagamento e montar o resultado. Isso não significa que não existam erros, desvios e compensações; eles recebem um lugar explícito em relação à linha principal. Cada etapa também possui um contrato local: ele declara o que lê de Params, State e Context e o que adiciona a State para etapas posteriores.

O que o cenário da operação oferece

O domínio deu um lugar ao cenário, ao Action um nome e ao Contract um começo e um resultado prometido. O Pipeline adiciona ordem causal e limites internos. A etapa reserve_inventory é isolada como uma mudança de estado distinta: recebe dados já validados e sai de reservation_id para que o cenário continue.

regularvalidatevalidated_items
regularreserve_inventoryreservation_id
regularcharge_paymentpayment_id
summarycreate_resultOrderResult
Documentação completa ↗

Só agora, o comportamento concreto

Onde mora o comportamento concreto?

O Pipeline localiza o comportamento necessário com precisão e restringe drasticamente a área de pesquisa. Agora podemos abrir não o repositório inteiro ou mesmo o Action inteiro, mas uma etapa cujo propósito, entrada e saída já são conhecidos.

Code é o quinto nível, não porque os detalhes não sejam importantes, mas porque agora estão rodeados de significado. Sabemos qual domínio possui o comportamento, qual capacidade o Action implementa, o que o Contract promete e onde reserve_inventory fica no Pipeline. A implementação é interpretada como uma resposta local a uma tarefa concreta, em vez de uma entrada para uma investigação interminável do repositório.

O que o isolamento do comportamento em uma etapa oferece

Ao abrirmos reserve_inventory, já sabemos sua intenção, entradas e localização no cenário. O princípio permanece o mesmo: os efeitos externos devem passar pelos Resources declarados e pelos Actions públicos. A gramática arquitetônica torna os desvios visíveis e verificáveis ​​sem pretender que o texto descritivo por si só já torne qualquer desvio fisicamente impossível.

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
saída
Documentação completa ↗

Os cinco níveis não escondem o código nem reduzem o sistema a um diagrama. Eles preservam o caminho do modelo geral até um detalhe concreto. Quem está chegando constrói o contexto progressivamente; o arquiteto gerencia a gramática e as fronteiras em vez de cada arquivo; e o agente de IA executa trabalho local dentro de regras declaradas: as pessoas projetam a linguagem do sistema e a máquina atua nessa linguagem.

Cinco níveis de imersão em um sistema complexo · aoa.run