DEV Community

Samcorp
Samcorp

Posted on

Five Architecture Decisions We Regret

Five Architecture Decisions We Regret
Architecture decisions rarely look terrible when we make them.

Most begin with reasonable intentions:

  • “We'll need this when we scale.”
  • “This will keep everything flexible.”
  • “Microservices will make teams independent.”
  • “Let's create one shared layer so we don't duplicate code.”

Six months later, that “flexibility” can become the reason a simple feature touches twelve files and three services.

Looking back at common architecture mistakes, the painful ones usually aren't caused by choosing the wrong programming language or framework.

They're caused by making the system more complicated than the problem required.

Here are five decisions I would challenge much earlier.


1. Splitting Into Microservices Too Early

The original reasoning sounds convincing:

Independent deployments
+ Independent scaling
+ Clear ownership
+ Smaller services
= Better architecture
Enter fullscreen mode Exit fullscreen mode

But there's another side:

Service discovery
Network failures
Retries
Distributed tracing
Message queues
Deployment pipelines
Data consistency
Observability
Enter fullscreen mode Exit fullscreen mode

A function call that once looked like:

const user = getUser(id);
Enter fullscreen mode Exit fullscreen mode

can become:

Service A
   ↓ HTTP
Service B
   ↓ Database
   ↓ Event
Service C
Enter fullscreen mode Exit fullscreen mode

Now every boundary is also a failure boundary.

Microservices can be excellent when there is a real need for independent deployment, scaling, ownership, or isolation.

But adopting them before understanding the domain can create a distributed monolith: many deployable services that are still tightly coupled.

What we'd choose instead

Start with strong module boundaries.

Application
├── Users
├── Orders
├── Payments
├── Inventory
└── Notifications
Enter fullscreen mode Exit fullscreen mode

Keep extraction possible.

Then turn a module into a service when there is a measurable reason to do it.


2. Sharing One Database Across Everything

This one feels incredibly productive at first.

Three services need customer information?

Easy:

User Service ─────┐
Order Service ────┼──→ Shared Database
Billing Service ──┘
Enter fullscreen mode Exit fullscreen mode

No duplicate data.

No APIs.

No synchronization.

Then someone changes:

customers.status
Enter fullscreen mode Exit fullscreen mode

Suddenly three systems can break.

The database schema has quietly become the API.

That's one of the more expensive architecture mistakes because the coupling isn't always visible in application code.

What we'd do differently

Make ownership explicit.

Users Service
     ↓
Users Data

Orders Service
     ↓
Orders Data
Enter fullscreen mode Exit fullscreen mode

Other components should interact through deliberate interfaces rather than silently depending on internal tables.

This doesn't mean every application immediately needs ten databases.

It means:

Know who owns the data before everyone starts owning it.

Architecture planning and data ownership are exactly the kinds of decisions worth examining during software architecture consulting before implementation makes those boundaries expensive to change.


3. Building Abstractions Before We Needed Them

Developers dislike duplication.

Sometimes too much.

Suppose two payment providers initially look similar:

processStripePayment()
processPayPalPayment()
Enter fullscreen mode Exit fullscreen mode

So we immediately design:

AbstractPaymentProvider
        ↓
PaymentStrategyFactory
        ↓
PaymentProviderAdapter
        ↓
PaymentExecutionContext
        ↓
ConcreteProvider
Enter fullscreen mode Exit fullscreen mode

Beautiful diagram.

Until the third provider behaves completely differently.

Now we're fighting our abstraction instead of solving the business problem.

What we'd do instead

Accept small amounts of duplication while the domain is still becoming clear.

Three similar implementations can teach us more than one premature abstraction.

The better sequence is:

Build
  ↓
Observe repetition
  ↓
Understand differences
  ↓
Extract abstraction
Enter fullscreen mode Exit fullscreen mode

Not:

Predict future
  ↓
Build abstraction
  ↓
Hope reality agrees
Enter fullscreen mode Exit fullscreen mode

Good architecture removes unnecessary complexity.

It doesn't hide complexity behind more interfaces.


4. Designing for Imaginary Scale

“We should design this for 10 million users.”

How many users do we currently have?

“About 8,000.”

Planning for growth is responsible.

Designing every subsystem around hypothetical extreme scale is different.

It can lead to:

Kafka
Redis
Elasticsearch
Multiple databases
Kubernetes
Microservices
Complex caching
Event-driven everything
Enter fullscreen mode Exit fullscreen mode

before the product actually needs them.

Now developers spend time operating architecture instead of improving the product.

What we'd do differently

Design for:

Current load
+
Expected near-term growth
+
A reasonable safety margin
Enter fullscreen mode Exit fullscreen mode

Then identify the parts that must remain replaceable.

For example:

Today

App
 ↓
PostgreSQL

Later

App
 ├── Cache
 ├── Read replicas
 ├── Search
 └── Queue
Enter fullscreen mode Exit fullscreen mode

A simple architecture with clean boundaries can evolve surprisingly far.

The goal isn't to predict every future requirement.

The goal is to avoid making future change unnecessarily expensive.

That's also why maintainability and controlled growth matter in custom software development: scalability should be designed around actual product requirements rather than added as infrastructure for its own sake.


5. Treating Architecture Decisions as Permanent

This may be the biggest regret.

We once tended to think:

Choose architecture
       ↓
Build system
       ↓
Architecture finished
Enter fullscreen mode Exit fullscreen mode

Real systems behave more like:

Choose
  ↓
Build
  ↓
Learn
  ↓
Measure
  ↓
Change
  ↓
Repeat
Enter fullscreen mode Exit fullscreen mode

The architecture that works for:

5 developers
10k users
1 product
Enter fullscreen mode Exit fullscreen mode

may be wrong for:

50 developers
2M users
8 product lines
Enter fullscreen mode Exit fullscreen mode

That doesn't necessarily mean the original architecture failed.

The context changed.

The real mistake is refusing to reconsider the decision.


Architecture Decision Records Help

One practice I would adopt earlier is recording important decisions.

An ADR doesn't need to be complicated.

Decision:
Use PostgreSQL as the primary datastore.

Context:
Small team, transactional workload,
no demonstrated need for multiple databases.

Why:
Operational simplicity and team experience.

Trade-offs:
Some future workloads may require
specialized storage.

Revisit when:
Database becomes a demonstrated bottleneck.
Enter fullscreen mode Exit fullscreen mode

Now future developers know why something exists.

Without that context, architecture can become archaeology.


The Pattern Behind These Mistakes

These five decisions look different:

Premature microservices
Shared databases
Premature abstractions
Imaginary scale
Permanent decisions
Enter fullscreen mode Exit fullscreen mode

But they have something in common.

We optimized for a future we didn't understand yet.

A better principle is:

Make today's architecture simple while keeping tomorrow's important changes possible.

That doesn't mean ignoring scalability.

It means earning complexity.


A Better Architecture Checklist

Before introducing a major architectural component, ask:

□ What problem does this solve today?

□ Do we have evidence that the problem exists?

□ What new complexity does it introduce?

□ Who will operate it?

□ What happens when it fails?

□ Does it create additional coupling?

□ Can we postpone this decision?

□ How difficult will it be to reverse?

□ What would trigger us to reconsider it?
Enter fullscreen mode Exit fullscreen mode

If we can't answer those questions, the architecture probably isn't ready.


Final Takeaway

The architecture mistakes I worry about most aren't:

Wrong framework
Wrong database
Wrong cloud
Enter fullscreen mode Exit fullscreen mode

They're decisions that create unnecessary coupling and complexity.

A useful architecture doesn't need to predict the next five years perfectly.

It needs to make today's system understandable and tomorrow's changes possible.

So instead of asking:

“What is the most scalable architecture we can build?”

I'd ask:

“What is the simplest architecture that satisfies our current constraints without trapping us later?”

That question tends to produce much healthier systems.

And usually fewer regrets.

Top comments (0)