ViennaNET: un conjunto de bibliotecas para el backend. Parte 2

La comunidad de desarrolladores Raiffeisenbank .NET continúa repasando brevemente el contenido de ViennaNET. Sobre cómo y por qué llegamos a esto, puedes leer la primera parte.

En este artículo, analizaremos bibliotecas aún por considerar para trabajar con transacciones, colas y bases de datos distribuidas, que se pueden encontrar en nuestro repositorio de GitHub (las fuentes están aquí), y Paquetes Nuget aquí.

ViennaNET: un conjunto de bibliotecas para el backend. Parte 2

VienaNET.Sagas

Cuando un proyecto cambia a DDD y arquitectura de microservicios, cuando la lógica empresarial se distribuye entre diferentes servicios, surge un problema relacionado con la necesidad de implementar un mecanismo de transacción distribuida, porque muchos escenarios a menudo afectan a varios dominios a la vez. Puede familiarizarse con dichos mecanismos con más detalle, por ejemplo, en el libro "Patrones de microservicios", Chris Richardson.

En nuestros proyectos hemos implementado un mecanismo simple pero útil: una saga, o más bien una saga basada en orquestación. Su esencia es la siguiente: existe un determinado escenario empresarial en el que es necesario realizar operaciones secuencialmente en diferentes servicios, y si surge algún problema en algún paso, es necesario llamar al procedimiento de reversión para todos los pasos anteriores, donde es proporcionó. Así, al final de la saga, independientemente del éxito, recibimos datos consistentes en todos los ámbitos.

Nuestra implementación todavía se realiza en su forma básica y no está vinculada al uso de ningún método de interacción con otros servicios. No es difícil de usar: simplemente crea un descendiente de la clase abstracta base SagaBase<T>, donde T es tu clase de contexto en la que puedes almacenar los datos iniciales necesarios para que la saga funcione, así como algunos resultados intermedios. La instancia de contexto pasará a todos los pasos durante la ejecución. Saga en sí es una clase sin estado, por lo que la instancia se puede colocar en DI como Singleton para obtener las dependencias necesarias.

Anuncio de ejemplo:

public class ExampleSaga : SagaBase<ExampleContext>
{
  public ExampleSaga()
  {
    Step("Step 1")
      .WithAction(c => ...)
      .WithCompensation(c => ...);
	
    AsyncStep("Step 2")
      .WithAction(async c => ...);
  }
}

Llamada de ejemplo:

var saga = new ExampleSaga();
var context = new ExampleContext();
await saga.Execute(context);

Se pueden ver ejemplos completos de diferentes implementaciones. aquí y en asamblea con pruebas.

VienaNET.Orm.*

Un conjunto de bibliotecas para trabajar con varias bases de datos a través de Nhibernate. Usamos el enfoque DB-First usando Liquibase, por lo que solo hay funcionalidad para trabajar con datos en una base de datos ya preparada.

ViennaNET.Orm.Seedwork и ViennaNET.Orm – ensamblajes principales que contienen interfaces básicas y sus implementaciones, respectivamente. Veamos sus contenidos con más detalle.

Interfaz IEntityFactoryService y su implementación EntityFactoryService son el principal punto de partida para trabajar con la base de datos, ya que aquí se crean la Unidad de Trabajo, repositorios para trabajar con entidades específicas, así como ejecutores de comandos y consultas SQL directas. A veces es conveniente limitar las capacidades de una clase para trabajar con una base de datos, por ejemplo, para brindar la capacidad de leer solo datos. Para tales casos IEntityFactoryService hay un antepasado - interfaz IEntityRepositoryFactory, que solo declara un método para crear repositorios.

Para acceder directamente a la base de datos, se utiliza el mecanismo del proveedor. Cada DBMS que utilizamos en nuestros equipos tiene su propia implementación: ViennaNET.Orm.MSSQL, ViennaNET.Orm.Oracle, ViennaNET.Orm.SQLite, ViennaNET.Orm.PostgreSql.

Al mismo tiempo, se pueden registrar varios proveedores en una aplicación al mismo tiempo, lo que permite, por ejemplo, dentro de un servicio, sin costos de modificación de la infraestructura, realizar una migración paso a paso desde un DBMS a otro. El mecanismo para seleccionar la conexión requerida y, por lo tanto, el proveedor para una clase de entidad específica (para la cual se escribe el mapeo a las tablas de la base de datos) se implementa mediante el registro de la entidad en la clase BoundedContext (contiene un método para registrar entidades de dominio) o su sucesora. ApplicationContext (contiene métodos para registrar entidades de aplicación, solicitudes directas y comandos), donde se acepta como argumento el identificador de conexión de la configuración:

"db": [
  {
    "nick": "mssql_connection",
    "dbServerType": "MSSQL",
    "ConnectionString": "...",
    "useCallContext": true
  },
  {
    "nick": "oracle_connection",
    "dbServerType": "Oracle",
    "ConnectionString": "..."
  }
],

Ejemplo de contexto de aplicación:

internal sealed class DbContext : ApplicationContext
{
  public DbContext()
  {
    AddEntity<SomeEntity>("mssql_connection");
    AddEntity<MigratedSomeEntity>("oracle_connection");
    AddEntity<AnotherEntity>("oracle_connection");
  }
}

Si no se especifica el ID de la conexión, se utilizará la conexión denominada "predeterminada".

El mapeo directo de entidades a tablas de bases de datos se implementa utilizando herramientas estándar de NHibernate. Puede utilizar la descripción tanto a través de archivos xml como a través de clases. Para escribir cómodamente repositorios de resguardos en pruebas unitarias, existe una biblioteca ViennaNET.TestUtils.Orm.

Se pueden encontrar ejemplos completos del uso de ViennaNET.Orm.* aquí.

ViennaNET.Messaging.*

Un conjunto de bibliotecas para trabajar con colas.

Para trabajar con colas, se eligió el mismo enfoque que con varios DBMS, es decir, el enfoque más unificado posible en términos de trabajo con la biblioteca, independientemente del administrador de colas utilizado. Biblioteca ViennaNET.Messaging es precisamente el responsable de esta unificación, y ViennaNET.Messaging.MQSeriesQueue, ViennaNET.Messaging.RabbitMQQueue и ViennaNET.Messaging.KafkaQueue contienen implementaciones de adaptadores para IBM MQ, RabbitMQ y Kafka, respectivamente.

Cuando se trabaja con colas, existen dos procesos: recibir un mensaje y enviarlo.

Considere recibir. Aquí hay 2 opciones: escuchar continuamente y recibir un solo mensaje. Para escuchar constantemente la cola, primero debe describir la clase de procesador heredada de IMessageProcessor, que será el responsable de procesar el mensaje entrante. A continuación, se debe “vincular” a una cola específica; esto se hace mediante el registro en IQueueReactorFactory indicando el identificador de cola de la configuración:

"messaging": {
    "ApplicationName": "MyApplication"
},
"rabbitmq": {
    "queues": [
      {
        "id": "myQueue",
        "queuename": "lalala",
        ...
      }
    ]
},

Ejemplo de cómo empezar a escuchar:

_queueReactorFactory.Register<MyMessageProcessor>("myQueue");
var queueReactor = queueReactorFactory.CreateQueueReactor("myQueue");
queueReactor.StartProcessing();

Luego, cuando se inicia el servicio y se llama al método para comenzar a escuchar, todos los mensajes de la cola especificada irán al procesador correspondiente.

Para recibir un solo mensaje en una interfaz de fábrica IMessagingComponentFactory hay un método CreateMessageReceiverlo que creará un destinatario esperando un mensaje de la cola especificada:

using (var receiver = _messagingComponentFactory.CreateMessageReceiver<TestMessage>("myQueue"))
{
    var message = receiver.Receive();
}

Para enviar un mensaje necesitas usar el mismo IMessagingComponentFactory y crear un remitente de mensaje:

using (var sender = _messagingComponentFactory.CreateMessageSender<MyMessage>("myQueue"))
{
    sender.SendMessage(new MyMessage { Value = ...});
}

Hay tres opciones listas para usar para serializar y deserializar un mensaje: solo texto, XML y JSON, pero si es necesario, puede crear fácilmente sus propias implementaciones de interfaz. IMessageSerializer и IMessageDeserializer.

Hemos intentado preservar las capacidades únicas de cada administrador de colas, p.e. ViennaNET.Messaging.MQSeriesQueue le permite enviar no solo mensajes de texto, sino también de bytes, y ViennaNET.Messaging.RabbitMQQueue admite enrutamiento y colas sobre la marcha. Nuestro contenedor de adaptador para RabbitMQ también implementa algo parecido a RPC: enviamos un mensaje y esperamos una respuesta de una cola temporal especial, que se crea solo para un mensaje de respuesta.

aquí está un ejemplo de uso de colas con matices de conexión básicos.

VienaNET.CallContext

Usamos colas no solo para la integración entre diferentes sistemas, sino también para la comunicación entre microservicios de una misma aplicación, por ejemplo, dentro de una saga. Esto llevó a la necesidad de transmitir junto con el mensaje datos auxiliares como el inicio de sesión del usuario, el identificador de solicitud para el registro de un extremo a otro, la dirección IP de origen y los datos de autorización. Para implementar el envío de estos datos, desarrollamos una biblioteca. ViennaNET.CallContext, que le permite almacenar datos de una solicitud que ingresa al servicio. En este caso, no importa cómo se realizó la solicitud, a través de una cola o vía Http. Luego, antes de enviar la solicitud o mensaje saliente, se toman datos del contexto y se colocan en los encabezados. Así, el siguiente servicio recibe los datos auxiliares y los gestiona de la misma forma.

¡Gracias por su atención, esperamos sus comentarios y solicitudes de extracción!

Fuente: habr.com

Añadir un comentario