Release 0.10.3, December 2019
1. Overview
Variant Java client is a library which enables the host application to communicate with Variant AIM server. It exposes server’s functionality in a native Java API and in terms of native Java classes. It can be consumed by any host application, written in Java or another JVM language. It requires Java runtime 8 or higher. Variant Java client is consumable by any Java program because it makes no assumptions about the technology stack of the host application. This flexibility comes at the expense of some deferred dependencies, such as a mechanism for tracking Variant session ID between state request. These deferred dependencies are provided by stack-specific adapters, as explained further. For Java stacks not (yet) supported by an adapter, application developer can supply a custom implementation. Most Java Web applications are written on top of the Servlet API. These applications should take advantage of the servlet adapter, which implements deferred dependencies and wraps Variant client in an identical but simplified API, whose deferred methods have been re-written in terms of familiar servlet objects, like HttpServletRequest
and HttpServletResponse
.
2. Installation
- Download Variant Java client distribution.
- Unpack the distribution:
% unzip /path/to/variant-java-<release>.zip
This will inflate the following artifacts:
File | Description |
---|---|
variant-java-client-<release>.jar | Variant Java client library. Must be present on the host application’s classpath. |
variant-core-<release>.jar | Dependent Variant core library. Must be present on the host application’s classpath. |
- If your Java application is built using a dependency management tool like Maven, you have the following options:
- Install into your company’s Maven repository. Contact your DevOps for help. Once installed, you can reference them in your aplication’s
pom.xml
file like so:<dependency> <groupId>com.variant</groupId> <artifactId>java-client</artifactId> <version>[0.10,)</version> </dependency> <dependency> <groupId>com.variant</groupId> <artifactId>variant-core</artifactId> <version>[0.10,)</version> </dependency> - Install into your private Maven repository by typing the following (replacing
<release>
with the particular version number you’re installing, e.g.0.10.2
):% mvn install:install-file -Dfile=/path/to/variant-java-client-<release>.jar \ -DgroupId=com.variant -DartifactId=java-client -Dversion=<release> -Dpackaging=jar % mvn install:install-file -Dfile=/path/to/variant-core-<release>.jar \ -DgroupId=com.variant -DartifactId=variant-core -Dversion=<release> -Dpackaging=jar You may now reference these artifacts the same was as in the previous paragraph. - Reference the artifacts directly from your file system by placing them in some directory, e.g.
lib
, and adding the following to your application’spom.xml
file (replacing<release>
with the particular version number you’re installing, e.g.0.10.2
):<dependency> <groupId>com.variant</groupId> <artifactId>java-client</artifactId> <version>[0.10,)</version> <scope>system</scope> <systemPath>${project.basedir}/lib/variant-java-<release>.jar </dependency> <dependency> <groupId>com.variant</groupId> <artifactId>variant-core</artifactId> <version>[0.10,)</version> <scope>system</scope> <systemPath>${project.basedir}/lib/variant-core-<release>.jar </dependency>
Variant Java client has two external transitive dependencies, which are not included in the distribution: Apache HTTP Client (4.5+) and Simple Logging Facade for Java (1.7+) . If these dependencies aren’t already used by your host application, you may have to add them to your application’s pom.xml
file:
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>4.5.1</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.12</version>
</dependency>
3. Developing with Variant Java Client
3.1. Typical Usage Example
NOTE: Typically you will not be consuming Variant client API directly. Rather, your applicatoin will consume a stack-specific adapter, like the servlet adapter, which implements deferred dependencies and wraps Variant client in an identical but simplified API. Refer to the documentation for the Variant client adapter suitable for your technology stack. The remainder of this chapter is only apropriate for the cases when no suitable Variant adapter (yet) exists.
- Create an instance of the
VariantClient
object by using theVariantClient.Builder
class:
VariantClient client = VariantClient.builder()
.withSessionIdTrackerClass(MyCustomSessionIdTracker.class)
.build();
Note, that an implementation of the SessionIdTracker
is required. A re-instantiation of the VariantClient
is never required. The host application should hold on to and reuse it for the life of the JVM.
- Connect to a variation schema on Variant server:
Connection connection =
client.connectTo("variant://myVariantServer.com/myschema");
The Variant connection URI has the following format: [variant:]//netloc[:port]/schema
The variant
protocol spec is optional and may be omitted. If the port is omitted, 5377 is assumed. Thus, localhost/myschema
is okay. The host application should hold on to the connection object and reuse it for all user sessions interested in participating in code variations contained in the given schema. Variant connections are stateless, so they are reusable even after a server restart.
- Obtain (or create) a Variant session. They are completely distinct from your host application’s sessions.
// The meaning of userData depends on the environment.
Session session = connection.getOrCreateSession(userData);
The userData
argument is a deferred dependency, as discussed in the next section.
- Obtain the schema and the state.
Schema schema = session.getSchema();
Optiona loginPage = schema.getState("loginPage");
if (!loginPage.isPresent()) {
System.out.println("State loginPage is not in the schema. Falling back to control.");
}
- Target this session for the state and figure out the live experience(s) the session is targeted for.
ServletStateRequest request = session.targetForState(loginPage.get());
request.getLiveExperiences().forEach(e ->
System.out.println(
String.format(
"We're targeted to experience %s in variation %s",
e.getName(),
e.getVariation().getName()));
);
At this point, the application can take the code path suitable for the combination of live experience it has been targeted for. Note, that the application does not have to know the names of the variations or experiences to be targeted for a state.
- After the host application’s code path is complete, the state request must be committed (if no exceptions were encountered) or failed (if something went awry).
request.commit(userData); // or .fail(userData)
Here again the userData
argument is a deferred dependency and its meaning is explained in the next section. Committing (or failing) a state request triggers the associated state visited trace event with the corresponding completion status.
3.2. Trace Events
Trace events can be triggered implicitly by Variant server or explicitly by client code. The only implicitly triggered trace event is the state visited event, which signifies that a user session visited a particular state. (Refer to the Server User Guide for details on how Variant models interactive appliations.) State visited event is created by the Session.targetForState()
method and is accessible via the StateRequest.getStateVisitedEvent()
method. The host application can add custom event attributes to it, which will be serialized with the event by the event flusher. The state visited event is triggered when the host applicatoin either commits or fails the request.
4. Deferred Dependencies
Variant Java client makes no assumptions about host application technology stack or operational details. This generality enables broad applicability: any JVM host application can use it to access Variant server. The price of this generality is that Variant Java client must rely on the application developer to provide certain components at runtime. These are collectively known as deferred dependencies — the subject of this section.
4.1. Session ID Tracker
Variant maintains its own sessions, independent from those maintained by the host application. Variant server creates and maintains these sessions, but the client must provide a way of relating two consecutive state requests to the same session. Session ID tracker does exactly that. The session state is kept on Variant server, but the host application is responsible for holding on to the session ID, by which this state can be retrieved. To fulfill this responsibility, the application developer must supply an implementation of the SessionIdTracker interface. By contract, an implementation must provide the following public constructor signature and implement the following public methods:
public MyImplClass(Object...) |
---|
The constructor Variant uses to instantiate an instance of the session ID tracker within the scope of Connection.getSession(Object...) or Connection.getOrCreateSession(Object...) methods by passing it these arguments without interpretation. |
String get() |
Retrieves the current value of the session ID from this tracker. |
void set(String sessionId) |
Sets the value of session ID. |
void save(Object...userData) |
Saves the currently held session ID to the underlying persistence mechanism. The meaning of userData is up to the implementation: Variant will pass the arguments to the enclosing call to StateRequest.commit(Object...userData) or StateRequest.fail(Object...userData) into this method without interpretation. |
The implementing class must be placed on the host application’s classpath and configured via the withTargetingTrackerClass() method . For a sample implementation, see Section 3.4.
4.2. API Methods with Deferred Signatures
The following table lists all the methods in Variant Java client whit environment dependent signatures.
Connection.getOrCreateSession(Object...userData) |
---|
Get, if exists, or create, if does not exist, the Variant session with the externally tracked ID. The arguments are passed, without interpretation, to the underlying session ID tracker’s init() method. |
Connection.getSession(Object...userData) |
Get existing Variant session with the externally tracked ID. The arguments are passed, without interpretation, to the underlying session ID tracker’s init() method. |
StateRequest.commit(Object...userData) |
Commit this state request. The arguments are passed, without interpretation, to the underlying session ID tracker’s save() method. |
StateRequest.fail((Object...userData) |
Fail this state request. The arguments are passed, without interpretation, to the underlying session ID tracker’s save() method. |
5. Stack-Specific Adapters for Variant Java Client
5.1. Servlet Adapter
5.1.1. Overview and Installation
Most Java Web applications are written on top of the Servlet API, either directly or via a servlet-based framework, such as Spring. Such applications should take advantage of the Servlet adapter for Variant Java client discussed here. The servlet adapter consists of two components:
- Wrapper client API, which re-writes all deferred method signatures in terms of familiar servlet objects, like
HttpServletRequest
. See Section 5.1.2 for further details - Servlet-based implementation of the session ID tracker, utilizing HTTP cookies. See Section 5.1.3 for details.
To install the servlet adapter for Variant Java client, follow the installation instructions on GitHub.
5.1.2. Servlet Adapter Wrapper API
Java web applications, built on top of the servlet API, should communicate with Variant server via the com.variant.client.servlet.*
classes, provided by the servlet adapter API , instead of the com.variant.client.*
classes, provided by the general purpose Java API . The servlet adapter classes wrap the general purpose classes in a functionally identical API, whose only difference is that it rewrites all deferred environment-dependent method signatures with those that operate on the familiar servlet objects, like HttpServletRequest
and HttpServletResponse
. Here’s the typical usage example from Section 3, re-written in terms of the servlet adapter API:
- Host application instantiates an instance of
ServletVariantClient
from the factory method:
ServletVariantClient client = new ServletVariantClient.Builder().build();
A re-instantiation of the ServletVariantClient
is never required. The host application should hold on to and reuse it for the life of the JVM.
- Connect to a variation schema on Variant server:
ServletConnection connection = client.connectTo("variant://myVariantServer.com/myschema");
The host application should hold on to the connection object and reuse it for all user sessions interested in participating in code variations contained in the given schema. Variant connections are stateless, so they are reusable even after a server restart.
- Obtain (or create) a Variant session. They are completely distinct from your host application’s sessions.
// request is the current HttpServletRequest
ServletSession session = connection.getOrCreateSession(request);
- Obtain the schema and the state.
Schema schema = session.getSchema();
Optional loginPage = schema.getState("loginPage");
if (!loginPage.isPresent()) {
System.out.println("State loginPage is not in the schema. Falling back to control.");
}
- Target this session for the state and figure out the live experience(s) the session is targeted for.
ServletStateRequest request = session.targetForState(loginPage.get());
request.getLiveExperiences().forEach(e ->
System.out.println(
String.format(
"We're targeted to experience %s in variation %s",
e.getName(),
e.getVariation().getName()))
);
At this point, the application can take the code path suitable for the combination of live experience it has been targeted for. Note, that the application does not have to know the names of the variations or experiences to be targeted for a state.
- After the host application’s code path is complete, the state request must be committed (if no exceptions were encountered) or failed (if something went awry).
// response is the HttpServletResponse
request.commit(response); // or .fail(response)
Committing or failing a state request both implicitly trigger the associated state visited trace event with the corresponding completion status.
5.1.3. SessionIdTrackerHttpCookie
Servlet adapter for Variant Java client comes with a concrete implementation of the SessionIdTracker
interface . It uses the HTTP cookie mechanism to track Variant session ID between state requests in the session-scoped cookie named variant-ssnid
, much like servlet containers use the JSESSIONID
cookie to track the HTTP session ID. No action is required to configure this session ID tracker.
5.2. Play! Adapter
TBA