Spectolabs Blog

Using API Simulation to Build Microservices Alongside a Java Monolith


By Daniel Bryant


This is a guest post from Daniel Bryant, Chief Scientist at OpenCredo.

At OpenCredo and SpectoLabs we’re helping a lot of organisations embrace the microservice architectural style. One problematic pattern we repeatedly see when organisations are migrating from working with a single Java-based monolith to multiple microservices is the development team stumbling with orchestrating multiple services for local development and pipeline-based automated testing.

The goal of this post is to share our practical lessons of incorporating the Hoverfly ‘API simulation’ service virtualisation tool into our local development environments and automated tests. Some readers may already be using record/replay tools like VCR and Betamax, but the big benefit of Hoverfly (which we’ll cover in a later post) is the ability to modify requests and responses in real-time by using configurable ‘middleware’ plugins that can be written in any language.

Let’s create an experimental playground with a fake Java ‘monolith’ and an example microservice. When working with Java microservices, I’m a big fan of the Spring Boot platform, and so this is what we will use...

Building a sample ‘monolith’ macroservice

I have built an example monolith/macroservice and uploaded this to a GitHub repository under my account, 'hoverfly-blog'. If you want to follow along with this blog post, then please clone the repository and cd into the hoverfly-blog directory.


workspace $ [email protected]:daniel-bryant-uk/hoverfly-blog.git
workspace $ cd hoverfly-blog
hoverfly-blog $ ls -lsa
total 16
0 drwxr-xr-x   7 danielbryant  staff   238 17 Feb 16:21 .
0 drwxr-xr-x  43 danielbryant  staff  1462 16 Feb 15:02 ..
0 drwxr-xr-x  12 danielbryant  staff   408 17 Feb 16:30 .git
8 -rw-r--r--   1 danielbryant  staff    96 15 Feb 14:48 .gitignore
8 -rw-r--r--   1 danielbryant  staff  3843 17 Feb 16:21 README.md
0 drwxr-xr-x   7 danielbryant  staff   238 16 Feb 15:05 macroservice
0 drwxr-xr-x  10 danielbryant  staff   340 17 Feb 16:21 microservice

The ‘monolithic’ application in the macroservice folder is in fact a lightweight Spring Boot service that I’m only using to prove the concepts of API simulation. However, you can use your imagination and pretend that the codebase contains a canonical data model (CDM), seven God objects, five methods of sending email, and fourteen different logging frameworks

The application within the ‘microservice’ is another Spring Boot application, which will act as our proper microservice.

Let’s get started...

Introduce seams to the monolith

I’ve included the macroservice ComplexController code below, which shows we have a single ‘slowFragileRequest’ HTTP API endpoint. A common pattern we have used when breaking apart a large application is to create a API ‘seam’ like this within the monolith, which can be used by new microservices for making queries or issuing commands. In this example we are going to pretend that ‘slowFragileRequest’ endpoint returns data from an expensive query run within the monolith:


@Controller
public class ComplexController {

    @Autowired
    private ComplexObjectService complexObjectService;

    @RequestMapping("/slowFragileRequest")
    @ResponseBody
    public List slowFragileRequest(@RequestParam(value = "origin", required = false) final String origin,
                                                  @RequestParam(value = "destination", required = false) final String destination) {
        //todo - note that the request params are just for show, and are ignored in this example
        return complexObjectService.getLatestComplexObjects();
    }
}

Creating a microservice to integrate with the seam

We have found that although mocking and stubbing are very useful techniques, as you pull apart a monolith you will need to test against the actual application at some point. This often occurs when building microservices locally, and almost always within the integration testing phase of the build pipeline.

To compound the difficulties of spinning up the monolith, sometimes we simply haven’t had access to the source code or even the binary, and here we have had to request time (via a ticketing system) and lease an environment that contained the monolith. The ultimate challenge occurs when the monolith has throughput/throttling limits or constantly falls over.

The ComplexObjectService code shown below comes from the microservice application, and uses the Spring RestTemplate to make requests against the monolith endpoint described above.


@Service
public class ComplexObjectService {

    @Autowired
    private RestTemplate restTemplate;

    public List getLatestComplexObjects(final String origin, final String destination) {
        Map urlVariables = new HashMap<>();
        urlVariables.put("origin", origin);
        urlVariables.put("destination", destination);

        ComplexObject[] objects =
                restTemplate.getForObject("http://localhost:8080/slowFragileRequest", ComplexObject[].class, urlVariables);

        // Process ComplexObjects into something else
        return Arrays.asList(objects);
    }
}

The problem of working with an unavailable monolith

We can now spin up both our fake monolith and supporting microservice, and make calls to the microservice, which in turn makes a call to the seam within the monolith.

For convenience we are using the Maven Spring Boot plugin to run our applications, but you would want to use something more robust for production use (i.e. an executable JAR launched with a supervisor process):


hoverfly-blog $ cd macroservice
macroservice $ mvn spring-boot:run

…

2016-02-16 15:09:05.399  INFO 97776 --- [           main] o.s.j.e.a.AnnotationMBeanExporter        : Registering beans for JMX exposure on startup
2016-02-16 15:09:05.467  INFO 97776 --- [           main] s.b.c.e.t.TomcatEmbeddedServletContainer : Tomcat started on port(s): 8080 (http)
2016-02-16 15:09:05.471  INFO 97776 --- [           main] i.s.exmpls.macroservice.Application      : Started Application in 2.173 seconds (JVM running for 4.735)

I recommend opening another terminal session/window, and then launching the microservice:


hoverfly-blog $ cd microservice
microservice $ mvn spring-boot:run

….

2016-02-16 15:09:40.861  INFO 97992 --- [           main] o.s.j.e.a.AnnotationMBeanExporter        : Registering beans for JMX exposure on startup
2016-02-16 15:09:40.931  INFO 97992 --- [           main] s.b.c.e.t.TomcatEmbeddedServletContainer : Tomcat started on port(s): 8090 (http)
2016-02-16 15:09:40.936  INFO 97992 --- [           main] i.s.exmpls.microservice.Application      : Started Application in 2.354 seconds (JVM running for 5.645)

Now open up one more terminal window and make a request against the microservice using curl or your favourite REST tool/browser:


hoverfly-blog $ curl localhost:8090/speedyRequest
[{"id":"1","name":"largeObject","mappings":["one","two"]}]

If you look at the logging output for both the microservice and monolith/macroservice you will see that both applications dealt with their respective requests.

Now kill the monolith (using ^C / SIGINT etc in the terminal windows in which this is running)


2016-02-16 15:09:05.471  INFO 97776 --- [           main] i.s.exmpls.macroservice.Application      : Started Application in 2.173 seconds (JVM running for 4.735)
^C
macroservice $

Try calling the microservice endpoint again:


hoverfly-blog $ curl localhost:8090/speedyRequest
{"timestamp":1455635508958,"status":500,"error":"Internal Server Error","exception":"org.springframework.web.client.ResourceAccessException","message":"I/O error on GET request for \"http://localhost:8080/slowFragileRequest\": Connection refused; nested exception is java.net.ConnectException: Connection refused","path":"/speedyRequest"}

Boom! We can no longer test our microservice due to the dependency on the macroservice HTTP endpoint for downstream communication.

Let’s introduce some API simulation with Hoverfly...

Shut everything down (^C in both macroservice and microservice), and start the monolith again


macroservice $ mvn spring-boot:run

Now start the microservices, but this time run the application using the TEST profile.


microservice $ mvn spring-boot:run -Dspring.profiles.active=TEST

The primary difference with the TEST profile is that all communication via the Spring RestTemplate will now be made through a proxy - this is the Hoverfly proxy that we will be starting in a moment. (It’s worth noting that this way of programmatically forcing communication via a proxy is quite invasive, and we could use other techniques)


@Bean
    @Profile("TEST")
    public RestTemplate getHoverflyProxiedRestTemplate() {
        SimpleClientHttpRequestFactory requestFactory = new SimpleClientHttpRequestFactory();
        Proxy proxy = new Proxy(Proxy.Type.HTTP, new InetSocketAddress(hoverflyHost, hoverflyPort));
        requestFactory.setProxy(proxy);
        return new RestTemplate(requestFactory);
    }

    @Bean
    @Profile("PROD")
    public RestTemplate getRestTemplate() {
        return new RestTemplate();
    }

Let’s now start our Hoverfly proxy. I have downloaded an appropriate Hoverfly binary from the project’s Github release page, and placed this in the microservice directory. I have also made sure the Hoverfly binary is executable ($ chmod u+x hoverfly) and excluded from git (using .gitignore) the binary and the associated ‘requests.db’ file that stores Hoverfly state.

If I list the microservice directory this is what I have. If you are following along, then please make sure your directory contains the same content:


microservice $ ls -lsa
total 21112
    0 drwxr-xr-x  10 danielbryant  staff       340 15 Feb 15:23 .
    0 drwxr-xr-x   6 danielbryant  staff       204 15 Feb 14:48 ..
    0 drwxr-xr-x  12 danielbryant  staff       408 16 Feb 15:15 .idea
    8 -rw-r--r--   1 danielbryant  staff        44 15 Feb 15:23 application.properties
21016 -rwxr--r--   1 danielbryant  staff  10758992 12 Feb 17:08 hoverfly
   16 -rw-r--r--   1 danielbryant  staff      5157 15 Feb 15:01 microservice.iml
    8 -rw-r--r--   1 danielbryant  staff      1275 15 Feb 14:57 pom.xml
   64 -rw-------   1 danielbryant  staff     32768 16 Feb 12:07 requests.db
    0 drwxr-xr-x   4 danielbryant  staff       136 15 Feb 14:54 src
    0 drwxr-xr-x   7 danielbryant  staff       238 16 Feb 15:09 target

We can now start Hoverfly in ‘capture’ mode where it will record all requests and responses made via the application as a proxy


microservice $ ./hoverfly -capture

{"databaseName":"requests.db","level":"info","msg":"Initiating database","time":"2016-02-16T15:20:03Z"}
{"Destination":".","Mode":"capture","ProxyPort":"8500","level":"info","msg":"Proxy prepared...","time":"2016-02-16T15:20:03Z"}
{"AdminPort":"8888","level":"info","msg":"Admin interface is starting...","time":"2016-02-16T15:20:03Z"}
[negroni] listening on :8888

Now all of our communication between microservices and macroservice is being recorded! Lets test our microservice again:


hoverfly-blog $ curl localhost:8090/speedyRequest
[{"id":"1","name":"largeObject","mappings":["one","two"]}]

All good. Now let’s stop Hoverfly and start it again in the default ‘virtualize’ playback mode


microservice $ ./hoverfly
Let’s kill the monolith/macroservice again.

^C
2016-02-16 15:23:04.313  INFO 98315 --- [       Thread-2] ationConfigEmbeddedWebApplicationContext : Closing org.springframework.boot.context.embedded.AnnotationConfigEmbedde[email protected]: startup date [Tue Feb 16 15:13:51 GMT 2016]; root of context hierarchy
2016-02-16 15:23:04.316  INFO 98315 --- [       Thread-2] o.s.j.e.a.AnnotationMBeanExporter        : Unregistering JMX-exposed beans on shutdown
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 09:14 min

Now, make the microservice call again:


hoverfly-blog $ curl localhost:8090/speedyRequest
[{"id":"1","name":"largeObject","mappings":["one","two"]}]

Everything still works - even without the monolith running! If we look at the logging output on Hoverfly we can see that it served the request targeted at the monolith, rather than the (non-running) monolith replying itself:


{"bodyLength":58,"destination":"localhost:8080","key":"7e5f6ff30f2adcf5d402e7bbbaf632db","level":"info","method":"GET","middleware":"","mode":"virtualize","msg":"Response found, returning","path":"/slowFragileRequest","rawQuery":"","status":200,"time":"2016-02-16T15:25:32Z"}

Incorporating Hoverfly into the testing workflow

The above example demonstrates how requests against an unavailable or unreliable monolithic application (or indeed any internal or third-party service) can be recorded and replayed when working locally with a dependent application. Now let’s describe a simple process to incorporate the use of Hoverfly into automated testing via JUnit.

Creating a Hoverfly JUnit Rule

We’ll cover the process of automatically capturing requests/response with Hoverfly in a future blog post, but for the moment we will assume that we have captured all of the traffic we need for the API simulation through manual testing.

We could create a Maven plugin to automatically execute the Hoverfly binary during Failsafe integration testing, but for the moment let’s keep this simple by starting the binary via a Hoverfly JUnit Rule that extends the ExternalResource abstract class.


public class HoverflyRule extends ExternalResource {

    private static final String HOVERFLY_LOCATION = "/hoverfly-blog/microservice/";

    private static Process hoverflyProcess;

    @Override
    protected void before() throws Throwable {
        ProcessBuilder builder = new ProcessBuilder()
                .inheritIO()
                .command(HOVERFLY_LOCATION + "hoverfly");
        hoverflyProcess = builder.start();
    }

    @Override
    protected void after() {
        hoverflyProcess.destroy();
    }
}

Please note the that the use of a static HOVERFLY_LOCATION variable is only for convenience in this example, and we would ideally inject this value in at runtime.

Once we have created this rule we can create simple tests using the example below as a template:


@RunWith(SpringJUnit4ClassRunner.class)
@ActiveProfiles(profiles = "TEST")
@SpringApplicationConfiguration(Application.class)
@WebIntegrationTest
public class ShinyMicroIntegrationTest {

    public static final String SPEEDY_REQUEST_URI = "http://localhost:8090/speedyRequest";

    @ClassRule
    public static TestRule hoverflyRule = new HoverflyRule();

    private RestTemplate restTemplate = new RestTemplate();

    @Test
    public void test() {
        ComplexObject[] complexObjects =
                restTemplate.getForObject(SPEEDY_REQUEST_URI, ComplexObject[].class);
        assertTrue(complexObjects.length == 1);
    }
}

If you aren’t familiar with Spring Boot, then the annotations at the top of the Class cause the application to be fully started with an in-memory application server (@WebIntegrationTest,taking the Spring configuration from the Application.class and running with the TEST profile (@ActiveProfiles(profiles = "TEST"))) via the Spring JUnit runner.

The full microservice test suite can be run via Maven:


microservice $ mvn clean verify
...
Tests run: 1, Failures: 0, Errors: 0, Skipped: 0

[INFO]
[INFO] --- maven-jar-plugin:2.5:jar (default-jar) @ microservice ---
[INFO] Building jar: /Users/danielbryant/Documents/dev/daniel-bryant-uk/hoverfly-blog/microservice/target/microservice-0.0.1-SNAPSHOT.jar
[INFO]
[INFO] --- spring-boot-maven-plugin:1.3.2.RELEASE:repackage (default) @ microservice ---
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 7.073 s
[INFO] Finished at: 2016-02-17T18:42:39+00:00
[INFO] Final Memory: 28M/326M
[INFO] ------------------------------------------------------------------------

In conclusion

When decomposing a monolithic Java application it is often good practice to introduce ‘seams’ as API endpoints that newly created microservices can call. The challenge, however, is running the monolith locally, or getting access to a test environment with the monolith running. API simulation, using a tool like Hoverfly, can help here. The above examples explain the basic concepts of using Hoverfly with two Java applications, but the real fun starts when we start introducing Hoverfly ‘middleware’, which can be used to simulate a variety of failure scenarios, or even create an entirely synthetic service response.

References

"Testing Strategies in a Microservice Architecture" by Toby Clemson

"Working Effectively with Legacy Code" by Michael C. Feathers

"Building software against the meetup API whilst 20,000ft above the North Sea" by Mark Coleman

"API mocking for development and E2E testing – Part 1: from zero to hero" by Karolis Rusenas