Celesta 7.x: ORM ، الترحيل والاختبار "في حزمة واحدة"

ربما كنت تعرف بالفعل شيئا عن مكتبة سيليستا مفتوحة المصدر. إذا لم يكن الأمر كذلك ، فهذا لا يهم ، الآن سنخبرك بكل شيء. بعد مرور عام آخر ، تم إصدار الإصدار 7.x وتغيير الكثير من الأشياء ، وقد حان الوقت لتلخيص التغييرات ، وفي الوقت نفسه لتذكير ماهية Celesta بشكل عام.



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


ما الذي تغير في الإصدار 7.x؟


  1. رفضنا استخدام Jython كلغة مضمّنة في Celesta. إذا بدأنا في وقت سابق الحديث عن Celesta مع حقيقة أن منطق العمل مكتوب في Python ، الآن ... يمكن لأي لغة Java أن تكون لغة منطق العمل: Java ، Groovy ، JRuby أو Jython نفسها. الآن لا يستخدم Celesta رمز منطق الأعمال ، ولكن رمز منطق العمل يستخدم Celesta وفئات الوصول إلى البيانات الخاصة به باعتبارها مكتبة Java الأكثر شيوعًا. نعم ، تم انتهاك التوافق السابق بسبب هذا ، ولكن هذا هو الثمن الذي كنا مستعدين لدفعه. لسوء الحظ ، فقد رهاننا على جيثون. عندما بدأنا استخدام Jython قبل بضع سنوات ، كان مشروعًا حيويًا واعدًا ، ولكن على مر السنين تباطأ تطويره ، تراكمت المتراكمة من مواصفات اللغة ، ولم يتم حل مشاكل التوافق لمعظم مكتبات البرامج. القشة الأخيرة كانت الأخطاء الجديدة في أحدث إصدارات اللغات ، والتي تجلى عند العمل على حمل الإنتاج. نحن أنفسنا لا نملك الموارد اللازمة لدعم مشروع جيثون ، وقررنا الانفصال عنه. لم يعد سيليستا يعتمد على جيثون.
  2. يتم الآن إنشاء فئات الوصول إلى البيانات في لغة Java (وليس بيثون ، كما كان من قبل) باستخدام المكوّن الإضافي Maven. ونظرًا لأننا انتقلنا من الكتابة الديناميكية إلى الكتابة الثابتة بسبب هذا ، فقد كانت هناك فرص أكثر لإعادة بناء المساكن وأصبح من السهل كتابة التعليمات البرمجية الصحيحة الذاتية.
  3. ظهرت الإضافة لـ JUnit5 ، لذا أصبح من المناسب للغاية كتابة اختبارات المنطق التي تعمل مع قاعدة البيانات في JUnit5 (والتي سيتم مناقشتها لاحقًا).
  4. لقد ظهر مشروع منفصل ، وهو فصل الربيع-بداية الحذاء-celesta ، والذي ، كما يوحي الاسم ، هو بداية Celesta في Spring Boot. تعوض القدرة على حزم تطبيقات Celesta في خدمات Spring Boot القابلة للنشر بسهولة عن فقدان القدرة على تحديث التطبيق على الخادم عن طريق تغيير المجلد ببساطة باستخدام البرامج النصية Python.
  5. قمنا بنقل جميع الوثائق من ويكي إلى تنسيق AsciiDoctor ، ووضعناها في التحكم في الإصدار جنبا إلى جنب مع الكود ، والآن لدينا وثائق حديثة لكل إصدار من إصدارات Celesta. للحصول على أحدث إصدار ، تتوفر الوثائق عبر الإنترنت هنا: https://courseorchestra.imtqy.com/celesta/
  6. لقد سئلنا كثيرًا ما إذا كان من الممكن استخدام ترحيل قاعدة البيانات من خلال DDL idempotent بشكل منفصل عن Celesta. الآن هناك مثل هذه الفرصة باستخدام أداة 2bass .

ما هي سيليستا وماذا يمكنها أن تفعل؟


باختصار ، سيليستا هو:


  • طبقة وسيطة بين قاعدة البيانات العلائقية ورمز منطق الأعمال ، استنادًا إلى نهج تصميم قاعدة البيانات الأولى ،
  • آلية ترحيل هيكل قاعدة البيانات ،
  • إطار لاختبار رمز يعمل مع البيانات.

نحن ندعم أربعة أنواع من قواعد البيانات العلائقية: PostgreSQL ، MS SQL Server ، Oracle و H2.


الملامح الرئيسية سيليستا:


  1. مبدأ مشابه جدًا للمبدأ الأساسي في Java: "الكتابة مرة واحدة ، قم بتشغيل كل RDBMS المدعومة." كود منطق العمل لا يعرف نوع قاعدة البيانات التي سيتم تشغيلها عليها. يمكنك كتابة رمز منطق العمل وتشغيله في MS SQL Server ، ثم التبديل إلى PostgreSQL ، وسوف يحدث هذا دون تعقيدات (جيدًا ، تقريبًا :)
  2. إعادة الهيكلة التلقائية على قاعدة بيانات حية. تحدث معظم دورة حياة مشاريع Celesta عندما تكون قاعدة بيانات العمل موجودة بالفعل وتمتلئ بالبيانات التي يجب حفظها ، ولكن من الضروري أيضًا تغيير بنيتها باستمرار. تتمثل إحدى الميزات الرئيسية لسيليستا في القدرة على "ملائمة" هيكل قاعدة البيانات تلقائيًا لنموذج البيانات الخاص بك.
  3. الاختبار. يتم إيلاء الكثير من الاهتمام لضمان أن رمز Celesta قابل للاختبار ، حتى نتمكن من اختبار الأساليب التي تعدل البيانات في قاعدة البيانات تلقائيًا ، وذلك بسهولة وسرعة وأنيق ، دون استخدام أدوات خارجية مثل DbUnit والحاويات.

لماذا تحتاج الاستقلال عن نوع DBMS؟


لم تكن استقلالية كود منطق العمل عن نوع قواعد البيانات DBMS هي النقطة الأولى التي وضعناها: الشفرة المكتوبة من أجل Celesta لا تعرف على الإطلاق قواعد بيانات قواعد البيانات التي تعمل عليها. لماذا؟


أولاً ، نظرًا لحقيقة أن اختيار نوع DBMS ليس مشكلة تكنولوجية ، ولكنه مشكلة سياسية. عند القدوم إلى عميل تجاري جديد ، غالبًا ما نجد أنه لديه بالفعل نوع مفضل من قواعد بيانات إدارة قواعد البيانات (DBMS) يتم فيه استثمار الأموال ، ويريد العميل رؤية حلول أخرى على البنية التحتية الحالية. المشهد التكنولوجي آخذ في التغير: تم العثور على PostgreSQL بشكل متزايد في الوكالات الحكومية والشركات الخاصة ، على الرغم من أن MS SQL Server ساد في ممارستنا منذ بضع سنوات. يدعم نظام Celesta قواعد البيانات الأكثر شيوعًا ، ونحن لسنا قلقين بشأن هذه التغييرات.


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


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


CelestaSQL


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


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


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



إذا وفرنا استقلالية تامة عن نوع قاعدة البيانات ، فإن الوظيفة التي نفتحها لمبرمجي منطق الأعمال يجب أن تكون داخل تقاطع جميع القواعد. للوهلة الأولى ، يبدو أن هذا قيد كبير. نعم: بعض الميزات المحددة ، على سبيل المثال ، لا يمكننا استخدام SQL Server. ولكن بدون استثناء ، تدعم قواعد البيانات العلائقية الجداول والمفاتيح الخارجية وطرق العرض والتسلسلات واستعلامات SQL مع JOIN و GROUP BY. وفقا لذلك ، يمكننا إعطاء هذه الفرص للمطورين. نحن نوفر للمطورين "SQL depersonalized SQL" ، والتي نسميها "CelestaSQL" ، وفي هذه العملية نقوم بإنشاء استعلامات SQL لهجات قواعد البيانات المقابلة.


تتضمن لغة CelestaSQL DDL لتعريف كائنات قاعدة البيانات واستعلامات SELECT لطرق العرض والمرشحات ، ولكنها لا تحتوي على أوامر DML: تُستخدم المؤشرات لتعديل البيانات ، التي لا تزال بحاجة إلى مناقشتها.


كل قاعدة بيانات لديها مجموعة من أنواع البيانات الخاصة بها. لدى CelestaSQL أيضًا مجموعة الأنواع الخاصة بها. في وقت كتابة هذا التقرير ، كان هناك تسعة منها ، وهذا الجدول يقارنها بأنواع حقيقية في قواعد بيانات مختلفة وأنواع بيانات Java.


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


يتم وصف لغة CelestaSQL نفسها في الوثائق مع عدد كبير من مخططات بناء الجملة.


تعديل هيكل قاعدة البيانات. Idempotent DDL


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


باختصار ، عندما نكتب في CelestaSQL النص التالي:


CREATE TABLE OrderLine( order_id VARCHAR(30) NOT NULL, line_no INT NOT NULL, item_id VARCHAR(30) NOT NULL, item_name VARCHAR(100), qty INT NOT NULL DEFAULT 0, cost REAL NOT NULL DEFAULT 0.0, CONSTRAINT Idx_OrderLine PRIMARY KEY (order_id, line_no) ); 

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


مع هذا النهج ، نقوم بتطبيق القدرة على refactor والنصوص التحكم في الإصدار لتحديد بنية قاعدة البيانات:


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

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


من أجل تسريع عملية فحص / تحديث بنية قاعدة البيانات ، يطبق Celesta تخزين المجموع الاختباري لـ DDL في قاعدة البيانات (حتى يتم تغيير المجموع الاختباري ، لا تبدأ عملية التحقق من بنية قاعدة البيانات وتحديثها). من أجل متابعة عملية التحديث دون حدوث مشاكل تتعلق بترتيب تغيير الكائنات التي تعتمد على بعضها البعض ، يتم تطبيق التصنيف الطوبولوجي للتبعيات بين المخططات بواسطة المفاتيح الخارجية. يتم وصف عملية الترحيل التلقائي بمزيد من التفاصيل في الوثائق .


إنشاء مشروع وبيانات سيليستا


المشروع التجريبي ، الذي سننظر فيه ، متاح على جيثب . دعونا نرى كيف يمكنك استخدام Celesta عند كتابة تطبيق Spring Boot. إليك تبعيات Maven التي تحتاجها:


  • org.springframework.boot:spring-boot-starter-web و ru.curs:spring-boot-starter-celesta (لمزيد من التفاصيل ، ru.curs:spring-boot-starter-celesta الوثائق).
  • إذا كنت لا تستخدم Spring Boot ، فيمكنك توصيل ru.curs:celesta-system-services تبعية ru.curs:celesta-system-services مباشرة.
  • ru.curs:celesta-maven-plugin التعليمات البرمجية لفئات الوصول إلى البيانات استنادًا إلى البرامج النصية Celesta-SQL ، هناك حاجة إلى ru.curs:celesta-maven-plugin - التعليمات البرمجية المصدر لمثال تجريبي أو وثائق توضح كيفية توصيله.
  • للاستفادة من القدرة على كتابة اختبارات وحدة JUnit5 للأساليب التي تعدل البيانات ، يجب عليك توصيل ru.curs:celesta-unit في نطاق الاختبار.

الآن إنشاء نموذج بيانات وتجميع فئات الوصول إلى البيانات.


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


يجب أن يكون تطبيق "نقطة الدخول" تقليديًا: خدمة HTTP مع عمليات CRUD التي تخزن البيانات في قاعدة بيانات علائقية.


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


لذلك ، للحصول على الوظيفة: إنشاء


  • src/main/celestasql - بشكل افتراضي ، هذا هو المسار إلى البرامج النصية لمشروع CelestaSQL
  • أنه يحتوي على مجلدات فرعية تكرر بنية مجلد حزم java ( ru/curs/demo في حالتنا).
  • في مجلد الحزمة ، قم بإنشاء ملف .sql بالمحتويات التالية:

 CREATE SCHEMA demo VERSION '1.0'; /** */ CREATE TABLE OrderHeader( id VARCHAR(30) NOT NULL, date DATETIME, customer_id VARCHAR(30), /**  */ customer_name VARCHAR(50), manager_id VARCHAR(30), CONSTRAINT Pk_OrderHeader PRIMARY KEY (id) ); /** */ CREATE TABLE OrderLine( order_id VARCHAR(30) NOT NULL, line_no INT NOT NULL, item_id VARCHAR(30) NOT NULL, item_name VARCHAR(100), qty INT NOT NULL DEFAULT 0, cost REAL NOT NULL DEFAULT 0.0, CONSTRAINT Idx_OrderLine PRIMARY KEY (order_id, line_no) ); ALTER TABLE OrderLine ADD CONSTRAINT fk_OrderLine FOREIGN KEY (order_id) REFERENCES OrderHeader(id); CREATE VIEW OrderedQty AS SELECT item_id, sum(qty) AS qty FROM OrderLine GROUP BY item_id; 

وصفنا هنا جدولين متصلين بمفتاح خارجي ، وطريقة عرض واحدة ستعيد كمية ملخص للبضائع الموجودة في جميع الطلبات. كما ترون ، هذا لا يختلف عن SQL العادي ، باستثناء الأمر CREATE SCHEMA ، الذي أعلنا فيه إصدار مخطط demo (لمعرفة كيفية تأثير رقم الإصدار على الترحيل التلقائي ، راجع الوثائق ). ولكن هناك أيضا الميزات. على سبيل المثال ، يمكن أن تكون جميع أسماء الجداول والحقول التي نستخدمها فقط بحيث يمكن تحويلها إلى أسماء صالحة للفئات والمتغيرات بلغة Java. لذلك ، يتم استبعاد المسافات والأحرف الخاصة. يمكنك أيضًا ملاحظة أن التعليقات التي وضعناها على أسماء الجداول وبعض الحقول ، لم نبدأ بـ / * ، كالعادة ، ولكن مع / ** ، كيف تبدأ تعليقات JavaDoc - وهذا ليس مصادفة! سيكون تعليق معرف عبر كيان يبدأ بـ / ** متاحًا في وقت التشغيل في .getCelestaDoc() لهذا الكيان. يكون هذا مفيدًا عندما نريد تزويد عناصر قاعدة البيانات بمعلومات التعريف الإضافية: على سبيل المثال ، أسماء الحقول القابلة للقراءة البشرية ، ومعلومات حول كيفية تمثيل الحقول في واجهة المستخدم ، إلخ.


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


يمكننا الآن إنشاء فئات وصول إلى البيانات ، فقط قم بتشغيل mvn generate-sources ، أو إذا كنت تعمل في IDEA ، فانقر فوق الزر "إنشاء مصادر وتحديث المجلدات" في لوحة تحكم Maven. في الحالة الثانية ، تقوم IDEA " target/generated-sources/celesta المجلد الذي تم إنشاؤه في target/generated-sources/celesta وإتاحة محتوياته للاستيراد في أكواد مصدر المشروع. ستبدو نتيجة إنشاء الشفرة كما يلي - فئة واحدة لكل كائن في قاعدة البيانات:



يتم تحديد الاتصال بقاعدة البيانات في إعدادات التطبيق ، في حالتنا ، في ملف src/main/resources/application.yml . عند استخدام spring-boot-starter-celesta ، ستخبرك IDEA بخيارات التعليمات البرمجية المتوفرة في إكمال التعليمات البرمجية.


إذا لم نكن نريد عناء RDBMS "الحقيقي" لأغراض العرض التوضيحي ، فيمكننا أن نجعل Celesta يعمل مع قاعدة بيانات H2 المضمنة في وضع الذاكرة باستخدام التكوين التالي:


 celesta: h2: inMemory: true 

لتوصيل قاعدة بيانات "حقيقية" ، قم بتغيير التكوين إلى شيء من هذا القبيل


 celesta: jdbc: url: jdbc:postgresql://127.0.0.1:5432/celesta username: <your_username> password: <your_password> 

(في هذه الحالة ، ستحتاج أيضًا إلى إضافة برنامج تشغيل PostgreSQL JDBC إلى تطبيقك من خلال تبعية Maven).


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


إنشاء طرق معالجة البيانات


بمجرد معرفة كيفية إنشاء بنية قاعدة بيانات ، يمكنك البدء في كتابة منطق العمل.


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


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

إذا استخدمنا spring-boot-starter-celesta ، فسيتم تنفيذ هذه الإجراءات تلقائيًا لجميع الطرق @CelestaTransaction بواسطة @CelestaTransaction .


لنفترض أننا نرغب في كتابة معالج يحفظ المستند في قاعدة البيانات. قد يبدو رمز مستوى التحكم الخاص به كما يلي:


 @RestController @RequestMapping("/api") public class DocumentController { private final DocumentService srv; public DocumentController(DocumentService srv) { this.srv = srv; } @PutMapping("/save") public void saveOrder(@RequestBody OrderDto order) { CallContext ctx = new CallContext("user1"); //new SystemCallContext(); srv.postOrder(ctx, order); } 

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


قد تبدو طريقة حفظ الفاتورة على مستوى الخدمة كما يلي:


 @Service public class DocumentService { @CelestaTransaction public void postOrder(CallContext context, OrderDto doc) { try (OrderHeaderCursor header = new OrderHeaderCursor(context); OrderLineCursor line = new OrderLineCursor(context)) { header.setId(doc.getId()); header.setDate(Date.from(doc.getDate().atStartOfDay(ZoneId.systemDefault()).toInstant())); header.setCustomer_id(doc.getCustomerId()); header.setCustomer_name(doc.getCustomerName()); header.insert(); int lineNo = 0; for (OrderLineDto docLine : doc.getLines()) { lineNo++; line.setLine_no(lineNo); line.setOrder_id(doc.getId()); line.setItem_id(docLine.getItemId()); line.setQty(docLine.getQty()); line.insert(); } } } 

لاحظ التعليق التوضيحي @CelestaTransaction . بفضل ذلك ، سيقوم الكائن الوكيل DocumentService بتنفيذ جميع إجراءات الخدمة هذه باستخدام معلمة CallContext ctx الموضحة أعلاه. وهذا هو ، في بداية تنفيذ الطريقة ، سيكون بالفعل مرتبطًا باتصال قاعدة البيانات ، وستكون المعاملة جاهزة للبدء. يمكننا التركيز على كتابة منطق العمل. في حالتنا ، قراءة كائن OrderDto في قاعدة البيانات.


للقيام بذلك ، نحن نستخدم ما يسمى المؤشرات - الطبقات التي تم إنشاؤها باستخدام celesta-maven-plugin . لقد رأينا بالفعل ما هم عليه. يتم إنشاء فئة واحدة لكل كائن من كائنات المخطط - جدولان وطريقة عرض واحدة. والآن يمكننا استخدام هذه الفئات للوصول إلى كائنات قاعدة البيانات في منطق أعمالنا.


لإنشاء مؤشر على جدول الطلبات وتحديد السجل الأول ، تحتاج إلى كتابة التعليمات البرمجية التالية:


 OrderHeaderCursor header = new OrderHeaderCursor(context); header.tryFirst(); 

بعد إنشاء كائن الرأس ، يمكننا الوصول إلى حقول إدخال الجدول من خلال getters و setters:



عند إنشاء مؤشر ، يجب أن نستخدم سياق الاتصال النشط - هذه هي الطريقة الوحيدة لإنشاء مؤشر. يحمل سياق الاتصال معلومات حول المستخدم الحالي وحقوق الوصول الخاصة به.


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


على سبيل المثال ، يمكن تطوير رمز مثالنا على النحو التالي:


 OrderHeaderCursor header = new OrderHeaderCursor(context); header.setRange("manager_id", "manager1"); header.tryFirst(); header.setCounter(header.getCounter() + 1); header.update(); 

في هذا المثال ، قمنا بتعيين عامل التصفية حسب الحقل manager_id ، ثم نجد السجل الأول باستخدام طريقة tryFirst.


(لماذا "جرب")

تحتوي أساليب get ، first ، insert ، update على خيارين: بدون بادئة try (فقط get(...) ، وما إلى ذلك) ومع بادئة try ( tryGet(...) ، tryFirst() ، إلخ) . الأساليب دون بادئة المحاولة رمي استثناء إذا كانت قاعدة البيانات لا تحتوي على البيانات المناسبة لتنفيذ الإجراء. على سبيل المثال ، أولًا () سوف يرمي استثناءًا في حالة عدم وجود سجلات في عامل التصفية على المؤشر. في الوقت نفسه ، لا تؤدي الطرق التي تحتوي على بادئة المحاولة إلى استثناء ، ولكنها تُرجع بدلاً من ذلك قيمة منطقية تشير إلى نجاح أو فشل العملية المقابلة. الممارسة الموصى بها هي استخدام الطرق دون بادئة المحاولة كلما أمكن ذلك. بهذه الطريقة ، يتم إنشاء رمز "الاختبار الذاتي" ، مما يشير إلى وجود أخطاء في الوقت المناسب في بيانات المنطق و / أو قاعدة البيانات.


عندما tryFirst تشغيل tryFirst ، يتم تعبئة متغيرات tryFirst ببيانات سجل واحد ، يمكننا قراءة القيم وتعيينها لهم. وعندما يتم إعداد البيانات الموجودة في المؤشر بالكامل ، نقوم بتنفيذ update() ، ويقوم بتخزين محتويات المؤشر في قاعدة البيانات.


ما المشكلة التي قد تتأثر هذه الشفرة؟ بالطبع ، ظهور حالة السباق / تحديث المفقودة! لأنه بين اللحظة التي تلقينا فيها البيانات في السطر "tryFirst" وبين اللحظة التي نحاول فيها تحديث هذه البيانات عند نقطة "التحديث" ، يمكن لشخص آخر استلام هذه البيانات وتعديلها وتحديثها بالفعل في قاعدة البيانات. بعد قراءة البيانات ، يمنع المؤشر بأي شكل من الأشكال استخدامها من قبل المستخدمين الآخرين! للحماية من التحديثات المفقودة ، يستخدم Celesta مبدأ القفل المتفائل. في كل جدول ، يقوم Celesta افتراضيًا بإنشاء حقل recversion ، وعلى مستوى ON من UPDATE يؤدي إلى زيادة رقم الإصدار والتحقق من أن البيانات المحدثة لها نفس إصدار الجدول. في حالة حدوث مشكلة ، يلقي استثناء. يمكنك قراءة المزيد حول هذا الموضوع في المقالة " حماية ضد التحديثات المفقودة ".


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


اختبار طرق البيانات


لنقم بإنشاء اختبار وحدة يتحقق من صحة طريقة الخدمة التي تخزن OrderDto في قاعدة البيانات.


عند استخدام JUnit5 وملحق JUnit5 المتاح في celesta-unit ، يكون ذلك سهلاً للغاية. هيكل الاختبار على النحو التالي:


 @CelestaTest public class DocumentServiceTest { DocumentService srv = new DocumentService(); @Test void documentIsPutToDb(CallContext context) { OrderDto doc =... srv.postOrder(context, doc); //Check the fact that records are in the database OrderHeaderCursor header = new OrderHeaderCursor(context); header.tryFirst(); assertEquals(doc.getId(), header.getId()); OrderLineCursor line = new OrderLineCursor(context); line.setRange("order_id", doc.getId()); assertEquals(2, line.count()); } } 

بفضل الشرح @CelestaTest ، وهو امتداد لـ JUnit5 ، يمكننا إعلان معلمة CallContext context في طرق الاختبار. هذا السياق مفعل بالفعل ومرتبط بقاعدة البيانات (في الذاكرة H2) ، وبالتالي نحن لسنا بحاجة إلى التفاف فئة الخدمة في الخادم الوكيل - فنحن ننشئها باستخدام new ، وليس باستخدام Spring. ومع ذلك ، إذا لزم الأمر ، قم بإدخال الخدمة في الاختبار باستخدام أدوات Spring ، فلا توجد عوائق أمام ذلك.


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


لنقم بإنشاء إجراء ثانٍ يُرجع JSON بقيم مجمعة توضح عدد المنتجات التي طلبناها.


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


 @Test void reportReturnsAggregatedQuantities(CallContext context) { srv.postOrder(context, . . .); srv.postOrder(context, . . .); Map<String, Integer> result = srv.getAggregateReport(context); assertEquals(5, result.get("A").intValue()); assertEquals(7, result.get("B").intValue()); } 

لتطبيق طريقة getAggregateReport سنستخدم طريقة العرض OrderedQty ، التي أذكرها ، في ملف CelestaSQL يبدو كما يلي:


 create view OrderedQty as select item_id, sum(qty) as qty from OrderLine group by item_id; 

الطلب قياسي: نلخص خطوط الطلب حسب الكمية وتجميعها حسب كود المنتج. تم بالفعل إنشاء مؤشر OrderedQtyCursor للعرض ، والذي يمكننا استخدامه. نعلن هذا المؤشر ونكرره ونجمع Map<String, Integer> المطلوبة Map<String, Integer> :


 @CelestaTransaction public Map<String, Integer> getAggregateReport(CallContext context) { Map<String, Integer> result = new HashMap<>(); try (OrderedQtyCursor ordered_qty = new OrderedQtyCursor(context)) { for (OrderedQtyCursor line : ordered_qty) { result.put(ordered_qty.getItem_id(), ordered_qty.getQty()); } } return result; } 

يتحقق سيليستا المشاهدات


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


يحاول Celesta تنفيذ جميع المهام القياسية التي يواجهها مبرمجو منطق الأعمال باستمرار على مستوى النظام الأساسي.


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


لكن في حال عملنا مع PostgreSQL من خلال Celesta ، ماذا يمكننا أن نفعل؟ أعد تعريف العرض بإضافة الكلمة الملموسة:


 create materialized view OrderedQty as select item_id, sum(qty) as qty from OrderLine group by item_id; 

لنبدأ النظام ونرى ما حدث لقاعدة البيانات.


سنلاحظ أن OrderedQty قد اختفت ، وقد ظهر جدول OrderedQty . علاوة على ذلك ، بما أن جدول OrderLine مليء بالبيانات ، سيتم تحديث المعلومات الموجودة في جدول OrderedQty "بطريقة سحرية" ، كما لو كان OrderedQty سيكون طريقة عرض.


لا يوجد سحر هنا إذا نظرنا إلى المشغلات المبنية على جدول OrderLine . بعد أن تلقى Celesta مهمة إنشاء "عرض ملموس" ، قام بتحليل الاستعلام وإنشاء مشغلات على جدول OrderLine الذي يقوم بتحديث OrderedQty . بإدخال كلمة أساسية واحدة - materialized - في ملف CelestaSQL ، حللنا مشكلة تدهور الأداء ، ولم يكن من الضروري تغيير رمز منطق العمل!


, , , . «» Celesta , , JOIN-, GROUP BY. , , , , . . .


استنتاج


Celesta. — .

Source: https://habr.com/ru/post/ar455746/


All Articles