VBCS: JavaScript usage, challenges, architecture and governance for enterprises

Oracle Cloud Integration (OIC), Visual Builder Cloud Service (VBCS)
JavaScript usage, challenges, architecture and governance for enterprises
Including smart debugging for action chains

In our current project we are using OIC as a combination of: Process Cloud Service (PCS), Integration Cloud Service (ICS), Oracle Field Service Cloud (OFSC) and Visual Builder Cloud Service (VBCS). This is a two-year project with over 200 people in the project teams. Some five of those (agile) teams work with OIC and this poses some challenges with regards to architecture and governance. This post shows how we mitigate the risks of custom code and bring the application to operations successfully.

Let’s go through some of the project’s history first, the project began with an architecture of no-code in VBCS with custom JET components is where custom code could be placed. After a few months we already saw that this was not a feasible solution as VBCS is not even designed as a no-code product and the custom JET components are UI components. Something like the mapping of a custom structure required JavaScript, which is not allowed under the chosen architecture. This is the reason why we needed to create a model with which we could maintain control of the custom code.

Important! Most of this post still remains true, however, the /latest is not on the roadmap as of this moment. Our project will use the same strategy, but we are moving towards either a CDN approach or a Developer Cloud Service build that pushes our libraries to a location where we can host the static JavaScript files.

NB: Oracle has positioned VBCS as a Rapid Application Development (RAD) framework with a low-code environment, but not as a no-code environment.

We set off to create a plan to tackle this problem. First, we identified the categories of JavaScript we need with each it’s own challenges and recommendations.

Categories of JavaScript

  • Data mapping, takes an object as input and returns an object as output
    • Example: for a filter of a Service Data Provider we use a complex filter with multiple $contains which we want to map
    • We should try and avoid using custom code for mappings that call a create, update or delete as we want those actions to be transparent
  • Front end data manipulation, this is data manipulation on the client side. Data is never manipulated on the backend.
    • Example: quick search of a list of expected size (say we know the list is of a certain small size so that we can always get all items to the frontend), where when you begin to type the list is filtered
    • Example #2: date time manipulation (say I want to add one week to a date?), here we want to use something like momentJS
  • HTML component changes, changes to page layout or elements
    • Example: create a child <li> under an <ul>
  • API calls to 3rd party libraries or components
    • Example: calls to the GoogleMaps API when you have such a map component on your page
  • Cookies, we want to save and read functional cookies
    • Example: we want to save the column show/hide preferences of the user
  • What we never want to do:
    • Any HTTP / HTTPS calls directly from JavaScript code, these calls will not go through the Oracle proxy. This would give all sorts of problems with CORS, authentication and using the client devices proxy.

After we identified each of these categories and decided on how we wanted to use these inside the application we went on with another important question. How are we going to apply this to VBCS??

Complexity in enterprises

We had already created several applications when we realised the complexity of using VBCS within an enterprise:

  • Multiple applications (5 teams working in OIC!)
  • Multiple places where JS code can exist
    • Shell
    • Application
    • Flow
    • Page

So what happens when an error occurs? Or when we want to change for example the date format throughout our whole organization? The answer to these questions? You must dive into each application. If an error occurs on a certain “screen” you have to check the page, the flow, the application and the shell for the code. For example if we would want to change a date format inside a JavaScript method formatDate(date) on application level, we’d have to edit each application within our enterprise. This is when we decided to search for a different solution, custom JavaScript libraries. This took some time to find out how these work inside VBCS, but is possible to include own or 3rd party libraries in a clean way (I will show that later). These libraries have their own set of requirements though.

Custom JavaScript library requirements

  • custom JS libraries can be easily managed by the teams, with generic and team specific libraries
  • custom JS libraries can be easily managed by operations
  • custom JS libraries are versioned (repository)
  • custom JS libraries are hosted on a high available location (preferably at least as available as VBCS) and have no CORS problems
  • custom JS libraries can be unit tested
  • custom JS libraries are stateless (only input and output parameters, no states, can only access global variables that are NOT changed by code, example follows later)
  • custom JS libraries are written in AMD export format so that VBCS “understands” them
  • custom JS libraries should be documented (for example JSDoc format)

After a lot of thinking we created a solution which ticks all of our boxes. Let’s see how we include a custom library in VBCS (we do this only for staging in this example).

Step 1: Create a JavaScript libraries VBCS application (I called it GN_JavaScript_Libraries)

This application will encapsulate all of our custom JavaScript libraries. Why do we do this in VBCS? Because it ticks many of the requirements of course! We can do versioning in VBCS, it is hosted as the same availability as the VBCS application, we have no CORS problems and can be easily managed by our teams (everybody can already access VBCS!).

Create a web application as well, I created myJavaScriptApplication for the example.

Step 2: Create your folder structure and upload it to the JavaScript library

Create a folder structure on your disk, this needs the folder structure of the source view, for the WEB application myJavaScriptApplication:
webApps/myJavaScriptApplication/resources/lib/gn/development.js
webApps/myJavaScriptApplication/resources/lib/gn/visual.js
webApps/myJavaScriptApplication/resources/lib/sa/something.js

Where GN in our enterprise is the short notation for generic and SA for one of our teams.
ZIP this and use the VBCS import function to import this ZIP into the application. With this folder structure we already have some information about what each of the files do.
What this also accomplishes is that operations does not need insider VBCS knowledge, they only need to be able to navigate to each of these .JS files in VBCS to change code.

Step 3: setting some initial JS code

Let’s put some code in one of the libraries. We can just navigate from VBCS now from the source view to the resources folder and edit the .js files in the inline VBCS editor.

In the development.js copy paste the following code (ticking the Asynchronous Module Definition (AMD) export and documentation requirements):

define([], function() {
     var exports = {};

  /**
  * Logs a line to the client console
  * @author Rick Kelder
  * @param {object} The object to be logged to the console in default format
  */
  exports.LogMe = function(objectToBeLogged) {
    console.log("ApplicationLog: ", objectToBeLogged);
    return;
  };

  return exports;
});

Step 4: Getting the right URL

To be able to complete this step we need some additional information, as well as we need some explanation as to how VBCS works. Each time an application is staged or put live, the folders in the source view are NOT published to the stageURL, but rather to a auto generated version. So each stage action will also cause this generated version number to change. So how do we know this generated version?

Stage the application and run the empty myJavaScriptApplication from staging, use F12 and see the first network response (js/ it’s called in chrome) and open the response. There is a BASE_URL_TOKEN in here, this ID is what we need (we can also see this ID when clicking a file like for example shell-page.js, we see that the request URL has the same generated version number).

Step 5: Putting the right URL inside a consuming application

VBCS can require libraries in the application-flow.json (source view -> webapps -> app), this can be done by creating the following piece of code. We include two custom libraries in this example (I always put this part of the code just above the “security:{” line) and use the correct version number:

"requirejs": {
  "paths": {
	   "gnDevelopment":"../../../../../GN_JavaScript_Libraries/1.0/webApps/myJavaScriptApplication/version_1562228076000/resources/lib/gn/development"
	}
},

The left side is the short notation for each library which we will use in the consuming application.

Step 6: calling the LogMe function from inside an action chain

So how do we call this from an action chain? We need to define a VBCS JavaScript function for this as the action chain does not “understand” external functions (yet). I have chosen the application level for consuming my LogMe function so I can use it throughout all action chains of my consuming application and paste the following in the JS part of the application:

/**
   * LogMe
   * @param {String} objectToBeLogged
   */
  AppModule.prototype.callLogMe = function (objectToBeLogged) {
    gnDevelopment.LogMe(objectToBeLogged);
  };

This will cause an error. Why is that? Because we haven’t defined gnDevelopment in this context yet. We can fix this by changing the first line of the code:
define(["gnDevelopment"], function (gnDevelopment) {

Now we can add a call function module step in an action chain and let it perform callLogMe with something like “test” mapped to objectToBeLogged. If we run the application and trigger the action chain we will see a successfully logged message. We have successfully included our own JS library!

Extended

We also have a really nice way of changing code throughout EVERY application. We will give an example using a date time format that we can change throughout the complete enterprise (step 7) which will also use a 3rd party library momentJS. We will also use the library call inside the html code of the application.

We have some really smart ways to use logging inside of action chains. Normally we have a log step in the action chain which writes a message to the chrome console. When we go to live we dont want this logging to occur so we remove this logging when the action chain works. When we need to update the action chain though we need to readd those logging steps if we need them. We have a clean solution for this (step 8).

Step 7: including momentJS and formatting a date

In this step we want to include momentJS inside our custom JavaScript library. To do this we edit the application-flow.json FROM THE LIBRARY ITSELF as follows:

"requirejs": {
	  "paths": {
       "moment":"https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.24.0/moment-with-locales.min"
    }
  },

Please note that this does NOTHING for the consuming application, as this will try and read moment from it’s current path when it is not loaded, so:
myJavaScriptApplication/version_1562228076000/resources/lib/gn/moment

We need to use moment inside our own custom library, this time I am editing visual.js where we include moment in the top line:

define(["moment"], function(moment) {
    var exports = {};
    
    /**
     * Formats a date into one consistent format
     * @author Rick Kelder
     * @param {string} The date to be converted, must be parseable by momentJS
     * @return {string} A formatted date
     */
    exports.FormatDate = function(date) {
        return moment(date).format("lll");
    };

    return exports;
});

Now we can stage the custom JavaScript library again. We need to run it and get the right version, and edit our consuming application:

"requirejs": {
	  "paths": {
	     "moment":"https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.24.0/moment-with-locales.min",
       "gnDevelopment":"../../../../../GN_JavaScript_Libraries/1.0/webApps/js/version_1562328770000/resources/lib/gn/development",
       "gnVisual":"../../../../../GN_JavaScript_Libraries/1.0/webApps/js/version_1562328770000/resources/lib/gn/visual"
    }
  },

Note that we need to include moment in the consuming application because of reasons mentioned earlier.

Let’s define a callFormatDate on application level again, by including the visual and adding a JavaScript function in the application JS:

/**
   * Converts date to moment
   * @param {String} date
   * @return {String}
   */
  PageModule.prototype.callFormatDate = function (date) {
    return gnVisual.FormatDate(date);
  };

Now add HTML and do a bind text to the following (for example using a standard Business Object of VBCS that has the field lastUpdateDate):

<oj-bind-text value="[[ $application.functions.callFormatDate($current.data.lastUpdateDate) ]]"></oj-bind-text>

Run this consuming application and see how moment will format the date in the way you have defined.

Now imagine that we have 50 applications and someone says: “hey I want to change how all the date formats look”, this would be impossible without our custom JavaScript library solution. With our solution however, we can easily change the date format in ONE spot (at this moment we do need to update the version numbers, but that’s it). One can only imagine how much more powerful this pattern is with more complex solutions that consist of multiple lines of code (for example quicksearching arrays). It will also ensure one consistent behaviour of all applications.

Step 8: smart debugging

When we have an action chain and we call the LogMe step, we get an entry in our chrome console. This costs performance, and it is not something we want for live versions. How do we fix this? Remember the LogMe step from step 6 and the “exception” we have been talking about?

Let’s introduce module variables (we should NEVER change this from runtime, but ONLY as a configuration parameter).
I will use this variable to be able to set a debugMode:

define([], function() {
    var exports = {};

    /**
     * Boolean to control if development functions should do debugging output
     * @constant
     * @type {string}
     */
    exports.debugMode = true;

    /**
     * Logs a line to the client console when global flag debug mode is on
     * @author Rick Kelder
     * @param {object} The object to be logged to the console in default format
     */
    exports.LogMe = function(objectToBeLogged) {
        if (exports.debugMode)
            console.log("ApplicationLog: ", objectToBeLogged);
        return;
    };

    return exports;
});

When we want to use this we must stage the custom library again, get the version and update that in our consuming applications. We do however get a nice bonus! We can switch a flag to enable all custom chrome logging, in ALL our applications at once!

Ofcourse we don’t want to do this manually, so in our pipeline we will do a automatic replace on exports.debugMode = true;

Requirements left

  • custom JS libraries are stateless (only input and output parameters, no states, can only access global variables that are NOT changed by code, example follows later)
    • We can solve this requirement by having all the developers listen to us. The exception is in the extended work.
  • custom JS libraries can be unit tested
    • We have not yet implemented this yet, but how I envision this is that we will use a 3rd party library like qunit (also test framework for custom JET components). It would be great if we can run that test with each staging or build, but also manually when running the application manually (and making a VBCS screen with green ticks for each test!)

Caveats

  • Right now we need to change the requireJS versions EACH time we stage the JavaScript library application. This is not really that bad, as this will force us to check the consuming application each time we change a library. A /latest symbolic link would be a really nice addition from Oracle though.
  • When we use requireJS includes inside any of the custom libraries itself (see extended step 7) we need to include it in the consuming application as well, otherwise the pathing goes wrong as it will try and resolve the standard path which is the same as the custom libraries own path (please let me know if anyone knows a solution for this to be able to use the application-flow.json settings of the custom JavaScript library!)

Thanks for reading this post.

Thanks to Coen Elbersen, Jan Kettenis, Rutger Saalmink and Kees Terburg
Thanks to Oracle for answering questions regarding VBCS

Leave a comment

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

%d bloggers like this: