Dois padrões que salvam sistemas que precisam crescer: DDD e RBAC
Quando um sistema começa a crescer, dois problemas aparecem quase sempre juntos: a lógica de negócio começa a vazar para lugares onde não deveria estar, e o controle de quem acessa o quê vira um campo minado de condicionais espalhadas pelo código. DDD tático e RBAC não são conceitos acadêmicos — são as duas decisões arquiteturais que mais impactam a capacidade de um sistema de crescer sem se tornar impossível de manter.
Todo sistema começa pequeno. E todo sistema pequeno parece não precisar de arquitetura.
Aí ele cresce. O cliente pede uma nova regra. Depois outra. Entra um segundo perfil de usuário. Depois um terceiro. O código que estava "funcionando bem" começa a acumular condicionais, duplicações e aquela sensação crescente de que mexer em qualquer coisa pode quebrar outra.
Dois problemas surgem cedo nesse processo — e quase sempre juntos.
O primeiro: a lógica de negócio está em todo lugar. No Controller, na View, no Model, no middleware, no helper que alguém criou numa sexta-feira.
O segundo: o controle de acesso virou uma colcha de retalhos. if ($user->role === 'admin') repetido em dez lugares diferentes. Permissões hardcoded. Ninguém mais sabe ao certo quem pode fazer o quê.
No CIM Base, resolvemos os dois de forma deliberada desde o início. Aqui está como.
Parte 1 — DDD Tático: tirar a lógica do Controller de verdade
Domain-Driven Design é um tema que assusta. Livros enormes, terminologia pesada, diagramas complexos. Mas o que interessa na prática cotidiana de um sistema Laravel é o DDD tático — um conjunto de padrões concretos que mudam como você organiza o código.
A premissa é simples: o Controller não deve saber como o negócio funciona. Ele só recebe e responde.
O problema que isso resolve
Imagine um Controller que cria um lead:
public function store(Request $request): Response
{
$request->validate([...]);
$lead = Lead::create([
'name' => Str::title($request->name),
'email' => strtolower($request->email),
'status' => 'novo',
'origin' => $request->origin ?? 'site',
]);
// notifica o comercial
Mail::to('comercial@empresa.com')->send(new NovoLeadMail($lead));
// registra no log
activity()->on($lead)->log('criado');
return Inertia::render('Leads/Show', ['lead' => $lead]);
}
Funciona. Mas agora essa lógica existe apenas aqui. Se você precisar criar um lead de outro lugar — via importação, via API, via webhook — vai copiar esse código? Vai extrair para um helper? Vai lembrar de fazer tudo isso nas três versões?
E quando a regra mudar — e ela vai mudar — quantos lugares você vai precisar atualizar?
A solução: Service + DTO + Evento
No CIM Base, a criação de um lead passa por uma LeadService que concentra toda essa responsabilidade:
final readonly class LeadService
{
public function create(LeadData $data): Lead
{
return DB::transaction(function () use ($data) {
$lead = Lead::create([
'name' => Str::title($data->name),
'email' => strtolower($data->email),
'status' => LeadStatus::Novo,
'origin' => $data->origin ?? LeadOrigin::Site,
]);
LeadCreatedEvent::dispatch($lead);
return $lead;
});
}
}
E o Controller volta para o que é seu:
public function store(StoreLeadRequest $request, LeadService $service): Response
{
$lead = $service->create(LeadData::from($request->validated()));
return Inertia::render('Leads/Show', ['lead' => $lead]);
}
Seis linhas. Sem lógica. Sem surpresa.
O e-mail e o log saem do Controller e vão para um LeadCreatedListener — desacoplado, testável, substituível sem tocar no fluxo principal.
O que muda na prática
Quando você precisa criar um lead via importação de CSV, chama $service->create(). Quando vem de um webhook externo, chama $service->create(). A regra de negócio existe em um lugar só. Muda em um lugar só. É testada em um lugar só.
Isso não é elegância por elegância — é a diferença entre uma refatoração de uma hora e uma investigação de dois dias.
Parte 2 — RBAC: controle de acesso que escala com a equipe
RBAC significa Role-Based Access Control — controle de acesso baseado em papéis. A ideia é que permissões não são atribuídas diretamente a usuários, mas a roles (perfis), e usuários recebem roles.
Parece óbvio. Mas a maioria dos sistemas não faz isso — faz outra coisa disfarçada de RBAC.
O falso RBAC que todo sistema tem
// Em algum Controller
if ($user->role === 'admin') {
// pode fazer X
}
// Em outro lugar
if ($user->type !== 'viewer') {
// pode fazer Y
}
// Em outro
if (in_array($user->role, ['admin', 'manager'])) {
// pode fazer Z
}
Isso parece controle de acesso. Mas é uma armadilha.
Quando você precisa criar um novo perfil — digamos, supervisor, que pode fazer X e Z mas não Y — você vai caçar todas as condicionais do sistema? E quando as regras mudarem?
Além disso, esse código está espalhado. Não existe um lugar centralizado onde você consegue ver o que cada perfil pode fazer.
O RBAC real com Spatie Laravel Permission
No CIM Base, usamos spatie/laravel-permission. A lógica fica assim:
Permissões são declaradas de forma semântica:
// Permissões
'leads.view'
'leads.create'
'leads.edit'
'leads.delete'
'leads.assign'Roles agrupam permissões:
$admin = Role::create(['name' => 'admin']);
$admin->givePermissionTo(['leads.view', 'leads.create', 'leads.edit', 'leads.delete', 'leads.assign']);
$comercial = Role::create(['name' => 'comercial']);
$comercial->givePermissionTo(['leads.view', 'leads.create', 'leads.edit']);
$suporte = Role::create(['name' => 'suporte']);
$suporte->givePermissionTo(['leads.view']);
E a verificação no código se torna declarativa:
// No Controller ou Policy
$this->authorize('leads.edit');
// No frontend (via Inertia)
v-if="$page.props.auth.permissions.includes('leads.edit')"
O que muda na prática
Criar um novo perfil? Você cria uma role e atribui permissões existentes. Nenhuma linha de código de verificação muda.
Mudar o que um perfil pode fazer? Você ajusta as permissões da role. Nenhum if precisa ser caçado.
E o melhor: como as roles e permissões vivem no banco de dados, o próprio administrador do sistema consegue gerenciar isso pelo painel — sem abrir um ticket para o dev.
No CIM Base, o gestor da CIM adiciona um novo colaborador, atribui a role correta, e pronto. O sistema sabe exatamente o que aquele usuário pode ver e fazer.
DDD e RBAC juntos: por que eles se complementam
Os dois padrões partem do mesmo princípio: responsabilidades claras, fronteiras definidas.
DDD tático diz que a lógica de negócio tem um lugar certo para viver — e não é o Controller.
RBAC diz que o controle de acesso tem um lugar certo para viver — e não é um if espalhado pelo código.
Quando os dois estão presentes, o sistema ganha algo valioso: previsibilidade. Você sabe onde a regra está. Você sabe quem pode executá-la. E quando precisar mudar — e vai precisar — você sabe exatamente onde mexer.
Resumindo para levar
DDD tático não é para sistemas complexos — é para sistemas que vão crescer. Começar com Services e DTOs não é over-engineering, é o custo de não ter que reescrever tudo quando o segundo cliente aparecer.
RBAC não é para sistemas com muitos usuários — é para sistemas com mais de dois perfis. Se você tem admin e usuário comum, já precisa de RBAC. Tudo o que vier depois é só atribuição de permissões.
Os dois custam um pouco mais no início. E poupam muito mais ao longo do caminho.
É essa a conta que a gente faz antes de escrever a primeira linha.
Esse post faz parte da série de insights do blog da MC — pensamentos diretos sobre desenvolvimento, arquitetura e o que aprendemos construindo sistemas reais.
Está construindo algo que vai crescer? Fala com a gente.
