Skip to content
1000TurquoisePogs edited this page May 7, 2021 · 12 revisions

Dataservices are a dynamic component of the backend of a Zowe App. These are optional, as the App server may only serve static content for a particular App. However, when included in an App, a dataservice defines a URL space for which the server will execute the extensible code from the App. Primarily, dataservices are intended to be used to create REST APIs and Websocket channels.

Defining a Dataservice

Let's find out more about Dataservices by observing a simple App: sample-angular-app Within this repository, you'll find a file in the top directory, pluginDefinition.json. Each App requires this file, as it defines how the server will register and use the backend of an App, called a Plugin in the terminology of the App server.

Within this JSON, there's a top level attribute, dataServices.

  "dataServices": [
    {
      "type": "router",
      "name": "hello",
      "version": "1.0.0",
      "initializerLookupMethod": "external",
      "fileName": "helloWorld.js",
      "routerFactory": "helloWorldRouter",
      "dependenciesIncluded": true,
      "httpCaching": false
    }
  ]

Dataservices defined in pluginDefinition

The following attributes are valid for each dataservice in the dataServices array:

  • type: Valid values are either "router" or "service".
    • router: Router dataservices are those which will run under the App server, and utilize ExpressJS Routers for attaching actions to URLs & Methods.
    • service: Service dataservices are those which will run under ZSS, and utilize the API of ZSS dataservices for attaching actions to URLs & Methods. TODO: Wiki page covering the ZSS API
    • import: Used to import a dataservice present in another plugin into the namespace of this plugin, for easy retrieval in your web code via the URI Broker.
    • external: Used to set up an endpoint in your plugin's namespace as a proxy for an endpoint on another server. External services have the following values to state how to proxy.
      • host: The host of the destination server
      • port: The port of the destination server
      • urlPrefix: Each proxied request will have this string prepended to the URL
      • isHttps: Whether or not to proxy to an HTTP or HTTPS host
  • name: The name of the service which must be unique per pluginDefinition.json file. This name is used to refer to the dataservice during logging, but also is used in construction of the URL space that the dataservice will occupy. name is not used for import dataservices, see localName.
  • version: A semver version string. More than one version of the same service can be present within a plugin, but versions must be declared to track dependencies.
  • initializerLookupMethod: This should be "external" unless otherwise instructed.
  • fileName: This is the name of the file that is the entry point for construction of the dataservice, relative to the App's /lib folder. In the case of sample-app, you'll see that upon transpilation of the typescript code, javascript files are placed into /lib.
  • dependenciesIncluded: Must be true for anything in pluginDefinition.json. This is only false when adding dataservices to the server dynamically.
  • httpCaching: Defaults to false. When false, server sets up default values for HTTP headers "Cache-control: no-store" and "Pragma: no-cache". These can be overridden by dataservice code as desired. If httpCaching is set to true, the server does not set default values for these headers.

Router-type specific attributes

  • routerFactory (Optional): The dataservice will be included in the App server via a require() statement. If the dataservice's exports are set up such that the Router is provided via a factory of a particular name, then you must state the name of the exported factory here.

Import-type specific attributes

  • sourcePlugin: The ID of the plugin where the service can be found for an import type dataservice.
  • sourceName: The name of the dataservice to be found in the source plugin when using an import type dataservice.
  • localName: The name of the dataservice within your plugin's namespace when using an import type dataservice.
  • versionRange: A semver string plus range modifiers such as "^" to denote what range of versions would satisfy the import.

Note: Import-type dataservices cannot at this time override the attributes of the imported dataservice. For example, if the original dataservice had httpCaching:false, and the import used httpCaching:true, the import's value is ignored and the original value used instead.

External-type specific attributes

  • urlPrefix: The prefix to be prepended when making the proxy connection to the external source. For example, if the user tried to access <your external service>/foo, but your urlPrefix was /bar, then the proxy would make a connection to <your external destination>/bar/foo.
  • isHttps: Boolean used to tell the server whether to proxy to a destination that is or is not using https instead of http.

Note: External-type dataservices also require specification of the proxied host & port. This is accomplished via making a JSON file, remote.json, with attributes host and port, and placing it within the internal configuration storage for that dataservice. See more about that storage here: https://github.com/zowe/zlux/wiki/Configuration-Dataservice#internal--bootstrapping-use

Dataservice API

The API for a dataservice can be broken down into 4 categories: Router-based or ZSS-based, and Websocket or not. So, let's take a look at each category.

Note: Each Router dataservice can safely import express, express-ws & bluebird without needing the modules present, as these modules exist in the App server's directory and the NODE_MODULES environment variable can include this directory.

Router-based Dataservices

HTTP/REST Router Dataservices

Router-based dataservices must return a (bluebird) Promise that resolves to an ExpressJS router upon success. Check the ExpressJS guide on use of Router middleware to see the full capabilities this provides: Using Router Middleware

Because of the nature of Router middleware, the dataservice need only specify URLs steming from a root '/' path, as the paths specified in the router will later be prepended with the unique URL space of the dataservice.

The Promise for the Router can either be within a Factory export function, as mentioned in the pluginDefinition spec for routerFactory above, or by the module constructor.

Example: Sample App backend

Websocket Router Dataservices

ExpressJS routers are fairly flexible, so the contract to create the Router for Websockets is not significantly different. Here, we are using express-ws, which adds websockets via ws to ExpressJS. The two changes you will see between a websocket-based Router and a normal Router is that the method is 'ws', as in router.ws(<url>,<callback>), and that the callback provides the websocket for which you must set up event listeners on. Please check the ws and express-ws pages linked above for more detail on how they work, as the API for websocket router dataservices is primarily provided in these packages.

Example: Terminal proxy

Router Dataservice Context

Every Router-based dataservice is provided with a Context object on creation that provides definitions of its surroundings and functions that are helpful. This Context object will evolve to include more content over time, but at this point the following items are present in the Context:

  • serviceDefinition: The dataservice definition, originally from the pluginDefinition.json file within a plugin.
  • serviceConfiguration: An object containing the contents of configuration files, if present. TODO: Describe these files.
  • logger: An instance of a ZLUX Logger, which has its component name as the unique name of the dataservice within a plugin.
  • makeSublogger: A function to create a ZLUX Logger with a new name which gets appended to the unique name of the dataservice.
  • addBodyParseMiddleware: A function which provides common body parsers for HTTP bodies, such as JSON & plaintext.
  • plugin: An object that contains more context from the plugin scope. This includes:
    • pluginDef: The contents of the pluginDefinition.json file that this dataservice is a part of.
    • server: An object that contains information about the server's configuration. This includes:
      • app: Information about the product in question. This contains the productCode, such as ZLUX.
      • user: Configuration information of the server, such as what port it is listening on.
      • startUp: TODO

ZSS-based Dataservices

An example of a ZSS dataservice is here: https://github.com/jordanfilteau1995/hello-world-data-service

HTTP/REST ZSS Dataservices

Websocket ZSS Dataservices

TODO