Yükleniyor

.NET Core ile Onion Architecture Rehberi

Blog Kategorileri

.Net Core Mimariler
Tasarım Desenleri
ORM Araçları
API Geliştirme
Web Geliştirme
Veritabanları
12 Ocak 2026 Pazartesi
.NET Core ile Onion Architecture Rehberi

Merhaba,

Yazılım geliştirme dünyasında, çalışan kod sadece başlangıçtır. Asıl mücadele, proje büyüdükçe kodun yönetilebilir, test edilebilir ve genişletilebilir kalmasını sağlamaktır. İşte tam bu noktada, geleneksel N-Tier (Katmanlı) mimarilerin tıkandığı yerlerde Onion Architecture devreye giriyor. Bu yazıda, .NET Core projelerinizde Onion Architecture yapısını nasıl kuracağınızı, katmanların mantığını ve bu mimarinin bize neler kazandırdığını detaylıca inceleyeceğiz.

Neden Geleneksel Mimariden Vazgeçiyoruz?

Klasik katmanlı mimaride genellikle veritabanı en alttadır ve tüm iş mantığı veritabanına bağımlıdır. Bu durum şunlara yol açabilir:

  1. Eğer mimari iyi uygulanmamış ise veritabanı teknolojisini değiştirmek zorlaşabilir.

  2. İş mantığının test edilebilirliği azalır.

  3. Kodlar birbirine sıkı sıkıya bağlanır.

Onion Architecture, bağımlılık yönünü tersine çevirerek iş mantığını merkeze alır ve bu sorunları ortadan kaldırmayı hedefler.

Onion Architecture'ın Katmanları

Bu mimariyi bir soğan gibi düşünebilirsiniz. En değerli kısım (Domain) en içte saklıdır ve dış katmanlar onu korur.

1. Domain Layer (Çekirdek Katman)

Burası uygulamanızın kalbidir. Projenizin neyle ilgili olduğunu anlatan varlıklar (Entities) burada bulunur. Başka hiçbir katmana veya dış kütüphaneye bağımlı değildir. Saf C# sınıflarıdır. İçeriğinde Entities, Value Objects, Enums, Exception sınıfları barındırır.

2. Application Layer (Uygulama Katmanı)

Domain katmanını sarmalar. İş kurallarının (Business Logic) işletildiği yerdir. Ancak dikkat: Veritabanı erişim kodları burada yazılmaz, sadece arayüzleri (Interfaces) tanımlanır. Domain katmanına bağımlıdır. İçeriğinde Service Interfaces (IRepository vb.), DTOs (Data Transfer Objects), Validation kuralları, CQRS (Command/Query) desenlerini barındırır.

3. Infrastructure Layer (Altyapı Katmanı)

Application katmanında tanımlanan arayüzlerin (Interface) doldurulduğu, yani implemente edildiği yerdir. Veritabanı işlemleri, E-posta gönderme servisleri, Dosya sistemleri burada yer alır. Application ve Domain katmanını bilir. İçeriği: Entity Framework Core Context, Migrations, Repository Implementation, External API Clients. olabilir.

4. Presentation Layer (Sunum Katmanı)

Kullanıcının sistemle etkileşime girdiği yerdir. Bir Web API, MVC projesi veya bir Blazor uygulaması olabilir. Uygulamanın giriş kapısıdır.

.NET Core Proje Örneği

Örnek Klasör yapısı

Core (Klasör)

  1. MyProject.Domain (Class Library)

  2. MyProject.Application (Class Library)

Infrastructure (Klasör)

  1. MyProject.Persistence (Class Library - EF Core burada)

  2. MyProject.Infrastructure (Class Library - Mail, Notification vb.)

Presentation (Klasör)

  1. MyProject.WebAPI (ASP.NET Core Web API)

Bu klasör yapısı, Onion Architecture’ın temel prensiplerini .NET Core projelerinde uygulamak için sıkça tercih edilen bir yaklaşımdır. Örnek klasör yapısını gördüğümüze göre, şimdi .NET Core ile Onion Architecture yaklaşımını sıfırdan, katman katman bir senaryo üzerinden inşa edebiliriz.

Senaryo

Basit bir e-ticaret sistemi için ürünleri veritabanına kaydeden bir yapı geliştireceğiz. Temel hedefimiz; kullanılan veritabanı teknolojisi değişse dahi iş mantığı kodlarının bu değişiklikten etkilenmemesini sağlamak.

1. Adım: Domain Layer (Çekirdek)

Sadece C# nesnelerimiz (Entities) bulunur.

// Proje: MyProject.Domain
// Klasör: Entities

public class Product : BaseEntity
{
    public string Name { get; set; }
    public decimal Price { get; set; }
}
// Proje: MyProject.Domain
// Klasör: Entities

public class BaseEntity
{
    public int Id { get; set; }
}

2. Adım: Application Layer (İş Mantığı & Soyutlama)

Bağımlılık: Domain Layer.

Application katmanında sistemin ne yapacağını tanımlarız; ancak bu işlemlerin nasıl gerçekleştirileceği (veritabanı erişimi, altyapı detayları vb.) bu katmanın sorumluluğunda değildir. Bu nedenle, dış dünyaya ve altyapıya yönelik bağımlılıklar arayüzler (interfaces) üzerinden soyutlanır.

Bu kapsamda, önce veritabanı işlemleri için kullanılacak genel bir Repository arayüzü tanımlayalım.

public interface IRepository<T> where T : BaseEntity
{
    Task<List<T>> GetAllAsync();
    Task<T> GetByIdAsync(int id);
    Task AddAsync(T entity);
    void Update(T entity);
    void Delete(int id);
}

IRepository arayüzünü oluşturduktan sonra, Application katmanının organizasyonuna geçebiliriz. Onion Architecture çoğunlukla CQRS veya MediatR (Mediator) desenleriyle birlikte uygulanır. Bu örnekte CQRS yaklaşımını tercih edeceğim.

Bu nedenle Application katmanı altında bir Features klasörü oluşturulacak ve her iş senaryosu bu klasör altında konumlandıracağız.

Örneği sade tutmak amacıyla yalnızca ekleme (Create) işlemi ele alacağım. Bu kapsamda CQRS yaklaşımına uygun olarak bir Command ve buna karşılık gelen Handler tanımlayalım.
CQRS’in detaylı anlatımı bu yazının kapsamı dışında tutuyorum daha detaylı CQRS rehberi için Net Core CQRS Kullanımı bloğuma bir göz atabilirsiniz.

Features > Product > Commands > CreateProductCommand

public class CreateProductCommand
{
    public string Name { get; set; }
    public decimal Price { get; set; }
}

Features > Product > Handlers > CreateProductCommandHandler

  public class CreateProductCommandHandler(IRepository<Product> _repository)
  {
      public async Task Handle(CreateProductCommand request)
      {
          //burada validasyon kontrolleri yapılabilir.
          //mapleme işlemleri yapılır
          var product = new Product
          {
              Name = request.Name,
              Price = request.Price
          };
          await _repository.AddAsync(product);
      }
  }

3. Adım: Infrastructure Layer

Bağımlılık: Application Layer ve Domain Layer.

İşte "Nasıl yapılacağının" belirlendiği yer. Entity Framework Core burada devreye girer. Application katmanındaki IProductRepository arayüzünü burada doldururuz (Implementasyon).

public class Repository<T> : IRepository<T> where T : BaseEntity
{
    protected readonly DbContext _context;
    protected readonly DbSet<T> _dbSet;
    public Repository(DbContext context)
    {
        _context = context;
        _dbSet = context.Set<T>();
    }

    public async Task<List<T>> GetAllAsync()
    {
        return await _dbSet.ToListAsync();
    }
    public async Task<T> GetByIdAsync(int id)
    {
        return await _dbSet.FirstOrDefaultAsync(x => x.Id == id);
    }

    public async Task AddAsync(T entity)
    {
        await _dbSet.AddAsync(entity);
        await _context.SaveChangesAsync();
    }
    public void Update(T entity)
    {
        _dbSet.Update(entity);
        _context.SaveChanges();
    }

    public void Delete(int id)
    {
        var entity = _dbSet.FirstOrDefault(x => x.Id == id);
        if (entity == null)
            return;
        _dbSet.Remove(entity);
        _context.SaveChanges();
    }
}

Context Sınıfı 

   public class AppDbContext(DbContextOptions options) : DbContext(options)
   {
       public DbSet<Product> Products{ get; set; }
   }

4. Adım: Presentation Layer (Sunum - Web API)

Bağımlılık: Application Layer.

Kullanıcıyla konuşan kısımdır. Buradaki Controller, ne veritabanını bilir ne de Entity Framework'ü. Sadece IRepository'i tanır.

[ApiController]
[Route("api/[controller]")]
public class ProductsController(CreateProductCommandHandler _handler) : ControllerBase
{
    [HttpPost]
    public async Task<IActionResult> Add(CreateProductCommand command)
    {
        await _handler.Handle(command);
        return Ok();
    }
}

Son Dokunuş: Dependency Injection (Program.cs)

Bu parçaların çalışması için Web API projesindeki Program.cs dosyasında birbirlerine tanıtılması gerekir.

// DB Context Bağlantısı (Infrastructure)
builder.Services.AddDbContext<AppDbContext>(options => 
    options.UseSqlServer(builder.Configuration.GetConnectionString("DefaultConnection")));

builder.Services.AddScoped(typeof(IRepository<>), typeof(Repository<>));
builder.Services.AddScoped<CreateProductCommandHandler>();

Sonuç 

Domain (Entity), veritabanından habersiz. Application (Service), hangi veritabanını kullandığını bilmiyor, sadece Interface'i biliyor. Yarın SQL Server yerine MongoDB kullanmak isterseniz, sadece Infrastructure katmanında yeni bir Repository yazmanız ve Program.cs'de tek satırı değiştirmeniz yeterli olacaktır.

Diğer Bloglarımda Görüşmek Üzere 👋