10 min read

Using the Coherence API

One of the great things about Coherence is that it has a very simple and intuitive API that hides most of the complexity that is happening behind the scenes to distribute your objects. If you know how to use a standard Map interface in Java, you already know how to perform basic tasks with Coherence.

In this section, we will first cover the basics by looking at some of the foundational interfaces and classes in Coherence. We will then proceed to do something more interesting by implementing a simple tool that allows us to load data into Coherence from CSV files, which will become very useful during testing.

The basics: NamedCache and CacheFactory

As I have briefly mentioned earlier, Coherence revolves around the concept of named caches. Each named cache can be configured differently, and it will typically be used to store objects of a particular type. For example, if you need to store employees, trade orders, portfolio positions, or shopping carts in the grid, each of those types will likely map to a separate named cache.

The first thing you need to do in your code when working with Coherence is to obtain a reference to a named cache you want to work with. In order to do this, you need to use the CacheFactory class, which exposes the getCache method as one of its public members. For example, if you wanted to get a reference to the countries cache that we created and used in the console example, you would do the following:

NamedCache countries = CacheFactory.getCache("countries");

Once you have a reference to a named cache, you can use it to put data into that cache or to retrieve data from it. Doing so is as simple as doing gets and puts on a standard Java Map:

countries.put("SRB", "Serbia");
String countryName = (String) countries.get("SRB");

As a matter of fact, NamedCache is an interface that extends Java’s Map interface, so you will be immediately familiar not only with get and put methods, but also with other methods from the Map interface, such as clear, remove, putAll, size, and so on.

The nicest thing about the Coherence API is that it works in exactly the same way, regardless of the cache topology you use. For now let’s just say that you can configure Coherence to replicate or partition your data across the grid. The difference between the two is that in the former case all of your data exists on each node in the grid, while in the latter only 1/n of the data exists on each individual node, where n is the number of nodes in the grid.

Regardless of how your data is stored physically within the grid, the NamedCache interface provides a standard API that allows you to access it. This makes it very simple to change cache topology during development if you realize that a different topology would be a better fit, without having to modify a single line in your code.

In addition to the Map interface, NamedCache extends a number of lower-level Coherence interfaces. The following table provides a quick overview of these interfaces and the functionality they provide:

The “Hello World” example

In this section we will implement a complete example that achieves programmatically what we have done earlier using Coherence console—we’ll put a few countries in the cache, list cache contents, remove items, and so on.

To make things more interesting, instead of using country names as cache values, we will use proper objects this time. That means that we need a class to represent a country, so let’s start there:

public class Country implements Serializable, Comparable {
private String code;
private String name;
private String capital;
private String currencySymbol;
private String currencyName;
public Country() {
}
public Country(String code, String name, String capital,
String currencySymbol, String currencyName) {
this.code = code;
this.name = name;
this.capital = capital;
this.currencySymbol = currencySymbol;
this.currencyName = currencyName;
}
public String getCode() {
return code;
}
public void setCode(String code) {
this.code = code;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getCapital() {
return capital;
}
public void setCapital(String capital) {
this.capital = capital;
}
public String getCurrencySymbol() {
return currencySymbol;
}
public void setCurrencySymbol(String currencySymbol) {
this.currencySymbol = currencySymbol;
}
public String getCurrencyName() {
return currencyName;
}
public void setCurrencyName(String currencyName) {
this.currencyName = currencyName;
}
public String toString() {
return "Country(" +
"Code = " + code + ", " +
"Name = " + name + ", " +
"Capital = " + capital + ", " +
"CurrencySymbol = " + currencySymbol + ", " +
"CurrencyName = " + currencyName + ")";
}
public int compareTo(Object o) {
Country other = (Country) o;
return name.compareTo(other.name);
}
}

There are several things to note about the Country class, which also apply to other classes that you want to store in Coherence:

  1. Because the objects needs to be moved across the network, classes that are stored within the data grid need to be serializable. In this case we have opted for the simplest solution and made the class implement the java.io.Serializable interface. This is not optimal, both from performance and memory utilization perspective, and Coherence provides several more suitable approaches to serialization.
  2. We have implemented the toString method that prints out an object’s state in a friendly format. While this is not a Coherence requirement, implementing toString properly for both keys and values that you put into the cache will help a lot when debugging, so you should get into a habit of implementing it for your own classes.
  3. Finally, we have also implemented the Comparable interface. This is also not a requirement, but it will come in handy in a moment to allow us to print out a list of countries sorted by name.

Now that we have the class that represents the values we want to cache, it is time to write an example that uses it:

import com.tangosol.net.NamedCache;
import com.tangosol.net.CacheFactory;
import ch02.Country;
import java.util.Set;
import java.util.Map;
public class CoherenceHelloWorld {
public static void main(String[] args) {
NamedCache countries = CacheFactory.getCache("countries");
// first, we need to put some countries into the cache
countries.put("USA", new Country("USA", "United States",
"Washington", "USD", "Dollar"));
countries.put("GBR", new Country("GBR", "United Kingdom",
"London", "GBP", "Pound"));
countries.put("RUS", new Country("RUS", "Russia", "Moscow",
"RUB", "Ruble"));
countries.put("CHN", new Country("CHN", "China", "Beijing",
"CNY", "Yuan"));
countries.put("JPN", new Country("JPN", "Japan", "Tokyo",
"JPY", "Yen"));
countries.put("DEU", new Country("DEU", "Germany", "Berlin",
"EUR", "Euro"));
countries.put("FRA", new Country("FRA", "France", "Paris",
"EUR", "Euro"));
countries.put("ITA", new Country("ITA", "Italy", "Rome",
"EUR", "Euro"));
countries.put("SRB", new Country("SRB", "Serbia", "Belgrade",
"RSD", "Dinar"));
assert countries.containsKey("JPN")
: "Japan is not in the cache";
// get and print a single country
System.out.println("get(SRB) = " + countries.get("SRB"));
// remove Italy from the cache
int size = countries.size();
System.out.println("remove(ITA) = " + countries.remove("ITA"));
assert countries.size() == size - 1
: "Italy was not removed";
// list all cache entries
Set<Map.Entry> entries = countries.entrySet(null, null);
for (Map.Entry entry : entries) {
System.out.println(entry.getKey() + " = " + entry.getValue());
}
}
}

Let’s go through this code section by section.

At the very top, you can see import statements for NamedCache and CacheFactory, which are the only Coherence classes we need for this simple example. We have also imported our Country class, as well as Java’s standard Map and Set interfaces.

The first thing we need to do within the main method is to obtain a reference to the countries cache using the CacheFactory.getCache method. Once we have the cache reference, we can add some countries to it using the same old Map.put method you are familiar with.

We then proceed to get a single object from the cache using the Map.get method , and to remove one using Map.remove. Notice that the NamedCache implementation fully complies with the Map.remove contract and returns the removed object.

Finally, we list all the countries by iterating over the set returned by the entrySet method. Notice that Coherence cache entries implement the standard Map.Entry interface.

Overall, if it wasn’t for a few minor differences, it would be impossible to tell whether the preceding code uses Coherence or any of the standard Map implementations. The first telltale sign is the call to the CacheFactory.getCache at the very beginning, and the second one is the call to entrySet method with two null arguments. We have already discussed the former, but where did the latter come from?

The answer is that Coherence QueryMap interface extends Java Map by adding methods that allow you to filter and sort the entry set. The first argument in our example is an instance of Coherence Filter interface. In this case, we want all the entries, so we simply pass null as a filter.

The second argument, however, is more interesting in this particular example. It represents the java.util.Comparator that should be used to sort the results. If the values stored in the cache implement the Comparable interface, you can pass null instead of the actual Comparator instance as this argument, in which case the results will be sorted using their natural ordering (as defined by Comparable.compareTo implementation).

That means that when you run the previous example, you should see the following output:

get(SRB) = Country(Code = SRB, Name = Serbia, Capital = Belgrade,
CurrencySymbol = RSD, CurrencyName = Dinar)
remove(ITA) = Country(Code = ITA, Name = Italy, Capital = Rome,
CurrencySymbol = EUR, CurrencyName = Euro)
CHN = Country(Code = CHN, Name = China, Capital = Beijing, CurrencySymbol
= CNY, CurrencyName = Yuan)
FRA = Country(Code = FRA, Name = France, Capital = Paris, CurrencySymbol
= EUR, CurrencyName = Euro)
DEU = Country(Code = DEU, Name = Germany, Capital = Berlin,
CurrencySymbol = EUR, CurrencyName = Euro)
JPN = Country(Code = JPN, Name = Japan, Capital = Tokyo, CurrencySymbol =
JPY, CurrencyName = Yen)
RUS = Country(Code = RUS, Name = Russia, Capital = Moscow, CurrencySymbol
= RUB, CurrencyName = Ruble)
SRB = Country(Code = SRB, Name = Serbia, Capital = Belgrade,
CurrencySymbol = RSD, CurrencyName = Dinar)
GBR = Country(Code = GBR, Name = United Kingdom, Capital = London,
CurrencySymbol = GBP, CurrencyName = Pound)
USA = Country(Code = USA, Name = United States, Capital = Washington,
CurrencySymbol = USD, CurrencyName = Dollar)

As you can see, the countries in the list are sorted by name, as defined by our Country.compareTo implementation. Feel free to experiment by passing a custom Comparator as the second argument to the entrySet method, or by removing both arguments, and see how that affects result ordering.

If you are feeling really adventurous and can’t wait to learn about Coherence queries, take a sneak peek by changing the line that returns the entry set to:

Set<Map.Entry> entries = countries.entrySet(
new LikeFilter("getName", "United%"), null);

As a final note, you might have also noticed that I used Java assertions in the previous example to check that the reality matches my expectations (well, more to demonstrate a few other methods in the API, but that’s beyond the point). Make sure that you specify the -ea JVM argument when running the example if you want the assertions to be enabled, or use the run-helloworld target in the included Ant build file, which configures everything properly for you.

That concludes the implementation of our first Coherence application. One thing you might notice is that the CoherenceHelloWorld application will run just fine even if you don’t have any Coherence nodes started, and you might be wondering how that is possible.

The truth is that there is one Coherence node—the CoherenceHelloWorld application. As soon as the CacheFactory.getCache method gets invoked, Coherence services will start within the application’s JVM and it will either join the existing cluster or create a new one, if there are no other nodes on the network. If you don’t believe me, look at the log messages printed by the application and you will see that this is indeed the case.

Now that you know the basics, let’s move on and build something slightly more exciting, and much more useful.

LEAVE A REPLY

Please enter your comment!
Please enter your name here