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.
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
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.
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:
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.
This is where
cbstreams can really help you.
box install cbstreams
You can see that it’s very easy to chain method calls when building up your
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:
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.
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:
Making a fresh request to the API endpoint, now populated with
cbstreams data, we would see our transformed data:
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!