ViennaNET: sada knižníc pre backend. Časť 2

Komunita vývojárov Raiffeisenbank .NET pokračuje v krátkom preskúmaní obsahu ViennaNET. O tom, ako a prečo sme sa k tomu dostali, môžete si prečítať prvú časť.

V tomto článku si prejdeme zatiaľ neuvažované knižnice na prácu s distribuovanými transakciami, frontami a databázami, ktoré nájdete v našom úložisku GitHub (zdroje sú tu), a Nugetové balíčky tu.

ViennaNET: sada knižníc pre backend. Časť 2

ViennaNET.Sagas

Keď projekt prejde na architektúru DDD a mikroslužieb, potom keď je obchodná logika distribuovaná medzi rôzne služby, vzniká problém súvisiaci s potrebou implementovať mechanizmus distribuovaných transakcií, pretože veľa scenárov často ovplyvňuje niekoľko domén naraz. S takýmito mechanizmami sa môžete bližšie zoznámiť napr. v knihe "Microservices Patterns", Chris Richardson.

V našich projektoch sme implementovali jednoduchý, ale užitočný mechanizmus: ságu, alebo skôr ságu založenú na orchestrácii. Jeho podstata je nasledovná: existuje určitý obchodný scenár, v ktorom je potrebné postupne vykonávať operácie v rôznych službách, a ak sa v ktoromkoľvek kroku vyskytnú nejaké problémy, je potrebné vyvolať procedúru rollback pre všetky predchádzajúce kroky, kde je poskytnuté. Na konci ságy tak bez ohľadu na úspech dostávame konzistentné dáta vo všetkých doménach.

Naša implementácia je stále robená v základnej podobe a nie je viazaná na použitie akýchkoľvek spôsobov interakcie s inými službami. Nie je to ťažké používať: jednoducho vytvorte potomka základnej abstraktnej triedy SagaBase<T>, kde T je vaša kontextová trieda, do ktorej môžete uložiť počiatočné údaje potrebné na to, aby sága fungovala, ako aj niektoré priebežné výsledky. Inštancia kontextu sa počas vykonávania prenesie do všetkých krokov. Samotná Saga je bezstavová trieda, takže inštanciu možno umiestniť do DI ako Singleton, aby sa získali potrebné závislosti.

Príklad reklamy:

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

Príklad hovoru:

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

Môžete si pozrieť úplné príklady rôznych implementácií tu a v zostave s testy.

ViennaNET.Orm.*

Sada knižníc na prácu s rôznymi databázami cez Nhibernate. Používame DB-First prístup pomocou Liquibase, takže je tu len funkcionalita pre prácu s dátami v hotovej databáze.

ViennaNET.Orm.Seedwork и ViennaNET.Orm – hlavné zostavy obsahujúce základné rozhrania a ich implementácie, resp. Pozrime sa na ich obsah podrobnejšie.

rozhranie IEntityFactoryService a jeho realizácii EntityFactoryService sú hlavným východiskom pre prácu s databázou, od Útvaru práce sa tu vytvárajú úložiská pre prácu s konkrétnymi entitami, ako aj vykonávatelia príkazov a priamych SQL dotazov. Niekedy je vhodné obmedziť možnosti triedy na prácu s databázou, napríklad poskytnúť možnosť iba čítať údaje. Pre takéto prípady IEntityFactoryService existuje predok - rozhranie IEntityRepositoryFactory, ktorý iba deklaruje metódu vytvárania úložísk.

Na priamy prístup k databáze sa používa mechanizmus poskytovateľa. Každý DBMS, ktorý používame v našich tímoch, má svoju vlastnú implementáciu: ViennaNET.Orm.MSSQL, ViennaNET.Orm.Oracle, ViennaNET.Orm.SQLite, ViennaNET.Orm.PostgreSql.

Zároveň môže byť v jednej aplikácii registrovaných viacero poskytovateľov súčasne, čo umožňuje napríklad v rámci jednej služby, bez akýchkoľvek nákladov na úpravu infraštruktúry, uskutočniť postupnú migráciu z jeden DBMS do druhého. Mechanizmus výberu požadovaného spojenia a teda poskytovateľa pre konkrétnu triedu entity (pre ktorú je napísané mapovanie do databázových tabuliek) je implementovaný prostredníctvom registrácie entity do triedy BoundedContext (obsahuje metódu registrácie doménových entít) alebo jej nástupcu. ApplicationContext (obsahuje metódy na registráciu aplikačných entít, priamych požiadaviek a príkazov), kde sa ako argument akceptuje identifikátor pripojenia z konfigurácie:

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

Príklad kontextu aplikácie:

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

Ak nie je zadané ID pripojenia, použije sa pripojenie s názvom „predvolené“.

Priame mapovanie entít do databázových tabuliek je realizované pomocou štandardných nástrojov NHibernate. Popis môžete použiť ako cez xml súbory, tak aj cez triedy. Pre pohodlné písanie stub repozitárov v Unit testoch existuje knižnica ViennaNET.TestUtils.Orm.

Úplné príklady používania ViennaNET.Orm.* nájdete tu.

ViennaNET.Messaging.*

Sada knižníc na prácu s frontami.

Pre prácu s frontami bol zvolený rovnaký prístup ako pri rôznych DBMS, a to maximálne možný jednotný prístup z hľadiska práce s knižnicou bez ohľadu na použitého správcu frontov. Knižnica ViennaNET.Messaging je práve zodpovedný za toto zjednotenie, a ViennaNET.Messaging.MQSeriesQueue, ViennaNET.Messaging.RabbitMQQueue и ViennaNET.Messaging.KafkaQueue obsahujú implementácie adaptérov pre IBM MQ, RabbitMQ a Kafka.

Pri práci s frontami existujú dva procesy: príjem správy a jej odoslanie.

Zvážte prijímanie. Tu sú 2 možnosti: pre nepretržité počúvanie a pre príjem jednej správy. Ak chcete neustále počúvať frontu, musíte najprv opísať triedu procesora, z ktorej ste zdedili IMessageProcessor, ktorá bude zodpovedná za spracovanie prichádzajúcej správy. Ďalej musí byť „prepojený“ s konkrétnym frontom; to sa deje prostredníctvom registrácie IQueueReactorFactory s uvedením identifikátora frontu z konfigurácie:

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

Príklad spustenia počúvania:

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

Potom, keď sa služba spustí a zavolá sa metóda na začatie počúvania, všetky správy zo špecifikovaného frontu pôjdu do zodpovedajúceho procesora.

Ak chcete prijať jednu správu v továrenskom rozhraní IMessagingComponentFactory existuje metóda CreateMessageReceiverktorý vytvorí príjemcu čakajúceho na správu z frontu, ktorý mu bol zadaný:

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

Ak chcete odoslať správu musíte použiť to isté IMessagingComponentFactory a vytvorte odosielateľa správy:

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

Existujú tri hotové možnosti na serializáciu a deserializáciu správy: iba text, XML a JSON, ale v prípade potreby si môžete ľahko vytvoriť vlastné implementácie rozhrania. IMessageSerializer и IMessageDeserializer.

Snažili sme sa zachovať jedinečné schopnosti každého správcu frontov, napr. ViennaNET.Messaging.MQSeriesQueue umožňuje odosielať nielen textové, ale aj bajtové správy a ViennaNET.Messaging.RabbitMQQueue podporuje smerovanie a zaraďovanie do fronty za chodu. Náš adaptér wrapper pre RabbitMQ tiež implementuje určitú podobu RPC: pošleme správu a čakáme na odpoveď zo špeciálneho dočasného frontu, ktorý je vytvorený iba pre jednu správu s odpoveďou.

Tu príklad použitia frontov so základnými nuansami pripojenia.

ViennaNET.CallContext

Fronty využívame nielen na integráciu medzi rôznymi systémami, ale aj na komunikáciu medzi mikroslužbami tej istej aplikácie, napríklad v rámci ságy. To viedlo k potrebe prenášať spolu so správou také pomocné údaje, ako je prihlasovacie meno používateľa, identifikátor požiadavky pre end-to-end protokolovanie, zdrojová IP adresa a autorizačné údaje. Na implementáciu preposielania týchto údajov sme vyvinuli knižnicu ViennaNET.CallContext, ktorý umožňuje ukladať údaje z požiadavky vstupujúcej do služby. V tomto prípade nezáleží na tom, ako bola požiadavka vykonaná, prostredníctvom frontu alebo cez Http. Potom sa pred odoslaním odchádzajúcej požiadavky alebo správy vyberú údaje z kontextu a umiestnia sa do hlavičiek. Ďalšia služba teda prijíma pomocné dáta a spravuje ich rovnakým spôsobom.

Ďakujeme za pozornosť, tešíme sa na vaše komentáre a žiadosti o stiahnutie!

Zdroj: hab.com

Pridať komentár