معاملات تمیز در Golang Hexagon

ساخت وبلاگ

چگونه می توان مدیریت معاملات را در یک میکروسرویس شش ضلعی پیاده سازی کرد؟چگونه می توان جداسازی لایه برنامه و آداپتور پایگاه داده را حفظ کرد؟در این مقاله تجربه خود را در حل این مشکل به اشتراک می گذارم.

معماری تمیز (معماری پیاز) در توسعه میکروسرویس مدرن بسیار محبوب است. این رویکرد به وضوح به بسیاری از سؤالات معماری پاسخ می دهد ، و همچنین برای خدمات با پایگاه کوچک کد مناسب است. یکی دیگر از ویژگی های خوب معماری تمیز این است که به خوبی با توسعه دامنه محور ترکیب می شود - آنها کاملاً یکدیگر را تکمیل می کنند.

یکی از کاربردهای معماری تمیز ، معماری شش ضلعی است ، رویکردی که صریحاً لایه ها ، آداپتورها و غیره را متمایز می کند. این رویکرد عشق را در بین توسعه دهندگان GO به وجود آورده است زیرا نیازی به انتزاع پیچیده یا الگوهای پیچیده ندارد و با اصطلاح پیچیده زبان متناقض نیست - به اصطلاح راه.

اما مشکلی وجود دارد که من اغلب در بسیاری از تیم ها با همبستگی شش ضلعی مشاهده می کنم ، که من خودم با آن روبرو شده ام و با موفقیت حل کرده ام - اجرای معاملات پایگاه داده در DDD و خود شش ضلعی. من به شما خواهم گفت که در این پست چه کاری انجام داده ام.

مشکل بالای انتزاع

یک معماری شش ضلعی به معنای وارونگی وابستگی ها به شرح زیر است: در مرکز همه چیز مدل داده است ، منطق دامنه در اطراف آن ساخته شده است (و به آن بستگی دارد) ، لایه ای از منطق کاربرد در بالای آن قرار می گیرد ، و سپس در آنجا قرار می گیرد. آداپتورهای پنهان در پشت رابط به نام پورت ها هستند. این می تواند متفاوت باشد ، اما ایده اصلی که وابستگی ها از مرکز به حاشیه واگرای دارند ، باقی می ماند.

به عنوان نمونه ، بیایید برای لحظه ای تصور کنیم که ما یک میکروسرویس را انجام می دهیم که اتومبیل های استفاده شده را می فروشد.

بیایید تصور کنیم که یکی از آداپتورها یک ماژول برای تعامل پایگاه داده است. اما هیچ پایگاه داده تصادفی نیست ، اما موردی که از معاملات اسید پشتیبانی می کند. تعامل با پایگاه داده بسیار آسان است - ما مدل های دامنه را در مخازن می بندیم ، هر مخزن را در پشت یک رابط (پورت) مخفی می کنیم و آن را در داخل آداپتور پیاده می کنیم. چنین بندری ممکن است چیزی شبیه به این باشد:

از طرف منطق دامنه ، این آداپتور به عنوان DI از طریق رابط منتقل می شود:

به عنوان مثال ، منطق جستجوی خودرو براساس سال تولید به شرح زیر خواهد بود:

این کار بسیار خوب با عملیات ساده اتمی مانند ایجاد ، حذف یا خواندن است. اما اغلب شما باید منطق پیچیده را در یک معامله پایگاه داده واحد اجرا کنید. من مثالها را توصیف نمی کنم ، شما آنها را به اندازه کافی خوب می شناسید.

مسئله در اینجا این است که از نظر معماری یک معامله بخشی از آداپتور پایگاه داده است - با دستورات خاصی باز و بسته می شود (شروع ، تعهد یا بازگشت در SQL) و اتصال به نهاد تولید شده - شیء معامله. خود شیء معامله معمولاً در ابرهای برنامه جهانی برنامه جهانی در حال معلق نیست ، بلکه صریحاً به جلسه اتصال پایگاه داده از طریق اتصال TCP محدود می شود. بنابراین ما نمی توانیم (و نمی خواهیم) به طور انتزاعی "شروع معامله" و "پایان معاملات" را در کد تجارت اعلام کنیم. هنگام باز کردن معامله ، ما برخی از نهادها را داریم - یک شیء معامله - که برای انجام عملیات پایگاه داده دقیقاً در این معامله باید به آداپتور منتقل کنیم.

اینجاست که مشکل مرغ و تخم مرغ بوجود می آید. از یک طرف ، آداپتور مستلزم آن است که برای هر پرس و جو در یک معامله ، اطلاعات مربوط به آن معامله باید منتقل شود - معمولاً کتابخانه استفاده شده معامله را به عنوان نوعی شیء که از طریق آن می توان پرس و جو می کند ، پیاده سازی می کند. از طرف دیگر ، دامنه یا لایه برنامه نباید از اجرای آداپتور در الگوی شش ضلعی اطلاع داشته باشد. شما می توانید معامله را در برخی از رابط ها ببندید ، اما این رابط کاربری بسیار عظیم خواهد بود (با روشهایی مانند انتخاب ، درج ، حذف و دیگران - کتابخانه SQL مورد علاقه خود را باز کنید و ببینید که چند روش در آنجا وجود دارد). و به دامنه آن معنی نخواهد داشت - این روش ها در آداپتور استفاده می شوند ، جایی که دسترسی مستقیم به شیء معامله بدون هیچ گونه انتزاعی وجود دارد.

ما می توانیم به روش دیگری برویم و معامله را به عنوان رابط منتقل کنیم<>و سپس آن را از طریق بازتاب به نوع مورد نظر در آداپتور ریخته است ، اما من این رویکرد را غیرقانونی و نامناسب برای کد تولیدی می دانم. علاوه بر این ، من نمی خواهم با عبور از یک شیء معامله اضافی ، امضای روش را آلوده کنم ، زیرا به طور مستقیم به خود روش مربوط نمی شود. شیء معامله A مشخصات کل فرآیند کار با پایگاه داده را به عنوان بخشی از عملیات نشان می دهد. خوب چه کار کنیم؟

راه حل پیاده سازی خاص

اکنون چند کلمه در مورد زمینه راه حل ما. در جستجوی یک اجرای زیبا ، من چندین بار با راه حل هایی مانند UnitOfwork روبرو شده ام که یک معامله را به عنوان برخی از نهادهای تجاری (که هسته منطق تجارت از آن می داند) نشان داده ام. در واقع ، یک معامله می تواند به عنوان نوعی از نهاد تجاری ارائه شود - از این گذشته ، منطق تجارت می تواند به اجرای اتمی و غیر رقابتی یک معامله نیاز داشته باشد. اما مشکل ایده های ظریف ، اجرای بی فایده است - کارخانه های انتزاعی ، انعکاس و رسیدگی به کثیف روشهای خود آداپتور.

غالباً این عوارض با تمایل به کار با چندین بانک اطلاعاتی دیکته می شوند ، یا اینکه فقط با تغییر کد آداپتور (و بدون تغییر منطق تجارت) از یک پایگاه داده به دیگری تغییر می کنند.

با درک اینکه این خیلی "انتزاعی" است ، و به طور کلی با این راه روبرو نمی شود ، من چند محدودیت از پروژه ما را که قرار بود این کار را ساده کند ، به دست آوردم:

  1. این سرویس فقط با یک پایگاه داده کار می کند.
  2. ما از یک کتابخانه خاص برای کار با پایگاه داده استفاده می کنیم.

این سرویس فقط با یک پایگاه داده کار می کند

و این posgresql است. خوب ، واقعاً ، چند بار بین پایگاه داده ها تغییر می کنید؟بسیاری از افراد سعی می کنند برای کار با یک پایگاه داده عمومی SQL ، کد عمومی بنویسند ، اما آیا این معنی دارد؟این تجربه نشان می دهد که جابجایی از یک پایگاه داده SQL به دیگری ، شما را مجبور به کار مجدد کل پروژه می کند و تغییر از SQL به NOSQL یا برعکس از این مسئله خارج است.

ما از یک کتابخانه خاص برای کار با پایگاه داده استفاده می کنیم

و این Go-PG است. من شخصاً آن را به عنوان یک سازنده پرس و جو (به جای ORM) دوست دارم و عملکرد خوبی دارد. این یک ویژگی است که من در مورد بعدی به شما خواهم گفت ، بدون آن من تلاش می کردم آنچه را که در ذهن داشتم اجرا کنم. اما این ویژگی در کتابخانه های دیگر نیز موجود است ، بنابراین عجله نکنید که کد خود را بازنویسی کنید.

در نهایت با چه چیزی روبرو شدیم

به همین دلیل من در GO-PG به عنوان پایه و اساس با ذهن روشن معاملات را انجام دادم. من می خواستم امضاهای روش مخزن را تمیز نگه دارم (فقط پارامترهای فراخوانی زمینه و روش) ، بلکه از منظر GO ، راه حل را نیز ایجاد می کند.

Golang ابزاری عالی دارد که به شما امکان می دهد داده های ابزار را که مربوط به یک روش خاص نیست ، منتقل کنید ، اما مربوط به زمینه یک عملیات - Context. Context است. غالباً از راه دور ، چوب چوب ، شناسه های idempotent و موارد دیگر به آنجا می روند. از نظر من اطلاعات معامله کاملاً متناسب با تعریف "داده های ابزار" است - این نوعی اصلاح کننده فرآیند است که مستقیماً بر منطق تأثیر نمی گذارد بلکه اثر غیرمستقیم دارد. از کلمات به اعمال!

مرحله اول اضافه کردن روش هایی برای قرار دادن شیء معامله در متن و بازیابی معامله از متن است. روشها غیر قابل استفاده هستند ، یعنی فقط می توان آنها را در داخل آداپتور نامید. توجه - معامله ای از بسته G O-PG در اینجا بدون هیچ گونه بسته بندی یا انتزاع استفاده می شود. ما می توانیم آن را در داخل آداپتور تحمل کنیم!

در مرحله بعد ، ما باید خود آداپتور (مخزن) نحوه کار با یک معامله را آموزش دهیم. و این جایی است که ما به ویژگی هایی که در GO-PG داریم ، نیاز داریم ، اما در برخی از کتابخانه های دیگر مانند SQLX نیست. این یک رابط واحد برای روشهای پرس و جو است که توسط کتابخانه اجرا می شود ، چه در یک معامله و چه بدون آن. آنها انتخاب می شوند ، درج می شوند ، حذف می شوند و دیگران - آنها باید همان امضای معامله و عدم تعادل را داشته باشند تا بتوانند از رابط خارج شوند. اگر نه ، شما باید یک بسته بندی بنویسید. در مورد GO-PG هر دو شیء اتصال پایگاه داده و شیء معامله دارای روش ModelContext (C Context. Context ، مدل. رابط کاربری<>) *پرس و جو مورد استفاده ما.

ما کمی بسته بندی کردیم که آیا معامله در متن وجود دارد. اگر وجود داشته باشد - پرس و جو را از شیء معامله باز می گرداند ، و اگر اینطور نیست - پرس و جو را از شی اتصال پایگاه داده برمی گرداند.

پایگاه داده در اینجا ساختار واقعی است که Carrepository را پیاده سازی می کند ، که روش های آن حاوی نمایش داده های SQL به PostgreSQL و همچنین اتصال (یا یک استخر اتصال) به پایگاه داده است. اگر تعداد زیادی از آنها را داشته باشید می تواند مخازن بیشتری را پیاده سازی کند.

در نتیجه ، روشی که اتومبیل ها را از پایگاه داده می خواند به این شکل خواهد بود:

علاوه بر این ، شما می توانید از یک روش با یا بدون معامله استفاده کنید - نه امضا و نه خود روش تغییر می کند. و این بخشی از خدمات با منطق تجارت است که تصمیم می گیرد از معامله استفاده کند یا خیر. بیایید ببینیم که چگونه این کار انجام شده است.

معاملات در تجارت و تجارت در معاملات

تنها چیزی که باقی مانده است ، اجرای یک روش برای ایجاد معامله در آداپتور است که زمینه "بارگذاری شده" با معامله را برمی گرداند. این روش را به رابط اضافه کنید و آن را در منطق تجارت بنامید و زمینه را با معامله بدست آورید. و سپس آن زمینه را به همه تماس های مخزن منتقل کنید ، و در پایان متعهد یا بازپرداخت کنید.

منطقی به نظر می رسد ، اما نوعی کثیف است. شاید GO ابزار ظریف تری داشته باشد؟

و یکی وجود دارد! بسته شدنبیایید روشی را ایجاد کنیم که به ما امکان دهد بدون نیاز به ترک صندلی خود ، کل معامله را اجرا کنیم:

این روش زمینه و تابعی را برای اجرای در یک معامله انجام می دهد. بر اساس زمینه ، زمینه جدیدی با معامله ایجاد می شود و به عملکرد منتقل می شود. این امر همچنین اجازه می دهد تا هنگام لغو زمینه والدین ، اجرای عملکرد را قطع کنید - به عنوان مثال ، هنگامی که یک خاموش شدن برازنده رخ می دهد.

در مرحله بعد ، اگر عملکرد بدون خطا اجرا شود ، تعهد اجرا می شود ، در غیر این صورت یک بازگشت مجدد اجرا می شود و خطا از روش بازگردانده می شود.

ما این روش را در یک درگاه جداگانه قرار خواهیم داد - جداسازی باید رعایت شود!

بیایید آن را از طریق DI به دامنه اضافه کنیم:

این به ما اجازه می دهد تا به هیچ وجه نگران معاملات درون منطق تجارت نباشیم و عملیات معاملات را به موارد زیر ساده کنیم:

در اینجا ، مدیریت معاملات در لایه برنامه انجام می شود.

توجه داشته باشید که روشهای UpsertCarhistoryEvent و Upsertcar در همان معامله اجرا می شوند ، حتی اگر لایه دامنه آن را نداشته باشد.

نتایج آرامش بخش

در پایان ، ما دریافت کردیم:

  • مکانیسم ساده برای اجرای معاملات از نظر لایه منطق کسب و کار.
  • جداسازی سطح ، انتزاع نشت نمی کند.
  • بدون تأمل ، تمام کارهای معامله تایپ شده و تحمل گسل است.
  • روش های مخزن تمیز ، نیازی به اضافه کردن معامله به یک امضا نیست.
  • روشهای پرس و جو معامله آگنوستیک هستند - اگر معامله ای وجود داشته باشد ، اگر نه - به طور مستقیم در پایگاه داده ، در داخل آن اجرا می شوند.
  • تعهد و بازگشت به طور خودکار با توجه به نتیجه اجرای عملکرد اجرا می شوند. بدون تعویق
  • در صورت وحشت ، بازگشت به داخل tx. close () اجرا می شود.

این رویکرد برای هر پایگاه داده ای که از معاملات اسید پشتیبانی می کند ، کاربرد دارد ، تا زمانی که یک رابط کاربری مشترک برای هر دو پرس و جوهای خارج از کشور وجود داشته باشد. در صورت تمایل ، اگر کتابخانه مورد علاقه شما آن را ندارد ، می توانید بسته بندی خود را اضافه کنید.

این روش هنگام کار با چندین پایگاه داده در یک سرویس قابل اجرا نیست و باید دو معاملات را به یک پیوند دهید. در این حالت ، من به شما حسادت نمی کنم.

من ممکن است از اصول DDD عزیمت کرده ام یا از مفاهیم معماری شش ضلعی غفلت کرده ام ، اما نتیجه آن ساده ، زیبا و خواندنی به وجود آمده است.

ویدیو های آموزشی فارکس...
ما را در سایت ویدیو های آموزشی فارکس دنبال می کنید

برچسب : نویسنده : محبوب امانی بازدید : 57 تاريخ : پنجشنبه 24 فروردين 1402 ساعت: 17:22