This post is base on the talk, “Good Enough” Architecture • Stefan Tilkov • GOTO 2019 : https://www.youtube.com/watch?v=PzEox3szeRc
“Just Enough Architecture” — an approach to software architecture that strikes a balance between over-engineering and under-designing. The central message is that architecture should be deliberate, flexible, and context-sensitive. Architects and teams must continuously evolve their systems in response to changes in scale, complexity, and organizational structure rather than adhering rigidly to initial designs or popular patterns.
Key Principles
-
Architecture is Not Just Upfront Design:
Your system has an architecture whether you plan it or not. Instead of treating architecture as a one-time, big-design-up-front activity, view it as a property of the system that will inevitably evolve. Good architects expect—and plan for—change. -
Context Determines “Good” Architecture:
There’s no universally “good” architecture. Whether building for 10 users or 10 million, designing a simple internal app or a global commerce platform, the right approach depends entirely on the system’s quality attributes (performance, security, maintainability, etc.). -
Focus on Value, Not Busywork:
Architecture should address what matters most. Avoid trivializing architectural decisions (like enforcing petty coding standards) and instead concentrate on choices that “hurt” if they’re wrong—those that are difficult or costly to change later. -
Think in Terms of Trade-Offs and Evolution:
The tension between designing ahead for flexibility and sticking to the simplest possible solution is inevitable. Sometimes it makes sense to invest early in adaptability; other times, a straightforward implementation now—accompanied by a plan for manageable refactoring later—is more cost-effective.
Common Pitfalls and How to Avoid Them
-
Non-Extensible “Extensibility”:
Scenario: A platform attempts to accommodate both large, high-paying clients and small, low-investment customers through a single, overly generic, customizable solution. The result is a system too complex for small clients and too limited for large ones.
Lesson: Don’t try to be everything to everyone. If needed, create tailored solutions for different segments rather than forcing a one-size-fits-all configurability that satisfies no one. -
Overly Fine-Grained Architectures (Misapplied Microservices):
Scenario: Starting with a microservice architecture inspired by Netflix-like scale can lead to “entity services”—tiny services that do too little on their own and become central bottlenecks. This often emerges when initial team structures change or grow and the architecture no longer reflects real domain boundaries or organizational needs.
Lesson: Align architecture with organizational and domain boundaries. Don’t blindly copy patterns from tech giants. Adjust granularity to your team size, domain complexity, and scaling requirements. Reduce unnecessary dependencies that complicate maintenance and coordination. -
Rigid, Model-Driven Designs That Resist Change:
Scenario: Systems locked into rare “modeling windows” or constrained by legacy code-generation tools force teams into absurd workarounds, like reusing unused database columns for new features.
Lesson: Avoid architectures that make routine changes cumbersome. Allow for incremental, on-demand evolution. Rigid processes push developers into hacks that compromise maintainability and data integrity. -
Freestyle Architecture Without Any Standards:
Scenario: Teams granted absolute autonomy produce a patchwork of incompatible interfaces, formats, and front-ends. Without minimal architectural guidelines, the system becomes fragile and inconsistent.
Lesson: Balance autonomy with a small set of hard rules. Minimal standards for APIs, data formats, and integration methods can prevent chaos. This “macro architecture” framework keeps teams agile while still ensuring interoperability and maintainability. -
Unmanaged Architectural Growth and Complexity:
Scenario: Long-lived, successful products often accrue layers of technologies, databases, and custom integrations. Without oversight, the architecture grows unwieldy and expensive to maintain—an “entropy at scale” problem.
Lesson: Regularly review and prune your architecture. A measure of governance, even if minimal, can keep complexity under control. Architecture should never be “set and forget.” -
Replacing Complexity with Focused Solutions:
Scenario: A bank replaced an overly complex, proprietary integration platform (with its own IDE and hidden logic) by simpler “smart endpoints” and “dumb pipes.” Rather than a monolithic integration engine, smaller, more focused adapters and standard messaging middleware simplified development and maintenance.
Lesson: Embrace “just enough” integration. Lean on standard tools and code rather than intricate proprietary frameworks. Simplify to achieve a more evolvable and understandable system.
Practical Guidelines for “Just Enough Architecture”
-
Start with Minimal but Meaningful Constraints:
Define a handful of non-negotiable rules to ensure system-wide cohesion—e.g., “no direct database calls across components,” “consistent API style,” or “common data exchange format.” Keep these rules few but enforce them strictly. -
Regularly Revisit Architectural Decisions:
Architecture isn’t static. Review your assumptions every few months. As the domain, team size, or load patterns evolve, reconsider your boundaries, communication paths, and technology choices. -
Optimize for Evolvability:
Don’t aim for architectural perfection upfront. Instead, ensure that refactoring, re-partitioning, or switching tech stacks can be done without rewriting everything from scratch. Agile architectures are more valuable than static “ideal” ones. -
Align Architecture with Organization and Domain:
Team structure and organizational boundaries often influence where components should begin and end. Architectural units that map well onto natural domain boundaries and stable team configurations reduce friction. -
Foster Communication and Shared Understanding:
While not everyone must carry the “architect” title, architectural thinking should be a team-wide responsibility. Discuss trade-offs openly and involve developers in decision-making. Transparent architectural reasoning leads to buy-in and better long-term outcomes.
“Just Enough Architecture” means exercising judgment and restraint. Avoid the extremes: don’t build generic, one-size-fits-all frameworks that satisfy no one, and don’t splinter your system into unmanageable components chasing trends. Instead, adopt a mindful approach. Make incremental, context-aware architectural decisions supported by minimal but clear standards. Evolve your design over time, resist architectural lock-in, and ensure that architecture ultimately serves the business and its users—not the other way around.