In 2017, we introduced a major change in Mule 4, the introduction of DataWeave as our primary expression language. While this was a major milestone at the time, people have had a few years to adapt. That said, some of you may still have questions about why DataWeave is the main expression language in Mule 4 Beta. Don’t worry – we’re here to cover the essential information you need to know.
Why we chose DataWeave as our main expression language
To help you better understand why we chose DataWeave, let’s start with an example. A few weeks ago, I decided to create a Slack app to check the status of our tests on Jenkins. This meant I had to integrate Slack and Jenkins APIs to create a personalized experience for our team – exactly what MuleSoft is all about. After reading some docs on each API, I decided to start building the app.
First, I used an HTTP listener to receive Slack commands and a choice router that decides which action should be taken based on the input; in this case, I either retrieve the test status or explain how to use the command. Then, I created my main logic to retrieve the test data from Jenkins, returning XML data that I transformed to JSON, as required by Slack’s API.
Something became clear very quickly: all my routing logic required MEL and all my transformation logic required DataWeave. But, why? Why was I forced to use and learn two languages when DataWeave is powerful enough to handle all of it?
Well, the answer to that is just timing. When MEL was introduced, Mule was very Java-oriented. Up to that point, there were a variety of evaluators to handle different inputs, like Groovy and JSON. As a result, MEL was created to build a consistent experience when dealing with these expressions.
On the other hand, transformations were considered separate; for the most part, only transformers and DataMapper were used at the time. Yet, our customers still needed to handle more complex transformations with high performance, which is why we introduced DataWeave in 2015.
With a highly performing transformation engine and rich querying capabilities, DataWeave became a hit. So, there we were, with two languages inside our platform, and one so powerful that the other featured a function to call it: dw().
So we asked ourselves, “wouldn’t it make more sense to just have one experience for building expressions, querying, and transforming data?” Using DataWeave would mean leveraging all of its power.
- In Mule 3, you have to transform everything to Java objects to evaluate any expressions (e.g. when routing payloads or logging data). You also have to learn the specifics of each transformer. With DataWeave as the expression language, you could simply query the data directly and forget about those transformations.
- With DataWeave, our expressions could be focused on the structure of our data, rather than its format. This is because a Java array is the same as a JSON one in DataWeave – we don’t need different expressions to handle them.
- Access to binary data could be done anywhere you need it, and thanks to some exceptional streaming improvements, you can get larger than memory, random, and repeatable access.
That’s why Mule 4 unifies expressions and transformations with a single, modern, consolidated language that has performance at its core, making all of the above a reality.
How DataWeave Revolutionizes Mule 4
DataWeave now provides a service that is used by the Mule Runtime engine to evaluate expressions. The runtime, in turn, gives DataWeave all data regarding the current execution, including payload, variables, expected output, and metadata. This enables DataWeave to know, for example, whether a String or a Map is required, how to handle each variable, whether to coerce a type, and so on. Then, one can write expressions as in the example below:
#[payload ++ variables.myStatus]
#[attributes.statusCode]
In this example, the payload, variables, and attributes keywords will be interpreted as such.
You might wonder how this one-liner DataWeave expression actually works, especially since DataWeave requires users to declare the output format. The output type is inferred, when possible, but you can also add it to that one-liner.
In the example below, we use a JSON payload to set an HTTP request’s headers, taking that existing map of headers and adding one to it with the expression:
#[output application/java --- payload ++ { host : 'httpbin.org' }]
The backend will answer with the received headers, which contain the values sent to our HTTP listener as body and the host one we added.
Full integration
So far we have only talked about DataWeave expressions as one liners for routers and simple attributes. Another simplification we created is for flows. By allowing users to define content “inline,” we decreased the number of transform elements needed. For example, you can build the content of a file inside of the File connector’s ‘write’ component; there’s no need to use a ‘transform’ component in order to get the payload you need beforehand.
In the example above, you do not need additional steps to iterate the received JSON payload, and the new file path is determined with the expression:
#[payload.name ++ '.' ++ dataType.mimeType.subType]
Also, we add a desired “date” attribute within the write operation, exactly where it’s needed, setting the content to:
#[payload ++ { date : now() }]
That last expression is a great example of the output type being inferred. Since we know the payload is JSON, there’s no need to specify the output and the generated files will be in that format as well. This works for all new connectors since it’s supported by our Mule SDK. In the example below, the HTTP body of a request is set with a script, where you could take advantage of all of DataWeave’s features – as in any transform component script:
#[
%dw 2.0
output application/json
---
payload ++ {location : 'LATAM', office : 'BA'}
]
Additionally, we can configure the listener response body with the expression:
#[payload.data]
This is because the backend server will return a JSON, where that attribute represents the payload sent to it. So the data received by the listener will be modified to include some more attributes, and later forwarded back.
Notice in the above example that the data received in the HTTP listener is actually application/x-www-form-urlencoded. DataWeave is now handling that out-of-the-box – no parsing needed. Even if I had sent a JSON payload, the result would have been the same. This is because DataWeave allows us to focus on the data’s structure, rather than its format.
Compatibility
Enough about DataWeave, let‘s address the elephant in the post: Mule 3 compatibility and MEL. MEL is still around, it’s just deprecated. Although DataWeave is the primary and default expression language, every expression can feature the “mel:” prefix to indicate that it should be evaluated with MEL.
In the example below, the HTTP listener will answer whether a variable starts with “SUCCESS” or not, using MEL to configure the response body with the following expression:
#[mel:flowVars.receivedMessage.startsWith('SUCCESS').toString()]
This should help Mule 3 users adapt more easily to DataWeave.
What else to expect from DataWeave
The DataWeave team made extensive efforts on the language version 2.0. They supported the runtime integration, and also worked to improve the language itself. Some other exciting information about DataWeave includes:
- Imports and modules: You can package and import scripts into others, enabling you to reuse and share your code.
- Java interoperability: Static methods can be executed through DataWeave.
- New data formats: We added text/plain, application/x-www-form-urlencoded, and multipart support.
- Type system: You can indicate the type for functions and variables with type inference helping you out.
- Simplified syntax: Everything is a function, and if/else clauses are now the norm.