Production Engineering Reference
Production Engineering Reference
API styles, caching, databases, languages — what to use, when, and why
Covers: REST vs gRPC vs GraphQL (with code), Redis caching patterns, the database layer (SQL/NoSQL/cache/search),
language tradeoffs across the stack, and a testing layer — each with concrete examples and "use this when / not this when"
guidance.
Best for Public APIs, simple CRUD, Service-to-service (internal Aggregating many sources for a
browser-friendly microservices) flexible frontend
Payload format JSON (text, verbose) Protobuf (binary, compact) JSON, but client picks the shape
Streaming Limited (SSE/websockets Native (unary, server, client, bidi Subscriptions (via websockets,
bolted on) streams) bolted on)
Caching Easy (HTTP caching, CDNs) Hard (binary, point-to-point) Hard (single endpoint, custom
caching needed)
Where each one actually shows up in the architecture from your diagram
• Clients → API Gateway/BFF: REST or GraphQL — browsers and mobile apps need something that works over plain
HTTP/1.1 without extra proxies, and GraphQL specifically helps the BFF avoid over-fetching when the web app, mobile app,
and admin portal each need different shapes of the same data.
• API Gateway/BFF → Microservices (Identity, Academics, Scheduling, Finance, Examination, Attendance,
Notification, Analytics): gRPC — this is pure internal service-to-service traffic, so binary Protobuf + HTTP/2 multiplexing
gives lower latency and higher throughput than JSON/REST, and the .proto contract keeps every service's interface strongly
typed and version-checked at build time.
• Public/partner-facing API (if VivekLab ERP ever exposes one): REST — third parties expect REST + OpenAPI; it is the
lowest-friction option for external integrators.
Rule of thumb: REST at the edge (talking to humans and third parties), gRPC in the middle (talking to your own services), GraphQL
when one endpoint needs to flexibly aggregate many backends for a UI that changes a lot.
Bidirectional streaming stream ↔ stream, both ways, LiveNotificationChannel(stream Ack) ↔ stream Notification
concurrently — used for the Notification service pushing live alerts
service AttendanceService {
rpc MarkAttendance (MarkAttendanceRequest) returns (MarkAttendanceResponse);
rpc StreamClassAttendance (ClassRequest) returns (stream AttendanceEvent);
}
message MarkAttendanceRequest {
string student_id = 1;
string class_id = 2;
bool present = 3;
int64 timestamp = 4;
}
message MarkAttendanceResponse {
bool success = 1;
string message = 2;
}
message AttendanceEvent {
string student_id = 1;
bool present = 2;
int64 timestamp = 3;
}
2.4 Server implementation (Go) — matches the diagram's Attendance Node (Go)
func (s *attendanceServer) MarkAttendance(ctx [Link],
req *[Link]) (*[Link], error) {
Service-to-service calls inside your own cluster Calling directly from a browser (needs grpc-web + Envoy
proxy — extra moving part)
You need streaming (live attendance feed, chat, telemetry) Your consumers are third-party partners expecting plain
REST/JSON
You want compile-time-checked contracts across many The API needs to be human-browsable/debuggable
languages/teams casually (curl-friendly REST wins here)
Latency and payload size matter (binary Protobuf is far Team has no protobuf tooling experience and the deadline
smaller than JSON) is tight
type Query {
student(id: ID!): Student
}
3.4 Client query — exactly what the Admin portal asks for, nothing more
query StudentSummary($id: ID!) {
student(id: $id) {
name
attendanceRate
grades { subject score }
}
}
# Notice: examResults is NOT requested here, so that resolver never even runs.
# A REST endpoint would have returned it anyway (over-fetching) unless you
# built a bespoke /summary endpoint just for this one screen.
Multiple very different clients (web, mobile, admin) need You have one simple client and one simple backend —
different shapes of the same data REST is less ceremony
You're aggregating data from many microservices for a Strong HTTP-level caching (CDN, browser cache) is the
single screen priority — GraphQL's single endpoint defeats this
Frontend teams need to iterate without waiting on new File uploads / binary-heavy operations (REST/multipart
backend endpoints handles these more naturally)
Reducing mobile over-fetching matters for bandwidth/battery Your team has no budget to manage schema evolution,
N+1 issues, and query-cost limiting
Hash Object-like cache entry (partial field updates) HSET student:S1029 name 'Aritra' attendance_pct
92
Set Unique membership checks (e.g. "has this SADD notice:seen:NTC55 user42
user seen this notice")
Sorted Set (ZSET) Leaderboards, rate limiting windows, priority ZADD exam:rank:EX10 92 'S1029'
queues
Stream Lightweight event log (simpler alternative to XADD attendance:stream * student S1029 present
Kafka for smaller volume) true
Pub/Sub Real-time fan-out (e.g. push live notification PUBLISH notifications:live '{...}'
to connected admin dashboards)
4.2 Caching patterns — pick the right one per use case
Pattern How it works Best for
Cache-aside (lazy load) App checks Redis first; on miss, reads DB, then Most read-heavy endpoints (student profile,
writes to Redis course catalog) — simplest, most common
pattern
Write-through App writes to Redis and DB at the same time, Data that must never be stale right after a
synchronously write (e.g. exam result just published)
TTL-based expiry Every key has an expiry; stale data self-heals Session tokens, rate-limit counters,
anything time-bound by nature
// Invalidate on write, don't wait for TTL, to avoid serving stale data:
async function updateStudentProfile(studentId: string, data: object) {
await [Link]('UPDATE students SET ... WHERE id = $1', [studentId]);
await [Link](`student:profile:${studentId}`); // invalidate
}
5.2 When you'd add a NoSQL store (not in the original diagram, but common)
Store Data shape Production use case
MongoDB / Flexible, nested JSON documents Storing arbitrary form submissions, audit logs with varying shape
DocumentDB per event type
DynamoDB / Key-value / wide-column, massive Append-only event history, IoT-style sensor data, very high write
Cassandra write scale throughput with simple access patterns
Neo4j Graph (nodes + relationships) Modeling complex relationships, e.g. course prerequisite chains,
org reporting structures
Record a high-volume, append-only stream of events for later processing Kafka topic → (optionally)
Cassandra/DynamoDB
TypeScript / [Link]
Strengths: Huge ecosystem (npm), same language as the React frontend (shared types/DTOs possible), excellent for
I/O-bound workloads (API gateway, BFF, CRUD services) due to its non-blocking event loop.
Weaknesses: Single-threaded by default for CPU-bound work (heavy computation blocks the event loop unless offloaded to
worker threads); runtime type errors still possible despite TS if discipline slips.
Used for in this architecture: API Gateway/BFF, Identity, Academics, Finance, Examination services.
Example win: A `/dashboard` BFF endpoint making 4 parallel downstream gRPC calls via [Link]() — Node's async
model handles this with minimal code and low memory overhead.
Go
Strengths: Goroutines make massive concurrency cheap (handle 10,000 simultaneous notification sends with one
process); fast cold start and tiny container images — ideal for Kubernetes autoscaling; compiles to a single static binary,
simple deploys.
Weaknesses: More verbose than Python/TS for everyday CRUD (no generics-heavy ORMs, more boilerplate error handling
`if err != nil`); smaller ecosystem for, e.g., ML or complex business-rule DSLs.
Example win: Notification service fanning out 50,000 push notifications concurrently with a worker-pool pattern, finishing in
seconds instead of minutes, using a fraction of the memory a thread-per-request model would need.
Python
Strengths: Best ecosystem on Earth for data/ML (pandas, NumPy, scikit-learn, PyTorch); extremely fast to prototype and
iterate; readable, lowers onboarding cost for analytics-focused hires.
Weaknesses: Slow at raw CPU-bound execution compared to Go/Java (GIL limits true multi-threading); not the first choice
for high-throughput low-latency services.
Example win: Computing per-class attendance trend predictions with pandas + scikit-learn in 20 lines of code — would take
far longer to hand-roll in Go.
Weaknesses: Higher memory footprint and slower cold start than Go — less ideal for serverless/very elastic autoscaling;
more verbose/ceremony-heavy than Node or Python for simple services.
Used for in this architecture (common alternative choice): Could replace Node for Finance/Identity if the team wants
Spring Security's maturity for auth/payment flows, or for Kafka Streams consumers.
Example win: A Finance service using Spring's @Transactional + strong typing to make a half-applied payment transaction
structurally hard to ship.
Weaknesses: Not Turing-complete by design (intentional) — anything genuinely procedural (loops with complex branching)
needs to drop into a real language (e.g. Terraform's `for_each`/modules only go so far before you need a wrapper script).
Used for in this architecture: PostgreSQL queries, Terraform (Platform Infrastructure), Kubernetes manifests, GitHub
Actions pipelines.
Example win: `terraform plan` showing exactly what infrastructure will change before it happens — impossible to get that
kind of safety from an imperative bash script doing the same job.
Node/TS Single-threaded event loop Low for I/O-bound, poor for Web/API tooling, npm Fast
+ libuv CPU-bound
Java/Kotlin OS threads + JVM thread Low after JIT warm-up Enterprise/transactional, Slow(er), JVM
pools Kafka startup
Unit Jest/Vitest (TS), pytest (Python), go test Logic bugs in one function/class, isolated
(Go), JUnit5 (Java)
Contract Pact, Spring Cloud Contract Breaking API changes between services without spinning
up the whole system
Integration Testcontainers, Supertest Bugs in how a service talks to its real DB/Kafka/Redis
Performance/load k6, Gatling, Locust N+1 queries, connection pool exhaustion under real traffic
Chaos Gremlin, Chaos Mesh Cascading failure when a dependency dies or network
degrades
Security OWASP ZAP, Snyk, Trivy, Semgrep Vulnerable deps, container CVEs, SQLi/XSS patterns
Synthetic monitoring Checkly, Datadog Synthetics Live production outages before users report them
Canary/progressive Argo Rollouts, Flagger, LaunchDarkly Bad deploys, limited to a small % of traffic before full rollout
delivery
Java/Kotlin JUnit5, Kotest Spring Boot Test, Selenium, Playwright-java Mockito, WireMock
Testcontainers
8.2 Storage
Scenario Pick
End of document.