So far we’ve covered using Node-RED triggering REST services which could be SmartNSF routes, XAgents or custom Java REST services in OSGi plugins – and indeed any REST endpoints, on Domino or beyond. Apart from re-using XPages code, there’s no real advantage so far over LotusScript or Java agents. But if agents are dependent upon one another, typically with Agent Manager that means scheduling the agents with enough gap so that the first one will have finished before the second starts. It’s possible to create a separate agent that kicks off each in turn, but that’s not usually the approach taken, not least because the response from agent.run()
doesn’t actually say whether the first agent hit an error or not. Those who have supported larger environments may have used a more enterprise-ready tool like Lotus Enterprise Integrator or Notrix. But that tends to be for bigger environments, not least because of additional license costs.
The good news is that with Node-RED it’s possible to build such chaining, as well as route depending on success / failure, plus make it easier to migrate flows between different environments, plus build it with a GUI display, plus use an additional Node-RED module to display all the scheduled jobs in a table…all for free.
First, I’ll point you in the direction of that Node-RED module for displaying the schedule, on GitHub called node-red-schedule. I’ve not used it myself, but it looks a nice little add-on.
First, let’s step through what’s set up in Node-RED. The key area is below:
First we have an “inject” node, as before, which holds our schedule. This is what kicks off the task and calls the REST service in the “http request” node. This time the HTTP request is pointing to a REST service in an OSGi plugin, the URL relative to the server is just “scheduled-xots-demo/doChain”. This could be an XAgent, I’m just showing the alternative approach here. The REST service itself handles throwing a 405 (Method not found) error if the method selected in Node-RED is e.g. “GET” instead of “DELETE”. It’s no longer requiring authentication, that’s an advantage of being outside the core NSF. But that doesn’t mean just anyone can access it.
The point of particular interest here is the node in the middle, the function node called “setHeaders”. This adds some HTTP headers to what is passed to the DELETE REST service, as below:
Here we’re adding an appId and appSecret to pass across. The REST service code in Domino will check that appId and appSecret against what’s in the code. This means that the REST service can’t just be called by anyone who has access to the server. We’re ensuring they know an appId and appSecret that’s only available in our code. If you think about your LotusScript scheduled agents, for example, if you’ve not hidden them from the web they can be called from a REST client like Postman or a browser by any user who has access to the database. Here we’re adding extra security, particularly required because we’re intending the scheduled processes to be triggered from the web. As mentioned in a previous part of the series, it’s possible to retrieve headers in an XAgent or SmartNSF route from the request object, and it’s also possible to check the method used by whatever’s calling Domino, so none of this is specifically restricted to an OSGi plugin.
The final header we’re adding is a redirectUrl. This is what we’ll use to call back to Node-RED after the Xots task has completed. Node-RED runs by default on port 1880, hence the server URL. The “/api/xpagescallback” route corresponds to the GET “http” node on the second line. When that is called it triggers the second Domino REST service, this time an XAgent, “genericXAgent.xsp?process=loadData&userCount=800” – the generic XAgent that manages different processes and a flexible user count, which we covered in the last part. Thr redirectUrl is passed from the ChainReinitialiseResource REST service to the DeletionTask Xots task and the specific part of the code that uses that is here:
URL url = new URL(redirectUrl); HttpURLConnection conn = (HttpURLConnection) url.openConnection(); conn.setDoOutput(true); conn.setRequestMethod("GET"); conn.setRequestProperty("Content-Type", MediaType.APPLICATION_JSON); conn.setRequestProperty("appId", appId); conn.setRequestProperty("appSecret", appSecret); conn.connect(); if (conn.getResponseCode() == 201 || conn.getResponseCode() == 200) { System.out.println("Chain request sent successfully"); } else { System.out.println("ERROR SENDING REQUEST: " + conn.getResponseCode()); }
This creates an HTTP connection to the relevant URL and calls it. There are a couple of options if we wanted to handle different routing depending on success / failure. One option would be to send to a different URL on success. The other would be to add different headers or other params, and do the routing within Node-RED. Personally, I’d probably favour the latter, keeping all the routing in the GUI. It seems like that would be more visibly obvious and the place one would intuitively look for workflow routing.
As I said, the same kind of code could be used in an XAgent. An example is the XPage SchedReallyEasyJavaChain. The code from this XPage through the Java classes is virtually the same as the SchedReallyEasyJavaArchive XPage covered at the start of Part Five. The XPage calls Utils.processBackgroundChainingCallback()
, which calls GenericHttpRequestUtils.initialiseAndProcessBackgroundTaskAndChain()
. Instead of creating a BasicXotsCallbackRunnable
it creates a BasicXotsChainingCallbackRunnable
. The only difference is that this class uses the URL and HttpURLConnection you see in the code above to make a GET request.
@Override public void run() { try { URL url = new URL(params.get("redirectUrl")); HttpURLConnection conn = (HttpURLConnection) url.openConnection(); conn.setDoOutput(true); conn.setRequestMethod("GET"); conn.setRequestProperty("Content-Type", HttpServiceConstants.CONTENTTYPE_APPLICATION_JSON); callback.process(params, conn); conn.connect(); if (conn.getResponseCode() == 201 || conn.getResponseCode() == 200) { System.out.println("Chain request sent successfully"); } else { System.out.println("ERROR SENDING REQUEST: " + conn.getResponseCode()); } } catch (Throwable t) { XotsUtil.handleException(t, getContext()); } }
The difference is the callback.process()
line, which is where the custom code is triggered and allows the connection details to be overridden, if required.
When it comes to migrating the flows to a different environment, or sending to a customer, you can easily export the flow, do a “find-and-replace” to change URLs, then import and set up the logon credentials as required. This makes it much easier for migration.
This completes the blog series and hopefully gives an idea of some of the power of Node-RED for scheduling tasks. But I want to give you an idea of where it can further go. Many years ago I tried using Lotus Workflow as a visual workflow tool. It was good, except when it came to migrating workflows. Node-RED on the other hand allows a visual workflow modeler, lots of flexibility, and the ability to export the flow with a “find-and-replace” and deploy to another environment. And that’s without additional nodes for Watson integration or for creating dashboard charts, as well as many I’ve not yet come across. With Domino 10, I firmly believe Node-RED is a tool that should be in every Domino developer’s toolbox.
Paul, actually we do chain LotusScript agents together. We created microservice LotusScript agents with a common communication interface between agents. We have not chained more than a few but it does work. We ran into an issue where we needed to plug different functionality that created exports to Excel and CSV file depending on the requested services and this approach was the best. It allows us to chain the different microservices together depending on the need.
Yes, I’ve got one application with a monthly process that, for ease and to ensure no timeouts was broken into five agents, all chained from a single agent that manages the process. But particularly because
agent.run()
always returns true if it can start running the agent, it always felt less flexible than something like LEI. But for a single process requirement, LEI is overkill. Node-RED starts to add lots of additional benefits (though I’m sure you’re much more familiar with Node-RED than I am!)Paul,
agent.run() is always true but we using parameterDocID to check for success or failure and any error. Until the parameterDocID handle is reconnected the agent is still running. I want to try Node-RED with our microservice agents but have not had time. It should work. Since we are moving some of the services to Node it would be interesting to use Node-RED to connect all services regardless of their type.