Skip to main content

MEMO: Tonic 0.14 Upgrade Path

Context

During dependency consolidation (#76), we attempted to upgrade the Rust client gRPC stack from tonic 0.11/prost 0.12 to tonic 0.14/prost 0.14. The upgrade was deferred due to breaking API changes that require significant build system restructuring.

Current State

Stable Stack (as of 2024-01-24):

  • tonic = "0.11"
  • prost = "0.12"
  • tonic-build = "0.11"
  • Location: clients/rust/prism-client/Cargo.toml

Build Configuration:

// clients/rust/prism-client/build.rs
tonic_build::configure()
.build_server(false)
.compile(&[...protos...], &[...includes...])?;

Breaking Changes in Tonic 0.14

1. API Redesign

Old API (0.11):

tonic_build::configure()
.build_server(false)
.compile(protos, includes)?;

New API (0.14):

  • configure() method removed
  • Replaced with CodeGenBuilder + prost_build::Config integration
  • No direct compile_protos() function

2. Build System Architecture

Tonic 0.14 split responsibilities:

Code Generation: tonic_build::CodeGenBuilder

  • Generates gRPC service client/server code
  • Does NOT implement prost_build::ServiceGenerator trait
  • Only provides token generation methods

Proto Compilation: prost_build::Config

  • Handles .proto file parsing and compilation
  • Requires custom ServiceGenerator implementation
  • Must manually wire together with CodeGenBuilder

3. Manual Service Definition Required

The manual module now requires explicit service construction:

use tonic_build::manual::{Builder, Service, Method};

let service = Service::builder()
.name("MyService")
.package("my.package")
.method(Method::builder()
.name("my_method")
.input_type("MyRequest")
.output_type("MyResponse")
.build())
.build();

Builder::new()
.build_client(true)
.build_server(false)
.compile(&[service]);

Problem: This requires parsing .proto files manually or duplicating service definitions.

Migration Path

Phase 1: Research & Design (1-2 days)

  1. Study tonic 0.14 examples:

    • Review official tonic repository examples
    • Check tonic-build documentation for migration guides
    • Examine other projects that completed this migration
  2. Design options:

    • Option A: Use prost_build::Config with custom ServiceGenerator
    • Option B: Use tonic_build::manual::Builder with parsed proto metadata
    • Option C: Wait for tonic 0.15+ with simplified API
  3. Prototype approach:

    • Create test branch with single .proto file
    • Verify generated code matches current output
    • Validate client functionality

Phase 2: Implementation (2-3 days)

  1. Update Cargo.toml:

    [dependencies]
    tonic = "0.14"
    prost = "0.14"

    [build-dependencies]
    tonic-build = "0.14"
    prost-build = "0.14" # Now required
  2. Rewrite build.rs:

    Recommended Approach (Option A):

    fn main() -> Result<(), Box<dyn std::error::Error>> {
    let mut config = prost_build::Config::new();

    // Create custom service generator wrapping CodeGenBuilder
    struct TonicServiceGen {
    builder: tonic_build::CodeGenBuilder,
    }

    impl prost_build::ServiceGenerator for TonicServiceGen {
    fn generate(&mut self, service: prost_build::Service, buf: &mut String) {
    // Convert prost Service to tonic Service
    // Generate client code using builder
    let code = self.builder.generate_client(&service, "");
    buf.push_str(&code.to_string());
    }
    }

    config.service_generator(Box::new(TonicServiceGen {
    builder: tonic_build::CodeGenBuilder::new(),
    }));

    config.compile_protos(&[...], &[...])?;
    Ok(())
    }
  3. Handle generated code differences:

    • Generated struct names may change
    • Import paths may differ
    • Client initialization API may vary
  4. Update client code (clients/rust/prism-client/src/):

    • Review all tonic:: imports
    • Update service client instantiation
    • Verify request/response handling

Phase 3: Testing & Validation (1-2 days)

  1. Build verification:

    cargo clean
    cargo build
    cargo test
  2. Integration testing:

    • Run all Rust client examples
    • Verify gRPC communication with proxy
    • Test error handling paths
  3. Performance validation:

    • Compare request latency
    • Check memory usage
    • Verify no protocol regressions

Phase 4: Documentation (1 day)

  1. Update client docs:

    • Note API changes in RFC-040
    • Update code examples
    • Document new build requirements
  2. Update changelog:

    • Describe breaking changes
    • List migration steps for users

Estimated Effort

Total: 5-8 days

  • Research: 1-2 days
  • Implementation: 2-3 days
  • Testing: 1-2 days
  • Documentation: 1 day

Risk: Medium

  • Build system changes are isolated to build.rs
  • Client code changes expected to be minimal
  • Rollback is straightforward (revert Cargo.toml)

Alternative: Wait for Tonic 0.15+

Consideration: The tonic maintainers may simplify the API in future versions based on community feedback about 0.14's complexity.

Trade-offs:

  • Pro: Potentially easier migration path
  • Pro: More examples and documentation available
  • Con: Delayed security/performance updates
  • Con: Growing version drift with ecosystem

Recommendation: Monitor tonic releases quarterly. Upgrade when either:

  1. Tonic 0.15+ restores simpler API
  2. Critical security update requires tonic 0.14+
  3. New tonic 0.14 feature needed for Prism functionality

Dependencies Successfully Updated

These updates were completed in PR #76:

  • reqwest: 0.11.27 → 0.12.24
  • thiserror: 1.0.69 → 2.0.17
  • rand: 0.8.5 → 0.9.2 (dev-dependency)
  • All Go modules (grpc, protobuf)
  • All GitHub Actions
  • All npm packages

References

Next Steps

  1. Monitor: Watch tonic repository for 0.15 release or API improvements
  2. Document: Link this memo from RFC-040 (client SDK architecture)
  3. Schedule: Add "Tonic upgrade evaluation" to Q1 2025 technical debt review
  4. Alert: Create dependabot rule to notify on tonic 0.15+ releases