Home Comunidade Eventos YouTube Networks Desafios // produtos Academy News CTI | CVE Contato

05 - Separation of Concerns (SoC)

#camadas#modulos#preocupacoes-transversais#soc
video explicativo
Video explicativoEspaco reservado para o video desta anotação — adicione 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