À medida que o cenário digital evolui, também evolui a complexidade dos sites modernos. Com uma demanda crescente por melhor experiência do usuário e recursos avançados, os desenvolvedores de front-end enfrentam o desafio de criar arquiteturas escaláveis, sustentáveis e eficientes.
Entre a infinidade de artigos e recursos disponíveis sobre arquitetura frontend, um número significativo é focado na Arquitetura Limpa e sua adaptação. De fato, mais de 50% dos quase 70 artigos pesquisados discutem a Arquitetura Limpa no contexto do desenvolvimento front-end.
Apesar da riqueza de informações, uma questão gritante persiste: muitas das ideias arquitetônicas propostas podem nunca ter sido implementadas em ambientes de produção do mundo real. Isso gera dúvidas sobre sua eficácia e aplicabilidade em cenários práticos.
Impulsionado por essa preocupação, embarquei em uma jornada de seis meses para implementar a Clean Architecture no frontend, permitindo-me confrontar a realidade dessas ideias e separar o joio do trigo.
Neste artigo, compartilharei minhas experiências e insights dessa jornada, oferecendo um guia abrangente sobre como implementar com sucesso a Arquitetura Limpa no frontend.
Ao esclarecer os desafios, as melhores práticas e as soluções do mundo real, este artigo visa fornecer aos desenvolvedores de front-end as ferramentas necessárias para navegar no mundo em constante evolução do desenvolvimento de sites.
No ecossistema digital em rápida evolução de hoje, os desenvolvedores têm muitas opções quando se trata de estruturas de front-end. Essa abundância de opções resolve vários problemas e simplifica o processo de desenvolvimento.
No entanto, também leva a debates intermináveis entre os desenvolvedores, cada um alegando que seu framework preferido é superior aos outros. A verdade é que, em nosso mundo acelerado, novas bibliotecas JavaScript surgem diariamente e estruturas são introduzidas quase mensalmente.
Para manter a flexibilidade e a adaptabilidade em um ambiente tão dinâmico, precisamos de uma arquitetura que transcenda estruturas e tecnologias específicas.
Isso é particularmente crucial para empresas de produtos ou contratos de longo prazo que envolvam manutenção, onde mudanças de tendências e avanços tecnológicos devem ser acomodados.
Ser independente dos detalhes, como frameworks, nos permite focar no produto em que estamos trabalhando e nos preparar para mudanças que possam surgir durante seu ciclo de vida.
Não tema; este artigo pretende dar uma resposta a este dilema.
Em minha busca para implementar a Arquitetura Limpa no frontend, trabalhei em estreita colaboração com vários desenvolvedores fullstack e backend para garantir que a arquitetura fosse compreensível e de fácil manutenção, mesmo para aqueles com experiência mínima em frontend.
Portanto, um dos principais requisitos de nossa arquitetura é sua acessibilidade para desenvolvedores de back-end que podem não ser versados nas complexidades do front-end, bem como desenvolvedores de full-stack que podem não ter ampla experiência em front-end.
Ao promover a cooperação perfeita entre as equipes de front-end e back-end, a arquitetura visa preencher a lacuna e criar uma experiência de desenvolvimento unificada.
Infelizmente, para construir coisas incríveis, precisamos obter algum conhecimento prévio. Uma compreensão clara dos princípios subjacentes não apenas facilitará o processo de implementação, mas também garantirá que a arquitetura siga as melhores práticas de desenvolvimento de software.
Nesta seção, apresentaremos três conceitos-chave que formam a base de nossa abordagem arquitetônica: princípios SOLID , arquitetura limpa (que na verdade vem dos princípios SOLID) e design atômico . Se você tem uma forte opinião sobre essas áreas, pode pular esta seção.
SOLID é um acrônimo que representa cinco princípios de design que orientam os desenvolvedores na criação de software escalável, sustentável e modular:
Se você gostaria de explorar este tópico com mais profundidade, o que eu recomendo fortemente que você faça, então não há problema. No entanto, por enquanto, o que apresentei é suficiente para ir mais longe.
E o que o SOLID nos dá em termos deste artigo?
Robert C. Martin, baseado nos princípios SOLID e em sua vasta experiência no desenvolvimento de diversas aplicações, propôs o conceito de Clean Architecture. Ao discutir esse conceito, o diagrama abaixo é frequentemente referenciado para representar visualmente sua estrutura:
Então, Clean Architecture não é um conceito novo; tem sido amplamente utilizado em vários paradigmas de programação, incluindo programação funcional e desenvolvimento de back-end.
Bibliotecas como Lodash e várias estruturas de back-end adotaram essa abordagem arquitetônica, que está enraizada nos princípios SOLID.
A Arquitetura Limpa enfatiza a separação de preocupações e a criação de camadas independentes e testáveis dentro de um aplicativo, com o objetivo principal de tornar o sistema fácil de entender, manter e modificar.
A arquitetura é organizada em círculos ou camadas concêntricas; cada um tendo limites claros, dependências e responsabilidades:
A Arquitetura Limpa promove o fluxo de dependências das camadas externas para as camadas internas, garantindo que a lógica principal do negócio permaneça independente das tecnologias ou estruturas específicas usadas.
Isso resulta em uma base de código flexível, passível de manutenção e testável que pode se adaptar facilmente às mudanças de requisitos ou pilhas de tecnologia.
O Atomic Design é uma metodologia que organiza os componentes da interface do usuário dividindo as interfaces em seus elementos mais básicos e, em seguida, remontando-os em estruturas mais complexas. Brad Frost introduziu o conceito pela primeira vez em 2008 em um artigo intitulado "Atomic Design Methodology".
Aqui está um gráfico mostrando o conceito de Atomic Design:
É composto por cinco níveis distintos:
Ao adotar o Atomic Design, os desenvolvedores podem colher vários benefícios, como modularidade, reusabilidade e uma estrutura clara para os componentes de UI, pois exige que sigamos a abordagem do Design System, mas esse não é o tópico deste artigo, então continue.
A fim de desenvolver uma perspectiva bem informada sobre Arquitetura Limpa para desenvolvimento de front-end, embarquei em uma jornada para criar um aplicativo. Durante um período de seis meses, ganhei informações e experiências valiosas enquanto trabalhava neste projeto.
Consequentemente, os exemplos fornecidos ao longo deste artigo são extraídos de minha experiência prática com o aplicativo. Para manter a transparência, todos os exemplos são derivados de código acessível publicamente.
Você pode explorar o resultado final visitando o repositório em
Como mencionado anteriormente, existem inúmeras implementações de Arquitetura Limpa disponíveis online. No entanto, alguns elementos comuns podem ser identificados nessas implementações:
Ao entender essas semelhanças, podemos apreciar a estrutura fundamental da Clean Architecture e adaptá-la às nossas necessidades específicas.
A parte principal do nosso aplicativo contém:
Casos de uso : Os casos de uso descrevem as regras de negócios para várias operações, como salvar, atualizar e buscar dados. Por exemplo, um caso de uso pode envolver a obtenção de uma lista de palavras do Notion ou o aumento da frequência diária do usuário para palavras aprendidas.
Essencialmente, os casos de uso lidam com as tarefas e processos do aplicativo de uma perspectiva de negócios, garantindo que o sistema funcione de acordo com os objetivos desejados.
Modelos : os modelos representam as entidades de negócios dentro do aplicativo. Eles podem ser definidos usando interfaces TypeScript, garantindo que estejam alinhados com as necessidades e requisitos de negócios.
Por exemplo, se um caso de uso envolver a busca de uma lista de palavras do Notion, você precisará de um modelo para descrever com precisão a estrutura de dados dessa lista, respeitando as regras e restrições de negócios apropriadas.
Operações : às vezes, definir certas tarefas como casos de uso pode não ser viável, ou você pode querer criar funções reutilizáveis que possam ser empregadas em várias partes do seu domínio. Por exemplo, se você precisar escrever uma função para pesquisar uma palavra de noção pelo nome, é aqui que essas operações devem residir.
As operações são úteis para encapsular a lógica específica do domínio que pode ser compartilhada e utilizada em vários contextos dentro do aplicativo.
Interfaces de repositório : os casos de uso requerem um meio para acessar os dados. De acordo com o Princípio de Inversão de Dependência, a camada de domínio não deve depender de nenhuma outra camada (enquanto as outras camadas dependem dela); portanto, esta camada define as interfaces para os repositórios.
É importante observar que ele especifica as interfaces, não os detalhes de implementação. Os próprios repositórios utilizam o Repository Pattern, que é independente da fonte de dados real e enfatiza a lógica para buscar ou enviar dados de e para essas fontes.
É importante mencionar que um único repositório pode implementar várias APIs e um único Caso de Uso pode utilizar vários repositórios.
Essa camada é responsável pelo acesso aos dados e pode se comunicar com várias fontes conforme necessário. Considerando que estamos desenvolvendo um aplicativo front-end, essa camada servirá principalmente como um wrapper para as APIs do navegador.
Isso inclui APIs para REST, armazenamento local, IndexedDB, síntese de voz e muito mais.
É importante observar que, se você deseja gerar tipos de OpenAPI e clientes HTTP, a camada de API é o local ideal para colocá-los. Dentro desta camada, temos:
Adaptador de API : O Adaptador de API é um adaptador especializado para APIs de navegador utilizadas em nosso aplicativo. Este componente gerencia chamadas REST e comunicação com a memória do aplicativo ou qualquer outra fonte de dados que você deseja usar.
Você pode até criar e implementar seu próprio sistema de armazenamento de objetos, se desejar. Ao ter um adaptador de API dedicado, você pode manter uma interface consistente para interagir com várias fontes de dados, tornando mais fácil atualizá-los ou alterá-los conforme necessário.
A camada de repositório desempenha um papel crucial na arquitetura do aplicativo, gerenciando a integração de várias APIs, mapeando tipos específicos de API para tipos de domínio e incorporando operações para transformar dados.
Se você deseja combinar a API de síntese de voz com armazenamento local, por exemplo, este é o lugar perfeito para fazê-lo. Esta camada contém:
A camada adaptadora é responsável por orquestrar as interações entre essas camadas e vinculá-las. Esta camada contém apenas módulos responsáveis por:
A camada de apresentação é responsável por renderizar a interface do usuário (UI) e manipular as interações do usuário com o aplicativo. Ele aproveita o adaptador, o domínio e as camadas compartilhadas para criar uma IU funcional e interativa.
A camada de apresentação emprega a metodologia Atomic Design para organizar seus componentes, resultando em um aplicativo escalável e de fácil manutenção. No entanto, esta camada não será o foco principal deste artigo, pois não é o assunto principal em termos de implementação de Arquitetura Limpa.
Um local designado é necessário para todos os elementos comuns, como utilitários centralizados, configurações e lógica compartilhada. No entanto, não vamos nos aprofundar muito nessa camada neste artigo.
Vale a pena mencionar apenas para fornecer uma compreensão de como os componentes comuns são gerenciados e compartilhados em todo o aplicativo.
Agora, antes de mergulhar na codificação, é essencial discutir os testes. Garantir a confiabilidade e correção de seu aplicativo é vital e é crucial implementar uma estratégia de teste robusta para cada camada da arquitetura.
Ao implementar uma estratégia de teste abrangente para cada camada da arquitetura, você pode garantir a confiabilidade, correção e capacidade de manutenção de seu aplicativo, reduzindo a probabilidade de introdução de bugs durante o desenvolvimento.
No entanto, se você estiver construindo um aplicativo pequeno, os testes de integração na camada do adaptador devem ser suficientes.
Tudo bem, agora que você tem uma compreensão sólida da Arquitetura Limpa e talvez até tenha formado sua própria opinião sobre ela, vamos nos aprofundar um pouco mais e explorar algum código real.
Lembre-se de que apresentarei apenas um exemplo simples aqui; no entanto, se você estiver interessado em exemplos mais detalhados, sinta-se à vontade para explorar meu repositório GitHub mencionado no início deste artigo.
Na "vida real", a Clean Architecture realmente brilha em grandes aplicativos de nível empresarial, embora possa ser um exagero para projetos menores. Dito isso, vamos ao que interessa.
Usando meu aplicativo como exemplo, demonstrarei como realizar uma chamada de API para buscar sugestões de dicionário para uma determinada palavra. Esse terminal de API específico recupera uma lista de significados e exemplos por web scraping de dois sites.
Do ponto de vista comercial, esse terminal é crucial para a exibição "Localizar palavra", que permite aos usuários pesquisar uma palavra específica. Depois que o usuário encontra a palavra e faz login, ele pode adicionar as informações extraídas da Web ao banco de dados do Notion.
Para começar, devemos estabelecer uma estrutura de pastas que reflita com precisão as camadas que discutimos anteriormente. A estrutura deve se parecer com o seguinte:
client ├── adapter ├── api ├── domain ├── presentation ├── repository └── shared
O diretório do cliente serve a um propósito semelhante ao da pasta "src" em muitos projetos. Neste projeto específico do Next.js, adotei a convenção de nomear a pasta front-end como "cliente" e a pasta back-end como "servidor".
Essa abordagem permite uma distinção clara entre os dois principais componentes do aplicativo.
Escolher a estrutura de pastas correta para o seu projeto é, de fato, uma decisão crucial que deve ser tomada no início do processo de desenvolvimento. Diferentes desenvolvedores têm suas próprias preferências e abordagens quando se trata de organizar recursos.
Alguns podem agrupar recursos por nomes de página, outros podem seguir as convenções de nomenclatura de subdiretórios geradas pelo OpenAPI e, ainda, outros podem acreditar que seu aplicativo é muito pequeno para justificar qualquer uma dessas soluções.
A chave é escolher uma estrutura que melhor atenda às necessidades específicas e à escala do seu projeto, mantendo uma organização clara e sustentável dos recursos.
Estou no terceiro grupo, então minha estrutura fica assim:
client ├── adapter │ ├── local-storage │ ├── rest │ ├── speech-synthesis │ └── supabase ├── api │ ├── local-storage │ ├── rest │ ├── speech-synthesis │ └── supabase ├── domain │ ├── local-storage │ ├── rest │ ├── speech-synthesis │ ├── supabase └── repository ├── local-storage ├── rest ├── speech-synthesis └── supabase
Decidi omitir as camadas compartilhada e de apresentação neste artigo, pois acredito que aqueles que desejam se aprofundar podem consultar meu repositório para obter mais informações. Agora, vamos prosseguir com alguns exemplos de código para ilustrar como a Arquitetura Limpa pode ser aplicada em um aplicativo front-end.
Vamos considerar nossos requisitos. Como usuário, gostaria de receber uma lista de sugestões, incluindo seus significados e exemplos. Portanto, uma única sugestão de dicionário pode ser modelada da seguinte forma:
interface DictionarySuggestion { example: string; meaning: string; }
Agora que descrevemos uma única sugestão de dicionário, é importante mencionar que às vezes a palavra obtida por meio do web scraping difere ou é corrigida em relação ao que o usuário digitou. Para acomodar isso, usaremos a versão corrigida posteriormente em nosso aplicativo.
Consequentemente, precisamos definir uma interface que inclua uma lista de sugestões de dicionário e correções de palavras. A interface final fica assim:
export interface DictionarySuggestions { suggestions: DictionarySuggestion[]; word: string; }
Estamos exportando essa interface, e é por isso que a palavra-chave export
está incluída.
Temos nosso modelo e agora é hora de colocá-lo em uso.
import { DictionarySuggestions } from './rest.models'; export interface RestRepository { getDictionarySuggestions: (word: string) => Promise<DictionarySuggestions | null>; }
Neste ponto, tudo deve estar claro. É importante observar que não estamos discutindo a API aqui! A estrutura do repositório em si é bastante simples: apenas um objeto com alguns métodos, onde cada método retorna dados de um tipo específico de forma assíncrona.
Lembre-se de que o repositório sempre retorna dados no formato de modelo de domínio.
Agora, vamos definir nossa regra de negócios como um caso de uso. O código fica assim:
export type GetDictionarySuggestionsUseCaseUseCase = UseCaseWithSingleParamAndPromiseResult< string, DictionarySuggestions | null >; export const getDictionarySuggestionsUseCase = ( restRepository: RestRepository, ): GetDictionarySuggestionsUseCaseUseCase => ({ execute: (word) => restRepository.getDictionarySuggestions(word), });
A primeira coisa a observar é a lista de tipos comuns usados para definir casos de uso. Para conseguir isso, criei um arquivo use-cases.types.ts
no diretório do domínio:
domain ├── local-storage ├── rest ├── speech-synthesis ├── supabase └── use-cases.types.ts
Isso me permite compartilhar facilmente tipos para casos de uso entre meus subdiretórios. A definição de UseCaseWithSingleParamAndPromiseResult
se parece com isto:
export interface UseCaseWithSingleParamAndPromiseResult<TParam, TResult> { execute: (param: TParam) => Promise<TResult>; }
Essa abordagem ajuda a manter a consistência e a capacidade de reutilização dos tipos de casos de uso na camada de domínio.
Você pode estar se perguntando por que precisamos da função execute
. Aqui, temos uma fábrica que retorna o caso de uso real.
Essa escolha de design se deve ao fato de que não queremos fazer referência à implementação do repositório diretamente no código do caso de uso, nem queremos que o repositório seja usado por uma importação. Essa abordagem nos permite aplicar facilmente a injeção de dependência posteriormente.
Ao usar o padrão de fábrica e a função execute
, podemos manter os detalhes de implementação do repositório separados do código do caso de uso, o que melhora a modularidade e a capacidade de manutenção do aplicativo.
Essa abordagem segue o Princípio de Inversão de Dependência, onde a camada de domínio não depende de nenhuma outra camada, e permite maior flexibilidade na hora de trocar diferentes implementações de repositório ou modificar a arquitetura do aplicativo.
Primeiro, vamos definir nossa interface:
export interface RestApi { getDictionarySuggestions: (word: string) => Promise<AxiosResponse<DictionarySuggestions>>; }
Como você pode ver, a definição dessa função na interface é muito parecida com a do repositório. Como o tipo de domínio já descreve a resposta, não há necessidade de recriar o mesmo tipo.
É importante observar que nossa API retorna dados brutos, e é por isso que retornamos o AxiosResponse<DictionarySuggestions>
completo. Ao fazer isso, mantemos uma separação clara entre a API e as camadas de domínio, permitindo mais flexibilidade na manipulação e transformação de dados.
A implementação desta API fica assim:
export const getRestApi = (axiosInstance: AxiosInstance): RestApi => ({ getDictionarySuggestions: async (word: string) => { const encodedCurrentDate = encodeURIComponent(word); const response = await axiosInstance.get( `${RestEndpoints.GET_DICTIONARY_SUGGESTIONS}?word=${encodedCurrentDate}`, ); return response; } });
Neste ponto, as coisas ficam mais interessantes. O primeiro aspecto importante a discutir é a injeção de nosso axiosInstance
. Isso torna nosso código muito flexível e nos permite construir testes sólidos facilmente. Este também é o local onde lidamos com a codificação ou análise dos parâmetros de consulta.
No entanto, você também pode executar outras ações aqui, como cortar a string de entrada. Ao injetar o axiosInstance
, mantemos uma clara separação de preocupações e garantimos que a implementação da API seja adaptável a diferentes cenários ou mudanças nos serviços externos.
Como nossa interface já está definida pelo domínio, basta implementar nosso repositório. Assim, a implementação final fica assim:
export const getRestRepository = (restApi: RestApi): RestRepository => ({ getDictionarySuggestions: async (word) => { const { data } = await restApi.getDictionarySuggestions(word); if (!data?.suggestions?.length) { return null; } return formatDictionarySuggestions(data); } });
Um aspecto importante a ser mencionado está relacionado às APIs. Nosso getRestRepository
nos permite passar um restApi
previamente definido. Isso é vantajoso porque, como mencionado anteriormente, permite testes mais fáceis. Podemos examinar brevemente formatDictionarySuggestions
:
export const formatDictionarySuggestions = ({ suggestions, word, }: DictionarySuggestions): DictionarySuggestions => { const cleanedWord = cleanUpString(word); const cleanedSuggestions = suggestions.map((_suggestion) => { const cleanedMeaning = cleanUpString(_suggestion.meaning); const cleanedExample = cleanUpString(_suggestion.example); return { meaning: cleanedMeaning, example: cleanedExample, }; }); return { word: cleanedWord, suggestions: cleanedSuggestions, }; };
Essa operação usa nosso modelo DictionarySuggestions
de domínio como um argumento e executa uma limpeza de string, o que significa remover espaços desnecessários, quebras de linha, tabulações e letras maiúsculas. É bastante simples, sem complexidades ocultas.
Uma coisa importante a observar é que, neste ponto, você não precisa se preocupar com a implementação da API. Como lembrete, o repositório sempre retorna dados no modelo de domínio! Não pode ser de outra forma porque isso quebraria o princípio da inversão de dependência.
E por enquanto, nossa camada de domínio não depende de nada definido fora dela.
Neste ponto, tudo deve estar implementado e pronto para injeção de dependência. Aqui está a implementação final do módulo rest:
import { getRestRepository } from '@repository/rest/rest.repository'; import { getRestApi } from '@api/rest/rest.api'; import { getDictionarySuggestionsUseCase } from '@domain/rest/rest.use-cases'; import { axiosInstance } from '@shared/axios.instance'; const restApi = getRestApi(axiosInstance); const restRepository = getRestRepository(restApi); export const restModule = { getDictionarySuggestions: getDictionarySuggestionsUseCase(restRepository).execute, };
Isso mesmo! Passamos pelo processo de implementação dos princípios da Arquitetura Limpa sem estar vinculado a uma estrutura específica. Essa abordagem garante que nosso código seja adaptável, facilitando a troca de estruturas ou bibliotecas, se necessário.
Quando se trata de testes, verificar o repositório é uma ótima maneira de entender como os testes são implementados e organizados nessa arquitetura.
Com uma base sólida em Clean Architecture, você pode escrever testes abrangentes que abrangem vários cenários, tornando seu aplicativo mais robusto e confiável.
Conforme demonstrado, seguir os princípios da Arquitetura Limpa e separar preocupações leva a uma estrutura de aplicativo sustentável, escalável e testável.
Essa abordagem torna mais fácil adicionar novos recursos, refatorar código e trabalhar com uma equipe em um projeto, garantindo o sucesso de longo prazo de seu aplicativo.
No aplicativo de exemplo, o React é usado para a camada de apresentação. No diretório do adaptador, há um arquivo adicional chamado hooks.ts
que lida com a interação com o módulo rest. O conteúdo deste arquivo é o seguinte:
import { restModule } from '@adapter/rest/rest.module'; import { useAxios } from '@shared/hooks'; export const useDictionarySuggestions = () => { const { data, error, isLoading, mutate } = useAxios(restModule.getDictionarySuggestions); return { dictionarySuggestions: data, getDictionarySuggestions: mutate, dictionarySuggestionsError: error, isDictionarySuggestionsLoading: isLoading, }; };
Essa implementação torna incrivelmente fácil trabalhar com a camada de apresentação. Ao usar o gancho useDictionarySuggestions
, a camada de apresentação não precisa se preocupar com o gerenciamento de mapeamentos de dados ou outras responsabilidades não relacionadas à sua função principal.
Essa separação de interesses ajuda a manter os princípios da Clean Architecture, levando a um código mais gerenciável e sustentável.
Em primeiro lugar, encorajo você a mergulhar no código do repositório GitHub fornecido e explorar sua estrutura.
O que mais você pode fazer? O céu é o limite! Tudo depende de suas necessidades específicas de design. Por exemplo, você pode considerar a implementação da camada de dados incorporando um armazenamento de dados (Redux, MobX ou até mesmo algo personalizado - não importa).
Como alternativa, você pode experimentar diferentes métodos de comunicação entre as camadas, como usar RxJS para lidar com a comunicação assíncrona com o back-end, que pode envolver votação, notificações por push ou soquetes (essencialmente, sendo preparado para qualquer fonte de dados).
Em essência, sinta-se à vontade para explorar e experimentar como quiser, desde que mantenha a arquitetura em camadas e siga o princípio da dependência inversa. Certifique-se sempre de que o domínio esteja no centro do seu design.
Ao fazer isso, você criará uma estrutura de aplicativo flexível e sustentável que pode se adaptar a vários cenários e requisitos.
Neste artigo, nos aprofundamos no conceito de Clean Architecture dentro do contexto de um aplicativo de aprendizado de idiomas construído usando React.
Destacamos a importância de manter uma arquitetura em camadas e aderir ao princípio da dependência inversa, bem como os benefícios de separar interesses.
Uma vantagem significativa da Clean Architecture é sua capacidade de permitir que você se concentre no aspecto de engenharia de seu aplicativo sem estar vinculado a uma estrutura específica. Essa flexibilidade permite que você adapte seu aplicativo a vários cenários e requisitos.
No entanto, existem algumas desvantagens para esta abordagem. Em alguns casos, seguir um padrão de arquitetura estrito pode levar ao aumento do código clichê ou à complexidade adicional na estrutura do projeto.
Além disso, confiar menos na documentação pode ser um pró e um contra - embora permita mais liberdade e criatividade, também pode resultar em confusão ou falta de comunicação entre os membros da equipe.
Apesar desses desafios potenciais, a implementação da Clean Architecture pode ser altamente benéfica, especialmente no contexto do React, onde não há um padrão de arquitetura universalmente aceito.
É essencial considerar sua arquitetura no início de um projeto, em vez de abordá-la após anos de luta.
Para explorar um exemplo da vida real de Clean Architecture em ação, sinta-se à vontade para conferir meu repositório em
Uau, este é provavelmente o artigo mais longo que já escrevi. É incrível!