Cyprian Gracz September 12, 2023
In a “less is more” world full of simple, multi-purpose technology, it’s very easy to fall into the habit of defaulting to the most lightweight tools to solve our problems. Sometimes, this works out great – your company already uses a tool for a specific use case, and it turns out you can repurpose it with little to no training, code, or investment. Other times? Not so much. Turns out, that “lightweight” solution might buckle under the pressure of your evolving needs – and worst case, it can even stifle innovation. Counterintuitively, sometimes the approach that seemed way too complex at first turns out to be the simple, elegant solution you needed all along.
Recently, we helped one of our clients navigate this paradox. Read on to learn more about the problem and how we solved it – while unlocking some new opportunities along the way.
The Use Case: Message-Driven APIs
Our client is developing a cloud-enabled security device designed to safeguard valuable items stored in vehicle cargo spaces. Considering that vehicles can operate in areas with poor or intermittent internet connectivity, MQTT emerged as the natural choice for communication between the device and the cloud. MQTT is a lightweight, publish-subscribe, machine-to-machine network protocol commonly used for exchanging messages between connected devices.
A Simple Solution That Added Complexity
The client’s team needed to define the format of messages exchanged between the device and the cloud. Without a well-defined structure, the receiver on the other end of the channel would struggle to process the messages effectively.
Initially, they chose to document all known message types in Atlassian Confluence. For each new API, the cloud and device teams would collaborate, agreeing on the format and adding it to the Confluence page. Subsequently, they would individually implement the message format in their respective codebases using different programming languages. Once both parties completed their implementation, they would attempt to integrate the two. Although this approach appeared lightweight, it proved to have significant drawbacks. It required time-consuming manual implementation, was prone to errors, and had limited versioning capabilities.
A Better Choice: AsyncAPI
The team had an understandable bias for simplicity, and was wary of a solution that would add more overhead. However, we knew from experience that a more formal tool was needed to enhance the development experience and overall project quality.
With that in mind, we proposed AsyncAPI: a specification format that streamlines the process of defining and documenting communication protocols, particularly well-suited for message-driven APIs. It acts as a standardized and machine-readable contract, precisely describing the structure and behavior of asynchronous APIs, including those using MQTT. By providing a clear and structured representation of the communication protocol, AsyncAPI ensures consistency and interoperability across different components and systems. It acts as a common language for developers, enabling seamless integration and reducing the chances of misinterpretation or errors.
Tangible Value, Delivered Quickly
To promote adoption, we knew that we needed to make the case that AsyncAPI could solve the team’s problems (and add real value) in a way that didn’t carry a lot of up-front overhead.
AsyncAPI utilizes a clear and human-readable specification format (JSON or YAML) that makes it easier for developers to understand and work with, reducing the learning curve. It is also supported by many IDEs, making the process of creating, editing, and validating AsyncAPI documents even more convenient.
Additionally, the implementation was straightforward, with the first APIs migrated to AsyncAPI within hours.
Right from the start, it yielded numerous benefits.
Automatic Code Generation. Leveraging tools like Modelina and AsyncAPI’s generator, we automated the development process, generating message payloads and clients. This automation resulted in a more reliable and robust codebase, eliminating the need for manual implementation, greatly minimizing the potential for human errors, and significantly reducing the time and effort required to implement the communication logic.
Efficient Workflows. Because it’s a YAML/JSON file, the AsyncAPI specification allowed for independent work for developers on their proposals for API changes. They could collaborate, discuss, and approve these changes using standard pull request mechanisms, providing a streamlined and efficient workflow. Additionally, the use of a standardized specification simplified the tracking of changes, ensuring transparency and making it easier to monitor and audit the evolution of the API over time.
Convenient Knowledge-Sharing. AsyncAPI facilitated the generation of HTML documentation that could be easily shared with developers and other stakeholders involved in the project. This documentation provided a comprehensive and easily accessible reference point for understanding the communication protocol.
AsyncAPI Unlocks Innovation
AsyncAPI adoption had solved the original problem, but that was only the beginning. By implementing this powerful tool for one use case, we were able to unlock new and previously unknown opportunities for efficiency and innovation. Here’s an example.
Although the automatic code generation for each new API substantially reduced the implementation time for new features, a bottleneck persisted during the testing phase on the cloud side. Developers had to wait for their counterparts working on the device side to complete the implementation and deploy it to the physical devices. This process often entailed coordination and availability of the devices, significantly hindering rapid delivery.
While it was feasible to manually publish messages to MQTT topics, the level of interaction required made this approach unsustainable for complex scenarios involving multiple message exchanges or time-sensitive tasks such as video uploads. We needed a way to enable cloud developers to easily test existing and new APIs, thereby reducing the time required to create prototypes, and to automate multi-step scenarios for end-to-end testing.
Our proposed solution? A virtual device simulator. We decided to build a proof of concept for a web-based virtual device simulator by leveraging existing libraries and components.
Thanks to AsyncAPI’s code generators, we were able to automatically generate browser-compatible code for existing APIs. With the capability to send messages to the cloud, our next objective was to provide developers with a straightforward means of sending valid data. Leveraging AsyncAPI’s utilization of JSON Schema to define the message payload format made this much simpler, by enabling us to integrate an existing library to generate and display web forms based on that schema. Developers simply needed to select the desired topic from the AsyncAPI specification to automatically generate feature-rich web forms equipped with data validation mechanisms.
In situations where the message payload does not require customization for each call, developers have the flexibility to incorporate a straightforward button that triggers a message with a predefined payload.
To effectively handle incoming messages from the cloud, particularly commands, and accurately emulate the behavior of a real device, we integrated with browser APIs to leverage the capabilities of the computer’s hardware. This integration allowed us to take advantage of resources such as the computer’s webcam, enabling functionalities like live video streaming. By leveraging these features, our virtual device simulator closely emulates the behavior and capabilities of an actual device, providing a comprehensive testing environment for API interactions.
The tool we created quickly became a must-have in every developer’s toolbox. What we didn’t foresee was that it would also be used on a daily basis by Quality Engineers and Product Managers. The former use it to perform manual testing, while the latter use it for quick prototyping of business use-cases.
None of this would have been possible if the team had continued using Confluence to document their message types.
Sometimes, More Is More
It’s a common misconception that using standard specifications for documenting APIs (like AsyncAPI) is overly time-consuming and requires a learning investment that’s too significant. While it may appear daunting at first, the advantages of adopting these specifications often far outweigh the limited overhead required to implement them.
- They promote collaboration, facilitate understanding between API providers and consumers, and enhance overall productivity.
- With standardized documentation, developers can effortlessly consume and integrate APIs, reducing trial-and-error and saving time.
- And they often provide add-ons that integrate with IDEs and tools for automating tasks like code generation, validation, and testing, resulting in increased efficiency and decreased error rates.
By embracing these specifications, developers can optimize their development workflows, streamline communication, and achieve enhanced efficiency and reliability in their projects.
Furthermore, the adoption of standard specifications like AsyncAPI unlocks new opportunities by fostering innovation and enabling interoperability across diverse systems and technologies. These specifications act as a solid foundation for developing scalable and flexible solutions, empowering developers to explore new functionalities and integrate their APIs with other systems. With standardized documentation and tooling, developers are empowered to push boundaries, think creatively, and unlock the full potential of their projects.
So, what can we learn from this experience? Two things: first, that lightweight solutions, though tempting, are not always the right path; and second, that standard specifications for documenting APIs are a powerful resource that’s often worth the investment.