ViennaNET: súbor knižníc pre backend. Diel 2

Komunita .NET vývojárov Raiffeisenbanky pokračuje v krátkom rozbore obsahu ViennaNET. O tom, ako a prečo sme sa k tomu dostali, si môžete prečítať v prvej časti.

V tomto článku sa pozrieme na ďalšie nezohľadnené knižnice pre prácu s distribuovanými transakciami, frontami a databázami, ktoré nájdete v našom repozitári na GitHub (zdrojové kódy sú tu), a Nuget balíky sú tu.

ViennaNET: súbor knižníc pre backend. Diel 2

ViennaNET.Sagas

Keď v projekte prebieha prechod na DDD a mikroslužbovú architektúru, pri rozdeľovaní biznis logiky do rôznych služieb nastáva problém týkajúci sa potreby implementácie mechanizmu distribuovaných transakcií, pretože mnohé scenáre často zasahujú do viacerých domén. S takýmito mechanizmami sa môžete podrobnejšie oboznámiť napríklad v knihe „Microservices Patterns“, Chris Richardson.

V našich projektoch sme implementovali jednoduchý, ale užitočný mechanizmus: saga, presnejšie saga založená na orchestrácii. Jej podstatou je nasledovné: existuje určitý biznis scenár, v ktorom je potrebné postupne uskutočňovať operácie v rôznych službách, pričom v prípade vzniku akýchkoľvek problémov v ktoromkoľvek kroku je potrebné vyvolať procedúru rollbacku všetkých predchádzajúcich krokov, kde to je predpokladané. Takto, na konci vykonávania ság, bez ohľadu na úspešnosť, získavame konzistentné údaje vo všetkých doménach.

Naša implementácia zatiaľ prebehla v základnej forme a nie je závislá na používaní akýchkoľvek spôsobov interakcie s inými službami. Použiť ju nie je ťažké: stačí vytvoriť dediča od základnej abstraktnej triedy SagaBase, kde T je váš kontextový trieda, v ktorej môžete uchovávať vstupné údaje potrebné na vykonanie ság, ako aj niektoré medzi výsledky. Inštancia kontextu bude predávaná do všetkých krokov počas vykonávania. Sama saga je bezstavová trieda, takže inštancia môže byť umiestnená do DI ako Singleton, aby získala potrebné závislosti.

Príklad vyhlásenia:

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

Príklad volania:

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

Plné príklady rôznych realizácií si môžete pozrieť tu aj v balíku s testami.

ViennaNET.Orm.*

Súbor knižníc na prácu s rôznymi DB cez Nhibernate. Používame prístup DB-First s využitím Liquibase, preto tu existuje len funkčnosť pre prácu s údajmi v hotovej DB.

ViennaNET.Orm.Seedwork a ViennaNET.Orm sú hlavné zostavy, ktoré obsahujú základné rozhrania a ich implementácie. Podrobnejšie sa zameriame na ich obsah.

Rozhranie IEntityFactoryService a jeho implementácia EntityFactoryService sú hlavnou východiskovou bodkou pre prácu s DB, pretože tu sa vytvára Unit of Work, repozitáre pre prácu s konkrétnymi entitami, ako aj vykonávatelia príkazov a priamych SQL dotazov. Niekedy je užitočné obmedziť schopnosti triedy na prácu s DB, napríklad umožniť len čítanie údajov. Pre takéto prípady má IEntityFactoryService predka – rozhranie IEntityRepositoryFactory, v ktorom je deklarovaná len metóda na vytváranie repozitárov.

Na priame oslovenie DB sa používa mechanizmus poskytovateľov. Pre každú DBMS, ktorú používame v tímach, existuje jej vlastná implementácia: ViennaNET.Orm.MSSQL, ViennaNET.Orm.Oracle, ViennaNET.Orm.SQLite, ViennaNET.Orm.PostgreSql.

Zároveň môže byť v jednom aplikácii registrovaných niekoľko poskytovateľov súčasne, čo umožňuje, napríklad, v rámci jedného služby bez akýchkoľvek nákladov na úpravu infraštruktúry realizovať krokovú migráciu z jednej DBMS na druhú. Mechanizmus výberu potrebného pripojenia a teda poskytovateľa pre konkrétnu triedu-entity (pre ktorú je písaný mapping na tabuľky DB) je realizovaný prostredníctvom registrácie entity v triede BoundedContext (obsahuje metódu na registráciu doménových entít) alebo jeho potomka ApplicationContext (obsahuje metódy na registráciu aplikačných entít, priamych dotazov a príkazov), kde ako argument sa prijíma identifikátor pripojenia z konfigurácie:

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

Príklad ApplicationContext:

internal sealed class DbContext : ApplicationContext
{
  public DbContext()
  {
    AddEntity("mssql_connection");
    AddEntity("oracle_connection");
    AddEntity("oracle_connection");
  }
}

Ak identifikátor pripojenia nie je zadaný, použije sa pripojenie s názvom „default“.

Mapovanie entít na databázové tabuľky sa realizuje štandardnými prostriedkami NHibernate. Môže sa používať popis cez XML súbory alebo cez triedy. Pre pohodlné písanie testovacích repositárov existuje knižnica ViennaNET.TestUtils.Orm.

Plnohodnotné príklady použitia ViennaNET.Orm.* nájdete tu.

ViennaNET.Messaging.*

Súbor knižníc na prácu s frontami.

Pre prácu s frontami bol zvolený rovnaký prístup, aký sa používa s rôznymi databázovými systémami, a síce maximálne možné unifikovaný prístup z hľadiska práce s knižnicou, bez ohľadu na používaného správcu fronty. Knižnica ViennaNET.Messaging presne zabezpečuje túto unifikáciu a ViennaNET.Messaging.MQSeriesQueue, ViennaNET.Messaging.RabbitMQQueue a ViennaNET.Messaging.KafkaQueue obsahujú implementácie adaptér pre IBM MQ, RabbitMQ a Kafka.

Pri práci s frontami existujú dva procesy: prijímanie správ a odosielanie.

Poďme sa pozrieť na prijímanie. Existujú 2 možnosti: na trvalé počúvanie alebo na prijímanie jednej správy. Pre trvalé počúvanie fronty je potrebné najprv opísať triedu procesora, ktorá dedí od IMessageProcessor, ktorá bude zodpovedná za spracovanie prichádzajúcej správy. Potom je potrebné ju "pripnúť" k určitej fronte, čo sa robí prostredníctvom registrácie v IQueueReactorFactory s uvedením identifikátora fronty 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, pri štarte služby a vyvolaní metódy pre začatie počúvania, sa všetky správy z určenej fronty dostanú do príslušného procesora.

Na prijímanie jednej správy v rozhraní továrne IMessagingComponentFactory je metóda CreateMessageReceiver, ktorá vytvorí prijímač, čakajúci na správu z ním určenej fronty:

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

Na odoslanie správy je potrebné využiť rovnakú IMessagingComponentFactory a vytvoriť odosielateľ správ:

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

Na serializáciu a deserializáciu správ existujú tri pripravené varianty: obyčajný text, XML a JSON, ale ak je to potrebné, je možné si bez problémov vytvoriť vlastné implementácie rozhraní. IMessageSerializer a IMessageDeserializer.

Snažili sme sa zachovať unikátne možnosti každého správcu front, napríklad: ViennaNET.Messaging.MQSeriesQueue umožňuje posielať nielen textové, ale aj bytové správy, a ViennaNET.Messaging.RabbitMQQueue podporuje routovanie a vytváranie front „za behu“. V našej obálke adaptéra pre RabbitMQ je tiež implementované niečo ako RPC: posielame správu a čakáme na odpoveď z špeciálnej dočasnej fronty, ktorá sa vytvára len pre jednu odpovedajúcu správu.

Tu sú príklad použitia front s hlavnými nuansami pripojenia..

ViennaNET.CallContext

Používame fronty nielen na integráciu medzi rôznymi systémami, ale aj na komunikáciu medzi mikroslužbami jednej aplikácie, napríklad v rámci ságy. To viedlo k potrebe prenosu spolu so správou aj takých pomocných údajov, ako sú prihlasovacie údaje užívateľa, identifikátor požiadavky na komplexné logovanie, IP adresa zdroja a autentifikačné údaje. Na realizáciu predávania týchto údajov bola vyvinutá knižnica ViennaNET.CallContext, ktorá umožňuje uchovávať údaje z prichádzajúcej požiadavky do služby. Pri tom, akým spôsobom bola požiadavka vykonaná, cez front alebo cez Http, nehrá úlohu. Následne, pred odoslaním odchádzajúcej požiadavky alebo správy, sa extrahujú údaje z kontextu a umiestnia sa do hlavičiek. Takto nasledujúca služba dostane pomocné údaje a podobne s nimi nakladá.

Ďakujeme za pozornosť, tešíme sa na vaše komentáre a pull requesty!

Zdroj: habr.com

Kúpte si spoľahlivý hosting pre webové stránky s ochranou proti DDoS, VPS VDS servery 🔥 Kúpte si spoľahlivý hosting pre webové stránky s ochranou proti DDoS, VPS VDS servery | ProHoster