Introduction

The RxGORM for REST project aims to implement a non-blocking REST client using RxJava, RxNetty and the RxGORM API. RxGORM is an Object mapping technology that has implementations for MongoDB and other databases. In fact RxGORM for REST uses the same Object-to-Document encoding engine used in RxGORM for MongoDB, resulting in high performance encoding and decoding of JSON documents from REST web services.

Currently RxGORM REST Client only supports JSON responses and XML support is not planned at this time.

Getting Started

Installation

RxGORM for REST is designed to be very lightweight, and so can be used in a number of scenarios either standalone or within a Grails application.

Installation with Grails

To use RxGORM for REST within a Grails 3 application add the following dependency to your build.gradle file:

dependencies {
    compile 'org.grails.plugins:rx-gorm-rest-client:1.1.0'
}

Installation without Grails

If you are not developing a Grails application then define the following dependency in your build.gradle or pom.xml:

build.gradle
compile "org.grails:grails-datastore-gorm-rx-rest-client:1.1.0.RELEASE"

With that done you will need to place the following logic in the bootstrap path of your application:

import org.grails.datastore.mapping.core.*
import org.grails.datastore.rx.rest.*
...

PropertyResolver configuration = DatastoreUtils.createPropertyResolver('grails.gorm.rest.readTimeout':1500,
                                                                       'grails.gorm.rest.host':'localhost')
new RxRestDatastoreClient(
            configuration, (1)
            MyClass (2)
)
1 The configuration. This parameter is optional with the default server being localhost. See Settings for available options.
2 One or many classes that implement RxRestEntity

The RxRestDatastoreClient class is required to initialize RxGORM for REST.

Creating Domain Classes

When using RxGORM each entity that you submit to a REST service is known as a domain class.

If you are using RxGORM within Grails you can create a domain class with the create-domain-class command provided by the command line. Alternatively if you are not using Grails simply creating a Groovy class within src/main/groovy of your project will suffice.

A RxGORM for REST domain class must as a minimum implement the RxRestEntity trait. For example:

package example

import grails.gorm.rx.rest.*

class Book implements RxRestEntity<Book> {
    String title
}

If you are not using Grails and have placed the domain class in src/main/groovy you must also annotate the domain class with the Entity annotation:

...
import grails.gorm.annotation.*

@Entity
class Book implements RxRestEntity<Book> {
    ...
}
The type is provided as a generic argument to the RxRestEntity trait. This is important to ensure the correct return signatures are produced for methods provided by the trait.

For more information on domain modelling in GORM, take a look at the GORM user guide documentation on the subject.

Performing CRUD Operations

Create, Read, Update and Delete (or CRUD) in REST applications are typically modelled by the HTTP verbs GET, POST and DELETE. The following examples will work you through how to perform each.

Create - Saving domain instances

To create and save a new instance to a REST web service you should use the save() method:

new Book(title:"The Stand")
        .save()
        .toBlocking()
        .first()

The save() method returns an rx.Observable.

Notice that in this example we are calling toBlocking(), this is purely for demonstration and testing purposes to ensure the operation completes immediately.

In a real world application you would typically subscribe to the observable in order to receive a result:

new Book(title:"The Stand")
        .save()
        .subscribe { Book it ->
    println "Title = ${it.title}"
}
If you don’t call subscribe(..) or convert the Observable into a blocking operation, then no query will be executed! At least one call to subscribe(..) is required.

The Observable will by default submit to the /book URI using an HTTP POST with a content type of application/json using the following body:

{"title":"The Stand"}

You can alter both the URI and the MediaType to submit in the POST request using your domain mapping:

package example

import grails.gorm.rx.rest.*
import grails.http.*

class Book implements RxRestEntity<Book> {
    String title

    static mapping = {
        uri "/books{/id}"
        contentType MediaType.HAL_JSON
    }
}

In the above example a URI is specified that makes the POST request submit to /books instead. URIs are specified using the RFC 6570 URI Template syntax.

The example also alters the MediaType used to instead be application/hal+json. HAL (Hypertext Application Language) is a draft specification for representing JSON responses in REST applications also supported by Grails' JSON Views.

Read - Retrieving domain instances

To retrieve an instance you can use the static get(..) method:

Book.get(id)
    .subscribe { Book it ->
    println "Title = ${it.title}"
}

The get method will issue an HTTP GET request to the /books/id URI and decode the Book from the JSON response. Both HAL and regular JSON responses are supported. To list all the available books you can use the list() method:

Book.list()
    .subscribe { List<Book> books ->
    for(book in books)
        println "Title = ${book.title}"
}

To handle errors you can use the RxJava Observer interface:

observable.subscribe(new Observer<Book>() {
    @Override
    void onCompleted() {
        // when all work is completed
    }

    @Override
    void onError(Throwable e) {
        // when an occur occurs
    }

    @Override
    void onNext(Book book) {
        // when a book is read
    }
})

Update - Updating instances

To update an instance after retrieving it you can use the Observable.switchMap method:

Book.get(id)
    .switchMap() { Book it ->
    it.title = "The Shining"
    return it.save()
}.subscribe { Book updated ->
    println "Book updated!"
}

The switchMap transforms an Observable and converters the result into another Observable.

In this example we first retrieve an instance by id which uses an HTTP GET method, then we call save() on that instance which by default will use an HTTP PUT method to the /books/id URI containing the updated JSON body in full.

If you want to send only the updated properties JSON you can use the patch() method:

book.patch()

The patch method will issue an HTTP PATCH with only the JSON that has changed.

Some REST web services use POST or PATCH instead of PUT for updates. If you want to configure RxGORM to always use another HTTP method you can set the grails.gorm.rest.updateMethod property in your configuration to the method of your choosing.

Delete - Deleting instances

To delete an instance after retrieving it you can use the Observable.switchMap method:

Book.get(id)
    .switchMap() { Book it ->
    return it.delete()
}.subscribe { Boolean wasDeleted ->
    if(wasDeleted) {
        println "Book deleted!"
    }
}

This example once again will issue an HTTP GET method to retrieve an instance and then an HTTP DELETE method to delete it.

If you know ahead of time the identifier of the resource you want to delete you can simply create a new instance and call delete() to issue an HTTP DELETE:

Book book = new Book()
book.id = 1L
book.delete().subscribe { Boolean wasDeleted ->
    if(wasDeleted) {
        println "Book deleted!"
    }
}
Using the constructor syntax new Book(id:1L) will not work in a Grails application as Grails will prevent binding the id via the constructor for security reasons.

Configuration

All of the configuration options for RxGORM for REST can be found within the Settings class.

Basic Configuration

Server Host

You can configure the server host to connect to when issuing HTTP requests using the grails.gorm.rest.host setting in grails-app/conf/application.yml:

grails:
    gorm:
        rest:
            host: http://localhost:8080

For environment specific configuration you can nest the configuration within each environment block:

environments:
    development:
        grails:
            gorm:
                rest:
                    host: http://localhost:8080
    production:
        grails:
            gorm:
                rest:
                    host: http://myapiserver.com

Read Timeout

You can configure the default read timeout (in milliseconds) using the grails.gorm.rest.readTimeout setting:

grails:
    gorm:
        rest:
            host: http://localhost:8080
            readTimeout: 2000

Wire Logging

To enable RxNetty wire logging you first need to set the log level using the grails.gorm.rest.logLevel setting:

grails:
    gorm:
        rest:
            host: http://localhost:8080
            readTimeout: 2000
            log:
                level: TRACE

Then ensure your log configuration enables logging for the grails.gorm.rx.rest.logging package. For example in logback.groovy:

logger("grails.gorm.rx.rest.logging", TRACE)

Single Host Connection Pooling

With a single host configuration RxGORM for REST will create a connection pool for that single host.

The connection pool is defined by a io.reactivex.netty.client.pool.PoolConfig instance.

The properties of this instance can configured via the grails.gorm.rest.pool.options property. For example:

grails:
    gorm:
        rest:
            host: http://localhost:8080
            pool:
                options:
                    maxConnections: 20

Client Side Load Balancing

If you wish to use client side load balancing between multiple servers you can use the grails.gorm.rest.hosts option and specify multiple server hosts:

grails:
    gorm:
        rest:
            hosts:
                - http://one.myapiserver.com
                - http://two.myapiserver.com

The default load balancing strategy is EWMABasedP2CStrategy. If you which to specify an alternate strategy you can do so with the grails.gorm.rest.loadBalanceStrategy property.

Working with Domain Classes

When using RxGORM for REST simple types will be converted to their JSON equivalents during the encoding process. For types that cannot be represented in JavaScript such as BigDecimal these will be converted to a JSON String type.

JSON arrays can be represented with the List type:

package example

import grails.gorm.rx.rest.*

class Book implements RxRestEntity<Book> {
    String title
    List<String> authors
}

Nested JSON objects can be represented using the Map type:

package example

import grails.gorm.rx.rest.*

class Book implements RxRestEntity<Book> {
    String title
    Map<String, String> attributes
}

Custom Types

RxGORM for REST is built ontop of MongoDB’s JSON encoding framework. This means it is possible to implement custom Codecs for encoding and decoding values to and from JSON.

For example consider the following simple Groovy class:

class Birthday {
    Date date
}

By default the encoding engine does not know how to represent this type as a JSON value. To make the encoding engine understand this type you have to implement a custom codec:

import org.bson.*
import org.bson.codecs.*

class BirthdayCodec implements Codec<Birthday> {
    Birthday decode(BsonReader reader, DecoderContext decoderContext) {
        return new Birthday(date: new Date(reader.readDateTime())) (1)
    }
    void encode(BsonWriter writer, Birthday value, EncoderContext encoderContext) {
        writer.writeDateTime(value.date.time) (2)
    }
    Class<Birthday> getEncoderClass() { Birthday } (3)
}
1 Decodes the Birthday type from the BsonReader
2 Encodes the Birthday type to the BsonWriter
3 Returns the type that is to be encoded. In this case Birthday.

With that done you then need to register the custom Codec. There are two ways to achieve this.

You can register a list of codecs in the grails.gorm.rest.codecs setting in application.yml:

grails:
    gorm:
        rest:
            codecs:
                - my.company.BirthdayCodec

Or you can create a META-INF/services/org.bson.Codec file containing the class name of the Codec

Identity Generation

By default it is assumed that numeric identifiers will be used for REST resources. This means that when you save an entity:

Book book = new Book(title:"The Stand")
book.save()

A HTTP POST request will be sent to the URI the Book maps to. If the REST endpoint returns JSON then the JSON will be read and the id field populated with the value from the server.

If the id of your REST endpoint is not numeric or a different property then you can control that via the mapping block:

import static grails.gorm.rx.rest.mapping.MappingBuilder.*

@Entity
class Product implements RxRestEntity<Product> {

    String name

    static mapping = endpoint {
        id {
            name "name"
            generator "assigned"
        }
    }
}

In this case we have configured the id of the REST resource to be the name property of the entity. Now if you create an entity and save it:

Product product = new Product(name:"MacBook")
product.save()

RxGORM for REST will send a PUT request to the URI /product/MacBook. This is because RxGORM assumes that if there is an id then what you are trying to do is an update. To instead perform a POST request to the /product URI you must use the insert() method instead which forces saving of a new entity:

Product product = new Product(name:"MacBook")
product.insert()

URI Templates

As mentioned previously, URIs are specified using the RFC 6570 URI Template syntax. A URI template can contain any properties of a domain class. For example consider the following domain class:

package example

import grails.gorm.rx.rest.*
import static grails.gorm.rx.rest.mapping.MappingBuilder.*

class Book implements RxRestEntity<Book> {
    String title
    String author

    static mapping = endpoint {
        id {  name "title" }
        uri "/authors/{author}/books{/title}"
    }
}

In this example when saving the entity:

new Book(title:"It", author:"Stephen King").save()

A PUT request will be issued to the URI /authors/Stephen%20King/books/It as the properties of the domain are expanded into the URI template. To issue a POST request you can do:

new Book(title:"It", author:"Stephen King").insert()

A POST request will be sent to the URI /authors/Stephen%20King/books or you can choose whichever method you prefer to use with:

import static grails.http.HttpMethod.*

new Book(title:"It", author:"Stephen King").save(method:PATCH)

To perform a query you can do the following:

Observable<Book> observable = Book.find {
    title == "It" && author == "Stephen King"
}

And a GET request will be issued to /authors/Stephen%20King/books/It.

If you wish to alter the URI template for only a single write operation or query you can do so by passing the uri argument:

// write operation
new Book(title:"It", author:"Stephen King").insert(uri:"/authors/{author}/books{/title}")

// query
Observable<Book> observable = Book.where {
    title == "It" && author == "Stephen King"
}.find(uri:"/authors/{author}/books{/title}")

Converting to and from JSON

You can convert any domain instance to a JSON string with to toJson() method:

Book book = new Book(title:"The Stand")
println book.toJson()

You can also pass a java.io.Writer to the method if you wish to write the JSON to a specify target writer:

Book book = new Book(title:"The Stand")
StringWriter writer = new StringWriter()
book.toJson(writer)
println writer.toString()

You can read an instance from JSON using the fromJson static method:

String json = ...
Book book = Book.fromJson(json)

A variation of the method accepts a java.io.Reader to read JSON from an alternate source:

Reader jsonReader = ...
Book book = Book.fromJson(jsonReader)

Dynamic Attributes

All RxRestEntity instances also implement the DynamicAttributes trait which allows you to assign arbitrary values to the object using Groovy’s subscript operator:

Book book = new Book(title:"The Stand")
book['pages'] = 1000

These will be encoded into the JSON that is sent to the server. Any JSON attributes retrieved from the server that are not declared by the domain class are also available via the subscript operator:

println book['pages']

Customizing the Request

Each method that issues an HTTP request also accepts a Closure which delegates to the HttpClientRequestBuilder class and allows customization of the outgoing request. For example:

import static grails.http.HttpHeader.*

Observable<Book> observable = new Book(title: "The Stand").save {
    header ACCEPT_CHARSET, "UTF-8"
}

The above example adds an additional ACCEPT_CHARSET header to the request. The same same technique can be used with static methods:

import static grails.http.HttpHeader.*

Observable<Book> observable = Book.get(1) {
    header ACCEPT_VERSION, "v1.0.0"
}

Another common use case is to add BASIC auth credentials:

Observable<Book> observable = Book.get(1) {
    auth "myusername", "mypassword"
}

Request Interceptors

As an alternative to passing Closure to each invocation you can register RequestInterceptor instances. For example:

package example

import grails.gorm.rx.rest.interceptor.*

class AuthInterceptor extends RequestBuilderInterceptor {
    @Override
    Closure build(HttpClientRequest request, InterceptorContext context )
        buildRequest {
            auth System.getenv('SERVICE_USERNAME'), System.getenv('SERVICE_PASSWORD')
        }
    }
}

In the example above the credentials for BASIC auth are ready from environment variables.

If you wish to configure BASIC auth environment variables globally you can do so with the grails.gorm.rest.username and grails.gorm.rest.password settings in grails-app/conf/application.yml

The interceptor can then either be passed as an argument to write operation or query:

// write operation with interceptor
book.save(interceptor: new AuthInterceptor())

// query with interceptor
Observable<Book> o = Book.get(1L, [interceptor: new AuthInterceptor()])

Or you can register an interceptor globally by defining the grails.gorm.rest.interceptors in grails-app/conf/application.yml:

grails:
    gorm:
        rest:
            interceptors: example.AuthInterceptor

Or alternatively you can create a META-INF/services/grails.gorm.rx.rest.interceptor.RequestInterceptor file containing the class name of the RequestInterceptor and it will be picked up automatically.

Batch Updates

You can perform multiple HTTP write operations (POST or DELETE etc.) by using either the saveAll or deleteAll methods:

// multiple POST operations
Observable observable = Book.saveAll(
    new Book(title:"The Stand"),
    new Book(title:"The Shining")
)

// multiple DELETE operations
Observable observable = Book.deleteAll(
    book1,
    book2
)

These methods will return an Observable that only completes when all the operations have been successful.

Modelling Associations

RxGORM for REST supports defining associations between REST endpoints and adds some additional integration with HAL (Hypertext Application Language) to automatically interpret the HAL _embedded and _links attributes.

Embedded

Embedded associations are supported for both entity and collection types. Simply declare the assocation as embedded:

package example

import grails.gorm.rx.rest.*

class Book implements RxRestEntity<Book> {
    String title

    Author author
    List<Author> otherAuthors

    static embedded = ['author', 'otherAuthors']
}

class Author implements RxRestEntity<Author> {
    String name
}

When JSON is sent back from the server the embedded association will be decoded from the JSON:

{"title":"The Stand", "author":{"name":"Stephen King"}}

Single Ended Associations

Single-ended associations can be represented by simply declaring the associated type as a property of the domain class:

package example

import grails.gorm.rx.rest.*

class Book implements RxRestEntity<Book> {
    String title

    Author author
}

class Author implements RxRestEntity<Author> {
    String name
}

When a JSON response is returned that contains only the association id then an ObservableProxy is returned. For example given the following JSON response:

{"title":"The Stand", "author":1}

An ObservableProxy that executes a GET request to the /author/1 URI will be returned. This allows you to subscribe the association in order to read it lazily:

Book.get(1).subscribe { Book book ->
    book.author.subscribe { Author author ->
        println "got author $author.name"
    }
}

If you wish to load both the Book and the Author there are a couple of ways to achieve that. One way is to use the switchMap and zip methods to load both in a non-blocking manner:

Book.get(1).switchMap { Book book ->
    Observable.zip( Observable.just(book), book.author.toObservable()) { Book b1, Author author ->
        return [b1, author]
    }
}

Alternatively a much simpler way is to use an eager query:

Book.get(1, [fetch:[author:'eager']).subscribe { Book book ->
    println "Title $book.title"
    println "Author $book.author.name"
}

Conversely if the JSON returned from the server includes the author embedded within the JSON, then no ObservableProxy will be created and instead the association will be loaded fully initialized. For example the following JSON will produce an initialized entity:

{"title":"The Stand", "author":{"id":1, "name":"Stephen King"}}

Or if you are using HAL the following JSON will also work:

{"_embedded":{"author":{"id":1, "name":"Stephen King"}},"title":"The Stand"}

You can alter the URI Template used by RxGORM to load the association using the mapping block:

package example

import grails.gorm.rx.rest.*
import static grails.gorm.rx.rest.mapping.MappingBuilder.*

class Book implements RxRestEntity<Book> {
    String title

    Author author

    static mapping = endpoint {
        authors property {
            uri '/authors/{name}'
        }
    }
}

In the above case the name property of the Author will be used to load the association instead. You can also override the URI used to load an association from the server by sending back HAL links:

{"_links":{"author":{"href":"/authors/{name}"},"title":"The Stand"}

In this case RxGORM will create an ObservableProxy to load the author from the /author/{name} URI template.

If you define a single-ended association using the hasOne syntax then the foreign key is assumed to always exist on the inverse side of the association:

package example

import grails.gorm.rx.rest.*

class Book implements RxRestEntity<Book> {
    String title

    static hasOne = [author:Author]
}

class Author implements RxRestEntity<Author> {
    String name
    static belongsTo = [book:Book]
}

In this case by default a URI template of /book/{book}/author will be created as it is assumed that the /book resource wholly owns the author and therefore it is nested within it. You can alter how the URI maps using the mapping block:

package example

import grails.gorm.rx.rest.*

class Book implements RxRestEntity<Book> {
    String title

    static hasOne = [author:Author]

    static mapping = endpoint {
        authors property {
            uri '/books/{book}/info/author'
        }
    }
}
Within the URI Template for the association you can include any properties of the Book class.

Many Ended Associations

Only bidirectional many ended associations are supported. To define an association you can use the hasMany syntax:

package example

import grails.gorm.rx.rest.*

class Book implements RxRestEntity<Book> {
    String title

    static hasMany = [authors:Author]
}

class Author implements RxRestEntity<Author> {
    String name
    static belongsTo = [book:Book]
}

In this case by default a URI template of /book/{book}/authors will be created as it is assumed that the /book resource wholly owns the authors and therefore it is nested within it. You can alter how the URI maps using the mapping block:

package example

import grails.gorm.rx.rest.*
import static grails.gorm.rx.rest.mapping.MappingBuilder.*

class Book implements RxRestEntity<Book> {
    String title

    static hasMany = [authors:Author]

    static mapping = endpoint {
        authors property {
            uri '/books/{book}/info/authors'
        }
    }
}
Within the URI Template for the association you can include any properties of the Book class.

When an entity is retrieved all many ended associations are lazy by default and return an instance of RxPersistentCollection which allows you to subscribe to the collection to load it in a non-blocking manner:

Book.get(1).subscribe { Book book ->
    book.authors.subscribe { Author author ->
        println "got author $author.name"
    }
}

Lazy Loading

As mentioned previously you can retrieve any lazy association using an eager query:

// query by id
Book.get(1, [fetch:[authors:'eager']).subscribe { Book book ->
    println "Title $book.title"
    println "Authors $book.authors"
}

// query an alternative URI
Book.where {
    title == "The Stand"
}.find(uri:'/books/byTitle/{title}', fetch:[authors:'eager'])
 .subscribe { Book book ->
      println "Title $book.title"
      println "Authors $book.authors"
}

You can however also configure an association to be eager by default:

package example

import grails.gorm.rx.rest.*
import static grails.gorm.rx.rest.mapping.MappingBuilder.*

class Book implements RxRestEntity<Book> {
    String title

    static hasMany = [authors:Author]

    static mapping = endpoint {
        authors property {
            fetch 'eager'
        }
    }
}

Additionally, if the server sends back the association embedded within the JSON then no lazy loading is necessary.

Finally, some REST APIs support the notion of "resource expansion", for example Grails' JSON views support an expand request parameter. You can send the expand parameter to the server using the expand argument to any query:

// query by id
Book.get(1, [expand:'authors']).subscribe { Book book ->
    println "Title $book.title"
    println "Authors $book.authors"
}

// query an alternative URI
Book.where {
    title == "The Stand"
}.find(uri:'/books/byTitle/{title}',[expand:'authors'])
 .subscribe { Book book ->
      println "Title $book.title"
      println "Authors $book.authors"
}

In this example a parameter called expand will be appended to the request URI in the form ?expand=authors. If you which to expand multiple associations then multiple parameters will be added, for example ?expand=authors&expand=publisher

By default the name of the expand parameter is expand but you can change this by setting grails.gorm.rest.parameters.expand in application.yml

Querying with RxGORM

Generally in REST applications the URI is the query. So to formulate queries the most typically pattern is pass the uri argument to the query.

For example say we you want to add a query to obtain the latests books. That can be done quite simply by altering the URI passed to the findAll method:

package example

import grails.gorm.rx.rest.*

class Book implements RxRestEntity<Book> {
    String title


    static Observable<Book> latestBooks(Map arguments = [:]) {
        arguments.uri = "/books/latest"
        return findAll(uri)
    }
}

...
Book.latestBooks().subscribe { Book book ->
    println "Title $book.title"
}

If the REST endpoint is parameterized you can use URI Templates to define the query:

package example

import grails.gorm.rx.rest.*

class Book implements RxRestEntity<Book> {
    String title


    static Observable<Book> latestBooks(String category, Map arguments = [:]) {
        arguments.uri = "/books/{category}/latest"
        return where {
            eq 'category', category
        }.findAll(arguments)
    }
}

...
Book.latestBooks("Fiction").subscribe { Book book ->
    println "Title $book.title"
}

In the above example a GET request to /books/Fiction/latest will be produced. You can include any additional parameters not declared in the URI template, for example:

Book.where {
    eq 'category', 'Fiction'
    eq 'foo', 'bar'
}.findAll(uri:"/books/{category}/latest")

The above example will produce a GET request to /books/Fiction/latest?foo=bar.

You can also control pagination and sorting. For example:

Book.findAll(max:10, offset:10, sort:"title", order:"desc")

Results in a query such as /books?max=10&offset=10&sort=title&order=desc.

The names of the pagination paremeters are configurable. See the Configuration Options section for more information.

MongoDB-style Queries

When using the URI, only "equals" style queries are supported and other conditional operators (greater than, less than etc.) are not supported.

If you are implementing a search endpoint you may need more richness in the querying ability. One option to implement this is to use a MongoDB-style BSON query.

For example:

Book.where {
    author == "Stephen King" && pages > 500
}.findAll(queryType:"bson", uri:"/books/search")

This will produce a query to the URI /books/search passing a parameter called q with the following URL encoded JSON:

{"author:"Stephen King", "pages":{"$gt":500}}

If you are using MongoDB on the server you can use this to perform a query:

import org.bson.*

def search(String q) {
    Document query = Document.parse(params.q)
    respond Book.collection.find(q)
}

If you are not using MongoDB on the server you can use the org.grails.datastore.bson.query.BsonQuery class to parse the query into DetachedCriteria instance:

import org.grails.datastore.bson.query.*

def search(String q) {
    DetachedCriteria<Book> criteria = BsonQuery.parse(Book, new BsonReader(new JsonReader(params.q)))
    respond criteria.list()
}

Testing

RxGORM for REST is designed to be very lightweight, so it is easy to spin up in a unit test. A specialized org.grails.datastore.rx.rest.test.TestRxRestDatastoreClient class exists to help make testing even easier.

To use the TestRxRestDatastoreClient in Spock add a field to your Spock specification declared with AutoCleanup:

import spock.lang.*
import org.grails.datastore.rx.rest.test.TestRxRestDatastoreClient

class BookSpec extends Specification {
    @AutoCleanup TestRxRestDatastoreClient client

     void setup() {
        client = new TestRxRestDatastoreClient(Book)
     }
}

The TestRxRestDatastoreClient class with create a test HTTP server running on an ephemeral port.

You can then use the test client to create expectations within your test methods:

import static grails.http.MediaType.*
...

given:"A Test Client"
def mock = client.expect { (1)
    uri '/book/1' (2)
    accept(JSON) (3)
}
.respond { (4)
    json { (5)
        id 1
        title "The Stand"
    }
}
1 Use the expect method to define expectations for the request
2 Here we specify that a uri of /book/1/ is expected
3 An HTTP Accept header containing application/json is expected
4 Here we define the mocked response from the server using the respond method
5 The respond method defines the JSON using StreamingJsonBuilder

Then you can simply use the regular RxGORM API:

when:"A get request is issued"
Observable<Book> observable = Book.get(1)
Book book = observable.toBlocking().first()

And finally verify the expectations:

then:"The response is correct"
mock.verify()
book.title == "The Stand"

For testing JSON requests and responses the toJson method can come in handy. For example to test a POST request you can use an existing instance as test data:

import static grails.http.MediaType.*
import static grails.http.HttpMethod.*
...

...

given:"A POST request"
Book book = new Book(title:"The Stand") (1)
def mock = client.expect {
    uri '/book' (2)
    method POST (3)
    json book.toJson() (4)
}
.respond {
    created() (5)
    json { (6)
        id 1
    }
}
1 Create a test instance
2 Verify the uri is /book
3 Verify the HTTP method is POST
4 Verify the JSON sent to the server is the same as the JSON produced from converting the Book
5 Respond with an HTTP 201 status of CREATED
6 Send the id back as JSON

Then perform a POST:

when:"A get request is issued"
Observable<Book> observable = new Book(title:"The Stand").save()
Book book = observable.toBlocking().first()

And then verify the expectations:

then:"The response is correct"
mock.verify()
book.id == 1L
book.title == "The Stand"

Simple HTTP Client

RxGORM for REST includes a low level client in the form of the RxHttpClientBuilder class that can be used to fulfill use cases that don’t warrant the creation of a domain class.

The most simple use case for obtaining content can be seen below:

import grails.http.client.*
...
RxHttpClientBuilder client = new RxHttpClientBuilder()
Observable<RxHttpClientBuilder> p = client.post("https://localhost:8080/foo/bar") {
    contentType 'application/json'
    json {
        title "Ping"
    }
}
p.toJson().subscribe { Object json ->
    assert json.title == 'Pong'
}

If you are using the Grails plugin then you do not need to instantiate a new instance and can instead just autowire the existing instance into your controller:

@Autowired
RxHttpClientBuilder httpClient

Sending & Receiving JSON

To send JSON you can use the json method which uses an instance of Groovy’s StreamingJsonBuilder:

Observable<HttpClientResponse> p = client.post("https://localhost:8080/foo/bar") {
    contentType 'application/json'
    json {
        title "Hello World"
    }
}

The json method is defined within the HttpMessageBuilder which allows you to build JSON with StreamingJsonBuilder in a variety of ways:

json [1,2,3] // a JSON array
json title:"Hello World" // a map

To read a JSON response use the toJson method property of the HttpClientResponse:

p.toJson().subscribe { json ->
    assert json.title == 'Pong'
}

Sending & Receiving XML

To send XML you can use the xml method which uses an instance of Groovy’s StreamingMarkupBuilder:

Observable<HttpClientResponse> p = client.post("https://localhost:8080/foo/bar") {
    xml {
        message {
            title "Ping"
        }
    }
}

The xml method is defined within the HttpMessageBuilder class.

To read an XML response use the toXml method of the HttpClientResponse, which returns a Groovy GPathResult:

p.toXml().subscribe { GPathResult xml ->
    assert xml.message.text() == 'Pong'
}

Reference

Configuration Options

The following configuration options are available:

  • grails.gorm.rest.host - The default host to connect to

  • grails.gorm.rest.hosts - The hosts to connect to in a load balanced configuration

  • grails.gorm.rest.charset - The character encoding to use

  • `grails.gorm.rest.readTimeout - The default read timeout

  • grails.gorm.rest.logLevel - The default wire log level

  • grails.gorm.rest.updateMethod - The default HTTP method to use for updates. Defaults to PUT

  • grails.gorm.rest.pool.options - The configuration options to create the io.reactivex.netty.client.pool.PoolConfig with (only applicable to single host configurations).

  • grails.gorm.rest.username - The username to ues for BASIC auth

  • grails.gorm.rest.password - The password to use for BASIC auth

  • grails.gorm.rest.interceptors - The RequestInterceptor instances to use

  • grails.gorm.rest.query.type - The type of query implementation to use. Either "simple" or "bson"

  • grails.gorm.rest.parameters.order - The name of the parameter used to send the order (descending or ascending) to the server

  • grails.gorm.rest.parameters.expand - The name of the parameter used to expand nested resources

  • grails.gorm.rest.parameters.sort - The name of the parameter used to send the property to sort by to the server

  • grails.gorm.rest.parameters.max - The name of the parameter used to send to restrict the maximum number of results

  • grails.gorm.rest.parameters.offset - The name of the parameter used to send to provide the offset for pagination

  • grails.gorm.rest.parameters.query - The name of the parameter used to send the query when using "bson" query type