Skip to content

App Developers: Swagger Best Practices

dotasek edited this page Oct 24, 2017 · 41 revisions

Excellent API documentation is crucial to a user's ability to easily and successfully use your app's Cytoscape Commands and Functions. Cytoscape enables users to discover and understand APIs (including your app's API) via the Help --> Automation menu items, which display Swagger documents in a browser.

This page explains Cytoscape API conventions that app developers can use to produce Swagger document pages that in turn enable users to quickly understand and apply Cytoscape APIs.

For each API, users benefit most if the documentation explains the purpose and context of the API, parameters that can be used, and results that can be returned. Swagger itself takes care of implementing the Try it out! button that allows users to experiment with an API directly.

The audience for Swagger documents is both the new and expert user, with the new user trying to understand if and how the API can be used as a component of a specific workflow. The expert user will likely focus on the presence and form of specific parameters and return results.

Philosophy

In spirit, we model our documentation after the highly successful *NIX man pages, where every page has standard layout and content:

  • Command/Function name
  • Narrative describing intended use and context, and details of actual usage
  • List of parameters and meanings
  • Return results

Swagger allows pages to have a wide range of content, including virtually no content at all. Rich documentation enables users to make good use of your APIs; poor documentation likely leads to users passing your APIs over.

To position your apps (and Cytoscape Automation in general) to best serve both new and experienced users, app writers should spend time developing meaningful and unambiguous content for each of these four categories, then packaging it using Swagger facilities as described below.

Combining this content with Swagger's availability, layout and Try it now button will enable users to spend time developing their workflows instead of trying to understand app Commands and Functions.

Recommendation: Use Swagger Tags

An app should include documentation for each endpoint it exposes, where the documentation explains the intended use, context, and details of actual usage. In addition, it should describe each parameter and the endpoint result concisely. This saves the reader from having to guess purpose and usage from names and titles. Swagger allows the following tags for these purposes:

Tag Descriptive Fields
@Api name
@ApiOperation value,notes
@ApiParam value
@ApiResponse message
@ApiModel value,description
@ApiModelProperty value
@ApiImplicitParams list of @ApiImplicitParam
@ApiImplicitParam value,datatype,paramtype

Swagger tags allow the option of using Markdown syntax to enhance the appearance of your documentation, and allows you to include code snippets, text formatting, and HTTP links.

Note that while Swagger uses tags beginning with @Api to construct page content, it also leverages JAX-RS tags (e.g., @Path, @Produces, @Consumes) to supplement the page with additional HTTP-oriented content. JAX-RS tags define the actual endpoint so it can be called; their use and function are described in Adding Automation to Existing Apps.

@Api - App Name Tag

For the sake of organization in the Swagger UI, your app should be assigned a tag consisting of App: followed by the actual name of the app. This Swagger tag enables Swagger to group pages for app operations under a single heading, thus making them easier to find.

A Java snippet from the Best Practices Sample App illustrates:

@Api(tags="Apps: Best Practices")
@Path("/cyrestbestpractices/v1/classroom/")
public interface ClassroomResource {
   ...
}

This Swagger tag produces the 'Apps: Best Practices' heading, as shown in the Swagger UI below:

Individual app operations can be found by expanding the 'Apps: Best Practices' heading.

@ApiOperation - Operation Tag

App authors should use operation tags to provide a narrative describing intended use and context of an endpoint, and the specifics of its use in practice, and a description of parameters and return results. For example:

@ApiOperation(
    value = "Replace the teacher",
    notes = "Replaces the classes teacher.\n\nYou can use this to 'edit' the teacher's information by replacing their entire record with a newer one.\n\nBest not to do this before a holiday.", 
    response=Person.class)
@Path("teacher")
@PUT
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
public Person putTeacher(
    @ApiParam(value = "The new teacher data", required = true)
    Person teacher 
);

The @ApiOperation tag identifies the endpoint name and response type, and the @ApiParam describes the teacher parameter.

@ApiModel & @ApiModelProperty - Model Tags

An app author should use Swagger model tags to describe the structure and contents of any classes involved in parameter values and return results. Such classes are assumed to be POJOs (Plain Old Java Objects). For example:

@ApiModel(value="Person", description="Defines a person by name and age")
public class Person {
    @ApiModelProperty(value = "The persons's first name", example="Jeff")
    public String firstName;
    @ApiModelProperty(value = "The persons's last name", example="Winger")
    public String lastName;
    @ApiModelProperty(value = "The persons's age at the start of the semester", example="42")
    public int age;
}

The @ApiModel tag describes the class, and the @ApiModelProperty describes class members.

Example: Documented Swagger UI Operation

This is the Swagger page produced for the Replace the teacher operation described above. Note that it includes a description of the Person model as the function result.

Recommendation: Use Models

Swagger can use POJOs to provide example JSON for input and output parameters. If your method returns a POJO, Swagger will use that POJO's class to generate a model for it, and it will appear on the Swagger page.

Apps that consume or produce POJOs should use Swagger tags to document those models so users can see them embedded in Swagger pages.

The two code snippets above, Operation Tags and Model Tags, indicate that the putTeacher operation uses the Person model, and then adds documentation and examples to that model.

As a result, the Documented Swagger UI Operation includes descriptions of the input and output, as well as common example values.

Recommendation: Include Sensible Default Parameter Example Values

Swagger allows you to provide examples for fields inside your models using the example field in the @ApiModelProperty tag. If possible, make these examples such that they will always be valid (the operation will always execute with them, regardless of the conditions). If you cannot ensure the validity of an example value, use one that a user can reasonably expect to see, and note the requirements in the value field of the ApiModelProperty tag.

You can also limit the expected values to an explicit set by using the allowableValues field of the @ApiModelProperty tag. Note that this will only be enforced by the Swagger UI, and your code must still account for all possible values.

Note that endpoint paths may contain fields to be filled in. For example:

{networkSUID}/views/{networkViewSUID}/diffuse_with_options

specifies that networkSUID and networkViewSUID will be replaced by values that will be recognized only in a running instance of Cytoscape. In such cases, it is not possible to put example values in your documentation, as they depend on an execution environment that may be dynamic.

Regardless, your field description should explain how or where values may be found for these fillins, thereby giving the user the best chance of quickly and easily using your endpoint.

Recommendation: Use @ApiImplicitParams when Parameters Cannot be Models

For parameters that are primitives or Swagger-annotated POJOs, the combination of @ApiParam and JAX-RS tags inform Swagger of the data models to be included in an operation’s Swagger page. However, it may occasionally be necessary to accept input parameters in a more efficient manner, such as streaming data. An example of this is the java.io.InputStream parameter used in CyREST’s TableResource class.

public Response createColumn(
	@ApiParam(value="Network ID") @PathParam("networkId") Long networkId, 
	@ApiParam(value="Table Type", allowableValues="defaultnode,defaultedge,defaultnetwork") 		
	     @PathParam("tableType") String tableType,
	… final InputStream is) {
   ...
}

Within the createColumn method, this parameter is used to read a JSON-encoded stream, which is more efficient than loading an entire data model into memory. If we we were to leave the method without additional annotations, Swagger would be able to provide little information on the expected structure of the input stream.

Fortunately, we can do this explicitly. We can describe the Model for this JSON-encoded stream using the org.cytoscape.rest.internal.model.NewColumn POJO, and include this type using the @ApiImplicitParams annotation.

@Path("/v1/networks/{networkId}/tables")
public class TableResource extends AbstractResource {
...
@POST
@Path("/{tableType}/columns")
@Consumes(MediaType.APPLICATION_JSON)
@ApiOperation(value="Create new column(s) in the table", 
         notes="Creates a new, empty column in an assigned table.\n\n"
	 + "This resource can also accepts an array of NewColumn objects to "
         + "create multiple columns via the 'is' parameter.")
	@ApiImplicitParams(value= {
			@ApiImplicitParam(value="New Column Info", 
                                          dataType="org.cytoscape.rest.internal.model.NewColumn", 
                                          paramType="body", 
                                          required=true),
	})
	@ApiResponses (value = {
			@ApiResponse(code=201, message="Column(s) created"),
			@ApiResponse(code=412, message="Could not process column JSON")
	})

public Response createColumn(
	@ApiParam(value="Network ID") @PathParam("networkId") Long networkId, 
	@ApiParam(value="Table Type", allowableValues="defaultnode,defaultedge,defaultnetwork") 		
	     @PathParam("tableType") String tableType,
	@ApiParam(hidden=true) final InputStream is) {
   ...
}

In the snippet above, the caller uses the /v1/networks/{networkId}/tables/{tableType}/columns to call the createColumn function. The {networkId} component maps to the networkID parameter, and the {tableType} component maps to the tableType parameter. In both cases, the @ApiParam provides the Swagger documentation information.

According to the JAX-RS rules, the is parameter is bound to the HTTP payload. The app author intends that the payload contain JSON encoding an org.cytoscape.rest.internal.model.NewColumn instance, and will enforce this at runtime. The app author documents this in the @ApiOperation tag and uses the @ApiImplicitParams tag to notify Swagger to assist the user by including the org.cytoscape.rest.internal.model.NewColumn definition on the Swagger page.

@ApiImplicitParams should be used to include documentation for any non-modeled type used by any parameter.

Recommendation: Use @ApiOperation(response) when a Response can't be a Model

For return values that are primitives or Swagger-annotated POJOs, Swagger can infer the data model to be included in an operation’s Swagger page. However, it may occasionally be necessary to produce return values that customize features of the response payload, such as the response type (201, 404, etc), or provide streaming output instead of an entire data object. An example of a return value that uses customization is that of the Diffusion App’s diffuseWithOptions method.

public Response diffuseWithOptions(@ApiParam(value = "Diffusion Parameters", required = true) DiffusionParameters diffusionParameters)
{
   ...
}

Within the diffuseWithOptions method, a javax.ws.rs.core.Response object is created and returned with a custom status code. If this method were left without additional annotations, Swagger would be able to provide little information on the expected structure of the return data.

As with input parameters, we can set output models explicitly. We can describe the model for our return data using the DiffusionAppResponse class, and include this type using the response field of the @ApiOperation annotation.

@POST
@Produces("application/json")
@Consumes("application/json")
@Path("currentView/diffuse_with_options")
@ApiOperation(value = "Execute Diffusion Analysis on Current View with Options",
	notes = GENERIC_SWAGGER_NOTES,
	response = DiffusionAppResponse.class)
@ApiResponses(value = {@ApiResponse(code = 404, message = "Network or Network View does not exist", response = CIResponse.class)})
public Response diffuseWithOptions(@ApiParam(value = "Diffusion Parameters", required = true) DiffusionParameters diffusionParameters)
{
   ...
}

Note that DiffusionAppResponse must be derived from the CIResponse in order to comply with Cytoscape Function Best Practices. It is defined as follows:

@ApiModel(value="Diffusion App Response", description="Diffusion Analysis Results in CI Format", parent=CIResponse.class)

    public static class DiffusionAppResponse extends CIResponse<DiffusionResultColumns>{
    }

Best Practices Checklist

When using Swagger to document a function it is advised to use the checklist below to evaluate if your command follows best practices in documentation.

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

  • Does your app tag start with the prefix "Apps: "?
  • Does your app tag uniquely identify your app?
  • Do all your operations have an @ApiOperation tag?

@ApiOperation value

  • Do all your @ApiOperation tags have a value field?
  • Do all these fields clearly identify the purpose of each operation?
  • Do all these fields differentiate each operation from all the others in Cytoscape?
  • Are all these fields sufficiently brief? They should all display without difficulty in the Swagger UI.

@ApiOperation notes

  • Do all your @ApiOperation tags have a notes field?
  • Do all these fields guide a user through using the operation?
  • Do all these fields explain any necessary preconditions, multiple modes of execution, or parameter interdependencies?
  • Do all these fields explain any conditions that can cause errors?
  • Do any of your @ApiOperation annotations require a response field?
  • Do all these fields point to well documented API Models?
  • Does each API Model have a valid @ApiModel annotation?
  • Does each @ApiModel annotation have a value field to differentiate it from other models?
  • Does each @ApiModel annotation have a description field to fully describe the model to the user?
  • Does every public field in an API Model have a valid @ApiModelProperty annotation?
  • Does each @ApiModelProperty annotation have a value field to differentiate it from other fields?
  • Does each @ApiModelProperty annotation have a description field to describe the field in prose?
  • Does each @ApiModelProperty annotation have an example field representative of a reasonable input?
Clone this wiki locally