What is FEEL

As a part of the Decision Model and Notation (DMN) specification, the OMG also defined the Friendly Enough Expression Language (FEEL). 

Since DMN is intended to be used by designers and business analysts who would like to build decision tables, FEEL needed to be designed as an uncomplicated and human-readable expression language that would help readers understand the logic being described or executed by the DMN table. 

Expression languages are typically used by developers to evaluate data to produce a result based on query parameters or conditions. This means it’s syntactically closer to code than human-readable sentences. Since one of the goals of FEEL is to be “process analyst friendly,” it prioritizes readability more than other expression languages would. For example to find if a book has a rating above five you can simply type: 

=book.rating > 5. 

For Camunda, the use of FEEL goes beyond just DMN tables. FEEL’s flexibility and readability makes it a perfect candidate for use within BPMN models as well. With Camunda Platform 8, you will be using FEEL in many different places. 

In this blog post, I’m going to share some of the ways FEEL can help you add evaluation complexity without hiding it behind the usual complex technical expressions. My goal here is to show how FEEL can make the complicated look easy and readable. It’s also got some really powerful functionality I’m going to explore. 

Getting started with FEEL

You can try out all of the examples we’re going to cover in your browser using the Getting started FEEL Playground or our more generic FEEL Playground and you can always get more in-depth knowledge from the FEEL Docs.

You can divide FEEL into two types of scripts: Unary-Tests and Expressions. They’re quite different in how they are used and so I’m going to start this post with a breakdown of both types.

In both cases, it’s important to know how they work on a high level. You start with a context which is often some set of variables or complex objects, FEEL is then used to interrogate this context in order to produce a required result. For the purposes of this post, we’re going to use an example context which contains an author, book, and various pieces of information about it. The structure looks like this:

{
    "author" : "Mike Duncan",
    "book" :{
                    "title": "Storm Before the Storm",
                    "score": 4.8,
                    "genre": "History",
                    "published": "2017-10-24"
                }
}

We can then use FEEL in various ways to get information depending on what we need to know about this context.

Unary-tests with FEEL

In FEEL, unary-tests are used to create an expression that will always result in a boolean(i.e. either true or false). The first place you’ll find these would be as part of a DMN table. Specifically, to evaluate some data from an input field. For example, in the screenshots below we would send in a variable containing the star rating for a book. It’s using a “First” hit policy so it will go through each row in sequence until a match is found. On the third line, we check if it’s between 3 and 4 stars.

The basic FEEL expressions are detailed in the docs, but before diving into more complex examples, it’s important to explain the basics with a fairly straightforward example.

Let’s return to the context described earlier — An object describing an author and a book, including details of the book:

{
    "author" : "Mike Duncan",
    "book" :{
                    "title": "Storm Before the Storm",
                    "score": 4.8,
                    "genre": "History",
                    "published": "2017-10-24"
                }
}

If you’re anything like me, you will only read books where the author’s name has the sequence of letters “can” located somewhere in it. Obviously, I need to create a FEEL expression to check this. FEEL does have some built-in functionality for this. Specifically, the contains function. It works like this:

contains(author, "can")

It would also mean a slight change to our DMN table.

That’s a good start, but I’m interested in quality so I also want to ensure that I don’t bother with any low rated books. I can still do this in the same expression by using and as well as accessing the nested variable: 

 contains(author, "can") and book.score > 4.5

With this unique formula, you can be safe in the knowledge that your reading choices will be premium reading choices.

But there is a potential pitfall. For newly released books, there may not be a score given to a book yet and so it’s possible that the variable doesn’t exist. Luckily enough, we can dip into the built-in functions list and in this case there’s a particular one that is going to be very helpful:

is defined(score)

In Camunda’s FEEL implantation this function will return true if there is a defined variable in the scope called “score”. Otherwise, we get a false. This is very handy when dealing with potentially incomplete data. 

If you’d like to explore unary-tests further, I’d suggest you go check out the docs. In the meantime, let’s move on to more complex stuff. 

Expressions with FEEL

Unlike unary-tests, expressions can return far more complex values and can be used in a variety of places across DMN and BPMN. Starting with DMN, you’ll find expressions used in “Literal Expression” boxes in Decision Requirements Diagrams (DRDs).

Here, you can evaluate the results from other tables or you can manipulate the context variables for use in other parts of the DRD. Let’s take the same input as before:

{
    "author" : "Mike Duncan",
    "book" :{
                    "title": "Storm Before the Storm",
                    "score": 4.8,
                    "genre": "History",
                    "published": "2017-10-24"
                }
}

From this, we want to execute a DMN table as pictured below. Here, a book published this year needs to be over a score of 4.5 or I’m not going to bother with it. However, if it was published in different year, I’ll accept it if it’s more than 4.1. A score of 5 is always accepted. All other options default to a no. This would look like the following:

From the input you can see that I don’t actually have a variable that directly indicates if it was published the current year, and we couldn’t really expect a data object to keep itself up to date as time moves on. To solve this, we can use a literal expression. 

The literal expression contains the following line:

 today().year = date(book.published).year

This adds the result of the expression to a variable that can be used by the Should I Read This Book table. This separates the logic really nicely, because the alternative to using the literal expression here would be to embed this logic inside the table itself. However, this would make maintainability and readability more difficult, especially for a more complex table. 

Complex objects, BPMN, and FEEL

One of the bigger changes in migrating from Camunda Platform 7 to Camunda Platform 8 is to unify the expression language being used by the standards we implement. We’ve already talked about how and where FEEL can be implemented in DMN tables, but new to Camunda Platform 8 is that FEEL is used as a scripting language in BPMN models, too. FEEL is used as part of the execution semantics of the model and so there are a wide variety of places where you can use FEEL.

The most common place you’re going to be using FEEL is likely as conditions on gateways. Let’s discuss this with a specific example: 

We have a process where we could read a book or not based on if it’s suitable. So, how do we decide if it’s suitable? 

Let’s assume we have the following object as input:

{
    "author" : "Mike Duncan",
    "book" :[
                    "title": "Storm Before the Storm",
                    "score": 4.8,
                    "genre": "History",
                    "published": "2017-10-24"
                }
                { 
                    "title": "Hero of Two Worlds",
                    "score": 4.6,
                    "genre": "Biography",
                    "published": "2021-08-15" 
                 }
            ]
}

This is a slightly more complicated object. We now have a short list of books for a given author. Personally, I’m a big fan of historical books and so I’d be happy to look through a list of books by an author provided the genre of at least one of the books is “History.” 

In Camunda Platform 7, this logic is not trivial and in fact would probably be too complicated to assess as part of a gateway condition—Mainly because Camunda Platform 7 uses Java Unified Expression Language (JUEL) to build expressions and it’s really built for Java users specifically. It’s best practice for a gateway condition to be easily readable so that people can understand and maintain these conditions. FEEL is particularly good in this regard, so if I wanted to achieve the above condition it’s just one line: 

some book in books satisfies book.genre = "History"

This expression uses the “some” keyword to go through the list of books and check each genre for “History”. It’ll return true if any of the books have this genre. You could also use the “every” keyword instead of “some” if you’re only interested in a single topic. 

There are a lot of other places where FEEL can be used, and luckily these places are indicated with a fun little icon. For example, the assignee of a user task can be resolved from a FEEL expression: 

Anywhere on the properties panel where you find that icon you can use a FEEL expression. 

For example, let’s say we would like to read each book that a given author has written. For that, we’re going to need two things:

  1. Create a list of the book titles.
  2. A way to dynamically create a task for each item on the list.

Creating a dynamic list of tasks is pretty easy as BPMN has the multi-instance marker that can be applied to the task. 

 This just leaves us to create the list, which it turns out is incredibly simple. 

 books.title

This will go through the list of all books and gather all the titles returning a list like this:

[
   "Storm Before the Storm",
    "Hero of Two Worlds"
]

Finally, to get this working and executable, we just need to put the FEEL expression as part of the multi-instance’s input collection: 

Personally, I’m a little more particular about the books I read and generally I wouldn’t want to read everything written by an author, often it’s enough to read their best books. So, I want to make sure this preference is taken into consideration when creating the multi-instance.

For this, I’ll return to our FEEL expression and add some filtering to it. In FEEL, if you have a list of items you can add a filter to that list by putting [ ]  after the list. Inside those brackets you can add some kind of filter condition. In our case, I want to make sure the score of the book is above a 4.6 and so our expression would look like this:

books[score > 4.6].title

And the result would be:

[
   "Storm Before the Storm",
]

Final FEELings

The addition of FEEL as the main expression language across Camunda Platform 8 components helps users get a deeper understanding of what is really happening in the execution of your process models. 

BPMN and DMN were designed in part as a way of making complex execution more visual and easier to follow for both designers and developers. FEEL takes that further, expanding on the original idea so that designers should now be able to read and understand complex technical implementations that in the past would have required at least some understanding of JavaScript, JUEL, or Java. 

There’s no doubt this will get us closer to a point where collaboration and shared responsibility for models will lead to more efficient and faster automation development. 

If you want to really start to express your deepest FEElings, we’ve published a getting started with FEEL guide that you can find here.

Ready to get started?

Still have questions?