I Spent Years on Python and Java.
I’d never seriously touched the Node ecosystem. Python and Java covered everything I built. Then I started building a gateway for a RAG…
I Spent Years on Python and Java. A RAG Gateway Made Me Use Fastify — And My Architecture Reading Made Me Confident About It.
I’d never seriously touched the Node ecosystem. Python and Java covered everything I built. Then I started building a gateway for a RAG system and ended up fully on Fastify. The switch itself isn’t the interesting part — what’s interesting is that I’d recently spent months deliberately studying software architecture concepts, somewhat academically, and this was the first time I watched that reading actually do something instead of just sitting there as theory. I want to walk through the decision the way I’d want a more experienced architect to walk me through one: not just “what I picked,” but which concept did the work at each step.
Step one: name the quality attribute before naming the technology
A habit I picked up from that reading was: before reaching for any technology, write down what quality attribute actually matters for this component. Not “what’s fast” or “what’s good” in the abstract — name the specific -ility you’re optimising, because different attributes pull you toward different architectures.
For the gateway, the attribute was throughput under concurrent I/O wait, not computational speed. The gateway barely computes anything — it takes a request, fans it out to an embedding service, a vector store, a reranker, and an LLM endpoint, waits on all of them, and stitches the response together. Almost the entire request lifecycle is the system doing nothing but waiting on a network call to return.
That’s a different problem than “make this code run fast,” and naming it precisely is what made the next step possible.
Step two: match the concurrency model to the actual bottleneck
Python and Java handle concurrency with thread-per-request (or process-per-request). Each request holds a thread for its entire lifetime, including all the time it spends waiting. That’s a fine model when requests are doing real work. It’s a bad fit when requests are mostly idle, waiting on something else to respond — you’re paying thread overhead (memory, context-switching) for time spent doing nothing.
Node’s event loop is built for exactly the bottleneck I’d named: I/O wait, not computation. A waiting request doesn’t block the loop; the loop just services other pending work and comes back when the response lands. This is a textbook case of a known architectural trade-off — thread-per-request vs. event-loop concurrency — and once I’d correctly named the quality attribute in step one, the concurrency model basically picked itself. That’s the part I’d have gotten wrong by instinct alone a year ago: I’d have defaulted to “Python is what I know” instead of asking what the bottleneck actually was first.
Step three: treat the framework choice as an ADR, not a default
Once Node made sense, the lazy move was Express — most tutorials, most Stack Overflow answers, the path of least resistance. Writing actual Architecture Decision Records for a while changed how I approached this. An ADR forces you to write down the alternatives you rejected and why, not just the choice you made. So instead of “Express because it’s popular,” I made myself answer: what does this gateway specifically need that a generic default doesn’t give me?
Two things came out of that exercise:
Schema-enforced contracts at the boundary. This gateway sits between a frontend and several backend services — every request and response crossing that line needed a contract, not just a convention everyone hopefully follows. Fastify lets every route declare a JSON Schema for its request and response, compiled ahead of time into a validator and serializer. That’s the closest thing Node has to Pydantic models or Java’s Bean Validation, and it meant the contract was enforced by the framework, not maintained by discipline.
Encapsulation over a global default. Express middleware is global by default — register something and it applies everywhere unless you’re careful, which tends to decay into a pile of middleware nobody fully understands a few months in. Fastify’s plugin system gives each plugin its own scope, closer to how a well-bounded module stays contained than to Express’s flatter middleware stack. That’s a small thing, but it’s the same instinct DDD’s bounded-context thinking gave me elsewhere: keep things from leaking into each other by default, not by convention.
Writing this out as if it were an ADR — alternatives considered, trade-off, decision — is what made me confident in the call instead of just going with a hunch.
Step four: draw the boundary, don’t dissolve it
The thing I’m proudest of isn’t “I moved to Fastify.” It’s where I stopped. The actual RAG logic — embeddings, retrieval scoring, anything compute-heavy — has no business sitting on a single-threaded event loop, and I didn’t try to drag it there. Fastify owns the gateway: the layer that’s mostly traffic. Python still owns the layer that’s mostly computation.
That’s a bounded context, in the literal DDD sense — a clear line around what belongs on each side, justified by what each side is actually optimized for, not by “let’s use one language everywhere” or “let’s use what I already know everywhere.” Drawing that line deliberately, instead of letting the codebase sprawl into whichever language was nearest, is the actual skill I was practicing. Fastify was just the artifact it produced.
The honest takeaway
I didn’t switch frameworks because I read a few books on architecture and Fastify happened to be the answer. I switched because the habits from that reading — name the quality attribute first, treat framework choice as a decision worth writing down, draw boundaries on purpose — gave me a process that led somewhere specific instead of somewhere familiar. The technology is incidental. The process is the part worth keeping for the next decision, whatever it turns out to be.
메타데이터
- post_id
- b2d34235fde9
- slug
- i-spent-years-on-python-and-java-b2d34235fde9
- url
- https://medium.com/@chsharma23051998/i-spent-years-on-python-and-java-b2d34235fde9
- canonical_url
- https://medium.com/@chsharma23051998/i-spent-years-on-python-and-java-b2d34235fde9
- author_url
- https://medium.com/@chsharma23051998
- status
- ok
- fetched_at
- 2026-07-16 00:55:23