There’s an advantage to Microsoft’s work at scale in Azure: It will find problems much earlier and solve them much faster than the rest of us. In the past, we may not have seen those solutions for years until they finally made it into Visual Studio tools. But now things are different, as Microsoft’s conversion to open source means solutions are designed in the open and shared with the rest of the world via GitHub.
One big problem is API design and finding ways to make all the APIs from Azure consistent. A modern API definition, even one written using OpenAPI, can be complex and large; some of Azure’s are several thousand lines long. Even with the same approach and guidelines, API definitions from different teams will emphasize different elements and offer different ways of working with similar functions.
With Azure recompiling its API surface at least once a day, it needs to ensure that APIs are consistent and that documentation and tests can be automated—as well as allowing a developer familiar with one set of APIs to not have to learn a whole new set of techniques when adding additional services to an application.
Inconsistent definitions are a problem for those of us working at the other end of the service, bringing those API definitions into our applications using OpenAPI tools to generate the appropriate endpoints. It becomes even more of an issue when we need to go beyond REST to GraphQL and to gRPC as our APIs evolve and require more than HTTP access.
Microsoft has begun to move much of its API development to a language called Cadl, which helps you define API structures programmatically before compiling to OpenAPI definitions. The intent is to do for APIs what Bicep does for infrastructure, providing a way to repeatably deliver API definitions. By abstracting design away from definition, Cadl can deliver much more concise outputs, ensuring that the OpenAPI tool in platforms like Visual Studio can parse it quickly and efficiently.
Making Cadl a language makes a lot of sense; it allows you to encapsulate architectural constraints into rules and wrap common constructs in libraries. An API description won’t compile if it steps outside the bounds of your architectural rules, ensuring they’re enforced by the Cadl environment. API designers will need to work within the guidelines set by the architecture team, a process the Cadl designers describe as making outputs “correct by construction.”
Like many Microsoft open source projects, Cadl is being developed on GitHub, with clear and in-depth documentation. There’s even an online playground where you can experiment with Cadl code, comparing it with both OpenAPI and Swagger format outputs. You’ll quickly see that Cadl code can be a lot more concise than the OpenAPI output: A 34-line Cadl API description expands to 359 lines of OpenAPI. That’s 10 times larger. It’s a lot easier to maintain the Cadl code too, as it all fits on one screen with functionality grouped appropriately.
Build an API definition with Cadl
Installing Cadl is simple enough. The compiler and CLI are part of the same npm-hosted application. Once downloaded and installed, you can use the CLI to install the bundled Visual Studio and Visual Studio Code extensions. You’re now ready to build your first API definition, using the Cadl CLI to initialize your project. This runs you through a brief set of prompts to choose a base template, add a name, and pick a default library. A quick compilation tests that everything is in place, ready to work.
Writing an API definition in Cadl is straightforward. You need to declare both the REST and OpenAPI bindings, usually in the main file of your Cadl code. In Cadl, keywords are called “decorators” and are prefixed with @, helping make files human-readable.
Your definition needs to start with service definitions, giving a service a name and, more importantly, a version number. You can next add a server definition with a URI for the endpoint. Useful for globally distributed systems, this feature can add additional parameters, such as the regions an API is available. Although this is clearly designed with Azure in mind, it’s helpful for any distributed system with multiple endpoints.
Next, define the routes and resources used by the API. Routes are the path to the resource relative to the service URI and are linked to namespaces that wrap the API operations. Operations are defined using the HTTP verb that’s expected or by function, for example “list.” Where names are used, the compiler will add the appropriate verb when generating the API. These can define the request body of a REST API call, so you can use this to send JSON to a complex API, or just simple text for something relatively basic. The same process works for responses, too.
For more complex APIs, an autoroute option can generate paths using parameters, for example where you’re passing usernames or IDs to an API that expects them in the path. You’re not limited to delivering parameters in paths; there’s an option to pass them as a URL query.
The result is a relatively simple way to define your REST APIs, somewhat reminiscent of working with the Express node.js application framework. I suspect this is mainly due to how Cadl constructs URLs for service calls, abstracting APIs into code. Coming to it with experience using tools like Express could help developers come to grips with a new language.
Using Cadl to enforce standards
Writing API definitions is only part of the Cadl story. Perhaps more importantly, it enforces API standards on development teams by providing structures that must be used to deliver an API. The key to building standards into Cadl definitions is to create libraries. These let you manage reusable templates for common functions with a very C#-like syntax. It’s worth looking at how Microsoft uses these to implement Azure API features.
For example, you can create a generic interface to define a REST API format, detailing the capabilities of an API. Once you have a library, you can extend your API interfaces to use the library template, mapping it to a route and tagging it appropriately. Next, use a model definition to create the API, listing any expected responses.
Cadl provides a simple way to add documentation to an API: Use the @doc statement to include documentation that can be extracted when creating documentation from the resulting OpenAI definition. It’s quick and easy, which is how Microsoft generates much of its Azure API documentation. Encourage your API designers to use this approach to not only add documentation in the main body of an API design, but also in any libraries you use.
There’s a lot to like in Cadl. It’s one of those domain-specific languages you look at and think, “Why wasn’t that here already?” By providing a logical way to both construct and constrain APIs, Cadl lets architects be opinionated about what an API can do and deliver and gives API developers the freedom to quickly build a definition that matches what their code can do. Cadl allows both client developers and, perhaps more importantly, testing tools to consume consistent API definitions.
Copyright © 2022 IDG Communications, Inc.