ViennaNET : ensemble de bibliothèques pour le backend. Partie 2

La communauté des développeurs .NET de Raiffeisenbank continue de résumer brièvement le contenu de ViennaNET. Pour en savoir plus sur comment et pourquoi nous en sommes arrivés là, vous pouvez lire la première partie.

Dans cet article, nous allons examiner les bibliothèques non encore abordées pour travailler avec des transactions distribuées, des files d'attente et des bases de données, que vous pouvez trouver dans notre dépôt GitHub (les sources se trouvent ici), tandis que Les packages Nuget sont ici.

ViennaNET : ensemble de bibliothèques pour le backend. Partie 2

ViennaNET.Sagas

Lorsque le projet passe à l'architecture DDD et microservices, en répartissant la logique métier entre différents services, un problème apparaît lié à la nécessité de mettre en œuvre un mécanisme de transactions distribuées, car de nombreux scénarios impliquent souvent plusieurs domaines. Vous pouvez en apprendre davantage sur ces mécanismes, par exemple, dans le livre « Microservices Patterns », Chris Richardson.

Dans nos projets, nous avons mis en œuvre un mécanisme simple mais utile : une saga, plus précisément une saga basée sur l'orchestration. Son principe est le suivant : il existe un scénario métier dans lequel il est nécessaire d'effectuer successivement des opérations dans différents services, et en cas de problème à n'importe quelle étape, il est nécessaire d'appeler la procédure d'annulation de toutes les étapes précédentes, où cela est prévu. Ainsi, à la fin de l'exécution de la saga, indépendamment du succès, nous obtenons des données cohérentes dans tous les domaines.

Notre mise en œuvre est pour l'instant de base et n'est pas liée à un moyen d'interaction avec d'autres services. Son utilisation est simple : il suffit de créer un héritier de la classe abstraite de base SagaBase, où T est votre classe de contexte, dans laquelle vous pouvez stocker les données d'entrée nécessaires pour travailler avec la saga, ainsi que certains résultats intermédiaires. Une instance de contexte sera transmise à toutes les étapes lors de l'exécution. La saga elle-même est une classe stateless, donc l'instance peut être enregistrée dans DI comme Singleton pour obtenir les dépendances nécessaires.

Exemple de déclaration :

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

Exemple d'appel :

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

Vous pouvez voir des exemples complets de différentes implémentations ici et dans l'ensemble avec des tests.

ViennaNET.Orm.*

Ensemble de bibliothèques pour travailler avec différentes bases de données via Nhibernate. Nous utilisons une approche DB-First avec Liquibase, c'est pourquoi seule la fonctionnalité de travail avec des données dans une base de données prête est présente ici.

ViennaNET.Orm.Seedwork et ViennaNET.Orm sont les principaux assemblies contenant respectivement les interfaces de base et leurs implémentations. Examinons leur contenu plus en détail.

Interface IEntityFactoryService et son implémentation EntityFactoryService sont le point de départ principal pour travailler avec la base de données, car ici le Unit of Work, les repositories pour travailler avec des entités spécifiques, ainsi que les exécuteurs de commandes et les requêtes SQL directes sont créés. Parfois, il est utile de limiter les capacités de la classe de travail avec la base de données, par exemple, pour permettre uniquement la lecture des données. Pour de tels cas, il existe un ancêtre : l'interface IEntityFactoryService IEntityRepositoryFactory , dans laquelle seule la méthode de création de repositories est déclarée.Pour un accès direct à la base de données, le mécanisme des providers est utilisé. Pour chaque SGBD utilisé dans nos équipes, il existe sa propre implémentation :

ViennaNET.Orm.MSSQL, ViennaNET.Orm.Oracle, ViennaNET.Orm.SQLite, ViennaNET.Orm.PostgreSql Dans une application, plusieurs providers peuvent être enregistrés simultanément, ce qui permet, par exemple, de réaliser une migration pas à pas d'un SGBD à un autre dans le cadre d'un même service, sans aucun coût de développement de l'infrastructure. Le mécanisme de sélection de la connexion nécessaire et, par conséquent, du provider pour une classe d'entité spécifique (pour laquelle le mappage aux tables de la base de données est effectué) est réalisé par l'enregistrement de l'entité dans la classe BoundedContext (qui contient une méthode pour enregistrer les entités domaine) ou son héritier ApplicationContext (qui contient des méthodes pour enregistrer des entités applicatives, des requêtes directes et des commandes), où comme argument est pris l'identifiant de la connexion de la configuration :.

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

Exemple de ApplicationContext :

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

Si l'identifiant de connexion n'est pas spécifié, la connexion nommée « default » sera utilisée.

Si l'identifiant de connexion n'est pas spécifié, la connexion nommée « default » sera utilisée.

Le mappage direct des entités vers les tables de la base de données est réalisé par les moyens standards de NHibernate. On peut utiliser une description à travers des fichiers xml, ainsi qu'à travers des classes. Pour faciliter l'écriture des dépôts de test, il existe une bibliothèque ViennaNET.TestUtils.Orm.

Des exemples complets d'utilisation de ViennaNET.Orm.* peuvent être trouvés ici.

ViennaNET.Messaging.*

Un ensemble de bibliothèques pour travailler avec les files d'attente.

Pour travailler avec les files d'attente, une approche similaire à celle des différents SGBD a été choisie, à savoir - une approche unifiée maximale en ce qui concerne le travail avec la bibliothèque, indépendamment du gestionnaire de files d'attente utilisé. La bibliothèque ViennaNET.Messaging est précisément responsable de cette unification, et ViennaNET.Messaging.MQSeriesQueue, ViennaNET.Messaging.RabbitMQQueue et ViennaNET.Messaging.KafkaQueue contiennent des implémentations d'adaptateurs pour IBM MQ, RabbitMQ et Kafka, respectivement.

Dans le travail avec les files d'attente, il y a deux processus : la réception de messages et l'envoi.

Considérons la réception. Il y a deux options : pour l'écoute continue et pour la réception d'un message unique. Pour l'écoute continue d'une file d'attente, il faut d'abord décrire une classe de processeur, héritée de IMessageProcessor, qui sera responsable du traitement du message entrant. Ensuite, il doit être « lié » à une certaine file d'attente, ce qui se fait par l'enregistrement dans IQueueReactorFactory en indiquant l'identifiant de la file d'attente dans la configuration :

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

Exemple de démarrage de l'écoute :

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

Ensuite, au démarrage du service et lors de l'appel de la méthode pour commencer l'écoute, tous les messages de la file spécifiée seront envoyés au processeur correspondant.

Pour recevoir un message unique dans l'interface de la fabrique IMessagingComponentFactory , il existe une méthode CreateMessageReceiver, qui créera un récepteur attendant des messages de la file qui lui est spécifiée :

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

Pour envoyer un message , il faut utiliser la même IMessagingComponentFactory et créer un expéditeur de message :

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

Pour la sérialisation et la désérialisation de messages, il existe trois options prêtes à l'emploi : texte simple, XML et JSON. Cependant, il est également possible de réaliser ses propres implémentations des interfaces. IMessageSerializer et IMessageDeserializer.

Nous avons essayé de préserver les fonctionnalités uniques de chaque gestionnaire de file d'attente, par exemple, ViennaNET.Messaging.MQSeriesQueue permet d'envoyer non seulement des messages texte, mais aussi des messages binaires, tandis que ViennaNET.Messaging.RabbitMQQueue prend en charge le routage et la création dynamique de files d'attente. Dans notre wrapper d'adaptateur pour RabbitMQ, nous avons également implémenté une sorte de RPC : nous envoyons un message et attendons une réponse d'une file d'attente temporaire spéciale, qui est créée uniquement pour un message de réponse.

Voici exemple d'utilisation de files d'attente avec les principales nuances de connexion.

ViennaNET.CallContext

Nous utilisons des files d'attente non seulement pour l'intégration entre différents systèmes, mais aussi pour la communication entre les microservices d'une même application, par exemple, dans le cadre d'une saga. Cela a conduit à la nécessité de transmettre, avec le message, des données auxiliaires telles que le nom d'utilisateur, l'identifiant de la demande pour le logging complet, l'adresse IP de la source et les informations d'authentification. Pour permettre le passage de ces données, nous avons développé une bibliothèque ViennaNET.CallContext, qui permet de stocker les données de la demande entrante vers le service. À cet égard, la manière dont la demande a été faite, qu'elle soit via une file d'attente ou via HTTP, n'a pas d'importance. Ensuite, avant d'envoyer la demande ou le message sortant, les données sont extraites du contexte et placées dans les en-têtes. Ainsi, le service suivant reçoit les données auxiliaires et les gère de manière similaire.

Merci de votre attention, nous attendons vos commentaires et pull requests !

Source : habr.com

Acheter un hébergement fiable pour les sites avec protection DDoS, serveurs VPS VDS 🔥 Acheter un hébergement fiable pour les sites avec protection DDoS, serveurs VPS VDS | ProHoster