Skip to content

Data Services for IO

ehennum edited this page Sep 29, 2021 · 4 revisions

Document IO With Data Service Endpoints

The Data Services approach provides an interface on a middle tier or other backend server for data operations implemented by an endpoint on a MarkLogic enode. This approach supports a good division of responsibilities between the middle-tier or backend generalist and the MarkLogic specialist as well as encapsulation of database knowledge within the data service.

As described elsewhere, the Java API provides tooling for generating a Java interface based on an endpoint declaration. For more information about that approach, see:

http://docs.marklogic.com/guide/java/DataServices

The Java API also provides the following out-of-the-box interfaces in the dataservices package for Data Service endpoints that implement document IO:

Interface Behavior required for the implementing endpoint
InputCaller Takes zero or more documents as input with no output
OutputCaller Returns zero or more documents as output with no input
InputOutputCaller Both takes documents as input and returns documents as output
ExecCaller Executes with neither input or output

An instance of one of the interfaces is constructed by calling its on() factory method with

  • a DatabaseClient specifying the appserver and user authentication
  • a JSONWriteHandle providing the *.api declaration for the endpoint
  • a BufferableContentHandle specifying the Java representation of the input documents if the endpoint takes input
  • a BufferableContentHandle specifying the Java representation of the output documents if the endpoint takes output

Classes implementing BufferableContentHandle include InputStreamHandle for representing and a document as an InputStream and StringHandle for representing a document as a string.

The following example constructs an InputCaller instance for calling an endpoint that takes JSON document input as Java String instances without returning any output:

InputCaller<String> caller = InputCaller.on(
    dbClient, apiDeclaration, new StringHandle()
    );

The *.api declaration must have an endpoint property specifying the full path of the main module in the modules database. Any functionName property in the *.api declaration is ignored when using an out-of-the-box IO endpoint interface.

In most cases, the data type of the input parameter or return value specifies the format of the input or output documents. The only exception is the anyDocument data type, which allows a mix of formats such as a mix of JSON and XML documents.

For the anyDocument data type, us the onHandles() factory method instead of the on() factory method to construct the caller. The input or output must be sent or received as handle instances of the same BufferableContentHandle subclass passed to the onHandles() factory method. In particular, each input handle must declare the format of the input document (for instance, by calling the withFormat() fluent setter).

For other data types, the BufferableContentHandle passed to the on() factory method must either specify a Format consistent with the document data type in the *.api declaration or specify no format.

Input callers

When constructing an InputCaller instance, the endpoint must conform to the following *.api declaration:

  {
    "endpoint": "INPUT_CALLER_PATH",
    "params": [
      {"name":"input", "datatype":"DOCUMENT_TYPE", "multiple":true,  "nullable":true}
      ]
    }

To call the specified input caller, use the InputCaller.call() method, passing an array of objects in the representation supported by the input BufferableContentHandle:

void call(I[] input);

For instance, for an input caller constructed with a StringHandle, the call() method takes a String[] array.

Note: Where possible, supply input documents in a representation that can be resent if the request needs to be retried. Where the document representation can be sent more than once (as with String), the handle implements the ResendableContentHandle marker interface. Conversely, where the document representation can only be sent once (as with InputStream), the handle implements the StreamingContentHandle marker interface.

Output callers

When constructing an OutputCaller instance, the endpoint must conform to the following *.api declaration:

{
  "endpoint": "OUTPUT_CALLER_PATH", 
  "return": {"datatype":"DOCUMENT_TYPE", "multiple":true, "nullable":true}
  }

To call the specified Output caller, use the OutputCaller.call() method to receive an array of objects in the representation supported by the output BufferableContentHandle:

O[] call();

For instance, for an output caller constructed with a InputStreamHandle, the call() method returns an InputStream[] array.

Where input documents should be resendable, output documents can be streaming because the output are received instead of sent.

Input-output callers

When constructing an InputOutputCaller instance, the endpoint must conform to the following *.api declaration:

  {
    "endpoint": "INPUT_OUTPUT_CALLER_PATH", 
    "params": [
      {"name":"input", "datatype":"DOCUMENT_TYPE", "multiple":true,  "nullable":true}
      ],
    "return": {"datatype":"DOCUMENT_TYPE", "multiple":true, "nullable":true}
    }

To call the specified input-output caller, use the InputOutputCaller.call() method:

O[] call(I[] input);

Exec callers

When constructing an ExecCaller instance, the endpoint must conform to the following *.api declaration:

    {
      "endpoint": "EXEC_CALLER_PATH"
      }

To call the specified exec caller, use the ExecCaller.call() method:

void call();

Note: The same interfaces are also used for constructing bulk IO callers when Integrating Dataflow Frameworks With MarkLogic.