Implementando o padrão Specification no Node.js com TypeScript
Tags: ddd, programming, node, tutorial
Published: 2023-04-16 11:30:36

O padrão Specification é um padrão de projeto de software que permite definir regras de negócio em um formato legível e reutilizável. Ele é particularmente útil em aplicações que precisam implementar regras de negócio complexas que envolvem múltiplas entidades. Neste artigo, vamos ver como implementar o padrão Specification em uma aplicação `Node.js` com TypeScript.

## Definindo a entidade

Para exemplificar o uso do padrão Specification, vamos criar uma entidade simples chamada `Transaction`. Ela possui três propriedades: `amount` (valor da transação), `date` (data da transação) e `description` (descrição da transação).

Crie um arquivo chamado `transaction.ts` na pasta `src/entities` com o seguinte conteúdo:

```ts
export class Transaction {
  constructor(
    public readonly amount: number,
    public readonly date: Date,
    public readonly description: string,
  ) {}
}
```

Esta classe representa uma transação financeira com os seguintes atributos:

`amount`: o valor da transação
`date`: a data da transação
`description`: uma descrição da transação

## Criando as regras de negócio
Agora que temos a entidade de transação, vamos criar as regras de negócio para verificar se uma transação é válida ou não. Crie um arquivo chamado `specification.ts`  na pasta `src/specifications` com o seguinte conteúdo:

```ts
export interface Specification<T> {
  isSatisfiedBy(candidate: T): boolean;
  and(other: Specification<T>): Specification<T>;
  or(other: Specification<T>): Specification<T>;
  not(): Specification<T>;
}

export class CompositeSpecification<T> implements Specification<T> {
  public isSatisfiedBy(candidate: T): boolean {
    throw new Error("Not implemented");
  }

  public and(other: Specification<T>): Specification<T> {
    return new AndSpecification(this, other);
  }

  public or(other: Specification<T>): Specification<T> {
    return new OrSpecification(this, other);
  }

  public not(): Specification<T> {
    return new NotSpecification(this);
  }
}

class AndSpecification<T> extends CompositeSpecification<T> {
  constructor(
    private readonly left: Specification<T>,
    private readonly right: Specification<T>
  ) {
    super();
  }

  public isSatisfiedBy(candidate: T): boolean {
    return (
      this.left.isSatisfiedBy(candidate) && this.right.isSatisfiedBy(candidate)
    );
  }
}

export class OrSpecification<T> extends CompositeSpecification<T> {
  constructor(
    private readonly left: Specification<T>,
    private readonly right: Specification<T>
  ) {
    super();
  }

  public isSatisfiedBy(candidate: T): boolean {
    return (
      this.left.isSatisfiedBy(candidate) || this.right.isSatisfiedBy(candidate)
    );
  }
}

export class NotSpecification<T> extends CompositeSpecification<T> {
  constructor(private readonly specification: Specification<T>) {
    super();
  }

  public isSatisfiedBy(candidate: T): boolean {
    return !this.specification.isSatisfiedBy(candidate);
  }
}


```

Este arquivo define uma interface Specification que representa uma regra de negócio e uma classe CompositeSpecification que implementa a lógica de combinação de regras de negócio.

As classes AndSpecification, OrSpecification e NotSpecification são subclasses de CompositeSpecification que implementam as operações lógicas AND, OR e NOT, respectivamente. 

Crie um arquivo chamado  `transaction.ts` na pasta `src/specifications` com o seguinte conteúdo:

```ts

import { Transaction } from "../entities/transaction";
import { CompositeSpecification } from "./specification";

export class TransactionSpecification extends CompositeSpecification<Transaction> {
  public isSatisfiedBy(candidate: Transaction): boolean {
    return TransactionSpecification.amountIsGreaterThan(50)
      .and(TransactionSpecification.dateIsGreaterThan(new Date(2022, 0, 1)))
      .and(TransactionSpecification.descriptionContains("supermercado"))
      .isSatisfiedBy(candidate);
  }

  public static amountIsGreaterThan(value: number): TransactionSpecification {
    return new (class extends TransactionSpecification {
      public isSatisfiedBy(candidate: Transaction): boolean {
        return candidate.amount > value;
      }
    })();
  }

  public static amountIsLessThan(value: number): TransactionSpecification {
    return new (class extends TransactionSpecification {
      public isSatisfiedBy(candidate: Transaction): boolean {
        return candidate.amount < value;
      }
    })();
  }

  public static dateIsGreaterThan(value: Date): TransactionSpecification {
    return new (class extends TransactionSpecification {
      public isSatisfiedBy(candidate: Transaction): boolean {
        return candidate.date > value;
      }
    })();
  }

  public static dateIsLessThan(value: Date): TransactionSpecification {
    return new (class extends TransactionSpecification {
      public isSatisfiedBy(candidate: Transaction): boolean {
        return candidate.date < value;
      }
    })();
  }

  public static descriptionContains(value: string): TransactionSpecification {
    return new (class extends TransactionSpecification {
      public isSatisfiedBy(candidate: Transaction): boolean {
        return candidate.description.includes(value);
      }
    })();
  }
}


```

A classe TransactionSpecification é uma subclasse de CompositeSpecification que define as regras de negócio específicas para a entidade Transaction.

As funções estáticas amountIsGreaterThan, amountIsLessThan, dateIsGreaterThan, dateIsLessThan e descriptionContains retornam instâncias de TransactionSpecification que representam cada uma das regras de negócio. O método `isSatisfiedBy` contem a composição da regra. a classe esta com as subsclasses publica a modo que ao criar uma outra regra pode ser apenas ajustada e extendida para compor.

## Utilizando as regras de negócio
Agora que temos as regras de negócio definidas, podemos utilizá-las para validar uma transação. Crie um arquivo chamado `transaction.ts` na pasta `src/validators` com o seguinte conteúdo:

```typescript
import { Transaction } from './Transaction';
import { TransactionSpecification, Specification } from './TransactionSpecification';

export class TransactionValidator {
  constructor(private readonly specification: Specification<Transaction>) {}

  public validate(transaction: Transaction): boolean {
    return this.specification.isSatisfiedBy(transaction);
  }
}

// testando o validator
const transaction = new Transaction(100, new Date(), 'Compra no supermercado');

const validator = new TransactionValidator()

console.log(validator.validate(transaction)); // true

```

Este arquivo define a classe `TransactionValidator`, que recebe uma instância de `Specification<Transaction>` no construtor e possui um método validate que recebe uma transação e retorna um valor booleano indicando se a transação é válida ou não.

No exemplo acima, criamos uma transação com valor 100, data new Date() e descrição 'Compra no supermercado'. Em seguida, criamos uma instância de `TransactionValidator` as regra de negócio verifica se o valor da transação é maior que 50, se a data da transação é maior que 01/01/2022 e se a descrição da transação contém a palavra 'supermercado'. Finalmente, chamamos o método validate passando a transação criada e exibimos o resultado no console.

## Conclusão
Neste artigo, vimos como implementar o padrão `Specification` em uma aplicação `Node.js` com `TypeScript`. Criamos uma entidade `Transaction`, definimos regras de negócio para validar uma transação utilizando a classe `TransactionSpecification` e criamos um validador de transações utilizando a classe TransactionValidator.

O padrão `Specification` pode ser aplicado em diversos contextos e é uma boa opção quando precisamos implementar regras de negócio complexas que envolvem múltiplas entidades. Com ele, podemos tornar o código mais modular, fácil de manter e extensível.

Segue exemplo no github: https://github.com/jhonesgoncalves/example-specification-ts