Caffeinefreecode logo
Porque não usar ORMs no Domínio da Aplicação
Autor

24/10/2023

Porque não usar ORMs no Domínio da Aplicação

Essa semana separei um tempo para tratar algo que estava me incomodando há algum tempo.

Eu tinha uma aplicação que tinha uma arquitetura que se tornou bem comum recentemente que é a separação entre Backend e Frontend.

Porém, ter essa separação merece alguns pontos de atenção...

Ao separar em dois serviços, você ganha a possibilidade de subi-los separadamente inclusive em servidores diferentes. Consegue escalá-los de forma mais simples, entre outros.

São preocupações completamente válidas, porém não era essa a realidade da aplicação que eu estava trabalhando. Era uma aplicação que até o momento não precisava lidar com auto-scaling nem era necessário subir em servidores separados.

Ao contrário disso, as únicas preocupações dessa aplicação era ser renderizada no servidor SSR e SEO optimization.

Para fazer essa otimização de SEO, uma das coisas mais importantes, é a latência de resposta.

Se seu Frontend precisa ir a uma API externa e buscar os dados, a API tem o trabalho de ir ao banco de dados para só então retornar a resposta, seu frontend montar o conteúdo estático para depois voltar a resposta para o cliente, dependendo da situação, você pode acabar perdendo ranking no SEO.

O fato de eu saber de antemão que precisaria de SSR, me fez escolher o Next.js. Eu não poderia ter escolhido um framework ou lib como Angular ou React pois eles tratam de SPAs, que não são interessantes quando o assunto é Server Side Rendering.

Porém, eu já tinha uma API feita em Node.js e aproveitei ela para servir o conteúdo para o Next.js.

Mas, seguindo esse modelo, eu tinha preocupações como a latência da requisição síncrona entre APIs. Não era nada de mais, até ia bem nos benchmarks de SEO na questão de latência.

Porém, outro ponto é a questão de precisar de um servidor extra para a API. Eu poderia usar o mesmo servidor que estava a aplicação do Next.js para diminuir a latência, porém o servidor não teria memória suficiente para lidar com dois containers Docker - um NGINX e outro com a aplicação em Next.js - e um PM2 com a API em Node.js.

Nesse momento me passou pela cabeça, por que não trazer a API para dentro do Next.js usando a feature API Routes e parar de precisar pagar um servidor a mais?

Foi nesse momento que surgiu o desafio de migrar toda uma API para dentro do Next.js e criar um monolito.

Para minha sorte, eu segui na criação da API, a estratégia da Clean Architecture de separação da camada de domínio da camada de infraestrutura. A camada de domínio, a qual é a central, não pode depender de ninguém, e as setas de dependências sempre seguindo o fluxo de fora para dentro.

Agora, o problema. A API usava TypeORM. Não sei se você já tentou usar TypeORM com o Next.js, mas eu tive uma série de problemas, e pelo que vi nos fóruns é bem chatinho mesmo.

Então decidi aproveitar a oportunidade para aprender um pouco mais sobre o Prisma, que vem ganhando uma certa popularidade ante a outros ORMs.

No final, descobri que existem alguns caveats para usar ele com o Next.js que não estão bem documentados, mas não vem ao caso agora.

Como a CA prega, não seria nenhum problema trocar o provedor de acesso ao banco de dados. E adivinha só o que aconteceu?

Nada. Assim como o esperado haha. Foi necessário apenas trocar a implementação do repositório, e a aplicação continuou funcionando normalmente.

O repositório é uma interface que define um contrato de dados e fica na camada de domínio. Ele deve sempre, no contrato de retorno de seus métodos, retornar a entidade definida nessa camada.

export default interface IPostsRepository {
  create(post: Post): Promise<Post>;
  update(post: Post): Promise<Post>;
  paginate(paginateQuery: PaginateQuery): Promise<Paginated<Post>>;
  findById(id: string): Promise<Post | null>;
  findBySlug(slug: string): Promise<Post | null>;
  findByTitle(title: string): Promise<Post | null>;
}

Aqui muitas pessoas se confundem. A sua entidade JAMAIS (se você estiver disposto a seguir a CA) pode possuir anotações de ORMs ou qualquer dependência. Ela deve ser a mais pura possível. Segue o exemplo:

import Image from '../../images/Entity/Image';

export default class Post {
  id: string;

  slug: string;

  title: string;

  tags: string;

  body: string;

  timesVisited: number = 0;

  images: Image[];

  createdAt: Date;

  updatedAt: Date;
}

Repare que o único import dentro dela é para outra entidade. Não há imports de ORMs e/ou frameworks aqui dentro.

Já a sua implementação deve ficar na camada mais externa, pois lida com acesso a banco de dados. Aqui, sim, você pode ter frameworks de qualquer tipo que desejar.

Dessa forma, basta apenas seguir o contrato de dados estipulado pela interface do repositório e você pode inclusive usar 2, 3 ou quantos ORMs quiser haha. Basta usar DTOs e mapeadores para construir a entidade pedida no retorno da interface e injetá-las no seu serviço.

class FindBySlugUseCase {
  constructor(private repository: IPostRepository) {}

Repare no construtor que estou injetando a interface e não a implementação.

Outro problema foi a camada do servidor, mas como também a CA prega, a camada de comunicação HTTP deve também estar no último nível.

A API usava Express, que é de certa forma semelhante às API Routes do Next.js porém, ainda assim precisaram de mudanças na implementação.

A notícia boa é que como ela estava no último nível da arquitetura, mudar a implementação do servidor não impactou em nada em nenhuma funcionalidade, pois a camada de serviços não depende de nada externo, só lida com as entidades e como elas interagem entre si.

No final das contas, foi só copiar a API para dentro do Next.js e criar um arquivo PrismaEntityRepository que implementa o IEntityRepostory e mudar a implementação das rotas.

Espero que esse exemplo possa esclarecer mais a lição da CA sobre separações das camadas externas e internas do software e, porque não devemos usar os ORMs de forma indevida.

Você também pode ler brevemente sobre dependência de Frameworks no domínio nesse Post da série arquiteturas de frameworks famosos.

Não esqueça de beber sua água!