Matt Gifford aka coldfumonkeh | Consultant Developer
View Github Profile


Adventures with CBStreams - API Transformations

Oct 7, 2019

I’ve been using cbstreams a lot recently over the last few years.

It’s a CFML wrapper that interacts with the Java Streams, allowing you to use those streams directly within your CFML code without having to deal with the Java code underneath. In true Ortus Solutions fashion, it’s very well documented.

Most of the examples that you see for Java Streams are for array manipulation and collection, and not really something that can be easily compared to current problems that you may face in your working day that it can solve.

So, this is the first of a few cbstreams posts I wanted to make to help clarify what it can do to help give you some clearer examples of how you could use it within your code.

Use streams as API data transformers

I LOVE APIs. I mean, I LOOOOOOOVE APIs.

I have spent a lot of time writing APIs for various projects, using various tools.

I often change between API tooling when developing. It depends a lot upon the code base (is it in a framework and do modules exist for that framework), the requirements (is a large feature-rich API tool worthwhile for this small app) and cost (how long will it take me to get it up and running).

I have two default go-to API tools for CFML applications:

  • Taffy - it works out of the box. Tweet-sized code
  • ColdBox - creating a custom API module for each project

In this example we’ll use Taffy as the API resource provider as it’s lightweight and ideal to use for demonstration purposes.

Let’s build up the initial endpoint to fetch some data for musicians.

Using the Taffy documentation as a guide, the following file is placed within the /resources directory, and will effectively become our GET request handler for all artists within our database when users call http://*.*.*.*/artists

Setting up the /artists resource to fetch information on all musicians within our datastore, we can pass in optional parameters as part of the GET request to limit the number of records and provide an offset for pagination purposes:

http://*.*.*.*/artists?limit=2&offset=1 as an example

As of yet we have no data, and so we pass an empty array value to the internal Taffy rep function, as well as an accompanying structure of parameters used to make the request - helpful for the API caller to confirm the values they sent were used to make up the request.

The empty API response

Let’s add a little bit of code to fetch our data. This could be from an in-line query call, or from an instantiated service component, as used here in the example.

By default, our returned query recordset looks like this:

The base query resultset

So, we have the query response, but we still need to transform the data and output it as an array.

There are CFML functions out there to help you convert a query into an array, but we want to try and create a data transformer of sorts so that we can define an explicit structure with associated data to return within the API response.

When it comes to data transformation, I’ll typically use the incredible cffractal library. I’ve given presentations on it before and find it an invaluable tool to use. I’ve used it inside ColdBox applications, inside other framework-based CFML applications, and alongside Taffy to transform the data before returning the API response.

It can, however, come with a bit of a learning curve (although not much, that’s doing it a huge disservice).

As I’ve been using cbstreams a lot recently over the last few years, I wanted to show an optional way to transform your query data into a readable structural format that you can use when returning JSON data.

Enter CBStreams!

This is where cbstreams can really help you.

Firstly, you would need to install cbstreams into your application. It is available from the Github repository or you can install it from Forgebox using CommandBox:

box install cbstreams

You can see that it’s very easy to chain method calls when building up your streambuilder object.

The parallel() method call is optional. By adding that in, the stream wil leverage parallel programming to help run the process asynchronously. This only really shows a benefit when dealing with a high number of records; working on smaller data sets I haven’t really seen much change, but I wanted you to know that it’s there and should be used if you want to perform asynch processes on your data.

As part of the stream map() function, once we have the row from the query, the value sent to the transformArtistData() method is a structural representation of that row:

Each query row is passed to the function as a struct

We simply pass our struct of data (for each row of the query) into the transform method. It can manipulate it however you see fit and return whatever you want it to return.

The following example is relatively basic in terms of the data coming back (no super-heavy processing or nested resources), but was created as a way to show you how simply you can control data responses for your API, transforming the records into something readable and useable for the end users.

Our transformArtistData method could be in a service component, a dedicated model or inline within the Taffy resource itself.

If we run a writeDump( arrData ); before we return the API response, we can see the array generated by the stream processessing:

cbstreams for CFML

Making a fresh request to the API endpoint, now populated with cbstreams data, we would see our transformed data:

The transformed API response

The completed get() method in the artists.cfc resource would look something like this:

So, there you go. I’ve used Java Streams thanks to the cbstreams module for a number of things. This is the first of a few I wanted to bring to your attention.

You never know when you may need to unleash the power of Java Streams inside your CFML application!


Latest Blog Posts

Jul 16, 2020
Github Actions with CommandBox and TestBox
Read More
Jul 9, 2020
Azure pipelines with CommandBox and TestBox
Read More
Dec 23, 2019
CFML content moderation detection component library
Read More