Technology Blogs by SAP
Learn how to extend and personalize SAP applications. Follow the SAP technology blog for insights into SAP BTP, ABAP, SAP Analytics Cloud, SAP HANA, and more.
cancel
Showing results for 
Search instead for 
Did you mean: 
matt_steiner
Active Contributor

In today's post we'll talk about every developer's favorite activity: writing documentation! :wink:

Well, truth be told I yet have to meet a developer that enjoys this aspect of the job, yet everybody knows it's a necessity and hence developers often tend to apply a "let's get it over with" mentality when they (are asked to) do it. Needless to say that's not the best approach imaginable!

Documentation is like S-E-X: when it is good, it is very, very good; and when it is bad, it is better than nothing.
-- Dick Brandon [REF]

Interestingly enough though, most developers however do appreciate quality documentation (when sitting on the consuming side!) and in the context of using open-source software it's often the quality of the documentation that separates the top from the rest of the pack. Especially, when it comes to understanding APIs it's a make-or-break topic; hence, if you're out for adoption you better put in the extra effort to ensure you have a good documentation. (Please refer to: The three commandments of a good API.)

Characterizing 'good' documentation

The best documentation is self-documenting code and an intuitive user interface.
-- a Bellevue Linux Users Group member, 2005 [REF]

I believe there's a lot of truth in the quote above, especially in regards to documenting APIs. By design, APIs are technical in nature and tightly coupled to the underlying functionality they are exposing. Therefore, it is absolutely necessary to keep the documentation in sync with the respective API endpoints! Ultimately, the documentation of the APIs is a part of the source-code itself. In the Java world this is typically achieved via JavaDoc. Based on this rationale, it sounds desirable to use JavaDoc and JAX-RS annotations to embed the API documentation directly into the respective source-code.

And - surprise, surprise - there's an open source project that matches these requirements (among others) and supports them very well. It's called: Enunciate. In short, Enunciate is a framework that generates a full-fledged HTML documentation of your API based on the respective JavaDoc and JAX-RS annotations, which can be integrated into the Maven build process via a corresponding plugin. This way, every time your project is build the documentation is updated.

Let's now have a closer look  at how-to integrate Enunciate into the build process.

Integrating Enunciate

Maven

Alright, the first thing we need to do is to add the needed dependencies to the pom.xml file. This is achieved by adding the following three coding sections into the respective sections.

First, we define a custom property (variable) to maintain the desired version in a central place, which needs to be added within the <properties> section of the pom.xml file:


<org.codehaus.enunciate-version>1.28</org.codehaus.enunciate-version>

Next, the dependency itself, which needs to be added to the <dependencies> section as follows:


<!-- enunciate (REST API documentation tool) -->
<dependency>
    <groupId>org.codehaus.enunciate</groupId>
    <artifactId>enunciate-docs</artifactId>
    <version>${org.codehaus.enunciate-version}</version>
</dependency>

The final step is to integrate the maven-enunciate-plugin into the build process. This is accomplished, by adding the following code snippet into the <plugins> section within the <build> section:


<!-- enunciate - REST documentation tool -->
<plugin>
    <groupId>org.codehaus.enunciate</groupId>
    <artifactId>maven-enunciate-plugin</artifactId>
    <version>${org.codehaus.enunciate-version}</version>
    <executions>
        <execution>
            <goals>
                <goal>docs</goal>
            </goals>
            <configuration>
                <docsDir>${project.basedir}/src/main/webapp/docs</docsDir>
                <configFile>${project.basedir}/src/main/resources/META-INF/enunciate/enunciate.xml</configFile>
            </configuration>
        </execution>
    </executions>
</plugin>

There are mainly two things to point out here: a) the <docsDir> and the <configFile> declaration. The first one defines the location, where the generated HTML documentation will be stored (in our case, we put it directly within the web application folder itself). The second declaration - as the name suggests - points to the Enunciate configuration file.

Note: The complete file can be viewed on github here.

Enunciate configuration

Now, let's have a closer look at the enunciate.xml configuration file:


<?xml version="1.0"?>
<enunciate label="Enterprise Granny" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:noNamespaceSchemaLocation="http://enunciate.codehaus.org/schemas/enunciate-1.28.xsd">
    <namespaces>
        <namespace id="Addressbook" uri="http://api.enterprise-granny.samples.cloud.sap.com/model" />
    </namespaces>
    <services>
        <rest defaultRestSubcontext="/api/v1" />
    </services>
    <webapp disabled="true" doCompile="false" doLibCopy="false" doPackage="false" />
    <modules>
        <!-- Disable all the client generation tools -->
        <basic-app disabled="true" />
        <c disabled="true" />
        <csharp disabled="true" />
        <java-client disabled="true" />
        <jaxws-client disabled="true" />
        <jaxws-ri disabled="true" />
        <jaxws-support disabled="true" />
        <jersey disabled="true" />
        <obj-c disabled="true" />
        <xml disabled="true" />
        <docs splashPackage="com.sap.hana.cloud.samples.granny.api" title="Granny's Addressbook"
            copyright="SAP SE" includeDefaultDownloads="false" includeExampleXml="false"
            includeExampleJson="true" groupRestResources="grouping" applyWsdlFilter="false"
            docsDir="docs" freemarkerXMLProcessingTemplate="docs.fmt" base="docs-base.zip">
        </docs>
    </modules>
</enunciate>

As you can see by looking at the <modules> section we barely use a fraction of Enunciates's feature set, yet in this blog post we focus on documentation, hence I disabled all but the docs module. (Note: Many of the disabled modules allow to automatically create client libraries for consumption of the API in various programming languages and environments.)

Going through the configuration from top to bottom there are a couple of things to explain:

  1. The  <namespaces> section is primarily intended to support namespaces when dealing with WS*-standards. However, it can also be used to define groups of specific API endpoints. (For our simple application that's a bit of an overkill, but in more complex projects it certainly makes sense to split up the API in various groups.)
  2. The  <services> section defines the base URL for the RESTful API and consequently needs to match the settings we provided in both the web.xml and the Spring root-context.xml configuration file.
  3. We don't need the  <webapp> module either as we bundle the generated docs directly into our regular web app as explained earlier.
  4. Fast forward to the  <docs> module within the  <modules> section. Most of the attributes should be pretty self-explanatory, but in case you want to have a closer look make sure to read the respective documentation on the Enunciate web site. However, let's go through the values for the various attributes now.

AttributeDescription
splashPackageThe name of the Java package that contains a package-info.java file with JavaDoc providing some introduction to the API.
titleYou guessed it... the title of the API documentation.
copyrightPretty much self explanatory right? The copyright statement will be included in the footer of the API documentation.
includeDefaultDownloadsAs stated, Enunciate provide means to generate client libs for various programming languages and environments. Since we disabled the respective modules, we also disable exclude the respective download links within the generated documentation.
includeExampleXmlSince we focus on REST and JSON we disable the generation of XML examples.
includeExampleJsonSure want to include JSON examples for the domain model objects used in our API.
groupRestResourcesWith these feature you can use so-called Enunciate facets to group various API endpoints into a group. We'll explain that in more detail later-on when we have a look at the instrumented code.
applyWsdlFilterAgain, we are solely focusing on RESTful API endpoints and hence do not need to include WS*-standard support.
docsDirThat's the subdirectory where the generated documentation will be stored.
freemarkerXMLProcessingTemplateThat's probably the most interesting attribute, since it allows to specify a custom Freemarker template to be used for the creation of the documentation. I usually modify the default template to a large extend to make the documentation blend more nicely with the rest of the application. We'll focus on that topic in more detail in a separate chapter.
base
Reference to a ZIP archive that will be unpacked during the creation of the documentation. This archive may include other resources (e.g. CSS stylesheets, Javascript or other MIME types) needed for the documentation. Note: I usually just provide an empty ZIP file and reference the resources used by the webapp itself.

Note: There are plenty more attributes and configuration settings that can be applied. Please consult the respective module documentation for further information.

Embedding JavaDoc and API documentation annotations

The last thing we need to do is to clean up our JavaDoc in the classes exposing the RESTful API and add a few annotations to provide further hints/instructions how-to render the result.

In order to get the complete picture have a look at the annotations starting with org.codehaus.enunciate within the ContactFacade class located here. For illustration purposes let's discuss the following two code fragments:


/**
*  Provides the public API for {@link Contact}-related operations and services.
*/
@Service()
@Path("/contacts")
@Produces({ "application/json" })
@org.codehaus.enunciate.Facet(name = "grouping", value= "Addressbook API",
documentation = "Provides REST endpoints to manage address data.")
public class ContactFacade extends BaseFacade

This is the header of the ContactFacade class. Here, we use the @org.codehaus.enunciate.Facet to provide a specific name for the respective API group (= Adressbook API) and a brief documentation.


    /**
     * Creates a new {@link Contact} object.
     *
     * @param contact The {@link Contact} to be created
     * @return {@link Response} representation of the created {@link Contact}
     *
     * @name Create Contact
     */
    @POST
    @Consumes(MediaType.APPLICATION_JSON)
    @org.codehaus.enunciate.jaxrs.TypeHint(Contact.class)
    public Response create(@Valid Contact contact)
    {
        contact = contactSrv.createContact(contact);
        return Response.ok(contact).status(Status.CREATED).build();
    }

The above coding fragment shows one of our API service methods exposed via JAX-RS. Here, we provide a so-called TypeHintto prode Enunciate with information regarding the concrete type returned inside of the Response. This way, the documentation can reference the respective domain model object (or better it's JSON representation). Please also note the @name attribute I provided as part of the JavaDoc for this method. That's a custom attribute I used to have more control over how the name of the respective API endpoint (by default, Enunciate simply uses the mount point URL, which I find a bit cumbersome!) This custom attribute is not used by the default docs.fmt template, hence I added some custom logic within the template as well.

Embedding the API documentation into the web app

The last remaining step we need to do is to integrate the documentation into the web app. For that purpose we add a link into the top-level navigation. This is done in the navbar.jsp tile located here. Here's the respective code snippet:


<%@ page language="java" contentType="text/html; charset=ISO-8859-1" pageEncoding="ISO-8859-1"%>
<%@ taglib prefix="tiles" uri="http://tiles.apache.org/tags-tiles" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<div class="navbar navbar-default">
   <div class="navbar-header">
     <button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-collapse">
       <span class="icon-bar"></span>
       <span class="icon-bar"></span>
       <span class="icon-bar"></span>
     </button>
     <a class="navbar-brand" href="<c:url value="/"/>" data-pjax><img src="<c:url value="/resources/img/icon_9727.png"/>" style="width: 40px; height: 40px; margin-top: -10px; margin-right: 10px;" class="hidden-xs hidden-sm"> <tiles:getAsString name="title" ignore="true" /></a>
   </div>
   <div class="navbar-collapse collapse">
     <ul class="nav navbar-nav">
       <li><a href="<c:url value="/about"/>" data-pjax>About</a></li>
       <li><a href="<c:url value="/docs"/>" >API</a></li>
     </ul>
     <ul class="nav navbar-nav navbar-right">
        <c:if test="${not empty pageContext.request.remoteUser}">
            <li class="active"><a href="<c:url value="/profile"/>" data-pjax>${pageContext.request.remoteUser}</a></li>
        </c:if>
     </ul>
   </div><!--/.nav-collapse -->
</div>

Finally, we add one more line to the servlet-context.xml to make sure that the documentation is served more efficiently and not going through the whole process required to serve dynamic content. Here's the respective coding:


<mvc:resources mapping="/docs/**" location="/docs/"/>

Build it

If you now run a Maven build you'll see the output of the docs goal right after the output of the clean as follow:


[INFO] --- maven-enunciate-plugin:1.28:docs (default) @ enterprise-granny ---
[INFO] initializing enunciate.
[INFO] invoking enunciate:generate step...
[INFO] invoking enunciate:compile step...
[INFO] invoking enunciate:build step...
[INFO] closing enunciate.

Wrap-up

With that we are finished and can deploy the app - including the API documentation - to either our local server or your cloud account. To recap, it's quite simple to embed your API documentation within the single source of truth: the source code. Of course, that does not guarantee to result in a good documentation, but at least it eases the process to keep code & documentation in sync.

PS: The custom freemarker template will follow shorty in a separate commit to provide more transparency into the changes I applied to the original source.