27 · Programming Languages and Backend Framework Selection
The thesis in one line: language and framework choice is not a belief system. It is a constraint problem. An architect does not ask "which one is hottest"; an architect asks "will this choice change team speed, runtime cost, performance ceiling, ecosystem availability, and future migration cost?" If yes, it is an architecture decision. If it only changes syntax, it is implementation detail.
🧰 Technology Stack Selection Track, Chapter 1 · One thing to practice
The first 26 chapters kept saying "architecture is not framework choice." That remains true. But real systems still need Java, Go, Python, TypeScript, Rust, and backend frameworks. This track does not teach syntax. It brings "what tech should we use" back into the framework from Chapter 02: requirements, constraints, quality attributes, trade-offs.
Opening: selection is not a popularity vote
Teams often choose languages like this:
I like Go -> use Go
Java hiring is easy -> use Java
AI ecosystem is Python -> use Python
Frontend is TS -> use TypeScriptThese are signals, not decisions. Keep asking:
- What is the bottleneck of this business path?
- Is the team short on delivery speed, runtime efficiency, stability, or hiring supply?
- Does the ecosystem have mature libraries, test tools, monitoring, and deployment patterns?
- Can someone new maintain this code three years from now?
Rule of thumb: if a language/framework choice materially affects quality attributes (Chapter 06), it is architecture. If it is only code style, do not turn it into a technology war.
1. Separate language, runtime, and framework
Beginners often mix three layers:
| Layer | What it decides | Examples | Architecture concern |
|---|---|---|---|
| Language | Expression style, type system, ecosystem entry | Java, Go, Python, TypeScript, Rust | Team familiarity, maintainability, early error detection |
| Runtime | Concurrency, memory, startup, deployment shape | JVM, Node.js, CPython, Go runtime | Latency, throughput, resource cost, cold start |
| Framework | Conventions and component assembly | Spring Boot, FastAPI, NestJS, Gin | Delivery speed, testability, plugin ecosystem, team consistency |
So "Java or Go?" is not a complete question. A better question is:
Given this team, business, and quality target, what runtime and delivery model do we need?
For an internal SaaS backend such as PatchDesk, the early challenge is not maximum QPS. It is permissions, tenant isolation, audit, reports, and long-term maintenance. In that context, mature framework conventions often matter more than raw performance.
2. Five rulers before tool names
| Ruler | Question | What it pushes toward |
|---|---|---|
| Business complexity | Many rules, states, permissions? | Strong typing, clear conventions, mature testing |
| Performance and resources | Is CPU, memory, or P99 tail latency central? | Lower runtime overhead, clearer concurrency model |
| Ecosystem maturity | Auth, ORM, queues, observability, SDKs ready? | Deep ecosystem, stable docs, active community |
| Team capability | What does the team know? Can it review and hire? | Main team language, or a new language with controlled learning cost |
| Delivery and evolution | Fast iteration or long-term reliability? | Clear framework conventions and migration paths |
Architectural wisdom: do not change stack because a language is "more modern." A new technology deserves consideration only when it clearly buys a quality attribute and you are willing to pay learning, ops, hiring, and migration costs.
3. Common backend choices as trade-offs
| Technology | Common strengths | Common costs | Good fit |
|---|---|---|---|
| Java / Kotlin + JVM | Mature ecosystem, stable performance, enterprise libraries | Can feel heavy; framework complexity | Medium/large business systems, finance, e-commerce, SaaS |
| Go | Simple deployment, direct concurrency, low resource use | Complex business modeling needs discipline | Gateways, infrastructure, microservices, realtime paths |
| Python | AI/data ecosystem, fast prototyping | Runtime performance and concurrency need care | AI services, data platforms, automation |
| TypeScript / Node.js | One language across frontend/backend, I/O friendly | CPU-heavy work is a poor fit; dependency quality varies | BFF, small/mid SaaS, lightweight realtime services |
| Rust | Performance and memory safety | Higher learning curve, slower delivery for many teams | Storage, proxies, engines, safety/performance-critical components |
A system does not need one language only:
Core business service: Java / Go / TypeScript
AI and data processing: Python
High-performance proxy or engine: Rust / Go
Frontend and BFF: TypeScriptPolyglot stacks are not wrong, but they carry cognitive tax: build, deploy, monitor, debug, hire, and review all get harder. A small team that uses five stacks because "each module gets the best language" is often spending organizational capacity it does not have (Chapter 15).
4. Framework selection: mature by default
Frameworks are team agreements in code:
A framework gives:
routing / dependency injection / config / data access / auth / testing / observability entry points
A framework takes:
freedom / learning cost / upgrade cost / debugging transparencyFor MVPs, reduce delivery risk. For long-lived team systems, reduce collaboration risk. Often, a mature framework that looks boring is safer than a cool lightweight stack that relies on everyone maintaining perfect discipline.
| Question | If yes | Tendency |
|---|---|---|
| Many new teammates, long-term maintenance? | Yes | Strong conventions, good docs, mature ecosystem |
| Fast experimentation, unclear business? | Yes | Lightweight framework + clear module boundaries |
| Lots of enterprise integration, transactions, permissions? | Yes | Mature enterprise framework |
| High-concurrency gateway/proxy? | Yes | Low overhead runtime and mature network model |
| AI/data-heavy service? | Yes | Stay close to Python/data ecosystem, wrap it with stable APIs |
5. When to change language or framework
Change should be driven by signals:
| Signal | Meaning | Possible action |
|---|---|---|
| P99 keeps missing SLO due to runtime mismatch | Not just bad code | Move hot path to a better runtime |
| Delivery slows because framework conventions fight the business | Complexity outgrew the framework | Modularize first, then migrate locally |
| Ecosystem gaps force heavy self-building | Maintenance cost keeps rising | Move to a richer ecosystem |
| Hiring and review are hard | Team cannot support the choice | Converge stack or strengthen platform constraints |
| Security/compliance/performance bar rises | Old stack cannot cover new requirements | Upgrade the critical component |
As in Chapter 14, changing stack should be evolutionary: carve a boundary, run in parallel, use shadow traffic where useful, and switch gradually.
🎯 Quick check
- AUse Rust everywhere immediately for maximum performance
- BUse a familiar mature Java / Spring Boot style stack as a modular monolith and keep boundaries and tests clear
- CLet every module owner freely choose a favorite language
- AWhenever someone likes or dislikes a language
- BWhen the choice materially affects performance, availability, cost, collaboration, ecosystem availability, or future migration cost
- COnly in microservice systems
Chapter summary
- Language/framework choice is a constraint problem: business complexity, performance, ecosystem, team, and evolution come before tool names.
- Separate language, runtime, and framework: they affect different parts of the system.
- Mature is the default unless a new stack clearly buys a quality attribute.
- Polyglot has cognitive tax: build, deploy, monitor, debug, hire, and review get harder.
- Stack changes should evolve: carve boundaries and migrate gradually, as in Chapter 14.
Next: language and frameworks decide how services run and teams collaborate. But data is the part that stays. Chapter 28 · Database and Storage Selection asks where data should live, how it should be queried, and how it should remain correct.
Related links
- Method core: 02 · Thinking framework · 06 · Quality attributes · 08 · ADRs
- Evolution: 14 · Evolution and splitting · 15 · Organization as architecture
- Case references: PatchDesk · CodePilot
💬 Comments