Welcome to episode two of the Couchbase series. In this article, we will start writing some code that integrates with the Couchbase. We will look into how we establish the connection and how to open buckets and start reading and writing from/to them.
Make sure to check out the other articles in the series as well:
- NoSQL with Couchbase: Setting everything up with Docker
- NoSQL with Couchbase: Getting started
- NoSQL with Couchbase: Working with JSON
- NoSQL with Couchbase: Querying with N1QL
- NoSQL with Couchbase: Creating Indexes that Scale
Couchbase
In the previous episode, we looked into learned how to get our Couchbase cluster up and running. If you haven’t checked that out yet, I recommend doing that before proceeding with this article: read the previous article.
So, we have a cluster up and running and we are almost ready to start using it. But first we have to do two more things, we have to create a bucket set up the authentication so that we can work with the bucket.
Creating a bucket
Start by navigating to Bucket and press Add bucket give the bucket a name, I will name it thecuriosdev-demo and you can feel free to assign all available data ram to it. We won’t be using that much but you might as well, right?
For bucket type you will choose Couchbase. Memcached is the old type that comes from MemcacheDB which means that everything is stored in RAM and it is not what we are interested in. The ephemeral bucket works quite similar to the Memcached bucket, it is very quick but doesn’t persist on disk. But it is perfect for when you only need to store something for a short time and it doesn’t matter for you if it is persisted on disk or not.
For conflict resolution, it doesn’t really matter what we use since we only have one cluster and node and won’t be doing any XDCR (Cross datacenter replication). The timestamp resolution is considered the better option as long as you set up an NTP that makes sure the time stays the same on all nodes. For this and the upcoming articles, the sequence number option is good enough.
We won’t bother much with the other fields, just select the values below:
- Ejection method: Value-only
- Bucket priority: Default
- Flush: Enabled
Create the bucket and let’s proceed.
Setting up security
Navigate to the security tab and add a new user. Enter some login details what you will remember and for roles, we will select Bucket roles -> Bucket Full Access -> Select your bucket. Save the user and we should be good to start writing some actual code.
Using the Couchbase SDK
To more quickly get up and running we are going to clone the skeleton project that I introduced in my previous blog post Improve your JUnit tests with Mockito and PowerMock. The project can be found at my GitHub.
Add theCouchbase SDK to pom.xml
com.couchbase.client
java-client
2.5.2
Connecting to the bucket
We need to configure the host and authentication towards our new bucket. I prefer to do my configuration in YAML format instead of the properties format, so we are going to navigate to our src/main/resources and remove application.properties and instead create application.yml.
server.port: 9000
couchbase:
host: localhost
bucket:
name: thecuriousdev-demo
user:
name:
password:
Create the following class org.thecuriousdev.demo.skeleton.configuration.CouchbaseConnector
package org.thecuriousdev.demo.skeleton.configuration;
import com.couchbase.client.java.Bucket;
import com.couchbase.client.java.Cluster;
import com.couchbase.client.java.CouchbaseCluster;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class CouchbaseConnector {
private static final Logger LOGGER = LoggerFactory.getLogger(CouchbaseConnector.class);
@Value("${couchbase.host}")
private String host;
@Value("${couchbase.bucket.name}")
private String bucketName;
@Value("${couchbase.bucket.user.name}")
private String username;
@Value("${couchbase.bucket.user.password}")
private String password;
@Bean
public Cluster cluster() {
LOGGER.info("Creating connection to host [{}] with username [{}]", host, username);
Cluster cluster = CouchbaseCluster.create(host);
cluster.authenticate(username, password);
return cluster;
}
@Bean
@ConditionalOnBean(Cluster.class)
public Bucket bucket(Cluster cluster) {
LOGGER.info("Opening bucket with name [{}]", bucketName);
return cluster.openBucket(bucketName);
}
}
Using the bucket
Instead of simulation a database we are now going to be working against our Couchbase database that we set up. Start by removing the class org.thecuriousdev.demo.skeleton.db.SimulatedDatabase
.
Let’s rewrite org.thecuriousdev.demo.skeleton.db.PersonRepository. In this article, we will start with a basic implementation that doesn’t really do much error handling, in the next tutorial we will come back and polish the implementation and make something more robust. This is usually a good approach to take with everything, work in iterations. Start with something small and make it work. Then come back and refactor it, baby steps.
What makes Couchbase so nice to work with (at least in my opinion) is that everything is in JSON. Because of that we will use the com.fasterxml.jackson.databind.ObjectMapper
to serialize and deserialize our objects. Spring boot configures an ObjectMapper that we can use, it is good enough for us in this article, but we will configure it later.
package org.thecuriousdev.demo.skeleton.db;
import com.couchbase.client.java.Bucket;
import com.couchbase.client.java.document.JsonDocument;
import com.couchbase.client.java.document.RawJsonDocument;
import com.couchbase.client.java.document.json.JsonObject;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;
import org.thecuriousdev.demo.skeleton.db.domain.Person;
import java.io.IOException;
import java.util.Optional;
@Repository
public class PersonRepository {
private static final Logger LOG = LoggerFactory.getLogger(PersonRepository.class);
private Bucket bucket;
private ObjectMapper objectMapper;
@Autowired
public PersonRepository(Bucket bucket, ObjectMapper objectMapper) {
this.bucket = bucket;
this.objectMapper = objectMapper;
}
public Optional findById(String name) {
Optional json = Optional.ofNullable(bucket.get(name))
.map(JsonDocument::content)
.map(JsonObject::toString);
if (json.isPresent()) {
try {
return Optional.ofNullable(objectMapper.readValue(json.get(), Person.class));
} catch (IOException e) {
LOG.warn("Failed to deserialize person from json {}", json.get());
}
}
return Optional.empty();
}
public void save(Person person) {
try {
bucket.upsert(RawJsonDocument.create(person.getName(), objectMapper.writeValueAsString(person)));
LOG.info("Saved person: {}", person);
} catch (JsonProcessingException e) {
LOG.warn("Failed to save user {}", person);
}
}
public void delete(String name) {
bucket.remove(name);
LOG.info("Deleted person: {}", name);
}
}
We should now be good to go, let’s boot up our application which we configured in our application.yml file to run on port 9000.
Time to run send some requests to our application. Remember that we are working with JSON so do not forget to set the Content-Type header to application/json.
Let’s store a new user by running the following request.
POST localhost:9000/person
{
"name" : "Viktor",
"age" : 24,
"favouriteFood" : "tacos"
}
We received a 204 which means it went OK. We can also the following log in our application.
Saved user: Person{name=Viktor, age=24, favouriteFood=tacos}
We can now open up the Bucket’s documents in the Couchbase graphical user interface and voilà, the document is there! Let’s verify it as well and check that our findById(String)
works by trying to retrieve the user.
GET localhost:9000/person/Viktor
And there we go. We got the following response, and it seems to work!
{ "name": "Viktor", "age": 24, "favouriteFood": "tacos" }
Final words
We have now successfully integrated with Couchbase with a very basic implementation. There are a lot of improvements that I want to do. For example, at the moment we can only store one Viktor in the database, probably the name is not a good value to use as ID. We also do not handle the https://developer.couchbase.com/documentation/server/current/sdk/concurrent-mutations-cluster.html value and the structure of our Person object aren’t really optimal for storing in Couchbase yet. But these are items that we will look at in the next article.
The code for this article can be found here on GitHub.
If you enjoyed the article, please help me out by giving it a thumbs up and sharing it on social media.
Until next time, happy coding!