distributedsystem/microservice/grpc
Interface Definition Language (IDL)
[!video]- Protocol Buffers Crash Course
Protocol Buffers Documentation
🌟 Practical Protobuf - From Basic to Best Practices
🌟 How Protobuf Works—The Art of Data Encoding
Protocol buffers allow you to serialize structured data to be transmitted over a wire.
Info
Protocol buffers are Google’s language-neutral, platform-neutral, extensible mechanism for serializing structured data – think XML, but smaller, faster, and simpler. You define how you want your data to be structured once, then you can use special generated source code to easily write and read your structured data to and from a variety of data streams and using a variety of languages.
Protocol Buffers Documentation
Syntax
syntax = "proto3"
message CreateOrderRequest {
int64 user_id = 1;
repeated Item Items = 2;
float amount = 3;
}Fields
singular—> message can have at most one of the fields; this is default behaviour.repeated—> May contain multiple values, including zero.
Field Types
👉 Language Guide (proto 3) | Protocol Buffers Documentation
- For strings, the default value is the empty string.
- For bytes, the default value is empty bytes.
- For bools, the default value is false.
- For numeric types, the default value is zero.
- For message fields, the field is not set. Its exact value is language-dependent. See the generated code guide for details.
- For enums, the default value is the first defined enum value, which must be 0. See Enum Default Value.
- The default value for repeated fields is empty (generally an empty list in the appropriate language).
- The default value for map fields is empty (generally an empty map in the appropriate language).
Field Names
Field Names are required and it should be lowercase and if it contains two or more words it should bet separated underscore(user_address_city).
Field Numbers and Reservations
Protobuf messages are serialized into a binary format that makes them compact and efficient. Each field in a message has a unique identifier (field number) that helps in serialization and deserialization.
When a message is converted to binary, it does **not store field names**, only the **field numbers and values**.
Why Field Numbers Shouldn’t Change ?
Since field numbers are used for identification in binary messages, changing them can break compatibility.
- Suppose you had
customer_id = 3;in an older message definition.- You later change
3touser_idwith a different type.- If an old client sends a message with
customer_id, but the server now expectsuser_id, it will misinterpret the data.This is why changing or reusing field numbers should be avoided.
Safely Remove Fields using reserved
If you need to remove a field, you should reserve its number and name. This prevents future developers from accidentally reusing them.
reserved field names cannot be reused or reassigned.
/* initial Definition */
message CreateOrderRequest {
int64 customer_id = 3; // This field will be removed in version 2
repeated Item items = 4;
float amount = 5;
}/* Updated Definition */
message CreateOrderRequest {
reserved 3; // Prevents future reuse of field number 3
reserved "customer_id"; // Prevents reuse of field name "customer_id"
int64 user_id = 6; // Introduced a new field instead of customer_id
repeated Item items = 4;
float amount = 5;
}Performance
Use numbers 1–15 to minimize message size.
- Field numbers 1–15 → Encoded in 1 byte (fast & efficient).
- Field numbers 16–2047 → Encoded in 2 bytes.
- Higher numbers require more bytes, increasing message size.
Using oneof for Exclusive Fields
The oneof feature in Protocol Buffers allows defining multiple fields where only one can be set at a time. This ensures mutual exclusivity without requiring extra logic in the implementation.
Example: Enforcing Exclusive Payment Methods
message CreatePaymentRequest {
oneof payment_method {
CreditCard credit_card = 1;
PromoCode promo_code = 2;
}
}In this example, a request can contain either credit_card or promo_code, but not both.
Removing Fields from oneof (Backward Incompatibility)
- If
promo_codeis removed fromoneofand the server is upgraded, any older clients that sendpromo_codewill lose that data. - This is considered a backward-incompatible change.
- Clients using the previous version will send
promo_code, but the server will discard the unknown field.
Updated Message (v2 - Breaking Change)
message CreatePaymentRequest {
oneof payment_method {
CreditCard credit_card = 1;
}
}📌 Impact: If a v1 client sends promo_code, the v2 server will not recognize it, leading to data loss.
Adding Fields to oneof (Forward Incompatibility)
- If a new field is added to
oneof, older clients will be unaware of this field. - Clients using an older version may not handle new fields properly, leading to forward-incompatible issues.
Updated Message (v3 - Breaking Change)
message CreatePaymentRequest {
oneof payment_method {
CreditCard credit_card = 1;
BankTransfer bank_transfer = 3;
}
}📌 Impact: Older clients do not recognize bank_transfer and may not process requests correctly.
Moving Fields In/Out of oneof (Data Loss Risk)
Moving a field into or out of oneof can cause data loss, as previous messages could have had both fields set.
Before Moving (Non-exclusive promo_code)
message CreatePaymentRequest {
oneof payment_method {
CreditCard credit_card = 1;
}
PromoCode promo_code = 2;
}After Moving (Exclusive promo_code)
message CreatePaymentRequest {
oneof payment_method {
CreditCard credit_card = 1;
PromoCode promo_code = 2;
}
}📌 Impact: If a v1 client sends both credit_card and promo_code, only one will be preserved in v2, causing unexpected data loss.
Best Practices for Compatibility
- Avoid removing fields from
oneof– If removal is necessary, consider introducing a new version of the message. - Avoid moving fields in or out of
oneof– This can cause loss of previously valid fields. - Use semantic versioning (https://semver.org/) – Breaking changes should be indicated with a major version update.
- Communicate changes to API consumers – Ensure clients are aware of updates to prevent compatibility issues.
Predefined Protobuf Types
It is a standard Protobuf wrapper library(google.protobuf.wrappers.proto) that provides wrapper messages for primitive types (e.g., int, string, float). These wrappers allow you to handle optional values without default values.
- Normally, primitive fields like
stringorintcannot be null in Protobuf. - But with wrapper types like
google.protobuf.StringValue, fields can be nullable.
Why Use google.protobuf.StringValue?
In your service definition:
rpc getOrder(google.protobuf.StringValue) returns (Order);- Instead of using
string, you are usinggoogle.protobuf.StringValue. - This means the request parameter can be empty (
null), which is not possible with a regularstring.
Equivalent Message Definition Without Wrapper
If you did not use google.protobuf.StringValue, you’d need to define a custom message like this:
message GetOrderRequest {
string id = 1;
}And update the RPC method:
rpc getOrder(GetOrderRequest) returns (Order);