Reputation: 21
I'm building a spring application that has multiple couchbase buckets. The master bucket contains data about other tenants configuration, while rest of the buckets are specific to tenant/customer and shares same DAOs, Services.
I want to save/retrieve documents to/from tenant specific buckets, but Spring data couchbase is static in nature and does not allow to dynamically bind buckets to repositories or to switch between buckets on runtime. Is there an way to map/bind the bucket to repository dynamically in spring data couchbase?
I am initializing 3 tenants templates, setting master as default and want to switch between other 2 based on tenants on runtime.
@Configuration
@EnableCouchbaseRepositories(basePackages = {"com.user"})
@EnableCouchbaseAuditing
public class CouchbaseMultiBucketConfig extends AbstractCouchbaseConfiguration {
private static final String ACCESS_FAILED = "Failed to access bucket template: ";
@Value("${bucket.master.name}")
private String masterBucketName;
@Value("${bucket.master.password}")
private String masterBucketPassword;
@Value("${bucket.master.host:#{null}}")
private String masterBucketHost;
@Value("${bucket.tenant1.name}")
private String tenant1BucketName;
@Value("${bucket.tenant1.password}")
private String tenant1BucketPassword;
@Value("${bucket.tenant1.host:#{null}}")
private String tenant1BucketHost;
@Value("${bucket.tenant2.name}")
private String tenant2BucketName;
@Value("${bucket.tenant2.password}")
private String tenant2ucketPassword;
@Value("${bucket.tenant2.host:#{null}}")
private String tenant2ucketHost;
@Value("${cb.hosts}")
private String hosts;
@Bean
@Qualifier("tenant1")
public Bucket tenant1Bucket() {
return openBucket(tenant1BucketName, tenant1BucketPassword, tenant1BucketHost);
}
@Bean
@Qualifier("tenant2")
public Bucket tenant1Bucket() {
return openBucket(tenant2BucketName, tenant2BucketPassword, tenant2BucketHost);
}
@Bean
@Qualifier("master")
public Bucket masterBucket() {
return openBucket(masterBucketName, masterBucketPassword, masterBucketHost);
}
@Bean
@Qualifier("masterTemplate")
public CouchbaseTemplate masterTemplate() {
try {
final CouchbaseTemplate template = new CouchbaseTemplate(
couchbaseClusterInfo(), //reuse the default bean
masterBucket(), //the bucket is non-default
mappingCouchbaseConverter(), translationService() //default beans here as well
);
template.setDefaultConsistency(getDefaultConsistency());
return template;
} catch (Exception ex) {
throw new IllegalStateException(ACCESS_FAILED, ex);
}
}
@Bean
@Qualifier("tenant1Template")
public CouchbaseTemplate tenant1Template() {
try {
final CouchbaseTemplate template = new CouchbaseTemplate(
couchbaseClusterInfo(), //reuse the default bean
tenant1Bucket(), //the bucket is non-default
mappingCouchbaseConverter(), translationService() //default beans here as well
);
template.setDefaultConsistency(getDefaultConsistency());
return template;
} catch (Exception ex) {
throw new IllegalStateException(ACCESS_FAILED, ex);
}
}
@Bean
@Qualifier("tenant2Template")
public CouchbaseTemplate tenant2Template() {
try {
final CouchbaseTemplate template = new CouchbaseTemplate(
couchbaseClusterInfo(), //reuse the default bean
tenant2Bucket(), //the bucket is non-default
mappingCouchbaseConverter(), translationService() //default beans here as well
);
template.setDefaultConsistency(getDefaultConsistency());
return template;
} catch (Exception ex) {
throw new IllegalStateException(ACCESS_FAILED, ex);
}
}
private Bucket openBucket(final String bucketName, final String bucketPassword, final String alternativeHost)
throws IllegalStateException {
try {
return couchbaseCluster(alternativeHost).openBucket(bucketName, bucketPassword);
} catch (Exception ex) {
throw new IllegalStateException("Failed to open bucket " + bucketName, ex);
}
}
@Override
public void configureRepositoryOperationsMapping(final RepositoryOperationsMapping baseMapping) {
baseMapping.setDefault(masterTemplate());
}
@Override
protected List<String> getBootstrapHosts() {
return parse(hosts);
}
@Override
protected String getBucketName() {
return tenant1BucketName;
}
@Override
protected String getBucketPassword() {
return tenant1BucketPassword;
}
private List<String> parse(final String hosts) {
return Arrays.asList(hosts.split(","))
.stream().map(in -> in.trim()).collect(Collectors.toList());
}
}
@Repository
public interface UserRepository extends CouchbaseRepository<User, String> {
User getByDocKey(final String docKey);
@Query("#{#n1ql.selectEntity} WHERE tenantId= $1")
List<User> findByTenantId(String tenantId);
}
Thanks
Upvotes: 2
Views: 490
Reputation: 2460
Simple answer
Looks like it can be done but it is not trivial. https://forums.couchbase.com/t/spring-couchbase-multiple-buckets/16984
Long answer
You are pushing to the database something that should be handled by your application. If you are having this kind of problem, it probably means that you should have a single bucket to serve all your clients. After all, using this approach won't allow you to serve 100 clients.
PS: With n1ql you can still easily switch among buckets and even use multiple buckets in a single query.
Upvotes: 1