Java kurumsal geliştirme dünyasında, "modüler monolit", erken mikro hizmet ayrıştırmasının pragmatik bir alternatifi olarak yeniden popülerlik kazanmıştır. Ancak, sıkı mimari koruyucu bariyerler olmadan, bu sistemler genellikle modüllerin birbirine sızdığı ve gelecekte mikro hizmetlere çıkarılmasını acı verici veya imkânsız hale getiren sıkı bir bağımlılık yaratan "mimari entropi"den muzdarip olur. Bu bozulma nadiren kasıtlıdır; kolaylık yoluyla gerçekleşir. Bununla mücadele etmek için, geleneklerin ötesine geçmeli ve sınırları derleme seviyesinde uygulamalıyız.
Sorun: Modüler Monolitlerde Gizli Bağımlılık
İyi tasarlanmış bir modüler monolit, her modülü potansiyel bir gelecekteki mikro hizmet olarak ele alır. Bu, Modül A'nın asla Modül B'nin iç varlıklarına doğrudan erişmemesi gerektiği anlamına gelir. Bunun yerine, Modül B ile iyi tanımlanmış bir API aracılığıyla etkileşime girmelidir. Pratikte, geliştiriciler genellikle bir depo veya servise doğrudan çağrı yapmanın "daha kolay" olduğu durumlarda bu API'leri atlar. Zamanla bu kısayollar birikir ve monolit bir "çamur topuna" dönüşür.
Manuel kod incelemeleri bu amaç için ölçeklenebilir değildir. Bir geliştirici, karmaşık bir yeniden yapılandırmada ince bir bağımlılığı gözden kaçırabilir. Otomatik ve sürekli doğrulamaya ihtiyacımız var.
ArchUnit'ı Tanıtıyoruz: Mimari Birim Testleri
ArchUnit, Java platformu için "mimari birim testleri" yazmanıza olanak tanıyan güçlü bir kütüphanedir. Bu testler, kod yapınızı doğrulayan normal JUnit testleridir. Bir kural ihlal edilirse, derleme başarısız olur ve kötü kodun birleştirilmesini engeller.
Pratik Uygulama: Sınırları Tanımlama
İki modülü olan bir modüler monolite sahip olduğumuzu varsayalım: order-service ve inventory-service. order-service'in inventory-service'e yalnızca com.myapp.inventory.api paketi üzerinden, iç uygulama sınıfları üzerinden değil erişmesini sağlamak istiyoruz.
import com.tngtech.archunit.core.domain.JavaClasses;
import com.tngtech.archunit.core.importer.ClassFileImporter;
import com.tngtech.archunit.lang.ArchRule;
import org.junit.jupiter.api.Test;
import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.noClasses;
import static com.tngtech.archunit.library.dependencies.SlicesRuleDefinition.slices;
public class ArchitectureTest {
private final JavaClasses classes = new ClassFileImporter().importPackages("com.myapp");
@Test
public void order_service_should_not_access_inventory_implementation() {
ArchRule rule = noClasses().that().resideInAPackage("com.myapp.order..")
.should().dependOnClassesThat().resideInAPackage("com.myapp.inventory.internal..");
rule.check(classes);
}
@Test
public void modules_should_only_interact_via_api_packages() {
ArchRule rule = slices().matching("com.myapp.(*)..")
.should().notDependOnEachOther()
.allowAccessBetween("com.myapp.order..", "com.myapp.inventory.api..");
rule.check(classes);
}
}
CI/CD ve Derleme Araçlarıyla Entegrasyon
Bu kontrollerin etkili olması için, standart derleme sürecinin bir parçası olmaları gerekir. Bir Maven veya Gradle projesinde, ArchUnit testleri test aşamasında çalıştırılır. Bir geliştirici, sipariş servisi sınıfından envanter iç sınıfına doğrudan bir bağımlılık eklemeye çalışırsa, derleme hemen başarısız olur.
Bu anında geri bildirim döngüsü hayati önem taşır. Mimari uyumu, sonradan yapılan bir denetimden gerçek zamanlı bir geliştirme kısıtlamasına dönüştürür. Ayrıca, ArchUnit'ın FreezingArchRule'ını kullanarak bu yaklaşımı eski kodu yönetmek için genişletebilirsiniz. Mevcut ihlalleriniz varsa, bunları "dondurarak" derlemenin geçmesine izin verebilir ve yeni ihlalleri önleyebilirsiniz. Bu, büyük eski kod tabanlarını yeniden yapılandırmayı mümkün kılar.
Daha İleriye Giderek: Katman Bağımlılıklarını Uygulama
Modül sınırlarının ötesinde, modüller içinde katman ayrımını uygulayabilirsiniz. Örneğin, Controller'ların doğrudan Repository'lere erişmemesini, bunun yerine Service katmanları üzerinden gitmelerini sağlamak. ArchUnit bunu basit hale getirir:
@Test
public void controllers_should_not_access_repositories() {
ArchRule rule = noClasses().that().resideInAPackage("..controller..")
.should().dependOnClassesThat().resideInAPackage("..repository..");
rule.check(classes);
}
Sonuç
Modüler monolitler, her iki dünyanın da en iyisini sunar: tek bir artefaktın dağıtılabilirliği ve mikro hizmetlerin yapısal netliği. Ancak, bu netlik kendini koruyamaz. ArchUnit'ı derleme hattınıza entegre ederek, mimari niyetinizi koruyan otomatik bir bekçi oluşturursunuz. Bu, iyi mimariyi takip edilecek bir hedeften, karşılanması gereken bir kısıtlamaya dönüştürür ve sisteminizin sağlam, test edilebilir ve gelecekteki evrime hazır kalmasını sağlar.