Application Architecture

Application Architecture Overview — The Internal Rules

Application Architecture Overview — The Internal Rules

About this article

This article is the entry point of the “Application Architecture” category in the Architecture Crash Course for the Generative-AI Era series, surveying the domain.

Before debating monolith vs microservices, if the code inside the app isn’t being written in a unified style, no overall structure works in practice. This article surveys what’s different from software architecture, the rules covering class design, naming, domain logic, and error handling, and the goal: “the whole team writes the same code.”

What is application architecture, anyway?

Picture a team kitchen. When five chefs share the same workspace, without rules like “knives go here,” “salt to this standard,” “plate in this order,” every dish comes out tasting and looking different.

Application architecture is the set of internal rules that lets the whole team write code the same way. Class design, naming conventions, where domain logic lives, error-handling policies — it aligns the “quality bar” of the code.

Without application architecture, each team member writes differently, and “is this style OK?” resurfaces at every review, dragging productivity down continuously.

Rules for “how to write the code”

Sometimes treated as part of software architecture, but folding it in there throws off the granularity balance, so it’s better to separate it. Decisions here are heavily influenced by software architecture choices, so consistency between the two matters.

Projects with weak application architecture get scattered code styles across the team, and “is this style OK?” debates flare up at every review. Boring conventions, enforced thoroughly, are what quietly sustain daily productivity and long-term maintainability.

Software vs Application

flowchart TB
    subgraph SW["Software Architecture (outer skeleton)"]
        SW1[Monolith vs Microservices]
        SW2[Language selection]
        SW3[API design]
        SW4[DB selection]
    end
    subgraph APP["Application Architecture (inner rules)"]
        APP1[Class design / SOLID]
        APP2[Domain logic]
        APP3[Naming]
        APP4[Error handling]
    end
    SW -->|provides assumptions| APP
    SW -.|One-way Door<br/>hard to change|.- L1[Decided at requirements]
    APP -.|Two-way Door<br/>easier to change|.- L2[Decided at implementation]
    classDef sw fill:#dbeafe,stroke:#2563eb;
    classDef app fill:#fef3c7,stroke:#d97706;
    class SW,SW1,SW2,SW3,SW4 sw;
    class APP,APP1,APP2,APP3,APP4 app;

The names sound similar, but software architecture and application architecture are designs at fundamentally different granularities. Software architecture decides the app’s overall skeleton — monolith or microservices, the language, API design, database selection — the outer framework. Application architecture decides, within that framework, how the code itself gets written: class splits, naming conventions, how domain logic is expressed.

The reason for separating them is that the responsible decision-maker and the cost of change are very different. Software architecture has a lot of One-way Door decisions architects deliberate on during requirements. Application architecture is settled at implementation time, led by tech leads / lead engineers, and is mostly Two-way Door — relatively easy to redo.

AspectSoftware ArchitectureApplication Architecture
GranularityApp’s external structureApp’s internal structure
ExamplesMonolith / microservices / language / API designClass design / naming / domain logic
When decidedProject planning to requirementsDesign through implementation
Ease of changeVery hard (One-way Door)Relatively easy (Two-way Door)

In housing terms: software architecture is the floor plan (how many rooms, where the entrance is, whether plumbing is centralized); application architecture is “furniture layout, outlet positions, wallpaper patterns.” Changing the floor plan means knocking down walls; furniture moves easily. And day-to-day livability is decided by furniture, not the floor plan.

Why separate them

Mixing the two means the abstraction level of every discussion is uneven and meetings stop converging. You’re discussing “should we adopt microservices?” when “how should the classes inside split?” cuts in, or you’re settling naming conventions when “actually, the language choice…” drags everyone backward. “Aligning the granularity of decisions” is a precondition for moving design discussions forward.

This is exactly where early-career engineers get stuck — they assume “if I get class design right, the architecture gets better.” In reality, when the outer decisions about microservices or DB separation are wrong, no amount of clean class structure recovers it. Conversely, an architect who decides only the outside and leaves the inside open ends up with each team writing differently — eventually unmaintainable. The starting point is to run both layers in parallel as separate concerns.

  • The software side gets dragged into fine implementation rules and the big-picture design wobbles.
  • The application side stays vague and each team’s implementation drifts during development.

A healthy split: “architect” vs “lead engineer / tech lead”, each owning a different layer’s decisions.

What you must decide — what’s your project’s answer?

For each of the following, articulate your project’s answer in 1-2 sentences. Leaving them ambiguous now will always come back as “why did we decide that?” later.

Internal structure / domain design

ItemExamples
Class compositionSRP / inheritance / composition / mixins
Domain logicProcedural (Transaction Script) / Rich Domain (DDD — Domain-Driven Design)
Error-handling policyExceptions / Result types / Error boundary design
Naming and conceptual modelUbiquitous language / glossary maintenance
Code conventionsFormatter / linter / review policy

Code organization / test / observability

ItemExamples
Directory layoutFeature-based / Layer-based
Dependency directionNo cycles / Clean Architecture
Test strategyUnit / integration / E2E balance
Validation designWhich layer / how to reuse
Logging / auditLog granularity / correlation IDs / PII (Personally Identifiable Information) masking

Selection by case

The right amount of effort to put into application architecture varies a lot by project. Going for strict design across the board is over-engineering; leaving it alone collapses maintainability. Allocate investment by situation.

CaseWhere to focus
New CRUD-centric appNaming + linter/formatter enforcement. Procedural domain logic is fine.
Complex business logic (insurance, finance, accounting)Heavy domain model (DDD). Prioritize aligning code with business vocabulary (ubiquitous language).
Multi-team microservicesUnify directory layout, API contracts, error conventions across teams.
Legacy refactoringRespect existing naming and structure; apply new conventions only to newly added pieces.

Bringing DDD into a new CRUD app is the canonical over-engineering case — simpler procedural code is more maintainable. In a complex domain, the procedural style explodes in conditional branches and becomes unmaintainable, so domain-model investment pays off. For legacy refactors, “respect for existing conventions” comes first; applying new rules in one big sweep is a failure pattern.

The two main domain-logic styles

StyleTraitFits
Procedural (Transaction Script)Just function-shaped flow. Simpler than DDD.Small / CRUD-centric / short-term
Rich Domain (DDD)Business logic concentrated inside domain objectsLarge / complex business logic / long-term ops

“Start small, migrate to rich domain when it gets complex” is also a normal path. Avoiding over-engineering means not starting with DDD.

The Clean Architecture idea

The principle is to “point dependencies inward” so that changes in the outer layers (DB, UI, framework) don’t ripple into the business logic.

flowchart TB
    subgraph OUT["Outer (changes often)"]
        FW["Framework / DB / UI"]
        subgraph ADAPT["Interface adapter layer"]
            subgraph USE["Use cases"]
                ENT["Entities<br/>(innermost = stable)"]
            end
        end
    end
    FW -->|dependencies only point inward| ADAPT
    ADAPT --> USE
    USE --> ENT
    classDef out fill:#fee2e2,stroke:#dc2626;
    classDef adapt fill:#fef3c7,stroke:#d97706;
    classDef use fill:#dbeafe,stroke:#2563eb;
    classDef ent fill:#dcfce7,stroke:#16a34a,stroke-width:2px;
    class OUT,FW out;
    class ADAPT adapt;
    class USE use;
    class ENT ent;

Scale × business-complexity ladder

The investment needed in application architecture changes with codebase size and business complexity. Conclusion up front: up to 10K LOC, plain layered is enough. Cross 50K LOC and Hexagonal pays off. Only at the 200K-LOC level for finance/insurance does serious DDD pay back. As of 2026 this path has the best ROI.

CodebaseComplexityRecommended patternDomain expressionFile-line target
Up to 10K LOCCRUD-centricLayered / plain MVCTransaction Script<=300 lines
Up to 50K LOCModerateLayered or HexagonalTS + Value Object<=300 lines
Up to 200K LOCComplex (e-commerce, logistics)Hexagonal / CleanDomain Model (light DDD)<=300 lines
200K LOC+Highly complex (finance, insurance)Clean + tactical DDDFull DDD (Aggregates, etc.)<=300 lines

”<=300 lines/file, <=50 lines/method, cyclomatic complexity 10” is the quantitative guardrail many projects use. Crossing those lines is the signal to split. The modern approach detects them automatically with ESLint or SonarQube. “Apply Clean’s 4 layers to a new CRUD screen” is canonical over-engineering.

Patterns escalate by scale. DDD on an MVP is excessive; procedural on a giant project breaks down.

Architecture-level traps

The forbidden moves at the application-architecture level:

Forbidden moveWhy
Growing god classes / god modules”While we’re here…” accumulates into 3000-line files; splitting later is essentially a rewrite.
Anemic domain modelDDD shape only — all logic ends up in service layers.
Leaving cyclic dependenciesA->B->A is a guaranteed bug-breeding ground; detect with ESLint.
Names like Util / Manager / HelperNames that don’t state responsibility; if responsibility is clear, use a concrete name.
Inheritance trees 4+ deepLSP violations, ripple effects, hard to understand.
Swallowing errors (catch { })Silent failure breeding ground; production-blind for cause.
Naming conventions different per teamThe User / Member / Account / Customer problem.
Retries without idempotencyDouble-payment, double-registration classic; Idempotency-Key required.
Comments saying WHATDon’t write what code already says — write WHY.
Linter / Formatter not enforced in CI, only in READMEConventions that aren’t enforced may as well not exist.
Assuming application design = class diagramsClass diagrams are one artifact among many. Boring rules — naming, error policy, test strategy — drive daily productivity more.
Adding error handling after the factError design always has gaps when bolted on. Which layer catches what is a first-class decision made alongside class design.

Application architecture is decided by enforcement of conventions. Mechanical enforcement via tools is the only solution.

AI decision axes

AI-era favorableAI-era unfavorable
Strict types, schemas, interface definitionsDynamic typing, implicit conventions
Unified directory layout and namingTribal style, per-team
Pure functions / side-effect separation (Hexagonal etc.)Procedural code rich in side effects
Single-responsibility small classes / functionsGiant god classes / long methods

Selection priority:

  1. Be consistent with software architecture (respect outer constraints).
  2. Prioritize internal consistency (enforcement over doctrine).
  3. Start small, harden as needed (DDD-first is over-engineering).
  4. Express types, conventions, structure in code (the AI-era requirement).

Summary

This article served as the entry point of the “Application Architecture” category, surveying the big picture of how to decide the internal rules.

Enforcement of conventions over choice of doctrine; gradual growth over over-engineering; design as a constraint on AI, not just for humans. Those are the three cores of application architecture.

The next articles dive deeper, starting with class design (SOLID, inheritance vs composition).

Back to series TOC -> ‘Architecture Crash Course for the Generative-AI Era’: How to Read This Book

I hope you’ll read the next article as well.