Abstrair os os componentes de um sistema em camadas é algo comum e, até certo ponto, inevitável. Identificamos rapidamente partes dos sistemas com “externalidade maior”, como em interfaces com usuários ou integrações. Com menos externalidade, temos as partes contendo a “inteligência e persistência” da solução. Dessa “recorrência”, provavelmente, emerge a popularidade do estilo arquitetural baseado em camadas.
Arquiteturas baseadas no estilo de camadas são tecnicamente particionadas (em oposição às arquiteturas particionadas por domínio). Ou seja, grupos de componentes, ao invés de serem estabelecidos pelo domínio, são formados por sua função técnica (como apresentação ou negócio).
'Layer' e 'Tier'
O termo camada é frequentemente associado a dois conceitos distintos (em inglês): layer e tier.
Uma layer é uma separação lógica de código, geralmente relacionada a algum aspecto funcional. Idealmente a comunicação entre camadas deve acontecer explicitamente e de maneira pouco acoplada.
Uma tier é uma separação física aplicada a uma camada. Essa “separação” física pode indicar um servidor distinto ou processo dedicado.
Nesse capítulo, quando nos referimos a camadas, tratamos de layers.
Conceito fundamental
A ideia essencial desse estilo arquitetural é decompor os componentes de um sistema em uma pilha de camadas. Tradicionalmente, uma camada é autorizada a utilizar apenas a camada imediatamente abaixo. Uma camada inferior funciona como uma espécie de “máquina virtual” para a camada acima.
As camadas “mais baixas” são responsáveis por prover funções mais elementares que servem como “blocos de construção”, nas camadas “mais altas”, para a elaboração de funções mais sofisticadas.
Na sua forma mais restrita, este estilo arquitetural indica que uma determinada camada tenha acesso apenas a camada imediatamente abaixo, sendo que todas as demais ficam ocultas. Dessa forma, habilitam atributos de qualidade desejáveis como manutenabilidade, portabilidade e reuso, logo, promovendo o evolvability.
Sempre que uma camada é inserida na pilha, oferece um nível de abstração mais alto as funcionalidades disponibilizadas nas camadas mais baixas. |
Como cada camada depende apenas da camada imediatamente abaixo desta, todas as camadas “mais baixas” podem ser substituídas ou “emuladas”, facilitando testes. Quanto mais camadas são adicionadas, maiores são as oportunidades de troca, entretanto, mais comuns são implicações para o desempenho.
Filosofia de design
Atentando ao estilo sob a perspectiva de design, a separação de um software em camadas revela, acima de tudo, uma estratégia de modularização. As camadas “mais baixas” recebem “responsabilidades” mais primitivas. Enquanto as camadas “mais altas” ficam responsáveis por “responsabilidades mais complexas”, que podem ser cumpridas, graças a combinação das camadas em nível mais baixo.
O nível de “acoplamento para desenvolvimento e operacional” das camadas fica, então determinado pela estratégia de conexão entre as camadas. Cada camada pode fornecer um ou mais componentes, cada componente pode se ligar, com componentes de camadas superiores ou inferiores, com conectores diversos (exemplo, requisições HTTP ou simples chamadas de métodos, dependendo da separação ou não em tiers).
Tipos de Acoplamento Controlar o acoplamento é sempre aspecto central para qualquer discussão arquitetural. Quer relembrar os tipos de acoplamento, conforme classificação de Nygard? Acessar tópico |
Lidando com falhas para prevenir erros e defeitos Programação Orientada a Objetos A “externalidade” de um componente, ou seja, a camada em que ele se encontra, é um dos fatores que determina, também, o quanto excepcional uma falha deve ser considerada. Esse capítulo do livro “Programação Orientada a Objetos” trata exatamente dessa característica. |
Camadas abertas e fechadas
Eventualmente, uma camada pode dispensar as features fornecidas por aquela, ou aquelas, imediatamente abaixo. Neste caso, precisa “pular” estas camadas, acessando diretamente aquela que provê aquilo de que necessita.
Idealmente, esses “pulos” precisam ser projetados, indicando as camadas dispensáveis como “abertas”. Enquanto aquelas que não podem ser ignoradas devem ser apontadas como “fechadas”.
Aplicações que fornecem APIs para consumo externo, por exemplo, porém que dispensam o uso na própria camada de apresentação, mantém a camada das APIs abertas.
Outra decisão que “abre” algumas camadas são implementações do padrão fast-lane reader, onde a camada de apresentação faz queries diretamente contra o banco de dados, tentando maximizar performance.
Violações mais frequentes
Ambas violações, quando observadas, geralmente são bem justificadas. Entretanto, acabam comprometendo parte dos benefícios da adoção desse estilo arquitetural e são indícios de que, provavelmente, 1) outro estilo fosse mais adequado ; ou 2) houve exagero na definição de quantas camadas eram necessárias.
Camadas telefone-sem-fio
Um problema recorrente em muitas aplicações LOB (line-of-business) são implementações “ingênuas” do estilo arquitetural baseado em camadas, onde muitas não adicionam valor algum a solução.Não são raras aplicações SPA, que interagem com APIs “pseudo-REST” CRUD, que acionam serviços de aplicação, que apenas traduzem objetos inputmodels em commands com propriedades idênticas, que são direcionado a um mecanismo de mensageria in-memory (como o MediatR em .NET), que aciona handlers, que materializam “entidades anêmicas”, que são, submetidas a persistência em repositórios (devidamente abstraídos em interfaces), que traduzem tais entidades em objetos de persistência, que são, enfim, salvos em um banco de dados.
A justificativa comum para tanta complexidade acidental é puritanismo ingênuo ou “movimento em manada” (a ilusão de que todo mundo faz assim) envolto na crença de que tais práticas diminuem o custo de manutenção quando, na verdade, fazem com que a simples inclusão de um campo de cadastro em um CRUD implique em modificações em diversos arquivos “explodindo” o changing coupling. Trata-se do architecture sinkhole anti-pattern. Parabéns a todos os envolvidos!
“Pular” camadas, acessando camadas “mais baixas” diretamente
Com alguma frequência, algumas implementações do estilo arquitetural em camadas “afrouxam” a restrição de que, em qualquer nível, apenas a “camada imediatamente abaixo” pode ser acessada diretamente. A justificativa geralmente tem relação com o desempenho.
Uma aplicação escrita em .NET ou Java, por exemplo, ao fazer uso direto de um recurso do sistema operacional, torna-se menos “portável”.
“Camadas compartilhadas” ou verticais
Outra violação comum são as “camadas compartilhadas” ou verticais, diretamente acionadas por todas as demais camadas da solução. Geralmente, essas “camadas” fornecem features genéricas e úteis, como para instrumentação ou segurança.
Na prática, essas implementações verticais não são “camadas” de fato. Ou, ainda, em cenários mais complexos, habilitam uma visão arquitetural 2D, com uma dimensão de negócios e outra técnica, o que pode ser interessante. De qualquer forma, é importante destacar a explosão do acoplamento aferente nas camadas verticais.
Camadas em aplicações LOB (line-of-business)
Aplicações de negócios desenvolvidas com arquiteturas baseadas em camadas geralmente apresentam variações de quatro camadas lógicas: 1) apresentação; 2) negócios; 3) persistência; e 4) banco de dados. Algumas vezes, as camadas de persistência e banco de dados são “mescladas” e ampliadas para uma descrição mais genérica, como “infraestrutura”. Outras vezes, a camada de negócios é “fragmentada” em “aplicação” e “domínio”.
A reminder on Three/Multi Tier/Layer Architecture/Design Nesse artigo, Scott Hanselmann apresenta em detalhes motivações e considerações a respeito das camadas associadas com aplicações LOB |
Um jeito (nem tão) antigo de fazer software LOB
Até bem pouco tempo, a “análise de sistemas” consistia em descobrir quais relatórios e consultas um software precisaria produzir. Daí, se inferia que dados seriam necessários e que “cadastros” teriam que ser elaborados.Nesses tempos, a implementação de um sistema de software começava com a modelagem do banco de dados, somente depois eram implementadas telas de cadastro para, finalmente, elaborar relatórios. Não eram incomuns ferramentas “geradoras” de telas de cadastro (como Magic, Genero e afins) e outras para elaboração de relatórios (como Crystal Reports).
Nessa “visão antiga”, o banco de dados é o rei. Aliás, seguindo essa linha, é natural enxergar o banco como lugar ideal para implementar lógica de negócio. Há um bocado de “gente grande” que ainda pensa e desenvolve software desse jeito, procurando apenas formas de evoluir no frontend para criar “telinhas mais bonitas”.
Implicações para o Deploy
Embora o estilo arquitetural em camadas preconize a independência de cada camada, com vistas a melhoria do evolvability, não é difícil perceber que esta “promessa” não se cumpre, com base na forma como ocorrem os deploys das aplicações.
Não raro, as camadas de apresentação, negócios e persistência formam um bloco único de deploy com acoplamento eferente para o banco de dados, que segue gestão independente.
Outras vezes, a camada de apresentação é segmentada no deploy dando autonomia para atualizações e implementações de “perfumarias estéticas” (reproduzindo aqui comportamento desrespeitoso e míope, frequentemente observado no mercado).
Finalmente, há ainda cenários, geralmente em aplicações menores, onde todas as camadas formam um único bloco de deploy.
Uma boa forma de “forçar” a independência no deploy das camadas é projetar que cada layer habite em uma tier separada. |
Conway, outra vez!
As arquiteturas de 4 (as vezes 3) camadas, tão comuns em aplicações LOB, são, também expressão da lei de Conway.
Qualquer empresa que projeta um sistema, inevitavelmente, produz um projeto cuja a estrutura é uma cópia da estrutura de comunicação da organização. Melvin Conway |
Na maioria das organizações é comum encontrar times de desenvolvedores especialistas em frontend (UI e UX), backend, especialistas de negócio e DBAs. Essa “estrutura” da organização se encaixa naturalmente no modelo em camadas adotado em boa parte do mercado, se tornando assim, natural, entretanto, podendo implicar em alguns desafios para o crescimento. Quando times separados tratam de cada camada, os “limites das camadas” se convertem em pontos de “coordenação e sincronização” e, não raro, se convertem em tensão e ofensa ao lead-time.
A alternativa, separar o código nas camadas por features, se mal conduzida leva a difusão de responsabilidades gerando ainda mais demanda de “coordenação e sincronização”.
Two-pizzas rule
Jeff Bezos, fundador da Amazon, argumentou que o número ideal de membros em um time é aquele onde todos possam ser alimentados com duas pizzas – geralmente, sete pessoas. Esse posicionamento ficou conhecido como “The two pizzas rule”.
Times pequenos apresentam menos desafios para comunicação, reduzindo também problemas de coordenação e sincronização. Em uma arquitetura em quatro camadas, tradicional, com um time responsável por cada camada, teríamos então um limite razoável de 30 pessoas – mas geralmente esse número seria menor, não mais do que 20, afinal, as “alocações” na gestão do banco, por exemplo, pelo seu alto acoplamento aferente são menores. No mesmo modelo em camadas, uma tentativa de separar times por contexto delimitado, sem “colapsar” o código, restringe o número de times a, no máximo, três (~20 pessoas).
Indicações e contraindicações
O estilo arquitetural em camadas tem fit perfeito para domínios onde seja possível resolver problemas compondo soluções a partir de blocos mais primitivos. Uma camada 0, nesse contexto, forneceria os elementos mais básicos que seriam combinados nas camadas superiores, de maneira similar ao que acontece com os protocolos HTTP -> TCP -> IP.
O estilo em camadas, por outro lado, não tem fit natural com aplicações LOB modernas. Afinal, embora suportem bem evoluções tecnológicas, sofrem para acomodar adaptações necessárias para o negócio. Tendo em vista que qualquer mudança de negócio desencadeia, potencialmente, mudanças em todas as camadas, aponta eveleado changing coupling comprometendo o evolvability.
Para cada jornada, as ferramentas certas
// TODO
Antes de avançar para o próximo capítulo, recomendo algumas reflexões:
- Você já lidou, ou está lidando, com arquiteturas “viciadas” pela implementação do sinkhole anti-pattern.
- Você já lidou com arquiteturas baseadas em camadas “exóticas” em LOB? Como elas eram?
- Você enfrenta problemas de performance em sua aplicação decorrentes de decisões de manter todas as camadas fechadas?
Gostei da forma que foi abordado este parágrafo em relação a todo conteúdo exposto.
Pois em momentos, é a única solução.
Porém não podemos nos conter a isso e procurar melhores soluções para romper esses estigmas.