ViennaNET: et sæt biblioteker til backend. Del 2

Raiffeisenbank .NET-udviklerfællesskabet fortsætter med kort at gennemgå indholdet af ViennaNET. Om hvordan og hvorfor vi kom til dette, du kan læse første del.

В этой статье пройдемся по еще не рассмотренным библиотекам для работы с распределенными транзакциями, очередями и БД, которые можно найти в нашем репозитории на GitHub (kilder er her), og Nuget-pakker her.

ViennaNET: et sæt biblioteker til backend. Del 2

ViennaNET.Sagaer

Når et projekt skifter til DDD og mikroservicearkitektur, så når forretningslogik er fordelt på tværs af forskellige tjenester, opstår der et problem relateret til behovet for at implementere en distribueret transaktionsmekanisme, fordi mange scenarier ofte påvirker flere domæner på én gang. Du kan stifte nærmere bekendtskab med sådanne mekanismer, f.eks. i bogen "Microservices Patterns", Chris Richardson.

I vores projekter har vi implementeret en enkel, men brugbar mekanisme: en saga, eller rettere en orkestreringsbaseret saga. Dens essens er som følger: der er et bestemt forretningsscenarie, hvor det er nødvendigt at udføre operationer sekventielt i forskellige tjenester, og hvis der opstår problemer på et hvilket som helst trin, er det nødvendigt at kalde tilbagerulningsproceduren for alle tidligere trin, hvor det er stillet til rådighed. Således modtager vi i slutningen af ​​sagaen, uanset succes, konsistente data på alle domæner.

Vores implementering er stadig lavet i sin grundlæggende form og er ikke bundet til brugen af ​​nogen metoder til interaktion med andre tjenester. Det er ikke svært at bruge: Lav bare en efterkommer af den grundlæggende abstrakte klasse SagaBase<T>, hvor T er din kontekstklasse, hvor du kan gemme de indledende data, der er nødvendige for, at sagaen kan fungere, samt nogle mellemliggende resultater. Kontekstinstansen vil blive videregivet til alle trin under udførelsen. Saga i sig selv er en statsløs klasse, så instansen kan placeres i DI som en Singleton for at få de nødvendige afhængigheder.

Eksempelannonce:

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

Eksempel opkald:

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

Fuldstændige eksempler på forskellige implementeringer kan ses her og i samling med tests.

ViennaNET.Orm.*

Et sæt biblioteker til at arbejde med forskellige databaser via Nhibernate. Vi bruger DB-First tilgangen ved hjælp af Liquibase, så der er kun funktionalitet til at arbejde med data i en færdig database.

ViennaNET.Orm.Seedwork и ViennaNET.Orm – hovedsamlinger, der indeholder henholdsvis grundlæggende grænseflader og deres implementeringer. Lad os se på deres indhold mere detaljeret.

grænseflade IEntityFactoryService og dens gennemførelse EntityFactoryService er det primære udgangspunkt for at arbejde med databasen, da Unit of Work, repositories til at arbejde med specifikke entiteter, samt eksekvere af kommandoer og direkte SQL-forespørgsler oprettes her. Nogle gange er det praktisk at begrænse en klasses muligheder for at arbejde med en database, for eksempel for at give mulighed for kun at læse data. For sådanne tilfælde IEntityFactoryService der er en forfader - grænseflade IEntityRepositoryFactory, som kun erklærer en metode til oprettelse af repositories.

For at få direkte adgang til databasen bruges udbydermekanismen. Hvert DBMS, vi bruger i vores teams, har sin egen implementering: ViennaNET.Orm.MSSQL, ViennaNET.Orm.Oracle, ViennaNET.Orm.SQLite, ViennaNET.Orm.PostgreSql.

Samtidig kan flere udbydere registreres i én applikation på samme tid, hvilket giver mulighed for eksempelvis inden for rammerne af én tjeneste uden omkostninger til ændring af infrastrukturen at foretage en trin-for-trin migrering fra en DBMS til en anden. Mekanismen til at vælge den påkrævede forbindelse og derfor udbyderen for en specifik enhedsklasse (hvortil der er skrevet tilknytning til databasetabeller) implementeres ved at registrere enheden i klassen BoundedContext (indeholder en metode til registrering af domæneenheder) eller dens efterfølger ApplicationContext (indeholder metoder til registrering af applikationsenheder, direkte anmodninger og kommandoer), hvor forbindelsesidentifikatoren fra konfigurationen accepteres som et argument:

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

Eksempel på ansøgningskontekst:

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

Hvis forbindelses-id'et ikke er angivet, vil forbindelsen med navnet "default" blive brugt.

Direkte kortlægning af enheder til databasetabeller er implementeret ved hjælp af standard NHibernate-værktøjer. Du kan bruge beskrivelsen både gennem xml-filer og gennem klasser. For praktisk skrivning af stub-repositories i enhedstests er der et bibliotek ViennaNET.TestUtils.Orm.

Fuldstændige eksempler på brug af ViennaNET.Orm.* kan findes her.

ViennaNET.Messaging.*

Et sæt biblioteker til at arbejde med køer.

Til at arbejde med køer blev der valgt samme tilgang som ved forskellige DBMS'er, nemlig den maksimalt mulige unified tilgang i forhold til at arbejde med biblioteket, uanset hvilken kømanager der anvendes. Bibliotek ViennaNET.Messaging er netop ansvarlig for denne forening, og ViennaNET.Messaging.MQSeriesQueue, ViennaNET.Messaging.RabbitMQQueue и ViennaNET.Messaging.KafkaQueue indeholder adapterimplementeringer til henholdsvis IBM MQ, RabbitMQ og Kafka.

Når du arbejder med køer, er der to processer: at modtage en besked og sende den.

Overvej at modtage. Der er 2 muligheder her: for kontinuerlig lytning og for at modtage en enkelt besked. For konstant at lytte til køen skal du først beskrive den processorklasse, der er arvet fra IMessageProcessor, som vil være ansvarlig for at behandle den indgående besked. Dernæst skal det "linkes" til en specifik kø, det sker ved tilmelding i IQueueReactorFactory angiver kø-id'et fra konfigurationen:

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

Eksempel på at begynde at lytte:

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

Derefter, når tjenesten starter, og metoden kaldes for at begynde at lytte, vil alle beskeder fra den angivne kø gå til den tilsvarende processor.

At modtage en enkelt besked i en fabriksgrænseflade IMessagingComponentFactory der er en metode CreateMessageReceiverhvilket vil oprette en modtager, der venter på en besked fra den specificerede kø:

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

For at sende en besked du skal bruge det samme IMessagingComponentFactory og opret en beskedafsender:

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

Der er tre færdige muligheder for at serialisere og deserialisere en besked: kun tekst, XML og JSON, men hvis det er nødvendigt, kan du nemt lave dine egne grænsefladeimplementeringer IMessageSerializer и IMessageDeserializer.

Vi har forsøgt at bevare den enkelte kømanagers unikke muligheder, f.eks. ViennaNET.Messaging.MQSeriesQueue giver dig mulighed for at sende ikke kun tekst, men også byte-beskeder og ViennaNET.Messaging.RabbitMQQueue understøtter routing og on-the-fly kø. Vores adapterindpakning til RabbitMQ implementerer også en vis form for RPC: vi sender en besked og venter på et svar fra en speciel midlertidig kø, som kun oprettes for én svarmeddelelse.

her et eksempel på brug af køer med grundlæggende forbindelsesnuancer.

ViennaNET.CallContext

Vi bruger køer ikke kun til integration mellem forskellige systemer, men også til kommunikation mellem mikrotjenester af samme applikation, for eksempel inden for en saga. Dette førte til behovet for sammen med meddelelsen at transmittere sådanne hjælpedata som brugerlogin, anmodnings-id for ende-til-ende-logning, kilde-IP-adresse og autorisationsdata. For at implementere videresendelsen af ​​disse data har vi udviklet et bibliotek ViennaNET.CallContext, som giver dig mulighed for at gemme data fra en anmodning, der går ind i tjenesten. I dette tilfælde er den måde, anmodningen blev lavet, gennem en kø eller via Http, ligegyldig. Derefter, før den udgående anmodning eller besked sendes, tages data fra konteksten og placeres i overskrifterne. Således modtager den næste tjeneste hjælpedataene og administrerer dem på samme måde.

Tak for din opmærksomhed, vi ser frem til dine kommentarer og pull-anmodninger!

Kilde: www.habr.com

Tilføj en kommentar