Zero effort API client

Recently I start using Netflix feign to write clients to access third party APIs (thank you for the hint Becker)

On Feign you describe your client as a java interface and feign maps it into http requests.

With a few lines of code you can create a client to access github API:

interface GitHub {
  @RequestLine("GET /repos/{owner}/{repo}/contributors")
  List<Contributor> contributors(@Param("owner") String owner, @Param("repo") String repo);
}

static class Contributor {
  String login;
  int contributions;
}

public static void main(String... args) {
  GitHub github = Feign.builder()
                       .decoder(new GsonDecoder())
                       .target(GitHub.class, "https://api.github.com");

  // Fetch and print a list of the contributors to this library.
  List<Contributor> contributors = github.contributors("netflix", "feign");
  for (Contributor contributor : contributors) {
    System.out.println(contributor.login + " (" + contributor.contributions + ")");
  }
}

After a while accessing 3rd party APIs, came the need to access my own API. So, it was just a matter of writing an interface for my own API.

Guess what, this interface was very, very similar to my controller class. Same annotations, similar names, same parameters felt like copy and paste. I hate repeting myself! So I decided to create one interface to define both the client and the server.

Example

To demonstrate what I mean, I took the Dropwizard hello world project and tweak to create feign client.

Why Dropwizard? No reason, just wanna a simple JAX-RS application.

First things first, I extracted the interface for HelloWorldResource:

@Path("/hello-world")
@Produces(MediaType.APPLICATION_JSON)
public interface HelloWorldClient {

    @GET
    Saying receiveHi();

    @GET
    @Path("custom")
    Saying receiveHi(@QueryParam("message") String message);

}

This is pretty much the same interface I would need for a feign-client.

The main advantages of this are:

  • Eliminate control JAX-RS annotations from the Resource class

public class HelloWorldResource implements HelloWorldClient {

    @Override
    public Saying receiveHi() {
        return new Saying("HI");
    }

    @Override
    public Saying receiveHi(String message) {
        return new Saying(message);
    }

}
  • Compile time check on client

  • Code reuse

  • Java client for your API with zero effort, ensured to match the server

Now, to invoke the client is a simple code like this:

  HelloWorldClient client = Feign.builder().contract(new JAXRSContract())
    .encoder(new JacksonEncoder())
    .decoder(new JacksonDecoder())
    .target(HelloWorldClient.class, url);
  Saying saying = client.receiveHi("Howdy mate!");

I also decided to move the client side of things into a new project. Now I can publish a java client to anyone interested on it.

This project also exposes a ClientFactory. This factory knows how to create all clients for my API. A third-party that decides to use my client doesn’t need to know about feign, just about my client API.

This is how I use the client on integration tests:

  ClientFactory factory = new ClientFactory("http://localhost:" + RULE.getLocalPort());
  Saying saying = factory.newHelloWorldClient().receiveHi("Howdy mate!");
  assertThat(saying.getContent()).isEqualTo("Howdy mate!");

To sumarize:

  • extract interface

  • move interface + DTOs to client project

  • add client dependency on server

  • created a factory to hide feign "complexity" (optional)

Hope that is useful to people out there. Till next time!

comments powered by Disqus