This post is based on my Oracle Code 2018 Warsaw talk. View presentation on slides share:
Wondering what micro frontends term means? Check micro frontends description here. Simply speaking, micro frontend must implement business logic from top to bottom (database, middleware and UI) in isolated environment, it should be reusable and pluggable into main application UI shell. There must be no shared variables between micro frontends. Advantage - distributed teams can work on separate micro frontends, this improves large and modular system development. There is runtime advantage too - if one of the frontends stops working, main application should continue to work.
I have implemented micro frontends architecture with Oracle JET. Source code is available on GitHub repository. There are three applications, two with micro frontends and one is the master UI shell. Both micro frontends are implemented as JET Composite Components. First is hosted on WebLogic, it calls ADF BC REST service in the backend. Second is hosted on Node.JS and returns static data. First micro frontend implements listener, it allows to handle actions from the outside.
When JET application is accessed in your browser, bunch of HTML, JS and CSS files are downloaded from the server. Core idea with micro frontends - instead of loading HTML, JS and CSS for micro frontend from the same host as master app - load it from different host. JET Composite Component rendered inside master application will be downloaded from different host. Not only downloaded, all backend calls should go to that host too, not to the master host. JET Composite Component integration into master application architecture:
This is how it works in practice. Each of these charts is separate JET Composite Component, loaded as micro frontend from different host into master application. We can see that in network monitor. Loader.js scripts for both micro frontends are downloaded from different hosts:
Runtime advantage - if one or multiple micro frontends are down, application continues to run:
JET Composite Component runs on the client, even it is hosted in its own micro frontend. This gives possibility to subscribe to the events happening in the component in the master app and route that event to another micro frontend. In this example, once item is selected in job chart - employees chart (another micro frontend) is filtered:
Server where micro frontend is hosted, must set Access-Control-Allow-Origin header.
Main module where both micro frontends are integrated is using JET module component. Each micro frontend in master UI shell is wrapped into JET module. This allows main application to function, even when micro frontend in the module stops:
JET module is initialized from variable, which returns module name:
Jobs module contains Jobs micro frontend - JET Composite Component. It is hosted and WebLogic and calls ADF BC REST in the backend. Component is assigned with listener:
The most important part is in JS script. Here instead of referencing JET Composite Component locally, we load it from remote host. This allows to develop and host micro frontend JET Composite Component on its own:
Listener refers to c2 element and cals the method. Element c2 in the main app relates to second micro frontend:
This component is loaded from another host, from Node.JS:
Important hint - for JET Composite Component to load from remote host, make sure to add .js for JET Composite Component script, as highlighted (refer to source code):
I have created sample set of intents with phrases (five phrases per intent, and ten intents). Using this set of data to train and build classification model with TensorFlow and Oracle Chatbot machine learning. Once model is trained, classifying identical sample phrases with both TensorFlow and Oracle Chatbot to compare results. Using Oracle Chatbot with both Linguistic and Machine Learning models.
1. Overall TensorFlow model performs better. The main reason for this - I was training TensorFlow model multiple times, until good learning output (minimized learning loss) was produced.
2. Oracle Chatbot doesn't return information about learning loss after training, this makes it hard to decided if training was efficient or no. As consequence - worse classification results, can be related to slightly less efficient training, simply because you don't get information about training efficiency
List of intents for TensorFlow is provided in JSON file. Same intents are used to train model in Oracle Chatbot:
TensorFlow classification model is created by training 2-layer neural network. Once training is completed, it prints out total loss for the training. This allows to repeat training, until model is produced with optimal loss (as close as possible to 0): 0.00924 in this case:
TensorFlow classification result is good, it failed to classify only one sentence - "How you work?" This sentence is not directly related to any intent, although I should mention Oracle Chatbot Linguistic model is able to classify it. TensorFlow offers correct classification intent as second option, coming very close to correct answer:
Oracle Chatbot provides UI to enter intents and sample phrases - same set of intents with phrases is used as for TensorFlow:
Oracle Chatbot offers two training models - linguistic and machine learning based.
Once model is trained, there is no feedback about training loss. We can enter phrase and check intent classification result. Below is sample for Linguistic model classification failure - it fails to classify one of the intents, where sentence topic is not perfectly clear, however same intent is classified well by Oracle Chatbot Machine Learning model:
Oracle Chatbot Machine Learning model fails on another intent, where we want to check for hospital (hospital search) to monitor blood pressure. I'm sure if it would be possible to review training quality loss (may be in the next release?), we could decide to re-train model and get results close to TensorFlow. Classification with Oracle Chatbot Machine Learning model:
Here you can see it in action. Send Event button from JET - sends message through Socket.IO to Node.js server side. Message is handled on server side and response is sent back to client (displayed in browser console):
Server side part with Socket.io is implemented in Node.js application and it runs on Express. To create Node.js application (which is just one json file in the beginning), run command:
To start Node.js application on Express, run command:
Double check package.json, it should contain references to Express and Socket.IO:
Here is server side code for Socket.IO (I created server.js file manually). When connection is established with the client, message is printed. Method socket.on listens for incoming messages. Method socket.emit transmits message to client. In both cases we can use JSON structure for payload variable. There is cheatsheet for socket.emit - Socket.IO - Emit cheatsheet. Socket.IO server side:
Socket.IO client side can be installed into JET application with NPM. There is separate section in Oracle JET documentation, where you can read step by step instructions about 3-rd party library installation into Oracle JET - Adding Third-Party Tools or Libraries to Your Oracle JET Application. I would recommend manually include Socket.IO dependency entry into package.json in JET:
Then run command to fetch Socket.IO library into JET application node modules:
Import Socket.IO into JET module and use io.connect to establish socket connection. Connect to the end point where Express is running with server side Socket.IO listener. Client side is using same socket.on and socket.emit API methods as server side:
It might look like a challenge to run Java in Docker environment, by default Java is not aware of Docker memory limits. Check this article for example - Java inside docker: What you must know to not FAIL. I was able to run WebLogic and ADF (Essential WebLogic Tuning to Run on Docker and Avoid OOM) on Docker previously without Java memory issues, using JAVA_OPTIONS=-XX:+UnlockCommercialFeatures -XX:+ResourceManagement -XX:+UseG1GC. However after Docker upgrade to latest version, these settings didn't help anymore. I did't want to hardcode memory setting with -Xmx.
Java started to consume all available memory in Docker and eventually was killed. You can see this from chart below - memory is growing, killed and after restart growing again:
To solve this behaviour, I have applied settings from Java Platform Group, Product Management Blog - Java SE support for Docker CPU and memory limits. I have replaced JAVA_OPTIONS=-XX:+UnlockCommercialFeatures -XX:+ResourceManagement -XX:+UseG1GC set previously with JAVA_OPTIONS=-XX:+UnlockExperimentalVMOptions -XX:+UseCGroupMemoryLimitForHeap -XX:+UseG1GC.
JAVA_OPTIONS=-XX:+UnlockExperimentalVMOptions - XX:+UseCGroupMemoryLimitForHeap -XX:+UseG1GC did the job - JVM stays in Docker memory limits sharp:
This chart shows Java memory behaviour before and after settings were applied. From March 27th - Java memory is a straight line with JAVA_OPTIONS=-XX:+UnlockExperimentalVMOptions - XX:+UseCGroupMemoryLimitForHeap -XX:+UseG1GC:
This gif shows scenario, where we go to offline mode and then changing data in multiple rows. Data update happens offline and each PATCH request is tracked by offline persistence toolkit:
As soon as we go online (Offline checkbox value is changed in Chrome Developer Tools) - requests executed while offline are replayed automatically against backend server:
We should see, how update flow is implemented in JET in this particular case. Once data is changed, we call submitUpdate function. This function in turn calls JET Model API function save. This triggers PATCH call to back-end to update data. If we are offline, JET offline persistence toolkit, transparently records PATCH request to be able to replay it later while online. There are no specific code changes needed by developer to support offline logic during REST call:
Once we go online, listener is invoked and it calls our function synchOfflineChanges. This function triggers request replay to the backend. This means we can control, when requests are replayed. Besides this, we can control each request which failed to be replayed - this is important, when data conflict happens during update in backend:
Online handler is registered with window.addEventListener in the same module, where persistence manager is defined:
Offline Persistence Toolkit 1.1.1 supports extensive logging. You can update to 1.1.1 version by running: npm install @oracle/offline-persistence-toolkit command:
To enable persistence toolkit logger, add persist/impl/logger module to your target module and call logger.option('level', logger.LEVEL_LOG):
Logger prints useful information about offline update, this helps to debug offline functionality:
Download sample application from GitHub repository.
ADF Declarative Component support is popular ADF framework feature, but in this post I would like to explain it from slightly different angle. I will show how to pass ADF binding and Java bean objects into component through properties, in these cases when component must show data from ADF bindings, such approach could offer robustness and simplify component development.
This is component implemented in the sample app - choice list renders data from ADF LOV and button calls Java bean method to print selected LOV item value (retrieved from ADF bindings):
JDeveloper provides wizard to create initial structure for declarative component:
This is ADF declarative component, it is rendered from our own tag. There are two properties. List binding is assigned with LOV binding object instance and bean property with Java bean instance defined in backing bean scope. In this way, we pass objects directly into the component:
LOV binding is defined in target page definition file, where component is consumed:
Bean is defined in the same project, where page which consumes ADF declarative component is created. We need to define component property type to match bean type, for that reason, we must create class interface in the component library and in target project implement it:
Component and main projects can be in the same JDEV application, we can use JDEV working sets to navigate between projects (when running main project, we dont want to run component project, component project is deployed and reused through ADF JAR library):
Bean interface is defined inside component:
Property for list binding is defined with JUCtrlListBinding type, this allows to pass binding instance directly to the component. Same for bean instance, using interface to define bean instance type, which will be assigned from the page, where component is used:
Declarative component is based on combination of ADF Faces components:
Download sample application from GitHub repository.
What if you need to verify - if row with given key exists in fetched rowset? This could be useful while implementing validation logic. ADF BC API method findByKey - will trigger SQL call and fetch row from DB, if row with given key doesn't exist in fetched rowset. Luckily there is ADF BC API method called findInCacheByKey, this method only checks for row in fetched rowset, without going to DB - very convenient in certain situations, when you actually don't want to bring record from DB, if it wasn't fetched.
Imagine table with pagination feature. First ten rows are fetched and exist in the cache:
Now if we call custom method, where findInCacheByKey is invoked twice - you will see different results. First call is using key from fetched rowset - this call will find a row. Second call is using key, which doesn't belong to the fetched rowset - row is not in cache and call will return zero rows:
Pagination is a must for large REST resources, its great that Oracle offline persistence toolkit supports it. Let's see it in action.
I navigate through the data with left/right arrows, this triggers REST call with pagination parameters - limit and offset. These are standard parameters supported by ADF BC REST. Requests are executed online:
All pages of data are cached by offline toolkit, if while offline we try to access previously cached page by executing REST request with paging parameters - we will get data from offline toolkit. Now I switch offline and try to navigate to the one of cached pages - data is retrieved from cache automatically:
If I navigate to the page, which was not cached (meaning - not accessed while online) - no results returned. In such situation I can navigate back (paging parameters will be updated) and cached data will be displayed for the page which was cached:
Paging navigation control buttons are calling JS functions to update startIndex:
Sample application is using JET Collection API to execute fetch requests. Collection is extended with getURL function which sets limit and offset parameters to execute paging request:
Once again, make sure to use Oracle Rest Query Handler in offline persistence toolkit configuration:
Fetch function is called through JET Collection API. Start index value is calculated dynamically - this allows to execute paging requests. Same function works online and offline, no need to worry about connection status, all online/offline logic is handled by persistence toolkit:
Sample application for this post is available on GitHub.
Visual conversation flow is a first thing to create, when you want to build chatbot. Such flow will help to define proper set of intents along with dialog path. Otherwise it is very easy to get lost in conversation transitions and this will lead to chatbot implementation failure. Our chatbot for medical system doesn't make any decisions, instead it helps user to work with enterprise system. It gets user input and during conversation leads to certain API call - which at the end triggers enterprise system to execute one or another action. If user is looking for patient blood pressure results, chatbot will open blood pressure module with patient ID. If user wants to edit or review blood pressure results in general, chatbot will load blood pressure results module without parameters. This kind of chatbot is very helpful in large and complex enterprise systems, this helps to onboard new users much quicker without extra training for system usage. Example of visual conversation flow for chatbot:
Conversation intents can be logged in JSON file. Where you should list conversation patterns mapped with tags, responses and contextual information. Chatbot is not only about machine learning and user input processing, very important is to handle conversation contextual flow and usually this is done outside of machine learning area in another module. We will look into it later. Machine learning with neural network is responsible to allow chatbot to calculate tag probability based on user input. In other words - machine learning helps to bring the best matching tag for current sentence, based on predefined intents patterns. As long as we get probability for the intent tag - we know what user wants, we can set conversation context and in the next user request - react based on current context:
TensorFlow runs neural network, which trains on supplied list of intents. Each training run may produce different learning results, you should check total loss value - lower value, better learning result. Probably you will run training multiple times to get optimal learning model:
TensorFlow can save learned model to be reusable by classification API. REST interface which calls classification API is developed as separate TensorFlow module. REST is handled by Flask library installed into TensorFlow runtime:
Classification function gets user input from REST call and runs it through TensorFlow model. Results with higher probability than defined by threshold are collected into ordered array and returned back. We have classification function without REST annotation for local tests within TensorFlow runtime:
Let's see how classification works, result of classification will drive next action for the chatbot. Each classification request returns matched tag and probability. User input is not identical to the patterns defined in intents, thats why matching probability may differ - this is core part of machine learning. Neural network constructed with TensorFlow, based on learned model, assumes the best tag for current user input.
User input "Checking blood pressure results for patient". This input can be related to both tags blood_pressure_search and blood_pressure, but classification decides higher probability for the first option, and this is correct. Similar for user input "Any recommendations for adverse drugs?":
Through REST endpoint we can call classification function outside of TensorFlow environment. This will allows us to maintain conversation context outside TensorFlow:
ADF remote regions - functionality available in the latest ADF 12c versions. ADF remote region runs on different server and content is delivered to consuming module through ADF remote region servlet. This allows to decouple large enterprise system into separate modules, each running independently on separate servers. Gained advantage - system becomes more scalable and reliable, even if several modules will be down, system will continue to be functional.
Concept of ADF remote regions, reminds me closely microservices architecture. Microservices - also known as the microservice architecture - is an architectural style that structures an application as a collection of loosely coupled services, which implement business capabilities. The microservice architecture enables the continuous delivery/deployment of large, complex applications. It also enables an organization to evolve its technology stack (as describe here).
Let's see how ADF remote regions are configured and consumed. Sample application (available for download from GitHub repository) is based on Employees and Jobs modules. Each module is deployed on different servers, Employees module is consumed in Jobs. Microservice here - Employees table. This table comes from loosely coupled service and is consumed within Jobs module:
Jobs module still works, even Employees module is not available anymore - it is stopped. Otherwise if both modules would be deployed as single application - if application is down, system will be completely unavailable. But now users can access part of the functionality:
Will dive into technical part. ADF remote region is not different from the way how regular ADF region is consumed. You still must use ADF region tag, to define region:
Key difference is in region bindings - there is one extra property called Remote Connection. This connection defines source, from where remote region content is transferred. All other properties are the same, we can pass parameters too:
Consuming module must define ADF remote region connection. If connection details are correct, you should see ADF task flows with remote access listed:
Remote region connection wizard. You should use module context root and point to ADF remote region servlets rtfquery and rr:
This wizard can be opened by right clicking on Connections folder and going to New Connections section:
Consumer module should be enabled with remote region consumer support:
Producer module should be enabled with remote region producer support:
Producer module is configured with required servlets automatically, as soon as you enable remote region producer support: