Question

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?

Was it helpful?

Solution

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).

Licensed under: CC-BY-SA with attribution
scroll top