Software Development

Spring Data Couchbase 4 Multibuckets in Spring Boot 2

By default, Spring Data Couchbase implements single-bucket configuration. In this default implementation, all POJO (Plain Old Java Object) models/documents will be stored in a single bucket. This is possible, because Couchbase is a NoSQL database which can store schemaless data. To differentiate between type of documents, Spring Data adds _class field that contains the fully-qualified class name of the POJO.

{
  "name": "Jane Doe",
  "createdAt": 1624102211338,
  "_class": "id.gultom.model.Customer",
  "updatedAt": 1624102211338
}

Spring Data Couchbase has an extensive documentation available here, but unfortunately the section about multibucket(s) implementation is missing. There are also some tutorials like this one, but most of them are only for single-bucket implementation. There are some references regarding multi-buckets implementation (like this Github Ticket) scattered in the internet, but I couldn’t find a single comprehensive example. So I decided to post the result of my research here.

⚠️ Please be aware that this implementation is for the Spring Data Couchbase 4.2.1. The older or newer version may require different approach — although I believe this is compatible for the same major versions (4.x). Also, in this example I only use the non-reactive repository. Implementing reactive repository may require some adjustments as well.

In order to implement multi-buckets in Spring Boot application, we need to setup a configuration class with custom Couchbase template beans for each buckets. Only model/document that is mapped to custom Couchbase template that will have separate bucket. Otherwise they will be stored in the default bucket. In this example, we define supplierTemplate and customerTemplate for Supplier and Customer models respectively.

package id.gultom.config;

import id.gultom.model.Customer;
import id.gultom.model.Supplier;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.couchbase.CouchbaseClientFactory;
import org.springframework.data.couchbase.SimpleCouchbaseClientFactory;
import org.springframework.data.couchbase.config.AbstractCouchbaseConfiguration;
import org.springframework.data.couchbase.core.CouchbaseTemplate;
import org.springframework.data.couchbase.core.convert.MappingCouchbaseConverter;
import org.springframework.data.couchbase.repository.config.EnableCouchbaseRepositories;
import org.springframework.data.couchbase.repository.config.RepositoryOperationsMapping;

@Slf4j
@Configuration
@EnableCouchbaseRepositories(basePackages = "id.gultom.repository.couchbase")
public class CouchbaseConfig extends AbstractCouchbaseConfiguration {
    @Value("${spring.couchbase.connection-string}")
    private String connectionString;

    @Value("${spring.couchbase.username}")
    private String username;

    @Value("${spring.couchbase.password}")
    private String password;

    // this bucket will be used by models/repositories
    // that is not mapped to custom couchbase templates
    @Value("${spring.data.couchbase.bucket-name:Default}")
    private String defaultBucketName;

    @Autowired
    private MappingCouchbaseConverter mappingCouchbaseConverter;

    @Override
    public String getConnectionString() {
        return this.connectionString;
    }

    @Override
    public String getUserName() {
        return this.username;
    }

    @Override
    public String getPassword() {
        return this.password;
    }

    @Override
    public String getBucketName() {
        return this.defaultBucketName;
    }

    @Override
    protected void configureRepositoryOperationsMapping(RepositoryOperationsMapping mapping) {
        // only model that is mapped to custom template will be mapped to non-default bucket
        mapping.mapEntity(Supplier.class, supplierTemplate());
        mapping.mapEntity(Customer.class, customerTemplate());
    }

    @Bean
    public CouchbaseTemplate supplierTemplate() {
        // define the custom template bucket name
        return customCouchbaseTemplate(customCouchbaseClientFactory("Supplier"));
    }

    @Bean
    public CouchbaseTemplate customerTemplate() {
        // define the custom template bucket name
        return customCouchbaseTemplate(customCouchbaseClientFactory("Customer"));
    }

    // do not use couchbaseTemplate for the name of this method, otherwise the value of that been
    // will be used instead of the result from this call (the client factory arg is different)
    public CouchbaseTemplate customCouchbaseTemplate(CouchbaseClientFactory couchbaseClientFactory) {
        return new CouchbaseTemplate(couchbaseClientFactory, mappingCouchbaseConverter);
    }

    // do not use couchbaseClientFactory for the name of this method, otherwise the value of that bean will
    // will be used instead of this call being made ( bucketname is an arg here, instead of using bucketName() )
    public CouchbaseClientFactory customCouchbaseClientFactory(String bucketName) {
        return new SimpleCouchbaseClientFactory(getConnectionString(), authenticator(), bucketName);
    }
}

That’s all! ?

The remaining POJO and repository classes can be defined just like the default/single-bucket implementation. Because as we can see in the configuration class above, we already explicitly map the POJOs with the custom Couchbase template and the repositories should be mapped to the POJOs like example below.

package id.gultom.repository.couchbase;

import id.gultom.model.Supplier;
import org.springframework.data.couchbase.repository.CouchbaseRepository;

public interface SupplierRepository extends CouchbaseRepository<Supplier, String> {

}

Besides that, we just need to make sure the buckets and indexes are created properly in the Couchbase database server our application is connected to. Because Spring Data Couchbase doesn’t automatically create them for us (like Hibernate).

As a reference, this is the relevant part of the pom.xml for this project:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>2.5.1</version>
		<relativePath/> <!-- lookup parent from repository -->
	</parent>
	<groupId>id.gultom</groupId>
	<artifactId>rest-api</artifactId>
        ...
	<dependencies>
                ...
		<dependency>
			<groupId>org.springframework.data</groupId>
			<artifactId>spring-data-couchbase</artifactId>
		</dependency>
                ...
	</dependencies>
        ...
</project>

Obviously, my configuration is not the only way to implement multi-buckets. But this is the configuration that I’ve personally tested and apparently working well. I’d appreciate if you can give a comment if you thing any improvement can be made.

The full code (including minimal Couchbase setup) can be found in this Github repository https://github.com/yohanesgultom/spring-boot-microservice.

Enjoy! ?

5 3 votes
Article Rating
yohanes.gultom@gmail.com

View Comments

Share
Published by
yohanes.gultom@gmail.com

Recent Posts

Get Unverified SSL Certificate Expiry Date with Python

Getting verified SSL information with Python (3.x) is very easy. Code examples for it are…

3 years ago

Firebase Auth Emulator with Python

Last year, Google released Firebase Auth Emulator as a new component in Firebase Emulator. In…

3 years ago

Google OIDC token generation/validation

One of the authentication protocol that is supported by most of Google Cloud services is…

4 years ago

Fast geolocation query with PostGIS

If you need to to add a spatial information querying in your application, PostGIS is…

4 years ago

Auto speech-to-text (Indonesian) with AWS Transcribe and Python

Amazon Web Service Transcribe provides API to automatically convert an audio speech file (mp3/wav) into…

4 years ago

Splitting video with ffmpeg and Python

I had a project to build a simple website that split uploaded video into parts…

4 years ago