Skip to main content

Drawing the Boundaries

Share

Share this page

Pass it to someone who needs it.

Star on GitHub

Key takeaway: Make dependencies point one way, inward

You named the five pieces of your app. Inside any one of them, though, the code is usually a single tangle: a screen that runs its own database query, a pricing rule buried in a button handler. When everything can touch everything, one change ripples everywhere and your agent cannot tell you what a file is for. This chapter draws the boundaries inside a piece: three layers, with one clean rule about which way they depend.

3.4.1Presentation, logic, data

Slice any part of your app into three layers, each with one job. The presentation layer is what the user sees and touches: screens, buttons, the shape of a form. The business logic is the rules that make it your app and not a generic one: what a discount is, who may cancel an order, when a booking is valid. The data layer is storage: reading and writing rows, talking to the database or a file.

The point of the split is that each layer changes for its own reason. You redesign the screen without touching a pricing rule, and you swap the database without rewriting what a discount means. Reaching your data through one stable interface is called the repository pattern, and it is what lets the underlying database change without business logic ever noticing.

3.4.2How modules talk

A layer offers the one above it a short list of calls and hides everything else behind them. The presentation layer calls placeOrder(cart), never sees the query the logic runs underneath, and never skips a layer to reach the one below it.

Keep that boundary narrow on purpose. If the screen only ever asks for placeOrder, you can rewrite how orders are placed without opening a single screen file.

The screen calls one named method; the query underneath stays hidden.

3.4.3Dependencies point one way

Every dependency points one direction: inward, toward the stable core. The business logic is that core. The presentation and data layers depend on it, it depends on neither, and it must never import (pull in the code of) a screen or a database driver.

Presentation Data (screens, forms) (database, files) | | | depends on | depends on v v Business logic = the stable core (the rules that make it your app)

Both edges point in. Screens and storage are swappable details, and the rules at the center stay put. This inward-pointing, framework-independent shape is called clean architecture (also known as hexagonal architecture). Reverse an arrow, so the core imports the UI, and you glue the two together. The part that should outlive every redesign is now tied to the part that changes most.

3.4.4Don't let a layer leak

A layer leak is a call that jumps the boundary: the presentation layer running a query straight against the database, or a business rule written into a screen.

# Leak: the screen reaches into the data layer button.onClick = () => db.query("UPDATE orders SET status='paid' ...") # Fixed: the screen calls the logic; logic owns the rule button.onClick = () => payForOrder(orderId)

The first line works today and rots tomorrow. The rule for paying an order now lives in a click handler, so the next screen that pays an order either duplicates it or gets it wrong. Route the call through the layer that owns the rule and the leak closes.

Sometimes a layer does need to hand data across the boundary. Give the next layer a DTO (data transfer object): a small plain shape built for the trip, instead of your internal objects. That keeps the layers decoupled.

This prompt has your agent sort your code into layers and check the arrows:

Ready prompt
Act as a senior engineer auditing my architecture. Read my rules file, my spec, and my folder layout first, so you sort the code I actually have against the boundaries I already chose. Sort my code into three layers: presentation (the UI), business logic (the rules), and data (storage), naming the layer of each file or module. Then check the dependency direction: the business logic must not import the UI or the database library, and the UI must not run queries or hold business rules. Flag every place a layer reaches past its neighbor or points the wrong way, and give the one move that fixes each. Where the code contradicts my recorded structure, say so rather than quietly picking a side. Then write the layer rule and its allowed import direction into my rules file, so new code follows it. If you need the full reasoning behind this step, read https://zalt.me/guides/vibe-coding/architect/boundaries-and-layers My app's layers today:

Do this now: paste the prompt so your agent maps your files onto the three layers and flags every leak, then fix the one that points the wrong way.

Mahmoud Zalt

Mahmoud Zalt

Software engineer, 16+ yrs · built Sistava.com in 3 months, idea to production, using these methods

Resources
Star on GitHubContribute
Donate

Support my work

A small tip keeps the free work coming.

© 2026 Mahmoud Zalt. Free to read, not to republish.
Copyright & license