gRPC Service Definition

A service definition tells gRPC what functions your server offers. It lives inside a proto file and describes every callable operation — what data goes in, and what data comes out. The code generator reads this definition and writes the networking layer for you.

What a Service Is

Think of a service as a menu at a restaurant. The menu lists every dish available (functions). Each dish description says what ingredients go in (request message) and what you receive on the plate (response message). You, the client, pick from the menu. The kitchen, the server, cooks and delivers.

┌──────────────────────────────────────────────────────┐
│               BookService (the menu)                 │
├──────────────────────────────────────────────────────┤
│  GetBook    → send BookId   → receive Book           │
│  ListBooks  → send Empty    → receive BookList       │
│  CreateBook → send Book     → receive Book           │
│  DeleteBook → send BookId   → receive Empty          │
└──────────────────────────────────────────────────────┘

Basic Service Syntax

A service block groups all related RPC methods. Each method uses the rpc keyword, followed by the method name, the request type in parentheses, the keyword returns, and the response type in parentheses.

syntax = "proto3";
package library;

import "google/protobuf/empty.proto";

// Data structures
message Book {
  int32  id     = 1;
  string title  = 2;
  string author = 3;
  double price  = 4;
}

message BookId {
  int32 id = 1;
}

message BookList {
  repeated Book books = 1;
}

// Service definition
service BookService {
  rpc GetBook    (BookId)                 returns (Book);
  rpc ListBooks  (google.protobuf.Empty)  returns (BookList);
  rpc CreateBook (Book)                   returns (Book);
  rpc DeleteBook (BookId)                 returns (google.protobuf.Empty);
}

Request and Response Are Always Messages

Every RPC method must take exactly one message as input and return exactly one message as output. You cannot pass a raw integer or string. This rule forces you to use a message wrapper, which makes it easy to add new fields later without changing the method signature.

Wrong — proto does not allow this:
  rpc GetBook (int32) returns (string);   ← compile error

Correct — always wrap primitives in a message:
  message GetBookRequest  { int32  book_id = 1; }
  message GetBookResponse { string title   = 1; }
  rpc GetBook (GetBookRequest) returns (GetBookResponse);

Benefit: adding a second filter to the request later does not break
existing clients:
  message GetBookRequest {
    int32  book_id  = 1;
    string language = 2;   ← added in v2, old clients send it as empty
  }

Naming Conventions for Services and Methods

The community follows a consistent naming style. Sticking to it makes your proto files readable by any gRPC developer.

┌──────────────┬───────────────────┬─────────────────────────────────┐
│ Element      │ Convention        │ Example                         │
├──────────────┼───────────────────┼─────────────────────────────────┤
│ Service name │ PascalCase + Svc  │ UserService, PaymentService     │
│ Method name  │ PascalCase verb   │ GetUser, CreateOrder, ListItems │
│ Request msg  │ MethodName+Request│ GetUserRequest, CreateOrderReq  │
│ Response msg │ MethodName+Response│ GetUserResponse, OrderResponse │
└──────────────┴───────────────────┴─────────────────────────────────┘

The URL gRPC Generates

Every gRPC method maps to an HTTP/2 POST path automatically. Knowing the format helps when you inspect network traffic or configure a proxy.

Format:
  /./

Example:
  Package:  library
  Service:  BookService
  Method:   GetBook

  gRPC URL: /library.BookService/GetBook

This path appears in HTTP/2 headers as the :path header value.

Request and Response Wrapper Pattern

A widely used practice is to create dedicated request and response messages for every method, even if they initially contain only one field. This avoids breaking changes when requirements grow.

Compact style (avoid for production):
  service OrderService {
    rpc GetOrder (OrderId) returns (Order);
  }
  ← Hard to extend OrderId or Order without affecting other methods

Wrapper style (recommended):
  service OrderService {
    rpc GetOrder (GetOrderRequest) returns (GetOrderResponse);
  }

  message GetOrderRequest {
    int32  order_id  = 1;
    // Add filters later without changing GetOrder's signature
    bool   include_items = 2;
  }

  message GetOrderResponse {
    Order order = 1;
    // Add pagination metadata later
    string next_page_token = 2;
  }

Multiple Services in One Proto File

A single proto file can define more than one service. Keep related services together. Split into separate files when services handle genuinely different domains.

// catalog.proto
syntax = "proto3";
package store;

service ProductService {
  rpc GetProduct    (GetProductRequest)    returns (Product);
  rpc SearchProducts(SearchRequest)        returns (SearchResponse);
}

service CategoryService {
  rpc GetCategory   (GetCategoryRequest)  returns (Category);
  rpc ListCategories(ListCategoriesRequest) returns (ListCategoriesResponse);
}

What the Code Generator Produces

Running protoc on your proto file generates two pieces of code for each service definition.

For BookService in Go:
┌──────────────────────────────────────────────────────────────┐
│  BookServiceClient (interface)                               │
│  • Use this on the CLIENT side                               │
│  • Call GetBook, ListBooks, etc.                             │
│  • Created by: NewBookServiceClient(conn)                    │
├──────────────────────────────────────────────────────────────┤
│  BookServiceServer (interface)                               │
│  • Implement this on the SERVER side                         │
│  • Write your business logic in GetBook, ListBooks, etc.     │
│  • Register with: RegisterBookServiceServer(grpcServer, impl)│
└──────────────────────────────────────────────────────────────┘

Unimplemented Server Base

Modern gRPC generators create an Unimplemented base struct or class. You embed it in your server implementation to avoid compile errors when you have not yet coded every method.

In Go (generated):
  type UnimplementedBookServiceServer struct{}
  func (UnimplementedBookServiceServer) GetBook(ctx, req) (*Book, error) {
    return nil, status.Errorf(codes.Unimplemented, "method GetBook not implemented")
  }

Your implementation:
  type myBookServer struct {
    pb.UnimplementedBookServiceServer   ← embed the generated base
  }

  func (s *myBookServer) GetBook(ctx, req) (*pb.Book, error) {
    // Your real logic here
  }
  // ListBooks, CreateBook, DeleteBook all return Unimplemented automatically

Summary

A service definition lists all callable methods using the rpc keyword inside a service block. Every method takes one message in and returns one message out. The wrapper pattern future-proofs each method against expanding requirements. The code generator produces a client interface and a server interface from a single proto file, so both sides always agree on the contract.

Leave a Comment

Your email address will not be published. Required fields are marked *