Статьи

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

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

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

Во-первых, нам нужно  создать экземпляры  ReadOnlyMapWriteOnlyMap  и  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 неблокирующим способом, даже если они немного более многословны, чем предложено на других языках.
  • All data-handling methods for WriteOnlyMap return CompletableFuture<Void>, meaning that the user can find out when the operation has completed but nothing else, because there’s nothing the function can provide that could not be computed in advance or outside the function.
  • The return type for most of the data handling methods in ReadOnlyMap (and ReadWriteMap) are quite flexible. So, a function can decide to return value information, or metadata, or for convenience, it can also return the ReadEntryView it receives as parameter. This can be useful for users wanting to return both value and metadata parameter information.
  • The read-write operation demonstrated above showed how to remove an entry and return the previously associated value. In this particular case, we know there’s a value associated with the entry and hence we called ReadEntryView.get() directly, but if we were not sure if the value is present or not, ReadEntryView.find() should be called and return the Optional instance instead.
  • In the example, Lifespan metadata parameter is constructed using the new Java Time API available in Java 8, but it could have been done equally with java.util.concurrent.TimeUnit as long as the conversion was done to number of milliseconds during which the entry should be accessible.
  • Lifespan-based expiration works just as it does with other Infinispan APIs, so you can easily modify the example to lower the lifespan, wait for duration to pass and then verify that the value is not present any more.

If storing a constant value, WriteOnlyMap.eval(K, Consumer) could be used instead of WriteOnlyMap.eval(K, V, Consumer), making the code clearer, but if the value is variable, WriteOnlyMap.eval(K, V, Consumer)should be used to avoid, as much as possible, functions capturing external variables. Clearly, operations exposed by functional map can’t cover all scenarios and there might be situations where external variables are captured by functions, but these should in general, should be a minority. Here is as example showing how to implement ConcurrentMap.replace(K, V, V) where external variable capturing is required:

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();

The reason we didn’t add a WriteOnly.eval(K, V, V, Consumer) to the API is because value-equality-based replace comparisons are just one type of replace operations that could be executed. In other cases, metadata parameter based comparison might be more suitable, e.g. Hot Rod replace operation where version (a type of metadata parameter) equality is the deciding factor to determine whether the replace should happen or not.

In the next blog post, we’ll be looking at how to work with multiple entries using the Functional Map API.

Cheers,
Galder