5 minutes (or little bit more..) with Activiti

A good solution for leaving the tree of  “if-then-else” nightmare is, or might be, using one of the Business Process Model (Bpm) available in open source license.

In this article I’d like to explore, at first glance, a solution that not using a traditional way to implement a business logic in your code.

A pair of days ago, I was looking at a method that was similar at the below:


SampleUser user = new SampleUser();
if (user.getGender().equals("M"))
{
         user.setProfiles("Sport,DIY");
}
if (user.getGender().equals("F"))
{
	user.setProfiles("Fashion,Cooking");
}
UUID uniqueId = UUID.randomUUID();
user.setUniqueId(uniqueId.toString());
if (user.getAge()<18)
{
	user.setBusinessAge("Teenager");
}
if (user.getAge() >= 18 && user.getAge() < 60)
{
	user.setBusinessAge("Adult");
}
if (user.getAge() >= 60)
{
	user.setBusinessAge("Senior");
}

So, the problem is not in missing constants (“Sport,DIY”) and the code running without any problem.
The problem is when a “not-developer” (typically an employee of the Business Intelligence department) has to understand how it works and must change the logic. For example, the Seniors limit is not 60 years old but one year less.

You might illustrate it by using a graph like this:

ProcessProfiling

You’ll became their hero with this. Wouldn’t be wonderful whether we could using it even in our procedure? I got the solution by using Activiti.

Activiti is a “light-weight workflow and Business Process Management (BPM)” and you can get more information from official website. Let’s go on with our example to take a look more inside.

We’ve already seen the business logic diagram and you can easily get the aim of the example. Activiti used an XML file for defining it with .bpmn extension.
Between the nodes “Start and Stop” you can put all the elements for composing your business logic. Elements like “exclusiveGateway” are used for implementing “if” and “serviceTask” contains the business logic using Pojo object.

I suggest you to take a look at official web site to get the whole list of available nodes.

Let’s start to see the activi-config.xml file


<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://www.springframework.org/schema/beans
       		http://www.springframework.org/schema/beans/spring-beans.xsd">

	<bean id="processEngineConfiguration"
		class="org.activiti.engine.impl.cfg.StandaloneInMemProcessEngineConfiguration">
		<property name="databaseSchemaUpdate" value="true" />
		<property name="history" value="full" />
		<property name="jobExecutorActivate" value="false" />
	</bean>

	<bean id="processEngine" class="org.activiti.spring.ProcessEngineFactoryBean">
		<property name="processEngineConfiguration" ref="processEngineConfiguration" />
	</bean>

	<bean id="activitiRule" class="org.activiti.engine.test.ActivitiRule">
		<property name="processEngine" ref="processEngine" />
	</bean>

	<bean id="runtimeService" factory-bean="processEngine"
		factory-method="getRuntimeService" />

	<bean id="historyService" factory-bean="processEngine"
		factory-method="getHistoryService" />

</beans>

In this file are defined:

  • processEngine: A real engine of the Activiti business model processor. It’s used to set information like db storage system.
  • processEngineConfiguration: StandaloneInMemProcessEngineConfiguration means that will be used an in-memory H2 database instance for running the process.
  • activitiRule: It’s used in the JUnit test for deploying the model.
  • runtimeService: Start the business process and keep the process reference during the execution.
  • historyService: Used for reading the process result once the process has finished.

We use all these objects in the below JUnit class test.


@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:activiti-config.xml")
public class ProfilingProcessTest {

	static Logger log = Logger.getLogger(ProfilingProcessTest.class.getName());

	@Autowired
	private RuntimeService runtimeService;

	@Autowired
	private HistoryService historyService;

	// Missing this part the Business Process Model is not deployed
	@Autowired
	@Rule
	public ActivitiRule activitiRule;

	@Test
	@Deployment(resources = { "diagrams/ProcessProfiling.bpmn" })
	public void startFormSubmit() {

		SampleUser teenagerUser = getUserProfiled(new SampleUser("Jerry", "M",
				16));

		assertEquals(teenagerUser.getBusinessAge(), "Teenager");

		log.info("********************");
		log.info("Teenager User");
		log.info("********************");
		log.info(teenagerUser);
		log.info("");

		SampleUser adultUser = getUserProfiled(new SampleUser("Sofia", "F",
				38));

		assertEquals(adultUser.getBusinessAge(), "Adult");

		log.info("********************");
		log.info("Adult User");
		log.info("********************");
		log.info(adultUser);
		log.info("");

		SampleUser seniorUser = getUserProfiled(new SampleUser("James", "M",
				70));

		assertEquals(seniorUser.getBusinessAge(), "Senior");

		log.info("********************");
		log.info("Senior User");
		log.info("********************");
		log.info(seniorUser);
		log.info("");

	}

	private SampleUser getUserProfiled(SampleUser user) {
		Map<String, Object> processVariables = new HashMap<String, Object>();
		processVariables.put("user", user);

		ProcessInstance processInstance = runtimeService.startProcessInstanceByKey("ProfilingProcess", processVariables);

		List<HistoricDetail> historyVariables = historyService.createHistoricDetailQuery().variableUpdates().list();

		historyService.deleteHistoricProcessInstance(processInstance.getId());

		HistoricVariableUpdate userProfiled = ((HistoricVariableUpdate) historyVariables.get(3));

		return (SampleUser) userProfiled.getValue();
	}
}

At line 61 I start the process named “ProfilingProcess”. At line 63 I read the variables result and, at line 67 I get the result of user profiled.

The business process file, as I said, is an xml file as the follow (I removed the graphic references).


<?xml version="1.0" encoding="UTF-8"?>
<definitions xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:activiti="http://activiti.org/bpmn" xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI" xmlns:omgdc="http://www.omg.org/spec/DD/20100524/DC" xmlns:omgdi="http://www.omg.org/spec/DD/20100524/DI" typeLanguage="http://www.w3.org/2001/XMLSchema" expressionLanguage="http://www.w3.org/1999/XPath" targetNamespace="http://www.activiti.org/test">
  <process id="ProfilingProcess" name="Profiling Process" isExecutable="true">
    <startEvent id="startevent1" name="Start"></startEvent>
    <exclusiveGateway id="exclusivegateway1" name="Exclusive Gateway"></exclusiveGateway>
    <serviceTask id="servicetask_set_male_profile" name="Service Task Male Profile" activiti:class="it.useridentityprocess.service.ProfileProcessTask">
      <extensionElements>
        <activiti:field name="profiles">
          <activiti:string><![CDATA[Sport,DIY]]></activiti:string>
        </activiti:field>
      </extensionElements>
    </serviceTask>
    <serviceTask id="servicetask_set_female_profile" name="Service Task Female Profile" activiti:class="it.useridentityprocess.service.ProfileProcessTask">
      <extensionElements>
        <activiti:field name="profiles">
          <activiti:string><![CDATA[Fashion,Cooking]]></activiti:string>
        </activiti:field>
      </extensionElements>
    </serviceTask>
    <sequenceFlow id="flow1" sourceRef="startevent1" targetRef="exclusivegateway1"></sequenceFlow>
    <sequenceFlow id="flow2" name="Female" sourceRef="exclusivegateway1" targetRef="servicetask_set_female_profile">
      <conditionExpression xsi:type="tFormalExpression"><![CDATA[${user.gender == "F"}]]></conditionExpression>
    </sequenceFlow>
    <sequenceFlow id="flow3" name="Male" sourceRef="exclusivegateway1" targetRef="servicetask_set_male_profile">
      <conditionExpression xsi:type="tFormalExpression"><![CDATA[${user.gender == "M"}]]></conditionExpression>
    </sequenceFlow>
    <exclusiveGateway id="exclusivegateway2" name="Exclusive Gateway"></exclusiveGateway>
    <sequenceFlow id="flow4" sourceRef="servicetask_set_male_profile" targetRef="exclusivegateway2"></sequenceFlow>
    <sequenceFlow id="flow5" sourceRef="servicetask_set_female_profile" targetRef="exclusivegateway2"></sequenceFlow>
    <serviceTask id="servicetask_set_uniqueid" name="Service Task UniqueId" activiti:class="it.useridentityprocess.service.UniqueIdProcessTask"></serviceTask>
    <sequenceFlow id="flow6" sourceRef="exclusivegateway2" targetRef="servicetask_set_uniqueid"></sequenceFlow>
    <exclusiveGateway id="exclusivegateway3" name="Exclusive Gateway"></exclusiveGateway>
    <sequenceFlow id="flow7" sourceRef="servicetask_set_uniqueid" targetRef="exclusivegateway3"></sequenceFlow>
    <serviceTask id="servicetask_set_teenager" name="Service Task Teenager" activiti:class="it.useridentityprocess.service.BusinessAgeProcessTask">
      <extensionElements>
        <activiti:field name="businessAge">
          <activiti:string><![CDATA[Teenager]]></activiti:string>
        </activiti:field>
      </extensionElements>
    </serviceTask>
    <serviceTask id="servicetask_set_adult" name="Service Task Adult" activiti:class="it.useridentityprocess.service.BusinessAgeProcessTask">
      <extensionElements>
        <activiti:field name="businessAge">
          <activiti:string><![CDATA[Adult]]></activiti:string>
        </activiti:field>
      </extensionElements>
    </serviceTask>
    <serviceTask id="servicetask_set_senior" name="Service Task Senior" activiti:class="it.useridentityprocess.service.BusinessAgeProcessTask">
      <extensionElements>
        <activiti:field name="businessAge">
          <activiti:string><![CDATA[Senior]]></activiti:string>
        </activiti:field>
      </extensionElements>
    </serviceTask>
    <sequenceFlow id="flow8" name="Teenager" sourceRef="exclusivegateway3" targetRef="servicetask_set_teenager">
      <conditionExpression xsi:type="tFormalExpression"><![CDATA[${user.age < 18}]]></conditionExpression>
    </sequenceFlow>
    <sequenceFlow id="flow9" name="Adult" sourceRef="exclusivegateway3" targetRef="servicetask_set_adult">
      <conditionExpression xsi:type="tFormalExpression"><![CDATA[${user.age >= 18 && user.age < 60}]]></conditionExpression>
    </sequenceFlow>
    <sequenceFlow id="flow10" name="Senior" sourceRef="exclusivegateway3" targetRef="servicetask_set_senior">
      <conditionExpression xsi:type="tFormalExpression"><![CDATA[${user.age >= 60}]]></conditionExpression>
    </sequenceFlow>
    <endEvent id="endevent1" name="End"></endEvent>
    <sequenceFlow id="flow11" sourceRef="servicetask_set_teenager" targetRef="endevent1"></sequenceFlow>
    <sequenceFlow id="flow12" sourceRef="servicetask_set_adult" targetRef="endevent1"></sequenceFlow>
    <sequenceFlow id="flow13" sourceRef="servicetask_set_senior" targetRef="endevent1"></sequenceFlow>
  </process>
</definitions>

In the process I defined three “serviceTask”. “ProfileProcessTask” class sets the profile:


public class ProfileProcessTask implements JavaDelegate {

	private Expression profiles;
	@Override
	public void execute(DelegateExecution execution) {

		SampleUser user = (SampleUser)execution.getVariable("user");

		user.setProfiles(profiles.getExpressionText());

		execution.setVariable("user", user);
	}
}

The class implements the org.activiti.engine.delegate.JavaDelegate interface. This interface has one method to override (execute) and it’ll be called when the process goes through the node.

The others tasks are “UniqueIdProcessTask”:


public class UniqueIdProcessTask implements JavaDelegate {

	@Override
	public void execute(DelegateExecution execution) {

		SampleUser user = (SampleUser)execution.getVariable("user");

		UUID uniqueId = UUID.randomUUID();

		user.setUniqueId(uniqueId.toString());

		execution.setVariable("user", user);
	}
}

And “BusinessAgeProcessTask”:


public class BusinessAgeProcessTask implements JavaDelegate {

	private Expression businessAge;
	@Override
	public void execute(DelegateExecution execution) {

		SampleUser user = (SampleUser)execution.getVariable("user");

		user.setBusinessAge(businessAge.getExpressionText());

		execution.setVariable("userProfiled", user);
	}
}

The last code missing is the Bean “SampleUser”:

public class SampleUser implements Serializable {

	protected String name;
	protected String gender;
	protected int age;

	// They will be filled by business process
	protected String uniqueId;
	protected String profiles;
	protected String businessAge;

	public SampleUser() {
	}

	public SampleUser(String name, String gender, int age) {
		this.name = name;
		this.gender = gender;
		this.age = age;
	}

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	public String getGender() {
		return gender;
	}

	public void setGender(String gender) {
		this.gender = gender;
	}

	public int getAge() {
		return age;
	}

	public void setAge(int age) {
		this.age = age;
	}

	public String getUniqueId() {
		return uniqueId;
	}

	public void setUniqueId(String uniqueId) {
		this.uniqueId = uniqueId;
	}

	public String getProfiles() {
		return profiles;
	}

	public void setProfiles(String profiles) {
		this.profiles = profiles;
	}

	public String getBusinessAge() {
		return businessAge;
	}

	public void setBusinessAge(String businessAge) {
		this.businessAge = businessAge;
	}

	@Override
	public String toString() {
		return "name=" + name +
		"\r\ngender=" + gender +
		"\r\nage=" + age +
		"\r\nuniqueId=" + uniqueId +
		"\r\nprofiles=" + profiles +
		"\r\nbusinessAge=" + businessAge;
	}
}

In conclusion, We’ve taken a fast look of Activiti framework and a first glance of its features. The official web site, the book “Activiti in Action” and this blog (http://bpmn20inaction.blogspot.it/) are all good resources for starting your business model. In the future articles I’d like to explore others features of this framework.

I hope you enjoy it.

Advertisements

One thought on “5 minutes (or little bit more..) with Activiti

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s