Kako ukloniti nasljedstvo jednog stola iz monolitra s tračnicama

Nasljeđivanje je jednostavno - sve dok se ne morate nositi s tehničkim dugom i porezima.

Kada je prije pet godina nastala glavna baza koda, nasljeđivanje jednostrukih tablica (STI) bilo je prilično popularno. Tim Flatiron laboratorija u to je vrijeme sve napredovao - koristeći ga za sve, od ocjenjivanja i nastavnog plana i programa do sadržaja događaja i sadržaja unutar našeg rastućeg sustava upravljanja učenjem. I to je bilo sjajno - posao je dovršen. To je omogućilo instruktorima da predaju nastavni plan i program, prate napredak učenika i stvaraju zanimljivo korisničko iskustvo.

No, kao što su istaknuli mnogi postovi na blogovima (ovaj, ovaj, i ovaj primjer na primjer), STI ne mjeri jako dobro, pogotovo kako podaci rastu i nove podklase počinju uvelike varirati od njihovih superklasa i jedna od druge. Kao što ste mogli pretpostaviti, isto se dogodilo i u našoj kodnoj bazi! Naša se škola širila i podržavali smo sve više značajki i vrsta predavanja. S vremenom su modeli počeli puhati i mutirati i više ne odražavaju pravu apstrakciju za domenu.

Neko smo vrijeme živjeli u tom prostoru, dajući tom šifri vezu i zakrpajući ga samo kad je to bilo potrebno. A onda je došlo vrijeme za odbojnik.

Proteklih nekoliko mjeseci krenuo sam u misiju da uklonim jedan posebno gadan primjer primjerice STI, onaj koji je uključivao pomalo dvosmisleno imenovani model sadržaja. Kao što je STI u početku postaviti, zapravo je prilično teško ukloniti.

Dakle, u ovom postu ću se malo pozabaviti STI-jem, pružiti kontekst o našoj domeni, opisati opseg posla i razgovarati o strategijama koje sam koristio za sigurno raspoređivanje promjena, dok umanjim površinu zbog ozbiljnih oštećenja dok sam temeljnu jezgru naše aplikacije.

O nasljeđivanju jedne tablice (STI)

Ukratko, nasljeđivanje jednostruke tablice u tračnicama omogućuje vam spremanje više vrsta klasa u istu tablicu. U aktivnom zapisu naziv klase pohranjuje se kao tip u tablici. Na primjer, možda imate Laboratorij, Readme i Projekt sve uživo u tablici sa sadržajem:

klasa Lab 

U ovom su primjeru laboratoriji, čitanja i projekti sve vrste sadržaja koji se mogu povezati s lekcijom.

Shema naše tablice sa sadržajem izgledala je nekako ovako, tako da možete vidjeti da je tip pohranjen u tablici.

create_table "content", force:: kaskada do | t |
  t.integer "curriculum_id",
  t.string "upišite",
  t.text "markdown_format",
  t.string "naslov",
  t.integer "track_id",
  t.integer "github_repository_id"
kraj

Utvrđivanje opsega rada

Sadržaj se širio cijelom aplikacijom, ponekad i zbunjujući. Na primjer, ovo je opisalo odnose u modelu Lekcije.

razred Lekcija  {poredak (redoslijed:: asc)}
  has_one: sadržaj, Foreign_key:: curriculum_id
  has_many: readmes, Foreign_key:: curriculum_id
  has_one: lab, Foreign_key:: curriculum_id
  has_one: readme, Foreign_key:: curriculum_id
  has_many: dodijeljeno_repos, kroz:: sadržaj
kraj

Zbunjeni? Tako je bilo i mene. I to je bio samo jedan model mnogih koji sam morao promijeniti.

Tako sam sa svojim sjajnim i talentiranim suigračima (Kate Travers, Steven Nunez i Spencer Rogers) smislio bolji dizajn kako bih smanjio zbrku i olakšao ovaj sistem.

Novi dizajn

Koncept koji je Content pokušavao predstavljati bio je posrednik između GithubRepository-a i Lekcije.

Svaki dio „kanonskog“ sadržaja lekcije povezan je sa spremištem na GitHub-u. Kada se lekcije objave ili „rasporede“ učenicima, napravimo kopiju tog GitHub-ovog spremišta i studentima dajemo poveznicu do njega. Veza između lekcije i implementirane verzije naziva se AssignedRepo.

Dakle, postoje GitHub skladišta na oba kraja lekcije: kanonska i raspoređena verzija.

Sadržaj klase 
klasa AssignedRepo 

U jednom su trenutku pouke mogle imati više dijelova sadržaja, ali u našem sadašnjem svijetu to više nije slučaj. Umjesto toga, postoje razne vrste lekcija, koje mogu sami uvidjeti gledajući datoteke uključene u njihova pridružena spremišta.

Dakle, ono što smo odlučili učiniti je zamijeniti Sadržaj novim konceptom koji se zove CanonicalMaterial, te dodijeliti AssignedRepo izravnu referencu na povezanu lekciju, umjesto da prođemo kroz Sadržaj.

Dijagram starog do novog sustava, gdje crvene isprekidane crte označavaju staze označene za uklanjanje

Ako to zvuči zbunjujuće i sviđa vam se puno posla, to je zato što jest. Ključni korak, međutim, jest taj što smo morali zamijeniti model u prilično velikoj bazi koda, a na kraju smo se promijenili negdje u carstvu od 6000 linija koda.

Ključni korak, međutim, jest taj što smo morali zamijeniti model u prilično velikoj bazi koda, a na kraju smo se promijenili negdje u području od 6000 linija koda.

Strategije obnove i zamjene SPI

Novi model

Prvo smo stvorili novu tablicu pod nazivom canonical_materials i stvorili novi model i asocijacije.

klasa CanonicalMaterial 

Dodali smo i inozemni ključ canonical_material_id u tablicu nastavnih programa kako bi lekcija mogla održati referencu na njega.

U tablicu dodijeljeni_repos dodali smo stupac lekcije.

Dvostruki zapisi

Nakon postavljanja novih tablica i stupaca, počeli smo istovremeno pisati u stare tablice i nove kako nam zadatak ponovnog popunjavanja ne bi bio potreban više puta. Svaki put kada bi nešto pokušalo stvoriti ili ažurirati sadržajni redak, također bismo stvorili ili ažurirali kanonski_materijal.

Na primjer:

lesson.build_content (
  'repo_name' => repo.name,
  'github_repository_id' => repo_id,
  'markdown_format' => repo.readme
)

lekcija.canonical_material = repo.canonical_material
lesson.save

To nam je omogućilo da postavimo temelje za konačno uklanjanje sadržaja.

zatrpavanje

Sljedeći korak u postupku bio je ponovno punjenje podataka. Napisali smo zadatke grablje kako bismo popunili naše tablice i osigurali postojanje CanonicalMaterial-a za svaki GithubRepository i da je svaki lekciju imao CanonicalMaterial. A onda smo izvršili zadatke na našem proizvodnom poslužitelju.

U ovom krugu refactoring-a preferirali smo valjane podatke kako bismo mogli napraviti čistu pauzu sa naslijeđenim načinom postupanja. Druga je održiva opcija, međutim, pisanje koda koji i dalje podržava starije modele. Prema našem iskustvu, održavanje konfiguracije koda koja podržava naslijeđeno razmišljanje puno je zbunjujuće i skuplje nego što je to bilo dopunsko punjenje i provjeravanje valjanosti podataka.

Prema našem iskustvu, održavanje konfiguracije koda koja podržava naslijeđeno razmišljanje puno je zbunjujuće i skuplje nego što je to bilo dopunsko punjenje i provjeravanje valjanosti podataka.

Zamjena

A onda je počeo zabavni dio. Kako bismo zamjenu učinili što sigurnijom, koristili smo zastave značajki za slanje tamnog koda u manjim PR-ovima, što nam je omogućilo stvaranje brže petlje za povratne informacije i što prije ako se stvari pokvari. Za to smo koristili dragulj rollout, koji također koristimo za razvoj standardnih značajki.

Što tražiti

Jedan od najtežih dijelova zamjene bio je čisti broj stvari koje treba tražiti. Riječ "sadržaj" je nažalost super općenita, tako da je bilo nemoguće jednostavno, globalno pretražiti i zamijeniti, pa sam težio na opsežnije pretraživanje pokušavajući objasniti varijacije.

Kada uklanjate STI, ovo biste trebali potražiti:

  • Jednina i množina oblika modela, uključujući sve njegove podrazrede, metode, korisne metode, asocijacije i upite.
  • Tvrdi kodirani SQL upiti
  • kontroleri
  • Serializers
  • Posjeta

Na primjer, za sadržaj koji je značio traženje:

  • : sadržaj - za udruge i upite
  • : sadržaj - za udruge i upite
  • .joins (: content) - za upite o pridruživanju, koji bi trebali biti uhvaćeni u prethodnom pretraživanju
  • .includes (: content) - za nestrpljivo učitavanje asocijacija drugog reda, koje bi također trebalo biti uhvaćene u prethodnom pretraživanju
  • sadržaj: - za ugniježđene upite
  • sadržaj: - opet, više ugniježđenih upita
  • content_id - za upite izravno putem id-a
  • .content - pozivi metoda
  • .contents - pozivi metoda prikupljanja
  • .build_content - uslužni način koji je dodao has_one i pripada_drugu
  • .create_content - uslužni način koji je dodao has_one i pripada_druživanju
  • .content_ids - uslužni način koji je dodala asocijacija has_many
  • Sadržaj - sam naziv klase
  • sadržaj - obični niz za sve tvrdo kodirane reference ili SQL upite

Vjerujem da je to prilično opsežan popis sadržaja. A onda sam isto učinio za laboratorij, čitanje i projekt. To možete vidjeti jer je Rails tako fleksibilan i dodaje mnogo korisnih metoda, teško je pronaći sva mjesta na kojima se neki model koristi.

Kako zapravo zamijeniti implementaciju nakon što ste pronašli sve pozivatelje

Nakon što zapravo pronađete sve stranice poziva na modelu koje pokušavate zamijeniti ili ukloniti, stvari ćete prepisati. Općenito, postupak koji smo slijedili bio je

  1. Zamijenite ponašanje metode u definiciji ili promijenite metodu na mjestu poziva
  2. Napišite nove metode i nazovite ih iza zastave značajke na mjestu poziva
  3. Prekinuti ovisnosti o asocijacijama s metodama
  4. Povećajte pogreške iza zastavice značajke ako niste sigurni u metodu
  5. Zamijenite objekte s istim sučeljem

Evo primjera svake strategije.

1a. Zamijenite ponašanje ili upit metode

Neke su zamjene prilično izravne. Zastavu značajke postavite na mjesto da kažete "nazovite ovaj kôd umjesto ovog drugog koda kada je ta zastava uključena."

Dakle, umjesto upita na temelju sadržaja, ovdje upitamo na osnovu kanonskog_materijala.

1b. Promijenite metodu na mjestu poziva

Ponekad je jednostavnije zamijeniti metodu na mjestu poziva kako biste standardizirali pozvane metode. (Kad to učinite, trebali biste pokrenuti testni paket i / ili pisati testove.) Ako to učinite, možete otvoriti put prema daljnjem osmišljavanju.

Ovaj primjer pokazuje kako razbiti ovisnost o stupcu canonical_id, koji uskoro više neće postojati. Primijetite da smo zamijenili metodu na mjestu poziva, a da je ne postavimo iza zastave značajke. Radeći ovaj refaktoring, primijetili smo da smo canonical_id iskopčali na više mjesta, tako da smo logiku to učinili na drugoj metodi koju smo mogli povezati s drugim upitima. Metoda na mjestu poziva promijenjena je, ali ponašanje se nije promijenilo dok nije uključena zastava značajke.

2. Napišite nove metode i nazovite ih iza obilježja značajke na mjestu poziva

Ova je strategija povezana s zamjenom metoda, samo u ovom pišemo novu metodu i zovemo je iza obilježja značajke na mjestu poziva. Posebno je bila korisna za metodu koja se zvala samo na jednom mjestu. Također nam je omogućilo bolji pristup metodi - uvijek korisnu.

3. Prekinuti ovisnosti o asocijacijama s metodama

U ovom sljedećem primjeru staza has_many laboratorijima. Budući da znamo da asocijacija has_many dodaje korisne metode, zamijenili smo onu koja se najčešće zove i uklonili liniju has_many: labs. Ova metoda je u skladu s istim sučeljem, tako da sve što poziva metodu prije uključivanja značajke i dalje će raditi.

4. Povećajte pogreške iza zastavice značajke ako niste sigurni u metodu

Bilo je slučajeva da nismo bili sigurni da li smo propustili mjesto poziva. Stoga smo, umjesto samo teških metoda uklanjanja, namjerno podigli pogreške kako bismo ih uhvatili tijekom faze ručnog testiranja. Ovo nam je omogućilo bolji način da utvrdimo gdje se zove metoda.

5. Zamijenite objekte s istim sučeljem

Budući da smo se željeli riješiti udruženja laboratorija, jesmo li napisali provedbu laboratorija? metoda. Umjesto provjere prisutnosti laboratorijskog zapisa, zamijenili smo se materijom canonical_material, delegirali poziv i natjerali taj objekt da odgovori istom metodom.

Ovo su bile najkorisnije strategije za razbijanje ovisnosti i zamjenu novih objekata u čitavom našem monolitnom tipu. Nakon pregleda stotina definicija i mjesta za pozive, zamijenili smo ih ili prepisali jednu po jednu. To je mučan proces koji nikome ne želim, ali na kraju je bio izuzetno koristan u poboljšanju čitljivosti naše baze podataka i uklanjanju starog koda koji nije sjedio. Bilo je potrebno nekoliko frustrirajućih i poteznih tjedana da dođemo do kraja, ali nakon što smo zamijenili većinu referenci, počeli smo raditi ručno testiranje.

Ispitivanje i ručno testiranje

Budući da su promjene utjecale na značajke u čitavoj bazi kodova, od kojih neke nisu bile testirane, bilo je teško osigurati kvalitetu sa sigurnošću, ali dali smo sve od sebe. Ručno smo testirali na našem QA poslužitelju, koji je zabilježio puno grešaka i rubnih slučajeva. A onda smo krenuli naprijed i kritičnijim putovima pisali nove testove.

Izvedite, idite uživo i očistite

Nakon prolaska QA, uključili smo zastavicu i pustili sustav da se podmiri. Nakon što smo bili sigurni da je stabilna, uklonili smo karakteristične zastave i stare kodne staze iz baze podataka kodova. To je, nažalost, bilo teže nego što se očekivalo, jer je podrazumijevalo prepisivanje velikog broja testnih paketa, uglavnom tvornica koje su se implicitno oslanjale na Model sadržaja. Retrospektivno, mogli smo napisati dva skupa testova dok smo radili refaktoring, jedan za trenutni kôd i jedan za kôd iza zastave značajke.

Kao posljednji korak, koji tek treba doći, trebali bismo izraditi sigurnosnu kopiju podataka i odbaciti svoje neiskorištene tablice.

A to je, prijatelji, jedan od načina da se riješite raspršivanja nasljeđivanja pojedinačnih tablica u vašem monolitu Rails. Možda će vam i ova studija slučaja pomoći.

Imate li druge načine uklanjanja SPI ili refactoringa? Znatiželjni smo znati. Javite nam se u komentarima.

Također, zapošljavamo se! Pridružite se našem timu. Super smo, obećavam.

Resursi i dodatno čitanje

  • Nasljeđivanje vodiča
  • Kako i kada koristiti nasljeđivanje jednog stola u tračnicama Eugene Wang (Flatiron Grad!)
  • Refactoring naša tračnica s nasljeđivanjem jednog stola
  • Nasljeđivanje jedne tablice nasuprot polimorfnim udruženjima u tračnicama
  • Nasljeđivanje jednostrukih stolova pomoću tračnica 5.02

Da biste saznali više o Flatiron školi, posjetite web stranicu, pratite nas na Facebooku i Twitteru i posjetite nas na nadolazećim događajima u vašoj blizini.

Flatiron škola ponosni je član WeWork obitelji. Pogledajte blogove naše sestrinske tehnologije WeWork Technology i stvaranje zajedničkog sastanka.