Статьи

Couchbase Java SDK 2.0.0 Developer Preview 3

Первоначально написано Майкл Nitschinger

От имени всей команды SDK я рад объявить о третьем предварительном выпуске серии релизов Java / JVM SDK под названием Armstrong . Он содержит как основной пакет JVM «core-io» 0.3, так и предварительный просмотр Java 3 SDK 2.0 для разработчиков.

Этот предварительный просмотр предоставляет намного больше API, которые приближают нас к завершению API (и к бета-версии), а также к изменениям в управлении конфигурацией и зависимостях.

Вот как вы можете получить это:

<dependencies>
    <dependency>
        <groupId>com.couchbase.client</groupId>
        <artifactId>couchbase-client</artifactId>
        <version>2.0.0-dp3</version>
    </dependency>
</dependencies>

<repositories>
    <repository>
        <id>couchbase</id>
        <name>couchbase repo</name>
        <url>http://files.couchbase.com/maven2</url>
        <snapshots><enabled>false</enabled></snapshots>
    </repository>
</repositories>

Изменения зависимостей

Каждая зависимость увеличивает вероятность конфликтов версий с пользовательскими библиотеками. В целях уменьшения вероятности мы внесли следующие изменения:

  • Избавление от несущественных зависимостей.
  • Упакуйте только внутренние зависимости в «толстую банку» и переместите их пакеты, чтобы избежать конфликтов.

Мы удалили жесткие зависимости в SLF4J (и добавили дополнительную поддержку для большего) и в библиотеку Typesafe Config. Хотя удаление библиотеки Config оставляет нам потенциально меньше возможностей, когда дело доходит до управления конфигурацией, мы позаботились о том, чтобы самостоятельно реализовать хороший построитель DSL, который также можно настроить через системные свойства. 

Пакет «core-io» теперь скрывает внутренние зависимости, такие как LMAX Disruptor , Netty и Jackson . Это не только снижает вероятность того, что SDK конфликтует с другими версиями этих зависимостей, но также позволяет нам свободно обновлять эти пакеты по своему усмотрению и даже полностью заменять их чем-то другим, и пользователи не будут замечать или нуждаться в этом. изменить что-то в их стеке приложений.

Начиная с этого предварительного просмотра разработчика, SDK имеет только две явные зависимости: пакет core-io и RxJava . RxJava не может быть скрыт, потому что это основной интерфейс наших асинхронных API и жизненно важная часть создания приложений с помощью SDK.

Новый API построителя конфигурации и строка подключения

Одна вещь в списке «задач» стала очень важной с удалением библиотеки конфигурации: DSL-компоновщик для конфигурации кластера с разумными настройками по умолчанию и возможность конфигурировать их через системные свойства.

Порядок настроек теперь выглядит следующим образом (приоритет выше):

  • Свойства системы
  • Настройки Builder
  • Значения по умолчанию

Это позволяет вам переопределять настройки по умолчанию через компоновщик для всех ваших развертываний, но в разных средах перезаписывать определенные настройки через системные свойства на лету при запуске JVM / контейнера.

Также была добавлена ​​первоначальная поддержка «строки подключения», которая обеспечивает похожую семантику начальной загрузки во всех наших официальных SDK. Для этого конструкция объекта «CouchbaseCluster» была изменена на заводские методы, которые обеспечивают большую гибкость. Если вам не нужны пользовательские настройки, вы можете использовать один из них:

// Default settings & localhost
CouchbaseCluster.create();

// Default settings & vararg list of nodes
CouchbaseCluster.create("192.168.56.101", "192.168.56.102");

// Default settings & list of nodes
List<String> nodes = Arrays.asList("192.168.56.101", "192.168.56.102");
CouchbaseCluster.create(nodes);

Если вы хотите использовать API строки подключения (который здесь подробно не рассматривается), вы можете вместо этого использовать метод `fromConnectionString`:

CouchbaseCluster.fromConnectionString("couchbase://192.168.56.101,192.168.56.102");

Все показанные методы имеют дополнительные перегрузки, которые позволяют ему проходить в «ClusterEnvironment». Среда является компонентом с состоянием, который также содержит свойства (параметры, которые вы можете изменить).

Итак, включение N1QL-запросов, например, работает следующим образом (и мы все еще хотим подключиться к localhost):

ClusterEnvironment environment = DefaultClusterEnvironment.create(
    DynamicClusterProperties.builder()
        .queryEnabled(true)
        .build()
);
CouchbaseCluster.create(environment);

Обратите внимание, что мы могли бы еще больше упростить этот API для бета-версии, поэтому ожидайте небольшие изменения в этой области.

Среда содержит помимо настраиваемых свойств важные фрагменты среды выполнения SDK (включая различные пулы потоков для правильного функционирования), и она предназначена для повторного использования в объектах CouchbaseCluster. Это означает, что не имеет значения, если вы подключаетесь к нескольким сегментам в одном кластере или к нескольким кластерам, ресурсы могут быть общими и оптимально распределены.

Теперь, если вы не хотите использовать конструктор, а вместо этого задаете системные свойства, «динамические свойства», которые также используются по умолчанию, также прослушивают определенные свойства. Это еще один способ включить запросы (или даже переопределить настройку компоновщика):

System.setProperty("com.couchbase.queryEnabled", "true");
CouchbaseCluster cluster = CouchbaseCluster.create();

Legacy Transcoder

Чтобы быть совместимыми со старыми приложениями, мы реализовали LegacyTranscoder / LegacyDocument, который устанавливает и понимает флаги, сжатие и сериализацию объектов, как и в серии 1.4. * (И старше). Поскольку API не привязаны к какому-либо конкретному транскодеру, его можно использовать через тот же API, что и JsonTranscoder (и соответствующий JsonDocument).

// Will be stored as a string on the server, can also be a JSON string
bucket.upsert(LegacyDocument.create("legacydoc-string", "mystring"));

// Will be stored as a number
bucket.upsert(LegacyDocument.create("legacydoc-int", 25));

// A POJO that will be serialized if serializable
bucket.upsert(LegacyDocument.create("legacydoc-pojo", user));

Данные также могут быть загружены:

// Loads a legacy document based on the ID.
bucket.get("legacydoc", LegacyDocument.class);

// Also loads the legacy document and infers its document by the input.
bucket.get(LegacyDocument.create("legacydoc"));

Изменения в View & Query API

Мы внесли небольшие изменения в API запросов и API запросов N1QL, чтобы предоставить вам больше информации в сценариях ошибок и отладки. Раньше все методы запроса возвращали строки напрямую, но есть доступная связанная информация (запрос статистики, сообщения об ошибках, …), которая не могла быть открыта через такой интерфейс.

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

bucket
    .query(ViewQuery.from("design", "view"))
    .doOnNext(result -> {
        if (!result.success()) {
            System.err.println(result.error());
        }
    })
    .flatMap(ViewResult::rows)
    .subscribe(row -> System.out.println(row.id()));

Вы можете использовать doOnNext, чтобы добавить побочный эффект, и если результат не будет успешным, зарегистрируйте ошибку. Если вас не волнуют другие части, кроме реальных строк, вы можете использовать flatMap напрямую, чтобы вывести строки, как и раньше. Мы также добавили возможность загружать все содержимое документа, если установлено «withDocuments»:

bucket
    .query(ViewQuery.from("design", "view").withDocuments())
    .flatMap(ViewResult::rows)
    .flatMap(ViewRow::document)
    .subscribe(System.out::println);

Поскольку каждый ViewRow предоставляет способ загрузки полного документа, вы можете использовать flatMap, чтобы получить полный документ (который внутренне использует вызовы get), а затем распечатать его вместо просто ID.

Тот же API используется для запросов N1QL, но вывод «info» сам по себе является Observable, поскольку внутренне он передается после возвращенных строк.

Постоянство и ограничения репликации

API-интерфейсы мутации были расширены для поддержки функции Durability Requirements, которая дает вам ограничения на репликацию и постоянство. Они предоставляются в виде простых перегрузок для сохранения той же семантики. Несколько примеров:

bucket.insert(doc, PersistTo.ONE);
bucket.upsert(doc, PersistTo.MASTER, ReplicateTo.ONE);
bucket.replace(doc, ReplicateTo.THREE);

Для пользователей, знакомых с текущим SDK, это будет выглядеть очень похоже. Вы можете предоставить перечисления PersistTo / ReplicateTo для предоставления информации о том, какое ограничение вы хотите установить. По умолчанию они установлены на NONE, что означает, что после подтверждения документа сервером в его управляемом кэше, наблюдаемое успешно завершено. Изменяя аргумент (ы), клиент внутренне блокирует / проверяет эффективным способом, пока ограничения не будут выполнены или не произойдет сбой. 

Больше API

В дополнение к тому, чтобы сделать существующие API более мощными и выразительными, были добавлены дополнительные API:

Получить из реплики

Добавлены дополнительные возможности чтения, которые позволяют получать потенциально устаревшие (устаревшие) данные из узлов реплики. API идентичен обычному get, но, кроме того, вам необходимо предоставить ReplicaMode. Существует два типа режимов: либо будут выбраны все возможные документы (из главной и всех реплик), либо может быть загружен один конкретный документ-реплика. Вот некоторые примеры:

// will return 0 to N documents
bucket.getFromReplica("id", ReplicaMode.ALL);

// will return 0 or 1 document
bucket.getFromReplica("id", ReplicaMode.SECOND);

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

bucket
    .get("mydoc0")
    .onErrorResumeNext(bucket.getFromReplica("mydoc0", ReplicaMode.ALL))
    .first()
    .subscribe();

Обратите внимание, что, поскольку режим «все» потенциально возвращает документы в режиме, вы можете использовать метод «первый», чтобы выбрать только первый поступающий.

сенсорный

Прикосновение к документу приводит к сбросу срока его действия. Оба метода, «touch» и «getAndTouch», имеют одинаковый эффект, но последний также получает документ.

// touch and set to 5 seconds expiry
Observable<Boolean> result = bucket.touch("id", 5);

// touch, set the expiry time and return the document
Observable<JsonDocument> result = bucket.getAndTouch("id", 5);

Блокировка и разблокировка

Подобные методы были добавлены, чтобы разрешить блокировку разблокировки документов с блокировкой записи. Блокировка записи выполняется с помощью метода «getAndLock». Чтобы разблокировать документ, его можно разблокировать с помощью команды «разблокировать» или после того, как в него будет добавлено соответствующее значение CAS. Также обратите внимание, что на стороне сервера документ будет разблокирован через 30 секунд автоматически.

// get and write lock for 10 seconds.
Observable<JsonDocument> doc = bucket.getAndLock("id", 10);

// unlock with the matching cas
long cas = doc.toBlocking().single().cas();
Observable<Boolean> done = bucket.unlock("id", cas);

счетчик

Если вам нужны атомарные операции увеличения и уменьшения для документа с числовым значением, здесь вам поможет метод счетчика. Начальное значение, дельта и необязательное время истечения:

// increment by 5 and set the initial value to 5 too
Observable<LongDocument> result = bucket.counter("id", 5);

// increment by 5 and the initial document to 0
Observable<LongDocument> result = bucket.counter("id", 5, 0);

// decrement by 3 and set the initial document to 10 and the expiry to 5
Observable<LongDocument> result = bucket.counter("id", -3, 10, 5);

Обратите внимание, что для удобства возвращается LongDocument, который содержит атомарно увеличенный / уменьшенный результат.

Добавить и добавить

Наконец, операции добавления и добавления документов были добавлены. Одно замечание: это не работает с документами JSON и должно использоваться только с произвольными байтовыми структурами или строками. Мы говорим о некоторых будущих улучшениях здесь, поэтому, пожалуйста, дайте нам знать, что вам будет интересно увидеть. Также обратите внимание, что документ должен быть создан, если он не существует. Вот пример, который пытается добавить что-то к документу (используя устаревший документ) и, если он не существует, создать его заново.

LegacyDocument doc = LegacyDocument.create("doc", "1,");
bucket
    .append(doc)
    .onErrorResumeNext(throwable -> {
        if (throwable instanceof DocumentDoesNotExistException) {
            return bucket.insert(doc);
        } else {
            // handle other errors
            return null;
        }
    })
    .subscribe();

Тот же API можно использовать для предварительной подготовки.

Дорога к бете

Единственный недостающий элемент API — это API-интерфейсы управления на уровне кластера (создание, удаление и т. Д.), Которые будут предоставлены в бета-версии. В настоящее время акцент смещается в сторону документации и стабильности. Прежде чем мы заблокируем API для выпуска 2.0, нам нужны ваши отзывы и комментарии, чтобы сделать его максимально простым и полезным. Кроме того, дайте нам знать, на каких деталях вы хотели бы видеть обширную документацию и руководство.