英文:
How to make a global variable in the drools file synchronous?
问题
我正在开发一个使用Spring Boot的应用程序,其中我创建了一些REST API。有一个Readings实体和一个Alert实体。我通过POST方法发送读数,在ReadingsService类的postReadings方法中,我使用KIE检查读数是否符合某些条件,并在rules.drl文件中编写了规则。我创建了一个Alert对象,并使用session.setGlobal方法将其设置为全局对象。然后我检查Alert对象是否为null,并将其保存到Alert Repository中。在Drools文件中,我添加了一个打印语句来检查是否正确创建了所有警报。所有的警报都通过打印语句正确地打印出来了,然而只有其中一些被保存到了警报repository中。有谁可以帮忙解决一下吗?
这是ReadingsService:
@Service
public class ReadingsServiceImpl implements ReadingsService{
@Autowired
ReadingsRepository repository;
@Autowired
VehicleRepository vehicleRepo;
@Autowired
private KieSession session;
@Autowired
AlertRepository alertRepo;
@Override
public List<Readings> findAll() {
return repository.findAll();
}
@Override
public Readings postReadings(Readings readings) {
Alert alert = new Alert();
session.setGlobal("alert", alert);
session.insert(readings);
session.fireAllRules();
if(!Optional.ofNullable(alert).map(o -> o.getVin()).orElse("").isBlank()) {
alertRepo.save(alert);
}
return repository.save(readings);
}
这是Drools文件:
import com.shiva.truckerapi.entity.Readings;
global com.shiva.truckerapi.entity.Alert alert;
rule "EngineCoolantOrCheckEngineLight"
when
readingsObject: Readings(engineCoolantLow || checkEngineLightOn);
then
alert.setVin(readingsObject.getVin());
alert.setPriority("LOW");
alert.setDescription("Engine Coolant LOW Or Check Engine Light ON");
alert.setTimestamp(readingsObject.getTimestamp());
alert.setLatitude(readingsObject.getLatitude());
alert.setLongitude(readingsObject.getLongitude());
System.out.println(alert.toString());
end;
英文:
I'm working on a Spring Boot application where I created some REST APIs. There is a Readings entity, Alert entity. I'm sending readings via POST method and in the postReadings
method of the ReadingsService
class I'm checking whether the readings match certain criteria using KIE
and wrote the rules in a rules.drl
file. I'm creating an Alert object and setting it as a global using session.setGlobal
method. Then I'm checking if the Alert object is null and saving it to the Alert Repository. In the drools file I added a print statement to check whether all the alerts are correctly created. All the alerts are correctly printed via the print statement, however, only some of them are being saved to the alert repository. Can anyone please help?
This is the ReadingsService
@Service
public class ReadingsServiceImpl implements ReadingsService{
@Autowired
ReadingsRepository repository;
@Autowired
VehicleRepository vehicleRepo;
@Autowired
private KieSession session;
@Autowired
AlertRepository alertRepo;
@Override
public List<Readings> findAll() {
return repository.findAll();
}
@Override
public Readings postReadings(Readings readings) {
Alert alert = new Alert();
session.setGlobal("alert", alert);
session.insert(readings);
session.fireAllRules();
if(!Optional.ofNullable(alert).map(o -> o.getVin()).orElse("").isBlank()) {
alertRepo.save(alert);
}
return repository.save(readings);
}
This is the drools file
import com.shiva.truckerapi.entity.Readings;
global com.shiva.truckerapi.entity.Alert alert;
rule "EngineCoolantOrCheckEngineLight"
when
readingsObject: Readings(engineCoolantLow || checkEngineLightOn);
then
alert.setVin(readingsObject.getVin());
alert.setPriotity("LOW");
alert.setDescription("Engine Coolant LOW Or Check Engine Light ON");
alert.setTimestamp(readingsObject.getTimestamp());
alert.setLatitude(readingsObject.getLatitude());
alert.setLongitude(readingsObject.getLongitude());
System.out.println(alert.toString());
end;
答案1
得分: 1
以下是翻译好的内容:
你的规则没有问题。但是你的保存由一个“if”语句保护,该语句检查非空/非null的VIN的存在,所以这可能导致缺失保存的问题:
if(!Optional.ofNullable(alert).map(o -> o.getVin()).orElse("").isBlank()) {
alertRepo.save(alert);
}
你可以轻松编写单元测试来验证这个行为 - 如果警报存在(在同一个方法中稍早实例化),并且有一个VIN,那么它就会保存;如果警报存在但没有VIN,它就不会保存。
这是一种非常老式的保存规则输出的方式。现在我们通常在规则的右侧使用带有副作用的操作,或者使用对象来传递信息。
你当前的设置甚至存在逻辑错误:你总是会有一个警报,因为你在将其添加到会话之前对其进行了实例化。
如果你想要像这样使用全局变量,你需要在Alert类本身中添加一些指示符来表示警报已被添加。因此,例如,如果你只是添加一个带有适当的getter的boolean added = false
方法,你可以像这样更新你的规则:
rule "Engine Coolant Or Check Engine Light"
when
readingsObject: Readings(engineCoolantLow || checkEngineLightOn);
then
alert.setAdded(true); // 标记警报已被添加
alert.setVin(readingsObject.getVin());
alert.setPriority("LOW");
alert.setDescription("Engine Coolant LOW Or Check Engine Light ON");
alert.setTimestamp(readingsObject.getTimestamp());
alert.setLatitude(readingsObject.getLatitude());
alert.setLongitude(readingsObject.getLongitude());
System.out.println(alert.toString());
end
然后你可以修复你的调用方法,不再依赖于非null对象的可空性,而是依赖于是否已添加警报:
public Readings postReadings(Readings readings) {
Alert alert = new Alert();
session.setGlobal("alert", alert);
session.insert(readings);
session.fireAllRules();
if(alert.isAdded()) {
alertRepo.save(alert);
}
return repository.save(readings);
}
(我当然假设你的'repository'实例中有适当的连接和事务处理。)
如果你仍然需要一个非null的VIN,在规则的左侧应该进行检查;类似于这样:
readingsObject: Readings( vin != null,
engineCoolantLow || checkEngineLightOn )
相关问题:https://stackoverflow.com/questions/60504675/how-to-return-the-value-from-cosequence-of-drl-file-to-java
英文:
There's nothing wrong with your rule. However your save is guarded by an "if" statement that checks for the presence of a non-blank/non-null VIN, so that's likely causing the issue of the missing saves:
if(!Optional.ofNullable(alert).map(o -> o.getVin()).orElse("").isBlank()) {
alertRepo.save(alert);
}
You can easily write unit tests to verify this behavior -- that if the alert exists (which is always does since you instantiate it earlier in the same method) and there is a VIN, then it saves; and that if the alert exists and there is no VIN that it does not save.
This is a very old-fashioned way of saving the output for rules. These days we generally use actions with side effects in our rules on the right hand side, or use objects to pass back information.
Your current set up even has a logic flaw: you will always have an Alert because you instantiate it before you add it to your session.
If you want to use a global like this, you'll need to add some sort of indicator to the Alert class itself to indicate that the alert has been added. So, for example, if you simply add a boolean added = false
method with appropriate getters, you can update your rule like this:
rule "Engine Coolant Or Check Engine Light"
when
readingsObject: Readings(engineCoolantLow || checkEngineLightOn);
then
alert.setAdded(true); // Mark the alert as now being added
alert.setVin(readingsObject.getVin());
alert.setPriotity("LOW");
alert.setDescription("Engine Coolant LOW Or Check Engine Light ON");
alert.setTimestamp(readingsObject.getTimestamp());
alert.setLatitude(readingsObject.getLatitude());
alert.setLongitude(readingsObject.getLongitude());
System.out.println(alert.toString());
end
And then you can fix your calling method to not key off of the nullability of a non-null object, and instead key off of whether you have added an alert:
public Readings postReadings(Readings readings) {
Alert alert = new Alert();
session.setGlobal("alert", alert);
session.insert(readings);
session.fireAllRules();
if(alert.isAdded()) {
alertRepo.save(alert);
}
return repository.save(readings);
}
(I'm assuming, of course, that you've got the appropriate connection and transaction handling in your 'repository' instances.)
If you still need a non-null VIN, you should be checking for that on the rule's left hand side; something like this:
readingsObject: Readings( vin != null,
engineCoolantLow || checkEngineLightOn )
Related question: https://stackoverflow.com/questions/60504675/how-to-return-the-value-from-cosequence-of-drl-file-to-java
答案2
得分: 1
以下是翻译好的内容:
Drools在同一(单一)线程中评估规则,除非您进行了其他配置。因此,postReadings()
和 drools then block
将由同一线程调用。但是,postReadings()
可以在并发上下文中执行,并且它使用共享的全局对象引用 Alert alert
(因为共享会话实例的缘故)。Drools与使您的 ReadingsServiceImpl
线程安全的顺序问题无关。有许多方法可以实现这一点,第一个反模式是“全局变量”... 不要在 drools 文件中使用共享对象,而要使用共享线程安全服务引用。您可以使用不同的同步方法、线程局部变量来使您的服务以线程安全的方式处理内部内容,但是您真的需要在这里使用共享对象吗?
import com.shiva.truckerapi.entity.Readings;
import com.shiva.truckerapi.entity.Alert;
global com.shiva.truckerapi.service.ReadingsService readingsService;
rule "EngineCoolantOrCheckEngineLight"
when
readingsObject: Readings(engineCoolantLow || checkEngineLightOn);
then
Alert alert = new Alert();
alert.setVin(readingsObject.getVin());
alert.setPriotity("LOW");
alert.setDescription("Engine Coolant LOW Or Check Engine Light ON");
alert.setTimestamp(readingsObject.getTimestamp());
alert.setLatitude(readingsObject.getLatitude());
alert.setLongitude(readingsObject.getLongitude());
System.out.println(alert.toString());
readingsService.onAlert(alert);
end;
服务更改:
@PostConstruct
private void postConstruct() {
session.setGlobal("readingsService", this);
}
@Override
public void postReadings(Readings readings) {
session.insert(readings);
session.fireAllRules();
repository.save(readings);
}
@Override
public void onAlert(Alert alert) {
// this looks ugly. There should not be produced 'invalid' alert, if vin is empty alert should not be generated by the rule itself
if (!Optional.ofNullable(alert).map(o -> o.getVin()).orElse("").isBlank()) {
alertRepo.save(alert);
}
}
英文:
Drools evaluates rule in the same (single) thread unless you make additional configuration. Thus postReadings()
and drools then block will be invoked by the same thread. But postReadings()
can be executed in concurrent context and it uses shared global object reference Alert alert
(because of the shared session instance). Drools has nothing to do with the ordinal problem of making your ReadingsServiceImpl
thread safe. And there are many ways to achieve this and the very first antipattern is 'global variables'... Do not use shared object in drools file but use shared thread safe service reference. You can use different synchronization approaches, thread local variables to make your service deal with your inner stuff in a thread safe way but do you really need shared object here at all?
import com.shiva.truckerapi.entity.Readings;
import com.shiva.truckerapi.entity.Alert;
global com.shiva.truckerapi.service.ReadingsService readingsService;
rule "EngineCoolantOrCheckEngineLight"
when
readingsObject: Readings(engineCoolantLow || checkEngineLightOn);
then
Alert alert = new Alert();
alert.setVin(readingsObject.getVin());
alert.setPriotity("LOW");
alert.setDescription("Engine Coolant LOW Or Check Engine Light ON");
alert.setTimestamp(readingsObject.getTimestamp());
alert.setLatitude(readingsObject.getLatitude());
alert.setLongitude(readingsObject.getLongitude());
System.out.println(alert.toString());
readingsService.onAlert(alert);
end;
Service changes
@PostConstruct
private void postConstruct() {
session.setGlobal("readingsService", this);
}
@Override
public void postReadings(Readings readings) {
session.insert(readings);
session.fireAllRules();
repository.save(readings);
}
@Override
public void onAlert(Alert alert) {
// this looks ugly. There should not be produced 'invalid' alert, if vin is empty alert should not be generated by the rule itself
if(!Optional.ofNullable(alert).map(o -> o.getVin()).orElse("").isBlank()) {
alertRepo.save(alert);
}
}
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论