Utilisation de Couchbase dans une application Spring

Utilisation de Couchbase dans une application Spring

1. introduction

Dans ce suivi de nosintroduction to Couchbase, nous créons un ensemble de services Spring qui peuvent être utilisés ensemble pour créer une couche de persistance de base pour une application Spring sans utiliser Spring Data.

2. Service de cluster

Afin de satisfaire la contrainte selon laquelle seul un seulCouchbaseEnvironment peut être actif dans la JVM, nous commençons par écrire un service qui se connecte à un cluster Couchbase et donne accès aux compartiments de données sans exposer directement ni lesCluster ou les instancesCouchbaseEnvironment.

2.1. Interface

Voici notre interfaceClusterService:

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

2.2. la mise en oeuvre

Notre classe d'implémentation instancie unDefaultCouchbaseEnvironment et se connecte à un cluster pendant la phase@PostConstruct lors de l'initialisation du contexte Spring.

Cela garantit que le cluster n'est pas nul et qu'il est connecté lorsque la classe est injectée dans d'autres classes de service, leur permettant ainsi d'ouvrir un ou plusieurs compartiments de données:

@Service
public class ClusterServiceImpl implements ClusterService {
    private Cluster cluster;

    @PostConstruct
    private void init() {
        CouchbaseEnvironment env = DefaultCouchbaseEnvironment.create();
        cluster = CouchbaseCluster.create(env, "localhost");
    }
...
}

Ensuite, nous fournissons unConcurrentHashMap pour contenir les buckets ouverts et implémentons la méthodeopenBucket:

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. Service de godet

Selon l'architecture de votre application, vous devrez peut-être fournir un accès au même compartiment de données dans plusieurs services Spring.

Si nous essayons simplement d'ouvrir le même compartiment dans deux ou plusieurs services lors du démarrage de l'application, le deuxième service qui tente cela est susceptible de rencontrer unConcurrentTimeoutException.

Pour éviter ce scénario, nous définissons une interfaceBucketService et une classe d'implémentation par bucket. Chaque classe d'implémentation agit comme un pont entre lesClusterService et les classes qui ont besoin d'un accès direct à unBucket particulier.

3.1. Interface

Voici notre interfaceBucketService:

public interface BucketService {
    Bucket getBucket();
}

3.2. la mise en oeuvre

La classe suivante permet d'accéder au compartiment «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;
    }
}

En injectant lesClusterService dans notre classe d'implémentationTutorialBucketService et en ouvrant le bucket dans une méthode annotée avec@PostConstruct,, nous nous sommes assurés que le bucket sera prêt à être utilisé lorsque leTutorialBucketService est puis injecté dans d'autres services.

4. Couche de persistance

Maintenant que nous avons un service en place pour obtenir une instance deBucket, nous allons créer une couche de persistance de type référentiel qui fournit des opérations CRUD pour les classes d'entités à d'autres services sans leur exposer l'instance deBucket.

4.1. L'entité personne

Voici la classe d'entitéPerson que nous souhaitons conserver:

public class Person {

    private String id;
    private String type;
    private String name;
    private String homeTown;

    // standard getters and setters
}

4.2. Conversion de classes d'entités vers et depuis JSON

Pour convertir les classes d'entités vers et depuis les objetsJsonDocument que Couchbase utilise dans ses opérations de persistance, nous définissons l'interfaceJsonDocumentConverter:

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

4.3. Implémentation du convertisseur JSON

Ensuite, nous devons implémenter unJsonConverter pour les entitésPerson.

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

Nous pourrions utiliser la bibliothèqueJackson en conjonction avec les méthodestoJson etfromJson de la classeJsonObject pour sérialiser et désérialiser les entités __, mais cela entraîne une surcharge supplémentaire.

Au lieu de cela, pour la méthodetoDocument, nous utiliserons les méthodes fluentes de la classeJsonObject pour créer et remplir unJsonObject avant de l'encapsuler dans unJsonDocument:

@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);
}

Et pour la méthodefromDocument, nous utiliserons la méthodegetString de la classeJsonObject avec les setters de la classePerson dans notre méthodefromDocument:

@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

Nous créons maintenant une interface génériqueCrudService qui définit les opérations de persistance pour les classes d'entités:

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. Implémentation du service CRUD

Une fois les classes d'entité et de convertisseur en place, nous implémentons maintenant lesCrudService pour l'entitéPerson, en injectant le service de compartiment et le convertisseur de document illustrés ci-dessus et en récupérant le compartiment lors de l'initialisation:

@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. Mettre tous ensemble

Maintenant que nous avons tous les éléments de notre couche de persistance en place, voici un exemple simple de service d'enregistrement qui utilise lesPersonCrudService pour conserver et récupérer les registrants:

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

Nous avons montré qu'avec quelques services Spring de base, il est assez trivial d'intégrer Couchbase dans une application Spring et d'implémenter une couche de persistance de base sans utiliser Spring Data.

Le code source montré dans ce didacticiel est disponible dans lesGitHub project.

Vous pouvez en savoir plus sur le SDK Java Couchbase sur le site officielCouchbase developer documentation site.