Takip et

Veritabanı İstemcisini Değiştirdim, GraphQL Ekledim: Etki Alanı Asla Fark Etmedi

Yazılım dünyasında değişim kaçınılmazdır. Teknolojiler gelişir, iş gereksinimleri evrilir ve mimari kararlar zamanla güncellenme ihtiyacı duyar…

Veritabanı İstemcisini Değiştirdim, GraphQL Ekledim: Etki Alanı Asla Fark Etmedi

Yazılım dünyasında değişim kaçınılmazdır. Teknolojiler gelişir, iş gereksinimleri evrilir ve mimari kararlar zamanla güncellenme ihtiyacı duyar. Ancak bu değişimler, uygulamanın temel iş mantığını, yani “etki alanını” (domain) ne kadar etkilemeli? Bu makalede, veritabanı istemcisini değiştirmemize ve sistemimize GraphQL gibi yeni bir API katmanı eklememize rağmen, etki alanımızın bu değişiklikleri adeta “fark etmemesini” nasıl sağladığımızı, altyapıdan bağımsız bir etki alanı oluşturmanın temel prensiplerini ve pratik uygulamalarını ele alacağız.

Neden Altyapıdan Bağımsız Bir Etki Alanı İstiyoruz?

Modern yazılım mimarilerinde, uygulamanın temel iş kurallarını içeren etki alanının, veri tabanı, UI çerçeveleri veya harici API’ler gibi altyapı detaylarından izole edilmesi büyük önem taşır. Bu izolasyon, yazılımın uzun ömürlü, sürdürülebilir ve esnek olmasını sağlar.

Değişimin Maliyeti

Altyapı detaylarına sıkıca bağlı bir etki alanı, bir veritabanı teknolojisini değiştirmek veya farklı bir API protokolüne geçmek istediğinizde, iş mantığının da yeniden yazılması veya kapsamlı bir şekilde adapte edilmesi gerektiği anlamına gelir. Bu, hem zaman hem de kaynak açısından yüksek maliyetli bir süreçtir ve projenin ilerlemesini yavaşlatır.

Test Edilebilirlik

Etki alanı mantığının altyapıdan bağımsız olması, onu daha kolay test edilebilir kılar. Veritabanı bağlantısı kurmaya, harici servislere bağımlı olmaya veya UI katmanını ayağa kaldırmaya gerek kalmadan, saf iş kurallarını birim testleriyle (unit tests) hızlı ve güvenilir bir şekilde doğrulayabiliriz. Bu, hata ayıklama sürecini hızlandırır ve yazılım kalitesini artırır.

Esneklik ve Ölçeklenebilirlik

Altyapıdan bağımsız bir etki alanı, uygulamanızın farklı ortamlara veya ölçeklendirme stratejilerine daha kolay adapte olmasını sağlar. Örneğin, başlangıçta ilişkisel bir veritabanı kullanırken, daha sonra NoSQL bir çözüme geçmek veya sunucusuz (serverless) bir mimariye adapte olmak, etki alanı katmanını etkilemeden çok daha kolay hale gelir.

Mimari Yaklaşımlar: Temiz Kod ve Portlar/Adaptörler

Altyapıdan bağımsız bir etki alanı yaratmanın temelinde, iyi tanımlanmış mimari prensipler yatar. Bu prensipler, uygulamanın farklı katmanları arasındaki bağımlılıkları yöneterek, her katmanın kendi sorumluluklarını yerine getirmesini sağlar.

Temiz Mimari (Clean Architecture)

Robert C. Martin (Uncle Bob) tarafından popülerleştirilen Temiz Mimari, yazılımın farklı katmanlarını iç içe geçmiş daireler şeklinde düzenler. En içteki daire “Etki Alanı Varlıkları”nı (Entities) ve “Kullanım Durumlarını” (Use Cases) içerirken, dıştaki daireler veritabanları, UI ve harici servisler gibi altyapı detaylarını barındırır. Bu mimarinin temel kuralı, dış katmanların iç katmanlara bağımlı olması, ancak iç katmanların dış katmanlardan habersiz olmasıdır. Bu, “Bağımlılık Tersine Çevirme Prensibi”nin (Dependency Inversion Principle) bir uygulamasıdır.

Altıgen Mimari (Hexagonal Architecture / Ports & Adapters)

Alistair Cockburn tarafından önerilen Altıgen Mimari, uygulamanın çekirdeğini (etki alanı ve iş mantığı) bir “altıgen” olarak görselleştirir. Bu altıgen, dış dünyayla “portlar” (arayüzler) aracılığıyla etkileşime girer. Dış dünyadan gelen her türlü girdi (kullanıcı arayüzü, API çağrıları, veritabanı olayları) veya dış dünyaya yapılan her türlü çıktı (veritabanı yazma, e-posta gönderme) bu portlara bağlanan “adaptörler” aracılığıyla gerçekleşir. Böylece, altıgenin içi (etki alanı) dış dünyadan tamamen izole edilmiş olur.

Katmanlı Mimarinin Ötesinde

Geleneksel katmanlı mimariler (örneğin, sunum, iş mantığı, veri erişim katmanları) bazen katmanlar arası sıkı bağımlılıklara yol açabilir. Temiz ve Altıgen mimariler, bu bağımlılıkları daha esnek ve yönetilebilir bir şekilde ele alarak, etki alanının merkezde kalmasını ve altyapı detaylarının değiştirilebilir adaptörler olarak davranmasını sağlar.

Veritabanı İstemcisini Değiştirmek: Bir Adaptör Değişimi

Veritabanı istemcisini değiştirmek, genellikle bir uygulamanın en riskli ve maliyetli değişikliklerinden biri olarak görülür. Ancak doğru mimari yaklaşımla, bu işlem sadece bir “adaptör” değişimi haline gelir.

Veritabanı Soyutlama Katmanı

Etki alanı katmanı, doğrudan veritabanı teknolojisiyle (SQL, NoSQL, ORM kütüphanesi vb.) etkileşime girmez. Bunun yerine, etki alanı ihtiyaç duyduğu veri operasyonlarını soyutlayan arayüzler (portlar) tanımlar. Örneğin, bir UserRepository arayüzü, kullanıcıları kaydetme, bulma veya güncelleme gibi işlemleri tanımlar.


// Etki Alanı Katmanında Tanımlanan Port (Arayüz)
namespace MyDomain.Ports
{
    public interface IUserRepository
    {
        User GetById(Guid id);
        void Save(User user);
        void Delete(User user);
        IEnumerable GetAll();
    }
}

// Etki Alanı Varlığı
namespace MyDomain.Entities
{
    public class User
    {
        public Guid Id { get; private set; }
        public string Name { get; private set; }
        public string Email { get; private set; }

        public User(Guid id, string name, string email)
        {
            Id = id;
            Name = name;
            Email = email;
        }

        public void UpdateEmail(string newEmail)
        {
            // İş kuralı: Email formatı kontrolü vb.
            Email = newEmail;
        }
    }
}

Repository Deseni

Repository deseni, etki alanı ile veri kalıcılık katmanı arasında bir köprü görevi görür. Etki alanı, verileri bir koleksiyon gibi ele alır ve Repository'den veri eklemesini, çıkarmasını veya sorgulamasını ister. Repository'nin iç implementasyonu (örneğin, Entity Framework, Dapper, MongoDB sürücüsü) etki alanını ilgilendirmez.

Başlangıçta bir SQL veritabanı için Entity Framework kullanan bir implementasyonunuz olabilir:


// Altyapı Katmanında Tanımlanan Adaptör (SQL için)
namespace MyInfrastructure.Sql
{
    public class SqlUserRepository : IUserRepository
    {
        private readonly AppDbContext _context;

        public SqlUserRepository(AppDbContext context)
        {
            _context = context;
        }

        public User GetById(Guid id) => _context.Users.Find(id);
        public void Save(User user) => _context.Users.Add(user); // veya Update
        public void Delete(User user) => _context.Users.Remove(user);
        public IEnumerable GetAll() => _context.Users.ToList();
    }
}

Daha sonra bir NoSQL veritabanına (örneğin, MongoDB) geçmek istediğinizde, sadece yeni bir adaptör yazarsınız ve Dependency Injection (Bağımlılık Enjeksiyonu) mekanizması aracılığıyla eski adaptör yerine yenisini bağlarsınız. Etki alanı katmanındaki kodunuzda hiçbir değişiklik yapmanıza gerek kalmaz.


// Altyapı Katmanında Tanımlanan Adaptör (MongoDB için)
namespace MyInfrastructure.Mongo
{
    public class MongoUserRepository : IUserRepository
    {
        private readonly IMongoCollection _users;

        public MongoUserRepository(IMongoDatabase database)
        {
            _users = database.GetCollection("Users");
        }

        public User GetById(Guid id)
        {
            var doc = _users.Find(d => d.Id == id).FirstOrDefault();
            return doc != null ? new User(doc.Id, doc.Name, doc.Email) : null;
        }
        // ... diğer metodlar
    }

    // MongoDB'ye özel bir doküman sınıfı
    public class UserDocument
    {
        public Guid Id { get; set; }
        public string Name { get; set; }
        public string Email { get; set; }
    }
}

Farklı ORM'ler veya Düşük Seviyeli Kütüphaneler Arası Geçiş

Bu yaklaşım, aynı veritabanı türü içinde farklı ORM'ler veya düşük seviyeli kütüphaneler arasında geçiş yaparken de geçerlidir. Örneğin, Entity Framework'ten Dapper'a geçiş yapmak istediğinizde, sadece SqlUserRepository implementasyonunu değiştirmeniz yeterlidir. Etki alanı bundan haberdar olmaz.

GraphQL'i Entegre Etmek: Yeni Bir Sunum Katmanı Adaptörü

GraphQL, REST'e modern bir alternatif olarak ortaya çıkan güçlü bir API sorgulama dilidir. Mevcut bir REST API'ye sahip bir sisteme GraphQL eklemek, doğru mimariyle, veritabanı istemcisini değiştirmek kadar sorunsuz olabilir.

GraphQL Şeması ve Çözücüler (Resolvers)

GraphQL'de, istemcilerin sorgulayabileceği verilerin yapısını tanımlayan bir şema oluşturursunuz. Bu şema, etki alanı varlıklarınızı ve kullanım durumlarınızı yansıtır. Şemadaki her alan için, veriyi nasıl alacağını bilen bir "çözücü" (resolver) tanımlarsınız. Bu çözücüler, doğrudan etki alanı katmanınızdaki kullanım durumlarını veya servisleri çağırır.


# GraphQL Şeması
type User {
  id: ID!
  name: String!
  email: String!
}

type Query {
  user(id: ID!): User
  users: [User!]!
}

Uygulama Katmanı ile Etkileşim

GraphQL çözücüleri, genellikle bir "Uygulama Katmanı" (Application Layer) veya "Servis Katmanı" (Service Layer) aracılığıyla etki alanı katmanıyla etkileşime girer. Bu katman, etki alanı kullanım durumlarını koordine eder ve dış dünyaya sunulacak verileri hazırlar. Böylece, GraphQL adaptörü, etki alanı varlıklarını doğrudan manipüle etmek yerine, etki alanının tanımladığı arayüzler üzerinden işlemlerini gerçekleştirir.


// Uygulama Katmanı Servisi (Use Case)
namespace MyApplication.Services
{
    public class UserService
    {
        private readonly IUserRepository _userRepository;

        public UserService(IUserRepository userRepository)
        {
            _userRepository = userRepository;
        }

        public UserDto GetUserById(Guid id)
        {
            var user = _userRepository.GetById(id);
            return user != null ? new UserDto(user.Id, user.Name, user.Email) : null;
        }

        public IEnumerable GetAllUsers()
        {
            return _userRepository.GetAll()
                                  .Select(u => new UserDto(u.Id, u.Name, u.Email));
        }

        // ... diğer iş mantığı metodları
    }

    public record UserDto(Guid Id, string Name, string Email);
}

// GraphQL Çözücüsü (Adaptör)
namespace MyPresentation.GraphQL
{
    public class UserQuery
    {
        private readonly UserService _userService;

        public UserQuery(UserService userService)
        {
            _userService = userService;
        }

        public UserDto GetUser(Guid id) => _userService.GetUserById(id);
        public IEnumerable GetUsers() => _userService.GetAllUsers();
    }
}

REST'ten GraphQL'e Geçişin Kolaylığı

Bu yapı sayesinde, uygulamanızda zaten var olan bir REST API'ye ek olarak veya onun yerine GraphQL eklemek, sadece yeni bir sunum katmanı adaptörü yazmak anlamına gelir. Etki alanı ve uygulama katmanları, bu yeni API katmanının varlığından tamamen habersiz kalır. Her iki API de aynı temel iş mantığını ve veri erişim katmanlarını kullanabilir.

Bağımlılık Tersine Çevirme Prensibi (Dependency Inversion Principle)

Bu mimari yaklaşımların temelinde yatan en önemli prensiplerden biri, Bağımlılık Tersine Çevirme Prensibi'dir (DIP). DIP, "üst seviye modüllerin alt seviye modüllere bağımlı olmaması gerektiğini, her ikisinin de soyutlamalara bağımlı olması gerektiğini" ve "soyutlamaların detaylara bağımlı olmaması gerektiğini, detayların soyutlamalara bağımlı olması gerektiğini" belirtir.

Soyutlamalara Bağımlılık

Etki alanı katmanımız, somut veritabanı implementasyonlarına veya API çerçevelerine değil, IUserRepository gibi arayüzlere (soyutlamalara) bağımlıdır. Bu, etki alanının, altyapı detaylarının değişmesinden etkilenmemesini sağlar.

Arayüzlerin Rolü

Arayüzler, etki alanı ile dış dünya arasında bir "sözleşme" görevi görür. Etki alanı, neye ihtiyacı olduğunu bu arayüzler aracılığıyla belirtir. Altyapı katmanı ise bu arayüzleri implemente ederek, etki alanının ihtiyaç duyduğu işlevselliği sağlar.

Uygulama Katmanının Önemi

Uygulama katmanı, etki alanı ile sunum katmanı arasındaki köprüdür. Etki alanı varlıklarını doğrudan dışarıya açmak yerine, DTO'lar (Data Transfer Objects) aracılığıyla veri transferi yapar. Bu, etki alanı varlıklarının kendi iç tutarlılıklarını korumasını ve dış dünyanın manipülasyonundan korunmasını sağlar.

Pratikte Uygulama ve Karşılaşılan Zorluklar

Bu mimari prensipleri uygulamak, teoride kulağa hoş gelse de pratikte bazı zorlukları beraberinde getirebilir.

Başlangıç Maliyeti

Temiz veya Altıgen Mimari gibi yaklaşımlar, başlangıçta daha fazla soyutlama ve daha fazla dosya/klasör yapısı gerektirdiğinden, ilk kurulum maliyeti daha yüksek olabilir. Ancak bu maliyet, uzun vadede daha düşük bakım ve değişim maliyetleriyle fazlasıyla telafi edilir.

Ekip Eğitimi

Geleneksel katmanlı mimarilere alışkın ekipler için bu yeni yaklaşımları benimsemek zaman alabilir. Ekip üyelerinin Bağımlılık Tersine Çevirme, Portlar ve Adaptörler gibi kavramları anlaması ve uygulaması için eğitim ve mentorluk gerekebilir.

Karmaşıklık Yönetimi

Çok fazla soyutlama ve arayüz, projenin ilk aşamalarında gereksiz karmaşıklık gibi görünebilir. Doğru dengeyi bulmak önemlidir. Her küçük detay için bir arayüz tanımlamak yerine, değişim potansiyeli yüksek olan veya farklı implementasyonlara sahip olabilecek alanlara odaklanmak daha akıllıca olacaktır.

Elde Edilen Faydalar ve Gelecek

Bu mimari prensipleri benimsemenin getirdiği faydalar, başlangıçtaki zorlukların üstesinden gelmeye değerdir.

Daha Hızlı Geliştirme

Etki alanı, altyapıdan bağımsız olduğu için, farklı ekipler farklı katmanlar üzerinde paralel olarak çalışabilir. Örneğin, bir ekip UI ve GraphQL adaptörü üzerinde çalışırken, başka bir ekip veritabanı adaptörünü geliştirebilir. Etki alanı katmanı ise tüm bu değişikliklerden izole bir şekilde ilerleyebilir.

Daha Az Risk

Altyapı değişiklikleri artık tüm sistemi baştan aşağı etkileme riskini taşımaz. Yeni bir veritabanı veya API teknolojisi denemek, sadece ilgili adaptörü değiştirmekle sınırlı kalır, bu da hata riskini önemli ölçüde azaltır.

Teknolojiden Bağımsızlık

Uygulamanız, belirli bir teknoloji yığınına (stack) sıkıca bağlı kalmaz. Bu, gelecekte daha iyi veya daha uygun bir teknoloji ortaya çıktığında, mevcut sistemi büyük ölçüde yeniden yazmaya gerek kalmadan adapte olabilmenizi sağlar. İş mantığınız, zamanın testine dayanıklı hale gelir.

Sonuç

Veritabanı istemcisini değiştirmek veya GraphQL gibi yeni bir API katmanı eklemek, doğru mimari prensipler uygulandığında, uygulamanın temel iş mantığını, yani etki alanını asla etkilememelidir. Temiz Mimari, Altıgen Mimari ve Bağımlılık Tersine Çevirme Prensibi gibi yaklaşımlar, etki alanını altyapı detaylarından izole ederek, yazılımın daha esnek, test edilebilir ve sürdürülebilir olmasını sağlar. Bu, yazılım geliştirmenin kaçınılmaz bir parçası olan değişimi yönetmek için güçlü bir stratejidir ve uzun vadede projelerinizin başarısı için kritik öneme sahiptir.

SSS (Sık Sorulan Sorular)

Bu mimari yaklaşım her proje için uygun mudur?

Küçük ve basit projeler için başlangıçtaki ek maliyet nedeniyle aşırı mühendislik gibi görünebilir. Ancak orta ve büyük ölçekli, uzun ömürlü olması beklenen, değişim potansiyeli yüksek projeler için kesinlikle tavsiye edilir. Özellikle ekip büyüdükçe ve farklı uzmanlık alanlarına sahip geliştiriciler bir araya geldikçe faydaları daha belirgin hale gelir.

"Etki Alanı Asla Fark Etmedi" derken ne kastediliyor?

Bu ifade, etki alanı katmanındaki kodun, yapılan altyapı değişikliklerinden (veritabanı, API tipi vb.) habersiz olduğu ve bu değişiklikler nedeniyle etki alanı kodunda hiçbir değişikliğe gerek kalmadığı anlamına gelir. Etki alanı, yalnızca soyut arayüzlerle (portlarla) etkileşime girer ve bu arayüzlerin arkasındaki somut implementasyonların değişmesi onu etkilemez.

Bu mimaride performans endişeleri olabilir mi?

Ekstra soyutlama katmanları, teorik olarak çok küçük bir performans yükü getirebilir. Ancak modern donanım ve yazılım optimizasyonları sayesinde, bu fark çoğu zaman ihmal edilebilir düzeydedir. Performans kritik uygulamalarda bile, iyi tasarlanmış soyutlamalarla bu sorunların üstesinden gelinebilir. Genellikle, elde edilen sürdürülebilirlik ve esneklik faydaları, potansiyel mikro-performans kayıplarından çok daha ağır basar.

GraphQL'i eklemek, mevcut REST API'yi kaldırmam gerektiği anlamına mı gelir?

Hayır, kesinlikle değil. Bu mimariyle, GraphQL'i mevcut REST API'nizin yanına yeni bir sunum adaptörü olarak ekleyebilirsiniz. Her iki API de aynı temel uygulama ve etki alanı katmanlarını kullanabilir. Bu, istemcilerinize kademeli bir geçiş yolu sunar veya farklı ihtiyaçlara sahip istemciler için her iki seçeneği de sunmanıza olanak tanır.

Yorumlar
İçeriği beğendiniz mi? Bir tartışma başlatın veya görüşlerinizi paylaşın.
Yorum Yaz

Bir yanıt yazın

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

E-posta Bülteni
Yazılım Topluluğuna Katılın
En son güncellemeleri, yaratıcı ipuçlarını ve özel kaynakları doğrudan e-posta kutunuza alın. Tasarım ve inovasyonun geleceğini birlikte keşfedelim.