io.sphere.sdk.meta.AsyncDocumentation Maven / Gradle / Ivy
package io.sphere.sdk.meta;
import java.util.concurrent.CompletionStage;
import java.util.concurrent.Executor;
import java.util.concurrent.ForkJoinPool;
import java.util.function.BiFunction;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Supplier;
/**
Why asynchronous
If you don't care about threads and asynchronous computation you will probably have a slow and inefficient application.
Suppose you want to show a customer detail page with the cart items and the customer data. For doing this, you need to fetch the cart and the customer.
Let's suppose fetching those two unrelated documents from SPHERE.IO takes 100ms for each document.
{@include.example io.sphere.sdk.meta.AsyncDocumentationTest#serialWayToFetchCustomerAndCart()}
So it takes around 200ms since the requests are done one after another. By fetching them in parallel 100ms of time can be saved.
{@include.example io.sphere.sdk.meta.AsyncDocumentationTest#parallelWayToFetchCustomerAndCart()}
Using futures (We use it here as synonym for {@link java.util.concurrent.CompletableFuture} and {@link java.util.concurrent.CompletionStage}.) can be very handy for executing code in parallel.
You can use future APIs to run code in separate Threads so that the result will not be immediately available, but in the future.
The overhead of creating a future can be lower than the overhead of creating new Thread.
Functional Composition
Functional composition covers transforming one future into another for the happy cases. {@link java.util.concurrent.CompletionStage#thenApply(java.util.function.Function)}
and {@link java.util.concurrent.CompletionStage#thenCompose(java.util.function.Function)} will only be called if the future finishes successfully.
ThenApply (map, function returns directly a value)
Mostly, it is easier to reason about side-effect free code. A future is monad so you do not work with the value directly,
but you provide functions to transform the value or the exception into a new future.
To use a future for further computation, you probably need to use {@link java.util.concurrent.CompletionStage#thenApply(java.util.function.Function)}.
{@include.example io.sphere.sdk.meta.AsyncDocumentationTest#thenApplyFirstDemo()}
With {@link java.util.concurrent.CompletionStage#thenApply(java.util.function.Function)} you apply a function to a stage
if this stage completes successfully. The function is a first class member, so you can store it in a value or even make it the return type of
a method.
{@include.example io.sphere.sdk.meta.AsyncDocumentationTest#thenApplyFirstDemoVerbose()}
{@include.example io.sphere.sdk.meta.FunctionAsReturnValueDemo}
It has similar semantics like {@link java.util.stream.Stream#map(java.util.function.Function)}.
{@include.example io.sphere.sdk.meta.AsyncDocumentationTest#functionalCompositionMapStreamExample()}
ThenCompose (flatMap, function returns a CompletionStage)
Sometimes you run in situations where you create a new future inside a future.
For example if you load a cart and want to fetch the first line item in it.
{@include.example io.sphere.sdk.meta.AsyncDocumentationTest#shouldUseFlatMap()}
Instead of creating an unmaintainable {@link java.util.concurrent.CompletionStage} of {@link java.util.concurrent.CompletionStage},
you can use {@link java.util.concurrent.CompletionStage#thenCompose(java.util.function.Function)}.
{@include.example io.sphere.sdk.meta.AsyncDocumentationTest#flatMapFirstDemo()}
It has similar semantics like {@link java.util.stream.Stream#flatMap(java.util.function.Function)}.
{@include.example io.sphere.sdk.meta.AsyncDocumentationTest#functionalCompositionFlatMapStreamExample()}
Callbacks
For some occasions you do not want to transform a value, but to perform a side effect task, like logging a value or an error,
writing sth. into a file or sending a response for a request.
{@include.example io.sphere.sdk.meta.AsyncDocumentationCallbackTest#loggingCallbackExample()}
{@link java.util.concurrent.CompletionStage#whenComplete(java.util.function.BiConsumer)} keeps the result as it is and performs side-effects,
so it is nice to log in between and then map the stage to a new one:
{@include.example io.sphere.sdk.meta.AsyncDocumentationCallbackTest#whenCompleteAsyncDemo()}
Creation and filling
Creation of a successful future
A future can be created as fulfilled immediately:
{@include.example io.sphere.sdk.meta.AsyncDocumentationTest#createImmediatelyFulfilledFuture()}
Also future can be fulfilled later:
{@include.example io.sphere.sdk.meta.AsyncDocumentationTest#createFulfilledFuture()}
For the immediately fulfilled future an SDK utility method also exists:
{@include.example io.sphere.sdk.meta.AsyncDocumentationTest#createImmediatelyFulfilledFutureShortcut()}
Creation of a failed future
{@include.example io.sphere.sdk.meta.AsyncDocumentationTest#createFailedFuture()}
Using an SDK shortcut:
{@include.example io.sphere.sdk.meta.AsyncDocumentationTest#createFailedFutureShortcut()}
If you complete a future, it is possible that the same Thread is used for functional compositions or executing callbacks. If you don't want this, the calls need to use the methods which end with "Async".
Blocking Access and Immediate Access
{@link java.util.concurrent.CompletionStage} does not provide immediate or blocking access to its value or error,
but it is possible, but not encouraged to transform the {@link java.util.concurrent.CompletionStage} with {@link CompletionStage#toCompletableFuture()} to a {@link java.util.concurrent.CompletableFuture}.
Blocking access
{@include.example io.sphere.sdk.meta.AsyncDocumentationTest#futureJoinDemo()}
Access with timeout
Future completes in time:
{@include.example io.sphere.sdk.meta.AsyncDocumentationTest#futureGetTimeoutDemo()}
future does not complete in time:
{@include.example io.sphere.sdk.meta.AsyncDocumentationTest#futureGetTimeoutDemoWithActualTimeout()}
Access for the impatient
Future completed:
{@include.example io.sphere.sdk.meta.AsyncDocumentationTest#demoGetNowCompleted()}
Future did not yet complete:
{@include.example io.sphere.sdk.meta.AsyncDocumentationTest#demoGetNow()}
Workaround if the value should be lazy computed:
{@include.example io.sphere.sdk.meta.AsyncDocumentationTest#testOrElseGet()}
See {@link io.sphere.sdk.utils.CompletableFutureUtils#orElseGet(CompletionStage, Supplier)}.
Workaround if exception should be thrown:
{@include.example io.sphere.sdk.meta.AsyncDocumentationTest#testOrElseThrow()}
{@include.example io.sphere.sdk.meta.AsyncDocumentationTest#testOrElseThrowHappyPath()}
See {@link io.sphere.sdk.utils.CompletableFutureUtils#orElseThrow(CompletionStage, Supplier)} .
Summary of Blocking Access and Immediate Access methods
Blocking Access and Immediate Access methods of {@link java.util.concurrent.CompletableFuture}
future.join()
future.get()
future.get(12, TimeUnit.MILLISECONDS)
future.getNow("default")
returns value if present
x
x
x
x
blocks potentially forever
x
x
uses alternative, if value not present
x
throws TimeoutException
x
throws CompletionException
x
x
throws ExecutionException
x
x
throws only unchecked Exceptions
x
x
Java Functions
Since Java 8, the JDK provides Lambda Expressions
and Method References.
Important {@link FunctionalInterface}s
number of arguments
behavior
checked Exception
purpose
{@link java.util.function.Function}<T,R>
1
create value
transforms one value into another
{@link java.util.function.BiFunction}<T,U,R>
2
create value
transforms two values into another
{@link java.util.function.Consumer}<T>
1
side-effects
side effect for one value
{@link java.util.function.BiConsumer}<T,U>
2
side-effects
side effect for two values
{@link java.util.function.Supplier}<T>
0
create value
on-demand creation of a value
{@link java.util.concurrent.Callable}<V>
0
create value
x
like Supplier but throws Exception
{@link java.lang.Runnable}
0
side-effects
task which causes side-effects
Threads and the Trinity
Which Thread is used for functional composition and callbacks depends on the method.
For {@link java.util.concurrent.CompletionStage#thenApply(Function)},
{@link java.util.concurrent.CompletionStage#thenAccept(Consumer)} ,
{@link java.util.concurrent.CompletionStage#handle(BiFunction)} etc. exist three variants:
- {@link java.util.concurrent.CompletionStage#thenApply(Function)}, no suffix, if the future is not yet completed, the the thread which calls
{@link java.util.concurrent.CompletableFuture#complete(Object)} is used to apply the function, if the future is completed, the thread which calls {@link java.util.concurrent.CompletionStage#thenApply(Function)} is used.
So this method is discouraged if you use actors or tend to block threads.
- {@link java.util.concurrent.CompletionStage#thenApplyAsync(Function)} with suffix "Async" calls the function inside a Thread of {@link ForkJoinPool#commonPool()}. So you are better protected against deadlocks.
- {@link java.util.concurrent.CompletionStage#thenApplyAsync(Function, Executor)} with suffix "Async" and additional {@link Executor} parameter calls the function inside a Thread pool you specify as second parameter.
Error Handling
If an exception occurs with the computation, it should be propagated to the future with {@link java.util.concurrent.CompletableFuture#completeExceptionally(Throwable)}, but only once in the lifetime of the future.
As a result the following try catch block does not make sense, since the error is inside the future which is most likely computed in another Thread:
{@include.example io.sphere.sdk.meta.AsyncDocumentationTest#wrongWayOfErrorHandling()}
Recover from a failure without a new CompletionStage
{@include.example io.sphere.sdk.meta.AsyncDocumentationTest#simpleRecover()}
Recover from a failure with producing a new CompletionStage
{@include.example io.sphere.sdk.meta.AsyncDocumentationTest#simpleRecover()}
Handle
{@link CompletionStage#exceptionally(Function)} is like applying {@link CompletionStage#thenApply(Function)} and then {@link CompletionStage#exceptionally(Function)}:
{@include.example io.sphere.sdk.meta.AsyncDocumentationTest#handleLikeExceptionallyAndThenApply()}
You can use the exceptions to give error specific text to the user:
{@include.example io.sphere.sdk.meta.AsyncDocumentationTest#exceptionallyWithExceptionTypes()}
But you do not need to cover all problems:
{@include.example io.sphere.sdk.meta.AsyncDocumentationTest#exceptionallyWithExceptionTypesButUncoveredPart()}
Working with multiple futures
Traps
Advanced Examples
Summary
{@link java.util.concurrent.CompletionStage} vs. {@link java.util.concurrent.CompletableFuture}
CompletionStage
CompletableFuture
type
interface
concrete class
implements CompletionStage
x
x
functional composition
x
x
implements Future interface
x
can be filled with value or exception
x
can be cancelled
x
can check if completed
x
blocking usage directly possible
x
provides static methods for creation
x
CompletionStage methods
value
effect
Function param
Consumer param
Runnable param
maps value
maps error
or
and
Scala Future
Play F.Promise
thenApply
x
x
x
map
map
thenCompose
x
x
x
flatMap
flatMap
thenAccept
x
x
x
onSuccess
onSuccess
thenRun
x
x
exceptionally
x
x
x
recover
recover
handle
x
x
x
x
andThen
whenComplete
x
x
x
x
x
acceptEither
x
x
x
x
thenAcceptBoth
x
x
x
x
zip
zip
applyToEither
x
x
x
x
fallbackTo
fallbackTo
thenCombine
x
x
x
x
runAfterEither
x
x
x
runAfterBoth
x
x
x
Further Topics
Further read and sources
*/
public final class AsyncDocumentation {
private AsyncDocumentation() {
}
}