Search code examples
protocol-buffers

Extracting an enum from a proto message: Breaking change or not?


I've got the following

message Value {
  enum Strategy {
    STRATEGY_1 = 1;
    STRATEGY_2 = 2;
  }
  
  Strategy strategy = 1;
}

I need to reuse that enum now so I want to refactor it to

enum Strategy {
  STRATEGY_1 = 1;
  STRATEGY_2 = 2;
}

message Value {
  Strategy strategy = 1;
}

Is this a breaking change protocol wise or not?


Solution

  • It's not a breaking change protocol wise.


    This can be verified by comparing the proto serialized data for each case.

    v1.proto

    syntax = "proto3";
    
    package v1;
    
    option go_package = "github.com/stackoverflow/pbtest/gen/v1";
    
    message Value {
      enum Strategy {
        STRATEGY_0 = 0;
        STRATEGY_1 = 1;
        STRATEGY_2 = 2;
      }
    
      Strategy strategy = 1;
    }
    

    v2.proto

    syntax = "proto3";
    
    package v2;
    
    option go_package = "github.com/stackoverflow/pbtest/gen/v2";
    
    enum Strategy {
      STRATEGY_0 = 0;
      STRATEGY_1 = 1;
      STRATEGY_2 = 2;
    }
    
    message Value {
      Strategy strategy = 1;
    }
    

    Setup a Go module and generate code from the protobuf definitions:

    $ go mod init github.com/stackoverflow/pbtest
    go: creating new go.mod: module github.com/stackoverflow/pbtest
    go: to add module requirements and sums:
            go mod tidy
    $ protoc --go_out=. --go_opt=module=github.com/stackoverflow/pbtest v1.proto
    $ protoc --go_out=. --go_opt=module=github.com/stackoverflow/pbtest v2.proto
    $ go mod tidy
    go: finding module for package google.golang.org/protobuf/runtime/protoimpl
    go: finding module for package google.golang.org/protobuf/reflect/protoreflect
    go: found google.golang.org/protobuf/reflect/protoreflect in google.golang.org/protobuf v1.30.0
    go: found google.golang.org/protobuf/runtime/protoimpl in google.golang.org/protobuf v1.30.0
    

    Then write some code that marshals each version

    main.go

    package main
    
    import (
        "bytes"
        "fmt"
    
        "google.golang.org/protobuf/proto"
    
        v1 "github.com/stackoverflow/pbtest/gen/v1"
        v2 "github.com/stackoverflow/pbtest/gen/v2"
    )
    
    func main() {
        v1pb, err := proto.Marshal(&v1.Value{
            Strategy: v1.Value_STRATEGY_1,
        })
        if err != nil {
            panic(err)
        }
    
        v2pb, err := proto.Marshal(&v2.Value{
            Strategy: v2.Strategy_STRATEGY_1,
        })
        if err != nil {
            panic(err)
        }
    
        fmt.Printf("v1: %x\n", v1pb)
        fmt.Printf("v2: %x\n", v2pb)
        fmt.Printf("equal: %t\n", bytes.Equal(v1pb, v2pb))
    }
    

    ..and run it:

    $ go run .
    v1: 0801
    v2: 0801
    equal: true
    

    As you can see, the values are identical.


    Small aside

    You mention you want to re-use the enum - it's perhaps not too well know, but you can still do that when an enum is defined within another message definition. You simply need to qualify the reference with the message name. For example:

    message AnotherValue {
      Value.Strategy strategy = 1;
    }
    

    That said, it's often going to be cleaner to simply extract the enum definition out.