Arquitetura Gritante

Um dos meus capítulos preferidos do livro Arquitetura Limpa, do Robert C. Martin (o Uncle Bob) é um chamado Arquitetura Gritante.

O conceito é muito simples, mas certamente nos faz refletir sobre como estruturamos nossas aplicações, e como a estrutura escolhida dá uma cara para uma aplicação.

O tema da aplicação deve ser evidente na sua estrutura.

À primeira vista, esta fala pode parecer óbvia, mas vamos ver alguns exemplos para entender as nuances do problema.

Arquitetura Muda

Essa não foi o Uncle Bob que nomeou, mas o nome serve para ilustrar o problema.

Quando nos deparamos com uma estrutura de diretórios e arquivos como esta a seguir, o que podemos inferir sobre a aplicação?

|-Entities
|---CarrinhoCompras
|---Clientes
|---Enderecos
|---Pedidos
|---Produtos
|-Repositories
|---CarrinhoComprasRepository
|---ClientesRepository
|---EnderecosRepository
|---PedidosRepository
|---ProdutosRepository
|-Services
|---CarrinhoComprasService
|---ClientesService
|---EnderecosService
|---PedidosService
|---ProdutosService
|-Controllers
|---ClientesController
|---PedidosController
|---ProdutosController

Podemos deduzir que a aplicação se trata de algum tipo de pedido, que deve incluir certos produtos, e que é feito por certos clientes, que inclusive possuem endereço. Mas o que esta aplicação realmente faz? Será que ela recebe pedidos prontos de outra aplicação? Será que realmente registra os pedidos? Ou só gera relatórios?

Para responder essa perguntas precisamos investigar os fontes, olhar os controllers e os serviços para começar a ter uma ideia do que se trata. E dependendo da aplicação, ainda assim pode ser difícil.

A aplicação é, de certa, muda. Ela não fala muito sobre a aplicação e o que ela faz porque valoriza as coisas erradas.

Valas comuns (ou, inferninhos de manutenção)

Talvez você já tenha se deparado com um serviço como o ClientesService, que tem uns vinte e poucos métodos públicos. Vários deles inclusive são bastante parecidos.

Então você navega para o ClientesRepository e é aí que leva um susto: dos trinta métodos que encontra lá, uns doze são overloads com argumentos levemente diferentes. Alguns desses métodos tentam até compartilhar trechos de consultas, filtros, ordenações.

Um método do repositório que foi originalmente desenhado para atender uma situação bem específica agora está sendo usado para outra situação que não tem nada a ver. Quase por acidente, está tudo acoplado. A coisa toda é uma gororoba difícil de engolir.

Este tipo de organização do código é ruim porque tende a enfatizar os tipos de coisas que encontramos na aplicação. Mas porque seria útil enfatizar os tipos de coisas da aplicação?

Por que criar um serviço que vai acabar virando uma vala comum? Um terreno onde é despejado qualquer entulho que pareça relacionado com aquele tema?

Existe um jeito melhor.

Arquitetura Gritante

No livro Arquitetura Limpa, o Uncle Bob ilustra o que é uma arquitetura gritante usando plantas arquiteturais de casas e apartamentos:

Planta baixa ilustrada de uma casa

A planta da casa acima dispensa qualquer legenda. Facilmente entendemos quais são os cômodos, temos boa uma ideia da proporção e dos espaços.

A arquitura está gritando “QUARTO!”, “SALA!”, “COZINHA!”, “PISCINA!”.

Já esta outra, mesmo não sendo toda colorida e ilustrada, ainda consegue deixar tudo isso claro:

Planta baixa de uma casa, versão técnica

Mas então, como podemos tornar as nossas aplicações gritantes?

Fazendo a aplicação gritar

No livro Object Oriented Software Engineering: A Use Case Driven Approach, I. Jacobsel reflete sobre os elementos que devem ser enfatizados. Ele os chama de Regras Cruciais de Negócio.

As regras de negócio são o combustível de um produto: elas definem as interações dos usuários com o sistema, do sistema com outros sistemas, tudo para gerar valor para a organização.

Assim, são as regras de negócio que devem ser enfatizadas, e não elementos estruturais quase arbitrários como controllers, serviços e repositórios.

Uma das ferramentas mais úteis para delinear as regras de negócio são os diagramas UML de casos de uso, como este a seguir:

Diagrama de caso de uso da loja, alternativa ao exemplo do começo

Esses diagramas são muito explícitos ao delinear as interações do usuário com um sistema. Essas interações estão profundamente atreladas às regras de negócio que geram valor para o usuário e para a organização, e são elas que devem ser destacadas.

Muitas arquiteturas, como a Onion, a Hexagonal, a DDD, a Clean, entre outras, todas buscam enfatizar os casos de uso de uma forma ou outra.

Destacando os Casos de Uso

Se fizermos um exercício rápido de reestruturar o exemplo da aplicação do início do artigo, desta vez focando em destacar os casos de uso, a figura já muda bastante:

|-Entidades
|---Clientes
|---CarrinhoCompras
|---Enderecos
|---Pedidos
|---Produtos
|
|-CarrinhoCompras
|---AdicionarProduto
|------...
|---RemoverProduto
|------...
|-Clientes
|---RealizarCadastroNaLoja
|------...
|-Pedidos
|---RealizarPedido
|------...
|-Produtos
|---PesquisarProdutos
|------...
|
|-Controllers
|---ClientesController
|---PedidosController
|---ProdutosController

Agora podemos identificar, só de vislumbrar a estrutura de diretórios, as funcionalidades que a aplicação implementa: cadastro de usuários na loja, pesquisa de produtos, carrinho de compras e realizar pedidos.

Essa abordagem exige uma certa dose de mudança em relação a forma como muitos de nós estão acostumados a organizar o código das nossas aplicações. E esta organização traz consigo vários benefícios.

Sem mais códigos compartilhados por acidente

Uma das primeiras diferenças que notamos é a ausência do ClientesService e do ClientesRepository, que antes eram valas comuns para jogar todo e qualquer código relacionado a um cliente.

No lugar, temos em cada diretório de caso de uso, um código extremamente especializado em atender àquele caso de uso específico, e nenhum outro.

A reutilização de código de lógica de negócio não é apenas desencorajada, mas até certo ponto proibida.

Mas reutilizar código não é uma coisa boa? Às vezes não.

Reutilização de código é boa quando essa reutilização não for gerar problemas no futuro. Um tipo de problema que pode ocorrer quando a reutilização é indevida é quebrar uma regra de negócio ao atualizar outra regra que não está necessariamente relacionada.

Neste caso, uma reutilização indevida gera um acoplamento indevido. O problema é que essas valas comuns não fornecem limites claros (bounded contexts) necessários para guiar o desenvolvimento de novas funcionalidades sem o perigo de cair nessas armadilhas.

Tolerando um pouco de duplicação

Às vezes pode ser tentador compartilhar código comum entre dois casos de uso diferentes. Especialmente se alguns trechos são iguais ou bastante iguais. Mas esta similaridade é acidental na maior parte dos casos.

Para entender o porquê desta acidentalidade, podemos revisitar um dos princípios do SOLID, o Single Responsibility Principle (ou SRP), segundo o qual qualquer módulo deve ser responsável por apenas uma parte de uma funcionalidade. Nas palavras do Uncle Bob, “uma classe deve ter apenas uma razão para mudar”.

No livro Arquitetura Limpa, Robert C. Martin explica em maiores detalhes o que significa ter apenas uma razão para mudar. Para isso, precisamos nos perguntar:

O que significa ter uma apenas uma razão para mudar? Qual é a razão de mudar de uma classe ou módulo?

Segundo Uncle Bob, o usuário é a razão de mudar de uma classe ou módulo. É quando uma necessidade do usuário muda que certas regras de negócio precisam mudar junto.

Este não é o entendimento da maioria das pessoas quando discutimos SOLID. Eu mesmo tinha um entendimento bastante distinto do SRP, mais relacionado a um contrato simples e claro da classe, com poucas funções públicas, ou de preferência, apenas uma; uma classe que faz uma coisa só e bem feita.

Então, se entendermos que o usuário é a razão de ser e de mudar de uma classe, que a única responsabilidade de uma classe é performar uma função para atender um usuário específico, então o escopo da reutilização desta classe é naturalmente reduzido.

E seguindo este raciocínio, sempre que uma classe atende dois casos de uso distintos (e possivelmente dois usuários), ela pode ser vítima de um conflito de interesses, e assim, pode estar violando o princípio de responsabilidade única (SRP).

Diagrama de caso de uso com dois relatórios de horas, uma para o financeiro e outro para o DHO

Por exemplo, suponhamos que um sistema possui dois relatórios de horas trabalhadas, um para o Departamento Humano Organizacional (DHO), e outro para o Financeiro. Os dois repositórios são bastantes parecidos, então eles compartilham certos cálculos. Se os requisitos de um relatório mudam por conta dos interesses e projetos do Departamento Financeiro, e essa mudança envolver o código compartilhado, há uma grande chance dessa alteração afetar, de forma não intencional, o relatório do DHO.

Isto reforça que o uso de casos de uso genéricos é desencorajado em casos em que este compartilhamento atenda diferentes usuários, e por consequência diferentes interesses. Um exemplo disso é o caso de uso “Gerar relatório de horas”.

Podemos também olhar para dois dos Princípios de Coesão de Componentes:

  • CCP: Common Closure Principle
  • CRP: Common Reuse Principle

De acordo com o Common Closure Principle, um componente não deve ter mais de uma razão para mudar.

E segundo o Common Reuse Principle, classes que tendem a mudar juntas, no mesmo tempo e pelos motivos, devem permanecer juntas. Essa é uma forma de decidir como agrupar os fontes.

Pois bem, se um componente não deve ter mais de uma razão para mudar, e a razão de mudar é o usuário e sua necessidade, que forma melhor de organizar a aplicação do que os próprios casos de uso, que já desenham o sistemas segundo os usuários e suas interações?

Plus: Design de Sistemas

O conceito da Arquitetua Gritante não se aplica somente ao nível de aplicação, mas também no nível de design de sistemas.

Alguns exemplos disso podem ser encontrados no repositório The System Design Primer do usuário do GitHub donnemartin.

A ideia neste caso é que a arquitetura de um sistema deve parecer com o que ele se propõe a fazer.

Por exemplo, a arquitetura geral de um motor de busca para uma key-value store deve parecer com isto:

Diagrama representando um motor de busca para um armazém de chave-valor

Enquanto a estrutura de dados de uma rede social poderia parecer com isto:

Diagrama representando o design de uma estrutura de dados para uma rede social

Se você gostou do post, ou gostaria de contribuir, deixe um comentário!

Outras opções para compartilhar:
comments powered by Disqus