In the last lesson, we explored how gRPC achieves its high performance by building on the foundations of HTTP/2, leveraging features like multiplexing and header compression. You learned that gRPC's efficiency stems from the powerful combination of Protobuf for data serialization and HTTP/2 for data transport.
Now, it's time to put that knowledge into practice. This lesson focuses on defining the service contract, which is the formal agreement between a client and a server. Using a .proto file, you will learn to define a service and its methods, specifying not only simple request-response interactions but also the four powerful streaming patterns that HTTP/2 makes possible. This contract is the language-agnostic blueprint for all our microservices.
The .proto File: A Blueprint for Communication
Before a client can talk to a server, they must agree on a few things: what services are available, what functions (or methods) each service provides, and what the data for requests and responses looks like. In gRPC, this contract is defined in a special file with a .proto extension.
This file is the single source of truth. As your experience with Django and REST APIs has shown, keeping client and server logic in sync can be challenging. The .proto file solves this by serving as a language-agnostic starting point from which both client and server code are generated.

A service definition in a .proto file has a few key components:
syntax: Specifies the Protocol Buffers version (we will useproto3).package: Provides a namespace for your service, preventing naming conflicts, much like Python packages.message: Defines the data structures for your requests and responses. We covered this in the lesson on Protobuf.service: A collection of remotely callable methods.rpc: A single remote procedure call method declaration within a service.
The Four gRPC Communication Patterns
Thanks to its foundation on HTTP/2 streams, gRPC is not limited to the simple request-response pattern common in REST. It supports four distinct communication modes, which you'll need to choose from when designing your services.

- Unary RPC (Simple RPC): The most straightforward pattern, identical to a standard function call. The client sends a single request, and the server returns a single response.
- Server Streaming RPC: The client sends a single request and receives a stream of multiple response messages back. This is useful for "subscribing" to data from the server, like getting live updates on an order's status.
- Client Streaming RPC: The client sends a stream of multiple request messages, and the server returns a single response after processing the entire stream. This is ideal for scenarios like uploading a large file in chunks or sending a series of IoT sensor readings.
- Bidirectional Streaming RPC: Both the client and server send independent streams of messages to each other over a single connection. This pattern is perfect for real-time, interactive communication, such as a chat service or a collaborative document editor.
Defining Services and Methods
Now let's see how to declare these patterns in a .proto file. The syntax is surprisingly simple; the stream keyword is the key.
Basics tutorial | Python - Defining the service
The official gRPC documentation provides a clear and authoritative guide to defining services.
Read the section titled "Defining the service". Focus on the four bullet points that describe and provide code examples for each of the RPC types: simple (unary), response-streaming, request-streaming, and bidirectionally-streaming. Notice how the stream keyword is the only syntactic change needed to define the different patterns.
As you saw in the documentation, the core syntax is:
rpc MethodName(RequestType) returns (ResponseType) {}
The streaming patterns are enabled by adding the stream keyword to either the request type, the response type, or both.
- Unary:
rpc GetFeature(Point) returns (Feature) {} - Server Streaming:
rpc ListFeatures(Rectangle) returns (stream Feature) {} - Client Streaming:
rpc RecordRoute(stream Point) returns (RouteSummary) {} - Bidirectional Streaming:
rpc RouteChat(stream RouteNote) returns (stream RouteNote) {}
A Practical Walkthrough
Let's watch a short video that demonstrates creating a .proto file from scratch and defining these methods. Since your background is in Python, this video uses a Python project context, which should feel familiar.
For this exercise, focus only on the creation and modification of the .proto file. We will cover code generation and implementation in the next lesson.
What is gRPC? Implement gRPC Simple and Bidirectional RPC in Python in just 30 minutes.
This video from Innovate Skills Software Institute walks through building a .proto file step-by-step.
First, watch how a unary RPC is defined, from creating the file to defining the messages and the service: Unary Definition. Next, see how this file is modified for bidirectional streaming by adding the stream keyword to both the request and response: Bidirectional Definition. Finally, observe how client-streaming and server-streaming methods are defined by selectively applying the stream keyword: Half-Duplex Definitions.
Designing a Production-Style Service
The examples so far are simple, but in a real-world system like the ones you've built, services are more complex. They often handle multiple related functionalities, import common data types, and expose a variety of unary and streaming methods.
Let's look at a more realistic example from an e-commerce backend. This will give you a feel for how to structure .proto files in a larger project.
How to Define Protocol Buffers (Protobuf) for gRPC Services
This article from the OneUptime blog provides excellent examples of production-style service definitions for a user management and e-commerce system.
Review the complete service examples provided in the article. Specifically, examine the UserService and OrderService definitions. As you read, try to identify: Which RPC methods are unary? (e.g., CreateUser, GetUser) Which are server-streaming? (e.g., WatchUsers, WatchOrder) Which are client-streaming? (e.g., BulkCreateUsers) How are common types like google.protobuf.Empty and google.protobuf.Timestamp imported and used?
These larger examples demonstrate important concepts for building scalable systems:
- Granularity: The
UserServicefocuses solely on user-related actions, while theOrderServicehandles orders. This reflects the principle of a Bounded Context we'll discuss later. - Method Naming: The RPC names are clear, action-oriented verbs (e.g.,
CreateUser,UpdateOrderStatus). - Reusability: Common message types (like
PaginationorMoneyin the full article) can be defined once and imported into multiple service files, promoting consistency.
Conclusion
In this lesson, you've learned how to create the single most important artifact in a gRPC-based system: the .proto service definition file. This file acts as an unambiguous, language-agnostic contract that dictates how your microservices will communicate.
Key Takeaways:
- The
.protofile defines theservice, itsrpcmethods, and themessagetypes used for requests and responses. - gRPC supports four communication patterns: Unary, Server Streaming, Client Streaming, and Bidirectional Streaming.
- The
streamkeyword is used in therpcdefinition to specify which side (or both) will be streaming data. - Well-designed
.protofiles are crucial for creating maintainable and scalable microservice architectures.
Preview of the Next Lesson
We now have the theoretical knowledge to define a service contract. The next logical step is to turn that contract into working code. In the next lesson, we will cover the end-to-end lifecycle of a gRPC call. You will use the grpc_tools compiler to generate Python client and server code from a .proto file and begin implementing the actual service logic.