ViennaNET: et sett med biblioteker for backend. Del 2

Raiffeisenbank .NET-utviklerfellesskapet fortsetter å kort gjennomgå innholdet i ViennaNET. Om hvordan og hvorfor vi kom til dette, du kan lese første del.

I denne artikkelen vil vi gå gjennom biblioteker som ennå ikke er vurdert for å jobbe med distribuerte transaksjoner, køer og databaser, som kan finnes i vårt GitHub-lager (kilder er her), a Nuget-pakker her.

ViennaNET: et sett med biblioteker for backend. Del 2

ViennaNET.sagaer

Når et prosjekt bytter til DDD og mikrotjenestearkitektur, så når forretningslogikk er distribuert på tvers av ulike tjenester, oppstår det et problem knyttet til behovet for å implementere en distribuert transaksjonsmekanisme, fordi mange scenarier ofte påvirker flere domener samtidig. Du kan bli kjent med slike mekanismer mer detaljert, for eksempel i boken "Microservices Patterns", Chris Richardson.

I våre prosjekter har vi implementert en enkel, men nyttig mekanisme: en saga, eller rettere sagt en orkestrasjonsbasert saga. Essensen er som følger: det er et visst forretningsscenario der det er nødvendig å utføre operasjoner sekvensielt i forskjellige tjenester, og hvis det oppstår problemer på et hvilket som helst trinn, er det nødvendig å kalle tilbakerullingsprosedyren for alle tidligere trinn, der det er sørget for. På slutten av sagaen, uansett suksess, mottar vi derfor konsistente data på alle domener.

Implementeringen vår er fortsatt laget i sin grunnleggende form og er ikke bundet til bruken av noen metoder for interaksjon med andre tjenester. Det er ikke vanskelig å bruke: bare lag en etterkommer av den grunnleggende abstrakte klassen SagaBase<T>, der T er kontekstklassen din der du kan lagre de innledende dataene som er nødvendige for at sagaen skal fungere, samt noen mellomresultater. Kontekstforekomsten vil bli sendt gjennom til alle trinn under utførelse. Saga i seg selv er en statsløs klasse, så instansen kan plasseres i DI som en Singleton for å få de nødvendige avhengighetene.

Eksempelannonse:

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

Eksempelanrop:

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

Fullstendige eksempler på forskjellige implementeringer kan sees her og i sammenstilling med tester.

ViennaNET.Orm.*

Et sett med biblioteker for å jobbe med ulike databaser via Nhibernate. Vi bruker DB-First tilnærmingen ved hjelp av Liquibase, så det er kun funksjonalitet for å jobbe med data i en ferdig database.

ViennaNET.Orm.Seedwork и ViennaNET.Orm – hovedsammenstillinger som inneholder henholdsvis grunnleggende grensesnitt og deres implementeringer. La oss se på innholdet deres mer detaljert.

grensesnitt IEntityFactoryService og dens gjennomføring EntityFactoryService er hovedstartpunktet for å jobbe med databasen, siden Arbeidsenheten, depoter for arbeid med spesifikke enheter, samt utførere av kommandoer og direkte SQL-spørringer er opprettet her. Noen ganger er det praktisk å begrense mulighetene til en klasse for å jobbe med en database, for eksempel for å gi muligheten til kun å lese data. For slike tilfeller IEntityFactoryService det er et forfedre-grensesnitt IEntityRepositoryFactory, som kun erklærer en metode for å lage repositories.

For å få direkte tilgang til databasen brukes leverandørmekanismen. Hvert DBMS vi bruker i teamene våre har sin egen implementering: ViennaNET.Orm.MSSQL, ViennaNET.Orm.Oracle, ViennaNET.Orm.SQLite, ViennaNET.Orm.PostgreSql.

Samtidig kan flere tilbydere registreres i én applikasjon samtidig, noe som gjør det mulig for eksempel innenfor rammen av én tjeneste, uten kostnader for å modifisere infrastrukturen, å gjennomføre en trinnvis migrering fra en DBMS til en annen. Mekanismen for å velge den nødvendige tilkoblingen og derfor leverandøren for en spesifikk enhetsklasse (som tilordning til databasetabeller er skrevet for) implementeres ved å registrere enheten i BoundedContext-klassen (inneholder en metode for registrering av domeneenheter) eller dens etterfølger ApplicationContext (inneholder metoder for registrering av applikasjonsenheter, direkte forespørsler og kommandoer), der tilkoblingsidentifikatoren fra konfigurasjonen aksepteres som et argument:

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

Eksempel på applikasjonskontekst:

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

Hvis tilkoblings-IDen ikke er spesifisert, vil tilkoblingen som heter "standard" bli brukt.

Direkte kartlegging av enheter til databasetabeller er implementert ved bruk av standard NHibernate-verktøy. Du kan bruke beskrivelsen både gjennom xml-filer og gjennom klasser. For praktisk skriving av stubbelagre i enhetstester finnes det et bibliotek ViennaNET.TestUtils.Orm.

Fullstendige eksempler på bruk av ViennaNET.Orm.* finnes her.

ViennaNET.Messaging.*

Et sett med biblioteker for arbeid med køer.

For å jobbe med køer ble samme tilnærming valgt som med ulike DBMS-er, nemlig maksimalt mulig enhetlig tilnærming i forhold til å jobbe med biblioteket, uavhengig av hvilken kømanager som brukes. Bibliotek ViennaNET.Messaging er nettopp ansvarlig for denne foreningen, og ViennaNET.Messaging.MQSeriesQueue, ViennaNET.Messaging.RabbitMQQueue и ViennaNET.Messaging.KafkaQueue inneholder adapterimplementeringer for henholdsvis IBM MQ, RabbitMQ og Kafka.

Når du jobber med køer er det to prosesser: motta en melding og sende den.

Vurder å motta. Det er 2 alternativer her: for kontinuerlig lytting og for å motta en enkelt melding. For å hele tiden lytte til køen, må du først beskrive prosessorklassen som er arvet fra IMessageProcessor, som vil være ansvarlig for å behandle den innkommende meldingen. Deretter må det være "lenket" til en spesifikk kø, dette gjøres gjennom registrering i IQueueReactorFactory som indikerer køidentifikatoren fra konfigurasjonen:

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

Eksempel på å begynne å lytte:

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

Så, når tjenesten starter og metoden kalles for å begynne å lytte, vil alle meldinger fra den angitte køen gå til den tilsvarende prosessoren.

For å motta en enkelt melding i et fabrikkgrensesnitt IMessagingComponentFactory det finnes en metode CreateMessageReceiversom vil opprette en mottaker som venter på en melding fra køen som er spesifisert til den:

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

For å sende en melding du må bruke det samme IMessagingComponentFactory og opprett en meldingsavsender:

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

Det er tre ferdige alternativer for å serialisere og deserialisere en melding: bare tekst, XML og JSON, men om nødvendig kan du enkelt lage dine egne grensesnittimplementeringer IMessageSerializer и IMessageDeserializer.

Vi har forsøkt å bevare de unike egenskapene til hver køansvarlig, f.eks. ViennaNET.Messaging.MQSeriesQueue lar deg sende ikke bare tekst, men også byte-meldinger, og ViennaNET.Messaging.RabbitMQQueue støtter ruting og kø underveis. Adapterinnpakningen vår for RabbitMQ implementerer også et visst utseende av RPC: vi sender en melding og venter på svar fra en spesiell midlertidig kø, som bare opprettes for én svarmelding.

Her et eksempel på bruk av køer med grunnleggende koblingsnyanser.

ViennaNET.CallContext

Vi bruker køer ikke bare for integrasjon mellom ulike systemer, men også for kommunikasjon mellom mikrotjenester av samme applikasjon, for eksempel innenfor en saga. Dette førte til behovet for å sende sammen med meldingen slike hjelpedata som brukerinnlogging, forespørselsidentifikator for ende-til-ende-logging, kilde-IP-adresse og autorisasjonsdata. For å implementere videresending av disse dataene utviklet vi et bibliotek ViennaNET.CallContext, som lar deg lagre data fra en forespørsel som går inn i tjenesten. I dette tilfellet spiller ingen rolle hvordan forespørselen ble gjort, gjennom en kø eller via Http. Deretter, før du sender den utgående forespørselen eller meldingen, blir data hentet fra konteksten og plassert i overskriftene. Dermed mottar den neste tjenesten hjelpedataene og administrerer dem på samme måte.

Takk for oppmerksomheten, vi ser frem til dine kommentarer og henvendelser!

Kilde: www.habr.com

Legg til en kommentar