Skip to content

App Developers: Cytoscape Function Best Practices

dotasek edited this page Apr 13, 2018 · 25 revisions

When developing apps that enable automation, observing best practices in coding and design can greatly improve the accessibility of that automation and its interaction with other apps and services, and aid in tracing any errors during testing and at runtime.

This page documents best practices for app developers who are enabling automation features as Cytoscape Functions. For Cytoscape Commands, see Cytoscape Command Best Practices.

App Resource Paths

A resource path identifies the REST endpoint associated with an API.

To call your endpoint, the caller appends it to the CyREST base URL and uses the result in an actual REST call as shown below:

Resource path: /cyndex2/v1/networks/{suid}
CyREST base URL: http://localhost:1234
Actual REST call: http://localhost:1234/cyndex2/v1/networks/{suid}

Additionally, if the resource path conforms to the standard REST document model, it can contain object references (e.g., {suid}) that resolve to actual Cytoscape objects.

You should use the JAX-RS @Path annotation to specify the resource path(s) served by your App.

In the simplest case, a resource path identifies a single function in your app, and the resource path becomes synonymous with a single operation. However, you can arrange for your app to provide different functions for the same resource path, corresponding to different REST commands (e.g., GET, PUT, DELETE). For example, GET could return the resource, PUT could update the resource, and DELETE could remove the resource.

You should use the JAX-RS @GET, @PUT, and similar annotations to establish the app's Java function that responds for each HTTP command.

You can understand more about resources via a REST resource book or by inspecting the existing Cytoscape Swagger document accessible via Cytoscape's Help --> Automation --> CyREST API menu item.

Uniqueness

The paths you choose for your resources should be unique in the CyREST API; a path to any of your resources should not map to any existing path in CyREST, including the paths of other apps.

Violating this requirement could result in resources not being registered, or throwing runtime errors.

You should make your app's namespace unique by using the app name as the root of all paths served by the app. For example, the CyREST Best Practices Sample App uses the namespace cyrestbestpractices as the root of all its paths).

Versioning

Since you may decide to evolve your app's REST APIs in future implementations, it is good practice to ensure that future versions of your REST resources can co-exist with the resources you've implemented in the present day.

To ensure this, you should include a version number in your resource (e.g., v1). This way, you can add new resources under a new version (e.g., v2) and still use the same path elements without breaking existing API contracts -- a resource v2/classroom/teacher can change its functionality without impacting v1/classroom/teacher clients.

This version number can be treated as a Major Version according to the Semantic Versioning standard: the functionality of and data models used by this version of a resource must be compatible in subsequent releases. If a change is made that is incompatible (for example, a new parameter is required in the resource request), the version number of the resource should incremented.

Data Models

Your function should return results or errors in the standardized CIResponse and CI Error data models described below. These models are shared between Cytoscape Cyber-infrastructure services, such as Diffusion and CyNDEx, and allow the responses from those services to be uniformly handled by those calling them. There are a number of ways to return these models, documented in detail in the Building CI Responses wiki page.

In this section, we describe the model and its various parts.

CIResponse

CIResponse is a standardized data model that is used in multiple Cytoscape Cyberinfrastructure services to return the results of an operation execution.

The model follows the JSON format below:

{
  "data": ...,
  "errors": [
    {
	"status": 0,
	"type": "string",
	"message": "string",
	"link": "string"
    }
  ]
}

For reference, the equivalent code for a Java class that defines CIResponse is shown in the snippet below:

public class CIResponse<T> {
    public T data;
    public List<CIError> errors;
}

public class CIError {
   public Integer status;
   public String type;
   public String message;
   public URI link;
}

CIResponse Data

If an app operation succeeds, it should return a value as valid JSON in the data field. If it returns no value, the data field should be empty. Either way, the errors array should be empty.

Developers should also be aware that there are a number of data models provided by Cytoscape itself; if you are exporting Cytoscape data such as CyNetwork or CyNode, it is best practice to use one of the JSON models described in the Standard Cytoscape JSON Models wiki page.

CIResponse Errors

If an app operation fails, it should return one or more entry in the errors array, and the contents of the data field should be treated as invalid if it is not empty. Even if invalid, non-empty data should always be parsable JSON.

If the function produces errors entries, each entry must satisfy the following JSON structure and requirements:

{
  "status": 500,
  "type": “urn:cytoscape:ci:(service):(service version):(resource):errors:(error #)”,
  "message": "The thing with the thing broke",
  "link": "/logresource",
}
  • status is an HTTP status code describing the overall error cause.
  • type is a unique, machine-readable URN, structured to indicate the service of the originating error as well as the resource name and a resource specific code. For example, the Cytoscape Diffusion app returns ‘urn:cytoscape:ci:diffusion-app:v1:diffuse:errors:1’ in this field if a network isn’t found while calling a resource the developer has labelled ‘diffuse’. Note that error# can be any alphanumeric value, and type values should be documented as part of the Function interface.
  • message is a short human readable message explaining the condition described in type.
  • link is a link to a log resource, possibly with filters, that can offer more insight into the nature of the error.

Callers that interpret an errors entry should focus on the type field instead of the message field -- wording of the message field may vary, while the content of the type field is intended for comparison leading to branching.

Error Chaining

If an error occurs in an operation, it should return the CIResponse model containing an array of error (e.g., CIError) elements, with the first element of the array describing the error condition.

If the operation failed because of an error encountered in called services or libraries (e.g., Diffusion), the array should also contain the error element returned by that service or library. If there is a chain of failures (e.g., the operation called a service, which called another service), the array should contain contain an error element for each failure in the chain, with the original failure being the last in the list. This practice makes error tracing in complex apps easier.

For example, an app processing a request may call a service and receive a response that looks like the following:

{
	"data" : ...,
	"errors":[	
		{
			"status":"418",
			"type":"urn:cytoscape:ci:foo_service:v1:bar_resource:1",
			"message":"Error decoding token from stream, EOF",
			"link":"http://server.io/foo_service/log"
		}
	]
}

At this point, if the request to the app cannot succeed, the app should produce an errors array containing its own response plus errors from called services. This response would look similar to the following -- with the app's error readable as errors[0]:

{
	"data" : ...,
	"errors":[	
		{	
			"status": 500,
			"type": "urn:cytoscape:ci:foo-app:v1:bar:15",
			"message": "Request to service failed",
			"link": "file:/Users/hodor/CytoscapeConfiguration/3/framework-cytoscape.log"
		},
		{
			"status":"418",
			"type":"urn:cytoscape:ci:foo-service:v1:bar-resource:1",
			"message":"Error decoding token from stream, EOF",
			"link":"http://server.io/foo_service/log"
		}
	]
}

Meaningful Errors

All app automation operations should return meaningful, human interpretable errors when they fail. Returning a stack trace, or worse, returning a generic 500 error with no information at all, should be avoided at all costs. The CIResponse model was designed to coherently report errors and lead to an understanding of root causes, and the use of that model is strongly encouraged.

Documentation

CyREST recognizes Swagger annotations, and uses them to create rich and interactive documentation. Using Swagger to annotate your Cytoscape Functions is a highly encouraged best practice and is covered in detail in the Swagger Best Practices wiki page.

Best Practices Checklist

After implementing one or several CyREST functions, it is advised to use the checklist below to evaluate if your function follows best practices in output and documentation.

Links in checklist headings will direct you to any relevant portions of the Best Practices document.

  • Does every path to a function in your app use a namespace that references your app?
  • Is every path to a function in your app unique?
  • Does every path to a function in your app use correct versioning?
  • Do your apps functions use data models that can be expressed as POJOs for input and output?
  • Are all your output functions wrapped in a CI Response?
  • Do your app functions report errors using CI Errors?