Abstract
In this post I’ll talk about the problems callbacks introduce into the readability of asynchronous Java programs. I’ll also discuss an implementation of method pointers for Java which aids the transformation of a callback-style program into a linear program flow, greatly increasing readability while still being a 100% asynchronous program.
On reactive programming in Java
The traditional JEE thread-per-request model does, apparently, not scale well because these threads sit most of the time idly waiting on some blocking operation such as a database query, a filesystem operation or an external web service. Thus, large enterprise programs require very many threads, which tax the operating system’s memory and task scheduler significantly.
Clever people came up with the idea to write asynchronous programs: instead of having a thread wait for an I/O operation, the program execution creates a callback object, encodes all information it needs to process the results of the I/O operation, gives that object to the I/O code and continues with something else.
The problem of blocking resources is tackled with new APIs which require callbacks. Thus, instead of waiting for a blocking call…
ResultSet rs = stmt.executeQuery("SELECT * FROM USERS");
The benefit here is that the call won’t block and the thread is free to do something else. Once the database returns the result set, a callback will handle the results.
ResultSetCallback rsCallback = new ResultSetCallback() { public void onResultSet(ResultSet rs) { while (rs.hasNext()) { rs.next(); System.out.println(rs.getLong(1) + " " + rs.getString(2) + " " + rs.getString(3) + " " + rs.getTimestamp(4)); } } }; st.executeQuery("select * from test", rsCallback);
Callbacks
Futures?
Reactive programming is many things to many people; to me it is the return to a linear program notation by using references to future values.
The Java API hides some of the problems with the use of Futures, but futures don’t do away with the conceptual mismatch: callbacks are the necessary and only bridge that joins a sequential program with components that return asynchronous data.
In an ideal world, the entire JRE and every library would support futures. Lets look at a simple example of a login form. A listener on the submit button listens for clicks. It then calls an asynchronous service and passes along a callback. Once the service call returns, the callback is called and user information is shown on a form.
Form form = ...; AuthService service = ...; form.button.addClickListener(new ClickListener(){ void onClick(){ String login = form.getLogin(); String password = form.getPassword(); service.authenticate(login, password, new Callback(){ void success(User user){ form.show(user); } void error(Exception e){ form.error(e); } }); } } );
This example contains two asynchronous structures: a click listener on a button and a network callback on a remote service.
A 100% reactive API where both the UI components and the remote service API support futures would look like this:
Form form = ...; AuthService service = ...; Future login = form.getLogin(); Future password = form.getPassword(); Future user = service.authenticate(login, password); form.show(user);
The reactive-programming library
The library has similarities to other reactive libraries: there are promises and there are callbacks. It differs however from other approaches in that it uses conventions which allow methods to be used directly as callbacks instead of callback objects:
FunctionPointer onUserAvailable(Promise user, Form form){ FunctionPointer fp = new FunctionPointer(this, user); if (user.isAvailable()){ form.showUserName(user.fullName); } return fp; } void loginUser(){ String login =form.getLogin(); String password =form.getPassword(); Promise user = service.getUser(login, password); user.invokeWhenAvailable(onUserAvailable(user, form)); }
This maybe doesn’t look like much at first sight, but we actually managed to call the method “onUserAvailable” once the “user” promise is resolved without any callbacks! Now what exactly happens here?
Function pointers are a little bit of magic. They are like bookmarks into code which can be called later on (I’ll disappoint Scheme fans here, this little trick is way short of full continuation support) . A simple example of just function pointers, without any reactive semantics:
FunctionPointer sayHi(String name){ System.out.println("Hello "+name); return new FunctionPointer(this, name); } ... FunctionPointer fp = sayHi("george"); // prints "Hello george" fp.invoke(); // prints "Hello george", again
If you dig into the API you’ll notice that function pointers also implement the Promise interface, hence they can contain values. A trivial example of where that might come in handy:
FunctionPointer sum(Promise b1, Promise b2){ FunctionPointer fp = new FunctionPointer(this, b1, b2); if (b1.isAvailable() && b2.isAvailable()){ fp.set(b1.get() + b2.get()); } return fp; } ... Promise balance1 = bank.queryAccountBalance(12345); Promise balance2 = bank.queryAccountBalance(67890); Promise sum = balance1.invokeWhenAvailable(sum(balance1, balance2)); reactiveForm.showAccountBalanceSum(sum);