• Stars
    star
    196
  • Rank 198,553 (Top 4 %)
  • Language
    Clojure
  • License
    MIT License
  • Created about 6 years ago
  • Updated about 2 years ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

data driven, stateless, process engine

Dativity

Dativity is a stateless, data driven workflow engine library for Clojure and ClojureScript.

It is inspired by the Artifact centric business process model.

Table of Contents

Motivation
Design
Features
Examples
Dependencies
License

Latest version

Clojars Project

See Changelog for release contents.

Motivation

Conventional process engines such as Activiti and Camunda are centered around the sequence in which activities should be performed, often modeled using BPMN.

The key idea of dativity is that the progression of data is the main driver of a workflow. It is achieved by revealing what actions can be performed given how actions depend on data and what data is currently available, rather than what actions should be performed.

For example, an insurance claim can not be reviewed until it has been submitted. The action of submitting produces data - the claim, and the review action depends on it. As soon as there is a claim, it's possible to perform the review action.

Many business process tools require a database to track the progress of a workflow. By adopting the Artifact centric business process model, dativity is able to provide the same core value as those other tools without requiring a dedicated database. Deciding how data is stored is up to you.

By being stateless, dativity allows you to reduce the complexity of your application.

This blog post offers a more extensive motivation.

Design

Dativity models a process into three different entities:

  • Action
  • Data
  • Role

The entites relate in the following ways:

  • Data (green) is required by an action
  • Actions (purple) produce data
  • Roles (yellow) perform an action

a simple credit application process

In the above example, the action 'create case' produces the data 'case id' and 'customer-id'. When those pieces of information have been added to the case, 'enter-loan-details' can be performed because it only depends on 'case-id' to be present.

Features

Basic functionality

Given a process definition and a set of collected data, Dativity can answer questions like:

  • What actions can be performed next?
  • What actions can be performed next by role r
  • What actions have been performed?
  • Can action a be performed?
  • What data is required for action a?

Invalidating

Sometimes a user goes back and modify data. Then all data that is produced 'subsequently' has to be invalidated. Dativity has support for this type of scenario, where the case is 'rewinded' to the action that was re-done. Previously entered data is kept, but 'uncommitted', and depending actions need to be performed again. see example.

Conditional requirements

It is possible to specify that a data is required by an action if and only if a given predicate is true. The condition depends on a specified data. see example.

Examples

Basic functionality

The case data is just a map

(def case {})

Define a case model with actions, data, roles and their relationships.

(def case-model
  (dativity.define/create-model
    {:actions                     [:create-case
                                   :enter-loan-details
                                   :produce-credit-application-document
                                   :sign-credit-application-document
                                   :payout-loan]

     :data                        [:case-id
                                   :customer-id
                                   :loan-details
                                   :credit-application-document
                                   :applicant-signature
                                   :officer-signature
                                   :loan-number]

     :roles                       [:applicant
                                   :system
                                   :officer]

     :action-produces             [[:create-case :customer-id]
                                   [:create-case :case-id]
                                   [:enter-loan-details :loan-details]
                                   [:produce-credit-application-document :credit-application-document]
                                   [:sign-credit-application-document :applicant-signature]
                                   [:sign-credit-application-document :officer-signature]
                                   [:payout-loan :loan-number]]

     :action-requires             [[:enter-loan-details :case-id]
                                   [:produce-credit-application-document :loan-details]
                                   [:produce-credit-application-document :customer-id]
                                   [:sign-credit-application-document :credit-application-document]
                                   [:payout-loan :applicant-signature]
                                   [:payout-loan :officer-signature]]

     :role-performs               [[:applicant :create-case]
                                   [:applicant :enter-loan-details]
                                   [:applicant :sign-credit-application-document]
                                   [:officer :sign-credit-application-document]
                                   [:system :payout-loan]
                                   [:system :produce-credit-application-document]]
     :action-requires-conditional []}))

Generate an image of the process definition (requires graphviz, clj only).

(dativity.visualize/generate-png case-model)

What actions are possible?

(dativity.core/next-actions case-model case)
=> #{:create-case}

What can the roles do?

(dativity.core/next-actions case-model case :applicant)
=> #{:create-case}
(dativity.core/next-actions case-model case :officer)
=> #{}

What data is produced by ':create-case'?

(dativity.core/data-produced-by-action case-model :create-case)
=> #{:customer-id :case-id}

Add some data to the case to simulate a few actions

(def case
  (-> case
    (dativity.core/add-data :case-id "542967")
    (dativity.core/add-data :customer-id "199209049345")
    (dativity.core/add-data :loan-details {:amount 100000 :purpose "home"})))

What actions have been completed?

(dativity.core/actions-performed case-model case)
=> #{:enter-loan-details :create-case}

Who can do what?

(dativity.core/next-actions case-model case :applicant)
=> #{}
(dativity.core/next-actions case-model case :system)
=> #{:produce-credit-application-document}
(dativity.core/next-actions case-model case :officer)
=> #{}

The document is produced and it is signed by the officer

(def case 
  (-> case
    (dativity.core/add-data :credit-application-document {:document-id "abc-123"})))

Who can do what?

(dativity.core/next-actions case-model case :applicant)
=> #{}
(dativity.core/next-actions case-model case :system)
=> #{}
(dativity.core/next-actions case-model case :officer)
=> #{:sign-credit-application-document}

Invalidation

A user might go back in your UI and modify data, it is then likely that 'subsequent' data is no longer valid. For example, if the loan amount is changed, the produced application document is probably not valid anymore.

Dativity supports this via the function invalidate-data. When a data is invalidated, all the data that is produced by actions that depend on the invalidated data is invalidated recursively.

When data is invalidated, it is not deleted. The data is kept, but it is 'uncommitted' which means that dativity will say that actions that depend on the uncommitted data are not allowed.

(def case
    (dativity.core/invalidate-data case-model case :loan-details))

Are the loan details gone from the case?

(:loan-details case)
=> {:amount 100000 :purpose "home"}

Now the only available action is to enter loan details again.

(dativity.core/next-actions case-model case :applicant)
=> #{:enter-loan-details}
(dativity.core/next-actions case-model case :system)
=> #{}
(dativity.core/next-actions case-model case :officer)
=> #{}

It's not possible to sign the credit application document.

(dativity.core/action-allowed? case-model case :sign-credit-application-document)
=> false

Conditionally required data

An action-requires-data edge (red arrow in the diagram) can be conditional. The requirement is enforced if and only if a given predicate is true. The predicate is a function of a given data node.

To say that applications for loans of more than 300 000 require signatures from two officers we can write

(def case-model 
     (dativity.define/add-relationship-to-model case-model
                                                (dativity.define/action-requires-conditional
                                                  :payout-loan
                                                  :counter-signature
                                                  (fn [loan-details]
                                                      (> (:amount loan-details) 300000))
                                                  :loan-details)))

Dependencies

graphviz

To generate graph images you need graphviz.

Check if it's installed on your system (mac): dot -v

If not, install it: brew install graphviz

Ubergraph

Ubergraph is used as an adapter to graphviz for vizualisation and is not used by the main namespaces so it will not be included in a cljs build.

Ysera

Ysera is a convenience library from which testing and errror macros are used.

License

MIT License

Copyright (c) 2019 Morgan Bentell