electronicarts / ea-async

EA Async implements async-await methods in the JVM.

Home Page:https://go.ea.com/ea-async

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Without EA Async (2)

slisaasquatch opened this issue · comments

  public CompletableFuture<Boolean> buyItem(String itemTypeId, int cost) {
    return bank.decrement(cost)
        .thenCompose(decResult -> {
          if (decResult) return completedFuture(false);
          return completedFuture(null) // defer error
              .thenCompose(_void -> inventory.giveItem(itemTypeId))
              .handle((giveResult, t) -> {
                if (t == null) return completedFuture(true);
                return bank.refund(cost)
                    .<Boolean>thenApply(refundResult -> {
                      throw new RuntimeException(t);
                    });
              })
              .thenCompose(Function.identity());
        });
  }

I'm not saying it's prettier than the example, but I don't think this is ugly.

Definitely more difficult to follow the flow though, which is the main issue with promises/futures.

I saw this in the readme and couldn't help but think how it would be done.
I saw there are already 2 issues so i'm not making a new one, but it's an interesting example.

two problems make it difficult.

  1. The bank decrement has no error handling, so the later possible exception in inventory cannot be caught using exceptionally as it would not guarantee the cost was decremented
  2. The inventory give item refunds the cost on failure, which returns another future. you cannot handle exceptions in a compose function so you have to propagate this some how.

It makes a lot more sense to use transactions here as that would simplify the code and provide a greater guarantee of database consistency, but I'll assume that these are microservices being called or something and you can't do that.

The solution i came up with is similar to @slisaasquatch

public CompletableFuture<Boolean> buyItem(String itemTypeId, int cost) {
    return bank.decrement(cost).thenCompose(success -> {
        if(!success) return completedFuture(false);

        return inventory.giveItem(itemTypeId)
                .thenApply(result -> completedFuture(true))
                .exceptionally(inventoryException -> bank.refund(cost)
                        .handle((refundResult, refundException) -> {
                            throw new AppException(inventoryException);
                        }))
                .thenCompose(f -> f);
    });
}

Thanks for making this project and for the interesting challenge haha.
I guess there are really specific circumstances where futures just create syntax spaghetti and it becomes hard to follow.