O Domain-Driven Design (DDD) não é apenas um padrão de pastas, mas uma filosofia de design de software que coloca a Regra de Negócio (o Domínio) como o centro de todas as decisões. No Laravel, aplicar DDD significa elevar o nível de maturidade do código, garantindo que o framework sirva à aplicação, e não o contrário.

1. O que é DDD e por que aplicá-lo? Link para o cabeçalho

No desenvolvimento tradicional (MVC puro), é comum encontrarmos lógica de banco de dados em Controllers ou Models gigantes. O DDD propõe o desacoplamento. Ele divide o sistema em camadas para que, se você precisar trocar o banco de dados ou a forma como um comando é disparado, a regra de negócio permaneça intacta.


2. A Anatomia das Camadas e Recursos no Laravel Link para o cabeçalho

Para implementar o DDD com sucesso utilizando os recursos do Laravel, precisamos definir claramente a função de cada componente:

A. O Model (Eloquent) - Camada de Persistência Link para o cabeçalho

No DDD, o Model é apenas um Data Mapper. Ele é o reflexo da tabela no banco de dados.

  • Finalidade: Realizar a comunicação técnica com o banco.

  • O que evitar: Colocar cálculos, validações complexas ou envio de e-mails dentro da Model.

B. O Repository - Abstração de Dados Link para o cabeçalho

O Repository isola o Eloquent da Service.

  • Finalidade: Centralizar as consultas ao banco. A Service não deve saber que existe um where() ou um first(); ela apenas pede ao Repository: buscarPorIdentificador().

  • Vantagem: Facilita a criação de testes unitários e a troca de tecnologias de armazenamento.

C. A Service - O Coração (Domain/Application Service) Link para o cabeçalho

É onde a “mágica” acontece.

  • Finalidade: Orquestrar as regras de negócio. A Service decide o que deve ser feito. Ela recebe dados, valida contra as regras da empresa e chama o Repository para salvar.

  • Exemplo: O método aplicaUpdateOrCreate que refatoramos é uma lógica de Service.

D. Controller e Command - Camada de Interface (Entry Points) Link para o cabeçalho

São as portas de entrada da aplicação.

  • Finalidade: Receber o input do usuário (via HTTP ou Terminal), validar o formato básico dos dados e repassar para a Service.

  • Regra de Ouro: Devem ser “magros” (Skinny). Se houver um if de regra de negócio aqui, ele deve ser movido para a Service.

E. DTO (Data Transfer Object) Link para o cabeçalho

Embora não discutido profundamente antes, o DTO é essencial para transportar dados entre o Command e a Service de forma tipada, evitando o uso de “arrays genéricos” que podem causar erros.


3. Aplicação Prática: Cadastro de Cliente Link para o cabeçalho

Imagine que, ao cadastrar um cliente, precisamos verificar se o CPF é válido e enviar um e-mail de boas-vindas.

A. O Repositório (Interface e Implementação) Link para o cabeçalho

Primeiro, definimos o contrato. Isso permite trocar o banco de dados sem quebrar a Service.

// app/Repositories/Contracts/ClienteRepositoryInterface.php
interface ClienteRepositoryInterface {
    public function salvar(array $dados): object;
}

// app/Repositories/Eloquent/ClienteRepository.php
class ClienteRepository implements ClienteRepositoryInterface {
    public function salvar(array $dados): object {
        return Cliente::create($dados);
    }
}

B. A Service (O Coração) Link para o cabeçalho

Aqui aplicamos a lógica. Note que ela recebe o Repositório pelo construtor (Injeção de Dependência).

// app/Services/ClienteService.php
class ClienteService {
    public function __construct(
        protected ClienteRepositoryInterface $repository
    ) {}

    public function registrarCliente(array $dados): object {
        // Regra de Negócio: Validar algo específico do domínio
        if ($this->cpfJaExiste($dados['cpf'])) {
            throw new \Exception("Este cliente já possui cadastro.");
        }

        $cliente = $this->repository->salvar($dados);

        // Outra ação de orquestração
        // Mail::to($cliente->email)->send(new BoasVindas());

        return $cliente;
    }
}

C. O Controller (A Porta de Entrada) Link para o cabeçalho

Ele é “magro” (Skinny Controller). Apenas repassa o trabalho.

// app/Http/Controllers/ClienteController.php
class ClienteController extends Controller {
    public function store(Request $request, ClienteService $service) {
        try {
            $cliente = $service->registrarCliente($request->all());
            return response()->json($cliente, 201);
        } catch (\Exception $e) {
            return response()->json(['error' => $e->getMessage()], 422);
        }
    }
}

Como o Laravel conecta tudo? Link para o cabeçalho

Para que o Laravel saiba que, quando você pede ClienteRepositoryInterface, ele deve entregar o ClienteRepository (Eloquent), usamos o Service Provider.

// app/Providers/RepositoryServiceProvider.php
public function register() {
    $this->app->bind(
        \App\Repositories\Contracts\ClienteRepositoryInterface::class,
        \App\Repositories\Eloquent\ClienteRepository::class
    );
}

4. Finalidade de cada recurso no contexto DDD Link para o cabeçalho

Recurso Finalidade no DDD Analogia
Controller Entrada: Apenas recebe a requisição (Request), valida o formato básico e entrega para a Service. Não toma decisões de negócio. O garçom que anota o pedido.
Service Orquestração: Onde reside a regra de negócio. Ela sabe o que deve ser feito e coordena os recursos necessários. O chef que conhece a receita e coordena a cozinha.
Repository Abstração de Dados: Isola o Eloquent. A Service não sabe se o dado vem do MySQL, Redis ou API; o Repository resolve a busca. O estoquista que busca os ingredientes, não importa onde estejam.
Model (Eloquent) Persistência: No DDD, ela é apenas o mapeador de dados (Data Mapper). Reflete a tabela no banco, sem lógica de negócio. A prateleira do estoque.
DTO (Data Transfer Object) Transporte: Garante que os dados passem entre camadas (ex: Command -> Service) de forma tipada e segura. A bandeja padronizada onde o pedido viaja.

5. Benefícios da Refatoração para DDD Link para o cabeçalho

Ao atender aos pedidos do seu líder técnico, você está aplicando:

  • Desacoplamento: O Command não depende mais diretamente do Eloquent.

  • Tipagem Estrita: O uso de : ?Model e array nos parâmetros evita que dados inválidos corrompam o Domínio.

  • Manutenibilidade: Se a lógica de duplicidade mudar, você altera apenas um lugar (a Service ou o Repository), e não todos os comandos que usam aquela tabela.

  • Testabilidade: Você pode testar a ClienteService sem precisar de um banco de dados real, usando um “Mock” do repositório.