I guess the last time that I needed to implement the specification pattern was one and half year ago. My goal was the encapsulate the relevant domain rules and make them reusable without making the business domain too complex and duplicating the domain information.
As many of us know, the specification pattern is not a new pattern. Recently, I have come across different thoughts and discussions about this pattern. So I decided to write something about this pattern. To be honest, it is still a cool approach for me to implement this pattern at the points that I see applicable.
In this article, I will mention the specification pattern and try to show how we can implement it in the simplest way.
We can say that the specification pattern is a pattern that allows us to encapsulate the desired domain rules/information and create reusable parts.
Thus, instead of spreading lambda expressions which are belonging to the same domain rule within the application, we can handle all related domain rules from a single point and make them reusable by sticking to the single responsibility principle.
For example, let’s imagine that we are working within the product domain of an e-commerce company and we want to list the virtual stock products.
Assume that the product model has the properties as below.
public class Product { public string Name { get; set; } public bool IsVirtualStock { get; set; } public bool IsFreeShipping { get; set; } }
In general, we can simply solve such an operation with a lambda expression like below.
List<Product> products = _dbContext.Products.Where(p => p.IsVirtualStock == true).ToList();
Everything is fine.
Then let’s imagine that we need to check whether the products have virtual stocks at a different point. Of course, we can solve this operation also as follows.
if(product.IsVirtualStock) { .. }
As with the example above, whenever we need a new requirement, we will have to repeat the relevant domain rule at different points and violate the DRY principle. Also, a domain rule can consist of multiple requirements. So, we can encapsulate domain rules with the help of the specification pattern and make them reusable from a single point.
Sometimes lambda expression complexity can also happen in scenarios where more than one domain rules are used as a chain. So this situation may decrease the readability. It is also possible to reverse this situation with the help of the specification pattern.
First, let’s create an abstract class named “Specification” as below.
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); } }
As we can see, there is a method called “IsSatisfiedBy” in the core of the specification pattern to check whether a domain model is compatible with the requested domain rule.
In concrete specification classes, we will encapsulate the appropriate domain rules in the “Expression” method.
Now let’s create the virtual stock product specification as follows.
public class VirtualStockSpecification : Specification<Product> { public override Expression<Func<Product, bool>> Expression() { return p => p.IsVirtualStock == true; } }
That’s all.
So we can use this specification we created for both a query or validation operation.
For example, instead of returning an IQueryable within a repository method, we can make it to work with specifications as follows.
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(); } }
Then we can use the specification for different purposes as below.
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 } // ... } }
As we can see at the code block above, we have used “VirtualStockSpecification” for both the query and the validation operation within the different method.
Of course, the usage of the specification pattern is not limited like that. It is also possible to use specifications for different needs as chained by combining them with capabilities like “AND“, “OR“, “NOT“.
This approach is called Composite Specification. You can find more detailed information about it here.
Although there are different opinions about this pattern, most of the time it is still a useful pattern for me. You can see the base classes that we created to use the specifications as a reason which increase the complexity of the project. However, when we consider the reusability and testability that it can bring to us, especially if we are talking about the domain rules, I think it becomes acceptable. I believe that it also has a direct impact on the maintenance of the project.
So, what are your opinions about that?
https://en.wikipedia.org/wiki/Specification_pattern
https://stackoverflow.com/questions/9709764/specification-inside-linq-with-ef-4-3
{:tr} Makalenin ilk bölümünde, Software Supply Chain güvenliğinin öneminden ve containerized uygulamaların güvenlik risklerini azaltabilmek…
{:tr}Bildiğimiz gibi modern yazılım geliştirme ortamında containerization'ın benimsenmesi, uygulamaların oluşturulma ve dağıtılma şekillerini oldukça değiştirdi.…
{:tr}Bildiğimiz gibi bir ürün geliştirirken olabildiğince farklı cloud çözümlerinden faydalanmak, harcanacak zaman ve karmaşıklığın yanı…
{:tr}Bazen bazı senaryolar vardır karmaşıklığını veya eksi yanlarını bildiğimiz halde implemente etmekten kaçamadığımız veya implemente…
{:tr}Bildiğimiz gibi microservice architecture'ına adapte olmanın bir çok artı noktası olduğu gibi, maalesef getirdiği bazı…
{:tr}Bir önceki makale serisinde Dapr projesinden ve faydalarından bahsedip, local ortamda self-hosted mode olarak .NET…
View Comments
Çok güzel bir yazı teşekkürler.
thanks for sharing this
Eline saglik guzel yazi.
C# yazıyorsam specification pattern yerine extension method kullanımını tercih ediyorum reusable olacak mantık için. Deneyimlerni merak ediyorum, bu çizgiyi nasıl ayırıyorsun ?
Selam, teşekkür ederim yorumunuz için. Bana göre o ayrımı yapma konusu oldukça zor, çünkü aralarında herhangi bir üstünlük görmüyorum açıkcası. Sanırım developer'ın yoğurt yeme biçimine de bağlı diyebiliriz. Dediğiniz gibi bu işlemleri extension method'larla da kolaylıkla gerçekleştirebiliriz. Hatta domain modeller içerisinde getter'larla da gerçekleştirebiliriz eğer high cohesion konusuna takıntılı isek.
Thank you for this great post
Hocam merhabalar,
Katmanlı mimaride Specification hangi katmanda olmalı? Servis katmanında kullandığımızda data katmanında referans alamıyoruz. Direk data katmanında kullanılması doğru mu? Specification ISpecification'u data katmanına aldığımızda query oluşturabilmek için yine servis katmanında kullanmak gerekiyor. Açıkcası hangi katmanlarda daha uygun olacak belirleyemedim.
Merhaba, sorunun cevabı kullanmış olduğunuz mimari tarzına göre değişir. Eğer clean architecture tercih ediyorsanız, specification'lar da business'ın bir parçası olduğu için kesinlikle domain layer'da yer alması gerekmektedir. Clean architecture'ın doğası gereği tüm mimarinin ortasında konumlandığı ve diğer katmanların onu referans aldığından da dolayı, specification'lara data katmanından da erişebilirsiniz.
this is just overkill and certainly over engineering.