Spring Boot & Drools 룰 엔진 사용해보기

회사 프로젝트 중에서 Drools 룰 엔진을 사용하는 프로젝트를 진행하면서, Drools 룰 엔진에 대한 학습 및 테스트 위해서 만든 예제를 중심으로 Spring Boot에서 Drools 룰 엔진 사용을 하는 방법에 대한 내용을 정리한 내용임.
작성된 예제 코드는 아래 GitHub 저장소에서 확인 가능하다.
kenux196/drools-springboot-ex

예제 소스에서 구현된 기능은 가상의 온도 센서의 온도 변화에 따른 디바이스(에어컨과 같은) 제어(Power on / off) 처리를 Drools 룰 엔진으로 하는 간단한 예제이다.

Drools 룰 엔진에 대한 내용은 국내에 서적도 거의 없고, 검색으로 찾을 수 있는 한글화된 사이트가 거의 없다. 검색된 내용도 대부분이 과거 버전에서 테스트된 내용이었다. Drools 룰 엔진 공식 사이트를 살펴보는 것이 좋은 방법일 것이라 생각되고, 나 역시 공식 사이트를 참고하였다.

drools 공식 사이트

Drools Dependency 추가

build.gradle 파일

ext {
	droolsVersion = "7.0.0.Final"
}

dependencies {
	implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
	implementation 'org.springframework.boot:spring-boot-starter-validation'
	implementation 'org.springframework.boot:spring-boot-starter-web'
	compileOnly 'org.projectlombok:lombok'
	developmentOnly 'org.springframework.boot:spring-boot-devtools'
	runtimeOnly 'com.h2database:h2'
	runtimeOnly 'org.postgresql:postgresql'
	annotationProcessor 'org.projectlombok:lombok'
	testImplementation 'org.springframework.boot:spring-boot-starter-test'

	implementation 'org.slf4j:slf4j-api'

	implementation "org.kie:kie-api:${droolsVersion}"
	implementation "org.kie:kie-ci:${droolsVersion}"
	implementation "org.kie:kie-spring:${droolsVersion}"
	implementation "org.drools:drools-core:${droolsVersion}"
	implementation "org.drools:drools-compiler:${droolsVersion}"
//    implementation "org.drools:drools-mvel:${droolsVersion}"
	implementation "org.drools:drools-templates:${droolsVersion}"
//    implementation "org.drools:drools-bom:${droolsVersion}"

}

Rule 파일 작성

resources 아래에 rules 디렉토리 생성하고, xxx.drl 파일을 다음과 같이 작성한다.

import study.example.drools.domain.Device
import study.example.drools.domain.TempSensor

rule "룰 - 실내 온도가 30도 이상이면 에어컨을 켠다"
    no-loop true
    lock-on-active true
    when
      $tempSensor : TempSensor(indoorTemp >= 30)
      $device : Device(operating == false)
    then
      modify($device) { setOperating(true) }
      System.out.println($device.getDeviceInfo() + " Power On");
    end

rule "룰 - 실내 온도가 30도 미만이면 에어컨 끈다"
    no-loop true
    lock-on-active true
    when
      $tempSensor : TempSensor(indoorTemp < 30)
      $device : Device(operating == true)
    then
      modify($device) { setOperating(false) }
      System.out.println($device.getDeviceInfo() + " Power Off");
    end

Configuration 추가

DroolsAutoConfiguration 클래스 생성한다.

@Configuration
public class DroolsAutoConfiguration {

    public static final String RULES_PATH = "rules/";

    @Bean
    @ConditionalOnMissingBean(KieFileSystem.class)
    public KieFileSystem kieFileSystem() throws IOException {
        KieFileSystem kieFileSystem = getKieServices().newKieFileSystem();
        for (Resource file : getRuleFiles()) {
            kieFileSystem.write(ResourceFactory.newClassPathResource(RULES_PATH + file.getFilename(), "UTF-8"));
        }
        return kieFileSystem;
    }

    private Resource[] getRuleFiles() throws IOException {
        ResourcePatternResolver resourcePatternResolver = new PathMatchingResourcePatternResolver();
        return resourcePatternResolver.getResources("classpath*:" + RULES_PATH + "**/*.*");
    }

    @Bean
    @ConditionalOnMissingBean(KieContainer.class)
    public KieContainer kieContainer() throws IOException {
        final KieRepository kieRepository = getKieServices().getRepository();
        kieRepository.addKieModule(kieRepository::getDefaultReleaseId);

        KieBuilder kieBuilder = getKieServices().newKieBuilder(kieFileSystem());
        kieBuilder.buildAll();

        return getKieServices().newKieContainer(kieRepository.getDefaultReleaseId());
    }

    private KieServices getKieServices() {
        return KieServices.Factory.get();
    }

    @Bean
    @ConditionalOnMissingBean(KieBase.class)
    public KieBase kieBase() throws IOException {
        return kieContainer().getKieBase();
    }

    @Bean
    @ConditionalOnMissingBean(KieSession.class)
    public KieSession kieSession() throws IOException {
        return kieContainer().newKieSession();
    }

    @Bean
    @ConditionalOnMissingBean(KModuleBeanFactoryPostProcessor.class)
    public KModuleBeanFactoryPostProcessor kiePostProcessor() {
        return new KModuleBeanFactoryPostProcessor();
    }
}

Fact 설정

본 예제에서는 온도 센서의 온도 값에 따라 에어컨 제어를 하는 모델의 예제이므로 Device, TempSensor class 추가하였다.

@Data
public class Device {
    private Long id;
    private DeviceType type;
    private Boolean operating;

    public String getDeviceName() {
        return id + "번 디바이스";
    }

    public static Device createAirConditioner(long id, boolean operating) {
        return new Device(id, DeviceType.AIR_CONDITIONER, operating);
    }

    public Device(long id, DeviceType type, boolean operating) {
        this.id = id;
        this.type = type;
        this.operating = false;
    }

    public String getDeviceInfo() {
        return id + "번 디바이스 : " + type.getName();
    }

}


@Data
@ToString
public class TempSensor {
    private int indoorTemp;
}

DeviceService 추가

Drools 룰 엔진을 이용해서 Device 제어 기능을 제공하는 서비스 구현

@Slf4j
@Service
@RequiredArgsConstructor
public class DeviceService {

    private final KieContainer kieContainer;
    private final DeviceRepository deviceRepository;

    private KieSession kieSession;

    private final TempSensor tempSensor = new TempSensor();

    @PostConstruct
    private void initService() {
        kieSession = kieContainer.newKieSession();
        kieSession.addEventListener(new CustomAgendaEventListener());
        kieSession.addEventListener(new CustomRuleRunTimeEventListener());
        kieSession.addEventListener(new CustomProcessEventListener());
    }

    public FactHandle addDevice(Device device) {
        deviceRepository.addDevice(device);
        FactHandle factHandle = kieSession.insert(device);
        kieSession.fireAllRules();
        printFactSize("기기 추가 => " + device.getDeviceInfo(), false);
        return factHandle;
    }

    public void updateSensorData(int temp) {
        log.debug("온도 센서 값 변경 = " + temp);
        tempSensor.setIndoorTemp(temp);
        FactHandle factHandle = kieSession.getFactHandle(tempSensor);
        if (factHandle != null) {
            kieSession.update(factHandle, tempSensor); // update exist fact
            log.debug("온도 센서 Fact 업데이트");
        } else {
            kieSession.insert(tempSensor); // insert new fact
            log.debug("온도 센서 Fact 추가");
        }
        kieSession.fireAllRules();
        printFactSize("온도 값 변경 완료", false);
    }

    public Collection<FactHandle> getFactHandles() {
        return kieSession.getFactHandles();
    }

    private void printFactSize(String msg, boolean showFactHandleInfo) {
        log.info(msg + " ==> kieSession.getFactCount() = " + kieSession.getFactCount());

        if (showFactHandleInfo) {
            Collection<FactHandle> factHandles = kieSession.getFactHandles();
            for (FactHandle factHandle : factHandles) {
                log.info("factHandle = " + factHandle.toExternalForm());
            }
        }
    }

    public List<Device> getDevices() {
        return deviceRepository.getDeviceList();
    }
}

Test

@SpringBootTest
class DeviceServiceTest {

    @Autowired
    DeviceService deviceService;

    @Test
    @DisplayName("기기 추가 테스트")
    void addDeviceTest() {
        final Device device = Device.createAirConditioner(1, false);
        deviceService.addDevice(device);
        final Collection<FactHandle> factHandles = deviceService.getFactHandles();
        assertThat(factHandles).hasSize(1);
    }

    @Test
    @DisplayName("온도가 30도 이상이면 에어컨 켠다.")
    void airConditionerOnTest() {
        final Device device = Device.createAirConditioner(1, false);
        deviceService.addDevice(device);
        deviceService.updateSensorData(35);

        assertThat(device.getOperating()).isTrue();
    }

    @Test
    @DisplayName("온도가 30도 미만이면 에어컨 끈다")
    void airConditionerOffTest() {
        final Device device = Device.createAirConditioner(1, true);
        deviceService.addDevice(device);
        deviceService.updateSensorData(24);

        assertThat(device.getOperating()).isFalse();
    }
}

아래는 온도 센서 값이 30도 이상인 경우에 대한 테스트와 결과이다

@Test
@DisplayName("온도가 30도 이상이면 에어컨 켠다.")
void airConditionerOnTest() {
    final Device device = Device.createAirConditioner(1, false);
    deviceService.addDevice(device);
    deviceService.updateSensorData(35);
    assertThat(device.getOperating()).isTrue();
}
objectInserted event.getObject().getClass() = class study.example.drools.domain.Device
objectInserted event.getFactHandle() = [fact 0:1:662786209:-314770286:1:DEFAULT:NON_TRAIT:study.example.drools.domain.Device:Device(id=1, type=AIR_CONDITIONER, operating=false, dummy=[{"name":null,"nameDisplayCoordinate":null,"type":null,"mappedType":null,"description":null,"parentId":null,"registrationStatus":null,"configuration":{"mobilityType":null,"remoteControl":"Permit","time":null,"locationType":null},"modes":[{"id":"AirConditioner_Indoor_IndoorMode","description":null,"enabled":null,"value":"Auto"},{"id":"AirConditioner_Indoor_ErvMode","description":null,"enabled":null,"value":"Auto"},{"id":"AirConditioner_Indoor_DhwMode","description":null,"enabled":null,"value":"Standard"}],"operations":[{"id":"AirConditioner_Indoor_IndoorPower","value":"on"},{"id":"AirConditioner_Indoor_ErvPower","value":"on"},{"id":"AirConditioner_Indoor_DhwPower","value":"on"}],"temperatures":[{"id":"AirConditioner_Indoor_IndoorSetTemp","enabled":null,"unit":null,"desired":24.0},{"id":"AirConditioner_Indoor_WateroutSetTemp","enabled":null,"unit":null,"desired":24.0},{"id":"AirConditioner_Indoor_DhwSetTemp","enabled":null,"unit":null,"desired":40.0}],"meters":null,"lights":null,"id":null,"airConditioner":null}, {"name":null,"nameDisplayCoordinate":null,"type":null,"mappedType":null,"description":null,"parentId":null,"registrationStatus":null,"configuration":{"mobilityType":null,"remoteControl":"Permit","time":null,"locationType":null},"modes":[{"id":"AirConditioner_Indoor_IndoorMode","description":null,"enabled":null,"value":"Auto"},{"id":"AirConditioner_Indoor_ErvMode","description":null,"enabled":null,"value":"Auto"},{"id":"AirConditioner_Indoor_DhwMode","description":null,"enabled":null,"value":"Standard"}],"operations":[{"id":"AirConditioner_Indoor_IndoorPower","value":"on"},{"id":"AirConditioner_Indoor_ErvPower","value":"on"},{"id":"AirConditioner_Indoor_DhwPower","value":"on"}],"temperatures":[{"id":"AirConditioner_Indoor_IndoorSetTemp","enabled":null,"unit":null,"desired":24.0},{"id":"AirConditioner_Indoor_WateroutSetTemp","enabled":null,"unit":null,"desired":24.0},{"id":"AirConditioner_Indoor_DhwSetTemp","enabled":null,"unit":null,"desired":40.0}],"meters":null,"lights":null,"id":null,"airConditioner":null}, {"name":null,"nameDisplayCoordinate":null,"type":null,"mappedType":null,"description":null,"parentId":null,"registrationStatus":null,"configuration":{"mobilityType":null,"remoteControl":"Permit","time":null,"locationType":null},"modes":[{"id":"AirConditioner_Indoor_IndoorMode","description":null,"enabled":null,"value":"Auto"},{"id":"AirConditioner_Indoor_ErvMode","description":null,"enabled":null,"value":"Auto"},{"id":"AirConditioner_Indoor_DhwMode","description":null,"enabled":null,"value":"Standard"}],"operations":[{"id":"AirConditioner_Indoor_IndoorPower","value":"on"},{"id":"AirConditioner_Indoor_ErvPower","value":"on"},{"id":"AirConditioner_Indoor_DhwPower","value":"on"}],"temperatures":[{"id":"AirConditioner_Indoor_IndoorSetTemp","enabled":null,"unit":null,"desired":24.0},{"id":"AirConditioner_Indoor_WateroutSetTemp","enabled":null,"unit":null,"desired":24.0},{"id":"AirConditioner_Indoor_DhwSetTemp","enabled":null,"unit":null,"desired":40.0}],"meters":null,"lights":null,"id":null,"airConditioner":null}, {"name":null,"nameDisplayCoordinate":null,"type":null,"mappedType":null,"description":null,"parentId":null,"registrationStatus":null,"configuration":{"mobilityType":null,"remoteControl":"Permit","time":null,"locationType":null},"modes":[{"id":"AirConditioner_Indoor_IndoorMode","description":null,"enabled":null,"value":"Auto"},{"id":"AirConditioner_Indoor_ErvMode","description":null,"enabled":null,"value":"Auto"},{"id":"AirConditioner_Indoor_DhwMode","description":null,"enabled":null,"value":"Standard"}],"operations":[{"id":"AirConditioner_Indoor_IndoorPower","value":"on"},{"id":"AirConditioner_Indoor_ErvPower","value":"on"},{"id":"AirConditioner_Indoor_DhwPower","value":"on"}],"temperatures":[{"id":"AirConditioner_Indoor_IndoorSetTemp","enabled":null,"unit":null,"desired":24.0},{"id":"AirConditioner_Indoor_WateroutSetTemp","enabled":null,"unit":null,"desired":24.0},{"id":"AirConditioner_Indoor_DhwSetTemp","enabled":null,"unit":null,"desired":40.0}],"meters":null,"lights":null,"id":null,"airConditioner":null}, {"name":null,"nameDisplayCoordinate":null,"type":null,"mappedType":null,"description":null,"parentId":null,"registrationStatus":null,"configuration":{"mobilityType":null,"remoteControl":"Permit","time":null,"locationType":null},"modes":[{"id":"AirConditioner_Indoor_IndoorMode","description":null,"enabled":null,"value":"Auto"},{"id":"AirConditioner_Indoor_ErvMode","description":null,"enabled":null,"value":"Auto"},{"id":"AirConditioner_Indoor_DhwMode","description":null,"enabled":null,"value":"Standard"}],"operations":[{"id":"AirConditioner_Indoor_IndoorPower","value":"on"},{"id":"AirConditioner_Indoor_ErvPower","value":"on"},{"id":"AirConditioner_Indoor_DhwPower","value":"on"}],"temperatures":[{"id":"AirConditioner_Indoor_IndoorSetTemp","enabled":null,"unit":null,"desired":24.0},{"id":"AirConditioner_Indoor_WateroutSetTemp","enabled":null,"unit":null,"desired":24.0},{"id":"AirConditioner_Indoor_DhwSetTemp","enabled":null,"unit":null,"desired":40.0}],"meters":null,"lights":null,"id":null,"airConditioner":null}, {"name":null,"nameDisplayCoordinate":null,"type":null,"mappedType":null,"description":null,"parentId":null,"registrationStatus":null,"configuration":{"mobilityType":null,"remoteControl":"Permit","time":null,"locationType":null},"modes":[{"id":"AirConditioner_Indoor_IndoorMode","description":null,"enabled":null,"value":"Auto"},{"id":"AirConditioner_Indoor_ErvMode","description":null,"enabled":null,"value":"Auto"},{"id":"AirConditioner_Indoor_DhwMode","description":null,"enabled":null,"value":"Standard"}],"operations":[{"id":"AirConditioner_Indoor_IndoorPower","value":"on"},{"id":"AirConditioner_Indoor_ErvPower","value":"on"},{"id":"AirConditioner_Indoor_DhwPower","value":"on"}],"temperatures":[{"id":"AirConditioner_Indoor_IndoorSetTemp","enabled":null,"unit":null,"desired":24.0},{"id":"AirConditioner_Indoor_WateroutSetTemp","enabled":null,"unit":null,"desired":24.0},{"id":"AirConditioner_Indoor_DhwSetTemp","enabled":null,"unit":null,"desired":40.0}],"meters":null,"lights":null,"id":null,"airConditioner":null}, {"name":null,"nameDisplayCoordinate":null,"type":null,"mappedType":null,"description":null,"parentId":null,"registrationStatus":null,"configuration":{"mobilityType":null,"remoteControl":"Permit","time":null,"locationType":null},"modes":[{"id":"AirConditioner_Indoor_IndoorMode","description":null,"enabled":null,"value":"Auto"},{"id":"AirConditioner_Indoor_ErvMode","description":null,"enabled":null,"value":"Auto"},{"id":"AirConditioner_Indoor_DhwMode","description":null,"enabled":null,"value":"Standard"}],"operations":[{"id":"AirConditioner_Indoor_IndoorPower","value":"on"},{"id":"AirConditioner_Indoor_ErvPower","value":"on"},{"id":"AirConditioner_Indoor_DhwPower","value":"on"}],"temperatures":[{"id":"AirConditioner_Indoor_IndoorSetTemp","enabled":null,"unit":null,"desired":24.0},{"id":"AirConditioner_Indoor_WateroutSetTemp","enabled":null,"unit":null,"desired":24.0},{"id":"AirConditioner_Indoor_DhwSetTemp","enabled":null,"unit":null,"desired":40.0}],"meters":null,"lights":null,"id":null,"airConditioner":null}, {"name":null,"nameDisplayCoordinate":null,"type":null,"mappedType":null,"description":null,"parentId":null,"registrationStatus":null,"configuration":{"mobilityType":null,"remoteControl":"Permit","time":null,"locationType":null},"modes":[{"id":"AirConditioner_Indoor_IndoorMode","description":null,"enabled":null,"value":"Auto"},{"id":"AirConditioner_Indoor_ErvMode","description":null,"enabled":null,"value":"Auto"},{"id":"AirConditioner_Indoor_DhwMode","description":null,"enabled":null,"value":"Standard"}],"operations":[{"id":"AirConditioner_Indoor_IndoorPower","value":"on"},{"id":"AirConditioner_Indoor_ErvPower","value":"on"},{"id":"AirConditioner_Indoor_DhwPower","value":"on"}],"temperatures":[{"id":"AirConditioner_Indoor_IndoorSetTemp","enabled":null,"unit":null,"desired":24.0},{"id":"AirConditioner_Indoor_WateroutSetTemp","enabled":null,"unit":null,"desired":24.0},{"id":"AirConditioner_Indoor_DhwSetTemp","enabled":null,"unit":null,"desired":40.0}],"meters":null,"lights":null,"id":null,"airConditioner":null}, {"name":null,"nameDisplayCoordinate":null,"type":null,"mappedType":null,"description":null,"parentId":null,"registrationStatus":null,"configuration":{"mobilityType":null,"remoteControl":"Permit","time":null,"locationType":null},"modes":[{"id":"AirConditioner_Indoor_IndoorMode","description":null,"enabled":null,"value":"Auto"},{"id":"AirConditioner_Indoor_ErvMode","description":null,"enabled":null,"value":"Auto"},{"id":"AirConditioner_Indoor_DhwMode","description":null,"enabled":null,"value":"Standard"}],"operations":[{"id":"AirConditioner_Indoor_IndoorPower","value":"on"},{"id":"AirConditioner_Indoor_ErvPower","value":"on"},{"id":"AirConditioner_Indoor_DhwPower","value":"on"}],"temperatures":[{"id":"AirConditioner_Indoor_IndoorSetTemp","enabled":null,"unit":null,"desired":24.0},{"id":"AirConditioner_Indoor_WateroutSetTemp","enabled":null,"unit":null,"desired":24.0},{"id":"AirConditioner_Indoor_DhwSetTemp","enabled":null,"unit":null,"desired":40.0}],"meters":null,"lights":null,"id":null,"airConditioner":null}, {"name":null,"nameDisplayCoordinate":null,"type":null,"mappedType":null,"description":null,"parentId":null,"registrationStatus":null,"configuration":{"mobilityType":null,"remoteControl":"Permit","time":null,"locationType":null},"modes":[{"id":"AirConditioner_Indoor_IndoorMode","description":null,"enabled":null,"value":"Auto"},{"id":"AirConditioner_Indoor_ErvMode","description":null,"enabled":null,"value":"Auto"},{"id":"AirConditioner_Indoor_DhwMode","description":null,"enabled":null,"value":"Standard"}],"operations":[{"id":"AirConditioner_Indoor_IndoorPower","value":"on"},{"id":"AirConditioner_Indoor_ErvPower","value":"on"},{"id":"AirConditioner_Indoor_DhwPower","value":"on"}],"temperatures":[{"id":"AirConditioner_Indoor_IndoorSetTemp","enabled":null,"unit":null,"desired":24.0},{"id":"AirConditioner_Indoor_WateroutSetTemp","enabled":null,"unit":null,"desired":24.0},{"id":"AirConditioner_Indoor_DhwSetTemp","enabled":null,"unit":null,"desired":40.0}],"meters":null,"lights":null,"id":null,"airConditioner":null}], base={"name":null,"nameDisplayCoordinate":null,"type":null,"mappedType":null,"description":null,"parentId":null,"registrationStatus":null,"configuration":{"mobilityType":null,"remoteControl":"Permit","time":null,"locationType":null},"modes":[{"id":"AirConditioner_Indoor_IndoorMode","description":null,"enabled":null,"value":"Auto"},{"id":"AirConditioner_Indoor_ErvMode","description":null,"enabled":null,"value":"Auto"},{"id":"AirConditioner_Indoor_DhwMode","description":null,"enabled":null,"value":"Standard"}],"operations":[{"id":"AirConditioner_Indoor_IndoorPower","value":"on"},{"id":"AirConditioner_Indoor_ErvPower","value":"on"},{"id":"AirConditioner_Indoor_DhwPower","value":"on"}],"temperatures":[{"id":"AirConditioner_Indoor_IndoorSetTemp","enabled":null,"unit":null,"desired":24.0},{"id":"AirConditioner_Indoor_WateroutSetTemp","enabled":null,"unit":null,"desired":24.0},{"id":"AirConditioner_Indoor_DhwSetTemp","enabled":null,"unit":null,"desired":40.0}],"meters":null,"lights":null,"id":null,"airConditioner":null})]
기기 추가 => 1번 디바이스 : AirConditioner ==> kieSession.getFactCount() = 1
objectInserted event.getObject().getClass() = class study.example.drools.domain.TempSensor
objectInserted event.getFactHandle() = [fact 0:2:660626311:94:2:DEFAULT:NON_TRAIT:study.example.drools.domain.TempSensor:TempSensor(indoorTemp=35)]
matchCreated event.getMatch() = [Rule name=룰 - 실내 온도가 30도 이상이면 에어컨을 켠다, agendaGroup=MAIN, salience=0, no-loop=true]
beforeMatchFired event.getMatch() = [Rule name=룰 - 실내 온도가 30도 이상이면 에어컨을 켠다, agendaGroup=MAIN, salience=0, no-loop=true]
objectUpdated event.getObject().getClass() = class study.example.drools.domain.Device
objectUpdated event.getFactHandle() = [fact 0:1:662786209:-314770286:3:DEFAULT:NON_TRAIT:study.example.drools.domain.Device:Device(id=1, type=AIR_CONDITIONER, operating=true, dummy=[{"name":null,"nameDisplayCoordinate":null,"type":null,"mappedType":null,"description":null,"parentId":null,"registrationStatus":null,"configuration":{"mobilityType":null,"remoteControl":"Permit","time":null,"locationType":null},"modes":[{"id":"AirConditioner_Indoor_IndoorMode","description":null,"enabled":null,"value":"Auto"},{"id":"AirConditioner_Indoor_ErvMode","description":null,"enabled":null,"value":"Auto"},{"id":"AirConditioner_Indoor_DhwMode","description":null,"enabled":null,"value":"Standard"}],"operations":[{"id":"AirConditioner_Indoor_IndoorPower","value":"on"},{"id":"AirConditioner_Indoor_ErvPower","value":"on"},{"id":"AirConditioner_Indoor_DhwPower","value":"on"}],"temperatures":[{"id":"AirConditioner_Indoor_IndoorSetTemp","enabled":null,"unit":null,"desired":24.0},{"id":"AirConditioner_Indoor_WateroutSetTemp","enabled":null,"unit":null,"desired":24.0},{"id":"AirConditioner_Indoor_DhwSetTemp","enabled":null,"unit":null,"desired":40.0}],"meters":null,"lights":null,"id":null,"airConditioner":null}, {"name":null,"nameDisplayCoordinate":null,"type":null,"mappedType":null,"description":null,"parentId":null,"registrationStatus":null,"configuration":{"mobilityType":null,"remoteControl":"Permit","time":null,"locationType":null},"modes":[{"id":"AirConditioner_Indoor_IndoorMode","description":null,"enabled":null,"value":"Auto"},{"id":"AirConditioner_Indoor_ErvMode","description":null,"enabled":null,"value":"Auto"},{"id":"AirConditioner_Indoor_DhwMode","description":null,"enabled":null,"value":"Standard"}],"operations":[{"id":"AirConditioner_Indoor_IndoorPower","value":"on"},{"id":"AirConditioner_Indoor_ErvPower","value":"on"},{"id":"AirConditioner_Indoor_DhwPower","value":"on"}],"temperatures":[{"id":"AirConditioner_Indoor_IndoorSetTemp","enabled":null,"unit":null,"desired":24.0},{"id":"AirConditioner_Indoor_WateroutSetTemp","enabled":null,"unit":null,"desired":24.0},{"id":"AirConditioner_Indoor_DhwSetTemp","enabled":null,"unit":null,"desired":40.0}],"meters":null,"lights":null,"id":null,"airConditioner":null}, {"name":null,"nameDisplayCoordinate":null,"type":null,"mappedType":null,"description":null,"parentId":null,"registrationStatus":null,"configuration":{"mobilityType":null,"remoteControl":"Permit","time":null,"locationType":null},"modes":[{"id":"AirConditioner_Indoor_IndoorMode","description":null,"enabled":null,"value":"Auto"},{"id":"AirConditioner_Indoor_ErvMode","description":null,"enabled":null,"value":"Auto"},{"id":"AirConditioner_Indoor_DhwMode","description":null,"enabled":null,"value":"Standard"}],"operations":[{"id":"AirConditioner_Indoor_IndoorPower","value":"on"},{"id":"AirConditioner_Indoor_ErvPower","value":"on"},{"id":"AirConditioner_Indoor_DhwPower","value":"on"}],"temperatures":[{"id":"AirConditioner_Indoor_IndoorSetTemp","enabled":null,"unit":null,"desired":24.0},{"id":"AirConditioner_Indoor_WateroutSetTemp","enabled":null,"unit":null,"desired":24.0},{"id":"AirConditioner_Indoor_DhwSetTemp","enabled":null,"unit":null,"desired":40.0}],"meters":null,"lights":null,"id":null,"airConditioner":null}, {"name":null,"nameDisplayCoordinate":null,"type":null,"mappedType":null,"description":null,"parentId":null,"registrationStatus":null,"configuration":{"mobilityType":null,"remoteControl":"Permit","time":null,"locationType":null},"modes":[{"id":"AirConditioner_Indoor_IndoorMode","description":null,"enabled":null,"value":"Auto"},{"id":"AirConditioner_Indoor_ErvMode","description":null,"enabled":null,"value":"Auto"},{"id":"AirConditioner_Indoor_DhwMode","description":null,"enabled":null,"value":"Standard"}],"operations":[{"id":"AirConditioner_Indoor_IndoorPower","value":"on"},{"id":"AirConditioner_Indoor_ErvPower","value":"on"},{"id":"AirConditioner_Indoor_DhwPower","value":"on"}],"temperatures":[{"id":"AirConditioner_Indoor_IndoorSetTemp","enabled":null,"unit":null,"desired":24.0},{"id":"AirConditioner_Indoor_WateroutSetTemp","enabled":null,"unit":null,"desired":24.0},{"id":"AirConditioner_Indoor_DhwSetTemp","enabled":null,"unit":null,"desired":40.0}],"meters":null,"lights":null,"id":null,"airConditioner":null}, {"name":null,"nameDisplayCoordinate":null,"type":null,"mappedType":null,"description":null,"parentId":null,"registrationStatus":null,"configuration":{"mobilityType":null,"remoteControl":"Permit","time":null,"locationType":null},"modes":[{"id":"AirConditioner_Indoor_IndoorMode","description":null,"enabled":null,"value":"Auto"},{"id":"AirConditioner_Indoor_ErvMode","description":null,"enabled":null,"value":"Auto"},{"id":"AirConditioner_Indoor_DhwMode","description":null,"enabled":null,"value":"Standard"}],"operations":[{"id":"AirConditioner_Indoor_IndoorPower","value":"on"},{"id":"AirConditioner_Indoor_ErvPower","value":"on"},{"id":"AirConditioner_Indoor_DhwPower","value":"on"}],"temperatures":[{"id":"AirConditioner_Indoor_IndoorSetTemp","enabled":null,"unit":null,"desired":24.0},{"id":"AirConditioner_Indoor_WateroutSetTemp","enabled":null,"unit":null,"desired":24.0},{"id":"AirConditioner_Indoor_DhwSetTemp","enabled":null,"unit":null,"desired":40.0}],"meters":null,"lights":null,"id":null,"airConditioner":null}, {"name":null,"nameDisplayCoordinate":null,"type":null,"mappedType":null,"description":null,"parentId":null,"registrationStatus":null,"configuration":{"mobilityType":null,"remoteControl":"Permit","time":null,"locationType":null},"modes":[{"id":"AirConditioner_Indoor_IndoorMode","description":null,"enabled":null,"value":"Auto"},{"id":"AirConditioner_Indoor_ErvMode","description":null,"enabled":null,"value":"Auto"},{"id":"AirConditioner_Indoor_DhwMode","description":null,"enabled":null,"value":"Standard"}],"operations":[{"id":"AirConditioner_Indoor_IndoorPower","value":"on"},{"id":"AirConditioner_Indoor_ErvPower","value":"on"},{"id":"AirConditioner_Indoor_DhwPower","value":"on"}],"temperatures":[{"id":"AirConditioner_Indoor_IndoorSetTemp","enabled":null,"unit":null,"desired":24.0},{"id":"AirConditioner_Indoor_WateroutSetTemp","enabled":null,"unit":null,"desired":24.0},{"id":"AirConditioner_Indoor_DhwSetTemp","enabled":null,"unit":null,"desired":40.0}],"meters":null,"lights":null,"id":null,"airConditioner":null}, {"name":null,"nameDisplayCoordinate":null,"type":null,"mappedType":null,"description":null,"parentId":null,"registrationStatus":null,"configuration":{"mobilityType":null,"remoteControl":"Permit","time":null,"locationType":null},"modes":[{"id":"AirConditioner_Indoor_IndoorMode","description":null,"enabled":null,"value":"Auto"},{"id":"AirConditioner_Indoor_ErvMode","description":null,"enabled":null,"value":"Auto"},{"id":"AirConditioner_Indoor_DhwMode","description":null,"enabled":null,"value":"Standard"}],"operations":[{"id":"AirConditioner_Indoor_IndoorPower","value":"on"},{"id":"AirConditioner_Indoor_ErvPower","value":"on"},{"id":"AirConditioner_Indoor_DhwPower","value":"on"}],"temperatures":[{"id":"AirConditioner_Indoor_IndoorSetTemp","enabled":null,"unit":null,"desired":24.0},{"id":"AirConditioner_Indoor_WateroutSetTemp","enabled":null,"unit":null,"desired":24.0},{"id":"AirConditioner_Indoor_DhwSetTemp","enabled":null,"unit":null,"desired":40.0}],"meters":null,"lights":null,"id":null,"airConditioner":null}, {"name":null,"nameDisplayCoordinate":null,"type":null,"mappedType":null,"description":null,"parentId":null,"registrationStatus":null,"configuration":{"mobilityType":null,"remoteControl":"Permit","time":null,"locationType":null},"modes":[{"id":"AirConditioner_Indoor_IndoorMode","description":null,"enabled":null,"value":"Auto"},{"id":"AirConditioner_Indoor_ErvMode","description":null,"enabled":null,"value":"Auto"},{"id":"AirConditioner_Indoor_DhwMode","description":null,"enabled":null,"value":"Standard"}],"operations":[{"id":"AirConditioner_Indoor_IndoorPower","value":"on"},{"id":"AirConditioner_Indoor_ErvPower","value":"on"},{"id":"AirConditioner_Indoor_DhwPower","value":"on"}],"temperatures":[{"id":"AirConditioner_Indoor_IndoorSetTemp","enabled":null,"unit":null,"desired":24.0},{"id":"AirConditioner_Indoor_WateroutSetTemp","enabled":null,"unit":null,"desired":24.0},{"id":"AirConditioner_Indoor_DhwSetTemp","enabled":null,"unit":null,"desired":40.0}],"meters":null,"lights":null,"id":null,"airConditioner":null}, {"name":null,"nameDisplayCoordinate":null,"type":null,"mappedType":null,"description":null,"parentId":null,"registrationStatus":null,"configuration":{"mobilityType":null,"remoteControl":"Permit","time":null,"locationType":null},"modes":[{"id":"AirConditioner_Indoor_IndoorMode","description":null,"enabled":null,"value":"Auto"},{"id":"AirConditioner_Indoor_ErvMode","description":null,"enabled":null,"value":"Auto"},{"id":"AirConditioner_Indoor_DhwMode","description":null,"enabled":null,"value":"Standard"}],"operations":[{"id":"AirConditioner_Indoor_IndoorPower","value":"on"},{"id":"AirConditioner_Indoor_ErvPower","value":"on"},{"id":"AirConditioner_Indoor_DhwPower","value":"on"}],"temperatures":[{"id":"AirConditioner_Indoor_IndoorSetTemp","enabled":null,"unit":null,"desired":24.0},{"id":"AirConditioner_Indoor_WateroutSetTemp","enabled":null,"unit":null,"desired":24.0},{"id":"AirConditioner_Indoor_DhwSetTemp","enabled":null,"unit":null,"desired":40.0}],"meters":null,"lights":null,"id":null,"airConditioner":null}, {"name":null,"nameDisplayCoordinate":null,"type":null,"mappedType":null,"description":null,"parentId":null,"registrationStatus":null,"configuration":{"mobilityType":null,"remoteControl":"Permit","time":null,"locationType":null},"modes":[{"id":"AirConditioner_Indoor_IndoorMode","description":null,"enabled":null,"value":"Auto"},{"id":"AirConditioner_Indoor_ErvMode","description":null,"enabled":null,"value":"Auto"},{"id":"AirConditioner_Indoor_DhwMode","description":null,"enabled":null,"value":"Standard"}],"operations":[{"id":"AirConditioner_Indoor_IndoorPower","value":"on"},{"id":"AirConditioner_Indoor_ErvPower","value":"on"},{"id":"AirConditioner_Indoor_DhwPower","value":"on"}],"temperatures":[{"id":"AirConditioner_Indoor_IndoorSetTemp","enabled":null,"unit":null,"desired":24.0},{"id":"AirConditioner_Indoor_WateroutSetTemp","enabled":null,"unit":null,"desired":24.0},{"id":"AirConditioner_Indoor_DhwSetTemp","enabled":null,"unit":null,"desired":40.0}],"meters":null,"lights":null,"id":null,"airConditioner":null}], base={"name":null,"nameDisplayCoordinate":null,"type":null,"mappedType":null,"description":null,"parentId":null,"registrationStatus":null,"configuration":{"mobilityType":null,"remoteControl":"Permit","time":null,"locationType":null},"modes":[{"id":"AirConditioner_Indoor_IndoorMode","description":null,"enabled":null,"value":"Auto"},{"id":"AirConditioner_Indoor_ErvMode","description":null,"enabled":null,"value":"Auto"},{"id":"AirConditioner_Indoor_DhwMode","description":null,"enabled":null,"value":"Standard"}],"operations":[{"id":"AirConditioner_Indoor_IndoorPower","value":"on"},{"id":"AirConditioner_Indoor_ErvPower","value":"on"},{"id":"AirConditioner_Indoor_DhwPower","value":"on"}],"temperatures":[{"id":"AirConditioner_Indoor_IndoorSetTemp","enabled":null,"unit":null,"desired":24.0},{"id":"AirConditioner_Indoor_WateroutSetTemp","enabled":null,"unit":null,"desired":24.0},{"id":"AirConditioner_Indoor_DhwSetTemp","enabled":null,"unit":null,"desired":40.0}],"meters":null,"lights":null,"id":null,"airConditioner":null})]
objectUpdated event.getRule().getName() = 룰 - 실내 온도가 30도 이상이면 에어컨을 켠다

afterMatchFired event.getMatch() = [Rule name=룰 - 실내 온도가 30도 이상이면 에어컨을 켠다, agendaGroup=MAIN, salience=0, no-loop=true]
온도 값 변경 완료 ==> kieSession.getFactCount() = 2

이상으로 간단하게 Drools 룰 엔진을 스프링 부트에 적용해서 테스트 해 보았다. 개인적으로 업무가 아니었으면, 전혀 몰랐을 내용인데, 경험해 볼 수 있어서 좋지만, 깊은 내용은 조금 더 공부하고 경험해봐야 할 것 같다.

참고 사항

Junit 테스트에서는 잘 동작하였는데, Controller를 붙여서 웹 서비스 형태로 동작을 시키니 룰 매칭 동작하지 않는 이슈가 있었다.
확인해 보니, spring-boot-devtools 설정되어 있으면 동작하지 않는다는 내용이 검색되어서 해당 dependency를 제거하니 잘 동작함. 관련 내용은 아래 링크에서 확인 가능하다.
No rules are fired in helloworld using Spring rest controller!
그리고, 이 이슈는 drools 7.26.0.Final 버전에서 수정되어서, 예제 소스에서는 해당 버전으로 변경하였다.

좋은 웹페이지 즐겨찾기