A high-performance, async gRPC service built in Rust for managing and retrieving exam results with both unary and server-streaming RPC capabilities.
ExamService demonstrates a production-ready implementation of gRPC communication patterns using the Tonic framework. The service provides exam result queries with support for both single requests and real-time streaming responses.
Server (server.rs)
ExamServiceImpl: Core service implementation with thread-safe exam data management- Uses
Arc<RwLock<HashMap>>for concurrent read access to exam records - Implements two RPC methods: unary and server-streaming
Client (client.rs)
ExamServiceClient: gRPC client for communicating with the server- Demonstrates both unary and streaming request patterns
- Async/await based response handling with Tokio
Protocol (exam.proto)
- Service definition with two RPC methods
- Message schemas for requests and responses
- Proto3 syntax for compatibility
✅ Unary RPC - Single request/response pattern for direct exam result queries ✅ Server-Streaming RPC - Server sends multiple streamed responses for long-running operations ✅ Thread-Safe Data - Arc ensures safe concurrent access ✅ Async/Await - Built on Tokio for non-blocking operations ✅ Error Handling - Proper gRPC status codes and error propagation ✅ Structured Logging - Request/response visibility for debugging
- Rust 1.70+
- Cargo
- protoc (Protocol Buffers compiler)
- Clone the repository and navigate to the project directory:
cd exam-service- Add required dependencies to
Cargo.toml:
[dependencies]
tonic = "0.10"
tokio = { version = "1", features = ["full"] }
tokio-stream = "0.1"
futures = "0.3"
prost = "0.12"
[build-dependencies]
tonic-build = "0.10"- Create a
build.rsfor proto compilation:
fn main() -> Result<(), Box<dyn std::error::Error>> {
tonic_build::compile_protos("proto/exam.proto")?;
Ok(())
}Start the server:
cargo run --bin serverExpected output:
🚀 ExamService listening on [::1]:50051
Run the client (in another terminal):
cargo run --bin clientExpected output:
Unary Response: GetExamResultResponse { student_name: "John Doe", subject: "Math 101", marks_obtained: 95, total_marks: 100, grade: "A+" }
Stream Response: Streamed - Grade: Processing result for 456_physics101 (1/3)
Stream Response: Streamed - Grade: Still working on 456_physics101 (2/3)
Stream Response: Streamed - Grade: Completed result for 456_physics101 (3/3)
Returns exam results for a specific student and exam.
Request:
message GetExamResultRequest {
string student_id = 1;
string exam_id = 2;
}Response:
message GetExamResultResponse {
string student_name = 1;
string subject = 2;
int32 marks_obtained = 3;
int32 total_marks = 4;
string grade = 5;
}Streams exam result processing updates in real-time.
Request: Same as GetExamResultRequest
Response: Multiple GetExamResultResponse messages streamed sequentially
The service comes with sample exam data:
| Student ID | Exam ID | Student Name | Subject | Marks | Grade |
|---|---|---|---|---|---|
| 123 | math101 | John Doe | Math 101 | 95/100 | A+ |
| 456 | phy101 | Jane Smith | Physics 101 | 88/100 | A |
Query format: {student_id}_{exam_id}
exam-service/
├── src/
│ ├── server.rs # gRPC server implementation
│ └── client.rs # gRPC client implementation
├── proto/
│ └── exam.proto # Protocol buffer definitions
├── Cargo.toml # Project dependencies
└── README.md # This file
- Tonic - Async gRPC framework for Rust
- Tokio - Async runtime for concurrent operations
- Protocol Buffers - Efficient serialization format
- tokio-stream - Stream utilities for Tokio
This project demonstrates:
- gRPC service design patterns (unary and server-streaming)
- Rust async programming with Tokio
- Protocol Buffer usage and code generation
- Thread-safe data structures with Arc
- Error handling in distributed systems
Implement a method where the client sends multiple requests and receives a single aggregated response.
Enable simultaneous request and response streaming for interactive applications.
Replace in-memory HashMap with a persistent database (PostgreSQL, MongoDB, etc.).
Add TLS/mTLS support and token-based authorization.
Integrate tracing and metrics collection for production monitoring.
- Connection Reuse: gRPC uses HTTP/2 multiplexing for efficient connection handling
- Concurrency: Tokio's work-stealing scheduler handles thousands of concurrent requests
- Memory: Arc minimizes locking overhead for read-heavy workloads
- Serialization: Protocol Buffers provide compact, efficient message encoding
Error: "failed to resolve: use of undeclared type ExamServer"
- Ensure you're importing
ExamServiceServer as ExamServerfrom the generated module
Error: "unresolved import futures"
- Add
futures = "0.3"to yourCargo.tomldependencies
Connection refused on client startup
- Verify the server is running on
[::1]:50051 - Check firewall settings for IPv6 loopback access
MIT
A practical deep-dive into gRPC communication patterns.