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. 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:
Interface Name
|
Description
|
---|
CacheMap
|
Provides a way to specify expiration for a map entry
|
ObservableMap
|
Adds the ability to register listeners for various cache events
|
QueryMap
|
Allows you to execute queries against the cache
|
InvocableMap
|
Enables execution of processing agents within the grid
|
ConcurrentMap
|
Adds support for distributed concurrency control
|
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:
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 .
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.
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, 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.