Hybris Process Case: an email process development
1. Introduction
In Hybris framework, one very important feature and widely applied (also recommended when suitable) is the Business Process Management built upon the hybris processengine. In this article, it will be shown a brief implementation of a business process, specifically an email send process.
2. The Business Process Feature Concept
The Business Process Feature is based on the Cluster aware processing. This means that the business process can be redistributed through the nodes of a Hybris cluster, performing accordingly. This implies that the process is executed asynchronously. Therefore, any business process is transparent to the user (that wants to send an email or place an order, for example). So, responsiveness is kept and the application flow on the front-end (storefront) shall be fine.
A business process can be described as a state machine flows accordingly with its actual state and its outputs, consisting in a Moore Machine. Technically, the description of its flow is written in a XML file and, for each one of the states, a bean must be implemented, so a business logic will be executed inside the state and will perform an output, that will lead the machine to a specific new state. One must know that the machine goes from one state to another led by a Transition. These transitions are mapped in the same XML file.
One important benefit of the business process feature is that the state is always persisted. Thus, if the system crashes, the processengine will know where it was before and will continue as soon as the processengine context is up again. With that, there’s a major warranty that the process will execute safely and won’t execute the same step twice, except for a statistically small amount of cases.
With the proper logging and administration by a responsible user (i.e., an administrator), it can be easy to locate bottlenecks and take the adequate actions to improve performance. Additionally, if something went bad and there’s a need to cancel the process, there’s a standard feature for it and, therefore, the process can be manually interrupted.
3. Scenario: The Business Process Concept applied to an Email Send Process
Let’s assume we want to send emails without waiting a response from the SMTP server (or any response at all), for instance, to return the navigation flow to the end-user’s hands. That is a very common case in the e-commerce industry that, to keep the responsiveness to the end-user, the email send process shall not be synchronous and shall be invisible to the user (the user must navigate freely while the backend handles the email process). In case of timeout from the SMTP server or another issue, the backend handles these errors, without noticing the user.
For the scenario of this article, let’s assume, for simplicity, that we just want to send a simple email through a partner mailing system, which handles a simple post request from us and send the email to the end-user. For that, this partner already have the email structure and only needs some basic information, as the end-user email. Here, the reader can notice that this is much simpler than following the standard email process from Hybris, that requires emails templates, medias, render templates, and much more. The focus here is not the building of a email object/component (which can be very similar to the building process of a CMS Page with content slots and so on), but the business process feature.
Let’s assume, for even more simplicity, that the email needs to be sent when the place order takes place and we need to warn the user that the order process will be started. The point where this process is initiated will be put in the end of the technical descriptions of this article, as it will link everything on the email process with the business logic. One more rule (just to exemplify the alternative transitions in a process): if the user that placed the order is of gmail.com account, we will send a specific parameter to our partner so, its implementation must be different from the other domains.
4. The meta algorithm for building our Email Process
That said, we will need some steps listed below:
- Define the email process state machine in a XML file
- Define the starting action for the process
- Define the actions that will be executed inside each of the states of the process
- Define the transitions between the states
- Define the end(s) of the process
5. Building the Email Process
To build and code the Email Process, let’s follow the steps described in 4. BUT, before that, the location of the process descriptor XML file needs to be in a place where Hybris will realize it exists and will raise its context. And, obviously, the beans will be declared – and coded – next to this process descriptor. For the location, Hybris has a template extension for creating extensions for defining the fulfillment of processes that is the [yacceleratorfulfilmentprocess] template.
When generating an extension with this template, we will have a structure in which there will be a XML file called
Suppose we have created an extension that is of [yacceleratorfulfilmentprocess] and its name is gregorekiemailprocess. Thus, the XML file name will be gregorekiemailprocess-process-definitions.xml
Below we have its contents:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | <?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> <!-- Process Definitions --> <bean id="gregorekiEmailProcessDefinitionResource" class="de.hybris.platform.processengine.definition.ProcessDefinitionResource" > <property name="resource" value="classpath:/gregorekiemailprocess/process/gregorekiemailprocess-process.xml"/> </bean> <!-- Process Actions --> <import resource="/gregorekiemailprocess/process/gregorekiemailprocess-process-spring.xml"/> </beans> |
Lets take a look at the code above. It specifies a bean that will have ‘gregorekiEmailProcessDefinitionResource’ as its ID and is an instance of ‘ProcessDefinitionResource’ class. As said before, this will be the bean that will contain the starting point of the Process, and the Business Process Engine will see this bean as the entry point for identifying the rest of the chain. Inside this bean there will be a property called resource that contains a path to another xml configuration, that is the process descriptor itself, its transitions and states. As these transitions and states points themselves to the reference of beans of actions (the beans that will perform actions when a state is reached), we need spring configuration for those classes to become beans. That’s exactly why there is a import of a -process-spring.xml file here. When importing this file, we expect that the actions (that are inside beans) will be up in the context.
Now, let’s take a look into this gregorekiemailprocess-process-spring.xml file:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | <?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> <context:annotation-config/> <bean id="checkEmailDomainAction" class="com.gregorekiemailprocess.actions.CheckEmailDomainAction" parent="abstractAction"> </bean> <bean id="requestGmailSendEmail" class="com.gregorekiemailprocess.actions.RequestGmailSendEmailAction" parent="abstractAction"> </bean> <bean id="requestOthersSendEmail" class="com.gregorekiemailprocess.actions.RequestOthersSendEmailAction" parent="abstractAction"> </bean> </beans> |
In this file, one can notice that there are three beans declared: checkEmailDomainAction, requestGmailSendEmail and requestOthersSendEmail. These beans are the ones that will perform actions to send the correspondent emails when the state machine comes up to their correlated states. The implementation of those classes will be put later on.
Now, Let’s define the process state machine and, after that, write it to a XML file.
First of all, we will have one starting step which identifies the user email address and checks out if it is a gmail account address or any other domain else. If it is a gmail account, this action jumps out with the ‘GMAIL’ transition and the process goes to the fit state. Else, the process jumps to another state with the ‘OTHERS’ transition.
In the ‘GMAIL’ state, the action performed will send a request to our partner email system requesting that a gmail email to be sent. It waits our partner system to respond and, when that happens, identifies if there was an error. If timeout or any other error occurs, it will navigate to the final state of ‘ERROR’. If the response is successful, then it will navigate to a final state of ‘SUCCESS’.
The same is applied to the ‘OTHER’ state, except for the obvious part, in which the email requested to be sent will be a ‘other’ email.
Finally, the ‘SUCCESS’ or ‘ERROR’ transitions will mean the end of the process.
Below is the XML file that describes the process:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | <?xml version="1.0" encoding="utf-8"?> <process xmlns="http://www.hybris.de/xsd/processdefinition" start="start" name="gregoreki-email-process" processClass="com.gregoreki.myhybrissystem.model.email.GregorekiEmailProcessModel" onError="ERROR"> <action id="start" bean="checkEmailDomainAction"> <transition name="GMAIL" to="requestGmailSendEmail"/> <transition name="OTHERS" to="requestOthersSendEmail"/> </action> <action id="requestGmailSendEmail" bean="requestGmailSendEmailAction"> <transition name="OK" to="SUCCESS" /> <transition name="NOK" to="ERROR" /> </action> <action id="requestOthersSendEmail" bean="requestOthersSendEmailAction"> <transition name="OK" to="SUCCESS" /> <transition name="NOK" to="ERROR" /> </action> <end id="ERROR" state="ERROR">Could not send e-mail request.</end> <end id="SUCCESS" state="SUCCEEDED">E-mail request sent successfully.</end> </process> |
At this point, everything is connected: the process steps/states, the beans that were declared to perform actions and the transitions, as the start point and the end points of the process.
At least it seems so… We already need two additional steps:
- Build the com.gregoreki.myhybrissystem.model.email.GregorekiEmailProcessModel that is declared as the model of the process in the later XML file;
- Implement the actions in the right classes pointed by the spring.XML file
To do the first one, we need to declare it in our -items.xml. In this case, the file is called gregorekiemailprocess-items.xml. The declaration will build a Class that will be persisted into the Hybris database as it is created and the process is triggered, so Hybris will keep track of what is happening with the process. At the end of each step of the process, Hybris updates the object with its suitable properties. Here is the XML fragment for this:
1 2 3 4 5 | <!-- a beautiful email process =) --> <itemtype code="GregorekiEmailProcess" autocreate="true" generate="true" jaloclass="com.gregoreki.gregorekiemailprocess.jalo.email.GregorekiEmailProcess" extends="BusinessProcess"> </itemtype> |
We have supposed that the email process would be triggered when the order-process was about to begin. So, it would be suitable to have a relationship between the order and the email-process. Thus, in this same -items.xml, we can add the following code:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | <relations> ... <relation generate="true" localized="false" code="Order2GregorekiEmailProcess" autocreate="true"> <sourceElement type="Order" cardinality="one" qualifier="order"> <modifiers initial="true" optional="true" /> </sourceElement> <targetElement type="GregorekiEmailProcess" cardinality="many" qualifier="gregorekiEmailProcess"> <modifiers initial="true" optional="true" /> </targetElement> </relation> ... </relations> |
Because of these lines, the OrderModel will have a relationship to many GregorekiEmailProcessModel and it will be seen both in HMC and BACKOFFICE, with proper configuration.
Finally, we can show the Java classes that are the action-beans for the states:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 | package com.gregorekiemailprocess.actions; import java.util.HashSet; import java.util.Set; // additional imports ommited public class CheckEmailDomainAction extends extends AbstractAction<GregorekiEmailProcessModel>{ /* please, notice that the email process acts passing through a GregorekiEmailProcessModel, which is the model declared as a hook for this process. */ public Transition executeAction(final GregorekiEmailProcessModel processModel) throws Exception { /* this method executes when entering the state. here, choose the next state of the process by returning a transition. */ final OrderModel orderModel = processModel.getOrder(); if (StringUtils.containsIgnoreCase(orderModel.getUser().getUid(), "@gmail.com") return Transition.GMAIL; else return Transition.OTHERS; } /* define the transitions for this state/action. */ public enum Transition { GMAIL, OTHERS; public static Set<String> getStringValues() { final Set<String> result = new HashSet<String>(); for (final Transition transition : Transition.values()) { result.add(transition.toString()); } return result; } } @Override public String execute(GregorekiEmailProcessModel processModel) throws RetryLaterException, Exception { return executeAction(processModel).toString(); } @Override public Set<String> getTransitions() { // TODO Auto-generated method stub return Transition.getStringValues(); } } |
This example is enough for the three of the declared beans. It is very simple and this one is the starting point of the process, which verifies the customer uid (email) and return a Transition based on the decision. After that, the proper state will take place and the proper bean will be executed (the bean shall be very likely this one) and will return ‘SUCCESS’ or ‘ERROR’ accordingly to the truth of the events.
Finally, somewhere inside the code, to start up this BusinessProcess, one can start it by sentencing:
1 2 3 4 5 6 7 | final String processCode = "gregoreki-email-process" + "-" + order.getCode() + "-" + System.currentTimeMillis(); final GregorekiEmailProcessModel gregorekiEmailProcessModel = (GregorekiEmailProcessModel) getBusinessProcessService().createProcess(processCode, "gregoreki-email-process"); gregorekiEmailProcessModel.setOrder(order); getModelService().save(gregorekiEmailProcessModel); getBusinessProcessService().startProcess(gregorekiEmailProcessModel); |
And then (my friend), the process will be start asynchronously. The email will be sent and there will be information of it inside the Order objects in both HMC and Backoffice (if they are properly configured).
6. Conclusion
In this article we saw some concepts and ideas behind the fantastic Hybris BusinessProcess and how it can be created, configured, processed, designed, used and contemplated.