Blog

Gizli Silah: Specification Pattern

Sanırım specification pattern‘ı en son bir buçuk yıl önce implemente etme ihtiyacım olmuştu. Amacım ise ilgili business domain’ini çok fazla kompleks bir hale getirmeden ve domain bilgilerini duplicate etmeden, domain kurallarını encapsulate ederek tekrar kullanılabilir bir hale getirebilmekti.

Bir çoğumuzun bildiği gibi specification pattern, yeni bir pattern değil. Son dönemlerde ise bu pattern hakkında farklı düşünceler ve tartışmalara denk geldim. Böylece bu pattern hakkında bende bir şeyler yazmaya karar verdim. Dürüst olmak gerekirse gerekli gördüğüm noktalarda bu pattern’ı implemente etmek, benim için hala hoş bir yaklaşım.

Bu makale kapsamında ise biraz specification pattern’dan bahsedip, en basit haliyle nasıl implemente edebileceğimizi göstermeye çalışacağım.

Peki, nedir?

Specification pattern için en basit haliyle, istediğimiz domain bilgilerini/kurallarını encapsulate ederek tekrar kullanılabilir parçalar oluşturabilmemize olanak sağlayan bir pattern’dır diyebiliriz.

Böylece uygulama içerisinde aynı domain kuralına ait lambda expressions’ları yaymak yerine, single responsibility prensibine bağlı kalarak ilgili tüm domain kurallarını tek bir noktadan yönetip, tekrar kullanılabilir bir hale getirebilmekteyiz.

Örneğin bir e-ticaret firmasının ürün domain’i içerisinde çalıştığımızı ve sanal stok’lu ürünleri listelemek istediğimizi düşünelim.

Ürün modeli aşağıdaki gibi property’lere sahip olsun.

public class Product
{
    public string Name { get; set; }
    public bool IsVirtualStock { get; set; }
    public bool IsFreeShipping { get; set; }
}

Genelde bu gibi bir işlemi, aşağıdaki gibi bir lambda expression ile basitçe çözebiliriz.

List<Product> products = _dbContext.Products.Where(p => p.IsVirtualStock == true).ToList();

Bu noktaya kadar her şey güzel.

Daha sonra farklı bir noktada ise ürünlerin, sanal stok’lu olup olmadıklarını kontrol etme ihtiyacımızın olduğunu düşünelim. Elbette bu işlemi de aşağıdaki gibi basitçe çözebiliriz.

if(product.IsVirtualStock)
{
    ..
}

Bu örnekte olduğu gibi her yeni bir ihtiyacımızda, ilgili domain kuralını sürekli farklı noktalarda tekrar etmek zorunda kalacağız ve DRY prensibini ihlal edeceğiz. Ayrıca bu domain kuralı, birden fazla gereksinimin bir araya gelmesiyle de oluşabilir.  Bunun gibi bir araya geldiğinde önem kazanan domain kurallarını specification pattern yardımıyla encapsulate edebilir, tek bir noktadan tekrar kullanılabilir bir hale getirebiliriz.

Birden fazla domain kuralının zincirleme olarak bir arada kullanıldığı senaryolarda ise, lambda expression karmaşası meydana gelebiliyor ve okunabilirliği de oldukça düşürebiliyor. Bu durumu da specification pattern yardımıyla tersine çevirebilmek mümkün.

Haydi Kodlayalım!

Öncelikle aşağıdaki gibi “Specification” isimli bir abstract class oluşturalım.

public abstract class Specification
<T>
{
    public abstract Expression<Func<T, bool>> Expression();

    public bool IsSatisfiedBy(T entity)
    {
        Func<T, bool> predicate = Expression().Compile();

        return predicate(entity);
    }
}

Gördüğümüz gibi specification pattern temelinde, bir domain model’inin istenilen domain kuralına uyumlu olup olmadığını kontrol edebilmek için “IsSatisfiedBy” isimli bir method yer almaktadır.

Concrete specification class’larında ise istediğimiz domain kurallarını, “Expression” method’u içerisinde encapsulate edeceğiz.

Şimdi sanal stoklu ürün specification’ını aşağıdaki gibi oluşturalım.

public class VirtualStockSpecification : Specification
<Product>
{
    public override Expression<Func<Product, bool>> Expression()
    {
        return p => p.IsVirtualStock == true;
    }
}

Hepsi bu kadar.

Oluşturmuş olduğumuz bu specification’ı ister bir query işlemi sırasında, istersek de bir validation işlemi sırasında kullanabiliriz.

Örneğin repository içerisinden geriye bir IQueryable dönmek yerine, aşağıdaki gibi specification kabul edebilir bir hale getirebiliriz.

public class ProductRepository
{
    private readonly DbContext _dbContext;

    public ProductRepository(DbContext dbContext)
    {
        _dbContext = dbContext;
    }

    public IEnumerable<Product> Filter(Specification<Product> specification)
    {
        return _dbContext.Products.Where(specification.Expression()).ToList();
    }
}

Ardından aşağıdaki gibi farklı amaçlarla specification’ı kullanabiliriz.

public class ProductService
{
    private readonly ProductRepository _productRepository;

    public ProductService(ProductRepository productRepository)
    {
        _productRepository = productRepository;
    }

    public List<ProductDTO> GetProducts()
    {
        List<Product> products = _productRepository.Filter(new VirtualStockSpecification()).ToList();

        // ...
    }

    public ProductDTO AnotherMethod(int id)
    {
        Product product = _productRepository.Get(id);

        var virtualStockSpecification = new VirtualStockSpecification();

        if(virtualStockSpecification.IsSatisfiedBy(product))
        {
            // do something
        }

        // ...
    }
}

Yukarıdaki kod bloğundan görebileceğimiz üzere, hem query işlemi için hem de diğer method içerisinde validation işlemi için “VirtualStockSpecification” ı kullandık.

Elbette specification’ın kullanımı sadece bunlardan ibaret değil. Daha farklı ihtiyaçlar için specification’ları zincirleme olarak “AND“, “OR“, “NOT” gibi yeteneklerle birleştirerek kullanabilmekte mümkün.

Bu yaklaşım ise Composite Specification olarak adlandırılıyor. Bununla ilgili bir örneğe ise, buradan erişebilirsiniz.

Sonuç

Her ne kadar bu pattern hakkında farklı fikir ayrılıkları olsada, çoğu zaman benim için hala kullanışlı bir pattern. Specification’ları kullanabilmek için oluşturduğumuz base class’ları, projenin karmaşıklığını arttıran unsurlar olarak görebilirsiniz. Fakat bize kazandırabilecek olduğu tekrar kullanılabilirlik ve test edilebilirliği göz önüne aldığımızda, özellikle söz konusu domain kuralları ise, bence kabul edilebilir bir hale geliyor. Keza projenin maintenance’ı üzerinde de doğrudan bir etkisi olduğuna inanıyorum.

Peki bu konuda sizin düşünceleriniz nedir?

Referanslar

https://en.wikipedia.org/wiki/Specification_pattern
https://stackoverflow.com/questions/9709764/specification-inside-linq-with-ef-4-3

İlgili Makaleler

Bir Yorum

Bir yanıt yazın

E-posta adresiniz yayınlanmayacak. Gerekli alanlar * ile işaretlenmişlerdir

Başa dön tuşu