Domain Driven Design ne zaman uygulanmalıdır

28 Şub

Bir önceki yazının konusu Domain Driven Design(DDD) konulu bir kitap tanıtımıydı. Bu yazıda DDD yaklaşımının hangi durumlarda kullanılması gerektiğini bir kaç cümleyle özetlemeye çalışacağım. Yaklaşımın özelliklerini daha ilerki yazılarda fırsat buldukça anlatmaya çalışacağım.

Domain Driven Desing(DDD) kavramı karmaşık problemlerin küçük parçalara bölünerek ele alınmasını amaçlayan bir yaklaşım tarzıdır. Karmaşık problemlerin parçalara ayrılması da aslında çözülmesi gereken bir problemdir. Taleplerin alınması, iş kurallarının
belirlenmesi ve taleplerin yazılım terminolojisine uygun hale getirilmesi sürecin başlıca gereksinimlerdir. DDD birbiriyle ilişkili iş kurallarının ele alındığı bir yöntem olması sebebiyle çok geniş kapsamlı kurumsal yazılımlar geliştirirken uygulanması gereken bir yaklaşımdır. Örneğin hava yolu rezervasyon sistemleri, e-ticaret uygulamaları, bir devlet kurumu bünyesindeki otomasyon sistemleri şeklinde örnekler verilebilir. İş kurallarının sık olarak değiştiği ve taleplerin sürekli artacağı öngörülen yazılım sistemleri için yazılımın geleceğine aydınlık bir altyapı tasarlamak amacıyla uygulanabilir. Örneğin kanun ve yönetmeliklerin çok sık değiştiği  kurumlarda geliştirilen yazılımları gibi. Ancak karmaşık iş kuralları olmayan basit sistemler için DDD uygulamak uygun olmaz. Örneğin blog sayfaları veya ürün tanıtım amaçlı web projeleri için DDD uygulamak uygun değildir. Bu tür uygulamalar sadece  veri erişimi ve görüntülemesi amaçlanmıştır. Bu tür uygulamaları veriyi kullanıcı ara yüzüne direk aktaran teknikler ile geliştirmek mantıklı bir seçim olacaktır. Aksi taktirde  basit olan bir çözümü, karmaşık hale getirmiş oluruz.

Kitap – Domain Driven Design

27 Şub
Domain Driven Design
Domain Driven Design

Kitap tavsiye kategorisinin bu bölümünde Domain Driven Desing Tackling Complexity in the Heart of Software isimli kitaptan bahsedeceğiz.

Kitap Eric Evans tarafından kaleme alınmış olup dili ingilizcedir. Basılı kitabı buradan temin etmek mümkündür.

Kitapta karmaşık yapıdaki yazılımlar geliştirirken nasıl bir yazılım tasarımının uygulanması gerektiği konusu ele alınmıştır. Bu tasarım Domain Driven Design diye adlandırılmıştır.

Domain Driven Design yaklaşımı, problemin çözümünün domain diye adlandırılan bir alanda yapılması esasına dayanmaktadır. Burada problem, bir hava yolları rezervasyon sistemi veya bir e-ticaret sistemi tasarlamak olabilir. Bu noktada müşteri ve programcı karşı karşıya gelmektedir. Müşteri gereksinimleri, Domain Expert denen kişiler tarafından iyi bir şekilde tespit edilerek programcının anlayacağı bir şekle dönüştürülür. Programcı ve Müşteri arasında  sağlıklı bir iletişimin kurulabilmesi için Ubiquitous Language diye adlandırılan ortak bir dil oluşturulur. Gerekli iletişim sağlandıktan sonra sistem programcılar tarafından geliştirilmeye başlar.

Domain Driven Design yaklaşımı bize genel olarak dört temel katman üzerine inşaa önerir. Bunlar:

  • Presentation layer
  • Application layer
  • Domain layer
  • Infrastructure layer

Problemin çözümü için gerekli model Domain Layer üzerinde kurulur. Diğer katmanlar sunum ve iletişim katmanları olarak kullanılır.

Eric Evans’a göre Domain-Driven Design yaklaşımı, yazılım dünyasının popüler konularından birisi oldu ve kapsamlı yazılım projelerinde kullanılıp faydası görüldükçe yaygınlaşacak. Bunun yanında her projede Domain Driven Design yaklaşımını uygulamak yanlış olur. Sadece gerektiğinde kullanmak uygun olacaktır.

Asp.Net Web Api Message Handler Yapısı

21 Şub

Asp.Net Web Api 2 çatısı bünyesinde, Message Handler tipleri HTTP talebini alarak geriye yine HTTP cevap döndüren somut sınıflardır. Message Handler tipleri Web Api iletim hattında(pipeline) adeta bir zincir misali dizilmiş şekilde bulunur. Bu zincirdeki ilk handler talebi alır yapması gereken işlemi bitirdikten sonra talebi bir sonraki handler sınıfına aktarır. Bazı handler sınıfları talebi bir sonraki handler sınıfına aktarmaya gerek duymadan akışı kesip cevabı kullanıcıya döndürebilir. Bu işleyiş şekli Web Api tarafında delegating handler olarak adlandırılır. Asp.Net Web Api 2 versiyonuna ait mesaj yaşam döngüsüne ait şema buradaki posterde sunulmuştur.

Web Api iletim akışına dahil olan mevcut message handler tiplerine örnek verecek olursak:

  • HttpServer, talepleri alır.
  • HttpRoutingDispatcher, route kuralına göre talebi ilgili kaynağa yönlendirir.
  • HttpControllerDispatcher, talebi ilgili controller sınıfına gönderir.

Kendi oluşturduğumuz bir handler tipini akışa dahil etmemiz mümkündür. Örneğin, gelen HTTP talebi eğer PDF tipinde bir dosya istiyorsa cevap olarak yetkisiz kullanıcı şeklinde bir mesaj döndürebiliriz.

Server Handler

Server Handler

Resimde ifade edildiği üzere MessageHandler1 ve MessageHandler2 tipleri geliştiriciler tarafından özel olarak oluşturularak akışa dahil edilmiştir.

Kendi özel message handler tiplerimizi oluştururken, System.Net.Http.DelegatingHandler soyut(abstract) sınıfından bir sınıf türetmeliyiz. Daha sonra bu sınıfın SendAsync() metodunu override ederek talebi değerlendirebiliriz.

public class MessageHandler1 : DelegatingHandler
{
    protected async override Task<HttpResponseMessage> SendAsync(
          HttpRequestMessage request, CancellationToken cancellationToken)
    {
        // Talebin kontrol edildiği alan.

        var response = await base.SendAsync(request, cancellationToken);

        // Cevabın kontrol edildiği alan. (await, base.sendAsync() işlemi bitene kadar süreci bekletir)

        return response;
    }
}

SendAsync() metodu HTTP talebini  HttpRequestMessage tipinde alır ve geriye HttpResponseMessage tipinde asenkron olarak bir mesaj döndürür. base.SendAsync() metosu ise  mesajı Web Api akışındaki mevcut handler tipine aktarır. Bu durumda kullanıcıya dönen mesaj, dahili Web Api handler tipi tarafından sunulur.

Eğer Web Api akışındaki handler tiplerine işi bırakmadan kullanıcıya mesajı kendimiz vermek istiyorsak aşağıdaki gibi bir yöntem izlememiz gerekmektedir.

public class MessageHandler2 : DelegatingHandler
{
      protected override Task<HttpResponseMessage> SendAsync(
            HttpRequestMessage request, CancellationToken cancellationToken)
     {
        // HttpResponseMessage tipinde bir cevap oluşturulur.
        var response = new HttpResponseMessage(HttpStatusCode.OK)
        {
            Content = new StringContent("Hello!")
        };

        // Not: TaskCompletionSource sınıfı bir Task oluşturur ve sarmalar ancak delege almaz.
        var tsc = new TaskCompletionSource<HttpResponseMessage>();
        tsc.SetResult(response);
        return tsc.Task;
     }
}

webapi_handlers_04

Bu kodda dikkat edersek base.SendAsync() metodunu kullanmadık. HttpResponseMessage tipinde bir cevabı kendimiz oluşturarak ve kullanıcıya sunmuş olduk.

Oluşturduğumuz message handler tiplerini akışa dahil etmek için HttpConfiguration sınıfının MessageHandlers kolleksiyonuna eklememiz gerekmektedir. Asp.Net MVC 4 web uygulamasında bu işlem şu şekilde yapılabilir:

public static class WebApiConfig
{
       public static void Register(HttpConfiguration config)
       {
          config.MessageHandlers.Add(new MessageHandler1());
          config.MessageHandlers.Add(new MessageHandler2());
       }
}

Message handler eklemeleri yapılırken sıralama önemlidir. Çünkü akışta çalışma sıralaması bu şeklide olacaktır. Çalışma sıralamasını görmek için handler sınıfları içine Debug.WriteLine() medodu ile mesajlar yazdırıp debug modunda sıralamayı görebiliriz.

Eğer Web Api uygulaması self hosting yöntemiyle barındırılıyorsa konfigurasyon ayarlaması şu şekilde olacaktır:

var config = new HttpSelfHostConfiguration("http://localhost");
config.MessageHandlers.Add(new MessageHandler1());
config.MessageHandlers.Add(new MessageHandler2());

Bu şekilde eklenen message handler tipleri global olarak eklenmektedir ve sunucuya gelen her talebi etkiler. Ancak bu istenmeyen bir durum olabilir. Bir diğer yöntem ise talep edilen kaynağa göre Route ayarlaması sırasında message handler tiplerinin akışa dahil edilmesidir.

public static class WebApiConfig
{
     public static void Register(HttpConfiguration config)
     {
         config.Routes.MapHttpRoute(
             name: "Route1",
             routeTemplate: "api/{controller}/{id}",
             defaults: new { id = RouteParameter.Optional }
         );

         config.Routes.MapHttpRoute(
             name: "Route2",
             routeTemplate: "api/{controller}/{id}",
             defaults: new { id = RouteParameter.Optional },
             constraints: null,
             handler: new MessageHandler2() // per-route message handler
         );

         config.MessageHandlers.Add(new MessageHandler1());  // global message handler
     }
}

Bu örneğe göre eğer gelen talebe ait URI, Route2 ile eşleşirse talep MessageHandler2  tarafına yönlendirilir. Aşağıdaki diyagramda akış yönlendirmesi grafiksel olarak gösterilmiştir.

webapi_handlers_05

 

Talep önce global olarak tanımlanan MessageHandler1 üzerinden geçip ardından HttpRouteDispatcher tarafından belirlenen kurala göre yönlendirilmektedir. Burada dikkat edecek olursak MessageHandler2 kullanıldığında Web Api yapısındaki mevcut HttpControllerDispatcher tipi devre dışı kalmıştır. Bu nedenle Route2 ile eşleşen talepler hiçbir controller sınıfına uğramazlar. Bu sayede kendi özel tiplerimizle controller görevi gören mekanizmalar oluşturabiliriz.

Eğer route mekanizmasından geçen talebin tekrar bir HttpControllerDispatcher üzerinden geçmesini istiyorsak şu şekilde bir ayarlama yapmamız gerekir:

// List of delegating handlers.
DelegatingHandler[] handlers = new DelegatingHandler[] {
      new MessageHandler3()
};

// Create a message handler chain with an end-point.
var routeHandlers = HttpClientFactory.CreatePipeline(
                  new HttpControllerDispatcher(config), handlers);

config.Routes.MapHttpRoute(
       name: "Route2",
       routeTemplate: "api2/{controller}/{id}",
       defaults: new { id = RouteParameter.Optional },
       constraints: null,
       handler: routeHandlers
);

Bu yapıya ilişkin diyagram aşağıdaki gibidir.

webapi_handlers_06

 

Kaynak: http://www.asp.net/web-api/overview/advanced/http-message-handlers

Asp.Net Web Api ile Kullanıcı Doğrulama ve Yetkilendirme

15 Şub

Asp.Net Web Api ile oluşturulan uygulamalar dış dünyaya tamamen açık olmayacaksa yani bazı kaynaklar sadece kayıtlı kullanıcılara sunulacaksa bu noktada Api güvenliğinin sağlanması gerekmektedir. Api güvenliği temelde, sistem kullanıcıların kim olduğunu ve hangi kaynaklara erişebileceğini belirleyen bir mekanizmanın oluşturulması ile sağlanabilir.

Güvenlik mekanizmalarının oluşturulmasında kullanılan iki temel kavram vardır. Bunlar:

  • Kullanıcı Doğrulama (Authentication), bir kullanıcının kim olduğunu belirleme işlemidir.
  • Kullanıcı Yetkilendirme (Authorization), belirlenen kullanıcıların hangi kaynaklara erişeceğini belirleme işlemidir.

Kullanıcı Doğrulama (Authentication)

Web Api çatısı, kullanıcı doğrulama işleminin host tarafından gerçekleştiğini varsayar. Örneğin IIS üzerinde host edilmiş bir uygulama, ilgili HTTP modülü tarafından doğrulanır. Kendi yazdığımız bir HTTP modülü üzerinde de doğrulama işlemini gerçekleştirebiliriz. Ancak alternatif olarak doğrulama işlemi Web Api HTTP Message Handler tarafından da gerçekleştirilebilir. Bu yöntemde sorumluluk Web Api message handler tarafına yüklenmiş olur. Bu durum aşağıdaki sebeplerden dolayı geliştiricide bazı çekinceler meydana getirebilir:

  • HTTP modülleri, Asp.Net iletim hattı(pipeline) boyunca tüm talepleri kontrol eder, ancak Web Api message handler tipleri sadece Web Api üzerine yönlendirilen talepleri kontrol eder.
  • Geliştirici isterse Route mekanizmasına göre doğrulama yapabilir.
  • HTTP modülleri IIS için özeldir, message handler tipleri ise Web Api host ortamı için özeldir. Web host veya self host.
  • HTTP modülleri IIS logging ve denetim sürecine dahildir.
  • HTTP modülleri, iletim akışın başlamadan önce çalışır. Eğer message handler kullanırsanız handler çalışana kadar kimlik bilgileri belirlenmez.

Hangi yöntem kullanılmalı konusunda kısaca şunu söylemek gerekir ki: Eğer web host kullanılacaksa HTTP modül kullanmak iyi bir seçim olacaktır. Ancak self host kullanılacaksa Web Api message handler kullanmak uygun olacaktır.

Eğer kullanıcı doğrulama işlemini kendi özel sınıfınız yardımıyla gerçekleştirdiyseniz iki farklı noktaya kullanıcı bilgilerini kaydetmelisiniz. Bunlar:

  • Thread.CurrentPrincipal
  • HttpContext.Current.User
private void SetPrincipal(IPrincipal principal)
{
   Thread.CurrentPrincipal = principal;
   if (HttpContext.Current != null)
   {
       HttpContext.Current.User = principal;
   }
}

Self host durumunda Thread.CurrentPrincipal kullanılır, Web host durumunda ise HttpContext.Current.User kullanılır.

Kullanıcı Yetkilendirme (Authorization)

Yetkilendirme işlemi, iletim hattında(pipeline) doğrulama işleminden sonra gelir. Yetkilendirme sırasında önce kullanıcının doğrulanmış olup olmadığı kontrol edilir. Yetkilendirme işlemi Controller veya Action seviyesinde gerçekleştirilebilir. Akışta yetkilendirme filitreleri controller ve action metodlarından önce çalışır.

webapi_auth01

Web Api uygulamalarında yetkilendirme işlemi filtreler akışa dahil edilir. Varsayılan olarak AuthorizeAttribute filtresi vardır. Filtreler iletim hattında ki akışa 3 şekilde dahil edilebilir.

  • Global seviyede
  • Controller seviyesinde
  • Action metod seviyesinde

Filtre aşağıdaki şekilde global seviyede akışa dahil edilebilir.

public static void Register(HttpConfiguration config)
{
    config.Filters.Add(new AuthorizeAttribute());
}

Eğer filtreler global olarak akışa dahil edilirse tüm talepler doğrulama işlemi için kontrol edilir. Eğer doğrulanmamış ise kullanıcıya Http 401 hata kodu döndürülür.

Controller seviyesinde sınırlandırma yapmak için filtre, attribute olarak kullanılır.

[Authorize]
public class ValuesController : ApiController
{
     public HttpResponseMessage Get(int id) { ... }
     public HttpResponseMessage Post() { ... }
}

Action seviyesinde sınırlandırma yapmak için de aynı attribute kullanılır.


public class ValuesController : ApiController
{
     [Authorize]
     public HttpResponseMessage Get(int id) { ... }
}

Bir controller sınıfı Authorize attribute ile işaretlendiğinde içerisindeki tüm action metodları  sınırlı erişime dahil olur. Ancak bazı action metodları sınırlamadan çıkarmak için AllowAnonymous attribute kullanılabilir.

[Authorize]
public class ValuesController : ApiController
{
    [AllowAnonymous]
    public HttpResponseMessage Get(int id) { ... }
    public HttpResponseMessage Post() { ... }
}

Authorize attribute kullanırken kullanıcı adı ve rol adı belirlemek de mümkündür. Bu şekilde sadece ilgili rol veya kullanıcıya izim verilmiş olur. Örnek:

[Authorize(Users="Bayram")]
public class ValuesController : ApiController{}

[Authorize(Roles="Admin")]
public class ValuesController : ApiController{}

Özel Yetkilendirme

Kendi özel sınıflarımızla yetkilendirme işlemini gerçekleştirmek istiyorsak oluşturacağımız sınıfı aşağıdaki tiplerden birinden türetmek gerekir.

  • AuthorizeAttribute, geçerli kullanıcı ve rolüne göre dayalı doğrulama için kullanılır.
  • AuthorizationFilterAttribute, senkron bir yetkilendirme için kullanılır. Geçerli kullanıcı olmasına gerek yoktur.
  • IAuthorizationFilter, asenkron bir yetkilendirme için kullanılır. Örneğin dosya sistemi veya network kullanıdığı durumlarda.

webapi_auth02

Kaynak: http://www.asp.net/web-api/overview/security/authentication-and-authorization-in-aspnet-web-api

 

Blog temasında değişiklik yapıldı

14 Şub

WordPress tarafında default temalar arasında olan ve benim de uzun yıllardır kullandığım iNove temasını değiştirmeye karar verdim. iNove teması worpress resmi sitesinde hala yer almakta. Ancak güncelliğini yitirmiş durumda.

iNove Tema WordPress
Eski iNove Tema WordPress

Bu temanın yerine yine wordpress resmi sitesinde yer alan The Box adlı temayı kullanmaya karar verdim.

 

EntityFramework 6 ve Postgresql Kullanımı

6 Şub

PostgreSQL veritabanına .NET tarafından erişebilmek için Npgsql adında bir provider geliştirilmiştir. Bu provider aracılığı ile veri tabanına sorgular atmak mümkündür.

Ancak günümüzde ORM araçlarının yaygınlaşmasıyla birlikte provider kullanarak veri tabanına erişim yerine ORM araçları tercih edilmektedir. Microsoft .NET tarafında kullanılan ORM aracı Entity Framework‘tür.

Entity Framework kullanarak PostgreSQL veritabanına erişmek için Npgsql paketlerini Nuget üzerinden indirmek gerekmektedir. İndirme işlemi için aşağıdaki komutlar bize yardımcı olacaktır.

Install-Package EntityFramework
Install-Package Npgsql.EntityFramework

Şu anda EntityFramework 6 sürümü piyasaya sürülmüş durumdadır. Bunu da bir not olarak belirtmekte fayda var.

EF ve Npgsql
Entity Framework ve Npgsql

App.config veya Web.config dosyalarında gerekli ayarlamaların yapılması gerekmektedir.

<?xml version="1.0" encoding="utf-8"?>
<configuration>
    <configSections>
      <section name="entityFramework"
               type="System.Data.Entity.Internal.ConfigFile.EntityFrameworkSection, EntityFramework, Version=6.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"
               requirePermission="false" />
    </configSections>
    <connectionStrings>
       <add name="ChinookContext"
            connectionString="Server=10.0.1.7;Database=deneme;User Id=denuser;Password=denpass;"
            providerName="Npgsql" />
     <providers>
       <provider
        invariantName="System.Data.SqlClient" type="System.Data.Entity.SqlServer.SqlProviderServices, EntityFramework.SqlServer" />
       <provider invariantName="Npgsql" type="Npgsql.NpgsqlServices, Npgsql" />
     </providers>
   </connectionStrings>
   <entityFramework>
     <defaultConnectionFactory
        type="System.Data.Entity.Infrastructure.SqlConnectionFactory, EntityFramework" />
     <providers>
          <provider
              invariantName="System.Data.SqlClient" type="System.Data.Entity.SqlServer.SqlProviderServices, EntityFramework.SqlServer" />
          <provider invariantName="Npgsql" type="Npgsql.NpgsqlServices, Npgsql.EntityFramework" />
     </providers>
   </entityFramework>
   <system.data>
     <DbProviderFactories>
        <add name="Npgsql Data Provider"
          invariant="Npgsql"
          support="FF"
          description="Data Provider for PostgreSQL"
          type="Npgsql.NpgsqlFactory, Npgsql" />
        </DbProviderFactories>
    </system.data>
    <runtime>
       <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
       <dependentAssembly>
       <assemblyIdentity name="Npgsql" publicKeyToken="5d8b90d52f46fda7" culture="neutral" />
       <bindingRedirect oldVersion="0.0.0.0-2.0.14.3" newVersion="2.0.14.3" />
       </dependentAssembly>
       </assemblyBinding>
   </runtime>
</configuration>

Geri kalan işlem klasik Code First yaklaşımının uygulanışı şeklindedir. Öncelikle tablolarımızı temsil eden sınıfları oluşturmalıyız. Bu örnekte Book ve Author sınıfları oluşturulmuştur.

  public class Book
  {
      public string Name { get; set; }
      public virtual Author Author { get; set; }
  }

  public class Author
  {
      public string Name { get; set; }
      public virtual ICollection<Book> Books { get; set; }
  }

Daha sonra Entity Framework için gerekli olan DbContext oluşturmak gerekir.

  public class ChinookContext : DbContext
  {
      public DbSet<Book> Books { get; set; }
      public DbSet<Book> Authors { get; set; }

      protected override void OnModelCreating(DbModelBuilder modelBuilder)
      {
           modelBuilder.Entity<Book>().ToTable("Book", "public");
           modelBuilder.Entity<Author>().ToTable("Author", "public");

          modelBuilder.Conventions.Remove<StoreGeneratedIdentityKeyConvention>();
      }
 }

DbContext sınıfında DbModelBuilder nesnesinin Entity özelliğinde tabloların hangi  şemaya ait olduğunu belirtmemiz gerekmektedir. Aksi taktirde provider, varsayılan olarak “dbo” şemasını arayacaktır. Bu durum bize Exception olarak geri dönebilir.

Bir sonraki yazıda görüşmek dileğiyle.

Unit Test metodlarında bulunması gereken özellikler

1 Şub

Test Driven Developmant uygulamalarında karşılaştığımız kalıp terimlerden biri olan birim testlerin(unit tests) sahip olması gereken bazı temel özelliklerden bahsetmeye çalışacağız.

Birim test(unit test), bir kod parçasını çağırarak o kod parçasının istenen davranışı sergileyip sergilemediğini kontrol eden başka bir kod parçasıdır. “Birim” kavramı bir metod veya bir fonksiyon olarak düşünülebilir.(osherove – The Art of Unit Testing)

Birim testlerde bulunması gereken bazı ortak özellikler vardır. Bunlar:

  • Atomik
  • Deterministik
  • Tekrarlanabilir
  • Bağımsız
  • Hız

Atomik: Birim test metodu tek bir işlevselliği test etmelidir. Birden fazla işlevi test etmek,  anlaşılabilirliği azaltacağı gibi süreçte karmaşıklığa yol açar.

Deterministik: Bir test metodu ya başarıyla testten geçer(pass) ya da geçemez(fail). Yani sonuçsuz bir durum söz konusu değildir.

Tekrarlanabilirlik: Bir kere başarıyla geçen birim test, test metodunda veya test edilen kodda bir değişiklik yapılmadığı sürece her çalıştırıldığında aynı davranışı sergiler. Bazen geçip bazen geçmeyen testler tekrarlanabilir değildir.

Bağımsız: Birim testin çalışması için herhangi bir başka teste, uygulamaya veya dış kaynağa(veri tabanı, mail sunucu v.b) bağımlı olmaması gerekir.

Hız: Birim testler olabildiğince hızlı çalışmalıdır. Bir birim test, dakikalar, saniyeler değil milisaniyeler içinde sonuçlanmalıdır.