Skip to content
This repository has been archived by the owner on Jan 8, 2020. It is now read-only.
Petter Måhlén edited this page Jan 29, 2014 · 22 revisions

What is Trickle?

Trickle is syntactic sugar on top of ListenableFutures. Its objective is to make it easier to understand code that implements non-trivial asynchronous call graphs. A typical example of when it is useful in a Spotify context is a search view, where you need to make the following calls:

  1. Find out which tracks match a certain search query.
  2. Based on the search results, fetch data such as album covers, composers, artists, etc., about each track.
  3. Based on the search results, find out from a separate service how many playlists each track is included in.
  4. When all the results are ready, convert them into the format the caller expects and return.

Doing this synchronously is easy, but asynchronously using raw ListenableFutures it gets a lot harder. Trickle makes it more pleasant.

Key design objectives:

  • Improve readability of code making nested asynchronous calls, in particular with regard to making the relationships between different calls clearer.
  • Type-safety.
  • Great error handling - ensure errors are always reported to the caller, unless explicitly configured to be hidden.
  • Making the fact that the business logic is executed in a concurrent context as hidden as possible. This means that a) application code shouldn't be able to interrupt the concurrent flow (so inputs should never be futures - application code should only be invoked when results are ready), and b) it's important to move as many as possible of the hard concurrency problems into the framework rather than solve them in application code.

Concepts

The following diagram shows a Trickle Graph, where the colour of a node indicates which function is executed in it.

A Trickle Graph

  • Graph: Trickle allows you to connect a set of calls into a graph. Graphs in Trickle have the following characteristics:
  • They are directed and acyclic.
  • They can have any number of named input parameters.
  • They have a single sink that returns the result of invoking the Graph. A sink in Trickle is a node whose output no other nodes need as inputs, and which no other nodes need to be executed after.
  • Nodes contain Functions that are executed with the inputs defined when connecting the graph. It is possible to execute the same exact Function in different Nodes in the same Graph.
  • Edges are either parameter dependencies (the function in node B uses the result of node A), or time dependencies (node B doesn't need the result of node A, but needs to happen afterwards).
  • Func: A func(tion) takes some parameters and returns a future to a result. Note that Trickle Functions are not pure functions, and thus not required to be side-effect-free. When a function is wired into a Graph, a Node is created, to which you can assign the following properties:
  • inputs using the with() method. An input is either a Name for a parameter or a previously defined Graph.
  • predecessors using the after() method. A predecessor is a graph that needs to be completed before the Function in this node can be invoked. The difference between a predecessor and an input is that we don't care about the value of the predecessor, so it's not going to be passed as a parameter to the function.
  • a fallback using the fallback() method. This fallback will get invoked in case of an exception and returns a fallback value to use instead.
  • a name using the named() method. This is just for debugging your graph.
  • Name: call graphs are often quite static, just like the code in a regular method. But there are usually some inputs to the graph that need to vary from invocation to invocation. These are parameters that you can Name and have to bind to concrete values before invoking a graph.

For examples, see Examples.java.

Other options that can help you simplify writing asynchronous code in Java that we know of and like are:

For some reasoning around design choices we made, see Design Rationales.

For an as-yet incomplete list of best practices, see Best Practices.

Clone this wiki locally