Yeni bir mühendis bana domain katmanının neden database paketini import ettiğini sordu — “bir ADR import etmemesi gerektiğini söylemiyor mu?” Söylüyordu. İki yıldır söylüyordu. Ve o iki yılın çoğunda, bir yerlerde, kural sessizce çiğnenmişti: şurada bir helper, burada bir “sadece bu seferlik” import, her biri kendi pull request’inde makul. Karar wiki’de hâlâ doğruydu. Kodda aylar önce doğru olmaktan çıkmıştı ve kimse fark etmedi, çünkü onu izleyen hiçbir şey yoktu.

Yazıp da zorlayamadığın bir karar bir kural değildir. İyi formatlanmış bir dilektir.

Diyagram ile kod, küçük ve makul adımlarla ayrışır

Kimse mimariyi çiğnemeye karar vermez. Her seferinde savunulabilir bir commit’le aşınır: async yolu yazmak daha yavaş olduğu için eklenen senkron bir çağrı, ihtiyacın olan fonksiyon öbür tarafta olduğu için bir sınırı geçen bir import. Her diff tek başına iyi görünür. Hasar birikimli ve yapısaldır — tek bir değişiklik ölçeğinde görünmez, ancak geri çekilip katmanlamanın yok olduğunu bulunca belli olur.

Bu tam da bir insan review’ının çalıştığı ölçek ve tam da kaçırdığı ölçek. Review en iyi bir değişikliğin mimarisinde işler, ama bir reviewer tek bir PR görür, sınırı nihayet eriten bininci import’u değil. Bir insandan her commit’te tüm bağımlılık grafiğini kafasında tutmasını isteyemezsin. Wiki sayfası da tutamaz; o nesirdir ve nesir bir build’i patlatmaz.

Kısıtı çalıştırılabilir yap

Çözüm daha iyi yazılmış bir ADR değil. Kararın zorlanabilir kısmını nesirden çıkarıp çalışan bir şeye taşımak.

“Domain altyapıya bağımlı olmamalı” özünde bir görüş değildir — import grafiği üzerinde bir kısıttır: domain/ altındaki dosyalar db/’yi import edemez. Bu, bir aracın her commit’te deterministik olarak değerlendirebileceği ve çiğnendiğinde build’i kırabileceği bir kural — çiğneyen import main’e ulaşmadan, iki yıl sonra değil. Yakalama maliyeti, “bir şey nihayet kırılınca yapılan bir arkeoloji seansı”ndan “onu sokan PR’daki kırmızı bir kontrol”e iner.

Bu, bir şemayı değiştirmek yerine sürümlemenin aynı hamlesi: herkesin üzerinde anlaştığı bir kural, mekanik bir şey çizgiyi tutana dek hiçbir şey etmez — çünkü insan disiplini, her katkıcının her commit’ine ölçeklenmez.

Dokümanda ne kalır, ne kod olur

Bu ADR’yi öldürmez — onu ikiye böler. Bir mimari kararın iki parçası vardır: gerekçe (hexagonal katmanlamayı niye seçtik, neyi feda ettik, ne zaman yeniden gözden geçiririz) ve kısıt (domain hiçbir şey import etmez; API, DB’yle yalnızca bir repository üzerinden konuşur). Gerekçe nesre aittir — bir aracın tutamayacağı, bir insanın ihtiyaç duyduğu bağlam. Kısıt ise koda aittir, kontrol edilebileceği yere.

Neden’i yazmaya devam et. Ne’yi zorlamak için nesre güvenmeyi bırak.

Bu ne zaman önemsizleşir?

Tüm sistemin iki katman ve tek bir masanın etrafına sığan bir takımsa, sınırlar herkesin kafasında yaşar ve onlar için bir CI kapısı törendir. Üçüncü bir katkıcı katıldığı ya da kod tabanı tek bir kişinin tutabileceğini aştığı an, zorlanmayan sınır zaten aşınıyordur — sadece yeni mühendis domain’in neden database’i import ettiğini sorana dek görmezsin.


Bir mimari, karar verdiğin şey değildir; kodunun şu an yaptığı şeydir. Büyüyen bir kod tabanıyla temasta hayatta kalan tek kararlar, bir şeyin her commit’te kontrol ettikleridir. Gerekçeyi insanlar için yaz — ve sınırı, onu zorlamaktan asla yorulmayan bir makineye devret.


İlgili: archlint, tam da bunun için yazdığım araç — katman kurallarını okuyup bir ihlalde build’i kıran, deterministik bir import-sınır linter’ı (Go, TypeScript ve Python), paketlenmiş bir CI Action’ıyla.