Como HerbsJS aborda o Domain-Driven Design

Como HerbsJS aborda o Domain-Driven Design
Imagem de Zoltan Matuska por Pixabay

Você aí que já utiliza o Domain-Driven Design no seu dia a dia, ou que apenas tem interesse em saber mais sobre ele, já ouviu falar no HerbsJS? Ainda não?? Fique tranquil@, vamos falar coisas bacanas sobre ele aqui e como é sua abordagem com o DDD!

HerbsJS é um grupo de bibliotecas Javascript open source cujo objetivo é trazer entregas mais rápidas, softwares de alta qualidade, mais robustos, além de garantir o foco e energia do desenvolvimento onde realmente importa: nas regras de negócio, ou seja, na camada de domínio da aplicação. E, não por coincidência, esse também é o grande objetivo do DDD.

Força, fé e foco no domínio!

A complexidade do domínio trata do problema que se está tentando resolver.

O Domain-Driven Design nasceu pelas mãos de Eric Evans em seu livro de mesmo nome em 2003, com o objetivo de atacar a complexidade no coração do software. É uma abordagem de modelagem de software que pensa no design guiado pelo negócio, ou seja, a complexidade do domínio é o foco principal. Ele fornece uma estrutura de práticas e terminologias para tomar decisões de design que acelerem projetos de software que lidam com domínios complicados.

Porém, quando tratamos da complexidade de software, é preciso diferenciar certos cenários:

  • Complexidade da solução técnica: dificuldades vindas das tecnologias utilizadas para a criação do software;
  • Complexidade do legado: software que precisamos manter e seus débitos técnicos que podem ser difíceis de lidar;
  • Complexidade do domínio: é a complexidade do negócio, do problema que estamos buscando solucionar;

É possível perceber, então, que existe somente uma Complexidade Essencial, e trata-se da Complexidade do Domínio.

Ela é a única com foco na solução para o negócio, já que ambas Complexidade da solução técnica e Complexidade do legado são Complexidades Acidentais, ou seja, nós escolhemos ter a medida que optamos por ferramentas, bibliotecas, modelos de arquitetura, linguagens de programação, e não podemos abrir mão da complexidade do legado quando escolhemos dar suporte a ele.

O HerbsJS foi criado para nos ajudar a resolver a Complexidade do Domínio durante o desenvolvimento de software, com a meta de entregar valor nas soluções para negócio de forma bastante dinâmica e, assim, diminuir os custos de manutenibilidade.

Para isso, é utilizada a abordagem Domain-First, a qual estabelece que o time de tecnologia dedique seus esforços no domínio enquanto o HerbsJS lida com a infraestrutura através dos metadados extraídos. As camadas de transporte e repositórios serão criadas dinamicamente em tempo de execução. Isso significa que a complexidade do domínio fica sob responsabilidade da pessoa desenvolvedora, enquanto fica por conta do HerbsJS a complexidade da solução técnica.

Fica ainda mais evidente o desejo de manter a pessoa desenvolvedora dentro da complexidade do domínio e reduzir os custos com os esforços nas complexidades acidentais quando conhecemos sua CLI. Recomendo a leitura do artigo Crie um microsserviço em 5 passos com HerbsJS da Julia Dibo. Nele, ela mostra como rapidamente é possível criar um microsserviço utilizando a CLI do HerbsJS, além de mantê-lo sempre atualizado sem grandes esforços na solução técnica.

Vale destacar que, entretanto, é fato que nós pessoas de tecnologia temos uma predisposição a depositar nossas preferências na complexidade da solução técnica e do legado do que na do domínio. Então, antes de tudo, para desenvolvermos nossa habilidade na solução de negócio é necessário entendermos onde estamos inseridos. A forma adequada de alcançar esse objetivo é dialogando com especialistas de domínio.

As pessoas especialistas de domínio são aquelas com profundo conhecimento de negócio e é de grande importância que haja interações consistentes entre elas e o time técnico. Elas são essenciais para trazer à tona aquilo que precisa ser realizado como solução. Porém, frequentemente, a comunicação entre essas partes não é fácil. Na prática, é como se cada parte falasse um idioma e, para isso, o DDD apresenta a Linguagem Onipresente como a essência transformadora desse entendimento e como é preciso torná-la clara para contornar as dificuldades. Ela, apresentando aqui de forma introdutória, é a linguagem das pessoas de negócio que deve ser compartilhada por todos do time. Para saber mais sobre o assunto, recomendo a leitura do artigo de Elemar Junior DDD Do Jeito Certo - Linguagem Onipresente.

Entidades

O HerbsJS entra como um facilitador nessa conversa entre o time técnico e os especialistas de domínio, já que deseja entregar um código alinhado com o negócio e que fala sobre ele, mostrando esse vocabulário no código. As Entidades surgem nessas conversas quando especialistas de domínio utilizam substantivos, ou nomes, para descrever o trabalho. Elas podem ser identificadas por um código único, um Id exclusivo. Por exemplo, cadastro de Usuário, ele é uma Entidade.

No contexto do DDD, uma Entidade precisa implementar a lógica do domínio e como o propósito do DDD é atacar a sua complexidade, não faria muito sentido ele estar envolvido em contextos pouco complexos, com entidades anêmicas, por exemplo, que são entidades sem comportamentos nem validações, onde faltam informações relacionadas ao domínio e possui apenas propriedades que expressam atributos do objeto. Como exemplo disso, temos a entidade Usuario a seguir:

const { entity, id, field } = require('@herbsjs/herbs')
const { herbarium } = require('@herbsjs/herbarium')

const Usuario =
        entity('Usuario', {
          id: id(Number),
          nome: field(String)
        })

module.exports =
  herbarium.entities
    .add(Usuario, 'Usuario')
    .entity

Quando representamos entidades desta forma, estamos basicamente representando algo sem falar de domínio, sem esclarecer motivos para mudanças que são acompanhados de regras de negócio. O HerbsJS nos ajuda a definir entidades não anêmicas, que tratam do domínio, na medida que nos permite inserir validações dos valores das propriedades e construir métodos que definem comportamento das entidades, tudo isso podendo, inclusive, ser personalizado conforme necessidade:

const Usuario =
    entity('Usuario', {
        ...
        senha: field(String, { validation: {
          // validação nativa herbs
          presence: true,
          length: { minimum: 6 } }
        }),
        numeroCartao: field(String, { validation: {
          // validação customizada
          custom: { numeroCartaoInvalido: (valor) => valor.length === 16 } }
        })
    })

const usuario = new Usuario()
usuario.senha = '1234'
usuario.numeroCartao = '1234456'
usuario.validate()
usuario.errors // [{ senha: [ { isTooShort: 6 } ] , { "numeroCartaoInvalido": true }]
usuario.isValid // false

Casos de Uso

Um Use Case é responsável pelo controle dos fluxos de dados entre Entidades, Repositórios e outros componentes do domínios. No HerbsJS, nossos casos de uso são construídos em steps que organizam a lógica do domínio mantendo cada etapa segmentada para facilitar o encapsulamento, a leitura, a manutenção e a testabilidade daquele código, conceitos de Clean Architecture que são mais bem detalhados no artigo Como o HerbsJS Aborda o Clean Architecture do Gabriel Melo.

No exemplo abaixo, é possível ver como regras de negócio se expressam a medida que seguimos os steps de use cases, tentando diminuir distâncias. Os steps são executados um por um, compartilhando objetos de contexto, se necessário:

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

const criaUsuario = injection => () =>
  usecase('Cria usuário', {
    // Input/Request metadata e validação 
    request: {
      nickname: String,
      password: String
    },

    // Output/Response metadata
    response: User,

    // Autorização
    // authorize: (usuario) => (usuario.podeCriarUsuario ? Ok() : Err()),
    authorize: () => Ok(),

    setup: ctx => (ctx.di = injection),

    // Step 1
    'Checa se o usuário é válido': step(ctx => {
      ctx.usuario = Usuario.fromJSON(ctx.req)
      
      if (!ctx.usuario.isValid()) 
        return Err.invalidEntity({
          message: 'A entidade Usuário é inválida.', 
          payload: { entity: 'Usuario' },
          cause: ctx.usuario.errors 
        })

      // Retorno Ok continua a execução no próximo step. Err interrompe a execução do use case.
      return Ok() 
    }),

    'Salva Usuário': step(async ctx => {
      // ctx.ret é o retorno do use case
      return (ctx.ret = await ctx.di.UsuarioRepository.insert(ctx.usuario))
    })
  })

module.exports =
  herbarium.usecases
    .add(criaUsuario, 'CriaUsuario')
    .metadata({ group: 'Usuario', operation: herbarium.crud.create, entity: Usuario })
    .usecase

Documentação

Outro ponto importante a ser tratado é que código e documentação andam juntos no HerbsJS. A propagação do conhecimento sobre o negócio é facilitada a medida que as regras estão ali presentes no código. O Herbs Shelf é uma documentação gerada de forma automática, baseada nos use cases, nas entidades do domínio e no readme da aplicação. Com ele, é possível que haja uma forte colaboração entre o time de tecnologia e os especialistas de domínio já que não há leitura de código. A documentação da aplicação é gerada em tempo de execução e qualquer alteração realizada não vai deixá-la desatualizada.

herbsshelf

Por fim...

Desta forma, o HerbsJS vem para possibilitar a entrega de softwares cujo modelo de domínio foi cuidadosamente elaborado. Ele está no coração da aplicação, nos use cases e entidades, para que o desenvolvimento em si seja dedicado à complexidade de negócio e todo resto seja uma mera consequência. O HerbsJS não concorre com bibliotecas de infraestrutura, frameworks, drivers etc. e sim permite simplesmente que o nosso desenvolvimento seja focado em resolver problemas do negócio.

Gostou? Quer saber mais sobre o HerbsJS? Clique aqui!