System Design#Architecture#System Design#Mental Models#Best Practices

How I Think About Architecture Before Writing a Single Line of Code

The mental models, boundary diagrams, and failure mode analyses I use to clarify system topology before opening the editor.

Arya
Arya
Full-Stack Product Builder & Engineer
Published on
•
7 min read
How I Think About Architecture Before Writing a Single Line of Code
Share this dispatch:

The most dangerous moment in software development is the first hour of a new project. You are energized, the repository is spotless, and the urge to jump directly into writing routes, components, and schema files is intoxicating.

Resist that urge.

Whenever I've jumped directly into code without mapping system topology first, I've had to throw away significant chunks of work within two weeks. Architecture isn't about creating abstract UML diagrams to satisfy corporate processes. It is about answering five concrete questions before touching the keyboard.


1. Where Does the Source of Truth Live?

In every system, data consistency breaks down when multiple services believe they own the canonical state.

Before writing a single entity model, clearly designate:

  • Which service is the Writer of Record?
  • Are downstream consumers reading synchronous replicas or subscribing to eventual consistency event streams?
  • What happens if a write succeeds in Database A but fails in Cache B?
text
flowchart LR
    Client[Client App] --> API[API Gateway]
    API --> Primary[(PostgreSQL Primary: Source of Truth)]
    Primary -.->|CDC / Debezium| Kafka[Event Stream]
    Kafka -.-> Search[(Elasticsearch Read Model)]
    Kafka -.-> Analytics[(BigQuery Warehouse)]

When you document this flow explicitly, you immediately prevent distributed dual-write inconsistencies.


2. Defining Bounded Contexts

One of the most common pitfalls in growing web applications is the "god model" — a single User or Account type that accumulates fields from billing, authentication, profile preferences, social feeds, and analytics until nobody dares refactor it.

Instead, slice entities by domain context:

  • AuthUser: Contains credentials, MFA secrets, sessions.
  • Customer: Contains billing tokens, Stripe customer IDs, invoices.
  • PublicProfile: Contains username, bio, display avatar.

Each context only cares about its specific domain, keeping models focused, decoupled, and easily testable.


3. The 3 AM Failure Mode Test

Ask yourself this simple question:

"If this external dependency or microservice goes down at 3:00 AM on Sunday, what happens to the user experience?"

  • If the email verification service fails, does the user's registration freeze, or does it queue an asynchronous retry job and log them into a restricted sandbox?
  • If the payment webhook drops, how do we reconcile lost transactions on Monday morning?

Designing systems with fallback queues and idempotent processors ensures that localized failures never metastasize into complete system outages.


Conclusion

Good architecture is invisible when things run smoothly. By spending an hour mapping data flows, identifying single sources of truth, and defining failure modes upfront, you save weeks of painful rewrites down the road.

Share this dispatch:
Further Reading

Related Dispatches

View all stories →