Usando o Couchbase em um aplicativo Spring
1. Introdução
Neste acompanhamento de nossointroduction to Couchbase, criamos um conjunto de serviços Spring que podem ser usados juntos para criar uma camada de persistência básica para um aplicativo Spring sem o uso de Spring Data.
2. Serviço de cluster
Para satisfazer a restrição de que apenas um únicoCouchbaseEnvironment pode estar ativo na JVM, começamos escrevendo um serviço que se conecta a um cluster Couchbase e fornece acesso a depósitos de dados sem expor diretamente oCluster ou instâncias deCouchbaseEnvironment.
2.1. Interface
Aqui está a nossa interfaceClusterService:
public interface ClusterService {
Bucket openBucket(String name, String password);
}
2.2. Implementação
Nossa classe de implementação instancia aDefaultCouchbaseEnvironmente conecta a um cluster durante a fase@PostConstruct durante a inicialização do contexto Spring.
Isso garante que o cluster não seja nulo e esteja conectado quando a classe for injetada em outras classes de serviço, permitindo que eles abram um ou mais depósitos de dados:
@Service
public class ClusterServiceImpl implements ClusterService {
private Cluster cluster;
@PostConstruct
private void init() {
CouchbaseEnvironment env = DefaultCouchbaseEnvironment.create();
cluster = CouchbaseCluster.create(env, "localhost");
}
...
}
Em seguida, fornecemos umConcurrentHashMap para conter os intervalos abertos e implementamos o métodoopenBucket:
private Map buckets = new ConcurrentHashMap<>();
@Override
synchronized public Bucket openBucket(String name, String password) {
if(!buckets.containsKey(name)) {
Bucket bucket = cluster.openBucket(name, password);
buckets.put(name, bucket);
}
return buckets.get(name);
}
3. Serviço de balde
Dependendo de como você arquitetou seu aplicativo, pode ser necessário fornecer acesso ao mesmo depósito de dados em vários serviços Spring.
Se apenas tentarmos abrir o mesmo depósito em dois ou mais serviços durante a inicialização do aplicativo, o segundo serviço a tentar isso provavelmente encontraráConcurrentTimeoutException.
Para evitar esse cenário, definimos uma interfaceBucketService e uma classe de implementação por bucket. Cada classe de implementação atua como uma ponte entreClusterService e as classes que precisam de acesso direto a um determinadoBucket.
3.1. Interface
Aqui está a nossa interfaceBucketService:
public interface BucketService {
Bucket getBucket();
}
3.2. Implementação
A seguinte classe fornece acesso ao intervalo “example-tutorial”:
@Service
@Qualifier("TutorialBucketService")
public class TutorialBucketService implements BucketService {
@Autowired
private ClusterService couchbase;
private Bucket bucket;
@PostConstruct
private void init() {
bucket = couchbase.openBucket("example-tutorial", "");
}
@Override
public Bucket getBucket() {
return bucket;
}
}
Ao injetarClusterService em nossa classe de implementaçãoTutorialBucketService e abrir o balde em um método anotado com@PostConstruct,, garantimos que o balde estará pronto para uso quando oTutorialBucketService for em seguida, injetado em outros serviços.
4. Camada de persistência
Agora que temos um serviço para obter uma instânciaBucket, criaremos uma camada de persistência semelhante a um repositório que fornece operações CRUD para classes de entidade para outros serviços sem expor a instânciaBucket a eles.
4.1. A Pessoa Entidade
Aqui está a classe de entidadePerson que desejamos persistir:
public class Person {
private String id;
private String type;
private String name;
private String homeTown;
// standard getters and setters
}
4.2. Convertendo classes de entidade para e de JSON
Para converter classes de entidade de e para os objetosJsonDocument que o Couchbase usa em suas operações de persistência, definimos a interfaceJsonDocumentConverter:
public interface JsonDocumentConverter {
JsonDocument toDocument(T t);
T fromDocument(JsonDocument doc);
}
4.3. Implementando o conversor JSON
Em seguida, precisamos implementar umJsonConverter para entidadesPerson.
@Service
public class PersonDocumentConverter
implements JsonDocumentConverter {
...
}
Poderíamos usar a bibliotecaJackson em conjunto com os métodostoJsonefromJson da classeJsonObject para serializar e desserializar as entidades __, no entanto, há sobrecarga adicional ao fazer isso.
Em vez disso, para o métodotoDocument, usaremos os métodos fluentes da classeJsonObject para criar e preencher umJsonObject antes de envolvê-lo emJsonDocument:
@Override
public JsonDocument toDocument(Person p) {
JsonObject content = JsonObject.empty()
.put("type", "Person")
.put("name", p.getName())
.put("homeTown", p.getHomeTown());
return JsonDocument.create(p.getId(), content);
}
E para o métodofromDocument, usaremos o métodogetString da classeJsonObject junto com os setters na classePerson em nosso métodofromDocument:
@Override
public Person fromDocument(JsonDocument doc) {
JsonObject content = doc.content();
Person p = new Person();
p.setId(doc.id());
p.setType("Person");
p.setName(content.getString("name"));
p.setHomeTown(content.getString("homeTown"));
return p;
}
4.4. Interface CRUD
Agora criamos uma interfaceCrudService genérica que define operações de persistência para classes de entidade:
public interface CrudService {
void create(T t);
T read(String id);
T readFromReplica(String id);
void update(T t);
void delete(String id);
boolean exists(String id);
}
4.5. Implementando o Serviço CRUD
Com as classes de entidade e conversor em vigor, agora implementamosCrudService para a entidadePerson, injetando o serviço de intervalo e o conversor de documento mostrado acima e recuperando o intervalo durante a inicialização:
@Service
public class PersonCrudService implements CrudService {
@Autowired
private TutorialBucketService bucketService;
@Autowired
private PersonDocumentConverter converter;
private Bucket bucket;
@PostConstruct
private void init() {
bucket = bucketService.getBucket();
}
@Override
public void create(Person person) {
if(person.getId() == null) {
person.setId(UUID.randomUUID().toString());
}
JsonDocument document = converter.toDocument(person);
bucket.insert(document);
}
@Override
public Person read(String id) {
JsonDocument doc = bucket.get(id);
return (doc != null ? converter.fromDocument(doc) : null);
}
@Override
public Person readFromReplica(String id) {
List docs = bucket.getFromReplica(id, ReplicaMode.FIRST);
return (docs.isEmpty() ? null : converter.fromDocument(docs.get(0)));
}
@Override
public void update(Person person) {
JsonDocument document = converter.toDocument(person);
bucket.upsert(document);
}
@Override
public void delete(String id) {
bucket.remove(id);
}
@Override
public boolean exists(String id) {
return bucket.exists(id);
}
}
5. Juntando tudo
Agora que temos todas as peças de nossa camada de persistência no lugar, aqui está um exemplo simples de um serviço de registro que usaPersonCrudService para persistir e recuperar registrantes:
@Service
public class RegistrationService {
@Autowired
private PersonCrudService crud;
public void registerNewPerson(String name, String homeTown) {
Person person = new Person();
person.setName(name);
person.setHomeTown(homeTown);
crud.create(person);
}
public Person findRegistrant(String id) {
try{
return crud.read(id);
}
catch(CouchbaseException e) {
return crud.readFromReplica(id);
}
}
}
6. Conclusão
Mostramos que, com alguns serviços básicos do Spring, é bastante trivial incorporar o Couchbase em um aplicativo Spring e implementar uma camada de persistência básica sem o uso de dados do Spring.
O código-fonte mostrado neste tutorial está disponível emGitHub project.
Você pode aprender mais sobre o Couchbase Java SDK noCouchbase developer documentation site. oficial