Is there a best practice for using Guava's ListenableFuture
in parameters in API calls? Compare these two versions of an interface that internally does a compare-and-set (I'm using Guava's ListenableFuture
):
interface MyDao {
ListenableFuture<Data> getData(String id);
ListenableFuture<Boolean> updateData(ListenableFuture<Data> data, int newFieldValue);
}
interface MyDao {
ListenableFuture<Data> getData(String id);
ListenableFuture<Boolean> updateData(Data data, int newFieldValue);
}
The first looks a bit messy, and the implementation will likely involve a Futures.transform
of its own, but you could also argue that it's an asynchronous API, so taking futures as parameters might make sense. Where it gets nice is that it tidies up the business logic when using the interface so it reads like synchronous code:
ListenableFuture<Data> data = dao.getData("123");
ListenableFuture<Boolean> success = dao.updateData(data, 10);
Compare that to
ListenableFuture<Data> data = dao.getData("123");
ListenableFuture<Boolean> success = Futures.transform(data, (Data d) -> dao.updateData(d, 10));
And that's with a lambda. It's hideous in Java 7.
The obvious drawback is the interface looks inconsistent. Why is data
a future, but newFieldValue
isn't? Should id
be a future, too? Along those lines, it makes a trivial call incredibly complicated—almost like Java wrapper objects before autoboxing.
It also doesn't do anything to clean up hideous stack traces when futures fail; they still cascade through every transform
.
modifyData
method with parameters ofString id, Function[Data => Data]