ViennaNET: isang hanay ng mga aklatan para sa backend. Bahagi 2

Ang komunidad ng developer ng Raiffeisenbank .NET ay nagpapatuloy sa maikling pagsusuri sa mga nilalaman ng ViennaNET. Tungkol sa kung paano at bakit tayo napunta dito, maaari mong basahin ang unang bahagi.

Sa artikulong ito, dadaan tayo sa mga hindi pa isinasaalang-alang na mga aklatan para sa pagtatrabaho sa mga ipinamamahaging transaksyon, pila at database, na makikita sa aming GitHub repository (narito ang mga mapagkukunan), at Nuget packages dito.

ViennaNET: isang hanay ng mga aklatan para sa backend. Bahagi 2

ViennaNET.Sagas

Kapag lumipat ang isang proyekto sa DDD at arkitektura ng microservice, pagkatapos ay kapag naipamahagi ang lohika ng negosyo sa iba't ibang serbisyo, lumitaw ang isang problema na nauugnay sa pangangailangang ipatupad ang isang mekanismo ng distributed na transaksyon, dahil madalas na nakakaapekto ang maraming sitwasyon sa ilang domain nang sabay-sabay. Maaari kang maging pamilyar sa mga naturang mekanismo nang mas detalyado, halimbawa, sa aklat na "Microservices Patterns", Chris Richardson.

Sa aming mga proyekto, nagpatupad kami ng isang simple ngunit kapaki-pakinabang na mekanismo: isang alamat, o sa halip ay isang orkestrasyon na nakabatay sa saga. Ang kakanyahan nito ay ang mga sumusunod: mayroong isang tiyak na senaryo ng negosyo kung saan kinakailangan na sunud-sunod na magsagawa ng mga operasyon sa iba't ibang mga serbisyo, at kung may anumang mga problema na lumitaw sa anumang hakbang, kinakailangang tawagan ang pamamaraan ng rollback para sa lahat ng mga nakaraang hakbang, kung saan ito ay ibinigay. Kaya, sa dulo ng alamat, anuman ang tagumpay, nakakatanggap kami ng pare-parehong data sa lahat ng domain.

Ang aming pagpapatupad ay ginawa pa rin sa pangunahing anyo nito at hindi nakatali sa paggamit ng anumang paraan ng pakikipag-ugnayan sa ibang mga serbisyo. Hindi mahirap gamitin: gumawa lang ng descendant ng base abstract class na SagaBase<T>, kung saan ang T ang iyong context class kung saan maaari mong iimbak ang paunang data na kailangan para gumana ang saga, pati na rin ang ilang intermediate na resulta. Ipapasa ang instance sa konteksto sa lahat ng hakbang sa panahon ng pagpapatupad. Ang Saga mismo ay isang stateless class, kaya ang instance ay maaaring ilagay sa DI bilang Singleton para makuha ang mga kinakailangang dependencies.

halimbawa ng ad:

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

Halimbawa ng tawag:

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

Maaaring tingnan ang buong halimbawa ng iba't ibang pagpapatupad dito at sa pagpupulong na may mga pagsubok.

ViennaNET.Orm.*

Isang hanay ng mga aklatan para sa pagtatrabaho sa iba't ibang database sa pamamagitan ng Nhibernate. Ginagamit namin ang DB-First na diskarte gamit ang Liquibase, kaya mayroon lamang functionality para sa pagtatrabaho sa data sa isang handa na database.

ViennaNET.Orm.Seedwork и ViennaNET.Orm – mga pangunahing asembliya na naglalaman ng mga pangunahing interface at kanilang mga pagpapatupad, ayon sa pagkakabanggit. Tingnan natin ang kanilang mga nilalaman nang mas detalyado.

interface IEntityFactoryService at pagpapatupad nito EntityFactoryService ay ang pangunahing panimulang punto para sa pagtatrabaho sa database, dahil ang Unit of Work, mga repository para sa pagtatrabaho sa mga partikular na entity, pati na rin ang mga tagapagpatupad ng mga utos at direktang mga query sa SQL ay nilikha dito. Minsan ito ay maginhawa upang limitahan ang mga kakayahan ng isang klase para sa pagtatrabaho sa isang database, halimbawa, upang magbigay ng kakayahang magbasa lamang ng data. Para sa mga ganitong kaso IEntityFactoryService mayroong isang ninuno - interface IEntityRepositoryFactory, na nagdedeklara lamang ng isang paraan para sa paglikha ng mga repositoryo.

Upang direktang ma-access ang database, ginagamit ang mekanismo ng provider. Ang bawat DBMS na ginagamit namin sa aming mga koponan ay may sariling pagpapatupad: ViennaNET.Orm.MSSQL, ViennaNET.Orm.Oracle, ViennaNET.Orm.SQLite, ViennaNET.Orm.PostgreSql.

Kasabay nito, maraming mga provider ang maaaring mairehistro sa isang aplikasyon nang sabay-sabay, na nagbibigay-daan, halimbawa, sa loob ng balangkas ng isang serbisyo, nang walang anumang gastos para sa pagbabago ng imprastraktura, upang magsagawa ng sunud-sunod na paglipat mula sa isang DBMS sa isa pa. Ang mekanismo para sa pagpili ng kinakailangang koneksyon at, samakatuwid, ang provider para sa isang partikular na klase ng entity (kung saan isinulat ang pagmamapa sa mga talahanayan ng database) sa pamamagitan ng pagrehistro ng entity sa BoundedContext na klase (naglalaman ng paraan para sa pagrerehistro ng mga entity ng domain) o ang kahalili nito ApplicationContext (naglalaman ng mga pamamaraan para sa pagrerehistro ng mga entity ng application , mga direktang kahilingan at utos), kung saan ang pagkakakilanlan ng koneksyon mula sa configuration ay tinatanggap bilang argumento:

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

Halimbawa ng ApplicationContext:

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

Kung ang koneksyon ID ay hindi tinukoy, pagkatapos ay ang koneksyon na pinangalanang "default" ay gagamitin.

Ang direktang pagmamapa ng mga entity sa mga talahanayan ng database ay ipinatupad gamit ang mga karaniwang tool ng NHibernate. Maaari mong gamitin ang paglalarawan sa pamamagitan ng mga xml file at sa pamamagitan ng mga klase. Para sa maginhawang pagsulat ng mga stub repository sa mga Unit test, mayroong library ViennaNET.TestUtils.Orm.

Ang buong halimbawa ng paggamit ng ViennaNET.Orm.* ay matatagpuan dito.

ViennaNET.Messaging.*

Isang hanay ng mga aklatan para sa pagtatrabaho sa mga pila.

Upang magtrabaho sa mga queues, ang parehong diskarte ay pinili tulad ng sa iba't ibang mga DBMS, ibig sabihin, ang maximum na posibleng pinag-isang diskarte sa mga tuntunin ng pagtatrabaho sa library, anuman ang queue manager na ginamit. Aklatan ViennaNET.Messaging ay tiyak na responsable para sa pagkakaisa na ito, at ViennaNET.Messaging.MQSeriesQueue, ViennaNET.Messaging.RabbitMQQueue и ViennaNET.Messaging.KafkaQueue naglalaman ng mga pagpapatupad ng adaptor para sa IBM MQ, RabbitMQ at Kafka, ayon sa pagkakabanggit.

Kapag nagtatrabaho sa mga pila, mayroong dalawang proseso: pagtanggap ng mensahe at pagpapadala nito.

Isaalang-alang ang pagtanggap. Mayroong 2 pagpipilian dito: para sa patuloy na pakikinig at para sa pagtanggap ng isang mensahe. Upang patuloy na makinig sa queue, kailangan mo munang ilarawan ang klase ng processor na minana mula sa IMessageProcessor, na magiging responsable para sa pagproseso ng papasok na mensahe. Susunod, dapat itong "naka-link" sa isang partikular na pila; ito ay ginagawa sa pamamagitan ng pagpaparehistro sa IQueueReactorFactory na nagpapahiwatig ng pagkakakilanlan ng pila mula sa pagsasaayos:

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

Halimbawa ng pagsisimula ng pakikinig:

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

Pagkatapos, kapag nagsimula ang serbisyo at tinawag ang pamamaraan upang simulan ang pakikinig, lahat ng mensahe mula sa tinukoy na pila ay mapupunta sa kaukulang processor.

Upang makatanggap ng isang mensahe sa isang factory interface IMessagingComponentFactory may paraan CreateMessageReceiverna lilikha ng isang tatanggap na naghihintay para sa isang mensahe mula sa pila na tinukoy dito:

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

Para magpadala ng mensahe kailangan mong gamitin ang parehong IMessagingComponentFactory at lumikha ng nagpadala ng mensahe:

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

Mayroong tatlong handa na mga opsyon para sa pagse-serialize at deserializing ng isang mensahe: text lang, XML at JSON, ngunit kung kinakailangan, madali kang makakagawa ng sarili mong mga pagpapatupad ng interface IMessageSerializer и IMessageDeserializer.

Sinubukan naming panatilihin ang mga natatanging kakayahan ng bawat manager ng pila, hal. ViennaNET.Messaging.MQSeriesQueue nagbibigay-daan sa iyo na magpadala hindi lamang ng teksto, kundi pati na rin ng mga byte na mensahe, at ViennaNET.Messaging.RabbitMQQueue sumusuporta sa pagruruta at on-the-fly queuing. Ang aming adapter wrapper para sa RabbitMQ ay nagpapatupad din ng ilang pagkakahawig ng RPC: nagpapadala kami ng mensahe at naghihintay ng tugon mula sa isang espesyal na pansamantalang pila, na nilikha para lamang sa isang mensahe ng tugon.

Dito isang halimbawa ng paggamit ng mga pila na may mga pangunahing nuances ng koneksyon.

ViennaNET.CallContext

Gumagamit kami ng mga pila hindi lamang para sa pagsasama-sama sa pagitan ng iba't ibang mga system, ngunit para din sa komunikasyon sa pagitan ng mga microservice ng parehong application, halimbawa, sa loob ng isang alamat. Ito ay humantong sa pangangailangang magpadala kasama ng mensahe ng naturang auxiliary data gaya ng pag-login ng user, paghiling ng identifier para sa end-to-end na pag-log, source IP address at data ng pahintulot. Para ipatupad ang pagpapasa ng data na ito, bumuo kami ng library ViennaNET.CallContext, na nagpapahintulot sa iyo na mag-imbak ng data mula sa isang kahilingang pumapasok sa serbisyo. Sa kasong ito, hindi mahalaga ang paraan ng paghiling, sa pamamagitan ng isang queue o sa pamamagitan ng Http. Pagkatapos, bago ipadala ang papalabas na kahilingan o mensahe, kinukuha ang data mula sa konteksto at inilalagay sa mga header. Kaya, ang susunod na serbisyo ay tumatanggap ng auxiliary data at pinamamahalaan ito sa parehong paraan.

Salamat sa iyong pansin, inaasahan namin ang iyong mga komento at pull request!

Pinagmulan: www.habr.com

Magdagdag ng komento