جامعه توسعه دهندگان Raiffeisenbank .NET به بررسی مختصر محتویات ViennaNET ادامه می دهد. در مورد اینکه چگونه و چرا به این موضوع رسیدیم،
در این مقاله، کتابخانههایی را برای کار با تراکنشهای توزیعشده، صفها و پایگاههای داده، که در مخزن GitHub ما یافت میشوند، بررسی خواهیم کرد.
ViennaNET.Sagas
هنگامی که یک پروژه به معماری DDD و میکروسرویس تغییر میکند، زمانی که منطق کسبوکار در سرویسهای مختلف توزیع میشود، مشکلی در ارتباط با نیاز به پیادهسازی مکانیزم تراکنش توزیعشده ایجاد میشود، زیرا سناریوهای زیادی اغلب چندین دامنه را به طور همزمان تحت تأثیر قرار میدهند. می توانید با چنین مکانیسم هایی با جزئیات بیشتری آشنا شوید، به عنوان مثال،
ما در پروژههای خود یک مکانیسم ساده اما مفید را اجرا کردهایم: حماسه یا بهتر بگوییم حماسه مبتنی بر ارکستراسیون. ماهیت آن به شرح زیر است: یک سناریوی تجاری خاص وجود دارد که در آن لازم است به طور متوالی عملیات در سرویس های مختلف انجام شود، و اگر در هر مرحله مشکلی ایجاد شود، لازم است رویه بازگشت به عقب را برای همه مراحل قبلی فراخوانی کنید، جایی که در آن است. ارائه شده است. بنابراین، در پایان حماسه، صرف نظر از موفقیت، دادههای ثابتی را در همه حوزهها دریافت میکنیم.
پیاده سازی ما هنوز به شکل اولیه خود انجام می شود و به استفاده از هیچ روشی برای تعامل با سایر خدمات وابسته نیست. استفاده از آن دشوار نیست: فقط یک نسل از کلاس انتزاعی پایه SagaBase<T> ایجاد کنید، جایی که T کلاس زمینه شما است که در آن می توانید داده های اولیه لازم برای کار حماسه و همچنین برخی از نتایج میانی را ذخیره کنید. نمونه زمینه در طول اجرا به تمام مراحل منتقل می شود. Saga خود یک کلاس بدون حالت است، بنابراین میتوان نمونه را به عنوان Singleton در DI قرار داد تا وابستگیهای لازم را بدست آورد.
نمونه تبلیغ:
public class ExampleSaga : SagaBase<ExampleContext>
{
public ExampleSaga()
{
Step("Step 1")
.WithAction(c => ...)
.WithCompensation(c => ...);
AsyncStep("Step 2")
.WithAction(async c => ...);
}
}
تماس مثال:
var saga = new ExampleSaga();
var context = new ExampleContext();
await saga.Execute(context);
نمونه های کاملی از پیاده سازی های مختلف قابل مشاهده است
ViennaNET.Orm.*
مجموعه ای از کتابخانه ها برای کار با پایگاه داده های مختلف از طریق Nhibernate. ما از رویکرد DB-First با استفاده از Liquibase استفاده می کنیم، بنابراین فقط قابلیت کار با داده ها در یک پایگاه داده آماده وجود دارد.
ViennaNET.Orm.Seedwork и ViennaNET.Orm
- مجموعه های اصلی شامل رابط های اصلی و پیاده سازی آنها به ترتیب. بیایید با جزئیات بیشتری به محتوای آنها نگاه کنیم.
رابط IEntityFactoryService
و اجرای آن EntityFactoryService
نقطه شروع اصلی برای کار با پایگاه داده هستند، زیرا واحد کار، مخازن برای کار با نهادهای خاص، و همچنین مجریان دستورات و پرس و جوهای مستقیم SQL در اینجا ایجاد می شوند. گاهی اوقات محدود کردن قابلیت های یک کلاس برای کار با پایگاه داده راحت است، به عنوان مثال، برای ارائه توانایی فقط خواندن داده ها. برای چنین مواردی IEntityFactoryService
یک جد - رابط وجود دارد IEntityRepositoryFactory
، که فقط روشی را برای ایجاد مخازن اعلام می کند.
برای دسترسی مستقیم به پایگاه داده، مکانیسم ارائه دهنده استفاده می شود. هر DBMS که در تیمهایمان استفاده میکنیم پیادهسازی خاص خود را دارد: ViennaNET.Orm.MSSQL, ViennaNET.Orm.Oracle, ViennaNET.Orm.SQLite, ViennaNET.Orm.PostgreSql
.
همزمان، چندین ارائه دهنده را می توان در یک برنامه به طور همزمان ثبت کرد، که به عنوان مثال، در چارچوب یک سرویس، بدون هیچ گونه هزینه ای برای تغییر زیرساخت، اجازه می دهد تا مهاجرت گام به گام را از یک DBMS به دیگری مکانیسم انتخاب اتصال مورد نیاز و در نتیجه ارائه دهنده یک کلاس موجودیت خاص (که نگاشت به جداول پایگاه داده برای آن نوشته شده است) از طریق ثبت موجودیت در کلاس BoundedContext (شامل روشی برای ثبت موجودیت های دامنه) یا جانشین آن پیاده سازی می شود. ApplicationContext (شامل روش هایی برای ثبت نهادهای برنامه، درخواست های مستقیم و دستورات)، که در آن شناسه اتصال از پیکربندی به عنوان آرگومان پذیرفته می شود:
"db": [
{
"nick": "mssql_connection",
"dbServerType": "MSSQL",
"ConnectionString": "...",
"useCallContext": true
},
{
"nick": "oracle_connection",
"dbServerType": "Oracle",
"ConnectionString": "..."
}
],
مثال ApplicationContext:
internal sealed class DbContext : ApplicationContext
{
public DbContext()
{
AddEntity<SomeEntity>("mssql_connection");
AddEntity<MigratedSomeEntity>("oracle_connection");
AddEntity<AnotherEntity>("oracle_connection");
}
}
اگر شناسه اتصال مشخص نشده باشد، از اتصال با نام "پیش فرض" استفاده می شود.
نگاشت مستقیم موجودیت ها به جداول پایگاه داده با استفاده از ابزار استاندارد NHibernate اجرا می شود. می توانید از توضیحات هم از طریق فایل های xml و هم از طریق کلاس ها استفاده کنید. برای نوشتن راحت مخازن خرد در تست های واحد، یک کتابخانه وجود دارد ViennaNET.TestUtils.Orm
.
نمونه های کامل استفاده از ViennaNET.Orm.* را می توان یافت
ViennaNET.Messaging.*
مجموعه ای از کتابخانه ها برای کار با صف.
برای کار با صف ها، همان رویکردی که برای DBMS های مختلف وجود داشت، انتخاب شد، یعنی حداکثر رویکرد یکپارچه ممکن از نظر کار با کتابخانه، صرف نظر از مدیر صف مورد استفاده. کتابخانه ViennaNET.Messaging
دقیقاً مسئول این اتحاد است و ViennaNET.Messaging.MQSeriesQueue, ViennaNET.Messaging.RabbitMQQueue и ViennaNET.Messaging.KafkaQueue
به ترتیب شامل اجرای آداپتور برای IBM MQ، RabbitMQ و Kafka است.
هنگام کار با صف، دو فرآیند وجود دارد: دریافت پیام و ارسال آن.
دریافت را در نظر بگیرید. در اینجا 2 گزینه وجود دارد: برای گوش دادن مداوم و برای دریافت یک پیام. برای گوش دادن مداوم به صف، ابتدا باید کلاس پردازنده به ارث رسیده از آن را توصیف کنید IMessageProcessor
، که وظیفه پردازش پیام دریافتی را بر عهده خواهد داشت. بعد، باید به یک صف خاص "پیوند" شود؛ این کار از طریق ثبت نام در انجام می شود IQueueReactorFactory
نشان دهنده شناسه صف از پیکربندی:
"messaging": {
"ApplicationName": "MyApplication"
},
"rabbitmq": {
"queues": [
{
"id": "myQueue",
"queuename": "lalala",
...
}
]
},
مثالی از شروع گوش دادن:
_queueReactorFactory.Register<MyMessageProcessor>("myQueue");
var queueReactor = queueReactorFactory.CreateQueueReactor("myQueue");
queueReactor.StartProcessing();
سپس، هنگامی که سرویس شروع می شود و متد برای شروع گوش دادن فراخوانی می شود، تمام پیام ها از صف مشخص شده به پردازنده مربوطه می روند.
برای دریافت یک پیام واحد در رابط کارخانه IMessagingComponentFactory
یک روش وجود دارد CreateMessageReceiver
که گیرنده ای را ایجاد می کند که منتظر یک پیام از صف تعیین شده برای آن است:
using (var receiver = _messagingComponentFactory.CreateMessageReceiver<TestMessage>("myQueue"))
{
var message = receiver.Receive();
}
برای ارسال پیام شما باید از همان استفاده کنید IMessagingComponentFactory
و یک فرستنده پیام ایجاد کنید:
using (var sender = _messagingComponentFactory.CreateMessageSender<MyMessage>("myQueue"))
{
sender.SendMessage(new MyMessage { Value = ...});
}
سه گزینه آماده برای سریالسازی و سریالزدایی یک پیام وجود دارد: فقط متن، XML و JSON، اما در صورت لزوم، میتوانید به راحتی پیادهسازی رابط کاربری خود را انجام دهید. IMessageSerializer и IMessageDeserializer
.
ما سعی کرده ایم قابلیت های منحصر به فرد هر مدیر صف را حفظ کنیم، به عنوان مثال. ViennaNET.Messaging.MQSeriesQueue
به شما امکان می دهد نه تنها متن، بلکه پیام های بایتی را نیز ارسال کنید ViennaNET.Messaging.RabbitMQQueue
از مسیریابی و صف در حال پرواز پشتیبانی می کند. بستهبندی آداپتور ما برای RabbitMQ نیز برخی از شباهتهای RPC را پیادهسازی میکند: ما یک پیام ارسال میکنیم و منتظر پاسخ از یک صف موقت ویژه هستیم که فقط برای یک پیام پاسخ ایجاد میشود.
در اینجا این است
ViennaNET.CallContext
ما از صفها نه تنها برای یکپارچهسازی بین سیستمهای مختلف، بلکه برای ارتباط بین میکروسرویسهای یک برنامه، به عنوان مثال، در یک حماسه استفاده میکنیم. این امر منجر به نیاز به انتقال همراه با پیام دادههای کمکی مانند ورود کاربر، شناسه درخواست برای ورود به سیستم پایان به انتها، آدرس IP منبع و دادههای مجوز شد. برای پیاده سازی ارسال این داده ها، ما یک کتابخانه ایجاد کردیم ViennaNET.CallContext
، که به شما امکان می دهد داده های درخواستی را که وارد سرویس می شود ذخیره کنید. در این مورد، نحوه درخواست، از طریق صف یا از طریق Http، مهم نیست. سپس قبل از ارسال درخواست یا پیام خروجی، داده ها از متن گرفته شده و در هدرها قرار می گیرند. به این ترتیب سرویس بعدی داده های کمکی را دریافت کرده و به همان روش مدیریت می کند.
با تشکر از توجه شما، ما مشتاقانه منتظر نظرات و درخواست های شما هستیم!
منبع: www.habr.com