Hipótesis: los modelos CQRS con blockchain como capa de persistencia funcionan

El artículo muestra cómo aplicar el patrón CQRS con la tecnología Blockchain para resolver un caso de negocios que no es posible con las características nativas que ofrece la plataforma Blockchain.

En este artículo encontrará:

  • Problema: Lo que pretendemos resolver. Desde el lado comercial y técnico.
  • Posibles escenarios: Todo es más sencillo si tenemos un ejemplo
  • Introducción a los conceptos CQRS
  • Explicando el porqué de CQRS y Blockchain
  • Explicando por qué Event-Sourcing y Blockchain quizás no
  • Un ejemplo de código para solucionar un posible escenario
  • Recapitulación de lo que se ha explicado
  • Lista de blockchains compatibles con este enfoque

¿Por qué necesitamos de los modelos CQRS? Que tenemos hoy

Para saber por qué podríamos necesitar modelos CQRS en Blockchain, explicaremos el problema que queremos resolver primero.

Todos los productos son la implementación de la solución específica en un espacio problemático. El espacio de la solución debe guiarse a través del espacio problemático, de lo contrario, podríamos estar construyendo algo que el usuario no necesita.

Diagrama Problema-Solución

El espacio problema es su dominio. Un dominio es lo que la empresa quiere resolver. Podríamos tener el caso de una plataforma de gestión de activos. El dominio es la gestión de activos.

Podríamos querer dividir el dominio en un subdominio para tener una mejor manera de organizar nuestro espacio de solución, una estrategia básica de Dividir y Conquistar. Podríamos querer definir / implementar cada una de manera diferente dependiendo de los requisitos. Puede que resulte más familiar los siguientes términos Diseño impulsado por el dominio o la Arquitectura de microservicio, pero no son necesarios para el entendimiento de este artículo.

Queremos definir esos subdominios porque, una de las razones, pero no la única ni la más importante, solo unirá un subdominio con Blockchain. Por lo tanto, tenemos todos los problemas posibles con Blockchain en ese subdominio específico y no permitimos que todas las aplicaciones sufran ese acoplamiento no deseado.

Es posible que desee utilizar Blockchain en más de un subdominio, podría ser necesario, pero le sugeriría que no comenzará de esa forma. Ya que es posible que primero necesite experiencia con Blockchain y, si al final, necesita abandonar esa implementación. Se tendrá que cambiar esa solución de subdominio a otra. Por lo tanto, controlar la complejidad accidental que trae Blockchain al sistema en solo un subdominio será suficiente trabajo y le dará suficiente dolor de cabeza, no comience con dos subdominios utilizando Blockchain.

En cada subdominio, tendrá diferentes clases que representan conceptos de un negocio.

Ejemplo de entidades que pueden pertenecer a un subdominio

Ese concepto de negocio, lo referiremos como un modelo de dominio. El modelo de dominio es un modelo conceptual de uno que contiene datos y comportamientos. El modelo de dominio contiene el código para resolver el problema del negocio. Entonces, le da valor a la plataforma ya que resuelve lo que el usuario necesita. Como desarrollador y como empresa, desea invertir sus recursos en esos modelos porque, generalmente, es de dónde obtiene dinero.

Podemos estar de acuerdo en que un Modelo de Dominio es siempre incompleto e imperfecto, y algunas veces es útil. Por lo tanto, debemos repetir rápidamente nuestras suposiciones y adaptar las necesidades comerciales a las necesidades reales de los usuarios, para que puedan cambiar más rápido que la solución de la empresa.

Para saber que estamos construyendo la herramienta correcta, queremos tener tiempos de entrega cortos y un bucle de retroalimentación rápido. Cuanto más rápido sea este circuito de retroalimentación, más competitivo será su producto en el mercado. En esencia, ser ágil.

El Caso

Supongamos que el espacio de la solución requiere un libro mayor inmutable para interactuar con actores de terceros a través de una red compartida y confiable. Un actor en el sistema es cualquier persona que interactúa con el sistema que hemos definido.

Nuestro escenario de caso es una empresa que proporciona un sistema de gestión de activos. Como equipo de desarrollo, hemos visto que Blockchain parece ser una tecnología prometedora que se adapta a nuestras necesidades iniciales. Por lo tanto, hacemos una investigación de qué Blockchains podríamos usar.

Algunas Blockchains tienen un mejor enfoque que otros para resolver cualquier problema de negocios. Agrupemos dos formas principales de resolverlo: contrato inteligente y características / contratos predefinidos.

Enfoque Smart Contract, estilo Ethereum. Ayuda a representar todo el modelo de negocio.

Enfoque de características predefinidas, estilo NEM. Proporciona un conjunto de transacciones predefinidas que combinadas brindan una mejor manera de interactuar. Problema, el modelo de dominio no se puede definir para que coincida con nuestro negocio como lo haríamos con las herramientas tradicionales.

Propiedades que deberíamos tener

  1. Ciclos pequeños de Desarrollo
  2. Una actualización sencilla que se pueda hacer en cualquier momento de la lógica de negocio
  3. Modelo de Dominio enriquecido
  4. Inmutabilidad del almacenamiento de la data
  5. Facilidad de uso en la red

Hay diferentes maneras de implementar un modelo de dominio con Blockchain. En mi humilde opinión, todos los métodos que conozco son demasiado caros y el tiempo de desarrollo es demasiado alto en comparación con las tecnologías “tradicionales”. Asegúrese de que los gastos generales de Blockchain valgan la pena.

Como equipo de desarrollo, hemos elegido la NEMBlockchain debido a su enfoque en los activos.

Entonces, ¿qué opciones tenemos para construir un modelo de dominio útil? Debido a que el primer requisito comercial es el uso de tokens, encontramos que NEM proporciona este modelo en su núcleo. Entonces, ya tenemos lo que necesitamos usando NEM Blockchain. Ruta del modelo de dominio para que se ajuste al modelo de blockchain actual.

En el caso de NEM2, también conocido como Catapult, tendrá la opción de extender el comportamiento de Blockchain para cadenas privadas, pero quiero compartir el caso donde no tenga esta opción para ayudar a los proyectos que utilizan otras plataformas de Blockchain que no tienen la opción de extienden el comportamiento en la cadena pero proporcionan una transacción que puede contener un mensaje de texto sin procesar.

El problema del equipo de desarrollo

¡Hay un cambio de negocio! El equipo de negocios ha visto que los clientes necesitan activos no fungibles. Un activo no es fungible cuando es único, tiene un propietario único.

  • Activo Fungible: Tokens, hay 100,000 X tokens, todos son iguales entre ellos.
  • Activo no fungible: un automóvil, usted es propietario del automóvil con ID 1202.

El negocio debe poner el producto rápidamente marcado para verificar el supuesto de la propuesta de valor de negocio. Es por eso que el propietario del producto compartió la preocupación de que necesitan probar las cosas rápidamente y girar más rápido con el equipo de desarrollo.

Después de la primera implementación, el equipo de desarrollo se da cuenta de que las transacciones de NEM Blockchain no representan todo lo que necesita la empresa, por ejemplo, no tiene el soporte nativo para Activos No Fungibles.

Como equipo de desarrollo, tenemos dos opciones:

  1. Reescribir todo para usar un Blockchain con soporte Smart Contract. Implica volver a aprender una nueva tecnología, sus límites y volver a adaptar lo que tenemos con la nueva tecnología. Si tenemos suerte, solo tenemos algunas cosas, pero si tenemos 2–3 componentes (aplicación móvil, aplicación web, servidor), reescribir parte de esa lógica puede ser doloroso y un desperdicio de recursos
  2. Adaptar la forma en que interactuamos con Blockchain para tener más libertad al definir los Modelos de Dominio. El equipo de desarrollo ha adquirido experiencia con Blockchain y conocen los límites de esa tecnología y, además, comenzaron a estimar mejor las características relacionadas con Blockchain, no queremos volver a comenzar este proceso. Además podemos reutilizar parte de lo que ya tenemos.

Procedamos a la solución

El equipo de desarrollo tiene que modelar un activo no fungible sobre una blockchain que no lo ofrece de forma nativa.

El equipo de desarrollo realiza una investigación de diferentes enfoques de diseño y encuentra el patrón CQRS. CQRS significa Segregación de Responsabilidad de Consulta de Comando.


Usando el patrón CQRS, el equipo de desarrollo podría crear una representación virtual de activos no fungibles utilizando características nativas NEM, como mensajes de transacciones.

Introducción de conceptos CQRS

Utilizaremos modelos de comandos para actualizar el estado y modelos de consulta para obtener el estado.

El CQRS se usa a menudo con Event-Sourcing, pero no es necesario que se usen juntos. Event-Sourcing garantiza que cada cambio en el estado se capture en un objeto de evento y se mantenga.

Event-Sourcing tiene algunos beneficios deseados que queremos aplicar en Blockchain.

  • Reconstrucción completa: podemos reconstruir el estado de la aplicación volviendo a ejecutar los eventos del registro de eventos en una aplicación vacía.
  • Consulta temporal: podemos determinar cuáles eran los estados de aplicación en un punto determinado.
  • Repetición de eventos: si algún evento pasado fue incorrecto, podemos calcular las consecuencias y aplicar una solución.

Con la tecnología Blockchain, algunos puntos son más difíciles que otros.

¿Qué es un registro de eventos? Es una base de datos donde todos los eventos son persistentes.

Un comando y un evento no contienen lógica, solo datos. Son objetos de transferencia de datos (DTO).

Un comando muestra la intención de cambiar el estado. Un evento comparte que algo sucedió. Hay una gran diferencia. Un comando puede fallar, mientras que un evento refleja algo que ya sucedió en el sistema.

¿Por qué es razonable el uso de modelos CQRS con Blockchain?

Recapitulando de donde venimos, tuvimos la necesidad de representar una cosa de negocios que debería cambiar su estado con el tiempo y seguir utilizando Blockchain.


Un modelo CQRS nos ayuda a definir un conjunto de comandos que almacenaremos en la cadena de bloques y publicaremos nuestros modelos de consulta con una API pública.

El almacenamiento de los comandos en la cadena nos ayuda a recrear el estado y auditar el caché de estado que un tercero proporciona para saber si es válido o no válido.

Compartir los comandos nos ayuda a que otros sepan rápidamente cómo interactuar con nuestro Modelo de dominio a través de una red confiable sin la necesidad de compartir la lógica interna de nuestro Modelo de dominio.

Como equipo, podemos elegir el nivel de información compartida que deseamos.

Comandos para compartir casos: la gente necesitará a) confiar en nuestra API para leer b) emular nuestra lógica modelo en caso de que algo esté mal y probar que estamos haciendo trampa

Comandos para compartir casos y lógica modelo: las personas a) confiarán en nuestra API para leer o b) crearán una aplicación para ejecutar nuestra lógica modelo y no confiarán en nuestra API.

Caso: compartir todas las aplicaciones: las personas a) confiarán en nuestra API para leer b) iniciarán todas las aplicaciones desde su final y no nos utilizarán.

Hay un límite gris para compartir la lógica del modelo y todo.

En nuestro caso, optamos por compartir los comandos y la lógica del modelo. Queremos que otros puedan ejecutar esto por sí mismos si lo desean, y confían en nosotros para alguna lógica específica, como los complejos modelos de consulta.


Con el modelo CQRS, podemos escalar de manera diferente el Modelo de Comando y el Modelo de Consulta. Queremos que el sistema responda rápidamente a las consultas complejas, pero no nos importa tener una escritura lenta, ya que la creación o administración de activos no es tan frecuente como leer los datos.

Además, este enfoque nos permite elegir solo compartir los Comandos si quisiéramos. Es muy importante para una empresa elegir qué compartir y qué no compartir.

La diferenciación de la empresa imaginaria es cómo administrar la información, no controlar la creación de activos.

Utilizaremos una cuenta de NEM para guardar todos los comandos

Código

Feature: non-fungible asset

Scenario: Create an asset

 Given a source and asset id
 When it’s announced into the network
 Then the asset can be fetched by asset address
 And the owner is the announcer

Scenario: Add metadata to an asset

 Given a metadata
 When it’s attached to an asset
 And the announcer is the owner
 Then the asset has the metadata attached

 Given a metadata
 When it’s attached to an asset
 And the announcer is NOT the owner
 Then the asset keeps as it is

Un posible diagrama UML podría ser

El siguiente código se extrae de la implementación de la biblioteca nem2-nofungible-asset. Podría haber cambiado en el momento en que lea el artículo. Por lo que se sugiere que verifique los archivos a los que se hace referencia a continuación para obtener un ejemplo de código más preciso.

Como se ve el comando

export class CreateAssetCommand implements Command {
   public static readonly TYPE = “CreateAssetCommand”;
   public static readonly VERSION = 1;

   public static of(source: string, identifier: string, networkType: NetworkType): CreateAssetCommand {
       return new CreateAssetCommand(source, identifier, AssetId.of(source, identifier, networkType));
   }

   public static fromTransaction(transaction: TransferTransaction): CreateAssetCommand {
       let command;
       try {
           command = JSON.parse((transaction.message).payload);
       } catch (e) {
           throw new Error(‘It is not a CreateAssetCommand’);
       }
       if (command.type === undefined || command.type !== CreateAssetCommand.TYPE) {
           throw new Error(‘It is not a CreateAssetCommand’);
       }
       const source = command.source;
       const identifier = command.identifier;
       const assetId = AssetId.of(source, identifier, transaction.networkType);
       return new CreateAssetCommand(source, identifier, assetId, transaction.signer);
   }

   constructor(public readonly source: string,
               public readonly identifier: string,
               public readonly id: AssetId,
               public readonly signer?: PublicAccount) {
   }

   toTransaction(): TransferTransaction {
       return TransferTransaction.create(
           Deadline.create(),
           this.id.address,
           [],
           PlainMessage.create(JSON.stringify({
               type: CreateAssetCommand.TYPE,
               version: CreateAssetCommand.VERSION,
               source: this.source,
               identifier: this.identifier
           })),
           this.id.address.networkType
       );
   }
}

Hará tres cosas

  • Ser creado con la información requerida (líneas 5-7).
  • Cree una transacción específica de NEM2 con un mensaje de texto específico (líneas 36–41)
  • Recrear desde una transacción (líneas 9–23)

Observe que falta información hasta que se inserta en la red, como el firmante (propietario).

En el futuro, nos gustaría almacenar la información de la transacción dentro del Comando para saber que se ejecutan en orden.

El Modelo de Dominio, Activo, está diseñado como:

export class Asset {
   public metadata: object = {};

   public static create(createAssetCommand: CreateAssetCommand): Asset {
       return new Asset(createAssetCommand.id,
           createAssetCommand.signer!,
           createAssetCommand.source,
           createAssetCommand.identifier,
           [createAssetCommand]);
   }

   public static createAndConsumeCommands(commands: Command[]): Asset {
       const createAssetCommand = commands.shift();
       let asset: Asset;
       if (createAssetCommand instanceof CreateAssetCommand) {
           asset = Asset.create(createAssetCommand as CreateAssetCommand);
       }
       else {
           throw new Error(“First Command is not a CreateAssetCommand”);
       }
       commands.forEach((command) => asset.apply(command));
       return asset;
   }

   constructor(public readonly assetId: AssetId,
               public readonly owner: PublicAccount,
               public readonly source: string,
               public readonly identifier: string,
               public readonly commands: Command[]) {
   }

   private apply(command: Command) {
       if (command instanceof AddMetadataCommand) {
           this.applyAddMetadataCommand(command as AddMetadataCommand);
       }
   }

   private applyAddMetadataCommand(command: AddMetadataCommand) {
       this.metadata = Object.assign(this.metadata, command.metadata);
       this.commands.push(command);
   }
}

Tenemos una manera de recrear el estado dados todos los comandos (líneas 12–23). Delegan en el método de aplicación donde, debido a las limitaciones de TypeScript, asigna los comandos a los métodos que tienen la lógica de aplicar el cambio.

En cada método de aplicación, colocaremos la lógica de negocio. En caso de que falle, deberíamos tener diferentes estrategias para informar la falla al creador. Te lo explicaré en otro post.

Cómo leer desde el blockchain

export class AssetRepository {
   private readonly availableCommands = [CreateAssetCommand, AddMetadataCommand];

   constructor(private readonly accountRepository: AccountHttp,
               private readonly networkType: NetworkType) {
   }

   public byAssetIdentifier(source: string, identifier: string): Observable<Asset> {
       return this.byPublicKey(AssetId.of(source, identifier, this.networkType).publicKey);
   }

   public byPublicKey(publicKey: string): Observable<Asset> {
       const publicAccount = PublicAccount.createFromPublicKey(publicKey, this.networkType);
       return of(publicAccount).pipe(
           mergeMap<PublicAccount, Transaction[]>((publicAccount: PublicAccount): Observable<Transaction[]> =>
               this.accountRepository.incomingTransactions(publicAccount)),
           map((txs: Transaction[]) => {
               const commands: Command[] = [];
               txs.forEach(tx => {
                   if (tx.type === TransactionType.TRANSFER) {
                       this.availableCommands.forEach(avaiableCommand => {
                           try {
                               commands.push(avaiableCommand.fromTransaction(tx as TransferTransaction));
                           } catch (e) {
                              // ignore
                           }
                       });

                   }

               });
               return Asset.createAndConsumeCommands(commands);
           }));
   }
}

Buscamos todas las Transacciones de Transferencia (línea 20) y obtenemos cada Comando (líneas 21–27). Después de eso, reconstruimos el estado del Activo.

El Repositorio tiene algunas limitaciones debido a la forma en que se implementa Catapult, pero ya está en curso una solicitud de función (# 7 y # 8).

Lo más importante es no poder ordenar por la altura de la transacción, queremos leer desde la transacción más antigua a la más nueva. Todavía no está implementado en el actual Repositorio de Activos.


Es posible que tengamos que buscar todas las transacciones en la memoria y luego revertir el orden mientras esas características no están implementadas.

Cosas pendientes para implementar

  • El estado del caché
  • Consulta los modelos en ese estado de caché
  • Aplicación de comando incremental a través de notificaciones del lado del servidor (WebSockets)

Inconvenientes

  • ¿Qué pasa si un comando se incluye antes de otro anunciado previamente?

Suponemos que será una concurrencia en la escritura y podrían causar una aplicación de comandos no deseada. En caso de que el modelo de dominio dependa de la orden, podría causar algunos problemas de condición de carrera, pero podría no ser el caso. Somos conscientes de que podría suceder y desarrollamos nuestro Modelo de Dominio teniendo eso en cuenta.

  • La escritura es tan lenta como el Blockchain Block.

Sí, es. Cuando elegimos una tecnología Blockchain / DLT, nos dimos cuenta de las consecuencias. No podemos hacerlo más rápido en la escritura, pero con CQRS, podemos escalar la forma en que leemos. El caso de uso debe tener estas propiedades, más lecturas que escrituras.

Recapitulación

Probamos una primera iteración de un modelo CQRS con Blockchain. Parece ser viable aplicar y extender las características.

La solución propuesta no está completamente implementada y todavía hay margen de mejora.

¡Pruébelo en su próxima biblioteca y comparta su experiencia con nosotros y con otros!

Lista de blockchains / DLT que podrían beneficiarse de este enfoque

NEM2, también conocido como catapulta. Ejemplo de biblioteca. https://github.com/aleixmorgadas/nem2-nonfungible-asset
NEM1. No hay ejemplo de biblioteca todavía

Fuente: Medium