Architecture (DDD Light)
DDD Light conventions
Section titled “DDD Light conventions”- VO-first — modela tudo como Value Object. Promove a Entity quando precisa de identidade ao longo do tempo. Promove a Aggregate Root quando um conjunto de entities tem invariantes compartilhados.
- Context nomeado pelo aggregate root —
budget/orquestraBudget,accounts/orquestraAccount, etc. - Exceção: capability-named — quando não há aggregate root próprio, context é nomeado pela capability (
planning/,agent/). Só Domain Services stateless lendo aggregates de outros contexts. - Ubiquitous Language — code terms = termos que o casal usa. PT/EN mix livre.
- No anemic models — comportamento mora nos objetos de domínio.
- Light — sem CQRS mandatory, sem event sourcing default. Promove só quando o cenário cobrar.
Detalhes em AGENTS.md ## DDD Light Conventions.
Bounded contexts atuais
Section titled “Bounded contexts atuais”budget/—Budgetaggregate root,RecurringExpenseentity. Custos mensais, expected vs actual, variance.accounts/—Accountaggregate,Invoiceentity,TransactionVO. Cartões e faturas.goals/—Goalaggregate,ContributionVO. Metas de poupança do casal.household/—Householdaggregate,IncomeEntryVO. Renda fixa + variável, members.planning/— capability-named.FeasibilityCheck,BudgetAlerts,ReminderService,EvalRunner. Domain Services cross-aggregate read-only.agent/— capability-named.AgentChat(Domain Service stateless),ChatMessageVO, read/write tools,ConversationGuard, portsAgentMemory/WhatsAppGateway.referrals/—Referralsroot,ReferralLinkentity,ShareTokenVO. Attribution analítico (sem fila).shared-kernel/—Money,BillingDay,Period,Memberentity,ExchangeRateVO, portExchangeRateProvider.
Layering
Section titled “Layering”src/contexts/<ctx>/ domain/ # puro: VOs, entities, aggregates, Domain Services application/ # ports (interfaces), orchestrators infrastructure/ # adapters (real + fakes) interface/ # entrada (HTTP, CLI, WhatsApp) quando aparecer- Domain nunca importa de application ou infrastructure. Inversão de dependência canônica.
- Ports em
application/quando a coisa é infra-facing (persistência, LLM, rede). - Domain Services em
domain/services/quando a coisa é pura (input aggregate → output, sem I/O). - Cross-context infra ports moram em
shared-kernel/application/(ex:ExchangeRateProviderconsumido por planning/goals/agent).
Cross-context regras
Section titled “Cross-context regras”- Read-only via Domain Service —
ExpenseReconcilerembudget/domain/services/importaInvoice/Transactiondeaccounts/direto. Inversão (port/interface) só quando aparecer segundo consumidor. - Agent atravessa context pra ler port, nunca adapter — write tools em
agent/domain/tools/importamBudgetRepositorydebudget/application/(interface). NUNCA debudget/infrastructure/(impl). - Stringly-typed cross-boundary —
shareToken,waid,resourceId,threadIdsão strings opacas atravessando contexts. Caller resolve mapping; domínio não conhece a fonte.
Test fakes são adapters legítimos
Section titled “Test fakes são adapters legítimos”InMemoryBudgetRepository, MockWhatsAppGateway, InMemoryAgentMemory — todos moram em <ctx>/infrastructure/ junto com o adapter real. Mock inline na spec seria mais barato hoje, mas o fake vira reuso quando outros cenários precisarem.
Gotchas críticas
Section titled “Gotchas críticas”- Aritmética de data em UTC — sempre
getUTCFullYear/getUTCMonth/Date.UTC(). Local timezone (BRT) quebra silenciosamentemonthsBetween. - Domain não converte moeda — caller (UX/tool) entrega
ExchangeRateinjetado. - Métricas derivadas, não armazenadas —
pace,forecast,total()recalculam a cada chamada. - Verbos do cenário revelam a Entity — “registrar” puxa VO; adicionar “atualizar” força promoção a Entity.
Lista completa em AGENTS.md ## Gotchas.