لماذا يعتبر الفطرة السليمة أكثر أهمية من الأنماط ، و Active Record ليست بهذا السوء

لقد حدث فقط أن المطورين ، وخاصة الصغار ، يحبون أنماط الحب ، يحبون الجدل حول النمط الذي يجب تطبيقه هنا أو هناك. الجدل لدرجة البحة: هذه واجهة أو وكيل ، وربما حتى مفردة. وإذا لم تكن الهندسة المعمارية الخاصة بك نظيفة ، سداسية ، فإن بعض المطورين مستعدون للحرق على حساب محكمة التفتيش المقدسة.



وبذلك ، ينسون أن الأنماط ليست سوى حلول ممكنة. الأنماط ، مثل أي مبادئ ، لها حدود للتطبيق ، ومن المهم فهمها. الطريق إلى الجحيم ممهد بالتمسك الأعمى والديني حتى بكلمات موثوقة.



ووجود الأنماط الضرورية في الإطار لا يضمن تطبيقها الصحيح والواعي.







بريق وفقر Active Record



دعونا ننظر إلى نمط السجل النشط باعتباره مضادًا للنمط ، والذي تحاول بعض لغات البرمجة وأطر العمل تجنبها بكل الطرق الممكنة.



إن جوهر Active Record بسيط: نقوم بتخزين منطق الأعمال باستخدام منطق تخزين الكيان. بعبارة أخرى ، وببساطة شديدة ، يتوافق كل جدول في قاعدة البيانات مع فئة الكيان إلى جانب السلوك.





هناك رأي قوي إلى حد ما بأن الجمع بين منطق الأعمال ومنطق التخزين في فئة واحدة هو نمط سيء للغاية وغير قابل للاستخدام. ينتهك مبدأ المسؤولية الفردية. ولهذا السبب فإن Django ORM سيء حسب التصميم.



في الواقع ، قد لا يكون من الجيد الجمع بين منطق التخزين ومنطق المجال في نفس الفئة.


لنأخذ نماذج المستخدم والملف الشخصي على سبيل المثال. هذا نمط شائع إلى حد ما. هناك لوحة رئيسية ، وهناك لوحة إضافية لا تخزن دائمًا البيانات الإلزامية ، ولكن في بعض الأحيان ضرورية.





اتضح أن كيان مجال "المستخدم" مخزن الآن في جدولين ، وفي الكود لدينا فئتان. وفي كل مرة نجري فيها بعض التصحيحات مباشرة user.profile، علينا أن نتذكر أن هذا نموذج منفصل وأننا قمنا بتغييرات فيه. واحفظه بشكل منفصل.



   def create(self, validated_data):
        # create user 
        user = User.objects.create(
            url = validated_data['url'],
            email = validated_data['email'],
            # etc ...
        )

        profile_data = validated_data.pop('profile')
        # create profile
        profile = Profile.objects.create(
            user = user
            first_name = profile_data['first_name'],
            last_name = profile_data['last_name'],
            # etc...
        )

        return user


للحصول على قائمة بالمستخدمين ، من الضروري التفكير فيما إذا كان سيتم أخذ سمة من هؤلاء المستخدمين من profileأجل تحديد علامتين على الفور مع صلة وعدم الحصول عليها SELECT N+1في حلقة.



user = User.objects.get(email='example@examplemail.com')
user.userprofile.company_name
user.userprofile.country


تزداد الأمور سوءًا إذا تم تخزين جزء من بيانات المستخدم في خدمة أخرى ضمن بنية الخدمات المصغرة - على سبيل المثال ، الأدوار والحقوق في LDAP.



في الوقت نفسه ، بالطبع ، لا أريد حقًا أن يهتم المستخدمون الخارجيون لواجهة برمجة التطبيقات بهذا الأمر بطريقة ما. يوجد مورد REST /users/{user_id}، وأود العمل معه دون التفكير في كيفية ترتيب تخزين البيانات بالداخل. إذا تم تخزينها في مصادر مختلفة ، فسيكون من الصعب تغيير المستخدم أو الحصول على قائمة بالبيانات.



بشكل عام ، ORM! = نموذج المجال!





وكلما زاد اختلاف العالم الحقيقي عن افتراض "جدول واحد في قاعدة البيانات - كيان واحد للمجال" ، زادت المشكلات المتعلقة بنمط Active Record.


اتضح أنه في كل مرة تكتب فيها منطق الأعمال ، يجب أن تتذكر كيف يتم تخزين جوهر المجال.



طرق ORM هي أدنى مستوى من التجريد. لا يدعمون أي قيود على مجال الموضوع ، مما يعني أنهم يوفرون فرصة للأخطاء. كما أنها تخفي عن المستخدم ما هي الاستعلامات التي يتم إجراؤها بالفعل في قاعدة البيانات ، مما يؤدي إلى استعلامات طويلة وغير فعالة. الكلاسيكية ، عندما يتم إجراء الاستعلامات في حلقات ، بدلاً من الصلة أو التصفية.



وماذا أيضًا ، بصرف النظر عن بناء الاستعلام (القدرة على إنشاء استعلامات) ، هل تمنحنا ORM؟ لا يهم. القدرة على الانتقال إلى قاعدة بيانات جديدة؟ ومن الذي في عقله الصحيح وذاكرته الراسخة انتقل إلى قاعدة بيانات جديدة وساعدته ORM في ذلك؟ إذا كنت لا تنظر إليها على أنها محاولة لتعيين نموذج المجال (!) في قاعدة البيانات ، ولكن كمكتبة بسيطة تسمح لك بإجراء استعلامات على قاعدة البيانات بطريقة مناسبة ، فسيتم وضع كل شيء في مكانه الصحيح.



وعلى الرغم من استخدامها في أسماء الفئات Modelوفي أسماء الملفات models، فإنها لا تصبح نماذج. لا تخدع نفسك. هذا مجرد وصف للتسميات. لن يساعدوا في تغليف أي شيء.



ولكن إذا كان كل شيء سيئًا للغاية ، فماذا تفعل؟ تأتي أنماط من الأبنية متعددة الطبقات للإنقاذ.



الهندسة المعمارية متعددة الطبقات تعود إلى الوراء!



الفكرة وراء البنى متعددة الطبقات بسيطة: نحن نفصل بين منطق العمل ، ومنطق التخزين ، ومنطق الاستخدام.



يبدو من المنطقي تمامًا فصل التخزين عن تغيير الحالة. أولئك. إنشاء طبقة منفصلة يمكنها استقبال وحفظ البيانات من التخزين "المجرد".



نترك كل منطق التخزين ، على سبيل المثال ، في فئة التخزين Repository. وتستخدمها وحدات التحكم (أو طبقة الخدمة) فقط للحصول على الكيانات وحفظها. ثم يمكننا تغيير منطق التخزين والاستلام كما نحب ، وسيكون هذا مكانًا واحدًا! وعندما نكتب رمز العميل ، يمكننا التأكد من أننا لم ننسى مكانًا آخر نحتاج إلى الحفظ فيه أو نحتاج إلى أخذها منه ، ولا نكرر نفس الكود عدة مرات.





لا يهمنا إذا كان الكيان يتكون من سجلات في جداول مختلفة أو خدمات مصغرة. أو إذا تم تخزين الكيانات ذات السلوك المختلف بناءً على النوع في جدول واحد.



لكن هذا التقسيم للمسؤوليات ليس مجانيًا . يجب أن يكون مفهوماً أنه يتم إنشاء طبقات تجريد إضافية من أجل منع التغييرات "السيئة" في الشفرة. من الواضح أنه Repositoryيخفي حقيقة أن الكائن مخزن في قاعدة بيانات SQL ، لذلك يجب أن نحاول ألا نسمح لـ SQLism بالخروج من الحدود Repository. وجميع الطلبات ، حتى الأكثر بساطة ووضوحًا ، يجب سحبها عبر طبقة التخزين.



على سبيل المثال ، إذا أصبح من الضروري الحصول على مكتب بالاسم والقسم ، فسيتعين عليك كتابة هذا:



#     
interface OfficeRepository: CrudRepository<OfficeEntity, Long> {
    @Query("select o from OfficeEntity o " +
            "where o.number = :office and o.branch.number = :branch")
    fun getOffice(@Param("branch") branch: String,
                  @Param("office") office: String): OfficeEntity?
 ...


وفي حالة Active Record ، يكون كل شيء أبسط بكثير:



Office.objects.get(name=’Name’, branch=’Branch’)


الأمر ليس بهذه البساطة حتى لو تم تخزين الكيان التجاري بالفعل بطريقة غير تافهة (في عدة جداول ، في خدمات مختلفة ، وما إلى ذلك). لتنفيذ هذا بشكل جيد (وبشكل صحيح) - الذي تم إنشاء هذا النمط من أجله - غالبًا ما يتعين عليك استخدام أنماط مثل التجميعات ووحدة العمل ومخططي البيانات.



من الصعب تحديد مجموعة بشكل صحيح ، ومراقبة جميع القيود المفروضة عليها بشكل صحيح ، وعمل خرائط البيانات بشكل صحيح. ومطور جيد فقط يمكنه التعامل مع هذه المهمة. الشخص الذي ، في حالة Active Record ، كان بإمكانه فعل كل شيء "بشكل صحيح".



ماذا يحدث للمطورين العاديين؟ أولئك الذين يعرفون جميع الأنماط ولديهم قناعة راسخة بأنهم إذا استخدموا بنية ذات طبقات ، فإن كودهم يصبح تلقائيًا قابلاً للصيانة وجيدًا ، وليس مثل Active Record. ويقومون بإنشاء مستودعات CRUD لكل جدول. وهم يعملون في مفهوم



لوحة واحدة - مستودع واحد - كيان واحد.



ليس:



مستودع واحد - كائن مجال واحد.





كما أنهم يعتقدون بشكل أعمى أنه إذا تم استخدام كلمة في الفصل Entity، فإنها تعكس نموذج المجال. مثل كلمة Modelفي Active Record.



والنتيجة هي طبقة تخزين أكثر تعقيدًا وأقل مرونة تحتوي على جميع الخصائص السلبية لكل من مصممي الخرائط Active Record و Repository / Data.


لكن العمارة الطبقية لا تنتهي عند هذا الحد. عادة ما يتم تمييز طبقة الخدمة.



يعد التنفيذ الصحيح لطبقة الخدمة هذه مهمة صعبة أيضًا. وعلى سبيل المثال ، ينشئ المطورون عديمي الخبرة طبقة خدمة ، وهي خدمة - وكيل للمستودعات أو ORM (DAO). أولئك. تتم كتابة الخدمات بحيث لا تغلف منطق الأعمال:



#      
@Service
class AccountServiceImpl(val accountDaoService: AccountDaoService) : AccountService {
    override fun saveAccount(account: Account) =
            accountDaoService.saveAccount(convertClass(account, AccountEntity::class.java))

    override fun deleteAccount(id: Long) =
            accountDaoService.deleteAccount(id)


وهناك مجموعة من عيوب كل من Active Record و Service layer.



نتيجة لذلك ، في أطر عمل Java ذات الطبقات والتعليمات البرمجية المكتوبة من قبل عشاق الأنماط الشباب وعديمي الخبرة ، يبدأ عدد التجريدات لكل وحدة من منطق الأعمال في تجاوز جميع الحدود المعقولة.





هناك طبقات ، لكنها كلها تافهة وهي مجرد طبقات لاستدعاء الطبقة التالية.



لا يضمن وجود أنماط OOP في الإطار تطبيقها الصحيح والكافي.



لا وجود للرصاص الفضي



من الواضح تماما أنه لا يوجد حل سحري. الحلول المعقدة للمشاكل المعقدة ، والحلول البسيطة للمشاكل البسيطة.



ولا توجد أنماط جيدة وسيئة. في أحد المواقف ، يكون Active Record جيدًا ، وفي حالات أخرى ، يكون لهندسة الطبقات. ونعم ، بالنسبة للغالبية العظمى من التطبيقات الصغيرة والمتوسطة الحجم ، يعمل Active Record بشكل جيد. وبالنسبة للغالبية العظمى من التطبيقات الصغيرة والمتوسطة الحجم ، فإن أداء العمارة الطبقية (a la Spring) يكون أسوأ. والعكس تمامًا بالنسبة للتطبيقات المعقدة الغنية بالمنطق وخدمات الويب.



كلما كان التطبيق أو الخدمة أبسط ، قل عدد طبقات التجريد التي تحتاجها.



ضمن الخدمات المصغرة ، حيث لا يوجد الكثير من منطق الأعمال ، غالبًا ما يكون من غير المجدي استخدام البنى متعددة الطبقات. قد تكون البرامج النصية للمعاملات العادية - البرامج النصية الموجودة في وحدة التحكم - مناسبة تمامًا للمهمة قيد البحث.



في الواقع ، يختلف المطور الجيد عن السيئ في أنه لا يعرف الأنماط فحسب ، بل يفهم أيضًا متى يطبقها.



All Articles