Specification Pattern: Tasarım Örüntülerinde Bilinmeyen Kahraman. Specification Pattern hakkında detaylı bir makale.Java Spring boot kod örnekleri ile.

Başlık: Specification Pattern: Tasarım Örüntülerinde Bilinmeyen Kahraman



Bilgisayar bilimlerinin dünyasında, belirli bir hedefe ulaşmak için belirli bir yol takip eden tekrarlanabilir çözüm yöntemleri tasarım örüntüleri (design patterns) olarak bilinir. Tasarım örüntüleri, yazılım geliştirme süreçlerinde işlevselliği artırmak, kod tekrarını azaltmak ve genel olarak daha anlaşılabilir, bakımı ve ölçeklendirilebilir kod üretmek için kullanılır. Bu yazıda, Specification Pattern (Şartname Örüntüsü) adı verilen, ancak genellikle fazla bilinmeyen bir tasarım örüntüsü üzerinde duracağız.


Specification Pattern Nedir?


Specification Pattern, bir iş kuralının doğru olup olmadığını belirlemek için kullanılan bir tasarım örüntüsüdür. Özünde, bu örüntü belirli bir koşulu karşılayıp karşılamadığını kontrol etmek için bir "isSatisfiedBy" adlı bir yöntem sağlar. Bu tür bir tasarım örüntüsü, iş kurallarının karmaşık olduğu ve birçok farklı faktörün işin gerçekleştirilip gerçekleştirilmeyeceğini belirlediği durumlarda son derece yararlıdır.


Neden Specification Pattern Kullanmalıyız?


Specification Pattern'ın kullanılmasının ana nedeni, kodun karmaşıklığını ve anlaşılmazlığını azaltmaktır. Bir iş kuralını bir sınıf veya metot olarak ifade etmek, bu kuralı iş akışının diğer bölümlerinden ayırır. Bu, kodun bakımını ve okunabilirliğini kolaylaştırır, çünkü her iş kuralı kendi kendine açıklayıcıdır ve iş kuralının kendisi hakkında her şey bir aradadır. Bu, aynı zamanda test etmeyi de kolaylaştırır, çünkü her iş kuralı bağımsız bir birim olarak test edilebilir.


Specification Pattern Nasıl Kullanılır?


Specification Pattern'ın kullanımı genellikle bir "isSatisfiedBy" metodunu içerir. Bu metot, bir koşulun sağlanıp sağlanmadığını kontrol eder ve genellikle bir Boolean değer döndürür. Metodun parametreleri, koşulun karşılanıp karşılanmadığını belirlemek için kullanılan verilerdir. Metot, bu verilerin belirli bir kriteri karşılayıp karşılamadığını kontrol eder.


Örneğin, bir e-ticaret uygulamasında, bir müşterinin bir ürünü satın alabilmesi için belirli bir yaşın üzerinde olması gerektiği bir iş kuralı olabilir. Bu durumda, "AgeSpecification" adında bir sınıf oluşturabiliriz ve bu sınıfın "isSatisfiedBy" metodunda müşterinin yaşı kontrol edilebilir. Eğer müşterinin yaşı belirtilen yaşın üzerindeyse, metot True döner; aksi takdirde False döner. Bu şekilde, müşterinin yaşı kontrol edilir ve ürünün satın alınması gerektiğinde bu kontrol uygulanır.


Sonuç


Specification Pattern, karmaşık iş kurallarını yönetmekte büyük bir yardımcıdır. Bu örüntüyü kullanmak, kodunuzun daha düzenli ve okunabilir olmasını sağlar. Ayrıca test sürecini de kolaylaştırır, çünkü her bir iş kuralı ayrı ayrı test edilebilir. Bu, kodun kalitesini artırmak ve hataları önlemek için mükemmel bir yoldur. Her ne kadar Specification Pattern sıkça kullanılan bir örüntü olmasa da, belirli durumlar ve uygulamalar için son derece yararlı olabilir. Her yazılım geliştiricinin bu güçlü aracı bilmesi ve gerektiğinde kullanabilmesi önemlidir.


Specification Pattern kullanırken, genellikle isSatisfiedBy() adlı bir metoda sahip bir Specification arayüzü tanımlarız. Bu metod, bir kuralın belirli bir örnekleme (entity instance) uygulanıp uygulanamayacağını kontrol eder. Bu arayüzün, genel mantıksal işlemler olan and(), or() ve not() işlemlerini gerçekleştiren metodları da vardır.


Öncelikle, Specification arayüzünü tanımlayalım:


java

Copy code

public interface Specification<T> {

    boolean isSatisfiedBy(T t);

    default Specification<T> and(Specification<T> other) {

        return t -> this.isSatisfiedBy(t) && other.isSatisfiedBy(t);

    }

    default Specification<T> or(Specification<T> other) {

        return t -> this.isSatisfiedBy(t) || other.isSatisfiedBy(t);

    }

    default Specification<T> not() {

        return t -> !this.isSatisfiedBy(t);

    }

}

Şimdi bu Specification arayüzünü Customer sınıfı için kullanabiliriz. Örneğin, belirli bir yaşın üzerinde olan müşterileri ve belirli bir e-posta alan adına sahip olan müşterileri bulmak için iki spesifikasyon oluşturalım:


java

Copy code

public class AgeSpecification implements Specification<Customer> {

    private int age;


    public AgeSpecification(int age) {

        this.age = age;

    }


    @Override

    public boolean isSatisfiedBy(Customer customer) {

        return customer.getAge() > age;

    }

}


public class EmailDomainSpecification implements Specification<Customer> {

    private String domain;


    public EmailDomainSpecification(String domain) {

        this.domain = domain;

    }


    @Override

    public boolean isSatisfiedBy(Customer customer) {

        return customer.getEmail().endsWith(domain);

    }

}

Artık Specification'larımızı müşteri hizmeti sınıfımızda (CustomerService) kullanabiliriz:


java

Copy code

public class CustomerService {

    private List<Customer> customers;


    public CustomerService(List<Customer> customers) {

        this.customers = customers;

    }


    public List<Customer> findCustomers(Specification<Customer> specification) {

        return customers.stream()

                .filter(specification::isSatisfiedBy)

                .collect(Collectors.toList());

    }

}

findCustomers() metodu, belirli bir spesifikasyonu karşılayan müşterileri bulur. Bu, Specification'ların herhangi bir kombinasyonu olabilir. Örneğin, belirli bir yaşın üzerinde olan ve belirli bir e-posta alan adına sahip olan müşterileri bulabiliriz:


public class Main {

    public static void main(String[] args) {

        List<Customer> customers = ... // müşterileri al

        CustomerService customerService = new CustomerService(customers);


        Specification<Customer> specification = 

            new AgeSpecification(18).and(new EmailDomainSpecification("@example.com"));

        

        List<Customer> result = customerService.findCustomers(specification);

        

        // sonuçları işle

    }

}

Bu örnekte, AgeSpecification ve EmailDomainSpecification spesifikasyonları, and() operatörü ile birleştirilir ve belirli bir yaşın üzerinde olan ve belirli bir e-posta alan adına sahip olan müşterileri buluruz. Bu yaklaşım, daha karmaşık iş kurallarını yönetmeyi ve uygulamayı kolaylaştırır.



Spring Data JPA'nın Specification arayüzü ile Spring Boot kod örnekleri ve karmaşık senaryolar ile devam et.

Yukarıda belirtildiği gibi, Specification Pattern özellikle karmaşık iş kurallarını ifade etmek ve yönetmek için yararlıdır. Java Spring Boot uygulamasında Specification Pattern'i kullanmak için, Spring Data JPA'nın Specification arayüzünü kullanabiliriz. Bu arayüz, toPredicate adlı bir metoda sahiptir ve bu metot, belirtilen şartı kontrol etmek için kullanılır.


Öncelikle, bir Customer sınıfı oluşturalım:


java

Copy code

@Entity

public class Customer {


    @Id

    @GeneratedValue(strategy = GenerationType.AUTO)

    private Long id;


    private String name;

    private Integer age;

    private String email;


    // getter ve setter metotları

}

Diyelim ki, belirli bir yaşın üzerinde olan ve belirli bir e-posta alan adına sahip olan müşterileri bulmak için bir iş kuralımız var. Bu durumda, iki farklı Specification oluşturabiliriz:


java

Copy code

public class CustomerSpecifications {


    public static Specification<Customer> isOlderThan(Integer age) {

        return (root, query, criteriaBuilder) -> criteriaBuilder.greaterThan(root.get("age"), age);

    }


    public static Specification<Customer> hasEmailDomain(String domain) {

        return (root, query, criteriaBuilder) -> criteriaBuilder.like(root.get("email"), "%" + domain);

    }

}

Her Specification bir Predicate döndürür, bu da belirli bir koşulun doğru olup olmadığını kontrol eder.


Şimdi, bu özellikleri CustomerRepository'de kullanabiliriz:


java

Copy code

public interface CustomerRepository extends JpaRepository<Customer, Long>, JpaSpecificationExecutor<Customer> {

}

Son olarak, CustomerService içerisinde bu özellikler kullanılabilir:


java

Copy code

@Service

public class CustomerService {


    private final CustomerRepository customerRepository;


    public CustomerService(CustomerRepository customerRepository) {

        this.customerRepository = customerRepository;

    }


    public List<Customer> findCustomers(Integer age, String domain) {

        return customerRepository.findAll(Specification.where(CustomerSpecifications.isOlderThan(age))

                .and(CustomerSpecifications.hasEmailDomain(domain)));

    }

}

findCustomers metodu, belirli bir yaşın üzerinde olan ve belirli bir e-posta alan adına sahip olan müşterileri bulur. Bu, Specification Pattern'in gücünü gösterir: karmaşık iş kurallarını yönetme ve birleştirme yeteneği.


Bu örnekte, iş kurallarının her biri ayrı ayrı tanımlanmış ve daha sonra birleştirilmiştir. Bu, kodun daha okunabilir ve bakımının daha kolay olmasını sağlar. Ayrıca, her bir iş kuralını ayrı ayrı test edebiliriz, bu da test sürecini basitleştirir.


Böylelikle, Specification Pattern, karmaşık iş kurallarını yönetmek, kodu düzenlemek ve test sürecini basitleştirmek için mükemmel bir araçtır.


Specification Pattern'ın genellikle tek başına kullanıldığı gibi, bir araya getirilerek de daha karmaşık ve gelişmiş senaryolar oluşturmak için kullanılabilir. İki veya daha fazla spesifikasyonun birleştirilmesi, daha karmaşık iş kurallarını ifade etme yeteneği sağlar. Örneğin, and(), or() ve not() gibi mantıksal operatörler, spesifikasyonları birleştirmek ve mantıksal ifadeler oluşturmak için kullanılabilir.


Bunu göstermek için, daha karmaşık bir iş kuralı olan müşterinin belirli bir yaşın üzerinde olması veya belirli bir e-posta alan adına sahip olması durumunu ele alalım. Bu durumda, or() operatörünü kullanarak iki spesifikasyonu birleştirebiliriz:


java

Copy code

@Service

public class CustomerService {


    private final CustomerRepository customerRepository;


    public CustomerService(CustomerRepository customerRepository) {

        this.customerRepository = customerRepository;

    }


    public List<Customer> findCustomers(Integer age, String domain) {

        return customerRepository.findAll(Specification.where(CustomerSpecifications.isOlderThan(age))

                .or(CustomerSpecifications.hasEmailDomain(domain)));

    }

}

Bu örnekte, or() operatörü iki spesifikasyonu birleştirir ve sonuçta belirli bir yaşın üzerinde olan veya belirli bir e-posta alan adına sahip olan müşterileri buluruz.


Ayrıca, aşağıdaki gibi belirli bir yaşın üzerinde olan ve belirli bir e-posta alan adına sahip olmayan müşterileri bulmak için not() operatörünü de kullanabiliriz:


java

Copy code

@Service

public class CustomerService {


    private final CustomerRepository customerRepository;


    public CustomerService(CustomerRepository customerRepository) {

        this.customerRepository = customerRepository;

    }


    public List<Customer> findCustomers(Integer age, String domain) {

        return customerRepository.findAll(Specification.where(CustomerSpecifications.isOlderThan(age))

                .and(Specification.not(CustomerSpecifications.hasEmailDomain(domain))));

    }

}

Bu örnekte, and() ve not() operatörleri birleştirilerek belirli bir yaşın üzerinde olan ve belirli bir e-posta alan adına sahip olmayan müşterileri buluruz.


Görüldüğü gibi, Specification Pattern'ı kullanmak, bir uygulamanın iş kurallarını ifade etmek ve yönetmek için güçlü ve esnek bir yöntemdir. Farklı iş kurallarını birleştirme yeteneği, uygulamanın karmaşık iş kurallarını daha kolay yönetmesini sağlar ve kodun daha düzenli ve anlaşılabilir olmasına yardımcı olur. Bu, özellikle büyük ve karmaşık uygulamalar için çok değerli bir özelliktir.


Dah advanced bir örnek 

Specification Pattern'ı daha karmaşık senaryolarda kullanmak, iş kurallarının kodu karmaşıklaştırabileceği ve okunabilirliği azaltabileceği durumları hafifletebilir. Gelişmiş bir örnekle bunu açıklayalım. Diyelim ki, bir sipariş işlemi yürütülüyor ve bir siparişin işlenmesi için çeşitli koşulların karşılanması gerekiyor.


Öncelikle, Order ve Customer sınıflarımızın olduğunu varsayalım:


java

Copy code

@Entity

public class Order {

    @Id

    @GeneratedValue(strategy = GenerationType.AUTO)

    private Long id;


    private BigDecimal total;

    private String status;


    @ManyToOne

    private Customer customer;


    // getter ve setter metotları

}


@Entity

public class Customer {

    @Id

    @GeneratedValue(strategy = GenerationType.AUTO)

    private Long id;


    private String name;

    private BigDecimal balance;

    

    // getter ve setter metotları

}

Her bir siparişin işlenebilmesi için aşağıdaki kuralların sağlanması gerektiğini varsayalım:


Müşterinin hesap bakiyesi, siparişin toplam değerinden daha fazla olmalıdır.

Siparişin durumu "yeni" olmalıdır.

Bu iş kurallarını Specification Pattern kullanarak ifade edebiliriz:


public class OrderSpecifications {

    public static Specification<Order> isAccountBalanceSufficient() {

        return (root, query, criteriaBuilder) -> criteriaBuilder.greaterThan(root.get("customer").get("balance"), root.get("total"));

    }


    public static Specification<Order> isNewOrder() {

        return (root, query, criteriaBuilder) -> criteriaBuilder.equal(root.get("status"), "new");

    }

}

Artık bu iş kurallarını OrderRepository'de kullanabiliriz:


public interface OrderRepository extends JpaRepository<Order, Long>, JpaSpecificationExecutor<Order> {

}

Ve son olarak, OrderService içerisinde bu iş kurallarını kullanabiliriz:


java

Copy code

@Service

public class OrderService {


    private final OrderRepository orderRepository;


    public OrderService(OrderRepository orderRepository) {

        this.orderRepository = orderRepository;

    }


    public List<Order> findProcessableOrders() {

        return orderRepository.findAll(Specification.where(OrderSpecifications.isAccountBalanceSufficient())

                .and(OrderSpecifications.isNewOrder()));

    }

}

Bu örnekte, findProcessableOrders metodu, işlenebilecek siparişleri bulmak için Specification Pattern kullanır. İki iş kuralını bir araya getirerek, karmaşık bir iş kuralı oluşturduk: müşterinin hesap bakiyesinin yeterli olması ve siparişin durumunun "yeni" olması gerekiyor.


Bu yaklaşımın avantajı, her bir iş kuralını ayrı ayrı tanımlayabilmemiz ve daha sonra bunları karmaşık bir iş kuralı oluşturmak için birleştirebilmemizdir. Bu, kodun anlaşılabilirliğini ve bakımını büyük ölçüde artırır. Dahası, bu yaklaşım, yeni iş kurallarının eklenmesi veya mevcut kuralların değiştirilmesi gerektiğinde çok esnektir. Her bir iş kuralı ayrı ayrı test edilebilir ve değiştirilebilir, bu da test sürecini ve hata ayıklamayı çok daha kolay hale getirir.

Please Select Embedded Mode To Show The Comment System.*

Daha yeni Daha eski

نموذج الاتصال