In this chapter you’ll...
- Look at alternative Java web architectures
- Collect knowledge for a smart e-commerce application
- Write rules and queries for a web application
A sizeable and still-growing fraction of all software written today is deployed on intranets or the Internet. The rise of electronic commerce, enterprise applications, and web-based entertainment is a visible indicator of this trend.
Rule-based systems are commonly used in web-based applications. A Java rule engine can run on a client machine (as an applet) or, more commonly, on the server (in a J2EE application server). Rules can be used for order configuration, inventory management, human resources, manufacturing resource planning, games, and more.
In part 6 of this book, you’ll develop a Recommendations Agent for an online store. The Recommendations Agent will look at the items a customer is buying and has bought in the past, and make recommendations for additional purchases. In this chapter, we’ll look at an overview of web architectures and then develop the data structures and rules for the agent. In chapter 18, you’ll learn how to embed Jess as a library in a larger application, and we’ll study techniques for interacting with Jess through its Java API. In chapter 19, you’ll package the Recommendations Agent as a web application deployed in a standard J2EE container. The application will be built from servlets and JavaServer Pages (see section 17.1.2 for definitions). You’ll build a complete e-commerce web site in miniature to serve as an example platform for the Recommendations Agent, although you’ll leave out the bits that are entirely unrelated to the agent (such as credit-card validation).
There are many different ways to deploy applications on the Web. (In this part of this book, I’ll use the phrase the Web to refer to applications that use the HTTP and HTTPS protocols, either over corporate intranets or over the Internet as a whole.) In fact, new methods are being developed all the time, and many of these methods blur the traditional categories, making it hard to use any absolute terms to describe what is possible on the Web. For our purposes, though, web-based software can be divided into two broad categories: fat-client and thin-client applications.
A fat-client web application is most similar to a traditional client-server application: The code is divided more or less equally between a client (desktop) machine and a server (remote) machine. Fat-client solutions can automatically download and install the client software to the desktop machine, or they may use fixed clients (desktop applications) installed using physical media or manual network downloads. The client software might include an elaborate graphical interface, as well as some fraction of the business logic for the application.
There are two ways to build Java-based fat-client solutions that can be automatically downloaded to the client: using an applet or using Java Web Start.
The Java Applet API was included in the first release of the Java Developer’s Kit (JDK). A Java applet is just a class that extends java.applet.Applet and provides implementations for one or more of the Applet methods init, start, stop, and destroy. These methods are called at well-defined times by the Java environment that hosts the applet—invariably a web browser.
Writing applets that can work effectively inside a browser can be challenging, because the user can browse away from the page containing an applet at any time, only to return later. An applet may or may not be destroyed during this time, and on returning to the original page the user may find a new instance of the applet. This lifecycle issue makes maintaining a consistent user session difficult.
To compound the problem, web browsers vary widely in their level of Java support. The major browsers include very old JVMs as standard equipment; in fact, some Internet Explorer users have no JVM installed. JavaSoft offers a Java Plug-in that provides a standard Java environment for browsers that use Netscape-style plug-ins, and targeting this platform can be a good solution, especially on an intranet. But the Java Plug-in is a large download, and many Internet users won’t have it and won’t want to download it. Dealing with the JVM support issue is the biggest obstacle to deploying substantial applets.
Jess supplies a simple example applet in the jess.ConsoleApplet class. ConsoleApplet presents the same GUI as jess.Console, so it’s useful only in limited situations—applications that involve interview-style interaction with a user.
The Java Web Start system lets a client click a link on a web page to download and automatically install what amounts to a normal desktop application written in Java. Java Web Start applications don’t have the same kind of lifecycle problems that applets do; they can be launched, used, and exited like a normal application. Java Web Start is relatively new but is rapidly gaining acceptance. Like applets, Java Web Start applications rely on users having a properly configured JVM on their desktop. Again, it’s easier to make this assumption on an intranet than on the Internet.
- Little processing power is required on the server, because much of the code executes on the client.
- The programmer has maximum control over user interface, because it can be written using all of Java’s APIs.
These are the disadvantages:
- It’s hard to write complex client software that will work in a wide range of desktop environments.
- Downloading the client software may be intolerably slow for many users, especially on the Internet.
- If the client software is installed persistently on the desktop, backward compatibility becomes an issue; forcing all users to update software can be difficult.
The first disadvantage is enough of a problem that fat-client architectures are not used very often. This problem is most acute for applets, because web browser behavior varies widely between brands, or even between releases of a single browser. The Java Plug-in was designed to mitigate this problem, but getting potential users to install this hefty download is still an issue, especially on the Internet.
In a thin-client architecture, most of the application-specific code runs on the server. A web-based thin client is usually just HTML in a browser. There are many ways to write thin-client web applications in Java. Although I will describe them here one by one, they are often used in combination.
Servlets, the server-side analogue of applets, are small modules that a web server or other container invokes in response to browser requests. Just as an applet is a subclass of java.applet.Applet, a servlet is an implementation of the javax.servlet.Servlet interface, or most commonly a subclass of the class javax.servlet.http.HttpServlet. Whereas applets run inside a browser on the user’s desktop, servlets run in a container on a server machine. The Java 2 Enterprise Edition (J2EE) is a standard for deploying applications on a server; it includes, among other things, a servlet container.
The HttpServlet class includes methods you can override to process client requests that come in over the Web. It also provides methods the container invokes to initialize and destroy the servlet. You’ll develop several servlets in chapter 19.
Another way to deploy server-side Java applications is via JavaServer Pages (JSPs). A JSP is an HTML page with embedded Java code. In a way, a JSP is an inside-out version of a servlet: A servlet is Java code that often includes statements that print HTML. JSPs are compiled by a special program on the web server, generally into servlets, and then executed in response to browser requests. Whereas servlets are ideal when a server-side component needs to do a nontrivial amount of work and produce only a small amount of HTML, JSPs are perfect when a large HTML page needs to embed a small amount of computed information. Servlets and JSPs are often used together, with the JSPs providing the interface and the servlets providing the logic. You’ll use JSPs to provide the user interface for the Recommendations Agent in chapter 19.
1 Bruce A. Tate, Bitter Java (Greenwich, CT: Manning, 2002).
Perhaps the best thing about JSPs is that they can be written in a regular HTML editor. Servlets must be written by experienced Java programmers, but JSPs can be written by web designers, perhaps with the use of some boilerplate provided by a Java guru. It’s difficult to maintain a web page created by a servlet using a series of println calls, especially for a nonprogrammer, but maintaining a JSP-based site is not very different than maintaining a site built from static HTML pages.
Servlets and JSPs are designed to deliver HTML interfaces to human users. Web services, on the other hand, are designed to provide an interface that other software can use. The term web service generally refers to an application that can be sent commands using XML-based Simple Object Access Protocol (SOAP) messages. Web services can be used to build web applications with regular user interfaces, but they are also used as components of larger software systems.
Deploying an application as a web service lets people and companies around the world interact with it. For example, if company A deploys its order-configuration application as a web service, then company B’s purchasing department can develop software to automatically purchase A’s product. JavaSoft offers the Java Web Services Development Pack (JWSDP), an add-on for the J2EE application server, as a platform for web-service development.
- Little processing power is required on the client, because much of the code executes on the server.
- There are few or no client configuration problems. Few requirements are placed on the client, so compatibility isn’t usually an issue.
- Upgrades are easy, because they happen at the server.
Here are the disadvantages:
- Powerful servers may be needed to meet demand.
Because the configuration and maintenance issues are much simpler, thin-client architectures are usually the preferred way to deploy an application on the Web. In chapter 19, you will develop the Recommendations Agent using a thin-client architecture.
One of the first and most famous rule-based applications was Digital Equipment Corporation’s XCON order configuration system (see section 1.2.2). XCON helped sales consultants configure orders for DEC mainframe computers, making sure the order included all the accessories the customer needed.
The grandchildren of XCON are now on display all over the Web. Every web site that helps you select compatible options when you buy a computer online owes an obvious tip of the hat to XCON. Not quite so obvious, perhaps, are sites that recommend additional purchases the way a good sales clerk might (“This top would look cute with those pants!”).
Rule-based order configuration and recommendation systems are common today. In this chapter and the next two, you’ll build a system that combines both of these tasks: It will recommend items the customer probably needs, as well as items it thinks the customer might just enjoy.
You’re going to write a fairly general system to look at what a customer is ordering and suggest other things they might want to buy. Two kinds of “experts” might have knowledge you could add to such a system: technical folks and marketing folks. The technical knowledge will let the system tell the customer how to assemble a working system from individual components. The marketing knowledge can be used to try to convince the customer to buy more stuff.
For the current system, you’ll base technical recommendations on the concept of categories. Every product will belong to one category; to use it, products from other categories may be required or desirable. For instance, the product Univac 2000 belongs to the category computer; to use it, you might need a video monitor (category monitor), because the monitor is not included. A monitor is therefore a requirement for computer customers. If the user of your system buys a Univac 2000, the system should recommend that she buy a monitor, too. Other accessories might also be nice: speakers and software, for example. These should also be recommended, and for the purposes of this system, you’ll also call them requirements.
The marketing-derived rules that you’ll include will be designed to help sell videotapes and video discs (DVDs). Assume that the marketing folks have identified four opportunities in this area:
- If a customer is buying a VCR or DVD player, recommend the appropriate media.
- If a customer has bought a VCR or DVD player on a previous visit, recommend more media for it.
- If a customer is buying a videotape or DVD, recommend another one.
- If a customer has bought a videotape or DVD in the past, recommend another one.
In every case, you should keep track of previous purchases, so you don’t recommend a videotape or DVD the customer has already bought from you.
After talking with the experts, you find that the important entities in the system include the following:
- Products— Things that are for sale
- Customers— Current and past users of the system
- Orders— Current and past customer purchases
- Line items— A collection of items that form an order
- Recommendations— What the system produces as output
You can define a deftemplate for each item in this list. The product template should include slots for the name, catalog number, and price of the product, of course. It also should include a slot to specify the category the product belongs to and the categories of other products required by this product:
(deftemplate product (slot name) (slot category) (slot part-number) (slot price) (multislot requires))
For example, a product named TekMart 19 TV might have the category tv, and the requires slot could contain batteries (for the remote control).
A customer is just a person with a name, address, and customer ID:
(deftemplate customer (multislot name) (multislot address) (slot customer-id))
An order is a collection of line items. Each line item knows which order it belongs to, what product is being ordered, and in what quantity. Although it’s not strictly necessary, you’ll find later that including the customer ID in each line item simplifies your rules considerably:
(deftemplate order (slot customer-id) (slot order-number)) (deftemplate line-item (slot order-number) (slot part-number) (slot customer-id) (slot quantity (default 1)))
Finally, recommendations link an order to a recommended product. The because multislot lists other products that triggered this recommendation, and the type slot distinguishes between product requirements (XCON’s kind of recommendations) and marketing recommendations:
(deftemplate recommend (slot order-number) (multislot because) (slot type) (slot part-number))
With these basic data structures put together, you’re ready to begin writing the rules for the order-configuration system.
The rules you will write in this chapter examine facts and assert recommendations, but they won’t display anything or otherwise interact with a user. You’ll write the user interfaces for the web-based applications in HTML as JSPs, and you won’t do that until chapter 19. For now, to see the effects of the rules you write, you’ll have to use debugging commands like (watch) and (facts) (you learned about these in section 6.1.1).
As you write these rules, remember that Jess will be simultaneously processing many line items, belonging to many orders, placed by many different customers. There’s nothing wrong with this scenario, and Jess will handle it very well. However, it will be important to make sure that all the rules identify the specific customer and order they are processing—that is, you can’t accidentally recommend products one customer might like to some second unrelated customer. It will be easy to do this, but it is important to keep in mind.
The rules you’re about to write will eventually be embedded in a web application. When they are, they won’t have any command-line or GUI access, so it will be hard to debug them at that point. Therefore, it’s important to test these rules as you write them. You can put some product, order, and line-item facts in a separate file, and load them using load-facts when you need to use them as test cases. Try to design test facts to probe each individual rule, and think about what the correct results should be before you run a test. For example, imagine you’ve written a rule like this one:
(defrule recommend-rubber-duck (customer (customer-id ?id)) (not (and (order (order-number ?order) (customer-id ?id)) (line-item (order-number ?order) (part-number ?part)) (product (part-number ?part) (name "Rubber duck")))) => (assert (recommend (order-number ?order) (part-number ?part))))
This rule says, “If there’s a customer, and they’ve never bought a rubber duck from us, then recommend that they do so.” To test this rule, you need several sets of facts: one set with a customer who has bought a rubber duck and another set with a customer who has not. You may also want to test a customer who has bought multiple rubber ducks. You should also test using customers who have placed multiple orders in the past, and customers who are new to the web site—both duck-buying customers and duckless ones.
Perhaps the most important rule in the Recommendations Agent is the one that creates recommendations for products that are explicitly required by other products (like the batteries for the television set, mentioned earlier). Because you’re interested in using the Recommendations Agent to maximize revenue, you won’t recommend just any batteries for the television set: You’ll recommend the most expensive package in the catalog. This rule is the longest one you’ve written, so let’s break it down a little at a time:
The first two patterns match an order such that no other order with the same customer-id has a higher order-number—that is, it matches only the current order for any given customer.
(defrule recommend-requirements (order (customer-id ?id) (order-number ?currentOrder)) (not (order (customer-id ?id) (order-number ?order2&:(> ?order2 ?currentOrder))))
The next pattern matches a line item in this customer’s current order. The pattern after that matches the product represented by this line item, but only if that product requires some other category of product. This second pattern therefore identifies a case where you can make a recommendation:
(line-item (order-number ?currentOrder) (part-number ?part)) (product (part-number ?part) (name ?product) (requires $? ?category $?))
The fifth, positive pattern matches some other product, belonging to the category required by the first product. The sixth, negated pattern matches another product in this category that costs more than the first. Because it’s negated, it means there’s no such product—these two patterns together identify the most expensive product you could recommend:
(product (category ?category) (price ?price) (part-number ?second-part)) (not (product (category ?category) (price ?p&:(> ?p ?price))))
Now that you’ve identified the product to recommend, you can assert a recommend fact for it:
=> (assert (recommend (order-number ?currentOrder) (type requirement) (part-number ?second-part) (because ?product))))
This one long rule will be a workhorse in your system.
You’ll recall that the folks in marketing are interested in selling customers some extra videotapes and DVDs. There are four situations in which you should recommend some kind of media: when the customer is buying, or has bought in the past, a VCR or DVD player; and when the customer is buying, or has bought in the past, a videotape or DVD. You can handle the first case—recommending a tape or disk to go with a new player—by adding the appropriate category to the requires multislot of each player in the catalog. The other three cases require adding specific rules. First, let’s look at the rule that recommends new media to anyone who has ever bought a player in the past:
(defrule recommend-media-for-player "If customer has bought a VCR or DVD player in the past, recommend some media for it." (product (part-number ?media) (category ?c&dvd-disk|videotape)) (product (name ?name) (part-number ?player) (category =(if (eq ?c dvd-disk) then dvd else vcr)))
The first pattern matches any DVD or videotape, and the second pattern matches all the players that can play it. These two patterns include some fancy matching: The first pattern uses the | (or) connective constraint to match either dvd-disk or videotape in the category slot. The second pattern uses the = return value constraint and a conditional expression to select a value for the category slot that goes with the media category: vcr for videotape and dvd for dvd-disk.
We’ve deliberately put these two patterns at the beginning of this rule, because they match only things from the catalog and therefore will never change while the program is running. It’s generally good to put the patterns that match fixed, unchanging facts at the top of a rule, so they won’t need to be evaluated more than once. In general, ordering patterns is a balancing act, with at least three important, interacting factors:
- Clarity— You wrote the patterns in a rule in a specific order because that’s the way they made sense.
- Performance— You certainly want to avoid doing repetitive work; ordering patterns to put the static ones at the top of the rule, as you’ve done here, helps you achieve this goal.
- Memory use— If you put patterns that match fewer facts toward the beginning of a rule, you’ll reduce the number of partial matches and limit memory consumption.
Often, these three considerations conflict, and you have to make a judgment call. In this chapter, I’ve ordered the patterns placing the most emphasis on readability. In general, this is a good idea at first; you can always go back and reorder the patterns to increase performance and reduce memory usage later, if it turns out to be necessary.
The following three patterns identify a customer who has bought a player in the past and is placing a new order. The line-item pattern identifies a purchase of a player matched in the first part of the rule. The next two patterns identify the customer whose past order contained that line item, and who is placing a new order:
(line-item (customer-id ?id) (order-number ?order1) (part-number ?player)) (order (customer-id ?id) (order-number ?currentOrder&:(> ?currentOrder ?order1))) (not (order (customer-id ?id) (order-number ?order3&:(> ?order3 ?currentOrder))))
The last two patterns narrow the matches a bit. The first pattern eliminates videotapes or DVDs the customer has purchased or is currently purchasing, and the final pattern ensures that you only recommend one videotape or one DVD per order:
(not (line-item (customer-id ?id) (part-number ?media))) (not (recommend (order-number ?currentOrder) (type =(sym-cat discretionary- ?c))))
In the previous section, all the recommendations were of type requirement. The recommendations you’re creating here are discretionary-videotape and discretionary-dvd-disk. Other parts of the system will therefore be able to tell the difference between true requirements and mere sales pitches. You compose the correct category name using the sym-cat function.
Finally, if all the patterns match, you’ve identified a videotape you can recommend to the customer:
=> (assert (recommend (order-number ?currentOrder) (because ?name) (part-number ?media) (type =(sym-cat discretionary- ?c)))))
This rule simply asserts a recommend fact, as recommend-requirements did, and leaves it up to other software to communicate with the user.
If one DVD or videotape is good, more must be even better. The rule recommend-more-media asks the customer if they’d like to buy a second videotape or DVD whenever they pick one out themselves. Although this version of the Recommendations Agent doesn’t do it, a more fleshed-out version could offer a discount on purchases of multiples (“Buy one, get a second for half price!”).
These first three patterns identify all pairs of videotapes and DVDs; the third pattern ensures that you consider only pairs of two different items:
(defrule recommend-more-media "If customer buys a disk or tape, recommend a random other item of the same category." ?p1 <- (product (part-number ?part1) (category ?c&dvd-disk|videotape) (name ?name)) ?p2 <- (product (part-number ?part2) (category ?c)) (test (neq ?p1 ?p2))
The test pattern compares the fact-ids of the matched facts for inequality; this is a common idiom in Jess.
The line-item pattern identifies orders that include one member of a pair of media, and then the negated pattern eliminates orders that already include a discretionary media recommendation for this media type:
(line-item (order-number ?order) (part-number ?part1)) (not (recommend (order-number ?order) (type =(sym-cat discretionary- ?c))))
Because the customer is buying the first member of a pair of items, this rule recommends the second member:
=> (assert (recommend (order-number ?order) (because ?name) (part-number ?part2) (type =(sym-cat discretionary- ?c)))))
There’s one more marketing rule you haven’t written yet: the rule recommend-same-type-of-media, which tries to get a past media customer to buy more media on every return visit. It’s really just a combination of recommend-more-media and recommend-media-for-player, so I’ll present it here without any narration:
(defrule recommend-same-type-of-media "If customer has bought a disk or tape in the past, recommend a random other item of the same category." ;; There are two recordings of the same type ?p1 <- (product (part-number ?part1) (category ?c&dvd-disk|videotape) (name ?name)) ?p2 <- (product (part-number ?part2) (category ?c)) (test (neq ?p1 ?p2)) ;; This customer has bought one of them in a past order (line-item (customer-id ?id) (order-number ?order1) (part-number ?part1)) (order (customer-id ?id) (order-number ?currentOrder&:(> ?currentOrder ?order1))) (not (order (customer-id ?id) (order-number ?order3&:(> ?order3 ?currentOrder)))) ;; But not the other (not (line-item (customer-id ?id) (part-number ?part2))) ;; and we haven't recommended any media of this type yet (not (recommend (order-number ?currentOrder) (type =(sym-cat discretionary- ?c)))) => ;; Recommend the other one. (assert (recommend (order-number ?currentOrder) (because ?name) (part-number ?part2) (type =(sym-cat discretionary- ?c)))))
The rules you’ve written so far can sometimes generate multiple recommendations for the same product. You could complicate all the previous rules such that they won’t generate the duplicate recommendations, or you could simply allow them to be created and then clean them up at the end. I’ve chosen to take the latter route. A single rule coalesce-recommendations combines multiple recommendations for the same product:
(defrule coalesce-recommendations "If there are multiple recommendations for the same product, coalesce them." ?r1 <- (recommend (order-number ?order) (type ?type) (because ?product) (part-number ?part)) ?r2 <- (recommend (order-number ?order) (part-number ?part) (because $?products& :(not (member$ ?product $?products)))) => (retract ?r1 ?r2) (assert (recommend (order-number ?order) (type ?type) (because (create$ ?product $?products)) (part-number ?part))))
The rule recommend-requirements can generate another kind of invalid recommendation: It may recommend a product as a requirement even though the customer is already buying another product in that same category. If the customer is buying cheap batteries, it would be pointless to recommend the expensive ones. Again, you could complicate the original rule to account for this situation, or you could write a cleanup rule. The rule remove-satisfied-recommendations retracts recommendations that are satisfied by other purchases in the same order:
(defrule remove-satisfied-recommendations "If there are two products in the same category, and one is part of an order, and there is a recommendation of type 'requirement' for the other part, then remove the recommendation, as the customer is already buying something in that category." (product (part-number ?part1) (category ?category)) (product (part-number ?part2) (category ?category)) (line-item (order-number ?order) (part-number ?part2)) ?r <- (recommend (order-number ?order) (part-number ?part1) (type requirement)) => (retract ?r))
You’re finished writing the rules for the Recommendations Agent. Recall that you’re going to integrate these rules into a web application, and that the web application will need access to the list of items in the current order, the list of recommendations, and the list of products in the catalog. The web application can use queries to get access to this information. In the next section, you’ll write some queries for the web application to call.
Queries let you efficiently access specified elements in Jess’s working memory. In this way, they turn Jess into a relational database—albeit a slightly strange one that doesn’t use SQL. When you build your web application, you won’t use a traditional database: You’ll use Jess to store all the application data, instead.
Recall from section 7.7 that a defquery is like a rule without a right-hand side. It includes a set of patterns that match facts the same way a rule’s patterns do. The difference is that a rule is matched automatically, whereas a query is triggered by calling the function run-query. This function returns a java.util.Iterator that represents the list of matches for the query’s patterns. The query all-products is the simplest possible example; it matches every product in the catalog:
(defquery all-products (product))
The web application will be able to get a list of all the products in the catalog by calling the Jess function (run-query all-products). You’ll need this to build an order form.
Queries can also contain a variables declaration. The variables declaration lists the arguments to the query. The arguments you supply to the run-query function are bound to the variables of the same names in the patterns, so you can specify at runtime the specific values the query’s patterns should match. A query to list all the items in an order is a good example: It should take one argument, ?order, which specifies the order number of interest:
(defquery items-for-order (declare (variables ?order)) (line-item (order-number ?order) (part-number ?part)) (product (part-number ?part)))
Each match returned by this query will contain an item-number fact and the associated product fact. You’d need this information to compute an order total, print a shipping list, and so on. The recommendations-for-order query is similar:
(defquery recommendations-for-order (declare (variables ?order)) (recommend (order-number ?order) (part-number ?part)) (product (part-number ?part)))
This query lists all the recommendations associated with a given order. In this query and the last one, ?part is an internal variable: an undeclared variable used only to perform matching within the patterns of the query.
The web application you’re going to build needs to give each order a unique number. Traditionally, this task is handled using a stored procedure in a database: A tiny database table holds a single number, and a stored procedure increments the value in the table and returns it. Using a database makes this process safe for multiple concurrent instances of the web application; the database allows only one instance at a time to be updating the order number. In this application, you’ll use the same technique, implemented in Jess using a deftemplate with a single slot to represent the tiny table, and a deffunction instead of a stored procedure. Jess will automatically supply the concurrency control (as you’ll see in the next chapter). Here’s the deftemplate:
(deftemplate next-order-number (slot value))
Here’s a query to retrieve the single fact you expect to be using this template:
(defquery order-number (next-order-number))
And finally, here’s a deffunction to return the next order number. If there’s no next-order-number fact, this function creates one and returns the lowest possible order number (which you’ve arbitrarily specified as 1001). If there is such a fact, this function increments the value, modifies the fact, and returns the unincremented order number. get-new-order-number uses the order-number query to get hold of the fact it needs:
(deffunction get-new-order-number () (bind ?it (run-query order-number)) (if (not (?it hasNext)) then (assert (next-order-number (value 1002))) (return 1001) else (bind ?token (?it next)) (bind ?fact (?token fact 1)) (bind ?number (?fact getSlotValue value)) (modify ?fact (value (+ ?number 1))) (return ?number)))
Before we move on to writing the Java code for the Recommendations Agent, you need to assemble one more chunk of Jess code: a module dedicated to initializing new orders.
Web users are notoriously unpredictable. They’ll click Stop to cancel transactions; they’ll click Back right in the middle of a series of screens. As a result, your web application needs a way to reinitialize an order that’s been partially filled out. Removing all the line-items, all the recommends, and the order fact from working memory should do it, because the web application will be able to reassert these facts based on other state information. You can use a defmodule containing a few auto-focus rules to do the cleanup (see section 7.6 for information about modules). Asserting the fact (clean-up-order ?order) will then clean up the order number ?order:
(defmodule CLEANUP) (defrule CLEANUP::initialize-order-1 (declare (auto-focus TRUE)) (MAIN::initialize-order ?number) ?item <- (line-item (order-number ?number)) => (retract ?item)) (defrule CLEANUP::initialize-order-2 (declare (auto-focus TRUE)) (MAIN::initialize-order ?number) ?rec <- (recommend (order-number ?number)) => (retract ?rec)) (defrule CLEANUP::initialize-order-3 (declare (auto-focus TRUE)) ?init <- (MAIN::initialize-order ?number) (not (line-item (order-number ?number))) (not (recommend (order-number ?number))) => (retract ?init)) (defrule CLEANUP::clean-up-order (declare (auto-focus TRUE)) ?clean <- (MAIN::clean-up-order ?number) ?order <- (order (order-number ?number)) => (assert (initialize-order ?number)) (retract ?clean ?order))
The rule initialize-order-1 deletes all the line-item facts when it sees an initialize-order fact, and initialize-order-2 deletes all the recommend facts. initialize-order-3 deletes the initialize-order fact when no more line items or recommendations are left. Finally, clean-up-order serves as the entry point; it retracts the order fact, and then runs the other rules by asserting the initialize-order fact.
It’s important that these rules be auto-focus rules in their own module. Because they delete individual recommendations, these rules could otherwise thrash back and forth with the recommendation rules: Each time a recommend fact was deleted, one of the recommendation rules could activate and fire, putting the same fact back into working memory, only to have it be deleted again, in an endless chain reaction. This kind of problem is sometimes called an assertion storm. You can stop an assertion storm either by removing all the requisite facts or by calling the Rete.halt() method, which forces that Rete object to halt immediately.
In this chapter, you’ve developed a basic set of rules for a Recommendations Agent. It’s important to realize that you could add many other rules to this system without changing any of the data structures. With minor changes, you could add rules to support sale prices and two-for-one deals. However, this small core of rules is enough to support the goal here, which is to see how Jess can be embedded in web applications.
In chapter 19, you’ll build a web application around the Recommendations Agent rule base. Before you can do that, however, you need to learn about using Jess from Java code. This is the subject we’ll tackle in chapter 18.