Fintech Insights & Banking Technology Trends | Industry Blog

Simplify Backend Architecture by Removing Unnecessary Layers - Opus | Engineering the NEXT

Written by Abhishek Ojha | Oct 6, 2026, 9:48:44 AM

Every architectural layer in a system pays rent. It must justify its existence through functionality that cannot be delivered more simply elsewhere. When a layer remains because it was created under earlier constraints, even though the system has evolved around it, that layer becomes accidental complexity: infrastructure that consumes maintenance effort without providing proportional value.

I was part of a project where a separate backend repository with build artifacts sat between the frontend and the web proxy. The frontend called the backend, the backend called the proxy, and the proxy called the application programming interfaces (APIs). That created three hops where two would suffice, three repositories to version and deploy in lockstep, and three places where a mismatch could break the system.

The layer had originally supported server-side transformations between the user interface (UI) and the API gateway. Over time, the proxy matured, and the frontend framework took on more responsibility, eroding the middle layer's purpose. Yet it remained, adding maintenance cost, deployment friction, and environment failures unrelated to business logic.
When I walk teams through this redesign, the discussion usually starts with one question: how do you know an architectural layer has stopped earning its place?

The answers below explain how we evaluated the trade-off, safely removed the dependency, and measured the changes.

Why was three-way coordination becoming a delivery problem?

The separate backend repository created a dependency chain that required coordination across all three components for every meaningful change:

  • A UI feature often required a corresponding backend change, such as a new transformation or endpoint exposure
  • The backend change had to be built, tested, and packaged as a deployable artifact
  • The frontend, backend, and proxy needed aligned versions to deploy correctly

As the project grew, this coordination overhead became a persistent tax on delivery speed. Every sprint included time spent not on features, but on ensuring that three repositories remained aligned. Version pinning, dependency conflict resolution, and artifact management consumed engineering hours without producing user-visible value.

The build-artifact model added more friction. Artifact version management, transitive dependency conflicts, and environment-specific classpath issues repeatedly caused 'works on my machine' failures. These were not defects in business logic; they were infrastructure problems that took hours to diagnose without generating insight that helped us build better software.

What made us question whether the backend layer was still necessary?

We asked a direct question: what does this backend layer provide that the other two layers cannot provide more simply?

The backend layer was performing three functions:

  • Data transformation between API responses and formats the UI could consume
  • Request routing and aggregation
  • Authentication-token forwarding

The proxy already handled routing and authentication as core responsibilities. The remaining transformations fell into two groups: presentation concerns, such as formatting and field mapping, which belonged in the frontend; and API orchestration concerns, such as aggregation and caching, which the proxy could handle through configuration.

Once we established that the layer provided nothing that could not be handled more simply elsewhere, the architectural decision became clear: remove it.

How does direct frontend-to-proxy integration work?

Previously, the architecture relied on a separate repository with compiled backend artifacts to manage authentication flows, sessions, and request routing between the frontend and the web proxy. Developers had to build that repository, manage its artifact versions, and keep it running before the frontend could reach a live backend.

The new approach embeds a lightweight reverse-proxy server in the frontend repository. When a developer starts the frontend application, the configuration checks whether backend mode is enabled and, when needed, launches a local proxy server as a child process. There is no separate build, repository, or manual startup step.

The embedded server takes on the responsibilities that previously required the separate backend layer:

  • Authentication orchestration: completes the OpenID Connect (OIDC) handshake on the server and injects session tokens into subsequent API requests
  • Request routing: forwards API calls to the configured backend environment through the web proxy
  • Environment switching: one configuration flag moves between mock mode, with no external dependencies, and live-backend mode, routed through the proxy to real services

The proxy configuration sits alongside the frontend code. At startup, it reads the environment settings, determines whether backend routing is active, and routes matching Uniform Resource Locator (URL) patterns through a local proxy. The setup is declarative: change one setting and the system adapts.

In deployment, routing moves to infrastructure-level reverse-proxy rules, removing the runtime middleware entirely. The compiled backend artifacts that once sat between the UI and the proxy no longer exist.

The result is a two-hop flow, from frontend to web proxy to APIs, replacing the previous three-hop flow through backend middleware. That means one fewer repository, build pipeline, deployment artifact, and opportunity for versions to drift out of alignment.

How did we remove the layer without introducing regressions?

Removing an architectural layer carries a clear risk: silently losing behavior the layer previously supported. We therefore treated the migration as a zero-regression exercise.

The safeguards were methodical:

  • Automated API contract tests confirmed that previously supported interactions remained functional after each incremental change
  • Integration tests validated end-to-end user journeys across the full application
  • A period of parallel operation ran both architectures simultaneously and compared their responses for functional equivalence
  • Features moved incrementally, with each component verified before the next migration step

We decommissioned the repository only after every validation layer confirmed equivalent behavior. Simplification that breaks existing functionality is not simplification; it merely creates a different form of complexity.

What changed after the layer was removed?

The effects were immediate and categorical rather than incremental:

Deployment coordination moved from a three-component release process to a two-component process. Changes that once required coordinated releases across repositories can now be deployed independently.

Version-mismatch failures dropped to zero. The entire class of failures caused by the frontend expecting one backend version while an environment ran another disappeared because we removed the dependency rather than patching it.

Iteration became faster because fewer components needed to be built, tested, and deployed in sequence. Removing one step shortened the route from a code change to deployed validation.

Maintenance overhead was eliminated permanently. No one maintains the removed repository, resolves its dependency conflicts, debugs its classpath issues, or coordinates its releases. That capacity is now available for work that produces user-visible value.

The separate backend-tools repository, once a recurring item on the maintenance backlog, no longer exists as a concern.

Why was this more than a technical decision?

Architectural complexity creates organizational complexity. When three repositories must remain aligned, teams build processes around that requirement: release meetings, version checks, environment-validation steps, and rollback procedures for partial deployments. Each process carries a real calendar cost.

Once the architecture was reduced to two components that deploy together, much of that coordination became unnecessary. Meetings held to align releases, checks created to verify version compatibility, and rollback procedures for the removed layer could all disappear.

That is the hidden cost of accidental complexity. It consumes organizational time as well as engineering time. Every unnecessary layer generates a process around itself; removing the layer removes much of that process, too.

What principles guided the decision?

Every architectural layer must earn its keep. A layer that exists for historical reasons rather than a current need is a candidate for removal. Systems accumulate layers as requirements change, but those layers rarely disappear unless someone explicitly asks whether they still justify their cost.

Simplification requires validation proportional to risk. The simpler target architecture must be reached through a rigorous migration path. Parallel operation, contract testing, and incremental migration are prerequisites for maintaining a zero-regression standard.

Co-location reduces coordination cost. Components that change and deploy together are easier to manage when they live together. A single repository for a deployment unit is simpler than several repositories that must remain synchronized.

The best infrastructure is often the infrastructure you do not need. Every component can break, drift, or conflict. The most reliable dependency is the one that does not exist.

What did we learn from simplifying the architecture?

The most valuable architectural improvements are often subtractive rather than additive. Removing a layer that no longer justifies its existence can deliver more value than adding another feature to a system that is already too complex.

'Works on my machine' failures are often a symptom of unnecessary coupling. When deployment depends on three-way version alignment, environment-specific failures become more likely. Reducing the coordination surface removes the failure mode.

Migration discipline matters more than migration speed. A gradual, validated migration is better than an aggressive one that creates regressions. Zero regression is the standard; speed is secondary.

Architecture should be questioned regularly. The value a layer provides may have changed since it was introduced. Systems evolve, constraints shift, and a once-essential layer can become accidental complexity. Asking the question costs little; the answer can remove substantial ongoing maintenance.