05 - Separation of Concerns (SoC)
video: https://youtu.be/... no frontmatter da nota.Separation of Concerns (SoC)
Separation of Concerns é o princípio que manda dividir um sistema em seções distintas, cada
uma responsável por uma única preocupação, com interação mínima e bem definida entre elas. A
formulação de referência vem de Edsger Dijkstra, no ensaio de 1974 sobre o papel do
pensamento científico, em que descreve a técnica de isolar aspectos do problema para
tratá-los um de cada vez. Em produção, o princípio explica por que alguns sistemas aceitam
mudanças com tranquilidade e outros exigem cirurgia ampla a cada ajuste: quando as
preocupações estão separadas, o impacto de uma mudança fica confinado à seção
correspondente. O conceito opera em todas as escalas, da função ao ecossistema de serviços,
e é a base lógica de camadas, módulos, pipelines e malha de serviços. Confundir as
fronteiras é a origem mais comum de acoplamento difícil de desfazer.
O conceito e o problema que resolve
Uma preocupação é um interesse que motiva código: calcular frete, autenticar requisição,
registrar log, persistir pedido, expor métrica. O problema que SoC resolve é a explosão
combinatória quando várias preocupações compartilham o mesmo trecho de código. Um handler
que autentica, valida, calcula, persiste e audita tem cinco eixos de variação independentes
no mesmo arquivo, e a matriz de teste cresce como produto, e não como soma. A separação
troca esse produto por soma: cada eixo evolui em sua seção, e as interações ficam reduzidas
a interfaces explícitas.
O ganho aparece em três frentes operacionais. Teste fica mais barato, porque cada seção tem
menos causas de mudança e menos contexto para montar. Diagnóstico fica mais rápido, porque a
falha aponta a preocupação afetada antes de exigir leitura do sistema inteiro. E o trabalho
paralelo se torna viável, porque times distintos modificam seções distintas sem conflito
estrutural.
O exemplo original de Dijkstra é o sistema operacional THE, organizado em camadas com
responsabilidades progressivas, do hardware ao escalonamento de processos; cada nível só
enxerga o anterior, e cada problema tem um endereço único. A lição sobrevive intacta em
qualquer stack moderna: seções com endereço único são a matéria-prima de sistemas operáveis.
Camadas, módulos e componentes
No nível de código, SoC se materializa em camadas: apresentação, aplicação, domínio e
infraestrutura, cada uma com vocabulário e motivos de mudança próprios. O domínio expressa
regras de negócio sem conhecimento de HTTP, SQL ou filas; a aplicação orquestra casos de
uso; a infraestrutura implementa detalhes técnicos. No nível de organização do código,
módulos estabelecem fronteiras compiláveis, com visibilidade restrita e contratos
explícitos, impedindo que qualquer arquivo acesse qualquer arquivo. No nível de sistema,
componentes e serviços separam preocupações de negócio: catálogo, pagamento e expedição
evoluem em serviços distintos porque mudam por razões distintas.
A regra de traçado é a mesma em todas as escalas: fronteiras seguem eixos de variação, e não
apenas camadas técnicas. Uma fronteira só cumpre o papel de separar preocupações quando
existe mecanismo que a faça valer, seja o compilador, seja o contrato da API, seja a rede
entre serviços. Sem enforcement, a fronteira vira convenção de docstring, e convenção
degrada sob pressão de prazo.
Camadas estritas exigem que cada nível fale apenas com o nível imediatamente abaixo, o que
maximiza isolamento e aumenta o custo de repasse; camadas relaxadas permitem saltos e
reduzem burocracia. A escolha importa menos que a consistência, porque regra violada em
silêncio é pior que regra frouxa respeitada. Concorrência é outro exemplo de preocupação
que merece seção própria: código de domínio que cria threads, adquire locks ou espera
respostas de rede mistura a lógica com o mecanismo de execução, e essa mistura é a que mais
dói em produção, porque transforma bug de regra em bug de escalonamento.
Preocupações transversais
Logging, segurança, transações, métricas, rate limiting e tracing atravessam todas as
camadas, e espalhá-los manualmente pelo código é a violação mais custosa de SoC, porque
contamina cada função com código que não pertence ao seu objetivo. As soluções consolidadas
movem essas preocupações para o perímetro. Middleware e interceptores executam autenticação
e logging antes do handler; decorators empilham comportamento sem editar o método;
programação orientada a aspectos injeta políticas declaradas; sidecars na malha de serviços
assumem mTLS, retry e limitação de taxa fora do processo; instrumentação como OpenTelemetry
entra por biblioteca e exporta telemetria sem tocar na lógica.
A regra prática é clara: quando uma preocupação precisa estar em quase todo lugar, ela deve
virar infraestrutura configurável, e não código repetido. O ganho em produção é imediato.
Mudar a política de log ou a estratégia de autenticação toca um único ponto, a mudança é
auditável por um review, e a ausência da preocupação em algum caminho se torna impossível, e
não improvável.
Exemplo de mistura e separação
O handler a seguir concentra quatro preocupações em uma função só: autenticação, telemetria,
persistência e regra de negócio.
# ANTES: segurança, telemetria, persistência e negócio misturados.
def criar_pedido(request):
inicio = time.time()
if not request.token or not validar_jwt(request.token): # segurança
return resposta(401)
log.info("criar_pedido", usuario=request.usuario) # telemetria
pedido = montar_pedido(request.corpo) # negócio
with conexao() as c:
c.executar("insert into pedidos ...") # persistência
metrica_tempo.observe(time.time() - inicio) # telemetria
return resposta(201, pedido)
A versão separada move cada preocupação para o perímetro onde ela pertence, e o caso de uso
fica restrito à regra que justifica sua existência.
# DEPOIS: o pipeline de middleware assume segurança e telemetria,
# e o caso de uso fica restrito à regra de negócio.
@pipeline(middleware=[autenticar, medir_tempo, logar])
def criar_pedido(comando: CriarPedido) -> Pedido:
return finalizar_pedido.executar(comando) # sem HTTP, sem JWT, sem métrica
Com as fronteiras traçadas, mudar a estratégia de autenticação toca apenas o middleware;
mudar a regra do pedido toca apenas o caso de uso; e os testes do caso de uso rodam sem
servidor web e sem banco.
SoC em nível de sistema e de processo
O princípio escala para além do código. Um pipeline de CI/CD separa build, teste, scan e
deploy porque são preocupações com falha, gatilho e responsabilidade diferentes.
Infraestrutura como código separa provisionamento de aplicação, evitando que mudança de
regra de negócio passe pelo mesmo review de mudança de rede. Na malha de serviços, o sidecar
carrega comunicação, enquanto o serviço carrega negócio, e essa separação permite evoluir
TLS e roteamento sem recompilar aplicação. Observabilidade segue o mesmo desenho: a coleta é
infraestrutura de perímetro, e o aplicativo apenas emite eventos padronizados. Em todos os
casos, o critério é o mesmo: quem muda por motivo X não deveria precisar tocar no código que
muda por motivo Y.
Erros comuns e boas práticas
- Separar demais: uma camada que apenas repassa chamadas sem transformação não separa preocupação, apenas aumenta o caminho de depuração; cada fronteira precisa justificar existência.
- Separar só por técnica: dividir por controller, service e repository sem respeitar eixos de negócio produz acoplamento horizontal entre domínios distintos.
- Deixar a preocupação transversal vazar: autenticação checada dentro de casos de uso é sinal de que o middleware não cobre o caminho; corrija o perímetro, e não cada função.
- Confundir SoC com ausência de interação: as seções precisam conversar por contratos explícitos; isolamento total é fragmentação.
- Traçar fronteira sem mecanismo de enforcement: sem compilador, contrato ou rede, a fronteira vira convenção e degrada em meses.
- Migrar sem testes de caracterização: movimentação de código entre seções precisa de rede de proteção, senão a separação introduz regressão silenciosa.
Sintomas e mitigações
A tabela conecta sintomas observados em produção às violações de separação mais comuns e às
correções correspondentes.
| Sintoma em produção | Preocupação violada | Mitigação |
|---|---|---|
| Toda feature toca dez arquivos | Fronteiras seguem técnica, não variação | Reagrupar por eixo de mudança e contrato |
| Bug de autenticação em handler esquecido | Segurança espalhada no código | Mover para middleware no perímetro |
| Falha de rede investigada no código de negócio | Comunicação dentro do domínio | Sidecar ou biblioteca de infra isolada |
| Log muda formato em cada serviço | Telemetria escrita à mão | Instrumentação padronizada central |
| Deploy trava em review de infra | Infra e aplicação no mesmo fluxo | Separar IaC e revisões por contexto |