Modern API Communication for High-Performance Systems
Week 12 • Day 1 • 3 HoursBy the end of this session, you will be able to:
Open your terminal and navigate to the project folder:
Install the required gRPC packages:
$ pip install grpcio grpcio-tools protobuf
grpcio - Core gRPC library for Pythongrpcio-tools - Protocol Buffer compiler for Pythonprotobuf - Protocol Buffers runtime libraryCompile the .proto files to generate Python code:
$ python -m grpc_tools.protoc \
--proto_path=protos \
--python_out=generated \
--grpc_python_out=generated \
protos/*.proto
generated/ folder:
user_service_pb2.py, user_service_pb2_grpc.py, etc.
Run the performance demo to verify everything is working:
$ python performance_demo.py
You should see output comparing gRPC vs REST performance with ~40% latency improvement.
Cause: gRPC packages not installed or installed in wrong Python environment.
Solution:
# Check which Python you're using
which python
# Install packages explicitly
pip3 install grpcio grpcio-tools protobuf
# Or with python3
python3 -m pip install grpcio grpcio-tools protobuf
Cause: Using protoc directly instead of through Python.
Solution: Always use the Python module:
# Wrong:
protoc --python_out=generated protos/*.proto
# Correct:
python -m grpc_tools.protoc --proto_path=protos --python_out=generated --grpc_python_out=generated protos/*.proto
Cause: Generated files not found or path issues.
Solutions:
ls generated/generated/__init__.py file exists# Create __init__.py if missing
touch generated/__init__.py
# Regenerate proto files
python -m grpc_tools.protoc --proto_path=protos --python_out=generated --grpc_python_out=generated protos/*.proto
gRPC (gRPC Remote Procedure Call) is a high-performance RPC framework developed by Google. It enables efficient communication between services using:
Binary serialization format that's 3x smaller and 10x faster than JSON
Multiplexed connections, header compression, and server push
Native support for bidirectional streaming
Code generation for 10+ programming languages
| Pattern | Client → Server | Use Cases | Example |
|---|---|---|---|
| Unary (Simple) | 1 request → 1 response | Standard API calls, CRUD | GetUser(id) |
| Server Streaming | 1 request → N responses | Data feeds, progress, A/B testing | StreamPredictions() |
| Client Streaming | N requests → 1 response | File upload, batch processing | BatchPredict() |
| Bidirectional | N requests ↔ N responses | Chat, real-time analytics | Chat() |
┌─────────────┐ REST ┌─────────────┐ gRPC ┌─────────────┐
│ Browser │ ────────────> │ API Gateway │ ────────────> │ Service │
└─────────────┘ JSON └─────────────┘ Protobuf └─────────────┘
Use REST for external clients, gRPC internally for performance.
$ python performance_demo.py
Open protos/user_service.proto and study the schema:
syntax = "proto3";
package user_service;
// User data structure
message User {
string user_id = 1; // Field number 1
string name = 2; // Field number 2
string email = 3; // Field number 3
int32 age = 4; // Integer field
repeated string interests = 5; // List of strings
UserPreferences preferences = 6; // Nested message
}
// Service definition
service UserService {
rpc GetUser(GetUserRequest) returns (GetUserResponse);
rpc CreateUser(CreateUserRequest) returns (CreateUserResponse);
rpc GetAllUsers(Empty) returns (stream User);
}
$ python services/user_service.py
🚀 gRPC User Service Started Listening on: [::]:50051
$ python client.py
The client runs through several scenarios:
| Test | What It Does | Expected Result |
|---|---|---|
| GetUser (exists) | Fetches user_001 | ✅ Returns Alice Johnson |
| GetUser (missing) | Fetches user_999 | ❌ NOT_FOUND error |
| CreateUser | Creates David Lee | ✅ User created |
| CreateUser (duplicate) | Creates David again | ❌ ALREADY_EXISTS error |
| GetAllUsers | Streams all users | ✅ Receives 4 users |
Cause: The server is not running.
Solution:
lsof -i :50051pkill -f user_service.pyCause: Firewall or network issue.
Solution:
# Check if server is listening
netstat -an | grep 50051
# Try localhost explicitly
# Edit client.py to use '127.0.0.1' instead of 'localhost'
Cause: Generated code is outdated or corrupted.
Solution: Regenerate the proto files:
# Delete old generated files
rm -f generated/*.py
# Regenerate
python -m grpc_tools.protoc --proto_path=protos --python_out=generated --grpc_python_out=generated protos/*.proto
# Restart the server
# Terminal 1 - Press Ctrl+C to stop User Service, then:
$ python services/ml_service.py
$ python ml_client.py
Method: Predict()
Pattern: 1 request → 1 response
Use Case: Single ML prediction
response = stub.Predict(request)
Method: PredictStream()
Pattern: 1 request → N responses
Use Case: A/B testing model variants
for response in stub.PredictStream(request):
print(response)
Method: PredictBatch()
Pattern: N requests → 1 response
Use Case: Batch prediction
def generate():
for data in batch:
yield request
response = stub.PredictBatch(generate())
Method: PredictRealtime()
Pattern: N requests ↔ N responses
Use Case: Real-time analytics
for response in stub.PredictRealtime(generate()):
process(response)
services/ml_service.py and study:yield is used for streaming responsesrequest_iterator handles streaming requestscontext.set_code()context.is_active() check for client disconnectionCause: Client disconnected or timeout.
Solution: Check for context.is_active() in server code and handle gracefully:
def PredictStream(self, request, context):
for i in range(100):
if not context.is_active():
print("Client disconnected")
break
yield response
Cause: Generator exhausted or not properly yielding.
Solution: Ensure your request generator keeps yielding:
def generate_requests():
for i in range(10):
yield request
time.sleep(0.1) # Don't exhaust immediately
# Call with the generator
for response in stub.PredictRealtime(generate_requests()):
print(response)
# Terminal 1 - User Service
$ python services/user_service.py
# Terminal 2 - ML Service
$ python services/ml_service.py
# Terminal 3 - API Gateway
$ python assignment/api_gateway/gateway.py
# Health check
$ curl http://localhost:8080/health
# Get all users (REST → gRPC streaming)
$ curl http://localhost:8080/api/users
# Get single user
$ curl http://localhost:8080/api/users/user_001
# List ML models
$ curl http://localhost:8080/api/models
# Make a prediction
$ curl -X POST http://localhost:8080/api/predict \
-H "Content-Type: application/json" \
-d '{"model_id":"fraud_detection","features":{"amount":500,"time":14}}'
# Create a new user
$ curl -X POST http://localhost:8080/api/users \
-H "Content-Type: application/json" \
-d '{"user_id":"user_100","name":"Test User","email":"[email protected]","age":25}'
assignment/api_gateway/gateway.pyNotice how the gateway:
Cause: Backend gRPC services not running.
Solution:
# Check what's running
lsof -i :50051
lsof -i :50052
lsof -i :8080
Cause: Cross-origin request blocked.
Solution: The gateway includes CORS headers. If you still have issues:
# Test with curl instead of browser
curl -v http://localhost:8080/api/users
# Or add to browser fetch:
fetch('http://localhost:8080/api/users', {
mode: 'cors'
})
# Find what's using the port
$ lsof -i :50051
# Kill the process
$ kill -9 <PID>
# Or kill all Python processes (careful!)
$ pkill -f "python.*service"
gRPC requires Python 3.7+. Check your version:
$ python --version
# Should be 3.7 or higher
# If using wrong version, try:
$ python3 services/user_service.py
This project uses insecure channels for learning. For production:
# Server with TLS
server_credentials = grpc.ssl_server_credentials([(key, cert)])
server.add_secure_port('[::]:50051', server_credentials)
# Client with TLS
credentials = grpc.ssl_channel_credentials(root_cert)
channel = grpc.secure_channel('localhost:50051', credentials)
When streaming large data, process items one at a time:
# DON'T: Collect all in memory
all_users = list(stub.GetAllUsers(request)) # Bad for large data
# DO: Process one at a time
for user in stub.GetAllUsers(request):
process(user) # Good - constant memory
import logging
logging.basicConfig(level=logging.DEBUG)
import os
os.environ['GRPC_VERBOSITY'] = 'DEBUG'
os.environ['GRPC_TRACE'] = 'all'
import grpc
channel = grpc.insecure_channel('localhost:50051')
try:
grpc.channel_ready_future(channel).result(timeout=5)
print("Connected!")
except grpc.FutureTimeoutError:
print("Connection failed")
Build a complete microservices system with: