PolyAPI Architecture¶
Overview¶
PolyAPI is a polyglot microservices architecture that demonstrates how to build production-ready APIs using multiple programming languages. The system uses a Python FastAPI gateway for orchestration and language-agnostic JSON contracts for inter-module communication.
Why FastAPI?¶
FastAPI was chosen as the gateway framework for several compelling reasons:
- Native Async Support: FastAPI's async capabilities allow efficient handling of concurrent requests to multiple modules
- Automatic Documentation: OpenAPI/Swagger documentation is generated automatically
- Pydantic Integration: Built-in request/response validation using Pydantic models
- Performance: Comparable to Node.js and Go in terms of raw performance
- Ecosystem: Large ecosystem of middleware, authentication, and database integrations
Polyglot Philosophy¶
PolyAPI embraces the principle that different tasks are best solved with different tools:
Language Selection Criteria¶
- Python: Rapid development, strong typing with Pydantic, excellent for orchestration layers
- Go: Performance-critical services, simple deployment, excellent standard library
- Future modules: Could include Rust (performance), Node.js (I/O-heavy), Java (enterprise)
Benefits¶
- Best Tool for the Job: Each module can use the most appropriate language
- Team Flexibility: Teams can work in their preferred languages
- Incremental Adoption: New modules can be added without rewriting existing code
- Learning Opportunity: Developers can contribute in their language of choice
JSON Contract Decoupling¶
The JSON contract is the backbone of PolyAPI's polyglot architecture. It ensures that:
- Language Independence: Any language that can send/receive JSON can participate
- Loose Coupling: Modules don't import each other's code
- Interface Segregation: The contract defines exactly what needs to be implemented
- Independent Deployment: Modules can be updated independently
Request Lifecycle¶
┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐
│ Client │────▶│ Gateway │────▶│ Module │────▶│ Gateway │────▶│ Client │
└──────────┘ └──────────┘ └──────────┘ └──────────┘ └──────────┘
│ │
│ 1. Receive request │
│ 2. Validate contract │
│ 3. Generate request_id │
│ 6. Return │
│ contract │
│ response │
4. Forward to
module via HTTP
5. Receive
module response
Detailed Steps¶
- Client Request: External client sends HTTP POST to gateway with JSON body
- Gateway Validation: Gateway validates request against Pydantic models
- Request ID Injection: If not provided, gateway generates UUID v4
- Module Forwarding: Gateway forwards request to appropriate module
- Module Processing: Module validates, processes, and returns response
- Response Transformation: Gateway may transform or enrich the response
- Client Response: Gateway returns contract-compliant JSON to client
Versioning Strategy¶
Contract Versioning¶
The JSON contract uses Semantic Versioning (SemVer):
- MAJOR: Breaking changes to contract structure
- MINOR: Backward-compatible additions
- PATCH: Bug fixes
Module Versioning¶
Each module declares its version in the response envelope:
{
"module": "sort",
"version": "1.0.0"
}
Gateway Versioning¶
The gateway itself is versioned and may support multiple contract versions simultaneously.
Security Considerations¶
Network Isolation¶
- Modules are not directly exposed to the internet
- Only the gateway listens on externally accessible ports
- Internal communication happens on a Docker network
Input Validation¶
- Gateway validates all incoming requests using Pydantic
- Each module validates input before processing
- Error responses follow the contract format (no raw tracebacks)
Environment Variables¶
- No hardcoded URLs or ports in code
- All configuration via environment variables
- Sensitive data should use Docker secrets or external config management
Future Enhancements¶
Possible improvements to the architecture:
- Service Discovery: Replace static URLs with dynamic service discovery
- Authentication: Add JWT or API key authentication at the gateway
- Rate Limiting: Implement rate limiting per client/module
- Tracing: Add distributed tracing with OpenTelemetry
- Caching: Implement response caching for expensive operations
- Circuit Breakers: Add resilience patterns for module failures