Software Architecture Styles
Choosing a software architecture style is the highest-level design decision. It shapes performance even when performance wasn’t the explicit goal. From ECE459 L10.
Why does this matter for performance?
Architecture decisions determine where data lives and how far it has to travel. A call that’s in-memory in one architecture is a network hop in another. The bill shows up as latency, contention, and chattiness, regardless of how tight the inner code is.
Top-level choice: monolith vs microservices
- Monolith: one codebase, one deployable unit, in-memory calls
- Microservices: many services, many deploys, network calls
Start with a monolith
Only break off microservices when it proves valuable later. The industry has cycled through “monoliths only”, “microservices everywhere”, and backlash. Most real companies end up somewhere in the middle.
Middle-level patterns. Once the top-level is fixed, pick the pattern that fits the work:
- Producer-consumer for pipeline-shaped work
- Blackboard for shared-state coordination
- Message passing vs shared memory for inter-component comm
Assembly-line modelling isn't free
Moving data from queue to queue between stages adds overhead. A single thread taking one item through all steps can beat a pipeline with N stages, especially when stages are short.
Implementation-level choices. How many threads? Spawn dynamically or use a thread pool? If your framework already uses a pool, you’re often just tuning its size. More threads is not always better — overhead and communication costs grow.
Complexity is the enemy
Big-company practices get cargo-culted as “silver bullets” that don’t fit smaller scales. Don’t conflate necessary domain complexity (tax code) with accidental complexity (FizzBuzzEnterpriseEdition-style over-abstraction).
Rule of thumb: use as much abstraction as necessary and no more. Beware “architecture astronauts” who design absurd all-encompassing pictures that don’t mean anything.
Four pitfalls from L10:
- Excessive network calls. Each call costs setup + auth + parsing. N+1 queries are the canonical case, often silently generated by ORMs like Hibernate or Rails ActiveRecord
- Chokepoints. A central auth service that every request hits. Fix by distributing: JWT lets the receiving service validate credentials itself, like a passport
- Over-taking responsibility. Filtering a DB table in the app, or rendering on the backend when the frontend could. Put work where the data already lives
- Too much waiting. Synchronous calls that could be async. Lock contention on an
Arc<Mutex<Vector>>where every thread wants to push; replace with one owner and channels
See Microservices for the pitfalls unpacked in context.