Microservices automated testing and local development

Using Roxy in your backend for your automated testing and local development flow

This page describes the challenge and solution for dealing with feature flags in local development and automated testing in a microservice architecture. It shows how you can use Roxy, a Docker image that mocks CloudBees Rollout storage and provides REST API to control flag behavior on local development or for automated testing (creating flag behavior fixture, controlling the flag values, etc…​).

Understanding feature flags in the microservice architecture

What are microservices?

"In short, the microservice architectural style is an approach to developing a single application as a suite of small services, each running in its own process and communicating with lightweight mechanisms, often an HTTP resource API. These services are built around business capabilities and independently deployable by fully automated deployment machinery. There is a bare minimum of centralized management of these services, which may be written in different programming languages and use different data storage technologies."

 — James Lewis and Martin Fowler

Contrary to common belief, Feature flags should not be a standalone service in your environment. Adding a Feature flagging microservice does not follow the guidelines of microservice architecture, and introduces the following architectural flows:

  • The Feature Flags service is not a vertical service - choosing the flag service approach is more suitable for SOA and not microservices architecture

  • Feature flags evaluation latency is dependant on the network topography and feature flags service load

  • Other services are not independently deployable because of coupling with feature flags microservices

Some of these issues can be solved by an implementing a caching layer on the consumer microservices, but this solution introduces more complexity and fragility into the system. It is better to choose the right architecture, a distributed one.

In a distributed solution the Feature Flags SDK is installed on the relevant microservice, the SDK uses CloudBees Rollout storage to get the flags configuration but a connection is not required when evaluating flags. Here is a diagram that demonstrates this Architecture:

Microservice architecture diagram

Microservices Architecture Diagram

As you can see, the SDK is installed on the microservices and fetches the configuration from CloudBees Rollout storage. Calculating whether a flag is enabled/disabled is done in local memory with a distributed algorithm, thus solving all the architectural flaws described above.

The issue with automated testing

Not Unit testing

This section does not describe how to unit test, it is focused on automated acceptance testing that is done on the entire microservice environment (or parts of it).

The above diagram shows the desired architecture for a feature flag enabled environment. As you can see the Feature Flag SDK runs on multiple instances of multiple microservices. Each microservice consumes different or shared flags.

When writing tests for this environment, you first need to set up the fixture of the test. The test fixture includes setting up various components, loading relevant data into the database and setting flag values.

When we set flag values we want to know as little as possible on the system, to eliminate any dependencies to implementation that will make the test fragile. In order to do so, CloudBees Rollout has released a component called Roxy. Roxy is a docker image that mocks CloudBees Rollout storage and provides REST API to control flags behavior in a non production environment.

The issue with local development

When developing a service, the developer is often required to set a flag value for their specific localhost environment, these flags can be consumed on the service they are developing or on other services in their environment.
It is not the developer’s concern to understand which flags are consumed by which microservice and how many instances each microservice is running. To hide these implementation details from the developer it is required to have a single point of abstraction to set up flags value across the environment

To allow the developer to control the flags values on their development environment, CloudBees Rollout has released a component called Roxy. Roxy is a docker image that mocks CloudBees Rollout storage and provides REST API to control flag behavior in a non production environment.

Roxy architecture view

Roxy replaces CloudBees Rollout storage and runs from inside your domain, in practice it supplies a mock service on top of CloudBees Rollout software as a service solution. Here is how Roxy fits into the microservice diagram

Microservice architecture with roxy

As can be seen above, the CloudBees Rollout SDK that is running on each microservice does the configuration fetching from Roxy instead of CloudBees Rollout storage.

Running Roxy

Roxy is distributed as a docker image. Roxy listens on port 3333
Here is how you run it with the docker command line:

docker run -p 4444:3333 -d rollout/roxy:latest

This command will start Roxy inside the container and will expose port 4444 as Roxy port. The next step is to configure CloudBees Rollout SDK to work with Roxy as its configuration source.

Redirecting the SDK to Roxy

Configure the CloudBees Rollout SDK to work with Roxy as its configuration source:

Java Node.js JavaScript SSR .NET Python Go Ruby PHP C C++
RoxOptions options = new RoxOptions.Builder()
  .withRoxyURL(new URL("http://localhost:4444"))
  .build();
Rox.setup(environmentKey, options);
const options = {
  roxy: 'http://localhost:4444'
}

Rox.setup(<ROLLOUT_KEY>, options);
import {Rox} from 'rox-ssr';

const options = {
  roxy: 'http://localhost:4444'
}

Rox.setup(<ROLLOUT_KEY>, options);
var Options = new RoxOptions(new RoxOptions.RoxOptionsBuilder
{
    RoxyURL = new Uri("http://localhost:4444")
}

Rox.Setup(<ROLLOUT_KEY>, Options);
options = RoxOptions(
            roxy_url = 'http://localhost:4444/'
)

Rox.setup(<ROLLOUT_KEY>, options)
options := server.NewRoxOptions(server.RoxOptionsBuilder {
  RoxyURL = 'http://localhost:4444/' },
)

rox.setup(<ROLLOUT_KEY>, options)
option = Rox::Server::RoxOptions.new(
            roxy_url = 'http://localhost:4444/'
)

Rox::Server::RoxServer.setup(<ROLLOUT_KEY>, option).join
$roxOptionsBuilder = (new RoxOptionsBuilder())
    ->setRoxyURL("http://localhost:4444/");

Rox::setup("<ROLLOUT_ENV_KEY>",  new RoxOptions($roxOptionsBuilder));
int main(int argc, char **argv) {
    RoxOptions *options = rox_options_create();
    rox_options_set_roxy_url(options, "http://localhost:4444/");

    rox_setup(DEFAULT_API_KEY, options);
    rox_shutdown();
}
int main(int argc, char **argv) {
    Rox::Options *options = Rox::OptionsBuilder()
        .SetRoxyUrl("http://localhost:4444/")
    .Build();

  Rox::Setup(DEFAULT_API_KEY, options);
    Rox::Shutdown();
}

After setting this withRoxyUrl configuration the SDK will fetch its configuration from localhost:4444

Supported on SDK 3.2.0 and higher

This withRoxyUrl configuration is supported on Java SDK from version 3.2.0

Controlling flags via REST API

Roxy supports the following REST API for setting flags values:

  • GET /flags/<flagname> - get flag behavior

  • GET /flags/ - get all flags behavior

  • POST /flags/<flagname> - set flag behavior within body. To set a flag to true send body: { expression: “true” }, to set a flag to false send body: { expression: “false” }

  • DELETE /flags/<flagname> - Reset flag behavior

  • DELETE /flags/ - Reset all flags behavior

The API is also available via a Swagger interface at http://localhost:4444/api-docs.