1. The solution folder
the top of the tree: everything hangs off this one folderInside the solution folder sit six folders as siblings: none of them is nested inside another. That flatness is the whole point of an n-tier layout: every folder holds one job, and its name tells you what it is allowed to know about.
-
project solution folder
solution folder
-
webhost
The front door: it hosts the HTTP endpoints and starts the application.
-
logic
The business rules. Nothing here knows a web request exists.
-
data
Talks to the store: the entities, the queries, the code that saves them.
-
adapter
The translator: it turns an outside service into a shape logic already understands.
-
shared
The small, boring things more than one folder needs: contracts, constants, helpers.
-
initializer
Startup wiring that runs once: configuration, dependencies, schema setup.
-
webhost
2. webhost
the front door: hosts the HTTP endpoints and starts the application
This folder is the front door of the application. Every request from the
outside world arrives here, and every check that has to pass before the application will
answer it is made here. Nothing reaches the folders behind webhost until this
layer says it may.
- webhost
- Authentication: who the caller is. A request proves it comes from a known user or service before any real work starts.
- Authorization: what that caller is allowed to do. Being who you say you are is not the same as being allowed to do this.
- Validation of data being stored: the payload is checked at the door: required fields present, types right, values inside the allowed range. Whatever fails here never reaches the store.
- Infrastructure configuration: the config files this layer needs to run: connection strings, endpoints, keys, and the settings that describe the host itself. The machine-specific ones live here precisely so they stay out of source control.
That is the boundary in one line: if a check is about trusting a stranger, it belongs in
webhost; if it is about the business, it belongs in logic.
webhost is the only folder allowed to know that HTTP exists.
3. logic
the business rules: nothing here knows a web request existsThe rulebook: what has to be true, in what order, and who may change it. Everything in this folder decides, and anything it needs from the outside arrives through a contract it does not implement itself, which is why the patterns that shape decisions rather than plumbing live here. These five turn up again and again.
- logic
- CQRS: a command changes something and reports only how it went; a query answers a question and changes nothing. Keeping the two apart is what lets the read side be flattened and optimized on its own, without disturbing the rules that guard the writes.
- Factories: the one place that knows how a valid object comes into being. The rule "an order always has at least one line" is stated once, in the factory, instead of being remembered by every caller that builds the object for itself.
- Strategy: a family of interchangeable rules behind one interface: pricing, tax, scheduling, whatever the business keeps changing its mind about. The choice is made once, from configuration or data, rather than as a chain of conditions that grows with every variant.
- Builder: for objects assembled step by step, with parts that are optional. Each call adds one piece, the last call hands back the finished object, and the checking happens along the way; nothing half-built ever escapes.
- Decorator: a wrapper with the same interface as the thing it wraps, adding one concern (logging, caching, retries, a permission check) without editing the original. Because the interface does not change, decorators stack, and the class underneath never learns that the concern exists.
Notice what the five have in common: none of them is about infrastructure. Each one lets
logic grow by adding a class rather than editing one that is already trusted, and
each can be exercised in a test with plain objects and no server in sight. Anything that needs
a socket, a vendor SDK or a table belongs in webhost, adapter or
data instead.
4. data
talks to the store: the entities, the queries, the code that saves them
The only folder allowed to know what a store is. It owns the tables, the queries and
the transactions, and everyone else asks it for things and gets objects back; logic
never learns whether the answer came from SQL, a document store or a file on disk.
- data
-
Entities and mappings: the classes that mirror what is stored, and the
mapping that ties each one to a table or a collection. Records, not rulebooks: behaviour
belongs one folder up, in
logic. - Reads: one method per question the application actually asks, written where it can be tuned against the store: the everyday lookup as a repository call, the screen that must not be slow as a query written by hand.
- Writes and transactions: saving, updating, deleting, and the unit of work that decides when those changes commit together. A half-saved order is the failure this layer exists to prevent.
- The one door to the store: callers depend on a repository interface rather than on the store itself, so the query stays next to the thing it queries and the rest of the application can be handed a stand-in instead.
What it must never do is have an opinion. A rule like "an order needs at least one line" is a
decision, and decisions live in logic; data records decisions and
answers questions about them. Holding that boundary is what keeps the store swappable.
5. adapter
the translator: turns an outside service into a shape logic already understands
The outside world, behind a wall. A payment provider, a mail relay, a tax service: each one
arrives here and is translated into the shape logic already speaks, so no folder
above has to learn a vendor's name, its error codes, or what it calls a date.
- adapter
- One adapter per outside service: the payment provider, the mail relay, the tax service: each gets its own translation code, so a vendor upgrade, or a vendor replacement, lands in exactly one place.
-
Translation in both directions: the vendor's request and response types on
one side, the contract
logicdefined on the other, and the mapping written out in between where it can be read and tested. -
Errors in the caller's language: a timeout, a rate limit and a declined
card are three different sentences for
logic, not three different vendor payloads to decode. - The awkwardness of the wire: retries, backoff, paging, rate limits and timeouts are handled here. They are facts about the connection, not rules about the business.
-
The seam that keeps tests honest: because
logicdepends on the interface rather than on this folder, a test can put a stub in its place: no network, no sandbox, no flakiness.
This is the anti-corruption layer, and the point of it is a direction of travel: outside shapes
move inward and are translated on the way, never carried in raw. If a rule cannot be tested
without a network connection, something from out here has leaked into logic.
6. shared
the small, boring things more than one folder needs: contracts, constants, helpersThe smallest folder, and the one that has to stay the smallest. It holds the vocabulary the other folders agree on: the contracts, types and constants that have to mean the same thing in more than one place.
- shared
-
Contracts: the interfaces one folder implements and another consumes. This
is what lets
logicask for data without knowing thatdataexists, and letsadaptersit behind a namelogicchose for itself. -
The words that cross boundaries: the request and result shapes that travel
from
webhostdown todataand back, named once so that every folder means the same thing by them. - Constants and enums: statuses, roles, codes and setting keys. A string that has to match in three folders should be typed once, where all three can see it.
- Small helpers: the extension methods and one-line utilities with no state, no I/O and no opinion: the things that are easier to share than to rewrite in triplicate.
The danger of a folder with this name is that everything eventually lands in it. Two questions keep it honest: does more than one folder need it, and can it be written without depending on any of them? A type that fails either question belongs where it is actually used.
7. initializer
startup wiring that runs once: configuration, dependencies, schema setupThe only folder that sees the whole application at once. Every piece the other folders ask for is chosen, configured and handed over here (once, at startup, before the first request arrives), and then this folder gets out of the way.
- initializer
- The composition root: the name for what happens here: every interface is matched to the class that implements it, in one place, at the edge of the application. Nothing deeper down builds its own dependencies.
- Configuration: reading the settings the host was handed and binding them into shapes the rest of the application can use, so that no other folder parses a config file.
- Schema and migrations: the versioned scripts that shape the store, and the step that applies them: at startup, or as its own deployment action, decided here and once. A change to a table has to be repeatable on every machine, including the one that has never seen this database.
- Work that runs once: seeding, warm-up, and the background worker that is supposed to start with the application.
The rule that keeps this folder safe is a direction: initializer may know about
every other folder, and no other folder has a reason to know about it. It is the one place
allowed to see the whole picture, which is exactly why no business rule is allowed to live
here.