Rule Nasıl Yapılır?

Rule Nasıl Yapılır?
Rule Nasıl Yapılır?

Modern yazılım geliştirme süreçlerinde, iş mantığını (business logic) uygulamanın çekirdek kodundan ayırmak, sürdürülebilirlik açısından hayati bir önem taşır. Sürekli değişen kampanya koşulları, üyelik izinleri veya veri doğrulama kriterleri gibi dinamik kuralları yönetmek için esnek yapılar kurmalıyız. Bu rehberde, Python dilini kullanarak esnek, güvenli ve ölçeklenebilir bir kural motoru tasarımı ile Rule Nasıl Yapılır? sorusuna adım adım yanıt arayacağız.

Bu makale, Python geliştiricilerinin projelerinde spagetti kod (karmaşık ve okunaksız kod) yazmadan, iş kurallarını nasıl soyutlaştırabileceğini öğretmek amacıyla hazırlanmıştır. Nesne yönelimli programlama (OOP) prensiplerinden, modern veri doğrulama kütüphanelerine ve güvenli dinamik kural değerlendirme (evaluation) tekniklerine kadar geniş bir yelpazeyi ele alacağız. Rehber sonunda, kendi projelerinizde güvenle kullanabileceğiniz modüler bir kural motoru mimarisine sahip olacaksınız.

Gereksinimler ve Ön Hazırlık

Bu eğitimdeki örnekleri uygulayabilmeniz için bilgisayarınızda güncel bir Python ortamının kurulu olması gerekmektedir. Kod örnekleri, modern Python standartlarına (Python 3.10 ve üzeri) uygun olarak tasarlanmıştır. Bu sürümlerde gelen tip belirteçleri (type hinting) ve gelişmiş örüntü eşleştirme (pattern matching) özellikleri, kural motorumuzu daha okunabilir kılacaktır.

Örneklerimizde veri doğrulama kuralları için sektör standardı olan pydantic kütüphanesini de kullanacağız. Gerekli kütüphaneyi yüklemek ve temiz bir çalışma alanı oluşturmak için terminalinizde aşağıdaki komutları çalıştırabilirsiniz:

# Sanal ortam oluşturma
python -m venv rule_env

# Sanal ortamı aktif etme (Windows için)
rule_env\Scripts\activate

# Sanal ortamı aktif etme (macOS/Linux için)
source rule_env/bin/activate

# Gerekli pydantic kütüphanesinin kurulması
pip install pydantic==2.7.1

Yukarıdaki kurulum adımlarını tamamladıktan sonra, kural motorumuzun temelini oluşturacak ilk tasarım kalıbını uygulamaya hazırız demektir. Şimdi, nesne yönelimli programlama prensiplerini kullanarak ilk kural yapımızı inşa edelim.

Adım 1: Nesne Yönelimli Programlama ile Temel Kural (Rule) Yapısı Oluşturma

Kural tabanlı sistemlerde ilk adım, her bir kuralı bağımsız birer nesne (object) olarak tanımlamaktır. Bu yaklaşım, kuralların tek bir sorumluluğa sahip olmasını (Single Responsibility Principle) sağlar. Python'da soyut temel sınıflar (Abstract Base Classes - ABC) kullanarak tüm kuralların uyması gereken ortak bir arayüz (interface) tanımlayabiliriz.

Aşağıdaki kod örneğinde, bir kuralın temel yapısını tanımlayan BaseRule soyut sınıfını ve bu sınıftan türeyen, bir müşterinin sepet tutarına göre indirim kazanıp kazanamayacağını denetleyen MinimumCartAmountRule sınıfını oluşturuyoruz.

from abc import ABC, abstractmethod
from typing import Any, Dict

class BaseRule(ABC):
    """
    Tüm kuralların türeyeceği soyut temel sınıf.
    Her kural bu sınıfın 'evaluate' metodunu ezmek zorundadır.
    """
    
    @abstractmethod
    def evaluate(self, context: Dict[str, Any]) -> bool:
        """
        Kuralın mantığını çalıştırır ve kuralın geçip geçmediğini döner.
        
        :param context: Kuralın değerlendirileceği veri sözlüğü.
        :return: Kural geçerli ise True, değilse False.
        """
        pass

class MinimumCartAmountRule(BaseRule):
    """
    Sepet tutarının belirlenen minimum değerin üzerinde olup olmadığını denetleyen kural.
    """
    def __init__(self, min_amount: float) -> None:
        self.min_amount = min_amount

    def evaluate(self, context: Dict[str, Any]) -> bool:
        # Context içerisinde sepet tutarı bilgisi var mı kontrol ediliyor
        cart_total = context.get("cart_total")
        if cart_total is None:
            return False
        return cart_total >= self.min_amount

Bu kod bloğunda, BaseRule sınıfı sayesinde gelecekte yazacağımız tüm kuralların aynı imzaya sahip olacağını garanti altına aldık. MinimumCartAmountRule sınıfı ise kendisine verilen min_amount değeri ile işlem yapılacak olan context (bağlam) sözlüğündeki cart_total değerini karşılaştırır. Bu sayede kural mantığı, ana uygulama kodundan tamamen izole edilmiş olur.

Şimdi bu kuralı basit bir veri setiyle test edelim. Aşağıdaki kod bloğu, oluşturduğumuz kuralın farklı senaryolarda nasıl sonuç verdiğini göstermektedir:

# Kuralı 500 TL minimum limit ile başlatalım
discount_rule = MinimumCartAmountRule(min_amount=500.0)

# Test Senaryosu 1: Limit altı sepet
user_context_1 = {"username": "Ahmet", "cart_total": 350.0}
result_1 = discount_rule.evaluate(user_context_1)
print(f"Ahmet için kural sonucu: {result_1}")  # Çıktı: False

# Test Senaryosu 2: Limit üstü sepet
user_context_2 = {"username": "Ayşe", "cart_total": 620.0}
result_2 = discount_rule.evaluate(user_context_2)
print(f"Ayşe için kural sonucu: {result_2}")    # Çıktı: True

Bu test adımı, kuralımızın girdi olarak aldığı bağlam verisine göre tutarlı bir şekilde çalıştığını doğrulamaktadır. Ancak gerçek dünya senaryolarında tek bir kural nadiren yeterlidir. Birden fazla kuralı bir arada değerlendirmemiz gerekir.

Adım 2: Çoklu Kuralları Yönetmek İçin Kural Motoru (Rule Engine) Tasarımı

Birden fazla kuralı mantıksal operatörlerle (VE, VEYA) birleştirerek yöneten sisteme Kural Motoru (Rule Engine) denir. Bu adımda, kayıtlı tüm kuralları sırasıyla çalıştıran ve yalnızca tüm kurallar başarılı olduğunda onay veren bir kural motoru tasarlayacağız. Bu tasarımda "Composite" (Bileşik) tasarım kalıbından yararlanacağız.

Aşağıdaki kod örneğinde, birden fazla kural nesnesini bünyesinde barındıran ve bunları toplu olarak değerlendiren RuleEngine sınıfını görebilirsiniz. Ayrıca sisteme yeni bir kural olarak kullanıcının üyelik yaşını kontrol eden LoyaltyRule sınıfını ekliyoruz.

from typing import List

class LoyaltyRule(BaseRule):
    """
    Kullanıcının üyelik süresinin (ay bazında) yeterli olup olmadığını denetler.
    """
    def __init__(self, min_months: int) -> None:
        self.min_months = min_months

    def evaluate(self, context: Dict[str, Any]) -> bool:
        membership_months = context.get("membership_months")
        if membership_months is None:
            return False
        return membership_months >= self.min_months

class RuleEngine:
    """
    Kayıtlı kuralları yöneten ve toplu olarak değerlendiren motor.
    """
    def __init__(self) -> None:
        self._rules: List[BaseRule] = []

    def register_rule(self, rule: BaseRule) -> None:
        """Motora yeni bir kural ekler."""
        self._rules.append(rule)

    def evaluate_all(self, context: Dict[str, Any]) -> bool:
        """
        Tüm kuralların True dönüp dönmediğini kontrol eder (VE mantığı).
        Eğer kurallardan biri bile False dönerse, süreç durdurulur (Short-circuit).
        """
        if not self._rules:
            return False
            
        for rule in self._rules:
            if not rule.evaluate(context):
                return False
        return True

Tasarladığımız RuleEngine, "Short-circuit evaluation" (kısa devre değerlendirme) prensibiyle çalışır. Yani, kurallardan herhangi biri başarısız olduğunda diğer kuralları gereksiz yere çalıştırmayarak sistem kaynaklarını korur. Bu, özellikle veritabanı veya dış API sorgusu gerektiren ağır kurallarda performansı ciddi oranda artırır.

Şimdi bu kural motorunu, hem sepet limitini hem de sadık müşteri (loyalty) şartını aynı anda kontrol edecek şekilde test edelim:

# Kural motorunu başlatma
engine = RuleEngine()

# Kuralları motora kaydetme
engine.register_rule(MinimumCartAmountRule(min_amount=1000.0))
engine.register_rule(LoyaltyRule(min_months=12))

# Test Verisi: Sepet tutarı uygun ama üyelik süresi yetersiz
customer_context = {
    "username": "Can",
    "cart_total": 1200.0,
    "membership_months": 6
}

is_eligible = engine.evaluate_all(customer_context)
print(f"Can kampanyadan yararlanabilir mi?: {is_eligible}")  # Çıktı: False

Görüldüğü üzere, Can isimli müşteri sepet limiti kuralını geçmesine rağmen üyelik süresi kuralına takıldığı için kural motorundan onay alamamıştır. Bu yapı sayesinde kodunuzu değiştirmeden, sadece yeni kural sınıfları türeterek sistemi dilediğiniz kadar büyütebilirsiniz.

Adım 4: Pydantic v2 Kullanarak Veri Doğrulama Kuralları Yazma

Yazılım projelerinde kurallar sadece iş mantığıyla sınırlı değildir. Sisteme giren verilerin doğruluğunu, tiplerini ve sınırlarını belirleyen veri doğrulama (data validation) kuralları da kritik öneme sahiptir. Python ekosisteminde bu iş için en popüler ve performanslı kütüphane Pydantic'tir. Pydantic v2, Rust tabanlı çekirdeği ile son derece hızlı çalışır.

Aşağıdaki örnekte, bir kullanıcı kayıt isteğini doğrulayan ve özel şifre/e-posta kuralları içeren bir Pydantic modeli tanımlıyoruz.

from pydantic import BaseModel, EmailStr, Field, field_validator, model_validator
from typing import Self

class UserRegistrationRuleModel(BaseModel):
    """
    Kullanıcı kayıt bilgilerini ve doğrulama kurallarını içeren Pydantic modeli.
    """
    username: str = Field(..., min_length=3, max_length=20)
    email: EmailStr
    password: str = Field(..., min_length=8)
    confirm_password: str

    @field_validator('username')
    @classmethod
    def check_alphanumeric_username(cls, value: str) -> str:
        """Kullanıcı adının sadece harf ve sayılardan oluşmasını denetleyen kural."""
        if not value.isalnum():
            raise ValueError("Kullanıcı adı yalnızca alfanümerik karakterlerden oluşmalıdır.")
        return value

    @model_validator(mode='after                
Bu yazıya tepkinizi paylaşın:
Zeynep Kaya

Dijital içerik editörlüğü geçmişimle, adım adım rehberlerin en anlaşılır ve uygulanabilir şekilde kurgulanmasını sağlıyorum. Teknik detayları herkes için sadeleştirmeyi seviyorum.

Yorumlar (0)

Yorum Yaz