Kaique Mitsuo Silva Yamamoto
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

  1. domain: entidades, value objects, eventos, regras.
  2. application: use cases e orquestracao.
  3. infrastructure: banco, mensageria, web, integrações.
  4. 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.ts

Spring 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.java

Exemplo 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.ts valida 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

Papers e publicacoes tecnicas

Livros

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.