Yesterday I left you with rough code that started a Ratpack server and used a synchronous Couchbase repository, leaving the RxJava part out. I intend to fix that today, and also tell you more about Ratpack. I am not going to show you a complete REST API yet, but we'll get there 🙂

In my previous example, I was instantiating a new Repository object each time I needed one. Because of the way it was done, It also opened a new Cluter connection each time. There are ways you can avoid this with Ratpack, like the registry.

The Ratpack Registry

The ratpack registry is a store of objects available from the context of a handler. By default you get an empty registry. You can add object instances in. These objects are retrieved by type. They cannot be named. It means you can only have one object per type in your registry. It might seem weird but it's actually really usful when coding in Groovy. Take a look at this example for instance. It's just so easy to read.

In my code I am only using the Repository object to access Couchbase so that's the one I will put in the registry:

public static void main(String... args) throws Exception { RatpackServer.start(server -> server .registryOf(registry ->registry.add(CouchbaseCluster.create().openBucket().repository())) .handlers(chain -> chain.path("create", ctx -> { EntityDocument<User> document = EntityDocument.create(new User("ldoguin", 31, "Laurent", "Doguin")); Blocking.get(() -> { Repository repo = ctx.get(Repository.class); return repo.upsert(document); }).then(entityDoc -> ctx.render("OK")); }).path("get", ctx -> { Blocking.get(() -> { Repository repo = ctx.get(Repository.class); EntityDocument<User> ldoguin = repo.get("ldoguin", User.class); return ldoguin; }).then(user -> ctx.render(user.content().toString())); }))); } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 public static void main ( String . . . args ) throws Exception { RatpackServer . start ( server - > server . registryOf ( registry - > registry . add ( CouchbaseCluster . create ( ) . openBucket ( ) . repository ( ) ) ) . handlers ( chain - > chain . path ( "create" , ctx - > { EntityDocument < User > document = EntityDocument . create ( new User ( "ldoguin" , 31 , "Laurent" , "Doguin" ) ) ; Blocking . get ( ( ) - > { Repository repo = ctx . get ( Repository . class ) ; return repo . upsert ( document ) ; } ) . then ( entityDoc - > ctx . render ( "OK" ) ) ; } ) . path ( "get" , ctx - > { Blocking . get ( ( ) - > { Repository repo = ctx . get ( Repository . class ) ; EntityDocument < User > ldoguin = repo . get ( "ldoguin" , User . class ) ; return ldoguin ; } ) . then ( user - > ctx . render ( user . content ( ) . toString ( ) ) ) ; } ) ) ) ; }

As you can see retrieving an object from the registry is easy. You have to call the get method and give the type of the object as parameter.

This works quite well for really lightweight application. It can get a lot more serious using the Guice module.

Guice Modules

Ratpack is not only a simple async,non-blocking web server. It's also a comprehensive sets of modules allowing you to elegantly add new features. The registry can be easily extended using a DI framework. The most commonly used in Ratpack is Guice but you can use Spring or any other supported one. To activate the Guice module, you need to add one line in your build.gradle file in the dependency:

dependencies { compile ratpack.dependency("guice"), "com.couchbase.client:java-client:2.2.5" } 1 2 3 4 5 dependencies { compile ratpack . dependency ( "guice" ) , "com.couchbase.client:java-client:2.2.5" }

Ratpack has a great Gradle integration. Every Ratpack modules can be added as simply as that. Now that Guice support is enabled, I can start by adding a proper registry configuration. I will add a Bucket and Repository instances to my registry. The first step is to create a Guice module by adding a class that extends Guice's AbstractModule.

public class Config extends AbstractModule { protected void configure() { CouchbaseCluster cc = CouchbaseCluster.create(); Bucket bucket = cc.openBucket(); bind(Bucket.class).toInstance(bucket); bind(Repository.class).toInstance(bucket.repository()); } } 1 2 3 4 5 6 7 8 9 10 public class Config extends AbstractModule { protected void configure ( ) { CouchbaseCluster cc = CouchbaseCluster . create ( ) ; Bucket bucket = cc . openBucket ( ) ; bind ( Bucket . class ) . toInstance ( bucket ) ; bind ( Repository . class ) . toInstance ( bucket . repository ( ) ) ; } }

Once this class is created, I need to add it to my Ratpack registry like so:

RatpackServer.start(server -> server.registry(Guice.registry(b -> b.module(Config.class))) 1 2 RatpackServer . start ( server - > server . registry ( Guice . registry ( b - > b . module ( Config . class ) ) )

And now I have a cleaner configuration for my future instance injections. But everything is still synchronous. So I will change that configuration to use the async version of the Bucket and the Repository:

public class Config extends AbstractModule { protected void configure() { CouchbaseCluster cc = CouchbaseCluster.create(); Bucket bucket = cc.openBucket(); bind(AsyncBucket.class).toInstance(bucket.async()); bind(AsyncRepository.class).toInstance(bucket.repository().async()); } } 1 2 3 4 5 6 7 8 9 10 public class Config extends AbstractModule { protected void configure ( ) { CouchbaseCluster cc = CouchbaseCluster . create ( ) ; Bucket bucket = cc . openBucket ( ) ; bind ( AsyncBucket . class ) . toInstance ( bucket . async ( ) ) ; bind ( AsyncRepository . class ) . toInstance ( bucket . repository ( ) . async ( ) ) ; } }

The next step here is to make sure I can use my RxJava Observables in Ratpack. And because life is beautiful(some would say bootiful), there is a module for that.

RxJava Module

The RxJava module aims at creating a bridge between Rx Observables and Ratpack Promises. As the Guice module, it can be activated easily by adding the appropiate dependency:

dependencies { compile ratpack.dependency("guice"), ratpack.dependency("rx"), "com.couchbase.client:java-client:2.2.5" } 1 2 3 4 5 6 dependencies { compile ratpack . dependency ( "guice" ) , ratpack . dependency ( "rx" ) , "com.couchbase.client:java-client:2.2.5" }

You also need to initialize the module once per JVM. So the best place to run the initialization here is before the Ratpack server init. This can be done by calling:

RxRatpack.initialize(); 1 2 RxRatpack . initialize ( ) ;

The RxRatpack object has static methods to do Observable/Promise conversion. The simplest one works like this:

RxRatpack.promise(repo.upsert(document)).then(entityDoc -> ctx.render("OK")); 1 2 RxRatpack . promise ( repo . upsert ( document ) ) . then ( entityDoc - > ctx . render ( "OK" ) ) ;

It's worth mentioning that because an Observable can contain zero to any elements, it's transformed as a List when given to the Promise result. So if you have only one object in your Observable you need to explicitly get the first element of the list.

public static void main(String... args) throws Exception { RxRatpack.initialize(); RatpackServer.start(server -> server.registry(Guice.registry(b -> b.module(Config.class))) .handlers(chain -> chain.path("create", ctx -> { EntityDocument<User> document = EntityDocument.create(new User("ldoguin", 31, "Laurent", "Doguin")); AsyncRepository repo = ctx.get(AsyncRepository.class); RxRatpack.promise(repo.upsert(document)).then(entityDoc -> ctx.render("OK")); }).path("get", ctx -> { AsyncRepository repo = ctx.get(AsyncRepository.class); RxRatpack.promise(repo.get("ldoguin", User.class)) .then(users -> ctx.render(users.get(0).content().toString())); }))); } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 public static void main ( String . . . args ) throws Exception { RxRatpack . initialize ( ) ; RatpackServer . start ( server - > server . registry ( Guice . registry ( b - > b . module ( Config . class ) ) ) . handlers ( chain - > chain . path ( "create" , ctx - > { EntityDocument < User > document = EntityDocument . create ( new User ( "ldoguin" , 31 , "Laurent" , "Doguin" ) ) ; AsyncRepository repo = ctx . get ( AsyncRepository . class ) ; RxRatpack . promise ( repo . upsert ( document ) ) . then ( entityDoc - > ctx . render ( "OK" ) ) ; } ) . path ( "get" , ctx - > { AsyncRepository repo = ctx . get ( AsyncRepository . class ) ; RxRatpack . promise ( repo . get ( "ldoguin" , User . class ) ) . then ( users - > ctx . render ( users . get ( 0 ) . content ( ) . toString ( ) ) ) ; } ) ) ) ; }

What has changed since the previous version is the use of a Guice based registry for DI and the Couchbase calls are now all asynchronous. I am now in a good position to start creating a proper REST API for my users. And this is the topic for another blog post.