couchbase

couchbase

Couchbase is a NoSQL document database with a distributed architecture for performance, scalability, and availability. It enables developers to build applications easier and faster by leveraging the power of SQL with the flexibility of JSON.

This module provides couchbase access via Java SDK

dependency

<dependency>
 <groupId>org.jooby</groupId>
 <artifactId>jooby-couchbase</artifactId>
 <version>1.5.1</version>
</dependency>

exports

  • CouchbaseEnvironment
  • CouchbaseCluster
  • AsyncBucket
  • Bucket
  • AsyncDatastore
  • Datastore
  • AsyncRepository
  • Repository
  • Optionally a couchbase Session.Store

usage

Via couchbase connection string:

{
  use(new Couchbase("couchbase://locahost/beers"));

  get("/", req -> {
    Bucket beers = require(Bucket.class);
    // do with beer bucket
  });
}

Via property:

{
  use(new Couchbase("db"));

  get("/", req -> {
    Bucket beers = require(Bucket.class);
    // do with beer bucket
  });
}

The db property is defined in the application.conf file as a couchbase connection string.

create, read, update and delete

Jooby provides a more flexible, easy to use and powerful CRUD operations via AsyncDatastore/AsyncDatastore objects.

import org.jooby.couchbase.Couchbase;
import org.jooby.couchbase.N1Q;
...
{
  use(new Couchbase("couchbase://localhost/beers"));

  use("/api/beers")
    /**
     * List beers 
     */
    .get(req -> {
      Datastore ds = require(Datastore.class);
      return ds.query(N1Q.from(Beer.class));
    })
    /**
     * Create a new beer 
     */
    .post(req -> {
      Datastore ds = require(Datastore.class);
      Beer beer = req.body().to(Beer.class);
      return ds.upsert(beer);
    })
    /**
     * Get a beer by ID 
     */
    .get(":id", req -> {
      Datastore ds = require(Datastore.class);
      return ds.get(Beer.class, req.param("id").value());
    })
    /**
     * Delete a beer by ID 
     */
    .delete(":id", req -> {
      Datastore ds = require(Datastore.class);
      return ds.delete(Beer.class, req.param("id").value());
    });
}

As you can see benefits over AsyncRepository/Repository are clear: you don’t have to deal with EntityDocument just send or retrieve POJOs.

Another good reason is that AsyncDatastore/AsyncDatastore supports query operations with POJOs too.

design and implementation choices

The AsyncDatastore/AsyncDatastore simplifies a lot the integration between Couchbase and POJOs. This section describes how IDs are persisted and how mapping works.

A document persisted by an AsyncDatastore/AsyncDatastore looks like:

{
  "model.Beer::1": {
    "name": "IPA",
    ...
    "id": 1,
    "_class": "model.Beer"
  }
}

The couchbase document ID contains the fully qualified name of the class, plus :: plus the entity/business ID: mode.Beer::1.

The business ID is part of the document, here the business ID is: id:1. The business ID is required while creating POJO from couchbase queries.

Finally, a _class attribute is also part of the document. It contains the fully qualified name of the class and its required while creating POJO from couchbase queries.

mapping pojos

Mapping between document/POJOs is done internally with a custom EntityConverter. The EntityConverter uses an internal copy of ObjectMapper object from Jackson. So in theory anything that can be handle by Jackson will work.

In order to work with a POJO, you must defined an ID. There are two options:

  • Add an id field to your POJO:

public class Beer {
  private String id;
}
  • Use a business name (not necessarily id) and add Id annotation:
import import com.couchbase.client.java.repository.annotation.Id;
...
public class Beer {

  @Id
  private String beerId;
}

Auto-increment IDs are supported via GeneratedValue:

public class Beer {
  private Long id;
}

Auto-increment IDs are generated using {@link Bucket#counter(String, long, long)} function and they must be Long. We use the POJO fully qualified name as counter ID.

Any other field will be mapped too, you don’t need to annotate an attribute with {@link Field}. If you don’t want to persist an attribute, just ad the transient Java modifier:

public class Beer {
  private String id;

  private transient ignored;
}

Keep in mind that if you annotated your POJO with Jackson annotations they will be ignored… because we use an internal copy of Jackson that comes with Java Couchbase SDK

reactive usage

Couchbase SDK allows two programming model: blocking and reactive. We already see how to use the blocking API, now is time to see how to use the reactive API:

{
  use(new Couchbase("couchbase://localhost/beers"));

  get("/", req -> {
    AsyncBucket bucket = require(AsyncBucket.class);
    // do with async bucket ;)
  });
}

Now, what to do with Observables? Do we have to block? Not necessarily if we use the Rx module:

...
import org.jooby.rx.Rx;
...
{
  // handle observable route responses
  use(new Rx());

  use(new Couchbase("couchbase://localhost/beers"));

  get("/api/beer/:id", req -> {

    AsyncDatastore ds = require(AsyncDatastore.class);
    String id = req.param("id").value();
    Observable<Beer> beer = ds.get(Beer.class, id);
    return beer;
  });
}

The Rx module deal with observables so you can safely return Observable from routes (Jooby rocks!).

multiple buckets

If for any reason your application requires more than 1 bucket… then:

{
  use(new Couchbase("couchbase://localhost/beers")

    .buckets("extra-bucket"));
  get("/", req -> {

    Bucket bucket = require("beers", Bucket.class);
    Bucket extra = require("extra-bucket", Bucket.class);
  });
}

Easy, right? Same principle apply for Async* objects

multiple clusters

Again, if for any reason your application requires multiple clusters… then:

{
  CouchbaseEnvironment env = ...;

  use(new Couchbase("couchbase://192.168.56.1")
     .environment(env));

  use(new Couchbase("couchbase://192.168.57.10")
     .environment(env));
}

You must shared the CouchbaseEnvironment as documented here.

options

bucket password

You can set a global bucket password via: couchbase.bucket.password property, or local bucket password (per bucket) via couchbase.bucket.[name].password property.

environment configuration

Environment configuration is available via: couchbase.env namespace, here is an example on how to setup kvEndpoints:

couchbase.env.kvEndpoints = 3

cluster manager

A ClusterManager service is available is you set an cluster username and password:

couchbase.cluster.username = foo
couchbase.cluster.password = bar

couchbase.conf

These are the default properties for couchbase:

# configure the environment by adding properties to the `couchbase.env` namespace, like: 
# couchbase.env.kvEndpoints = 2 
# Some custom error codes 
err {

  com.couchbase.client.java.error.BucketAlreadyExistsException = 400

  com.couchbase.client.java.error.BucketDoesNotExistException = 404

  com.couchbase.client.java.error.DocumentAlreadyExistsException = 400

  com.couchbase.client.java.error.DocumentDoesNotExistException = 404

  com.couchbase.client.java.error.QueryExecutionException = 400

}

couchbase session store

A Session.Store powered by Couchbase.

dependency

<dependency>
 <groupId>org.jooby</groupId>
 <artifactId>jooby-couchbase</artifactId>
 <version>1.5.1</version>
</dependency>

usage

{
  use(new Couchbase("couchbase://localhost/bucket"));

  session(CouchbaseSessionStore.class);

  get("/", req -> {
    Session session = req.session();
    session.put("foo", "bar");
    ..
  });
}

Session data is persisted in Couchbase and document looks like:

{ "session::{SESSION_ID}": { "foo": "bar" } } 

options

timeout

By default, a session will expire after 30 minutes. Changing the default timeout is as simple as:

# 8 hours 
session.timeout = 8h

# 15 seconds 
session.timeout = 15

# 120 minutes 
session.timeout = 120m

Expiration is done via Couchbase expiry/ttl option.

If no timeout is required, use -1.

custom bucket

The session document are persisted in the application/default bucket, if you need/want a different bucket then use {@link Couchbase#sessionBucket(String)}, like:

{
  use(
      new Couchbase("couchbase://localhost/myapp")
          .sessionBucket("session")
  );

}