ViennaNET: sada knihoven pro backend. Část 2

Komunita vývojářů Raiffeisenbank .NET pokračuje ve stručném hodnocení obsahu ViennaNET. O tom, jak a proč jsme k tomu přišli, můžete si přečíst první díl.

V tomto článku si projdeme dosud neuvažované knihovny pro práci s distribuovanými transakcemi, frontami a databázemi, které lze nalézt v našem úložišti GitHub (zdroje jsou zde) a Nugetové balíčky zde.

ViennaNET: sada knihoven pro backend. Část 2

ViennaNET.Sagas

Když projekt přejde na architekturu DDD a mikroslužeb, pak při distribuci obchodní logiky mezi různé služby nastává problém související s potřebou implementovat mechanismus distribuovaných transakcí, protože mnoho scénářů často ovlivňuje několik domén najednou. S takovými mechanismy se můžete podrobněji seznámit např. v knize "Microservices Patterns", Chris Richardson.

V našich projektech jsme implementovali jednoduchý, ale užitečný mechanismus: ságu, nebo spíše ságu založenou na orchestraci. Jeho podstata je následující: existuje určitý obchodní scénář, ve kterém je nutné postupně provádět operace v různých službách, a pokud se v některém kroku vyskytnou nějaké problémy, je nutné u všech předchozích kroků vyvolat proceduru rollback, kde je pokud. Na konci ságy tak bez ohledu na úspěch dostáváme konzistentní data ve všech doménách.

Naše implementace je stále provedena v základní podobě a není vázána na použití jakýchkoliv metod interakce s jinými službami. Použití není obtížné: stačí vytvořit potomka základní abstraktní třídy SagaBase<T>, kde T je vaše kontextová třída, do které můžete ukládat počáteční data nezbytná k tomu, aby sága fungovala, a také některé mezivýsledky. Instance kontextu bude během provádění předána všem krokům. Samotná Saga je bezstavová třída, takže instanci lze umístit do DI jako Singleton a získat potřebné závislosti.

Příklad reklamy:

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

Příklad hovoru:

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

Můžete si prohlédnout úplné příklady různých implementací zde a v sestavě s testy.

ViennaNET.Orm.*

Sada knihoven pro práci s různými databázemi přes Nhibernate. Používáme DB-First přístup pomocí Liquibase, takže je zde pouze funkcionalita pro práci s daty v hotové databázi.

ViennaNET.Orm.Seedwork и ViennaNET.Orm – hlavní sestavy obsahující základní rozhraní a jejich implementace, resp. Podívejme se na jejich obsah podrobněji.

rozhraní IEntityFactoryService a jeho realizaci EntityFactoryService jsou hlavním výchozím bodem pro práci s databází, od Útvaru práce zde vznikají úložiště pro práci s konkrétními entitami, ale i vykonavatelé příkazů a přímých SQL dotazů. Někdy je vhodné omezit možnosti třídy pro práci s databází, například poskytnout možnost pouze číst data. Pro takové případy IEntityFactoryService existuje předek - rozhraní IEntityRepositoryFactory, který pouze deklaruje metodu pro vytváření úložišť.

Pro přímý přístup k databázi se používá mechanismus poskytovatele. Každý DBMS, který používáme v našich týmech, má svou vlastní implementaci: ViennaNET.Orm.MSSQL, ViennaNET.Orm.Oracle, ViennaNET.Orm.SQLite, ViennaNET.Orm.PostgreSql.

Zároveň lze v jedné aplikaci registrovat více poskytovatelů současně, což umožňuje např. v rámci jedné služby, bez jakýchkoli nákladů na úpravu infrastruktury, provést postupnou migraci z z jednoho DBMS do druhého. Mechanismus výběru požadovaného spojení a tedy i poskytovatele pro konkrétní třídu entity (pro kterou je napsáno mapování do databázových tabulek) je realizován registrací entity do třídy BoundedContext (obsahuje metodu pro registraci doménových entit) nebo jejího nástupce. ApplicationContext (obsahuje metody pro registraci entit aplikace, přímých požadavků a příkazů), kde je jako argument přijat 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í zadáno ID připojení, použije se připojení s názvem „výchozí“.

Přímé mapování entit do databázových tabulek je realizováno pomocí standardních nástrojů NHibernate. Popis můžete použít jak prostřednictvím souborů xml, tak prostřednictvím tříd. Pro pohodlné psaní pahýlových úložišť v Unit testech je zde knihovna ViennaNET.TestUtils.Orm.

Kompletní příklady použití ViennaNET.Orm.* naleznete zde.

ViennaNET.Messaging.*

Sada knihoven pro práci s frontami.

Pro práci s frontami byl zvolen stejný přístup jako u různých DBMS, a to maximálně jednotný přístup z hlediska práce s knihovnou bez ohledu na použitý správce front. Knihovna ViennaNET.Messaging je právě odpovědný za toto sjednocení, a ViennaNET.Messaging.MQSeriesQueue, ViennaNET.Messaging.RabbitMQQueue и ViennaNET.Messaging.KafkaQueue obsahují implementace adaptérů pro IBM MQ, RabbitMQ a Kafka.

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

Zvažte příjem. Zde jsou 2 možnosti: pro nepřetržitý poslech a pro příjem jedné zprávy. Chcete-li frontu neustále poslouchat, musíte nejprve popsat zděděnou třídu procesoru IMessageProcessor, která bude zodpovědná za zpracování příchozí zprávy. Dále musí být „propojen“ s konkrétní frontou; to se provádí registrací IQueueReactorFactory indikující identifikátor fronty z konfigurace:

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

Příklad zahájení poslechu:

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

Poté, když se služba spustí a zavolá se metoda pro zahájení naslouchání, všechny zprávy ze zadané fronty půjdou do odpovídajícího procesoru.

Chcete-li přijmout jednu zprávu v továrním rozhraní IMessagingComponentFactory existuje metoda CreateMessageReceiverkterý vytvoří příjemce čekajícího na zprávu z fronty, která je mu určena:

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

Chcete-li odeslat zprávu musíte použít totéž IMessagingComponentFactory a vytvořte odesílatele zprávy:

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

Existují tři připravené možnosti pro serializaci a deserializaci zprávy: pouze text, XML a JSON, ale v případě potřeby si můžete snadno vytvořit vlastní implementace rozhraní. IMessageSerializer и IMessageDeserializer.

Snažili jsme se zachovat jedinečné schopnosti každého správce front, např. ViennaNET.Messaging.MQSeriesQueue umožňuje odesílat nejen textové, ale i bajtové zprávy a ViennaNET.Messaging.RabbitMQQueue podporuje směrování a řazení do fronty za běhu. Náš adaptér wrapper pro RabbitMQ také implementuje určitou podobu RPC: odešleme zprávu a čekáme na odpověď ze speciální dočasné fronty, která je vytvořena pouze pro jednu zprávu s odpovědí.

zde je příklad použití front se základními nuancemi připojení.

ViennaNET.CallContext

Fronty využíváme nejen pro integraci mezi různými systémy, ale také pro komunikaci mezi mikroslužbami stejné aplikace, například v rámci ságy. To vedlo k nutnosti přenášet spolu se zprávou taková pomocná data, jako je přihlašovací jméno uživatele, identifikátor požadavku pro end-to-end protokolování, zdrojová IP adresa a autorizační data. Pro implementaci předávání těchto dat jsme vyvinuli knihovnu ViennaNET.CallContext, který umožňuje ukládat data z požadavku vstupujícího do služby. V tomto případě nezáleží na tom, jak byl požadavek učiněn, prostřednictvím fronty nebo přes Http. Poté, před odesláním odchozího požadavku nebo zprávy, jsou data převzata z kontextu a umístěna do záhlaví. Další služba tak přijímá pomocná data a spravuje je stejným způsobem.

Děkujeme za pozornost, těšíme se na vaše komentáře a žádosti o stažení!

Zdroj: www.habr.com

Přidat komentář