DEV Community

Cover image for Why Simple Architecture Wins
mortylen
mortylen

Posted on • Originally published at mortylen.hashnode.dev

Why Simple Architecture Wins

There is a common misconception shared by many developers: a good architecture has to be complex. It must have layers, abstractions, patterns, and frameworks. If a system doesn’t look impressive on a whiteboard, it’s probably not “professional enough.”

The truth is the opposite, and that is exactly what this article is about.

Complex systems don't appear by accident. But neither do simple ones.

Simplicity Is Not the Default

Most systems don’t start out complex. They begin as small projects with a clear goal: to solve a specific problem. Then another feature is added, and then another.

Someone adds a layer “just to be tidy.” Someone else adds an abstraction “for flexibility.” A year later, no one can quickly navigate the code that two people wrote over a weekend.

Complexity grows gradually. If you don’t actively manage it, it will eventually become overwhelming. Simplicity is therefore a conscious choice. Not just at the start, but throughout the system’s entire lifecycle.

You have to actively protect it during the entire life of the system.

What “Simple” Really Means

When people hear “simple architecture,” they often imagine something that isn’t what we actually mean:

  • Primitive solution – something thrown together quickly without much thought
  • Quick hack – code that works today but no one wants to touch tomorrow
  • Less code – short code can be just as confusing as long code

Simplicity actually means something different:

  • You can understand it quickly. A new developer can get up to speed in hours, not weeks.
  • Changes are local. When you modify something, you don’t accidentally break five other things.
  • Behavior is predictable. You know what will happen when you call a function.

A good way to understand this is by comparing it to so-called “flexible and generic” solutions. These systems are designed to handle almost anything that could come up. The problem is that they usually handle everything only moderately well and nothing particularly well.

On top of that, they are hard to understand. They are full of abstractions that exist “just in case something might happen someday,” which often ends up confusing developers unnecessarily.

Why Simplicity Is Hard

If simplicity is so good, why don’t we choose it automatically?

The reason is simple. It is psychologically challenging and requires discipline and constant conscious decision-making.

Planning for the Future

Developers naturally think ahead. “What if customers want more payment options?” “What if the system grows to ten times its current size?”

These questions are valid. But most of them will never happen. In the meantime, we pay the price for complexity that was never needed.

Complexity Looks Professional

A diagram with twenty boxes and arrows looks more impressive than one with five components. When presenting your architecture to colleagues or management, a simple solution can seem like you didn’t try hard enough.

This social dynamic is real and can be dangerous because developers or managers may start favoring complex solutions just because they look more professional, even when a simpler approach would work better and faster.

Fear of Rewriting

“What if we have to redo this later?” This is often the excuse behind unnecessary abstractions or complicated solutions.

The problem is that a system filled with abstractions meant to make future changes easier usually ends up being harder to change than simple, straightforward code. Instead of making your work easier, it can make every change unnecessarily complicated.

Following “Ideal” Examples

We often see articles about perfect architectures, training videos, or conference presentations where everything works elegantly and sophisticatedly.

It’s tempting to copy these ideal examples in your own project, creating layers, abstractions, and frameworks that look professional.

The problem is that these solutions were built for very specific conditions, like large teams, millions of users, and complex processes. For a small project, they are often unnecessary and only add extra complexity.

Simplicity often feels risky. Complexity feels safe, but only in the short term.

What Makes a System Unnecessarily Complex

Some problems in a system tend to repeat themselves and immediately signal unnecessary complexity. Here are the most common ones:

  • Too many layers. If adding a simple feature requires going through six classes and five interfaces, the system is too complicated.
  • Generic abstractions with no real use. For example, a class like AbstractBaseEntityManagerFactory exists, but only one part of the system actually uses it, and that part would work just fine without it.
  • Solving future scenarios. The system is designed for needs that don’t exist yet and may never appear.
  • Premature splitting. If a system is divided into ten separate services before anyone really understands what they do, unnecessary complexity is introduced.

If you recognize any of these in your own work, you are not alone. Most of us have been there. The important part is to consciously notice where complexity accumulates and choose a better direction.

Principles of Simple Architecture

These guidelines help you keep systems simple and easy to manage. They are not rigid rules that must be followed every time, but rather a compass to guide your decisions.

Prefer explicit over generic

Write a concrete solution for the current problem instead of creating a generic structure that claims to "handle everything." Explicit code is easy to read, while generic code often requires extra effort to understand.

# Generic – hard to follow
def process(entity, strategy, context):
    return strategy.execute(entity, context)

# Explicit – clear at first glance
def send_welcome_email(user):
    email_service.send(user.email, template="welcome")
Enter fullscreen mode Exit fullscreen mode

Solve today's problem first

Focus on solving today's problem well. If a new problem arises later, you will handle it with better understanding. Architecture designed for an uncertain future is often just unnecessarily complicated.

Add abstractions only when needed

Introduce abstractions only when they are truly necessary. They should emerge as a response to a real problem, not as a way to anticipate every possible future complication. If repeating code becomes difficult to manage, add an abstraction. Adding it too early creates unnecessary complexity and confusing code for both yourself and other developers.

Keep the system simple

The fewer concepts and rules a system has, the easier it is for everyone to navigate, including yourself six months from now. Every new layer, abstraction, or pattern adds cognitive load and makes the system harder to understand. Keep the system clear and minimize unnecessary parts to make working with the code simpler and faster.

Make the common case easy

The most frequent scenarios in the system should be simple to implement and understand. If a process represents the majority of system activity, it shouldn’t require five classes or complex configuration. More complex cases and exceptions can be more complicated, because they happen less often, and that is perfectly fine.

How to Tell if a Solution is Simple Enough

These principles provide a framework for thinking about system design. To judge whether a solution is truly simple, you can ask yourself a few practical questions. This simple checklist helps identify where unnecessary complexity is building up and what can still be simplified.

  • [ ] Can a new developer understand the core of the system within a few hours?
  • [ ] Can a new feature be added without major changes to unrelated parts of the system?
  • [ ] Does the code contain more business logic than “glue” (boilerplate, wiring, configuration)?
  • [ ] Can you explain the architecture clearly on a single A4 page?
  • [ ] Do you avoid giving every new teammate a 30-minute onboarding just to understand the basics of the system?

If you can answer “yes” to most of these, your solution is likely simple enough. If not, take a closer look at where complexity has accumulated and consider whether it is really necessary.

Practical Example: SQL vs. NewSQL

Imagine a small project building a web application. It needs an API and a database, and the team is deciding between a traditional SQL database and a NewSQL solution.

Why REST / Traditional SQL is Often the Better Choice

Traditional SQL databases, such as PostgreSQL, work well for most small to medium-sized applications. They are well-documented, have a large community, and offer tools that support developers.

  • Setting up a local development environment is easier and faster
  • Performance tuning and transaction management are predictable
  • New developers can quickly understand the system
  • Migrations and schema management are straightforward

Choosing PostgreSQL from the start allows the team to focus on the business logic and solve problems that actually exist without adding unnecessary complexity.

When NewSQL Makes Sense

NewSQL databases, like CockroachDB, TiDB, or PlanetScale, are powerful and scalable tools. Their power comes at a cost:

  • More complex local development environments
  • Different transaction and consistency models
  • A need to learn new concepts before solving the business problem
  • More complicated migrations and schema management

These databases make sense when a project truly needs horizontal scaling or must handle a very high number of concurrent users. For smaller projects, it is usually unnecessary to introduce them right away.

Key Takeaway

NewSQL is not a bad choice. It is the right solution for specific problems that most projects will never encounter. PostgreSQL can handle a massive amount of data and load, and when the time comes to scale, the team will have enough experience and data to decide whether NewSQL is truly needed.

Conclusion

Simplicity is not about laziness or avoiding thinking. On the contrary, it requires more discipline than adding extra layers, abstractions, or unnecessary generic solutions.

Good architecture is neither impressive nor complicated. It should fit the problem, the team, and the stage the project is in.

Every system that later appears elegant and simple was built through a series of deliberate decisions. These are decisions about what not to add, what not to solve in advance, and what can be safely addressed later.

Before making any major architectural decision, it’s worth asking yourself one simple question:

Are we making this simpler, or just more sophisticated?

👉 Explore practical tips for architectural decisions at Stack Compass Guide.

Top comments (2)

Collapse
 
lizzzzz profile image
Liz Acosta

Thank you for this! Makes me think of the KISS principle: Keep It Simple, Stupid. Memorable if a little snarky! 😅

How do you think a consistent practice of refactoring early and often works into this?

Collapse
 
mortylen profile image
mortylen

Good point, KISS fits really well here 🙂
I see refactoring as the thing that keeps code simple in practice. I usually do it in small steps, write something, then immediately look back and tweak it if it feels messy or harder than it should be.

I try to keep it balanced, just enough cleanup to keep things clear without overthinking every detail. Of course, if there’s no deadline, you can keep tweaking the code forever 😄