Перейти к основному содержимому

Использование Couchbase в приложении Spring

· 5 мин. чтения

1. Введение

В этом продолжении нашего введения в Couchbase мы создаем набор сервисов Spring, которые можно использовать вместе для создания базового уровня сохраняемости для приложения Spring без использования данных Spring.

2. Кластерная служба

Чтобы удовлетворить ограничение, согласно которому в JVM может быть активна только одна среда CouchbaseEnvironment , мы начинаем с написания службы, которая подключается к кластеру Couchbase и предоставляет доступ к сегментам данных без прямого доступа к экземплярам Cluster или CouchbaseEnvironment .

2.1. Интерфейс

Вот наш интерфейс ClusterService :

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

2.2. Реализация

Наш класс реализации создает экземпляр DefaultCouchbaseEnvironment и подключается к кластеру на этапе @PostConstruct во время инициализации контекста Spring.

Это гарантирует, что кластер не является нулевым и что он подключен, когда класс внедряется в другие классы обслуживания, что позволяет им открывать один или несколько сегментов данных:

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

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

Затем мы предоставляем ConcurrentHashMap для хранения открытых корзин и реализуем метод openBucket :

private Map<String, Bucket> 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. Ведро службы

В зависимости от того, как вы разрабатываете свое приложение, вам может потребоваться предоставить доступ к одному и тому же сегменту данных в нескольких службах Spring.

Если мы просто попытаемся открыть одно и то же ведро в двух или более службах во время запуска приложения, вторая служба, которая попытается это сделать, скорее всего, столкнется с ConcurrentTimeoutException .

Чтобы избежать этого сценария, мы определяем интерфейс BucketService и класс реализации для каждого сегмента. Каждый класс реализации действует как мост между ClusterService и классами, которым требуется прямой доступ к конкретному Bucket .

3.1. Интерфейс

Вот наш интерфейс BucketService :

public interface BucketService {
Bucket getBucket();
}

3.2. Реализация

Следующий класс предоставляет доступ к корзине « foreach-tutorial »:

@Service
@Qualifier("TutorialBucketService")
public class TutorialBucketService implements BucketService {

@Autowired
private ClusterService couchbase;

private Bucket bucket;

@PostConstruct
private void init() {
bucket = couchbase.openBucket("foreach-tutorial", "");
}

@Override
public Bucket getBucket() {
return bucket;
}
}

Внедрив ClusterService в наш класс реализации TutorialBucketService и открыв бакет в методе, аннотированном @PostConstruct, мы гарантировали, что бакет будет готов к использованию, когда TutorialBucketService будет затем внедрен в другие сервисы.

4. Уровень сохранения

Теперь, когда у нас есть служба для получения экземпляра Bucket , мы создадим уровень сохраняемости, подобный репозиторию, который предоставляет операции CRUD для классов сущностей другим службам, не раскрывая им экземпляр Bucket .

4.1. Личность

Вот класс сущности Person , который мы хотим сохранить:

public class Person {

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

// standard getters and setters
}

4.2. Преобразование классов сущностей в JSON и обратно

Чтобы преобразовать классы сущностей в объекты JsonDocument и из них , которые Couchbase использует в своих операциях сохранения, мы определяем интерфейс JsonDocumentConverter :

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

4.3. Реализация конвертера JSON

Далее нам нужно реализовать JsonConverter для сущностей Person .

@Service
public class PersonDocumentConverter
implements JsonDocumentConverter<Person> {
...
}

Мы могли бы использовать библиотеку Джексона в сочетании с методами toJson и fromJson класса JsonObject для сериализации и десериализации сущностей, однако при этом возникают дополнительные накладные расходы. ``

Вместо этого для метода toDocument мы будем использовать плавные методы класса JsonObject для создания и заполнения JsonObject перед его оберткой в 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);
}

И для метода fromDocument мы будем использовать метод getString класса JsonObject вместе с сеттерами в классе Person в нашем методе 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. CRUD-интерфейс

Теперь мы создаем общий интерфейс CrudService , который определяет операции сохранения для классов сущностей:

public interface CrudService<T> {
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. Внедрение службы CRUD

Имея классы сущностей и преобразователей, мы теперь реализуем CrudService для сущности Person , внедряя показанные выше службу корзины и преобразователь документов и извлекая корзину во время инициализации:

@Service
public class PersonCrudService implements CrudService<Person> {

@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<JsonDocument> 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. Собираем все вместе

Теперь, когда у нас есть все части нашего уровня сохраняемости, вот простой пример службы регистрации, которая использует PersonCrudService для сохранения и извлечения владельцев регистрации:

@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. Заключение

Мы показали, что с помощью нескольких базовых сервисов Spring довольно просто включить Couchbase в приложение Spring и реализовать базовый уровень сохраняемости без использования данных Spring.

Исходный код, показанный в этом руководстве, доступен в проекте GitHub .

Вы можете узнать больше о Couchbase Java SDK на официальном сайте документации для разработчиков Couchbase.