ViennaNET: un conjunto de bibliotecas backend. Parte 2

La comunidad de desarrollo .NET de Raiffeisenbank continúa con un breve desglose de ViennaNET. Puede leer sobre cómo y por qué llegamos a esto en la primera parte .



En este artículo, repasaremos las bibliotecas aún no consideradas para trabajar con transacciones distribuidas, colas y bases de datos, que se pueden encontrar en nuestro repositorio en GitHub (la fuente está aquí ), y los paquetes Nuget están aquí .







ViennaNET.Sagas



Cuando un proyecto pasa a DDD y una arquitectura de microservicio, cuando la lógica empresarial se distribuye entre diferentes servicios, surge un problema asociado con la necesidad de implementar el mecanismo de transacciones distribuidas, porque muchos escenarios suelen afectar a varios dominios a la vez. Puede obtener más información sobre estos mecanismos, por ejemplo, en el libro "Patrones de microservicios" de Chris Richardson .



En nuestros proyectos, hemos implementado un mecanismo simple pero útil: una saga, o más bien una saga basada en la orquestación. Su esencia es la siguiente: existe un determinado escenario de negocio en el que es necesario realizar de forma secuencial operaciones en diferentes servicios, mientras que, ante cualquier problema en algún paso, es necesario llamar al procedimiento de rollback de todos los pasos anteriores donde se brinda. Por lo tanto, al final de la saga, independientemente del éxito, obtenemos datos consistentes en todos los dominios.



Nuestra implementación sigue siendo básica y no está vinculada al uso de ningún método de interacción con otros servicios. No es difícil usarlo: basta con heredar 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 se reenviará a todos los pasos en tiempo de ejecución. La saga en sí es una clase sin estado, por lo que la instancia se puede colocar en la DI como Singleton para obtener las dependencias necesarias.



Declaración de ejemplo:



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


Ejemplo de llamada:



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


Se pueden encontrar ejemplos completos de diferentes implementaciones aquí y en el ensamblaje con pruebas .



ViennaNET.Orm. *



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



ViennaNET.Orm.Seedwork ViennaNET.Orm- ensamblajes principales que contienen interfaces base y sus implementaciones, respectivamente. Detengámonos en su contenido con más detalle.



La interfaz IEntityFactoryServicey su implementación EntityFactoryServiceson 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 restringir las capacidades de una clase para trabajar con una base de datos, por ejemplo, para habilitar datos de solo lectura. Para tales casos, IEntityFactoryServicetiene un antepasado: una interfaz IEntityRepositoryFactoryen la que solo se declara el método para crear repositorios.



Para el acceso directo a la base de datos, se utiliza el mecanismo del proveedor. Para cada utilizadas en nuestros equipos de base de datos tienen su propia aplicació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, en el marco de un servicio, sin ningún costo de actualización de la infraestructura, una migración paso a paso de 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 la asignación 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 el identificador de conexión de la configuración se toma como argumento:



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


ApplicationContext de ejemplo:



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


Si no se especifica ningún identificador de 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 usar la descripción tanto a través de archivos xml como a través de clases. Para escribir repositorios de códigos auxiliares en pruebas unitarias, hay una biblioteca ViennaNET.TestUtils.Orm.



Ejemplos completos de uso de ViennaNET.Orm. * Se pueden encontrar 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 unificado máximo posible en términos de trabajar con la biblioteca, independientemente del administrador de colas utilizado. La biblioteca ViennaNET.Messaginges solo responsable de esta unificación y ViennaNET.Messaging.MQSeriesQueue, ViennaNET.Messaging.RabbitMQQueue ViennaNET.Messaging.KafkaQueuecontiene las implementaciones del adaptador para IBM MQ, RabbitMQ y Kafka, respectivamente.



Hay dos procesos al trabajar con colas: recibir un mensaje y enviarlo.



Considere conseguirlo. Aquí hay 2 opciones: para escuchar constantemente y para recibir un solo mensaje. Para escuchar constantemente la cola, primero debe describir la clase de procesador heredada deIMessageProcessor, que se encargará de procesar el mensaje entrante. Además, debe estar "vinculado" a una cola específica, esto se hace registrándose IQueueReactorFactorycon el identificador de cola de la configuración:



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


Un ejemplo de 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 la interfaz de fábrica, IMessagingComponentFactoryexiste un método CreateMessageReceiverque 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, debe utilizar el mismo IMessagingComponentFactoryy crear un remitente del mensaje:



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


Hay tres opciones predefinidas para serializar y deserializar un mensaje: solo texto, XML y JSON, pero si es necesario, puede realizar sus propias implementaciones de las interfaces de manera segura IMessageSerializer IMessageDeserializer.



Intentamos preservar las capacidades únicas de cada administrador de colas, por ejemplo, ViennaNET.Messaging.MQSeriesQueuepermite enviar no solo mensajes de texto, sino también mensajes de bytes, y ViennaNET.Messaging.RabbitMQQueueadmite enrutamiento y cola sobre la marcha. Nuestro envoltorio 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 para un solo mensaje de respuesta.



A continuación, se muestra un ejemplo del uso de colas con matices de conexión básicos .



ViennaNET.CallContext



Usamos colas no solo para la integración entre diferentes sistemas, sino también para la comunicación entre microservicios de una aplicación, por ejemplo, en el marco de una saga. Esto llevó a la necesidad de transferir, junto con el mensaje, datos auxiliares como nombre de usuario, ID de solicitud para el registro de extremo a extremo, dirección IP de origen y datos de autorización. Para implementar el reenvío de estos datos, se desarrolló una biblioteca ViennaNET.CallContextque 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 la cola o mediante Http. Luego, antes de enviar una solicitud o mensaje saliente, los datos se sacan del contexto y se colocan en los encabezados. Así, el siguiente servicio recibe datos auxiliares y los elimina de la misma forma.



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



All Articles