Arquitetura software
DDD na Pratica: Next.js e Spring Boot
Domain-Driven Design (DDD) e uma abordagem para software com dominio complexo, focada em modelo de negocio, linguagem ubiqua e limites claros entre contextos.
Quando usar DDD
DDD vale mais quando:
- regras de negocio mudam com frequencia;
- termos de negocio sao ambiguos entre areas;
- existe alto acoplamento entre modulos;
- bugs surgem por entendimento errado de processo.
Se o sistema for CRUD simples, DDD completo pode ser excesso. Use apenas partes uteis.
Conceitos centrais
- Ubiquitous Language: termos unicos entre produto, negocio e engenharia.
- Bounded Context: limite semantico do modelo.
- Entity: objeto com identidade.
- Value Object: objeto imutavel sem identidade.
- Aggregate / Aggregate Root: fronteira de consistencia transacional.
- Domain Service: regra de dominio que nao pertence a uma entidade unica.
- Repository: contrato para recuperar/persistir aggregate roots.
- Domain Event: fato de negocio relevante.
Estrutura recomendada
domain: entidades, value objects, eventos, regras.application: use cases e orquestracao.infrastructure: banco, mensageria, web, integrações.interfaces(opcional): controllers, DTOs, serializers.
Next.js (TypeScript)
src/
core/
domain/
order/
Order.ts
OrderItem.ts
Money.ts
events/OrderPlaced.ts
application/
place-order/
PlaceOrderUseCase.ts
ports/
OrderRepository.ts
EventBus.ts
infra/
repositories/PrismaOrderRepository.ts
bus/InMemoryEventBus.ts
app/api/orders/route.tsSpring Boot (Java)
src/main/java/com/acme/orders/
domain/
Order.java
OrderItem.java
Money.java
event/OrderPlaced.java
application/
PlaceOrderUseCase.java
port/out/OrderRepositoryPort.java
port/out/EventPublisherPort.java
infrastructure/
web/OrderController.java
persistence/JpaOrderRepositoryAdapter.java
events/SpringEventPublisherAdapter.javaExemplo de modelagem (Aggregate)
export class Order {
private constructor(
public readonly id: string,
private status: "DRAFT" | "PLACED",
private readonly items: OrderItem[],
) {}
static draft(id: string) {
return new Order(id, "DRAFT", [])
}
addItem(item: OrderItem) {
if (this.status !== "DRAFT") throw new Error("Order is not editable")
this.items.push(item)
}
place() {
if (!this.items.length) throw new Error("Order must have at least one item")
this.status = "PLACED"
return { type: "OrderPlaced", orderId: this.id }
}
}Ponto chave: invariantes ficam no dominio, nao no controller.
Aplicando em Next.js
route.tsvalida transporte e chama use case.- use case depende de
OrderRepository(porta), nao de Prisma direto. - regras do aggregate sao testadas sem Next.js.
export class PlaceOrderUseCase {
constructor(
private readonly orders: OrderRepository,
private readonly events: EventBus,
) {}
async execute(input: PlaceOrderInput) {
const order = Order.draft(input.orderId)
input.items.forEach((i) => order.addItem(OrderItem.create(i)))
const domainEvent = order.place()
await this.orders.save(order)
await this.events.publish(domainEvent)
return { orderId: input.orderId }
}
}Aplicando em Spring Boot
- Controller converte HTTP -> comando.
- Use case chama dominio.
- Adapter JPA persiste sem contaminar dominio com annotations.
@Service
public class PlaceOrderUseCase {
private final OrderRepositoryPort orderRepository;
private final EventPublisherPort events;
public PlaceOrderUseCase(OrderRepositoryPort orderRepository, EventPublisherPort events) {
this.orderRepository = orderRepository;
this.events = events;
}
public PlaceOrderOutput execute(PlaceOrderInput input) {
Order order = Order.draft(input.orderId());
input.items().forEach(item -> order.addItem(OrderItem.create(item)));
OrderPlaced event = order.place();
orderRepository.save(order);
events.publish(event);
return new PlaceOrderOutput(order.getId());
}
}Erros comuns em DDD
- Tratar DDD como apenas "pastas bonitas".
- Ignorar linguagem ubiqua com o time de negocio.
- Criar aggregates grandes demais.
- Misturar entidade de dominio com entidade JPA/ORM.
- Fazer um unico bounded context para tudo.
Checklist rapido
- Entidades guardam invariantes do negocio?
- Casos de uso nao dependem de framework?
- Existe linguagem ubiqua documentada?
- Boundaries de contexto estao explicitos?
- Eventos de dominio representam fatos reais?
Recursos recomendados
YouTube
Artigos e documentacao
- Martin Fowler - Domain-Driven Design
- InfoQ - DDD
- Microsoft - Domain-driven design microservices
- Spring Modulith Reference
- Next.js Project Structure
Papers e publicacoes tecnicas
- Context Mapping in Domain-Driven Design (INFORMATIK 2014)
- On the Relationship of DDD and Microservice Architectures (IEEE)
Livros
- Domain-Driven Design - Eric Evans
- Implementing Domain-Driven Design - Vaughn Vernon
- Domain-Driven Design Distilled - Vaughn Vernon
Posts no X (opcional)
Conclusao
DDD melhora decisao arquitetural quando o problema e de dominio, nao apenas tecnico. Em Next.js e Spring Boot, o ganho vem de proteger o nucleo de negocio e deixar framework como detalhe de entrega.