DEV Community

Cover image for Architecture in Single Page Applications — The meal you are always ready to consume
Zlatoslav Marchev
Zlatoslav Marchev

Posted on • Originally published at Medium

Architecture in Single Page Applications — The meal you are always ready to consume

🍽️ Spoiler Alert I: I hope you like Italian food — because if not, the following analogy might be tougher to swallow than a day-old focaccia.

We will dive in some cooking/architecting details. But before that, let’s clarify what I mean by Layered Architecture and Clean Architecture — both are ways to structure software so that responsibilities are separated, code is maintainable, and most importantly the system is resilient to change.

🔹Layered Architecture

This is a traditional and widely used model where software is divided into horizontal layers (e.g., UI → business logic → data). Each layer depends on the one below it, and responsibilities are vertically stacked. It’s straightforward, but can become rigid over time.

🔹Clean Architecture

Proposed by Uncle Bob (Robert C. Martin), Clean Architecture builds on the idea of layers — but with strict rules for dependencies and clear separation of concerns. It organizes code around use cases and business rules, not frameworks or tools, making it technology-agnostic and easier to adapt over time.

⚠️ Spoiler Alert I: Clean architecture is Layered architecture with added rules

To better understand the details of layered architecture and clean architecture let’s imagine our codebase is a dish. The layered architecture is like lasagna — strict and pre-layered. The clean architecture is like a pizza — modular and assembled.

👨‍🍳 Why the Cooking Analogy?

Because just like in the kitchen, the secret of creating a great app (or meal), is in how we combine our ingredients.

Now… let’s bring the food to the table. 🍕

🍲 The Lasagna (Layered Architecture)

Imagine a perfectly baked lasagna (our app) with layers. The lasagna is neat, ordered, and built in defined layers. Each layer has a clear responsibility and depends on the one beneath it:

  • Top Layer — UI layer (Cheese): handles the user interaction and presentation
  • Middle Layers — Business Logic (Meat & Sauce ): processes rules, workflows, and application logic
  • Bottom Layer — Data layer (Pasta): manages persistence and access to backend services

But what happens if we mess around with our dish and try to change the recipe in order to satisfy the taste of the clients (if, of course it was possible with one dish to satisfy the taste of all clients :)).

🧀 Swapping the Cheese (UI Layer)

Suppose we decide to switch from React to Angular — perhaps for performance, team familiarity, or platform integration. Sounds simple, right?

Well, in many layered architectures, the UI layer is tightly coupled to the business logic. For instance, our state transitions might rely on React hooks, or our validation logic might sit directly in UI components. Swap out React, and we might find our business rules breake entirely.
The issue isn’t just taste — it’s that the sauce (logic) was never designed to handle whipped cream (Angular).

🍝 Changing the Pasta Sheets (Data Layer)

Now imagine replacing our traditional lasagna sheets — say, a REST API backed by MySQL — with GraphQL or a realtime Firebase database. These new ingredients might seem interchangeable, but the texture is different.

In code terms, GraphQL flips the model from server-driven to client-driven. Firebase introduces real-time syncing and eventually consistent data. If our business logic is built around assumptions like RESTful endpoints, synchronous flows, or structured SQL results, changing the data layer could force a rewrite of business rules — all because those rules depended on the shape of the data.

🌿 Adding Pesto (New Features)

Now imagine we want to enhance the dish — maybe with a little pesto drizzle. In app terms, that’s a new feature, like a recommendations widget.

Seems like a small addition, but in a layered architecture, even a dash of pesto can ripple through the whole lasagna.

In tightly coupled systems, adding pesto means reworking the sauce, cheese, and baking time. Similarly, in layered architecture, even minor features can become invasive across layers, increasing complexity and the risk of unintended side effects.

Disclaimer: take the above statement with a grain of salt because it might not be valid for every layered architecture.

⚖️ The Trade-offs of Lasagna (Layered Architecture)
While Layered Architecture is easy to understand and quick to implement, over time it can become rigid and harder to evolve.

One major challenge is high coupling between layers, which can limit both reusability and testability. Components often rely heavily on the layers directly below them, making isolation difficult. If we want to test business logic, for instance, we may end up needing a functioning UI or data layer — or worse, mock large parts of the stack.

Another issue arises when we try to change a lower layer, like swapping a traditional SQL database for a real-time backend or a GraphQL API. Even small adjustments at the base can ripple upward, affecting business logic and UI, because those upper layers often make assumptions about the data model and access patterns.

Perhaps the most subtle challenge is that business logic tends to become tightly bound to framework or infrastructure choices. Over time, core domain rules may depend on specific tools, libraries, or platforms — making it difficult to migrate, adapt, or scale the system without extensive rewrites.

✅🍲 When Lasagna Works Well
That said, Layered Architecture has its strengths — and in many cases, it’s a perfectly valid approach.

It shines when requirements are stable and well understood, when the team values predictability and clear structure, and when flexibility isn’t the top priority. For many internal tools, simple CRUD apps, or applications with a short lifecycle, lasagna is more than enough to satisfy the taste of the clients.

🍕 The Pizza (Clean Architecture)

Clean Architecture is like a well-made pizza — built on a stable, reusable base (the dough), with flexible and interchangeable toppings. At the heart of Clean Architecture is the direction of dependency: it always flows inward, toward the core logic. That means our business rules don’t care about frameworks, databases, or UI — those outer layers depend on the core, not the other way around.

Here’s how the pizza stacks up:

  • Core/Base (Dough): application-independent business rules and entities.
  • Use Cases (Sauce): orchestrate interactions, enforce rules, and coordinate flows.
  • Frameworks, UI, APIs (Toppings): added at the outer layers and can be swapped with minimal impact.

But what happens if we want to satisfy different appetites and update our pizza recipe to match evolving tastes.

🍄 Swap the Pepperoni (React) for Mushrooms (Vue)

Changing the UI framework in a traditional layered architecture often means untangling logic that’s buried inside components — state transitions, validation, and even API calls might be mixed in with presentation code.

But in Clean Architecture, the UI layer is just a topping — thin, replaceable, and decoupled. Want to move from React to Vue, or Svelte? We can, because our use cases and business logic (the sauce and dough) live underneath, untouched.

The interface simply acts as a presenter: it takes input from the user, delegates work to the core, and formats the output. This separation means UI choices are purely about user experience, not application behavior.

🌶️ Change the Olives (REST API) for Jalapeños (GraphQL or Firestore)

Let’s say we built our app around a traditional RESTful API — endpoints mapped to CRUD operations, fetching JSON from a MySQL backend. But now the client needs real-time syncing, or more flexible querying — so we want to switch to GraphQL or Firestore.

In Clean Architecture, that external data access sits at the outer layer, wrapped in interfaces. Our use cases don’t know — or care — if the data came from a REST call, a live Firestore document, or a JSON file on disk. They just receive what they asked for, in the format they need.

We can rework the front-end without rewriting our core logic. That’s flexibility. And when our company pivots to a mobile app (e.g. React Native), we are not starting from scratch — we are just changing the topping.

🍍 Add Pineapple (a New Feature)

Let’s say Product`s team wants a new feature: a recommendation engine, a dark mode toggle, or real-time collaboration. With Clean Architecture, features can be designed as independent modules. You can build that “pineapple” as a self-contained slice — its own use cases, entities, and adapters. The rest of the system doesn’t even need to know it exists, unless we explicitly integrate it.

We get feature modularity and safe experimentation. Clean Architecture gives us the power to scale horizontally — not just vertically — by adding slices instead of stacking more layers.

⚖️ The Trade-offs of Pizza (Clean Architecture)
While Clean Architecture offers flexibility, modularity, and long-term adaptability, it comes with its own set of challenges — it’s not all crispy crust and gooey cheese.

To begin with, the upfront investment is higher. Designing boundaries, interfaces, and layers that depend on abstractions — not concrete implementations — takes time and architectural discipline. It may feel like we are spending days kneading dough before we even start assembling toppings.

Clean Architecture also introduces more boilerplate and complexity at the beginning. Interfaces, dependency injection, and use case wrappers can feel heavy, especially in smaller projects or when the team is still learning the structure. For short-lived applications or simple CRUD systems, the overhead might outweigh the benefits.

And finally, not every team or project needs this level of modularity. If our recipe is unlikely to change, going full pizza might be overkill — a good old lasagna could serve just fine.

✅🍕 When Clean Architecture works well
It shines in long-term projects expected to evolve frequently, in systems that need to outlive any specific frameworks or libraries, and in teams that prioritize testability, modularity, and resilience to change.

🧾 Wrapping It Up: What’s for Dinner?
Both Layered Architecture (Lasagna) and Clean Architecture (Pizza) offer valuable patterns — the choice depends on our appetite and the context we are cooking in.

Lasagna is comforting, structured, and quick to prepare — perfect for projects with stable requirements, tight deadlines, or smaller scopes. But it can become hard to remix once it’s baked.

Pizza, on the other hand, is flexible, adaptable, and ideal when we need room to grow, experiment, or serve many different tastes. It’s more work upfront — but the payoff comes when the menu starts expanding.

So next time you’re architecting your Single Page Application, ask yourself:

👨‍🍳 Am I cooking for tonight, or building a menu for the future?

Because in software — just like in the kitchen — great results come from knowing your ingredients, respecting the process, and choosing the right recipe for the occasion.

Buon appetito. 🍽️

Top comments (0)