Repository Pattern ile İşlem Yapılacak Bir Kaydın Kitlenmesi ve Bir Dizi Kural Setinin Çalıştırılması

Selamlar,

Bu makalede,  iş hayatında karşımıza bolca çıkabilecek olan bir kayıdın güncellenmesi durumunda, bir başkası tarafından değiştirilememesi konusuna ve bir de DB’ karşılığı olmayan sadece ViewModel’de olan kolonların, belirlenen bir takım Bussines Rulelar ile  nasıl doldurulabileceği konularını tartışacağız. Ayrıca bütün bunların yanında, Repository katmanın gerçekten lazım olup olmadığı konusuna da detaylıca bakacağız.

Transaction:


Resim Kaynağı: docs.microsoft.com

Bu makalede .NET 5.0, Entity Framework ve DB olarak MSSQL kullanılacaktır. Günümüzde, bir kişinin bir kaydı güncelleme amaçlı üzerine aldığı zaman, bunun başkası tarafından değiştirilememesi amacı ile bir çok yöntem geliştirilmiştir.

  1. En basit olanı UpdateDate kontrolüdür. Örneğin Can bir kaydı üzerine aldığında, UpdateDate tarihi ile birlikte çeker. İlgili kayıt başka biri tarafından bu esnada güncellenir ise, UpdateDate alanı değişir. İşte bu sırada Can da aynı kaydı güncellemek istediğinde, ilgili kayda ait UpdateDate’in değiştiğini görecek ve güncelleme işlemini yapamayacaktır. Bu maalesef iyi bir çözüm değildir. Evet datanın birbirini ezmesi engellenmiştir ama, Can’ın doldurmuş olduğu form, kaydet butonuna basıldıktan sonra hata alındığı için, çöpe gitmiştir.
    if(“2020-12-19 02:48:59.820” == “2020-12-19 02:49:17.550”)
  2. Genel olarak gördüğüm ikinci bir çözüm ve asla yapılmaması gerekeni, üzerinde işlem yapılan tabloların kaydının ortak bir tabloda loglanmasıdır. Yani, üzerinde işlem yapılan herbir tablonun işlem yapılan kaydı, kimin yaptığı ve ne zaman yaptığı bilgilerinin ortak bir tabloda tutulmasıdır. Böylece 2. bir kişi aynı kayda girmek istediğinde, bu tabloya bakılarak izin verilmekte ya da verilmemektedir. Bu şekilde bakılınca, herşey masumane durmaktadır. Hatta kimin hangi kayıt ile uğraştığı bilgisine, ilgili tablo üzeriden kolayca erişilebilmektedir. Bu belki 50-100 kullanıcılı bir uygulama için gayet güzel bir çözümdür. Ama 5K, 10K gibi kullanıcı sayısının çok olduğu yapılarda, tam bir felakettir. Peki neden?


Resim Kaynağı: assets.site-static.com

Nedeni tamamen Lock mekanizmasıdır. Örneğin aşağıda Transaction açılarak bir locklama örneği verilmiştir. Kritik bir tabloda, kayıdın silinmesi ya da güncellenmesi anında, bir başka client’ın değişen kaydın, eski halini görüntülemesini engellemek için kullanılmaktadır. Ama tabi bu durum, tam bir performans düşmanıdır. Çünkü read ve write işlemlerine kilitli olan bir tablo üzerinde, kimse kilit kalkana kadar bizim örneğimizde Transaction.Commit() ya da Transaction.Rollback() olana kadar, bir işlem yapamaz. Bu da response time’ı doğal olarak arttırır.

Beklemek Güzeldir Ama Doğru Durakta ― Anonim

Örnek .Net 5.0 Transaction kodu: İlgili transaction altında çekilen kayıt, o client için locklanır. Bu arada ilgili kolonlar güncellenir iken, bir başka client’ın ilgili kaydı değiştirmesi engellenir. Bu da farklı tablolar ile çalışan clientların, aynı tabloya yazmaları yüzünden, birbirlerini beklemelerine neden olur.

SQL DB:

Buradan yola çıkar isek, 2. çözüm olarak sunulan tüm tabloların logların tutulduğu tek bir tablo öreneğine bir bakalım. Aşağıda görüldüğü gibi işlem yapılan tablo, işlem yapılan satır, işlem yapan kişi ve zamanı bir tabloda tutulmaktadır.

Yukarıdaki tabloya yeni bir kayıt girileceği ya da işi biten kaydın silineceği sırada Lock’lanması gerekmektedir. Eğer bu işlem yapılmaz ya da aşağıdaki gibi sorgularda tablonun kitli olup olunmamasına bakılmaz ise, 2 kişi aynı kaydı aynı anda üzerine alıp çalışabilmektedir. Bu da en istenmeyen kirli dataya yol açacaktır.

Eğer tüm tablolar tek bir tabloya kaydedilir ve ilgili tablo Locklanır ise, farklı tablolarda işlem yapan clientlar, birbirlerini beklemek zorunda kalacaklar ve bu da, artan client sayısı ile birlikte büyük bir performans problemine dönüşecektir.

O zaman gelin her tablonun Lock işlemini kendi üzerinde tutalım ve bu Locklama işini tüm tablolara dağıtalım. Böylece X tablosunda çalışan client, Y tablosunda çalışan client’ın işleminin bitmesini beklemesin.

Örnek olarak DB_SECURITY_ACTION tablosuna bakalım: ModDate, OpenStatus ve OpenStatusUserId kolonları, tüm entity tablolara eklenmiştir.

  • ModDate: Son değiştirilen tarih.
  • OpenStatus: Lockli mı  değil mi ?
  • OpenStatusUserId: Kaydı üzerine alan kişinin ID’si.

Örnek projede DB First kullanılmıştır. DbSecurityAction sınıfı aşağıdaki gibi scaffold komutu ile DB’den oluşturulmuştur.

Seçilen DB’den ilgili DBContext ve Entitiylerin oluştuğu, Terminalde proje ana dizininde çağrılan örnek scaffold komutu:

DbSecurityAction.cs: Üzerinde işlem yapılacak tablo.

Global Hale Getirme:

Şimdiki amacımız, bu locklama işleminin olacağı tabloları işaretlemek ve ona göre bir takım aksiyonlar almak olacaktır.

Bu amaçla IExpired interface’i, aşağıdaki gibi Core katmanında oluşturulmuştur.

IExpired.cs: İlgili interfaceden türemiş sınıflar güncellendiği ya da silindiği zaman, bu interfaceden türeyen sınıfların ilgili kolonları, locklama amaçlı doldurulur. Bu kolonlar aşağıda görüldüğü gibidir:

  • ModDate: Güncelleme amaçlı, kayıdın bir client tarafından üzerine alındığı an.
  • OpenStatus: Kayıdın birisi tarafından üzerine alındığını belirten işaret.
  • OpenStatusUserId: Editleme işlemini yapan kişi.

Bir başka Partail Class‘da, DB’den otomatik oluşturulan Entitylerin istenen interfaceden türetildiği DB katmanında tanımlanan classdır.

PartialEntites.cs: IExpire interface’inden türetilen classlar, bu şekilde işaretlenirler.

Not: Farklı bir partial class’da, ilgili interface atamasının yapmasının nedeni, scaffold çalıştırıldığı zaman otomatik oluşan classlar ile manuel yazılan kodların ezilmesinin önlenmesidir.

Şimdi DbSecurtiyAction Entitysini, IExpire interface’i ile işaretledik. Gelin bu interface ile işaretli classları, “Get()” işleminde Global olarak Lock’layalım.

1.Yol Inject StatusOperations Service:

İşte tam bu örnek, bazen projelerde servis katmanında nasıl araya girebileceğimizi gösteren bir durumdur.

IStatusOperations: Global olarak kayıdın durumunu değiştiren Interceptor bir servis yazılır. IStatusOperation interface’inin, IExpired interface’inden türemesi, zorunlu kılınır. Tekbir methodu vardır. O da ChangeOpenStatus(). Ve generic tipte bir değer almaktadır. Geriye dönüş tipi olarak da DateTime? yani ModDate dönmektedir.

StatusOperations: Oluşturulan Interceptor servisde, Dependency Injection ile Repository ve DBContext sınıfları almıştır. Amaç, çekilen kaydın ilgili kolonlara set edilerek locklanması, yani başka biri tarafından düzenlenmesinin engellenmesidir.

Seni kendime sakladım. Hepsini ben hesapladım ― Lyrics – Duman

  • “using (var transaction = _context.Database.BeginTransaction())”: İşlem yapılmadan önce gönderilen tablo, Transaction açılarak DB bazlı locklanır. Entitiy’de, default Transaction Isolation Level, READ COMMITTED‘dir. Bu işlem SqlProfile edilerek rahatlıkla görülebilir.

  • PART1 actions = _repository.Table.Where(sc => sc.OpenStatusUserId == userId).ToList()“: Client’ın o Entitiy için üzerine aldığı lock’lı olan önceki tüm kayıtlar boşa çıkarılır.
  • //PART2 if (idEntity != 0)“: Eğer seçilen bir kayıt var ise, ilgili Client için kitlenir.
  • action = _repository.GetById(idEntity)“: İlgili kayıt, Repository katmanı kullanılarak Id’sine göre çekilir.
  • action.OpenStatus = state; action.OpenStatusUserId = userId; action.ModDate = DateTime.Now” : İlgili kayıt’ın OpenStatus’ü false olarak setlenir ve başkasının ilgili kaydı üzerine alması engellenir. “ModDate” kayıdın, ilgili kişinin üzerine alandığı tarih ve saat alanıdır. OpenStatusUserId, kayıdı üzerine alan kişidir.
  • if (action.OpenStatus == true)“: Son bir kere başka biri, ilgili kaydı üzerine almış mı diye kontrol edilir.
  • else if (action.OpenStatusUserId != userId)“: Eğer, ilgili kaydı üzerine alan kişi biz değil isek, işleme devam edilmez ve geriye false değeri dönülür.
  • _repository.Update(actions)“: Kayıdın son halinin DB’ye, Repository katmanı kullanılarak kaydedildiği yerdir.
  • transaction.Commit(); response.IsSuccessful = true; return response“: Açılan Transaction kapatılır ve geriye “true” değeri dönülür.
  • catch (Exception ex) { transaction.Rollback()“: Hata durumunda, yapılan DB işlemleri geri alınmaktadır.
  • response.IsSuccessful = false; return response” : Geriye olumsuz cevap dönülmektedir.

Gelin ilgili servisi, .Net 5.0 projede Startup.cs’de aşağıdaki gibi tanıtalım.

Şimdi gelin bu servisi Dependecy Injection ile ,”Get()” ile üzerine alınan servisde kullanalım.

Service/SecurityActionService.cs: Aşağıda görüldüğü gibi, yukarıda tanımlanan”statusOperation” servisi, class’a Dependecy Injection ile ilgili Entity, “DbSecurityAction” şeklinde atanarak tanımlanmıştır. 

  • ChangeActionOpenStatus() methodunda, ilgili statusOperation sınıfının “ChangeOpenStatus()” methodu, (idSecurityAction, userId, state) parametreleri ile çağrılır.

WebApi/Controllers/ActionController.cs: Front-End tarafından çağrılan endpoint’dir. “ChangeActionOpenStatus()” editlenecek dosyaları, üzerine alan kişi tarafından kitler.

  • ChangeActionOpenStatus()“: Methodunda parametre olarak OpenStatus sınıfı beklenmektedir. Yani kilitlenecek veya serbest bırakılacak SecurityActionID ve yapılacak işlem status.
    • İlgili servis dışarıya, “[Route(“ChangeActionOpenStatus“)]”  şeklinde açılmaktadır.
  • _actionService.ChangeActionOpenStatus()”: Yukarıda tanımlanan ChangeActionOpenStatus() methodu parametre olarak, güncellenecek kayıdın ID’si, işlemi yapacak UserID’si ve status’u yani, kayıdın kitleneceği ya da boşa çıkaralacağı bilgisi gönderilir.

Not: “_workContext”, projede session gibi kullanılan ortak kullanılan alanları tutan bir sınıftır. Bu örnekde “CurrentUserId” değeri, _workContext’den alınmaktadır.

Aşağıda görüldüğü gibi 3 nolu kayıt UserID 1 tarafından kitlenmiştir.

Kısaca tüm servislere Injecte edilecek, “Generic” yeni bir servis yazılmış, ve böylece kod tekrarında kaçınılarak “Global” bir çözüme gidilmiştir.

2.Yol Repository Katmanı:

Bir çok yerde Repository katmanının gereksiz olduğu belirtilmektedir. Ama bu projesine göre değişiklik göstermektedir. Kısaca bu seneryoda, Repository pattern çok işimize yarıyacaktır. Nasıl mı?

Öncelikle yukarıdaki örnekte, ilgili “IStatusOperation” servisi, her serviste çağrılacak ve kod tekrarı olmasa da, servis heryerden birçok kere call edilecektir. Peki ya bu kodu tekrar tekrar çağırmak yerine, tek bir yerden çağırma şansımız olsa idi. İşte bu katmana Repository katmanı diyoruz. Servis katmanında DB ile iletişime girilmediği, bussines logic’in yazılıp gelen datanın işlendiği ve ilgili datanın en son olarak kaydedilmek ya da DB’den çekilme için gidildiği katman, Repository katmanıdır.

Repository/GeneralRepository/GetById(): 

Aşağıdaki örnekde tüm servisler,  Generic Repository katmanındaki aynı GetById() methodunu çağırdıkları için, kodlar Global hale getirilmiştir. Böylece, her bir servis de ilgili methodun çağrılması ile yapılacak kod tekrarından, kurtulunmuştur.

  • public virtual T GetById(object id)“: Generic tipde tüm Entityler, parametre olarak verilebilir.
  • if (entity is Core.Extensions.IExpired && _context.Database.CurrentTransaction==null)” : Eğer gelen Entity, “IExpired” interface’inden türetilmiş ise, ayrıca ilgili context üzerinde başka bir transaction başlatılmamış ise ilgili method çalıştırılır.
  • using (var transaction = _context.Database.BeginTransaction())“: Locklama işlemi için, transaction başlatılır. Bu şekilde aynı zamanda Lock işlemi yapılmış olunur.
  • var model = (Core.Extensions.IExpired)entity“: İlgili model, IExpire interface’ine cast edilir. Amaç ilgili propertylere erişebilmektir.
  • if (model.OpenStatus == true || (model.OpenStatus == false && model.OpenStatusUserId == _workContext.CurrentUserId))“: Son bir kontrol ile, ilgili kayıt Lock’lı değilse ya da bizim tarafımızdan locklı ise, işleme devam edilir.
  • model.OpenStatus = false; model.OpenStatusUserId = _workContext.CurrentUserId; model.ModDate = DateTime.Now; _context.SaveChanges()” : İlgili kayıt, bizim tarafımızdan o anki zaman ile ilgili propertyler set edilerek, kitlenir.
  • transaction.Commit()” : İşlemin başarılı olması durumunda Transaction tamamlanır.
  • catch (Exception ex) { transaction.Rollback()“: Hata durumunda yapılan tüm işler geri alınır.

Böylece tüm solution’daki servisler aynı Repository katmanını kullandığı için, “Get()” işleminde gerçek anlamda “Global” olarak istenen yani sadece “IExpired” interface’inden türeyen Entityler lock’lanabilmiştir.

Repository/GeneralRepository/UpdateMatchEntity():  Aşağıda görüldüğü gibi, güncellenen data, “IExpired” interface’inden türemiş ise, güncellenecek data ile DB’deki datanın “ModDate” kolonları karşılaştırılır. Farklı olması durumunda güncelleme işlemine deva edilmez. Bu da, ilgili kaydın başkası tarafından değiştirilip değiştirilmediğinin kontrolünün son adımıdır. 

Not: “InsertElastic(updateEntity, “Update”, _elasticConfig.Value.ElasticAuditIndex)”:  Konu dışı olarak, IAuditable interface’inden türüyen classlar, ElasticSearch’e loglama amacı ile atılırlar.

Rule Setleri:

Geldik istenen kural setlerinin, çekilen data üzerinde çalıştırılmasına. Amaç, DB’de karşılığı olmayan kolonların, yani sadece ViewModel’de olan kolonların, önceden belirlenen Bussines Rulelara göre doldurulup client’a gönderilmesini sağlamaktır. 

Bu işlemi Global olarak düşünmeli ve kod tekrarından kaçınılmalıdır.

SecurityActionModel: Aşağıda görüldüğü gibi ilgili ViewModel, DB’den bağımsız olarak “IsCancelState” ve “IsApprovelState” adında iki kolona sahiptir. Bu kolonlar bir dizi çalıştırılacak Bussines Rulelara göre doldurulacak, ve ilgili Client’a gönderilecektir. Bu kolonların DB’de bir karşılığı yoktur.

Gelin önce çalıştırılacak Ruleları tanımlayalım. Rulelar bir Bussines logic olduğuna göre, onları tanımlayacağımız en iyi yer Servis katmanıdır.

Service/Management/SecurityAction/SecurityActionRules:

ISecurityActionRule: Tüm rulelar bu interface’den türetilmektedir. Hepsinin ortak methodu “ExecuteRule“‘dur.

Service/Management/SecurityAction/SecurityActionRules/SecurityActionApprovelRule: Aşağıda görüldüğü gibi “IsApprovelState” kolonunu dolduran Bussines Ruledur. Yazılan Rule, örnek olması amacı ile tamamen hayal ürünüdür :)

  • “public class SecurityActionApprovelRule : ISecurityActionRule”: İlgili Rule sınıfı “ISecurityActionRule” interface’inden türetilmiştir.
  • “public SecurityActionApprovelRule(int currentUserId) “: İşlemi gerçekleştiren UserID constructor’da alınır.
  • “public List<SecurityActionModel> ExecuteRule(List<SecurityActionModel> actionList)”: Interface’in zorunlu kıldığı ExecuteRule, Approvel Bussines Logiğine göre özelleştirilir.
  • “actionList.ForEach(item =>”: Gönderilen tüm kayıtlar gezilir.
  • “if (_currentUserId != item.OpenStatusUserId && item.OpenStatus == false) { item.IsApprovelState = false; }”: Eğer ilgili kayıt başkası tarafından kitlenmiş ise, ilgili rule çalıştırılmadan false atanır.
  • “else if ((DateTime.Now – item.CreDate).TotalDays > 180) { item.IsApprovelState = false; }”: Esas Bussines Rule bu satırdır. Yaratılma tarihi 180 günden fazla ise ilgili “IsApprovelState” kolonu “false” dönülür.
  • “else { item.IsApprovelState = true; }”  Eğer 180 günden daha yeni ise “true” dönülür.

Service/Management/SecurityAction/SecurityActionRules/SecurityActionCancelRule: Aşağıda görüldüğü gibi “IsCancelState” kolonunu dolduran Bussines Ruledur. Bu yazılan Rule da, örnek olması amacı ile tamamen hayal ürünüdür :)

  • “public class SecurityActionCancelRule : ISecurityActionRule”: İlgili Rule sınıfı “ISecurityActionRule” interface’inden türetilmiştir.
  • “public SecurityActionCancelRule(int currentUserId) “: İşlemi gerçekleştiren UserID constructor’da alınır.
  • “public List<SecurityActionModel> ExecuteRule(List<SecurityActionModel> actionList)”: Interface’in zorunlu kıldığı ExecuteRule, Cancel Bussines Logiğine göre özelleştirilir.
  • “actionList.ForEach(item =>”: Gönderilen tüm kayıtlar gezilir.
  • “if (_currentUserId != item.OpenStatusUserId && item.OpenStatus == false) { item.IsCancelState = false; }”: Eğer ilgili kayıt başkası tarafından kitlenmiş ise, ilgili rule çalıştırılmadan false atanır.
  • “else if (item.ActionNumber % 2 != 0) { item.IsCancelState = false; }”: Esas Bussines Rule bu satırdır. Tamamen örnek amaçlı gönderilen kaydın, ActionNumber kolonu tek ise “IsCancelState” kolonu “false” dönülür.
  • “else { item.IsCancelState = true; }”  Eğer ilgili kaydın  ActionNumber’ı çift ise “true” değeri dönülür.

Şu ana kadar SecurityAction için ilgili bussines kuralları yazdık. Şimdi gelin bunların nasıl işletileceğine hep beraber bakalım.

WebApi/Controllers/ActionController.cs: Aşağıda görüldüğü gibi, daha en başta controller katmanında, istenen N tane rule oluşturulur. Burdan şunu anlıyoruz, “ISecurityActionRule“‘dan türemiş olmak kaydı ile, istenen sayıda rule oluşturulabilir. Bu da yeni eklencek rulelar için kodun değiştirilmesine gerek olmayacak anlamına gelmektedir. Tüm Ruleların ortak çalıştırdığı method, ExecuteRule’dur.

  • “List<ISecurityActionRule> rules = new List<ISecurityActionRule>()”: Tüm Ruleların paramtere olarak gönderileceği liste. Tüm Rulelar, “ISecurityActionRule“‘dan türediği için hepsini kapsamaktadır.

  • “SecurityActionApprovelRule approvelRule”: Yukarıda tanımlanan ApprovelRule çalıştırılmak üzere, “GetActionListByControllerId” servisine bir List elemanı olarak gönderilir.
  • “SecurityActionCancelRule cancelRule”: Yine yukarıda tanımlanan CancelRule çalıştırılmak üzere ilgili servise, bir List elemanı olarak gönderilir.

  • “response.List = _actionService.GetActionListByControllerId(idSecurityController, rules).List”: İlgili servis, Rule Liste’si ve SecurityControllerID parametre gönderilerek çalıştırılır.

Service/Management/SecurityAction/SecurityActionService.cs: Aşağıda görüldüğü gibi, kendisine parametre olarak gelen Rule listesi, tek tek gezilerek herbirinin “ExecuteRule()” methodu, çekilen result üzerinde çalıştırılarak, resultViewModel doldurulmuş ve client’a datanın nihai son hali geri dönülmüştür.

  • “var response = new ServiceResponse<SecurityActionModel>(null)”: Geri dönülücek view model. DB karşılığı olmayan “IsApprovelState” ve “IsCancelState” kolonları mevcuttur.
  • “var result = _actionRepository.Table.Where(q => q.IdSecurityController == idSecurityController).ToList()”: İstenen data belirlenen koşula göre çekilir.
  • “result = (List<DB.Entities.DbSecurityAction>)result.CheckExpire(_vbtConfig.Value.StatusExpireTime)”: İlgili Entity üzerinde, süresi 5 dakkayı geçmiş kilitli kayıtlar, boşa çıkarılır. Bu global amaçlı yazılmış bir extension’dır. Makalenin devamında değinilecektir. 5 dakika süresi configden parametre olarak alınmaktadır.
  • “var resultViewModel = _mapper.Map<List<SecurityActionModel>>(result)” : DB’den query ile alınan DBContext tipindeki result List, ilgili Ruleların işlenmesi amacı ile, ViewModel’e çevrilir.
  • “rules.ForEach(rule =>”: Tüm rulelar tek tek gezilir.
  • “resultViewModel = rule.ExecuteRule(resultViewModel)”: DB çekilen resultViewModel, ilgili rulelar üzerinde çalıştırılarak, DB’de olmayan sadece Client Side’da ihtiyac duyulan propertyler doldurulur.

Buradaki yönteme özellikle dikkat ettiyseniz, genişletilmeye açık ama değiştirmeye kapalı yapılar, ilerde yeni rulelar geldiği zaman kolayca sisteme implemente edilebilmesini sağlamış ve N tane sayıdaki Rule’un, art arda çalıştırılmasına olanak vermiştir.

Bu yöntemin Design Pattern karşılığı “Strategy Design Pattern“‘dir.

Resim Kaynağı: miro.medium.com

Core/Extensions/ExpireExtensions: Son olarak, yukarıda kullanılan, 5 dakka süreden fazala kilitli kalmış kayıtların boşa çıkarıldığı Extensiondır.

Bu işlemin birçok yerden çağrılacağı düşünülür ise, Global hale getirme amacında Extension olarak yazılması mantıklı bir yöntem gibi gözükmektedir.

  • public static IEnumerable<IExpired> CheckExpire(this IEnumerable<IExpired> entityList,int? expireMinute=5)” : “IEnumerable<IExpired>” interface’inden türeyen sınıflar için yazılarak, bir kısıtlamaya gidilmiştir.
  • “entityList.Where(re => re.OpenStatusUserId != null && (DateTime.Now – re.ModDate).Value.TotalMinutes >= expireMinute).ToList()”: İlgili entitiy üzerinde, kayıdın kitendiği zaman dilimi “ModDate“, şimdiki zamandan 5 dakika veya daha eski olan kayıtlar alınarak, kilitleri tek tek kaldırılır. Ve üzerinde değişiklik yapılan EntityList geri dönülür. 5 dakika süresi configden parametre olarak alınmaktadır. Burada amaç, sorgulama anında, kilitleri atıl kalmış kayıtların temizlenmesidir.

Geldik bir makalenin daha sonuna. Bu makalede, kritik bir kaydın güncellenmesi anında, bir başkası tarafından aynı zamanda değiştirilmemesinin yollarını hep beraber araştırdık. Sizin de aklınıza takıldı ise, makalede özellikle atlanan bir durum vardır. O da, bir kayıt bir client tarafından değiştirilmeye başlandığı zaman, bunun diğer clientlara bildirilmesidir. Yani, ekranlarında açık olan güncellenebilen kayıtların Socket ile kitlenmesidir. O konuya da ilerde tekrardan değinebiliriz. Bir ikinci husus da, art arda çalıştırılan Ruleların servis katmanında nasıl daha Global hale getirilebileceği sorusudur. DB ile bir işin olunmaması ve tamamen Bussines kuralların ViewModel üzerinde koşturulması adına, Repository katmanı kesinlikle düşünülmemelidir. O zaman alternatif bir yol olarak Extension düşünülebilir. 

Tüm ViewModeller, aşağıda görüldüğü gibi “BaseModel” sınıfından türetilmiştir. İstenirse, “List<BaseModel>” sınıfına static bir Extension yazılıp, parametre olarak işletilecek Rule listesi gönderilebilir. Böylece istenen kod hem daha Global bir hale gelmiş hem de biraz daha pratik olmuş olacaktır. Son olarak her projede olmasa da Repository katmanı, bu projede hayati bir öneme sahiptir. Siz de projelerinizde DB ile olan etkileşim anında, global bir takım işler yapmak isterseniz, Repository katmanını şiddet ile öneririm.

Yeni bir makalede görüşmek üzere hepinize hoşçakalın. Sağlıcakla kalın.

Source:

Herkes Görsün:

Bunlar da hoşunuza gidebilir...

1 Cevap

  1. Mustafa S. Koca dedi ki:

    :) Amatör olarak projeye başlıyorum ve bu lock olayı numara olmuş :) Emeğinize sağlık bir de size email attım bi yapı kurdum sizce olur mu :)

Bir cevap yazın

E-posta hesabınız yayımlanmayacak.