7 Writing Your First `.proto` File
7. Writing Your First .proto File: A Complete Guide for Beginners
When you start exploring the world of distributed systems, microservice integration, or cross-language communication, sooner or later you’ll run into Protocol Buffers (or Protobuf). Protobuf is a data serialization format developed by Google—which, besides being lightweight and blazingly fast, is also easy to define through a .proto file. Today, I’ll guide you through writing your first .proto file step by step, complete with code examples, usage simulations, and a handful of tips from real-world experience in the industry.
Let’s get our journey started right away!
Why Protobuf?
Before diving into the code, it’s important to understand why .proto is so popular among engineers:
- Language-agnostic: A single
.protofile can generate code in many languages (Go, Python, Java, C++, etc.) - Small message size & fast parsing: Far more economical on bandwidth and CPU compared to JSON/XML.
- Backward and Forward compatibility: Add or change fields without breaking older applications.
Anatomy of a .proto File
A .proto file is a blueprint—it not only defines the shape of your data, but also the services (operations/endpoints) that are available. Here are its basic components:
| Component | Function |
|---|---|
syntax | The Protobuf syntax version being used (required, usually “proto3”) |
package | A namespace to avoid name conflicts |
message | A data structure, similar to a “class” |
enum | An enumeration data type |
service | Used to describe RPC services (optional, in gRPC) |
1. Writing Your First .proto File
Let’s say we want to build a simple book management service. We’ll define a “Book” message, a “GetBookRequest” request message, and a “BookService” service.
File: book.proto
1syntax = "proto3";
2
3package library;
4
5// Enum for the type of book
6enum Genre {
7 GENRE_UNSPECIFIED = 0;
8 FICTION = 1;
9 NON_FICTION = 2;
10 SCIENCE = 3;
11 HISTORY = 4;
12}
13
14// "Book" data structure
15message Book {
16 int32 id = 1;
17 string title = 2;
18 string author = 3;
19 Genre genre = 4;
20 repeated string tags = 5;
21}
22
23// Request to fetch book data by ID
24message GetBookRequest {
25 int32 id = 1;
26}
27
28// Response for a single book
29message GetBookResponse {
30 Book book = 1;
31}
32
33// Service (gRPC)
34service BookService {
35 rpc GetBook(GetBookRequest) returns (GetBookResponse) {}
36}Explanation of Each Part
- Enum:
Genrewith a default value ofGENRE_UNSPECIFIED = 0(the first enum field must be 0 in proto3). - Book Message: Each field is assigned a number (important: once published, field numbers MUST NOT BE CHANGED in order to preserve compatibility).
repeated string tags: meanstagsis an array/list of strings.
- Service Block: Defines an RPC named
GetBookthat accepts aGetBookRequestand returns aGetBookResponse.
2. The BookService Data Flow
Let’s illustrate how the data flows when a client requests book data:
sequenceDiagram Client->>gRPC Server: GetBook(id=21) gRPC Server->>Database: Query book.id=21 Database-->>gRPC Server: Book record gRPC Server-->>Client: GetBookResponse(Book)
3. Simulating the Use of a .proto File
Once the .proto file is finished, the next step is to generate the code. As an example, here’s a simulation in Python (using grpcio-tools):
1python -m grpc_tools.protoc -I. --python_out=. --grpc_python_out=. book.protoThis will produce the files book_pb2.py and book_pb2_grpc.py.
Sketch of a Simple Server (Python)
1import grpc
2from concurrent import futures
3import book_pb2, book_pb2_grpc
4
5FAKE_BOOK_DB = {
6 21: book_pb2.Book(id=21, title='Atomic Habits', author='James Clear', genre=book_pb2.FICTION, tags=['habit', 'self-development'])
7}
8
9class BookServiceServicer(book_pb2_grpc.BookServiceServicer):
10 def GetBook(self, request, context):
11 book = FAKE_BOOK_DB.get(request.id, None)
12 if book:
13 return book_pb2.GetBookResponse(book=book)
14 context.set_code(grpc.StatusCode.NOT_FOUND)
15 context.set_details('Book not found')
16 return book_pb2.GetBookResponse()
17
18# Run the server
19if __name__ == '__main__':
20 server = grpc.server(futures.ThreadPoolExecutor(max_workers=5))
21 book_pb2_grpc.add_BookServiceServicer_to_server(BookServiceServicer(), server)
22 server.add_insecure_port('[::]:50051')
23 server.start()
24 print('Server running...')
25 server.wait_for_termination()4. Structure Comparison Table: Protobuf vs JSON
| Protobuf (binary) | JSON (textual) | |
|---|---|---|
| Size | Very small | Relatively large |
| Speed | Very fast parsing | Slow parsing |
| Evolution | Supports version compatibility | Difficult, prone to typos/missing fields |
| Compatibility | Works across languages | Possible, but less optimal |
| Typing | Stronger/stricter | Loose |
5. Valuable Tips When Writing .proto
- Use field numbers < 15 for the most frequently accessed fields. (Field numbers 1–15 are more storage-efficient)
- Always set the default value of the first enum entry to 0.
- Never change the meaning of an existing field (its number, name, or type) after it has been published.
- Use
repeatedonly if you’re sure the field is a list. - Namespace your definitions via
package, especially across teams or projects.
6. Conclusion
Writing a .proto file is the foundation of communication in large-scale modern APIs. Beyond creating an explicit contract, Protobuf enables efficiency, portability, and seamless application evolution.
As an engineer, understanding the structure of a .proto file along with its good practices will take you to the next level—especially when your team needs to scale or do polyglot programming.
Ready to create your very own first .proto file? Go ahead and experiment, and don’t hesitate to share your questions in the comments section!
Further reading:
Happy coding, and may your first .proto file be the start of high-standard collaboration in your engineering team! 🚀