Good vs Bad Architecture: Principles, Patterns, and Practices for Scalable Systems
This article explains how good software architecture enables easy evolution, fault tolerance, and low maintenance, while bad architecture leads to complexity and brittleness, and it outlines key principles, design styles, and practical techniques such as domain‑centric, microservices, event‑driven, and CQRS to build robust, adaptable systems.
A good architecture supports business evolution, is easy to modify, and offers better fault tolerance, whereas a bad architecture is fragile and can break with minor changes.
What Is Bad Architecture? How to Identify It
Developers often write spaghetti code to speed development, which hides bugs and makes refactoring costly. Bad architecture exhibits these traits:
Unnecessarily Complex – Simple code is hard to write, complex code is easy.
Rigid/Brittle – Small changes easily introduce errors.
Untestable – Tight coupling and violation of single‑responsibility hinder testing.
Unmaintainable – Low test coverage turns code into a maintenance nightmare.
What Is Good Architecture? Characteristics
Simple – Easy to understand.
Modular/Layered/Clear – Changes in one layer don’t affect others; minimal coupling.
Flexible/Extendable – Adapts readily to new requirements.
Testable/Maintainable – Facilitates automated testing and TDD.
Why Care About Architecture, Principles, and Practices?
Cost reduction – Initial speed may drop, but total build and maintenance costs decrease.
Build what is essential – Focus on necessary components to reduce technical debt.
Optimization – Improves maintainability for developers and users.
Performance optimization – Defer code‑level performance tweaks to the Last Responsible Time (LRT).
Last Responsible Time (LRT) – Delay decisions until enough knowledge is gathered, reducing premature costs.
Adaptability/Evolution – Follow evolutionary patterns as software scales.
Tools, Methods, and Techniques
Lean principles – Build the right and necessary things.
Agile methodology – Respond quickly to changing market demands.
Test‑driven development & Automated tests – Ensure testable design and shift testing left.
Which Architecture Styles to Follow?
There is no one‑size‑fits‑all; the choice depends on context and trade‑offs.
Domain‑Centric Architecture
Application‑Centric Architecture
Screaming Architecture
Microservices Architecture
Event‑Driven Architecture (EDA)
Command Query Responsibility Segregation (CQRS)
Domain‑Centric Architecture
The domain is the core model; everything else (application layer, presentation, persistence, services) surrounds it. The domain reflects the users' mental model and changes rarely.
Two common domain models are the Hexagonal and Onion architectures, where each outer layer depends on the inner one.
Advantages:
Supports Domain‑Driven Design (DDD) by focusing on domain, users, and use cases.
Reduces coupling between stable domain and frequently changing implementation details.
Disadvantages:
Higher initial cost due to extensive domain modeling.
Developers may resist the shift from database‑centric three‑layer architecture.
Application‑Centric Architecture
After defining domain boundaries, the application layer follows SOLID principles to become robust.
Abstraction (What?) – Build applications that encapsulate business logic via abstraction.
Decoupling (How?) – Use Dependency Injection to inject infrastructure (databases, caches, external services).
Contracts (Interaction) – Define clear interfaces for each layer, enabling mock testing and reducing coupling.
Screaming Architecture
The architecture should make the system’s intent obvious, as advocated by Uncle Bob.
For the backend layer, organize code by functional modules with a single aggregate root per module, simplifying intent.
For the presentation layer, keep it lightweight, avoid business logic, and follow MVC patterns to reduce duplication and aid UI developers.
Microservices Architecture
Splitting a system into independent services each with a single responsibility, own database, and deployable independently reduces coupling and allows technology heterogeneity.
Bounded Contexts isolate domain terminology to specific contexts, improving cohesion.
Drawbacks include higher initial cost, need for DevOps automation, and added complexity for latency, load balancing, logging, monitoring, and eventual consistency.
Event‑Driven Architecture (EDA)
Services communicate via events through a message broker, providing faster responses, lower latency, and better scalability.
Participants: producers, brokers, and consumers, forming a reactive programming model.
For ACID‑like consistency across microservices, the SAGA pattern can be used, though it adds complexity.
Event Sourcing stores state as a sequence of events, with snapshots for performance.
CQRS (Command Query Responsibility Segregation)
Separates write commands from read queries, enabling independent scaling of reads and writes and allowing caching strategies.
References: [1] https://sarada-sastri.medium.com/software-architecture-principles-practices-styles-a0263aa11530
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
21CTO
21CTO (21CTO.com) offers developers community, training, and services, making it your go‑to learning and service platform.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
