Asp.Net Core Middleware Nedir Nasıl Kullanılır

.Net Core Microsoft tarafından open-source olarak geliştirilmiş modüler bir .net framework'dür. Asp.Net Core ise klasik bildiğimiz Asp.Net kütüphanesinin open-source olarak microsoft tarafından release edilmiş halidir.

Daha önceki yazılarda da bahsettiğimiz üzre asp.net core'da bir çok feature yada özellik ayrı bir modül olarak kolayca entegre edilebilecek şekilde geliştirilmiştir. Middleware'de yine bunlardan biri. 

Middleware nedir diye soracak olursak; Middleware asp.net core içerisinde request-response pipeline'ını handle etmemizi sağlayan bir çeşit interceptor görevi gören sınıflardır. Bu sınıfları kullanarak controller metodunuza gelen request'leri veya response'ları modify edebilir, header check yapabilir yada authorization kontrollerini kolayca entegre edebiliriz.

Middleware asp.net core cycle'ın da ki konumunu anlamak için aşağıdaki resime bamak yeterli.

Uygulama içerisinde tanımlı olan middleware'ler register edilme sırasıyla birlikte yukarıdaki resimde olduğu gibi birbirlerini call ederek pipeline'ı tamamlarlar. 

Middleware Imp.

Örneğimiz şu şekilde olsun; bir api projemiz var ve bu projede middleware kullanarak client'dan request header da beklediğimiz Client-Key, Device-Id key-value parametrelerini gönderip göndermediğini kontrol edelim. Göndermediği durumda http400 ile geriye hata dönelim.

Middleware tanımlamanın birkaç farklı yolu var ancak en basit olanını inceleyeceğiz. İlk olarak vs.'da MiddlewareSample adında bir asp.net core projesi oluşturalım. Sonrasında projemize HeaderCheckMiddleware adında bir sınıf ekleyelim.

    public class HeaderCheckMiddleware
    {
        private readonly RequestDelegate _next;
        public HeaderCheckMiddleware(RequestDelegate next)
        {
            _next = next;
        }

        public async Task Invoke(HttpContext httpContext)
        {
            var key1 = httpContext.Request.Headers.Keys.Contains("Client-Key");
            var key2 = httpContext.Request.Headers.Keys.Contains("Device-Id");

            if (!key1 || !key2)
            {
                httpContext.Response.StatusCode = 400;
                await httpContext.Response.WriteAsync("Missing requeired keys !");
                return;
            }
            else
            {
                //todo
            }
            await _next.Invoke(httpContext);
        }
    }

Yukarıda görüldüğü üzre, Invoke metodu middleware call edildiğinde execute edilecek olan metottur. Bu metot; end-poit'e gelen request'i ve end-point'in return ettiği response'a müdahale edebilmemizi sağlar. Bizde yapılan her httpRequest'inde header de beklediğimiz Client-Key, Device-Id vs. gibi bilgileri kontrol etme işini yukarıda olduğu gibi bu metodun içerisinde yaptık. Eğer bu 3 header key'in den birisi dahi header'da yok ise htpp400 olarak geriye hata mesajı return ettik.

Middleware'imizi tanımladıktan sonra geriye bunu asp.net core projemize register etmek kalıyor. Bunun içinde aşağıdaki gibi bir extension metot yazalım ve register etme işlemini asp.net core ile birlikte gelen Startup.cs de bulunan Configure metodu içerisinde tıpkı projede ayağa kaldırabileceğimiz bir servismiş gibi enable edelim.

    public static class MiddlewareExtension
    {
        public static IApplicationBuilder UseHeaderCheckMiddleware(this IApplicationBuilder builder)
        {
            return builder.UseMiddleware<HeaderCheckMiddleware>();
        }
    }

Son adım olarak ise Configure metodu içerisinde middleware'i aktifleştirelim .

 public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
 {
     app.UseHeaderCheckMiddleware();
 }

Aşağıdaki gibi postman veya herhangi bir rest-call tool'u kullanarak projenizde bulunan herhangi bir end-point'e call yaptığınızda header da beklenen parametreleri göndermezsek hata almış olacağız.

Parametreleri doğru bir şekilde gönderdiğimiz durumda ise sorunsuz şekilde endpoint'e ulaşıp success response alabiliriz.

Middleware asp.net core projelerinde aop özelliklerini uygulayabilmemizi sağlar ve bununla birlikte bizlere projemiz için modüler özellikleri olan küçük feature'lar ekleterek kod tekrarından ve spaghetti code bloklarından bizleri kurtarabilir. Örnekte sadece küçük bir header check işlemi yaptık ancak middleware kullanarak bunu gibi daha bir çok geliştirmede yapabiliriz.

 

NULL Object Pattern Nedir ?

NULL Object Pattern Gang of Four’s Design Patterns kitabında anlatılmış olup behavioral design pattern'ler den biridir. Bu pattern'in amacı uygulama içeresinde null objeler return etmek yerine ilgili tipin yerine geçen ve expected value'nun null objesi olarak kabul edilen tipi geriye dönmektir diğer bir değişle null yerine daha tutarlı nesneler dönmektir. Bu nesne asıl return edilmesi gereken nesnenin null değeri olarak kabul edilirken onunla aynı özelliklere sahip değildir, çok daha az bilgi içermektedir. NULL Object Pattern , süreli olarak null kontrolü yaparak hem server-side hemde client-side için boilerplate code yazmaya engel olmak amacıyla ortaya çıkmış bir pattern dir.

Platform yada dil farketmeksizin geliştirme yaparken sürekli olarak nullreferenceexception aldığımız durumlar olmuştur bu durumdan kurtulmak adına obj null mı değil mi diye bir sürü if/else kontrolleri yaparız. Bu pattern'i kullanarak biraz sonraki örnekte yapacağımız gibi boilerplate code'lar yazmaktan nasıl kurtulabiliriz bunu inceleyeceğiz.

Örneğimizi 2 şekilde ele alalım. İlk olarak geriye null değer return ederek çoğunlukla nasıl geliştirme yapıyoruz o case'i ele alalım, sonrasında ise NULL Object Pattern kullanarak nasıl geliştirebiliriz onu inceleyelim.

Öncelikle Customer adında bir nesnemiz var ve repository kullanarak geriye bu nesneyi return edelim. 

    public class Customer
    {
        public int Id { get; set; }
        public string FirstName { get; set; }
        public string LastNae { get; set; }
        public int NumberOfChildren { get; set; }
        public string GetFullName()
        {
            return FirstName + " " + LastName;
        }
    }

Service katmanında generic bir repository yapımız varmış gibi varsayalım ve repository üzerinden GetCustomerByFirstName adında bir metot tanımlayalım.

public class CustomerService
    {
        public Customer GetCustomerByFirstName(string firstName)
        {
            return _customerRepository.List(c => c.FirstName == firstName).FirstOrDefault();
        }
    }

Sonrasında yukarıda tanımladığımız metodu call yaparak geriye customer objesini dönelim ve bazı değerleri ekrana yazdıralım.

   var customerService = new CustomerService();
   var customer = customerService.GetCustomerByFirstName("tosuner");
   Console.WriteLine("FullName : " + customer.GetFullName() + "\nNumber Of Childreen:" + customer.NumberOfChildren);

Yukarıdaki gibi customer'ın null geldiği durumda exception thrown 'system.nullreferenceexception' hatasını çoktan aldık gibi yani memory'de değeri assing edilmemiş bir yere erişmeye çalışıyoruz. Peki çözüm olarak ne yapabilirdik, ilk akla gelen aşağıdaki gibi bir kontrol olacaktır.

    var customerService = new CustomerService();
    var customer = customerService.GetCustomerByFirstName("tosuner");
    if (customer != null)
    {
        Console.WriteLine("FullName : " + customer.GetFullName() + "\nNumber Of Childreen:" + customer.NumberOfChildren);
    }
    else
    {
        Console.WriteLine("Name : Customer Not Found !" + "\nNumber Of Childreen: 0");
    }

Yukarıdaki gibi bir çözüme gittiğimizde customer objesini get ettiğimiz bir sürü yer olduğunu düşünün ve her yerde sürekli olarak null kontrolü yapıp sonrasında console'a değerleri yazıyor oluruz. Aslında bu şu deme değil;"null kontrolü yapma arkadaş !" kesinlikle bu değil tabikide ihtiyaç duyulan yerlerde bu kontrol yapılmalı hatta birçok case'de null ise throw new CustomBusinessException() vs şeklinde exception'da throw edeceğimiz durumlar olabilir. Demek istediğim yukarıdaki gibi client'a bu kontrolü olabildiğince bırakmamak.

NULL Object Pattern uygulayarak nasıl bir çözüm getirirdik ona bakalım. İlk olarak AbstractCustomer adında base sınıfımızı oluşturalım.

    public abstract class AbstractCustomer
    {
        public abstract int Id { get; set; }
        public abstract string FirstName { get; set; }
        public abstract string LastName { get; set; }
        public abstract int NumberOfChildren { get; set; }
        public abstract string GetFullName();
    }

Sonrasında Customer objesini bu sınıftan türetelim.

    public class Customer : AbstractCustomer
    {
        public override string FirstName { get; set; }
        public override string LastName { get; set; }
        public override int NumberOfChildren { get; set; }
        public override int Id { get; set; }

        public override string GetFullName()
        {
            return FirstName + " " + LastName;
        }
    }

Şimdi ise bu pattern'in getirdiği çözüm olarak geriye null value dönmeyip asıl return edilmek istenen sınıf yerine onun null olduğunu belirten bir sınıf geriye dönelim. Bu sınıfa da NullCustomer adını verelim.

    public class NullCustomer : AbstractCustomer
    {
        public override string FirstName { get; set; }
        public override string LastName { get; set; }
        public override int NumberOfChildren { get; set; }
        public override int Id { get; set; }

        public override string GetFullName()
        {
            return "Customer Not Found !";
        }
    }

Sonrasında service katmanını aşağıdaki gibi düzenleyelim.

    public class CustomerService
    {
        public AbstractCustomer GetCustomerByFirstName(string firstName)
        {
            return _customerRepository.Where(c => c.FirstName == firstName).FirstOrDefault().GetValue();
        }
    }
    public static class CustomerExtensions
    {
        public static AbstractCustomer GetValue(this AbstractCustomer customer)
        {
            return customer == null ? new NullCustomer() : customer;
        }
    }

Yukarıdaki kod bloğunda görüldüğü üzre repository null değer dönmek yerine yeni bir NullCustomer sınıfı return edecektir.

Son adım olarak da cient tarafında yazılacak kod ise yazımızın ilk başında yazdığımız kod bloğu ile aynı.

   var customerService = new CustomerService();
   var customer = customerService.GetCustomerByFirstName("tosuner");
   Console.WriteLine("FullName : " + customer.GetFullName() + "\nNumber Of Childreen:" + customer.NumberOfChildren);

Bu pattern ile;

  • null reference kontrollerinden kurtulduk,
  • duplicate kod oranını azalttık,
  • memory de değeri olmayan bir alana erişmek yerine null value görevi gören bir nesneye eriştik,
  • dahası client tarafı için daha temiz ve kolay anlaşılır bir kod bıraktık,

Daha öncede belirtiğim gibi bu pattern'i her zaman uygulama gibi bir durum söz konusu değil, daha doğrusu sürekli null check yapmak yerine bu pattern'i uygulayalım gibi bir düşünce doğru değil. Client-side geliştirme yapan developer'a bu kontrolleri yaptırmak istemediğimizde yada "ben server-side'dan hiçbir zaman null dönmicem.." şeklinde bir garanti vermek istediğinizde kullanabileceğimiz bir pattern dir.

Dapper ORM Nedir

Object relational mappers (ORMs); ilişkisel veritabanları ile programlama dillerinin nesne modelleri arasındaki uyumsuzluğu ortadan kaldırmak, kod ile database arasındaki uyumu %100 sağlamak için yazılım dünyasında uzun süredir kullanılmaktadırlar. Daha önceki ORM yazımızda Fluent NHibernate Nedir ve Kullanımı konusuna değinmiştik. Bu yazımızda ise birçok kişi tarafından en hızlı ORM olarak kabul edilen Dapper'ı inceleyeceğiz.

Dapper, Stack Overflow ekibi tarafından open-source geliştirilen lightweight bir ORM'dir. Lightweight olması sebebiyle diğer ORM'lere kıyasla çok daha hızlıdır.

Dapper, performans ve kullanım kolaylığı sağlaması düşünülerek geliştirilmiş bir ORM dir. Transaction, stored procedure yada bulk insery kullanarak static ve dynamic object binding yapabilmemizi sağlar.

Dapper Kullanımı

Dapper kullanımı için örnek basit bir app. geliştirelim. İlk olarak Vs'da Dapper_Sample adında bir ConsoleApp oluşturalım. Sonrasında nuget üzerinden Dapper'ı projemize indirip kuralım.

 Install-Package Dapper -Version 

Kurulum işlemi tamamlandıktan sonra örneğimiz şu şekilde olsun; ContosoCorp adında bir db ve bu db de Customer adında bir tablomuz olsun ve dapper kullanarak repository class ile bu tablo üzerinde CRUD işlemleri yapalım.

Öncelikle ContosoCorp db si için table create script'ini aşağıdaki gibi yazıp execute edelim ve Customer tablomuz oluşturalım.

create table Customer
(
 Id uniqueidentifier,
 FirstName nvarchar(50) not null,
 LastName nvarchar(50) not null,
 Email nvarchar(50) not null
)

İlk olarak tablomuza karşılık gelen Customer entity'sini aşağıdaki gibi oluşturalım.

    public class Customer
    {
        public Guid Id { get; set; }
        public string FirstName { get; set; }
        public string LastName { get; set; }
        public string Email { get; set; }
    }

Şimdi ise CRUD işlemleri için CustomerRepository adında bir class oluşturalım ve içerisini CRUD metotlarını tanımlayalım.

    public class CustomerRepository
    {
        protected SqlConnection GetOpenConnection()
        {
            var connection = new SqlConnection(ConfigurationManager.ConnectionStrings["ContosoCorpDbConn"].ConnectionString);
            connection.Open();
            return connection;
        }
    }

Config dosyamızda kullanacağımız db için gerekli olan conn. string'inin ContosoCorpDbConn adıyla yazılı olduğunu varsayalım ve GetOpenConnection() metodu ile repository metotlarında kullanacağımız SqlConnection yaratacağız.

Dapper'da bulunan Query() metodu veritabanından retrieve data işlemini yani veri almamızı sağlayan ve model mapping'i yaparak modellerimizi dolduran metoddur. Aşağıda yazacağımız All() metodu ile Customer tablosunda bulunan bütün kayıtları select edeceğiz. 

        public IEnumerable<Customer> All()
        {
            using (var conn = GetOpenConnection())
            {
                return conn.Query<Customer>("Select * From Customer").ToList();
            }
        }

İkinci metot olarak Id parametresi alarak geriye tek bir Customer objesi return eden Get() metodunu yazalım.

        public Customer Get(Guid id)
        {
            using (var conn = GetOpenConnection())
            {
                return conn.Query<Customer>("Select * From Customer WHERE Id = @Id", new { id }).SingleOrDefault();
            }
        }

Dapper'da bulunan Execute() metodu ise db de insert, update veya delete işlemlerinde kullanılır. Aşağıda yazacağımız metot ile parametre olarak verilen customer nesnesini insert edeceğiz.

        public void Insert(Customer customer)
        {
            using (var conn = GetOpenConnection())
            {
                string sqlQuery = "INSERT Customer(FirstName,LastName,Email) Values(@FirstName,@LastName,@Email)";
                conn.Execute(sqlQuery, customer);
            }
        }

Yukarıda görüldüğü üzre ado.net'e aşinalığı olan kişiler aslında bir nevi ado.net yazdığımızı göreceklerdir çünkü dapper'ın temelinde AdoçNet vardır. Son metot olarak dapper ile yukarıda yazdığımız All() metodunu birde stored-procedure kullanarak yazalım. İlk olarak db üzerinden aşağıdaki gibi GetAllCustomersSP adında stored-procedure'ü tanımlayalım.

CREATE PROCEDURE GetAllCustomersSP
AS
BEGIN
    SELECT * FROM Customer
END

Sonrasında bu procedure'ü execute eden repository metodunu yazalım.

        public IEnumerable<Customer> AllUsingSp()
        {
            using (var conn = GetOpenConnection())
            {
                return conn.Query<Customer>("GetAllCustomersSP", commandType: CommandType.StoredProcedure).ToList();
            }
        }

 Repository metotlarını tanımladık sırada bu metotları kullanmak var. Program.cs içerisinde bu metotları call ederek yazdığımız kodları test edelim.

        static void Main(string[] args)
        {
            var custRepo = new CustomerRepository();

            var customer1 = new Customer { Id = Guid.NewGuid(), FirstName = "Caner", LastName = "Tosuner", Email = "info@canertosuner.com" };
            var customer2 = new Customer { Id = Guid.NewGuid(), FirstName = "Berker", LastName = "Sönmez", Email = "info@berkersonmez.com" };

            custRepo.Insert(customer1);
            custRepo.Insert(customer2);

            //returns 2 records
            var allCustomersUsingQuery = custRepo.All();

            //returns 2 records
            var allCustomersUsingSp = custRepo.AllUsingSp();

            Console.ReadLine();
        }

Projemizi run ettiğimizde Customer tablosuna sırasıyla 2 kayıt atacaktır ve sonrasında All() ve AllUsingSp() metotlarını kullanarak tabloya select sorgusu yaptığımızda bize yukarıda kaydettiğimiz 2 kaydı return edecektir.

Dapper aynı zamanda transactional işlemleri de destekler. UnitOfWork pattern'i BeginTransaction() ve EndTransaction() metotlarını kullanarak uygulanabilir.

 

Not : Nuget de bulunan DapperExtensions kütüphanesi kullanarak gerekli mapping işlemlerini yaptıktan sonra repository metotları içerisinde sql komutları yerine aşağıdaki gibi de kullanabiliriz. Not: Bu mapping üzerinden table/column auto generate işlemi olmamakta sadece mapping işlemini sağlamakta.

Install-Package DapperExtensions 

    public class CustomerMapper : ClassMapper<Customer>
    {
        public CustomerMapper()
        {
            base.Table("Customer");
            Map(f => f.Id).Key(KeyType.Guid);
            Map(f => f.FirstName);
            Map(f => f.LastName);
            Map(f => f.Email);
        }
    }
        public IEnumerable<Customer> All()
        {
            using (var conn = GetOpenConnection())
            {
                return conn.GetList<Customer>().ToList();
            }
        }

        public Customer Get(Guid id)
        {
            using (var conn = GetOpenConnection())
            {
                return conn.Get<Customer>(id);
            }
        }

DapperExtensions kütüphanesi ile kolayca bir Generic Repository Pattern infrastructure'ı geliştirebilirsiniz. Yazının sadeliği açısından bu ben değinmedim ancak sonraki yazılarımızda bir IoC kütüphanesi kullanarak Dapper ile basitçe bir api projesi geliştireceğiz.

Dapper son derece geliştirmesi kolay ve kullanışlı bir ORM dir. NHibernate ve EntityFramework'de olduğu gibi model mapping'inizden table/column generate etmez ancak sql sorgularını oldukça hızlı bir şekilde execute eder ve result'larını POCO class'larınıza map'ler. Bu özelliğiyle developer'lar arasında oldukça popülerdir.

RabbitMQ MassTransit Kullanarak Producer Consumer Yapısı

Daha önceki RabbitMQ Nedir yazımızda genel hatlarıyla rabbitmq dan bahsedip windows makina üzerinde kurulumunu anlatıp kısaca; erişilebilirliği, güvenilirliği yüksek ve ölçeklenebilen loosly-coupled asenkron iletişim sağlayan uygulamalar geliştirmektir diye tanımlamıştık.

Bu yazımızda ise Masstransit kullanarak bir Producer Consumer projesi oluşturacağız ancak makinamızda rabbitmq kurulu çalışır vaziyette olduğundan emin olalım.

Enterprise Service Bus (ESB) Nedir ?

Enterprise service bus diğer ismiyle message broker; rabbitmq gibi messaging katmanlarının üzerinde bulunan ve distributed synchronous yada asynchronous communication sağlayabilen bir middleware' dir. Bize birbirinden tamamen farklı olabilen uygulamalar arasında message-based communication yapabilmemizi sağlayan bir transport gateway'i dir. Diğer bir deyişle; message'ları provider ve consumer arasında transport eden bir çeşit middleware tool'u dur. 

 

 

Şimdi ise masstransit kullanarak bir örnek üzerinden ilerleyelim. Örneğimiz şu şekilde olsun; günlük şirket bültenini tüm çalışanlara email olarak gönderen bir producer/consumer yapısı tasarlayalım.

VS da sırasıyla 3 proje oluşturacağız.

  1. RMQMessage(class lib.)
  2. RMQProducer(console app.)
  3. RMQConsumer(console app.)

1-) RMQMessage

İlk projemiz olan RMQMessage ile başlayalım. Vs da RMQMessage adından bir class-library oluşturalım. Masstransit'in message-based communication sağladığını söylemiştik ve hem producer hemde consumer tarafından kullanılacak bu message yada contract diyede adlandırdığımız model ve onun sub-model'lerini oluşturacağız. 

İlk olarak abstract olan BaseMessage.cs'i oluşturalım ve bu model içerisinde ortak olmasını istediğimiz property'ler yer alsın.

    public abstract class BaseMessage
    {
        public DateTime PublishedTime { get; set; }
        public DateTime ConsumedTime { get; set; }
        public Guid QueueId { get; set; }
    }

Sonrasında yukarıda bahsettiğimiz producer ve consumer'ın kullanacağı NewsletterMailMessage adında contract'ımızı aşağıdaki oluşturalım.

    public class NewsletterMailMessage: BaseMessage
    {
        public List<string> AddressList { get; set; }
        public NewsletterModel NewsLetter { get; set; }
    }
    public class NewsletterModel
    {
        public string MailSubject{ get; set; }
        public string HtmlContent { get; set; }
    }

Yukarıda modellerimizi oluşturduk ve RMQMessage projesindeki işimiz bitti. Şimdi sırada 2. projeyi oluşturmak var.

2-) RMQProducer

Bunun için aynı solution'da RMQProducer adında bir console app oluşturalım. Bu proje queue'ya ilgili message'ı publish etmeden sorumlu olacak. Projeyi oluşturduktan sonra nuget üzerinden package manager console kullanarak aşağıdaki masstransit.rabbitmq paketini yükleyelim.

Masstransit.RabbitMQ

 Install-Package MassTransit.RabbitMQ

Bu paket ile birlikte rabbitmq için kullanılan masstransit kütüphanesi onun dependency'leri projemize kurulmuş olacak.

Kurulum tamamlandıktan sonra RMQMessage projesini RMQProducer projesine reference olarak ekleyelim. Ekleme nedenimiz NewsletterMailMessage.cs modelini kullanabilmek.

Program.cs içerisine aşağıdaki gibi InitializeBus adında bir metot tanımlayalım ve bu metot IBusControl arayüzünü initialize etmekten sorumlu olsun.

    static IBusControl InitializeBus()
    {
        return Bus.Factory.CreateUsingRabbitMq(cfg =>
         {
             cfg.Host(new Uri("rabbitmq://localhost/"), h =>
             {
                 h.Username("guest");
                 h.Password("guest");
             });
         });
    }

local makinada kurulu olan rabbitmq url'i rabbitmq://localhost/ dir ve default username password ise guest olarak tanımlanmıştır.

RabbitMQ ya bağlantı kısmını halletik şimdi ise message objemizi initialize edelim.

    static NewsletterMailMessage InitializeMessage()
    {
        var message = new NewsletterMailMessage
        {
            AddressList = _customerService.GetAllMailAddresses(), //assume more than 2000
            NewsLetter = new NewsletterModel
            {
                MailSubject = "Daily Newsletter of Contoso Corp.",
                HtmlContent = "Lorem ipsum dolor sit amet, et nam mucius docendi hendrerit, an usu decore mandamus. Ei qui quod decore, cum nulla nostrud erroribus ut, est eu aperiri interesset. Legere mentitum per an. Hinc legimus nostrum cu vix."
            },
            PublishedTime = DateTime.Now,
            QueueId = Guid.NewGuid()
        };

        Console.WriteLine("Message published !\nSubject : " + message.NewsLetter.MailSubject + "\nPublished at : " + message.PublishedTime + "\nQueueId : " + message.QueueId);
        return message;
    }

Son adım olarak main fonksiyonu içerisinde bu iki metodu call ederek message'ı queue'ya push edip Producer projemizdeki işlemleri bitirelim.

   static void Main(string[] args)
   {
       var busControl = InitializeBus();
       busControl.Start();
       Console.WriteLine("Started publishing.");

       var message = InitializeMessage();

       busControl.Publish(message);
       Console.ReadLine();
   }

 

3-) RMQConsumer

Sırada son adım olan consumer projesini oluşturma var. Consumer Producer'ın gönderdiği message'ları dinleyerek ilgili queue için consume etmeden sorumlu. Projeyi oluşturduktan sonra nuget üzerinden package manager console kullanarak aşağıdaki masstransit.rabbitmq paketini yükleyelim.

Masstransit.RabbitMQ

 Install-Package MassTransit.RabbitMQ

Bu paket ile birlikte rabbitmq için kullanılan masstransit kütüphanesi onun dependency'leri projemize kurulmuş olacak.

Kurulum tamamlandıktan sonra RMQMessage projesini RMQConsumer projesine reference olarak ekleyelim. Ekleme nedenimiz NewsletterMailMessage.cs modelini kullanabilmek.

İlk olarak rabbitmq ile connection sağlama ve hangi queue kullanılacak gibi konfigurasyonları Pragram.cs içerisine tanımlayalım.

    static void Main(string[] args)
    {
        var busControl = Bus.Factory.CreateUsingRabbitMq(cfg =>
        {
            var host = cfg.Host(new Uri("rabbitmq://localhost/"), h =>
            {
                h.Username("guest");
                h.Password("guest");
            });

            cfg.ReceiveEndpoint(host, "DailyNewsletterMail", e =>
            e.Consumer<NewsletterMailMessageConsumer>());
        });

        busControl.Start();
        Console.WriteLine("Started consuming.");
        Console.ReadLine();
    }

Yukarıdaki kod bloğu şunu söylüyor; eğer rabbitmq local makinamızda kurulu ise adresi rabbitmq://localhost/ şeklindedir ve default userName-password guest olarak gelmektedir. Queue ismi olarak DailyNewsletterMail verdik ve bu queue için consume edilecek olan message da NewsletterMailMessageConsumer şeklinde belirttik.

Şimdi ise NewsletterMailMessageConsumer.cs adında message objemizi consume edecek olan kısmı geliştirelim.

public class NewsletterMailMessageConsumer : IConsumer<NewsletterMailMessage>
{
    public Task Consume(ConsumeContext<NewsletterMailMessage> context)
    {
        var message = context.Message;
        message.ConsumedTime = DateTime.Now;

        Console.WriteLine("Message consumed !\nSubject : " + message.NewsLetter.MailSubject + "\nConsumed at: " + message.ConsumedTime + "\nQueueId : " + message.QueueId);

        //todo send mail impr.

        return context.CompleteTask;
    }
}

Yukarıdaki kod blou şunu anlatıyor; NewsletterMailMessageConsumer adında IConsumer interface'inin implement eden ve bu implementasyon sonucunda Consume metoduna sahip olan bir consumer'ı mız var. Bu consumer DailyNewsletterMail queue'suna gelen NewsletterMailMessage modelini consume eder.

Sırasıyla Consumer'ı ve Producer'ı run ettiğimizde ilk olarak rmq management-console da DailyNewsletterMail queue'su aşağıdaki gibi listelenecektir.

 Producer ve Consumer projelerinin ayrı ayrı console çıktıları ise aşağıdaki gibi olacaktır.

Aynı QueueId ye ait message'ımız Producer dan çıkıp consumer'a ulaştığı bilgisini yukarıdaki gibi görüyoruz.

Queue yapıları özellikle async mesajlaşma gerektiren yerlerde oldukça önemlidir ve hayat kurtarır. Bu yazıda service bus'lar dan Masstransit kullanarak basit bir consume/producer uygulaması geliştirdik ve daha sonraki yazılarımızda diğer queue yapıları ile ilgilide konuları ele almaya devam edeceğiz.

RabbitMQ Nedir ? Windows Üzerinde Kurulumu

Messaging Queue (MQ), fire-and-forget communication dediğimiz asynchronous çalışma yapısı üzerine kurulmuş yapılar için günümüz yazılım dünyasının en popüler yapısıdır. Bu yapılara örnek olarak; JMS, MSMQ, RabbitMQ, Kafka etc. verebiliriz ve  genel çerçeveden baktığımızda messaging queue'ler bir sender-receiver şeklinde çalışırlar.

RabbitMQ Nedir

RabbitMQ, Erlang dili ile open-source olarak geliştirilen ve Open Telecom Platform kütüphanesi üzerinde build edilebilen günümüz server-to-server/app-to-app communication ihtiyaçları konusunda giderek popüler olan hızlı bir messaging queue yapısıdır. Advanced Message Queuing Protocol (AMQP) implement ederek uygulamalar ve server'lar arası veri alışverişini sağlar.

Rabbitmq Publisher ve Consumer mantığıyla çalışır. Örneğin data-exchange yapmanız gereken bir iş var bunu rabbitmq üzerinden publisher'ile ilgili queue'ya publish edip sonrasında bu queue'yu consume edecek bir consumer projesi oluşturup yapmak istediğimiz bu işlemi consumer'a yaptırabiliriz. Bu işlemler genelde ana uygulama üzerinde yapmak istemeyeceğimiz yükü fazla olan işlemler olabilir.

Daha basit anlatacak olursak; sunucu RabbitMQ sunucusuna bir message gönderir ve sunucu bu mesajı ilgili queue'ya yönlendirir. Sonrasında başka bir uygulama bu queue'yu dinler ve FIFO mantığıyla çalışan kuyruktaki bu mesajları consume ederek süreci sonlandırır. Sahip olduğu Web Management Interface ile de bulunan queue'ları görüntüleyebilme, requeue etme, delete, purge gibi daha bir çok işlemi yapabilmemizi sağlar.

Queue yapıları aslında projelerimiz için birer middleware görevi görmektedir. Ana uygulamanızdan queue ya push ettiğiniz message Consumer down olsa dahi o message'ı queue'da bekletir ve consumer tekrardan start olduğunda o message'ı tekrar tekrar push etmeye çalışır ve böylelikle veri kaybının da önüne geçmemizi sağlar. 

Bazı term'lere bakacak olursak;

Producer/Publisher : Queue'ya message'ı gönderen yapıya verilen isimdir.
Consumer : Queue'yu dinleyerek ilgili message'ları receive eden yapıdır.                                                                                                                                               Queue : First-in-first-out mantığıyla çalışan kuyruk yapımız.
Exchange : Routing yani kuyruğa iletilen message'ı route eden yapıdır ve routing işlemini yapan çeşitli yapılar bulunmaktadır.

 

Kurulum

İlk olarak rabbitmq erlang dili ile geliştirildiğinden makinamızda sahip olduğumuz işletim sistemine göre uygun versiyona ait erlang dosyalarını erlang.org sitesinden indirip kuralım.

 

Sonrasında ise rabbitmq.com sitesinden Windows için güncel rabbitmq server versiyonunu indirip kuralım.

Kurulumlar sorunsuz bir şekilde tamamlandıktan sonra yukarıda da bahsettiğimiz web arayüzünü aktifleştirelim. Bunun için rabbimq sbin dosyası içerisinde olan RabbitMQ Command Prompt'ı çalıştıralım ve aşağıdaki komut satırını yazalım.

> rabbitmq-plugins enable rabbitmq_management

RabbitMQ windows makinada Windows Service olarak çalışır ve yukarıda yazdığımız web arayüzünü enable etme komutunun hemen çalışabilmesi için rabbitmq'yu aşağıdaki gibi stop/start edelim.

> net service stop RabbitMQ
.....
> net service start RabbitMQ

Şimdi ise browser üzerinden http://localhost:15672/ adresine giderek login için default credential'lar username/password guest/guest olarak girdikten sonra aşağıdaki gibi arayüzü göreceğiz.

Şuan mevcutta herhangi bir queue oluşturmadığımızdan üstte bulunan exchange tab'ına tıkladığımızda aşağıdaki gibi default exchange listesini görüntüleyebiliriz.

RabbitMQ Windows Üzerinden kurulumu bu kadardı.

Sonraki yazılarda Exchange tool'larındanbirini kullanarak basit bir publisher consumer örneği ile devam edeceğiz.                               

                       

 

StructureMap Nedir ? WebApi ile Kullanımı

Daha önceki IoC container yazılarında Ninject ve Windsor 'dan bahsetmiştik. Bu yazımızda ise 2016 benchmark'larına göre en hızlı IoC container olduğu söylenen StructureMap'i WebApi üzerinde örnek proje ile inceleyeceğiz. 

StructureMap ilk release'i .Net framework 1.1 için 2004 yılında çıkmış ve 12 yıldır hayatımızda olan en eski IoC/DI tool'u dur. Uygulama genelindeki instance yönetiminden sorumlu olup bağımlılıkları enjecte edebilmemizi sağlar.

Yazımızda StructureMap kullanarak basit bir infrastructure tasarlamaya çalışacağız. 

İlk olarak Vs'da bir Web Api projesi oluşturalım.

Sonrasında projemize nuget üzerinden StructureMap.WebApi2 paketini install edelim.

Kurulum işlemi tamamlandıktan sonra solution'da DependencyResolution adında auto generate olan bir klasör ve hem bu klasör içerisinde hemde App_Start klasörü içerisinde StructureMap konfigurasyonlarını yapabilmemizi sağlayacak olan class'ları göreceğiz. Şimdilik bu klasörü es geçelim projemizi hazır hale getirdikten sonra gerekli register işlemlerini yaparız. 

Örnek case'imiz şu şekilde olsun; UserController adında bir controller oluşturalım ve bu controller içerisinde tanımlı IUserService intercase'ini contructor injection yöntemi ile inject edelim. GetUserFullNames adında end-point ile geriye List of string dönelim.

    public class UserController : ApiController
    {
        private readonly IUserService _userService;
        public UserController(IUserService userService)
        {
            _userService = userService;
        }

        [HttpGet]
        public HttpResponseMessage GetUserFullNames()
        {
            var response = _userService.GetUserFullNames();

            return Request.CreateResponse(response);
        }
    }

Controller içerisinde kullandığımız IUserService interface'i ve onun implemantasyonunu aşağıdaki gibi oluşturalım.

public interface IUserService
{
    List<string> GetUserFullNames();
}
 
public class UserService : IUserService
{
    public List<string> GetUserFullNames()
    {
        return new List<string>
                              { "Olcay Şahan",
                                "Anderson Talisca",
                                "Oğuzhan Özyakup",
                                "Ricardo Quaresma",
                                "Cenk Tosun" };
    }
}

Yukarıda da söylediğimiz gibi UserController içerisindeki GetUserFullNames metodu HttpGet isteği alarak geriye UserService içerisinde bulunan GetUserFullNames metodunun return ettiği List of string'i dönecektir.

Örneğimiz hazır. Şimdi ise geriye son 2 adım kaldı.

1-) StructureMap ile ilgili container konfigurasyonlarını yapmak. DependencyResolution klasörü içerisinde bulunan DefaultRegistry adlı class'a gidip aşağıdaki gibi UserService'i container'a register edeceğiz.

    public class DefaultRegistry : Registry {
        #region Constructors and Destructors

        public DefaultRegistry() {
            Scan(
                scan => {
                    scan.TheCallingAssembly();
                    scan.WithDefaultConventions();
                });
            For<IUserService>().Use<UserService>();
        }

        #endregion
    }

2-) Son olarak App_Start/WebApiConfig.cs class'ına gidip DI container'ı start etmemiz gerekiyor. 

    public static class WebApiConfig
    {
        public static void Register(HttpConfiguration config)
        {
            // Web API configuration and services

            // Web API routes
            config.MapHttpAttributeRoutes();

            config.Routes.MapHttpRoute(
                name: "DefaultApi",
                routeTemplate: "api/{controller}/{id}",
                defaults: new { id = RouteParameter.Optional }
            );
            
            //structureMap start
            StructuremapWebApi.Start();
        }
    }

Hem örneğimiz hemde container ile ilgili konfigurasyonlarımız hazır. Artık projemizi run edip yazdığımız kodları test edebiliriz. Bunun için projemizi run edelim ve yazmış olduğumuz http-Get end-point'ini browser da çağıralım.

Yukarıda görüldüğü gibi UserController da bulunan end-point'e call yaptık ve bize container'da bulunan IUserService'ine ait olan implementasyonu resolve edip UserService içerisinde bulunan GetUserFullNames metodunu execute edip geriye user listesini return etti

 StructureMap yazımız şimdilik bu kadar. İlerleyen günlerde daha farklı DI Container yazılarına devam edeceğiz.

Optimistic Lock Nedir ? Pessimistic Lock Nedir ? Data concurrency

Db'si olan ve son kullanıcı tarafından CRUD işlemlerinin bolca yapılabildiği bir proje geliştiriyorsanız veri tutarlılığı sizin için oldukça önemli bir hal almak durumundadır. Kayıtlı olan veriyi son kullanıcıya ulaştırabilip en güncel veri üzerinden transaction'ları işleyebilmek ve stale yada dirty data olarak da adlandırılan bayat veriyi handle edip kullanıcının erişmesini engellemek oldukça önemlidir. 

Transactional operasyonlar Concurrency'yi sağlayabilmek adına genelde üzerinde işlem yapılan veriye lock işlemi uygulanarak gerçekleştirilirler. Bu lock işlemi için 2 farklı yaklaşım vardır. Pessimistik Lock ve Optimistic Lock.

Pessimistic Lock

O an işlem gerçekleşirken üzerinde çalışılan kayıt lock'lanır ki o anda başka birisi o kayıt üzerinde değişiklik yapmasın. Bu işlem session bazlı olur ve transaction başlarken açılan session sonlandırılıncaya veya rollback yapılıncaya kadar işlem yapılan row db de lock'lanır. Örnek olarak bir bankacılık uygulaması düşünün ve bir hesaba aynı anda hem para çekme hemde para yatırma işlemi geldi. İlk para yatırma işlemini yapan thread öncelikli düşündüğümüzde bu transaction'ı gerçekleştiren session o account'u işlem sonlanıncaya kadar lock'lar ve para çekme işlemini bekletir. Transaction sonlandıktan sonra diğer thread'in gerçekleştireceği para çekme işleminin session'nını açarak güncel veri üzerinden işlemlerin gerçekleşmesine olanak sağlar. Bunu yapmasındaki amaç güncel veri üzerinden transaction'ı geçirip oluşabilecek kayıpları engellemektir. Ancak pessimistic lock'ın deadlock'lara sebep olabileceğini de unutmayalım.

 

Optimistic Lock

Optimistic Lock ise, adından da anlaşılacağı üzre "iyimser" birden fazla işlemin birbirini etkilemeden gerçekleşeceğini ve kimsenin kimse üzerinde bir lock koymayacağını söyler. Diğer bir deyişle farklı thread'ler de aynı row üzerinde işlem yapılırken herhangi bir lock işlemi olmadan update edilmek istenen verinin bayat olup olmadığını o verinin kayıtlı olduğu tabloda yer alan versiyon numarası olarak da adlandırılan bir column'da bulunan değeri kontrol eder ve eğer versiyon eşleşmiyorsa yani veri bayat ise işlem geri çekilir.

Peki bayat(stale) data ne demek ?

Örnek üzerinden anlatacak olursak; bir internet sitesinde kayıtlı bulunan adres bilginizi güncellemek istiyorsunuz. Aynı anda 2 farklı bilgisayardan bilgileri güncelle sayfasını açtınız ve adresiniz o an "Samsun" olarak kayıtlı yani 2 ekranda da "Samsun" yazıyor. İlk bilgisayarda bulunan kişi adres bilgisini "Ankara" olarak değiştirdi ve güncelle butonuna basıp bilgiyi güncelledi.

İkinci ekranda bulunan kişi ise ekranda halen "Samsun" yazılı iken adres bilgisini "İstanbul" olarak değiştirdi ve güncelle butonuna basıp bilgiyi güncelledi. Ekranda yazan "Samsun" kaydı artık bizim için bayat bir kayıttır ve birinci kullanıcı değişikliği "Samsun" => "Ankara" yaptığını düşünürken ikinci kişi bu değişikliği "Samsun" => "İstanbul" yaptığını düşünüyor. Halbuki gerçekte olan ikinci kişi adres bilgisi ekranda "Ankara" iken => "İstanbul" olarak değiştirmiş oldu.

Ne oldu ? Pek de istemediğimiz bir case oluştu. İkinci kullanıcı Samsun olan kaydı İstanbul yaptığını düşünürken aslında Ankara olan kaydı İstanbul yaptı. Yani stale olan kaydı güncellemiş oldu.

Optimistic Lock ile ikinci kullanıcının stale olan veriyi update etmesine şu şekilde engel olabiliriz; Eğer Ado.Net kullanıyorsanız ve db de bulunan her bir row icin birer versiyon

numarasi vb gibi kaydinizvar ise query nizde where koşuluna o row için güncel olarak bulunan version bilgisini ekleyerek kontrol sağlayabiliriz veya Entity Framework yada NHibernate gibi ORM tool'larından birini kullanıyorsanız bu işlemi size bırakmadan güncellenmek istenen row'a ait versiyon numarasını select işleminde memory de tutuyor ve o veri için update transaction'ı execute edilirken bu versiyon numarası db de karşılaştırıyor. Eğer o versiyon numarası db de bulunan ile aynı ise versiyon numarasını 1 artırıp execution'a izin veriyor değilse hata fırlatıyor. Hata mesajı olarak kullanılan ORM türüne göre "The record you attempted to edit was modified by another user after you got the original value" gibi bir message return ediyor.

 

Hem optimistic hemde pessimistic lock konuları çok fazla önemsemediğimiz anlar olsa da oldukça önemli konulardır. Sonraki yazılarımızda Nhibernate veya Entity Framework kullanarak nasıl bir Optimistic Lock yapısı implement edebiliriz inceleyeceğiz.

IoC Inversion of Control nedir ?

IoC den önce gerilere gidip Dependency Injection'ın tanımınada değinelim. Dependency Injection kısaca "bağımlılıkların  loose coupled yani gevşek bağlı bir şekilde dışarıdan enjecte edilmesi" şeklinde tanımlayabiliriz.

Inversion of Control (IoC) ise bir yazılım tasarım prensibidir ve basit tabiriyle nesnelerin uygulama boyunca ki yaşam döngüsünden sorumludur diyebiliriz. Uygulama içerisinde kullanılan objelerin instance'larının yönetimi sağlar ve bağımlılığı en aza indirgemeyi amaçlar. 

Container program içerisinde request edilen nesneleri abstraction'lara bağlı tutarak otomatik olarak oluşturan ve bağımlılıklarını inject eden bir framework diyebiliriz. Oluşturmuş olduğu bu nesneleri kendi içerisinde yönetimini yaparak tekrardan ihtiyaç duyulduğunda yeni bir instance oluşturmak yerine mevcut olan nesneyi atar.

IoC daha kolay test edilebilir loose coupling dediğimiz gevşek bağlı ve reusable bir yazılım desene oluşturmamızı sağlar.

IoC ilk başlarda implementasyonundan dolayı zor ve karmaşık gibi görünsede geliştirme yaptıkça ve sağladığı kolaylıkları fark ettikçe hayran kalınacak bir yazılım tasarım prensibidir. Basitçe işleyişini anlatmak gerekirse; soyut tiplerin hangi somut tipler tarafından register edildiği bilgisini tutar. Uygulama içerisinde container'dan abstract bir nesne talebinde bulursunuz ve size register bilgisinde tanımlı olan concrete type'ın instance'ını oluşturup verir. Bir tür object factory olarak düşünebilirsiniz.

IoC için kullanılabilecek çeşitli kütüphaneler bulunmakta. Bunlardan en popüler olanlarını ;

şeklinde sıralayabiliriz. Bu kütüphaneleri kullanmayıp kendi IoC infrastructure'ınızıda yaratabilirsiniz ancak instance yönetimi dışında bu kütüphanelerin sağladığı aspect oriented özellikleri de hayli önemli bir diğer özelliktir. 

Framework ler arasından en performanslı olan hangisi sorusunu soracak olursak internette araştırırken bir benchmark testine denk geldim ve aşağıdaki koşullar sağlanarak yapılan benchmark sonucuna göz atalım.

Test Verisi

  • 8 levels of depth for the dependency tree
  • 100 types per level
  • Between 0 and 8 dependencies for each type (excluding level 0)
  • 1 in 5 types are registered as singleton (20%)

 

Sonuç

Container 2012 Version 2012 Time Elapsed (Sec) 2014 Version Time Elapsed (Sec)
Ninject 3.0.1.10 31.29 3.2.2.0 30.84
StructureMap 2.6.2 1.98 3.1.4.143 1.95
Autofac 2.6.3.862 5.56 3.5.2 5.19
Castle Windsor 3.1.0 5.47 3.3.0 5.59
Unity 2.1.505.2 7.76 3.5.1404.0 3.71
SimpleInjector 1.5.0.12199 34.43 2.5.2 48.01
Dynamo 3.0.0.1 Fail 3.0.2 Fail
Hiro 1.0.2 Fail 1.0.2 Fail

 

Görüldüğü üzre StructureMap bütün 800 tipi register ve resolve etmede en hızlı olan ancak en çok kullanılan olarak bakacak olursak Castle Windsor galip geliyor. Unity'nin de son sürümüyle birlikte bugün itibariyle en hızlı olan IoC framework'ü olduğu söylenmekte.

IoC ile ilgili bugünlük bu kadar diyelim gelecek yazılarımızda seçtiğimiz bir IoC framework'ünü kullanarak örnek projeler geliştiriyor olacağız.

Repository Katmanı için Mocking Infrastructure Oluşturma (Moq Library)

Daha önceki Unit Test yazılarımızda Unit Test Nedir Nasıl Yazılır ve Moq Library Kullanarak Unit Test Yazma konularına değinmiştik. Bu yazımızda ise çokça kullandığımız Repository Pattern CRUD işlemlerinin yapıldığı metotlar için reusable bir mocking yapısı oluşturacağız. 

Öncelikle VS'da RepositoryMocking adında bir proje oluşturalım ve sonrasında projemize Generic Repository ile ilgili tanımlamalarımızı yapalım. İlk olarak IRepository adında bir interface ve database de bulunan tablolardaki unique Id-primary key alanına karşılık gelen generic IUniqueIdentifier interface'ini oluşturalaım. Bu interface'i oluşturmamızdaki amaç her tabloda Id alanı farklı tiplerde olabilir bu nedenle objelerimizi oluştururken IUniqueIdentifier interface'inden implement ederek Id alanı için veri tipini belirteceğiz. Bu bize test metotlarımızı tanımlarken ilgili linq sorgularını oluşturmada yarar sağlayacak.

public interface IUniqueIdentifier<Tkey>
{
    TKey Id { get; set; }
}
 
public interface IRepository<Tkey,TEntity> where TEntity : IUniqueIdentifier<Tkey>
{
    IQueryable<TEntity> All();
    TEntity Get(TKey Id);
    TEntity Add(TEntity entity);
    void Update(TEntity entity);
    void Delete(TEntity entity);
}

Şimdi ise abstract olan ve IRepository den inherit olan BaseRepository class'ını oluşturalım.

    public abstract class BaseRepository<TKey, TEntity> : IRepository<TKey, TEntity> where TEntity : IUniqueIdentifier<TKey>
    {
        public IQueryable<TEntity> All()
        {
            throw new NotImplementedException();
        }

        public TEntity Get(TKey id)
        {
            throw new NotImplementedException();
        }

        public TEntity Add(TEntity entity)
        {
            throw new NotImplementedException();
        }

        public void Update(TEntity entity)
        {
            throw new NotImplementedException();
        }

        public void Delete(TEntity entity)
        {
            throw new NotImplementedException();
        }
    }

Mocking işlemi yapacağımızdan metot içlerini doldurmadım ancak tabikide ilgili linq sorgularının yazılmasını gerekir.

BaseRepository tanımlamasını da yaptıktan sonra database de bulunan User tablosu için bir object ve bu tabloya ait UserRepository class'ını oluşturalım. 

    public class User : IUniqueIdentifier<int>
    {
        public int Id { get; set; }
        public string FirstName { get; set; }
        public string LastName { get; set; }
        public string Email { get; set; }
    }
    public class UserRepository : BaseRepository<int, User>
    {

    }

Buraya kadar olan kısımda Repository katmanı için gerekli olan her şey hazır. Artık Test projemizi oluşturabiliriz. Solution'a RepositoryMocking.UnitTest adında yeni bir test projesi oluşturalım ve içerisine RepositoryBaseTest adında bir class ekleyelim. Bu class reusable mocking setup işlemlerini yapacağımız class olacak.

 public abstract class RepositoryBaseTest
    {
        public void SetupRepositoryMock<TK, TE>(Mock mockRepo, List<TE> data) where TE : class, IUniqueIdentifier<TK>
        {
            var mock = mockRepo.As<IRepository<TK, TE>>();

            // setup All method
            mock.Setup(x => x.All()).Returns(data.AsQueryable());

            // setup Add method
            mock.Setup(x => x.Add(It.IsAny<TE>()))
                .Returns(new Func<TE, TE>(x =>
                {
                    dynamic lastId = data.Last().Id;
                    dynamic nextId = lastId + 1;
                    x.Id = nextId;
                    data.Add(x);
                    return data.Last();
                }));

            // setup Update method
            mock.Setup(x => x.Update(It.IsAny<TE>()))
                .Callback(new Action<TE>(x =>
                {
                    var i = data.FindIndex(q => q.Id.Equals(x.Id));
                    data[i] = x;
                }));

            // setup Get method
            mock.Setup(x => x.Get(It.IsAny<TK>()))
                .Returns(new Func<TK, TE>(
                    x => data.Find(q => q.Id.Equals(x))
                ));

            // setup Delete
            mock.Setup(x => x.Delete(It.IsAny<TE>()))
                .Callback(new Action<TE>(x =>
                {
                    var i = data.FindIndex(q => q.Id.Equals(x.Id));
                    data.RemoveAt(i);
                }));
        }
    }

Üstte bulunan kodlarda BaseRepository de bulunan db için All, Get, Insert, Update ve Delete işlemlerini yapacak olan metotlar için ortak bir setup yapısı oluşturduk ve UserRepository gibi diğer oluşturacağınız repository ler içinde RepositoryBaseTest class'ını kullanabileceğiz. Buda bizi her bir repository için ayrı ayrı setup işlemleri yapmaktan kurtarıyor. IRepository interface'ine yeni bir metot eklemek istediğinizde tekrardan yukarıda yazdığımız SetupRepositoryMock içerisine bu metot için gerekli setup işlemini tanımlayabiliriz. 

Şimdi ise UserRepository için UserRepositoryTest adında bir sınıf oluşturalım ve RepositoryBaseTest class'ını kullanarak mock işlemleri yapalım.

    [TestClass]
    public class UserRepositoryTest: RepositoryBaseTest
    {
        //db de bulunan tablo yerine geçecek fake tablomuz
        private List<User> _userList;

        //mock user repository
        private Mock<IRepository<int, User>> _mockRepo;

        [TestInitialize]
        public void Setup()
        {
            //tablomuzun içerisini dolduralım
            _userList = new List<User>();
            var user1 = new User
            {
                Id = 1,
                Email = "canertosuner@gmail.com",
                FirstName = "Caner",
                LastName = "Tosuner"
            };
            _userList.Add(user1);

            var user2 = new User
            {
                Id = 2,
                Email = "tanertosuner@gmail.com",
                FirstName = "Taner",
                LastName = "Tosuner"
            };
            _userList.Add(user2);

            var user3 = new User
            {
                Id = 3,
                Email = "janertosuner@gmail.com",
                FirstName = "Janer",
                LastName = "Tosuner"
            };
            _userList.Add(user3);

            var user4 = new User
            {
                Id = 4,
                Email = "yenertosuner@gmail.com",
                FirstName = "Yeneer",
                LastName = "Tosuner"
            };
            _userList.Add(user4);
             
            //mock respository değerini initialize edelim
            _mockRepo = new Mock<IRepository<int, User>>();

            //repositorybasetest class'ını kullarak crud metotlarını için setup işlemlerini yapalım
            SetupRepositoryMock<int, User>(_mockRepo, _userList);
        }
    }

UserRepository için setup işlemlerimizi tamamladık. Yukarıdaki işlemler sonrasında elimizde db de bulunan User tablosu yerine geçen bir _userList array'imiz ve bu array üzerinden repository metotlarını setup ettik. Şimdi bir kaç test metodu yazıp kodlarımızı test edelim. UserRepositoryTest class'ının son hali aşağıdaki gibidir.

    [TestClass]
    public class UserRepositoryTest: RepositoryBaseTest
    {
        private List<User> _userList;
        private Mock<IRepository<int, User>> _mockRepo;

        [TestInitialize]
        public void Setup()
        {
            _userList = new List<User>();
            var user1 = new User
            {
                Id = 1,
                Email = "canertosuner@gmail.com",
                FirstName = "Caner",
                LastName = "Tosuner"
            };
            _userList.Add(user1);

            var user2 = new User
            {
                Id = 2,
                Email = "tanertosuner@gmail.com",
                FirstName = "Taner",
                LastName = "Tosuner"
            };
            _userList.Add(user2);

            var user3 = new User
            {
                Id = 3,
                Email = "janertosuner@gmail.com",
                FirstName = "Janer",
                LastName = "Tosuner"
            };
            _userList.Add(user3);

            var user4 = new User
            {
                Id = 4,
                Email = "yenertosuner@gmail.com",
                FirstName = "Yeneer",
                LastName = "Tosuner"
            };
            _userList.Add(user4);

            _mockRepo = new Mock<IRepository<int, User>>();

            SetupRepositoryMock<int, User>(_mockRepo, _userList);
        }

        [TestMethod]
        public void Get_All_Count()
        {
            Assert.AreEqual(_userList.Count, _mockRepo.Object.All().Count());
        }

        [TestMethod]
        public void Get_By_Id_Then_Check_Name()
        {
            var item = _mockRepo.Object.FindBy(4);
            Assert.AreEqual("Yeneer", item.FirstName);
        }

        [TestMethod]
        public void Remove_User_Then_Check_Count()
        {
            var user4 = new User
            {
                Id = 4,
                Email = "yenertosuner@gmail.com",
                FirstName = "Yeneer",
                LastName = "Tosuner"
            };
            _mockRepo.Object.Delete(user4);
            Assert.AreEqual(3, _mockRepo.Object.All().Count());
        }

        [TestMethod]
        public void Add_New_User_Then_Check_Count()
        {
            var tempCount = _userList.Count;

            var user5 = new User
            {
                Email = "yenertosuner@gmail.com",
                FirstName = "Yeneer",
                LastName = "Tosuner"
            };
            _mockRepo.Object.Add(user5);

            Assert.AreEqual(tempCount + 1, _mockRepo.Object.All().Count());
        }
    }

RepositoryPattern için reusable mocking işlemi için hepsi bu kadar. Projenizde UserRepository dışında bulunan diğer repository'ler içinde aynı UserRepository de olduğu gibi generic oluşturduğumuz RepositoryBaseTest'i kullanarak setup işlemini yapıp testlerinizi yazabilirsiniz.

Redis Server Windows Üzerinde Kurulumu ve Kullanımı

Bu yazıda Distributed Caching sistemlerinden biri olan Redis'i inceliyor olacağız.

Redis Nedir ?

Redis için kısaca open source bir NOSQL Memcached veritabanı sistemidir diyebiliriz. Her ne kadar ilk olarak Linux için tasarlanmış olsada ihtiyaç doğrultusunda Windows işletim sistemlerinde de kullanılabilir hale getirildi. Çalışma şekli olarak Key-Value şeklinde gönderilen bilgileri store etmektedir. 

 

Veri Tipleri

Redis verileri String, Hashe, List, Set ve Sorted List olarak saklayabilir.

 Veri tipleri ile ilgili daha ayrıntılı bilgiyi bu linkte bulabilirsiniz. 

Kurulum ve Kullanımı

Öncelikle Redis'i indirip service olarak bilgisayarımıza kuruyoruz. Bunun için bu linkten sizin için uygun olan .rar uzantılı sürümü bulup bilgisayarımıza indiriyoruz. Sonrasında indirmiş olduğunuz dosyalardan redis-server.exe adlı exe'yi çalıştırıp kurulumu yapıyoruz. Default olarak 6379 port'unu hizmete sokar ancak istersek bunu değiştirebiliriz de. Exe çalıştıktan sonra aşağıdaki gibi bir ekran gördüyseniz kurulum OK dir.

Redis çalışıp çalışmadığına dair kontrol için redis-cli.exe'yi çalıştıralım ve aşağıdaki resimde olduğu gibi test amaçlı bir key-value tanımlayıp sonrasında get set işlemi yapalım

Bu yazımızda Windows üzerinde Redis Server nasıl kurulur ve kullanılır bunu gördük. Bir sonraki Redis yazımızda StackExchange.Redis redis client kullanarak NET dilleri için (C# etc) örnek proje yapıyor olacağız.