Top level functional map interface offering common functionality for the
read-only, read-write, and write-only operations that can be run against a
functional map.
DESIGN RATIONALES:
- Originally, I tried to come up with a single functional map interface
that would encompass read-only, read-write and write-only operations, but
it felt quite bloated. By separating each major type of operations to its
own interface, it becomes easier to figure out which are all the read-only
operations, which are write-only...etc, which also helps the user quickly
see what they can do without being obstructed by other operation types.
- The conscious decision to separate read-only, write-only and
read-write interfaces helps massively with type safety. So, if a user
gets a read-only map, it can't write to it by mistake since no such
APIs are exposed. The same happens with write-only maps, the user can
only write and cannot make the mistake of reading from the entry view
because read operations are not exposed. The previous design did not
have this level of type safety and hence it relied on runtime exceptions
to catch these wrong combinations.
- In the original design, we defined custom functional interfaces that
were serializable. By doing this, we could ship them around and apply
them remotely. However, for a function to be serializable, it can't
capture non-serializable objects which these days can only be detected
at runtime, hence it's not very type safe. On top of that, using foreign
function definitions would have made the code harder to read. For all
these reasons, we've gone for the approach of using standard lambda
functions. When these have to run in a clustered environment, instead of
shipping the lambda function around, necessary elements are brought to
the node where the function is passed, and the functions gets executed
locally. This way of working has also the benefit of matching how
Infinispan works internally, bringing necessary elements from other
nodes, executing operations locally and shipping results around. In the
future, we might decide to make these functions marshallable, but
outside the standard.
- For those operations that act on multiple keys, or return more than
one result, functional map uses a pull-based API whose main entry point
is
Traversable. Alternative designs based on push-based
approaches, e.g. Rx Java or similar, have been considered but it has
been decided against it. The main reason is usability, pull-based API
are easier from the user perspective. Even though it's a pull-based API,
it can be asynchronous underneath since the user can decide to work on
the traversable or iterator at a later stage.