Skip to content

Migrating from Protobuf

Teams adopt Protobuf for compact internal RPC and generated stubs. Twilic offers a different trade-off: schema-less start, self-describing payloads, and stronger wins on lists and bulk data — without abandoning schema when a hot path stabilizes.

This article covers when migration makes sense, side-by-side rollout patterns, and what you give up.

When migration is worth it

Your situationTwilic fit
Bulk/list responses dominate bandwidthStrong — Batch profile removes repeated field names
.proto files slow iteration on internal APIsStrong — Dynamic profile, add Bound later
gRPC is org-mandatory with generated stubs everywhereWeak — stay on Protobuf
Single-record hot paths with fixed schemaMixed — Bound profile matches Protobuf density
External partner contracts on ProtobufDo not migrate — keep Protobuf at the boundary

See also Internal APIs Without Protobuf Overhead for the architecture case.

What changes

AspectProtobufTwilic
SchemaRequired .proto + codegenOptional — Dynamic default
WireField numbers, no field namesSelf-describing or schema-bound
ListsRepeated fields, key overhead per elementBatch interning — keys sent once
RPC transportgRPC / HTTP/2 framingHTTP + application/vnd.twilic or custom
EvolutionField numbers, reserved tagsDynamic maps tolerate new keys; Bound uses schema versioning
Toolingprotoc, buf, grpcurlTwilic CLI, Playground

Twilic is not a drop-in gRPC replacement. Plan transport separately — typically HTTP content negotiation or an internal binary channel.

Migration phases

Phase 0: Measure

Pick one read-heavy internal endpoint returning arrays (user list, order history, inventory page). Capture:

  • JSON size (if dual-serving today)
  • Protobuf serialized size
  • Twilic Dynamic + Batch size on the same payload

Use Playground or pnpm example:api-response in examples.

Phase 1: Dual content type

Add Twilic alongside Protobuf on internal routes:

http
GET /internal/users HTTP/1.1
Accept: application/vnd.twilic, application/x-protobuf

Server encodes based on Accept. Keep Protobuf as default until clients migrate.

With Hono:

ts
import { twilicResponse } from "@twilic/hono";
import { encodeBatch } from "@twilic/core/advanced";

app.get("/internal/users", async (c) => {
const users = await fetchUsers();
if (c.req.header("accept")?.includes("application/vnd.twilic")) {
return twilicResponse(c, users); // auto batch when appropriate
}
return c.protobuf(UserList.encode({ users }).finish());
});

Phase 2: Client migration

Migrate internal consumers one service at a time:

  1. Add @twilic/fetch or language SDK
  2. Send Accept: application/vnd.twilic
  3. Decode with decode() or decodeBatch()
  4. Retire Protobuf client for that endpoint

No flag day — unmigrated clients keep Protobuf.

Phase 3: Hot path Bound profile

When 2–3 message types stabilize (payments, fraud checks), promote to Bound:

ts
import { encodeWithSchema } from "@twilic/core/advanced";
const bytes = encodeWithSchema(paymentSchema, event);

Density approaches Protobuf single-record size without .proto codegen — schema lives in application code or a shared JSON schema file.

Phase 4: Retire Protobuf on internal routes

Once all internal clients on an endpoint support Twilic:

  • Remove Protobuf encoder branch
  • Keep Protobuf on north-south / partner APIs

Mapping Protobuf concepts

ProtobufTwilic equivalent
message User { ... }Dynamic map or Bound Schema
repeated User usersencodeBatch(users)
oneofDynamic map with varying keys (no native oneof)
map<K,V>Twilic map value
optionalOmit key or explicit null
Field numbersBound schema field numbers
bytesTwilic binary
EnumBound enum with declared values
gRPC streamingWebSocket + Stateful profile

Protobuf oneof and deeply nested optional graphs map awkwardly to Dynamic mode — prefer Bound or flatten for hot paths.

gRPC streaming replacement

For server-streaming RPC:

Protobuf patternTwilic pattern
Unary RPCHTTP + Dynamic/Batch
Server streaming (full snapshots)Batch chunks over HTTP/2
Server streaming (incremental)WebSocket + encodePatch()

See Real-Time Dashboards and Streaming.

Schema governance

Protobuf teams often use buf breaking-change detection. Twilic equivalents:

Governance needApproach
Breaking field removalBound schema semver + CI decode tests
New optional fieldsDynamic mode — decoders ignore unknown keys
Cross-team contractShared schema JSON in monorepo or schema registry
Wire compatibility testsConformance fixtures

Security during migration

  • Treat Twilic bytes with same trust model as Protobuf — authenticated internal network only
  • Apply payload size limits on decode
  • Do not expose Twilic on public unauthenticated routes during pilot

Rollback plan

Keep Protobuf encoder path until:

  • All clients on the endpoint migrated
  • One full release cycle with Twilic-only traffic monitored
  • Rollback = revert Accept header handling (Protobuf path still deployed)

When to keep Protobuf

  • Platform mandate for gRPC everywhere
  • External API contracts with third parties
  • Heavy reliance on protoc plugins (validation, auth metadata)
  • Embedded targets with existing nanopb/protoc-gen

Next steps

Released under the CC-BY-4.0 License.