Buf CLI

Breaking change detection – Tutorial

As you evolve your Protobuf schemas, you might introduce breaking changes—either by breaking your generated code, or by breaking your ability to read existing data. Protobuf has many ways to evolve schemas without breaking existing code, but sometimes it's a better choice to make a breaking change rather than go to the extra effort of backwards compatibility. If you have few clients and can easily update and deploy them, it may be perfectly okay to break your schemas. Buf's breaking change detection reliably and mechanically identifies breaking changes so you and your team can focus on the important human decision of whether to allow them or not.

The tutorial takes you through running breaking change detection locally using common use cases. Read the overview to learn about editor integration, server-wide breaking change enforcement, and the review flow.

Prerequisites

We recommend completing the Buf CLI tour to get an overview of the Buf CLI first.

  • Install the Buf CLI
  • Clone the buf-tour repo:
    $ git clone git@github.com:bufbuild/buf-tour.git
    

1. Inspect the module

Modules represent a collection of files that are configured, built, and versioned as a logical unit when performing Buf CLI operations. The example code provides a module to work with, so start there. From the directory you cloned into, go to the tutorial code:

$ cd buf-tour/start/tutorial-breaking

Your module has the directory structure shown below, and is defined by the buf.yaml file in the proto directory (the root of your schemas). This module is your input for the buf breaking commands in the rest of the tutorial.

.
└── proto
    ├── buf.yaml
    └── pet
        └── v1
            └── pet.proto

The example buf.yaml file contains all of its required fields. The breaking field controls your breaking change detection settings. It's set to our recommended default of FILE, which provides the highest level of protection against breaking changes.

proto/buf.yaml
version: v1
name: buf.build/tutorials/breaking
breaking:
  use:
    - FILE
lint:
  use:
    - DEFAULT

For more information about specific fields, see the buf.yaml reference.

2. Compare against a local git repository

The example code is a git repository, so you can check whether your uncommitted changes break the schemas. First, make a non-breaking change to your schema and add a new type of pet to the enum:

proto/pet/v1/pet.proto
// PetType represents the different types of pets in the pet store.
enum PetType {
  PET_TYPE_UNSPECIFIED = 0;
  PET_TYPE_CAT = 1;
  PET_TYPE_DOG = 2;
  PET_TYPE_SNAKE = 3;
  PET_TYPE_HAMSTER = 4;
+ PET_TYPE_BIRD = 5;
}

Then run buf breaking to compare the module in the proto folder to the one in the original repo you downloaded. You should see no errors.

~/.../buf-tour/start/tutorial-breaking
$ buf breaking proto --against '../../.git#subdir=start/tutorial-breaking/proto'

Note that in the --against target, you need to point to the root of your git repository, then traverse back down to the directory you're comparing against using the subdir option.

3. Compare against the Buf Schema Registry (BSR)

For organizations that use the BSR, comparing against the version of the module stored there is the most common use case. The example module you're working with exists in the BSR already at https://buf.build/tutorials/breaking, and if you look in the buf.yaml file for your module, the name field points there.

proto/buf.yaml
version: v1
name: buf.build/tutorials/breaking
breaking:
  use:
    - FILE
lint:
  use:
    - DEFAULT

This time, make a breaking change to the schema by changing the fourth item in the enum.

~/.../buf-tour/start/tutorial-breaking/proto/pet/v1/pet.proto
// PetType represents the different types of pets in the pet store.
enum PetType {
  PET_TYPE_UNSPECIFIED = 0;
  PET_TYPE_CAT = 1;
  PET_TYPE_DOG = 2;
  PET_TYPE_SNAKE = 3;
- PET_TYPE_HAMSTER = 4;
+ PET_TYPE_RODENT = 4;
  PET_TYPE_BIRD = 5;
}

Run buf breaking again, this time comparing against the latest version of the tutorial module in the BSR. You should receive an error.

~/.../buf-tour/start/tutorial-breaking
$ buf breaking proto --against buf.build/tutorials/breaking
Output
proto/pet/v1/pet.proto:13:21:Enum value "4" on enum "PetType" changed name from "PET_TYPE_HAMSTER" to "PET_TYPE_RODENT".

Revert the change.

4. Compare against a remote Git repository

If your .proto files aren't in the BSR yet, generally you'd compare against your remote Git repository instead, since that represents the latest version of your code. That's a straightforward change to the --against target, so we'll also explore what happens when you change the configuration to a different rule set—from FILE to PACKAGE. PACKAGE allows elements to move within a package, unlike FILE, which is stricter.

First, move the PetType enum to a new pet_type.proto file.

~/.../buf-tour/start/tutorial-breaking
$ touch proto/pet/v1/pet_type.proto

Delete the enum from pet.proto, and add an import statement to reference the new .proto file:

~/.../buf-tour/start/tutorial-breaking/proto/pet/v1/pet.proto
+ import "pet/v1/pet_type.proto";
- // PetType represents the different types of pets in the pet store.
- enum PetType {
-   PET_TYPE_UNSPECIFIED = 0;
-   PET_TYPE_CAT = 1;
-   PET_TYPE_DOG = 2;
-   PET_TYPE_SNAKE = 3;
-   PET_TYPE_RODENT = 4;
-   PET_TYPE_BIRD = 5;
}

Then copy/paste the following into pet_type.proto (note that the package is still pet.v1):

syntax = "proto3";

package pet.v1;

// PetType represents the different types of pets in the pet store.
enum PetType {
  PET_TYPE_UNSPECIFIED = 0;
  PET_TYPE_CAT = 1;
  PET_TYPE_DOG = 2;
  PET_TYPE_SNAKE = 3;
  PET_TYPE_HAMSTER = 4;
}

Run buf breaking again, this time comparing against the latest version of the remote git repository. You should get an error showing that the enum was deleted.

~/.../buf-tour/start/tutorial-breaking
$ buf breaking proto --against 'https://github.com/bufbuild/buf-tour.git#branch=main,subdir=start/tutorial-breaking/proto'
Output
proto/pet/v1/pet.proto:1:1:Previously present enum "PetType" was deleted from file.

Let's assume that moving an enum within the same package isn't considered a breaking change in your organization. For buf breaking to align to this policy, it needs to be set to the PACKAGE rule set instead of FILE. Make that change in your buf.yaml file:

~/.../buf-tour/start/tutorial-breaking/proto/buf.yaml
version: v1
breaking:
  use:
-    - FILE
+    - PACKAGE
lint:
  use:
    - DEFAULT

Run the same buf breaking command, and you should no longer get the error.

These scenarios represent the most common use cases for locally using buf breaking. If your organization also has server-wide breaking change detection, you may see different results when running locally versus when you push a module to the BSR. See the breaking change enforcement documentation for the details.

To see a complete file set illustrating breaking change detection, visit this example project: