---
title: "08 — The Application Layer: Splitting the Web Tier and Finding Services"
uid: application-layer
tags: ["application-layer", "architecture", "service-discovery", "roadmap:system-design", "microservices", "system-design"]
excerpt: "Separating the web layer from the application layer lets each scale and change independently — and once services are independent, they need a way to find each other."
date: 2026-08-13T03:27:33+0000
source: https://www.aveshina.my.id/en/blog/application-layer
---

"Just add more boxes" was my reading of the web-tier split, and it missed the why. Writing it down made the reason explicit: **separating the web layer (HTTP handling, routing) from the application layer (business logic, the "platform") lets each scale and change independently.** [1] Adding a new API means adding application servers without necessarily adding web servers. Once you split, the single responsibility principle pushes you toward smaller services — and those need a way to find each other.

The framing that landed is that the application layer split is not about performance; it is about **independent lifecycle**. The web tier and the platform tier change at different rates, scale at different rates, and fail in different ways. Coupling them means a change to one forces a redeploy of the other. Separating them is the same reasoning that separates any two concerns: localize the blast radius of change.

## The split, and what it costs

The mechanical picture is two (or more) tiers behind the load balancer [1]:

```figure
<svg viewBox="0 0 740 260" xmlns="http://www.w3.org/2000/svg" class="my-6 w-full max-w-2xl" role="img" aria-label="Layered architecture. Clients hit a Load Balancer, which fans out to two Web servers (HTTP handling). The Web servers call Application servers (business logic), which call the Database. A Service Discovery registry sits to the side; each Application server registers with it and queries it to find peers.">
  <defs>
    <marker id="alarrow" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="7" markerHeight="7" orient="auto">
      <path d="M0,0 L10,5 L0,10 z" fill="#64748b"/>
    </marker>
  </defs>
  <g font-family="ui-sans-serif, system-ui, sans-serif" text-rendering="geometricPrecision">

    <!-- clients -->
    <rect x="30" y="20" width="90" height="32" rx="8" fill="#e0e7ff" stroke="#6366f1" stroke-width="1.5"/>
    <text x="75" y="41" font-size="11" font-weight="700" fill="#1e1b4b" text-anchor="middle">clients</text>
    <!-- LB -->
    <rect x="170" y="20" width="100" height="32" rx="8" fill="#fef9c3" stroke="#ca8a04" stroke-width="1.5"/>
    <text x="220" y="41" font-size="11" font-weight="700" fill="#422006" text-anchor="middle">Load Balancer</text>
    <path d="M120,36 L168,36" fill="none" stroke="#64748b" stroke-width="1.5" marker-end="url(#alarrow)"/>

    <!-- web tier -->
    <text x="135" y="85" font-size="11" font-weight="700" fill="#052e16" text-anchor="middle">Web tier (HTTP)</text>
    <rect x="60" y="95" width="90" height="34" rx="8" fill="#dcfce7" stroke="#16a34a" stroke-width="1.5"/>
    <text x="105" y="117" font-size="10" font-weight="700" fill="#052e16" text-anchor="middle">web A</text>
    <rect x="170" y="95" width="90" height="34" rx="8" fill="#dcfce7" stroke="#16a34a" stroke-width="1.5"/>
    <text x="215" y="117" font-size="10" font-weight="700" fill="#052e16" text-anchor="middle">web B</text>
    <path d="M200,52 L110,93" fill="none" stroke="#64748b" stroke-width="1.5" marker-end="url(#alarrow)"/>
    <path d="M240,52 L220,93" fill="none" stroke="#64748b" stroke-width="1.5" marker-end="url(#alarrow)"/>

    <!-- app tier -->
    <text x="420" y="85" font-size="11" font-weight="700" fill="#1e1b4b" text-anchor="middle">Application tier (logic)</text>
    <rect x="330" y="95" width="80" height="34" rx="8" fill="#e0e7ff" stroke="#6366f1" stroke-width="1.5"/>
    <text x="370" y="117" font-size="9" font-weight="700" fill="#1e1b4b" text-anchor="middle">users-svc</text>
    <rect x="420" y="95" width="80" height="34" rx="8" fill="#e0e7ff" stroke="#6366f1" stroke-width="1.5"/>
    <text x="460" y="117" font-size="9" font-weight="700" fill="#1e1b4b" text-anchor="middle">feed-svc</text>
    <rect x="510" y="95" width="80" height="34" rx="8" fill="#e0e7ff" stroke="#6366f1" stroke-width="1.5"/>
    <text x="550" y="117" font-size="9" font-weight="700" fill="#1e1b4b" text-anchor="middle">search-svc</text>
    <path d="M260,112 L328,112" fill="none" stroke="#64748b" stroke-width="1.5" marker-end="url(#alarrow)"/>
    <text x="294" y="105" font-size="9" fill="#475569" text-anchor="middle">calls</text>

    <!-- DB -->
    <rect x="370" y="170" width="100" height="36" rx="8" fill="#fee2e2" stroke="#dc2626" stroke-width="1.5"/>
    <text x="420" y="193" font-size="11" font-weight="700" fill="#7f1d1d" text-anchor="middle">Database</text>
    <path d="M370,129 L410,168" fill="none" stroke="#64748b" stroke-width="1.5" marker-end="url(#alarrow)"/>
    <path d="M460,129 L430,168" fill="none" stroke="#64748b" stroke-width="1.5" marker-end="url(#alarrow)"/>

    <!-- Service Discovery -->
    <rect x="600" y="170" width="120" height="36" rx="8" fill="#fce7f3" stroke="#db2777" stroke-width="1.5"/>
    <text x="660" y="187" font-size="10" font-weight="700" fill="#500724" text-anchor="middle">Service</text>
    <text x="660" y="200" font-size="10" font-weight="700" fill="#500724" text-anchor="middle">Discovery</text>
    <path d="M550,129 C620,140 640,155 640,168" fill="none" stroke="#db2777" stroke-width="1.2" stroke-dasharray="4 3" marker-end="url(#alarrow)"/>
    <path d="M620,170 C580,150 540,135 510,129" fill="none" stroke="#db2777" stroke-width="1.2" stroke-dasharray="4 3" marker-end="url(#alarrow)"/>
    <text x="600" y="148" font-size="9" fill="#500724" text-anchor="middle">register / query</text>
  </g>
</svg>
```

The honest cost is that this split is not free. A loosely-coupled multi-tier system demands a different approach — architectural, operational, and process — than a monolith [1]. Microservices add deployment and operational complexity. The single responsibility principle that justifies the split is also what multiplies the number of moving parts you have to deploy, monitor, and debug.

## Microservices: services as the unit of independence

Closely related to the application-layer split are microservices — a suite of independently deployable, small, modular services, each running its own process and communicating through a well-defined, lightweight mechanism to serve a business goal [2]. The Pinterest example the roadmap uses is the clearest illustration: instead of one application, you have a user-profile service, a follower service, a feed service, a search service, a photo-upload service, and so on [2]. Each can be shipped, scaled, and owned by a small team independently.

The trade-off is the same as the tier split, amplified. Small teams with small services can plan aggressively for growth [1]. But every cross-service call is now a network hop, every service boundary is a potential failure surface, and distributed debugging is genuinely harder than in-process debugging. Microservices are a tool for organizational and scaling problems, not a default architecture — a small team with a small system is usually better served by a well-factored monolith.

## Service discovery: how independent services find each other

The moment you have more than one independently-deployable service, a new problem appears: services need to find each other, and their addresses change as instances come and go. **Service discovery** is the pattern that solves this — systems like Consul, etcd, and Zookeeper keep track of registered service names, addresses, and ports [3][4]. A service registers itself on startup; callers query the registry to find healthy instances rather than hard-coding addresses.

Health checks are the other half of it. The registry periodically verifies each registered instance — often via an HTTP endpoint — and drops instances that fail [3]. That is how a caller avoids sending traffic to a service that has died but not yet been de-registered. Both Consul and etcd also include a built-in key-value store, which is useful for shared configuration and other small bits of coordination data [3].

The way of thinking I keep: in a monolith, function calls find their target at compile time. In a microservice system, the service-registry replaces that — it is the runtime lookup table that turns "call the feed service" into "call one of these three healthy instances."

## How I use this

The decision check is about lifecycle and ownership. If two pieces of the system change at different rates, are owned by different teams, or need to scale independently, separating them into tiers (and then into services) pays for itself. If they change together and are owned by one team, the operational tax of a distributed architecture is not worth it — I keep them in one process. And when I do split, service discovery is the first piece of plumbing I reach for, because hard-coded addresses are the fastest way to make an independent service fragile.

## References

[1] W. Le, "Intro to architecting systems for scale — the platform layer," lethain.com. [Online]. Available: [http://lethain.com/introduction-to-architecting-systems-for-scale/#platform_layer](http://lethain.com/introduction-to-architecting-systems-for-scale/#platform_layer)

[2] M. Fowler, "Microservices," martinfowler.com, 2014. [Online]. Available: [https://martinfowler.com/articles/microservices.html](https://martinfowler.com/articles/microservices.html)

[3] Amazon Web Services, "Introduction to microservices," AWS. [Online]. Available: [https://aws.amazon.com/microservices/](https://aws.amazon.com/microservices/)

[4] D. Martin, "Service discovery," system-design-primer (open source), 2024. [Online]. Available: [https://github.com/donnemartin/system-design-primer#Service-Discovery](https://github.com/donnemartin/system-design-primer#Service-Discovery)

[5] "Microservices," Wikipedia. [Online]. Available: [https://en.wikipedia.org/wiki/Microservices](https://en.wikipedia.org/wiki/Microservices)

```quiz
Q: The primary reason to separate the web tier from the application tier is…
- raw performance — separate tiers are always faster
- independent lifecycle — each tier can scale and change at its own rate
correct: 1
explain: The split is about independent lifecycle and blast-radius isolation, not raw speed. Each tier can be deployed and scaled without forcing a redeploy of the other.

Q: A service registry (Consul, etcd) is used to…
- cache database query results
- let services find each other's healthy instances by name at runtime
correct: 1
explain: Service discovery replaces hard-coded addresses with a runtime lookup: services register, callers query, and failed instances are dropped via health checks.

Q: Microservices are best described as…
- a default architecture every new project should adopt
- independently deployable services each owning one responsibility, communicating over lightweight mechanisms
correct: 1
explain: Microservices are independently deployable, modular services with one responsibility each. They solve organizational and scaling problems but add operational complexity — they are not a universal default.

Q: Which trade-off does adopting microservices most directly introduce?
- fewer moving parts to deploy
- more deployment and operational complexity, plus network hops between services
correct: 1
explain: Microservices multiply moving parts: every boundary is a network hop and a failure surface. The benefit is independent scaling and ownership; the cost is operational complexity.
```
