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:
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:
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:
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:
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
Top comments (0)