في هذه المقالة المتأخرة ، سأشرح لماذا ، في رأيي ، في معظم الحالات ، عند تطوير نموذج بيانات لتطبيق ، يجب عليك اتباع نهج قاعدة البيانات أولاً. بدلاً من نهج "Java [أي لغة أخرى] أولاً" ، الذي يأخذك إلى مسار طويل مليء بالألم والمعاناة ، بمجرد أن يبدأ المشروع في النمو.

المرخصة "مشغول للغاية لتكون أفضل" CC المرخصة من Alan O'Rourke / Audience Stack . الصورة الأصلية
هذه المقالة مستوحاة من سؤال StackOverflow الأخير .
مناقشات reddit مثيرة للاهتمام / r / java و / r / البرمجة .
إنشاء التعليمات البرمجية
لدهشتي ، يبدو أن مجموعة صغيرة من المستخدمين قد صدمت من حقيقة أن jOOQ مرتبط بشكل كبير بتوليد شفرة المصدر.
بينما يمكنك استخدام jOOQ تمامًا كما تريد ، فإن الطريقة المفضلة (وفقًا للوثائق) هي البدء بمخطط قاعدة البيانات الحالية ، ثم إنشاء فئات العملاء الضرورية (المطابقة لجداولك) باستخدام jOOQ ، وبعد ذلك من السهل كتابة آمنة من النوع استفسارات عن هذه الجداول:
for (Record2<String, String> record : DSL.using(configuration)
يمكن إنشاء الكود إما يدويًا خارج التجميع ، أو تلقائيًا مع كل تجميع. على سبيل المثال ، يمكن أن يحدث هذا التوليد مباشرة بعد تثبيت عمليات ترحيل Flyway ، والتي يمكن أيضًا أن تبدأ إما يدويًا أو تلقائيًا.
توليد كود المصدر
هناك فلسفات ومزايا وعيوب مختلفة فيما يتعلق بهذه الأساليب لإنشاء التعليمات البرمجية التي لا أريد مناقشتها في هذه المقالة. ولكن من حيث الجوهر ، فإن معنى الكود الذي تم إنشاؤه هو أنه تمثيل جافا لما نعتبره نوعًا من "المعيار" (داخل وخارج نظامنا). بطريقة ما ، يقوم المترجمون بالشيء نفسه عندما ينشئون رمزًا ثانويًا أو رمز الجهاز أو أي رمز مصدر آخر من المصدر - ونتيجة لذلك ، نحصل على فكرة عن "المعيار" لدينا بلغة أخرى محددة.
هناك عدد غير قليل من مولدات الكود. على سبيل المثال ، يمكن لـ XJC إنشاء كود Java من ملفات XSD أو WSDL . المبدأ هو نفسه دائمًا:
- هناك بعض المعايير (الخارجية أو الداخلية) ، مثل المواصفات ، ونموذج البيانات ، وما إلى ذلك.
- من الضروري الحصول على فكرتك الخاصة عن هذا المعيار بلغة البرمجة المعتادة.
وغالبًا ما يكون من المنطقي توليد هذا الرأي لتجنب العمل غير الضروري والأخطاء غير الضرورية.
اكتب الموفرين ومعالجة التعليقات التوضيحية
من الجدير بالذكر أن نهجًا آخر أكثر حداثة لإنشاء التعليمات البرمجية في jOOQ هو موفري النوع ( كما هو الحال في F # ) ، حيث يتم إنشاء الشفرة من قبل المترجم أثناء التجميع ولا توجد أبدًا في النموذج الأصلي. هناك أداة مماثلة (ولكن أقل تعقيدًا) في جافا هي معالجات التعليقات مثل لومبوك .
في كلتا الحالتين ، كل شيء هو نفسه كما هو الحال في إنشاء التعليمات البرمجية العادية ، باستثناء:
- أنت لا ترى الرمز الذي تم إنشاؤه (ربما بالنسبة للعديد من هذا زائد كبير؟)
- يجب أن تتأكد من أن "مرجعك" متاح في كل مجموعة. هذا لا يسبب أي مشاكل في حالة لومبوك ، التي تعلق مباشرة على كود المصدر نفسه ، وهو "المعيار" في هذه الحالة. أكثر تعقيدًا قليلاً مع نماذج قواعد البيانات التي تعتمد على اتصال مباشر دائمًا.
ما هي مشكلة توليد الكود؟
إلى جانب السؤال الصعب حول ما إذا كان سيتم إنشاء الرمز يدويًا أو تلقائيًا ، يعتقد بعض الأشخاص أن الرمز لا يحتاج إلى إنشاؤه على الإطلاق. السبب الذي أسمعه في أغلب الأحيان هو أن هذا الجيل يصعب تنفيذه في خط الأنابيب CI / CD. ونعم ، هذا صحيح ، لأنه نحمل النفقات العامة لإنشاء ودعم بنية تحتية إضافية ، خاصة إذا كنت جديدًا على الأدوات المستخدمة (jOOQ ، JAXB ، Hibernate ، إلخ).
إذا كانت النفقات العامة لدراسة منشئ الشفرة مرتفعة للغاية ، فلن تكون هناك فائدة تذكر. لكن هذه هي الحجة الوحيدة ضد. في معظم الحالات الأخرى ، ليس من المنطقي على الإطلاق كتابة التعليمات البرمجية يدويًا ، وهو التمثيل المعتاد لنموذج لشيء ما.
يدعي الكثير من الناس أنه ليس لديهم الوقت لذلك في الوقت الحالي ، تحتاج إلى طرح MVP آخر في أقرب وقت ممكن. وسيكونون قادرين على إنهاء خط أنابيب CI / CD في وقت لاحق. في مثل هذه الحالات ، عادةً ما أقول ، "أنت مشغول جدًا ولا تتحسن."
"لكن السبات / JPA يجعل تطوير جافا الأول أسهل بكثير."
نعم هذا صحيح. هذا فرح وألم لمستخدمي السبات. باستخدامه ، يمكنك ببساطة كتابة عدة كائنات من النموذج:
@Entity class Book { @Id int id; String title; }
وقد تم ذلك تقريبًا. بعد ذلك ، سيأخذ Hibernate روتين كامل حول كيفية تعريف هذا الكائن في DDL ولهجة SQL المطلوبة:
CREATE TABLE book ( id INTEGER PRIMARY KEY GENERATED ALWAYS AS IDENTITY, title VARCHAR(50), CONSTRAINT pk_book PRIMARY KEY (id) ); CREATE INDEX i_book_title ON book (title);
هذه حقًا طريقة رائعة لبدء التطوير بسرعة - عليك فقط بدء التطبيق.
ولكن ليس كل شيء وردية للغاية. لا يزال هناك العديد من الأسئلة:
- هل سينشئ الإسبات الاسم الذي أحتاجه للمفتاح الأساسي؟
- هل سأقوم بإنشاء الفهرس الذي أحتاجه في حقل TITLE؟
- هل سيتم إنشاء قيمة معرف فريدة لكل سجل؟
لا يبدو ذلك. ولكن أثناء وجود المشروع قيد التطوير ، يمكنك دائمًا التخلص من قاعدة البيانات الحالية وإنشاء كل شيء من البداية بإضافة التعليقات التوضيحية الضرورية إلى النموذج.
لذا ، فإن فئة الكتاب في شكلها النهائي ستبدو كما يلي:
@Entity @Table(name = "book", indexes = { @Index(name = "i_book_title", columnList = "title") }) class Book { @Id @GeneratedValue(strategy = IDENTITY) int id; String title; }
لكنك ستدفع ثمنها بعد ذلك بقليل
عاجلاً أم آجلاً ، يبدأ تطبيقك في الإنتاج ، وسيتوقف المخطط الموضح عن العمل:
في نظام حي وحقيقي ، لم يعد بإمكانك فقط التقاط وإسقاط قاعدة البيانات الخاصة بك ، لأن يتم استخدام البيانات فيه ويمكن أن يكلف الكثير من المال.
من الآن فصاعدًا ، تحتاج إلى كتابة البرامج النصية للترحيل لكل تغيير في نموذج البيانات ، على سبيل المثال ، باستخدام Flyway . ومع ذلك ، ماذا يحدث لفئات العملاء الخاصة بك؟ يمكنك إما تكييفها يدويًا (مما سيؤدي إلى عمل مزدوج) أو مطالبة Hibernate بإنشائها (ولكن ما مدى احتمالية أن تلبي نتائج هذا الجيل التوقعات؟). نتيجة لذلك ، قد تتوقع مشاكل كبيرة.
بمجرد دخول الكود إلى الإنتاج ، من الضروري تقريبًا إجراء التصحيحات ، وبأسرع ما يمكن.
ولأن تثبيت عمليات ترحيل قاعدة البيانات ليس مدمجًا في خط التجميع ؛ سيتعين عليك تثبيت هذه التصحيحات يدويًا على مسؤوليتك الخاصة. لن يكون هناك ما يكفي من الوقت للعودة وتفعل كل شيء بشكل صحيح. يكفي فقط إلقاء اللوم على السبات لجميع مشاكله.
بدلاً من ذلك ، كان بإمكانك التصرف بشكل مختلف تمامًا منذ البداية. وهي استخدام عجلات مستديرة بدلاً من العجلات المربعة.
انتقل إلى قاعدة البيانات أولاً
مرجع مخطط البيانات والتحكم في مكتب DBMS الخاص بك. قاعدة البيانات هي المكان الوحيد الذي يتم فيه تعريف المخطط ، ولدى جميع العملاء نسخة من هذا المخطط ، ولكن ليس العكس. البيانات موجودة في قاعدة البيانات الخاصة بك ، وليست في عميلك ، لذلك من المنطقي توفير التحكم في المخطط وتكامله حيث توجد البيانات بالضبط.
هذه حكمة قديمة ، ليست جديدة. المفاتيح الأساسية والفريدة جيدة. المفاتيح الخارجية جميلة. التحقق من القيود على جانب قاعدة البيانات رائع. التوكيد (عند تنفيذها في النهاية) عظيم.
وهذا ليس كل شيء. على سبيل المثال ، إذا كنت تستخدم Oracle ، فيمكنك تحديد:
- ما هي مساحة الجدول في الجدول الخاص بك؟
- ما معنى PCTFREE لديها
- ما هو حجم ذاكرة التخزين المؤقت التسلسل؟
ربما لا يهم كل هذا على الأنظمة الصغيرة ، ولكن في الأنظمة الأكبر حجمًا لا يتعين عليك اتباع مسار "البيانات الضخمة" حتى تضغط على جميع العصائر من وحدة التخزين الحالية. لن يسمح لك أي ORM واحد رأيته (بما في ذلك jOOQ) باستخدام مجموعة كاملة من معلمات DDL التي يوفرها نظام إدارة قواعد البيانات الخاص بك. تقدم ORMs بعض الأدوات فقط لمساعدتك على كتابة DDL.
في النهاية ، يجب كتابة مخطط جيد التصميم يدويًا فقط باستخدام DDL الخاص بـ DBMS. جميع DDLs التي تم إنشاؤها تلقائيًا هي مجرد تقريب لهذا.
ماذا عن نموذج العميل؟
كما ذكرنا سابقًا ، ستحتاج إلى تمثيل معين لمخطط قاعدة البيانات على جانب العميل. وغني عن القول ، يجب أن تتم مزامنة هذا الرأي مع النموذج الحقيقي. كيف تفعل ذلك؟ بالطبع باستخدام مولدات الكود.
توفر جميع قواعد البيانات الوصول إلى معلومات التعريف الخاصة بهم من خلال SQL القديمة الجيدة. لذلك ، على سبيل المثال ، يمكنك الحصول على قائمة بجميع الجداول من قواعد بيانات مختلفة:
يتم تنفيذ مثل هذه الاستعلامات (وكذلك الاستعلامات المماثلة لطرق العرض ، وطرق العرض المادية ، ووظائف الجدول) عندما يتم استدعاء طريقة DatabaseMetaData.getTables () لبرنامج تشغيل JDBC معين ، أو في وحدة jOOQ-meta.
من نتائج مثل هذه الاستعلامات ، من السهل نسبيًا إنشاء أي تمثيل للعميل لنموذج قاعدة البيانات ، بغض النظر عن تقنية الوصول إلى البيانات المستخدمة.
- إذا كنت تستخدم JDBC أو Spring ، يمكنك إنشاء مجموعة من ثوابت السلسلة
- إذا كنت تستخدم JPA ، يمكنك إنشاء كائنات بنفسك
- إذا كنت تستخدم jOOQ ، يمكنك إنشاء نماذج تعريفية لـ jOOQ
اعتمادًا على عدد الميزات التي تقدمها واجهة برمجة التطبيقات الخاصة بالوصول إلى البيانات (jOOQ أو JPA أو أي شيء آخر) ، يمكن أن يكون النموذج المتولد غنيًا وكاملًا حقًا. على سبيل المثال ، دالة الربط الضمنية في jOOQ 3.11 ، والتي تعتمد على المعلومات الوصفية حول علاقات المفاتيح الخارجية بين جداولك .
الآن سيؤدي أي تغيير في مخطط قاعدة البيانات تلقائيًا إلى تحديث رمز العميل.
تخيل أنك بحاجة إلى إعادة تسمية عمود في جدول:
ALTER TABLE book RENAME COLUMN title TO book_title;
هل أنت متأكد أنك تريد القيام بهذا العمل مرتين؟ مستحيل. ما عليك سوى تنفيذ DDL وتشغيل البنية والاستمتاع بالكائن المحدث:
@Entity @Table(name = "book", indexes = {
أيضًا ، لا يحتاج العميل المستلم إلى الترجمة في كل مرة (على الأقل حتى التغيير التالي في مخطط قاعدة البيانات) ، والذي يمكن أن يكون بالفعل إضافة كبيرة!
معظم تغييرات DDL هي أيضًا تغييرات دلالية ، وليست مجرد تغييرات نحوية. وبالتالي ، من الرائع أن نرى في رمز العميل الذي تم إنشاؤه بالضبط ما أحدث التغييرات في قاعدة البيانات المتأثرة.
الحقيقة دائما وحيدة
بغض النظر عن التكنولوجيا التي تستخدمها ، يجب أن يكون هناك دائمًا نموذج واحد فقط ، وهو المعيار للنظام الفرعي. أو ، على الأقل ، يجب أن نسعى جاهدين من أجل ذلك وتجنب الارتباك في العمل ، حيث يكون "المعيار" في كل مكان وفي أي مكان في نفس الوقت. يجعل كل شيء أسهل بكثير. على سبيل المثال ، إذا كنت تشارك ملفات XML مع نظام آخر ، فمن المحتمل أنك تستخدم XSD. كمعلومات نموذجية jOOQ INFORMATION_SCHEMA بتنسيق XML: https://www.jooq.org/xsd/jooq-meta-3.10.0.xsd
- XSD مفهومة جيدا
- يصف XSD بشكل مثالي محتوى XML ويسمح بالتحقق من الصحة بجميع لغات العميل
- XSD يجعل عملية الإصدار سهلة ومتوافقة مع السابقة
- يمكن تحويل XSD إلى كود Java باستخدام XJC
نولي اهتماما خاصا للنقطة الأخيرة. عند التواصل مع نظام خارجي من خلال رسائل XML ، يجب أن نتأكد من صحة الرسائل. ومن السهل جدًا القيام بأشياء مثل JAXB و XJC و XSD. سيكون من الجنون التفكير في مدى ملاءمة نهج جافا الأول في هذه الحالة. سيكون XML الذي يتم إنشاؤه استنادًا إلى كائنات XML ذو جودة رديئة ، وسيتم توثيقه بشكل سيئ ويصعب تمديده. وإذا كان هناك اتفاقية مستوى الخدمة لمثل هذا التفاعل ، فسوف تشعر بخيبة أمل.
بصراحة ، هذا مشابه لما يحدث مع واجهات برمجة تطبيقات JSON المختلفة الآن ، لكن هذه قصة مختلفة تمامًا ...
ما الذي يجعل قواعد البيانات أسوأ؟
عند العمل مع قاعدة بيانات ، كل شيء هو نفسه هنا. تمتلك قاعدة البيانات البيانات ، ويجب أن تكون أيضًا سيد مخطط البيانات. يجب إجراء جميع تعديلات المخطط مباشرة من خلال DDL لتحديث المرجع.
بعد تحديث المرجع ، يجب على جميع العملاء تحديث أفكارهم حول النموذج. يمكن كتابة بعض العملاء في Java باستخدام إما jOOQ و / أو Hibernate أو JDBC. يمكن كتابة عملاء آخرين بلغة Perl (حظ سعيد لهم) أو حتى في C #. لا يهم. النموذج الرئيسي موجود في قاعدة البيانات. في حين أن النماذج التي تم إنشاؤها باستخدام ORM ذات جودة رديئة ، غير موثقة جيدًا ويصعب تمديدها.
لذلك ، لا تفعل ذلك ، ومنذ البداية للتطوير. بدلاً من ذلك ، ابدأ بقاعدة بيانات. إنشاء خط أنابيب CI / CD مؤتمت. استخدم إنشاء التعليمات البرمجية فيه لإنشاء نموذج قاعدة بيانات للعملاء لكل بنية تلقائيًا. وتوقف عن القلق ، كل شيء سيكون على ما يرام. كل ما هو مطلوب هو جهد أولي بسيط لإنشاء البنية التحتية ، ولكن نتيجة لذلك ستحصل على مكاسب في عملية التطوير لبقية مشروعك لسنوات قادمة.
لا شكرًا.
تفسيرات
للدمج: لا تدعي هذه المقالة بأي حال من الأحوال أن نموذج قاعدة البيانات يجب أن ينطبق على نظامك بالكامل (مجال الموضوع ، منطق الأعمال ، وما إلى ذلك). تتكون عباراتي فقط من حقيقة أن رمز العميل الذي يتفاعل مع قاعدة البيانات يجب أن يكون تمثيلًا لمخطط قاعدة البيانات فقط ، ولكن ليس تعريفه وتشكيله بأي شكل من الأشكال.
في البنيات ذات المستويين التي لا يزال لها مكان ، قد يكون مخطط قاعدة البيانات المصدر الوحيد للمعلومات حول نموذج النظام الخاص بك. ومع ذلك ، في معظم الأنظمة ، أرى مستوى الوصول إلى البيانات على أنه "نظام فرعي" يشتمل على نموذج قاعدة بيانات. شيء من هذا القبيل.
الاستثناءات
كما هو الحال في أي قاعدة جيدة أخرى ، لدينا أيضًا استثناءات لها (وحذرت بالفعل من أن نهج قاعدة البيانات أولاً وتوليد التعليمات البرمجية ليس الخيار الصحيح دائمًا). هذه الاستثناءات (ربما لم تكتمل القائمة):
- عندما تكون الدائرة غير معروفة مسبقًا وتحتاج إلى التحقيق. على سبيل المثال ، أنت مقدم أداة لمساعدة المستخدمين على التنقل في أي مخطط. بالطبع ، لا يمكن أن يكون هناك توليد للكود. ولكن على أي حال ، يجب عليك التعامل مع قاعدة البيانات نفسها ومخططها.
- عندما تحتاج لبعض المهام لإنشاء مخطط على الطاير. قد يكون هذا مشابهًا لأحد أشكال نمط Entity-سمة-قيمة ، كما ليس لديك نمط محدد بوضوح. وكذلك ليس هناك يقين أن RDBMS في هذه الحالة هو الخيار الصحيح.
خصوصية هذه الاستثناءات هي أنها نادرا ما تلتقي في الحياة البرية. في معظم الحالات ، عند استخدام قواعد البيانات العلائقية ، يُعرف المخطط مقدمًا وهو "المعيار" لنموذجك ، ويجب على العملاء العمل مع نسخة من هذا النموذج الذي تم إنشاؤه باستخدام مولدات التعليمات البرمجية.