0

When microservices need to talk with each other, the common practice will be to have some REST (or gRPC) communication.

I'm wondering what should be a better approach? (let's assume all services are in Java) -

  1. Each service is using a freestyle REST client (e.g. OkHttp)
  2. When service A needs to talk with service X, it has to include a dependency jar library of "service X client" that hides the network communication from service A.

Let's say this is our system - where service A uses services X and Y as its data resources:

   /-X
A--
   \-Y

Here are some cases:

New Functionality in X

If service X has new functionality for A, in both approaches there will be a need to update the code of A to support it and to add new HTTP calls. If we are using a dependency JAR, we will also need to create a new version of the jar.

New versions of X / Y

Versions update in X and Y, as long as they don't break the interface doesn't require any change in A - in both approaches.

Different HTTP library versions in X & Y clients

It is possible that X & Y client libraries will include different versions of the same HTTP client - this may cause dependency library conjunction in service A.

What is the best practice these days? I found this post and this post where each of them supports the other approach. I also came across Feign as REST Client that might be a third option

1
  • "include a dependency jar" - this adds more complexity than you typically expect initially. Keep it simple. Jul 28, 2021 at 21:46

3 Answers 3

3

The core advantage of microservices is that they can be developed and deployed independently. This has some performance scalability benefits, but is usually more about organizational scalability and flexibility.

Introducing a client JAR appears convenient, but can destroy this organisational flexibility.

  • The interface between client and service has been moved from the REST API to the Java API, i.e. into the dependent services.
  • The underlying REST API is at risk of becoming a private implementation detail of the client JAR, thus likely breaking services that don't use the client JAR.
  • Updates to the service API necessitate updates to the clients – they are no longer really independent.
  • You are giving up language independence, and are now tied to JVM languages for dependent services.

So it would be counterproductive to require access via a client JAR. But such libraries can nevertheless be very useful and convenient. How can we mitigate their downsides?

  • Pay extra attention to evolve the REST API in a backwards-compatible manner.
  • Generate wrappers automatically from a machine-readable service description.
  • Maintain client libraries in multiple languages, e.g. Java and JS. This helps enforce the REST API as the actual API boundary.

It is also entirely possible to decide that you only need the performance scalability benefits from microservices, but not the organizational flexibility. If all relevant services are maintained in tandem by the same team, then a client JAR approach definitely makes sense. Managing libraries instead of services is perfectly fine, it just has a different set of tradeoffs.

From the perspective of the dependent service, using API wrappers almost always make sense. Version conflicts between unrelated connector libraries can potentially be mitigated via Java's module system.

1
  • 1
    Two suggestions, hints: 1) If you use proper versioning you don't need to be backwards compatible in the new version and the generated client simply makes the version change easier for all who use it, but it's transparent to others (caveat: you still need to be backwards-compatible by keeping the old version running until everyone has adopted the new one). If that's what you meant anyway, maybe elaborate... or I was simply not seeing it. 2) If you use a description language like swagger/openAPI you get the support for multiple client libraries for free (basically, minor caveats apply^^). Mar 29, 2020 at 23:46
0

what should be a better approach?

"It depends"

When service A needs to talk with service X, it has to include a dependency jar library of "service X client" that hides the network communication from service A.

Once upon a time, that's how the web worked. If you wanted to talk to a web server, you integrated with libwww.

REST is, in a sense, a response to the fact that that idea doesn't scale.

In particular, having a well designed contract and schema frees consumers to implement clients in other languages.

The value of that is going to depend on how large the audience is for your "microservices". In a closed system, where the consumers and the providers are all part of the same logical organization, there may not be a lot of value in the flexibility, and using a standard component to simplify the implementation may make sense (not that you still need to have care in ensuring that the client protocol is backwards compatible, so that you can upgrade your services independently).

On the other hand, if your microservices are expected to be autonomous, then they probably aren't sending messages to each other directly, but instead to some durable store. The store itself is likely to be domain agnostic (it implements messaging concerns), and there may be advantage to using an off the shelf solution that is multi lingual.

0

This is not an xor issue: You can have your cake and eat it. E.g. you can use swagger/openapi to define a REST API that any non-java service can call; this should be your API definition that holds the ultimate truth how the service works. On top of that you can auto-generate a java client from the spec that gives you all the benefits of the jar-client (hiding the actual details, saving the time to define the calls for each service that needs to call the other service etc). If service A calls service B, the client could be generated at service B once for everyone. This means you only need to generate it in one place. Which is neat, but can come with the dependency issues (haven't seen that being a problem in several years using this approach in regard to external libraries, as they work towards keeping their API stable, just update them all together in smallish increments and there shouldn't be issues; if you have classes of your own that frequently do breaking changes,t that is another issue...).

But if this is a problem for you, you can also generate the client at service A - for any service A calls and then those always use the same external dependency. Only drawback you generate the same client in multiple places, which can mean a bit more build time and storage space for artifacts. But especially in a microservice world both should be negligible (e.g. running tests should take more time and the main service should take more space)

0

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

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