Rails Monolith'ten Tek Tablo Kalıtımını Kaldırma

Kalıtım kolaydır - teknik borç ve vergilerle uğraşmak zorunda kalana kadar.

Learn'ün ana kod temeli beş yıl önce ortaya çıktığında, Tekli Tablo Kalıtımı (STI) oldukça popülerdi. O sırada Flatiron Labs ekibi her şeye dahil oldu - bunu, değerlendirme ve müfredattan etkinlik besleme etkinliklerine ve büyüyen öğrenme yönetim sistemimizdeki içeriğe kadar her şey için kullanıyordu. Ve bu harikaydı - işi bitirdi. Eğitmenlerin müfredat sunmasına, öğrencinin ilerlemesini izlemesine ve ilgi çekici bir kullanıcı deneyimi yaratmasına izin verdi.

Ancak birçok blog yazısının belirttiği gibi (bu, bu, ve bu, örneğin), STI özellikle iyi veri ölçeklendirmez ve yeni alt sınıflar üst sınıflarından ve birbirlerinden geniş ölçüde farklılaşmaya başladığında çok iyi ölçeklenemez. Tahmin edebileceğiniz gibi, aynısı bizim kod tabanında! Okulumuz genişledi ve gittikçe daha fazla özellik ve ders türünü destekledik. Zamanla, modeller şişirmeye ve değişmeye başladı ve artık etki alanı için doğru soyutlamayı yansıtmıyor.

Bir süre o alanda yaşadık, bu koda geniş bir rıhtım vererek ve sadece gerekli olduğunda onu yamaya koyduk. Ve sonra zaman refactor geldi.

Geçtiğimiz birkaç ay boyunca, biraz belirsiz adı verilen "Content" modelini içeren, özellikle garip bir STI örneğini kaldırma görevine başladım. STI başlangıçta ayarlamak kadar kolay olsa da, kaldırılması oldukça zordur.

Bu yüzden, bu yazıda, CYBE hakkında biraz bilgi vereceğim, alanımız hakkında bir bağlam sunacağım, çalışmanın kapsamını belirleyeceğim ve çekirdeği parçalarken ciddi hasar için yüzey alanını en aza indirirken değişiklikleri güvenli bir şekilde uygulamak için kullandığım stratejileri tartışacağım. Bizim app.

Tek Tablo Miras Hakkında (STI)

Kısacası, Raylardaki Tekli Tablo Kalıtımı aynı tabloda birden fazla sınıf türünü saklamanıza izin verir. Aktif Kayıtta, sınıf adı tablodaki tür olarak saklanır. Örneğin, içindekiler tablosunda bir Laboratuar, Benioku ve Proje uygulamasının tümü bulunabilir:

sınıf Lab <İçerik; son
sınıf Benioku <İçerik; son
sınıf Proje <İçerik; son

Bu örnekte, laboratuvarlar, readmes ve projeler, bir dersle ilişkilendirilebilecek her türlü içeriktir.

İçindekiler tablosunun şeması biraz buna benziyordu, bu nedenle türün yalnızca tabloda depolandığını görebilirsiniz.

create_table "content", zorla:: cascade do | t |
  t.integer "curriculum_id",
  t.string "type",
  t.text "markdown_format",
  t.string "unvan",
  t.integer "track_id",
  t.integer "github_repository_id"
son

İşin Kapsamını Belirleme

İçerik, bazen kafa karıştırıcı olarak, uygulama boyunca yayıldı. Örneğin, bu Ders modelindeki ilişkileri tanımladı.

sınıf Dersi  {order (ordinal:: asc)}
  has_one: content, 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: dedicated_repos, şöyle:: içerik
son

Şaşkın? Ben de öyleydim. Ve bu, değiştirmem gereken birçok modelden sadece biriydi.

Bu yüzden mükemmel ve yetenekli takım arkadaşlarım (Kate Travers, Steven Nunez ve Spencer Rogers) ile karışıklığı azaltmaya ve bu sistemin daha kolay hale getirilmesine yardımcı olmak için daha iyi bir tasarım beyin fırtınası yaptım.

Yeni Bir Tasarım

İçeriğin temsil etmeye çalıştığı kavram bir Github Alıcısı ve bir Ders arasında bir aracıydı.

“Kanonik” ders içeriğinin her bir parçası GitHub'taki bir depoya bağlanır. Dersler öğrencilere yayınlandığında ya da “konuşlandırıldığında”, bu GitHub deposunun bir kopyasını alıyor ve öğrencilere bir link veriyoruz. Bir ders ile dağıtılan sürüm arasındaki bağlantıya AssignedRepo adı verilir.

Yani dersin her iki ucunda GitHub depoları var: kanonik sürüm ve konuşlandırılmış versiyon.

sınıf İçeriği 
sınıf AssignedRepo 

Bir noktada, dersler çok sayıda içeriğe sahip olabiliyordu, ancak şu andaki dünyamızda durum böyle değil. Bunun yerine, ilişkili depolarında bulunan dosyalara bakarak kendilerini incelerken çeşitli dersler vardır.

Dolayısıyla yapmaya karar verdiğimiz şey, Content'i CanonicalMaterial adlı yeni bir konseptle değiştirmek ve AssignedRepo'ya Content'den geçmek yerine ilişkili dersine doğrudan bir referans vermek oldu.

Kırmızı Noktalı çizgilerin kullanımdan kaldırılmak üzere işaretlenmiş yolları gösterdiği Yeni Sistem Diyagramına Eski

Bu kafa karıştırıcı sesler ve çok iş gibi geliyorsa, çünkü öyle. Önemli bir paket servis de, oldukça büyük bir kod tabanında bulunan bir modeli değiştirmek zorunda kalmamız ve 6000 satırlık kod kodunda bir yerde değişmemiz.

Önemli bir paket servis, oldukça büyük bir kod temeli içindeki bir modeli değiştirmemiz gerektiği ve 6000 satırlık kod kodunda bir yer değiştirmemiz gerektiği.

STI'nin Yeniden Düzenlenmesi ve Değiştirilmesi Stratejileri

Yeni Model

İlk önce canonical_materials adlı yeni bir tablo ve yeni model ve dernekler oluşturduk.

sınıf CanonicalMaterial 

Ayrıca, ders programına bir canonical_material_id anahtarı ekledik, böylece bir Ders buna başvuruda bulunabilir.

Dedicated_repos tablosuna bir lesson_id sütunu ekledik.

İkili Yazma

Yeni tablolar ve sütunlar yerleştirildikten sonra, eski tablolara ve yenilere aynı anda yazmaya başladık, böylece bir defalarca dolum görevi yürütmemize gerek kalmayacaktı. Bir şey bir içerik satırı oluşturmayı veya güncellemeyi denediğinde, bir canonical_material oluşturduk veya güncelledik.

Örneğin:

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

lesson.canonical_material = repo.canonical_material
lesson.save

Bu, nihayetinde İçeriği kaldırmak için temel atmamızı sağladı.

Dolgu

Süreçteki bir sonraki adım veriyi doldurmaktı. Tablolarımızı doldurmak ve her GithubRepository için bir Canonical Materyalinin varlığını ve her bir Dersin bir Canonical Materyaline sahip olmasını sağlamak için komisyon görevleri yazdık. Sonra üretim sunucumuzdaki görevleri koştuk.

Bu yeniden düzenleme turunda, geçerli verilere sahip olmayı tercih ettik, böylece işleri yapmanın eski yolunu temiz bir şekilde kesebiliriz. Bununla birlikte, uygulanabilir başka bir seçenek, daha eski modelleri destekleyen kod yazmaktır. Tecrübelerimize göre, eski düşünceyi destekleyen kodu korumak ve verilerin geçerli olduğundan emin olmaktan daha kafa karıştırıcıydı.

Tecrübelerimize göre, eski düşünceyi destekleyen kodu korumak ve verilerin geçerli olduğundan emin olmaktan daha kafa karıştırıcıydı.

Değiştirme

Ve sonra eğlence kısmı başladı. Değişimi mümkün olduğu kadar güvenli hale getirmek için, koyu kodları daha küçük PR'lerde göndermek için özellik bayraklarını kullandık, bu da daha hızlı bir geri besleme döngüsü oluşturmamızı ve işlerin kopup çözülmediğini daha erken öğrenmemizi sağladı. Bunu yapmak için standart özellik geliştirmede de kullandığımız rollout gem'i kullandık.

Ne Aramalı?

Değişimi yapmanın en zor kısımlarından biri, aranacak şeylerin sayısıydı. “İçerik” kelimesi maalesef süper jeneriktir, bu nedenle basit, genel bir arama ve değiştirme yapmak imkansızdı, bu yüzden varyasyonları hesaba katarak daha kapsamlı bir arama yapma eğilimindeydim.

STI'yi çıkarırken aramanız gerekenler şunlardır:

  • Modelin tüm alt sınıfları, metotları, faydalı metotları, çağrışımları ve sorguları içeren tekil ve çoğul formları.
  • Sabit kodlanmış SQL sorguları
  • Kontrolörler
  • serileştiriciler
  • Görünümler

Örneğin, içerik için, bunun anlamı:

  • : içerik - dernekler ve sorgular için
  • : içerik - birlikler ve sorgular için
  • .joins (: content) - önceki aramada yakalanması gereken birleştirme sorguları için
  • .includes (: content) - önceki arama tarafından da yakalanması gereken ikinci dereceden dernekleri istekli yüklemek için
  • içerik: - iç içe geçmiş sorgular için
  • içeriği: - tekrar, daha fazla iç içe geçmiş sorgu
  • content_id - doğrudan kimliğe göre sorgular için
  • .content - yöntem çağrıları
  • .contents - toplama yöntemi çağrıları
  • .build_content - has_one tarafından eklenen yardımcı program yöntemi
  • .create_content - has_one tarafından eklenen yardımcı program yöntemi
  • .content_ids - has_many birliği tarafından eklenen yardımcı program yöntemi
  • İçerik - sınıf adının kendisi
  • içeriği - sabit kodlanmış referanslar veya SQL sorguları için düz dize

Bunun içerik için oldukça kapsamlı bir liste olduğuna inanıyorum. Sonra da aynı şeyi laboratuar, beni oku ve proje için yaptım. Rails'in çok esnek olması ve birçok faydalı yöntem eklemesi nedeniyle, bir modelin kullanımının sona erdiği tüm yerleri bulmak zordur.

Tüm Arayanları Bulduktan Sonra Uygulama Nasıl Gerçekleştirilir

Gerçekte değiştirmeye veya kaldırmaya çalıştığınız modelin tüm arama sitelerini bulduktan sonra, şeyleri yeniden yazabilirsiniz. Genel olarak izlediğimiz süreç

  1. Tanımdaki yöntem davranışını değiştirin veya çağrı sitesindeki yöntemi değiştirin
  2. Yeni yöntemler yazın ve onları arama sitesinde bir özellik bayrağının arkasına çağırın
  3. Yöntemlerle ilişkilere bağımlılığı kırmak
  4. Bir yöntemden emin değilseniz, özellik bayrağının arkasındaki hataları kaldırın.
  5. Aynı arayüze sahip nesnelerde takas

İşte her stratejinin örnekleri.

1 A. Yöntem davranışını veya sorguyu değiştirin

Değişimlerin bazıları oldukça basit. Özellik bayrağını "Bu bayrak açıkken bu diğer kod yerine bu kodu çağır" demek için yerine koyarsınız.

Yani içeriğe dayalı sorgulama yerine, burada canonical_material'e dayalı sorgulama yapıyoruz.

1b. Çağrı sitesindeki yöntemi değiştirin

Bazen, çağrılan yöntemleri standart hale getirmek için arama sitesindeki yöntemi değiştirmek daha kolaydır. (Bunu yaptığınızda test takımınızı çalıştırmalı ve / veya testler yazmalısınız.) Aksi takdirde daha fazla refactoring yolunu açabilirsiniz.

Bu örnek, artık var olmayacak olan canonical_id sütununa bağımlılığın nasıl kırılacağını gösterir. Çağrı alanındaki yöntemi bir özellik bayrağının arkasına koymadan değiştirdiğimize dikkat edin. Bu yeniden düzenlemeyi yaparken, canonical_id kodunu birden fazla yerde topladığımızı fark ettik, bu yüzden bunu yapmak için mantığı, başka sorgulara zincirleyebileceğimiz başka bir yöntemle tamamladık. Çağrı sitesindeki yöntem değiştirildi, ancak özellik bayrağı açılana kadar davranış değişmedi.

2. Yeni yöntemler yazın ve onları arama sitesinde bir özellik bayrağının arkasına çağırın

Bu strateji, yalnızca bu yöntemde yeni bir yöntem yazıp çağrı sitesinde bir özellik bayrağının arkasında çağırarak yöntem değiştirme ile ilgilidir. Sadece bir yerde denilen bir yöntem için özellikle kullanışlıdır. Ayrıca, yönteme daha iyi bir imza atmamızı sağladı - her zaman yararlı.

3. Yöntemlerle ilişkilere bağımlılığı kırmak

Bir sonraki örnekte, bir parça has_many laboratuvarına sahiptir. Has_many derneğinin faydalı yöntemler eklediğini bildiğimiz için, en yaygın olarak adlandırılan ve has_many: labs satırını kaldırdık. Bu yöntem aynı arabirime uygundur, bu nedenle özellik açılmadan önce yöntemi çağıran herhangi bir şey çalışmaya devam ederdi.

4. Bir yöntemden emin değilseniz, özellik bayrağının arkasındaki hataları kaldırın.

Bir çağrı sitesini kaçırıp kaçırmadığımızdan emin olmadığımız bazı zamanlar vardı. Bu nedenle, ilk başta sadece sert temizleme yöntemleri yerine, manuel test aşamasında onları yakalayabilmemiz için bilerek hatalar yaptık. Bu, bize bir yöntemin nerede çağırıldığını tespit etmemiz için daha iyi bir yol sağladı.

5. Aynı arayüze sahip nesnelerde takas

Laboratuar birliğinden kurtulmak istediğimiz için laboratuarın uygulamasını yeniden yazdık. yöntem. Bir laboratuvar kaydının varlığını kontrol etmek yerine, canonical_material'de yer değiştirdik, aramayı delege ettik ve bu nesnenin aynı yönteme yanıt vermesini sağladık.

Bunlar, Rails monolith'imiz boyunca bağımlılıkları kırmak ve yeni nesnelerde değiş tokuş etmek için en faydalı stratejilerdi. Yüzlerce tanım ve çağrı sitesini inceledikten sonra, onları tek tek değiştirdik veya yeniden yazdık. Kimseye istemediğim sıkıcı bir süreç, ancak sonuçta kod tabanımızı daha okunaklı hale getirmek ve etrafta hiçbir şey yapmadan oturan eski kodu kaldırmak için son derece yardımcı oldu. Sona gelmek birkaç sinir bozucu ve saç çekme haftası aldı, ancak referansların çoğunu değiştirdikten sonra manuel testler yapmaya başladık.

Test ve Manuel Test

Değişiklikler, bazıları test edilmeyen kod tabanının tamamındaki özellikleri etkilediği için, kalite güvencesi için kesin olarak zordu, ancak elimizden gelenin en iyisini yaptık. QA sunucumuzda, birçok hata ve uç durumu olan manuel testler yaptık. Sonra devam ettik ve daha kritik yollar için yeni testler yazdık.

Toplanın, Canlı Yayına Girin ve Temizleyin

KG'yi geçtikten sonra, özellik bayrağımızı çevirdik ve sistemin yerleşmesini sağladık. Kararlı olduğundan emin olduktan sonra, özellik bayraklarını ve eski kod yollarını kod tabanından kaldırdık. Ne yazık ki, beklenenden daha zordu çünkü çoğunlukla İçerik modeline dayanan fabrikalar olan birçok test odasının yeniden yazılmasını gerektiriyordu. Geçmişe bakıldığında, yapabileceğimiz tek şey yeniden yapılanma sırasında iki test seti yazmaktı; biri geçerli kod için, diğeri özellik bayrağının arkasındaki kod için.

Hala gelecek olan son bir adım olarak, verileri yedeklemeli ve kullanılmayan tablolarımızı bırakmalıyız.

Ve bu, arkadaşlar, Rails monolith'inizdeki Single Table Mirasını yaymanın bir yoludur. Belki de bu örnek olay incelemesi size yardımcı olacaktır.

CYBE veya yeniden düzenlemeyi kaldırmanın başka yolları var mı? Bilmeyi merak ediyoruz. Yorumlarda bize bildirin.

Ayrıca işe alıyoruz! Takımımıza katıl. Biz harikayız söz veriyorum.

Kaynaklar ve Ek Okuma

  • Raylar Kılavuzları Kalıtım
  • Raylarda Tekli Masa Kalıtımı Nasıl Yapılır ve Eugene Wang (Flatiron Grad!)
  • Tek Masalı Mirastan Dışarı Raylarımız Uygulamasını Yeniden Uygulamak
  • Tek Tablalı Kalıtım - Raylardaki Polimorfik Dernekler
  • Rails 5.02 Kullanarak Tekli Tablo Kalıtımı

Flatiron Okulu hakkında daha fazla bilgi edinmek için web sitesini ziyaret edin, Facebook ve Twitter'da bizi takip edin ve yakınınızdaki etkinliklerde bizi ziyaret edin.

Flatiron Okulu, WeWork ailesinin gururlu bir üyesidir. Kardeş teknoloji bloglarımıza WeWork Technology ve Meetup ile göz atın.