May 12, 2026
8 min read
Hexagonal Architecture Without Ceremony
How to separate UI, use cases, and infrastructure in a web app without turning every change into another layer.
Leer en espanolThe real problem is where decisions live
Most web apps become hard to change not because they lack a famous architecture, but because important logic ends up scattered across controllers, components, hooks, ORM models, and utility files. The system still compiles, but it stops being legible.
The useful version of hexagonal architecture is not about drawing six folders and introducing interfaces everywhere. It is about making one thing explicit: what part of the system makes business decisions, what part talks to the outside world, and how both connect without leaking into each other.
Put use cases and ports in the center
In a pragmatic web application, the center is usually a small set of use cases: create a workspace, invite a member, publish an article, recalculate a report. Each use case receives input, applies domain rules, and delegates external effects through ports.
A port only makes sense when it protects a real boundary: persistence, email, jobs, payments, cache, search, storage. If you need to swap providers later or test without the network, the port is already paying rent.
A small use case with explicit dependencies.
type PublishArticleInput = {
articleId: string;
publishedAt: Date;
};
interface ArticleRepository {
findDraftById(id: string): Promise<Article | null>;
save(article: Article): Promise<void>;
}
export async function publishArticle(
input: PublishArticleInput,
deps: { articles: ArticleRepository },
) {
const article = await deps.articles.findDraftById(input.articleId);
if (!article) throw new Error('Draft not found');
article.publish(input.publishedAt);
await deps.articles.save(article);
} Keep adapters thin
Adapters should be boring. A route handler translates HTTP into use-case input. A repository maps persistence models to domain models. A UI component renders state and emits intent. As soon as an adapter starts making business decisions, the edge of the system has taken over.
This also matters on the frontend. A form should not decide which state transitions are valid for an order or subscription. It should send intent, receive a result, and render feedback. The domain is still the domain even when part of the app runs in the browser.
- Use cases know rules and sequence.
- Adapters know protocols and formats.
- Infrastructure knows providers, SDKs, and operational details.
When it is worth it
You do not need a full hexagonal setup for a landing page with one form and two tables. But once the product has meaningful workflows, side effects, permissions, and rules that change often, separating decisions from the framework stops being purity and starts being a speed advantage.
The right signal is not repository size. It is how often the team has to touch five different places to change a single rule. When that is already happening, hexagonal architecture stops being an aspiration and becomes a tool.
More articles
Back to articlesPragmatic DDD for Web Products
Ubiquitous language, bounded contexts, and transaction boundaries without turning domain modeling into bureaucracy.
April 29, 2026
9 min read
Spec-Driven Development for Teams
Specify behavior before implementation to align frontend, backend, and product with less rework.
April 10, 2026
8 min read
ADRs That Keep Systems Sane
How to document technical decisions with context, tradeoffs, and consequences without writing an endless wiki.
March 21, 2026
7 min read