Back to blog

What I Learned Building a Full-Stack SaaS with Clean Architecture

2 min read

Clean Architecture sounds great in theory: dependencies point inward, business logic doesn't know about the database, and everything is testable in isolation. Applying it to a real project surfaced a few practical tradeoffs that the diagrams don't show.

The layers, briefly

The project split into four layers: Domain (entities, no dependencies), Application (use cases, depends only on Domain), Infrastructure (EF Core, Redis, SignalR — depends on Application), and Presentation (the API and React frontend).

Where it paid off

Swapping the notification mechanism from a simple polling endpoint to SignalR for real-time updates touched almost nothing outside the Infrastructure layer. Because the Application layer depended on an interface, not a concrete implementation, the use cases didn't need to change at all.

Where it added friction

Simple CRUD operations — the kind that would be a single database call in a smaller project — required a command, a handler, a repository interface, and a repository implementation. For a handful of endpoints, that ceremony didn't pay for itself. In hindsight, a lighter-weight pattern for truly trivial operations, reserving the full layered approach for genuinely complex business logic, would have saved time without sacrificing the benefits where they mattered.

Takeaway

Clean Architecture is a good default for a project expected to grow and change over time, but applying it uniformly — even to the simplest operations — trades short-term velocity for a consistency that isn't always worth the cost.