Documentation
Tutorials
JavaDoc
News
FAQ's
Lists/Support
Strange name?
Downloads
Downloads
CVS Access
|
An Intro Tutorial to OpenEmcee Microflows
By Scott Schenkein, Edited By
Michael Roane
Welcome to the OpenEmcee Microflow quick-start tutorial. After
reading this tutorial you should have a good feeling for how the system
works (and perhaps what it does). The engine has a slight learning
curve, but the following example shows you all you need to get started.
This simple example can not demonstrate the complexity reducing, reuse
encouraging properties of this development methodology. We will explore
more complex examples in a future tutorial.
Business Case
Let's consider a simple business process for creating new credit card accounts.
This system
- Take the user's information as input.
- Call to a credit service to determine suitability for awarding an
account.
- If the service is unavailable, return an appropriate error to the
caller.
- If the service is unable to determine a score, put the account into
the manual work queue.
- If the score is good, continue to step 3.
- Call to a service that verifies addresses.
- If the address is unverifiable, put the account into the manual
work queue (2.2).
- If the address is verifiable, continue to step 4.
- Create the account.
- If the creation succeeds, you're done. Congrats!
- If the creation fails, return an appropriate error to the caller.
Digest the Requirements
Now, lets deconstruct the requirements into business functions (model),
and business contexts (control).
Model: First lets look at business
functions. (The non-business function information is stricken through.)
This part of the spec will be implemented in "Task" classes. More
on that later.
Take the users information as input.
- Call to a credit service to determine suitability for awarding
an account.
If the service is unavailable, return an appropriate
error to the caller.
If the service is unable to determine a score,
put the account into the manual work queue.
If the score is good, continue to step 3.
- Call to a service that verifies addresses.
If the address is unverifiable, put the account
into the manual work queue (2.2).
If the address is verifiable, continue to step 4.
- Create the account.
If the creation succeeds, you're done. Congrats!
If the creation fails, return an appropriate error to
the caller.
|
Control: Next we look at the business
contexts, or decisions. (The business
function information is stricken though). These decisions will be
implemented in the XML Microflow jobs descriptor.
Take the users information as input.
Call to a credit service to determine suitability for awarding
an account.
- If the service is unavailable, return an appropriate error
to the caller.
- If the service is unable to determine a score,
put the
account into the manual work queue.
- If the score is good, continue to step 3.
Call to a service that verifies addresses
- If the address is unverifiable, put the account.
into
the manual work queue (2.2).
- If the address is verifiable, continue to step 4.
Create the account.
- If the creation succeeds, you're done. Congrats!
- If the creation fails, return an appropriate error to the
caller.
|
Define the Core Functions for which we must code
For each piece of core function, we create a "MicroflowTask".
-
Source of user data is irrelevant for this example...
- A module which takes user information, and calls to a credit service.
(com.mybank.CreditServiceTask)
- A module which takes user information, and calls an address verification
service. (com.mybank.AddressVerificationTask)
- A module which creates an account in our core banking system. (com.mybank.CreateAccountTask)
- A module which puts the account into the manual service queue. (com.mybank.AddToManualServiceQueue)
Implement the modules
package com.mybank;
import com.mybank.services.CreditService; import org.openemcee.microflow.usercore.*; // Don't really import *
public class CreditServceTask implements MicroflowTask {
private int acceptableScore;
public MicroflowTaskOutcome executeTask(MicroflowData data) { CustomerAddress addr = (CustomerAddress)data.getAttribute("address"); // Delegate to the model try { long score = CreditService.getScore(addr); data.setAttribute("credit_score", new Long(score)); return new MicroflowTaskOutcome("success"); } catch(CreditCalculationExcepton e) { return new MicroflowTaskOutcome("calc_failed"); } catch(ServiceNotAvailableException e) { return new MicroflowTaskOutcome("service_unavailable"); } } public boolean init(MicroflowParameters params) { return true; } public CreditServceTask () {} }
|
Sample Implementation of the CreditServiceTask
In the above implementation, notice that the code knows nothing about manual
work queues, what happens next, etc...
Write the XML to configure the flow of control between the tasks
Next we create an XML file containing the schema and flow for our microflow.
It will be saved in c:\temp\myflows\create_acct.xml
Please read the XML comments IN ALL CAPS to help understand the file.
<?xml version="1.0"?>
<!DOCTYPE openemcee-microflow-descriptor PUBLIC
"-//OpenEmcee Software//Microflow Job Definition 1.0//EN"
"http://openemcee.org/microflow/dtds/openemcee-mfjd-1_0.dtd">
<job-definition identifier="SampleJob"> <setup> <!-- MAKE AN ENTRY HERE FOR EACH PIECE OF
DATA THAT YOU WANT TO FLOW THOUGH THE SYSTEM --> <permissions> <data> <name>address</name> <type>com.mybank.data.CustomerAddress</type> </data> <data> <name>credit_score</name> <type>java.lang.Long</type> </data> <data> <name>account_id</name> <type>java.lang.String</type> </data> <data> <name>added_to_manual_queue</name> <type>java.lang.Boolean</type> </data> </permissions> </setup>
<!-- THE JOB STARTS AT WHATEVER TASK IS DEFINED HERE --> <first-task>CallCreditService</first-task>
<task name="CallCreditService" jclass="com.mybank.CreditServiceTask"> <outcome name="success" nextTask="VerifyAddress"/> <outcome name="calc_failed" nextTask="AddToManualWorkQueue"/>
<!-- Stop processing, call error handling hooks (none in this example) --> <outcome name="service_unavailable" terminal="true" failure="true"/>
</task>
<task name="VerifyAddress" jclass="com.mybank.VerifyAddressTask"> <outcome name="success" nextTask="CreateAccount"/> <outcome name="calc_failed" nextTask="AddToManualWorkQueue"/>
<!-- Stop processing, call error handling hooks (none in this example) --> <outcome name="default" terminal="true" failure="true"/> </task> <task name="CreateAccount" jclass="com.mybank.CreateAccountTask">
<!-- We're done. Notice that we didn't fail. --> <outcome name="success" terminal="true"/> <outcome name="calc_failed" nextTask="AddToManualWorkQueue"/>
<!-- Stop processing, call error handling hooks (none in this example) --> <outcome name="default" terminal="true" failure="true"/> </task>
<task name="AddToManualWorkQueue" jclass="com.mybank.AddToManualServiceQueueTask"> <outcome name="success" terminal="true"/>
<!-- Stop processing, call error handling hooks (none in this example) --> <outcome name="default" terminal="true" failure="true"/> </task>
</job-definition>
|
Sample Implementation of the CreditServiceTask
Add the microflow hooks to your client software
Finally, we need to incorporate a call to the Microflow engine into our
application (maybe its a web application, or a desktop application, or batch
process taking input from a database or a JMS queue, etc...).
import org.openemcee.microflow.frontend.*; // Never import *...
class .... {
public MicroflowFactory factory = new MicroflowFactory("file:c:\\temp\\myflows\\");
public String createAccount(CustomerAddress addr) throws AddedToManualQueueException, XYZFailure { MicroflowRunner runner = null;
try { // // Get the Microflow runner (thing that executes // the Microflow). The name here is your XML file // minus the base URL used to create the MicroflowFactory // and without the XML file extention. Here: // // c:\\temp\\myflows\\create_account.xml // // Note that this could easily have been from a web server // an ftp server, a jar file, a jar file on a web server, // "file t3", or anything which you can get a URL connection to. // runner = factory.getRunner("create_account"); // // Expose the customer address to the user. // runner.addAttribute("customer_address", addr); // // AND.... GO!!! // runner.run(); // // Now we have finished. Return the resulting account ID to the caller. // return (String)runner.getAttribute("account_id");
} catch(MicroflowRunner.BusinessException e) { // // The microflow terminated on a failure="true" outcome // // Lets find out if the account was added to the manual // work queue... Data would have been set by the // "AddToManualWorkQueueTask" // Boolean manualQueue = runner.getAttribute("added_to_manual_queue"); if(manualQueue != null && manualQueue == Boolean.TRUE) { throw new AddedToManualQueueException(); } else { throw new XYZFailure(e); } } catch(MicroflowRunner.MicroflowException e) { // // Something failed in execution maybe an unchecked exception was thrown? // Maybe an unknown class was in your XML? // throw new XYZFailure(e); } catch(MicroflowFactory.MicroflowNotFoundException e) { // Your XML file wasn't found... You need to handle this error. throw new XYZFailure(e);
}
|
And now... The Business Emergency that we're all way too familiar with....
Time: 4:45 on a Friday afternoon,
software is due on Monday.
Emergency: Your business support
folks finally understood what you were explaining to them about the system
not making a decision based on credit score. Sound familiar?
You need to add logic to add the account to the "manual processing queue"
if the score is less than 400 while the business people go home at 5:00
mad at you because THEY didn't listen to the developers in requirements
analysis. Arghh...
So, what do we do?
- Go to your team and complain about the *@** users. Regain composure.
- Create a task which returns a status of "ok" if it is 400+, or "score_too_low"
if it is less.
- Update the XML to call the new task after the credit check, and before
the address check. Set the outcome for "score_too_low" to "AddToManualWorkQueue".
- Check it into version control. Update the status in the defect
tracker so it goes with the next build (You do have a defect tracker
and automated build, right?)
- You're done. AND, you didn't have to deal with the complexities
of MODIFYING code.
Time: 7:30 you're home. Not
optimal, but much better than what it could have been had this been "traditional"
code.
The changes we made are in bold. I'll
leave the code ("Task") part to your imagination.
<task name="CallCreditService" jclass="com.mybank.CreditServiceTask"> <outcome name="success" nextTask="VerifyCreditScore"/> <outcome name="calc_failed" nextTask="AddToManualWorkQueue"/>
<!-- Stop processing, call error handling hooks (none in this example) --> <outcome name="service_unavailable" terminal="true" failure="true"/>
</task>
<task name="VerifyCreditScore" jclass="com.mybank.VerifyCreditScoreTask"> <outcome name="ok" nextTask="VerifyAddress"/> <outcome name="score_too_low" nextTask="AddToManualWorkQueue"/> <!-- USE A CONFIG PARAMETER RATHER THAN HARD-CODING THE VALUE YOU KNOW THAT THE USERS WILL CHANGE THEIR MINDS --> <params> <param> <name>score_threshold</name> <value>400</value> </param> </params> </task>
<task name="CallCreditService" jclass="com.mybank.CreditServiceTask"> <outcome name="success" nextTask="VerifyAddress"/> <outcome name="calc_failed" nextTask="AddToManualWorkQueue"/> <outcome name="service_unavailable" terminal="true" failure="true"/> </task>
<task name="VerifyAddress" jclass="com.mybank.VerifyAddressTask"> <outcome name="success" nextTask="CreateAccount"/> <outcome name="calc_failed" nextTask="AddToManualWorkQueue"/>
<!-- Stop processing, call error handling hooks (none in this example) --> <outcome name="default" terminal="true" failure="true"/> </task>
|
Many other features
The framework has many other features including:
- Data Validation (Validation of Microflow data attributes on initial
set and after each modification or access)
- Error Handling (Rollbacks, or tasks which are called when a microflow
fails, prior to returning to the caller)
- Subflows (The ability to call a Microflow Job from another Microflow
Job)
What we've learned
In this lesson, we've learned the basics of creating a Microflow:
- Deconstructing the requirements.
- Defining the components.
- Implementing the task modules.
- Writing the XML job definition.
- Invoking the Microflow from a client application.
You should now be prepared to create your first OpenEmcee Microflow.
Download a distribution and try it for yourself.
Click here for our download page.
Please let us know what you think about the product and this tutorial.
Click here to give your feedback.
PLEASE don't forget to leave me your email so I can respond!
Thanks,
Scott Schenkein and the whole OpenEmcee
Team
Any software or content on this or affiliated project site are released
for your use under MPL 1.1 License.
Please see source code modules for complete license.
|