اول حقیقت یا اینکه چرا سیستم باید بر اساس ساختار پایگاه داده طراحی شود

هی هابر!

ما به بررسی موضوع ادامه می دهیم جاوه и بهاراز جمله در سطح پایگاه داده. امروز پیشنهاد می کنیم در مورد اینکه چرا هنگام طراحی برنامه های کاربردی بزرگ، ساختار پایگاه داده است و نه کد جاوا که باید از اهمیت تعیین کننده ای برخوردار باشد، چگونه این کار انجام می شود و چه استثناهایی از این قانون وجود دارد، مطالعه کنیم.

در این مقاله نسبتاً دیرهنگام، توضیح خواهم داد که چرا فکر می‌کنم تقریباً در همه موارد، مدل داده در یک برنامه باید «از پایگاه داده» طراحی شود نه «بر اساس قابلیت‌های جاوا» (یا هر زبان کلاینت که هستید). کار با). با انتخاب رویکرد دوم، زمانی که پروژه شما شروع به رشد کرد، وارد مسیر طولانی درد و رنج می شوید.

مقاله بر اساس نوشته شده است یک سوال، در Stack Overflow داده شده است.

بحث های جالب در مورد reddit در بخش ها /r/java и /r/برنامه نویسی.

تولید کد

من چقدر متعجبم که چنین لایه کوچکی از کاربران وجود دارند که پس از آشنایی با jOOQ، از این واقعیت که jOOQ به طور جدی به تولید کد منبع برای اجرا متکی است ناراحت هستند. هیچ کس شما را از استفاده از jOOQ به روشی که صلاح می دانید باز نمی دارد، و هیچ کس شما را مجبور به استفاده از تولید کد نمی کند. اما به‌طور پیش‌فرض (همانطور که در دفترچه راهنما توضیح داده شده است)، jOOQ به این صورت عمل می‌کند: شما با یک طرح پایگاه داده (میراثی) شروع می‌کنید، آن را با تولیدکننده کد jOOQ مهندسی معکوس می‌کنید تا مجموعه‌ای از کلاس‌ها را که نشان‌دهنده جداول شما هستند، دریافت کنید، و سپس type- را بنویسید. پرس و جوهای امن در برابر این جداول:

	for (Record2<String, String> record : DSL.using(configuration)
//   ^^^^^^^^^^^^^^^^^^^^^^^ Информация о типах выведена на 
//   основании сгенерированного кода, на который ссылается приведенное
// ниже условие SELECT 
 
       .select(ACTOR.FIRST_NAME, ACTOR.LAST_NAME)
//           vvvvv ^^^^^^^^^^^^  ^^^^^^^^^^^^^^^ сгенерированные имена
       .from(ACTOR)
       .orderBy(1, 2)) {
    // ...
}

کد به صورت دستی در خارج از بیلد یا به صورت دستی در هر بیلد ایجاد می شود. به عنوان مثال، چنین بازسازی ممکن است بلافاصله پس از آن انجام شود انتقال پایگاه داده Flyway که می تواند به صورت دستی یا خودکار نیز انجام شود.

تولید کد منبع

فلسفه‌ها، مزایا و معایب مختلفی در ارتباط با این رویکردها برای تولید کد - دستی و خودکار - وجود دارد که من قصد ندارم در این مقاله به تفصیل در مورد آنها صحبت کنم. اما، به طور کلی، هدف اصلی کد تولید شده این است که به شما امکان می دهد "حقیقت" را که ما بدیهی می دانیم، در جاوا بازتولید کنید، چه در سیستم خود یا خارج از آن. به یک معنا، کامپایلرهایی که بایت کد، کد ماشین یا نوع دیگری از کد را از کد منبع تولید می کنند، همین کار را انجام می دهند - بدون توجه به دلایل خاص، نمایشی از "حقیقت" خود در زبان دیگری دریافت می کنیم.

از این قبیل تولید کننده های کد بسیاری وجود دارد. مثلا، XJC می تواند کد جاوا را بر اساس فایل های XSD یا WSDL تولید کند. اصل همیشه یکسان است:

  • مقداری حقیقت (داخلی یا خارجی) وجود دارد - به عنوان مثال، یک مشخصات، یک مدل داده و غیره.
  • ما به یک نمایش محلی از این حقیقت در زبان برنامه نویسی خود نیاز داریم.

علاوه بر این، تقریباً همیشه توصیه می شود که چنین نمایشی ایجاد شود - به منظور جلوگیری از افزونگی.

ارائه دهندگان نوع و پردازش حاشیه نویسی

توجه: یکی دیگر از رویکردهای مدرن تر و خاص تر برای تولید کد برای jOOQ شامل استفاده از ارائه دهندگان نوع، همانطور که در F# پیاده سازی شده اند. در این حالت کد توسط کامپایلر و در واقع در مرحله کامپایل تولید می شود. اصولاً چنین کدهایی در قالب کدهای منبع وجود ندارند. در جاوا، ابزارهای مشابه، اگرچه نه به همان اندازه، وجود دارد - به عنوان مثال، اینها پردازنده های حاشیه نویسی هستند. Lombok.

به تعبیری، همان چیزهایی که در مورد اول در اینجا اتفاق می افتد، به جز:

  • شما کد تولید شده را نمی بینید (شاید این وضعیت برای کسی خیلی زننده به نظر نرسد؟)
  • شما باید اطمینان حاصل کنید که انواع را می توان ارائه کرد، یعنی "درست" باید همیشه در دسترس باشد. این در مورد لومبوک که "حقیقت" را حاشیه نویسی می کند، آسان است. با مدل های پایگاه داده ای که به اتصال زنده ای که همیشه در دسترس است بستگی دارد کمی دشوارتر است.

مشکل تولید کد چیست؟

علاوه بر این سوال پیچیده که چگونه بهتر است تولید کد - دستی یا خودکار را شروع کنیم، باید اشاره کنم که افرادی هستند که معتقدند تولید کد اصلاً مورد نیاز نیست. توجیه این دیدگاه، که من اغلب با آن برخورد کردم، این است که پس از آن راه اندازی خط لوله ساخت دشوار است. بله، واقعاً سخت است. هزینه های زیرساختی اضافی وجود دارد. اگر به تازگی با یک محصول خاص (چه jOOQ، یا JAXB، یا Hibernate و غیره) شروع به کار کرده‌اید، راه‌اندازی یک workbench زمان می‌برد که بخواهید برای یادگیری خود API صرف کنید تا از ارزش آن بهره ببرید. .

اگر هزینه های مربوط به درک دستگاه ژنراتور بسیار زیاد باشد، در واقع، API در مورد قابلیت استفاده از تولید کننده کد کار ضعیفی انجام داد (و در آینده معلوم می شود که سفارشی سازی در آن نیز دشوار است). قابلیت استفاده باید بالاترین اولویت برای چنین API باشد. اما این تنها یک استدلال علیه تولید کد است. در غیر این صورت، بازنمایی محلی حقیقت درونی یا بیرونی را کاملاً با دست بنویسید.

بسیاری خواهند گفت که برای انجام همه این کارها وقت ندارند. آنها در آخرین مهلت برای سوپر محصول خود هستند. یک روز بعد ما نوار نقاله های مونتاژ را شانه می کنیم، زمان خواهیم داشت. من به آنها پاسخ خواهم داد:

اول حقیقت یا اینکه چرا سیستم باید بر اساس ساختار پایگاه داده طراحی شود
اصلی, آلن اورورک، پشته مخاطب

اما در Hibernate / JPA نوشتن کد "در جاوا" بسیار آسان است.

واقعا برای 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 واقعاً تعریف این کلید اصلی نامگذاری شده را اعمال خواهد کرد؟
  • آیا Hibernate یک شاخص در TITLE ایجاد می کند؟ من مطمئنم که ما به آن نیاز داریم.
  • آیا Hibernate این کلید را به یک کلید هویت در مشخصات هویت تبدیل می کند؟

احتمالا نه. اگر پروژه خود را از ابتدا توسعه می دهید، همیشه راحت است که به سادگی پایگاه داده قدیمی را دور بیندازید و به محض اضافه کردن حاشیه نویسی های لازم، یک پایگاه جدید ایجاد کنید. بنابراین، موجودیت Book در نهایت به شکل زیر در می آید:

	@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 بخواهید آنها را برای شما بازسازی کند (چقدر احتمال دارد محصولی که از این طریق تولید می شود انتظارات شما را برآورده کند؟) در هر صورت ضرر می کنید.

بنابراین، به محض اینکه وارد مرحله تولید شوید، به تکه های داغ نیاز خواهید داشت. و باید خیلی سریع به تولید برسند. از آنجایی که شما خط لوله روان مهاجرت های خود را برای تولید آماده و سازماندهی نکرده اید، به شدت وصله می کنید. و پس از آن شما برای انجام همه کارها به درستی وقت ندارید. و شما Hibernate را سرزنش می کنید، زیرا همیشه تقصیر هر کسی است، اما نه شما ...

در عوض، از همان ابتدا، همه چیز می توانست کاملاً متفاوت انجام شود. به عنوان مثال چرخ های گرد را روی دوچرخه قرار دهید.

ابتدا پایگاه داده

"حقیقت" واقعی در طرح پایگاه داده شما و "حاکمیت" بر آن در پایگاه داده نهفته است. طرحواره فقط در خود پایگاه داده تعریف شده است و در هیچ جای دیگری، و هر یک از مشتریان یک کپی از این طرحواره دارند، بنابراین کاملا منطقی است که پایبندی به طرح و یکپارچگی آن را اعمال کنیم، آن را درست در پایگاه داده انجام دهیم - جایی که اطلاعات ذخیره می شود.
این حکمتی کهنه و حتی هک شده است. کلیدهای اصلی و منحصر به فرد خوب هستند. کلیدهای خارجی خوب هستند. بررسی محدودیت ها خوب است. ادعاها - خوب.

و، این همه نیست. به عنوان مثال، با استفاده از Oracle، احتمالاً می خواهید مشخص کنید:

  • جدول شما در چه فضای جدولی است؟
  • ارزش PCTFREE او چقدر است
  • اندازه کش در دنباله شما چقدر است (پشت شناسه)

همه اینها ممکن است در سیستم های کوچک اهمیتی نداشته باشد، اما لازم نیست تا انتقال به حوزه "داده های بزرگ" صبر کنید - می توانید از بهینه سازی های ذخیره سازی ارائه شده توسط فروشنده، مانند موارد ذکر شده در بالا، خیلی زودتر بهره مند شوید. هیچ یک از ORM هایی که من دیده ام (از جمله jOOQ) به مجموعه کامل گزینه های DDL که ممکن است بخواهید در پایگاه داده خود استفاده کنید، دسترسی ندارند. ORM ها ابزارهایی را برای کمک به نوشتن 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 را ایجاد کنید

بسته به میزان قدرتی که API مشتری شما ارائه می دهد (به عنوان مثال 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 را می توان با استفاده از XJC به کد جاوا ترجمه کرد

نکته آخر مهم است. هنگام برقراری ارتباط با یک سیستم خارجی با استفاده از پیام های XML، می خواهیم مطمئن شویم که پیام های ما معتبر هستند. این کار با JAXB، XJC و XSD بسیار آسان است. دیوانگی محض است که فکر کنیم، در یک رویکرد طراحی جاوا اول، که در آن پیام‌های خود را به‌عنوان اشیاء جاوا می‌سازیم، می‌توان آن‌ها را به نحوی قابل فهم به XML ارائه کرد و برای مصرف به سیستم دیگری فرستاد. XML تولید شده به این روش کیفیت بسیار پایینی دارد، مستند نشده و توسعه آن دشوار است. اگر توافقی در مورد سطح کیفیت خدمات (SLA) در چنین رابطی وجود داشت، بلافاصله آن را خراب می کردیم.

صادقانه بگویم، این دقیقاً همان چیزی است که همیشه با JSON API اتفاق می افتد، اما این داستان دیگری است، دفعه بعد بحث خواهم کرد ...

پایگاه های داده: آنها یکسان هستند

با کار با پایگاه های داده، می فهمید که همه آنها اساساً یکسان هستند. پایگاه داده مالک داده های خود است و باید طرحواره را مدیریت کند. هر گونه تغییری که در طرحواره ایجاد می شود باید مستقیماً در DDL پیاده سازی شود تا منبع منفرد حقیقت به روز شود.

هنگامی که به روز رسانی منبع رخ داد، همه مشتریان باید نسخه های خود را از مدل نیز به روز کنند. برخی از مشتریان ممکن است با استفاده از jOOQ و Hibernate یا JDBC (یا هر دو) در جاوا نوشته شوند. سایر کلاینت ها ممکن است به زبان پرل نوشته شده باشند (برایشان آرزوی موفقیت کنیم)، دیگران در سی شارپ. مهم نیست. مدل اصلی در پایگاه داده است. مدل های تولید شده توسط ORM معمولاً کیفیت پایینی دارند، مستندات ضعیفی دارند و توسعه آنها دشوار است.

پس اشتباه نکنید از همان ابتدا اشتباه نکنید. از پایگاه داده کار کنید. یک خط لوله استقرار بسازید که می تواند خودکار باشد. مولدهای کد را فعال کنید تا مدل پایگاه داده شما را به راحتی کپی کرده و آن را روی کلاینت ها تخلیه کنند. و دیگر نگران تولیدکنندگان کد نباشید. آنها خوب هستند. با آنها، بهره وری بیشتری خواهید داشت. تنها کاری که باید انجام دهید این است که از همان ابتدا کمی زمان صرف تنظیم آنها کنید و سالها عملکرد بهتری برای ساختن داستان پروژه خود خواهید داشت.

بعدا هنوز از من تشکر نکن

وضوح

برای روشن بودن: این مقاله به هیچ وجه از این موضوع حمایت نمی کند که کل سیستم (یعنی دامنه، منطق تجاری و غیره و غیره) باید برای مطابقت با مدل پایگاه داده شما منعطف شود. آنچه من در این مقاله در مورد آن صحبت می کنم این است که کد مشتری که با یک پایگاه داده تعامل دارد باید بر اساس مدل پایگاه داده عمل کند تا مدل پایگاه داده را در وضعیت "کلاس اول" بازتولید نکند. چنین منطقی معمولاً در لایه دسترسی به داده در مشتری شما قرار دارد.

در معماری‌های دو سطحی، که هنوز در برخی مکان‌ها حفظ می‌شوند، چنین مدل سیستمی ممکن است تنها مورد ممکن باشد. با این حال، در اکثر سیستم ها، لایه دسترسی به داده به نظر من یک "زیر سیستم" است که مدل پایگاه داده را در بر می گیرد.

استثنا

برای هر قاعده ای استثنا وجود دارد و من قبلاً گفته ام که ابتدا پایگاه داده و رویکرد تولید کد منبع گاهی اوقات می توانند نامناسب باشند. در اینجا چند مورد از این استثناها وجود دارد (احتمالاً موارد دیگر وجود دارد):

  • زمانی که طرحواره ناشناخته است و باید باز شود. به عنوان مثال، شما ابزاری را برای کمک به کاربران در جهت یابی هر نمودار ارائه می دهید. فوو در اینجا تولید کد وجود ندارد. اما هنوز - اول از همه پایگاه داده.
  • زمانی که برای حل مشکلی باید مداری در حال تولید ایجاد شود. به نظر می رسد این مثال یک نسخه کمی فریبنده از الگو باشد مقدار ویژگی موجودیت، یعنی شما واقعاً یک طرح واره کاملاً تعریف شده ندارید. در این مورد، اغلب حتی نمی توانید مطمئن باشید که یک RDBMS برای شما مناسب است.

استثناها طبیعتاً استثنایی هستند. در بیشتر موارد مربوط به استفاده از RDBMS، طرح واره از قبل شناخته شده است، در داخل RDBMS قرار دارد و تنها منبع "حقیقت" است و همه مشتریان باید کپی های مشتق شده از آن را بدست آورند. در حالت ایده آل، این باید شامل یک تولید کننده کد باشد.

منبع: www.habr.com

اضافه کردن نظر