Clean Architecture

clean-architecture

Clean Architecture, yazılımların büyümesiyle birlikte ortaya çıkan kod karmaşıklığı, esnekliğin azalması, test edilebilirliğin zorlaşması, değişim ve yeniliklere uyum direnci gibi çeşitli teknik sorunları çözmeye yardımcı olur. Bu sorunları, uygulamaların farklı katmanlarını birbirinden bağımsız modüllere ayırarak çözmektedir. Robert C. Martin tarafından kendi bloğunda tanıtılmıştır.

Clean Architecture, Dependency Inversion ve Separation of Concerns ilkelerini uygulayarak mevcut yapıyı testable ve maintainable hale getirmektedir.

SoC prensibi, yazılımları işlevlerine göre ayırarak birbirinden bağımsız şekilde modüler yapıda tasarlanmasını hedefler. Her modül yazılım içerisinde belirli bir görevden sorumlu olmalıdır.

Bu mimaride modüller katmanlar olarak ele alınmaktadır. Her katmanın belirli bir sorumluluğu bulunmaktadır. Her bir katman diğer katmanlardan bağımsız olmalı ve diğer katmanlarla yalnızca tanımladıkları arayüzlerle iletişime geçmelidir. Bu yapının modüler olmasını ve test edilebilmesini sağlamaktadır.

DI prensibi, yüksek seviye modüllerin düşük seviyeli modüllere dayanması yerine ikisinin de soyutlamaya dayanmasını amaçlar. Bu sistemin geri kalanını etkilemeden modüllerin kolayca değiştirilmesini sağlar.

Bu mimari üzerinde soyutlamalar arayüzlerle yapılmaktadır. Üst düzey modüller concrete implementasyonlar yerine bu arayüzlere dayanır.

clean-architecture-layers

Yukarıdaki görsel incelendiğinde bağımlılıkların dışarıdan içeriye olduğu görülmektedir. Bu katmanlar arasındaki iletişim arayüzlerle tek yönlü olarak sağlanmaktadır. Görsel doğrultusunda mimariyi oluşturan katmanlara sırasıyla göz atalım.

Domain Katmanı

Domain katmanı uygulama mantığından sorumludur. Bu katman başka herhangi bir katmana bağımlılık göstermemelidir. Uygulamanın temel veri yapıları olan entities, use cases ve business rules gibi aşağıdaki varlıklardan oluşmaktadır.

Entities

Uygulamanın temel veri modellerini temsil eder. Benzersiz bir kimliğe, durum ve davranışını tanımlayan bir dizi üyeye sahiptirler. Değerleri değişebilir; ancak kimliği değişmez.

public class Customer
{
    public int Id { get; set; }
    public string Name { get; set; }
    public string Email { get; set; }
    public decimal Balance { get; set; }
    
    public void AddTransaction(decimal amount)
    {
        Balance += amount;
    }
    
    public void UpdateEmail(string newEmail)
    {
        Email = newEmail;
    }
}

Yukarıdaki Customer sınıfı incelendiğinde Id, Name, Email ve Balance alanları varlığın mevcut durumunu (state) tanımlamaktadır. Ayrıca davranışını (behaviour) temsil eden AddTransaction ve UpdateEmail methodlarına da sahiptir.

Value Objects

Value objects kimliği olmayan ve nitelikleriyle bir değeri temsil eden varlıklardır. Kimlikleri olmadığından dolayı içerdiği değerlere göre karşılaştırılırlar. Immutable olduklarından bir kere oluşturulduğunda değiştirilemezler. Sistem genelinde paylaşılabilirler.

public class Money
{
    public decimal Amount { get; }
    public string Currency { get; }

    public Money(decimal amount, string currency)
    {
        Amount = amount;
        Currency = currency;
    }
}

Para miktarının yanında hangi para biriminin kullanıldığı da önemli olduğundan Customer sınıfının Balance alanı Money value object tipinde olabilir.

Aggregates

Aggregates domain içerisinde birbiriyle ilgili entity ve value object nesnelerinin kümelenerek bir arada kullanılmasıdır. İlgili varlıklar birlikte gruplandığından transaction bütünlüğünü sağlamada önemli role sahiptirler. İlgili varlıkları kümeleyip yönetimi sağlayan entity Aggregate Root olarak adlandırılmaktadır.

public class Order
{
    public int Id { get; set; }
    public DateTime OrderDate { get; set; }
    public decimal TotalAmount { get; set; }
    public List<OrderItem> Items { get; set; }

    public void AddItem(OrderItem item)
    {
        Items.Add(item);
        TotalAmount += item.Price * item.Quantity;
    }

    public void RemoveItem(OrderItem item)
    {
        Items.Remove(item);
        TotalAmount -= item.Price * item.Quantity;
    }
}

public class OrderItem
{
    public int Id { get; set; }
    public decimal Price { get; set; }
    public int Quantity { get; set; }
}

Bir Aggregate Root bir diğer AR ile iletişim kurabilirken; birbirlerinin içerisindeki varlıklarla dolaylı yoldan dahi olsa iletişim kuramaz veya referansını içeremez.

Repositories

Repository varlıkları veri kaynakları üzerinde CRUD işlemlerini gerçekleştirmeye yarayan bir tasarım desenidir. Uygulama servisleri tarafından kullanılmaktadır. Bu arayüzlerin implementasyonu Infrastructure katmanında yapılmaktadır. Ayrıca her AR kendi repository arayüzü üzerinden transaction bütünlüğünü sağlar.

public interface ICustomerRepository
{
    Customer? GetById(int id);
    void Add(Customer customer);
    void Update(Customer customer);
    void Remove(Customer customer);
}

Yukarıda Customer sınıfına ait CRUD işlemlerini gerçekleştiren bir repository yer almaktadır.

Ayrıca Domain katmanı kendi içerisinde bu katmana özel Enum ve Exception sınıflarını da içermektedir. Domain katmanını diğer katmanlardan ayırmak sistemin zaman içerisinde bakımını ve test edilmesini kolaylaştırmaktadır.

Application Katmanı

Application katmanı uygulamanın iş mantığından sorumludur. Presentation ile Domain katmanları arasında köprü görevi görmektedir. Uygulama ve external kaynakları tüketecek servisler Application katmanında tanımlanır. Bu katmanda yapılan soyutlamalar Infrastructure katmanında uygulanır. Yalnızca Domain katmanına bağımlılık göstermelidir.

Application Services

Uygulamanın desteklemesi beklenen özellikleri sağlayan servisleri barındırmaktadır. Presentation katmanından aldığı istekleri işleyerek uygun sonucu dönerler. Validation, Mapping gibi işlemler bu servislerin concrete implementasyonlarında yapılmaktadır.

public interface ICustomerService
{
    void CreateCustomer(string customerName, string password);
}

public class CustomerManager : ICustomerService
{
    private readonly ICustomerRepository _customerRepository;

    public CustomerService(ICustomerRepository customerRepository)
    {
        _customerRepository = customerRepository;
    }

    public void CreateCustomer(string customerName, string password)
    {
        // Validate input
        // Hash password
        // Create Customer
        _customerRepository.Add(customer);
    }
}

Yukarıda Customer sınıfına ilişkin bir takım işlemler gerçekleştiren ICustomerService ve implementasyonu yer almaktadır.

Interfaces

Interfaces dış kaynaklarla iletişime geçecek arayüzleri ve uygulama içerisinde kullanılacak System Clock gibi arayüzleri barındırmaktadır. Bu arayüzlerin implementasyonları Infrastructure katmanında yapılmaktadır.

public interface IPaymentService
{
    bool ProcessPayment(Order order);
}

Yukarıda alınan bir siparişe ilişkin ödeme işlemini gerçekleştiren IPaymentService yer almaktadır.

Models

Uygulama servisleri tarafından kullanılacak Request, Response ve DTO nesnelerini barındırmaktadır.

public class CustomerForView
{
    public int Id { get; set; }
    public string Name { get; set; }
    public string Email { get; set; }
}

public class CustomerForUpsert
{
    public string Name { get; set; }
    public string Email { get; set; }
    public decimal Balance { get; set; }
}

Application katmanı ayrıca bu katmana özel Exception ve Validation sınıflarını içerebilir. Domain ve Application katmanı birlikte Core katman olarak adlandırılmaktadır. Bu iki katmanla birlikte uygulama oluşmaktadır.

Infrastructure Katmanı

Infrastructure katmanı uygulamanın implementasyon detaylarından sorumludur. Veritabanı erişimi, dosya sistemleri ve harici servisler gibi bileşenleri içerir. Bu katman Persistence ve Infrastructure olmak üzere iki katmana ayrılabilmektedir. Persistence katmanında veritabanı; Infrastructure katmanındaysa external servis implementasyonları yapılmaktadır.

Persistence

Application katmanında oluşturulan uygulama servislerini uygulayan katmandır. Bu katmanda ayrıca veritabanıyla ilgili Configuration, Seed ve Migration gibi sınıflar da yer almaktadır.

public class CustomerRepository : ICustomerRepository
{
    private readonly DbContext _dbContext;

    public CustomerRepository(DbContext dbContext)
    {
        _dbContext = dbContext;
    }

    public Customer? GetById(int id)
    {
        return _dbContext.Set<Customer>().FirstOrDefault(c => c.Id == id);
    }

    public void Add(Customer customer)
    {
        _dbContext.Set<Customer>().Add(customer);
        _dbContext.SaveChanges();
    }

    public void Update(Customer customer)
    {
        _dbContext.Entry(customer).State = EntityState.Modified;
        _dbContext.SaveChanges();
    }

    public void Remove(Customer customer)
    {
        _dbContext.Set<Customer>().Remove(customer);
        _dbContext.SaveChanges();
    }
}

Yukarıda müşterilerle ilgili veritabanı işlemlerini gerçekleştirecek ICustomerRepository arayüzü implemente edilmiştir.

Geleneksel n-katmanlı mimarilerde veri erişim katmanı mimarinin merkezinde bulunmaktadır. Clean Architecture içerisinde mimarinin merkezinde Domain katmanı bulunduğundan ve Persistence katmanı dış katmanlardan bir tanesi olduğundan veritabanı bağımsızlığı sağlanmaktadır.

Infrastructure

Harici kaynaklarla iletişime geçecek arayüzleri uygulayan katmandır.

public class PaymentService : IPaymentService
{
    public bool ProcessPayment(Order order)
    {
        // Call payment API to process payment
    }
}

Yukarıda bir sipariş sonrasında ödemeyle ilgili işlemleri gerçekleştirecek IPaymentService arayüzü implemente edilmiştir.

Presentation Katmanı

Presentation katmanı uygulamanın kullanıcı arayüzünden sorumludur. Sunum katmanı bir SPA, Console, MVC veya API uygulaması olabilir. Bu katman mantıksal işlemler içermemelidir.

[ApiController, Route("api/[controller]")]
public class CustomersController : ControllerBase
{
    private readonly ICustomerService _customerService;

    public CustomersController(ICustomerService customerService)
    {
        _customerService = customerService;
    }

    [HttpGet("{id}")]
    public IActionResult GetById(int id)
    {
        var customer = _customerService.GetById(id);
        if (customer is null) return NotFound();
        return Ok(customer);
    }

    [HttpPost]
    public IActionResult Create(CustomerForUpsert customerForInsert)
    {
        var customer = _customerService.CreateCustomer(customerForInsert);
        return CreatedAtAction(nameof(GetById), new { id = customer.Id }, customer);
    }
}

Yukarıda ICustomerService uygulama servisini kullanan bir Web API projesinin CustomersController sınıfı görülmektedir.

Presentation katmanını diğer katmanlardan ayırarak kullanıcı arayüzü sistemin geri kalanını etkilemeden değiştirilebilir hale gelmektedir.

Görüldüğü üzere Clean Arhitecture uygulanarak bakımı, testi ve genişletilmesi kolay modüler bir yapı elde edilmektedir. Bu modülerlikle birlikte katmanlar bir diğerini etkilemeden kolaylıkla değiştirilebilmektedir.

Bir yanıt yazın

E-posta adresiniz yayınlanmayacak. Gerekli alanlar * ile işaretlenmişlerdir