الحقيقة أولاً وقبل كل شيء ، أو لماذا يحتاج النظام إلى تصميم بناءً على جهاز قاعدة البيانات

مرحبا يا هابر!

نواصل استكشاف موضوع Java و Spring ، بما في ذلك على مستوى قاعدة البيانات. نقترح اليوم أن تقرأ لماذا ، عند تصميم التطبيقات الكبيرة ، يجب أن يكون لهيكل قاعدة البيانات ، وليس رمز Java ، أهمية حاسمة في كيفية القيام بذلك ، وما هي الاستثناءات لهذه القاعدة.

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

تستند هذه المقالة إلى سؤال واحد تم طرحه على Stack Overflow.

مناقشات مثيرة للاهتمام حول reddit في الأقسام / r / java و / r / البرمجة .

رمز الجيل


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

	for (Record2<String, String> record : DSL.using(configuration)
//   ^^^^^^^^^^^^^^^^^^^^^^^      
//     ,    
//   SELECT 
 
       .select(ACTOR.FIRST_NAME, ACTOR.LAST_NAME)
//           vvvvv ^^^^^^^^^^^^  ^^^^^^^^^^^^^^^  
       .from(ACTOR)
       .orderBy(1, 2)) {
    // ...
}

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

توليد كود المصدر


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

هناك العديد من مولدات الكود. على سبيل المثال ، يمكن لـ XJC إنشاء كود Java بناءً على ملفات XSD أو WSDL . المبدأ هو نفسه دائمًا:

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

علاوة على ذلك ، من المستحسن دائمًا إنشاء مثل هذا التمثيل - لتجنب التكرار.

اكتب موفري ومعالجة التعليقات التوضيحية


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

بمعنى ما ، تحدث نفس الأشياء هنا كما في الحالة الأولى ، باستثناء:

  • أنت لا ترى الكود الذي تم إنشاؤه (ربما يبدو هذا الموقف لشخص غير مثير للاشمئزاز؟)
  • , , , «» . Lombok, “”. , .

?


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

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

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


Original ، Alan O'Rourke، Audience Stack

ولكن في Hibernate / JPA ، من السهل جدًا كتابة الرمز "لـ Java".

هل حقا. بالنسبة إلى السبات ومستخدميه ، هذه نعمة ونقمة. في Hibernate ، يمكنك ببساطة كتابة كيانين ، مثل هذا:

	@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;
}

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

ولكن عليك أن تدفع ثمنها


عاجلاً أم آجلاً ، يجب أن تدخل الإنتاج. عندها فقط ، سيتوقف مثل هذا النموذج عن العمل. لأن:

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

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

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

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

قاعدة البيانات أولاً


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

علاوة على ذلك ، هذا ليس كل شيء. على سبيل المثال ، باستخدام Oracle ، ربما تريد تحديد:

  • ما هي مساحة الجدول في الجدول الخاص بك؟
  • ما هي قيمة PCTFREE؟
  • ما هو حجم ذاكرة التخزين المؤقت في التسلسل الخاص بك (خلف المعرف)

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

ولكن في النهاية ، تمت كتابة دائرة جيدة التصميم يدويًا باستخدام DDL. أي DDL ولدت هو تقريبًا منه.

ماذا عن نموذج العميل؟


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

توفر جميع قواعد البيانات معلومات التعريف الخاصة بهم من خلال SQL. إليك كيفية الحصول على جميع الجداول بلهجات مختلفة من SQL من قاعدة البيانات الخاصة بك:

	-- H2, HSQLDB, MySQL, PostgreSQL, SQL Server
SELECT table_schema, table_name
FROM information_schema.tables
 
-- DB2
SELECT tabschema, tabname
FROM syscat.tables
 
-- Oracle
SELECT owner, table_name
FROM all_tables
 
-- SQLite
SELECT name
FROM sqlite_master
 
-- Teradata
SELECT databasename, tablename
FROM dbc.tables

يتم تنفيذ هذه الاستفسارات (أو الاستفسارات المماثلة ، اعتمادًا على ما إذا كان عليك أيضًا التفكير في التمثيلات DatabaseMetaData.getTables()، أو التمثيلات الملموسة ، أو الوظائف ذات قيمة الجدول) باستخدام مكالمة من JDBC ، أو باستخدام الوحدة التعريفية jOOQ.

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

  • إذا كنت تستخدم 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 = {
 
  //    ?
  @Index(name = "i_book_title", columnList = "book_title")
})
class Book {
  @Id
  @GeneratedValue(strategy = IDENTITY)
  int id;
 
  @Column("book_title")
  String bookTitle;
}

أو فئة jOOQ محدثة. تؤثر معظم تغييرات DDL أيضًا على الدلالات ، وليس فقط بناء الجملة. لذلك ، قد يكون من الملائم أن نرى في التعليمات البرمجية المترجمة أي رمز (أو قد يتأثر) يتأثر بزيادة قاعدة البيانات الخاصة بك.

الحقيقة الوحيدة


بغض النظر عن التكنولوجيا التي تستخدمها ، هناك دائمًا نموذج واحد هو المصدر الوحيد للحقيقة لنظام فرعي - أو ، على الأقل ، يجب أن نسعى جاهدين من أجل هذا وتجنب ارتباك المؤسسة هذا ، حيث تكون "الحقيقة" في كل مكان وفي أي مكان. يمكن أن يكون كل شيء أبسط بكثير. إذا كنت تقوم فقط بتبادل ملفات XML مع نظام آخر ، فما عليك سوى استخدام XSD. ألق نظرة على النموذج التعريفي INFORMATION_SCHEMA من jOOQ بصيغة XML:
https://www.jooq.org/xsd/jooq-meta-3.10.0.xsd

  • XSD مفهومة جيدا
  • XSD XML
  • XSD
  • XSD Java XJC

النقطة الأخيرة مهمة. عند التواصل مع نظام خارجي باستخدام رسائل XML ، نريد التأكد من صحة رسائلنا. من السهل تحقيق ذلك مع JAXB و XJC و XSD. سيكون من الجنون تمامًا توقع أنه عند الاقتراب من تصميم "Java أولاً" ، حيث نصنع رسائلنا في شكل كائنات Java ، يمكن عرضها بطريقة أو بأخرى بتنسيق XML وإرسالها للاستهلاك إلى نظام آخر. سيكون XML الذي يتم إنشاؤه بهذه الطريقة ذو جودة رديئة جدًا ، ولا يتم توثيقه ، وسيكون من الصعب تطويره. إذا كان هناك اتفاق على مستوى جودة الخدمة (SLA) على مثل هذه الواجهة ، فسوف ندمرها على الفور.

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

قواعد البيانات: إنه نفس الشيء


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

عند حدوث تحديث المصدر ، يجب على جميع العملاء أيضًا تحديث نسخهم من النموذج. يمكن كتابة بعض العملاء في Java باستخدام jOOQ و Hibernate أو JDBC (أو جميعها في وقت واحد). يمكن كتابة عملاء آخرين في Perl (يبقى أتمنى لهم حظًا سعيدًا) ، والآخرين في C #. ليس مهما. النموذج الرئيسي موجود في قاعدة البيانات. النماذج التي تم إنشاؤها باستخدام ORMs ، عادة ما تكون ذات نوعية رديئة ، غير موثقة بشكل جيد ويصعب تطويرها.

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

حتى ذلك الحين ، شكرا لك.

تفسير


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

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

الاستثناءات


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

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

الاستثناءات استثنائية بطبيعتها. في معظم الحالات التي تتضمن استخدام نظام إدارة قواعد البيانات الموزعة (RDBMS) ، يُعرف المخطط مسبقًا ، ويقع داخل نظام إدارة قواعد البيانات الموزعة (RDBMS) وهو المصدر الوحيد "للحقيقة" ، ويتعين على جميع العملاء الحصول على نسخ مشتقة منه. من الناحية المثالية ، تحتاج إلى استخدام منشئ التعليمات البرمجية.

All Articles