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.6.6</version>
</dependency>
exports
CouchbaseEnvironmentCouchbaseClusterAsyncBucketBucketAsyncDatastoreDatastoreAsyncRepositoryRepository- 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
idfield to your POJO:
public class Beer {
private String id;
}
- Use a business name (not necessarily id) and add
Idannotation:
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.6.6</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")
);
}