Show Navigation

Grails with Micronaut HTTP Client

In this guide, we will learn how to use the Micronaut HTTP Client in a Grails app.

Authors: Nirav Assar

Grails Version: 4.0.1

1 Grails Training

Grails Training - Developed and delivered by the folks who created and actively maintain the Grails framework!.

2 Getting Started

In this guide you are going to learn how to execute an HTTP client to make external rest API calls as well perform spock testing. You will use Micronaut’s HTTP Client, which has both a low-level API and a higher level AOP-driven API.

2.1 What you will need

To complete this guide, you will need the following:

  • Some time on your hands

  • A decent text editor or IDE

  • JDK 1.8 or greater installed with JAVA_HOME configured appropriately

2.2 How to complete the guide

To get started do the following:

or

The Grails guides repositories contain two folders:

  • initial Initial project. Often a simple Grails app with some additional code to give you a head-start.

  • complete A completed example. It is the result of working through the steps presented by the guide and applying those changes to the initial folder.

To complete the guide, go to the initial folder

  • cd into grails-guides/grails-micronaut-http/initial

and follow the instructions in the next sections.

You can go right to the completed example if you cd into grails-guides/grails-micronaut-http/complete

3 Writing the Application

The /initial folder is a grails app created with the rest-api profile. It is a simple app that queries the Apple iTunes API. The app takes in a searchTerm and is layered with:

  • example.grails.SearchController

  • example.grails.ItunesSearchService

The call is stubbed out until we implement it in this guide.

Verify the functionality by running ./gradlew bootRun and then going to the URL http://localhost:8080/search/searchWithApi?searchTerm=U2.

You should see dummy data JSON returned.

4 Micronaut HTTP Client

The Micronaut HTTP Client dependency is included in the grails rest-api profile and replaces the datastore-rest-client. As of Grails 4, it is the recommended HTTP Client for Grails. (The old HTTP client is still backward compatible in Grails 4).

In our music app we will be using the Micronaut HTTP client in both service functionality and testing so we must change the qualifier in build.gradle to compile:

build.gradle
dependencies {
    ...
    compile "io.micronaut:micronaut-http-client"
}

4.1 Use the Low-Level Micronaut HTTP Client API

We will query the iTunes repository for the artist and expect to receive several pieces of information in the form of JSON, such as album name and a URL link to the album. In order to capture the return data easily, we can create POJOs with properties named exactly the same as the JSON structure. The iTunes API will return resultCount with a list of albums that contain:

  • artistName

  • collectionName

  • collectionViewUrl

Note the following POJOs are already created:

src/main/groovy/example/grails/SearchResult.groovy
package example.grails

import groovy.transform.CompileStatic

@CompileStatic
class SearchResult {
    int resultCount
    List<Album> results = []
}
src/main/groovy/example/grails/Album.groovy
package example.grails

import groovy.transform.CompileStatic

@CompileStatic
class Album {
    String artistName
    String collectionName
    String collectionViewUrl
}

We need to configure Micronaut to accept the text/javascript MIME type from the iTunes API.

grails-app/conf/application.yml
---
micronaut:
    codec:
        json:
            additionalTypes:
                - text/javascript

In the ItunesSearchService, we create the low level client and use the API to contact iTunes.apple.com. Conceptually we are going to create a client with a baseUrl, then form a request object that sends a GET request with URL params. Subsequently, we will issue a blocking request and received a String response. ObjectMapper from Jackson Databind will map the incoming JSON to the POJOs. Add the following to ItunesSearchService:

grails-app/services/example/grails/ItunesSearchService.groovy
    List<Album> searchWithApi(String searchTerm) {
        String baseUrl = "https://itunes.apple.com/"
        HttpClient client = HttpClient.create(baseUrl.toURL())
        HttpRequest request = HttpRequest.GET(UriBuilder.of('/search')
                .queryParam('limit', 25)
                .queryParam('media', 'music')
                .queryParam('entity', 'album')
                .queryParam('term', searchTerm)
                .build())
        HttpResponse<String> resp = client.toBlocking().exchange(request, String) (1)
        String json = resp.body()
        ObjectMapper objectMapper = new ObjectMapper()
        objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false) (2)
        SearchResult searchResult = objectMapper.readValue(json, SearchResult) (3)
        searchResult.results
    }
1 Issue the request with a blocking call, and accept a String response.
2 Ignore extra properties.
3 Map the JSON into the POJOs.

Run ./gradlew bootRun and hit http://localhost:8080/search/searchWithApi?searchTerm=U2 and see the results.

4.2 Use the Declarative Micronaut HTTP Client

The same functionality can be implemented with Micronaut’s Declarative HTTP Client. The @Client annotation can be declared on an interface and at compilation time a client will be created for you. The interface method can be declared with a @Get and return the data-binded POJO. This simple and elegant approach cuts down on much of the code in the previous example.

Create a declarative client:

src/main/groovy/example/grails/ItunesClient.groovy
package example.grails

import io.micronaut.http.annotation.Get
import io.micronaut.http.client.annotation.Client

@Client("https://itunes.apple.com/") (1)
interface ItunesClient {

    @Get("/search?limit=25&media=music&entity=album&term={term}") (2)
    SearchResult search(String term)
}
1 Declare with @Client and set the URL.
2 Define a @Get request with parameters.

Inject the client into the service, and add the call to the interface method.

grails-app/services/example/grails/ItunesSearchService.groovy
...
    @Autowired
    ItunesClient itunesClient

    List<Album> searchWithDeclarativeClient(String searchTerm) {
        SearchResult searchResult = itunesClient.search(searchTerm)
        searchResult.results
    }
...

Add a controller method that routes to the service method:

grails-app/controllers/example/grails/SearchController.groovy
...
    def searchWithDeclarativeClient(String searchTerm) {
        if(searchTerm) {
            List<Album> albums = itunesSearchService.searchWithDeclarativeClient(searchTerm)
            respond([searchTerm: searchTerm, albums: albums])
        }
    }
...

Run ./gradlew bootRun and hit http://localhost:8080/search/searchWithDeclarativeClient?searchTerm=U2 and see the results.

5 Test with Micronaut HTTP Client

The Micronaut Client can also be used for testing purposes with Spock. In our music app let’s create a domain object to capture record labels. We will use the grails built-in REST API for domain classes.

Create a domain object and tag it with @Resource and a given URI. This will expose REST functionality for the object.

grails-app/domain/example/grails/RecordLabel.groovy
package example.grails

import grails.rest.Resource

@Resource(uri='/recordlabels')
class RecordLabel {
    String name
}

In the BootStrap.groovy, add some seed data for testing purposes:

grails-app/services/example/grails/RecordLabelService.groovy
package example.grails

import grails.gorm.services.Service

@Service(RecordLabel)
interface RecordLabelService {
    RecordLabel save(String name)
}
grails-app/init/example/grails/BootStrap.groovy
package example.grails

import groovy.transform.CompileStatic

@CompileStatic
class BootStrap {

    RecordLabelService recordLabelService

    def init = { servletContext ->
        recordLabelService.save("Warner")
        recordLabelService.save("Sony")
    }
    def destroy = {
    }
}

Let’s create an integration test suite that first does a GET request to retrieve the existing list of RecordLabels. The test will also issue a POST to insert one more record label, with a map of data passed in. In both tests, we will verify that the response comes back correctly.

src/integration-test/groovy/example/grails/RecordLabelControllerSpec.groovy
package example.grails

import grails.gorm.transactions.Rollback
import grails.testing.mixin.integration.Integration
import grails.testing.spock.OnceBefore
import io.micronaut.core.type.Argument
import io.micronaut.http.HttpRequest
import io.micronaut.http.HttpResponse
import io.micronaut.http.HttpStatus
import io.micronaut.http.client.HttpClient
import spock.lang.Shared
import spock.lang.Specification

@Integration
@Rollback
class RecordLabelControllerSpec extends Specification {

    @Shared
    HttpClient client

    @OnceBefore
    void init() { (1)
        String baseUrl = "http://localhost:$serverPort"
        this.client  = HttpClient.create(baseUrl.toURL())
    }

    void "test rest get record labels"() {
        when:"record labels exist"
        HttpResponse<List<Map>> resp = client.toBlocking().exchange(HttpRequest.GET("/recordlabels"), Argument.of(List, Map)) (2)

        then: "client can retrieve them"
        resp.status == HttpStatus.OK  (3)
        resp.body().size() == 2
        resp.body()[0].name == "Warner"
        resp.body()[1].name == "Sony"
    }

    void "test rest post record labels"() {
        when:"a post is issued"
        HttpResponse<Map> resp = client.toBlocking().exchange(HttpRequest.POST("/recordlabels", [name: "Universal"]), Map) (4)

        then: "element is created"
        resp.status == HttpStatus.CREATED (5)
        resp.body().size() == 2
        resp.body().id
        resp.body().name == "Universal"

    }
}
1 Initialize the client once for all tests. Give it a URL with serverPort which is the assigned port of the integration test.
2 Issue a GET. The expected body type will be a List of Maps.
3 Verify the status and body elements of the JSON.
4 Send a map of data to the POST, which will be interpreted as JSON.
5 Verify the status and returned data for the test.

Test the app with ./gradlew check.

6 Running the Application

To run the application use the ./gradlew bootRun command which will start the application on port 8080.

7 Help with Grails

OCI sponsored the creation of this Guide. OCI offers several Grails services:

Free consultation

The OCI Grails Team includes Grails co-founders, Jeff Scott Brown and Graeme Rocher. Check our Grails courses and learn from the engineers who developed, matured and maintain Grails.

Grails OCI Team