يتعلم الناس الهندسة المعمارية من الكتب القديمة التي كانت مكتوبة لجافا. الكتب جيدة ، لكنها توفر حلاً لمشاكل ذلك الوقت بأدوات العصر. لقد تغير الوقت ، C # يشبه Scala الخفيفة أكثر من Java ، وهناك عدد قليل من الكتب الجيدة الجديدة.
في هذه المقالة ، سوف ندرس معايير الرمز الجيد والرمز السيئ ، وكيفية وماذا يجب قياسه. سنرى نظرة عامة على المهام والمناهج النموذجية ، وسنحلل إيجابيات وسلبيات. في النهاية ستكون هناك توصيات وأفضل الممارسات لتصميم تطبيقات الويب.
هذه المقالة هي نسخة من تقريري من مؤتمر موسكو DotNext 2018. بالإضافة إلى النص ، يوجد مقطع فيديو ورابط للشرائح أسفل المقطع.

الشرائح وصفحة التقرير على الموقع .
باختصار عني: أنا من قازان ، أعمل لدى High Tech Group. نحن نعمل على تطوير البرمجيات للأعمال. لقد قمت مؤخرًا بتدريس دورة في جامعة قازان الفيدرالية تسمى تطوير برامج الشركات. من وقت لآخر ما زلت أكتب مقالات عن هبر حول الممارسات الهندسية ، وعن تطوير برمجيات المؤسسات.
كما قد تكون خمنت على الأرجح ، سأتحدث اليوم عن تطوير برامج المؤسسة ، وهي كيفية هيكلة تطبيقات الويب الحديثة:
- المعايير
- تاريخ موجز لتطور الفكر المعماري (ما كان ، ما أصبح ، ما هي المشاكل) ؛
- نظرة عامة على عيوب فن النفخة الكلاسيكية
- القرار
- خطوة بخطوة تحليل التنفيذ دون الغوص في التفاصيل
- النتائج.
معايير
نحن صياغة المعايير. أنا حقا لا أحب ذلك عندما نتحدث عن التصميم على غرار "بلدي الكونغ فو أقوى من الكونغ فو الخاص بك". لدى الشركة ، من حيث المبدأ ، معيار واحد محدد يسمى المال. يعلم الجميع أن الوقت هو المال ، لذلك غالباً ما يكون هذان المكونان هو الأكثر أهمية.

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

لذلك ، حصلنا على هذا النمط المعماري. هذا يسمى "رمز المعكرونة" هنا ، يقولون "رمز المعكرونة" في الخارج. كل شيء مرتبط بكل شيء: نحن نغير شيئًا ما عند النقطة A - إنه يكسر عند النقطة B ، من المستحيل تمامًا فهم ما يرتبط بما. وبطبيعة الحال ، سرعان ما أدرك المبرمجون أن هذا لن ينجح ، ويجب القيام ببعض البنية ، وقرروا أن بعض الطبقات ستساعدنا. الآن ، إذا كنت تتخيل أن اللحم المفروم هو رمز ، ولازانيا مثل هذه الطبقات ، هنا هو مثال على الهندسة المعمارية للطبقات. بقيت اللحوم المفرومة مفرومة ، لكن الآن لا يمكن للحوم المفرومة من الطبقة رقم 1 الانتقال والتحدث مع اللحم المفروم من الطبقة رقم 2. لقد أعطينا الكود شكلاً من الأشكال: حتى في الصورة يمكنك أن ترى أن التسلق أكثر إطارًا.

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

كانت المرحلة التالية هي ما يسمى
بنية البصل . يبدو أن هناك فرق كبير: قبل ذلك كان هناك مربع صغير ، وهنا كانت هناك دوائر. يبدو أن تكون مختلفة تماما.

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

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

نحن بحاجة إلى إرسال طلب ، والتحقق من صحة ، وتحديث القيمة في قاعدة البيانات ، وإرسال إخطار إلى بريد إلكتروني جديد: "كل شيء على ما يرام ، قمت بتغيير بريدك الإلكتروني ، ونحن نعرف أن كل شيء على ما يرام" ، والرد على متصفح "200" - كل شيء على ما يرام.

قد يبدو الرمز شيئًا كهذا. لدينا هنا التحقق من صحة
ASP.NET MVC القياسي ، وهناك ORM لقراءة البيانات وتحديثها ، وهناك نوع من مرسل البريد الإلكتروني الذي يرسل إشعارًا. يبدو أن كل شيء جيد ، أليس كذلك؟ تحذير واحد - في عالم مثالي.
في العالم الحقيقي ، الوضع مختلف قليلاً. النقطة المهمة هي إضافة إذن ، والتحقق من الخطأ ، والتنسيق ، وقطع الأشجار والتوصيف. كل هذا ليس له علاقة بحالة الاستخدام لدينا ، ولكن يجب أن يكون كل شيء. وأصبحت هذه الشفرة الصغيرة كبيرة ومخيفة: مع الكثير من التداخل ، والكثير من التعليمات البرمجية ، مع حقيقة أنه من الصعب قراءتها ، والأهم من ذلك ، أن هناك رمز بنية تحتية أكثر من رمز المجال.

"أين الخدمات؟" - أنت تقول. كتبت كل المنطق إلى وحدات التحكم. بالطبع ، هذه مشكلة ، والآن سأضيف خدمات ، وكل شيء سيكون على ما يرام.

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

ولكن هنا كل شيء ليس على ما يرام. هذا الرمز لا يزال هنا. لقد نقلنا نفس الشيء إلى الخدمات. قررنا عدم حل المشكلة ، ولكن ببساطة لإخفائها ونقلها إلى مكان آخر. هذا كل شيء.

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

وحدثت هذه الفكرة لأكثر من شخص واحد. إذا كنت google ، فإن ثلاثة من هؤلاء الأزواج المحترمين على الأقل يكتبون عن نفس الشيء. من الأعلى إلى الأسفل: Stephen .NET Junkie (لسوء الحظ ، لا أعرف اسمه الأخير ، لأنها لا تظهر في أي مكان على الإنترنت) ، مؤلفة حاوية
Simple Injector IoC. جيمي بوجارد القادم هو مؤلف
AutoMapper . وفي الأسفل يوجد Scott Vlashin ، مؤلف كتاب
F # للمتعة والربح .

يتحدث كل هؤلاء الأشخاص عن نفس الشيء ويقترحون إنشاء تطبيقات ليس على أساس الطبقات ، ولكن على أساس حالات الاستخدام ، أي تلك المتطلبات التي يطلبها العمل من الشركة. وفقًا لذلك ، يمكن تحديد حالة الاستخدام في C # باستخدام واجهة IHandler. يحتوي على قيم الإدخال ، وهناك قيم الإخراج وهناك طريقة نفسها التي تنفذ في الواقع حالة الاستخدام هذه.

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

للبدء ، دعنا نتخلص من خدمة المستخدم. نقوم بإزالة الطريقة وإنشاء فصل. وسنقوم بإزالته ، وسنقوم بإزالته مرة أخرى. ثم تأخذ وإزالة الطبقة.

دعونا نفكر ، هل هذه الفصول معادلة أم لا؟ فئة GetUser تُرجع البيانات ولا تغير أي شيء على الخادم. هذا ، على سبيل المثال ، حول الطلب "أعطني معرف المستخدم." إرجاع فئات UpdateEmail و BanUser نتيجة العملية وتغيير الحالة. على سبيل المثال ، عندما نخبر الخادم: "الرجاء تغيير الحالة ، تحتاج إلى تغيير شيء ما."

دعونا نلقي نظرة على بروتوكول HTTP. هناك طريقة GET ، والتي وفقًا لمواصفات بروتوكول HTTP يجب أن تُرجع البيانات ولا تغير حالة الخادم.

وهناك طرق أخرى يمكنها تغيير حالة الخادم وإرجاع نتيجة العملية.

يبدو أن نموذج CQRS مصمم خصيصًا لبروتوكول HTTP. الاستعلام عبارة عن عمليات GET ، والأوامر هي PUT و POST و DELETE - لا داعي لاختراع أي شيء.

نحن نعيد تعريف معالجنا ونحدد واجهات إضافية. IQueryHandler ، والذي يختلف فقط في أننا علقنا القيد بأن نوع قيم الإدخال هو IQuery. IQuery هي واجهة علامة ، لا يوجد شيء فيها سوى هذا النوع العام. نحتاج إلى الأمر العام من أجل وضع قيد في QueryHandler ، والآن ، بإعلان QueryHandler ، لا يمكننا المرور إلى هناك ليس Query ، ولكن من خلال تمرير كائن Query هناك ، نحن نعرف قيمة الإرجاع الخاصة به. يعد هذا الأمر مناسبًا إذا كان لديك واجهة واحدة فقط ، لذلك لا يتعين عليك البحث عن تنفيذها في التعليمات البرمجية ، ومرة أخرى حتى لا تتوقف. تكتب IQueryHandler ، تكتب تطبيقًا ، وفي TOUT لا يمكنك استبدال نوع آخر من قيمة الإرجاع. انها فقط لا تجمع. وبالتالي ، يمكنك على الفور معرفة قيم الإدخال التي تتوافق مع بيانات الإدخال.

يتشابه الموقف تمامًا مع CommandHandler مع استثناء واحد: هناك حاجة إلى هذا النوع العام لخدعة أخرى ، والتي سنرى بعض الشيء أكثر من ذلك.
تنفيذ المعالج
معالجات ، أعلنا ، ما هو تنفيذها؟

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

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

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

لنبدأ مع التحقق من الصحة. نعلن ديكور. يأتي مُصادقون IEnumerable من النوع T إلى مُنشئ هذا الديكور ، وننفذهم جميعًا ، ونتحقق من فشل التحقق من الصحة ونوع الإرجاع هو
IEnumerable<validationresult>
، ثم يمكننا إرجاعه لأن الأنواع تتطابق. وإذا كان الأمر كذلك ، فهناك بعض العناصر الأخرى ، لذا يجب عليك رمي استثناء ، لأنه لا توجد نتيجة هنا ، أي نوع آخر من القيمة المرجعة.

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

الآن ، إذا استثنينا مثل هذه الفئة ، فغالبًا ما قد يبدو الأمر كهذا. على الأقل الرمز الذي أراه في العمل اليومي.

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

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

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

بعد هذه التغييرات ، بدلاً من الأنواع البدائية ، تظهر الأنواع المتخصصة هنا: الهوية والبريد الإلكتروني. وبعد الانتهاء من هذه ModelBinder و deserializer المحدثة ، نحن نعرف بالتأكيد أن هذه القيم صحيحة ، بما في ذلك أن هذه القيم موجودة في قاعدة البيانات. "الثوابت"

النقطة التالية التي أود أن أتطرق إليها هي حالة المتغيرين في الفصل ، لأنه في كثير من الأحيان يتم استخدام
نموذج فقر الدم ، حيث يوجد فقط فئة ، كثير من المستوطنين ، من غير الواضح تمامًا كيف ينبغي أن يعملوا معًا. نحن نعمل وفق منطق تجاري معقد ، لذلك من المهم بالنسبة لنا أن تكون الشفرة ذاتية التوثيق. بدلاً من ذلك ، من الأفضل الإعلان عن المُنشئ الحقيقي مع أنه فارغ بالنسبة إلى ORM ، ويمكن إعلان حمايته بحيث لا يمكن للمبرمجين في رمز التطبيق الخاص بهم الاتصال به ، ويمكن لـ ORM. هنا لا نمرر النوع البدائي ، ولكن نوع البريد الإلكتروني ، هو بالفعل صحيح بشكل صحيح ، إذا كان لاغيا ، فإننا لا نزال نلقي استثناء. يمكنك استخدام بعض Fody ، PostSharp ، لكن C # 8. ستصدر قريبًا ، وبناءً على ذلك ، سيكون هناك نوع مرجعي غير قابل للإلغاء ، ومن الأفضل انتظار دعمه باللغة. في اللحظة التالية ، إذا أردنا تغيير الاسم واللقب ، فمن المرجح أننا نريد تغييرهما معًا ، لذلك يجب أن يكون هناك طريقة عامة مناسبة لتغييرها معًا.

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

علاوة على ذلك ، يمكن إعادة استخدام هذه السمات في Dto. الآن ، إذا كنت أرغب في تغيير الاسم واللقب ، فقد يكون لدي أمر تغيير. هل يستحق إضافة مُنشئ خاص هنا؟ يبدو أن يستحق كل هذا العناء. سوف يصبح أفضل ، لا أحد سيغير هذه القيم ، لن يكسرها ، سيكون على حق تماماً.

في الواقع ليس حقا. والحقيقة هي أن Dto ليست كائنات حقا على الإطلاق. هذا مثل هذا القاموس الذي نضع فيه البيانات غير المحولة. أي أنهم يتظاهرون بأنهم كائنات ، بالطبع ، لكن لديهم مسؤولية واحدة فقط - ألا وهي التسلسل وإزالة التسلسل. إذا حاولنا محاربة هذا الهيكل ، فسنبدأ في الإعلان عن بعض ModelBinders مع المصممين ، لفعل شيء من هذا القبيل متعب بشكل لا يصدق ، والأهم من ذلك ، سوف ينفصل عن الإصدارات الجديدة من الأطر الجديدة. كل هذا وصفه جيدًا مارك سيمون في مقالته
"على حدود البرنامج ليست موجهة نحو الكائن" ، إذا كان من المثير للاهتمام ، فمن الأفضل أن تقرأ منصبه ، حيث يتم وصفه بالتفصيل.

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

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

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

سأعرض تسلسل Bogard مع تغيير صغير واحد ، لكن مهم. بدلاً من إلقاء الأحداث ، يمكننا إعلان بعض القوائم ، وفي تلك الأماكن التي يجب أن يحدث فيها نوع من رد الفعل ، مباشرة داخل الكيان لإنقاذ هذه الأحداث. في هذه الحالة ، يعد getter هذا
email
أيضًا فئة مستخدم ، وهذه الفئة ، لا تتظاهر هذه الخاصية بأنها خاصية تحتوي على
email
وأداة تسوية تلقائية ، ولكنها تضيف شيئًا ما إلى ذلك. وهذا هو ، التغليف الحقيقي ، وليس الألفاظ النابية. عند التغيير ، نتحقق من اختلاف البريد الإلكتروني ورمي حدثًا. لم يصل هذا الحدث إلى أي مكان حتى الآن ؛ فنحن نضعه في قائمة الكيانات الداخلية فقط.

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

هنا هي أصغر ، ولكن النقطة المهمة. تستخدم مقالة Jimmy تحميلًا زائدًا لأسلوب SaveChanges ، ومن الأفضل عدم ذلك. ومن الأفضل القيام بذلك في الديكور ، لأنه إذا قمنا بزيادة التحميل على طريقة SaveChanges واحتجنا إلى dbContext في Handler ، فسنحصل على تبعيات دائرية. يمكنك العمل مع هذا ، لكن الحلول أقل ملاءمة وأقل جمالًا. لذلك ، إذا كان خط الأنابيب مبنيًا على الديكور ، فلن أرى أي سبب للقيام بذلك بطريقة مختلفة.
تسجيل و التنميط

بقي تداخل الكود ، ولكن في المثال الأولي ، استخدمنا MiniProfiler أولاً ، ثم جرب catch ، ثم if. المجموع كان هناك ثلاثة مستويات من التعشيش ، والآن كل هذا المستوى من التعشيش هو في ديكوره الخاص. وداخل الديكور ، المسؤول عن التنميط ، لدينا مستوى واحد فقط من التعشيش ، تتم قراءة الرمز بشكل مثالي. بالإضافة إلى ذلك ، من الواضح أنه في هذه الديكورات هناك مسؤولية واحدة فقط. إذا كان المصمم مسؤولاً عن التسجيل ، فسوف يقوم بتسجيل الدخول فقط ، إذا كان الملف الشخصي ، على التوالي ، فقط الملف الشخصي ، كل شيء آخر موجود في أماكن أخرى.
استجابة
بعد أن يعمل خط الأنابيب بالكامل ، يمكننا فقط نقل Dto وإرساله إلى المستعرض بشكل أكبر ، إجراء تسلسل JSON.

لكن هناك شيئًا صغيرًا آخر ، وهو شيء يُنسى أحيانًا: في كل مرحلة ، يمكن أن يحدث استثناء هنا ، وتحتاج في الواقع إلى التعامل معه بطريقة أو بأخرى.

لا يسعني إلا أن أذكر سكوت فلاشين وتقريره
"البرمجة الموجهة للسكك الحديدية" هنا مرة أخرى. لماذا؟ إن التقرير الأصلي مخصص بالكامل للتعامل مع الأخطاء في لغة F # ، وكيفية تنظيم التدفق بشكل مختلف قليلاً ، ولماذا قد يكون هذا النهج مفضلاً لاستخدام Exception'ov. في F # ، يعمل هذا بشكل جيد للغاية ، لأن F # لغة وظيفية ، ويستخدم Scott وظيفة اللغة الوظيفية.

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

بدلاً من التصريح بالأخطاء العامة التي قد لا يقوم شخص ما بالتحقق منها في الرمز ، يتم استخدام مطابقة النمط. مرة أخرى ، في لغة F # ، ستكون لغة مطابقة للنمط مدمجة ، في لغة C # ، علينا أن نكتب طريقة منفصلة سنعمل بها على وظيفة واحدة تعرف ماذا تفعل بالنتيجة الناجحة للعملية ، وكيفية تحويلها إلى أسفل السلسلة ، وذلك مع وجود خطأ. وهذا هو ، بغض النظر عن الفرع الذي يعمل بالنسبة لنا ، يجب علينا أن يلقي هذا إلى نتيجة واحدة عاد. في F # ، كل هذا يعمل بشكل جيد للغاية ، لأن هناك تكوين وظيفي ، جيدًا ، وكل شيء آخر قمت بإدراجه بالفعل. في .NET ، يعمل هذا بشكل أسوأ بعض الشيء ، لأنه بمجرد حصولك على أكثر من نتيجة واحدة ، ولكن الكثير - وكل طريقة تقريبًا قد تفشل لسبب أو لآخر - تصبح جميع أنواع الوظائف الناتجة تقريبًا أنواع نتائج ، وتحتاج إليها للجمع بين شيء ما.

أسهل طريقة لدمجها هي
استخدام LINQ ، لأنه في الواقع لا يعمل LINQ فقط مع IEnumerable ، إذا قمت بإعادة تعريف أساليب SelectMany و Select بالطريقة الصحيحة ، فسوف يرى المترجم C # أنه يمكنك استخدام بناء جملة LINQ لهذه الأنواع. بشكل عام ، يتضح تتبع الورق باستخدام Haskell do-تدوين أو بنفس تعبيرات الحساب في F #. كيف ينبغي قراءة هذا؟ هنا لدينا ثلاث نتائج للعملية ، وإذا كان كل شيء على ما يرام هناك في جميع الحالات الثلاث ، فاتخذ هذه النتائج r1 + r2 + r3 وأضفها. سيكون نوع القيمة الناتجة هو النتيجة ، ولكن النتيجة الجديدة ، التي نعلنها في تحديد. بشكل عام ، هذا هو حتى نهج العمل ، إن لم يكن واحد ولكن.

بالنسبة إلى جميع المطورين الآخرين ، بمجرد البدء في كتابة هذه الشفرة في C # ، تبدأ في الظهور بمظهر من هذا القبيل. "هذه استثناءات مخيفة سيئة ، لا تكتب لهم! هم الشر! أفضل رمز الكتابة التي لا أحد يفهم ولا يمكن تصحيح! "

C # ليس F # ، إنه مختلف إلى حد ما ، لا توجد مفاهيم مختلفة على أساس ذلك يتم ذلك ، وعندما نحاول سحب بومة على الكرة الأرضية ، كما يبدو ، بعبارة ملطفة ، غير عادية.

بدلاً من ذلك ، يمكنك استخدام
الأدوات العادية المضمنة التي تم توثيقها ، والتي يعرفها الجميع والتي لن تسبب تنافرًا إدراكيًا بين المطورين. يحتوي ASP.NET على استثناء معالج عالمي.

نحن نعلم أنه في حالة وجود أي مشاكل في عملية التحقق من الصحة ، يلزمك إعادة الكود 400 أو 422 (الكيان غير القابل للمعالجة). إذا كانت هناك مشكلة في المصادقة والترخيص ، فهناك 401 و 403. إذا حدث خطأ ما ، حدث خطأ ما. وإذا حدث خطأ ما وتريد أن تخبر المستخدم تمامًا عن نوع الاستثناء الذي حددته ، فقل أنه IHasUserMessage ، وأعلن عن رسالة في هذه الواجهة وفحصها فقط: إذا تم تنفيذ هذه الواجهة ، فيمكنك التقاط رسالة من استثناء وتمريرها في JSON للمستخدم. إذا لم يتم تنفيذ هذه الواجهة ، فهذا يعني أن هناك خطأ ما في النظام ، ونقول للمستخدمين ببساطة أن هناك خطأ ما ، فنحن نفعله بالفعل ، كما نعلم جميعًا - تمامًا ، كالمعتاد.
خط أنابيب الاستعلام
نختتم هذا بالفرق وننظر إلى ما لدينا في مجموعة القراءة. بالنسبة للطلب ، التحقق من الصحة ، الاستجابة مباشرةً - هذا هو نفس الشيء ، لن نتوقف بشكل منفصل. ربما لا يزال هناك ذاكرة تخزين مؤقت إضافية ، ولكن بشكل عام لا توجد مشكلات كبيرة في ذاكرة التخزين المؤقت أيضًا.
أمن
دعونا ننظر بشكل أفضل في فحص أمني. قد يكون هناك أيضًا مصمم الأمان نفسه ، والذي يتحقق مما إذا كان يمكن تقديم هذا الطلب أم لا:

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

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

إذا كتبنا في C # ، فمن الأرجح أننا نستخدم LINQ ، وإذا لم يكن هناك أي متطلبات أداء وحشية فقط ، وإذا كانت هناك متطلبات ، فقد لا يكون لديك طلب شركة. بشكل عام ، يمكن حل هذه المشكلة مرة واحدة وإلى الأبد باستخدام LinqQueryHandler. إليك قيدًا مخيفًا جدًا على المستوى العام: هذا هو Query ، الذي يُرجع قائمة الإسقاطات ، ولا يزال بإمكانه تصفية هذه الإسقاطات وفرز هذه الإسقاطات. تعمل أيضًا مع بعض أنواع الكيانات فقط وتعرف كيفية تحويل هذه الكيانات إلى إسقاطات وإرجاع قائمة هذه الإسقاطات في شكل Dto إلى المستعرض.

يمكن أن يكون تنفيذ طريقة المقبض بسيطًا جدًا. فقط في حالة ، تحقق مما إذا كان مرشح TQuery هذا ينفذ للكيان الأصلي. علاوة على ذلك نقوم بعمل إسقاط ، وهو امتداد قابل للاستعلام AutoMapper'a. إذا كان شخص ما زال لا يعرف ، فيمكن لـ AutoMapper إنشاء إسقاطات في LINQ ، أي تلك التي ستنشئ طريقة Select ، ولن تقوم بتعيينها في الذاكرة.
ثم نطبق التصفية والفرز وعرضها كلها في المتصفح. , DotNext, ,
, , , , expression' , .
SQL
. , DotNext', — SQL. Select , , , queryable- .

, . , Title, Title , . , . SubTitle, , , - , queryable- . , .
, . , , . , , . «JsonIgnore», . , , Dto. , , . JSON, , Created LastUpdated , SubTitle — , . , , , , , . , - .

. , -, , . , pipeline, . — , , . , SaveChanges, Query SaveChanges. , , , NuGet, .
. , - , , . , , , , , — . , , : « », — . .
, ?

- . .

, , , . MediatR , . , , — , MediatR pipeline behaviour. , Request/Response, RequestHandler' . Simple Injector, — .

, , , , TIn: ICommand.

Simple Injector' constraint' . , , , constraint', Handler', constraint. , constraint ICommand, SaveChanges constraint' ICommand, Simple Injector , constraint' , Handler'. , , , .
? Simple Injector MeriatR — , , Autofac', -, , , . , .
,
, «».

, «Clean architecture». .

- - , MVC, , .

, , , Angular, , , , . , : « — MVC-», : « Features, : , Blog - Import, - ».
, , , , MVC-, , - , . MVC . , , — . .


- , - -, .
-, , . , . , - , User Service, pull request', , User Service , . , - , - , . - , .
. , . , , , . , , , , , , , - . , ( , ), , «Delete»: , , . .
— «», , , , . , : , , , . , . , , . , , .
: . « », : , , . , , , , , , , . , . , - pull request , — , — - , . VCS : - , ? , - , , .

, , , . : . , . , , , , . , , , . , , . « », , . , , — , , .
: , - , . . - , , , , . - , - , , , , . .

. , IHandler . .
IHandler ICommandHandler IQueryHandler , . , , . , CommandHandler, CommandHandler', .
لماذا هذا , Query , Query — . , , , Hander, CommandHandler QueryHandler, - use case, .
— , , , , : , .
, . , . , -.
C# 8, nullable reference type . , , , , .
ChangeTracker' ORM.
Exception' — , F#, C#. , - , - , . , , Exception', , LINQ, , , , , , Dapper - , , , .NET.
, LINQ, , permission' — . , , - , , . , — .
. :
- Vertical Slices
- Domain Events
- DDD
- ROP
- LINQ Expressions:
- Clean Architecture

— . . — «Domain Modeling Made Functional», F#, F#, , , , , . C# , , Exception'.
, , — «Entity Framework Core In Action». , Entity Framework, , DDD ORM, , ORM DDD .
دقيقة من الإعلانات. 15-16 2019 .NET- DotNext Piter, . , .