At Snyk, we're starting an API program that aims to maximize the value we provide to developers and the extensibility of our platform through our APIs.
Such an API needs some guardrails to stay cohesive, consistent and "unsurprising" to its consumers, as the platform scales in the number of concepts it provides and the number of teams delivering them.
Sweater Comb helps provide some of those guardrails with automation, initially by applying custom Spectral linter rules to our OpenAPI specifications.
Partial automation of our standards yields a tighter feedback loop than a purely manual review of every change.
Before diving into the standards automation in our API, it's worth pointing out that automated checks are limited in what they can catch and cover. Automation is no substitute for thoughtful design and planning when it comes to adding to and extending Snyk's core data model.
Automation is most helpful once such designs have been established. At this point, our automation can provide rapid development feedback to guide an implementation towards successful integration into the platform.
If you read through this document, you will find a lot of rules! A lot of things to keep track of! RFC2119-style DOs and DONTs and MUSTs and SHOULDs. And regardless of your opinion on them — everyone has an opinion — we should all agree that a consistent application of some sort of rules are necessary to provide a uniform, cohesive API for our customers.
Sweater Comb is our rule automation solution to address this specific problem — it "sweats the small stuff, so you don't have to". The rules described here are to give a sense of what our choices are and why, but the execution of these rules is actually performed with Sweater Comb as an OpenAPI linter — in your individual service projects, but also eventually, as part of a CI/CD service integration pipeline providing API governance.
These rules are automatically applied by Vervet when assembling our API from its component resources and services. Longer-term, these rules can be evaluated at the edge in a fully distributed service-oriented architecture, used to alert compliance problems, or even gate CI/CD.
As mentioned, our API standard taken as a whole is quite a lot of rules and processes, but in this document, they are introduced largely by example with references to more detailed resources.
It's intended to be a place to copy from, while explaining why you should do things a certain way. If I don't give you a place to copy from, you'll copy from the nearest implementation, which might work, but might miss some of that why.
Finally, refer to RFC 2119 for the precise meaning of words and phrases like must, must not, should, should not, etc.
I hope this document is clear, approachable and for the developers building our new and exciting API, it makes you successful! If it's missing something, please help us maintain it! We all own these standards collectively, and they too can evolve, just like our API versions.
For all others, I hope it is an interesting insight into how we're building APIs at Snyk!
— Casey Marshall, 2021-09-24~beta ;)
Snyk's API is essentially a catalogue of models, represented as resources.
The key abstraction of information in REST is a resource. Any information that can be named can be a resource: a document or image, a temporal service (e.g. "today's weather in Los Angeles"), a collection of other resources, a non-virtual object (e.g. a person), and so on. In other words, any concept that might be the target of an author's hypertext reference must fit within the definition of a resource. A resource is a conceptual mapping to a set of entities, not the entity that corresponds to the mapping at any particular point in time.
— Roy Thomas Fielding, Architectural Styles and the Design of Network-based Software Architectures, sec. 5.2.1.1
Our API presents the core models from which Snyk products and services are built, as such resources and collections. Models such as:
- Users
- Organizations
- Groups
- Projects
- Targets
- Issues
Object instances of these all relate to each other in a rich graph of associations, which are represented in the API with hypertext references — links.
Direct access to these core models allows customers and partners to utilize Snyk as a platform. Instead of waiting for Snyk to add a custom feature or solution, they can easily compose one from our data model in their own applications.
Snyk can provide higher-level abstractions over this API of data models (resources) such as GraphQL and SARIF. Both provide a rich and powerful interface to query and report on SAST results.
Snyk's API represents our product's core data model as resources, for the purpose of delivering that data model to our customers and partners.
To keep it about that data, the only operations allowed in the API are "CRUD"-type operations: Create, Retrieve, Update, Delete.
Contrast this with an RPC API which may offer a rich vocabulary of verbs at the granularity of method and function calls on objects. This approach may be necessary and useful in certain situations — however, these belong in a different sort of API from the one we're building here.
There are situations where it makes sense for Snyk to represent other resources with an API. Many of these may relate to Snyk's core data model. However, if they are describing a different interface or paradigm, they do not belong in /v3
, they should be located elsewhere. Examples of distinctly different models & paradigms:
- GraphQL
- A generalized query interface that merges many models into a unified graph
- SARIF
- Industry-standard representation of SAST results
- CSV
- Data export and reporting representation of results, models, activity
- OAuth
- Industry-standard authorization flow for third-party integrations
Our API is assembled from resources that are themselves defined in OpenAPI. OpenAPI presents a model description from which documentation, client SDKs, as well as internal governance mechanisms such as request and response validators, can be automatically generated.
However, the quality of this generated documentation and code depends entirely on the quality of that model's content. Our experience with generating documentation and code from OpenAPI specs informs many of our standards decisions — from requiring certain fields, to restricting OpenAPI schema definitions so that they can be expressed well in all languages we're targeting in our SDKs.
Consider carefully and thoughtfully:
- What does the resource really represent?
- How does it relate to other established parts of the platform?
How does the naming of this concept affect the language we use to describe the problems our platform solves?
Consider how this new concept will be represented from different perspectives:
- Product end-user experience
- API developer experience
- Partner integration experience
Know what models you're working with. When in doubt, ask!
- Different models == different APIs (see above for examples)
- Are you adding to an existing model appropriately?
- Or are you working with different (even if related) sorts of concepts entirely?
RESTful APIs (JSON or otherwise) are about representing models as resources. Think of such an API as providing a catalogue of model objects.
RPC, when necessary, belongs in a separate API.
Don't worry too much about this separation of models and paradigms! Distinct APIs can relate and link to each other.