خانه / microservices / مایکروسرویس ها و چالشی بنام DRY

مایکروسرویس ها و چالشی بنام DRY

مایکروسرویس ها و چالشی بنام DRY

یکی از بخش های مهم بهنگام مهاجرت از یک معماری سنتی به یک معماری مدرن(Modern Architecture) تغییر ذهنیت های قبلی شکل گرفته و تطابق با اصول و نگرش هایی است که لازمه ی یک مهاجرت موفق به یک سبک معماری خاص می باشد. قبلا در مورد بخش هایی از این تفاوت دیدگاه ها که مایکروسرویس های تحمیل می کند در اینجا صحبت شد. در این مقاله مختصرا درباره ی Don’t Repeat Yourself یا به اختصار DRY در معماری مایکروسرویس ها صحبت خواهم کرد. DRY ایده ای است که تلاش می کند با اجتناب از تکرارهای ناخواسته کد در بخش ها و ماژول های مختلف از اتلاف وقت و همچنین مشکلات ناخواسته ای که این تکرار شدنهای کد در بخش های مختلف باعث آنها می شوند؛ جلوگیری کند.

In software engineering, don’t repeat yourself (DRY) is a principle of software development aimed at reducing repetition of software patterns, replacing it with abstractions, or repetition of the same data, using data normalization to avoid redundancy.

Wikipedia

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

هرچند تعریف واحد و استانداردی از سبک معماری مایکروسرویس ها که بر سر آن اجماع نظر وجود داشته باشد نداریم؛ اما می توانیم با نگاهی به ویژگی های مشترک سیستم هایی مختلفی که بر پایه اصول معماری مایکروسرویس ها طراحی و توسعه داده شده اند به یک سری جملات مشترک برسیم.

مایکروسرویس ها چه هستند؟

در اینجا به دو تعریف اشاره می کنیم:

.Microservices are small, autonomous services that work together

(Sam Newman (Building Microservices–

In short, the microservice architectural style is an approach to developing a single application as a suite of small services, each running in its own process and communicating with lightweight mechanisms, often an HTTP resource API

 James Lewis & Martin Fowler–

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

 

  • اندازه سرویس ها را می توان از جنبه های مختلفی بررسی کرد. تعداد خط کد یکی از ساده ترین متریک هاست که واضع است که نمی توان خیلی به آن اتکا کرد. چرا که به عنوان مثال توسعه یک سرویس با زبان Scala نسبت به توسعه همان سرویس و با همان سطح عملیاتی بر روی استک های دیگری مثل دات نت می تواند با میزان LOC کمتری انجام شود. می توان گفت این اندازه می تواند تابعی از اندازه تیم؛ دامین سرویس ها؛ میزان تلاش مورد نیاز برای آن سرویس و … باشد. توجه به اصل (Single Responsibility Principle(SRP (اصل مسئولیت واحد) در سطح Business Capability (استعداد و توانایی های بالقوه ی محصول) و البته تاثیر اندازه هر سرویس بر سطح خوشه بندی (granularity) کلی سیستم می تواند در انتخاب اندازه مناسب کمک کننده باشند. به بحث اندازه سرویس ها در مقاله ای جداگانه خواهم پرداخت و در اینجا به این جمله ی James بسنده خواهم کرد.

.Small enough to fit in your head

 James Lewis, the 33rd Conference, 2012, Krakow, Poland–

 

  • اما در مورد ویژگی خود مختار بودن سرویس ها نکته ی بسیار مهم توجه به این خود مختاری در کل چرخه ی حیات سرویس هاست از مراحل Dev(توسعه) تا مرحله Ops(عملیات) از زمان توسعه سرویس ها توسط توسعه دهنده ها تا نحوه ی Integrate(یکپارچه) شدن و سپس Deploy(انتشار) سرویس ها.

خب تا به اینجا اشاره شد که سرویس ها باید دو خصیصه مهم را داشته باشند:

  • اندازه کوچک داشته باشند.
  • خود مختار باشند.

دراین قسمت سعی می کنم از دو اصطلاح مهم در مهندسی نرم افزار یعنی Coupling (وابستگی) و Cohesion (چسبندگی) استفاده کنم.

Cohesion معیاری است برای سنجیدن این مورد که در هر ماژول تا چه اندازه موارد بهم مرتبط جمع شده اند. می توان به این مورد به این صورت نیز نگاه کرد که به ازای هر کدام از عملیات منطقا بهم مرتبط در سطح سیستم این عملیات تا چه اندازه بین ماژول ها تقسیم شده اند.

In computer programming, cohesion refers to the degree to which the elements inside a module belong together. In one sense, it is a measure of the strength of relationship between the methods and data of a class and some unifying purpose or concept served by that class. In another sense, it is a measure of the strength of relationship between the class’s methods and data themselves.

 Wikipedia–

Cohesion انواع مختلفی دارد. از جمله Logical Cohesion که به این معنی است که تقسیم بندی موارد بر اساس یک منطق خاص است حال آنکه ممکن است این موارد قرار گرفته در یک گروه عملیاتی کاملا متفاوت داشته باشند؛ مثلا در طراحی یک کارکتر برای یک بازی تمام قسمت های مرتبط با بازیکن آن بازی در یک ماژول بنام Player باشد. یا Functional Cohesion  که قوی ترین سطح Cohesion است که به این معنی است که مواردی که در یک ماژول قرار گرفته اند به دلیل این موضوع است که آنها همگی بر روی یک عملیات واحد بصورت Unit Of Work عمل می کنند؛ مثلا تمامی عملیات ریاضی در ماژولی بنام Math. Cohesion بصورت درجه برای هر ماژول یا در سطحی پایین به ازای هر کلاس یا متد بصورت بالا/پایین بیان می شود. درصد بالای آن برای هر ماژول/کلاس/متد به معنی بهم مرتبط بودن تمام(اکثر) عملیاتی است که در هر ماژول/کلاس/متد می باشد و به عکس. Cohesion نتیجه ی مستقیم اعمال SRP می باشد. نقض  SRP به معنی افزودن مسئولیت نامرتبط بیشتر به یک ماژول/کلاس/متد و مسئولیت های نامرتبط بیشتر به معنی کنار هم قرار دادن موارد نامرتبط در یک ماژول/کلاس/متد و در نتیجه Cohesion پایین تر. در مورد ارتباط این اصل با ویژگی اندازه مایکروسرویس ها باید گفت  Logical Cohesionمی تواند منجر به بزرگ شدن اندازه یک مایکروسرویس شود و ما مجبور به شکستن آن به چندین مایکروسرویس شویم. و البته این تاثیر می تواند به گونه ای دیگری نیز هم بر اندازه مایکروسرویس ها و هم بر سطح granularity کلی سیستم تاثیر گذار باشد. به عنوان مثال فرض کنید در یک سیستم پخش و خرید آلبوم موسیقی؛ تصمیم گرفتید که اطلاعات آلبوم و هنرمند را در یک مایکروسرویس قرار دهید. اما در ادامه متوجه خواهید شد که با افزودن ویژگی های بیشتر به این مایکروسرویس در طی هر اسپرینت اندازه ی این مایکروسرویس به قدری بزرگ می شود که زمان توسعه؛ تست و دیپلوی آن را زیاد می کند. و علاوه بر آن به دلیل اینکه میزان جستجو بر اساس مشخصات هنرمند علاوه بر جستجو بر اساس مشخصات خود آلبوم توسط بازدید کنندگان زیاد می باشد؛ با حجم زیادی از درخواست های ارسال شده به این مایکروسرویس مواجه می شویم و تصمیم بگیریم که آنرا به دو Downstream سرویس تقسیم کنیم.

 


Coupling به عنوان معیاری از وابستگی های ورودی و خروجی میان ماژول های تشکیل دهنده ی نرم افزار می باشد. این وابستگی برای هر ماژول X می تواند بصورت وابستگی سایر ماژول ها به ماژول X که به fan-in معروف است و وابستگی ماژول X به سایر ماژول ها که اصطلاحا به آن fan-out گفته می شود وجود داشته باشد.

In software engineering, coupling is the degree of interdependence between software modules; a measure of how closely connected two routines or modules are the strength of the relationships between modules.

Wikipedia–

Coupling نیز دارای دو مقدار زیاد و کم می باشد. مقدار کم یعنی وابستگی بین ماژولی کمتر؛ یا به عبارتی استقلال و خودمختاری بیشتر ماژول ها.

اما برگردیم به تعریف DRY: از تکرارهای بخش های کد در سیستم جلوگیری کنید.

لازمه اینکار ایجاد ماژول/کلاس/متدهایی است که تمام کانسومرها به آن دسترسی داشته و از آن استفاده کنند؛ بجای اینکه آن بخش کد را در تمام این قسمت ها کپی کرد. در اهمیت و لازمه ی رعایت این اصل شکی وجود ندارد. تکراهای کد یکی از code smell هایی است که ضمن اینکه مغایر با اصل Open for extension Closed for modification Principle (OCP) است؛ می تواند در طی زمان یک BBoM را به بار آورد.

اما ببینیم استفاده از Shared Library ها که نتیجه ی اعمال DRY هستند؛ چه مشکلی را برای مایکروسرویس ها بوجود می آورند؟

وجود یک Shared Library بین چندین مایکروسرویس باعث Coupling این مایکروسرویس ها به آن می شود که این می تواند مشکلات عمده و بسیار زیادی را ببار آورد.

  • Forced changes: فرض کنید شما به دلایلی مجبور به تغییر لایبرری شوید و این تغییر باعث تغییر contract آن شود در نتیجه مجبورید تمام مایکروسرویس ها را متناسبا تغییر دهید. این مورد بخصوص وقتی که تیم های مختلفی درگیر توسعه مایکروسرویس ها و لایبرری باشند بیشتر نمایان می شود.
  • Versioning: بدلیل اینکه لایبرری مورد نظر باید توسط تمام سرویس ها بصورت مشترک استفاده شود؛ اعمال versioning امکان پذیر نمی باشد.
  • Delay on develop and deploy: فرض کنید مایکروسرویس x در حال استفاده از لایبرری l می باشد که توسط چندین سرویس دیگر نیز مورد استفاده قرار می گیرد. حال مایکروسرویس x نیاز دارد که تغییری در پیاده سازی l انجام شود اما این تغییر می تواند به دلایل مختلفی به تاخیر بیافتد. مثلا تیمی که درگیر توسعه l است در اسپرینت جاری وقت کافی برای اینکار ندارد. یا تغییرات مورد نظر در l انجام شده ولی از آنجایی که تمامی مایکروسرویس هایی که از l استفاده می کنند باید مطابق آن تغییر کنند؛ تا زمان بروز شدن تمام مایکروسرویس ها این عمل به تاخیر می افتد. در هر صورت مایکروسرویس x بدلایل خارجی و بدور از کنترل دچار تاخیر در توسعه و/یا انتشار می شود.
  • Coupling to specific technology stack: یکی دیگر از تاثیرات می تواند وابستگی به تکنولوژی خاص باشد. مثلا لایبرری که با دات نت نوشته شده منجر به این می شود که تمام کانسومرهای آن مجبور به استفاده از تکنولوژی هایی شوند که با دات نت سازگار باشند.

 

در اینجا این سوال ممکن است پیش بیاید که آیا باید کلا از رعایت و توجه به DRY اجتناب کنیم؟

باید توجه داشت بعضی از این Shared Library ها شامل مواردی هستند که جنرال بوده و به ندرت دچار تغییر می شوند. بخصوص اینکه این تغییرات از ناحیه دامنه ی مایکروسرویسی نمی آید. مثل لایبرری هایی برای log انداختن در سطح سیستم. پس باید گفت در معماری مایکروسرویس ها ما از دید رادیکال Don’t Repeat Everything اجتناب می کنیم و بیشتر به Don’t Repeat Abstraction تمرکز می کنیم. اما هیچگاه سراغ به اشتراک گذاری Business  یا Behavior بین مایکروسرویس ها نمی رویم. مثلا لایبرری مشترک برای انجام عملیاتی در مورد مشتری. بجای اینکه باید حتما ابتدا به طراحی مایکروسرویس ها مجدد نگاهی انداخته شود. در بیشتر موارد نیاز به اشتراک گذاری بیزینس بین مایکروسرویس ها بدلیل اشتباهی در طراحی مایکروسرویس ها رخ داده است. در غیر اینصورت باید این بیزنس مشترک رو بصورت نسخه های کاملا متفاوت(هرچند با پیاده سازی یکسان) دید و هر مایکروسرویس نسخه ی مخصوص خود را داشته باشد. ما در نرم افزار همواره و همیشه در حال thread-off هستیم.

 

 

Sam Newman در Building Microservices مثالی را بصورت زیر می آورد:

RealEstate.com.au makes use of a tailored service template to help bootstrap new service creation. Rather than make this code shared, the company copies it for every new service to ensure that coupling doesn’t leak in.

(Sam Newman (Building Microservices–

نکته ی دیگر این است که ما از احتیاط زیاد در اعمال DRY در سطح بین مایکروسرویس ها صحبت کردیم. اما درون خود مایکروسرویس ها بهیچ وجه DRY را فراموش نکنید.

Don’t violate DRY within a Microservice, but be careful about applying DRY between Microservices

درباره ی masoud@admin

همچنین ببینید

از آقا یا خانم DevOps توقع بیجا نداشته باشیم!!!

از آقا یا خانم DevOps توقع بیجا نداشته باشیم!!! احتمالا شما هم با دیدن عبارت “آقا …

اسید و بازها و چالش سازگاری در سیستم های توزیع شده

اسید و بازها و چالش سازگاری در سیستم های توزیع شده بدون شک سازگاری یکی …

4 دیدگاه

  1. محمدکاظم ذاکر

    سلام و تشکر بابت مطالب خوبتون
    سوال اینه در سطح داده ها این تکرار ها چطوری مدیریت میشه
    مثلا دو مایکروسرویس x و y که هر دو دارای مدلی به نام unit هستن چه اتفاقی برای این unit می افته؟

    • سلام. متشکرم.
      در صورتی که منظور از داده ها مدل های دامین هستند که حتما تفکیک خواهند شد. مثلا در مورد بالا ممکن است شما قبلا مدلی داشته باشید به نام آلبوم و یک مدل بنام هنرمند که اینها مستقیما ارتباط با هم داشته باشند. و شما می توانید براحتی مثلا بهنگام گرفتن کوئری یک آلبوم اطلاعات هنرمند را هم بگیرید و یا بهنگام ویرایش یک آلبوم براحتی اطلاعات هنرمند را در صورت نیاز ویرایش کنید. ولی پس از تفکیک این سیستم به دو مایکروسرویس دیگه مایکروسرویسی که مرتبط با عملیات آلبوم هست نمی تواند مستقیما مثلا اطلاعات هنرمند را بگیرد و باید از طریق API که توسط مایکروسرویس دیگری جهت Expose شدن سرویس ارائه شده درخواست شود. در سطح دیتابیس هم اولین پیشنهاد این است که تفکیک از سرویس ها از آنجا شروع شود چونکه می تونه یک مانع بسیار مهم در برابر مستقل بودن برای تمام مایکروسرویس ها ایجاد کنه. دیتابیس مهمترین و بززگترین Integration point توی سیستم هست.
      در صورتی هم که شما مثلا با یک سری utility سروکار دارید که به فرض مثال یکسری Validation در لایه سرویس ها انجام میده باز هم باید این نوع utility ها به ازای تمام مایکروسرویس ها تکرار شوند و از اشتراک گذاری حتما اجتناب شود.

      • محمدکاظم ذاکر

        منظورم دقیقا دامین مدل هست
        بزرگترین چالش برای تیم ما که تا الان توی مدل های رابطه کار کردیم و تفکرمون یکپارچگی سیستم بوده ، طراحی دامین مدله

        • پیشنهاد می کنم بعد از تحلیل و شناسایی مرزهای تفکیک سیستم جهت شکستن سیستم به یکسری سرویس؛ ابتدا از خود دیتابیس شروع کنید؛ از جدول های اشتراکی و سعی کنید اونها رو بر اساس شماهای مختلف توی همان دیتابیس تفکیک کنید. برای اینکار باید کلیدهای خارجی جداولی که توی سرویس های متفاوت هستند از بین بره؛ جدولهایی که توسط بخشهای مختلف کد که توی سرویس های مختلف هستند تکلیفشون مشخص شه چون این جدول ها نهایتا متعلق به یک سرویس خواهند شد و بقیه بخش ها باید از طریق API های ارائه شده توسط اون سرویس دسترسی داشته باشند و … برای خیلی از این موارد در صورتی که از Repository استفاده می کنید می تونه توی این تفکیک شماها بهتون کمک کنه.

پاسخ دهید

نشانی ایمیل شما منتشر نخواهد شد. بخش‌های موردنیاز علامت‌گذاری شده‌اند *

در تلگرام هم همراه شما هستم

اگر علاقمند به معماری نرم افزار و مبحث محبوب مایکروسرویس هستید؛ در کانال با ما همراه باشید. اطلاعات مفید زیادی در این کانال انتظار شما را می کشند. فقط کافیست دکمه ی پیوستن را بفشارید.

پیوستن بستن