Skip to content
System Design 4 min read

How to Structure a Full Stack Application for Maintainability

Maintainability is not a folder structure. It is where decisions live, which direction dependencies point, and how much you have to understand to change one thing.

By

Starter draft — edit to match the conventions you want to advocate.

Most "unmaintainable" codebases I have worked in were not badly written. They were codebases where you could not change one thing without understanding five others.

The real measure#

Maintainability is how much context a change requires. A codebase is maintainable when a developer who has never seen it can:

  1. Find where a given behaviour lives.
  2. Change it without reading unrelated code.
  3. Know whether the change broke something.

Everything below serves those three properties.

Dependencies point one way#

The most valuable structural rule is also the simplest: inner layers must not know about outer ones.

txt
HTTP / UI      →  knows about the application layer
Application    →  knows about the domain
Domain         →  knows about nothing
Infrastructure →  implements interfaces the inner layers declare

The test is mechanical: can your business rules be unit-tested without booting the framework or touching a database? If not, the dependency arrows are pointing the wrong way.

Organise by feature, not by technical role#

The folder layout every framework tutorial teaches:

txt
controllers/   models/   services/   views/

This scatters one feature across four directories. At small scale it is fine. At medium scale, "add a field to orders" means opening files in four places.

txt
features/
  orders/
    OrderController.php
    OrderService.php
    OrderRepository.php
    requests/
    resources/
  customers/
  reporting/

Now a feature is a folder. You can read it, change it, and — when it eventually needs extracting — move it.

Put business rules in one place#

The most common cause of inconsistent behaviour is the same rule implemented three times: once in the UI for immediate feedback, once in the controller, once in a database constraint. They drift, and then the system does different things depending on the route in.

Decide where each rule lives, and let the other layers ask:

  • The rule lives in the domain or application layer.
  • The API enforces it.
  • The UI renders from the API's answer rather than re-deciding.

Keep the data model and the API model separate#

Returning ORM entities directly from endpoints couples your public contract to your schema. Rename a column and you have broken every client.

DTOs feel like duplication until the first schema change, at which point they are the only reason the change is contained.

Configuration and content are not code#

Anything that changes for business reasons rather than engineering ones — copy, thresholds, feature flags, lists of options — should live where it can change without a code review of application logic.

This site is built that way: every piece of editorial content is a data file, and no Blade template needs touching to update a biography or add a project.

Make the test boundary explicit#

You do not need exhaustive coverage. You need to know what is covered.

  • Domain logic: unit tests. Fast, no I/O, run constantly.
  • Application/API: integration tests over real endpoints. Fewer, slower, high value.
  • UI: a handful of end-to-end tests over the paths that must not break.

An inverted pyramid — mostly slow end-to-end tests — produces a suite nobody runs.

Signals that structure is degrading#

  • A one-line change requires edits in four files.
  • Nobody can say where a rule is enforced.
  • New developers ask "where should this go?" and get different answers.
  • Shared helpers or utils files that everything imports.
  • Circular dependencies between modules.

Not worth it#

Balance matters. Over-structuring a small application is its own maintenance problem:

  • An interface with exactly one implementation, added "for flexibility".
  • Four layers of indirection for a CRUD screen.
  • Micro-services before a single team can agree on the domain.

The honest heuristic: add structure when the pain is real, not when you anticipate it.

Share this article LinkedIn X

Keep reading

Related articles

All articles
.NET 4 min read

React + .NET Web API: A Practical Architecture

How I structure a React front end against a .NET Web API — project layout, the API contract, authentication and the boundaries that keep both sides in...

#React #.NET #Web API
↑ ↓ to navigate · ↵ to open · esc to close Full search