34、基于Web的应用部署与企业应用中的规则引擎

基于Web的应用部署与企业应用中的规则引擎

1. 基于Web的应用部署

1.1 Recommend servlet 工作流程

1.1.1 开始阶段

Recommend servlet 继承自 BaseServlet 类。它首先调用 checkInitialized 方法确保 Rete 对象准备就绪,然后进行自身的初始化。以下是相关代码:

public class Recommend extends BaseServlet {
    public void doGet(HttpServletRequest request,
                      HttpServletResponse response)
    throws IOException, ServletException {
        checkInitialized();
        ServletContext servletContext = getServletContext();
        String[] items = (String[])
            request.getParameterValues("items");
        String orderNumberString = (String)
            request.getSession().getAttribute("orderNumber");
        String customerIdString = (String)
            request.getSession().getAttribute("customerId");
        if (items == null ||
            orderNumberString == null || customerIdString == null) {
            dispatch(request, response, "/index.html");
            return;
        }
    }
}

这里, getParameterValues 方法返回已购买商品的列表。如果购物车为空、客户未登录或没有订单号,Recommend 会将客户重定向到登录页面重新开始。需要注意的是,调用 dispatch 后必须使用 return ,否则该 servlet 会尝试将其输出追加到登录页面。

1.1.2 创建订单

接下来,需要在 Jess 的工作内存中设置订单。具体步骤如下:
1. 触发 CLEANUP 模块删除之前的部分订单:

try {
    Rete engine = (Rete) servletContext.getAttribute("engine");
    engine.executeCommand("(assert (clean-up-order " +
                          orderNumberString + "))");
    engine.run();
}

通过断言 (clean-up-order NNNN) 事实(其中 NNNN 是订单号),触发 CLEANUP 模块删除具有该订单号的任何先前部分订单。调用 run 方法让引擎触发 CLEANUP 规则。
2. 构建订单事实:

int orderNumber = Integer.parseInt(orderNumberString);
Value orderNumberValue =
    new Value(orderNumber, RU.INTEGER);
Value customerIdValue =
    new Value(customerIdString, RU.ATOM);
Fact order = new Fact("order", engine);
order.setSlotValue("order-number", orderNumberValue);
order.setSlotValue("customer-id", customerIdValue);
engine.assertFact(order);
  1. 构建单个订单项事实:
for (int i=0; i<items.length; ++i) {
    Fact item = new Fact("line-item", engine);
    item.setSlotValue("order-number", orderNumberValue);
    item.setSlotValue("part-number",
                      new Value(items[i], RU.ATOM));
    item.setSlotValue("customer-id", customerIdValue);
    engine.assertFact(item);
}

由于 Value 对象是不可变的,因此可以重复使用之前创建的 Value 对象。

1.1.3 获取推荐

所有订单项断言完成后,调用 run 方法触发 Recommendations Agent。相关代码如下:

engine.run();
Iterator result =
    engine.runQuery("recommendations-for-order",
           new ValueVector().add(orderNumberValue));

在多用户环境下,每个用户有唯一的客户 ID/订单号对,Recommendations Agent 可以分别跟踪他们。即使一个线程调用 run 导致规则因另一个线程断言的事实而触发,或者一个线程在另一个线程调用 run 后立即调用 run 没有效果,只要规则不是专门为特定状态下的 run 调用编写的,一切都能正常工作。

1.1.4 转发到 JSP

根据 recommendations-for-order 查询是否有结果,决定是将推荐信息渲染到 recommend.jsp 页面,还是直接跳转到 Purchase servlet 进行结账:

if (result.hasNext()) {
    request.setAttribute("queryResult", result);
    dispatch(request, response, "/recommend.jsp");
} else
    dispatch(request, response, "/purchase");

1.2 recommend.jsp 页面

recommend.jsp 页面用于渲染推荐信息,它与 catalog JSP 类似,但输出中多了一个 “Because you bought…” 列,将每个推荐与其他产品关联起来。以下是 recommend.jsp 的代码:

<HTML>
    <%@ page import="jess.*" %>
    <jsp:useBean id="queryResult" class="java.util.Iterator" 
         scope="request"/>
    <HEAD>
        <TITLE>Some recommendations for you from Tekmart.com</TITLE>
    </HEAD>
    <BODY>
        <H1>Your Recommendations</H1>
        You may also wish to purchase the following items:
        <FORM action="/Order/purchase" method="POST">
            <TABLE border="1">
                <TR>
                    <TH>Name</TH>
                    <TH>Catalog #</TH>
                    <TH>Because you bought...</TH>
                    <TH>Price</TH>
                    <TH>Purchase?</TH>
                </TR>
            <% while (queryResult.hasNext()) {
                    Token token = (Token) queryResult.next();
                    Fact fact1 = token.fact(1);
                    Fact fact2 = token.fact(2);
                    String partNum =
                        fact2.getSlotValue("part-number")
                        .stringValue(null); %>
                <TR>
                    <TD><%= fact2.getSlotValue("name")
                            .stringValue(null) %></TD>
                    <TD><%= partNum %>
                    <TD><% ValueVector vv =
                               fact1.getSlotValue("because")
                               .listValue(null);
                    for (int i=0; i<vv.size(); ++i) { %>
                        <%= vv.get(i).stringValue(null) %>
                        <% if (i != vv.size()-1) %>,
                        <% } %>
                    </TD>
                    <TD><%= 
                            fact2.getSlotValue("price")
                            .floatValue(null) %></TD>
                    <TD><INPUT type="checkbox" name="items"
                            value=<%= '"' + partNum + '"'%>></TD>
                </TR>
            <% } %>
            </TABLE>
            <INPUT type="submit" value="Purchase">
        </FORM>
    </BODY>
</HTML>

1.3 Purchase servlet

Purchase servlet 结合了其他两个 servlet 的功能,它确保客户已登录,将请求参数中的商品断言为 line-item 事实,运行 items-for-order 查询,并将迭代器传递给 purchase.jsp 页面。以下是 Purchase servlet 的代码:

public class Purchase extends BaseServlet {
    public void doGet(HttpServletRequest request,
                     HttpServletResponse response)
    throws IOException, ServletException {
        checkInitialized();
        ServletContext servletContext = getServletContext();
        String orderNumberString = (String)
            request.getSession().getAttribute("orderNumber");
        String customerIdString = (String)
            request.getSession().getAttribute("customerId");
        if (orderNumberString == null || customerIdString == null) {
            dispatch(request, response, "/index.html");
            return;
        }
        try {
            Rete engine =
                (Rete) servletContext.getAttribute("engine");
            int orderNumber = Integer.parseInt(orderNumberString);
            Value orderNumberValue =
                new Value(orderNumber, RU.INTEGER);
            Value customerIdValue =
                new Value(customerIdString, RU.ATOM);
            String[] items = (String[])
                request.getParameterValues("items");
            if (items != null) {
                for (int i=0; i<items.length; ++i) {
                    Fact item = new Fact("line-item", engine);
                    item.setSlotValue("order-number",
                                      orderNumberValue);
                    item.setSlotValue("customer-id",
                                      customerIdValue);
                    item.setSlotValue("part-number",
                        new Value(items[i], RU.ATOM));
                    engine.assertFact(item);
                }
            }
            Iterator result =
                engine.runQuery("items-for-order",
                        new ValueVector().add(orderNumberValue));
            request.setAttribute("queryResult", result);
        } catch (JessException je) {
            throw new ServletException(je);
        }
        dispatch(request, response, "/purchase.jsp");
    }
}

1.4 purchase.jsp 页面

purchase.jsp 页面用于显示用户购买的商品并计算总价:

<HTML>
    <%@ page import="jess.*" %>
    <jsp:useBean id="queryResult" class="java.util.Iterator"
                                  scope="request"/>
    <HEAD>
        <TITLE>Thank you for your order</TITLE>
    </HEAD>
    <BODY>
        <H1>Thanks for shopping at TekMart.com!</H1>
        These are the items you are purchasing. If this
        were a real web site, I'd be asking for your credit
        card number now!
        <P>
        <TABLE border="1">
            <TR>
                <TH>Name</TH>
                <TH>Catalog #</TH>
                <TH>Price</TH>
            </TR>
            <% double total = 0;
            while (queryResult.hasNext()) {
                Token token = (Token) queryResult.next();
                Fact fact = token.fact(2);
                double price =
                    fact.getSlotValue("price").floatValue(null);
                total += price; %>
            <TR>
                <TD><%= fact.getSlotValue("name").
                        stringValue(null) %></TD>
                <TD><%= fact.getSlotValue("part-number").
                        stringValue(null) %></TD>
                <TD><%= price %></TD>
            </TR>
            <% } %>
            <TR><TD></TD><TD>
                <B>Total:</B></TD><TD><%= total %>
            </TD></TR>
        </TABLE>
    </BODY>
</HTML>

1.5 数据持久化

由于 Web 应用可能会离线,Rete 对象中包含的所有过去的购买和推荐信息会丢失。为了保存这些有价值的客户信息,可以在 Servlet 的 destroy 方法中实现数据保存:

public void destroy() {
    try {
        ServletContext servletContext = getServletContext();
        Rete engine = (Rete)
            servletContext.getAttribute("engine");
        String factsFileName =
            servletContext.getInitParameter("factsfile");
        File factsFile = new File(factsFileName);
        File tmpFile =
            File.createTempFile("facts",
                                "tmp",
                                factsFile.getParentFile());
        engine.executeCommand("(save-facts " +
            tmpFile.getAbsolutePath() +
            " order recommend line-item next-order-number)");
        factsFile.delete();
        tmpFile.renameTo(factsFile);
    } catch (Exception je) {
        // Log error
    }
}

该实现先将事实写入临时文件,只有在成功后才将临时文件重命名为 factsfile ,以避免数据丢失。不过,长期使用后,从平面文件读写累积的事实可能会变得非常慢,且在启动和关闭时加载和保存所有数据可能会有数据丢失的风险。可以考虑使用关系型数据库来替代平面文件。

1.6 应用部署

部署这个 Web 应用与之前部署单个 servlet 一样简单,文件安装结构如下:

$(TOMCAT)/
    lib/
        jess.jar
    webapps/
        Order/
            catalog.jsp
            index.html
            purchase.jsp
            recommend.jsp
            WEB-INF/
                web.xml
                classes/
                    tekmart.clp
                    Catalog.class
                    Purchase.class
                    Recommend.class

web.xml 文件中为每个 servlet 配置了一个 servlet 元素和一个 servlet-mapping 标签,还包含 context-param 元素用于传递初始化参数:

<context-param>
    <param-name>rulesfile</param-name>
    <param-value>tekmart.clp</param-value>
</context-param>
<context-param>
    <param-name>factsfile</param-name>
    <param-value>/var/tekmart/data</param-value>
</context-param>

1.7 应用优化方向

  • 封装 Jess 知识 :可以将关于 Jess 的所有知识封装在 servlet 中,让 JSP 不依赖于特定的规则引擎。servlets 可以构建中立的数据结构,传递这些结构的迭代器,这样可以在不修改 JSP 的情况下修改模板和规则,甚至更换规则引擎。
  • 使用 javax.rules API :虽然该 API 不支持本应用中的许多功能(如查询),但在后续可以考虑使用。
  • 使用关系型数据库 :结合 Jess 使用关系型数据库可以提供更快速、更强大的历史数据访问,避免平面文件的缺点。

2. 企业应用中的规则引擎

2.1 企业应用概述

企业应用是企业依赖的大型软件应用,不同于员工个人使用的软件,如 Microsoft Word 或 Adobe Photoshop。企业应用包括 payroll 应用、客户数据库、资源规划应用、库存应用和在线订购应用等。企业应用通常需要具备以下特性:
- 可扩展性 :随着业务增长,应用应能随之扩展,通常通过添加更多计算机或升级软件基础设施来实现。
- 可用性 :用户应能每周 7 天、每天 24 小时访问应用,停机时间应尽量少或不存在。
- 事务性 :在最后一刻之前,应能撤销完整的用户交互。例如,当 ATM 机在客户使用时离线,任何未完成的交易应能干净地取消。

此外,许多企业应用还具有其他特性。企业应用这些特性的关系可以用以下 mermaid 流程图表示:

graph LR
    A[企业应用] --> B[可扩展性]
    A --> C[可用性]
    A --> D[事务性]
    B --> E[业务增长时扩展]
    C --> F[24/7 访问]
    D --> G[撤销未完成交易]

2.2 规则引擎在企业应用中的应用

将业务规则捕获到规则引擎中非常有意义,这是规则引擎在企业系统中的众多应用之一。目前,许多企业应用的核心是 Java 2 Enterprise Edition (J2EE) 环境,它将数据访问和计算资源整合到一个称为应用服务器的标准服务中。后续可以探讨使用 Jess 与 Enterprise JavaBeans 和 XML 的结合,以及使用规则在 Enterprise JavaBeans 应用中的应用、 javax.rules 规则引擎 API 等内容。通过这些技术,可以创建新的应用或开发企业级的规则应用,为企业业务提供更强大的支持。

2.3 规则的 XML 表示

在企业应用中,使用 XML 来表示规则是一种常见的做法。XML 具有良好的可读性和可扩展性,能够方便地对规则进行存储、传输和编辑。将 Jess 规则转换为 XML 格式,以及将 XML 规则转换回 Jess 规则,是实现规则在不同系统间交互的重要步骤。

2.3.1 Jess 规则转 XML

虽然文中未给出具体的转换代码,但可以想象,转换过程需要对 Jess 规则的结构进行解析,将规则的各个部分(如规则名、条件、动作等)映射到 XML 的标签和属性中。例如,一个简单的 Jess 规则:

(defrule example-rule
    (condition1)
    =>
    (action1))

可能会被转换为如下 XML 格式:

<rule name="example-rule">
    <conditions>
        <condition>(condition1)</condition>
    </conditions>
    <actions>
        <action>(action1)</action>
    </actions>
</rule>
2.3.2 XML 规则转 Jess

反之,将 XML 规则转换回 Jess 规则时,需要解析 XML 文件,提取规则的各个部分,并将其重新组合成 Jess 规则的格式。这个过程涉及到 XML 解析技术和字符串处理。

2.4 规则编辑软件

为了方便对规则进行管理和编辑,需要使用专门的规则编辑软件。这类软件通常提供图形化界面,让用户可以直观地创建、修改和删除规则。它可以将用户的操作转换为 XML 或 Jess 规则的格式,实现规则的存储和应用。

2.5 规则引擎与 J2EE 的结合应用

在企业应用中,将规则引擎与 J2EE 平台结合使用可以发挥更大的作用。以下是结合应用的一些常见场景和操作步骤:

2.5.1 使用 Jess 与 Enterprise JavaBeans

Enterprise JavaBeans (EJB) 是 J2EE 平台的重要组件,用于实现企业级的业务逻辑。将 Jess 规则引擎与 EJB 结合,可以在 EJB 中调用 Jess 规则进行业务决策。具体步骤如下:
1. 初始化 Jess 引擎 :在 EJB 中获取 Jess 引擎的实例,并进行必要的初始化操作。

import jess.Rete;

public class MyEJB {
    private Rete engine;

    public MyEJB() {
        try {
            engine = new Rete();
            // 加载规则文件
            engine.batch("rules.clp");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
  1. 触发规则执行 :在 EJB 的业务方法中,根据业务数据触发 Jess 规则的执行。
public class MyEJB {
    // ... 初始化代码 ...

    public void businessMethod() {
        try {
            // 插入事实
            engine.assertString("(fact1)");
            // 执行规则
            engine.run();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
2.5.2 使用 javax.rules 规则引擎 API

javax.rules 是 Java 提供的规则引擎 API,虽然它不支持本应用中的许多功能,但在某些场景下可以使用。使用 javax.rules 的步骤如下:
1. 创建规则服务提供者 :获取规则服务提供者的实例。

import javax.rules.RuleServiceProvider;
import javax.rules.RuleRuntime;

public class RuleEngineExample {
    public static void main(String[] args) {
        try {
            RuleServiceProvider provider = RuleServiceProvider.newInstance("com.example.RuleServiceProviderImpl");
            RuleRuntime runtime = provider.getRuleRuntime();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
  1. 加载规则和执行规则 :加载规则集,并执行规则。
import javax.rules.RuleServiceProvider;
import javax.rules.RuleRuntime;
import javax.rules.RuleSession;

public class RuleEngineExample {
    // ... 创建规则服务提供者代码 ...

    public static void main(String[] args) {
        try {
            // ... 创建规则服务提供者和运行时 ...
            RuleSession session = runtime.createRuleSession();
            // 加载规则集
            session.addRuleSet("rules.xml");
            // 执行规则
            session.executeRules();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

2.6 规则引擎在企业应用中的优势总结

规则引擎在企业应用中具有以下优势,通过表格展示如下:
| 优势 | 说明 |
| ---- | ---- |
| 提高业务灵活性 | 可以方便地修改和更新业务规则,无需修改大量代码。 |
| 增强可维护性 | 规则集中管理,易于维护和调试。 |
| 促进业务和技术分离 | 业务人员可以参与规则的制定,技术人员负责实现。 |
| 支持复杂业务逻辑 | 能够处理复杂的业务规则和条件判断。 |

2.7 未来展望

随着企业业务的不断发展和技术的不断进步,规则引擎在企业应用中的应用将会更加广泛和深入。未来可以进一步探索以下方向:
- 规则引擎与大数据的结合 :利用大数据分析结果动态调整规则,提高业务决策的准确性。
- 规则引擎的分布式应用 :在分布式系统中使用规则引擎,实现更高效的业务处理。
- 规则引擎的可视化开发 :提供更强大的可视化工具,让业务人员更方便地创建和管理规则。

通过以上对基于 Web 的应用部署和企业应用中规则引擎的介绍,我们可以看到规则引擎在现代企业应用中具有重要的作用。合理地使用规则引擎和相关技术,可以提高企业应用的性能、灵活性和可维护性,为企业的发展提供有力支持。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符  | 博主筛选后可见
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值