Истина на првом месту, или зашто систем треба да буде дизајниран на основу уређаја базе података

Хеј Хабр!

Настављамо да истражујемо тему Јава и Пролећеукључујући и на нивоу базе података. Данас нудимо да прочитамо зашто при дизајнирању великих апликација од одлучујућег значаја треба да буде структура базе података, а не Јава код, како се то ради и који су изузеци од овог правила.

У овом прилично закашњелом чланку, објаснићу зашто мислим да у скоро свим случајевима модел података у апликацији треба да буде дизајниран „из базе података“, а не „из могућности Јаве“ (или било ког клијентског језика који сте рад са). Одабиром другог приступа улазите на дуг пут бола и патње када ваш пројекат почне да расте.

Чланак је написан на основу једно питање, дато на Стацк Оверфлов.

Занимљиве дискусије на Реддиту у одељцима /р/јава и /р/програмирање.

Генерисање кода

Колико сам изненађен што постоји тако мали слој корисника који, пошто су се упознали са јООК, негодују због чињенице да се јООК озбиљно ослања на генерисање изворног кода за покретање. Нико вас не спречава да користите јООК онако како вам одговара, и нико вас не тера да користите генерисање кода. Али подразумевано (као што је описано у приручнику), јООК функционише овако: почињете са (застарелом) шемом базе података, вршите обрнути инжењеринг помоћу генератора кодова јООК да бисте добили скуп класа које представљају ваше табеле, а затим пишете тип- сигурни упити према овим табелама:

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

Код се генерише или ручно ван градње или ручно на свакој градњи. На пример, таква регенерација може уследити одмах након тога Миграција базе података Фливаи, која се такође може обавити ручно или аутоматски.

Генерисање изворног кода

Постоје различите филозофије, предности и мане повезане са овим приступима генерисању кода – ручно и аутоматско – о којима нећу детаљно да расправљам у овом чланку. Али, генерално, цела поента генерисаног кода је у томе што вам омогућава да репродукујете у Јави „истину“ коју узимамо здраво за готово, било у нашем систему или ван њега. У извесном смислу, компајлери који генеришу бајткод, машински код или неку другу врсту кода из изворног кода раде исту ствар – добијамо репрезентацију наше „истине“ на другом језику, без обзира на конкретне разлоге.

Постоји много таквих генератора кода. На пример, КСЈЦ може да генерише Јава код заснован на КССД или ВСДЛ датотекама. Принцип је увек исти:

  • Постоји нека истина (интерна или екстерна) - на пример, спецификација, модел података итд.
  • Треба нам локално представљање ове истине у нашем програмском језику.

Штавише, скоро увек је препоручљиво генерисати такву репрезентацију - како би се избегла редундантност.

Добављачи типова и обрада напомена

Напомена: Други, модернији и специфичнији приступ генерисању кода за јООК укључује употребу добављача типова, како су имплементирани у Ф#. У овом случају, код генерише компајлер, заправо у фази компилације. У принципу, такав код не постоји у облику изворних кодова. У Јави постоје слични, иако не тако елегантни алати - то су процесори за напомене, на пример, Ломбок.

У извесном смислу, овде се дешавају исте ствари као у првом случају, осим:

  • Не видите генерисани код (можда ова ситуација некоме не делује тако одбојно?)
  • Морате осигурати да се типови могу обезбедити, то јест, „труе“ мора увек бити доступно. Ово је лако у случају Ломбока, који означава „истину“. Мало је теже са моделима база података који зависе од живе везе која је увек доступна.

Шта је проблем са генерисањем кода?

Поред шкакљивог питања како је боље покренути генерисање кода - ручно или аутоматски, морам да напоменем да има људи који верују да генерисање кода уопште није потребно. Оправдање за ову тачку гледишта, на коју сам најчешће наилазио, је да је тада тешко поставити цевовод за изградњу. Да, заиста је тешко. Постоје додатни инфраструктурни трошкови. Ако тек почињете са одређеним производом (било да је то јООК, или ЈАКСБ, или хибернација, итд.), потребно је време да поставите радну површину коју бисте желели да потрошите на учење самог АПИ-ја да бисте извукли вредност из њега .

Ако су трошкови повезани са разумевањем уређаја генератора превисоки, онда је АПИ заиста лоше урадио употребљивост генератора кода (и у будућности се испоставља да је прилагођавање у њему такође тешко). Употребљивост би требало да буде највиши приоритет за било који такав АПИ. Али то је само један аргумент против генерисања кода. У супротном, напишите у потпуности руком локални приказ унутрашње или спољашње истине.

Многи ће рећи да немају времена за све ово. Истекао је рок за њихов супер производ. Једног дана касније ћемо прочешљати монтажне транспортере, имаћемо времена. Одговорићу им:

Истина на првом месту, или зашто систем треба да буде дизајниран на основу уређаја базе података
Оригинал, Алан О'Роурке, група публике

Али у Хибернате / ЈПА је тако лако написати код "у Јави".

Заиста. За Хибернате и његове кориснике ово је и благодат и проклетство. У хибернацији можете једноставно написати неколико ентитета, попут овог:

	@Entity
class Book {
  @Id
  int id;
  String title;
}

И скоро све је спремно. Сада је део Хибернате-а да генерише сложене „детаље“ о томе како ће тачно овај ентитет бити дефинисан у ДДЛ-у вашег „дијалекта“ СКЛ-а:

	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);

... и покрените апликацију. Заиста сјајна функција за брзо покретање и испробавање различитих ствари.

Међутим, дозволите ми. Ја сам лагао.

  • Да ли ће хибернација заиста применити дефиницију овог именованог примарног кључа?
  • Хоће ли Хибернате направити индекс на ТИТЛЕ? Знам сигурно да нам треба.
  • Да ли ће Хибернација овај кључ учинити кључем идентитета у спецификацији идентитета?

Вероватно не. Ако свој пројекат развијате од нуле, увек је згодно да једноставно одбаците стару базу података и генеришете нову чим додате потребне напомене. Дакле, ентитет књиге ће на крају попримити облик:

	@Entity
@Table(name = "book", indexes = {
  @Index(name = "i_book_title", columnList = "title")
})
class Book {
  @Id
  @GeneratedValue(strategy = IDENTITY)
  int id;
  String title;
}

Хладан. Регенерисати. Опет, у овом случају, на почетку ће бити врло лако.

Али ћете морати да платите за то касније.

Пре или касније ћете морати да уђете у производњу. Тада модел престаје да ради. Јер:

У производњи више неће бити могуће, ако је потребно, одбацити стару базу података и покренути све од нуле. Ваша база података ће се претворити у застарелу.

Од сада и заувек мораћете да пишете ДДЛ скрипте за миграцију, нпр. коришћењем Фливаи-а. А шта ће се десити са вашим ентитетима у овом случају? Можете или ручно да их прилагодите (и удвостручите своје радно оптерећење) или да их регенеришете у Хибернате-у (колико је вероватно да ће онај генерисан на овај начин испунити ваша очекивања?) Губите на било који начин.

Дакле, чим кренете у производњу, биће вам потребне вруће закрпе. И треба их врло брзо довести у производњу. Пошто нисте припремили и организовали неометано слање својих миграција за производњу, дивље се крпите. А онда немате времена да све урадите како треба. И грдите Хибернате, јер је увек било ко крив, али не ви...

Уместо тога, од самог почетка све је могло да се уради сасвим другачије. На пример, ставите округле точкове на бицикл.

Прво база података

Права "истина" у вашој шеми базе података и "суверенитет" над њом лежи у бази података. Шема је дефинисана само у самој бази података и нигде другде, а сваки од клијената има копију ове шеме, тако да је савршено логично наметнути придржавање шеме и њеног интегритета, да се то уради тачно у бази података - где информације се чувају.
Ово је стара чак и излуђена мудрост. Примарни и јединствени кључеви су добри. Страни кључеви су у реду. Провера ограничења је добра. Тврдње - Добро.

И то није све. На пример, користећи Орацле, вероватно бисте желели да наведете:

  • У ком табелном простору се налази ваша табела
  • Која је њена ПЦТФРЕЕ вредност
  • Која је величина кеша у вашој секвенци (иза ИД-а)

Све ово можда није важно у малим системима, али није неопходно чекати прелазак на област „великих података“ – можете почети да добијате користи од оптимизација складиштења које обезбеђује произвођач, као што су оне горе поменуте, много раније. Ниједан од ОРМ-ова које сам видео (укључујући јООК) не пружа приступ пуном скупу ДДЛ опција које бисте можда желели да користите у својој бази података. ОРМ-ови нуде неке алате који ће вам помоћи да напишете ДДЛ.

Али на крају дана, добро дизајнирана шема је руком написана у ДДЛ-у. Сваки генерисани ДДЛ је само његова апроксимација.

Шта је са моделом клијента?

Као што је горе поменуто, на клијенту ће вам требати копија ваше шеме базе података, приказ клијента. Непотребно је рећи да овај поглед клијента мора бити синхронизован са стварним моделом. Који је најбољи начин да се то постигне? Са генератором кода.

Све базе података дају своје мета-информације путем СКЛ-а. Ево како да добијете све табеле на различитим СКЛ дијалектима из ваше базе података:

	-- 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() из ЈДБЦ-а, или користећи мета-модул јООК.

Из резултата таквих упита, релативно је лако генерисати било коју репрезентацију вашег модела базе података на страни клијента, без обзира коју технологију користите на клијенту.

  • Ако користите ЈДБЦ или Спринг, можете креирати скуп стринг константи
  • Ако користите ЈПА, онда можете генерисати саме ентитете
  • Ако користите јООК, можете генерисати јООК мета модел

У зависности од тога колико могућности ваш клијентски АПИ нуди (нпр. јООК или ЈПА), генерисани мета модел може бити заиста богат и потпун. Узмимо, на пример, могућност имплицитног спајања, уведено у јООК 3.11, који се ослања на генерисане мета-информације о односима страних кључева између ваших табела.

Сада ће сваки пораст базе података аутоматски ажурирати клијентски код. Замислите на пример:

ALTER TABLE book RENAME COLUMN title TO book_title;

Да ли бисте заиста желели да радите овај посао двапут? Ни у ком случају. Само урезујемо ДДЛ, покрећемо га кроз ваш цевовод за изградњу и добијамо ажурирани ентитет:

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

Или ажурирана класа јООК. Већина ДДЛ промена такође утиче на семантику, а не само на синтаксу. Стога, може бити згодно видети у компајлираном коду на који ће код (или би могао) утицати повећање ваше базе података.

Једина истина

Без обзира коју технологију користите, увек постоји један модел који је једини извор истине за неки подсистем – или бар треба да тежимо томе и избегавамо забуну предузећа где је „истина“ свуда и нигде одједном. Све може бити много лакше. Ако само размењујете КСМЛ датотеке са неким другим системом, само користите КССД. Погледајте јООК-ов ИНФОРМАТИОН_СЦХЕМА мета-модел у КСМЛ облику:
https://www.jooq.org/xsd/jooq-meta-3.10.0.xsd

  • КССД се добро разуме
  • КССД веома добро означава КСМЛ садржај и омогућава валидацију на свим језицима клијента
  • КССД је добро верзионисан и веома компатибилан уназад
  • КССД се може превести у Јава код помоћу КСЈЦ

Последња тачка је важна. Када комуницирамо са спољним системом користећи КСМЛ поруке, желимо да будемо сигурни да су наше поруке валидне. Ово је врло лако постићи са ЈАКСБ, КСЈЦ и КССД. Било би потпуно лудило помислити да би, у приступу дизајна који је први у Јави, где своје поруке правимо као Јава објекте, оне на неки начин могле бити разумљиво приказане у КСМЛ-у и послате на употребу другом систему. КСМЛ генерисан на овај начин био би веома лошег квалитета, недокументован и тежак за развој. Да постоји договор о нивоу квалитета услуге (СЛА) на таквом интерфејсу, одмах бисмо га зезнули.

Да будем искрен, то је управо оно што се стално дешава са ЈСОН АПИ-јем, али то је друга прича, расправљаћу се следећи пут...

Базе података: исте су

Радећи са базама података, схватате да су све оне у основи исте. База података поседује своје податке и мора да управља шемом. Све модификације у шеми морају бити имплементиране директно у ДДЛ тако да се ажурира једини извор истине.

Када дође до ажурирања извора, сви клијенти такође морају да ажурирају своје копије модела. Неки клијенти могу бити написани у Јави користећи јООК и Хибернате или ЈДБЦ (или обоје). Други клијенти могу бити написани у Перлу (пожелимо им срећу), други у Ц#. Није битно. Главни модел је у бази података. ОРМ генерисани модели су обично лошег квалитета, слабо документовани и тешки за развој.

Зато немојте правити грешке. Не правите грешке од самог почетка. Радите из базе података. Изградите цевовод за примену који се може аутоматизовати. Омогућите генераторе кода за практично копирање вашег модела базе података и испуштање на клијенте. И престаните да бринете о генераторима кода. Они су добри. Са њима ћете постати продуктивнији. Све што треба да урадите је да потрошите мало времена на њихово подешавање од самог почетка и имаћете године побољшаних перформанси да бисте изградили причу свог пројекта.

Не захваљуј ми још, касније.

Разјашњење

Да будемо јасни: овај чланак ни на који начин не заговара да цео систем (тј. домен, пословна логика, итд., итд.) треба да буде савијен како би одговарао вашем моделу базе података. Оно о чему говорим у овом чланку је да клијентски код који је у интеракцији са базом података треба да делује на основу модела базе података тако да не репродукује модел базе података у статусу „прве класе“. Таква логика се обично налази на слоју за приступ подацима на вашем клијенту.

У двостепеним архитектурама, које су понегде још увек очуване, овакав системски модел може бити једини могућ. Међутим, у већини система, слој приступа подацима ми се чини као „подсистем“ који обухвата модел базе података.

Изузеци

Постоје изузеци од сваког правила, а раније сам рекао да приступ прве базе података и генерисања изворног кода понекад може бити неприкладан. Ево неколико таквих изузетака (вероватно има и других):

  • Када је шема непозната и треба је отворити. На пример, обезбеђујете алатку која помаже корисницима да се крећу кроз било који дијаграм. Фуј. Овде нема генерисања кода. Али ипак - база података пре свега.
  • Када треба генерисати коло у ходу да би се решио неки проблем. Чини се да је овај пример мало напета верзија шаблона вредност атрибута ентитета, тј., заправо немате добро дефинисану шему. У овом случају, често не можете ни бити сигурни да ће вам РДБМС одговарати.

Изузеци су по природи изузетни. У већини случајева који укључују коришћење РДБМС-а, шема је унапред позната, налази се унутар РДБМС-а и једини је извор „истине“, а сви клијенти морају да набаве копије изведене из ње. У идеалном случају, ово би требало да укључује генератор кода.

Извор: ввв.хабр.цом

Додај коментар