Design Orientado a Domínio (DDD) em Microsserviços
Quando as organizações fazem a transição de uma arquitetura monolítica para microsserviços, elas enfrentam uma questão crítica e de alto risco: Como traçamos os limites de nossos serviços?
Em teoria, os microsserviços deveriam ser unidades soltas e desacopladas que podem ser desenvolvidas, implantadas e dimensionadas de forma independente. Na prática, porém, muitas equipes acabam construindo um monólito distribuído — um sistema em que os serviços estão tão fortemente acoplados que uma única mudança nos negócios exige a modificação e a implantação de vários serviços simultaneamente, agravando a latência da rede e os impasses de implantação.
Para evitar essa armadilha, os arquitetos de software recorrem ao Domain-Driven Design (DDD). Introduzido pela primeira vez por Eric Evans em 2003, DDD é uma metodologia de desenvolvimento de software que alinha estruturas de código com os domínios de negócios complexos que representam.
Neste artigo, exploraremos como o DDD fornece os planos estratégicos e táticos para projetar microsserviços limpos, desacoplados e de alta manutenção.
1. O Plano Estratégico: Traçando os Limites do Serviço
O DDD é dividido em duas fases principais: Design Estratégico e Design Tático. O design estratégico trata da modelagem de contextos de negócios e da definição de limites de serviço. Ele fornece a visão “macro” da arquitetura.
A. Linguagem onipresente
Dentro de uma empresa, diferentes departamentos usam as mesmas palavras para significar coisas completamente diferentes. Por exemplo:
- Para a equipe de vendas, um “Usuário” é um lead ou cliente potencial.
- Para a equipe de segurança, um “Usuário” é um conjunto de credenciais de login.
- Para a equipe de envio, “Usuário” é um endereço físico do destinatário.
Tentar construir um modelo de banco de dados único e unificado para um “usuário” que satisfaça a todos resulta em uma base de código enorme e emaranhada. O DDD resolve isso estabelecendo uma linguagem onipresente — um vocabulário compartilhado definido e usado por especialistas e desenvolvedores do domínio de negócios dentro de um limite específico.
B. Contextos limitados
Um Contexto Limitado é o limite explícito dentro do qual um modelo de domínio se aplica. Dentro da fronteira, todos os termos da Linguagem Onipresente têm um significado único e inequívoco.
Em vez de um único modelo de “Usuário”, definimos modelos separados dentro de seus respectivos Contextos Limitados:
- No Contexto do pedido, temos um modelo
Ordercontendo informações de contato do cliente. - No Contexto de Identidade, temos um modelo
Credentials. - No Contexto de Envio, temos um modelo
DeliveryAddress.
A regra de ouro dos microsserviços: Um contexto limitado é mapeado diretamente para um microsserviço.
Ao particionar o sistema em contextos limitados, garantimos que os microsserviços sejam fracamente acoplados e representem capacidades de negócios distintas.
C. Mapeamento de contexto: como os serviços se comunicam
Contextos limitados não existem isoladamente; eles devem interagir. Um Mapa de Contexto define os relacionamentos e mecanismos de tradução entre contextos. Os principais padrões incluem:
- Kernel Compartilhado: Dois contextos compartilham um pequeno subconjunto do modelo de domínio e banco de dados (geralmente desencorajado em microsserviços devido ao acoplamento).
- Cliente-Fornecedor: Um contexto (fornecedor) deve fornecer dados a outro (cliente). O fornecedor deve coordenar os cronogramas de liberação com o cliente.
- Camada Anticorrupção (ACL): Uma camada de tradução que traduz dados recebidos de um sistema externo no modelo de domínio interno do cliente, evitando que modelos externos poluam a arquitetura interna.
2. O projeto tático: estruturando a base de código de microsserviços
Depois que os limites do serviço são traçados usando o design estratégico, o Design Tático fornece um conjunto de padrões de design para estruturar o código em um único microsserviço.
A. Entidades e objetos de valor
Dentro de um microsserviço, os modelos são divididos em duas categorias:
- Entidades: objetos que possuem uma identidade exclusiva que persiste ao longo do tempo, mesmo que seus atributos mudem. Os exemplos incluem
OrderouProduct. - Objetos de valor: Objetos que não possuem identidade única e são definidos inteiramente por seus atributos. Eles são imutáveis. Os exemplos incluem
ShippingAddressouProductDimensions. Se dois objetos de valor tiverem os mesmos atributos, eles serão considerados iguais.
B. Agregados e raízes agregadas
Um Agregado é um cluster de entidades e objetos de valor associados que são tratados como uma única unidade para alterações de dados. Cada agregado tem uma Raiz Agregada, que é o único ponto de entrada através do qual objetos externos podem interagir com o agregado. A raiz garante que todas as invariantes de negócios (regras) sejam aplicadas.
Por exemplo, no diagrama acima:
- No Serviço de Pedido, o
Orderé a Raiz Agregada. ContémOrderItem(Entidade) eShippingAddress(Objeto de Valor). Os serviços externos não podem modificarOrderItemdiretamente; eles devem chamar um método na raizOrder(por exemplo,order.AddItem()), que valida que o pedido ainda não foi enviado.
C. Eventos de domínio
Um Evento de Domínio é algo que aconteceu no domínio que interessa aos especialistas de negócios. Ele é usado para comunicar alterações em contextos limitados de forma assíncrona.
Quando um agregado executa um comando, ele publica um evento de domínio (por exemplo, OrderCreated). Outros serviços escutam esse evento e atualizam seu estado de acordo, garantindo consistência eventual sem dependências de API síncronas.
3. Um exemplo concreto: serviço de pedido vs. serviço de estoque
Vejamos uma implementação simplificada de DDD tático em Go para o Serviço de Pedidos, mostrando como a Raiz Agregada coordena as regras de negócios.
package domain
import (
"errors"
"time"
)
// Value Object (Immutable, identity-less)
type ShippingAddress struct {
Street string
City string
ZipCode string
}
// Entity (Has identity, mutable)
type OrderItem struct {
ProductID string
Quantity int
Price float64
}
// Aggregate Root (Enforces transactional boundaries)
type Order struct {
ID string
Items []OrderItem
Address ShippingAddress
Status string
CreatedAt time.Time
}
// NewOrder creates a new Order Aggregate Root
func NewOrder(id string, address ShippingAddress) *Order {
return &Order{
ID: id,
Items: []OrderItem{},
Address: address,
Status: "PENDING",
CreatedAt: time.Now(),
}
}
// AddItem enforces business rules before mutating state
func (o *Order) AddItem(productID string, qty int, price float64) error {
if o.Status != "PENDING" {
return errors.New("cannot add items to a finalized or cancelled order")
}
if qty <= 0 {
return errors.New("quantity must be greater than zero")
}
o.Items = append(o.Items, OrderItem{
ProductID: productID,
Quantity: qty,
Price: price,
})
return nil
}
Quando o pedido é salvo com sucesso, o serviço publica um evento em um corretor de mensagens:
{
"event_id": "evt_98231",
"event_type": "OrderCreated",
"timestamp": "2026-06-26T00:15:00Z",
"payload": {
"order_id": "ord_5521",
"items": [
{ "product_id": "prod_88", "quantity": 2 }
]
}
}
O Serviço de Inventário escuta esse evento, atualiza sua entidade StockLevel local e conclui o fluxo de forma assíncrona.
4. DDD estratégico vs. tático em microsserviços
| Fase/Aspecto | DDD Estratégico | DDD tático |
|---|---|---|
| Escopo | Arquitetura global do sistema (macro) | Dentro de uma única base de código de serviço (micro) |
| Meta principal | Traçar limites de serviço claros e desacoplados | Modele lógica de negócios rica e aplique invariantes |
| Conceitos-chave | Contextos limitados, linguagem onipresente, mapa de contexto | Entidades, Objetos de Valor, Agregados, Eventos de Domínio |
| Público | Arquitetos, Gerentes de Produto, Desenvolvedores | Desenvolvedores de software, revisores de código |
| Impacto nos microsserviços | Determina o número e o âmbito dos serviços | Determina transações de banco de dados e estrutura de diretórios |
Conclusão: comece primeiro com DDD estratégico
Aplicar Domain-Driven Design a microsserviços é uma maneira poderosa de garantir que sua arquitetura corresponda à estrutura de seu negócio. No entanto, as equipes muitas vezes cometem o erro de focar demais em padrões táticos (como escrever interfaces de repositório e objetos de valor) enquanto ignoram o design estratégico.
Se você não definir os limites corretamente, nenhuma quantidade de código tático limpo evitará que seu sistema se transforme em um monólito distribuído. Sempre comece com o mapeamento estratégico – defina sua linguagem ubíqua, agrupe a lógica em contextos limitados, mapeie os fluxos de comunicação e deixe os limites de seus microsserviços emergirem naturalmente dessas definições.
Explore mais arquitetura de software, padrões de design e insights de engenharia no Blog Ghaznix →