وبلاگ شخصی حامد شیربندی

حامد شیربندی

توسعه دهنده نرم افزار

بررسی الگوی Repository

تاریخچه

مطالب در مورد الگوی Repository حتی قبل از انتشار GOF وجود داشته است، David Garlan و Mary Shaw در مقاله ای که با عنوان An Introduction to Software Architecture در سال 1994 منتشر شده راجع به آن صحبت کرده اند.

محبوبیت این الگو  بعد از مقالات و کتابهای معروفی توسط Martin Fowler و Eric Evans شروع شد که در آنها به این الگو پرداخته شده بود. مارتین فاولر در سال 2002 و در کتاب Patterns of Enterprise Application Architecture و نیز اریک ایوانز هم در سال 2004 و در کتاب معروفش یعنی  Domain Driven Design به نقش موثر این الگو در به کار گیری آن در لایه DataAccess اشاره داشته اند.

تعریف

ریپازیتوری یک واسط بین دامین و لایه ی Data Access است. منبع داده می تواند یک دیتابیس،xml  و یا حتی یک وب سرویس باشد.

در حقیقت ریپازیتوری یک Abstraction روی لایه دسترسی به دیتا است و با پیاده سازی عملیاتی همچون ایجاد، ویرایش، حذف و ... کمک میکند تا جزییات این لایه (مکانیزم persistence) از دید لایه ی Business مخفی شود.

هدف

در این این مورد خاص یک  تصور اشتباه (Misconception) وجود دارد و آن هم این تفکر است که هدف اصلی ریپازیتوری قابل تعویض کردن تکنولوژی مورد استفاده برای کار با دیتا است. برای مثال زمانی که از ORM ها استفاده شده، هر زمان که خواستیم بتوانیم با کمترین هزینه از یک ORM دیگر استفاده کنیم. البته که ریپازیتوری ها این امکان را فراهم میکنند اما هدف اصلی آنها این نیست.

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

مزایا

 پیاده سازی ریپازیتوری درکل دارای مزایایی به شرح زیر می باشد:

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

  • اجازه میدهد به جای تستهای یکپارچه (Integration Test) تست های واحد بنویسیم (Unit Test)
با توجه به اینکه به سادگی میتوانیم ریپازیتوری ها را از طریق اینترفیس به کلاس های برنامه تزریق کنیم در نتیجه نوشتن آزمون های واحد بدون درگیر شدن واقعی با منبع داده برای ما امکان پدیر می شود و در کل یکی از مزیت های مهمی که ریپازیتوری به ما میدهد همین بالا بردن تست پذیری است.

  • خوانایی و نگهداری منطق تجاری را با جداسازی دسترسی به دیتا بالا میبرد.

  • یک کنترل مرکزی نسبت به دسترسی به دیتا پیدا کرده و میتوانیم قوانین مدنظرمان را پیاده کنیم.

  • به ما اجازه میدهد تا تکنولوژی مناسب را برای مسئله ی جاری انتخاب کنیم.
 مثلا ثبت رکورد جدید را با EF انجام دهیم و یک کوئری پیچیده را مستقیما با Ado.net پیاده کنیم.

  • این‌امکان را داریم تا در آینده تکنولوژی مربوط به دسترسی به دیتا را تعویض کنیم


Repository و ORM ، جنگ و صلح

از زمانی که ORM ها آمدند یک چالش جدید در استفاده از الگوی ریپازیتوری به وجود آمد.

موافقان استفاده از ریپازیتوری بر روی ORM میگویند :

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

  • از آنجا که لایه بیزنس با DbContext (در EF) درگیر نمیشود پیاده سازی تست ها آسانتر شده و به اصطلاح Testablity پروژه بالا میرود  چرا که دیگر مجبور به تقلید DbContext نخواهیم بود و خود Repository را تقلید خواهیم کرد که دارای یک سری متد اضافه و حذف و ... مشخص است و دقیقا میدانیم چه چیزهایی را باید تقلید کنیم. در واقع تقلید آن بسیار آسانتر از تقلید DbContext است. وقتی مستقیما از DbContext در بیزنس استفاده کرده باشیم و تست ناموفق باشد نمیتوانیم دقیقا مطمئن باشیم که کد تجاری ما اشتباه بوده یا کوئری لینک ایجاد شده و یا مپینگ ORM و نیز تقلید کردن یک اینترفیس از DbContext نیز به ما کمک نمیکند تا در این باره مطمئن باشیم چرا که رفتارهای یکسانی در linq to object و linq to entity وجود ندارد.

  • در یک پروژه خاص ممکن است فقط با ORM سروکار نداشته باشیم که در این صورت ریپازیتوری، سایر منابع داده مثل یک وب سرویس و ... را هم منتزع کرده و باعث میشود اصل Persistence Ignorance را رعایت کنیم.

مخالفان استفاده از ریپازیتوری بر روی ORM میگویند :

  • ORM ها دارای ریپازیتوری داخلی هستند. مثلا در EF این مورد در DbSet ها پیاده سازی شده است.پس پیاده سازی ریپازیتوری روی ریپازیتوری درست نیست و به گونه ای اصل DRY را هم زیر سوال میبرد.

  • ORM ها دارای ویژگی هایی هستند که با افزودن یک انتزاع روی آنها این ویژگی های از بین میروند.

  • هیچگاه نمیتوان دقیقا ریپازیتوری را پیاده کرد که درصورت تعویض ORM فعلی بتوان ORM جدید را جایگزین کرد.چرا که برای مثال NH دارای ویژگی هایی است که EF هیچگاه آنهارا پیاده سازی نکرده و بالعکس. حالا اگر ریپازیتوری ما به گونه ای پیاده شده که از متدهای Async در EF استفاده کرده بعدا چطور میتوانیم همین پیاده سازی را برای NH داشته باشیم؟ پس در این صورت رسیدن به Persistence Ignorance بسیار سخت خواهد بود.

  • از دیگر مشکلاتی که ریپازیتوری به همراه دارد سخت شدن واکشی اطلاعاتی ست که نیاز به join بین دو یا چند Entity دارند در صورتی که این مورد از طریق استفاده مستقیم از ORM به سادگی قابل انجام است.


در این موارد باید گفت که هم موافقان و هم مخالفان درست میگویند! اما هرکدام دارند درمورد مزایا و معایب حرف میزنند و در نهایت این مزایا و معایب هستند که باعث انتخاب یا عدم انتخاب یک رویکرد میشوند.

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


بهترین روش پیاده سازی

شاید پیاده سازی یک ریپازیتوری جنریک کار فوق العاده ای باشد و نیاز به کد نویسی را تا حد زیادی کاهش دهد ولی در حقیقت این چیزی جز تنبلی نیست. روش صحیح این است که هر ریپازیتوری اینترفیس خودش را داشته و نیاز های خودش را پیاده سازی کرده باشد.

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

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

توصیه میشود درکنار ریپازیتوری الگوی بسیار سودمند واحد کاری (Unit Of Work) را نیز پیاده کنید. در این مورد مقالات خوبی نیز در سطح وب موجود است که نشان میدهد چگونه میتوان در کنار ریپازیتوری زمانی که از یک ORM استفاده میکنیم الگوی واحد کاری را نیز داشته باشیم.

 

بهترین زمان پیاده سازی

بهترین زمانی که میتوانیم از ریپازیتوری استفاده کنیم وقتی است که پروژه به روش DDD در حال توسعه است و DDD هم فقط برای پروژه های بزرگ و پیچیده پیشنهاد میشود و استفاده از آن در پروژه های کوچک و معمولی فقط اتلاف وقت است!

در این روش (DDD) ما  برای هر Aggregate ریشه یک ریپازیتوری لحاظ میکنیم.

 

نتیجه گیری

زمانی که قصد داریم ریپازیتوری را روی ORM به کار ببریم مطمئن شویم که پروژه ی ما یک پروژه ی بزرگ و پیچیده است و بدانیم این الگو هزینه هایی را روی دست ما میگذارد که با هدف بهره وری در آینده انجام میشود و در زمان حال سود چندانی ندارد.

مطمئن شویم که واقعا تست نویسی برای ما حیاتی است و بخاطر آن نمیخواهیم درگیر ORM در سطح بیزنس باشیم.

مطمئن شویم که پیاده سازی که در سطح ریپازیتوری انجام داده ایم به گونه ای نیست که به ویژگی خاصی از یک ORM وابسته باشد که بعدا نتوان آن را جایگزین کرد.

مطمئن باشیم که الگو را طبق چالش های پیش روی پروژه انتخاب کرده ایم نه اینکه چون ما همیشه به این روش کد مینویسیم برای تمام پروژه ها همین روش را ادامه بدهیم.

نوشته شده توسط حامد شیربندی

اگر در مورد این نوشته سوال یا ابهامی وجود دارد میتوانید به ایمیل من ارسال کنید. البته در این مورد باید کمی صبور باشید. در آینده بخش نظرات اضافه خواهد شد.