Last Updated on 22/02/2019 by Patryk Bandurski
I like the idea of clean easily read XML configuration in my Mule projects. Therefore I externalize DataWeave transformations, SQL quires, and other content. In this article, I will compare assets that Mule gives us to share such things in Mule 3 and Mule 4.
Mule 3
It is up to the component if we can load content from the resource or not. The most obvious example is DataWeave Transform Message component that allows that. We have such possibility for Database component using Template query.
As a result we got clear XML configuration as depicted below
<sub-flow name="get-partner-db-subflow">
<db:select config-ref="DB" target="#[flowVars.partner]" doc:name="get Partner">
<db:template-query-ref name="getPartner.sql"/>
<db:in-param name="id" value="#[flowVars.id]"/>
</db:select>
<dw:transform-message>
<dw:set-payload resource="classpath:templates/dwl/transformToPartner.dwl"/>
</dw:transform-message>
</sub-flow>
It is much more readable, just by looking at XML we may infer what the code does on the high level. If we want to get know better transformation we have a path to the file, that we can examine.
Mule 4
First I was not aware of new functionality delivered with this Mule version. I had a case to rewrite the application to the new runtime. The application makes a couple of database calls.
When I drop Select operation on canvas I could not find any way to externalize my SQL statement. In Anypoint Studio I had a field called SQL Query Text only. Below you can find the XML snippet.
<db:select doc:name="Select records"config-ref="Database_Config">
<db:sql>
SELECT * FROM
users
WHERE age > 19
</db:sql>
</db:select>
File placeholder syntax
Then I found out that Mule has introduced new syntax to load content from the files
${file::path-to-the-file/filename}
We have three parts. The first one, file::, implies that the engine should load the file specified after the double colon. Next, you write the file name and path to it. The address is relative to the resources folder.
As you may remember we use similar syntax to load properties like ${http.port}. Mule will try to resolve property http.port before project startup. If the property is missing the project won’t start. If the property is present, placeholder ${} will be replaced with the loaded value for example 8091.
File placeholder syntax is pretty the same. Mule will look for the file just before startup and won’t start if the file is missing. After the content will be loaded it will be put in place of the placeholder ${}.
Information
This functionality is not only dedicated to load DataWeave files. We may load any file that we want. However, when we would like to load DataWeave we need to remember to embrace our call within expression brackets #[]. Otherwise, we may get a text of our transformation :).
Examples
Static SQL statement
File: src/main/resources/sql/select-records.sql
<db:select doc:name="Select records" config-ref="Database_Config">
<db:sql>${file::sql/select-records.sql}</db:sql>
</db:select>
Brief description: SQL statement from the file select-records.sql will be put between <db:sql> tags.
Choice logic
File: src/main/resources/dwl/rule.dwl
Content: sizeOf(payload) > 15
<choice doc:name="Choice">
<when expression="${file::dwl/rule.dwl}">
<logger level="INFO" doc:name="Logger" />
</when>
<otherwise >
<set-payload value="#[payload]" doc:name="Set Payload" doc:id="ddd0085c-1d3b-4428-bc6d-fa73598ee812" />
</otherwise>
</choice>
Brief description: content from the file rule.dwl will be put in expression attribute.
Advice
I guess that externalizing choice logic may be useful only if the expression is really complicated and long. For other cases do not use it in order to maintain greater readability.
Mock response
File: src/test/resources/test_data/system-response.dwl
Information
Why did I put a response in
Often I have a case that input should be a Java object, but I have its JSON representation. Therefore I use
<munit-tools:mock-when doc:name="System call" processor="http:request">
<munit-tools:then-return >
<munit-tools:payload value='#[${file::test_data/system-response.dwl}]'/>
</munit-tools:then-return>
</munit-tools:mock-when>
Brief description: As you may see I enclosed file placeholder with expression brackets #[]. I used this because the content of the dwl file is a DataWeave script and it needs to be evaluated at runtime. If I would omit the expression brackets my mock component would return string having transformation text.
Composition
File: src/test/resources/test_data/record-to-compare.json
<munit-tools:assert-that
doc:name="myRecord equals"
expression="#[vars.myRecord]"
is='#[MunitTools::equalTo(${file::test_data/record-to-compare.json})]'/>
Brief description: I could provide an inline object to compare in equalTo function. However, I decided to externalize all examples. We may use file placeholder even in the middle of DataWeave as in the example above.
Warning
In Anypoint Studio 7.3.2 using file placeholder you may have errors like that one:
Description Resource Path Location Type
Invalid input ‘{‘, expected ~ (line 1, column 2):
For the time being, just ignore it, you will be able to run and build your project without any problem. That is an issue with Studio.
Summary
I think that file placeholder is really great feature that gives more flexibility than we have before in Mule 3. Now we may use this wherever we like even in the middle of DataWeave transformation. It makes XML configuration files more concise and readable. Small drawback, that I see currently, is a problem with Anypoint Studio that misinforms you.