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

يا هبر!

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

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

تم كتابة المقال على أساس سؤال واحد، على موقع Stack Overflow.

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

رمز الجيل

كم أنا مندهش من وجود مثل هذه الطبقة الصغيرة من المستخدمين الذين ، بعد أن أصبحوا على دراية بـ 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 #. في هذه الحالة ، يتم إنشاء الكود بواسطة المترجم ، في الواقع في مرحلة التجميع. من حيث المبدأ ، لا توجد هذه الشفرة في شكل أكواد المصدر. في Java ، توجد أدوات متشابهة ، وإن لم تكن أنيقة - فهذه معالجات التعليقات التوضيحية ، على سبيل المثال ، لومبوك.

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

  • أنت لا ترى الرمز الذي تم إنشاؤه (ربما لا يبدو هذا الموقف مثيرًا للاشمئزاز لشخص ما؟)
  • يجب عليك التأكد من أنه يمكن توفير الأنواع ، أي ، يجب أن تكون "true" متاحة دائمًا. هذا سهل في حالة لومبوك ، التي تشرح "الحقيقة". يكون الأمر أكثر صعوبة مع نماذج قواعد البيانات التي تعتمد على اتصال مباشر متاح دائمًا.

ما هي مشكلة إنشاء الكود؟

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

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

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

الحقيقة أولاً ، أو لماذا يحتاج النظام إلى التصميم بناءً على بنية قاعدة البيانات
أصلي, آلان أورورك ، Audience Stack

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

حقًا. بالنسبة إلى Hibernate ومستخدميه ، يعد هذا نعمة ونقمة. في 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);

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

ومع ذلك ، اسمحوا لي. كنت مستلقيا.

  • هل سيفرض Hibernate (الإسبات) بالفعل تعريف هذا المفتاح الأساسي المسمى؟
  • هل سيؤدي وضع الإسبات إلى إنشاء فهرس على 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 #. لا يهم. النموذج الرئيسي موجود في قاعدة البيانات. عادة ما تكون النماذج المولدة من ORM ذات نوعية رديئة ، وموثقة بشكل سيئ ، ويصعب تطويرها.

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

لا تشكرني بعد ، لاحقًا.

توضيح

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

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

استثناءات

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

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

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

المصدر: www.habr.com

إضافة تعليق