Utilizzo di Couchbase in un'applicazione Spring

1. Introduzione

In questo seguito alla nostra introduzione a Couchbase, creiamo una serie di servizi Spring che possono essere utilizzati insieme per creare un livello di persistenza di base per un'applicazione Spring senza l'uso di Spring Data.

2. Servizio cluster

Per soddisfare il vincolo secondo cui solo un singolo CouchbaseEnvironment può essere attivo nella JVM, iniziamo scrivendo un servizio che si connette a un cluster Couchbase e fornisce l'accesso ai bucket di dati senza esporre direttamente le istanze Cluster o CouchbaseEnvironment .

2.1. Interfaccia

Ecco la nostra interfaccia ClusterService :

public interface ClusterService { Bucket openBucket(String name, String password); }

2.2. Implementazione

La nostra classe di implementazione crea un'istanza di DefaultCouchbaseEnvironment e si connette a un cluster durante la fase @PostConstruct durante l'inizializzazione del contesto Spring.

Ciò garantisce che il cluster non sia nullo e che sia connesso quando la classe viene iniettata in altre classi di servizio, consentendo loro di aprire uno o più bucket di dati:

@Service public class ClusterServiceImpl implements ClusterService { private Cluster cluster; @PostConstruct private void init() { CouchbaseEnvironment env = DefaultCouchbaseEnvironment.create(); cluster = CouchbaseCluster.create(env, "localhost"); } ... }

Successivamente, forniamo una ConcurrentHashMap per contenere i bucket aperti e implementare il metodo openBucket :

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. Servizio secchiello

A seconda di come si progetta l'applicazione, potrebbe essere necessario fornire l'accesso allo stesso bucket di dati in più servizi Spring.

Se abbiamo semplicemente tentato di aprire lo stesso bucket in due o più servizi durante l'avvio dell'applicazione, è probabile che il secondo servizio che lo tenta incontra un'eccezione ConcurrentTimeoutException .

Per evitare questo scenario, definiamo un'interfaccia BucketService e una classe di implementazione per bucket. Ogni classe di implementazione funge da ponte tra ClusterService e le classi che richiedono l'accesso diretto a un determinato Bucket .

3.1. Interfaccia

Ecco la nostra interfaccia BucketService :

public interface BucketService { Bucket getBucket(); }

3.2. Implementazione

La seguente classe fornisce l'accesso al bucket " baeldung-tutorial ":

@Service @Qualifier("TutorialBucketService") public class TutorialBucketService implements BucketService { @Autowired private ClusterService couchbase; private Bucket bucket; @PostConstruct private void init() { bucket = couchbase.openBucket("baeldung-tutorial", ""); } @Override public Bucket getBucket() { return bucket; } }

Iniettando il ClusterService nel nostro TutorialBucketService classe di implementazione e aprendo il secchio in un metodo annotato con @PostConstruct, abbiamo fatto sì che il secchio sarà pronto per l'uso quando la TutorialBucketService viene poi iniettato in altri servizi.

4. Livello di persistenza

Ora che disponiamo di un servizio per ottenere un'istanza Bucket , creeremo un livello di persistenza simile a un repository che fornisce operazioni CRUD per classi di entità ad altri servizi senza esporre loro l' istanza Bucket .

4.1. L'entità persona

Ecco la classe di entità Person che desideriamo persistere:

public class Person { private String id; private String type; private String name; private String homeTown; // standard getters and setters }

4.2. Conversione di classi di entità in e da JSON

Per convertire le classi di entità da e verso gli oggetti JsonDocument che Couchbase utilizza nelle sue operazioni di persistenza, definiamo l' interfaccia JsonDocumentConverter :

public interface JsonDocumentConverter { JsonDocument toDocument(T t); T fromDocument(JsonDocument doc); }

4.3. Implementazione del convertitore JSON

Successivamente, dobbiamo implementare un JsonConverter per entità Person .

@Service public class PersonDocumentConverter implements JsonDocumentConverter { ... }

Potremmo usare la libreria Jackson insieme ai metodi toJson e fromJson della classe JsonObject per serializzare e deserializzare ilentità, tuttavia c'è un sovraccarico aggiuntivo nel farlo.

Invece, per il metodo toDocument , utilizzeremo i metodi fluenti della classe JsonObject per creare e popolare un JsonObject prima di avvolgerlo in un JsonDocument :

@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 per il metodo fromDocument , useremo il metodo getString della classe JsonObject insieme ai setter nella classe Person nel nostro metodo fromDocument :

@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. Interfaccia CRUD

Creiamo ora un'interfaccia CrudService generica che definisce le operazioni di persistenza per le classi di entità:

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. Implementazione del servizio CRUD

Con le classi di entità e convertitore in atto, ora implementiamo CrudService per l' entità Person , inserendo il servizio bucket e il convertitore di documenti mostrati sopra e recuperando il bucket durante l'inizializzazione:

@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. Mettere tutto insieme

Ora che abbiamo tutte le parti del nostro livello di persistenza in atto, ecco un semplice esempio di un servizio di registrazione che utilizza PersonCrudService per persistere e recuperare i registranti:

@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. Conclusione

Abbiamo dimostrato che con alcuni servizi Spring di base, è abbastanza banale incorporare Couchbase in un'applicazione Spring e implementare un livello di persistenza di base senza utilizzare Spring Data.

Il codice sorgente mostrato in questo tutorial è disponibile nel progetto GitHub.

Puoi saperne di più su Couchbase Java SDK sul sito ufficiale della documentazione per sviluppatori Couchbase.