Skip to main content

· 3 min read

If the goal is to ship high quality, well-designed, reliable APIs to our consumers — I don't think linting our OpenAPI descriptions is enough to get us there. After months of helping companies like Snyk and SendCloud go API-first, I'm ready to make a case for why it's a good idea to replace linting with a tool that tests API changes.

· 6 min read

APIs have become the most important dependency in our stack. They allow us to collaborate at scale, and build software that is greater than the sum of its parts.

If you are on a team that produces an API, then you know that it comes with a lot of responsibility. Unlike code dependencies, when an API is changed, it affects all of its consumers immediately. This means your team has the power to break other team’s applications whenever you deploy changes (no pressure).

That is what makes APIs so different than any other kinds of development. Deploying an API is like launching a satellite. Once it is in orbit, you can not really change it. If you are clever, you’ve added the ability to make orbital adjustments and small software upgrades (an evolvable API-design). But if you want to make fundamental changes, you have to launch another satellite and de-orbit the first.

The constraints of API development are why many teams try to work API-first. They want to build the right API, the first-time (before it gets to orbit). They want everyone on the team to be thoughtful about introducing new capabilities and changing the API. Most importantly, they want to keep their promises to consumers.

It sounds great, but it takes work. Optic makes it a lot easier for developers without changing much about their workflow. The journey looks something like this:

alt

· 3 min read

When we’re reviewing each other’s code it’s a context change from whatever we were doing before. We’re still thinking about other tasks, but to do an effective review we have to build context about what our teammate is working on. When there is any friction at all in this process, the quality of the review goes down.

That's why best code review and CI tools were designed and built for busy humans. All the context needs to be right in front us us. On GitHub you can scroll through all the code changes, you don’t need to click around a file system or move windows around.

· 4 min read

Writing your style guide and API standards is one of the first tasks teams take on when they start working API-first. We’ve all seen the popular incarnations of these guides from Google or Microsoft. Each guide can be dozens of pages. It is great to have decided upon and written down guidelines, but how do you actually make them easy to follow?

A lot of teams that joined our beta face this “what’s next” problem. They have defined styles, standards and even made hard commitments in their SLAs about when and how API functionality will deprecated. Publishing documentation for your guidelines and expecting them to immediately get adopted is not realistic. It may not even be a fair thing to to ask.

If you want developers to work API-first and follow the standards, you should work to make that easier than the alternatives.

· 4 min read

The practices that have had the biggest impact on my work as a developer have been those that help me to think, learn, and work together with others. That is why I have always liked the idea of working on our APIs design-first.

But doing design in OpenAPI has never felt quite right. When I design an API the thing I’m thinking about in my head it is almost always examples. I can talk about examples, share them with consumers for feedback and imagine myself sending, and receiving data from the API.

Examples are great thought-objects for the design phase. But when I’m done doing design, it’s great to have a schema to help during implementation. How could we get the best of both worlds?

Design-first with examples

At Optic we’ve been helping teams adopt API-first practices. Design-first is one of those ideas everyone wants to try, but everyone has trouble putting into practice.

So what if designed our APIs using examples, right in OpenAPI?

Below is a valid OpenAPI document, but it looks different from what you are used to. We have left out the schema attribute and just focused on writing the example. You could share this with your team, consumers, and even people who are not OpenAPI experts. Examples make it easy to reason about the design being considered no matter who you are:

openapi: 3.0.1
info:
version: 0.0.0
title: Cities
paths:
"/city/{code}":
parameters:
- name: code
in: path
example: PHL
schema:
type: string
required: true
get:
operationId: getCityInfo
responses:
200:
description: "Results"
content:
application/json:
example:
name: Philadelphia
code: PHL
population_size: 1500000
country_code: USA
404:
description: "Not Found"
content:
application/json:
example:
message: "City Code 'PHL' not found"
docs: "http://myapi.com/docs#getCityInfo"

You could iterate on the design by sharing and changing the examples. Then, when you’re ready to go from design → implementation run:

optic update --learn-examples

And just like that, Optic has read your examples, diffed them against the spec, and patched the specification to include a schema that matches your example 👇 

openapi: 3.0.1
info:
version: 0.0.0
title: Cities
paths:
"/city/{code}":
parameters:
- name: code
in: path
example: PHL
schema:
type: string
required: true
get:
operationId: getCityInfo
responses:
200:
description: "Results"
content:
application/json:
schema:
type: object
properties:
name:
type: string
code:
type: string
population_size:
type: integer
country_code:
type: string
required:
- name
- code
- population_size
- country_code
example:
name: Philadelphia
code: PHL
population_size: 1500000
country_code: USA
404:
description: "Not Found"
content:
application/json:
schema:
type: object
properties:
message:
type: string
docs:
type: string
required:
- message
- docs
example:
message: "City Code 'PHL' not found"
docs: "http://myapi.com/docs#getCityInfo"

Optic collaborates with you, and will never overwrite changes you make manually. You can feel safe going back to add description, format or other keywords to the schemas Optic generated.

You can run optic update --learn-examples as many times as you want, even if there’s already a schema.

Let’s add code to the example:

schema:
type: object
properties:
message:
type: string
description: A helpful error message # I wrote this manually
docs:
type: string
description: A link to the docs # I wrote this manually
required: # I manually made `docs` optional
- message
example:
message: "City Code 'PHL' not found"
docs: "http://myapi.com/docs#getCityInfo"
code: 154

Now when I run optic update --learn-examples:

code is added to the schema, but my manual changes were preserved. Optic is not "generating the spec", it’s helping me write it.

schema:
type: object
properties:
message:
type: string
description: A helpful error message # still here!
docs:
type: string
description: A link to the docs # still here!
code: # added inline
type: number
required:
- message
- code
example:
message: "City Code 'PHL' not found"
docs: "http://myapi.com/docs#getCityInfo"
code: 154

Imagine if you could design an entire API with examples, then end up with an OpenAPI on the other end? We think this can help a lot of teams start working design-first.

Is this cool? What would you like to see next? We’ve been building tools like this every week with our beta partners. If you want to help us come up with and try out crazy ideas like this, join the beta! All this is open source and publicly available soon.

· 4 min read

We have all experienced the benefits of doing code reviews. They give everyone on the team an opportunity to give early feedback and share knowledge. This leads to better software, fewer bugs, and a stronger overall architecture for our applications. Because we all have different backgrounds and perspectives, code review works well when everyone on the team is included.

Starting the practice of reviewing API changes has similar benefits on the quality, reliability and the developer experience of your API.

· 4 min read

Let's say someone on your team got in a hurry and introduced a breaking change for your API in their Pull Request (PR). Let's say they added a required query parameter. Luckily, someone on your team caught it during the code review and kept it from getting into production. That was close! How can your team make sure this doesn't happen in the future? Instead of relying on someone to manually check for these things, you can write an API guideline that does it automatically.

We've recently released a new tool called optic-ci for writing these kinds of API guidelines. The features of optic-ci go beyond linting OpenAPI documents and making sure they follow the standards. With optic-ci, you can write checks that make sure your team is avoiding certain kinds of API changes, which in this case is introducing breaking changes.

Let's look at how you and your team might write a rule to avoid adding a required query parameter in the future.

Designing the API guideline

The first step to writing your new API guideline is to describe what you want to check and provide both passing and failing examples. This a design-first approach to writing API guidelines.

In this case, you describe the guideline, add a passing example where the added request parameter is optional, and add a failing example where the added request parameter is required.

export default check('prevent adding required query parameter')
.passingExample(
scenario('adding optional').requestParameter.added({
in: 'query',
name: 'exampleParam',
required: false,
})
)
.failingExample(
scenario('adding required').requestParameter.added({
in: 'query',
name: 'exampleParam',
required: true,
})
);

This gives your team a chance to think through the change and discuss the specifics of how you want to guide people as they add query parameters. The code is straightforward and the examples are narrowly-focused—no need to write a full OpenAPI document to show the scenarios. And this code acts as a guide to the developer who implements the check later, making sure the design and implementation match.

Implementing the API guideline

You're now ready to implement the guideline. The optic-ci tool will use the examples from above to guide you as you go.

export default check('prevent adding required query parameter')
.implementation(({ request }) => {
request.queryParameter.added.must('not be allowed', (param) => {
if (param.required) {
expect.fail('expected query parameter to not be required');
}
});
})
.passingExample(/* ... */)
.failingExample(/* ... */);

This check does a few really nice things:

  • Adds annotations where things fail. When optic-ci finds a failing check in the future, it will annotate the diff for the OpenAPI document in the PR as if someone on your team found the issue and commented on it.
  • Prevents breaking changes from making it through. When this check fails, it will automatically prevent the change from making it to production. This allows you to rely on the tool rather than someone manually catching it.
  • Cuts down on the noise. This check only runs when someone adds a new query parameter, limiting the the feedback to only what's changed. This cuts down on the noise and makes the guidelines actionable.

When everything looks good, you can add this code to your set of checks and start using it immediately in your code reviews.

There's more to share

With optic-ci, we wanted to create a workflow that enables people to go from nothing to a designed, tested, and implemented API guideline in minutes. We wanted the workflow to revolve around developers and their communication rather than being about exerting control over API designs. And we wanted a way to write guidelines that are implemented correctly based on the original design discussions, which is really important. We feel like we've hit our mark.

The great part of this is the authoring process takes minutes to do and can have an immediate impact on your developer productivity and API quality.

There's many more features to optic-ci and this workflow that we didn't share. We'll be going exploring those in the coming posts.

Get started

Optic CI is still in beta, and we're working towards a public release in coming weeks. If you'd like to partner to make optic-ci great, please join our beta. This has been really exciting to build! Stay tuned for more open source rulesets, case studies, and tools for authoring your own checks!

Try it yourself!

· 7 min read

As developers writing code in the 2020s you are probably running multiple linters and formatters in your IDEs that help you avoid common programmatic errors and make the code more readable for your teammates.

Linters are useful and a lot of teams we work with have tried to use API-specific linters as part of their API governance efforts. What they have found is that while API linting is great for making sure their specs are valid OpenAPI and basic standards are followed, in practice the things they really care about are not possible with linters. Linters can not prevent breaking changes, ensure they meet their SLAs, and have trouble enforcing the API style guides they've chosen.

· 4 min read

We’ll skip right to the good part: add this 15 line GitHub Action to your repository and point it at your OpenAPI file. Once added Optic will catch breaking changes in CI before they affect your consumers.

.github/workflows/api-checks.yaml
name: optic-ci runner
on: [ pull_request ]
jobs:
optic-ci:
name: check for breaking changes
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v2
with:
fetch-depth: 0
- name: Run Optic CI
uses: opticdev/optic-ci-starter@main
with:
file: /path/to/openapi.yaml #change me
ruleset: default