Polyglot gRPC Calculator - Multi-Language Expression Evaluator
AUG 2025
Overview
The Polyglot gRPC Calculator showcases the power of microservices architecture by implementing a distributed calculator system across six different programming languages. Each arithmetic operation is handled by an independent gRPC service, demonstrating cross-language interoperability and modern distributed system design patterns.
This project exemplifies how language barriers can be effectively bridged using gRPC and Protocol Buffers, making it invaluable for engineers interested in polyglot microservices, service meshes, and advanced API design.
Architecture
The system implements a hub-and-spoke architecture where a Node.js client orchestrates communication with specialized backend services:
Component | Language | Framework/Library | Port | Purpose |
---|---|---|---|---|
Add Server | Python | grpcio , grpcio-tools | 50051 | Addition |
Subtract Server | Java | gRPC Java , Maven | 50052 | Subtraction |
Multiply Server | C++ | gRPC C++ , CMake | 50053 | Multiplication |
Divide Server | Rust | Tonic , Tokio | 50054 | Division |
Modulus Server | Go | gRPC Go | 50055 | Modulus |
Exponentiation Server | Elixir | gRPC Elixir | 50056 | Exponentiation |
AST Client | Node.js | @grpc/grpc-js | 3000 | Expression parsing & evaluation |
All services use Protocol Buffers (calculator.proto
) for message schema, ensuring consistent data serialization across languages.
Key Features
AST-Based Expression Parsing
The Node.js client implements sophisticated Abstract Syntax Tree (AST) parsing that:
- Respects operator precedence and parentheses
- Handles decimal numbers and unary operations
- Supports complex mathematical expressions like
(5 + 3) * 2^2 - 10 % 3
Parallel Evaluation
Independent operations are dispatched in parallel to backend services using gRPC, optimizing performance for complex expressions with multiple operations.
Production-Ready Patterns
- Container Orchestration: Fully dockerized with Docker Compose
- Service Discovery: Uses container hostnames for communication
- Robust Error Handling: Comprehensive error management across all services
- Automated Testing: Integration tests validate complex expressions
Implementation Details
Protocol Buffers Schema
All services use a unified calculator.proto
schema ensuring consistent data serialization:
syntax = "proto3";
package calculator;
option go_package = "./calculator";
message TwoNumbers {
double a = 1;
double b = 2;
}
message Number {
double result = 1;
}
service Calculator {
rpc Add (TwoNumbers) returns (Number);
rpc Subtract (TwoNumbers) returns (Number);
rpc Multiply (TwoNumbers) returns (Number);
rpc Divide (TwoNumbers) returns (Number);
rpc Modulus (TwoNumbers) returns (Number);
rpc Exponentiate (TwoNumbers) returns (Number);
}
Service Implementation Flow
The development workflow follows a consistent pattern across all six programming languages:
1. Proto Stub Generation:
Each language generates source code from calculator.proto
using language-specific Protocol Buffer and gRPC plugins.
2. Service Implementation:
In each language, a server class/module is defined, which implements the Calculator service interface. The developer provides concrete implementations for methods like Add
, Subtract
, Multiply
, etc.—these methods handle the respective arithmetic operations.
- Python: Implements a
CalculatorServicer
class, overrides methods likeAdd(self, request, context)
, and registers it with the gRPC server. - Java: Extends
CalculatorImplBase
, implementspublic void add(TwoNumbers req, StreamObserver<Number> resp)
, and starts a gRPC server. - C++: Inherits from
Calculator::Service
, overrides methods likeStatus Add(ServerContext*, const TwoNumbers*, Number*)
, and creates a server with these handlers. - Rust: Implements the
Calculator
trait for a custom struct (e.g.,DivisionServer
), providesasync fn divide(...)
, then starts the server withServer::builder().add_service(...)
. - Go: Implements the
CalculatorServer
interface, defines functions likefunc (s server) Add(ctx context.Context, in TwoNumbers) (*Number, error)
, and runs the server. - Elixir: Defines a module implementing the
Calculator.PowServer
behaviour, provides functions likedef exponentiate(%Calculator.TwoNumbers{a: a, b: b}, _stream)
, and boots the server.
3. Build and Run:
Each service uses language-specific build tools—pip
, mvn
, cmake
, cargo
, go build
, mix compile
—to compile the source and start the server listening on its designated port.
General Pattern
Step 1: Generate language-specific gRPC service stubs from calculator.proto
.
Step 2: Implement the service interface/class, providing logic for each method.
Step 3: Build the project with the appropriate build tool.
Step 4: Start the server so it listens for gRPC requests.
This pattern is consistent across all services: proto → code implementation of methods → build → start server.
C++ Service Implementation Walkthrough
To illustrate the development process in detail, here's a complete walkthrough of implementing the C++ Multiplication Service:
1. Proto Stub Generation
The file calculator.proto
defines the gRPC service and messages. Use the Protocol Buffer compiler (protoc
) with the gRPC C++ plugin to generate C++ source files:
protoc -I. --cpp_out=. --grpc_out=. --plugin=protoc-gen-grpc=/usr/bin/grpc_cpp_plugin calculator.proto
This produces:
calculator.pb.h
/calculator.pb.cc
(protobuf messages)calculator.grpc.pb.h
/calculator.grpc.pb.cc
(gRPC service interface)
2. Implementing the Service Methods
Create a class that inherits from Calculator::Service
(defined in generated code). Override methods like Add
, Subtract
, Multiply
, etc., with your actual logic:
class MultiplyServer final : public Calculator::Service {
public:
grpc::Status Multiply(grpc::ServerContext* context, const TwoNumbers* request, Number* response) override {
response->set_result(request->a() * request->b());
return grpc::Status::OK;
}
// Other methods can be omitted or left unimplemented for this service
};
3. Building the Server
Use CMake for build configuration (see CMakeLists.txt
):
- Link with
gRPC::grpc++
,protobuf::libprotobuf
, and include generated proto files. - Build the executable, e.g.,
multiply_server
.
4. Starting the Server
In your main
function, instantiate the service and start the gRPC server:
void RunServer() {
std::string server_address("0.0.0.0:50053");
MultiplyServer service;
grpc::ServerBuilder builder;
builder.AddListeningPort(server_address, grpc::InsecureServerCredentials());
builder.RegisterService(&service);
std::unique_ptr<grpc::Server> server(builder.BuildAndStart());
std::cout << "Multiply Server listening on " << server_address << std::endl;
server->Wait();
}
int main() {
RunServer();
return 0;
}
5. Build and Run
Compile using CMake and Make:
mkdir -p build && cd build
cmake ..
make
./multiply_server
Or run in Docker (as per repository instructions).
Summary of Flow: proto → generate stubs → implement service logic → build → start gRPC server
Docker Orchestration
The entire system runs in containers with sophisticated dependency management and service discovery. The orchestration architecture establishes a custom Docker network enabling seamless inter-service communication using container hostnames as service endpoints.
Each language-specific service runs in its own optimized container environment, with carefully managed build processes using appropriate base images and dependency installations. The startup sequence ensures services come online in the correct order, with health checks and restart policies maintaining system reliability.
The Node.js client container mounts the shared Protocol Buffer definitions and establishes connections to all backend services through the Docker network. This containerized approach enables consistent deployment across development, testing, and production environments while abstracting the complexity of multi-language service coordination.
Volume mounts ensure Protocol Buffer definitions remain synchronized across all services, while environment variables configure service endpoints and connection parameters dynamically based on the deployment context.
Usage & Testing
Quick Start
# Clone and run
git clone "https://github.com/A-KGeorge/grpc-polyglot-calculator"
cd Calculator
docker-compose up --build
# Interactive usage
Enter expression: (5 + 3) * 2^2 - 10 % 3
Result: 31
Automated Testing
The system includes comprehensive integration tests validating complex expression evaluation across all services:
# Run test suite
docker-compose up -d
node test.js
# Expected results for complex expressions
✓ Simple: 2 + 3 = 5 (Python)
✓ Precedence: 2 + 3 * 4 = 14 (Python + C++)
✓ Parentheses: (2 + 3) * 4 = 20 (Python + C++)
✓ Decimals: 3.14 * 2 = 6.28 (C++)
✓ Modulus: 10 % 3 = 1 (Go)
✓ Power: 2^3 = 8 (Elixir)
✓ Complex: (5 + 3) * 2^2 - 10 % 3 = 31 (All services)
Technical Achievements
Language-Specific Strengths Showcased
- Python: Rapid prototyping and mathematical operations
- Java: Enterprise-grade robustness with Maven ecosystem
- C++: High-performance computing for intensive calculations
- Rust: Memory safety with zero-cost abstractions and async I/O
- Go: Concurrent processing with goroutines
- Elixir: Fault-tolerance and actor model concurrency
- Node.js: Event-driven architecture and JavaScript ecosystem
Production Patterns Demonstrated
- Service Mesh Architecture: Independent, scalable microservices
- Protocol Buffer Contracts: Type-safe cross-language communication
- Container Orchestration: Docker Compose with health checks
- Error Handling: Graceful degradation and comprehensive logging
- Testing Strategy: Automated integration tests across all services
Future Enhancements
Planned Improvements
- Kubernetes Deployment: Upgrade from Docker Compose to K8s for production scalability
- Load Balancing: Multi-instance service scaling with intelligent request distribution
- Enhanced Security: Authentication, authorization, and input validation layers
- Monitoring & Observability: Distributed tracing with Jaeger and metrics collection
- Additional Operations: Trigonometric, logarithmic, and root operations via C# service
Performance Optimizations
- Connection Pooling: Persistent gRPC connections for reduced latency
- Caching Layer: Redis integration for frequently calculated expressions
- Streaming Support: Server-side streaming for complex multi-step calculations
Resources & Links
Conclusion
The Polyglot gRPC Calculator demonstrates that modern distributed systems can successfully leverage multiple programming languages while maintaining clean architecture and robust communication patterns.
This project serves as both an educational resource for understanding polyglot microservices and a practical reference for implementing gRPC-based distributed systems.
Built with passion for distributed systems and cross-language interoperability.