Monday, February 13, 2023

Just the Good Bits


Sometimes you find a use case that has a simple and elegant solution in a particular programming language. It might come from a question in conversation, or a requirement in a project on the table. The nature of elegance in code defies firm definition, but as a famous Jurist once said, "I can't define it, but I do know what I like."

One characteristic I usually find in what most observers might call "elegant code" is brevity. Fewer lines of code mean fewer places for a software fault to lurk. DataWeave certainly lends itself to brevity, even nearly to the point of compulsion.

Recently, a student of mine raised a question after doing one of the standard exercises in the MuleSoft Developer series using DataWeave. His question surfaced a typical use case for an API. 

We had just finished a Walkthrough that has us observe how to filter the elements of an Object given a list of desired fields.

His idea was to allow a query parameter that listed the fields being requested. If an API interface enumerates the fields that may be requested, the input can be easily validated.

Once we walked through the idea, we ended with a single line of code that completely handled the task. Follow our logic, and see if you find the outcome to be elegant, or simply brief.

The invitation offered an opportunity for everyone in the class to fine tune their understanding of DataWeave, so we began with this understanding from the RAML:

#%RAML 1.0
title: Flight Info Selector
version: 1.0.0

/records:
description: Flight record(s) masked to selected fields only
get:
queryParameters:
element:
description: |
Parameter may appear one or more times with each request.
Any of the fields named here may be requested
enum:
- price
- flightCode
- availableSeats
- planeType
- departureDate
- origination
- airlineName
- destination
responses:
...

This implies that we might submit our request as:

http://host/records?element=price&element=availableSeats&element=flightCode

When such a request is received by an API with its interface generated by APIKit, the router will validate any inputs named "element" for membership in the enumeration. Or to say it more simply, the input validation can be built into the interface from just this much of the API specification.

Now let's do a better job of articulating the use case:

%dw 2.0

output application/json


/*

 * Use case: My API consumer will be presenting me with a

 * collection (ie. array) of queryParameters that name

 * the subset of fields needed to satisfy the request.

 * 

 * We must dynamically calculate the fieldMask and apply it to the

 * Object we return.

 */


So let's start with a view of the model object. This is the structure we need to mask before presenting it in our response.

//model object

var ogObject = {

    "price"750.0,

    "flightCode""A134DS",

    "availableSeats"40,

    "planeType""BOEING 777",

    "departureDate""Apr 11, 2018",

    "origination""MUA",

    "airlineName""Delta",

    "destination""LAX"

  }


An array with the full set of field names (or Object keys) can be had easily enough.

// fieldSet - ie. pluck $$

var allFields = [

    "price",

    "flightCode",

    "availableSeats",

    "planeType",

    "departureDate",

    "origination",

    "airlineName",

    "destination"

  ]


We're going to have to generate that dynamically when it comes time to filter our object. Fortunately the expression we need is concise and can be easily embedded in a larger expression.

To set up an experiment that will allow us to create our approach, we could use simulated input.


//simulated input

var message_attributes_queryParams_FIELDS = [

    "price",

//    "departureDate",

    "origination",

    "destination"

  ]


This simulates the effect of using:

    var message_attributes_queryParams_FIELDS = attributes.queryParams.*element


With that as our starting point, what is a good solution that gets us the fields we want?

Let's look at the starting Object again. 

//model object

var ogObject = {

    "price"750.0,

    "flightCode""A134DS",

    "availableSeats"40,

    "planeType""BOEING 777",

    "departureDate""Apr 11, 2018",

    "origination""MUA",

    "airlineName""Delta",

    "destination""LAX"

  }


In order to remove fields from this structure, we might say something like:

ogObject -- ["price","planeType"]

In an expression such as this, some people like to call this the use of the "decatenate" function. Surprisingly, -- is a function rather than an operator. And one of its uses is to filter an object, given an array of field names.

What we need for our work then, is an array of the fields we would like to remove from the object. That array would be the complete list of all the Object fields, reduced by the array of desired fields presented as our input. We might call the provided field names the "anti-mask." It is the set that must be removed from the final field mask before we filter the Object.

We saw earlier (in the definition of the allFields array) that we can get the full list of field names with a simple call to pluck $$

That resulting array can be reduced with the -- function just as we did with the Object. Using our definitions from before, we use the anti-mask like this:

(ogObject pluck $$) -- message_attributes_queryParams_FIELDS

That then, is the expression that dynamically gives us our field mask, and we get our final result like this:


ogObject -- ((ogObject pluck $$) -- message_attributes_queryParams_FIELDS)


We are left with some questions however. Should we refactor this further by writing a function to do the transformation? Are there operations here that we might generalize and keep in a DataWeave Module (ie. library)?

I will refrain from writing what I consider to be the canonical function to handle this, but perhaps you would like to take a shot at it.

Send me a function that can do this job, being handed the Object collection, and the Array of desired fields. I'll acknowledge my favorite in a future article.


To learn more about DataWeave, check out the DataWeave Tutorial on the DataWeave Playground (Find the button at the upper right-hand side of the screen). The MuleSoft Blog also provides a number of HowTo articles that may be helpful to you. The best way of course, is to visit the MuleSoft Training website to discover all your options.

Vincent Lowe is a Principle Technical Instructor for Salesforce Trailhead Academy. He has trained developers in C, Java, Perl. Python, Javascript, and DataWeave. The views expressed here are his, and not necessarily those of the employer.















No comments:

Post a Comment

Reduce to Dashboard

When developers use DataWeave, they often come to rely on the reduce() function to fill in any gaps left by the standard Core library. Altho...