Skip to content

Defining Services — gRPC Service Definitions with Protocol Buffers

DodaTech Updated 2026-06-28 2 min read

In this tutorial, you will learn about Defining Services. We cover key concepts, practical examples, and best practices to help you master this topic.

gRPC services are defined in .proto files using the service keyword, specifying RPC methods with their request and response message types and streaming direction.

Service Definition Syntax

syntax = "proto3";

package dodatech.threat.v1;

service ThreatService {
  // Unary
  rpc GetThreat(GetThreatRequest) returns (Threat);
  rpc CreateThreat(CreateThreatRequest) returns (Threat);
  
  // Server streaming
  rpc ListThreats(ListThreatsRequest) returns (stream Threat);
  
  // Client streaming
  rpc SubmitThreats(stream SubmitThreatRequest) returns (SubmitSummary);
  
  // Bidirectional streaming
  rpc MonitorThreats(stream MonitorRequest) returns (stream ThreatAlert);
}

Implementing a Service

class ThreatServiceServicer(threat_pb2_grpc.ThreatServiceServicer):
    def GetThreat(self, request, context):
        threat = db.get_threat(request.id)
        if not threat:
            context.abort(grpc.StatusCode.NOT_FOUND, "Threat not found")
        return threat_pb2.Threat(
            id=threat.id,
            name=threat.name,
            severity=threat.severity,
        )
    
    def ListThreats(self, request, context):
        threats = db.list_threats(severity=request.severity, limit=request.limit)
        for threat in threats:
            yield threat_pb2.Threat(
                id=threat.id,
                name=threat.name,
                severity=threat.severity,
            )

Common Mistakes

1. Forgetting the stream Keyword

Omitting stream creates a unary RPC instead of streaming. The method signature changes completely.

2. Overloading One Service

A service with 50+ methods is hard to maintain. Split into focused services: DeviceService, ThreatService, UserService.

3. Not Documenting RPC Methods

Use comments in proto files. They appear in generated code and documentation.

4. Making Methods Too Coarse

One RPC that does everything (GetOrCreateAndUpdate) is an anti-pattern. Each RPC should do one thing.

5. Ignoring Deadline Propagation

gRPC clients can set deadlines. Services should check context.is_active() to respect timeouts.

Practice Questions

  1. How do you define a server streaming RPC?
  2. What is the difference between rpc Foo(Bar) returns (Baz) and returns (stream Baz)?
  3. How do you organize services in a large project?
  4. What happens when an RPC returns an error?
  5. Can a service have no methods?

Answers:

  1. Add stream before the return type: rpc ListThreats(Request) returns (stream Threat).
  2. Without stream: unary RPC (single response). With stream: server sends multiple responses over time.
  3. Split by domain (DeviceService, ThreatService) and split large services when they exceed 10-15 methods.
  4. The server calls context.abort(status_code, message) or returns an error. The client receives a gRPC error with the status code.
  5. Yes — but it's not useful. An empty service definition still generates code but has no RPC methods.

Mini Project

Define gRPC services for DodaTech's complete API. Create DeviceService (CRUD + streaming updates), ThreatService (CRUD + real-time alerts), ScanService (manage scans + progress streaming), and UserService (auth + profile management).

What's Next

Topic Description
Code Gen Generating client/server code
Unary RPC Request-response RPC pattern
⬅ proto3 Data Types
➡ Code Generation

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro