You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

BuildingVaadinApplicationsOnTopOfActiviti.asciidoc 22KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589
  1. ---
  2. title: Building Vaadin Applications On Top Of Activiti
  3. order: 12
  4. layout: page
  5. ---
  6. [[building-vaadin-applications-on-top-of-activiti]]
  7. = Building Vaadin applications on top of Activiti
  8. by Petter Holmström
  9. [[introduction]]
  10. Introduction
  11. ~~~~~~~~~~~~
  12. In this article, we are going to look at how the
  13. http://www.activiti.org[Activiti] BPM engine can be used together with
  14. Vaadin. We are going to do this in the form of a case study of a demo
  15. application that is available on
  16. https://github.com/peholmst/VaadinActivitiDemo[GitHub]. The code is
  17. licensed under Apache License 2.0 and can freely be used as a foundation
  18. for your own applications.
  19. [[the-example-process]]
  20. The Example Process
  21. ^^^^^^^^^^^^^^^^^^^
  22. The following process is used in the demo application:
  23. image:img/process.png[Example process]
  24. Compared to the capabilities of Activiti and BPMN 2.0, the above process
  25. is almost ridiculously simple. However, it allows us to test the
  26. following things:
  27. * *Process start forms*, i.e. forms that need to be filled in before a
  28. process instance is created.
  29. * *User task forms*, i.e. forms that need to be filled in before a task
  30. can be marked as completed.
  31. * Parallell tasks
  32. * Different candidate groups (i.e. groups whose users are potential
  33. assignees of a certain task)
  34. Here is a short walk-through of the process:
  35. 1. Before a new process instance is created, the reporter has to fill
  36. in a _Submit bug report form_.
  37. 2. Once the instance has been created, two tasks are created:
  38. * *Update bug report*: a manager assigns priority and target version to
  39. the report. Potential assignees are members of the *managers* group.
  40. * *Accept bug report*: a developer accepts the bug report. Potential
  41. assignees are members of the *developers* group.
  42. 3. Both of these tasks require the assignee to fill in a form before
  43. they can be completed: the _Update bug report form_ and _Accept bug
  44. report form_, respectively.
  45. 4. Once the tasks have been completed, a new task is created, namely
  46. _Resolve bug report_. Potential assignees are members of the
  47. *developers* group. Ideally, this task should automatically be assigned
  48. to whoever claimed the *Accept bug report* task, but currently this is
  49. not implemented.
  50. 5. Before the task can be completed, the assignee has to fill in the
  51. _Resolve bug report form_.
  52. 6. All tasks have been completed and the process instance ends.
  53. [[prerequisites]]
  54. Prerequisites
  55. ^^^^^^^^^^^^^
  56. In order to get the most out of this article, you should already be
  57. familiar with both Vaadin and Activiti. If not, there is enough free
  58. material available on both products' web sites to get you started.
  59. The demo application is a standard Java EE 6 web application and can be
  60. deployed to any JEE 6 web container, such as
  61. http://tomcat.apache.org[Tomcat 7]. It uses an embedded in-memory
  62. http://www.h2database.com[H2 database] for storing data, which means
  63. that all your data will be lost when the server is restarted.
  64. http://www.eclipse.org/downloads/packages/eclipse-ide-java-ee-developers/heliossr2[Eclipse
  65. 3.6] and the http://vaadin.com/eclipse[Vaadin plugin] was used to create
  66. the application. Both the project files and the third-party libraries
  67. are included in the source code repository. At this point, I recommend
  68. you to download the source code before continuing.
  69. Once you have Eclipse, Tomcat and Git properly installed and configured,
  70. you can follow the following instructions to get the demo application up
  71. and running:
  72. 1. Open a command line and clone the Git repository:
  73. `git clone git://github.com/peholmst/VaadinActivitiDemo.git`
  74. 2. Start up Eclipse.
  75. 3. From the *File* menu, select *Import*.
  76. 4. Select *Existing Projects into Workspace* and click *Next*.
  77. 5. In the *Select root directory* field, click the *Browse* button and
  78. locate the cloned Git repository directory.
  79. 6. In the list of projects, check *VaadinActivitiDemo* and click
  80. *Finish*.
  81. 7. In the *Project Explorer*, right-click on *VaadinActivitiDemo*,
  82. point to *Run As* and select *Run on Server*.
  83. 8. Select the Tomcat 7 server and click *Finish*.
  84. 9. Open a web browser and point it to
  85. _http://localhost:8080/VaadinActivitiDemo_.
  86. [[scope]]
  87. Scope
  88. ^^^^^
  89. As Activiti has a huge amount of features, we are only going to look at
  90. a small subset of them in order to keep the scope of this article under
  91. control. More specifically, we are going to look at the following two
  92. questions:
  93. 1. How easy (or hard) is it to create custom-built forms using Vaadin
  94. and plug these into Activiti?
  95. 2. How easy (or hard) is it to combine process data from Activiti with
  96. other domain data from e.g. JPA?
  97. [[application-architecture]]
  98. Application Architecture
  99. ~~~~~~~~~~~~~~~~~~~~~~~~
  100. In this section, we are going to briefly discuss the architecture of the
  101. demo application on a general level and show how it has been implemented
  102. on more technical level. A simplified version of the architecture is
  103. illustrated here:
  104. image:img/architecture.png[Application architecture]
  105. [[the-h2-database]]
  106. The H2 Database
  107. ^^^^^^^^^^^^^^^
  108. The H2 database is used in in-memory mode and will start when the
  109. process engine is initialized and stop when the engine is destroyed. All
  110. you have to do is specify some connection parameters when you
  111. https://github.com/peholmst/VaadinActivitiDemo/blob/master/src/activiti.cfg.xml[configure
  112. Activiti] and the rest will be handled automatically.
  113. [[the-activiti-engine-and-process-definitions]]
  114. The Activiti Engine and Process Definitions
  115. ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  116. The Activiti engine is initialized and destroyed by a servlet context
  117. listener, like so:
  118. [source,java]
  119. ....
  120. @WebListener
  121. public class ProcessEngineServletContextListener implements ServletContextListener {
  122. @Override
  123. public void contextInitialized(ServletContextEvent event) {
  124. ProcessEngines.init();
  125. deployProcesses();
  126. }
  127. @Override
  128. public void contextDestroyed(ServletContextEvent event) {
  129. ProcessEngines.destroy();
  130. }
  131. private void deployProcesses() {
  132. RepositoryService repositoryService = ProcessEngines.getDefaultProcessEngine().getRepositoryService();
  133. repositoryService.createDeployment()
  134. .addClasspathResource("path/to/bpmn-document.bpmn20.xml")
  135. .deploy();
  136. }
  137. }
  138. ....
  139. Once the process engine has been initialized, the context listener
  140. deploys the BPMN 2.0 process definitions to it. In other words, the
  141. Activiti process engine becomes available as soon as the web application
  142. starts and remains up and running until the application is stopped. All
  143. the Vaadin application instances use the same Activiti engine.
  144. [[the-vaadin-application]]
  145. The Vaadin Application
  146. ^^^^^^^^^^^^^^^^^^^^^^
  147. The Vaadin application is designed according to the
  148. http://en.wikipedia.org/wiki/Model-view-presenter[Model-View-Presenter]
  149. (MVP) pattern and is implemented using
  150. https://github.com/peholmst/MVP4Vaadin[MVP4Vaadin]. This gives us the
  151. following benefits:
  152. * Clear separation between logic and UI (makes unit testing easier).
  153. * View navigation becomes easier (e.g. the breadcrumb bar shown in the
  154. demo screencast is a built-in part of MVP4Vaadin).
  155. The following diagram illustrates the different views and potential
  156. navigation paths between them:
  157. image:img/views.png[Application views and navigation]
  158. When the application is first started, the
  159. https://github.com/peholmst/VaadinActivitiDemo/tree/master/src/com/github/peholmst/vaadinactivitidemo/ui/login[Login
  160. View] is displayed in the main window. Once the user has logged on, the
  161. main window is replaced with the
  162. https://github.com/peholmst/VaadinActivitiDemo/tree/master/src/com/github/peholmst/vaadinactivitidemo/ui/main[Main
  163. View]:
  164. [source,java]
  165. ....
  166. public class DemoApplication extends Application implements ViewListener {
  167. // Field declarations omitted
  168. @Override
  169. public void init() {
  170. createAndShowLoginWindow();
  171. }
  172. private void createAndShowLoginWindow() {
  173. // Implementation omitted
  174. }
  175. private void createAndShowMainWindow() {
  176. // Implementation omitted
  177. }
  178. @Override
  179. public void handleViewEvent(ViewEvent event) {
  180. if (event instanceof UserLoggedInEvent) {
  181. // Some code omitted
  182. createAndShowMainWindow();
  183. } // Other event handlers omitted
  184. }
  185. // Additional methods omitted.
  186. }
  187. ....
  188. The main view acts as a controller and container for a number of
  189. embedded views:
  190. * The
  191. https://github.com/peholmst/VaadinActivitiDemo/tree/master/src/com/github/peholmst/vaadinactivitidemo/ui/home[Home
  192. View] is the main menu. From here, you can navigate to the _Process
  193. Browser View_ and the _Identity Management View_.
  194. * The
  195. https://github.com/peholmst/VaadinActivitiDemo/tree/master/src/com/github/peholmst/vaadinactivitidemo/ui/processes[Process
  196. Browser View] contains a list of all the available process definitions.
  197. From this view, you can start new process instances. If a process has a
  198. start form, you can also navigate to the _User Form View_.
  199. * The
  200. https://github.com/peholmst/VaadinActivitiDemo/tree/master/src/com/github/peholmst/vaadinactivitidemo/ui/identity[Identity
  201. Management View] allows you to manage users and user groups.
  202. * The
  203. https://github.com/peholmst/VaadinActivitiDemo/blob/master/src/com/github/peholmst/vaadinactivitidemo/ui/tasks/UnassignedTasksViewImpl.java[Unassigned
  204. Tasks View] contains a list of all unassigned tasks. You can navigate to
  205. this view from any other view. From this view, you can assign tasks to
  206. yourself.
  207. * The
  208. https://github.com/peholmst/VaadinActivitiDemo/blob/master/src/com/github/peholmst/vaadinactivitidemo/ui/tasks/MyTasksViewImpl.java[My
  209. Tasks View] contains a list of all tasks currently assigned to you. You
  210. can navigate to this view from any other view. From this view, you can
  211. complete tasks. If a task has a form, you can also navigate to the _User
  212. Form View_.
  213. * The
  214. https://github.com/peholmst/VaadinActivitiDemo/tree/master/src/com/github/peholmst/vaadinactivitidemo/ui/forms[User
  215. Form View] is responsible for displaying the _User Task Forms_, e.g.
  216. before a new process instance is created or before a task is completed.
  217. The information about which form to show (if any) is specified in the
  218. BPMN process definition. *Please note that when we are talking about
  219. forms in this article, we are referring to the Acticiti form concept. Do
  220. not confuse this with Vaadin forms.*
  221. These views (or technically speaking their corresponding presenters)
  222. communicate directly with the Activiti engine. For example, the
  223. following snippet is taken from the
  224. https://github.com/peholmst/VaadinActivitiDemo/blob/master/src/com/github/peholmst/vaadinactivitidemo/ui/processes/ProcessPresenter.java[`ProcessPresenter`]
  225. class:
  226. [source,java]
  227. ....
  228. @Override
  229. public void init() {
  230. getView().setProcessDefinitions(getAllProcessDefinitions());
  231. }
  232. public void startNewInstance(ProcessDefinition processDefinition) {
  233. try {
  234. if (processDefinitionHasForm(processDefinition)) {
  235. openFormForProcessDefinition(processDefinition);
  236. } else {
  237. getRuntimeService().startProcessInstanceById(processDefinition.getId());
  238. getView().showProcessStartSuccess(processDefinition);
  239. }
  240. } catch (RuntimeException e) {
  241. getView().showProcessStartFailure(processDefinition);
  242. }
  243. }
  244. private List<ProcessDefinition> getAllProcessDefinitions() {
  245. ProcessDefinitionQuery query = getRepositoryService().createProcessDefinitionQuery();
  246. return query.orderByProcessDefinitionName().asc().list();
  247. }
  248. private RepositoryService getRepositoryService() {
  249. return ProcessEngines.getDefaultProcessEngine().getRepositoryService();
  250. }
  251. private RuntimeService getRuntimeService() {
  252. return ProcessEngines.getDefaultProcessEngine().getRuntimeService();
  253. }
  254. ....
  255. The Main View also regularly checks if there are new tasks available and
  256. notifies the user if that is the case. The
  257. http://vaadin.com/addon/refresher[Refresher] add-on is used to handle
  258. the polling.
  259. [[some-notes-on-mvp4vaadin]]
  260. Some Notes on MVP4Vaadin
  261. ^^^^^^^^^^^^^^^^^^^^^^^^
  262. Thanks to MVP4Vaadin, navigation between views is very simple. For
  263. example, the following code snippet is taken from the
  264. https://github.com/peholmst/VaadinActivitiDemo/blob/master/src/com/github/peholmst/vaadinactivitidemo/ui/main/components/WindowHeader.java[`WindowHeader`]
  265. component, a part of the Main View implementation:
  266. [source,java]
  267. ....
  268. @SuppressWarnings("serial")
  269. private Button createMyTasksButton() {
  270. Button button = new Button();
  271. button.addListener(new Button.ClickListener() {
  272. @Override
  273. public void buttonClick(ClickEvent event) {
  274. mainPresenter.showMyTasks();
  275. }
  276. });
  277. button.addStyleName(Reindeer.BUTTON_SMALL);
  278. return button;
  279. }
  280. @SuppressWarnings("serial")
  281. private Button createUnassignedTasksButton() {
  282. Button button = new Button();
  283. button.addListener(new Button.ClickListener() {
  284. @Override
  285. public void buttonClick(ClickEvent event) {
  286. mainPresenter.showUnassignedTasks();
  287. }
  288. });
  289. button.addStyleName(Reindeer.BUTTON_SMALL);
  290. return button;
  291. }
  292. ....
  293. The corresponding snippets from the
  294. https://github.com/peholmst/VaadinActivitiDemo/blob/master/src/com/github/peholmst/vaadinactivitidemo/ui/main/MainPresenter.java[`MainPresenter`]
  295. class are as follows:
  296. [source,java]
  297. ....
  298. public void showUnassignedTasks() {
  299. getViewController().goToView(UnassignedTasksView.VIEW_ID);
  300. }
  301. public void showMyTasks() {
  302. getViewController().goToView(MyTasksView.VIEW_ID);
  303. }
  304. ....
  305. [[custom-forms]]
  306. Custom Forms
  307. ~~~~~~~~~~~~
  308. As you may already know, it is possible to use automatic form generation
  309. with Activiti, but the generated forms are not Vaadin based. In this
  310. article, we are going to use custom-built Vaadin forms instead. Even
  311. though this forces us to write Java code for each form we want to use,
  312. it gives us some advantages:
  313. * It is possible to have more complex forms with differnt kinds of
  314. components.
  315. * It is possible to tailor the appearance and look and feel of the forms
  316. to the user's needs.
  317. * It is easy to plug in other infrastructure services such as EJBs and
  318. JPA entities.
  319. The following approach is used to implement custom forms in the demo
  320. application:
  321. image:img/customForms.png[Custom forms]
  322. Here is a short walk-through of the most important classes:
  323. * The
  324. https://github.com/peholmst/VaadinActivitiDemo/blob/master/src/com/github/peholmst/vaadinactivitidemo/ui/util/UserTaskForm.java[`UserTaskForm`]
  325. interface is implemented by all custom forms. This interface defines
  326. several methods, the most interesting of which are the following:
  327. ** `populateForm(...)`: This method populates the form with initial data
  328. retrieved from the Activiti form service.
  329. ** `getFormProperties()`: This method creates a map of the form data
  330. that will be sent to the Activiti form service when the form is
  331. submitted.
  332. * The
  333. https://github.com/peholmst/VaadinActivitiDemo/blob/master/src/com/github/peholmst/vaadinactivitidemo/ui/util/UserTaskFormContainer.java[`UserTaskFormContainer`]
  334. is a class that contains user task forms. Each form can be accessed by a
  335. unique form key, which in turn is used in BPMN-documents to refer to
  336. forms. The main Vaadin application class is responsible for creating and
  337. populating this container. *Please note, that this container class has
  338. nothing to do with Vaadin Data Containers.*
  339. * The
  340. https://github.com/peholmst/VaadinActivitiDemo/blob/master/src/com/github/peholmst/vaadinactivitidemo/ui/forms/UserFormViewImpl.java[`UserFormViewImpl`]
  341. class (and its corresponding presenter) is responsible for looking up
  342. the correct form (by its form key), populating it, displaying it to the
  343. user and finally submitting it.
  344. [[some-code-examples]]
  345. Some Code Examples
  346. ^^^^^^^^^^^^^^^^^^
  347. We are now going to look at some snippets from the demo application
  348. source code.
  349. First up is a method from the
  350. https://github.com/peholmst/VaadinActivitiDemo/blob/master/src/com/github/peholmst/vaadinactivitidemo/ui/tasks/MyTasksPresenter.java[`MyTasksPresenter`]
  351. class that is invoked when the user wants to open the form for a
  352. specific task:
  353. [source,java]
  354. ....
  355. public void openFormForTask(Task task) {
  356. String formKey = getFormKey(task);
  357. if (formKey != null) {
  358. HashMap<String, Object> params = new HashMap<String, Object>();
  359. params.put(UserFormView.KEY_FORM_KEY, formKey);
  360. params.put(UserFormView.KEY_TASK_ID, task.getId());
  361. getViewController().goToView(UserFormView.VIEW_ID, params);
  362. }
  363. }
  364. ....
  365. The method checks if the task has a form and asks the view controller (a
  366. part of MVP4Vaadin) to navigate to the User Form View if that is the
  367. case. The task ID and form key is passed to the view as a map of
  368. parameters.
  369. The next code example is a method of the
  370. https://github.com/peholmst/VaadinActivitiDemo/blob/master/src/com/github/peholmst/vaadinactivitidemo/ui/forms/UserFormPresenter.java[`UserFormPresenter`]
  371. class that is invoked when the view controller has navigated to the User
  372. Form View:
  373. [source,java]
  374. ....
  375. @Override
  376. protected void viewShown(ViewController viewController,
  377. Map<String, Object> userData, ControllableView oldView,
  378. Direction direction) {
  379. if (userData != null) {
  380. String formKey = (String) userData.get(UserFormView.KEY_FORM_KEY);
  381. if (userData.containsKey(UserFormView.KEY_TASK_ID)) {
  382. String taskId = (String) userData.get(UserFormView.KEY_TASK_ID);
  383. showTaskForm(formKey, taskId);
  384. }
  385. // The rest of the implementation is omitted
  386. }
  387. }
  388. private void showTaskForm(String formKey, String taskId) {
  389. UserTaskForm form = userTaskFormContainer.getForm(formKey);
  390. TaskFormData formData = getFormService().getTaskFormData(taskId);
  391. form.populateForm(formData, taskId);
  392. getView().setForm(form);
  393. }
  394. ....
  395. The method first extracts the task ID and form key from the parameter
  396. map. It then invokes a helper method that looks up the corresponding
  397. form data and form from the Activiti form service and the
  398. `UserTaskFormContainer`, respectively. Finally, the form is populated
  399. and shown to the user.
  400. The final example is a method (also from `UserFormPresenter`) that is
  401. invoked when the user submits the form:
  402. [source,java]
  403. ....
  404. public void submitForm(UserTaskForm form) {
  405. if (form.getFormType().equals(UserTaskForm.Type.START_FORM)) {
  406. getFormService().submitStartFormData(form.getProcessDefinitionId(), form.getFormProperties());
  407. } else if (form.getFormType().equals(UserTaskForm.Type.TASK_FORM)) {
  408. getFormService().submitTaskFormData(form.getTaskId(), form.getFormProperties());
  409. }
  410. getViewController().goBack();
  411. }
  412. ....
  413. As there are two different kinds of forms (process start forms and user
  414. task forms, respectively), the method has to start by checking which
  415. kind it is currently processing. Then, the information is submitted to
  416. the Activiti form service. Finally, the view controller is asked to
  417. navigate back to what ever page it was on before the User Form View
  418. became visible.
  419. [[complex-domain-objects]]
  420. Complex Domain Objects
  421. ~~~~~~~~~~~~~~~~~~~~~~
  422. The demo application does not use any domain objects as all the
  423. information can be represented as Activiti process variables. However,
  424. in most real-world applications you probably want to use a dedicated
  425. domain model.
  426. We are now going to look at a potential design for combining Activiti
  427. with a complex domain model. *Please note that the design has not been
  428. tested in practice* - feel free to test it if you feel like it (and
  429. remember to tell me the results)!
  430. Here is a sketch of a process that involves a more complicated domain
  431. model than just a few strings:
  432. image:img/complexdomain.png[Complex domain]
  433. The idea is that although many different entities need to be created and
  434. stored throughout the process, only some small parts of the information
  435. is actually required to drive the process forward. For example, the
  436. *Send invoice* task does not necessarily need the entire invoice object;
  437. only the invoice number, order number and due date should be sufficient.
  438. Likewise, the *Receive payment* task needs only the invoice number to be
  439. able to check that the invoice has been paid, the timer needs the due
  440. date to be able to send out a new invoice, etc.
  441. [[implementation-ideas]]
  442. Implementation Ideas
  443. ^^^^^^^^^^^^^^^^^^^^
  444. The actual forms that the users fill in could be implemented in Vaadin,
  445. as described previously in this article. When the form is submitted, the
  446. entities are saved to some data store (e.g. a relational database).
  447. After this, the necessary form properties are submitted to the Activiti
  448. form service, completing the task in question. In other words, Activiti
  449. is used to drive the process forward (i.e. define the business logic),
  450. whereas JPA or any other object persistence solution is used to store
  451. data.
  452. There are a few things to keep in mind, though:
  453. * How are transactions handled?
  454. * How is data validation performed?
  455. * How is security enforced?
  456. * Is versioning of the domain data required? How should it be
  457. implemented if so? (Activiti already maintains a history log of the
  458. process operations.)
  459. In smaller applications, the following design could be sufficient:
  460. image:img/complexdomain_saving.png[Complex domain saving]
  461. Here, the Presenter (in the MVP-pattern) is responsible for extracting
  462. the needed form properties from the domain data, saving the entity and
  463. submitting the form. This moves some of the logic to the UI layer, but
  464. for small applications this is not a big problem as the presenter is
  465. itself decoupled from the actual UI code.
  466. For larger applications, the following design could be a better
  467. approach:
  468. image:img/complexdomain_saving2.png[Complex domain saving 2]
  469. Here, both the repository and the form service engine is hidden behind a
  470. facade. A Data Transfer Object (DTO) is used to convey the data from the
  471. Presenter to the facade. This approach requires more code, but decouples
  472. the business layer from the UI layer even more. Security enforcement and
  473. transaction handling also become easier.
  474. [[summary]]
  475. Summary
  476. ~~~~~~~
  477. In this article, we have looked at how the Activiti BPM engine and
  478. Vaadin fit together. We have covered how the engine is initialized and
  479. accessed by Vaadin application instances. We have also covered how
  480. custom-made Vaadin forms can be used instead of Activiti's own form
  481. generation. Finally, we have discussed a way of combining Activiti
  482. processes with a more complex domain model.
  483. The Activiti API is clear and does not force adopters to use a specific
  484. GUI technology. Therefore, it plays really well with Vaadin and should
  485. be concidered a serious alternative for process centric enterprise
  486. applications.
  487. Likewise, Vaadin should be considered a serious alternative as a front
  488. end technology for applications based on Activiti.
  489. If you have any comments or questions, for example if something in the
  490. article is unclear or confusing, feel free to either post them below or
  491. send them to me directly by e-mail.