RailsConf 2014

Designing the APIs for an Internal Set of Services

Designing the APIs for an Internal Set of Services

by Alberto Leal

In the video titled "Designing the APIs for an Internal Set of Services," Alberto Leal shares insights into the effective design of APIs within a microservices architecture. He emphasizes the importance of not only well-designed APIs but also the crucial need for clear communication contracts between services to facilitate seamless integration. Leal presents key best practices for API design, particularly as they relate to enhancing developer experiences in an increasingly complex environment.

Key Points Discussed:
- API Definition and Purpose: Leal defines an API (Application Programming Interface) and highlights its role as the interface through which developers can interact with applications, emphasizing the significance of the interface aspect.
- Microservices Architecture Explained: The concept of microservices is illustrated, describing applications as collections of small, loosely coupled services, each with a specific responsibility. This architecture permits independent development and deployment.
- Flexibility and Tool Usage: The video advocates using the right tools for specific tasks, emphasizing that different technologies (like Ruby, Java, etc.) can be employed per service need, leading to a more maintainable and stable application.
- Developer-Centric Design: Leal insists on treating developers as customers. He highlights the necessity for enjoyable API experiences and advocates building APIs with a conceptual model established through collaboration among various stakeholders.
- Common Language and Documentation: He stresses the importance of establishing a common vocabulary and comprehensive documentation, including quick start guides and code samples.
- Error Handling and Support: Effective error handling is outlined as critical for user understanding of API functionality. He expresses that clear and informative error messages are invaluable for developers.
- Versioning and Deployment: Leal discusses API versioning, recommending support for at least one previous version to ease transitions for users. Furthermore, he outlines that deployment in microservices should follow clear protocols to accommodate dependencies between services and ensure stability.
- Testing Strategies: The importance of contract testing and integration tests is emphasized to ensure that their services align with expectations before deployment.

Conclusions: Alberto Leal's presentation provides an in-depth look at the imperative elements of API design and integration within a microservices architecture. The primary takeaways include the need for a developer-focused approach, the value of clear contracts between services, disciplined versioning practices, and effective communication strategies to enhance both developer experience and application maintainability.

00:00:15.930 Thanks for coming! I'm super excited to be here to talk to you about designing the APIs for an internal set of services. My name is Alberto, and that's my Twitter, GitHub account, and my email address if you want to follow me on those. If you have any questions, feel free to reach out via that email. I work at Globo.com, which is the internet branch of Globo Organizations, the largest media conglomerate in Latin America. We operate some of the greatest vertical portals in Brazil, including news, soap operas, and entertainment videos. We also release a lot of open-source projects. One of them is Shiteru, which is our cloud application platform. If you want to check this and other projects out, you can find them on our GitHub page.
00:01:21.270 During this talk, I would like to cover two main topics. The first one is about designing APIs, and the second one is about the microservices environment. So, starting with the API itself—what is an API? It stands for Application Programming Interface. However, what I'm more interested in is the interface part because when we develop an application or library for other developers to integrate with, it is through the API that this integration becomes feasible. So, what about the microservices environment?
00:01:54.990 Here is a little definition: it is a concept where an application is decomposed into small and loosely coupled services, each having a single responsibility, and running in its own process. Let's look at an example to make this clearer. Imagine a simple webpage with a few lines of text, some videos, and some ads. This combined structure creates our content page. By thinking in a microservices architectural way, we can observe that we can construct some services and compose our services this way, which allows us to evolve them independently of each other. Different teams can work on each service. For instance, one team can handle video-related tasks, like compression and encoding, while another team takes care of image caching.
00:02:42.540 Coupling an application into those small services presents another opportunity: we can use the right tool for the job. For example, it is possible to use Node.js to write a simple API or Java for the backend. We are not stuck with a monolithic stack or platform, and this architecture leads to a more maintainable and stable application. For instance, it's quite simple to replace any service due to performance issues or any other problem that might arise, thus replacing that piece of code without needing to replace the entire application. However, we must ensure that the interface remains unchanged so that other services can still function with this new service. The concept behind the microservices environment may seem straightforward, but it raises many questions, such as how to define service contracts so that the services can communicate with one another, how to manage deployments, and how can we implement effective testing.
00:03:39.790 Let's go back to the idea of APIs and how we get started building one. Everything starts during the API modeling phase, where we gather all the stakeholders—the software engineers, designers, and everyone involved in the API development process—together in the same room. Here, we create a conceptual model and try to understand the purpose of the API. What is the main goal of the API we are building? Is it to save money, make money, or something else? The main goal might simply be to help other developers save time and effort.
00:04:07.900 We need to understand our audience. Who is going to use our API? Will it be used by Ruby developers, Java developers, or perhaps both? The API's consumers are developers, so we must prioritize their experience. It is crucial to put in extra effort to ensure that our API is enjoyable to use. Just as we keep in mind that products and applications must be user-friendly, we should extend that same thinking to our API. We should treat developers as customers and view the API itself as a product, because in fact, it is.
00:05:13.960 It's essential to create a common vocabulary for communication. This means we need to standardize terms used throughout the entire API lifecycle. When talking to business people, they must understand the same concepts as software engineers when discussing APIs and application services. We should ensure these terms are consistently used across resource names, documentation, data fields, and more. After establishing this common language, we can move on to modeling our resource, defining the necessary data fields, their relationships, and examining whether the resources available through the API should be publicly accessible or require authentication.
00:06:07.690 At this stage, we can also define the data format. At the moment, I will not discuss the particulars of data formats, but we will explore that in a few minutes. If you are going to create a hypermedia API, it's unnecessary to reinvent the wheel; you can leverage existing standards or libraries, such as JSON API and JSON Schema, which can significantly aid in this process. The examples I will share here are based on JSON Schema, which is the tool we are using at my workplace.
00:07:04.320 What makes a great API? Let's examine some examples. A great API is flexible and simple to use. Every developer has their own preference regarding data formats; some love JSON while others prefer XML. When designing an API, we must decide which formats to support. If your API needs to support both formats, keep in mind that it will require time and effort to test everything thoroughly in both formats. An API should also be intuitive; developers shouldn't spend excessive time figuring out how it works.
00:07:57.840 For example, it may sometimes be useful to retrieve all fields related to a resource, like 'cars', but at other times, this may not be necessary. It’s beneficial to add support for partial responses with additional attributes, helping developers access only the data they need. API URLs are also crucial; it's generally better to use nouns instead of verbs and organize them logically whenever possible. For example, you could have your collection followed by your resource identifier and subsequent filters. All HTTP operations, such as GET, POST, PUT, and DELETE, can be utilized effectively at the right urban structure.
00:08:59.730 Great API design leads to meaningful error responses. Error messages should be clear, providing enough information to understand what went wrong. Developers often face uncertainty when interacting with APIs, as they don't know what happens on the other side. These errors should aim to provide visibility into what transpires when something fails, regardless of whether the issue originates from the service or is an invalid request. Developers learn a lot about API integration through error messages.
00:10:14.900 Some API providers tend to return only a 200 status code for all requests, which makes it hard for users to diagnose issues. A better approach is using specific HTTP status codes along with detailed payloads explaining the errors. While there are over 50 status codes, it's not feasible for developers to memorize all of them. I've commonly utilized a handful of status codes: 400 for bad requests, 401 for unauthorized access, 403 for forbidden requests, 404 for not found resources, and 500 for internal server errors.
00:11:44.340 Another critical aspect of a great API is robust support. Creating an API requires substantial time and effort, and it’s essential to extend similar efforts toward documentation. When we purchase devices like iPhones or iPads, we tend to unbox them and start using them. Conversely, with APIs, we need to guide developers on how to use them. This includes providing guidelines, quick-starts, and sample code to illustrate how easy it is to integrate the API. Communication is also vital; maintain open channels with customers for feedback, be it through IRC channels, mailing lists, or GitHub.
00:12:56.579 Building a community around your API becomes increasingly important as it gains more users. When an API starts attracting a significant user base, it's crucial to foster relationships between developers and the API team. Another crucial element is security; using SSL to encrypt communications between your API and clients is critical. Moreover, ensure that sensitive resources are protected with proper permissions and can only be accessed by authorized users.
00:14:18.170 Now, let's talk about versioning. An API is never truly complete; there’s always room for improvement and change. The key question is how to manage these changes and how many versions to support. I suggest an API should support at least one version back. This practice greatly eases transitions, as managing multiple version changes can quickly become complex. Whenever we plan to release a new version, it’s important to communicate this to existing users, providing them with ample time to migrate to the improved version.
00:15:57.230 A good practice is to use the header field to indicate which version a user is trying to access. For example, you can use the HTTP Accept header or append the version as a query parameter. Moving on, once we’ve defined our APIs, we need to consider how they will communicate with one another. Defining service contracts is crucial so that one service understands which operations can be performed on another. Let's revisit our earlier example of a web content page and see how these components communicate.
00:17:46.990 Every service should expose its functionality through a well-defined contract, usually documented in a schema format. This way, other services or consumers can discover which functionalities they can utilize and what data they need to provide. While previous discussions highlighted that services can evolve independently, contracts also introduce some coupling between service providers and consumers. For instance, if one service needs to add a new required field to existing functionality, this could potentially disrupt other services that depend on it.
00:19:13.420 To manage such changes, a couple of approaches can be taken. One approach is to share the cost of the changes between the service provider and the consumers. The provider can implement the requested changes and communicate this to existing clients. Alternatively, the burden of adjustment can rest solely with the service provider, who would create a new contract and leave the old one unchanged for existing consumers, ensuring they are not affected.
00:20:18.860 The workflow would involve creating the updated contract without breaking existing functionality. A solid example comes from the JSON Schema, where you can define operations along with the required fields. As we evolve our APIs, it’s essential we ensure any adjustments maintain the integrity of earlier contracts to foster trust and reliability within the developer community.
00:21:47.690 The deployment of microservices generally feels easier, as we only need to update services that have changed rather than the whole application. However, complications can arise with dependencies between services. For instance, we must coordinate the order of deployment to avoid breaking functionality. Ideally, each service should have its own server, isolating their impacts. For example, if the video service goes down, other services, like those handling image management, should operate without issues.
00:24:50.170 Testing services is another crucial component. Services expose their functionality through contracts; thus, we can leverage contracted tests, allowing customers to fetch the contract and create mock tests. In contrast, integration tests involve hitting the actual services. Although these tests can be slower due to HTTP connections, they provide a definitive way to check that everything functions as expected.
00:25:03.720 As in most software development projects, things may not always go as planned, and that's alright. If necessary, you can always release a new version of your API.
00:25:46.379 Thank you very much!