DEV Community

A Arquitetura simples

Hugo Marques on August 20, 2022

Disclaimer: Os exemplos usados aqui embora baseados em projetos reais são de minha autoria. Eu simplifico/altero alguns detalhes de forma a simpli...
Collapse
 
marcelomrwin profile image
Marcelo Daniel Silva Sales

Parabéns pelo artigo e obrigado por compartilhar sua visão e experiência. Poderia endossar praticamente tudo que você mencionou neste artigo.

Eu somente gosto de trazer a tona uma deficiência da comunidade como um todo que diz respeito a conceitos. Estas propostas de arquiteturas de aplicação citadas estão repletas de conceitos, normalmente abordagens para problemas conhecidos.

Muitas delas não lhe dizem como você deve exatamente implementar, apenas mencionam que determinadas preocupações precisam ser tratadas.

Vou citar um exemplo muito simples, apesar de ser mais relevante pra quem usa java creio que qualquer desenvolvedor de outra linguagem vai entender o objetivo.

Imagine que existe uma regra de negócio que o CPF deve estar válido (isso pode ser tratado como regra de formatação ou algo assim, mas tem o famoso mod11 e tal). Uma outra regra para o nome ter entre 3 e 100 caracteres, e outra regra de que a data de nascimento deve estar obrigatoriamente no passado. Como se tratam de regras de domínio de negócio a sugestão é que estas sejam tratadas nas classes de domínio. Pois bem, se usar Beans Validation do java, três anotações em atributos de uma classe de domínio ou DTO resolvem isso. Se no controller vc adicionar uma anotação @valid o framework vai invocar estas regras bem antes de processar o controller ou service/entidades. Ai fica a questão: É válido chamar uma regra de domínio na camada do controller?

Entendo que isso esteja bem alinhado com o que muitos de nós já enfrentamos. Todos querem fazer sistemas à prova de tudo e perdemos tempo e dinheiro protegendo e preparando a aplicação para um cenário que simplesmente não existe. Creio que você foi muito feliz ao não atacar a metodologia mas sim a implementação. Muitos dos princípios que você adotou são usados pelas sugestões de arquitetura, apenas muda a nomenclatura mas os princípios são os mesmos.

Como sempre, o bom senso deve prevalecer e só nos alinharmos a uma arquitetura mais complexa quando precisarmos lidar com problemas e cenários mais complexos.

Tenho um exemplo que gosto de citar sobre um sistema monolitico com um banco H2 que suportou o fluxo de uma transpordora por um mês inteiro depois que uma versão mais moderna não parava de pé por consumir mais recursos e trabalhar distribuida num ambiente com uma infraestrutura de rede instável.

Collapse
 
hugaomarques profile image
Hugo Marques

Opa Marcelo, obrigado pelo comentário. Gostei bastante do seu exemplo com o Beans Validation.

Sendo bem honesto, eu talvez sendo o paranóico que eu sou, implementaria a validação no controller (fail-fast) e DE NOVO no domínio (entidades consistentes).

Você está certo, o foco do post foi mais mostrar como eu vi sistemas sendo feitos e demonstrar como isso quebraria outras arquiteturas mas mesmo assim os sistemas funcionam e são bem testados/etc...

A única parte que eu discordo levemente de você é essa aqui:

Muitas delas não lhe dizem como você deve exatamente implementar, apenas mencionam que determinadas preocupações precisam ser tratadas.

No caso da Clean especificamente. No livro que descreve a Clean o autor é BEM prescritivo e deixa claro como deve ser implementada. Somos obrigados a segui-lo cegamente? Não. Mas aí a nossa arquitetura é mais uma clean-hugo ou clean-marcelo do que a clean-architecture (e tudo bem ser assim, tá?).

Se curtiu dá uma olhada nos outros posts, seu comentário me agregou bastante. Obrigadão!

Collapse
 
kauanmocelin profile image
Kauan Mocelin

Olá Hugo, muito interessante teu artigo principalmente quando relata problemas enfrentados no "mundo real" que estão poucos ligados de fato ao modelo arquitetônico da solução.

Tenho algumas questões e gostaria do teu ponto de vista. Quero ressaltar que são dúvidas embasadas em domínios que possuem regras de negócio e não CRUDs:

  1. Você diz que as entidades em certos momentos podem ser anêmicas ou ricas. Como você faz OOP em cima de entidades anêmicas? Não consigo imaginar isso de nenhuma forma pois são somente estruturas de dados passando para os casos de uso/serviço e todas as regras de negócio ficando por conta dessa camada.
  2. Quando a entidade de persistência e domínio são representados na mesma classe noto problemas de encapsulamento, por exemplo para que o Hibernate funcione exige construtor padrão e get/set para os atributos. Como garantir encapsulamento para uma classe assim?
  3. Quando faz o acesso do caso de uso há uma dependência externa acessando diretamente a classe concreta não seria um pouco ingênuo? Acho que o preço de por uma interface ali pra abstrair muito barato do que, se no futuro, tiver que acessar outro serviço externo ou a API do atual mudar. Já tive essa dor de depender de classe externa e ter que alterar inúmeros pontos de chamada puramente por não ter abstraído isso quando foi concebido.
Collapse
 
hugaomarques profile image
Hugo Marques

Boas perguntas Kauan! Vou considerar o disclaimer que você mencionou (não-cruds). Vamos lá:

  1. Nesse caso não faz né? Se seu domínio exige entidades ricas por qualquer que seja o motivo eu não vejo razão pra tentar justificar um domínio anêmico. Esse é o principal mote do meu texto: "Os requisitos e o teu problemas que vão guiar o teu design". Se o seu design requer um domínio complexo então as suas entidades não serão anêmicas e logo você vai usar OOP. Se o seu domínio for composto apenas de estrutura de dados indo pra lá e pra cá então você vai ser mais anêmico, menos OO e mais procedural.
  2. Concordo, você precisa obedecer as regras do framework. Mas aí fica a pergunta: Por que você precisa do encapsulamento no caso específico de uma entidade anêmica? A vantagem do encapsulamento ali vale mais do que a simplificidade de uso do framework? Se sim, eu diria pra manter os modelos separados, se não então mantém eles juntos.
  3. Porque seria ingênuo? O Linhares na thread que originou essa discussão fez um excelente ponto que software complexo de vdd é extremamente difícil de você olhar o futuro e acertar qual vai ser a interface correta. Existem boas chances do nosso serviço que você pensou não encaixar direitinho na interface que você possuia. Tendo dito isso, note que eu descrevo qual foi minha experiência nos últimos 8 anos. Os nossos serviços dependem exclusicamente de um serviço externo que é fornecido por uma equipe específica. Se o fornecedor mudar, provavelmente será a mesma equipe que vai mudar ele, eu só preciso mudar a implementação da minha Facade em 1 único ponto. No nosso caso que você teve que alterar diversos pontos, o que deve ter ocorrido foi uma Facade não ser bem definida e detalhes externos vazaram para dentro do caso de uso. Se você trabalha em um software que dependencias externas mudam ou podem mudar com frequência, uma interface faz sentido, até porque você provavelmente tem os requisitos necessários pra criar uma abstração. Agora, criar uma abstração porque as coisas podem mudar, sem requisitos, só no "eu acho que vai mudar", existem boas chances que a sua interface não vai conseguir abstrair o contrato de fato. Como eu falei, tem software rodando faz 10 anos com os mesmos serviços como dependência, isso cobriu 90% dos casos. Valeria realmente a pena ter gastado energia pros outros 10%? No nosso contexto, não valeu e não vale à pena.

Gostei demais das perguntas, torna a discussão bem rica! obrigado demais por participar.

Collapse
 
jordihofc profile image
Jordi Henrique Silva

Kauan,
Atualmente para que as entidades sejam gerenciadas através do EntityManager ou Session do Hibernate não é necessário que a classe contenha getters e setters, apenas o construtor default. Então é possível favorece o uso do construtor cheio ou de builders para manter o encapsulamento, e também distribuir a responsabilidades de atualizar o estado dos atributos a metodos. Dessa forma podemos nos beneficiar do mecanismo de Dirty Checking do Hibernate para que ele mesmo defina qual são as melhores operações SQL e o melhor momento para atualizar o estado da entidade no BD.

Collapse
 
edmilsontorres profile image
Edmilson Torres

Excelente conteúdo!
Sou estagiário back-end e já estava ficando maluco tentando entender clean arch :D Fiz uma API simples de quiz usando n-tiers, mas falta muita coisa para implementar e colocar SOLID e padrões de projeto que estou aprendendo em prática nela. Obrigado pela clareada de ideias.

Collapse
 
fabianogoes profile image
Fabiano Góes • e-Programar

Cara muito legal o seu artigo, bem didático e pratico ao mesmo tempo, ficou bem simples pra que está querendo aprender sobre design de código, já sai sabendo os porquês de muita coisa.
Parabéns pelo belo trabalho!

Collapse
 
mrbrunelli profile image
Matheus Ricardo Brunelli

Muito bom o artigo Hugo, bem escrito e fácil de compreender.

Eu ia comentar minha dúvida, mas os comentários estão tão bons que já não tenho mais dúvidas.

A paz de espírito que tenho em mexer em códigos com mapeamentos 1:1 é indescritível haha, um momento de descanso das abstrações complexas. Eu gosto muito dessa pegada mais simples, mas meu ego idiota fica pensando em melhoria precoce o tempo todo.

Collapse
 
edyonil profile image
edyonil

Bom artigo. Parabéns pelo material. Agora para mim não ficou claro qual a diferença entre essa arquitetura simples e uma arquitetura limpa! Poderia exemplificar?

Collapse
 
hugaomarques profile image
Hugo Marques

A arquitetura limpa prega menos acoplamento com os frameworks e tecnologias específicas. Ela também faz uma inversão na dependência fazendo a camada de persisência depender do domínio e não o contrário como na clássica arquitetura n-tier (renomeada aqui pra arquitetura "simples).

Vê esse artigo aqui que pode ficar mais claro: zup.com.br/blog/clean-architecture...

Se você ainda tiver dúvidas, dá o toque que a gente conversa mais sobre o assunto :)

Collapse
 
edyonil profile image
edyonil

Certo. Na verdade uso arquitetura limpa no meu dia a dia. Como no caso você não deixou claro a sua relação de dependências com as outras camadas, se são camadas se comunicando com interfaces ou com classes mais concretas fiquei curioso para achar.

Com pequenas nomenclaturas essa arquitetura se tornaria um arquitetura limpa.

Mas obrigado pela resposta.

Thread Thread
 
hugaomarques profile image
Hugo Marques

Mas eu deixo claro a relação de dependência. Você pode ver pelas setas no segundo diagrama de caso de uso ou no diagrama de camadas que existe uma dependencia explícita entre a camada de caso de uso e a persistência, por exemplo.

A outra questão é que a dependência se dá a classe concretas, interfaces apenas quando temos mais de 1 implementação do mesmo contrato.

Para essa arquitetura se tornar limpa teríamos que mudar algumas coisas:

  1. Caso de uso depende de uma interface
  2. Persist6encia depende da interface (invertendo a dependencia)
  3. DTOs entre cada camada e manter as entidades de negócio dentro da camada de negócio sem uso dos frameworks.

Não é algo difícil de fazer pra ser honesto mas o resultado, embora sutil, é bem diferente.

Collapse
 
thalocofficial profile image
thales

Em relação ao tratamento de erros, você viu em algum momento da sua experiência a necessidade de "traduzir" as exceções entre as camadas (assim como você comentou com os DTOs)?

Collapse
 
hugaomarques profile image
Hugo Marques

No geral, eu evito esse captura/captura de exceções e traduz/traduz entre camadas. Eu gosto de classificar as ações entre retryable e non-retryable e deixa ela estourar até a camada mais alto (controller nesse caso).

Você acabou de me dar a idéia pra um excelente post! Eu já falei bastante sobre isso no passado, vou escrever pra vocês ;)

Collapse
 
daianerangel profile image
Daiane Rangel

Parabéns pelo artigo! Excelente conteúdo!

Collapse
 
gilsonsilvati profile image
Gilson Silva

Post muito bom e bem escrito. Parabéns!!!

Collapse
 
rafaelbmateus profile image
Rafael Mateus

Muito bom o post, valeu por compartilhar!

Collapse
 
marabesi profile image
Marabesi

tem a versão em inglês desse post?

Collapse
 
hugaomarques profile image
Hugo Marques

Nope. 100% original em português. Quem sabe eu traduzo?

Collapse
 
thiagotrs profile image
Thiago Rotondo Sampaio

Artigo muito bom. Tenho algumas questões.

  1. Fazer a o caso de uso depender de classes concretas dos adaptadores ao invés de interfaces é por existir só uma implementação desse adaptador?
  2. O mapeamento 1:1 das entidades para modelo se deve a elas serem anêmicas? se fossem ricas passar essa entidade para a persistência, não poderia ser probemático?
  3. Na arquitetura simples as regras de negócio ficariam todas nos casos de uso com entidades anêmicas?
  4. A arquitetura simples parece com o que os frameworks MVC fazem, essa é a ideia?
Collapse
 
hugaomarques profile image
Hugo Marques
  1. Isso, não há motivo de criar uma interface se você só tem uma única implementação.
  2. Sim e não. Mesmo que as classes não sejam anêmicas, por exemplo, o mapeamento só afeta os atributos. Ainda é possível ter uma classe com comportamento e anotada. Eu separaria elas se os atributos começassem a divergir.
  3. Não. Não existe regra definindo que você vai deixar tudo nos casos de uso ou nas entidades. Eu gosto de pensar nos casos de uso como "cola" entre entidades e as regras de negócio de cada entidade na própria entidade.
  4. Exato! Na vdd o que eu charmei de "arquitetura simples" se vc olhar bem de perto se assemelha bastante à classica n-tier architecture que tantos frameworks utilizam 😉.

Espero que tenha gostado. Mto obrigado pelas perguntas e se tiver mais dúvidas manda aí!

Collapse
 
fabianogoes profile image
Fabiano Góes • e-Programar

Fala Hugo, parabéns pelo post e muito obrigado por compartilhar sua reflexão sobre o assunto.
Tenho passado por experiências parecidas, onde me incomodei um pouco em seguir by-the-book tanto a Arquitetura Limpa quanto a Hexagonal, confesso que gosto mais da Hexagonal, mas você mencionou um ponto bem importante(IMHO) onde o case na Netflix eles usaram para resolver um problema, e isso tem me motivado muito quando estou refletindo sobre algum design, qual problema queremos resolver com aquele design?
Tenho tentado pensar em não usar determinada arquitetura só porque alguém usou sem refletir qual o problema esse alguém resolveu com aquela arquitetura.
Em algumas experiências eu senti que trouxe mais complexidade do que ajudou a resolver problemas.
Hoje em dia penso em algo “passeado” em arquitetura limpa mas adaptado sabe (:
Tipo o exemplo que vc deu das entidades de persistência 1:1 com DTO
Enfim, gostei muito da sua reflexão e pensamento do KISS e YAGNI sempre que estiver optando tento pela Arquitetura Limpa quanto Hexagonal.
Um abraço!