Derinlemesine yazılım eğitimleri için kanalımı takip edebilirsiniz...

Nedir Bu Onion Architecture? Tam Teferruatlı İnceleyelim

Merhaba,

Bu içeriğimizde, bir uygulamanın test edilebilirliğini, sürdürülebilirliğini ve güvenirliliğini daha da iyi inşa edebilmemiz için 2008 yılında Jeffrey Palermo tarafından tasarlanmış olan Onion Architecture üzerine istişare eyliyor olacağız. Onion Architecture, klasik bilinen 3 veya n katmanlı mimari yapılanmasındaki karşılaşılan zorlukları ve kaygıları ele alarak bunlara çözüm sağlamayı amaçlayan ve katmanlar arasında gevşek bir bağımlılık kurularak mimariyi inşa etmemizi sağlayan bir yaklaşımdır. Şimdi gelin bu yaklaşımın anatomisini tam manasıyla masaya yatıralım…

Onion Architecture Nedir?

Her yazılım, gayesine daha fazla hizmet edebilmek için uzun ömürlü olmak, yarınların teknolojileri ve yaklaşımlarına hızlıca adaptasyon kurabilecek kabiliyette olmak ister. Bu gün inşa edilen bir yazılım, yarının değişkenlik gösteren ihtiyaçlarıyla uyuşabilmek için tekrar inşa edilmek gibi büyük ve maliyetli bir yaklaşımdan ziyade hızlı ve modüler bir yapılanmayla o günün ihtiyaçlarına bir şekilde uyum gösterebiliyor olmalıdır. Bunun için uygulamanın gelecek vadedebiliyor olması gerekir.

Geliştirilen bir uygulamanın gelecek vadedebilmesi için iyi kurgulanmış olması gerekmektedir. Bu kurgu, yeni ihtiyaçlar doğrultusunda eklenmesi icap edecek olan yeni modüller ve katmanların dönem ve teknoloji fark etmeksizin hızlıca eklenebiliyor olmasını gerektirir. Aksi taktirde, geleceğin ihtiyaçları mevcut yazılıma eklenmeye çalışıldığında çakışma yahut uygunluk problemi yaşanırsa eğer bu durum ‘proje eskimesi’ olarak nitelendirilmektedir ki, kesinlikle istemeyeceğimiz bir durumdur.

İşte böyle bir durumla karşılaşmamak ve yazılım açısından dönemin teknolojik şartlarına ayak uydurabilmek için uygulamanın mümkün mertebe sorumlulukları net bir şekilde ayrıştırılmış katmanlara parçalanması gerekmektedir. Tam bu noktada, Eee hoca biz zaten bunu çok katmanlı mimariyle yapıyorduk! dediğinizi duyar gibiyim. Evet… Çok katmanlı mimarinin amacı zaten bu şekilde uygulamanın hem gelişime hem de dönemsel ihtiyaçlara göre olacak değişikliklere olan direncini kırmak ve yönetebilmek için geliştirdiğimiz bir yaklaşımdır. Gayet tabi doğrusunu yapıyorduk lakin bu yaklaşımında kendine göre getirdiği zorluklar mevcut. Dolayısıyla bu zorlukları aşabilmek ve test edilebilirlik, güvenirlik ve en önemlisi sürdürülebilirlik açısından daha farklı tasarımlarla uygulamalarımızı geliştirmemiz gerekmektedir. Bu durumu bir başka deyişle, ‘kullandığımız çok katmanlı mimari yapılanmasını stratejik açıdan daha da geliştirmeliyiz’ diyerekte özetleyebiliriz.

Haliyle Onion Architecture, yazılım süreçlerine daha gelişmiş bir katmansal mimari sunarak, klasik çok katmanlıdaki bazı problemleri aşabilmemizi ve ayrıca olabilecek değişiklik durumlarında daha az çaba sarf edilerek hızlı bir çözüm sunmamızı ve tüm bunları yaparken uygulama katmanları arasında Gevşek Bağlılık(Loose Coupling) oluşturmamızı sağlamaktadır.

Şimdi gelin, Onion Architecture’ı daha iyi anlayabilmek için klasik çok katmanlı mimariye göz atalım.

Geleneksel Katmanlı Mimari
Nedir Bu Onion Architecture?Yandaki şemada olduğu gibi geleneksel katmanlı mimari ‘UI’ -> ‘Business Logic’ -> ‘Data’ olmak üzere bir hiyerarşiye sahiptir. Bu mimarinin avantajlı getirilerinden ziyade zorluklarını ve bu zorlukların bizlere biçtiği maliyet üzerine konuşmakta fayda olduğu kanaatindeyim.

Görüldüğü üzere geleneksel katmanlı mimaride uygulamayı oluşturan katmanlar arasında sıkı bir bağlılık mevcuttur ve her katman hiyerarşik olarak altındaki katmana bağlılık göstermektedir. Ayrıca kullanıcı arayüzü sade ve sadece ‘Business Logic’ katmanıyla iletişimdedir ve ‘Data’ katmanıyla direkt iletişime geçmesine izin verilmemektedir. Böylece tasarımsal açıdan bir düzen getirilmiş ve ayrıca güvenlik sağlanmış olunsada, büyük ve karmaşık ölçekteki uygulamalarda bu tasarım yetersiz kalmaktadır. Bunun nedeni veri katmanı olan ‘Data’ katmanının merkezi bir rol üstlenmesidir. Böylece uygulama tasarımında veri ve verisel dönüşümler ciddi problemlere yol açmakta ve merkezi pozisyonda olmasından dolayı uygulamada veriye ve verinin geliş tarzına bir bağımlılık oluşturmaktadır. Bu da süreçte veri erişim mantığının değişmesi ihtiyacında yüksek maliyet olarak karşımıza çıkmaktadır.

Şimdi ise Onion Architecture’ın tasarımsal açıdan bu ve bunun gibi durumlara ne gibi çözümler getirdiğini inceleyelim.
Nedir Bu Onion ArchitectureGörüldüğü üzere Onion Architecture’da katmanlar iç içe dairesel şekilde seyretmektedir. Görüntüsü Onion’a yani Soğan’a benzediği için bu ismi almıştır. Hatta görüntüden ziyade işlevsel açıdan her bir katmanın sadece bir içteki katmana bağımlılık göstermesi gözde soğan anatomisini canlandırdığı için bu şekilde sıfatlandırılmıştır diyebiliriz.

Evet… Onion Architecture’da her katmanın daha merkezi katmanlara bağlılığı temel ilkedir. Bu durum merkezi katmanların dıştaki katmanlara bağlılık sergilememesini gerektirir. Yani bağlılık yine tek yönlüdür. Lakin bu sefer içe doğrudur. Bu da, herhangi bir katmanda yapılan değişikliğin içe doğru bir bağlayıcılığı olmadığı için merkeze doğru olan katmanları etkilemeyeceği lakin dıştaki katmanları etkileyeceği anlamına gelecektir.

Yukarıdaki görseli detaylandırırsak eğer içten dışa doğru;

  • Domain Entities/Domain/Core Katmanı
    Mimarinin merkezi katmanıdır. Tüm uygulama için olan Domain ve veritabanı entity’leri bu katmanda oluşturulur.

    • Entities
      ORM araçları tarafından kullanılan ve veritabanındaki tabloları temsil eden sınıflardır. *(En önemli)
    • Value Object
      Kimliksiz ve immutable(değişmez) olan nesnelerdir.
    • Enumeration
    • Exceptions
      Domain için oluşturulan exception sınıflarıdır.
  • Repository&Service Interfaces/Core Katmanı
    Bu katman, Domain katmanı ile uygulamanın iş/business/service katmanı arasında bir soyutlama katmanıdır. Repository olsun, service olsun tüm arayüzler burada tanımlanır. Amaç veri erişiminde Gevşek Bağlı(Loose Coupling) bir yaklaşım sergilemektir. Domain katmanını referans eder.

    Gerekli arayüzlerle birlikte uygulamanın geneline hitap edecek tüm objeler bu katmanda tanımlanır.

    • Custom Exception
      Kişiselleştirilmiş exception sınıflarıdır.
    • Response Object
    • Request Parameters Object
    • DTO Objects
    • ViewModels Objects
    • Interfaces(Repository, UnitOfWork)
    • Mapping
      CQRS tasarım kalıbı kullanılır.
    • Validators
Onion Architecture’da Repository & Service Interfaces katmanı bir bütün olarak Application isminde nitelendirilebilmektedir. Aynı şekilde, Domain ve Application katmanlarıda bütünsel olarak Core yani çekirdek katmanı olarak nitelendirilmektedirler.
  • Persistence Katmanı
    DbContext, migration ve veritabanı konfigürasyon işlemleri bu katmanda gerçekleştirilir. Ayrıca Application katmanındaki interface’ler burada implemente edilir.

    • DbContext
    • Migrations
    • Configurations
    • Seeding
    • Interface Implementation
      Özellikle repository ve unit of work gibi desenlerin concrete nesneleri burada oluşturulur.

    En dış katman olduğu için bu katmana herhangi bir katman bağımlılık göstermeyecektir.

  • Infrastructure Katmanı
    Esasında Persistence katmanı bu katmanla bütünleşik olarak kullanılmaktadır. Genellikle sisteme eklenecek dış/external yapılanmalar bu katmanda dahil edilir. Haliyle bu katmanda diğer en dış katman olduğu için herhangi bir katman tarafından bağlılık olmamalıdır.

    • Email/Sms
    • Notification
    • Payment
  • Presentation Katmanı
    Kullanıcının uygulama ile iletişime geçtiği katmandır.

    • Console App.
    • Web App.
    • MVC
    • Web API

    En üst katmandır diyebiliriz.

Onion Architecture, Clean Architecture uygulayabilmek için kullanılan tasarım kalıplarından biridir.

Nedir Bu Onion ArchitectureYukarıda mimarinin anatomisini incelemiş bulunmaktayız. Bu anatomiyi tam olarak anlayabilmek hak vereceğiniz üzere bol bol pratik ve çalışma gerektirmektedir. Lakin yukarıda da ara ara bildirildiği üzere bazı katmanlar bütünleşik olarak daha farklı şekilde isimlendirilebilmekte veya her biri farklı şekilde nitelendirilebilmektedir. Dolayısıyla internetten konuya dair araştırma yaptığınızda yukarıdakine alternatif olarak bu yandaki gibi ya da farklı muadillerinde görseller görebilmeniz oldukça muhtemeldir. Siz hangi görseli görürseniz görün bilmeniz gereken en nihayetinde Onion Architecture’ın mantığıdır. İşte o zaman tüm görsellerin aynı şeyi anlattığını anlayacaksınız…

Şimdi gelin Onion Architecture’ın avantajlarını sıralayalım ve ardından sıca sıcağına pratik bir uygulama üzerinden örneklendirme yapalım.

Onion Architecture’ın Avantajları

  • Onion Architecture, uygulama katmanlarının mimarisel olarak sadece iç katmana olan bağımlılığı sayesinde Tightly Coupling(Sıkıca Bağlanma)’ya son vermekte ve Loosely Coupled(Gevşek Bağlanma)’yı sağlamaktadır.
  • Sorumluluklarına göre projeyi katmanlara ayırmasından dolayı da Separation of Concerns prensibine de uygundur.
  • Katmanlar ilişkisel olarak içe doğru bağımlıdırlar. Dolayısıyla bu durum maintain etmesini kolaylaştırmaktadır.
  • Birim testleri uygulamanın diğer modüllerinin etkisi olmaksızın ayrı katmanlar için oluşturulabileceğinden dolayı daha iyi test edilebilirlik sağlar.

Onion Architecture’da, geleneksel mimarinin aksine veri katmanı(Persistence Katmanı) en iç katman olarak değil, en dış katman olarak belirlenmiştir. Böylece uygulamada verinin nerden geldiğinden bağımsız olarak geliştirme yapılabilmektedir.

Örnek Onion Architecture Uygulaması Yapalım!

Şimdi Onion Architecture’ı örneklendirebilmek için güzel bir pratik çalışma gerçekleştirelim.
Projenin Yapısı

Nedir Bu Onion Architecture

Onion Architecture ile geliştirilmiş bir uygulamanın en ideal düzeni…


Herşeyden önce boş bir solution oluşturup, bu solution altında ‘src’ isminde bir klasör oluşturunuz.

Onion Architecture mimarisinde bir uygulama temellerini atabilmek için merkezi yapılanmadan dışa doğru inşa sürecini başlatmakta fayda vardır. Bunun için aşağıdaki adımları rehber niteliğinde sırasıyla takip etmeniz yeterli olacaktır.

  • 1. Adım – Core Katmanı – Domain Katmanı
    Solution altındaki ‘src’ klasörü içerisine ‘Core’ adında bir klasör ekleyiniz. Ardından ilgili klasör içerisinde mimarideki merkezi katmanımızı temsil edecek olan OnionArcExample.Domain ismindeki class library’i oluşturunuz.
    Nedir Bu Onion Architecture

    Oluşturulan bu katman içerisinde entity’leri vs. tutmaktan sorumluydu. Dolayısıyla bizler de örnek amaçlı bir entity tasarlayalım. Bunun için ilgili class library içerisinde ‘Common’ ve ‘Entities’ isminde iki klasör oluşturalım ve içerisine aşağıdaki gibi örnek class’larımızı ekleyelim.

    namespace OnionArcExample.Domain.Common
    {
        public abstract class BaseEntity
        {
            public Guid Id { get; set; }
        }
    }
    
    namespace OnionArcExample.Domain.Entities
    {
        public class Product : BaseEntity
        {
            public string Name { get; set; }
            public string Description { get; set; }
            public int Stock { get; set; }
            public int Price { get; set; }
        }
    }
    

    Tabi burada tanımlanacak olan entityleri ve içeriklerini çoğaltabiliriz. Biz içeriğimizi fazla şişirmemek adına bu kadarla yetineceğiz…

  • 2. Adım – Core Katmanı – Application Katmanı
    Sıra yine ‘Core’ klasörü içerisinde gerekli soyutlamaları vs. yapacağımız application katmanını oluşturmaya gelmiştir. Bunun için ilgili dizinde OnionArcExample.Application isminde bir class library oluşturunuz.
    Nedir Bu Onion Architecture

    Application katmanına Domain katmanını bağımlılık olarak ekleyiniz.

    Application katmanı içerisinde aşağıdaki yapılar tanımlanabilir.
    ‘Dto’ class’ları;

    namespace OnionArcExample.Application.Dto
    {
        public class ProductDTO
        {
            public Guid Id { get; set; }
            public string Name { get; set; }
        }
    }
    

    ‘Custom Exception’ class’ları;

        public class MyException : Exception
        {
            public MyException() : base("My error occured")
            {
    
            }
            public MyException(string message) : base(message)
            {
    
            }
            public MyException(Exception exception) : this(exception.Message)
            {
    
            }
        }
    

    ‘Repository’, ‘UnitOfWork’ yahut ‘Context’ gibi arayüzleri;
    ‘Repository’

        public interface IRepository<T> where T : BaseEntity, new()
        {
            Task<List<T>> GetAsync();
            Task<T> GetByIdAsync(Guid id);
            Task<T> AddAsync(T entity);
        }
    
    namespace OnionArcExample.Application.Interfaces.Repositories
    {
        public interface IProductRepository : IRepository<Product>
        {
        }
    }
    

    ‘UnitOfWork’;

    namespace OnionArcExample.Application.Interfaces.UnitOfWork
    {
        public interface IUnitOfWork : IAsyncDisposable
        {
            //IDbContextTransaction : EntityFrameworkCore kütüphanesine ihtiyaç vardır.
            Task<IDbContextTransaction> BeginTransactionAsync();
            public IProductRepository ProductRepository { get; }
        }
    }
    

    ‘Context’;

    namespace OnionArcExample.Application.Interfaces.Context
    {
        public interface IApplicationContext
        {
            DbSet<Product> Products { get; set; }
        }
    }
    

    Gelen request’te ki parametreleri temsil edecek olan ‘Parameters Object’leri;

    namespace OnionArcExample.Application.Parameters
    {
        public class RequestParameter
        {
            public int PageSize { get; set; }
            public int PageNumber { get; set; }
            public RequestParameter(int pageSize, int pageNumber)
            {
                PageSize = pageSize;
                PageNumber = pageNumber;
            }
        }
    }
    

    Belli başlı Wrapper’ları;
    Örneğin

    namespace OnionArcExample.Application.Wrappers
    {
        public class BaseResponse
        {
            public Guid Id { get; set; }
            public bool Success { get; set; }
            public string Message { get; set; }
        }
    }
    

    Daha da arttırılabilir. Son olarak bu katmandaki servisleri, referans alan katmanlar tarafından enjekte edilebilir şekilde kullanabilmek için ‘ServiceRegistration’ isimli bir static sınıf oluşturmakta fayda var.

    namespace OnionArcExample.Application
    {
        public static class ServiceRegistration
        {
        }
    }
    
  • 3. Adım – Infrastructure Katmanı – Persistence Katmanı
    Çoğu uygulamada Persistence katmanı da Infrastructure altında inşa edilmektedir. O yüzden solution içerisinde oluşturulmuş olan ‘src’ klasörü altında ‘Infrastructure’ adında bir klasör açınız ve içerisine OnionArcExample.Persistence adında bir class library oluşturunuz.

    Nedir Bu Onion Architecture

    Persistence katmanı, veritabanı migration işlemleri ve implementasyon operasyonlarının gerçekleştirileceği katman olduğundan dolayı Application ve Domain katmanlarını referans almalıdır.

    Persistence katmanı içerisinde aşağıdaki işlemler gerçekleştirilmelidir;
    ‘Context’ sınıfı;

    namespace OnionArcExample.Persistence.Context
    {
        public class ApplicationDbContext : DbContext, IApplicationContext
        {
            public ApplicationDbContext(DbContextOptions dbContextOptions) : base(dbContextOptions)
            { }
            public DbSet<Product> Products { get; set; }
            protected override void OnModelCreating(ModelBuilder modelBuilder)
            {
                modelBuilder.Entity<Product>().HasData(
                    new Product { Id = Guid.NewGuid(), Name = "Product 1", Description = "Product 1 Description", Price = 1000, Stock = 10 },
                    new Product { Id = Guid.NewGuid(), Name = "Product 2", Description = "Product 2 Description", Price = 2000, Stock = 20 },
                    new Product { Id = Guid.NewGuid(), Name = "Product 3", Description = "Product 3 Description", Price = 3000, Stock = 30 },
                    new Product { Id = Guid.NewGuid(), Name = "Product 4", Description = "Product 4 Description", Price = 4000, Stock = 40 }
                    );
            }
        }
    }
    

    ‘DesignTimeDbContextFactory’ sınıfı;

    namespace OnionArcExample.Persistence.Context
    {
        public abstract class DesignTimeDbContextFactory<TContext> : IDesignTimeDbContextFactory<TContext> where TContext : DbContext
        {
            protected abstract TContext CreateNewInstance(DbContextOptions<TContext> options);
            public TContext CreateDbContext(string[] args)
            {
                DbContextOptionsBuilder<TContext> builder = new DbContextOptionsBuilder<TContext>();
                IConfiguration configuration = new ConfigurationBuilder()
                    .SetBasePath(Path.Combine(Directory.GetCurrentDirectory(), "../OnionArcExample.WebAPI"))
                    .AddJsonFile("appsettings.json")
                    .Build();
                builder.UseSqlServer(configuration.GetConnectionString("SQLConnection"));
                return CreateNewInstance(builder.Options);
            }
        }
    }
    
    namespace OnionArcExample.Persistence.Context
    {
        public class ApplicationDbContextFactory : DesignTimeDbContextFactory<ApplicationDbContext>
        {
            protected override ApplicationDbContext CreateNewInstance(DbContextOptions<ApplicationDbContext> options)
            {
                return new ApplicationDbContext(options);
            }
        }
    }
    

    ‘Concrete Repository’ sınıfları;
    ‘Repository’

    namespace OnionArcExample.Persistence.Repositories
    {
        public class Repository<T> : IRepository<T> where T : BaseEntity, new()
        {
            private readonly ApplicationDbContext _context;
            public Repository(ApplicationDbContext context)
            {
                _context = context;
            }
            private DbSet<T> Table { get => _context.Set<T>(); }
            public async Task<T> AddAsync(T entity)
            {
                await Table.AddAsync(entity);
                await _context.SaveChangesAsync();
                return entity;
            }
            public async Task<List<T>> GetAsync() => await Table.ToListAsync();
            public async Task<T> GetByIdAsync(Guid id) => await Table.FindAsync(id);
        }
    }
    

    ‘ProductRepository’

    namespace OnionArcExample.Persistence.Repositories
    {
        public class ProductRepository : Repository<Product>, IProductRepository
        {
            public ProductRepository(ApplicationDbContext context) : base(context)
            {
            }
        }
    }
    

    ‘Unit Of Work’ implemantasyonu;

    namespace OnionArcExample.Persistence.UnitOfWorks
    {
        public class UnitOfWork : IUnitOfWork
        {
            private readonly ApplicationDbContext _context;
            public IProductRepository ProductRepository { get; }
            public UnitOfWork(ApplicationDbContext context, IProductRepository productRepository)
            {
                _context = context;
                ProductRepository = productRepository;
            }
            public async Task<IDbContextTransaction> BeginTransactionAsync() => await _context.Database.BeginTransactionAsync();
            public async ValueTask DisposeAsync() { }
        }
    }
    

    Generate edilen ‘Migration’ sınıfları;

    Bu ve bunun gibi implemantasyonları yaptıktan sonra bu katmanı kullanacak olan UI’a içerideki servisleri enjekte edebilmemiz için ‘ServiceRegistration’ sınıfı oluşturulmalıdır. Zaten farkındaysanız eğer bu ‘ServiceRegistration’ sınıfının Domain dışındaki tüm katmanlarda olması tercihen gereklidir ve işimizi oldukça kolaylaştıracaktır.

    namespace OnionArcExample.Persistence
    {
        public static class ServiceRegistration
        {
            public static void AddPersistenceServices(this IServiceCollection serviceCollection, IConfiguration configuration = null)
            {
                serviceCollection.AddDbContext<ApplicationDbContext>(options =>
                options.UseSqlServer(configuration?.GetConnectionString("SQLConnection")));
    
                serviceCollection.AddTransient<IProductRepository, ProductRepository>();
                serviceCollection.AddTransient<IUnitOfWork, UnitOfWork>();
            }
        }
    }
    
  • 4. Adım – Infrastructure Katmanı
    External servis’leri oluşturabilmek için ‘src’ -> ‘Infrastructure’ klasörü içerisinde OnionArcExample.Infrastructure isminde bir class library oluşturunuz.
    Nedir Bu Onion Architecture

    Tabi bu katmanda dış servisleri oluşturacağı için bu servislerin arayüzleri yine Application katmanında olacaktır. O yüzden Infrastructure katmanına da Application referansını ekleyiniz.

    Application katmanındaki arayüz;

    namespace OnionArcExample.Application.Interfaces.Services
    {
        public interface IEmailService
        {
            bool Send(string to, string message);
        }
    }
    

    Bu arayüzün implemente edildiği Infrastructure katmanındaki servis class’ı;

    namespace OnionArcExample.Infrastructure
    {
        public class EmailService : IEmailService
        {
            public bool Send(string to, string message)
            {
                Console.WriteLine("mail sent");
                return true;
            }
        }
    }
    

    Ve tabi bu katmanında ‘ServiceRegistration’ sınıfını unutmuyoruz 🙂

    namespace OnionArcExample.Infrastructure
    {
        public static class ServiceRegistration
        {
            public static void AddInfrastructureServices(this IServiceCollection serviceCollection)
            {
                serviceCollection.AddTransient<IEmailService, EmailService>();
            }
        }
    }
    
  • 5. Adım – Presentation Katmanı
    Son olarak kullanıcı arayüzünü oluşturacağımız Presentation katmanının oluşturulması gerekmektedir. Bunun için; ‘src’ içerisinde ‘Presentation’ isminde bir klasör açınız ve içerisine OnionArcExample.WebAPI adında bir Asp.NET Core Web API projesi oluşturunuz. Tabi burada presentation olarak farklı bir platform yahut yaklaşımın seçilebilirliği sizlere kalmıştır. Biz örneklendirme olarak API üzerinden devam edeceğiz.

    Presentation katmanı ‘Infrastructure’ ve ‘Presentation’ katmanlarını referans almalıdır.

    Bu katmanda yapılacakların sınırı olmadığı için bizler diğer katmanlarda oluşturulan servislerin kullanımına örnek vererek yetineceğiz. Bunun için ‘ProductController’ isminde bir controller oluşturmanız ve içeriğini aşağıdaki gibi inşa etmeniz yeterlidir.

    namespace OnionArcExample.WebAPI.Controllers
    {
        [Route("api/[controller]")]
        [ApiController]
        public class ProductController : ControllerBase
        {
            readonly IProductRepository _productRepository;
            readonly IEmailService _emailService;
            public ProductController(IProductRepository productRepository, IEmailService emailService)
            {
                _productRepository = productRepository;
                _emailService = emailService;
            }
            [HttpGet]
            public async Task<IActionResult> Get()
            {
                List<Product> allProducts = await _productRepository.GetAsync();
                return Ok(allProducts);
            }
            [HttpGet("sendemail")]
            public IActionResult SendEmail()
            {
                bool result = _emailService.Send("gncy@gencayyildiz.com", "laylaylom galiba sana göre sevmeler...");
                return Ok(result);
            }
        }
    }
    

    Tabi bu çalışmanın yapılabilmesi için ilgili servislerin uygulamaya enjekte edilmiş olması gerekmektedir. Bunun içinde ‘Startup.cs’ dosyasındaki ‘ConfigureServices’ metodundan aşağıdaki gibi istifade edilecektir;

        public class Startup
        {
            public Startup(IConfiguration configuration)
            {
                Configuration = configuration;
            }
            public IConfiguration Configuration { get; }
    
            public void ConfigureServices(IServiceCollection services)
            {
                services.AddPersistenceServices(Configuration);
                services.AddInfrastructureServices();
                services.AddControllers();
                .
                .
                .
            }
            .
            .
            .
        }
    

    İşte bu kadar 🙂

Test Edelim

Şimdi Onion Architecture ile inşa edilen bu uygulamayı Swagger üzerinden test edelim.
Nedir Bu Onion Architecture
Nedir Bu Onion Architecture
Görüldüğü üzere uygulama gayet sağlıklı bir şekilde çalışmaktadır 🙂

İleride bu makaleye ek olarak Onion Architecture ile CQRS + MediatR pattern’inin nasıl uygulandığını inceliyor olacağız. O halde şimdilik görüşmek üzere 🙂

İlgilenenlerin faydalanması dileğiyle…
İyi çalışmalar…

Not : Örnek çalışmayı indirebilmek için buraya tıklayınız.

Bunlar da hoşunuza gidebilir...

19 Cevaplar

  1. Tarık dedi ki:

    Harika

  2. Nurettin dedi ki:

    Harika bir makale teşekkürler.

  3. Ermek dedi ki:

    Merhaba makaleniz guzel olmus tesekkur ederiz! Ama bir sorum var? Service katmani nerde yer almali? Infrasturcturdami? (Business codes)

    • Gençay dedi ki:

      Merhaba,

      Eğer veritabanı vs. işlemleri ile verinin kalıcılığını sağlayacak/gerçekleştirecek servisleriniz söz konusuysa Persistence katmanı, yok eğer verinin kalıcılığı ya da veritabanı işlemleri dışındaki diğer sorumlulukları üstlenecek servisler mevzu bahisse Infrastructure katmanı kullanılmaktadır.

      Kolay gelsin…
      Sevgiler…

  4. Soner dedi ki:

    Merhaba Hocam,

    Beni çok büyük bir dertten kurtardınız. İlk kez bir proje yazacağım ve bunu yaparken de düzgün bir şekilde yapmak istedim. Ama işin en başında projeyi oluşturamadım 🙂

    Örnekler buluyorum ama isimlendirmelerden dolayı bir türlü ne nedir algılayamadım. Bazıları şimdi baktığımda N-tier ile Onion karşımı bir şey olduklarını gördüm. En azından nasıl yol alacağıma karar vermiş oldum.

    2012 yılında yazmış olduğunuz “N-tier Architecture” bu yazıda da yazdığınız gibi daha açık ve net yazabilmenizi çok isterdim. Umarım bir gün bu konuyu tekrardan ele alırsınız. Çünkü bu mimari ile ilgili de çok isimlendirmeler var ve çok kafa karıştırmakta. (Bazılarında alt klasörler açılmakta ve “Concrete” ve “Abstract” isimlendirmeler de ayrıca yapılmakta)

    Başka mimariler var mıdır bilmiyorum ama eğer varsa onları da “Design Pattern” olarak açmış olduğunuz kategori de olduğu gibi “Architecture” kategorisi açıp, bu başlıkta da yeni yazılarınızı eklemenizi ümit etmekteyim.

    Teşekkürler.

  5. Osman dedi ki:

    Abi çok teşekkürler, uzun zamandır projenin nasıl bir mimaride olmasını düşünüyordum. bu kaynak süper oldu.

  6. Ardanuc AKAR dedi ki:

    Abi ben bu yaptıklarını projeme uygulamdım ama DesignTimeDbContextFactory kısmıyla ilgili bir sorum olacaktı. Şimdi connection string appsettings içerisinde açık açık ortada daha önceden anlattığın secret manager’ı kullanarak o kısmı secrets.json içersine koymak istiyorum. İnternette araştırdım nasıl koyabilirim diye yapılan örneklerin hepsi console uygulamasında. new ConfigurationBuilder dediğimiz yerde .AddUserSecrets() yaparak kullanılabiliyor ama biz class library olarak yaptığımız için nasıl kullanırız söyleyebilir misin?

    Konudan bağımsız olarak JWT, Identity kullanarak yapmaya çalışıyorum projemi sende böyle bir API çalışması yapmayı düşünüyor musun?

  7. Fatih dedi ki:

    Makalenizin sonundaki linkten projeyi indirdim. Çalıştırınca connection stringi okuyamıyorum hatası aldım.

    DesignTimeDbContextFactory.cs’de .SetBasePath satırını

    .SetBasePath(Path.Combine(Directory.GetCurrentDirectory(), “../../Presentation/OnionArcExample.WebAPI”));

    şeklinde düzeltince problem çözüldü. Yazı için teşekkürler.

  8. Thea dedi ki:

    Merhaba.

    Resimde en disdaki Tests ve Services(WCF) hangi katmana aittirler? Infrastructure-a mi?.

    resim – https://www.gencayyildiz.com/blog/wp-content/uploads/2021/03/Nedir-Bu-Onion-Architecture-300×300.png

    Infrastructure(Logging, Repositories) – parantezdeki Repositories Application katmanindaki abstractionin impelementasyonumu?

    sagolun.

    • Gençay dedi ki:

      Merhaba,

      Test ayrı bir katmandır. Services için ise verinin geliş yöntemiyle alakalı operasyonlar mevzu bahis ise Persistence’a, yok eğer dış dünya ya da başka servislerle etkileşim kuran operasyonlar mevzu bahis ise Infrastructure’a karşılık gelmektedir.

  9. Meral dedi ki:

    Öncelikle güzel makale için teşekkürler.

    “Domain Entities/Domain/Core Katmanı” katmanı altında bu madde yazmışsınız:

    “ORM araçları tarafından kullanılan ve veritabanındaki tabloları temsil eden sınıflardır.”

    Oysa ORM entity’leri database (persistance) sınıfıları diil midir?

    “ORM (DB) entity” ve “DD entity” farklı kavramlardır. doğru muyum? Aslında “ORM araçları” ile bahsedilen cümle sanki biraz yanlış anlaşılabiliyor. Orada ORM kelimesi olmasa daha iyi gibi… Bu cümlede sadece “Domain entity” yazsaydınız daha doğru olmaz mıydı?

    Ben tam bu kısmı kafamda netleştiremedim. Tekrar teşekkür ederim.

    • Gençay dedi ki:

      Merhaba Meral hanım,

      Şimdi şöyle temellendirerek başlayalım.

      Bir kimliği/identity’si olan nesneye entity diyoruz. Bunda hem fikir miyiz?

      Şimdi bu entity kavramı, hem bir teknik yazılım yaklaşımı olan ORM’de hem de prensipler kümesi olan ve belli bir yazılım modelini savunan DDD‘nin taktiksel yaklaşımında geçen ortak bir kavramdır.

      DDD’nin ana hedeflerinden biri, yapılan çalışmalarda gerçekliğin büyük ölçekte modellenmesi ve bu modelin geliştirilmesi sürecinde varsa ihtiyaç hızla yenilenmesidir.

      ORM’ye ise bu DDD entity’leri üzerinde OOP nimetlerini kullanarak teknik boyutta veritabanı işlemlerimizi yapan bir tool’dur diyebiliriz.

      Özünde ikisi de(biri felsefi biri teknik) aynı şeyi farklı şekillerde yorumlamakta olsa da pratikte birbirlerine muadil bir olgudan bahsetmektedirler diye düşünüyorum. Ki bunu gerçek bir projede daha net bir şekilde görebiliyoruz. Yani DDD modelinde bahsi geçen entity’ler ORM’de ki entity’lerle denktir.

      Tabi varsa yanlış olduğunu düşündüğünüz bir nokta lütfen bizleri aydınlatınız.

      İstişare için teşekkür ederim.
      Sevgiler.

  10. Salih dedi ki:

    Hocam merhabalar,

    Job’lar icin quartz paketini kullaniyorum. Uygulama ici joblarimi gerceklestiriyorum. Bundan kaynakli application’da olmali diye dusundum fakat sonucta bir operasyon da var. Infrastructure katmaninda mi olmali yoksa baska bir katmanda mi?

    Tesekkurler

    • Gençay dedi ki:

      Merhaba,

      Job’ın iş mantığını yürüteceğin sınıflar infrastructure’da, o sınıfları çağıracak arayüzler application’da olmalı diye düşünüyorum. Yaptığın tasarımı burada örneklendirme şansın var mı?

  11. Ekrem Güneş dedi ki:

    Hocam iyi günler,
    Presentation katmanında webAPI miz MVC vs. Bulunabilir demişsiniz biz apiyi burada tutacağız mvc projemizi onion dışında kullanacağız yani ; Front-Back diye ayırmak istiyoruz back kendi içinde onion olacak , bunu yayınlarken direk publish ettiğimizde api de mvc uygulamamızla birlikte ayağa kalkarmı bir sorun olur mu ?

  12. Faruk dedi ki:

    EF’yi Application Services katmanına sızdırmak anti pattern olmuyor mu hocam? Bu durum Onion’ın felsefesine ters düşüyor diye anlıyorum. Uygulama doğrudan framework bağlılığı içerecek ve teknoloji değişiminde business tarafını etkilemiş olacak.

  1. 16 Mart 2021

    […] yazılarımızdan Nedir Bu Onion Architecture? Tam Teferruatlı İnceleyelim başlıklı makalemizde incelediğimiz Onion Architecture üzerinde yaşanabilecek bir takım […]

  2. 18 Haziran 2022

    […] istediğiniz herhangi bir mimarisel altyapı eşliğinde geliştirebileceğiniz gibi bendeniz onion architecture altyapısında geliştirmeyi tercih […]

Bir cevap yazın

E-posta hesabınız yayımlanmayacak. Gerekli alanlar * ile işaretlenmişlerdir