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!