#2 Discover Couchbase: Couchbase Lite Live Query and Local Caching

Couchbase #2

In the previous article, we saw up close what Couchbase Lite is and how we can use it within our app for major CRUD operations. Today we’re going to find out what potential its Query engine offers and what tools it offers and how I can make the most of Live Queries.

Modern mobile applications require modern solutions to common problems. Today it is very rare to find a mobile app that does not communicate with the „outside world“. In most cases, data is retrieved from remote APIs, using simple HTTP REST calls. But an app can do even more. It can request data from Bluetooth devices, location services, and so on.

Video introduction to the contents of the article told by Matteo Sist and Damiano Giusti

All these cases have a common requirement to allow the application to work properly: a working connection to a defined source. In fact, it is quite difficult to think of an HTTP call without actually having a reachable cloud provider or a local server. Right?

Data available also offline

In this post, we will talk about the importance of data availability. For most mobile applications we can say that it is essential to provide offline data to the end-user.

As a mobile app developer, you’ll face this scenario. There is no way out. If you have applied the common principles of software engineering, you will find yourself with the code that performs basic data recovery and manipulation in an isolated layer of the application. But if you weren’t so wise in designing your basic architecture (for absolutely justifiable reasons, right?), the concepts I will present could help you in the same way.

Okay, so?

You need a cache. An optimal solution that can help you (also) store cached data is Couchbase Lite. I’ll show you two use cases, so it’s up to you to choose which solution can best meet your needs!

Wait, what is Couchbase Lite?

Simple. It is a NoSQL database that works very well with apps. It is in circulation since 2014 in the „Lite“ version, which is dedicated to the mobile world. Basically, it allows you to store data as JSON documents, with key-value elements. Even though it is a NoSQL store, queries are surprisingly fast, thanks to its powerful N1QL query language that offers a syntax closer to standard SQL to make it easier to access the information we are looking for.

#1 – Simple caching solution

Couchbase works very well as a cache storage layer. Thanks to its speed it offers quick access to cached data, even for writes.

Example: Couchbase Lite as local caching

Let’s take a common example: suppose you have to cache the results of an API call because this data is static and does not change frequently. First, let’s check if the requested data is already cached. If so, we will return it directly, otherwise, we will make an API call and cache the result.

Our repository class will appear as shown below:

public class MyDataRepository {
  private final Cache cache;
  private final HttpClient HttpClient;
 
  public MyDataRepository(Cache cache, HttpClient httpClient) {
    this.cache = cache;
    this.httpClient = httpClient;
  }
 
  public Cancelable getData(String identifier, Function<Data> callback) {
    final Data cachedData = cache.get(identifier);
    if (cachedData == null) {
      return httpClient.getData(identifier, (data) -> { 
        cache.set(identifier, data);
        callback.call(data);
      });
    } else {
      callback.call(cachedData);
      return Cancelable.empty();
    }
  }
}

Looking at this masterpiece, we assume that Cancelable is an object that can be used to delete running tasks, such as the API call we make in the snippet above. Let’s also assume Cancelable.empty() as a Cancelable that does nothing.

First, let’s see how the object is composed Data:

public class Data {
  private String identifier;
  private String description;
 
  public Data(String identifier, String description) {
    this.identifier = identifier;
    this.description = description;
  }
 
  public String getIdentifier() { 
    return identifier; 
  }
  
  public String getDescription() { 
    return description; 
  }
}

It is a simple value object, without setters, because we want to make it unchangeable from the outside. Let’s take a look at the integration with Couchbase Lite.

We indicate Cache as an interface defined as below:

public interface Cache<T, K> {
  @Nullable T get(K identifier);
  void set(K identifier, @Nullable T value);
}

And we can implement it using the Couchbase Lite SDK:

public class CouchbaseLiteCache implements Cache<Data, String> {
 
  private Database database;
 
  CouchbaseLiteCache(Database database) {
    this.database = database;
  }
 
  @Override @Nullable public Data get(String identifier) {
    final Document document = database.getDocument(identifier);
    if (document == null) {
      return null;
    } else {
      return new Data(
        document.getString("identifier"),
        document.getString("description")
      );
    }
  }
 
  @Override public void set(String identifier, @Nullable Data value) {
    try {
      if (value == null) {
        database.purge(identifier);
      } else {
        database.save(
          new MutableDocument(identifier)
            .setString("identifier", value.getIdentifier())
            .setString("description", value.getDescription())
        );
      }
    } catch (CouchbaseLiteException e) {
      throw new CachingException(e);
    }
  }
 
  public static CouchbaseLiteCache create(final String databaseName) throws CouchbaseLiteException {
    return new CouchbaseLiteCache(new Database(databaseName, new DatabaseConfiguration()));
  }
}

The implementation is quite simple and aims to cache HTTP call response data for later reuse.

#2 – “Single source of truth”

Another scenario concerns the adoption of Couchbase Lite as a „single source of truth“ for the data layer of our app. To get the most out of this solution, my suggestion is to apply it together with an Observer pattern. We can choose to implement the pattern from scratch or adopt some powerful and complete libraries, such as RxJava. Honestly, I highly recommend the latter option. But for the purposes of this example, let’s keep a callback-style flow.

We still have our repository class, which acts as a front for data access. But in this scenario, we will have the cache as the main data source, regardless of whether we need to call the REST API or not.

Couchbase Lite: the Live Query

Let’s first explain what Couchbase Lite offers us:

Like other modern databases, Couchbase Lite lets you listen to changes that occur at the database level. When we run a database query, we can run one of a particular type, called Live Query.

A Live Query allows you to be „observed“ through a callback attached to it. The callback will be invoked every time a change in the dataset undergoes a change that affects the result set of the specified query. The supported changes are basically all CRUD operations that can be performed on a database. 

That said, you may have begun to understand what we will implement. To create a single source of truth in „live“ mode, we subscribe to the updates of a Live Query that we define. This will „bring our results to life“. So, we call our HTTP REST API, storing the results in the database. In this way, the Live Query observer will be activated and our data source will issue an update.

Use Live Query with Couchbase Lite

Let us now see a practical example.

We add a method to our Cache interface, allowing implementations to issue updates:

public interface Cache<T, K> {
  @Nullable T get(K identifier);
  void set(K identifier, @Nullable T value);
  
  Cancelable getUpdating(K identifier, Function<Data> callback);
}

Then implement it using the Live Query feature of Couchbase Lite.

public class CouchbaseLiteCache implements Cache<Data, String> {
  
  private Database database;
 
  CouchbaseLiteCache(Database database) {
    this.database = database;
  }
  
  // ...
 
  @Override public Cancelable getUpdating(String identifier, Function<Data> callback) {
    final Query query = QueryBuilder
      .select(all())
      .from(database(database))
      .where(string("identifier").equalTo(string(identifier)));
 
    final ListenerToken token = query.addChangeListener(queryChange -> {
      final Data data = getDataFromQueryChange(queryChange);
      if (data != null) callback.call(data);
    });
 
    return () -> query.removeChangeListener(token);
  }
 
  // ...
 
  private static Data getDataFromQueryChange(final QueryChange queryChange) {
    final Iterator<Result> iterator = queryChange.getResults().iterator();
    if (iterator.hasNext()) {
      final Result result = iterator.next();
      return new Data(
        result.getString("identifier"),
        result.getString("description")
      );
    } else {
      return null;
    }
  }
}

We pay attention to the Cancelable implementation. When running a Live Query, the SDK returns a ListenerToken object. This token allows you to unsubscribe from query updates. Keep track of your ListenerToken, you have to unsubscribe to all your queries when they are no longer useful to you, to avoid leaks!

Also, note how beautiful the query API is. Thanks also to static Java imports, the query language becomes fluent, readable and type-safe. But this is Java… I wonder how much you can improve it by adding some Kotlin extension functions and some „infix“ syntax?

Let’s now integrate everything in the repository

At this point, we can integrate the new cache method into our repository.

public class MyLiveRepository {
 private final Cache<Data, String> cache;
 private final HttpClient httpClient;
 
 public MyLiveRepository(Cache<Data, String> cache, HttpClient httpClient) {
   this.cache = cache;
   this.httpClient = httpClient;
 }
 
 public Cancelable getData(String identifier, Function<Data> callback) {
   final Cancelable cacheSubscription = cache.getUpdating(identifier, callback);
   final Cancelable restCall = httpClient.getData(identifier, (data) -> cache.set(identifier, data));
   return () -> {
     restCall.cancel();
     cacheSubscription.cancel();
   };
 }
}

The getData method uses the callback provided to directly observe cache updates. It then executes the HTTP REST call, updating the dataset.
Note again the use of the Cancelable object. Here are grouped the cancellations of the remaining calls and subscriptions to the cache updates: when the resulting Cancelable is placed, we must obviously stop the operations in progress.

Done! You have promoted Couchbase Lite to „source of truth“ for your application data.

Summing up

Couchbase Lite is definitely an interesting technology. With this article I wanted to show how it can help you get effective solutions to common problems. Of course you can find out more by consulting the official documentation.

Couchbase is not just a local database: Couchbase Server and Couchbase Sync Gateway are leading tools offered for creating web and mobile real-time applications. And with the Community Edition, you can get them all for free. Surely, Couchbase is an ecosystem that’s definitely worth a try, to see if it can adapt to your needs or the needs of your business.

This and much more on Couchbase awaits you, stay tuned.

Thanks for reading!