/ Development  

Managing an unavailable (external) webservice in a BPM

Hi there AppWorks fans,

Welcome to a new installment of AppWorks tips.

By now (if you followed the posts on this blog), you all know how to call webservices (internally/externally) from a BPM perspective. These posts show you the happy case scenarios where the webservice is always up and running, but there will be a time when that same webservice starts to fail for any reason!? But my dear “AppWorks guy”…That’s why we deploy these services on multi-tenant SaaS cloud platforms, so they are always available!? Right??…Sure, keep believing in such mentality until it’s your turn on disaster recovery! 😟


Let get right into it…

Time to bring an (external) service up and running! With “external”, I mean just outside our AppWorks VM and in my case locally on my Windows laptop. For exposing a service we have posts available on this blog website, but I saw a new way passing by during my daily job…Let’s introduce the standalone version to WireMock.

Download the JAR from the website, start a command prompt, and start it like this: java -jar wiremock-jre8-standalone-2.31.0.jar

bpm_service_001

Ohw yeah…have Java installed, but what doesn’t need Java these days!?

The startup will create two new folders next to the downloaded JAR file:

  1. mappings for serving JSON files which describe an end-point with response information (we’ll use it later on)
  2. __files for serving content through our HTTP request (not for this post, but great to know!)

Now open browser and go to URL http://localhost:8080/__admin/ and have a response like this:

bpm_service_002

Time to add our first ReST endpoint via a POST command:

1
curl -X POST --data "{ 'request': { 'url': '/get/this', 'method': 'GET' }, 'response': { 'status': 200, 'body': 'Here it is!' }}" http://localhost:8080/__admin/mappings/new

2 Notes:

  • I use CURL for Windows
  • Watch the double-quotes and single quotes (yes, I’m calling it from a Windows laptop with the good old command prompt)

A refresh of the admin URL gives some more feedback:

bpm_service_003

Now we can also do a GET request like this: curl http://localhost:8080/get/this or open the URL from the browser. NICEEEE stuff…WireMock! 😘

Time for a WireMock shutdown via curl -X POST http://localhost:8080/__admin/shutdown and create a new ‘hello.json’ file with content like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
{
"request": {
"method": "GET",
"url": "/api/mytest"
},
"response": {
"status": 200,
"body": "{'glossary':{'title':'example glossary','GlossDiv':{'title':'S','GlossList':{'GlossEntry':{'ID':'SGML','SortAs':'SGML','GlossTerm':'Standard Generalized Markup Language','Acronym':'SGML','Abbrev':'ISO 8879:1986','GlossDef':{'para':'A meta-markup language, used to create markup languages such as DocBook.','GlossSeeAlso':['GML','XML']},'GlossSee':'markup'}}}}}",
"headers": {
"Content-Type": "application/json"
}
}
}

We save this file off-course in the created ‘mapping’ folder. After save, we can start WireMock again and have a GET request via URL:
curl http://localhost:8080/api/mytest

The advantage of this last file is the persistent availability of the end-point. The earlier POST request (to create a new end-point) is only saved ‘In memory’.

So, let your mind spin around and see what you can product on all the nicest dummy ReST responses via WireMock!

Next…


Call ReST from within AppWorks

Well, we did it before…So, let’s follow our own post!

In quick steps:

  • Add a document of type ‘XML Store Definition’, so we have this path available in the XML store: /OpenText/HttpConnector/config.xml

  • Create the config.xml in the project with content (in the relative folder path /OpenText/HttpConnector/):

    1
    2
    3
    4
    5
    6
    7
    <configurations xmlns="http://httpconnector.opentext.com/1.0/configuration">
    <connections>
    <connection id="DEMO">
    <url>http://192.168.56.1:8080</url>
    </connection>
    </connections>
    </configurations>
  • Publish the xml file, so it’s available in the XML store (double-check the ‘XMLStore Explorer’ artifact)

  • Add the runtime reference of type ‘Application Connector’ to the project in a folder called ‘runtimerefs’. The reference to make is to ‘OpenText HTTP Connector’ which is required for the creation of the next custom webservice!

  • Create a new custom webservice in the ‘webservices’ folder of the project

  • Update the implementation of the new generated webservice with this content part:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    <implementation xmlns:c="http://schemas.cordys.com/cws/1.0" xmlns="http://httpconnector.opentext.com/1.0/implementation" xmlns:SOAP="http://schemas.xmlsoap.org/soap/envelope/" type="HTTP">
    <connection-id>DEMO</connection-id>
    <uri>/api/mytest</uri>
    <http-method>GET</http-method>
    <request-handler class="com.opentext.applicationconnector.httpconnector.impl.RestRequestHandler">
    <req-headers>
    <header name="Content-Type">application/json</header>
    </req-headers>
    </request-handler>
    <response-handler class="com.opentext.applicationconnector.httpconnector.impl.RestResponseHandler" />
    <valid-response-code>200</valid-response-code>
    <namespaces />
    </implementation>
  • Publish the webservice (so we can connect it in the next step)

  • Create a new service container of type ‘OpenText HTTP Connector’

    • Service group name: http_service_group
    • Service name: http_service_container
    • Select the published service as webservice interface for the container
    • Config file: config.xml
  • Now it’s time to do a first test to see if it’s all working fine with a valid response like this:

    bpm_service_004

When we have it up and running let’s also have a quick test on the worst-case scenario…Time for a shutdown on the external webservice and have a test call again…You get a response like this:

1
2
3
4
5
6
7
8
9
10
<data>
<SOAP:Fault xmlns:SOAP="http://schemas.xmlsoap.org/soap/envelope/">
<faultcode xmlns:ns0="http://schemas.xmlsoap.org/soap/envelope/">ns0:Server</faultcode>
<faultstring xml:lang="en-US">HTTP connection failed: Connection refused (Connection refused)</faultstring>
<faultactor>nl.bos.webservice</faultactor>
<detail>
...
</detail>
</SOAP:Fault>
</data>

A valid response we also expected, but what if this scenario happens in a BPM?


Worst-case scenario evaluated in BPM

For this we first require a BPM. We create a new document of type ‘Business Process Model’ nicely saved in the ‘bpms’ folder of the project with the even nicer name ‘bpm_test’. Use this screenshot as a start where we insert our webservice operation into the activity:

bpm_service_005

Save it, publish it, and do a first run by hitting <F12> in the BPM modeler panel. Use the PIM to find out if it all runs fine by double-checking the message map of the BPM instance:

bpm_service_006

Fine, shutdown our external service and try that action again and see we and up in an “Aborted” state:

bpm_service_007

How to continue from here? For this we first start our external service again and do a ‘Restart’ action on our BPM:

bpm_service_008

We get a new modal popup with an option to restart from an activity:

bpm_service_009

Hit the ‘Start’ button and see (in the PIM) our completed process again! Nice work and as expected, but wouldn’t it be nicer to have a bit more flexibility? What if we call this flow twenty-five times per minute, and your service starts to fail for any reason?


Worst-case scenario update in BPM

What about a simple update like this?

bpm_service_010

Here we’re catching the failed webservice call with a 15 sec. ‘Delay’ construct. So, we fail, we wait 15 sec., and we try again! In the PIM this will look like this (we stop the external service, start the BPM, wait a while, and start external service again):

bpm_service_011

Nice…Next scenario!

Now all this happens in the week-end, because we have a scheduled BPM trigger each Saturday morning. We were not notified about the fact the external service gets an update that same Saturday and will be unavailable till Monday morning!? Yes, my friends…start crying! 😭 I hope you have a day off that next Monday morning!

Time to create a ‘try-10-times’ feature and the fallback scenario…The BPM would look like this:

bpm_service_012

Notes:

  • The activity ‘increase tries’ contains a simple mapping like this (where we first created the ‘tries’ element as process specific message!):

    bpm_service_013

  • The decision construct “within 10 tries?” is changed from the default ‘Inclusive’ type to the ‘Exclusive’ as we always know the outcome. The condition looks like this:

    bpm_service_014

  • Keep in mind:

    • number(bpm:tries) > 10 works the same as number(bpm:tries/text()) > 10
    • number('') will result to a 0 integer value, so there is no need to initialize our ‘tries’ element upfront (like we could do in the ‘Start’ construct for example)
  • The ‘Hold’ activity will be triggered after 10 tries and will (for the demo) wait for current user input, but you can do whatever you want. You see it shows the “user” icon because I applied a default “entity layout” to it (also just for the demo to have a human interaction which ‘holds’ the BPM).

Well, where are we waiting for…Save it, publish it, have a run, and monitor the PIM! I get green flags all over the place! Hooray! 🎉

Can we make it nicer? How about a configurable duration and configurable number of tries? Oeoeoeoeo…Now we’re getting somewhere!


Make it all configurable

For this we first need to add two configurable elements somewhere globally for our solution. A notable feature for this in the underestimated ‘Application configuration’ feature on project level:

bpm_service_015

Hit it like a pro as see the magic happening! It’s just an entity…with Building Blocks! How nice…Let’s create two properties and generate all the rest:

bpm_service_016

We choose The ‘Duration’ type wisely! Later on we’ll discover the BPM ‘Time-Out’ construct requires a certain input type! This should be an XSD date/time value which will have a value pattern like this: PnYnMnDTnHnMnS with a simple value of PT10S which represents a “period of 10 sec.”. Have a look at this site in the section ‘Duration Data Type’.

Save it directly and also publish it as we won’t change anything else for now (I guess!?). Time to set the values, and for this we need to dive into the ‘/app/admin/‘ part of the platform where we can start configuring our solution!

bpm_service_017

Hmmm…that’s an error I get:

bpm_service_018

Ok…Back to the ‘Application Configuration’ in design-time and explicitly add the ‘Security’ BB where we give the ‘Identity Administrator’ role full permission like this. We give the ‘Identity User’ role only READ permission on those two properties:

bpm_service_019

After a save, and a publication, we should be fine again in ‘/app/admin’:

bpm_service_020

Yes, even the admin-time is responsive! 😉

All right…We configured properties (with 3 retries, and a delay of PT1M)…What do you say? No “seconds” configuration? That’s correct! A limitation, but later on we’ll see this information saved in the database…What is saved in the database, can also be manipulated! 🤠

Time to read them from the BPM! For this we’ll use a read-webservice, but after research I conclude there is no webservice at all! Not OOTB from the platform (SOAP or ReST), and also no ‘Webservices’ BB on our crafted configuration entity! What we do know after all the posting and sharing is the understanding of the back-end database where eventually our values will be saved in a table called (in my case) o2appworkstipsgenericgeneric. What can we do with this wisdom? Well, remember the post about the ‘Northwind’ database!? The one where we use the “WS-AppServer service container” to extract webservices from database tables!?!? Let’s do it again…This time for our config entity table!! 🤗

In quick steps:

  • Create the ‘WS-AppServer service container’ via the ‘System Resource Manager’ artifact in your own organization

    • Service group name: wsappserver_service_group
    • Service name: wsappserver_service_container
    • Database config:
      • Name: AppWorks
      • JDBC Driver: PostgreSQL
      • Driver Class: org.postgresql.Driver
      • Connection String: jdbc:postgresql://localhost:5432/appworks_db
      • JDBC Driver XA Class: org.postgresql.xa.PGXADataSource
      • Default Database: appworks_db
      • DB User: postgres
      • Password: admin (for my account!)
  • Create a new document of type ‘Database Metadata’ saved in a ‘metadata’ folder in the project

    • Name: aw_db_metadata

    • Search the database for table o2appworkstipsgenericgeneric and move it to the right panel

    • Save it and close it! Normally we would also generate a webservice out of it like we see in the screenshot, but we’ll create a more custom webservice (instead of the default!):

      bpm_service_021

    • Back in the project view, we open the aw_db_metadata document which shows the exposed table. We can right-click on it and generate a custom webservice out of it:

      bpm_service_022

    • We get an interesting screen, where we can build our own input query for the webservice to be generated! Isn’t that a nice feature! 😍

      bpm_service_023

    • When done, just generate the damn thing…

    • Webservice input:

      • Operation name: AppWorksMetadataWSOps
      • Service name: AppWorksMetadataWS
      • Namespace: http://schemas.bos.nl/aw_db_metadata
      • Interface name: IAppWorksMetadataWS
      • Save it in the ‘webservices’ folder of the project
      • Publish it and do a first test

Ok, I assume at this moment you have the AppWorksMetadataWSOps operation available with a valid response like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
<data>
<AppWorksMetadataWSOpsResponse>
<tuple>
<old>
<o2appworkstipsgenericgeneric>
<bpm_config_ws_retries>3</bpm_config_ws_retries>
<bpm_config_ws_retry_delay>PT1M</bpm_config_ws_retry_delay>
</o2appworkstipsgenericgeneric>
</old>
</tuple>
...
</AppWorksMetadataWSOpsResponse>
</data>

That’s what I’m talking about…Three hoorays for AppWorks!! HaHaaaa! 😜

Well, now it’s easy to finish it off…right? Our BPM update:

bpm_service_024

Notes:

  • The AppWorksMetadataWSOps activity is our webservice injection…No mapping required, only the response (as we saw during our own test!)

  • Rename the ‘Decision’ construct and add a new condition based on the webservice response:

    1
    number(bpm:tries) > number(ns5:AppWorksMetadataWSOpsOutput/ns5:AppWorksMetadataWSOpsResponse/ns5:tuple/ns5:old/ns5:o2appworkstipsgenericgeneric/ns5:bpm_config_ws_retries)
  • The ‘Delay’ will get a duration from message (instead of the static X sec.)

    1
    ns5:AppWorksMetadataWSOpsOutput/ns5:AppWorksMetadataWSOpsResponse/ns5:tuple/ns5:old/ns5:o2appworkstipsgenericgeneric/ns5:bpm_config_ws_retry_delay/text()

Time to deploy and evaluate our solution! For me it’s a green flag…✅


Duration of PT10S!

!!BE CAREFULLY AS IT’S NOT RECOMMENDED UPDATING DATABASE INFORMATION THIS WAY!!

Now, let’s have a look with our HeidiSQL tool in the database table and do an update statement like this: UPDATE o2appworkstipsgenericgeneric set bpm_config_ws_retry_delay = 'PT10S' WHERE id = 2;

After the update, we can test our BPM again…For me this runs like a charm! 😉 But what about “one second” via PT1S!!?? Great question!…I tried it out, but it starts to give errors in the platform logging: Exception while creating the schedule Start date should be greater than current time. with a “Running” BPM-state as result…Too bad! Looks like 10 sec. is the minimal value!


I also found this forum post about a same issue on the Time-Out construct. So, we’re not alone! 👽

That’s it…Boy, did we learn valuable information again! It’s a timed-out “DONE” where we took a look on how to manage an unreachable webservice from a BPM standpoint of view. Interesting stuff to play around with, and you also see, I figure stuff out during the writing of my blog post. I was not aware of the ‘Duration’ part of the configuration check. That’s the same for the missing webservice parts but looks like we’re getting smart enough to build our own workaround this time…Hooray! Time for a beer, have a great week-end, and I CU next week with another great post.

Don’t forget to subscribe to get updates on the activities happening on this site. Have you noticed the quiz where you find out if you are also “The AppWorks guy”?