pratham.sh

A case for API client libraries in microservices architecture

May 31, 2020

In this post, I will make a case for maintaining API client libraries for inter-process communication in microservices. I will present the benefits they will have for a team maintaining multiple services, and how it can improve the team collaboration.

Microservices architecture is quite popular. Once you have microservices, you certainly have a few internal communication mechanisms. The interaction can be synchronous, asynchronous; it can be one-to-one or even one-to-many. Developing and integrating the integration is achieved with limited hurdles, and as often the case with engineering, the maintenance and collaboration are challenging.

With the teams I have worked, we often used HTTP based REST APIs for synchronous interactions. And, for asynchronous calls, we used SQS or Apache Kafka. Services were distributed across many small squads to develop and maintain. We would collaborate on inter-service dependency through postman collections or swagger documentation or a document spec with the payload details and examples.

For integrating third-party external APIs, an official client library by that target service comes in convenient. So, the question is, can the same notion be applied within a company for the internal services. Here are some points in its favour, and how to implement it effectively —

  • Client libraries provide simple, intuitive to use interfaces.
  • They can abstract the implementation of communication mechanism. For example, libraries can handle the implementation of REST, or gRPC or pushing queue messages.
  • All the other services using client libraries can avoid the boilerplate code needed to implement the communication mechanism.
  • In typed languages, the library can provide pre-defined entity classes for the requests and responses. So, they are essentially self-documented out of the box. And, with a good IDE, implementing such method calls is instinctual.
  • Service authors can abstract the error handling boilerplate code and have custom, defined error classes.
  • It provides control over the versioning of internal calls.
  • Ease of pushing updates.
  • Client libraries can provide instrumentation, by including logging of calls, events, errors. Such logs, traces will be useful information to measure performance, latency.
  • Authors can use code generators to create API client libraries. For example, for REST APIs, you can check these ー

Here is an example —

Without a client:

const fetch = require("node-fetch")

async function createNewBlog() {
  const content = {
    title: "A case for API client libraries....",
    content: "Deal or No Deal....",
  }

  const response = await fetch("https://example.com/blogs", {
    method: "post",
    body: JSON.stringify(content),
    headers: {
      "Content-Type": "application/json",
    },
  })

  if (!response.ok) {
    if (response.status === 400) {
      throw new ValidationError()
    }

    throw new MyCustomError(response.statusText)
  }

  return response.json()
}


And, with a client:

const client = require("awesome-blog-client")

async function createNewBlog() {
  const content = {
    title: "A case for API client libraries....",
    content: "Deal or No Deal....",
  }

  try {
    const blog = await client.createBlog(content)
  } catch (err) {
    if (err instanceof ValidationError) {
      // handle validation error;
    } else {
      // handle other errors;
    }
  }
}

So, is this worth the extra engineering effort?

Yes, I like to think so. It’ll make my teammates happy to collaborate, and it’ll certainly make me happy to maintain codebases.

What do you think? I will love to hear your thoughts on this topic.

#TODO Link to a service boilerplate repo with API client library setup.


Heyo 👋 I'm Prathamesh.

I'm a Software Engineer living in Mumbai, evolving and working on making cool things at Upstox.