دعنا نذهب بالترتيب. وفورًا إخلاء بسيط للمسؤولية: تمت كتابة المقالة بناءً على خطابي في Ya Subbotnik Pro لمطوري الواجهة الأمامية. إذا كنت مطورًا للواجهة الخلفية ، فقد لا تكتشف أي شيء جديد لنفسك. سأحاول هنا تلخيص تجربتي في الواجهة الأمامية في مؤسسة كبيرة ، وشرح لماذا وكيف نستخدم Node.js.
دعنا نحدد ما سنعتبره واجهة أمامية في هذه المقالة. دعونا نضع الخلافات حول المهام جانبًا ونركز على الجوهر.
الواجهة الأمامية هي جزء من التطبيق المسؤول عن العرض. يمكن أن يكون مختلفًا: متصفح ، سطح مكتب ، هاتف محمول. ولكن هناك دائمًا ميزة مهمة - الواجهة الأمامية تحتاج إلى بيانات. بدون الخلفية التي توفر هذه البيانات ، لا فائدة منها. هنا حدود واضحة إلى حد ما. تعرف الواجهة الخلفية كيفية الانتقال إلى قواعد البيانات ، وتطبيق قواعد العمل على البيانات المستلمة وإعطاء النتيجة للواجهة الأمامية ، والتي ستتلقى البيانات ، وتضع نموذجًا لها وتضفي جمالًا على المستخدم.
يمكننا القول أنه من الناحية المفاهيمية ، تحتاج الواجهة الخلفية إلى الواجهة الخلفية لتلقي البيانات وحفظها. مثال: موقع حديث نموذجي بهيكل خادم عميل. يقرع العميل في المتصفح (لنسميه ضعيفًا لن تتحول اللغة بعد الآن) على الخادم حيث تعمل الواجهة الخلفية. وبالطبع هناك استثناءات في كل مكان. هناك تطبيقات متصفح معقدة لا تحتاج إلى خادم (لن نأخذ في الاعتبار هذه الحالة) ، وهناك حاجة إلى تنفيذ واجهة أمامية على الخادم - ما يسمى بالعرض الجانبي للخادم أو SSR. لنبدأ به ، لأن هذه أبسط حالة وأكثرها فهمًا.
SSR
يبدو العالم المثالي للواجهة الخلفية كما يلي: تصل طلبات HTTP مع البيانات إلى مدخلات التطبيق ، وعند الإخراج لدينا استجابة ببيانات جديدة بتنسيق مناسب. على سبيل المثال JSON. من السهل اختبار واجهات برمجة تطبيقات HTTP وفهم كيفية تطويرها. ومع ذلك ، فإن الحياة تجري تعديلات: أحيانًا لا تكفي واجهة برمجة التطبيقات وحدها.
يجب أن يستجيب الخادم باستخدام HTML جاهز لإطعامه إلى زاحف محرك البحث ، أو تقديم معاينة بعلامات وصفية لإدراجها في الشبكة الاجتماعية ، أو الأهم من ذلك ، تسريع الاستجابة على الأجهزة الضعيفة. تمامًا كما في العصور القديمة عندما قمنا بتطوير Web 2.0 في PHP.
كل شيء مألوف وقد تم وصفه لفترة طويلة ، لكن العميل قد تغير - فقد وصلت محركات القوالب من جانب العميل إليه. في الويب الحديث ، تحكم JSX الكرة ، ويمكن مناقشة مزاياها وعيوبها لفترة طويلة ، ولكن لا يمكن رفض شيء واحد - في عرض الخادم ، لا يمكنك الاستغناء عن كود JavaScript.
اتضح عندما تحتاج إلى تنفيذ SSR عن طريق التطوير الخلفي:
- مجالات المسؤولية مختلطة. بدأ مبرمجو الواجهة الخلفية في تولي مسؤولية العرض.
- اللغات مختلطة. يبدأ مبرمجو الواجهة الخلفية باستخدام JavaScript.
المخرج هو فصل SSR عن الواجهة الخلفية. في أبسط الحالات ، نأخذ وقت تشغيل JavaScript ، ونضع عليه حلاً مكتوبًا ذاتيًا أو إطار عمل (Next ، Nuxt ، إلخ) يعمل مع محرك قالب JavaScript الذي نحتاجه ، ونمرر حركة المرور خلاله. نمط مألوف في العالم الحديث.
لذلك سمحنا بالفعل لمطوري الواجهة الأمامية بالخادم قليلاً. دعنا ننتقل إلى قضية أكثر أهمية.
جار استقبال البيانات
الحل الشائع هو إنشاء واجهات برمجة تطبيقات عامة. غالبًا ما يتم أخذ هذا الدور بواسطة API Gateway ، القادرة على استطلاع مجموعة متنوعة من الخدمات المصغرة. ومع ذلك ، تظهر المشاكل هنا أيضًا.
أولاً ، مشكلة الفرق ومجالات المسؤولية. تم تطوير تطبيق حديث كبير بواسطة العديد من الفرق. يركز كل فريق على مجال العمل الخاص به ، ولديه خدماته المصغرة الخاصة (أو حتى عدة) على الواجهة الخلفية وشاشات العرض الخاصة به على العميل. لن نتطرق إلى مشكلة الواجهات المصغرة والوحدات النمطية ، فهذا موضوع معقد منفصل. افترض أن وجهات نظر العميل منفصلة تمامًا وهي mini-SPA (تطبيق صفحة واحدة) داخل موقع واحد كبير.
كل فريق لديه مطورو الواجهة الأمامية والخلفية. يعمل الجميع على تطبيقهم الخاص. يمكن أن تكون بوابة API حجر عثرة. من المسؤول عن ذلك؟ من سيضيف نقاط نهاية جديدة؟ فريق فائق مخصص لواجهة برمجة تطبيقات سيكون مشغولًا دائمًا في حل المشكلات لأي شخص آخر في المشروع؟ كم ستكون تكلفة الخطأ؟ سيؤدي سقوط هذه البوابة إلى انهيار النظام بأكمله.
ثانياً ، مشكلة البيانات الزائدة / غير الكافية. دعونا نلقي نظرة على ما يحدث عندما تستخدم واجهتان مختلفتان نفس واجهة برمجة التطبيقات العامة.
هاتان الواجهتان مختلفتان تمامًا. يحتاجون إلى مجموعات بيانات مختلفة ، ولديهم دورات إصدار مختلفة. تنوع إصدارات الواجهة الأمامية للجوال هو الحد الأقصى ، لذلك نحن مضطرون لتصميم واجهة برمجة التطبيقات مع أقصى قدر من التوافق مع الإصدارات السابقة تنوع عميل الويب منخفض ، في الواقع نحتاج فقط إلى دعم إصدار واحد سابق لتقليل عدد الأخطاء في وقت الإصدار. ولكن حتى إذا كانت واجهة برمجة التطبيقات "العامة" تخدم عملاء الويب فقط ، فإننا لا نزال نواجه مشكلة البيانات الزائدة عن الحاجة أو غير الكافية.
يتطلب كل تعيين مجموعة بيانات منفصلة ، والتي يمكن استردادها باستخدام استعلام واحد مثالي.
في هذه الحالة ، لن تعمل واجهة برمجة التطبيقات العالمية بالنسبة لنا ، سيتعين علينا فصل الواجهات. هذا يعني أنك بحاجة إلى بوابة API الخاصة بك لكل منهانهاية المقدمة. تشير كلمة "كل" هنا إلى تعيين فريد يعمل على مجموعة البيانات الخاصة به.
يمكننا أن نعهد بإنشاء مثل هذا API إلى مطور الواجهة الخلفية الذي سيتعين عليه العمل مع الواجهة الأمامية وتنفيذ رغباته ، أو ، وهو الأمر الأكثر إثارة للاهتمام والأكثر كفاءة من نواح كثيرة ، إعطاء تنفيذ API لفريق الواجهة الأمامية. سيؤدي هذا إلى إزالة الصداع بسبب تنفيذ SSR: لم تعد بحاجة إلى تثبيت طبقة تقرع واجهة برمجة التطبيقات ، سيتم دمج كل شيء في تطبيق خادم واحد. بالإضافة إلى ذلك ، من خلال التحكم في SSR ، يمكننا وضع جميع البيانات الأولية اللازمة على الصفحة في وقت التقديم ، دون تقديم طلبات إضافية إلى الخادم.
هذه البنية تسمى Backend For Frontend أو BFF. الفكرة بسيطة: يظهر تطبيق جديد على الخادم يستمع لطلبات العميل ، ويستقصي الخلفيات ويعيد الاستجابة المثلى. وبالطبع يتم التحكم في هذا التطبيق من قبل مطور الواجهة الأمامية.
أكثر من خادم في الخلفية؟ لا مشكلة!
بغض النظر عما يفضله تطوير الواجهة الخلفية لبروتوكول الاتصال ، يمكننا استخدام أي طريقة ملائمة للتواصل مع عميل الويب. REST ، RPC ، GraphQL - نختار أنفسنا.
لكن أليست GraphQL في حد ذاتها هي الحل لمشكلة الحصول على البيانات في استعلام واحد؟ ربما لا تحتاج إلى سياج أي خدمات وسيطة؟
لسوء الحظ ، فإن العمل الفعال مع GraphQL مستحيل بدون التعاون الوثيق مع مطوري الواجهة الخلفية الذين يتولون تطوير استعلامات قاعدة بيانات فعالة. باختيار مثل هذا الحل ، سنفقد السيطرة مرة أخرى على البيانات ونعود إلى حيث بدأنا.
هذا ممكن بالطبع ، لكنه ليس مثيرًا للاهتمام (للواجهة الأمامية)
حسنًا ، دعنا نطبق BFF. بالطبع ، في Node.js. لماذا ا؟ نحتاج إلى لغة واحدة على العميل والخادم لإعادة استخدام تجربة مطوري الواجهة الأمامية وجافا سكريبت للعمل مع القوالب. ماذا عن بيئات وقت التشغيل الأخرى؟
GraalVM والحلول الغريبة الأخرى أدنى من V8 في الأداء وهي محددة جدًا. دينو لا تزال تجربة ولا تستخدم في الإنتاج.
ولحظة واحدة. Node.js هو حل جيد بشكل مدهش لتطبيق بوابة API. تسمح بنية العقدة بمترجم JavaScript أحادي الخيط مدمجًا مع libuv ، وهي مكتبة إدخال / إخراج غير متزامنة تستخدم بدورها تجمع خيوط.
ضربت الحسابات الطويلة على جانب JavaScript في أداء النظام. يمكنك التغلب على هذا: تشغيلهم في عمال منفصلين أو نقلهم إلى مستوى الوحدات الثنائية الأصلية.
ولكن في حالتها الأساسية ، فإن Node.js غير مناسب للعمليات كثيفة الاستخدام لوحدة المعالجة المركزية ، وفي نفس الوقت تعمل بشكل رائع مع الإدخال / الإخراج غير المتزامن ، مما يوفر أداءً عاليًا. أي أننا نحصل على نظام يمكنه دائمًا الاستجابة بسرعة للمستخدم ، بغض النظرحول مدى انشغال الواجهة الخلفية. يمكنك التعامل مع هذا الموقف عن طريق إخطار المستخدم على الفور لانتظار انتهاء العملية.
مكان تخزين منطق الأعمال
يحتوي نظامنا الآن على ثلاثة أجزاء كبيرة: الواجهة الخلفية والواجهة الأمامية و BFF بينهما. يطرح سؤال معقول (للمهندس المعماري): أين يتم الاحتفاظ بمنطق العمل؟
بالطبع ، لا يريد المهندس المعماري تشويه قواعد العمل عبر جميع طبقات النظام ؛ يجب أن يكون هناك مصدر واحد للحقيقة. وهذا المصدر هو الخلفية. في أي مكان آخر لتخزين السياسات عالية المستوى ، إن لم يكن في جزء النظام الأقرب إلى البيانات؟
لكن في الواقع ، هذا لا يعمل دائمًا. على سبيل المثال ، تأتي مشكلة العمل والتي يمكن تنفيذها بكفاءة وسرعة على مستوى BFF. التصميم المثالي للنظام رائع ، لكن الوقت هو المال. في بعض الأحيان يتعين عليك التضحية بنظافة العمارة ، وتبدأ الطبقات في التسرب.
هل يمكننا الحصول على الهندسة المعمارية المثالية عن طريق التخلي عن BFF لصالح خلفية Node.js "كاملة"؟ يبدو أنه في هذه الحالة لن يكون هناك تسريبات.
ليست حقيقة. ستكون هناك قواعد عمل ، إذا تم نقلها إلى الخادم ، فستؤثر على استجابة الواجهة. يمكنك مقاومة ذلك حتى النهاية ، ولكن على الأرجح لن تتمكن من تجنبه تمامًا. سوف يخترق منطق مستوى التطبيق العميل أيضًا: في SPA الحديث يتم تلطيخه بين العميل والخادم ، حتى في حالة وجود BFF.
بغض النظر عن مدى صعوبة المحاولة ، فإن منطق الأعمال سوف يتسلل إلى بوابة API على Node.js. دعنا نصلح هذا الاستنتاج وننتقل إلى التطبيق الأكثر لذة!
كرة كبيرة من الطين
الحل الأكثر شيوعًا لتطبيقات Node.js في السنوات الأخيرة هو Express. مُثبت ، لكنه منخفض جدًا ولا يقدم مناهج معمارية جيدة. النمط الرئيسي هو الوسيطة. تطبيق نموذجي في Express مثل كتلة كبيرة من الوحل (لا يطلق على الأسماء ، بل ونمط مضاد ).
const express = require('express');
const app = express();
const {createReadStream} = require('fs');
const path = require('path');
const Joi = require('joi');
app.use(express.json());
const schema = {id: Joi.number().required() };
app.get('/example/:id', (req, res) => {
const result = Joi.validate(req.params, schema);
if (result.error) {
res.status(400).send(result.error.toString()).end();
return;
}
const stream = createReadStream( path.join('..', path.sep, `example${req.params.id}.js`));
stream
.on('open', () => {stream.pipe(res)})
.on('error', (error) => {res.end(error.toString())})
});
يتم خلط جميع الطبقات ، يوجد في ملف واحد وحدة تحكم ، حيث يوجد كل شيء: منطق البنية التحتية ، والتحقق من الصحة ، ومنطق الأعمال. من المؤلم العمل مع هذا ، فأنت لا تريد الاحتفاظ بهذا الرمز. هل يمكننا كتابة رمز على مستوى المؤسسة في Node.js؟
يتطلب هذا قاعدة بيانات يسهل صيانتها وتطويرها. بمعنى آخر ، أنت بحاجة إلى الهندسة المعمارية.
بنية تطبيق Node.js (أخيرًا)
"الهدف من هندسة البرمجيات هو تقليل الجهد البشري المتضمن في بناء وصيانة النظام."
روبرت "العم بوب" مارتن
تتكون العمارة من شيئين مهمين: الطبقات والوصلات بينها. يجب علينا تقسيم تطبيقنا إلى طبقات ، ومنع التسرب من واحد إلى آخر ، وتنظيم التسلسل الهرمي للطبقات والروابط بينها بشكل صحيح.
طبقات
كيف أقوم بتقسيم طلبي إلى طبقات؟ هناك نهج كلاسيكي ثلاثي المستويات: البيانات ، والمنطق ، والعرض التقديمي.
يعتبر هذا النهج الآن عفا عليه الزمن. تكمن المشكلة في أن البيانات هي الأساس ، مما يعني أن التطبيق مصمم اعتمادًا على كيفية تقديم البيانات في قاعدة البيانات ، وليس على العمليات التجارية التي يشاركون فيها.
يفترض النهج الأكثر حداثة أن التطبيق يحتوي على طبقة مجال مخصصة تعمل مع منطق الأعمال وتمثل عمليات تجارية حقيقية في التعليمات البرمجية. ومع ذلك ، إذا انتقلنا إلى العمل الكلاسيكي لتصميم إريك إيفانز المستند إلى المجال ، فسنجد هناك مخطط طبقة التطبيق التالي:
ما الخطأ هنا؟ يبدو أن أساس تطبيق DDD المصمم يجب أن يكون مجالًا - سياسات عالية المستوى ، المنطق الأكثر أهمية وقيمة. ولكن تحت هذه الطبقة تكمن البنية التحتية بأكملها: طبقة الوصول إلى البيانات (DAL) ، والتسجيل ، والمراقبة ، وما إلى ذلك ، أي سياسات ذات مستوى أقل بكثير وأقل أهمية.
تحتل البنية التحتية مركزًا في التطبيق ، ويمكن أن يؤدي الاستبدال العادي للمسجل إلى تغيير منطق الأعمال بالكامل.
إذا لجأنا إلى Robert Martin مرة أخرى ، نجد أنه في كتاب Clean Architecture ، يفترض تسلسلًا هرميًا مختلفًا للطبقة في التطبيق ، مع وجود المجال في المركز.
وفقًا لذلك ، يجب ترتيب الطبقات الأربع بشكل مختلف:
لقد اخترنا الطبقات وحددنا تسلسلها الهرمي. الآن دعنا ننتقل إلى الاتصالات.
روابط
دعنا نعود إلى المثال باستدعاء منطق المستخدم. كيف تتخلص من الاعتماد المباشر على البنية التحتية لضمان التسلسل الهرمي الصحيح للطبقة؟ هناك طريقة بسيطة ومعروفة لعكس التبعيات - الواجهات.
الآن لا تعتمد UserEntity عالية المستوى على المسجل ذو المستوى المنخفض. على العكس من ذلك ، فإنه يفرض العقد الذي يجب تنفيذه من أجل تضمين المسجل في النظام. يأتي استبدال المسجل في هذه الحالة لربط تنفيذ جديد يحترم نفس العقد. السؤال المهم هو كيفية توصيله؟
import {Logger} from ‘../core/logger’;
class UserEntity {
private _logger: Logger;
constructor() {
this._logger = new Logger();
}
...
}
...
const UserEntity = new UserEntity();
الطبقات متصلة بشكل صارم. هناك ارتباط لهيكل الملف والتنفيذ. نحتاج إلى انعكاس التبعية ، وهو ما سنفعله باستخدام حقن التبعية.
export class UserEntity {
constructor(private _logger: ILogger) { }
...
}
...
const logger = new Logger();
const UserEntity = new UserEntity(logger);
الآن لا يعرف UserEntity "المجال" شيئًا أكثر عن تنفيذ أداة التسجيل. يوفر عقدًا ويتوقع أن يتوافق التنفيذ مع هذا العقد.
بطبيعة الحال ، فإن إنشاء مثيلات كيانات البنية التحتية يدويًا ليس هو الشيء الأكثر متعة. نحتاج إلى ملف جذر نعد فيه كل شيء ، وعلينا بطريقة ما سحب المثيل الذي تم إنشاؤه للمسجل عبر التطبيق بأكمله (من المفيد أن يكون لديك واحد ، وليس إنشاء الكثير). متعب. وهذا هو المكان الذي تلعب فيه حاويات IoC ويمكن أن تتولى هذا العمل.
كيف يمكن أن يبدو استخدام الحاوية؟ على سبيل المثال ، مثل هذا:
export class UserEntity {
constructor(@Inject(LOGGER) private readonly _logger: ILogger){ }
}
ماذا يحدث هنا؟ استخدمنا سحر المصممين وكتبنا التعليمات: "عند إنشاء مثيل UserEntity ، قم بحقن مثيل للكيان الموجود في حاوية IoC تحت رمز LOGGER في حقله الخاص _logger. من المتوقع أن يتوافق مع واجهة ILogger. " وبعد ذلك ستقوم حاوية IoC بعمل كل شيء بمفردها.
لقد اخترنا الطبقات ، وقررنا كيف سنقوم بفكها. حان الوقت لاختيار إطار العمل.
الأطر والعمارة
السؤال بسيط: من خلال ترك Express إلى إطار حديث ، هل سنحصل على بنية جيدة؟ دعنا نلقي نظرة على Nest:
- مكتوبة في TypeScript ،
- مبني فوق Express / Fastify ، هناك توافق على مستوى البرامج الوسيطة ،
- يعلن نمطية المنطق ،
- يوفر حاوية IoC.
يبدو أن لديها كل ما نحتاجه هنا! لقد تركوا أيضًا مفهوم التطبيق كسلاسل وسيطة. لكن ماذا عن العمارة الجيدة؟
حقن التبعية في العش
دعنا نحاول اتباع التعليمات . نظرًا لأنه يتم تطبيق مصطلح "كيان" في Nest عادةً على ORM ، أعد تسمية UserEntity إلى UserService. يتم توفير المُسجِّل بواسطة إطار العمل ، لذلك سنقوم بحقن FooService مجردة بدلاً من ذلك.
import {FooService} from ‘../services/foo.service’;
@Injectable()
export class UserService {
constructor(
private readonly _fooService: FooService
){ }
}
و ... يبدو أننا اتخذنا خطوة إلى الوراء! هناك حقنة ، لكن لا انعكاس ، التبعية
تهدف إلى التنفيذ وليس التجريد.
دعنا نحاول إصلاحه. الخيار رقم واحد:
@Injectable()
export class UserService {
constructor(
private _fooService: AbstractFooService
){ } }
نصف هذه الخدمة المجردة ونصدرها في مكان قريب:
export {AbstractFooService};
تستخدم FooService الآن AbstractFooService. على هذا النحو ، نقوم بتسجيله يدويًا في IoC.
{ provide: AbstractFooService, useClass: FooService }
الخيار الثاني. دعونا نجرب النهج الموصوف سابقًا مع الواجهات. نظرًا لعدم وجود واجهات في JavaScript ، فلن يكون من الممكن بعد الآن سحب الكيان المطلوب من IoC في وقت التشغيل باستخدام الانعكاس. علينا أن نعلن صراحة ما نحتاجه. سنستخدم @ Inject decorator لهذا الغرض.
@Injectable()
export class UserService {
constructor(
@Inject(FOO_SERVICE) private readonly _fooService: IFooService
){ } }
والتسجيل بالرمز المميز:
{ provide: FOO_SERVICE, useClass: FooService }
لقد فزنا بالإطار! لكن بأي ثمن؟ لقد أوقفنا القليل من السكر. هذا مريب ويوحي بأنه لا يجب عليك تجميع التطبيق بأكمله في إطار عمل. إذا لم أقنعك بعد ، فهناك مشاكل أخرى.
استثناءات
يتم وميض العش مع استثناءات. علاوة على ذلك ، يقترح استخدام رمي الاستثناءات لوصف منطق سلوك التطبيق.
هل كل شيء على ما يرام هنا من حيث العمارة؟ دعنا ننتقل إلى النجوم مرة أخرى:
"إذا كان الخطأ هو السلوك المتوقع ، فلا يجب استخدام الاستثناءات."الاستثناءات هي استثناءات. عند كتابة منطق الأعمال ، يجب أن نتجنب طرح الاستثناءات. إذا كان ذلك فقط لسبب عدم ضمان JavaScript أو TypeScript بمعالجة الاستثناء. علاوة على ذلك ، فإنه يحجب تدفق التنفيذ ، نبدأ البرمجة بأسلوب GOTO ، مما يعني أنه أثناء فحص سلوك الكود ، سيتعين على القارئ القفز خلال البرنامج.
مارتن فاولر
هناك قاعدة بسيطة تساعدك على فهم ما إذا كان استخدام الاستثناءات قانونيًا:
"هل سيعمل الرمز إذا قمت بإزالة كافة معالجات الاستثناءات؟" إذا كان الجواب بالنفي ، فربما تستخدم الاستثناءات في ظروف غير استثنائية ".هل من الممكن تجنب ذلك في منطق الأعمال؟ نعم! من الضروري تقليل طرح الاستثناءات ، وإرجاع نتيجة العمليات المعقدة بسهولة ، استخدم إما monad ، الذي يوفر حاوية في حالة نجاح أو خطأ (مفهوم قريب جدًا من الوعد).
المبرمج البراغماتي
const successResult = Result.ok(false);
const failResult = Result.fail(new ConnectionError())
لسوء الحظ ، داخل الكيانات التي توفرها Nest ، غالبًا لا يمكننا التصرف بطريقة أخرى - يتعين علينا طرح استثناءات. هذه هي الطريقة التي يعمل بها إطار العمل ، وهذه ميزة غير سارة للغاية. ومرة أخرى ، السؤال الذي يطرح نفسه: ربما لا يجب عليك تحديث التطبيق بإطار عمل؟ ربما سيكون من الممكن فصل إطار العمل ومنطق العمل في طبقات معمارية مختلفة؟
دعونا تحقق.
كيانات العش والطبقات المعمارية
حقيقة الحياة القاسية: كل شيء نكتبه باستخدام Nest يمكن تكديسه في طبقة واحدة. هذه طبقة التطبيق.
لا نريد أن ندع إطار العمل يتعمق في منطق الأعمال ، بحيث لا ينمو فيه مع الاستثناءات ، والديكورات وحاوية IoC. سيطرح مؤلفو إطار العمل مدى روعة كتابة منطق الأعمال باستخدام السكر ، لكن مهمتهم هي ربطك بأنفسهم إلى الأبد. تذكر أن إطار العمل هو مجرد وسيلة لتنظيم منطق مستوى التطبيق بشكل ملائم ، وربط البنية التحتية وواجهة المستخدم به.
"الإطار هو التفاصيل."
روبرت "العم بوب" مارتن
من الأفضل تصميم تطبيق كمُنشئ يسهل فيه استبدال المكونات. أحد الأمثلة على هذا التطبيق هو البنية السداسية (بنية المنفذ والمحول ). الفكرة مثيرة للاهتمام: نواة المجال مع كل منطق الأعمال يوفر منافذ للتواصل مع العالم الخارجي. كل ما هو مطلوب متصل خارجيًا عبر محولات.
هل من الواقعي تنفيذ مثل هذه البنية في Node.js باستخدام Nest كإطار عمل؟ الى حد كبير. لقد قدمت درسًا بمثال ، إذا كنت مهتمًا - يمكنك العثور عليه هنا .
دعونا نلخص
- Node.js مفيد لـ BFFs. يمكنك العيش معها.
- لا توجد حلول جاهزة.
- الأطر ليست مهمة.
- إذا أصبحت بنيتك معقدة للغاية ، إذا واجهت الكتابة ، فربما تكون قد اخترت الأداة الخاطئة.
أوصي بهذه الكتب:
- روبرت مارتن ، "Clean Architecture" ،
- Vaughn Vernon, Domain-Driven Design Distilled,
- Khalil Stemmler, khalilstemmler.com,
- Martin Fowler, martinfowler.com/architecture.