Browse Source

Migrate BuildingVaadinApplicationsOnTopOfActiviti

tags/7.7.11
Erik Lumme 6 years ago
parent
commit
4542dbab22

+ 584
- 0
documentation/articles/BuildingVaadinApplicationsOnTopOfActiviti.asciidoc View File

@@ -0,0 +1,584 @@
[[building-vaadin-applications-on-top-of-activiti]]
Building Vaadin applications on top of Activiti
-----------------------------------------------

by Petter Holmström

[[introduction]]
Introduction
~~~~~~~~~~~~

In this article, we are going to look at how the
http://www.activiti.org[Activiti] BPM engine can be used together with
Vaadin. We are going to do this in the form of a case study of a demo
application that is available on
https://github.com/peholmst/VaadinActivitiDemo[GitHub]. The code is
licensed under Apache License 2.0 and can freely be used as a foundation
for your own applications.

[[the-example-process]]
The Example Process
^^^^^^^^^^^^^^^^^^^

The following process is used in the demo application:

image:img/process.png[Example process]

Compared to the capabilities of Activiti and BPMN 2.0, the above process
is almost ridiculously simple. However, it allows us to test the
following things:

* *Process start forms*, i.e. forms that need to be filled in before a
process instance is created.
* *User task forms*, i.e. forms that need to be filled in before a task
can be marked as completed.
* Parallell tasks
* Different candidate groups (i.e. groups whose users are potential
assignees of a certain task)

Here is a short walk-through of the process:

1. Before a new process instance is created, the reporter has to fill
in a _Submit bug report form_.
2. Once the instance has been created, two tasks are created:
* *Update bug report*: a manager assigns priority and target version to
the report. Potential assignees are members of the *managers* group.
* *Accept bug report*: a developer accepts the bug report. Potential
assignees are members of the *developers* group.
3. Both of these tasks require the assignee to fill in a form before
they can be completed: the _Update bug report form_ and _Accept bug
report form_, respectively.
4. Once the tasks have been completed, a new task is created, namely
_Resolve bug report_. Potential assignees are members of the
*developers* group. Ideally, this task should automatically be assigned
to whoever claimed the *Accept bug report* task, but currently this is
not implemented.
5. Before the task can be completed, the assignee has to fill in the
_Resolve bug report form_.
6. All tasks have been completed and the process instance ends.

[[prerequisites]]
Prerequisites
^^^^^^^^^^^^^

In order to get the most out of this article, you should already be
familiar with both Vaadin and Activiti. If not, there is enough free
material available on both products' web sites to get you started.

The demo application is a standard Java EE 6 web application and can be
deployed to any JEE 6 web container, such as
http://tomcat.apache.org[Tomcat 7]. It uses an embedded in-memory
http://www.h2database.com[H2 database] for storing data, which means
that all your data will be lost when the server is restarted.

http://www.eclipse.org/downloads/packages/eclipse-ide-java-ee-developers/heliossr2[Eclipse
3.6] and the http://vaadin.com/eclipse[Vaadin plugin] was used to create
the application. Both the project files and the third-party libraries
are included in the source code repository. At this point, I recommend
you to download the source code before continuing.

Once you have Eclipse, Tomcat and Git properly installed and configured,
you can follow the following instructions to get the demo application up
and running:

1. Open a command line and clone the Git repository:
`git clone git://github.com/peholmst/VaadinActivitiDemo.git`
2. Start up Eclipse.
3. From the *File* menu, select *Import*.
4. Select *Existing Projects into Workspace* and click *Next*.
5. In the *Select root directory* field, click the *Browse* button and
locate the cloned Git repository directory.
6. In the list of projects, check *VaadinActivitiDemo* and click
*Finish*.
7. In the *Project Explorer*, right-click on *VaadinActivitiDemo*,
point to *Run As* and select *Run on Server*.
8. Select the Tomcat 7 server and click *Finish*.
9. Open a web browser and point it to
_http://localhost:8080/VaadinActivitiDemo_.

[[scope]]
Scope
^^^^^

As Activiti has a huge amount of features, we are only going to look at
a small subset of them in order to keep the scope of this article under
control. More specifically, we are going to look at the following two
questions:

1. How easy (or hard) is it to create custom-built forms using Vaadin
and plug these into Activiti?
2. How easy (or hard) is it to combine process data from Activiti with
other domain data from e.g. JPA?

[[application-architecture]]
Application Architecture
~~~~~~~~~~~~~~~~~~~~~~~~

In this section, we are going to briefly discuss the architecture of the
demo application on a general level and show how it has been implemented
on more technical level. A simplified version of the architecture is
illustrated here:

image:img/architecture.png[Application architecture]

[[the-h2-database]]
The H2 Database
^^^^^^^^^^^^^^^

The H2 database is used in in-memory mode and will start when the
process engine is initialized and stop when the engine is destroyed. All
you have to do is specify some connection parameters when you
https://github.com/peholmst/VaadinActivitiDemo/blob/master/src/activiti.cfg.xml[configure
Activiti] and the rest will be handled automatically.

[[the-activiti-engine-and-process-definitions]]
The Activiti Engine and Process Definitions
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

The Activiti engine is initialized and destroyed by a servlet context
listener, like so:

[source,java]
....
@WebListener
public class ProcessEngineServletContextListener implements ServletContextListener {
@Override
public void contextInitialized(ServletContextEvent event) {
ProcessEngines.init();
deployProcesses();
}

@Override
public void contextDestroyed(ServletContextEvent event) {
ProcessEngines.destroy();
}

private void deployProcesses() {
RepositoryService repositoryService = ProcessEngines.getDefaultProcessEngine().getRepositoryService();
repositoryService.createDeployment()
.addClasspathResource("path/to/bpmn-document.bpmn20.xml")
.deploy();
}
}
....

Once the process engine has been initialized, the context listener
deploys the BPMN 2.0 process definitions to it. In other words, the
Activiti process engine becomes available as soon as the web application
starts and remains up and running until the application is stopped. All
the Vaadin application instances use the same Activiti engine.

[[the-vaadin-application]]
The Vaadin Application
^^^^^^^^^^^^^^^^^^^^^^

The Vaadin application is designed according to the
http://en.wikipedia.org/wiki/Model-view-presenter[Model-View-Presenter]
(MVP) pattern and is implemented using
https://github.com/peholmst/MVP4Vaadin[MVP4Vaadin]. This gives us the
following benefits:

* Clear separation between logic and UI (makes unit testing easier).
* View navigation becomes easier (e.g. the breadcrumb bar shown in the
demo screencast is a built-in part of MVP4Vaadin).

The following diagram illustrates the different views and potential
navigation paths between them:

image:img/views.png[Application views and navigation]

When the application is first started, the
https://github.com/peholmst/VaadinActivitiDemo/tree/master/src/com/github/peholmst/vaadinactivitidemo/ui/login[Login
View] is displayed in the main window. Once the user has logged on, the
main window is replaced with the
https://github.com/peholmst/VaadinActivitiDemo/tree/master/src/com/github/peholmst/vaadinactivitidemo/ui/main[Main
View]:

[source,java]
....
public class DemoApplication extends Application implements ViewListener {
// Field declarations omitted

@Override
public void init() {
createAndShowLoginWindow();
}

private void createAndShowLoginWindow() {
// Implementation omitted
}

private void createAndShowMainWindow() {
// Implementation omitted
}

@Override
public void handleViewEvent(ViewEvent event) {
if (event instanceof UserLoggedInEvent) {
// Some code omitted
createAndShowMainWindow();
} // Other event handlers omitted
}
// Additional methods omitted.
}
....

The main view acts as a controller and container for a number of
embedded views:

* The
https://github.com/peholmst/VaadinActivitiDemo/tree/master/src/com/github/peholmst/vaadinactivitidemo/ui/home[Home
View] is the main menu. From here, you can navigate to the _Process
Browser View_ and the _Identity Management View_.
* The
https://github.com/peholmst/VaadinActivitiDemo/tree/master/src/com/github/peholmst/vaadinactivitidemo/ui/processes[Process
Browser View] contains a list of all the available process definitions.
From this view, you can start new process instances. If a process has a
start form, you can also navigate to the _User Form View_.
* The
https://github.com/peholmst/VaadinActivitiDemo/tree/master/src/com/github/peholmst/vaadinactivitidemo/ui/identity[Identity
Management View] allows you to manage users and user groups.
* The
https://github.com/peholmst/VaadinActivitiDemo/blob/master/src/com/github/peholmst/vaadinactivitidemo/ui/tasks/UnassignedTasksViewImpl.java[Unassigned
Tasks View] contains a list of all unassigned tasks. You can navigate to
this view from any other view. From this view, you can assign tasks to
yourself.
* The
https://github.com/peholmst/VaadinActivitiDemo/blob/master/src/com/github/peholmst/vaadinactivitidemo/ui/tasks/MyTasksViewImpl.java[My
Tasks View] contains a list of all tasks currently assigned to you. You
can navigate to this view from any other view. From this view, you can
complete tasks. If a task has a form, you can also navigate to the _User
Form View_.
* The
https://github.com/peholmst/VaadinActivitiDemo/tree/master/src/com/github/peholmst/vaadinactivitidemo/ui/forms[User
Form View] is responsible for displaying the _User Task Forms_, e.g.
before a new process instance is created or before a task is completed.
The information about which form to show (if any) is specified in the
BPMN process definition. *Please note that when we are talking about
forms in this article, we are referring to the Acticiti form concept. Do
not confuse this with Vaadin forms.*

These views (or technically speaking their corresponding presenters)
communicate directly with the Activiti engine. For example, the
following snippet is taken from the
https://github.com/peholmst/VaadinActivitiDemo/blob/master/src/com/github/peholmst/vaadinactivitidemo/ui/processes/ProcessPresenter.java[`ProcessPresenter`]
class:

[source,java]
....
@Override
public void init() {
getView().setProcessDefinitions(getAllProcessDefinitions());
}

public void startNewInstance(ProcessDefinition processDefinition) {
try {
if (processDefinitionHasForm(processDefinition)) {
openFormForProcessDefinition(processDefinition);
} else {
getRuntimeService().startProcessInstanceById(processDefinition.getId());
getView().showProcessStartSuccess(processDefinition);
}
} catch (RuntimeException e) {
getView().showProcessStartFailure(processDefinition);
}
}

private List<ProcessDefinition> getAllProcessDefinitions() {
ProcessDefinitionQuery query = getRepositoryService().createProcessDefinitionQuery();
return query.orderByProcessDefinitionName().asc().list();
}

private RepositoryService getRepositoryService() {
return ProcessEngines.getDefaultProcessEngine().getRepositoryService();
}

private RuntimeService getRuntimeService() {
return ProcessEngines.getDefaultProcessEngine().getRuntimeService();
}
....

The Main View also regularly checks if there are new tasks available and
notifies the user if that is the case. The
http://vaadin.com/addon/refresher[Refresher] add-on is used to handle
the polling.

[[some-notes-on-mvp4vaadin]]
Some Notes on MVP4Vaadin
^^^^^^^^^^^^^^^^^^^^^^^^

Thanks to MVP4Vaadin, navigation between views is very simple. For
example, the following code snippet is taken from the
https://github.com/peholmst/VaadinActivitiDemo/blob/master/src/com/github/peholmst/vaadinactivitidemo/ui/main/components/WindowHeader.java[`WindowHeader`]
component, a part of the Main View implementation:

[source,java]
....
@SuppressWarnings("serial")
private Button createMyTasksButton() {
Button button = new Button();
button.addListener(new Button.ClickListener() {
@Override
public void buttonClick(ClickEvent event) {
mainPresenter.showMyTasks();
}
});
button.addStyleName(Reindeer.BUTTON_SMALL);
return button;
}

@SuppressWarnings("serial")
private Button createUnassignedTasksButton() {
Button button = new Button();
button.addListener(new Button.ClickListener() {
@Override
public void buttonClick(ClickEvent event) {
mainPresenter.showUnassignedTasks();
}
});
button.addStyleName(Reindeer.BUTTON_SMALL);
return button;
}
....

The corresponding snippets from the
https://github.com/peholmst/VaadinActivitiDemo/blob/master/src/com/github/peholmst/vaadinactivitidemo/ui/main/MainPresenter.java[`MainPresenter`]
class are as follows:

[source,java]
....
public void showUnassignedTasks() {
getViewController().goToView(UnassignedTasksView.VIEW_ID);
}

public void showMyTasks() {
getViewController().goToView(MyTasksView.VIEW_ID);
}
....

[[custom-forms]]
Custom Forms
~~~~~~~~~~~~

As you may already know, it is possible to use automatic form generation
with Activiti, but the generated forms are not Vaadin based. In this
article, we are going to use custom-built Vaadin forms instead. Even
though this forces us to write Java code for each form we want to use,
it gives us some advantages:

* It is possible to have more complex forms with differnt kinds of
components.
* It is possible to tailor the appearance and look and feel of the forms
to the user's needs.
* It is easy to plug in other infrastructure services such as EJBs and
JPA entities.

The following approach is used to implement custom forms in the demo
application:

image:img/customForms.png[Custom forms]

Here is a short walk-through of the most important classes:

* The
https://github.com/peholmst/VaadinActivitiDemo/blob/master/src/com/github/peholmst/vaadinactivitidemo/ui/util/UserTaskForm.java[`UserTaskForm`]
interface is implemented by all custom forms. This interface defines
several methods, the most interesting of which are the following:
** `populateForm(...)`: This method populates the form with initial data
retrieved from the Activiti form service.
** `getFormProperties()`: This method creates a map of the form data
that will be sent to the Activiti form service when the form is
submitted.
* The
https://github.com/peholmst/VaadinActivitiDemo/blob/master/src/com/github/peholmst/vaadinactivitidemo/ui/util/UserTaskFormContainer.java[`UserTaskFormContainer`]
is a class that contains user task forms. Each form can be accessed by a
unique form key, which in turn is used in BPMN-documents to refer to
forms. The main Vaadin application class is responsible for creating and
populating this container. *Please note, that this container class has
nothing to do with Vaadin Data Containers.*
* The
https://github.com/peholmst/VaadinActivitiDemo/blob/master/src/com/github/peholmst/vaadinactivitidemo/ui/forms/UserFormViewImpl.java[`UserFormViewImpl`]
class (and its corresponding presenter) is responsible for looking up
the correct form (by its form key), populating it, displaying it to the
user and finally submitting it.

[[some-code-examples]]
Some Code Examples
^^^^^^^^^^^^^^^^^^

We are now going to look at some snippets from the demo application
source code.

First up is a method from the
https://github.com/peholmst/VaadinActivitiDemo/blob/master/src/com/github/peholmst/vaadinactivitidemo/ui/tasks/MyTasksPresenter.java[`MyTasksPresenter`]
class that is invoked when the user wants to open the form for a
specific task:

[source,java]
....
public void openFormForTask(Task task) {
String formKey = getFormKey(task);
if (formKey != null) {
HashMap<String, Object> params = new HashMap<String, Object>();
params.put(UserFormView.KEY_FORM_KEY, formKey);
params.put(UserFormView.KEY_TASK_ID, task.getId());
getViewController().goToView(UserFormView.VIEW_ID, params);
}
}
....

The method checks if the task has a form and asks the view controller (a
part of MVP4Vaadin) to navigate to the User Form View if that is the
case. The task ID and form key is passed to the view as a map of
parameters.

The next code example is a method of the
https://github.com/peholmst/VaadinActivitiDemo/blob/master/src/com/github/peholmst/vaadinactivitidemo/ui/forms/UserFormPresenter.java[`UserFormPresenter`]
class that is invoked when the view controller has navigated to the User
Form View:

[source,java]
....
@Override
protected void viewShown(ViewController viewController,
Map<String, Object> userData, ControllableView oldView,
Direction direction) {
if (userData != null) {
String formKey = (String) userData.get(UserFormView.KEY_FORM_KEY);
if (userData.containsKey(UserFormView.KEY_TASK_ID)) {
String taskId = (String) userData.get(UserFormView.KEY_TASK_ID);
showTaskForm(formKey, taskId);
}
// The rest of the implementation is omitted
}
}

private void showTaskForm(String formKey, String taskId) {
UserTaskForm form = userTaskFormContainer.getForm(formKey);
TaskFormData formData = getFormService().getTaskFormData(taskId);
form.populateForm(formData, taskId);
getView().setForm(form);
}
....

The method first extracts the task ID and form key from the parameter
map. It then invokes a helper method that looks up the corresponding
form data and form from the Activiti form service and the
`UserTaskFormContainer`, respectively. Finally, the form is populated
and shown to the user.

The final example is a method (also from `UserFormPresenter`) that is
invoked when the user submits the form:

[source,java]
....
public void submitForm(UserTaskForm form) {
if (form.getFormType().equals(UserTaskForm.Type.START_FORM)) {
getFormService().submitStartFormData(form.getProcessDefinitionId(), form.getFormProperties());
} else if (form.getFormType().equals(UserTaskForm.Type.TASK_FORM)) {
getFormService().submitTaskFormData(form.getTaskId(), form.getFormProperties());
}
getViewController().goBack();
}
....

As there are two different kinds of forms (process start forms and user
task forms, respectively), the method has to start by checking which
kind it is currently processing. Then, the information is submitted to
the Activiti form service. Finally, the view controller is asked to
navigate back to what ever page it was on before the User Form View
became visible.

[[complex-domain-objects]]
Complex Domain Objects
~~~~~~~~~~~~~~~~~~~~~~

The demo application does not use any domain objects as all the
information can be represented as Activiti process variables. However,
in most real-world applications you probably want to use a dedicated
domain model.

We are now going to look at a potential design for combining Activiti
with a complex domain model. *Please note that the design has not been
tested in practice* - feel free to test it if you feel like it (and
remember to tell me the results)!

Here is a sketch of a process that involves a more complicated domain
model than just a few strings:

image:img/complexdomain.png[Complex domain]

The idea is that although many different entities need to be created and
stored throughout the process, only some small parts of the information
is actually required to drive the process forward. For example, the
*Send invoice* task does not necessarily need the entire invoice object;
only the invoice number, order number and due date should be sufficient.
Likewise, the *Receive payment* task needs only the invoice number to be
able to check that the invoice has been paid, the timer needs the due
date to be able to send out a new invoice, etc.

[[implementation-ideas]]
Implementation Ideas
^^^^^^^^^^^^^^^^^^^^

The actual forms that the users fill in could be implemented in Vaadin,
as described previously in this article. When the form is submitted, the
entities are saved to some data store (e.g. a relational database).
After this, the necessary form properties are submitted to the Activiti
form service, completing the task in question. In other words, Activiti
is used to drive the process forward (i.e. define the business logic),
whereas JPA or any other object persistence solution is used to store
data.

There are a few things to keep in mind, though:

* How are transactions handled?
* How is data validation performed?
* How is security enforced?
* Is versioning of the domain data required? How should it be
implemented if so? (Activiti already maintains a history log of the
process operations.)

In smaller applications, the following design could be sufficient:

image:img/complexdomain_saving.png[Complex domain saving]

Here, the Presenter (in the MVP-pattern) is responsible for extracting
the needed form properties from the domain data, saving the entity and
submitting the form. This moves some of the logic to the UI layer, but
for small applications this is not a big problem as the presenter is
itself decoupled from the actual UI code.

For larger applications, the following design could be a better
approach:

image:img/complexdomain_saving2.png[Complex domain saving 2]

Here, both the repository and the form service engine is hidden behind a
facade. A Data Transfer Object (DTO) is used to convey the data from the
Presenter to the facade. This approach requires more code, but decouples
the business layer from the UI layer even more. Security enforcement and
transaction handling also become easier.

[[summary]]
Summary
~~~~~~~

In this article, we have looked at how the Activiti BPM engine and
Vaadin fit together. We have covered how the engine is initialized and
accessed by Vaadin application instances. We have also covered how
custom-made Vaadin forms can be used instead of Activiti's own form
generation. Finally, we have discussed a way of combining Activiti
processes with a more complex domain model.

The Activiti API is clear and does not force adopters to use a specific
GUI technology. Therefore, it plays really well with Vaadin and should
be concidered a serious alternative for process centric enterprise
applications.

Likewise, Vaadin should be considered a serious alternative as a front
end technology for applications based on Activiti.

If you have any comments or questions, for example if something in the
article is unclear or confusing, feel free to either post them below or
send them to me directly by e-mail.

+ 1
- 0
documentation/articles/contents.asciidoc View File

@@ -12,3 +12,4 @@
- link:FindingTheCurrentRootAndApplication.asciidoc[Finding the current root and application]
- link:CreatingABasicApplication.asciidoc[Creating a basic application]
- link:JasperReportsOnVaadinSample.asciidoc[Jasper reports on Vaadin sample]
- link:BuildingVaadinApplicationsOnTopOfActiviti.asciidoc[Building Vaadin applications on top of Activiti]

BIN
documentation/articles/img/architecture.png View File


BIN
documentation/articles/img/complexdomain.png View File


BIN
documentation/articles/img/complexdomain_saving.png View File


BIN
documentation/articles/img/complexdomain_saving2.png View File


BIN
documentation/articles/img/customForms.png View File


BIN
documentation/articles/img/process.png View File


BIN
documentation/articles/img/views.png View File


Loading…
Cancel
Save