ViennaNET: Sada knihoven pro backend. Díl 2

Komunita .NET vývojářů Raiffeisenbank pokračuje v krátkém přehledu obsahu ViennaNET. O tom, jak a proč jsme k tomu dospěli, si můžete přečíst v první části..

V tomto článku se podíváme na další knihovny pro práci s distribuovanými transakcemi, frontami a databázemi, které lze nalézt v našem repozitáři na GitHubu (zdrojové kódy jsou zde.), a Nuget balíčky zde..

ViennaNET: Sada knihoven pro backend. Díl 2

ViennaNET.Sagas

Když v projektu dochází k přechodu na DDD a mikroservisní architekturu, při rozdělování obchodní logiky do různých služeb se objevuje problém spojený s nutností realizace mechanismu distribuovaných transakcí, protože mnoho scénářů často zasahuje do několika domén. S těmito mechanismy se můžete podrobněji seznámit například v knize „Microservices Patterns“, Chris Richardson..

V našich projektech jsme implementovali jednoduchý, ale užitečný mechanismus: saga, a přesněji řečeno, saga na základě orchestrace. Jejím jádrem je následující: existuje určitý obchodní scénář, v němž je třeba postupně provést operace v různých službách, přičemž v případě jakýchkoli problémů na jakémkoli kroku je nutné vyvolat proceduru zpětného kroku pro všechny předchozí kroky, kde je to předpokládáno. Tímto způsobem, na konci provádění ság, bez ohledu na úspěšnost, získáváme konzistentní data ve všech doménách.

Naše realizace zatím není vázána na používání jakýchkoli metod interakce s jinými službami. Použít ji není složité: stačí vytvořit dědice od základní abstraktní třídy SagaBase, kde T je vaše třída kontextu, v níž lze uchovávat zdrojová data potřebná pro práci ság, a také některé mezivýsledky. Instance kontextu bude předávána do všech kroků během provádění. Sama saga je stateless třídou, takže instance může být vložena do DI jako Singleton pro získání potřebných závislostí.

Příklad deklarace:

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

Příklad volání:

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

Plné příklady různých realizací lze vidět zde i ve svazku s testy..

ViennaNET.Orm.*

Sada knihoven pro práci s různými databázemi pomocí Nhibernate. Používáme přístup DB-First s použitím Liquibase, a proto zde najdete pouze funkce pro práci s daty v hotové databázi.

ViennaNET.Orm.Seedwork a ViennaNET.Orm – hlavní balíčky obsahující základní rozhraní a jejich implementace. Podívejme se podrobněji na jejich obsah.

Rozhraní IEntityFactoryService a jeho implementace EntityFactoryService jsou hlavním výchozím bodem pro práci s databází, protože zde se vytváří Unit of Work, repozitáře pro práci s konkrétními entitami a také vykonavatelé příkazů a přímých SQL dotazů. Někdy je užitečné omezit schopnosti třídy pro práci s databází, například umožnit pouze čtení dat. Pro takové případy existuje předek – rozhraní IEntityFactoryService IEntityRepositoryFactory , ve kterém je deklarována pouze metoda pro vytváření repozitářů.Pro přímý přístup k databázi se používá mechanismus poskytovatelů. Pro každou používanou databázovou soustavu v našich týmech existuje vlastní implementace:

ViennaNET.Orm.MSSQL, ViennaNET.Orm.Oracle, ViennaNET.Orm.SQLite, ViennaNET.Orm.PostgreSql Přitom v jedné aplikaci může být registrováno několik poskytovatelů současně, což umožňuje, například, provést postupnou migraci z jedné databáze na jinou bez jakýchkoli nákladů na úpravu infrastruktury. Mechanismus výběru potřebného připojení a tedy i poskytovatele pro konkrétní třídu-entity (pro kterou se píše mapping na tabulky databáze) je realizován prostřednictvím registrace entity ve třídě BoundedContext (obsahuje metodu pro registraci doménových entit) nebo jejího potomka ApplicationContext (obsahuje metody pro registraci aplikačních entit, přímých dotazů a příkazů), kde jako argument je předán identifikátor připojení z konfigurace:.

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

Příklad ApplicationContext:

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

Pokud není identifikátor připojení uveden, použije se připojení s názvem „default“.

Pokud identifikátor připojení není uveden, bude použito připojení s názvem „default“.

Přímo mapování entit na tabulky DB se realizuje standardními prostředky NHibernate. Můžete použít popis jak prostřednictvím xml souborů, tak prostřednictvím tříd. Pro pohodlné psaní repozitářů-základen v unit testech existuje knihovna ViennaNET.TestUtils.Orm.

Kompletní příklady použití ViennaNET.Orm.* lze nalézt zde.

ViennaNET.Messaging.*

Sada knihoven pro práci s frontami.

Pro práci s frontami byla zvolena stejná metodika, jaká platí pro různé SGBD, a to maximálně možný unifikovaný přístup z pohledu práce s knihovnou, nezávisle na používaném správci front. Knihovna ViennaNET.Messaging se právě stará o tuto unifikaci, a ViennaNET.Messaging.MQSeriesQueue, ViennaNET.Messaging.RabbitMQQueue a ViennaNET.Messaging.KafkaQueue obsahují implementace adaptérů pro IBM MQ, RabbitMQ a Kafka, resp.

V práci s frontami existují dva procesy: příjem zprávy a odeslání.

Zaměříme se na příjem. Zde existují 2 varianty: pro trvalé naslouchání a pro příjem jednotlivé zprávy. Pro trvalé naslouchání fronty je nejprve nutné popsat třídu procesoru, odvozenou od IMessageProcessor, která bude odpovědná za zpracování příchozí zprávy. Dále je nutné ji „připojit“ k určité frontě, což se provádí registrací v IQueueReactorFactory s uvedením identifikátoru fronty z konfigurace:

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

Příklad spuštění naslouchání:

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

Pak, při spuštění služby a vyvolání metody pro zahájení naslouchání, všechny zprávy z uvedené fronty budou směřovat do odpovídajícího procesoru.

Pro příjem jednotlivé zprávy v rozhraní-fabrice IMessagingComponentFactory je metoda CreateMessageReceiver, která vytvoří příjemce čekající na zprávy z jím určené fronty:

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

Pro odeslání zprávy je nutné využít opět tentýž IMessagingComponentFactory a vytvořit odesílatele zprávy:

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

Pro serializaci a deserializaci zprávy existují tři hotové varianty: prostý text, XML a JSON, ale v případě potřeby lze snadno vytvořit vlastní implementace rozhraní IMessageSerializer a IMessageDeserializer.

Snažili jsme se zachovat jedinečné možnosti každého správce front, například ViennaNET.Messaging.MQSeriesQueue umožňuje odesílat nejen textové, ale i bajtové zprávy, a ViennaNET.Messaging.RabbitMQQueue podporuje routování a vytváření front „na míru“. V našem obalu adaptéru pro RabbitMQ je také implementována jistá forma RPC: odesíláme zprávu a očekáváme odpověď ze speciální dočasné fronty, která je vytvořena pouze pro jednu odpovědní zprávu.

Tady příklad použití front s hlavními nuancemi připojení.

ViennaNET.CallContext

Používáme fronty nejen pro integraci mezi různými systémy, ale i pro komunikaci mezi mikroslužbami jedné aplikace, například v rámci ság. To vedlo k potřebě předávat spolu se zprávou také pomocná data, jako je přihlašovací jméno uživatele, identifikátor požadavku pro podrobné protokolování, IP adresa zdroje a autorizační údaje. Pro realizaci přenosu těchto dat jsme vyvinuli knihovnu ViennaNET.CallContext, která umožňuje ukládat data z příchozího požadavku do služby. Přitom nezáleží na způsobu, jakým byl požadavek proveden, zda přes frontu či přes HTTP. Poté, před odesláním odchozího požadavku nebo zprávy, se data vyjmou z kontextu a vloží do hlaviček. Takto následující služba obdrží pomocná data a podle nich jedná.

Děkujeme za pozornost, těšíme se na vaše komentáře a pull requesty!

Zdroj: habr.com

Koupit spolehlivý hosting pro webové stránky s ochranou proti DDoS, VPS VDS servery 🔥 Koupit spolehlivý hosting pro webové stránky s ochranou proti DDoS, VPS VDS servery | ProHoster