عدم التوقف عن النشر وقواعد البيانات

عدم التوقف عن النشر وقواعد البيانات

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

إذا كنت تريد فهم أمثلة التعليمات البرمجية من المقالة، فيمكنك العثور عليها على GitHub جيثب:.

مقدمة

نشر التوقف صفر

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

كيفية تحقيق ذلك؟ هناك عدة طرق، وهنا واحدة منهم:

  • نشر الإصدار رقم 1 من خدمتك
  • تنفيذ ترحيل قاعدة البيانات
  • انشر الإصدار رقم 2 من خدمتك بالتوازي مع الإصدار رقم 1
  • بمجرد أن ترى أن الإصدار رقم 2 يعمل كما ينبغي، قم بإزالة الإصدار رقم 1
  • مستعد!

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

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

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

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

بعد قراءة كل ما سبق، قد تطرح السؤال التالي: ما علاقة التوقف الصفري بنشر اللون الأزرق الأخضر؟

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

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

وهنا نأتي إلى المشكلة الرئيسية في هذا المقال. قاعدة بيانات. دعونا نلقي نظرة أخرى على هذه العبارة.

تنفيذ ترحيل قاعدة البيانات.

الآن عليك أن تسأل نفسك السؤال - ماذا لو كان تغيير قاعدة البيانات غير متوافق مع الإصدارات السابقة؟ ألن يتعطل الإصدار الأول من التطبيق؟ والحقيقة أن هذا ما سيحدث بالضبط..

لذلك، حتى على الرغم من الفوائد الهائلة المتمثلة في عدم التوقف عن العمل / النشر باللون الأزرق الأخضر، تميل الشركات إلى اتباع العملية الأكثر أمانًا التالية لنشر تطبيقاتها:

  • قم بإعداد حزمة مع الإصدار الجديد من التطبيق
  • اغلاق تطبيق قيد التشغيل
  • تشغيل البرامج النصية لترحيل قاعدة البيانات
  • نشر وإطلاق نسخة جديدة من التطبيق

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

قضايا قاعدة البيانات

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

مخطط الإصدار

في هذه المقالة سوف نستخدم تسلكه كأداة للتحكم في الإصدار (تقريبا. الترجمة: نحن نتحدث عن عمليات ترحيل قاعدة البيانات). وبطبيعة الحال، سنقوم أيضًا بكتابة تطبيق Spring Boot الذي يحتوي على دعم Flyway مدمج وسنقوم بإجراء ترحيل المخطط أثناء إعداد سياق التطبيق. عند استخدام Flyway، يمكنك تخزين البرامج النصية للترحيل في مجلد المشروعات لديك (بشكل افتراضي في classpath:db/migration). هنا يمكنك رؤية مثال لملفات الترحيل هذه

└── db
 └── migration
     ├── V1__init.sql
     ├── V2__Add_surname.sql
     ├── V3__Final_migration.sql
     └── V4__Remove_lastname.sql

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

CREATE TABLE PERSON (
id BIGINT GENERATED BY DEFAULT AS IDENTITY,
first_name varchar(255) not null,
last_name varchar(255) not null
);

insert into PERSON (first_name, last_name) values ('Dave', 'Syer');

كل شيء واضح تمامًا: يمكنك استخدام SQL لتحديد كيفية تعديل قاعدة البيانات الخاصة بك. لمزيد من المعلومات حول Spring Boot وFlyway، راجع ذلك مستندات التمهيد الربيع.

باستخدام أداة التحكم بالمصدر مع Spring Boot، يمكنك الحصول على فائدتين كبيرتين:

  • يمكنك فصل تغييرات قاعدة البيانات عن تغييرات التعليمات البرمجية
  • يحدث ترحيل قاعدة البيانات مع بدء تشغيل التطبيق الخاص بك، أي. تم تبسيط عملية النشر الخاصة بك

استكشاف أخطاء قاعدة البيانات وإصلاحها

في القسم التالي من المقالة، سنركز على النظر إلى طريقتين لتغييرات قاعدة البيانات.

  • عدم التوافق مع الخلف
  • التوافق

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

سيكون مشروعنا الذي سنعمل عليه عبارة عن تطبيق بسيط لـ Spring Boot Flyway Person с first_name и last_name في قاعدة البيانات (تقريبا. ترجمة: Person هو الجدول وirst_name и last_name - هذه هي الحقول الموجودة فيه). نريد إعادة التسمية last_name в surname.

الافتراضات

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

المذكرة. الأعمال التجارية PRO-TIP. يمكن أن يوفر لك تبسيط العمليات الكثير من المال الذي تنفقه على الدعم (كلما زاد عدد الأشخاص الذين يعملون في شركتك، زادت الأموال التي يمكنك توفيرها)!

لا حاجة للتراجع عن قاعدة البيانات

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

يجب أن يكون من الممكن دائمًا استرجاع التطبيق مرة واحدة (لا أكثر)

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

المذكرة. ولزيادة سهولة القراءة، سنقوم في هذه المقالة بتغيير الإصدار الرئيسي للتطبيق.

الخطوة 1: الحالة الأولية

إصدار التطبيق: 1.0.0
نسخة قاعدة البيانات: v1

تعليق

ستكون هذه هي الحالة الأولية للتطبيق.

تغييرات قاعدة البيانات

يحتوي على قاعدة البيانات last_name.

CREATE TABLE PERSON (
id BIGINT GENERATED BY DEFAULT AS IDENTITY,
first_name varchar(255) not null,
last_name varchar(255) not null
);

insert into PERSON (first_name, last_name) values ('Dave', 'Syer');

تغييرات التعليمات البرمجية

يقوم التطبيق بتخزين بيانات الشخص في last_name:

/*
 * Copyright 2012-2016 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package sample.flyway;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;

@Entity
public class Person {
    @Id
    @GeneratedValue
    private Long id;
    private String firstName;
    private String lastName;

    public String getFirstName() {
        return this.firstName;
    }

    public void setFirstName(String firstName) {
        this.firstName = firstName;
    }

    public String getLastName() {
        return this.lastName;
    }

    public void setLastName(String lastname) {
        this.lastName = lastname;
    }

    @Override
    public String toString() {
        return "Person [firstName=" + this.firstName + ", lastName=" + this.lastName
                + "]";
    }
}

إعادة تسمية الأعمدة غير متوافقة مع الإصدارات السابقة

دعونا نلقي نظرة على مثال لكيفية تغيير اسم العمود:

انتباه. المثال التالي سوف يكسر الأشياء عمدا. نعرض هذا لتوضيح مشكلة توافق قاعدة البيانات.

إصدار التطبيق: 2.0.0.BAD

نسخة قاعدة البيانات: v2bad

تعليق

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

اختبار أ/ب

الوضع الحالي هو أن لدينا نسخة التطبيق 1.0.0, المنتشرة في الإنتاج وقاعدة البيانات v1. نحتاج إلى نشر نسخة ثانية من التطبيق 2.0.0.BAD، وتحديث قاعدة البيانات إلى v2bad.

الخطوات التالية:

  1. يتم نشر مثيل جديد لتطبيق الإصدار 2.0.0.BADالذي يقوم بتحديث قاعدة البيانات إلى v2bad
  2. في قاعدة البيانات v2bad عمود last_name لم يعد موجودا - تم تغييره إلى surname
  3. تم تحديث قاعدة البيانات والتطبيق بنجاح ويتم تشغيل بعض المثيلات 1.0.0وآخرون - في 2.0.0.BAD. كل شيء متصل بقاعدة البيانات v2bad
  4. كافة مثيلات الإصدار 1.0.0 سيبدأ في إلقاء الأخطاء لأنهم سيحاولون إدراج البيانات في العمود last_nameالذي لم يعد موجودا
  5. كافة مثيلات الإصدار 2.0.0.BAD سوف تعمل دون مشاكل

كما ترى، إذا أجرينا تغييرات عكسية غير متوافقة على قاعدة البيانات والتطبيق، فسيكون اختبار A/B مستحيلًا.

التراجع عن التطبيق

لنفترض أنه بعد محاولة إجراء نشر A/B (تقريبا. لكل .: ربما كان المؤلف يقصد اختبار A/B هنا) قررنا أننا بحاجة إلى إرجاع التطبيق إلى الإصدار 1.0.0. لنفترض أننا لا نريد التراجع عن قاعدة البيانات.

الخطوات التالية:

  1. نوقف مثيل تطبيق الإصدار 2.0.0.BAD
  2. قاعدة البيانات لا تزال v2bad
  3. منذ الإصدار 1.0.0 لا يفهم ما هو عليه surname، سوف نرى أخطاء
  4. لقد انفتح الجحيم، ولا يمكننا العودة بعد الآن

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

سجلات تنفيذ البرنامج النصي

Backward incompatible scenario:

01) Run 1.0.0
02) Wait for the app (1.0.0) to boot
03) Generate a person by calling POST localhost:9991/person to version 1.0.0
04) Run 2.0.0.BAD
05) Wait for the app (2.0.0.BAD) to boot
06) Generate a person by calling POST localhost:9991/person to version 1.0.0 <-- this should fail
07) Generate a person by calling POST localhost:9992/person to version 2.0.0.BAD <-- this should pass

Starting app in version 1.0.0
Generate a person in version 1.0.0
Sending a post to 127.0.0.1:9991/person. This is the response:

{"firstName":"b73f639f-e176-4463-bf26-1135aace2f57","lastName":"b73f639f-e176-4463-bf26-1135aace2f57"}

Starting app in version 2.0.0.BAD
Generate a person in version 1.0.0
Sending a post to 127.0.0.1:9991/person. This is the response:

curl: (22) The requested URL returned error: 500 Internal Server Error

Generate a person in version 2.0.0.BAD
Sending a post to 127.0.0.1:9995/person. This is the response:

{"firstName":"e156be2e-06b6-4730-9c43-6e14cfcda125","surname":"e156be2e-06b6-4730-9c43-6e14cfcda125"}

تغييرات قاعدة البيانات

البرنامج النصي للهجرة الذي يعيد تسمية last_name в surname

مصدر البرنامج النصي Flyway:

CREATE TABLE PERSON (
id BIGINT GENERATED BY DEFAULT AS IDENTITY,
first_name varchar(255) not null,
last_name varchar(255) not null
);

insert into PERSON (first_name, last_name) values ('Dave', 'Syer');

البرنامج النصي الذي يعيد تسمية last_name.

-- This change is backward incompatible - you can't do A/B testing
ALTER TABLE PERSON CHANGE last_name surname VARCHAR;

تغييرات التعليمات البرمجية

لقد قمنا بتغيير اسم الحقل lastName في surname.

إعادة تسمية عمود بطريقة متوافقة مع الإصدارات السابقة

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

المذكرة. تذكر أن لدينا قاعدة بيانات الإصدار v1. تحتوي على أعمدة first_name и last_name. علينا أن نتغير last_name في surname. لدينا أيضًا إصدار التطبيق 1.0.0, الذي لم يستخدم بعد surname.

الخطوة 2: إضافة اللقب

إصدار التطبيق: 2.0.0
نسخة قاعدة البيانات: v2

تعليق

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

نحن نطرح نسخة جديدة

الخطوات التالية:

  1. إجراء ترحيل قاعدة البيانات لإنشاء عمود جديد surname. الآن إصدار قاعدة البيانات الخاصة بك v2
  2. نسخ البيانات من last_name в surname. لاحظأنه إذا كان لديك الكثير من هذه البيانات، فيجب عليك التفكير في الترحيل المجمع!
  3. اكتب الكود حيث يتم استخدامها كلاهما и جديدو قديم عمود. الآن إصدار التطبيق الخاص بك 2.0.0
  4. قراءة القيمة من العمود surname، إذا لم يكن null، أو من لast_nameإذا surname غير محدد. يمكنك الحذف getLastName() من الكود، لأنه سوف يخرج null عند التراجع عن طلبك من 3.0.0 إلى 2.0.0.

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

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

المهم. يجب عليك إزالة الطريقة getLastName()، لأنه في الإصدار 3.0.0 لا يوجد مفهوم العمود في الكود last_name. هذا يعني أنه سيتم تعيين null هناك. يمكنك ترك الطريقة وإضافة الشيكات ل null، ولكن الحل الأفضل هو التأكد من ذلك في المنطق getSurname() لقد حددت القيمة الصحيحة غير الصفرية.

اختبار أ/ب

الوضع الحالي هو أن لدينا نسخة التطبيق 1.0.0، المنتشرة في الإنتاج، وقاعدة البيانات في v1. نحتاج إلى نشر مثيل ثانٍ لتطبيق الإصدار 2.0.0والتي سيتم تحديث قاعدة البيانات ل v2.

الخطوات التالية:

  1. يتم نشر مثيل جديد لتطبيق الإصدار 2.0.0الذي يقوم بتحديث قاعدة البيانات إلى v2
  2. في هذه الأثناء، تمت معالجة بعض الطلبات بواسطة مثيلات الإصدار 1.0.0
  3. تم التحديث بنجاح ولديك مثيلات متعددة قيد التشغيل لتطبيق الإصدار 1.0.0 وإصدارات أخرى 2.0.0. الجميع يتواصل مع قاعدة البيانات في v2
  4. نص 1.0.0 لا يستخدم عمود اللقب في قاعدة البيانات، ولكن الإصدار 2.0.0 الاستخدامات. إنهم لا يتداخلون مع بعضهم البعض، ويجب ألا تكون هناك أخطاء.
  5. نص 2.0.0 يقوم بتخزين البيانات في كل من العمود القديم والجديد، مما يضمن التوافق مع الإصدارات السابقة

المهم. إذا كان لديك أي استعلامات تحسب العناصر بناءً على قيم من العمود القديم/الجديد، فيجب أن تتذكر أن لديك الآن قيمًا مكررة (على الأرجح أنها لا تزال قيد الترحيل). على سبيل المثال، إذا كنت تريد حساب عدد المستخدمين الذين تبدأ أسماؤهم الأخيرة (أيًا كان اسم العمود) بالحرف A، ثم حتى اكتمال ترحيل البيانات (oldnew العمود) قد تكون لديك بيانات غير متناسقة إذا قمت بالاستعلام عن عمود جديد.

التراجع عن التطبيق

الآن لدينا نسخة التطبيق 2.0.0 وقاعدة البيانات في v2.

الخطوات التالية:

  1. استرجاع تطبيقك إلى الإصدار 1.0.0.
  2. نص 1.0.0 لا يستخدم عمود في قاعدة البيانات surname، لذلك يجب أن تكون عملية الاستعادة ناجحة

تغييرات قاعدة البيانات

تحتوي قاعدة البيانات على عمود اسمه last_name.

البرنامج النصي المصدر لمسار الطيران:

CREATE TABLE PERSON (
id BIGINT GENERATED BY DEFAULT AS IDENTITY,
first_name varchar(255) not null,
last_name varchar(255) not null
);

insert into PERSON (first_name, last_name) values ('Dave', 'Syer');

إضافة البرنامج النصي surname.

انتباه. تذكر أنه لا يمكنك إضافة أي قيود NOT NULL إلى العمود الذي تضيفه. إذا قمت باستعادة JAR، فلن يكون لدى الإصدار القديم أي فكرة عن العمود المضاف وسيقوم تلقائيًا بتعيينه على NULL. إذا كان هناك مثل هذا القيد، فسيتم كسر التطبيق القديم ببساطة.

-- NOTE: This field can't have the NOT NULL constraint cause if you rollback, the old version won't know about this field
-- and will always set it to NULL
ALTER TABLE PERSON ADD surname varchar(255);

-- WE'RE ASSUMING THAT IT'S A FAST MIGRATION - OTHERWISE WE WOULD HAVE TO MIGRATE IN BATCHES
UPDATE PERSON SET PERSON.surname = PERSON.last_name

تغييرات التعليمات البرمجية

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

/*
 * Copyright 2012-2016 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package sample.flyway;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;

@Entity
public class Person {
    @Id
    @GeneratedValue
    private Long id;
    private String firstName;
    private String lastName;
    private String surname;

    public String getFirstName() {
        return this.firstName;
    }

    public void setFirstName(String firstName) {
        this.firstName = firstName;
    }

    /**
     * Reading from the new column if it's set. If not the from the old one.
     *
     * When migrating from version 1.0.0 -> 2.0.0 this can lead to a possibility that some data in
     * the surname column is not up to date (during the migration process lastName could have been updated).
     * In this case one can run yet another migration script after all applications have been deployed in the
     * new version to ensure that the surname field is updated.
     *
     * However it makes sense since when looking at the migration from 2.0.0 -> 3.0.0. In 3.0.0 we no longer
     * have a notion of lastName at all - so we don't update that column. If we rollback from 3.0.0 -> 2.0.0 if we
     * would be reading from lastName, then we would have very old data (since not a single datum was inserted
     * to lastName in version 3.0.0).
     */
    public String getSurname() {
        return this.surname != null ? this.surname : this.lastName;
    }

    /**
     * Storing both FIRST_NAME and SURNAME entries
     */
    public void setSurname(String surname) {
        this.lastName = surname;
        this.surname = surname;
    }

    @Override
    public String toString() {
        return "Person [firstName=" + this.firstName + ", lastName=" + this.lastName + ", surname=" + this.surname
                + "]";
    }
}

الخطوة 3: إزالة اسم العائلة من الكود

إصدار التطبيق: 3.0.0

نسخة قاعدة البيانات:v3

تعليق

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

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

التراجع عن التطبيق

حاليا لدينا نسخة التطبيق 3.0.0 وقاعدة البيانات v3. إصدار 3.0.0 لا يحفظ البيانات ل last_name. وهذا يعني أنه في surname يتم تخزين أحدث المعلومات.

الخطوات التالية:

  1. استرجاع تطبيقك إلى الإصدار 2.0.0.
  2. نص 2.0.0 الاستخدامات و last_name и surname.
  3. نص 2.0.0 سوف تأخذ surname، إذا لم يكن صفراً، وإلا -last_name

تغييرات قاعدة البيانات

لا توجد تغييرات هيكلية في قاعدة البيانات. يتم تنفيذ البرنامج النصي التالي لإجراء الترحيل النهائي للبيانات القديمة:

-- WE'RE ASSUMING THAT IT'S A FAST MIGRATION - OTHERWISE WE WOULD HAVE TO MIGRATE IN BATCHES
-- ALSO WE'RE NOT CHECKING IF WE'RE NOT OVERRIDING EXISTING ENTRIES. WE WOULD HAVE TO COMPARE
-- ENTRY VERSIONS TO ENSURE THAT IF THERE IS ALREADY AN ENTRY WITH A HIGHER VERSION NUMBER
-- WE WILL NOT OVERRIDE IT.
UPDATE PERSON SET PERSON.surname = PERSON.last_name;

-- DROPPING THE NOT NULL CONSTRAINT; OTHERWISE YOU WILL TRY TO INSERT NULL VALUE OF THE LAST_NAME
-- WITH A NOT_NULL CONSTRAINT.
ALTER TABLE PERSON MODIFY COLUMN last_name varchar(255) NULL DEFAULT NULL;

تغييرات التعليمات البرمجية

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

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

/*
 * Copyright 2012-2016 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package sample.flyway;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;

@Entity
public class Person {
    @Id
    @GeneratedValue
    private Long id;
    private String firstName;
    private String surname;

    public String getFirstName() {
        return this.firstName;
    }

    public void setFirstName(String firstName) {
        this.firstName = firstName;
    }

    public String getSurname() {
        return this.surname;
    }

    public void setSurname(String lastname) {
        this.surname = lastname;
    }

    @Override
    public String toString() {
        return "Person [firstName=" + this.firstName + ", surname=" + this.surname
                + "]";
    }
}

الخطوة 4: إزالة اسم العائلة من قاعدة البيانات

إصدار التطبيق: 4.0.0

نسخة قاعدة البيانات: v4

تعليق

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

سجلات تنفيذ البرنامج النصي

We will do it in the following way:

01) Run 1.0.0
02) Wait for the app (1.0.0) to boot
03) Generate a person by calling POST localhost:9991/person to version 1.0.0
04) Run 2.0.0
05) Wait for the app (2.0.0) to boot
06) Generate a person by calling POST localhost:9991/person to version 1.0.0
07) Generate a person by calling POST localhost:9992/person to version 2.0.0
08) Kill app (1.0.0)
09) Run 3.0.0
10) Wait for the app (3.0.0) to boot
11) Generate a person by calling POST localhost:9992/person to version 2.0.0
12) Generate a person by calling POST localhost:9993/person to version 3.0.0
13) Kill app (3.0.0)
14) Run 4.0.0
15) Wait for the app (4.0.0) to boot
16) Generate a person by calling POST localhost:9993/person to version 3.0.0
17) Generate a person by calling POST localhost:9994/person to version 4.0.0

Starting app in version 1.0.0
Generate a person in version 1.0.0
Sending a post to 127.0.0.1:9991/person. This is the response:

{"firstName":"52b6e125-4a5c-429b-a47a-ef18bbc639d2","lastName":"52b6e125-4a5c-429b-a47a-ef18bbc639d2"}

Starting app in version 2.0.0

Generate a person in version 1.0.0
Sending a post to 127.0.0.1:9991/person. This is the response:

{"firstName":"e41ee756-4fa7-4737-b832-e28827a00deb","lastName":"e41ee756-4fa7-4737-b832-e28827a00deb"}

Generate a person in version 2.0.0
Sending a post to 127.0.0.1:9992/person. This is the response:

{"firstName":"0c1240f5-649a-4bc5-8aa9-cff855f3927f","lastName":"0c1240f5-649a-4bc5-8aa9-cff855f3927f","surname":"0c1240f5-649a-4bc5-8aa9-cff855f3927f"}

Killing app 1.0.0

Starting app in version 3.0.0

Generate a person in version 2.0.0
Sending a post to 127.0.0.1:9992/person. This is the response:
{"firstName":"74d84a9e-5f44-43b8-907c-148c6d26a71b","lastName":"74d84a9e-5f44-43b8-907c-148c6d26a71b","surname":"74d84a9e-5f44-43b8-907c-148c6d26a71b"}

Generate a person in version 3.0.0
Sending a post to 127.0.0.1:9993/person. This is the response:
{"firstName":"c6564dbe-9ab5-40ae-9077-8ae6668d5862","surname":"c6564dbe-9ab5-40ae-9077-8ae6668d5862"}

Killing app 2.0.0

Starting app in version 4.0.0

Generate a person in version 3.0.0
Sending a post to 127.0.0.1:9993/person. This is the response:

{"firstName":"cbe942fc-832e-45e9-a838-0fae25c10a51","surname":"cbe942fc-832e-45e9-a838-0fae25c10a51"}

Generate a person in version 4.0.0
Sending a post to 127.0.0.1:9994/person. This is the response:

{"firstName":"ff6857ce-9c41-413a-863e-358e2719bf88","surname":"ff6857ce-9c41-413a-863e-358e2719bf88"}

تغييرات قاعدة البيانات

حول v3 نحن فقط نزيل العمود last_name وإضافة القيود المفقودة.

-- REMOVE THE COLUMN
ALTER TABLE PERSON DROP last_name;

-- ADD CONSTRAINTS
UPDATE PERSON SET surname='' WHERE surname IS NULL;
ALTER TABLE PERSON ALTER COLUMN surname VARCHAR NOT NULL;

تغييرات التعليمات البرمجية

لا توجد تغييرات على التعليمات البرمجية.

إنتاج

لقد نجحنا في تطبيق تغيير اسم العمود غير المتوافق مع الإصدارات السابقة عن طريق إجراء العديد من عمليات النشر المتوافقة مع الإصدارات السابقة. فيما يلي ملخص للإجراءات التي تم تنفيذها:

  1. نشر نسخة التطبيق 1.0.0 с v1 مخطط قاعدة البيانات (اسم العمود = last_name)
  2. نشر نسخة التطبيق 2.0.0, الذي يخزن البيانات فيه last_name и surname. يقرأ التطبيق من last_name. قاعدة البيانات في الإصدار v2تحتوي على أعمدة مثل last_nameو surname. surname هي نسخة من لast_name. (ملاحظة: يجب ألا يحتوي هذا العمود على قيد غير فارغ)
  3. نشر نسخة التطبيق 3.0.0، الذي يقوم بتخزين البيانات فقط في surname ويقرأ من اللقب. أما بالنسبة لقاعدة البيانات، فإن عملية الترحيل الأخيرة جارية last_name в surname. أيضا القيد ليس فارغ تمت إزالته من last_name. قاعدة البيانات الآن في الإصدار v3
  4. نشر نسخة التطبيق 4.0.0 - لم يتم إجراء أي تغييرات على الكود. نشر قاعدة البيانات v4، الذي يزيل last_name. هنا يمكنك إضافة أي قيود مفقودة إلى قاعدة البيانات.

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

قانون

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

مشاريع

بعد استنساخ المستودع، سترى بنية المجلد التالي.

├── boot-flyway-v1              - 1.0.0 version of the app with v1 of the schema
├── boot-flyway-v2              - 2.0.0 version of the app with v2 of the schema (backward-compatible - app can be rolled back)
├── boot-flyway-v2-bad          - 2.0.0.BAD version of the app with v2bad of the schema (backward-incompatible - app cannot be rolled back)
├── boot-flyway-v3              - 3.0.0 version of the app with v3 of the schema (app can be rolled back)
└── boot-flyway-v4              - 4.0.0 version of the app with v4 of the schema (app can be rolled back)

مخطوطات

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

لترى الحالة مع التغييرات المتوافقة مع الإصدارات السابقة، يجري:

./scripts/scenario_backward_compatible.sh

وأن نرى حالة مع تغييرات غير متوافقة مع الوراء، يجري:

./scripts/scenario_backward_incompatible.sh

نموذج مسار الرحلة للتمهيد الربيعي

جميع الأمثلة مأخوذة من Spring Boot Sample Flyway.

يمكنك إلقاء نظرة على http://localhost:8080/flyway، هناك قائمة من البرامج النصية.

يتضمن هذا المثال أيضًا وحدة التحكم H2 (في http://localhost:8080/h2-console) حتى تتمكن من عرض حالة قاعدة البيانات (عنوان URL الافتراضي لـ jdbc هو jdbc:h2:mem:testdb).

بالإضافة إلى ذلك

اقرأ أيضًا مقالات أخرى على مدونتنا:

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

إضافة تعليق