ViennaNET: un conjunt de biblioteques per al backend. Part 2

La comunitat de desenvolupadors de Raiffeisenbank .NET continua revisant breument el contingut de ViennaNET. Sobre com i per què vam arribar a això, podeu llegir la primera part.

En aquest article, repassarem biblioteques que encara no s'han considerat per treballar amb transaccions distribuïdes, cues i bases de dades, que es poden trobar al nostre repositori de GitHub (les fonts són aquí), i Paquets Nuget aquí.

ViennaNET: un conjunt de biblioteques per al backend. Part 2

ViennaNET.Sagas

Quan un projecte canvia a l'arquitectura DDD i microservei, quan la lògica empresarial es distribueix entre diferents serveis, sorgeix un problema relacionat amb la necessitat d'implementar un mecanisme de transacció distribuït, perquè molts escenaris sovint afecten diversos dominis alhora. Podeu familiaritzar-vos amb aquests mecanismes amb més detall, per exemple, al llibre "Microservices Patterns", Chris Richardson.

En els nostres projectes, hem implementat un mecanisme senzill però útil: una saga, o més aviat una saga basada en l'orquestració. La seva essència és la següent: hi ha un determinat escenari de negoci en el qual és necessari realitzar operacions seqüencials en diferents serveis, i si sorgeix algun problema en qualsevol pas, cal cridar al procediment de rollback per a tots els passos anteriors, on és proporcionat. Així, al final de la saga, independentment de l'èxit, rebem dades consistents en tots els dominis.

La nostra implementació encara es fa en la seva forma bàsica i no està lligada a l'ús de cap mètode d'interacció amb altres serveis. No és difícil d'utilitzar: només cal que feu un descendent de la classe abstracta base SagaBase<T>, on T és la vostra classe de context en la qual podeu emmagatzemar les dades inicials necessàries perquè la saga funcioni, així com alguns resultats intermedis. La instància de context es passarà a tots els passos durant l'execució. Saga en si és una classe sense estat, de manera que la instància es pot col·locar a DI com a Singleton per obtenir les dependències necessàries.

Anunci d'exemple:

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

Exemple de trucada:

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

Es poden veure exemples complets de diferents implementacions aquí i en assemblea amb proves.

ViennaNET.Orm.*

Un conjunt de biblioteques per treballar amb diverses bases de dades mitjançant Nhibernate. Utilitzem l'enfocament DB-First mitjançant Liquibase, de manera que només hi ha funcionalitat per treballar amb dades en una base de dades ja feta.

ViennaNET.Orm.Seedwork и ViennaNET.Orm – conjunts principals que contenen interfícies bàsiques i les seves implementacions, respectivament. Vegem-ne el contingut amb més detall.

interfície IEntityFactoryService i la seva implementació EntityFactoryService són el principal punt de partida per treballar amb la base de dades, ja que la Unitat de Treball, es creen dipòsits per treballar amb entitats específiques, així com executors d'ordres i consultes SQL directes. De vegades és convenient limitar les capacitats d'una classe per treballar amb una base de dades, per exemple, per proporcionar la capacitat de llegir només dades. Per a aquests casos IEntityFactoryService hi ha una interfície avantpassat IEntityRepositoryFactory, que només declara un mètode per crear repositoris.

Per accedir directament a la base de dades, s'utilitza el mecanisme del proveïdor. Cada SGBD que utilitzem als nostres equips té la seva pròpia implementació: ViennaNET.Orm.MSSQL, ViennaNET.Orm.Oracle, ViennaNET.Orm.SQLite, ViennaNET.Orm.PostgreSql.

Al mateix temps, en una aplicació es poden registrar diversos proveïdors alhora, la qual cosa permet, per exemple, en el marc d'un servei, sense cap cost per modificar la infraestructura, realitzar una migració pas a pas des de un DBMS a un altre. El mecanisme per seleccionar la connexió necessària i, per tant, el proveïdor d'una classe d'entitat específica (per a la qual s'escriu el mapeig a taules de base de dades) s'implementa mitjançant el registre de l'entitat a la classe BoundedContext (conté un mètode per registrar entitats de domini) o el seu successor. ApplicationContext (conté mètodes per registrar entitats d'aplicació, sol·licituds directes i ordres), on l'identificador de connexió de la configuració s'accepta com a argument:

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

Exemple ApplicationContext:

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

Si no s'especifica l'identificador de connexió, s'utilitzarà la connexió anomenada "per defecte".

El mapeig directe d'entitats a taules de bases de dades s'implementa mitjançant eines estàndard de NHibernate. Podeu utilitzar la descripció tant mitjançant fitxers xml com mitjançant classes. Per a l'escriptura còmoda dels repositoris de talons a les proves unitàries, hi ha una biblioteca ViennaNET.TestUtils.Orm.

Es poden trobar exemples complets de l'ús de ViennaNET.Orm.* aquí.

ViennaNET.Messaging.*

Un conjunt de biblioteques per treballar amb cues.

Per treballar amb cues, es va triar el mateix enfocament que amb diversos SGBD, és a dir, el màxim enfocament unificat possible pel que fa al treball amb la biblioteca, independentment del gestor de cues utilitzat. Biblioteca ViennaNET.Messaging és precisament responsable d'aquesta unificació, i ViennaNET.Messaging.MQSeriesQueue, ViennaNET.Messaging.RabbitMQQueue и ViennaNET.Messaging.KafkaQueue conté implementacions d'adaptadors per a IBM MQ, RabbitMQ i Kafka, respectivament.

Quan es treballa amb cues, hi ha dos processos: rebre un missatge i enviar-lo.

Penseu en rebre. Aquí hi ha 2 opcions: per escoltar contínuament i per rebre un sol missatge. Per escoltar constantment la cua, primer heu de descriure la classe de processador de la qual heu heretat IMessageProcessor, que s'encarregarà de processar el missatge entrant. A continuació, s'ha d'"enllaçar" a una cua específica, això es fa mitjançant el registre IQueueReactorFactory indicant l'identificador de la cua de la configuració:

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

Exemple de començar a escoltar:

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

Aleshores, quan s'inicia el servei i es crida al mètode per començar a escoltar, tots els missatges de la cua especificada aniran al processador corresponent.

Per rebre un sol missatge en una interfície de fàbrica IMessagingComponentFactory hi ha un mètode CreateMessageReceiverque crearà un destinatari esperant un missatge de la cua especificada:

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

Per enviar un missatge cal fer servir el mateix IMessagingComponentFactory i creeu un remitent del missatge:

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

Hi ha tres opcions preparades per a la serialització i la deserialització d'un missatge: només text, XML i JSON, però si cal, podeu crear fàcilment les vostres pròpies implementacions d'interfície. IMessageSerializer и IMessageDeserializer.

Hem intentat preservar les capacitats úniques de cada gestor de cues, p. ViennaNET.Messaging.MQSeriesQueue us permet enviar no només missatges de text, sinó també missatges de bytes, i ViennaNET.Messaging.RabbitMQQueue admet l'encaminament i la cua sobre la marxa. El nostre embolcall d'adaptadors per a RabbitMQ també implementa una mica de RPC: enviem un missatge i esperem una resposta d'una cua temporal especial, que es crea només per a un missatge de resposta.

aquí està un exemple d'ús de cues amb matisos bàsics de connexió.

ViennaNET.CallContext

Utilitzem les cues no només per a la integració entre diferents sistemes, sinó també per a la comunicació entre microserveis d'una mateixa aplicació, per exemple, dins d'una saga. Això va comportar la necessitat de transmetre juntament amb el missatge dades auxiliars com l'inici de sessió de l'usuari, l'identificador de sol·licitud per al registre d'extrem a extrem, l'adreça IP d'origen i les dades d'autorització. Per implementar el reenviament d'aquestes dades, hem desenvolupat una biblioteca ViennaNET.CallContext, que permet emmagatzemar dades d'una sol·licitud entrant al servei. En aquest cas, no importa com s'ha fet la sol·licitud, a través d'una cua o via Http. Aleshores, abans d'enviar la sol·licitud o missatge de sortida, les dades es prenen del context i es col·loquen a les capçaleres. Així, el següent servei rep les dades auxiliars i les gestiona de la mateixa manera.

Gràcies per la vostra atenció, esperem els vostres comentaris i sol·licituds d'extracció!

Font: www.habr.com

Afegeix comentari