ViennaNET: un conxunto de bibliotecas para o backend. Parte 2

A comunidade de desenvolvedores de Raiffeisenbank .NET segue revisando brevemente o contido de ViennaNET. Sobre como e por que chegamos a isto, podes ler a primeira parte.

Neste artigo, imos pasar por bibliotecas aínda por considerar para traballar con transaccións distribuídas, colas e bases de datos, que se poden atopar no noso repositorio de GitHub (as fontes están aquí), e Paquetes Nuget aquí.

ViennaNET: un conxunto de bibliotecas para o backend. Parte 2

ViennaNET.Sagas

Cando un proxecto cambia á arquitectura DDD e de microservizos, cando a lóxica empresarial se distribúe entre distintos servizos, xorde un problema relacionado coa necesidade de implementar un mecanismo de transacción distribuída, porque moitos escenarios adoitan afectar a varios dominios á vez. Podes familiarizarte con tales mecanismos con máis detalle, por exemplo, no libro "Microservices Patterns", Chris Richardson.

Nos nosos proxectos, implementamos un mecanismo sinxelo pero útil: unha saga, ou máis ben unha saga baseada na orquestración. A súa esencia é a seguinte: existe un determinado escenario empresarial no que é necesario realizar operacións secuencialmente en diferentes servizos e, se nalgún paso xurde algún problema, é necesario chamar ao procedemento de retroceso para todos os pasos anteriores, onde se fornecido. Así, ao final da saga, independentemente do éxito, recibimos datos consistentes en todos os dominios.

A nosa implementación aínda está feita na súa forma básica e non está vinculada ao uso de ningún método de interacción con outros servizos. Non é difícil de usar: basta con facer un descendente da clase abstracta base SagaBase<T>, onde T é a túa clase de contexto na que podes almacenar os datos iniciais necesarios para que a saga funcione, así como algúns resultados intermedios. A instancia de contexto pasarase a todos os pasos durante a execución. Saga en si é unha clase sen estado, polo que a instancia pódese colocar en DI como Singleton para obter as dependencias necesarias.

Anuncio de exemplo:

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

Exemplo de chamada:

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

Pódense ver exemplos completos de diferentes implementacións aquí e en asemblea con probas.

ViennaNET.Orm.*

Un conxunto de bibliotecas para traballar con varias bases de datos a través de Nhibernate. Usamos o enfoque DB-First usando Liquibase, polo que só hai funcionalidades para traballar con datos nunha base de datos preparada.

ViennaNET.Orm.Seedwork и ViennaNET.Orm – conxuntos principais que conteñan interfaces básicas e as súas implementacións, respectivamente. Vexamos o seu contido con máis detalle.

interface IEntityFactoryService e a súa implantación EntityFactoryService son o principal punto de partida para traballar coa base de datos, xa que a Unidade de Traballo, aquí créanse repositorios para traballar con entidades específicas, así como executores de comandos e consultas SQL directas. Ás veces é conveniente limitar as capacidades dunha clase para traballar cunha base de datos, por exemplo, para proporcionar a capacidade de ler só datos. Para tales casos IEntityFactoryService hai un antepasado - interface IEntityRepositoryFactory, que só declara un método para crear repositorios.

Para acceder directamente á base de datos, utilízase o mecanismo do provedor. Cada DBMS que usamos nos nosos equipos ten a súa propia implementación: ViennaNET.Orm.MSSQL, ViennaNET.Orm.Oracle, ViennaNET.Orm.SQLite, ViennaNET.Orm.PostgreSql.

Ao mesmo tempo, pódense rexistrar varios provedores nunha aplicación ao mesmo tempo, o que permite, por exemplo, no marco dun servizo, sen custos de modificación da infraestrutura, realizar unha migración paso a paso desde un DBMS a outro. O mecanismo para seleccionar a conexión requirida e, polo tanto, o provedor dunha clase de entidade específica (para a que se escribe a asignación ás táboas de base de datos) impléntanse rexistrando a entidade na clase BoundedContext (contén un método para rexistrar entidades de dominio) ou o seu sucesor. ApplicationContext (contén métodos para rexistrar entidades de aplicación, solicitudes directas e comandos), onde se acepta como argumento o identificador de conexión da configuración:

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

Exemplo ApplicationContext:

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

Se non se especifica o ID de conexión, empregarase a conexión chamada "predeterminada".

O mapeamento directo de entidades a táboas de bases de datos implícase usando ferramentas estándar de NHibernate. Podes usar a descrición tanto a través de ficheiros xml como de clases. Para a escritura cómoda dos repositorios de stub nas probas unitarias, hai unha biblioteca ViennaNET.TestUtils.Orm.

Pódense atopar exemplos completos do uso de ViennaNET.Orm.* aquí.

ViennaNET.Messaging.*

Un conxunto de bibliotecas para traballar con filas.

Para traballar con colas, escolleuse o mesmo enfoque que con varios DBMS, é dicir, o enfoque unificado máximo posible en canto ao traballo coa biblioteca, independentemente do xestor de colas empregado. Biblioteca ViennaNET.Messaging é precisamente o responsable desta unificación, e ViennaNET.Messaging.MQSeriesQueue, ViennaNET.Messaging.RabbitMQQueue и ViennaNET.Messaging.KafkaQueue contén implementacións de adaptadores para IBM MQ, RabbitMQ e Kafka, respectivamente.

Cando se traballa con filas, hai dous procesos: recibir unha mensaxe e enviala.

Considere recibir. Aquí hai dúas opcións: para escoitar continuamente e para recibir unha única mensaxe. Para escoitar constantemente a cola, primeiro debes describir a clase de procesador da que herdou IMessageProcessor, que se encargará de procesar a mensaxe entrante. A continuación, debe estar "vinculado" a unha cola específica; isto faise mediante o rexistro en IQueueReactorFactory indicando o identificador da cola da configuración:

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

Exemplo de comezar a escoitar:

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

Despois, cando se inicie o servizo e se chame ao método para comezar a escoitar, todas as mensaxes da cola especificada irán ao procesador correspondente.

Para recibir unha única mensaxe nunha interface de fábrica IMessagingComponentFactory hai un método CreateMessageReceiverque creará un destinatario á espera dunha mensaxe da cola especificada:

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

Para enviar unha mensaxe cómpre usar o mesmo IMessagingComponentFactory e crea un remitente de mensaxe:

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

Hai tres opcións preparadas para serializar e deserializar unha mensaxe: só texto, XML e JSON, pero se é necesario, podes facer facilmente as túas propias implementacións de interface. IMessageSerializer и IMessageDeserializer.

Intentamos preservar as capacidades únicas de cada xestor de filas, p. ex. ViennaNET.Messaging.MQSeriesQueue permítelle enviar non só texto, senón tamén mensaxes de bytes e ViennaNET.Messaging.RabbitMQQueue admite o enrutamento e a cola ao voo. O noso envoltorio de adaptadores para RabbitMQ tamén implementa algunha aparencia de RPC: enviamos unha mensaxe e esperamos unha resposta dunha cola temporal especial, que se crea só para unha mensaxe de resposta.

Aquí un exemplo de uso de colas con matices de conexión básicos.

ViennaNET.CallContext

Usamos colas non só para a integración entre distintos sistemas, senón tamén para a comunicación entre microservizos dunha mesma aplicación, por exemplo, dentro dunha saga. Isto levou á necesidade de transmitir xunto coa mensaxe datos auxiliares como o inicio de sesión do usuario, o identificador de solicitude para o rexistro de extremo a extremo, o enderezo IP de orixe e os datos de autorización. Para implementar o reenvío destes datos, desenvolvemos unha biblioteca ViennaNET.CallContext, que permite almacenar datos dunha solicitude que entra no servizo. Neste caso, non importa como se realizou a solicitude, a través dunha cola ou vía HTTP. Despois, antes de enviar a solicitude ou mensaxe de saída, os datos tómanse do contexto e colócanse nas cabeceiras. Así, o seguinte servizo recibe os datos auxiliares e xestiona do mesmo xeito.

Grazas pola túa atención, agardamos os teus comentarios e solicitudes de extracción!

Fonte: www.habr.com

Engadir un comentario