Projetando software em camadas

Para quem só sabe usar martelo, todo problema é prego.
Abraham Maslow

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.

O estilo arquitetural em camadas é, provavelmente, a solução mais comum de design de arquitetura disponível. Aliás, tão comum ao ponto de muitos desenvolvedores entenderem que este é o único caminho arquitetural que existe.
0
Você concorda?x

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 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).

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.

Ler capítulo

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.

As decisões de quais camadas devem ser abertas, e os porquês, devem ser registradas em ADRs. O monitoramento ativo das ligações entre as camadas é uma excelente base fitness functions.
0
Você concorda?x

Violações mais frequentes

As violações mais observadas nas implementações de arquiteturas baseadas no estilo arquitetural de camadas são 1) “pular” camadas, acessando camadas “mais baixas” diretamente; e 2) implementação “camadas compartilhadas” ou verticais
0
Alguma outra violação poderia ser mencionada aqui?x

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.


Os principais pontos negativos dessa flexibilização são o potencial aumento da complexidade de implementação na camada que está violando a restrição, por estar lidando com elementos mais fundamentais, e o aumento do acoplamento aferente da camada do nível mais baixo que está sendo acessada “indevidamente”, tornando-a mais difícil de evoluir. A “camada violadora”, por sua vez, tem seu acoplamento eferente aumentado, tornando-se mais instável (passível de falhas) e “presa” a uma determinada configuração ou plataforma operacional.
0
Algo mais?x

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”.

O curioso, entretanto, é que, modernamente, essa composição não é fiel a ideia original do estilo em camadas no sentido de que os “níveis mais baixos” fornecem, necessariamente, base operacional para construção de elementos cada vez mais elaborados nos “níveis mais altos”. Talvez essa seja a razão para o aparente “descolamento” desse estilo com soluções contemporâneas.
0
Você concorda?x

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

Acessar artigo

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”.

Sistemas com arquiteturas particionadas tecnicamente, como em implementações mais simples do estilo em camadas, são adequadas para times pequenos, com no máximo 20 pessoas. Em times maiores, arquiteturas precisam ser particionadas pelo domínio para serem sustentáveis. Essa é uma das justificativas para desenvolver arquiteturas baseadas em serviços.
0
Você concorda?x

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

Não há nenhum estilo arquitetural definitivamente ruim ou bom. Todos são mais ou menos ajustados para cada contexto de problema e é papel do arquiteto selecionar aquele que vai atender melhor as necessidades do negócio, respeitando restrições e atingindo atributos de qualidade.
0
Você concorda?x

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

Não há problema na maioria dos sistemas serem projetados utilizando arquiteturas baseadas em camadas. O que seria problema é essa ser a escolha padrão por desconhecimento de outras alternativas.
0
Você concorda?x

Utilizar “camadas” é um “começo seguro” quando arquitetos ainda estão incertos quanto a qual seria o padrão arquitetural adequado, mas algum código precisa ser iniciado. Entretanto, para garantir que a migração seja possível, é importante manter as camadas “fechadas” pelo máximo de tempo possível.
0
Você concorda?x

// 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?

Referências bibliográficas

MUNROE, Randall. Abstraction Disponível em: https://xkcd.com/676/. Acesso em: 08 mai. 2021.

Compartilhe este capítulo:

Compartilhe:

Comentários

Participe da construção deste capítulo deixando seu comentário:

Inscrever-se
Notify of
guest
1 Comentário
Oldest
Newest Most Voted
Feedbacks interativos
Ver todos os comentários
Frederico Martins
Frederico Martins
10 meses atrás

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.

Fundador e CEO da EximiaCo, atua como tech trusted advisor ajudando diversas empresas a gerar mais resultados através da tecnologia.

Mentorias

para arquitetos de software

Imersão, em grupo, supervisionada por Elemar Júnior, onde serão discutidos tópicos avançados de arquitetura de software, extraídos de cenários reais.

Consultoria e Assessoria em

Arquitetura de Software

EximiaCo oferece a alocação de um Arquiteto de Software em sua empresa para orientar seu time no uso das melhores práticas de arquitetura para projetar a evolução consistente de suas aplicações.

ElemarJúnior

Fundador e CEO da EximiaCo, atua como tech trusted advisor ajudando diversas empresas a gerar mais resultados através da tecnologia.

Podcast

Arquitetura de Software Online

55 51 9 9942 0609  |  me@elemarjr.com

+55 51 99942-0609 |  contato@eximia.co

+55 51 99942-0609  contato@eximia.co

1
0
Quero saber a sua opinião, deixe seu comentáriox
()
x