From: Erik Lumme Date: Thu, 14 Sep 2017 13:14:22 +0000 (+0300) Subject: Migrate BuildingVaadinApplicationsOnTopOfActiviti X-Git-Tag: 7.7.11~6^2~29 X-Git-Url: https://source.dussan.org/?a=commitdiff_plain;h=4542dbab220720ad950eec4aad8e638bab4d3fe7;p=vaadin-framework.git Migrate BuildingVaadinApplicationsOnTopOfActiviti --- diff --git a/documentation/articles/BuildingVaadinApplicationsOnTopOfActiviti.asciidoc b/documentation/articles/BuildingVaadinApplicationsOnTopOfActiviti.asciidoc new file mode 100644 index 0000000000..d7c8fdba30 --- /dev/null +++ b/documentation/articles/BuildingVaadinApplicationsOnTopOfActiviti.asciidoc @@ -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 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 params = new HashMap(); + 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 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. diff --git a/documentation/articles/contents.asciidoc b/documentation/articles/contents.asciidoc index c92c9478db..ac5e2b9644 100644 --- a/documentation/articles/contents.asciidoc +++ b/documentation/articles/contents.asciidoc @@ -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] diff --git a/documentation/articles/img/architecture.png b/documentation/articles/img/architecture.png new file mode 100644 index 0000000000..fa0930179a Binary files /dev/null and b/documentation/articles/img/architecture.png differ diff --git a/documentation/articles/img/complexdomain.png b/documentation/articles/img/complexdomain.png new file mode 100644 index 0000000000..050049dff5 Binary files /dev/null and b/documentation/articles/img/complexdomain.png differ diff --git a/documentation/articles/img/complexdomain_saving.png b/documentation/articles/img/complexdomain_saving.png new file mode 100644 index 0000000000..97cf0cc790 Binary files /dev/null and b/documentation/articles/img/complexdomain_saving.png differ diff --git a/documentation/articles/img/complexdomain_saving2.png b/documentation/articles/img/complexdomain_saving2.png new file mode 100644 index 0000000000..af4d5d4f25 Binary files /dev/null and b/documentation/articles/img/complexdomain_saving2.png differ diff --git a/documentation/articles/img/customForms.png b/documentation/articles/img/customForms.png new file mode 100644 index 0000000000..0edb698b71 Binary files /dev/null and b/documentation/articles/img/customForms.png differ diff --git a/documentation/articles/img/process.png b/documentation/articles/img/process.png new file mode 100644 index 0000000000..f8b1b3e1ad Binary files /dev/null and b/documentation/articles/img/process.png differ diff --git a/documentation/articles/img/views.png b/documentation/articles/img/views.png new file mode 100644 index 0000000000..fd4bb48f64 Binary files /dev/null and b/documentation/articles/img/views.png differ