Arquitetura escalável de front-end

Como organizamos nossos projetos

Alguns frameworks têm um conjunto de convenções muito bem definidas de como um projeto deve ser organizado. Se você está trabalhando com um framework assim, eu te diria para fazer o que eles recomendam.

Um dos principais benefícios de usar um framework é a capacidade de entrar facilmente em qualquer projeto e saber como navegá-lo. Se você fizer as coisas do seu jeito, você perde isso.

Mas mesmo Angular, que acredito ser o framework front-end mais rígido, não aborda tudo em suas convenções de estilo, então você precisa definir algumas regras você mesmo.

Neste artigo vou mostrar como nós (eu e minha equipe) organizamos nossos projetos de front-end.

LinkRepositório

Em primeiro lugar, o front-end está sempre em um repositório isolado. Não usamos um único repositório para o projeto inteiro. Geralmente temos três repositórios para cada projeto.

  1. Front-end
  2. Back-end
  3. API client.

Essa estrutura é muito importante quando trabalhamos com clientes porque eles não querem que todo desenvolvedor tenha acesso a tudo, e essa estrutura permite melhor controle de acesso.

Vou deixar nossa organização de back-end para outro artigo porque tem muita coisa acontecendo lá. Temos até um projeto em andamento que estamos migrando para event sourcing e esse back-end tem vários contêineres se comunicando. É lindo, mas também é demais e merece seu próprio artigo.

Antes de chegarmos no front-end, eu quero explicar o repositório do API client.

LinkAPI client

O API client é uma biblioteca independente de qualquer framework e responsável pela comunicação com o back-end.

Honestamente, se você tiver que aprender apenas uma coisa desse artigo, espero que seja criar API clients. Ele limpa muito o front-end! Em vez de testar e escrever chamadas de API junto dos seus componentes visuais, faça isso em outro projeto isolado e deixe o front-end apenas com a interface visual.

Escrevemos nossos CPI clients usando TypeScript com todas as flags estritas habilitadas, então eles fornecem uma grande segurança de tipagem.

Os API clients também têm muitos testes. Geralmente usamos Jasmine, a menos que o cliente nos peça para usar outra tecnologia. Na maioria das vezes, não temos apenas testes de navegador (rodando com Karma), mas temos também testes do lado do servidor usando Node.

As branches master e dev são protegidas com um monte de verificações de CI (integração contínua). Temos verificações de CI para formatação, compilação, testes de navegador, testes de servidor e compatibilidade TypeScript.

Também temos um processo de CI que é executado quando criamos uma release (nova versão), e este processo publica a package no NPM (se ela for pública, claro) e no GitHub Package Registry (sendo o que mais usamos porque oferece packages privadas gratuitamente).

LinkEstrutura da Pasta

A estrutura de pastas do API client é dividida por módulos da API. Portanto, se tivermos uma API com três módulos, teremos uma pasta para cada um. Chamamos essas pastas de "módulos de recurso" (roubamos isso do Angular).

Dentro de um módulo de recurso, temos tipagens e um monte de funções isoladas. Por exemplo, digamos que temos um módulo de usuários. Dentro dele, haverá um UsersService que é uma interface com todos os possíveis métodos para interagir com a API de usuários.

TypeScript
export interface UsersService {
  readonly get: {
    readonly one: (userID: ID) => Promise<User>;
    readonly many: (userFilters: UserFilters) => Promise<Array<User>>;
    readonly all: () => Promise<Array<User>>;
  };

  readonly create: {
    readonly one: (data: CreatableUser) => Promise<User>;
  };

  readonly update: {
    readonly one: (data: UpdatableUser) => Promise<User>;
  };

  readonly delete: {
    readonly one: (userID: ID) => Promise<void>;
  };
}

Haverá também um arquivo chamado create-users-service.ts que expõe uma função para criar um UsersService e um arquivo para cada chamada de API, como get-many-users.ts e update-one-user.ts.

Modelos e utilidades também ficam lá, portanto, teremos arquivos como user.ts, updatable-user.ts, is-valid-user.ts e assim por diante...

No front-end, nós instalamos o API client com "npm install" como se fosse uma dependência normal, porque é uma dependência normal.

LinkFront-end

Agora vamos ao front-end.

A maioria dos nossos clientes usa Angular, esse é o meu framework preferido, então eu isso acaba atraindo mais clientes de Angular. Mas também trabalhamos com outros frameworks. Um cliente pode querer usar React ou Elm, e está tudo bem.

Estou dizendo isso porque, como mencionei no início, alguns frameworks já possuem uma forma bem definida de como organizar as coisas e sempre colocamos as convenções do framework acima das nossas.

Dito isso, nossa estrutura de pastas no front-end é muito semelhante à estrutura do API client. Temos uma pasta para cada módulo e dentro dessa pasta existem duas outras pastas (às vezes três).

  1. Components
  2. Pages
  3. Helpers (opcionalmente).

A pasta components/ possui componentes puros. Isso significa, componentes que sempre renderizam a mesma coisa dadas as mesmas entradas. Isso nem sempre é verdade, por exemplo, temos componentes que recebem um ID e buscam o objeto com esse ID de uma store. O mesmo ID pode resultar em uma visualização diferente se o objeto for modificado. Mas tudo bem, eles são "puros" o suficiente (se é que isso existe).

A pasta pages/ possui componentes de nível de página. Eles não recebem entradas porque eles não têm componentes-pai para passar entradas para eles. Em vez disso, eles usam a URL como entrada. Por exemplo, um componente de nível de página que renderiza em /users/:userID pode analisar a URL para obter o ID do usuário.

Alguns módulos de recurso também têm a pasta helpers/, é nela que armazenamos funcionalidades compartilhadas entre as páginas e os componentes puros, podem ser funções, serviços, tipos, o que for.

Chega de módulos de recurso, também temos uma pasta compartilhada, que é onde mantemos os módulos compartilhados, ou seja, módulos que não possuem páginas. Por exemplo, poderíamos ter um módulo compartilhado para notificações com todos os serviços, funções e componentes relacionados a notificações. Esse módulo seria então importado nos módulos de recurso que dependem dele.

Em resumo, tudo é dividido em módulos. Se um módulo não tem páginas, então ele é um módulo compartilhado e, logo, deve ficar na pasta shared. Se tiver páginas, então é um módulo de recurso e deve ter sua própria pasta na raiz do projeto.

Por fim, temos o módulo principal, que importa todos os módulos de recurso e registra as rotas.

LinkFluxo de Dependência

Para que essa estrutura de pastas funcione, precisamos respeitar o fluxo de dependências.

  • Módulos compartilhados só podem importar outros módulos compartilhados.
  • Módulos de recurso só podem importar módulos compartilhados.
    • Helpers só podem importar módulos compartilhados.
    • Componentes puros só podem importar helpers e módulos compartilhados.
    • Componentes de nível de página só podem importar helpers, componentes puros e módulos compartilhados.
  • Módulo principal pode importar qualquer coisa.

Além disso, todos os arquivos e módulos podem importar node_modules.

Se você não respeitar o fluxo de dependências, você terá dependências circulares e tudo vai quebrar.

LinkConclusão

Não posso deixar links para repositórios reais porque são todos privados. Mas criamos repositórios template para um API client e um front-end Angular. Os links estão na descrição, espero que ajude. Deixe um Tweet se faltou algo, faremos o nosso melhor para responder às suas perguntas.

Se você quer saber como os projetos da sua empresa ficariam com as nossas convenções, você pode nos contratar. Não somos uma agência, somos uma equipe, e eu sou pessoalmente responsável por todos os projetos. Por esse motivo, não podemos ter muitos clientes afinal, sou apenas um e me responsabilizo por tudo. Dito isso, estamos disponíveis no momento. Acesse lucaspaganini.com e nos diga, como podemos te ajudar? Tenha um ótimo dia e nos vemos em breve!

LinkReferências

  1. Guia de estilo do Angular
  2. Guia de estilo do Vue
  3. Guia de estilo do Meteor
  4. Repositório do Template em Angular
  5. Repositório de exemplo API Client

Assine a nossa Newsletter e seja avisado quando eu lançar um curso, postar um vídeo ou escrever um artigo.

Campo obrigatório
Campo obrigatório