Loading TOC...
Node.js Application Developer's Guide (PDF)

Node.js Application Developer's Guide — Chapter 10

Creating Data Services and Developer Actions in Node.js

Data Services is a convenient way to integrate MarkLogic into an existing enterprise environment. A data service is a fixed interface over the data managed in MarkLogic expressed in terms of the consuming application. Data services can run queries ("Find eligible insurance plans for an applicant"), updates ("Flag this claim as fraudulent"), or both ("Adjust the rates of plans that haven't made claims in the last year"). A MarkLogic cluster can support dozens or even hundreds of different data services operating over the data and metadata managed in a data hub.

As with Java generalists, a Node.js generalist and a MarkLogic Server expert need the ability to collaborate based on contractual data services that are implemented close to the data as service endpoints and are callable as functions in client programs. In particular:

Given

A proxy service directory with paired function declarations and SJS, MJS, or XQuery endpoint implementations that has been loaded into the modules database on the enode.

When A developer uses MarkLogic tooling to generate a Node.js module.
Then The developer can call the methods provided by the module to execute the enode endpoints.

The initial release doesn't provide a way to generate stubs for endpoint modules or to load the proxy service directory into the modules database. The MarkLogic expert can use the Gradle tasks provided by the Java API for those tasks.

To learn how to create and deploy a Data Services proxy service, follow the instructions in the Creating the Proxy Service Directory section of our Introduction to the Java API. Once you have created the Data Service Proxy, you may return to this chapter to learn how you use Gulp to generate modules as well as how to call them from your Node.js client application.

The following topics are covered:

Node.js Annotations for Declarations

The service and function declarations can take the following annotations to affect the generated module:

Declaration Annotation Purpose
service.json $jsModule

Supplies a name for the generated Node.js module to use instead of the default (the directory name).

*.api $jsOutputMode

Specifies whether to get return values as a Promise

(the default) or as a Stream of data objects.

*.api $jsType Specifies a mapping to a JavaScript data type other than the default for a return value

The $jsType annotation supports the mappings listed below for return values.

Declared data type of return value Mappable JavaScript types
boolean boolean (default) string
dateTime Date string (default)
float, int, or unsignedInt number (default) string

The $jsType annotation is not supported for other server data types or for parameters.

Parameters accept and implicitly convert JavaScript input values to server data types as follows:

Declared data types of parameter Convertible JavaScript types for values
boolean boolean or string
dateTime Date or string
numeric atomic data types (such as double or int) number or string
all other atomic data types string
array array, Buffer, Set, Readable stream, or string
binaryDocument Buffer or Readable stream
jsonDocument array, Buffer, Map, object, Set, Readable stream, or string
object Buffer, Map, object literals, Readable stream, or string
textDocument Buffer, Readable stream, or string
xmlDocument Buffer, Readable stream, or string

The declarations can also contain $java* annotations, which are ignored in the Node.js environment. Similarly, the $js* annotations are ignored in the Java environment.

Using Gulp to Generate Models

Gulp provides a tool for build task execution in Node.js environments that's comparable Gradle in Java environments.

The Node.js API makes it easy to write Gulp pipelines that generate proxy modules. The proxy-generator.js module provides a generate() factory function, which return a Node.js Transform that takes the Gulp Vinyl descriptor for a proxy service directory as input and produces the Gulp Vinyl wrapper for a generated proxy module as output.

Following the Gradle project convention (where ml-modules/root/ds contains proxy service directories), the example below of a gulpfile.js pipes proxy service directories to the proxy module generator and then pipes the generated proxy modules to the lib directory for the project:

'use strict';
const gulp  = require('gulp');
const proxy = require('marklogic/lib/proxy-generator.js');
function proxygen() {
  return gulp.src('ml-modules/root/ds/*')
      .pipe(proxy.generate())
      .pipe(gulp.dest('lib/'));
}
exports.proxygen = proxygen;

The following Gulp command command is used to process this gulfile.js:

gulp proxygen

The Node.js client modules can use require() to import the generated proxy modules in the usual way and then construct proxy objects and call the methods of the proxy objects to invoke the server endpoints.

Generated Modules

Similar to the Interface generated for Java, the JavaScript module generated for Node.js defines and exports a JavaScript class with the following characteristics:

  • A constructor that takes a database client with the host, port, and authentication for the server as well as an optional service declaration (where this optional service declaration specifies a directory in the modules database with custom endpoints that implement the interface for the service).
  • A static on() method that takes the database client and optional service declaration parameters of the constructor and provides a convenience for instantiating the class.
  • A method for each function declared in the proxy service directory.
  • If any of those functions takes a session parameter, a createSession() method for constructing a ServiceState argument.
  • JSDoc documentation comments for the class and each method for generating reference documentation where the content of the JSDoc comments uses the desc properties from the service declaration and *.api function declarations.

Expected Pattern of Use

A JavaScript MJS module can be invoked through the /v1/invoke endpoint. This is the preferred method.

The following listings show a generic example of how a developer might write code to use a Data Service method available on a service you have defined:

// Import the marklocig utilities
const marklogic = require('marklogic');
// import the generated class
const GENERATED_CLASS = require("./lib/GENERATED_CLASS.js");
// create a client for the host and port as the user
const client =
marklogic.createDatabaseClient({host:THE_HOST, port:THE_PORT, user:THE_USER, password:THE_PASSWORD});
// construct an instance of the class
const theInstance = GENERATED_CLASS.on(client);
// call a method of the instance to execute an endpoint on the server
const theOutput = theInstance.theMethod(...parameters...);

The method returns either a JavaScript Promise or a stream for the output depending on the $jsOutputMode annotation (as described earlier).

By providing the class instead of providing only a factory for instances, we make it possible to use the class in instanceof tests.

« Previous chapter