4lessandrodev / rich-domain

A lib to help you create a robust project based on domain driven-design (ddd) principles with typescript and zero dependencies.
https://www.npmjs.com/package/rich-domain
MIT License
122 stars 5 forks source link

Entity History #54

Closed hikinine closed 7 months ago

hikinine commented 1 year ago

Saudações,

A ideia de criar snapshots dos estados do Aggregate durante as possíveis mudanças é fantástica, mas o mesmo só ocorrerá se toda mutação dos dados do agregado ocorrer por meio dos métodos implementados na getters-and-setters. Pra alguns cenários eu acredito ser inviável mapear todos os campos de uma propriedade com get e set, uma situação em que o domínio proíba a mutação do campo email por exemplo por qualquer N motivo.

Sugestão:

Implementar o History do agregado utilizando o Proxy / Reflect nativo do javascript.

image Algo nesse formato, a ideia ainda é a mesma mas ao invés de mapear o snapshot com um método pré estabelecido, interceptamos qualquer atribuição em this.props refletindo 100% o mesmo comportamento com adicional do snapshot.

Você acha que seria viável?

4lessandrodev commented 1 year ago

Achei essa ideia sensacional

4lessandrodev commented 1 year ago

@hikinine você tem essa feature já implementada? gostei bastante, eu gostaria de testar...

hikinine commented 1 year ago

@4lessandrodev tenho sim, mas em um fork com bastante modificação. Como meu intuito era aprender fui modificando algumas coisas. segue o link

https://github.com/hikinine/rich-domain

PS: não adaptei os testes. Não estão funcionando

4lessandrodev commented 1 year ago

@hikinine Eu vi as mudanças, muito bacana. Você colocou diferentes modos de rastrear as mudanças nas props. Eu não tinha pensado no proxy, mas se encaixou perfeitamente, vou seguir sua sugestão e fazer essa alteração.

hikinine commented 1 year ago

Combinado, vou ir acompanhando então. Abraço

hikinine commented 1 year ago

@4lessandrodev

Encontrei um possível problema com o Proxy. Imagine a situação onde exista um Aggregate chamado Budget.

interface BudgetProps {
  id?: Domain.UUID4,
  products: Collection<Product>,
  company: Company,
  //...rest of properties
}

export class Budget extends Domain.Aggregate<BudgetProps> {
  private constructor(props: BudgetProps) {
    super(props);
  }

  public static create(props: BudgetProps): Either<
    | DomainErrors.UnexpectedError,
    | Budget
  > {

    return Result.ok(new Budget(props));
  }

  //rest of the methods...

  public registerCompanyEmail(email: string) {
    const emailResult = Email.create(email);

    if (emailResult.isFail()) {
      return emailResult
    }
    const value = emailResult.getValue()

    //AQUI MORA O PROBLEMA
    //imagine que company seja uma entidade que tem uma coleção de emails
    //o método addEmail nada mais é do que um this.props.email.push(email)
    //acontece que o proxy não é capaz de identificar a mudança por que não estamos necessariamente 
    //reescrevendo o valor de "email". 
    this.props.company.addEmail(value)
    return Result.ok()
  }
}

Se o metodo fizesse o seguinte:

this.props.email = [...this.props.email, newEmail]

mas parece uma alternativa inviável

4lessandrodev commented 1 year ago

ah não ser que a lib disponibilize uma lista que intercepta ou reescreva os métodos do array.


import { List } from 'rich-domain';

// Ao invés disso
interface Props { 
  emails: Array<Email>;
}

// Definir isso
interface Props { 
  emails: List<Email>;
}
hikinine commented 1 year ago

My bad, fui ler mais sobre deep nested objects com proxy, é só reescrever o trap do get que resolve

 const handler = function (): ProxyHandler<Props> {
      return {
        get: function (target, prop) {
          if (!isProxy(target) && isObject(target[prop])) {
            return new Proxy(target[prop], handler());
          }
          return target[prop];
        },
        set: function (target, prop, value, receiver) {
          const oldValue = Reflect.get(target, prop, receiver)
          self.metaHistory.addSnapshot(self.props, prop, oldValue, value)
          Reflect.set(target, prop, value, receiver)
          return true;
        }
      };
    };

    const proxy = new Proxy<Props>(this.props, handler());
    this.props = proxy