Table of contents:
Overview
What you need:
What you will build
We will be utilizing a MySQL database in order to build a Product System API, which we will publish to Exchange in order to utilize scaffolding; given that the focus of this article is query parameters, we will only be implementing the GET
operation on the /products
endpoint.
API Specification
- Log into https://anypoint.mulesoft.com.
- Navigate to design center
- Click
+ Create New
and select New API Spec
- Give the API a title;
Product System API
- In this case, our focus isn't on designing the API itself, so choose to use
RAML 1.0
rather than the visual editor. I will be providing you the starting RAML.
- Complete API specification. To start, copy the code below into your specification.
This gives us our basic API which returns a list of products, but it would be nice to be able to filter things! Our products have a field called weightUnitMeasureCode
, and it would be very useful to be able to filter by this code. A simple implementation might look like:
The disadvantage here though is we can only filter by a single weightUnitMeasureCode
! It would be nice to be able to specify multiple, ie: ?weightUnitMeasureCode=lb&weightUnitMeasureCode=oz
. Thankfully, query parameters can be specified as repeatable. Lets go ahead and add this to our spec now:
Another prime candidate for a query parameter is the field manufactured
. By default, we won't be filtering any products, so lets go ahead and add it as an optional field:
- Publish to exchange - now that we've finished the spec we're going to use, click
Publish
in the top right and then Publish to Exchange
. Set the asset version to 1.0.0
and API version to v1
. - Your published API specification should look like this.
Implementation
Now that we've built our API specification, we can open studio and scaffold our API and provide implementation logic.
Create Project
- Open Anypoint Studio
- Create a new project called 'Product System API'
- Click the
+
button to add an API from exchange to the project. - Sign into your platform account and search for 'Product System API'
- Create project and scaffold
Setup MySQL Connection
- In
src/main/resources
, create a new file called config.yaml - Copy the following code into config.yaml:
- In the mule palette, click
Add Modules
, and drag the Database
connector into the blue box. - Click on the
Global Elements
tab - Click
Create
and search for config
. Select Configuration properties
- Type
config.yaml
in for the file. - Click
Create
and search for database
. Select Database config
- In the connection dropdown, select
MySQL Connection
- Under required libraries, click
Configure
and then click Add recommended libraries
- Fill in the following fields:
- Host:
${mysql.host}
- Port:
${mysql.port}
- User:
${mysql.user}
- Password:
${mysql.password}
(tip: Click on the show password checkbox) - Database:
${mysql.database}
- Click on
Test connection
to verify connectivity.
Create Basic Select and Map Data
Now that we have our connectivity setup, we can create the basic implementation of our endpoint: select everything and map to our canonical data model.
- Find the flow
get:\producst:product-system-api-config
and delete the components - In the mule palette, select
Database
and drag a Select
operation into the flow - Select
Database_Config
in the Connector configuration
dropdown - Paste the following SQL into
SQL Query Text
(don't worry, we will walk through converting this to be dynamic later)
- In the mule palette, find a
Transform Message
component and add it after our Select
component. We need to map our data to our canonical model defined in our spec. - The
Transform Message
component should have automatically discovered the metadata; you can now drag and drop the fields to map. Go ahead and map a few fields to give it a try, and then replace the automatically generated data-weave with this code below:
- Start the app, open the console, and test your api! You should be getting back all Products in the database without any filtering.
Building Dynamic Parameterized SQL
Implementing the optional manufactured
query parameter will be our first path, as this will be the easiest.
In SQL, as in most languages, we can 'short circuit' our where clause and do something like this:
And then in the Input Parameters
window, you can click the 𝑓x
button and paste the following:
By putting this data-weave in the Input Parameters
fields, we are passing in parameterized values to our SQL. You should always take this approach when dealing with user input to protect against SQL injection. In our SQL, we are checking if the parameter is null first - if it is we are basically saying if (true === true)
, meaning don't filter anything. We don't need to check if attributes.queryParams.manufactured
is actually a boolean; the scaffolding has already validated it. MySQL doesn't support booleans and instead stores such information as a 1 or 0, so we have to convert the boolean to a 1 or 0 when parameterizing our value. Our logic is if the key exists, pass 1 for true 0 for false, otherwise pass null. Go ahead and run the API now and see how it does!
One query parameter down - but what about our repeatable parameter? In SQL we know we would do something like p.weight_unit_measure_code IN ('value1', 'value2')
. What we can't do is p.weight_unit_measure_code IN (:values)
; unfortunately, the JDBC driver isn't going to understand an array. You might be tempted to write some data-weave which manually creates the list of values in the SQL (ie: IN ('lb', 'oz')
), but the danger here is that we open ourselves to SQL injection. What do we need to do? Dynamically generate the parameter key names and the SQL.
Update your SQL block to contain the following; you must include the #[
at the beginning and the ]
at the end as these convert the field value from static text to data-weave; I suggest using the copy code button.
This looks confusing, and we'll break it down later, but as an example if I made a GET request like ?weightUnitMeasureCode=lb&weightUnitMeasureCode=oz
, the SQL we pass to the connector will be:
Now we get a query string that is still parametrized, where each value gets its own indexed parameter key. In our example, we need our input parameters to look like this:
To produce a map like this, our Input Parameters data-weave is going to get a bit more complicated.
Paste the following data-weave into your Input Parameters
field
Start up the API and give it a test via the API Kit Console located at
http://localhost:8081/console/. You should now be able to filter by multiple
weightUnitMeasureCode
query parameters.
Our buildParameterMap
function takes in the attributes.queryParams.*weightUnitMeasureCode
and builds a new map structure, or returns an empty object if the query parameter wasn't provided. If you're unfamiliar with the .*
selector, it exists to select values from a repeated key into a single array, typically used with XML; in this case, we're using it to select the multiple query parameters. In the query itself, which we've converted to data-weave with our #[ ]
, we are only including the WHERE
clause itself if the query parameter was provided.
Bonus Round - Making it Reusable
This was a lot of work for something that, frankly, isn't all that complicated. So to make things easier for ourselves, we can start building a reusable data-weave library.
As a starting point, I've built a few common functions and bundled them into the module dw::sql
This now lets us convert our SQL Query Text to this far more succinct script:
And our input parameters to:
Wrap Up
By taking these functions and building a reusable library that handles the logic for us, we make our lives much easier the next time around!
If you have any questions or issues, feel free to click the button at the
top of the page and submit a
post question
!