ViennaNET: eine Sammlung von Bibliotheken fĂŒr das Backend. Teil 2

Die .NET-Entwicklergemeinschaft der Raiffeisenbank setzt die kurze Analyse des Inhalts von ViennaNET fort. Wie und warum wir dazu gekommen sind, kann man im ersten Teil nachlesen.

In diesem Artikel gehen wir auf die noch nicht behandelten Bibliotheken fĂŒr die Arbeit mit verteilten Transaktionen, Warteschlangen und Datenbanken ein, die in unserem Repository auf GitHub zu finden sind (der Quellcode befindet sich hier), und Nuget-Pakete hier.

ViennaNET: eine Sammlung von Bibliotheken fĂŒr das Backend. Teil 2

ViennaNET.Sagas

Wenn in einem Projekt auf DDD und mikroservicebasierte Architektur umgestellt wird, entsteht beim Verteilen der GeschĂ€ftslogik auf verschiedene Services das Problem, einen Mechanismus fĂŒr verteilte Transaktionen zu implementieren, da viele Szenarien hĂ€ufig mehrere DomĂ€nen betreffen. Mit solchen Mechanismen kann man sich nĂ€her vertraut machen, zum Beispiel im Buch „Microservices Patterns“ von Chris Richardson.

In unseren Projekten haben wir einen einfachen, aber nĂŒtzlichen Mechanismus implementiert: die Saga, genauer gesagt eine orchestrierte Saga. Ihr Kern ist folgender: Es gibt ein bestimmtes GeschĂ€ftsszenario, in dem in verschiedenen Services sequenziell Operationen durchgefĂŒhrt werden mĂŒssen. Falls an irgendeinem Schritt Probleme auftreten, muss ein RĂŒckrollverfahren fĂŒr alle vorherigen Schritte, wo dies vorgesehen ist, aufgerufen werden. So erhalten wir am Ende der Saga, unabhĂ€ngig vom Erfolg, konsistente Daten in allen DomĂ€nen.

Unsere Implementierung ist derzeit in einer grundlegenden Form und nicht mit der Nutzung irgendwelcher Interaktionsmethoden mit anderen Services verknĂŒpft. Es ist einfach, sie anzuwenden: Man muss lediglich eine Ableitung von der abstrakten Basisklasse SagaBase erstellen, wobei T Ihre Kontextklasse ist, in der die Ausgangsdaten gespeichert werden können, die fĂŒr die Arbeit mit der Saga benötigt werden, sowie einige Zwischenergebnisse. Eine Instanz des Kontexts wird wĂ€hrend der AusfĂŒhrung an alle Schritte weitergegeben. Die Saga selbst ist eine stateless Klasse, sodass die Instanz als Singleton in die DI eingefĂŒgt werden kann, um die notwendigen AbhĂ€ngigkeiten zu erhalten.

Beispiel fĂŒr die Deklaration:

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

Beispielaufruf:

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

VollstĂ€ndige Beispiele fĂŒr verschiedene Implementierungen finden Sie hier auch in der Sammlung mit Tests.

ViennaNET.Orm.*

Ein Set von Bibliotheken zur Arbeit mit verschiedenen Datenbanken ĂŒber Nhibernate. Wir verwenden einen DB-First-Ansatz mit Liquibase, daher ist hier nur die FunktionalitĂ€t zur Arbeit mit Daten in einer vorhandenen Datenbank enthalten.

ViennaNET.Orm.Seedwork und ViennaNET.Orm – die Hauptbibliotheken, die die grundlegenden Schnittstellen und deren Implementierungen enthalten. Lassen Sie uns nĂ€her auf deren Inhalte eingehen.

BenutzeroberflĂ€che IEntityFactoryService und seine Implementierung EntityFactoryService sind der Hauptanlaufpunkt fĂŒr die Arbeit mit der Datenbank, da hier das Unit of Work, die Repositories zur Arbeit mit spezifischen EntitĂ€ten sowie die AusfĂŒhrenden von Kommandos und direkten SQL-Abfragen erstellt werden. Manchmal ist es sinnvoll, die Möglichkeiten der Klasse zur Arbeit mit der Datenbank zu beschrĂ€nken, beispielsweise nur Lesezugriff auf Daten zu gewĂ€hren. FĂŒr solche FĂ€lle gibt es IEntityFactoryService einen VorgĂ€nger – die Schnittstelle IEntityRepositoryFactory, in der nur die Methode zur Erstellung von Repositories deklariert ist.

FĂŒr den direkten Zugriff auf die Datenbank kommt ein Provider-Mechanismus zum Einsatz. Jede verwendete Datenbankmanagementsystem (DBMS) hat ihre eigene Implementierung in unseren Teams: ViennaNET.Orm.MSSQL, ViennaNET.Orm.Oracle, ViennaNET.Orm.SQLite, ViennaNET.Orm.PostgreSql.

In einer Anwendung können mehrere Provider gleichzeitig registriert sein, was beispielsweise eine schrittweise Migration von einem DBMS zu einem anderen innerhalb eines Dienstes ermöglicht, ohne dass zusĂ€tzliche Infrastrukturkosten anfallen. Der Mechanismus zur Auswahl der benötigten Verbindung und damit des Providers fĂŒr eine bestimmte EntitĂ€t (fĂŒr die das Mapping zu den DB-Tabellen geschrieben wird) wird durch die Registrierung der EntitĂ€t in der Klasse BoundedContext (beinhaltet eine Methode zur Registrierung von DomĂ€nenentitĂ€ten) oder deren Nachfolger ApplicationContext (beinhaltet Methoden zur Registrierung von AnwendungsentitĂ€ten, direkten Anfragen und Befehlen) realisiert, wobei als Argument die Verbindungs-ID aus der Konfiguration ĂŒbergeben wird:

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

Beispiel fĂŒr ApplicationContext:

interne versiegelte Klasse DbContext : ApplicationContext
{
  public DbContext()
  {
    AddEntity("mssql_connection");
    AddEntity("oracle_connection");
    AddEntity("oracle_connection");
  }
}

Wenn keine Verbindungs-ID angegeben ist, wird die Verbindung mit dem Namen „default“ verwendet.

Die Zuordnung von EntitĂ€ten zu Datenbanktabellen erfolgt mit den Standardmitteln von NHibernate. Die Beschreibung kann sowohl ĂŒber XML-Dateien als auch ĂŒber Klassen erfolgen. FĂŒr das bequeme Schreiben von Stubs in Unit-Tests steht eine Bibliothek zur VerfĂŒgung. ViennaNET.TestUtils.Orm.

VollstÀndige Beispiele zur Verwendung von ViennaNET.Orm.* finden Sie unter hier.

ViennaNET.Messaging.*

eine Sammlung von Bibliotheken zur Arbeit mit Warteschlangen.

FĂŒr die Arbeit mit Warteschlangen wurde der gleiche Ansatz gewĂ€hlt wie bei den verschiedenen DBMS, nĂ€mlich ein möglichst einheitlicher Ansatz in Bezug auf die Bibliotheksnutzung, unabhĂ€ngig vom verwendeten Warteschlangenmanager. Die Bibliothek ViennaNET.Messaging ist genau fĂŒr diese Vereinheitlichung verantwortlich, und ViennaNET.Messaging.MQSeriesQueue, ViennaNET.Messaging.RabbitMQQueue und ViennaNET.Messaging.KafkaQueue enthĂ€lt Implementierungen von Adaptern fĂŒr IBM MQ, RabbitMQ und Kafka.

Beim Arbeiten mit Warteschlangen gibt es zwei Prozesse: Empfang von Nachrichten und Versand.

Betrachten wir den Empfang. Es gibt hier zwei Optionen: fĂŒr das kontinuierliche Abhören und fĂŒr den Empfang einer einzelnen Nachricht. FĂŒr das kontinuierliche Abhören einer Warteschlange mĂŒssen Sie zuerst die Prozessor-Klasse beschreiben, die von IMessageProcessorerbt und fĂŒr die Verarbeitung der eingehenden Nachricht zustĂ€ndig sein wird. Anschließend muss diese an eine bestimmte Warteschlange „gebunden“ werden, was durch die Registrierung in IQueueReactorFactory mit der Angabe der Warteschlangen-ID aus der Konfiguration geschieht:

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

Beispiel fĂŒr den Start des Abhörens:

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

Dann werden beim Starten des Dienstes und beim Aufrufen der Methode zum Beginn des Abhörens alle Nachrichten aus der angegebenen Warteschlange an den entsprechenden Prozessor ĂŒbergeben.

FĂŒr den Empfang einer einzelnen Nachricht im Fabrik-Interface IMessagingComponentFactory gibt es die Methode CreateMessageReceiver, die einen EmpfĂ€nger erstellt, der auf Nachrichten aus der ihm zugewiesenen Warteschlange wartet:

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

Um eine Nachricht zu senden Sie mĂŒssen dasselbe nutzen IMessagingComponentFactory und einen Nachrichtensender erstellen:

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

FĂŒr die Serialisierung und Deserialisierung von Nachrichten stehen drei vorgefertigte Varianten zur VerfĂŒgung: reiner Text, XML und JSON, aber bei Bedarf können Sie problemlos eigene Implementierungen der Schnittstellen IMessageSerializer und IMessageDeserializer erstellen.

Wir haben uns bemĂŒht, die einzigartigen FĂ€higkeiten jedes Queue-Managers beizubehalten, zum Beispiel ViennaNET.Messaging.MQSeriesQueue die es ermöglicht, nicht nur Text-, sondern auch Byte-Nachrichten zu senden, wĂ€hrend ViennaNET.Messaging.RabbitMQQueue Routing und die Erstellung von Warteschlangen „on the fly“ unterstĂŒtzt. In unserem Adapter fĂŒr RabbitMQ haben wir auch eine Art RPC implementiert: wir senden eine Nachricht und erwarten eine Antwort aus einer speziellen temporĂ€ren Warteschlange, die nur fĂŒr eine Antwortnachricht erstellt wird.

Hier Beispiel fĂŒr die Verwendung von Warteschlangen mit den wichtigsten Anschlussnuancen.

ViennaNET.CallContext

Wir nutzen Warteschlangen nicht nur fĂŒr die Integration zwischen verschiedenen Systemen, sondern auch fĂŒr die Kommunikation zwischen den Mikrodiensten einer Anwendung, etwa im Rahmen einer Saga. Dies fĂŒhrte zu der Notwendigkeit, zusammen mit der Nachricht Hilfsdaten wie den Benutzernamen, die Anforderungs-ID fĂŒr das durchgĂ€ngige Logging, die IP-Adresse des Quellursprungs und Authentifizierungsdaten zu ĂŒbertragen. Zur Umsetzung dieser DatenĂŒbertragung haben wir eine Bibliothek entwickelt, ViennaNET.CallContext, die es ermöglicht, Daten aus der eingehenden Anfrage an den Dienst zu speichern. Dabei spielt es keine Rolle, wie die Anfrage gestellt wurde - ob ĂŒber eine Warteschlange oder ĂŒber HTTP. Vor dem Versenden der ausgehenden Anfrage oder Nachricht werden die Daten aus dem Kontext abgerufen und in die Header eingefĂŒgt. So erhĂ€lt der nĂ€chste Dienst die Hilfsdaten und kann entsprechend damit umgehen.

Vielen Dank fĂŒr Ihre Aufmerksamkeit, wir freuen uns auf Ihre Kommentare und Pull Requests!

Quelle: habr.com

Erwerben Sie zuverlĂ€ssiges Hosting fĂŒr Websites mit DDoS-Schutz, VPS VDS-Server đŸ”„ Kaufen Sie zuverlĂ€ssiges Hosting fĂŒr Websites mit DDoS-Schutz, VPS VDS-Server | ProHoster