C# İle Row Data Gateway Design Pattern(Row Data Gateway Tasarım Deseni)
Merhaba,
Bu içeriğimizde Veri Katmanı Modelleme Tasarım Desenleri‘nden olan Row Data Gateway pattern’ını hem teorik hem de pratik olarak inceliyor olacağız.
Row Data Gateway Design Pattern Nedir?
Row Data Gateway, bir veri kaynağındaki tek bir kayda/satıra/veriye geçit görevi gören/karşılık gelen nesnedir.
Row Data Gateway, tamamen veritabanındaki kaydın/verinin nesneye map edilmiş halidir. İhtiyaç duyulan veri her ne ise, o veriye erişimle ilgili arka planda olan tüm operasyonlar geliştiriciden soyutlanarak Row Data Gateway sınıfı aracılığıyla gerçekleştirilir. Böylece geliştirici direkt olarak iş mantığını veritabanı sorgularına vs. bulaşmaksızın Row Data Gateway nesnesi üzerinden yürütebilecektir ve direkt olarak istediği veriyi elde edip çalışmalarına devam edebilecektir.
Row Data Gateway ile Table Data Gateway Arasındaki Farklar Nelerdir?
Birkaç makale öncesinde Table Data Gateway‘e dair bir inceleme içeriği klavyeye almıştık. Row Data Gateway pattern’ı deyince aklınıza doğal olarak Table Data Gateway pattern’ı gelmiş olabilir. Nihayetinde her ikisi de Veri Katmanı Modelleme desenlerinden olduğu için teoride benzerlikler kaçınılmazdır. Hal böyleyken peki bu iki pattern arasındaki farklar nelerdir? sorunuzu duyar gibiyim… Gelin şimdi ikisini de mukayese ederek farklarını ortaya koyalım:
Table Data Gateway | Row Data Gateway |
---|---|
Row Data Gateway, tablodaki bir satıra erişimi kapsüllerken, Table Data Gateway ise tablo düzeyinde erişimi kapsüllemektedir. | |
Table Data Gateway; geriye SqlDataReader gibi, DTO nesnesi gibi hem koleksiyonel hem de tekil nesnel yapılar döndürürken, Row Data Gateway ise satır başına bir gateway sınıfı döndürmektedir. Bunun için bir Finder sınıfına ihtiyaç vardır. | |
Her ikisi de Transaction Script pattern’ın kullanıldığı yerlerde daha elverişli olacağı için ilgili pattern’a karşılık tercih edilmesi önerilir. | |
Table Data Gateway, Row Data Gateway’e nazaran uygulama açısından daha basit ve sadedir. | |
Table Data Gateway, genellikle bir tablo ve bir sınıf olmak üzere tek bir tabloya veya view’e(join sorguları da dahil) erişmek için kullanılan tüm SQL’i sağlamaktadır. Uygulamanın diğer parçaları veritabanıyla etkileşim kurmak istediği taktirde Table Data Gateway’i kullanır. |
Row Data Gateway İşlevselliği Nasıldır?
Row Data Gateway’de sadece veriye erişim söz konusudur. Sorgulama sürecinde büyükse/küçükse gibi condinationlara yer verilmemelidir. Oldukça basit bir pattern olduğu için küçük veya orta ölçekli projelerde kullanılmaya özen gösterilebilir.
İşlevsel olarak Row Data Gateway, daha çok domain katmanındaki nesneler ile veritabanındaki veriler arasındaki iletişimi sağlayan bir tampon bölge olarak düşünülebilir. Böylece veritabanı yapısı yahut sorgu mantığı değiştiği taktirde bu pattern sayesinde business logic’de herhangi bir değişiklik yapmamıza gerek kalmaksızın doğal yalıtım sergilenebilmektedir.
Row Data Gateway içerisinde tablodaki sütunlara karşılık propertyleri ve sql cümleciklerini(sorguları) barındıran fonksiyonları barındırmaktadır. Misal olarak; yandaki diyagrama göz atarsanız eğer ‘Person Gateway’ sınıfı içerisinde hem bir satırına karşılık geldiği tablodaki sütunları(lastname, firstname, numberOfDependents) hem de bir kayıt eklemek, güncellemek vs. gibi işlemler için ise metotları(insert, update) barındırmaktadır. Ve dikkat ederseniz eğer bir ‘Person Gateway’ oluşturabilmek için önceki satırlarda da bahsedildiği gibi ‘Finder’ sınıfından istifade etmekteyiz.
Row Data Gateway Pattern’ı Örneklendirelim
Row Data Gateway pattern bahsi geçtiği üzere oldukça basit bir pattern’dır. Haliyle örneklendirmesi de bir o kadar basit olacaktır. Şöyle ki;
Herşeyden önce veritabanı sorumluluğunu üstlenecek bir sınıf inşa ederek başlayalım.
public static class Database { static SqlConnection _connection; static Database() { object _lock = new(); lock (_lock) _connection = new("Server=localhost, 1433;Database=RowDataGatewayDB;User Id=sa;Password=1q2w3e4r+!"); _connection.Open(); } public static async Task<int> ExecuteNonQueryAsync(string query, params SqlParameter[] parameters) { SqlCommand command = new(query, _connection); command.Parameters.AddRange(parameters); return await command.ExecuteNonQueryAsync(); } public static async Task<SqlDataReader> ExecuteReaderAsync(string query, params SqlParameter[] parameters) { SqlCommand command = new(query, _connection); command.Parameters.AddRange(parameters); return await command.ExecuteReaderAsync(); } }
Ardından ‘PersonGateway’ sınıfını oluşturalım.
public class PersonGateway { public PersonGateway() { } public PersonGateway(int id, string name, string surname) { Id = id; Name = name; Surname = surname; } public int Id { get; set; } public string Name { get; set; } public string Surname { get; set; } public async Task AddPerson() => await AddPerson(Name, Surname); public async Task AddPerson(string name, string surname) => await Database.ExecuteNonQueryAsync("INSERT Persons(Name, Surname) VALUES(@name, @surname)" , new SqlParameter("name", name) , new SqlParameter("surname", surname)); public async Task RemoveCustomer() => await RemoveCustomer(Id); public async Task RemoveCustomer(int id) => await Database.ExecuteNonQueryAsync("DELETE FROM Persons WHERE Id = @id" , new SqlParameter("id", id)); public async Task UpdateCustomer() => await UpdateCustomer(Id, Name, Surname); public async Task UpdateCustomer(int id, string name, string surname) => await Database.ExecuteNonQueryAsync("UPDATE Persons SET Name = @name, Surname = @surname WHERE Id = @id" , new SqlParameter("id", id) , new SqlParameter("name", name) , new SqlParameter("surname", surname)); }
Görüldüğü üzere bu bizim tablodaki bir satıra karşılık gelen sınıfımızın ta kendisidir. Ve içerisinde tablodaki kolonlara karşılık gelen property’ler ve ayrıca person ekleme, silme ve güncelleme ile ilgili sorumlulukları üstlenen metotlar barındırmaktadır. Bu sınıfın zengin olması operasyonel açıdan işimizi kolaylaştıracaktır. Örneği incelerseniz eğer tüm metotların parametreli ve parametresiz overload’larını görmektesiniz. Parametreli metotlar direkt olarak işlem yapacakları verileri elde ederken, parametresizler ise kendilerinden(kendi instance’larındaki property’lerinden) edinmektedir. Bu iki farklı tarzın farkını kullanımda görüyor olacağız.
Şimdi ise sorgulama neticesinde ‘Person Gateway’ nesnesini elde etmemizi sağlayacak ‘Finder’ sınıfını oluşturalım.
public class PersonFinder { public PersonFinder() { } readonly PersonGateway _personGateway; public PersonFinder(PersonGateway personGateway) => _personGateway = personGateway; public async Task<PersonGateway> GetPersonById() => await GetPersonById(_personGateway.Id); public async Task<PersonGateway> GetPersonById(int id) { SqlDataReader dataReader = await Database.ExecuteReaderAsync("SELECT * FROM Persons WHERE Id = @id" , new SqlParameter("id", id)); await dataReader.ReadAsync(); PersonGateway person = new(int.Parse(dataReader["id"].ToString()), dataReader["name"].ToString(), dataReader["surname"].ToString()); await dataReader.CloseAsync(); await dataReader.DisposeAsync(); return person; } public async Task<List<PersonGateway>> GetPersonByName() => await GetPersonByName(_personGateway.Name); public async Task<List<PersonGateway>> GetPersonByName(string name) { SqlDataReader dataReader = await Database.ExecuteReaderAsync("SELECT * FROM Persons WHERE Name = @name" , new SqlParameter("name", name)); List<PersonGateway> persons = new(); while (await dataReader.ReadAsync()) persons.Add(new(int.Parse(dataReader["id"].ToString()), dataReader["name"].ToString(), dataReader["surname"].ToString())); await dataReader.CloseAsync(); await dataReader.DisposeAsync(); return persons; } public async Task<List<PersonGateway>> GetPersonBySurname() => await GetPersonBySurname(_personGateway.Surname); public async Task<List<PersonGateway>> GetPersonBySurname(string surname) { SqlDataReader dataReader = await Database.ExecuteReaderAsync("SELECT * FROM Persons WHERE Surname = @surname" , new SqlParameter("surname", surname)); List<PersonGateway> persons = new(); while (await dataReader.ReadAsync()) persons.Add(new(int.Parse(dataReader["id"].ToString()), dataReader["name"].ToString(), dataReader["surname"].ToString())); await dataReader.CloseAsync(); await dataReader.DisposeAsync(); return persons; } public async Task<List<PersonGateway>> GetAllPersons() { SqlDataReader dataReader = await Database.ExecuteReaderAsync("SELECT * FROM Persons"); List<PersonGateway> persons = new(); while (await dataReader.ReadAsync()) persons.Add(new(int.Parse(dataReader["id"].ToString()), dataReader["name"].ToString(), dataReader["surname"].ToString())); await dataReader.CloseAsync(); await dataReader.DisposeAsync(); return persons; } }
Bu sınıfı da incelerseniz eğer içerisinde parametreli ve parametresiz metotlar barındırmaktadır. Buradaki amaç yine ilgili sınıfın işlevselliğini arttırmak için zenginleştirmektir. Dikkat ederseniz parametre alan metotlar parametrelerinden gelecek olan değerlerle beslenirken, almayanlar ise constructor üzerinden edinilmiş değerler üzerinden sorgulamalarını gerçekleştirecektirler.
İşte bu kadar 🙂
Şimdi bu tasarımı kullanabilmek için;
PersonGateway eklenecekPerson = new(); eklenecekPerson.Name = "Nevin"; eklenecekPerson.Surname = "Yılmaz"; await eklenecekPerson.AddPerson(); await eklenecekPerson.AddPerson("Gençay", "Yıldız"); PersonGateway guncellenecekPerson = new(); guncellenecekPerson.Id = 101; guncellenecekPerson.Name = "Nevin"; guncellenecekPerson.Surname = "Yıldız"; await guncellenecekPerson.UpdateCustomer(); await guncellenecekPerson.UpdateCustomer(102, "Kürşad", "Yıldız"); PersonGateway silinecekPerson = new(); silinecekPerson.Id = 3; await silinecekPerson.RemoveCustomer(); await silinecekPerson.RemoveCustomer(4); PersonGateway arananPerson = new() { Id = 10 }; PersonFinder finder = new(arananPerson); var person1 = await finder.GetPersonById(); var person2 = await finder.GetPersonById(10);
şeklinde bir çalışma yapmamız yeterli olacaktır. Görüldüğü üzere elimizdeki ‘Person Gateway’ nesnesi üzerindeki değerlerde de işlemler gerçekleştirebildiğimiz gibi direkt parametreler üzerinden gelen değerlerle de çalışabilmekteyiz.
Eğer içeriğin bu noktasına kadar geldiyseniz;
La hoca! Sen Row Data Gateway’in bir kaydı işlediğini lakin Table Data Gateway’in ise tüm tabloyu işlediğini söylemiştin. Halbuki bu iki pattern’ın birbirine çok benzediği görülüyor ve özellikle her ikisi de tek veya çok sayıda kayıt döndürecek SQL sorgularını kapsıyor! Ne zaman hangisini kullanmam gerektiğini tam olarak anlamış değilim! Hatta internetten baktığım kadarıyla Martin Fowler dayının bile bir kayıt istendiği zaman Row Data Gateway’i kullanabileceğimizi söylemesi üzerine bu içeriğimizde Row Data Gateway’de birden fazla kayıt için işlem yapabildiğimizi görüyoruz. Öyleyse Table Data Gateway çöp değil mi? Ya da sen de bi yanlış mı var emmi? dediğinizi duyar gibiyim 🙂
Evet, bu dediğinizde sonuna kadar haklısınız. Nihayetinde her iki pattern’ın teorideki farklarını pratikte yansıtmamak kafanızın karışmasına sebebiyet verebilir. Bu durumda aradaki farkı yaratabilmek için özellikle odaklanılması gereken noktaları vurgulayalım istiyorum. Unutmayın ki; Table Data Gateway SqlDataReader yahut DTO tarzı nesneler döndürür. Lakin Row Data Gateway ise satır başına bir Gateway sınıfı döndürür.
Bu mantıkla Row Data Gateway’i aşağıdaki iki yaklaşım eşliğinde kullanabileceğinizi unutmayınız;
- Domain Model Object İçin Veri Sahibi Olarak Row Data Gateway Kullanımı
Domain model object içerisinde Row Data Gateway’in referans edilerek kullanılmasını sağlar. Bir nevi domain model object tarafından Row Data Gateway’in yetkilendirilmesi ve sorumluluğunun üstlendirilmesidir. Yandaki görsel şemaya bakarsanız eğer ‘Student’ nesnesi domain model object özelliklerinin hiçbirine sahip olmaksızın sadece ‘StudentGateway’ sınıfını referans ederek işlem görmektedir. Bu hem pek tercih edeceğimiz bir yaklaşım olmayacağı gibi hem de Gateway sınıfının bulunduğu katmanın ‘Domain Layer’ tarafından referans edilmesini sağlayacağından dolayı da tavsiye etmeyeceğimiz bir yöntemdir. - Domain Model Object’ten Kurtulmak İçin Row Data Gateway Kullanımı
Bu yöntemde ise domain model object’i komple devre dışı bırakarak, Row Data Gateway nesnelerini doğrudan manipüle etmeyi tercih ediyoruz. Böylece bu yöntem ile iyi bir örneklendirilebilirlik sağlamış olmasak da hiç yoktan ilk yaklaşıma nazaran daha ideal bir davranış ortaya koymuş oluyoruz.
İlgilenenlerin faydalanması dileğiyle…
Sonraki yazılarımda görüşmek üzere…
İyi çalışmalar…
Not : Örnek uygulamanın kaynak kodlarına aşağıdaki github adresinden erişebilirsiniz.
https://github.com/gncyyldz/Row_Data_Gateway_Design_Pattern