Development/Code

Spring Boot에서 자신만의 Rule Engine (룰 엔진) 구현하기

Danny Seo 2024. 6. 27. 15:12

목차

    룰 엔진 (Rule Engine) 은 입력 데이터에 대해 규칙을 실행하고 조건이 맞으면 해당 작업을 실행하는 전문 시스템 프로그램입니다.

    Rule Engine : Spring Boot로 구현하는 룰 엔진

    소프트웨어 개발에서 데이터를 필터링하고 처리하기 위해 여러 규칙이나 조건을 적용해야 하는 상황이 자주 발생합니다. 전통적인 if와 else 조건문을 사용하여 이러한 규칙을 관리하면 복잡하고 유지 보수가 어려워질 수 있습니다.

     

    룰 엔진 (Rule Engine)을 사용하면 이러한 규칙을 보다 유연하고 체계적으로 정의하고 실행할 수 있습니다.

     

    이 글에서는 Java 함수형 프로그래밍 원칙을 사용하여 Spring Boot 프로젝트에서 간단한 룰 엔진 (Rule Engine)을 구축하는 방법을 살펴보겠습니다.

    룰 엔진 (Rule Engine) 이란?

    룰 엔진 (Rule Engine)은 여러 규칙을 관리하고 객체 집합에 이를 적용하는 역할을 합니다. 규칙에 따라 객체를 필터링하고 필터링된 결과를 반환합니다.

    룰 엔진 (Rule Engine) 만들기 단계별 가이드

    먼저, Spring Initializr를 사용하여 간단한 스프링 부트 애플리케이션을 생성합니다. 이 애플리케이션에서 사용자 목록을 반환하는 REST 엔드포인트를 만들겠습니다.

    1. UserController.java 클래스 생성

    package com.example.spring_boot_rule_engine_demo.controller;
    
    import com.example.spring_boot_rule_engine_demo.model.User;
    import com.example.spring_boot_rule_engine_demo.engine.RuleEngine;
    import com.example.spring_boot_rule_engine_demo.rule.Rule;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RestController;
    import org.springframework.web.bind.annotation.GetMapping;
    import java.util.List;
    
    @RestController
    @RequestMapping("/user")
    public class UserController {
    
        @GetMapping
        public List<User> getAllUser() {
            return userData();
        }
    
        private List<User> userData() {
            return List.of(
                new User("sumit", 33),
                new User("agam", 13),
                new User("mohit", 23),
                new User("kailash", 43)
            );
        }
    }

    Rule Engine 구현 예제 #1

    여기서 나이가 30세 이상이고 이름이 'sum'으로 시작하는 사용자를 가져와야 한다고 생각해 봅시다.

    이럴 경우, 두 가지 규칙을 만들고, 클라이언트에게 응답을 반환하기 전에 적용해야 합니다.

    2. Rule (규칙) 정의

    먼저 Rule이라는 enum을 사용하여 규칙을 정의하겠습니다.

    enum의 각 규칙은 우리가 평가하고자 하는 특정 조건이나 기준을 나타냅니다.

    package com.example.spring_boot_rule_engine_demo.rule;
    
    import com.example.spring_boot_rule_engine_demo.model.User;
    import java.util.function.Predicate;
    
    public enum Rule implements TestRule {
    
        AGE_GREATER_THAN_30(user -> user.getAge() > 30),
        NAME_STARTS_WITH_SUM(user -> user.getName().startsWith("sum"));
    
        private final Predicate<User> predicate;
    
        Rule(Predicate<User> predicate) {
            this.predicate = predicate;
        }
    
        @Override
        public Predicate<User> getPredicate() {
            return predicate;
        }
    }

    3. TestRule 인터페이스 생성

    구현 없이 메서드를 정의하기 위해 TestRule 인터페이스를 만들겠습니다.

    package com.example.spring_boot_rule_engine_demo.rule;
    
    import java.util.function.Predicate;
    
    public interface TestRule {
        <T> Predicate<T> getPredicate();
    }

    4.  Rule Engine (룰 엔진) 클래스 생성

    이 클래스는 규칙의 관리와 실행을 담당합니다. 규칙 목록을 유지하며, 새로운 규칙을 추가하고 이러한 규칙을 기반으로 객체를 필터링하는 메서드를 제공합니다.RuleEngine 클래스의 filter 메서드에서 객체 목록을 반복하며 Java 8 Stream API의 allMatch 메서드를 사용해 각 규칙을 적용합니다. 이 메서드는 주어진 객체가 모든 규칙을 통과하는지 확인합니다. 모든 규칙을 통과하면 해당 객체를 필터링된 목록에 추가합니다.

    package com.example.spring_boot_rule_engine_demo.engine;
    
    import com.example.spring_boot_rule_engine_demo.rule.TestRule;
    import java.util.ArrayList;
    import java.util.List;
    import java.util.function.Predicate;
    import java.util.stream.Collectors;
    
    public class RuleEngine<T> {
        private final List<TestRule> rules;
    
        public RuleEngine() {
            this.rules = new ArrayList<>();
        }
    
        public void addRule(TestRule rule) {
            rules.add(rule);
        }
    
        public List<T> filter(List<T> items) {
            return items.stream()
                        .filter(item -> rules.stream()
                                             .map(TestRule::getPredicate)
                                             .allMatch(predicate -> predicate.test(item)))
                        .collect(Collectors.toList());
        }
    }

     

    filter메서드는 규칙 목록에 저장된 규칙을 기반으로 항목 목록을 필터링합니다:
    1. 입력으로 T 타입의 항목 목록을 받습니다.
    2. items.stream()을 사용해 항목 목록에서 스트림을 생성합니다.
    3. filter 메서드는 각 항목에 대해 해당 항목이 모든 규칙을 만족하는지 확인합니다:
         rules.stream()은 규칙 목록에서 스트림을 생성합니다.
         map(TestRule::getPredicate)는 각 규칙의 조건을 가져옵니다.
         allMatch(predicate -> predicate.test(item))는 항목이 모든 규칙을 만족하는지 확인합니다.
    4. 모든 규칙을 만족하는 항목은 필터링된 목록에 추가됩니다.
    5. 마지막으로, collect(Collectors.toList())를 사용해 필터링된 항목을 새로운 목록으로 수집하여 반환합니다.

    5. User 클래스 생성

    package com.example.spring_boot_rule_engine_demo.model;
    
    public class User {
        private String name;
        private int age;
    
        public User(String name, int age) {
            this.name = name;
            this.age = age;
        }
    
        public User() {}
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    
        public int getAge() {
            return age;
        }
    
        public void setAge(int age) {
            this.age = age;
        }
    }

     

    6. Rule Engine (룰 엔진) 사용하기

    이제 UserController.java 클래스에서 룰 엔진 (Rule Engine) 을 사용하여 필터링된 사용자 목록을 반환하도록 수정합니다.

    먼저, 우리는 사용자 목록을 생성하고, AGE_GREATER_THAN_30과 NAME_STARTS_WITH_SUM 두 가지 규칙으로 RuleEngine을 초기화합니다. 그런 다음 Rule Engine 클래스의 filter 메서드를 호출하여 사용자 객체 목록을 전달합니다. Rule Engine 클래스는 각 사용자에게 규칙을 적용하고 필터링된 목록을 반환합니다.

     

    마지막으로, 필터링된 목록을 반복하고 지정된 규칙을 충족하는 사용자 목록을 반환합니다.

    package com.example.spring_boot_rule_engine_demo.controller;
    
    import com.example.spring_boot_rule_engine_demo.engine.RuleEngine;
    import com.example.spring_boot_rule_engine_demo.model.User;
    import com.example.spring_boot_rule_engine_demo.rule.Rule;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RestController;
    import org.springframework.web.bind.annotation.GetMapping;
    import java.util.List;
    
    @RestController
    @RequestMapping("/user")
    public class UserController {
    
        @GetMapping
        public List<User> getAllUser() {
            return getFilteredUsers();
        }
    
        private List<User> getFilteredUsers() {
            RuleEngine<User> ruleEngine = new RuleEngine<>();
            ruleEngine.addRule(Rule.AGE_GREATER_THAN_30);
            ruleEngine.addRule(Rule.NAME_STARTS_WITH_SUM);
            return ruleEngine.filter(userData());
        }
    
        private List<User> userData() {
            return List.of(
                new User("sumit", 33),
                new User("agam", 13),
                new User("mohit", 23),
                new User("kailash", 43)
            );
        }
    }

     

    이제 규칙을 적용한 후 필터링된 사용자 목록을 응답으로 받을 수 있습니다.

     

    읽어주셔서 감사합니다! 😊
    개발 관련 궁금증이나 고민이 있으신가요?
    아래 링크를 통해 저에게 바로 문의해 주세요! 쉽고 빠르게 도움 드리겠습니다.

    '개발자서동우' 프로필 보기