Como HerbsJS aborda o Clean Architecture

Como HerbsJS aborda o Clean Architecture

Imagine o cenário no qual você precisa identificar o tipo de construção civil baseando-se apenas na visão aérea. Nossos inputs se limitam a:

  • Analisar a planta em si e disposição das paredes;
  • Entender o fluxo de passagem, corredores e espaços vazios;
  • Analisar o interior e móveis da planta;

É possível identificar com o que a planta abaixo precisamente se parece para você, considerando somente as paredes externas?

Certamente a resposta é:

NÃO...

Agora, observando a mesma planta mas com detalhes de layout acrescentados chegamos a algumas considerações:

  • Espaço aberto com vários bancos voltados para a mesma direção;
  • Portas grandes indicando a entrada / saída de um grande número de pessoas;
  • Corredores organizando o fluxo de passagem;

Dado as considerações feitas acima, agora é possível dizer que a planta pode ser um auditório, teatro, igreja ou qualquer outro espaço no qual as pessoas possam se encontrar em grande número. Desta forma, podemos deduzir qual o tipo da planta ela seria baseando-se nos detalhes que haviam dentro dela.

O desafio do design de software é expressar os casos de uso no código de uma forma que, à primeira vista, nos traduza o que o software faz, ao invés do que sua estrutura na qual foi construída. Isso é sobre a boa e velha arquitetura de software. E nada melhor para representar organização e detalhes, se não a Clean Architecture.

Clean Architecture: O que é?

A Clean Architecture foi criada por Robert C. Martin em meados de 2012 e publicado em seu livro "Clean Architecture: A Craftsman’s Guide to Software Structure". Assim como qualquer outro modelo de implementação de design de código, o Clean Architecture visa facilitar o processo de desenvolvimento de código, permitindo uma melhor manutenção, reusabilidade de código e fraco acoplamento.

A principais premissas desse modelo de implementação são:

  • Reusabilidade: Considerando que cada use case diz respeito a um contexto do negócio no qual está inserido e deve possuir uma única responsabilidade, você pode reutilizar um use case dentro de outro use case quando fizer sentido.
  • Testável: As regras de negócio podem ser testadas sem a presença de um componente externo (interface do usuário, banco de dados etc.). Isso garante que erros sejam detectados antes mesmo da versão ser lançada em um ambiente produtivo, por exemplo.
  • Independente: Seu domínio não é acoplado a uma interface de usuário, banco de dados ou qualquer outro componente. Qualquer alteração externa não tem impacto no domínio.
  • Regra de negócio / Domínio encapsulados: Na verdade, suas regras de negócios simplesmente não conhecem o mundo exterior (isolamento) e não estão ligadas a nenhum Framework.
  • Manutenibilidade: Por fim, todos os tópicos acima contribuem para a construção de um software mais fácil de se manter a longo prazo.

Um objetivo que ficou claro na implementação da Clean Architecture, é disponibilizar ao time de desenvolvimento uma maneira de organizar o código fonte de forma que o core (Entidades + Use Cases) da nossa aplicação sejam encapsulados, mas os mantenha isolados dos meios de entrega e todo o resto da aplicação.

Com base nas afirmações acima,  podemos classificar esse modelo de implementação em três grandes tópicos:

  1. Entidade (domínio)
  2. Use Cases (domínio)
  3. Adaptadores (Padrão de comunicação entre domínio e infraestrutura que seriam todo o "resto")

Clean Architecture: Principais conceitos

Entidade

As entidades encapsulam as regras de negócios de toda a empresa. Uma entidade pode ser um objeto com métodos ou pode ser um conjunto de estruturas de dados e funções e validações. Não importa, desde que as entidades possam ser usadas por muitas aplicações diferentes na empresa.

Nesse ponto, nenhuma alteração nas camadas externas devem afetar as entidades. Pra quem conhece princípios e abordagens como DDD e SOLID, conseguem facilmente descobrir que a Clean Architecture se inspirou bastante nestes dois conceitos que, consequentemente, também influenciaram a criação do HerbsJS

Use Case

O software nesta camada possui regras de negócios específicas da aplicação. Ele encapsula e implementa todos os casos de uso do sistema. Esses casos de uso orquestram o fluxo de dados de e para as entidades, e direcionam essas entidades a usar suas regras de negócios em toda a empresa para atingir as metas do caso de uso.

Nesse ponto, nenhuma alteração de camadas externas devem impactar os casos de uso, porém, alterações nos detalhes do caso de uso podem afetar algum código nessa camada.

Adapters

A arquitetura limpa aplica o princípio de separação de questões por meio do padrão Portas e Adaptadores. Isso significa que a camada de aplicação expõe portas (Interfaces) e os adaptadores são implementações na camada de infraestrutura.

O padrão recebe esse nome por conta da forma como uma aplicação que o utilize interage com o mundo externo: a partir de ports (portas) e adapters (adaptadores).

Ficou claro que um dos objetivos do Clean Architecture é realizar o isolamento do domínio, de modo que qualquer alteração que ocorra em outras camadas da aplicação tenham um impacto zero no nosso core.

Logo suas regras de negócio que estão na camadas central, não conhecem o mundo externo e não sentem o impacto de mudanças lá fora.

Essa é justamente uma das premissas que o HerbsJS busca tratar, e é aqui que a referências do Clean Architecture começa.

Abordando Clean Architecture através do HerbsJS

O HerbsJS é um conjunto de bibliotecas Javascript, open source, voltada para construção de microsserviços, no qual tem por objetivo resolver o problema de domínio de um negócio sem se preocupar com a infraestrutura necessária para tal.

Misturando conceitos do Clean Architecture e outros paradigmas, o HerbsJS foi construído para aumentar a velocidade das entregas mantendo um excelente nível de separação de responsabilidades, organização, testabilidade e qualidade do código. Logo, você se preocupa apenas em resolver o problema do domínio (negócio), e o HerbsJS gera toda a parte de infraestrutura pra você.

A ideia, então, é resolver o problema de domínio da aplicação, e não problemas relacionados às camadas de interfaces, frameworks, drivers etc. Com isso, fica claro que a biblioteca não concorre com outras bibliotecas de infraestrutura, como por exemplo Express e Nextjs, e sim trabalha em conjunto. Aqui na Vórtx, temos alguns microserviços escritos utilizando Express + HerbsJS ou api's simples escritas utilizando Nextjs + HerbsJS.

O isolamento do domínio (Entidades + Use Case) no HerbsJS acontece através de duas bibliotecas que representam respectivamente as entidades e use cases dentro do Clean Architecture (Gotu + Buchu).

Na imagem acima, podemos entender um pouco mais sobre a arquitetura do HerbJS, sendo dividida entre as bibliotecas primitivas e bibliotecas "glues".

As bibliotecas primitivas são o conjunto de libs que estão no coração do HerbJS. Elas ficam no "domínio" (sim, o HerbsJS também se estrutura desta forma) e são utilizadas para escrever nossas entidades e use cases da aplicação.

As bibliotecas "glues", ou as colas como são carinhosamente chamadas, são responsáveis por realizar a comunicação entre a camada de domínio e as bibliotecas de infraestrutura, por exemplo.

Vejamos o seguinte cenário:

Você possui uma entidade Item (1), com itens clássicos de uma lista de todo list, um use case (2), que pode adicionar ou atualizar um item do todo list, e um repositório (4), que será responsável por realizar a persistência em um banco de dados não relacional como o Mongodb.

O cenário descrito acima pode ser exemplificado no HerbsJS através da imagem abaixo:

As libs primitivas e que são responsáveis por resolver o domínio da aplicação são: Gotu (1 - Entidades), Buchu (2 - UseCases) e Suma (3 - Validações).

As libs "glue" que são responsáveis por realizar a comunicação entre a camada de domínio e a camada exterior no exemplo acima são: Herbs2Mongo (4), Herbs2Rest (5). Vale ressaltar que o ecossistema de colas do HerbsJS vai bem além dessas duas.

O Gotu (1) é responsável pela construção da entidades que irão conter propriedades, regras, validações e demais inteligência necessária para aquele contexto de entidade.

Segue o exemplo da nossa entidade Item descrita acima (Todo List) utilizando HerbsJS:

const { entity, field } = require('@herbsjs/gotu')

const Item = entity('Item', {
  id: field(Number),
  description: field(String),
  isDone: field(Boolean),
  position: field(Number)
})

Nessa entidade, podemos definir tipos, incluir validações para esses tipos, criar tipo encadeados e muito mais. Um detalhe interessante é que o Gotu aceita validações customizadas baseadas no Suma.

O Suma (3) é uma biblioteca de validação de valores únicos, sendo utilizada em conjunto com a biblioteca Gotu ou separadamente. O exemplo abaixo é o Suma sendo utilizado em um entidade do Gotu.

const User =
    entity('User', {
        ...
        password: field(String, { validation: {
            presence: true,
            length: { minimum: 6 } }
        }),
        cardNumber: field(String, { validation: {
          custom: { invalidCardNumber: (value) => value.length === 16 } }
        })
    })

O Buchu (2) é responsável  pela construção dos use cases que conterão os detalhes de um caso de uso do sistema em questão.

Segue exemplo do nosso use case que recupera a lista (Todo List) utilizando HerbsJS:

const { Ok, Err, usecase, step, ifElse } = require('@herbsjs/herbs')
const { herbarium } = require('@herbsjs/herbarium')
const { TodoList } = require('../entities/todoList')

const dependency = {
  ListRepository: require('../../infra/repositories/db/listRepository'),
}

const getLists = injection =>
  usecase('Get Lists', {
    request: { ids: [Number] },

    response: [TodoList],

    setup: ctx => (ctx.di = Object.assign({}, dependency, injection)),

    authorize: async user => (user.canGetLists ? Ok() : Err()),

    'Get List by ID or All': ifElse({

      'If it is was informed one or more IDs': step(async (ctx) => {
        return Ok(ctx.req.ids !== undefined && ctx.req.ids.length > 0)
      }),

      'Then return the all on the list': step(async (ctx) => {
        const repo = new ctx.di.ListRepository(injection)
        const list = await repo.find({ where: { id: ctx.req.ids } })
        return Ok(ctx.ret = list)
      }),

      'Else return all': step(async (ctx) => {
        const repo = new ctx.di.ListRepository(injection)
        const lists = await repo.findAll()
        return Ok(ctx.ret = lists)
      })

    })
  })


module.exports.getLists =
  herbarium.usecases
    .add(getLists, 'GetLists')
    .metadata({ group: 'Lists', operation: herbarium.crud.read, entity: TodoList })
    .usecase

Com a utilização dessa lib, percebe-se que temos a possibilidade de receber injeções dentro do nosso use case, o que facilita a construção de mocks para testes unitários e/ou integração.

O código de um use case é segmentado em steps, uma ideia presente em projetos de testes, que visam segmentar o máximo de código por contexto daquele "step". O objetivo é organizar melhor o código em "seções" a fim de manter uma boa escrita e manutenibilidade.

Temos ainda algumas funções do Buchu que são responsáveis por definir retorno de interação como o método "Err()", que indica o retorno de um erro encontrado dentro de um use case, e o método "Ok()", que indica o prosseguimento de execução do código.

Um ponto interessante que vale citar é que as três libs mencionadas acima estão sendo encapsuladas dentro do '@herbsjs/...', logo não precisam ser baixadas separadamente:

const { usecase, step, Ok, Err, entity, field, validate, errorCodes ,checker } = require('@herbsjs/herbs')

As libs Herbs2Mongo (4) e Herbs2Rest (5) são as nossas "glues" (colas) que são responsáveis por realizar a comunicação entre camada de domínio e camadas externas.

O Herbs2Mongo cria repositórios para recuperar e armazenar Entidades usando o MongoDB, garantindo que parte da infraestrutura de banco de dados está sendo gerada (repositórios) automaticamente.

Exemplo de utilização:

const { Repository } = require('@herbsjs/herbs2mongo')
const connection = require('connection')
const { Item } = require('../domain/entities/item')
const database = 'herbs2mongo_testdb'

class ItemRepository extends Repository {
    constructor() {
        super({
            entity: Item,
            collection: 'aCollection',
            database,
            ids: ['id'],
            mongodb: connection
        })
    }

    excludedItemFromLastWeek() {
        ...
    }
}

Para mais detalhes:

GitHub - herbsjs/herbs2mongo: Create repositories using Mongo Driver to retrieve and store Entities
Create repositories using Mongo Driver to retrieve and store Entities - GitHub - herbsjs/herbs2mongo: Create repositories using Mongo Driver to retrieve and store Entities

O Herbs2Rest cria uma API REST baseada em entidades HerbsJS (Gotu) e casos de uso (Buchu), garantindo que parte da infraestrutura (api) está sendo gerada automaticamente.

Para mais detalhes:

GitHub - herbsjs/herbs2rest: Create a REST API based on HerbsJS
Create a REST API based on HerbsJS. Contribute to herbsjs/herbs2rest development by creating an account on GitHub.

Considerações finais

Ambas bibliotecas são independentes e não conhecem o mundo exterior, assim como no Clean Architecture, de modo que alterações externas não tem impacto nessas duas bibliotecas que representam nossas camadas de Entidade e Use Case.

Existe ainda um outro fator que contribui para o isolamento do domínio, que é  a utilização das colas como forma de comunicação entre a nossa camada de domínio e o mundo externo (infraestrutura). As colas representam o padrão "ports and adapters", centralizando a comunicação de dados de entrada e saída entre a nossa camada de domínio e as camadas externas, garantindo um único ponto de acesso a nossa camada de domínio e demais camadas.

Dessa maneira, garantimos o isolamento do domínio (libs core), sua testabilidade, independência e reutilização assim como ocorre no Clean Architecture.

Referências

Clean Coder Blog
Como montar uma to do list eficiente – Santa Biblioteconomia
Herbs
A domain-first framework. Code your domain and your infrastructure will follow - Herbs