Како да уклоните наследство појединачног стола са вашег монолита из шина

Насљеђивање је лако - све док се не морате носити са техничким дугом и порезима.

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

Али како су истакли многи постови на блоговима (овај, овај, и овај на пример), СТИ се не скалира добро, поготово како подаци расту и нове подкласе почињу увелике варирати од њихових суперкласа и једна од друге. Као што сте могли претпоставити, исто се догодило и у нашој кодној бази! Наша се школа ширила и подржавали смо све више функција и врста предавања. Временом су модели почели да цветају и мутирају и више не одражавају праву апстракцију за домен.

Неко смо време живели у том простору, дајући том шифри шифру и закрпајући је само кад је то било потребно. А онда је дошло време за фактор.

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

Дакле, у овом посту ћу вам објаснити нешто о СТИ, пружити неки контекст о нашем домену, изнети опсег посла и разговарати о стратегијама које сам користио да безбедно разместим промене, док умањим површину за озбиљну штету док сам темељну језгру наше апликације.

О наслеђивању једне таблице (СТИ)

Укратко, наслеђивање једноструких таблица у трачницама омогућава вам да сместите више врста класа у исту табелу. У активном запису име класе се чува као тип у табели. На пример, можда имате Лаб, Реадме и Пројецт све уживо у табели са садржајем:

класа Лаб <Садржај; крај
класа Реадме <Садржај; крај
класни пројекат <Садржај; крај

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

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

цреате_табле "цонтент", форце:: каскада до | т |
  т.интегер "цуррицулум_ид",
  т.стринг "тип",
  т.тект "маркдовн_формат",
  т.стринг "наслов",
  т.интегер "трацк_ид",
  т.интегер "гитхуб_репоситори_ид"
крај

Идентификовање обима рада

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

лекција из разреда <Наставни план
  хас_мани: садржај, -> {ред (редослед:: асц)}
  хас_оне: садржај, Фореигн_кеи:: цуррицулум_ид
  хас_мани: реадмес, Фореигн_кеи:: цуррицулум_ид
  хас_оне: лаб, Фореигн_кеи:: цуррицулум_ид
  хас_оне: реадме, Фореигн_кеи:: цуррицулум_ид
  хас_мани: додато_репо, кроз:: садржај
крај

Збуњени? И ја сам то био само један модел, који сам морао да променим.

Тако сам са својим сјајним и талентованим саиграчима (Кате Траверс, Стевен Нунез и Спенцер Рогерс) смислио бољи дизајн како бих смањио конфузију и олакшао овај систем.

Нови дизајн

Концепт који је Цонтент покушавао да представља био је посредник између ГитхубРепоситори-а и Лекције.

Сваки део „канонског“ садржаја лекције повезан је са спремиштем на ГитХуб-у. Када се лекције објаве или „распореде“ за студенте, направимо копију тог ГитХуб-овог спремишта и дајемо студентима везу до њега. Веза између лекције и распоређене верзије назива се АссигнедРепо.

Дакле, постоје ГитХуб складишта на оба краја лекције: канонска верзија и распоређена верзија.

Садржај класе <АцтивеРецорд :: Басе
  припада_то:: лекција, инострани кључ:: наставни програм_ид
  припада_то:: гитхуб_репоситори
крај
класа АссигнедРепо <АцтивеРецорд :: Басе
  припада_то: садржај
  припада_то: реадме
  припада_то: лаб
  припада_то: пројекат
крај

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

Дакле, оно што смо одлучили да урадимо је да заменимо Садржај новим концептом који се зове ЦаноницалМатериал, и доделимо АссигнедРепо директну референцу на повезану лекцију уместо да прођемо кроз Цонтент.

Дијаграм старог до новог система, где црвене испрекидане линије означавају стазе означене за застаревање

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

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

Стратегије за обнављање и замјену СТИ

Нови модел

Прво смо направили нову табелу која се зове цаноницал_материалс и креирали нови модел и асоцијације.

класа ЦаноницалМатериал <АцтивеРецорд :: Басе
  припада_то: гитхуб_репоситори
  хас_мани: лекције
крај

Такође смо додали страни кључ цаноницал_материал_ид у табелу наставних планова, тако да би лекција могла да одржава референцу на њега.

У табелу додељених_репоса додали смо колону лекције.

Дуал Вритес

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

На пример:

лекција.буилд_цонтент (
  'репо_наме' => репо.наме,
  'гитхуб_репоситори_ид' => репо_ид,
  'маркдовн_формат' => репо.реадме
)

лекција.цаноницал_материал = репо.цаноницал_материал
лекција.саве

То нам је омогућило да поставимо темеље за коначно уклањање садржаја.

Поновно пуњење

Следећи корак у процесу био је поновно пуњење података. Написали смо раке задатке како бисмо попунили наше табеле и осигурали да постоји ЦаноницалМатериал за сваки ГитхубРепоситори и да сваки лекција има ЦаноницалМатериал. А онда смо извршили задатке на нашем производном серверу.

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

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

Замена

А онда је почео забавни део. Да бисмо замену учинили што сигурнијом, користили смо обележје функција за слање тамног кода у мањим ПР-овима, што нам је омогућило да створимо бржу петљу повратних информација и сазнамо пре него што се ствари покваре. За то смо користили драгуљ са роллоутом, који такође користимо за развој стандардних функција.

Шта треба тражити

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

Када уклањате СТИ, ово бисте требали потражити:

  • Једнине и множине облика модела, укључујући све његове подразреде, методе, корисне методе, асоцијације и упите.
  • СКЛ упити тврдо кодирани
  • Контролери
  • Серијализатори
  • Прикази

На пример, за садржај који је значио тражење:

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

Вјерујем да је то прилично опсежан списак садржаја. А онда сам исто урадио за лабораторију, читање и пројекат. То можете видети јер је Раилс тако флексибилан и додаје много корисних метода, тешко је пронаћи сва места на којима се модел користи.

Како заправо замијенити имплементацију након што сте пронашли све позиваоце

Једном када пронађете све локације за позиве модела које покушавате да замените или уклоните, преусмерићете ствари. Генерално, процес који смо пратили био је

  1. Замијените понашање методе у дефиницији или промијените методу на мјесту позива
  2. Напишите нове методе и позовите их иза заставе функције на месту позива
  3. Прекидајте зависности о асоцијацијама са методама
  4. Повећајте грешке иза заставе функције ако нисте сигурни у методу
  5. Замените објекте који имају исти интерфејс

Ево примера сваке стратегије.

1а. Замените понашање или питање методе

Неке су замјене прилично једноставне. Поставите заставицу функције да кажете „назовите овај код уместо овог другог кода када је та застава укључена“.

Дакле, уместо упита заснованог на садржају, овде постављамо питање на основу цаноницал_материал.

1б. Промените метод на месту позива

Понекад је једноставније заменити методу на месту позива за стандардизацију позваних метода. (Када то учините, требало би да покренете тестни пакет и / или напишете тестове.) То може отворити пут ка даљем рефакторингу.

Овај пример показује како да се разбије зависност о ступцу цаноницал_ид, који ускоро више неће постојати. Примјетите да смо замијенили метод на мјесту позива без стављања тога иза заставе функције. Радећи овај рефакторинг, приметили смо да смо цаноницал_ид ископчали на више места, тако да смо логику то учинили у другој методи коју бисмо могли повезати на друге упите. Метода на месту позива је промењена, али понашање се није променило све док се није укључила застава функције.

2. Напишите нове методе и позовите их иза заставе функције на месту позива

Ова стратегија је повезана са заменом метода, само у овом облику пишемо нови метод и зовемо га иза заставе функције на месту позива. Нарочито је био користан за методу која се зове само на једном месту. Такође нам је омогућио бољи метод - увек користан.

3. Прекините зависности од асоцијација методама

У следећем следећем примеру, траг хас_мани лабс. Будући да знамо да асоцијација хас_мани додаје корисне методе, заменили смо један који се најчешће назива и уклонили линију хас_мани: лабс. Ова метода је у складу са истим интерфејсом, тако да ће све што позива метод пре него што је функција укључена и даље радити.

4. Повећајте грешке иза заставе функције ако нисте сигурни у методу

Било је случајева да нисмо били сигурни да ли смо пропустили место позива. Дакле, уместо да се само тешко уклоне методе, намерно смо подигли грешке како бисмо их ухватили током фазе ручног тестирања. Ово нам је омогућило бољи начин да утврдимо где се зове метода.

5. Замените објекте који имају исти интерфејс

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

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

Тестирање и ручно тестирање

Будући да су промене утицале на карактеристике у читавој бази кода, од којих неке нису биле тестиране, било је тешко са сигурношћу КА, али дали смо све од себе. Обавили смо ручно тестирање на нашем КА серверу, који је ухватио доста грешака и рубних случајева. А онда смо кренули напријед и по критичнијим стазама писали нове тестове.

Изведите, идите уживо и очистите

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

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

А то је, пријатељи, један од начина да се ослободите ширења наслеђивања једноструког стола у вашем монолиту Раилс. Можда ће вам и ова студија случаја помоћи.

Имате ли друге начине уклањања СПИ или рефацторинга? Радознали смо што знамо. Јавите нам у коментарима.

Такође, ангажујемо се! Придружите се нашем тиму. Супер смо, обећавам.

Ресурси и додатно читање

  • Наслеђивање водича
  • Како и када користити наследство појединачних столова у шинама Еугене Ванг (Флатирон Град!)
  • Рефакторинг наше Раилс Апп од наслеђивања за једним столом
  • Насљеђивање једне таблице насупрот полиморфним асоцијацијама у шинама
  • Насљеђивање једне таблице помоћу шина 5.02

Да бисте сазнали више о Флатирон школи, посетите веб страницу, пратите нас на Фацебооку и Твиттеру и посетите нас на предстојећим догађајима у вашој близини.

Флатирон школа поносни је члан ВеВорк породице. Погледајте блогове наше сестринске технологије ВеВорк Тецхнологи и прављење меетупа.