Using reactive streams on serverless with cyclejs, xstream and Azure Functions

[ Update 2016-10-23 the code is now in the cyclejs community repo]

During development of my latest SaaS product, Brian, I’ve settled on a couple of key architectural decisions. For the Frontend I’m using Reactive programming (RP) with streams and for the backend I’ve decided on the ‘Serverless’ (FaaS) approach. Specifically, I’m using  Andre Stalz’s xstream with the incredibly light ‘framework’ cyclejs (but cyclejs supports other streaming libs, including the excellent RxJS) . Microsoft’s Azure Functions give a FaaS solution backed-up by many other options including BaaS & PaaS should they be required. This post looks at using them together on the backend.

Cyclejs

Cyclejs

Functions logo

Functions logo

 

 

 

 

 

 

 

 

I’m happy that the trade-offs and benefits with these approaches should meet my requirements. Namely, achieving  rapid development by focussing energy higher ‘up the stack’. I want to concentrate on innovation of user features and ‘business logic’ and not boilerplate or DevOps. From my early explorations I think Serverless and cyclejs manage to hit sweet spots of benefit and learning curve. However, they are most definitely not silver bullets, having wrinkles all their own that take time and effort to learn and overcome.

When I initially created Functions code using a traditional imperative style I rapidly found I missed the RP style I’d become familiar with when using CycleJS with RxJS. It’s a style that gets under your skin once you make the mental shift. Perhaps my background in real-time async communications predisposes me to seeing the benefits of asynchronous handling of streams. But whatever, I thought it would be fun to try it in the serverless context. At least both front and back ends would then be similar in architecture.

You may wonder why on earth I would consider using RP in a FaaS context. After all, the  FaaS architecture is all about small functions which are run once triggered and then quickly end. Thus, it would seem there isn’t much scope for streams when there is single trigger input event plus perhaps another data source or two.  One often touted advantage of RP is that it’s pure functions are easier to test, but that’s also a recommend practice with FaaS so that’s not an obvious advantage for having RP as well as FaaS.

One reason for wanting to use RP is that any non trivial functions are likely to have other asynchronous event sources, including SaaS requests and database updates over REST. Even so, there are other less tangible benefits of RP with cyclejs that I personally I found over imperative code style:

  • Loose coupling through reactive observers
  • Declarative style married to functional programming techniques
  • Separation of input, output side effects from the “pure” business logic

Together these engender a clean high-level way to describe program logic. Bugs also appear to be reduced and it also enables testing without excessive mocking due to the absence of side effects in the main code. Sometimes however, debugging can be more involved due to current tooling supporting imperative and not reactive. However, tooling is starting to appear as more turn to RP.

So what are the differences found when running xstream and cyclejs on Azure Functions environment compared to the usual browser (and sometimes nodejs) contexts?Surprisingly few it turns out. Fortunately, Functions builds on Azure Web Apps which supports nodejs and express. Better, it’s node 6.x that is provided which includes all those key ES6 features that really help clean up RP code. Another plus is that the cycle HTTP driver works fine on node.

In this implementation I’ve taken the approach of providing a cycle runtime in each Azure Function. Effectively, each Function is a component in the cycle sense of the word, though connections between components will have to be via HTTP, queues or other out of process couplings. This approach seems to be the a good choice as you can use Cyclejs or not for any individual function, depending on the complexity and preferences. As the Functions run time is open source there is scope to explore different and more deeply embedded approaches.

So without further a-do here’s the Functions driver code.

As with all drivers, the Function driver is there to handle useful side effects such as input and output. In this case it converts the Function inputs (“context” and an array of input bindings) into a source stream. It also sinks a stream containing the function’s output. This also acts as a signal that the function should complete (the driver calls context.done).  The sink also disposes of the streams created by run() for cleanup (this adds a little implementation complexity due to a forward declaration and JS’s lack of true pass-by-reference).

It turns out that using console.log is not useful in Functions, rather the alternative context.log is used. Thus, we also provide a Log driver that uses this channel. This is also used with xstream’s debug operator, which fortunately accepts a function argument as well as a value. I also decided the FunctionsDriver factory would return the log function itself as well as the driver. In this way nearly all the FaaS platform dependencies are encapsulated in the driver. This makes it possible to write a version for AWS Lamda or other serverless frameworks.

Here’s an example usage for a HTTP Function. It starts a 1 second ticker and on the 3rd tick makes a REST API request. It then returns the first item from the response in function output. The code demonstrates the use of all the driver features and the clarity of RP with cyclejs.

One issue that needs to be ironed out is sometimes exceptions such as syntax errors get lost and not presented in the Functions Logs. That’s probably xstream not re throwing captured exceptions. For now the fix is to put try…catch(log) blocks around parts of the code to get visibility.

What do you think. Does this approach work for you?

This entry was posted in development, web and tagged , , . Bookmark the permalink.
Skip to top

Comments

Leave a Reply