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.