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
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:
mappings
for serving JSON files which describe an end-point with response information (we’ll use it later on)__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:
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:
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 | { |
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:
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 | <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:
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:
Fine, shutdown our external service and try that action again and see we and up in an “Aborted” state:
How to continue from here? For this we first start our external service again and do a ‘Restart’ action on our BPM:
We get a new modal popup with an option to restart from an activity:
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?
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):
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:
Notes:
The activity ‘increase tries’ contains a simple mapping like this (where we first created the ‘tries’ element as process specific message!):
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:
Keep in mind:
number(bpm:tries) > 10
works the same asnumber(bpm:tries/text()) > 10
number('')
will result to a0
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:
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:
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 ofPT10S
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!
Hmmm…that’s an error I get:
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:
After a save, and a publication, we should be fine again in ‘/app/admin’:
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 panelSave 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!):
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: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! 😍
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 | <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:
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”?