Documenting Rest Api Grails 3.1.8 with Spring REST Docs

Why to document your code?

When writing RESTful endpoints in an application, it becomes a necessity to document your end-points. Documenting end points can lead to productivity gain, as anyone in your team will be able to refer documents and verify the API or share with your front-end team. 

There are many options available in Java for REST documentation, most famous among them are Swagger and Spring REST Docs.
Swagger’s approach to document API is more of Annotation based implementation. You have to add proper annotations to the endpoint.

 

Sample usage

@GET
@Path("/{userName}")
@Produces({ MediaType.APPLICATION_JSON })
@Consumes({ MediaType.APPLICATION_JSON })
@ApiOperation(value = "Get specific admin", httpMethod = "GET", notes = "Fetch the admin user details", response = Response.class)
@ApiResponses(value = { @ApiResponse(code = 200, message = "Given admin user found"),
@ApiResponse(code = 404, message = "Given admin user not found"),
@ApiResponse(code = 500, message = "Internal server error due to encoding the data"),
@ApiResponse(code = 400, message = "Bad request due to decoding the data"),
@ApiResponse(code = 412, message = "Pre condition failed due to required data not found") })

Multiple lines just to state your documentation. Clearly, we are focusing your code for documentation rather than readability.

Spring REST docs to the rescue.

This is an official Spring project, the aim of which is to assist with the production of REST API documentation.

The best part that Spring REST doc provides is :

  • generate documentation by writing tests for your API which approves that document is up to date and TDD followed.
  • with absolutely no annotation

The Spring Rest Docs project uses ASCIIDoctor, Spring MVC/REST Assured Test and is compatible with both Maven and Gradle.

It combines hand-written documentation with Asciidoctor and auto-generated snippets produced with Spring MVC Test which frees us from limitations imposed by Swagger.

 

How to document ?

Implementing Spring REST Docs with Grails (3.1.8)  REST API step by step :

1. Add to build.gradle
plugins {
id 'org.asciidoctor.convert' version '1.5.3'
}

2. In dependencyManagement add

dependencyManagement {
imports {
...
ext['spring-restdocs.version'] = '1.1.0.M1'
}
applyMavenExclusions false
}

3. Gradle dependencies

compile group: 'org.springframework.security', name: 'spring-security-test', version: '4.1.0.RELEASE'

4. Create simple gradle task for generating docs

task generateDocs(type: GradleBuild) {
dependsOn asciidoctor
dependsOn copyDocs
}

5. Add index.adoc file. This is an ASCII document where we can include our auto-generated Snippets by
Spring REST docs.

6. Create new file restdocs.gradle and add this to build.gradle as -

apply from: 'gradle/restdocs.gradle

7. Now we can add a new Integration-test for controller's end-point to generate snippets

8. One important thing to note here is that we can't miss any fields as given in JSON response.
If we do this will result in an org.springframework.restdocs.snippet.SnippetException

9. Now we can run gradle task as -

gradle generateDocs

 

 

Sample Code and Documentation snippets

 

 

   A.  Let’s say we have domain class User.groovy

    class User {
        String firstName
        String lastName

        User(String firstName, String lastName) {
             this.firstName = firstName
             this.lastName = lastName
        }
     }

 

   B. A Controller attached to domain class UserController.groovy which extends RestfulController

    class UserController extends RestfulController {
        static responseFormats = ['json']
        UserController() {
            super(User)
        }
    }

 

    C.  A BootStrap.goovy file :

    class BootStrap {
        def init = { servletContext ->

def testUser1 = new User('mac','coy').save(flush: true)
def testUser2 = new User ('andy','brown').save(flush: true)

assert User.count() == 2
}
}

 

D.  With this config doing a grails run-app hitting URL http://localhost:8080/user from Rest Client gives back JSON response as :

     [{
        "id": 1,
        "firstName": "mac",
        "lastName": "coy"
      },
      {
        "id": 2,
        "firstName": "andy",
        "lastName": "brown"
      }]

 

E. Now following the above implementation notes for Spring REST docs :

. build.gradle looks like –

    buildscript { 
        ext { 
          grailsVersion = project.grailsVersion 
        } 
        repositories { 
          mavenLocal() 
          maven { 
            url "https://repo.grails.org/grails/core" 
          } 
        }
        dependencies { 
          classpath "org.grails:grails-gradle-plugin:$grailsVersion" 
          classpath "org.grails.plugins:hibernate4:5.0.10" 
          classpath "org.grails.plugins:views-gradle:1.0.12" 
          }
      }
      plugins {
        id 'org.asciidoctor.convert' version '1.5.3'
      }

      version "0.1"
      group "test.api"

      apply plugin:"eclipse"
      apply plugin:"idea"
      apply plugin:"war"
      apply plugin:"org.grails.grails-web"
      apply plugin:"org.grails.plugins.views-json"

      ext {
        grailsVersion = project.grailsVersion
        gradleWrapperVersion = project.gradleWrapperVersion
      }

      repositories {
        mavenLocal()
        maven { url "https://repo.grails.org/grails/core" }
       }

      dependencyManagement {
        imports {
          mavenBom "org.grails:grails-bom:$grailsVersion"
          ext['spring-restdocs.version'] = '1.1.0.M1'
         }
       applyMavenExclusions false
       }

       dependencies {
         compile "org.springframework.boot:spring-boot-starter-logging"
         compile "org.springframework.boot:spring-boot-autoconfigure"
         compile "org.grails:grails-core"
         compile "org.springframework.boot:spring-boot-starter-actuator"
         compile "org.springframework.boot:spring-boot-starter-tomcat"
         compile "org.grails:grails-plugin-url-mappings"
         compile "org.grails:grails-plugin-rest"
         compile "org.grails:grails-plugin-codecs"
         compile "org.grails:grails-plugin-interceptors"
         compile "org.grails:grails-plugin-services"
         compile "org.grails:grails-plugin-datasource"
         compile "org.grails:grails-plugin-databinding"
         compile "org.grails:grails-plugin-async"
         compile "org.grails:grails-web-boot"
         compile "org.grails:grails-logging"
         compile "org.grails.plugins:cache"
         compile "org.grails.plugins:hibernate4"
         compile "org.hibernate:hibernate-ehcache"
         compile "org.grails.plugins:views-json"
         compile group: 'org.springframework.security', name: 'spring-security-test', version: '4.1.0.RELEASE'
         console "org.grails:grails-console"
         profile "org.grails.profiles:rest-api:3.1.8"
         runtime "com.h2database:h2"
         testCompile "org.grails:grails-plugin-testing"
         testCompile "org.grails.plugins:geb"
         testCompile "org.grails:grails-datastore-rest-client"
         testRuntime "org.seleniumhq.selenium:selenium-htmlunit-driver:2.47.1"
         testRuntime "net.sourceforge.htmlunit:htmlunit:2.18"
       }

       apply from: 'gradle/restdocs.gradle'

       task generateDocs(type: GradleBuild) {
         dependsOn asciidoctor
         dependsOn groovydoc
         dependsOn copyDocs
}

 

.  gradle/restdocs.gradle  code snippet

dependencies {
  testCompile 'org.springframework.restdocs:spring-restdocs-restassured:1.1.0.M1'
}

ext {
  snippetsDir = file('src/docs/generated-snippets')
  docsDir = file('docs')
}

task cleanTempDirs(type: Delete) {
  delete fileTree(dir: 'src/docs/generated-snippets')
  delete fileTree(dir: 'docs')
}

task copyDocs(type: Copy) {
  from 'build/asciidoc'
  into 'docs'
}

test {
  dependsOn cleanTempDirs
  outputs.dir snippetsDir
}

asciidoctor {
  dependsOn integrationTest
  mustRunAfter test
  mustRunAfter integrationTest 
  inputs.dir snippetsDir
  sourceDir = file('src/docs')
  separateOutputDirs = false
  attributes 'snippets': snippetsDir
}

build.dependsOn asciidoctor

 

 

 

.  Final snippet of Documentation generated :

Result Page

About CauseCode: We are a technology company specializing in Healthtech related Web and Mobile application development. We collaborate with passionate companies looking to change health and wellness tech for good. If you are a startup, enterprise or generally interested in digital health, we would love to hear from you! Let's connect at bootstrap@causecode.com
Have you subscribed to our blogs and newsletter? If not, what are you waiting for?  Click Here

Leave a Reply

Your email address will not be published. Required fields are marked *

SUBSCRIBE!

Do you want to get articles like these in your inbox?

Email *

Interested groups *
Healthtech
Business
Technical articles

Archives