Статьи

API функциональной карты: работа с отдельными записями

В этом сообщении мы продолжим знакомство с экспериментальным API-интерфейсом Functional Map, который был выпущен как часть Infinispan 8.0.0.Final , и сосредоточимся на том, как манипулировать данными с помощью одноключевых операций.

Как уже упоминалось во введении API-интерфейса Functional Map , существует три типа операций, которые могут быть выполнены с функциональной картой: операции только для чтения (выполняемые через ReadOnlyMap ), операции только для записи (выполняемые через WriteOnlyMap ) и операции чтения-записи ( выполняется через ReadWriteMap ) и.

Во-первых, нам нужно создать экземпляры ReadOnlyMap , WriteOnlyMap и ReadWriteMap, чтобы иметь возможность работать с ними:

import org.infinispan.AdvancedCache;
import org.infinispan.commons.api.functional.FunctionalMap.*;
import org.infinispan.functional.impl.*;
import org.infinispan.manager.DefaultCacheManager;

DefaultCacheManager cacheManager = new DefaultCacheManager();
AdvancedCache<String, String> cache = cacheManager.<String, String>getCache().getAdvancedCache();
FunctionalMapImpl<String, String> functionalMap = FunctionalMapImpl.create(cache);

ReadOnlyMap<String, String> readOnlyMap = ReadOnlyMapImpl.create(functionalMap);
WriteOnlyMap<String, String> writeOnlyMap = WriteOnlyMapImpl.create(functionalMap);
ReadWriteMap<String, String> readWriteMap = ReadWriteMapImpl.create(functionalMap);

Далее, давайте посмотрим на все три типа операций в действии, объединяя их в цепочку для хранения одной пары ключ / значение вместе с некоторыми метаданными, затем читаем ее и, наконец, удаляем возвращенные ранее сохраненные данные:

import org.infinispan.commons.api.functional.EntryView.ReadEntryView;
import org.infinispan.commons.api.functional.FunctionalMap.ReadOnlyMap;
import org.infinispan.commons.api.functional.FunctionalMap.ReadWriteMap;
import org.infinispan.commons.api.functional.FunctionalMap.WriteOnlyMap;
import org.infinispan.commons.api.functional.MetaParam.MetaLifespan;

import java.time.Duration;
import java.util.concurrent.CompletableFuture;

ReadOnlyMap<String, String> readOnlyMap = ...
WriteOnlyMap<String, String> writeOnlyMap = ...
ReadWriteMap<String, String> readWriteMap = ...

// Write a value and metadata to be associated with a key
CompletableFuture<Void> writeFuture = writeOnlyMap.eval("key1", "value1",
   (v, writeView) -> writeView.set(v, new MetaLifespan(Duration.ofHours(1).toMillis())));

// Chain a read operation to happen after the write operation has completed
CompletableFuture<ReadEntryView<String, String>> readFuture = writeFuture.thenCompose(
   ignore -> readOnlyMap.eval("key1", readView -> readView));

// Chain an operation to log the value read
CompletableFuture<Void> logReadFuture = readFuture.thenAccept(
   view -> System.out.printf("Read entry view: %s%n", view));

// Chain an operation to remove and return previously stored value
CompletableFuture<String> removeFuture = logReadFuture.thenCompose(
   ignore -> readWriteMap.eval("key1", readWriteView -> {
      String previousValue = readWriteView.get();
      readWriteView.remove();
      return previousValue;
   }));

// Finally, log the previously stored value returned by the remove
CompletableFuture<Void> logRemoveFuture = removeFuture.thenAccept(
   previousValue -> System.out.printf("Removed value: %s%n", previousValue));

// Wait for the chain of operations to complete
logRemoveFuture.get();

В этом примере демонстрируются некоторые ключевые аспекты работы с отдельными записями с использованием API-интерфейса Functional Map:

  • Методы с одной записью — это асинхронные возвращающие   экземпляры CompletableFuture, которые предоставляют методы для компоновки и цепочки операций, чтобы можно было чувствовать, что они выполняются последовательно. К сожалению, в Java нет нотации do Haskell или Scala для понимания, чтобы сделать его более приемлемым, но это хорошая новость, что Java наконец-то предлагает механизмы для работы с CompletableFutures неблокирующим способом, даже если они немного более многословны, чем предложено на других языках.
  • Все методы обработки данных для WriteOnlyMap возвращают CompletableFuture <Void> , что означает, что пользователь может узнать, когда операция завершена, но ничего другого, потому что функция не может предоставить ничего, что не могло бы быть вычислено заранее или вне функции.
  • Тип возврата для большинства методов обработки данных в ReadOnlyMapReadWriteMap ) достаточно гибок. Таким образом, функция может принять решение о возврате информации о значении или метаданных, или для удобства она также может возвратить ReadEntryView, который она получает в качестве параметра. Это может быть полезно для пользователей, желающих вернуть информацию о значении и параметрах метаданных.
  • Операция чтения-записи, показанная выше, показала, как удалить запись и вернуть ранее связанное значение. В этом конкретном случае мы знаем, что есть значение, связанное с записью, и, следовательно, мы вызвали ReadEntryView.get () напрямую, но если мы не были уверены, присутствует ли это значение или нет,  следует вызвать ReadEntryView.find () и вернуть Необязательный экземпляр вместо.
  • В этом примере параметр метаданных Lifespan создается с использованием нового API времени Java, доступного в Java 8, но его можно было бы сделать в равной степени с  java.util.concurrent.TimeUnit, если преобразование выполнялось в число миллисекунд, в течение которых запись должен быть доступен.
  • Срок действия, основанный на сроке службы, работает так же, как и с другими API Infinispan, поэтому вы можете легко изменить пример, чтобы уменьшить срок службы, подождать, пока пройдет продолжительность, а затем убедиться, что значение больше не присутствует.

Если хранится постоянное значение, WriteOnlyMap.eval (K, Consumer) может использоваться вместо  WriteOnlyMap.eval (K, V, Consumer) , что делает код более понятным, но если значение является переменным,  WriteOnlyMap.eval (K, V, Потребитель) следует использовать, чтобы избежать, насколько это возможно, функций, собирающих внешние переменные. Ясно, что операции, предоставляемые функциональной картой, не могут охватывать все сценарии, и могут быть ситуации, когда внешние переменные захватываются функциями, но в целом их должно быть меньшинство. Вот пример, показывающий, как реализовать ConcurrentMap.replace (K, V, V), где требуется захват внешних переменных:

import org.infinispan.commons.api.functional.FunctionalMap.ReadWriteMap;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;

ReadWriteMap<String, String> readWriteMap = ...

// First, do a put-if-absent like operation
CompletableFuture<Boolean> putIfAbsentFuture = readWriteMap.eval("key1", readWriteView -> {
   Optional<String> opt = readWriteView.find();
   boolean isAbsent = !opt.isPresent();
   if (isAbsent) readWriteView.set("value1");
   return isAbsent;
});

// Chain the put if absent with an operation to log whether the put-if-absent was successful
CompletableFuture<Void> logPutIfAbsentFuture = putIfAbsentFuture.thenAccept(
   success -> System.out.printf("Put if absent successful? %s%n", success));

// Next, chain a replace operation comparing a captured value via equals
String oldValue = "value1";
CompletableFuture<Boolean> replaceFuture = logPutIfAbsentFuture.thenCompose(x ->
   readWriteMap.eval("key1", "value2", (v, readWriteView) ->
      readWriteView.find().map(prev -> {
         if (prev.equals(oldValue)) {
            readWriteView.set(v);
            return true; // Old value matches so set new value and return success
         }
         return false; // Old value does not match
      }).orElse(false) // No value found in the map
));

// Finally, log the result of replace
CompletableFuture<Void> logReplaceFuture = replaceFuture.thenAccept(
   replaced -> System.out.printf("Replace successful? %s%n", replaced));

// Wait for the chain of operations to complete
logReplaceFuture.get();

Причина, по которой мы не добавили WriteOnly.eval (K, V, V, Consumer) в API, заключается в том, что сравнения замен на основе равенства на основе значений являются лишь одним из типов операций замены, которые могут быть выполнены. В других случаях сравнение на основе параметров метаданных может быть более подходящим, например, операция замены Hot Rod, где равенство версии (тип параметра метаданных) является решающим фактором, чтобы определить, должна ли произойти замена или нет.

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