A comunidade de desenvolvedores de Raiffeisenbank .NET segue revisando brevemente o contido de ViennaNET. Sobre como e por que chegamos a isto,
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 (
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,
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
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.*
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 CreateMessageReceiver
que 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í
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