Oracle Commerce Cloud functionality can be extended through widgets, REST APIs, webhooks and Server-side extensions.
For user interface customization and/or extensions, widgets and sometimes REST APIs are employed together. Webhooks on the other hand are used as an outbound communication to integrate with external systems and REST APIs are inbound from external systems. But until recently these were the only ways to extend the functionality. Since version 17.3, we have Server-Side Extensions, SSE, which allows customers and partners to develop custom code that runs on the server side. These extensions can be accessed either as a REST endpoint or configured as the receiving end of Webhooks. This blog explains how to create your own SSE.
SSE are built using JavaScript and Express Framework in a Node.js runtime environment. Developers and customers will not have direct access to this Node environment. Instead request are made to the storefront and admin server, which in turn are proxied to the Node.js servers. The response is then sent back to the end customer. SSE are therefore, Node.js applications that use Express framework to define REST endpoints (sometimes also referred to as routes.)
In the beginning, JavaScript was only executed in web browsers. In 2009 Node.js came along and enabled us to run JavaScript on servers through the Chrome’s V8 engine. Here are some advantages of Node.js:
But because of these the low level features, code is too verbose and difficult to use. To address these issues, Express was developed.
Express Framework is a light layer that runs on top of Node.js. It has a lot of extensions that have been created by other developers. The paradigm with Express is similar to jQuery. People want to add content to web pages, but standard APIs typically are too verbose. Therefore jQuery gets rid of monotonous, repetitive coding by simplifying the API and adding some helpful features. Express does the same.
SSE are packaged in a zip file and can accept JSON, HTML forms and XML. But they should only return JSON. Template files and images are not supported. The zip file should only contain JSON, JavaScript, pem, text or property files; nothing else.
The folder/files structure should be similar to any Node.js/Express project. Here is an example:
./index.js
./package.json
./gulpfile.js
./app
./app/index.js
./app/routes
./config
./node_modules
./tests
./locales
Some of these files and folders are self explanatory. But others deserve more commentary.
package.json is a file that contains metadata about a Node project. It contains simple data like the project’s name, author, dependencies, etc. Here is an example:
{ "name": “myFirstExtension”, "main": "/app/index.js", "version": "1.0.0", "private": true, "scripts": { "start": "nodemon app/index.js” }, "author": “Tony Stark", "dependencies": { "express": "^4.16.3", "morgan": "^1.9.0" }, "publicUrls": [ "/ccstorex/custom/v1/calculateShipping", "/ccstorex/custom/v1/orderSubmit", "/ccstorex/custom/v1/processPayment" ], "authenticatedUrls": [], "devDependencies": { "body-parser": "latest", "express": "latest", "gulp": "latest", "gulp-jasmine": "latest", "jshint": "latest", "request": "latest", "winston": "latest" } }
These and other properties are thoroughly explained in the Node.js documentation (https://docs.npmjs.com/files/package.json)
A couple of properties deserve special mention:
These are custom Commerce Cloud properties:
The application you write in Node.js/Express is a sub-application in the SSE architecture. Therefore it needs to be exported properly so it is available externally, i.e. to either to other modules in Node.js or as endpoint in Commerce Cloud.
In Node when you include other modules, say you want to include a module that does some calculation, this module has to export the calculated variable. Suppose you have a file calculator.js in folder Shipping (./Shipping/calculator.js):
function calculateShippingCharge() { … } module.exports = calculateShippingCharge;
Then you can include, or require in Node.js parlance, the above functionality to your module like this:
var shippingCalculator = require(./Shipping/calculator.js);
Without Express, Node.js has a single function that handles all requests.
Flow of request/response through Node.js with single handler function
This is fine for a Hello World application. But for real life size applications, to have a single function that handles many different kinds of requests is not the right thing to do. It’s too large to maintain, and error prone.
When using express, instead of having a single handler function, you may have an array of handler functions. Each function handles a single logical task. It uses the separation of concerns pattern. This is a best practice because it has several advantages, such as ease of testing and future expansion.
For those familiar with Oracle Commerce Platform, formerly known as ATG Commerce, think of Express middleware as the commerce pipeline. Just as processors are chained together in commerce pipeline, so are functions in the Express middleware, i.e. they are called one after another.
Flow of request/response through Express and smaller request handler functions
In the code below each function is executed in sequence:
var express = require("express"); var path = require("path"); var fs = require("fs"); var app = express(); app.use(function(req, res, next) { console.log("Request IP: " + req.url); console.log("Request date: " + new Date()); next(); }); app.use(function(req, res, next) { /* Calculate shipping */ next(); }); app.use(function(req, res, next) { /* Process payment */ }); app.listen(3000, function() { console.log("App started on port 3000"); });
This is a good introduction to Express middleware. But, again, a real life size application should be implemented with future expansion in mind. Therefore, best practice is to have each function/middleware in a separate file. This will be demonstrated next.
In Express routing allows you to map different requests to different request handlers:
var express = require(“express”); var app = express(); app.get(“/bob”, function(request, response) { response.send(“Welcome to Bob’s page”); }); app.get(“/faq”, function(request, response) { response.send(“Welcome to FAQ page”); }); app.use(function(request, response) { response.status(404).send(“Page not found”); }); app.listen(3000);
As we saw in Middleware, again here we have a very basic example to aid in basic understanding, but not suitable to a real world application. Therefore we need to break down the functionality into its pertinent files. Here is an example of the appropriate application structure:
/app
/app/index.js
/app/routes
/app/routes/index.js
/app/routes/calculateShipping.js
/app/routes/processPayment.js
/app/routes/…
As you can see, there are two request handler functions: calculateShipping and processPayment in their own JavaScript file. This is how you should implement your extension if it has different endpoints.
At application startup, all js files in routes are loaded. Your app/index.js file might look like this:
var express = require(‘express’); var logger = require(‘winston’); var app = module.exports = express(); … … try { // Load all routes require('./routes')(app, logger); } catch (e) { logger.error(e.message); }
Each file with the request handler function could be written like this:
calculateShipping.js:
function calculateShippingCharge(app) { ‘use strict’; app.post(‘/calculateShipping’, function(request, response) { /* code to calculate shipping goes here */ }); module.exports = calculateShippingCharge;
processPayment.js:
function processPayment(app) { ‘use strict’; app.post(‘/processPayment’, function(request, response) { /* code to calculate/process payment goes here */ }); module.exports = processPayment;
If you prefer a shorter version you could write these as:
module.exports = function (app) { ‘use strict’; app.post(‘/processPayment’, function(request, response) { /* code to calculate/process payment goes here */ });
Once you have tested your application locally, you will need to package it in a zip file with the filename being the extension name and then you are ready to upload your SSE to your Commerce Cloud instance.
The only way to upload your Node/Express application to Commerce Cloud is through REST APIs.
First you need to call the login endpoint:
POST {OCCS host}/ccadmin/v1/login
Header:
Content-Type:application/x-www-form-urlencoded
Authorization: Bearer {application_key}
Body:
grant_type=client_credentials
The {application_key} above comes from registering an application in Commerce Cloud (Settings > Web APIs > Registered Applications)
If successful, the above call will return an access token, which is needed for the next call:
POST {host}/ccadmin/v1/serverExtensions
Header:
Authorization:Bearer {access_token}
Body:
filename:{extension_name}.zip
uploadType:extensions
force:true
fileUpload: {open_handle_to_extension_file}
Once your extension is uploaded it will be available at https://{OCCS host}/ccstorex/custom/{your extension name} for storefront requests and https://{OCCS host}/ccadminx/custom/{your extension name} for admin only requests, such as webhooks.
For more information as well as sample source code checkout the Commerce Cloud community pages at https://community.oracle.com/groups/oracle-commerce-cloud-group
we have seen how SSE can be implemented using Node.js and Express framework to extend functionality on Commerce Cloud. I hope this will be useful to understand how all these pieces work together.
Previous Post
Next Post