If you are a new jclouds developer, or even if you are already developing jclouds support for any of the OpenStack or Rackspace APIs, you have likely seen the domain classes that are used throughout the the jclouds codebase. These classes are used to represent OpenStack resources, particularly the JSON structures supported by OpenStack APIs.
For example, when listing database users in openstack-trove (the OpenStack database API), the service returns a JSON response body describing the existing users. This JSON might look something like this:
{
"users": [
{
"databases": [],
"host": "%",
"name": "dbuser1"
},
{
"databases": [
{
"name": "databaseB"
},
{
"name": "databaseC"
}
],
"host": "%",
"name": "dbuser2"
},
{
"databases": [],
"name": "dbuser3",
"host": "%"
},
{
"databases": [
{
"name": "sampledb"
}
],
"host": "%",
"name": "demouser"
}
]
}
To parse the response, jclouds uses domain classes to represent the JSON data returned by the service. The array of "users" is unwrapped into individual User domain objects. Conversely, when creating users, domain objects are transformed into a JSON request body.
Because of the relative simplicity of user creation in trove, jclouds developers can use a create method in the features package without having to build an instance of the User class. For example, the developer might use a method such as
boolean create(String userName, String password, String databaseName);
In this case, it was easy to add support for this call by using a map binder.
However, some APIs send or receive significantly more complex JSON structures. Recent work on Neutron has shown that there are benefits to increased consistency among the domain classes and the OpenStack API calls that use them.
Current implementations have the following two issues :
In addition to fixing these issues, jclouds wants to provide developers with some compiler checks and other syntactic sugar (fluent builders), while also supporting different updating, creating, or listing validation strategies.
We want to
We have been able to identify a pattern that addresses these issues. Here is some sample code.
This approach reuses code by having GSON handle the domain objects directly, as much as possible, both for serialization and deserialization, thus eliminating map-binders and parsers in most cases. The domain classes annotate their member variables using the @Named (for serialization) and @ConstructorProperties (for deserialization) annotations.
Many of the JSON attributes in Neutron are optional. GSON's jclouds configuration supports such optional values by using @Nullable and boxed types. An alternate supported method, more convoluted, implements Optional
To ensure immutability, users have no access to a constructor or setters, and instead they must instantiate domain objects by using a slightly modified Builder pattern. The builder pattern also provides proper validation and user-friendliness.
Some simpler classes implement the regular fluent builder pattern.
In other cases, the same domain class has several different purposes, such as making sure users have different Network-subtype object instances for updating, creating, and listing networks:
CreateOptions and UpdateOptions extend Network and implement their own copy constructors, with custom validation, if needed.
To instantiate these create or update-specific objects, developers have access to CreateBuilder and UpdateBuilder, which both extend the regular Network builder abstract class. The only code these special builders implement: the constructor (taking as parameters any required properties), a build() method returning the create or update object, and also self(). The self method is needed to make sure we can reuse most of the Builder code, but still be able to chain the fluent builder methods.
This is how it all works out from the developer's perspective:
Network.CreateOptions createNetwork = Network.createOptions("jclouds-wibble")
.networkType(NetworkType.LOCAL)
.build();
Network network = networkApi.create(createNetwork);
Network.UpdateOptions updateNetwork = Network.updateOptions()
.name("jclouds-wibble-updated")
.networkType(NetworkType.LOCAL)
.build();
networkApi.update("some id", updateNetwork);
This ensures developers get an easy to understand interface, with validation and compiler checks. It also allows jclouds developers to use significantly less code when developing complex domain classes that need to be reused in list/create/update API calls.
Comments powered by Disqus