Entity Framework یکی از محبوب ترین ORM های NET. می باشد که در سال ۲۰۰۸ توسط مایکروسافت به بازار عرضه شد. بسیاری از شرکت های ایرانی که تا قبل از آن از تکنولوژی های قدیمی تر (مانند Linq2Sql و Dataset و … ) و یا از ORM های دیگر (مانند NHibernate ) استفاده میکردند، به این تکنولوژی روی آوردند. همچنین در سال های اخیر شاهد فراگیرتر شدن رویکرد DDD و استفاده از آن در پروژه های Enterprise بوده ایم. بسیاری از شرکت ها از Entity Framework در پروژه های Domain-Driven خود نیز استفاده میکنند. در این مقاله قصد داریم تا به بررسی مشکلات EF و کمبود های آن برای پیاده سازی تکنیک های DDD بپردازیم.
نکته : در زمان نوشته شدن این مقاله، اخرین نسخه ی EntityFramework که بر روی سایت Nuget قراردارد، نسخه ی ۶٫۱٫۲ می باشد و ممکن است مشکلات زیر در نسخه های بعدی آن رفع شوند.
مشکل ۱ : عدم پشتیبانی کامل از Entity های Encapsulate شده
Encapsulate کردن کلاس های Domain در رویکرد DDD اهمیت بسیار زیادی دارد. با این روش می توانید Invariant ها را همیشه تحت کنترل داشته باشید و اشیاء را همیشه در حالت Valid نگه دارید. برای مثال :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
public class Customer : Entity<long> { private string firstname; private string lastname; private long referralCustomerId; public string Firstname { get { return firstname; } } public string Lastname { get { return lastname; } } public long ReferralCustomerId { get { return referralCustomerId; } } public Customer(long id, string firstname, string lastname, Customer referralCustomer) { if (id <= 0) throw new ArgumentException("Id"); if (referralCustomer.Id == id) throw new Exception("Referral customer can't be same as the customer"); this.id = id; this.referralCustomerId = referralCustomer.Id; this.firstname = firstname; this.lastname = lastname; } . . . } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
public class Customer : Entity<long> { public string Firstname { get; private set; } public string Lastname { get; private set; } public long ReferralCustomerId { get; private set; } public Customer(long id, string firstname, string lastname, Customer referralCustomer) { if (id <= 0) throw new ArgumentException("Id"); if (referralCustomer.Id == id) throw new Exception("Referral customer can't be same as the customer"); this.id = id; this.ReferralCustomerId = referralCustomer.Id; this.Firstname = firstname; this.Lastname = lastname; } . . . } |
ممکن است استفاده از Property ها با Private set در سناریوهای ساده (مانند مثال بالا) بتواند مشکل شما را حل کند اما همچنان Encapsulate کردن Collection ها ممکن نیست. حتی اگر یک Collection را به صورت Private Set نیز تعریف نمایید، باز هم مشکل حل نخواهد شد. زیرا Collection از طریق متدهایی مانند Add, Remove و … قابل تغییر خواهد بود. تنها راه Encapsulate کردن کامل آنها استفاده از Collection های ReadOnly می باشد :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
public class Sale : Entity<long> { private List<SaleDetail> _saleDetails; public IList<SaleDetail> SaleDetails { get { return new ReadOnlyCollection<SaleDetail>(_saleDetails); }} public void AddSaleDetail(SaleDetail detail) { // // add some logic to add detail to list // } . . . } |
متاسفانه Map کردن کلاس فوق در Entity Framework امکان پذیر نمی باشد. هرچند راه حل هایی (۱ ۲ ۳) برای حل این مشکل ارائه شده اند اما هیچکدام راه حل مناسبی به نظر نمیرسد و ما را از اصل Persistence Ignorance دور میکند. این در حالی است که Map کردن کلاس فوق در NHibernate به راحتی امکان پذیر است.
مشکل ۲ : عدم پشتیبانی از Value Object ها
Value Object یکی از مفاهیمی است که در پیاده سازی Domain Model نقش اساسی دارد. Value Object ها فاقد Identity بوده و تنها توسط مقادیرشان شناخته می شوند. تنها راه حل ارائه شده توسط EF برای Map کردن Value Object ها، Complex Type ها می باشد که با محدودیت های بسیار جدی روبرو است. به مثال ساده زیر توجه کنید :
در مثال فوق OrderItem ماهیت Value Object دارد و کاملا وابسته به Order می باشد. Map کردن کلاس فوق با Complex Type ها امکان پذیر نیست زیرا در EF امکان تعریف لیستی از Complex Type ها وجود ندارد.
به همین دلیل بسیاری از برنامه نویسان ValueObject ها را Immutable تعریف کرده ولی آنها را مانند دیگر Entity ها Map میکنند :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
public class OrderItem { public Guid Id { get; private set; } public long ProductId { get; private set; } public double Price { get; private set; } public double Amount { get; private set; } public OrderItem(long productId, double price, double amount) { Id = Guid.NewGuid(); ProductId = productId; Price = price; Amount = amount; } } public class OrderItemMapping : EntityTypeConfiguration<OrderItem> { public OrderItemMapping() { ToTable("OrderItems").HasKey(a=>a.Id) .Property(a=>a.Id) .HasDatabaseGeneratedOption(DatabaseGeneratedOption.None); Property(a => a.Price); Property(a => a.Amount); Property(a => a.ProductId); } } |
1 2 3 4 5 6 7 8 9 10 11 12 |
public class Order { public ICollection<OrderItem> OrderItems { get; private set; } public void RemoveOrderItem(OrderItem orderItem) { this.OrderItems.Remove(orderItem); } . . . } |
در صورتی که اقدام به ذخیره ی Order مورد نظر کنید، خطای زیر را دریافت میکنید :
The operation failed: The relationship could not be changed because one or more of the foreign-key properties is non-nullable
هر چند راه حل هایی برای مشکل فوق عنوان شده است اما هیچکدام مناسب نمی باشند و اغلب نیازمند Reference دادن به EF در لایه ی Domain می باشد.
مشکل کار با Value Object ها فقط به کار با لیست ها ختم نمی شود. EF راه حلی برای Map کردن مقادیر دیتابیس به Value Object های پیچیده را نیز ندارد. برای مثال فرض کنید شماره تماس افراد را در قالب یک رشته در دیتابیس ذخیره میکنید :
home=99669966;office=11223;mobile=0912111
ولی در Domain Model کلاسی مانند کلاس زیر برای آن تعریف کرده اید :
1 2 3 4 5 6 7 8 9 |
public class Contact { public Dictionary<string, Phone> Phones { get; private set; } public Contact(Dictionary<string, Phone> phones) { Phones = phones; } } |
- نتیجه گیری
به علت وجود دو مشکل فوق، استفاده از Entity Framework در پروژه های مبتنی بر DDD، ساخت Domain Model غنی و اصولی را محدود و مشکل (و حتی ناممکن) میکند. این در حالی است که هیچکدام از مشکلات عنوان شده، در NHibernate مشاهده نمی شوند. Entity Framework را می توان به عنوان بهترین ابزار برای پروژه های Data-Driven در نظر گرفت (به علت سادگی، بازدهی بهتر و …) اما استفاده از آن در پروژه های Enterprise که اغلب با رویکرد DDD طراحی شده و نیاز به Domain Model های غنی دارند، پیشنهاد نمی شود. در مقابل می توان NHibernate را بهترین گزینه برای پروژه های Domain-Driven دانست.
مطلبي هم در اين مورد در اينجا هست:
http://enterprisecraftsmanship.com/2014/11/29/entity-framework-6-7-vs-nhibernate-4-ddd-perspective/
بله مطلب جالبی است
البته بنده با نویسنده ی مطلب فوق در برخی موارد موافق نیستم. مثل موردی که با اسم Case #2 در مقاله مشخص شده. چون در EF بدون قراردادن کلید خارجی روی Entity هم میتوان Relation برقرار کرد.
در اين مطلب سعي شده متدهاي Add و Remove به داخل كلاسهاي entity منتقل بشن. به اين روش اصطلاحا active recored ميگن. اين روش مشكلات خاص خودش رو داره:
http://www.mehdi-khalili.com/orm-anti-patterns-part-1-active-record
سلام
خیر دوست عزیز، روش استفاده شده ActiveRecord نیست. در این مثال چون Order و OrderItem در یک Aggregate قراردارند و Order به عنوان AggregateRoot مشخص شده است و مسئولیت چک کردن Invariant ها را برعهده دارد، لذا از ایجاد و حذف مستقیم OrderItem جلوگیری شده و به جای آن از متد های Add و Remove بر روی Entity استفاده شده است. اینجا بحث Object مطرح هست و نه دیتابیس. برخلاف ActiveRecord که بر روی متد Add، شی را در دیتابیس Insert میکند، اینجا OrderItem تنها به شیء Order اضافه شده است.
برای اطلاعات بیشتر در مورد Aggregate ها و Invariant ها به پست زیر مراجعه کنید :
آشنایی با مفهوم Aggregate ها
يك سؤال: شما واقعا در كارهاتون شماره تماس رو رشتهاي ذخيره ميكنيد به اين شكلي كه مثال زديد؟ بعد مشكلات گزارش گيري خاص و جستجوي دقيق پيدا نميكنيد؟
سلام محسن جان
مورد ذکر شده صرفا یک مثال بود که مانند مثال های دیگر، از جزئیات پیاده سازی در پروژه ی واقعی برای آن پرهیز شده. امروزه در نرم افزارهای Enterprise تمرکز کاملا بر روی ساخت Domain Model غنی و کاربردی است و مواردی مانند گزارش گیری و جستجو دغدغه نیستند. زیرا راه حل هایی مانند CQRS با ساخت یک مدل جدا برای Query (در بسیاری موارد برای بالا بردن Performance با ساخت یک دیتابیس Denormal جدا) میتوان نیازهای گزارش گیری و جستجو را برطرف نمود. اطلاعات بیشتر در مورد CQRS :
آشنایی با معماری نرم افزار در رویکرد DDD
البته این به این معنی نیست که شماره تماس ها همیشه به این شکل باید ذخیره شوند ! Domain Model بر اساس نیازهای پروژه و به تصمیم طراحان سیستم طراحی می شود و ممکن است در صورت نیاز به شکل فوق باشد و یا نباشد.
سلام
چرا با وجود دو مشکل فوق باز هم کتابی در مورد متودولوژی DDD منتشر شده است.
Applying Domain-Driven Design and Patterns
سلام فرزاد جان
راستش منظور شما رو دقیق متوجه نشدم. دو مشکل فوق صرفا مربوط به استفاده از Entity Framework به عنوان ORM در پروژه های با رویکرد DDD می باشد. کتاب فوق هم توسط Jimmy Nilsson در سال ۲۰۰۶ (دوسال قبل از Release اولین نسخه ی EF) نوشته شده و از NHibernate استفاده میکند.
نکته ی دیگر اینکه DDD متدولوژی نیست، به لینک زیر مراجعه کنید :
What is Domain Driven Design
سلام
آقای احمدی با توجه به دو مشکل Entity Framework شما NHibernate را تنها ORM مناسب برای پروژهای DDD می دانید یا ORM های مناسب دیگری وجود دارد
سلام
جناب اسماعیلی ممکن است ORM های دیگری هم وجود داشته باشند که بتوان از آنها در پروژه های DDD استفاده کرد (برای مثال LightSpeed که ادعا میکند بر پایه ی مفاهیم و الگوهای DDD مانند Entity، ValueObject ، Aggregate و … طراحی شده است). اما همانطور که میدانید انتخاب یک ابزار به عوامل زیادی مثل امکانات ابزار، مجوز استفاده، مستندات و کتاب های موجود، Community فعال و … بستگی دارد. به نظر من NHibernate شاید تنها انتخاب نباشد، اما میتوان گفت بهترین انتخاب برای پروژه های Domain-Driven است.
سلام
جناب احمدی این مطلبی رو که در مورد Ef فرمودین در مورد نسخههای اخیرش هم صادقه؟
سلام سعید جان
مشکل Encapsulate کردن Collection ها در EF Core حل شده که نحوه انجام آن در ویدیوی زیر توسط خانوم Julie Lernman توضیح داده شده :
https://channel9.msdn.com/Shows/Visual-Studio-Toolbox/Entity-Framework-Core
اما مشکل Value Object ها همچنان باقی هست.
سلام. مطلب خوب و ارزشمندی بود. بابت نگارش مقالات ارزشمند در باب موضوعاتی که کمتر داخل کشور بهشون پرداخته شده بهتون تبریک میگم. دلایلی که فرمودید، درست و در مورد EF کاملا صادق هستند. در عین حال این حرف که EF انتخاب خوبی برای ddd نیست کمی شاید اغراق باشد. همانطور که خودتون فرمودید: “انتخاب یک ابزار به عوامل زیادی مثل امکانات ابزار، مجوز استفاده، مستندات و کتاب های موجود، Community فعال و … بستگی دارد”. همچنان معتقدم هزینه های پرداختی استفاده از EF در ddd در مقابل آنچه که بدست می آوریم بسیار ناچیز خواهد بود.ضمن اینکه بنظر میرسد مطالب زیر در پاسخ به مطلب شما برای خوانندگان مفید خواهد بود :
https://msdn.microsoft.com/magazine/dn342868
https://msdn.microsoft.com/magazine/mt842503
سلام جناب کلاهی عزیز
فکر میکنم اینکه میفرمایید “هزینه های پرداختی استفاده از EF در DDD در مقابل آنچه که بدست می آوریم بسیار ناچیز خواهد بود” با فرض این هست که تیم موجود بر روی EF مسلط هستند و تجربه دارند. یکی از عواملی که روی انتخاب ابزار تاثیرگذار می باشد، همین موضوع تجربه و دانش تیم هست. خود بنده هم در پروژه های مختلفی (به علت دانش تیم روی EF) تغییر ابزار رو پیشنهاد نکرده ام و مشکلات EF را با روش های مختلف موجود حل کرده ام.
اما باید توجه داشته باشید که این مقاله موضوع را از زاویه کاملا فنی (و با حذف شرایط تیم) بیان میکند. از لحاظ فنی، ابزار NHibernate در Map کردن دومین مدل و مفاهیمی مثل Value Object بسیار موفق تر بوده و PI در آن بهتر رعایت می شود. البته مشکلات دیگری هم بوده اند که در این مقاله ذکر نشده اند، مثل عدم پشتیبانی EF از UserType ها که در مواردی مثل Map کردن State Pattern مشکل ساز می شود.
مقالاتی که فرمودید رو مطالعه کردم، مطالب جالبی هستند اما علیرغم گذشت (تقریبا) ۳ سال از نوشتن این مقاله، همچنان مشکلات فوق در EF وجود دارند و رفع نشده اند. البته با توجه تمرکز جامعه ی معماران و طراحان بر روی موضوع DDD، فکر میکنم این مشکلات به زودی در EF رفع شوند.
با عرض سلام
جناب مهندس احمدی ظاهرا در ef core مشکل value object ها حل شده است لینک توضیحات
https://docs.microsoft.com/en-us/dotnet/standard/microservices-architecture/microservice-ddd-cqrs-patterns/implement-value-objects
سلام محمد جان
در EF 6 هم قابلیت Complex Type وجود داشت که با استفاده از این قابلیت میشد یک Value Object را Map کرد. مشکل روی Collection ای از Value Object ها بود. مقاله ای که لینکش رو فرستادید رو اگر مقاله رو دقیق مطالعه بفرمایید در قسمت Limitations نوشته شده که هنوز این محدودیت وجود دارد :
No collections of owned types yet (but they will be supported in versions after EF Core 2.0).
که البته اخرش نوشته شده در آینده رفع خواهد شد.
با سلام و تشکر از مطالب مفید سایت . میخواستم بدونم که این مشکلات در EF Core حل شده یا نه ؟
با تشکر
سلام محمد جان
تا این لحظه که بنده این کامنت رو مینویسم مشکل Encapsulation در EF Core حل شده است. میتونید لینک زیر رو مطالعه بفرمایید :
https://ardalis.com/encapsulated-collections-in-entity-framework-core
اما مشکل Collection ای از Value Object ها پابرجاست اما تیم EF اشاره کرده که در نسخه های بعدی این مشکل رو هم رفع خواهند کرد.
مممممممنون
فک کنم هادی احمدی ها همشون برنامه نویسن