← Back to the blog

VMware Cloud Foundation

Using a Service-Oriented Architecture Approach to VCF Operations Orchestrator Development

Blue Ethernet cables connected to a network switch.
Photo: Brett Sayles / Pexels · Licence

This post introduces service-oriented architecture (SOA) and explains how I apply it to my VCF Operations Orchestrator development.

Service-oriented architecture (SOA) is a widely adopted approach to building loosely coupled, reusable services. These principles suit systems integration. Much of Orchestrator development involves integrating external systems, with Orchestrator coordinating the automation between them.

Here are some key principles of SOA:

  • Modularity – Divide integrations into self-contained services. A service can contain smaller sub-services.
  • Loose coupling – Keep services mostly independent to reduce dependencies. Services can still be composed of other services.
  • Reusability – Reuse services in other services, workflows or actions.
  • Scalability – Add new services easily.
  • Mask complexity – Hide or abstract a service's internal logic.

SOA promotes reuse and modular design. In Orchestrator, this reduces the need to duplicate functionality across actions.

SOA is particularly well-suited for developing integrations in Orchestrator, whether you’re working with built-in plugins or external systems via HTTP REST hosts.

Traditional Orchestrator approach

I often encounter the following approach in Orchestrator development. I have used it myself.

Consider a hypothetical API called MyAPI with five endpoints: MyAPI/endpoint1 through MyAPI/endpoint5. Each supports the HTTP GET method. This example focuses on the structure of the integration rather than the details of making requests.

Developers often create five separate actions (functions):

getEndpoint1
getEndpoint2

getEndpoint5

Orchestrator encourages this approach, and many built-in actions follow the same structure.

You call each action using System.getModule().

var result = System.getModule("com.simplygeek.myapi").getEndpoint1(restHost);
...
var result = System.getModule("com.simplygeek.myapi").getEndpoint5(restHost);

Separate actions provide some reuse, but this approach has limits.

With multiple integrations and dozens of endpoints, these actions can become difficult to manage, maintain and scale.

An approach based on SOA principles

Instead, create a dedicated service for MyAPI using JavaScript class-style functions and prototypal inheritance. A single Orchestrator action can contain the service and its API logic, making it reusable.

The following action, MyApiService, defines a service using a class-style function declaration. It includes the methods needed to interact with the API.

function MyApiService(restHost) {
    this.restHost = restHost;
    this.baseUri = "https://myapi.local/api/";
}

MyApiService.prototype.getEndpoint1 = function () {
   // Perform a GET on endpoint1
   var uri = this.baseUri + "/endpoint1";
   var results = this.get(uri);
   
   return results;
};

MyApiService.prototype.getEndpoint2 = function () {
    // Perform a GET on endpoint2
   var uri = this.baseUri + "/endpoint2";
   var results = this.get(uri);
   
   return results;
};

MyApiService.prototype.getEndpoint3 = function () {
    // Perform a GET on endpoint3
   var uri = this.baseUri + "/endpoint3";
   var results = this.get(uri);
   
   return results;
};

MyApiService.prototype.getEndpoint4 = function () {
    // Perform a GET on endpoint4
   var uri = this.baseUri + "/endpoint14";
   var results = this.get(uri);
   
   return results;
};

MyApiService.prototype.getEndpoint5 = function () {
    // Perform a GET on endpoint5
   var uri = this.baseUri + "/endpoint5";
   var results = this.get(uri);
   
   return results;
};

MyApiService.prototype.get = function () {
    // Backend code to handle GET call (handle params, pagination, etc)
};

return MyApiService;

Since VCF Operations Orchestrator uses ECMAScript 5, the class keyword is not supported. Instead, constructor functions are used to define classes, following the classical function-based approach.

The prototype lets us attach methods to the class. Instances share these methods, improving memory usage and keeping behaviour consistent.

This example defines methods for each of the five GET endpoints. An additional internal method handles shared GET request logic, including parameters, collections and pagination.

To use the service, we create a new instance of the MyApiService class using the new keyword. This is typically done alongside a System.getModule() call to reference the Action where the class is defined.

var myApiService new (System.getModule("com.simplygeek.myapi").MyApiService())(restHost);
var result = myApiService.getEndpoint1;
...
var result = myApiService.getEndpoint5;

Multiple API calls now share the same underlying logic. This reduces duplication, makes the code easier to read and maintain, and lets us add methods without repeating that logic.

Prototypal inheritance lets us split MyApiService into two focused classes: one for core backend logic and another for higher-level API calls. This separates their responsibilities and improves code organisation.

Example Action: MyApiBackendService

function MyApiBackendService(restHost) {
    this.baseUri = "https://myapi.local/api/";
    this.mediaType = "application/json";
    this.restHost = restHost;
}

MyApiBackendService.prototype.get = function () {
    // Backend code to handle GET call (handle params, pagination, etc)
};

MyApiBackendService.prototype.post = function () {
    // Backend code to handle POST call
};

MyApiBackendService.prototype.delete = function () {
    // Backend code to handle DELETE call
};

return MyApiBackendService;

Example Action: MyApiService

function MyApiService(restHost) {
    // Import Properties defined on MyApiBackendService.
    MyApiBackendService.call(this, restHost);
}

// MyApiService will inherit methods from MyApiBackendService.
var MyApiBackendService = System.getModule(
	"com.simplygeek.myapi"
).MyApiBackendService();

MyApiService.prototype = Object.create(
    MyApiBackendService.prototype
);
MyApiService.prototype.constructor = MyApiService;

// Add additional methods to MyApiService
MyApiService.prototype.getEndpoint1 = function () {
   // Perform a GET on endpoint1
   var uri = this.baseUri + "/endpoint1";
   var results = this.get(uri);
   
   return results;
};

MyApiService.prototype.getEndpoint2 = function () {
    // Perform a GET on endpoint2
   var uri = this.baseUri + "/endpoint2";
   var results = this.get(uri);
   
   return results;
};

MyApiService.prototype.getEndpoint3 = function () {
    // Perform a GET on endpoint3
   var uri = this.baseUri + "/endpoint3";
   var results = this.get(uri);
   
   return results;
};

MyApiService.prototype.getEndpoint4 = function () {
    // Perform a GET on endpoint4
   var uri = this.baseUri + "/endpoint14";
   var results = this.get(uri);
   
   return results;
};

MyApiService.prototype.getEndpoint5 = function () {
    // Perform a GET on endpoint5
   var uri = this.baseUri + "/endpoint5";
   var results = this.get(uri);
   
   return results;
};

return MyApiService;

These examples move the shared backend logic into MyApiBackendService. The MyApiService class extends it with higher-level API methods, reusing the backend functionality.

Inheritance connects MyApiService to MyApiBackendService.

MyApiService.prototype = Object.create(
    MyApiBackendService.prototype
);
MyApiService.prototype.constructor = MyApiService;

Modern programming languages typically use extends for this relationship. In ECMAScript 5, prototypal inheritance serves the same purpose. It lets us keep complex backend logic inside modular services and expose only the functionality callers need.

This separation improves maintainability and limits the impact of future changes by keeping shared backend logic stable and reusable. Dozens or hundreds of API calls can use distinct service modules built on the same backend.

I hope you’ve found this overview insightful. If you’ve developed your own techniques or have alternative approaches, I’d love to hear about them. Feel free to share!

Archived comments (1)

Comments from the original blog, preserved for reference.

  1. Mayank

    And this can be extended to Polyglot modules as well.

Join the discussion

Sign in with GitHub to leave a comment. View discussions on GitHub.

← Explore more articlesFollow via RSS ↗