If/Else Cumleleri Icin Rule Tasarim Kalibi

Bugun, projemizde kullandigimiz Rule tasarim kalibi (Rule Design Pattern) anlatacagim. Bu yontemi zaman baskisi yuzunden ekibe kabul ettirmek zor oldu ama ortaya cikan sonuctan memnun kaldik. Umarim sizin de isinize yarar.

Problem

Spring Boot ile yazilan backend uygulamamizda farkli musteri tipleri icin farkli kurallarin uygulandigi bir fonksiyon vardi. Musteri tiplerine gore onyuze farkli bildirim mesajlarini gondermek gerekiyordu. Sadece birkac tane if-else cumlesinden olustugu icin ilk basta cok masum gorunuyordu ­čÖé

if(RuleForIneligibleStatus){

  // send notification for ineligible customers
  sendNotificationForRuleForIneligibleStatus();  
  
} else if (RuleForOpenUpgradeOrderStatus){

 // send notification for OpenUpgradeOrder customers

} 
.... boyle 20 tane daha if-else dusun :))

Ancak biraz tecrubesi olanlar bilirler ki zamanla o fonksiyona baska if-else cumleleri eklenir. Musteriden yeni istekler geldikce de yazilimcilar zaman baskisindan sonra, benden sonrasi tufan diyerek bir tane daha “if” cakarlar ­čÖé Bir sure sonra if-else’ler dallanip budaklanir, refactoring yapilamayacak noktaya kadar gelir. If-else’ler arasinda kayboluruz, en basit degisikligi bile yapmak cok zaman alir, testleri yapmamiz zorlasir.

Ondan sonra en ufak degisiklik neden bu kadar suruyor diye soylenirler!

Bu tip if-else cumleleri icin Strategy, Chain Of Responsibility, Factory ve Strategy kalibinin birlestirilmesi gibi farkli tasarim kaliplariyla da cozumler uretilmis. Yazinin sonundaki linklere bakabilirsin. Rule kalibi, kurallarin degistirilmesinin daha kolay oldugu ve uygulanmasi diger yontemlere gore basit oldugu icin bana uygun gelmisti. Ayrica 3rd party bir kutuphane kullanmak istemedigimiz icin rule-engine secenegine de sicak bakmadik.

Alternatif Cozum: Rule Tasarim Kalibi

Rule tasarim kalibi, is kurallarini kural i┼čleme mant─▒─č─▒ndan ay─▒rarak ├žal─▒┼č─▒r (Tek Sorumluluk ─░lkesi).┬áBu, sistemin geri kalan─▒n─▒ de─či┼čtirmeden (A├ž─▒k/Kapal─▒ ─░lkesi) yeni kurallar eklemeyi kolayla┼čt─▒r─▒r.┬áYani yeni bir if-else eklemektense ekleyecegin kural icin yeni bir sinif yaratirsin. Tasarim kalibinin 3 bileseni vardir:

IRule: Kurallari tanimlayacagimiz bir interface. Burada her kuralin calistirmasi gereken iki metod belirleniyor. Ilki bir kuralin calisma kosulunu (if cumlesindeki kosul), ikincisi ise calistigi takdirde hangi eylemi yapacagi (if cumlesi gecerli olursa ne yapilacagi).

RuleOne, RuleTwo…: IRule’dan turettigimiz ve spesifik kurallari yaratacagimiz siniflar.

RulesEngine: Kurallari hangi sirayla calistiracagimizi tanimladigimiz sinif.

rules flowchart
https://tenmilesquare.com/resources/software-development/basic-rules-engine-design-pattern/

Biz Nasil Cozduk?

Daha dogrusu ben nasil cozdum? ­čÖé Egoyla ilgili degil ama bu konuda israr eden, degistirmesi daha kolay olan bir yapi kurmak isteyen bendim. Ve asagidaki UML diagramina benzer bir cozum ortaya cikti.

@suleyman.yildirim

Kural Arayuzu: IEligibilityRule

Aslinda burada yaptigimiz if-else cumlesindeki kosulu ve eylemi iki farkli fonksiyon olarak tanimlamak. Boylece her if-else yapisini specific siniflara donusturebiliyoruz, ornegin RuleForEligibleStatus.

if(RuleForIneligibleStatus){ --> isRuleApplicable() fonksiyonu 

  // send notification for ineligible customers
  sendNotificationForRuleForIneligibleStatus(); --> executeEligibilityRule() fonksiyonu
  
} 

Spesifik Kural Ornegi: RuleForEligibleStatus

IEligibilityRule ‘dan turetilen her bir sinifi bir if-else blogu olarak dusunun. Ornegin, RuleForEligibleStatus kuralinin hangi kosullarda calismasi gerektigini isRuleApplicable arayuz fonksiyonunu yeniden tanimlayarak belirliyoruz. Kuralin gecerli olmasi halinde ise nasil bir eylemde bulunacagini ise executeEligibilityRule fonksiyonuyla tanimliyoruz. Kisacasi IEligibilityRule‘dan turettigimiz her sinif icin isRuleApplicable ve executeEligibilityRule fonksiyonlari farkli eylemler iceriyor.

public class RuleForEligibleStatus implements IEligibilityRule {

    ...

    @Override
    public boolean isRuleApplicable(Eligibility eligibility) {
        return eligibility.getXXX()
                .map(Offer::isEligible)
                .filter(isEligible -> isEligible.equals(true))
                .isPresent();
    }

    @Override
    public void executeEligibilityRule(Eligibility eligibility, Notification notification) {
        if (this.isRuleApplicable(eligibility)) {
            eligibility.getXXX()
                    .ifPresent(details -> {
                        // perform some action here...
                    });
        }
    }
}

Kurallarin Calistirilmasi: EligibilityRuleEvaluator

Bu sinifta kurallari EligibilityRuleEvaluator() yapilandiricisi (constructor) icinde tanimliyoruz. Kurallari nasil calistiracagimiz ise is tanimina gore degisiyor. Bizim uygulamamizda, kurallarin oncelikleri var ve bir kural calistigi anda sonraki kural istetilmeden islem sonlandiriliyor. Bu nedenle evaluateRules fonksiyonunda, her kurali teker teker kontrol ederek isRuleApplicable fonksiyonu ile ilk gecerli olan kurali isletiyoruz.

public class EligibilityRuleEvaluator implements IEligibilityEvaluator {

    private final List<IEligibilityRule> rules = new ArrayList<>();

    /**
     * Kural siniflarini yapilandirici icinde tanimliyoruz
     */
    public EligibilityRuleEvaluator() {
        ...//rules   
        rules.add(new RuleForIneligibleStatus());
        rules.add(new RuleForOpenUpgradeOrderStatus());
        rules.add(new RuleForMultipleSubscriptions());
        rules.add(new RuleForEligibleStatus());
    }

    @Override
    public void evaluateRules(Eligibility eligibility, Notification notification) {
        IEligibilityRule rule = rules.stream()
                .filter(r -> r.isRuleApplicable(eligibility))
                .findFirst()
                .orElseThrow(() -> new IllegalArgumentException("Expression does not matches any Rule"));
        rule.executeEligibilityRule(eligibility, notification);
    }
}

Son Adim

Son olarak kurallari calistirmamiz icin EligibilityRuleEvaluator sinifinin evaluateRules fonksiyonunu cagirmamiz gerekiyor. Bu sekilde if-else yapisi kullanmadan is kurallarini daha moduler, surdurulebilir ve degisime acik bir sekilde koda dokebiliriz.

    /**
     * Executes eligibility rules based on the priority list defined in {@link EligibilityRuleEvaluator}
     */
    public void executeEligibilityRule(Notification notification, Eligibility upgradeEligibility) {
	    var eligibilityRuleEvaluator = new EligibilityRuleEvaluator();
	    eligibilityRuleEvaluator.evaluateRules(eligibility, notification);
 }

Referenslar

Leave a Reply

Your email address will not be published. Required fields are marked *