Transaction Yönetiminde C# İle OOP Yaklaşımı
Selamlar,
Bugün iş hayatında en kritik konuların başında gelen, birbirine bağımlı, çok adımlı işlemler yapılırken bir işlemin bitiminden sonra çalıştırılması gereken bir sonraki işlemin nasıl yönetilebileceğini, ayrıca oluşabilecek hata durumlarında o ana kadar yapılmış tüm işlemlerin nasıl geri alınabileceğini hep beraber inceleyeceğiz. Son olarak yapılan işlemler için notify mekanizmasını, var olan bu sistem üzerine nasıl oturtabileceğimizi hep beraber tartışacağız.
Gerçek hayattan örnek vermek gerekir ise bankalarda kullanılan para transferi, birçok firmada kullanılan döküman yönetimi ve son örnek olarak online sinema veya maç bileti alımı işlemlerini verebiliriz. Eğer tüm adımlar başarılı bir şekilde tamamlanmaz ise o ana kadar yapılan tüm işlemlerin geri alınması ve ayrıca geçilen her adımın kullanıcıya bildirilmesi gerekmektedir.
Birbirini takip eden işlemler için Chain of Responsibility design pattern tercih edilebilecek yaklaşımlardan biridir. Burada sadece ileri yönlü hareket yerine geriye dönüş sistemi de hesaba katılmalıdır. Bu örnekde birbirini takip eden bir takım para transfer işlemleri yapılacaktır. Bankaların işlem adımlarında, çeşitli birimlerin bilgilendirilmesi için Observer design pattern kullanılacaktır.
Chain of Responsibility Yapısı: Dikkat edilirse bu pattern sadece ileri yönlüdür. Ben bu yapıya bir de olumsuz durumlarda geriye dönülecek BackHandler tanımladım. Bu tamamen benim yaklaşımımdır. Chain of Responsibility’yi tanımlayabilecek benim en çok aklımda kalan örnek, yiyecek veya kahve otomatlarıdır.
Yapılacak transaction işlemler için ortak görülen “IOperator” interface’i aşağıdaki gibi tanımlanmıştır.
1 2 3 4 |
internal interface IOperator { void Notify(NotifyCenter nCenter,string message); } |
NotifyCenter: Bildirim yani notify tipleri Enum olarak aşağıdaki gibi tanımlanmıştır. Kısaca haber verilecek birimler burada tanımlanmıştır.
1 2 3 4 5 6 7 |
enum NotifyCenter { BTK, BDDK, BDDK2, BTK2 } |
TransactionStage: Transaction yani yapılacak işlem adımları gene Enum şeklinde aşağıdaki gibi tanımlanmıştır.
1 2 3 4 5 6 7 |
enum TransactionStage { Transaction1, Transaction2, Transaction3, Transaction4 } |
TransactionInfo: Transactin özelliklerinin tutulduğu sınıftır.
1 2 3 4 5 |
class TransactionInfo { public string Name { get; set; } public TransactionStage Stage { get; set; } } |
Observer Tasarım Kalbı Yapısı: En akılda kalıcı örneklerden biri borsada tahvil almış tüm müşterilerin, fiyat değişikliği durumunda yani property set durumunda müşterilerin ilgili değişiklikten dolayı bilgilendirilmesi işlemidir.
TransactionHandler abstarct sınıfı aşağıdaki gibi “IOperator” sınıfından türetilmiştir. Transaction sınıfına ait özellikler burada tanımlanır.
- “NotfiyType” ile uyarı işlemi yapılacak merkez belirlenir.
- “Notify()” methodu ile ilgili kuruma haber verme işlemi tanımlanır.
- “isFirst” ilk başlanacak transaction belirlenmiş, böylece bir hata durumunda geri dönülen transaction işlemindeki son nokta tanımlanmıştır.
- “NextTransaction()” methodu ile bu transaction’da yapılan işlemin olumlu bir şekilde sonuçlanması durumunda bir sonraki çağrılacak işlem burada tanımlanır.
- “BeforeTransaction()” methodu ile bu transaction’da yapılan işlemde çıkabilecek bir hata durumunda, geri dönülmesi gereken bir önceki işlem burada tanımlanır.
- “ProcessPreviewTransaction()” methodunda hatalı bir durum ile karşılaşıldığında işlemlerin geri alınması için çağrılan bir methoddur. Burada işlemin geri alınacağı mesajı ve “Notify()” işlemi yapılır. Son olarak ilk transaction’a gelinmemiş ise bir önceki transaction’a ait “ProcessPreviewTransaction()” methodu çağrılır.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 |
abstract class TransactionHandler:IOperator { public NotifyCenter NotifyType { get; set; } public void Notify(NotifyCenter nCenter,string message) { Console.WriteLine($"{message} : [{nCenter}]"); } public bool isFirst { get; set; } protected TransactionHandler nextTransaction; protected TransactionHandler beforeTransaction; public TransactionHandler NextTransaction { set { this.nextTransaction = value; } } public TransactionHandler BeforeTransaction { set { this.beforeTransaction = value; } } public abstract void ProcessTransaction(TransactionInfo sInfo); public void ProcessPreviewTransaction() { //Do Something By sInfo Console.WriteLine("RollBack " + this.GetType().ToString()); Notify(this.NotifyType, "İşlem Geri Alındı"); if (!this.isFirst) beforeTransaction.ProcessPreviewTransaction(); } } |
Transaction 1-2-3-4 aşağıda görüldüğü gibi tanımlanmıştır. Hepsi de “TransactionHandler()” abstract sınıfından türetilmişlerdir.
1-2 ve 3. Transaction’da yapılacak işlem, bu işlem sonucunun, birimlere iletileceği “Notify()” methodu aşağıdaki gibi tanımlanmıştır. Ayrıca işlemin olumlu olması durumunda, ilgili transaction’a tanımlanmış “nextTransaction”‘ın “ProcessTransaction()” methodu “TransactionInfo” sınıfına ait olan “sInfo” değişkeni, paramtere olarak geçilerek tetiklenir.
Eğer işlem başarız olunup hataya düşülünürse, yine işlemlerin geri alınacağına dahil “Notify()” methodu işletilip, ilgili transaction’a tanımlanmış “beforeTransaction”‘ın “ProcessTransaction()” methodu, “TransactionInfo” sınıfına ait olan “sInfo” değişkeni, parametre olarak geçilerek tetiklenir.
4.Transaction’da bilinçli olarak hata yapılması sağlanmış ve bu adıma kadar işletilen tüm Transactionların yaptığı işlerin geri alınması sağlanmıştır. Yani her bir Transaction’ın “beforeTransaction”‘ propertysine atanan Transaction’ın “ProcessTransaction()” methodunun tetiklenmesi sağlanmıştır. Bu işlem ilk atanan Transaction’a kadar devam edecektir. Yani “isFirst” propertysi true olan Transaction’a kadar geri gidilecektir.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 |
class Transaction1 : TransactionHandler { public override void ProcessTransaction(TransactionInfo sInfo) { try { Console.WriteLine("DoNext " + this.GetType().ToString()); Notify(this.NotifyType,"İşlem Başarıldı"); nextTransaction.ProcessTransaction(sInfo); } catch { Notify(this.NotifyType, "İşlem Geri Alındı"); beforeTransaction.ProcessPreviewTransaction(); } } } class Transaction2 : TransactionHandler { public override void ProcessTransaction(TransactionInfo sInfo) { try { Console.WriteLine("DoNext " + this.GetType().ToString()); Notify(this.NotifyType, "İşlem Başarıldı"); if (sInfo.Stage != TransactionStage.Transaction2) nextTransaction.ProcessTransaction(sInfo); } catch { Notify(this.NotifyType, "İşlem Geri Alındı"); beforeTransaction.ProcessPreviewTransaction(); } } } class Transaction3 : TransactionHandler { public override void ProcessTransaction(TransactionInfo sInfo) { try { Console.WriteLine("DoNext " + this.GetType().ToString()); Notify(this.NotifyType, "İşlem Başarıldı"); if (sInfo.Stage != TransactionStage.Transaction3) nextTransaction.ProcessTransaction(sInfo); } catch { Notify(this.NotifyType, "İşlem Geri Alındı"); beforeTransaction.ProcessPreviewTransaction(); } } } class Transaction4 : TransactionHandler { public override void ProcessTransaction(TransactionInfo sInfo) { try { //Burada Özellikle Hata Yapılır! int a = 0; int b = 4; int c = b / a; Console.WriteLine("DoNext " + this.GetType().ToString()); Notify(this.NotifyType, "İşlem Başarıldı"); // if (sInfo.Stage != TransactionStage.Transaction4) nextTransaction.ProcessTransaction(sInfo); } catch { Notify(this.NotifyType, "İşlem Geri Alındı"); beforeTransaction.ProcessPreviewTransaction(); } } } |
Program.cs: Örnek amaçlı oluşturulan 4 Transaction aşağıdaki gibi tanımlanmıştır.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 |
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace TransactionConsole { class Program { static void Main(string[] args) { TransactionHandler handlerTransaction1 = new Transaction1(); TransactionHandler handlerTransaction2 = new Transaction2(); TransactionHandler handlerTransaction3 = new Transaction3(); TransactionHandler handlerTransaction4 = new Transaction4(); handlerTransaction1.NextTransaction = handlerTransaction2; handlerTransaction1.BeforeTransaction = handlerTransaction1; handlerTransaction1.isFirst = true; handlerTransaction1.NotifyType = NotifyCenter.BTK; handlerTransaction2.NextTransaction = handlerTransaction3; handlerTransaction2.BeforeTransaction = handlerTransaction1; handlerTransaction2.NotifyType = NotifyCenter.BDDK; handlerTransaction3.NextTransaction = handlerTransaction4; handlerTransaction3.BeforeTransaction = handlerTransaction2; handlerTransaction3.NotifyType = NotifyCenter.BTK2; handlerTransaction4.NextTransaction = handlerTransaction4; handlerTransaction4.BeforeTransaction = handlerTransaction3; handlerTransaction4.NotifyType = NotifyCenter.BDDK2; TransactionInfo info = new TransactionInfo {Stage=TransactionStage.Transaction4}; handlerTransaction1.ProcessTransaction(info); Console.ReadLine(); } } } |
Ekran Çıktısı:
Böylece transaction gibi önemli bir durumda, design patternler ile işlerin ne kadar kolaylaşabileceğini, tasarım kalıpları polimorfizm’i ile eldeki ihtiyaca tekbir yöntem ile cevap verilemediğinde farklı tasarım kalıplarının bir arada kullanılması ile çözüme nasıl gidilebileceğine hep beraber inceledik. Ayrıca ben bu örnek de var olan “Chain of Responsibility design pattern“‘ına kendimce farklı bir esneklik katıp, sadece ileri yönlü değil aynı zamanda geriye doğru işleyen adımlarının da yapılmasını sağladım. Arada bir böyle çılgınlıklar yapmak güzel olabiliyor:) Bu konudaki fikir ve düşünceleriniz benle paylaşırsanız sevinirim.
Not: Momento Design Patternini de belki kullanabilirdik. Ama Undo ve Redo işlemlerinden, daha kapsamlı bir iş akşı içerisinde olduğumuzdan tercih etmedim.
Geldik bir makalenin daha sonuna. Yeni bir makalede görüşmek üzere hoşçakalın.
Source: https://www.dofactory.com/net/chain-of-responsibility-design-pattern
Elinize sağlık. Oldukça anlaşılır faydalı bir makale olmuş.