11

I was reading the Spring Cloud Netflix documentation when I found out about a way to share an interface between an HTTP server and its client. They use this example for microservices, although there's no reason why it can't extend to generic HTTP communication:

// The shared interface, in a common library
public interface UserService {
    @RequestMapping(method = GET, value = "/users/{id}")
    User getUser(@PathVariable long id);
}

// The controller, on the server
@RestController
public class UserResource implements UserService {
}

// The same interface used for the client
@FeignClient("users")
public interface UserClient extends UserService {
}

This defines an interface that gets used as both a server (The Spring @RestController turns it into an HTTP server) and a client (The Feign @FeignClient sets it up for HTTP client use). The server and client class implementations can be used in separate projects but still use the same interface to ensure that the types match.

However, underneath the example they put the following caveat:

Note: It is generally not advisable to share an interface between a server and a client. It introduces tight coupling, and also actually doesn’t work with Spring MVC in its current form (method parameter mapping is not inherited).

OK, so it's not well-integrated right now... but that part comes after the warning against sharing code and introducing coupling between the server and the client, which they think is more important. Why do they think it's such a bad idea to share an interface this way?

Without it, you lose the ability to guarantee that the server and client send each other data that they can both understand. You could add a field to one but not the other and only discover the mismatch until runtime. To my mind, it's not introducing coupling, but merely revealing coupling that already exists. Is the need to make servers completely independent greater than the need to let them know what types of data they'll receive?

  • 1
    The coupling that exists, in terms of data/formatting handled by clients/servers, is determined by the protocol -- a piece of documentation that can be used as a convention. The coupling introduced by sharing an interface is a compile-time coupling -- consider what happens when the interface is changed (in a backward-incompatible manner, for example), but the client/server code using that interface are deployed at different times. That deployment-time coupling can be harder to manage, especially at Netflix' scale. – Castaglia Jun 21 '16 at 17:39
  • 1
    I'm pretty sure I'm not operating at Netflix's scale :) but if the interfaces are changed in a backward-incompatible manner, then doesn't this just shift the error from being found at compile time to being found at runtime instead? Do they consider it OK to let a few function calls fail while they slowly upgrade all the servers? – Ben S Jun 21 '16 at 19:35
  • 1
    Possibly; depends on the client code. Consider also the other case: the servers are upgraded first, and the clients now have to deal with (unexpectedly) failing calls... – Castaglia Jun 21 '16 at 22:48
  • 1
    Just curious, by sharing this interface, does it limit what languages/stack you can build the client out of? – JeffO Jun 22 '16 at 13:42
  • Yes — it's a Java file so you'll have to use Java. You may be able to use another JVM language but I haven't tried it. – Ben S Jun 22 '16 at 16:47
6

The reason as stated in comments is that it results in tightly coupling your client platform to your server platform. Here, that means your client is required to use the language/platform you are using on the server in order to understand your server's expected contract. Note that there is a difference between sharing the same code (an artifact of a specific language/platform) and agreeing on a specific contract.

Many projects instead use documentation for their contracts. Example requests and responses in a neutral format (e.g. JSON) over standard protocols (e.g. REST). (See Stripe API docs, for example). Because it is impractical to write a code-based contract for every possible client platform you might want to use or allow. Still others use API management tools to define neutral contracts.

Your example of adding a field is a separate concern -- an example of why it is important to version API contracts. Let clients use the version for which they are designed. A backward-incompatible new API version exists alongside the old. The client for the old version continues working until its team gets around to updating it or until you retire the old version (after a deprecation/migration period). See Parallel Change.

Following the (implicit advice in the) warning helps the client and server to evolve in ways and paces that make sense for each. If you can reasonably guarantee that your server and client will always share the same language/platform AND evolve at the same pace, then using a language- and platform-specific code artifact as your contract will probably be ok. However, this is probably not a reasonable expectation, especially for projects targeting Netflix OSS (something specifically geared for cloud scalability and performance, with all of that requisite complexity).

  • 2
    Is the client really expected to use the interface? I've always seen such constructs as a way to make writing a clients easier. After all you can still write a REST client in a different language. – Jimmy T. Feb 13 '17 at 10:22
  • 1
    Exactly, api will still exist, with its route definitions, nothing prevents the creation of a client in another language, but as long as you use java could use the interface – Leonardo Villela Mar 31 '17 at 16:15

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.