Temiz Mimari Yaklaşımı ile Bir Servis Geliştirme

Huseyin Kutluca
Yazılım Mimarileri
6 min readSep 19, 2021
Salda Gölü

Temiz Mimari (Clean Architecture-Bob Martin), Soğan Mimarisi (Onion Architecture-Jeffrey Palermo) ve de Altıgen Mimari diğer adıyla Bağlantı ve Adaptör (Hexagonal Architecture or Port and Adaptor- Alistair Cockburn ve diğerleri) mimarileri yaklaşımları duymuşsunuzdur. Bunlar farklı sektör önderleri tarafında ortaya konmuş yaklaşımlar olmasına karşılık temelde benzer şeyleri farklı terimlerle anlatmaktadır. Çoğu kurumsal yazılımlara yönelik geliştirilmiş prensipler olmasına karşılık görev kritik ve algoritma ağırlıklı uygulamalar için de bu prensipler geçerlidir.

Yukarıda bahsedilen mimari yaklaşımların ortak noktasını şu şekilde özetleyebilirim: İş alanı nesneleri, kuralları çok sık değişmemektedir. Buna karşılık bu iş mantığını uygulamaya dökmek için kullandığımız teknolojiler, ara katmanlar, veri tabanları vs. daha sık değişmektedir. Uzun süreli çalışacak bir mimari kuracak isek teknoloji ve iş alanı mantıklarını ayırmak lazım. İş alanı sınıflarınızın kesinlikle teknoloji ve kütüphaneleri kullandığınız sınıflara bağımlı olmaması gerekir. Bu kapsamda tersine bir bağımlılık önerilmektedir. Bunun ötesinde bu farklı isimlendirmeler farklı terminolojiler kullanmakta, tasarımın farklı bir alanına daha detaylı değinmektedir.

Bu alanda nerdeyse bütün yazılar ve örnekler kurumsal uygulama alanında olmaktadır. Bu yaklaşımları görev kritik uygulamalarda (Telekom, Otomotiv, Savunma gibi) kullanacak mühendisler, felsefe güzel de ben bunu şimdi nasıl uyarlayacağım diye düşünmektedirler. Bu sebeple bu yazı biraz işin felsefesini ve görev kritik bir projede nasıl uygulanabilir bir örnek proje yapısı ile anlattım.

İş Alanı Nesneleri (Domain Entities): Bu nesneler tasarımınızın çekirdeğini oluşturur ve merkezde bulunur.

Bu iş alanı nesnelerini ve bu nesnelerin içerdiği iş alanı kullanım durumlarını İş Alanı Temelli Tasarım (Domain Driven Design -DDD) yaklaşımı ile oluşturursunuz. Burada Personel, Sipariş, Ürün, Çağrı, Teslimat, Hava Aracı gibi nesnelerden bahsediyoruz. Eğer veri tabanı kullanıyor iseniz bu nesneler çoğu zaman tablolarınız oluyor. Diğer durumlarda ise uygulamalar arası paylaştığınız ve liste ya da ağaç vb. yapıda tuttuğunuz temel veri yapılarını oluşturur. Bu nesneleri iş alanı mantığından bağımsız sadece veri nesneleri olarak tasarlamaktayız. Nesne tabanlı yaklaşım bile kullanıyor olsak bu iş alanı nesnelerini mümkün olduğunca karmaşık algoritmaların bulunduğu sınıflardan ayrı tutmak önemlidir. Bu şekilde bu nesneleri farklı sınıflarda kolayca işleyebiliriz.

Büyük projelerde bu iş alanı nesneleri birden fazla servis tarafından kullanılacağı için bunları ayrı bir modül olarak tasarlamak uygun olacaktır. Bu durumda bu nesne modülleri versiyon ile yönetilecek ve en önemli entegrasyon bileşeni olacaktır.

İş Alanı Kullanım Durumları: Domain Services (Use Cases): İş mantığı bu nesnelerin oluşturulması, güncellenmesi, işlenmesi, servisler arası paylaşılması gibi kodlardan oluşur.

Bu katmanda iş alanı kuralları ve algoritmaları gerçekleştirilir. Örneğin bankacılık işlemlerinde kredi hesaplama, faiz hesaplama burada yer alır. Bir teslimat sisteminde en kestirme yolun hesaplanması da benzer şekilde iş alanı mantığında yer alacaktır.

Geliştirdiğiniz servisin içinde genelde bir ya da birkaç alt iş alanı bulunmaktadır. Bu alt iş alanları farklı kullanım durumlarını ifade etmektedirler. İyi bir tasarımda servisin içindeki alt iş alanlarını belirleyip bunları modül olarak geliştirmeniz önerilmektedir.

Bu prensiplerle servis içi yapıları tasarladığımızda iş alanı modülleri sadece programlama dili özelliklerini içerir. İletişim yöntemine, kullanılan teknolojiye, sistemin dağıtık ya da tek parça olmasına ya da kullanıcı ara yüzü altyapısına bağımlı olmayan bir yapıdır. Bunun ötesinde ideal bir tasarımda bu modülleri içinde thread, timer, socket gibi bileşenler yer almaz. Kısacası bu modüller pasif algoritma ve ilgili veri yapılarını içerir. Bu şekilde tasarlanan modüller içindeki algoritmaları birim testler (google test vb) ile kolayca test edebilir. Bu aslında iyi bir servis yazabilmek açısından en kritik kazanç. Kritik algoritmalarınız iyi test edilebildiği için sahaya çıktığında daha az problem yaşarsınız. sistemde bir değişiklik yaptığınızda başka bir yeri bozmadığınızı birim testleri tekrar koşarak görürsünüz. Bu şekilde tasarlanmamış sistemlerde dışarıdan bir araç ile veri basılarak sistem bir bütün olarak test edilmekte ve çoğunlukla sadece ana akışlar üzerinden testler yapıldığı için sahada büyük problemler yaşanmakta ve daha baştan ürüne güven azalmaktadır.

Uygulama Katmanı: İş alanı kullanım durumları ile nasıl etkileşeceğimizi belirler. Bu katman yazılımın giriş bileşeni (main) dahil ana kontrol ve akış işlemlerinin bulunduğu modüldür. Burada REST, MQTT, AMQP, gRPC (bknz Dipnot)gibi iletişim teknolojileri ile veriyi alırız ve iş alanı servislerine sağlarız. İlgili teknolojiyi yönetme, verinin json ya da Google Protocol Buffer gibi bir formattan nesne haline getirme gibi işlemler yapılır.

Bu uygulama katmanına iş alanı bileşenlerinin bağımlı olmaması istenmekte. Peki iş alanı mantığında belli bir durumda dışarı bir mesaj iletecek isek ya da veri tabanına kaydedecek isek ne yapmalıyız. Burada yapılması gereken iş alanı alanında bir ara yüz sınıfı tanımlamak ve iş alanı servislerinden bu ara yüzü çağırmak olacaktır. Java gibi dillerde bunu interface olarak tanımlarken C++ da soyut sınıf olarak tanımlarız. Daha sonra bu ara yüzün gerçekleme sınıfını uygulama katmanında geliştiririz. Artık uygulama katmanında olduğumuz için istediğimiz gibi teknoloji, ara katman ya da veri tabanı bağımlılığımız olabilir. Benzer mantıkla iş alanı 1 modülünden iş alanı 2 modülüne çağrıları da buradan yapabiliriz. Bu yaklaşım aslında ağlantı ve adaptör denilen yapının kendisidir.

Örnek Temiz Mimari Yapısı

Yukarıdaki çizim bir C++ servisinin temiz mimariye göre düzenlenmiş klasör yapısını göstermektedir.

Sistemde yer alan Coremodule işletim sisteminin doğrudan sağlamadığı ek altyapıları içermekte ve sadece programlama dili bağımlılığı bulunmaktadır. Programlama dilinin sağladığı standart kütüphaneler ne kadar olgunsa bu tip ek kütüphanelere ihtiyaç o kadar azalmaktadır.

Projede kullanılan farklı teknoloji ve kütüphaneleri birden fazla sınıfa yaymamak için ve daha iyi yönetmek için bu kütüphaneleri sarmalayan sınıflar yazılabilir. Bu sınıfların konacağı modüller InfraModules altında yer almaktadır. Örnekte Gamma isimli bir ara katman yada iletişim teknolojisi GammaProtocolInterface altında sarmalanmıştır. Bu yaklaşımın avantajı ilgili kütüphane bağımlılıklarını tek bir yerde tutma, ilgili protokol için konfigürasyon, okuma ve yazma işlemlerini bir arada bulundurmaktır.

Kimi büyük projede hem bu InfraModules hem de CoreModules altında yer alan bileşenler ayrı bir ekip tarafından geliştirilmekte ve yönetilmekte ve birden fazla servise kütüphane olarak sağlanmaktadır. Böylelikle yeniden kullanılabilirlik artmakta, servisler arası mimari uyumluluklar oluşmaktadır

İş alanı kullanım durumları Modules altında yer almaktadır. Örnek olarak Alpha ve Beta modülü verilmiştir. Bunlar iki farklı alt iş alanının algoritmalarını geliştirmek üzere düşünülmüştür. Bu modüller ne birbirlerine ne de projede kullanılan teknolojilere bağımlıdır. Sadece CoreModule dediğimiz modüle bağımlılık bulunabilmektedir. Bu örnek projede Görüldüğü üzere hem Alpha hem de Beha modülleri kendi altlarında Test klasörü bulundurmaktadırlar. Bu klasör altında birim testler yer alır ve ilgili modülleri herhangi bir teknoloji yada iletişim kütüphanesine ihtiyaç duymadan test edebilir. Bu Alpha ve Beta modülleri ana proje için kütüphane olarak derlenebilmektedir. Bu yapısıyla istendiğinde başka servisler tarafından kullanılabilmektedir. Böyle bir durumda ilgili kaynak kod burada yönetilip diğer servise kütüphane olarak sürüm teslimi mümkündür. Ayrıca ihtiyaca göre bu modül ayrı bir çalışma alanına taşınarak ayrı bir proje haline de kolayca getirilebilmektedir. Özet olarak projenin en önemli bileşeni olan bu iş alanı kullanım durumu modülleri gayet modüler bir yapıda tasarlanmış olmaktadır. Bu proje içerisinde üretilen verilerin diğer modüllere aktarılması ya da iletişim katmanı üzerinden diğer servislere gönderilmesi için bu servisler içinde EventInterface dediğimiz soyut ara yüz sınıfları yer alabilmektedir. Örneğin Alpha modülü içinde yer alan AlphaEventInterface sınıfı soyut bir sınıftır. Örnekte bir tane verilmiş olmasına karşılık modülün karmaşıklığına göre birden fazla bu şekilde soyut ara yüz olabilmektedir.

Projenin uygulama katmanı çizimde MainModule olarak belirttiğimiz modüldür. Bu modüle çoğu zaman servisin kendi adı verilmektedir. Bu modül içinde öncelikle ana program girişinin yapıldığı “main” yer almaktadır. Bunun dışında proje de birlikte çalışmayı sağlayan görev sınıfları (tasks), ara katman ya da iletişim kütüphaneleri üzerinden veri giriş çıkışı yapılan sınıflar yer almaktadır. Eğer veri tabanı bağlantısı gerekiyor ise yine bu modül içinde yapılmaktadır. Burada bu dinamik işlerin yapıldığı sınıflarda ilgili kütüphanelere doğrudan erişmek mümkündür. Bu modül içinde yer alan en kritik bileşenlerden biriside Modules altında tanımlanan soyut EventInterface sınıflarının geliştirme sınıflarıdır. Örneğin yukarıdaki örnekte ki AlphaEventInterface sınıfının esas işlemi yapacak AlphaEventInterfaceImpl sınıfı bu MainModule altında yer almaktadır. Bu şekilde modüller arası ve diğer teknolojilerle iletişim iş alanı mantığı dışına taşınmış olur. Bu yapıyı oturtmak biraz detay oluşturmakla birlikte temiz mimari açısından önemli bir yaklaşımdır.

Son bir detay olarak MainModules üzerinden Modules altındaki modüllere nasıl erişildiğinden bahsedelim. Bu modules altında yer alan her bir modülün kendi tanımlı bir arayüz (API) sınıfı vardır. Bu modüllere bu arayüz sınıfı üzerinden erişilir. Arayüz tanımlamayı sağlayan java gibi dillerde bu arayüz soyut olarka tanımlanmakta ve geliştirme detayları MainModules içinden görülmektedir. C++ için ise Factory PAttern ile soyut arayüzün geliştirme sınıfına erişilebildiği gibi (ModuleFactory.GetAlhpaImplementation), PIML (Pointer To Implementation) yöntemi tercih edilebilmektedir.

Değerlendirme

Sonuç olarak Temiz/Altıgen/Soğan Mimarisi Çerçeve (Framework) yazılımlarından bağımsız, test edilebilir, veri tabanı ya da kullanıcı ara yüzünden bağımsız, diğer dış birimlerden bağımsız ve rş alanı nesneleri ile iş alanı mantığı arasında ayrım yapan bir mimari yaklaşımı ortaya koymaktadır. Bu yapı doğru uygulandığında bakımı kolay, sürdürülebilir, kaliteli yazılım ortaya konmuş olacaktır.

Dipnot: Terimler

REST: REpresentational State Transfer — WEB teknolojisi üzerinden veri paylaşımını sağlayan bir yaklaşımdır

MQTT: Message Queuing Telemetry Transport- Yayımla Abone ol mimarisinde bir arakatman standardı

AMQP:Advanced Message Queuing Protocol-Yayımla Abone ol mimarisinde bir arakatman standardı

GRPC: Google Remote Procedure Call- Uzaktan servis çağırma yaklaşımı

--

--

Huseyin Kutluca
Yazılım Mimarileri

Highly motivated Software Architect with hands-on experience in design and development of mission critical distributed systems.