Everything starts with creating an instance of
takes two core arguments, namely a reference to an Akka
ActorSystem and the location of the Riak node you want to interact with.
If your app is not based on Akka and therefor has no access to an initialized actor system,
you can leave off the
ActorSystem argument and one will be created for you
internally. The location of a riak node can be specified as either an HTTP url pointing
to root of your Riak node or a combination of a hostname and a port number.
It is important to note that internally
RiakClient uses an
Akka extension to ensure there can
be only one instance of
RiakClient per actor system. This means that you can "create"
as many instances of
RiakClient as you want because under the surface they all point at one single
Before we can fetch and store values, it might be a good idea to talk about how those values are represented.
represents the raw, serialized value stored in Riak and all its associated meta data,
such as its content type, its vclock, its etag, its last modified timestamp its indexes, etc.
Most interactions with Riak involve dealing with instances of
RiakValue in one way or another.
Since dealing with raw, untyped data is usually not what you want when programming in Scala, any
RiakValue can be transformed into an instance of
which represents the same Riak meta data as
RiakValue but now the raw data has been
deserialized into some type
T. This is accomplished by calling its
The reverse transformation is just as simple. Just call the
on any instance of
If you just want access to the deserialized T without any of the Riak meta data, you can call the
as[T] method on
This probably all seems pretty abstract to you. For a more integrated example of how to work with
RiakMeta, have a look at the Examples
Of course, these transformations between raw serialized data and typed deserialized
data don't happen by themselves. These transformations require compatible instances of the
type classes to be available in implicit scope. Let's have a look at their definitions:
String as the lowest level
encoding, which means binary formats are not currently supported. This was a design choice
made to keep the API as simple as possible. Please let us know if you would like support
for binary formats!
riak-scala-client comes with out-of-the-box support for (de)serializing
raw Strings (i.e no serialization at all, using text/plain as the content type) and
any case classes with an associated spray-json
RootJsonFormat (using application/json). These out-of-the-box implementations
will be used by default if you don't create your own serializers (and your classes fit the
criteria described above) See the Examples
section for examples using both builtin and custom serialization.
Just like in Riak, buckets in riak-scala-client are just a way to namespace your keys.
You can get a reference to a particular bucket simply by calling the
RiakClient, resulting in an instance of
Most functionality of riak-scala-client is exposed as methods on a
as you will see below.
As you would expect, fetching data from Riak is very simple indeed. Just call the
method on any bucket with a key fo your choice:
As you can see, the return type of
All operations in riak-scala-client that interact with Riak are non-blocking so all of those
operations will result in some type of
Future being produced. The
Examples section has lots of examples of how to interact with Futures.
Obviously not all keys will be bound to a value in Riak so the
fetch wraps an
Conflict resolution during fetch and fetching with secondary indexes will be discussed below.
Storing values in Riak is almost as easy as fetching them. The most basic store operation
is one that takes a (
String) key and a
RiakValue and returns
This is basically a fire-and-forget operation but you can use the returned Future to handle any
errors that might occur while riak-scala-client communicates with Riak. If you want to
have access to the value you just stored, or more probably to its meta data (i.e. to make sure you
are working a value based on the latest vclock), you can use the
which will perform a Riak HTTP store operation using the
returnbody=true query parameter.
Working with raw
RiakValue instances is fine when you get them returned from a
fetch operation but when you want to store data it is often more convenient to work
with your own domain classes. To that end you can also
store any type T for which
there is a
RiakSerializer[T] and a
RiakIndexer[T] in implicit scope.
As long as the value is either a
String, a class associated
spray-json RootJsonFormat[T], or a class associated with a custom
RiakSerializer[T], it will be automatically serialized and stored. The
Examples section has more details about how to use the builtin
serializers or how to define your own ones.
Don't worry about the indexer part for now since there is always a default indexer in scope that will not index anything. See the section on secondary indexes below for more information.
Lastly, you can also store any instance of
RiakMeta[T] directly without having
to convert it to a
Deleting values from Riak is probably the simplest operation you can perform. Just pass the key
As with the fire-and-forget version of
store, you can use the returned
to handle any possible errors gracefully.
If you decide to turn on support for siblings for one (or more) of your buckets, which you can do using
allow_mult bucket property (also available as
allowSiblings), then any
storeAndFetch can result in a conflict to be resolved.
riak-scala-client allows you to solve these conflicts yourself by specifying
when getting a reference to a bucket.
Conflict resolvers are usually implemented as (case) objects since they are stateless.
To create a custom resolver you will need to extend from the
trait and implement the
defined as follows:
Your custom conflict resolver will be presented with a
and its job is to produce one
RiakValue instance, which could be one from the set or a new one
created based on some combination of the values in the set.
If the bucket you are working with has sibling support turned on (i.e.
allow_mult == true), you
should always specify a conflict resolver. Failing to do so will activate the default no-op resolver, which will
throw an exception to remind you to specify a real resolver.
The Examples section contains at least one example of a custom resolver and you can find more examples in the riak-scala-client unit tests.
riak-scala-client fully supports Riak secondary indexes. Every
RiakValue has a set of
instances representing the secondary indexes it should be stored with. There are also a number of extra
versions of the
fetch method for fetching data by either a single index or an index range.
The result of such index fetches is always a
Future[List[RiakValue]] where riak-scala-client
takes care of fetching the individual values based on the keys returned by the HTTP index fetch.
Future[List[RiakValue]] here is not ideal since the list of values might be very big
and might not even fit into available memory. You also have to wait (in a matter of speaking) until all
the values have been retrieved before you can do anything with them. The next version of riak-scala-client
will very probably use Play Iteratees to reimplement these methods (or to create streaming versions of them).
That being said, let's look at some examples:
riak-scala-client will add the appropriate type suffix (i.e. "_bin" or "_int") to the name you specify for the index so you don't have to. All index names and index values will also be properly URL encoded so your index names and values can contain non-ascii values.
Just like you can create a custom (de)serializer type class for your domain class, you can also
create a custom indexer by providing an implicit implementation of the
trait, which looks like this:
riak-scala-client provides a default implementation of
RiakIndexer[T] for any
T that simply creates an empty set of indexes. This implementation is available from
the lowest possible implicit scopes so any custom implementation you make implicitly available will
always override it.