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:

ComponentLanguageFramework/LibraryPortPurpose
Add ServerPythongrpcio, grpcio-tools50051Addition
Subtract ServerJavagRPC Java, Maven50052Subtraction
Multiply ServerC++gRPC C++, CMake50053Multiplication
Divide ServerRustTonic, Tokio50054Division
Modulus ServerGogRPC Go50055Modulus
Exponentiation ServerElixirgRPC Elixir50056Exponentiation
AST ClientNode.js@grpc/grpc-js3000Expression 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:

protobuf
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 like Add(self, request, context), and registers it with the gRPC server.
  • Java: Extends CalculatorImplBase, implements public void add(TwoNumbers req, StreamObserver<Number> resp), and starts a gRPC server.
  • C++: Inherits from Calculator::Service, overrides methods like Status Add(ServerContext*, const TwoNumbers*, Number*), and creates a server with these handlers.
  • Rust: Implements the Calculator trait for a custom struct (e.g., DivisionServer), provides async fn divide(...), then starts the server with Server::builder().add_service(...).
  • Go: Implements the CalculatorServer interface, defines functions like func (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 like def 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: protocode implementation of methodsbuildstart 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:

bash
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:

cpp
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:

cpp
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:

bash
mkdir -p build && cd build
cmake ..
make
./multiply_server

Or run in Docker (as per repository instructions).

Summary of Flow: protogenerate stubsimplement service logicbuildstart 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

bash
# 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:

bash
# 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.