Anasayfa » Teknoloji » UnitOfWork Tasarım Modeli (Unit of Work Design Pattern) Kullanımı

UnitOfWork Tasarım Modeli (Unit of Work Design Pattern) Kullanımı

UnitOfWork tasarım modeli basitçe veritabanı süreçlerini bir noktadan aktararak/yöneterek farklı data kaynaklarında data tutarlılığını (transaction) sağlayan bir yazılım yaklaşımıdır. Süreçleri bir noktada toplamış olması kaynak kullanımını azaltmakta ve performansa katkısı olmaktadır. Uygulaması Depo tasarım modeli (Repository design pattern) ile birlikte gerçekleşir.

Depo tasarım modelinde depo (repository) veritabanında bulunan rastgele bir varlığı tabir etmektedir. Model genelde 2 halde uygulanmaktadır.

  • Jenerik (Generic)

Jenerik modelde genel bir depo tanımlaması yapılarak süreçler içerisine eklenir. Bu model her varlık için başka ayrı oluşturulsa da bir havuz üzeredir. Bir örnek ile tüm varlıklara kaynak teşkil eder.

  • Tekil varlığa has (Non-Generic)

Tekil varlığa mahsus modelde ise her varlık örneği başka ayrı tanımlanır. Her tarifin içerisinde kendine mahsus süreçleri vardır. Jenerikten farklı olarak birçok örnek ile birçok kaynağı tabir eder.

Jenerik yahut tekil varlığa mahsus depo yaklaşımlarından hangisi olursa olsun, depo yaklaşımının birçok yararının yanında tahminen bir dezavantaj olarak oluşturulan her varlık kendi içerisinde veritabanı kaynağı/bağlamı (context) içerir. Bu da her varlık için oluşturulan veritabanı kaynağı demektir. Ayrıyeten birbirini sıralı takip eden süreçlerde yanılgılı bir sürecin olması durumunda farklı data yapıları üzerinde geri alma prosedürü epeyce karmaşıktır. Bu durum data kaynağın tekilleştirmesi sayesinde aşılabilir.

unit of work olmadan

Üstteki formda görüleceği üzere kullanıcı ve sipariş idaresi, kendileri için kurgulanmış repository’lere sahipler ve repository’ler kendi içerisinde DbContext içermektedir.

UnitOfWork yaklaşımında ise alttaki halde görüleceği üzere DbContext bir örnek olarak oluşturulmakta ve repository’lere sağlanmaktadır.

unit of work ile

Örnek uygulamamızda UnitOfWorkManager isminde bir yapı oluşturarak birtakım temel süreçleri içerisine ekleyeceğiz. Oluşturacağımız yapı IUnitOfWorkManager arayüzünden türetilecektir.

  public interface IUnitOfWorkManager
    {
        int SaveChanges();
        Task SaveChangesAsync(CancellationToken cancellationToken = default);
        void BeginTransaction();
        void Commit();
        void Rollback();
        bool IsTransactionContinue();
    }
  public class UnitOfWorkManager : IUnitOfWorkManager
    {
        public DefaultDbContext Context { get; internal set; }
        private bool isTransaction { get; set; }

        public UnitOfWorkManager(DefaultDbContext context)
        {
            Context = context;
        }
        public int SaveChanges()
        {
            return Context.SaveChanges();
        }

        public Task SaveChangesAsync(CancellationToken cancellationToken = default)
        {
            return Context.SaveChangesAsync(cancellationToken);
        }

        public void BeginTransaction()
        {
            Context.Database.BeginTransaction();
            isTransaction = true;
        }

        public void Commit()
        {
            Context.Database.CommitTransaction();
            isTransaction = false;
        }

        public void Rollback()
        {
            Context.Database.RollbackTransaction();
            isTransaction = false;
        }

        public bool IsTransactionContinue()
        {
            return isTransaction;
        }        
    }

Üstteki kod bloğunda görüldüğü üzere DefaultDbContext isminde db context’imiz (veritabanı kaynağı/bağlamı) UnitOfWorkManager yapısı içerisinde oluşturulmaktadır. Oluşturulan data kaynağı Repository’ler ile paylaşılacaktır. Hasebiyle tüm Repository’ler tıpkı data kaynağını kullanacağından kaynak tekilleşecektir.

Unutmadan örnek çalışmamızda IoC container kullanıldığından çözümleme için kayıt süreci, istek (request) başına (service lifetime scope) olarak yapılmıştır. Bu durumun nedeni istek boyunca birebir unitofwork ve repository örneğinin oluşturulmasının sağlanmasıdır.

Autofac IoC kütüphanesi kullandığınızda örnek kayıt süreci (ConfigureContainer)

     builder.RegisterType<UnitOfWorkManager>().As<IUnitOfWorkManager>().InstancePerLifetimeScope();
     builder.RegisterGeneric(typeof(Repository<>)).As(typeof(IRepository<>)).InstancePerLifetimeScope();

Asp.Net Core IoC kütüphanesi ile kayıt süreci (Startup.cs)

     services.AddScoped<IUnitOfWorkManager, UnitOfWorkManager>();
     services.AddScoped(typeof(IRepository<>), typeof(Repository<>));

Uygulamamızda jenerik tip depo tasarım modeli kullanılacaktır. Repository ismindeki jenerik modelimiz IRepository arayüzünden türetilecektir. Türetilen jenerik model UnitOfWorkManager yapısını içerecektir ve bu yapıyla birlikte db context kaynağına erişecektir.

IRepository arayüzü ile devam edelim.

  public interface IRepository<T> where T : class
    {
        //Get Methods
        T Get(int Id);
        IQueryable Where(Expression<Func<T, bool>> where);
        IQueryable GetAll();

        //Get Async
        Task<T> GetAsync(int Id);

        //Execute Methods
        int Insert(T obj);
        int Update(T obj);
        int Delete(T obj);

        //Execute Async
        Task<int> InsertAsync(T obj);
        Task<int> UpdateAsync(T obj);
        Task<int> DeleteAsync(T obj);
    }
  public class Repository<T> : IRepository<T> where T : class
    {
        private DbSet<T> _objectSet { get; set; }
        private DefaultDbContext _dbcontext { get; set; }
        private IUnitOfWorkManager _unitOfWorkManager { get; set; }

        public Repository(IUnitOfWorkManager unitOfWorkManager)
        {
            _unitOfWorkManager = unitOfWorkManager;
            _dbcontext = (_unitOfWorkManager as UnitOfWorkManager).Context;
            _objectSet = _dbcontext.Set<T>();
        }

        public T Get(int Id)
        {
            return _objectSet.Find(Id);
        }

        public async Task<T> GetAsync(int Id)
        {
            return await _objectSet.FindAsync(Id);
        }

        public IQueryable<T> GetAll()
        {
            return _objectSet.AsQueryable();
        }

        public IQueryable<T> Where(Expression<Func<T, bool>> where)
        {
            return _objectSet.Where(where);
        }

        public int Insert(T obj)
        {
            _objectSet.Add(obj);
            return Save();
        }

        public async Task<int> InsertAsync(T obj)
        {
            await _objectSet.AddAsync(obj);
            return await SaveAsync();
        }

        public int Update(T obj)
        {
            return Save();
        }

        public async Task<int> UpdateAsync(T obj)
        {

            return await SaveAsync();
        }

        public int Delete(T obj)
        {
            _objectSet.Remove(obj);

            return Save();
        }

        public async Task<int> DeleteAsync(T obj)
        {
            _objectSet.Remove(obj);

            return await SaveAsync();
        }

        private int Save()
        {
            return _dbcontext.SaveChanges();
        }

        private async Task<int> SaveAsync()
        {
            return await _dbcontext.SaveChangesAsync();
        }
    }

Örneğimiz ile gereksinime nazaran 2 kullanım opsiyonu sağlanmıştır. Uygulamamızda CRUD süreçleri sırasında UnitOfWork ile BeginTransaction() kullanılmışsa kayıtlar Commit() ile kaydedilecek ve kusur durumunda RollBack() ile geri alınacaktır. Şayet BeginTransaction() kullanılmazsa her bir süreç sonunda kayıt süreci gerçek zamanlı gerçekleştirilecektir.

Transaction kullanılarak data tabanı süreci örneği :

      //transaction başlatılır.
      _unitOfWorkManager.BeginTransaction();
      try
      {
          //kullanıcı eklenir.
          var userId = _userRepo.Insert(user);

          //kullanıcı için claim eklenir.
          if (userId > 0)
              _userClaimRepo.Insert(new UserClaims() { ClaimId = 1, UserId = userId });

          //claim eklemede bir sorun var ise kullanıcı ekleme süreci de geri alınacaktır.
          //sorun yok ise süreç commit ile kaydedilecektir.
          _unitOfWorkManager.Commit();
      }
      catch {
          //sorun oluştuğunda geri alma işlemi
          _unitOfWorkManager.Rollback();
      }

Alttaki ekran alıntısında kullanıcı ekleme “Add” metodu örneğinde transaction kullanılarak bilgi tutarlılığı sağlanmıştır.

Altta göreceğiniz kullanıcı ekleme “Add” metodunda ise transaction kullanılmadan uygulama yapılmıştır.

Sağlıkla kalın..

İçeriği Oyla

Yorum yapın