Onion Architecture (Soğan Mimarisi) Nedir?

Onion mimarisi, yazılım projelerinde n-katmanlı bir yaklaşım sunan bir mimari tasarım paradigmasıdır. 2008 yılında Jeffrey Palermo tarafından tasarlanmıştır. Bu mimarinin en temel özelliği bağımlılığın, dışarıdan içeri doğru katmanlar halinde sıralanmasıdır. Ayrıca bu mimari, loose coupling (gevşek bağlantı) modeli ile projeyi temel işlevselliğe ve dış sistemlere karşı soyutlama sağlayan bir yapı üzerine inşa etmeyi amaçlar.

Bu mimarinin yapı diyagramı dıştan içeri doğru halkalar şeklinde oluştuğundan dolayı şekil olarak soğanın katmanlarına benzetilmiştir. Bu nedenle Onion (Soğan) Mimari olarak isimlendirilmiştir.

Onion mimari daha yakından tanımak için bu mimarinin yaratıcısı Jeffrey Palermo’nun blog sayfasından yazdıklarından kısaca bahsedelim.


Geleneksel katmanlı mimarilerde her katman altındaki katmanlara ve bazı ortak servislere/altyapılara bağlıdır. Böyle bir sistemde bir çok gereksiz bağlantı oluştuğundan dolayı sistemlerimiz bağlantılar olmadan hiçbir şey yapamaz hale gelir. Burada oluşan bağımlılık klasik katmanlı mimarinin en büyük dezavantajıdır.

Burada ki en yaygın hata UI (Kullanıcı ara yüzü) ve Business Logic (İş Mantığının) veriye erişimiyle birleştirilmesidir. Böylece Business Logic olmadan UI, Data olmadan Business Logic çalışmaz hale gelir. Özellikle geçmişe bakılınca veriye erişim teknolojilerinin üç yılda bir değiştiği görülür. Klasik bir katmanlı mimari yaklaşımında veriye erişim iş mantığı içerisine dahil edildiğinde dolayı böyle bir değişimde iş mantığı katmanı da çalışamaz hale gelir ve gün sonunda proje tekrar sıfırdan yazılır.

Onion mimaride temel kural, tüm kodların daha merkezi katmanlara bağlı olabilmesidir, ancak kod, çekirdeğin daha dışındaki katmanlara bağlı olamaz. Başka bir deyişle, tüm bağlantı merkeze doğrudur.

Merkezde organizasyon için gerçeği modelleyen durum ve davranışları temsil eden Domain Model bulunur. Uygulama alanı doğrultusunda katmanların sayısı değişse de Domain Model her zaman merkezde olacaktır ve bağlantıların merkeze doğru olması nedeniyle Domain Model yalnızca kendisine bağlı olacaktır.

Dış katman ise sıklıkla değişen şeylere ayrılmıştır. Repository’ler gibi. Bunlar kasıtlı olarak uygulama çekirdeğinden izole edilmelidir.

Oninon mimari büyük ölçüde Dependenct Injection ilkesine dayanır.

Yalnız şunu da söylemekte fayda var. Onion mimari küçük web siteleri için uygun değildir. Uzun ömürlü ve karmaşık davranışlara sahip uygulamalar için uygundur.


Yukarıda ki tanımlama Jeffrey Palermo’nun web sitesinden önemli yerler özetlenerek alınarak tercüme edilmiştir.

Özetleyecek olursak onion mimari iki temel prensip üzerine inşa edilir bir bağımlık çemberin dışından merkeze doğru olmalı iki bağımlılıkla kesinlikle soyut kavramlara dayanmalıdır yani Loose Coupling.

Burada dikkatinizi çekmiştir özellikler çok sık değişen teknolojileri Repository’ler gibi en dış katmandan tutmayı amaçlamıştır. Böylece değişen bir teknoloji doğrultusunda iç katmanlar etkilenmeyecektir. Örneğin onion mimari üzerinden bugün entity ile kurguladığımız bir repository katmanında yarın dapper mimarisine geçiş yapmak istediğimizde tek destek vereceğimiz katman en dış katman olacaktır. Böylece diğer katmanlar örneğin Service’ler gibi iç tarafta kalacağından dolayı bir etkilenme söz konusu olmayacaktır.

Lakin içeride bulunan service’lerimizin ise kullanıcıdan aldıkları direktif doğrultusunda database operasyonları yapma gereksinimi olacaktır. Peki bu durumda dışa doğru bir bağımlılık mı oluşturacağız? İşte burada da onion mimarinin ikinci prensibi loose coopling devreye girer repolarımızın interfacelerini service katmanında tanımlarız implement kısmını ise repository kapsamında gerçekleştirir ve service’de dependency injection uygularız. Böylece dışa doğru bir bağlantı oluşmamış olur.

Onion Mimarisinin Temel Prensipleri

İçe Doğru Bağımlılık:

  • Onion mimarisinde her bir dış katman iç katmana bağımlı olmalıdır. Bu, bağımlılıkların içe doğru yönlendirildiği bir prensiptir. İç katmanlar, genellikle iş mantığı ve domain mantığı gibi daha özgün ve uygulama özel işlevselliği içerirken, dış katmanlar daha genel işlemleri ve altyapı işlemlerini barındırır.
  • Bu, iç katmanların soyut bir ara yüzle dış dünya ile iletişim kurmasını sağlar. Bu soyut ara yüz, iç katmanların detaylarına dalmadan dış dünyayla iletişim kurmalarına olanak tanır.

Soyutlamalar ve Bağımsızlık:

  • Katmanlar arasındaki bağımlılıkların soyut ara yüzler üzerinden yapılması, bir katmanın iç detaylarının diğer katmanları etkilememesini sağlar.
  • Bu, bir katmanın değiştirilmesi veya başka bir katmanla değiştirilmesi durumunda minimum etki ile bu değişiklikleri gerçekleştirmeyi sağlar.

Test Edilebilirlik:

  • Her katmanın soyutlanması ve bağımsızlığa sahip olması, birim testlerin daha kolay yazılmasını ve uygulanmasını sağlar.
  • Özellikle iş mantığı (Servis Katmanı), bu prensipler sayesinde daha kolay bir şekilde test edilebilir.

Onion Mimari Kavramları

  1. Domain Model:
    • Amaç: İş mantığını temsil eden nesnelerin (objelerin) ve bu nesneler arasındaki ilişkilerin bir modelini sağlar. Bu genellikle uygulamanın temel işlevselliğini ve kurallarını yansıtan bir yapıdır.
    • Görevleri:
      • İş mantığı nesnelerini tanımlamak.
      • Bu nesneler arasındaki ilişkileri ve kuralları belirlemek.
    • Örnekler:
      • Müşteri sınıfı, Sipariş sınıfı, Ürün sınıfı.
  2. Domain Services:
    • Amaç: Domain Model’deki nesneler arasında işlevselliği sağlamak için kullanılan hizmet sınıflarıdır. Domain Services, genellikle bir varlık (entity) tarafından gerçekleştirilemeyen işlemleri temsil eder.
    • Görevleri:
      • Domain Model içinde belirli işlemleri gerçekleştirmek.
      • İş mantığı kurallarını uygulamak.
    • Örnekler:
      • SepetHesaplayici (Alışveriş sepetindeki ürünleri hesaplamak için).
      • ÖdemeServisi (Ödeme işlemlerini yönetmek için).
  3. Application Services:
    • Amaç: Dış katman (UI) ile iç katmanlar (Domain Model, Domain Services) arasında bir arayüz sağlar. Kullanıcıdan gelen talepleri alır, uygun iş mantığı işlemlerini başlatır ve sonuçları döner.
    • Görevleri:
      • Dış katmandan gelen istekleri işleyerek Domain Model veya Domain Services ile etkileşime geçmek.
      • Kullanıcı etkileşimlerini yorumlamak ve işlemleri başlatmak.
    • Örnekler:
      • AlışverişUygulamaServisi (Ürün eklemek, sepeti güncellemek gibi işlemleri sağlayan bir servis).
      • KullanıcıHesapServisi (Kullanıcı hesap yönetimi işlemlerini sağlayan bir servis).

Bu kavramlar, bir projede iş mantığını düzenli bir şekilde organize etmek ve bu işlemleri katmanlar arasında sağlıklı bir şekilde iletmek için kullanılır.

Temel Bir Örnek

Domain Model katmanı, genellikle bir projenin en önemli ve özgün parçalarını içerir. Örneğin, E-Ticaret Uygulaması örneğinizde Domain katmanını şu şekilde düşünebiliriz:

    public class Customer
    {
        public string Name { get; set; }
        public string Email { get; set; }
    }
    public class Order
    {
        public List<Product> Products { get; set; }
        public Customer Customer { get; set; }
        public decimal TotalAmount => Products.Sum(p => p.Price);
    }
    public class Product
    {
        public string Name { get; set; }
        public decimal Price { get; set; }
    }

Bu örnekte, Product, Customer ve Order gibi entity sınıfları bulunmaktadır.

Domain Service katmanı, iş mantığını gerçekleştiren ve domain logic’inin koordine edildiği bir katmandır. Bu katmanda, genellikle domain nesneleri arasında işlemleri gerçekleştiren servis sınıfları bulunur. Örneğin, E-Ticaret Uygulaması örneğinde, Domain Service katmanında şu türden servis sınıfları bulunabilir:

    public class BasketService
    {
        private readonly List<Product> basket;

        public BasketService()
        {
            this.basket = new List<Product>();
        }

        public void AddProduct(string name, int price)
        {
            // İş mantığı: Ürünü sepete ekle
            basket.Add(new Product {Name = name, Price = price });
        }

        public void GetBasket()
        {
            // İş mantığı: Sepeti kullanıcıya göster
            Console.WriteLine("Sepetinizdeki Ürünler:");
            foreach (var product in basket)
            {
                Console.WriteLine($"{product.Name} - Fiyat: {product.Price}");
            }
        }

        public decimal TotalAmount()
        {
            // İş mantığı: Sepetin toplam tutarını hesapla
            return basket.Sum(p => p.Price);
        }
    }
    public class PaymentService
    {
        public void MakePayment(decimal amount)
        {
            // Ödeme işlemleri
            Console.WriteLine($"Ödeme yapıldı: {amount} TL");
        }

        // Diğer ödeme işlemleri
    }

Bu örnekte, BasketService, sepet oluşturma işlemini yöneten bir servis sınıfını temsil eder. Bu sınıf, bir sepet oluşturma, görüntüleme ve hesaplama işlemini gerçekleştirir. PaymentService ise ödeme işlemlerini yöneten bir başka servis sınıfını temsil eder.

Bu şekilde, iş mantığı servisleri katman içinde gruplandırılır ve bu servisler, belirli bir iş sürecini yönetmek veya iş kuralını uygulamak gibi spesifik görevlere odaklanır. Bu sayede, uygulama iş mantığı daha düzenli ve modüler bir şekilde organize edilmiş olur.

Application Service katmanı, dış dünya ile (genellikle kullanıcı arayüzüyle) etkileşim sağlayan bir katmandır. Bu katman, kullanıcı taleplerini alır, ilgili iş mantığı servislerini çağırır, gerekirse veri dönüşümleri gerçekleştirir ve sonuçları dış dünyaya iletir. Bu katmanın temel amacı, uygulamanın kullanıcı arayüzü ve dış dünya ile olan etkileşimi yönetmektir.

E-Ticaret Uygulaması örneğinde, Application Service katmanında şu türden bir servis sınıfı bulunabilir:

    public class ShoppingService
    {
        private readonly BasketService basketService;
        private readonly PaymentService paymentService;

        public ShoppingService(BasketService basketService, PaymentService paymentService)
        {
            this.basketService = basketService;
            this.paymentService = paymentService;
        }

        public void AddProduct(string name, int count)
        {
            // İş mantığı: Ürünü sepete ekle
            basketService.AddProduct(name, count);
        }

        public void GetBasket()
        {
            // İş mantığı: Sepeti kullanıcıya göster
            basketService.GetBasket();
        }

        public void MakePayment()
        {
            // İş mantığı: Ödeme işlemini başlat
            paymentService.MakePayment(basketService.TotalAmount());
        }
    }

Bu örnekte, ShoppingService, kullanıcı arayüzü ile etkileşimde bulunarak iş mantığı servislerini çağıran bir uygulama servisi sınıfını temsil eder. Bu sınıf, kullanıcı tarafından yapılan ürün ekleme, sepeti gösterme ve ödeme yapma gibi talepleri iş mantığı servisine ileten metotları içerir. Bu katman, uygulamanın dış dünya ile etkileşimini yönettiği için genellikle Controller veya Facade gibi isimlendirilir. Application Service katmanı, Dış Katman (UI) ile içteki Servis Katmanı arasında bir köprü görevi görerek, kullanıcı etkileşimlerini iş mantığı servislerine iletilmesini sağlar.


Onion Mimari Düzeni

Yukarıdaki örneklemeyi Onion Mimarisinin genel prensiplerini anlatmak üzere oluşturdum. Şimdi temel bir e-ticaret sitesinin gerçek bir senaryo yakın temel bir hiyerarşik planlanmasını yapalım.

Core (Temel domain nesneleri/iş mantığı servisleri)
├── DomainModel (Temel varlıkları (entity) temsil eder.)
     ├── Product.cs
     ├── Order.cs
     └── OtherEntities.cs
├── DomainServices (Yalnızca varlıklara (entity) (Domain Model) ve iş kurallarını uygulayan temel servis sınıflar. Burada repo operasyonları yapan gibi domain odağı dışındaki servisler yazılmaz.)
     ├── ProductService.cs
     └── OtherDomainServices.cs

Application (Dış dünya ile etkileşim)
├── Services (Uygulamanın temel operasyonlarını içerir ve genellikle kullanıcı arabirimiyle etkileşim sağlar. Repo operasyonların yapıldığı service’ler burada yazılır. Yalnız burada kesinlikle dışa doğru bir bağımlılık yoktur. Dikkat ettiğiniz gibi repo interface’leri burada oluşturulmuştur. Böylece loose coupling uygulanarak sağlıklı bir bağlantı modeli oluşturulmuştur.)
     ├── ProductService.cs
     ├── OrderService.cs
     └── OtherServices.cs
├── DTOs (Servislerin dış dünye ile iletişimini sağlar.)
     ├── ProductDTO.cs
     ├── OrderDTO.cs
     └── OtherDTOs.cs
└── Interfaces (Servislerin Infrastructure katmanında repoları kullanabilmesi için soyutlama sağlar.)
     ├── IProductRepository.cs
     ├── IOrderRepository.cs
     └── OtherRepositories.cs
├── Exception (Kendi hata yönetim sınıfımız.)
     └── MyException.cs
├── Mappers (DTO ile Entity arasındaki bağlantı için AutoMapper kütüphanesi.)
     └── Mapping.cs
├── Utilities (Loglama ve diğer altyapı işlemleri için soyutlama sağlar.)
     ├── ILoggerService.cs
     ├── IUnitOfWork.cs
     ├── IOtherUtilities.cs
     └── ServiceRegistration.cs (Dependency Injection için static bir sınıf.)

Infrastructure (Alt yapı hizmetleri)
├── DataAccess (Veriye erişim teknolojilerinin tanımlanmasını sağlar.)
     ├── DbContext.cs
     └── OtherDataAccessClasses.cs
├── Repositories (Veri tabanı operasyonlarını sağlar.)
     ├── ProductRepository.cs
     ├── OrderRepository.cs
     └── OtherRepositoryImplementations.cs
├── Services (Veri tabanı erişimi, harici servis entegrasyonları.)
     ├── EmailNotificationService.cs
     └── PaymentService.cs
├── Utilities (Loglama ve diğer altyapı işlemlerinin implement’ini sağlar.)
     ├── LoggingService.cs
     ├── IUnitOfWork.cs
     └── OtherUtilities.cs

Presentation (Sunum katmanı MVC, API ve benzeri projeler)