Appearance
14.2 Architecture Styles: Modular Monolith vs Microservices
1. What It Is in Plain Words
An architecture style defines how the software boundaries, execution processes, and communication interfaces are organized—ranging from single self-contained monoliths to distributed microservices and serverless functions.
2. How THIS Project Uses It
The backend is built as a Modular Monolith ([app.py](file:///Users/avishjain/avishjain9-repos/21-bis-compliance-engine/app.py)):
- Module Separation: Clear internal domain boundaries:
- Vector search & embedding in
app.py - Domain guardrailing in [
guardrail.py](file:///Users/avishjain/avishjain9-repos/21-bis-compliance-engine/guardrail.py) - Statutory gap evaluation & CSV formatting in [
gap_engine.py](file:///Users/avishjain/avishjain9-repos/21-bis-compliance-engine/gap_engine.py)
- Vector search & embedding in
- Single Process Execution: All modules run in the same Python runtime, communicating via native in-memory function calls with zero network serialization overhead.
3. Full Comparison with Alternatives
| Architectural Pattern | Communication Overhead | Operational Complexity | Hardware Cost | Failure Isolation | Fit for This Engine |
|---|---|---|---|---|---|
| Modular Monolith (Used) | Zero (In-Memory Pointers) | Very Low (1 Docker container) | Minimal (~65 MB RAM) | Single Process Boundary | Optimal |
| Microservices | High (gRPC/HTTP 10-25ms) | High (Kubernetes, Service Mesh) | High ($150+/mo) | High per service | Overkill for 3 endpoints |
| Serverless (AWS Lambda) | Moderate (Cold starts 2-5s) | Moderate (CloudWatch, IAM) | Variable | High | Poor (Cold starts break vector index) |
| Event-Driven CQRS | High (Kafka/RabbitMQ broker) | Very High | High | High | Overkill (No writes/mutations) |
Why We Did NOT Choose Microservices:
Splitting this 3-endpoint service into separate "Embedding Service", "Guardrail Service", and "Gap Service" would add 10-20ms of network serialization latency across internal hops, transforming a sub-18ms local response into an 80ms multi-hop distributed system without providing any scaling benefit [CONFIRM WITH DEVELOPER].
4. Trade-offs Accepted
- Coupled Deployment: Updating a single regular expression in
guardrail.pyrequires restarting the entire FastAPI container, reloading the ONNX model into RAM. - Resource Coupling: CPU-intensive vector embedding competes with web socket I/O on the same machine.
5. What Breaks at Scale
- At 10x Load: Remains stable; handled cleanly by adding Uvicorn workers.
- At 100x Load: CPU saturation during ONNX embedding slows down HTTP response times for all modules.
- At 1000x Load: The monolith must be decoupled: split the computationally heavy FastEmbed inference into a dedicated GPU/ONNX worker cluster, while keeping the lightweight web controllers and gap rules as a stateless API gateway.
6. How a Senior Engineer Would Improve It
When traffic exceeds 500 RPS, separate into two distinct deployables:
- API Gateway & Rule Service (Node.js or Go): Handles client sockets, auth, rate limiting, and rule validation.
- Dense Vector Inference Microservice (Python/Triton): Dedicated embedding and FAISS search over high-speed gRPC.
7. 30-Second Spoken Answer
"We intentionally structured our backend as a Modular Monolith. Co-locating our FastEmbed ONNX model, FAISS index, and gap rules in a single process eliminates network hops and allows zero-copy in-memory pointer sharing, keeping total query latency under eighteen milliseconds with minimal operational complexity."