Good Platforms Are Not Built by Adding More Infrastructure
Why the real challenge of modern platforms lies not in additional components, but in clear responsibilities.
Consider for a moment how modern platforms have evolved over the past fifteen years. Almost every major innovation had the same goal: to reduce complexity. Virtual machines decoupled applications from physical hardware. Containers decoupled applications from the operating system. Kubernetes decoupled workloads from individual hosts. GitOps decoupled infrastructure from manual processes. Service meshes abstracted network communication within a cluster, while Infrastructure as Code ultimately aimed to make even highly complex platforms fully declarative. Looking back, this development can almost be understood as a continuous search for better abstraction. On the networking side, however, we often observe the opposite. Every new requirement produced another component. A DNS system. A Layer 4 load balancer. A Layer 7 proxy. A web application firewall. DDoS mitigation. Bot protection. API gateways. Rate limiting. Traffic analytics. Health checks. TLS offloading. None of these systems exists without reason. Each solves a real problem. And that is exactly why they all exist side by side today. Perhaps this is a development we question surprisingly rarely in everyday operations.
The larger a platform becomes, the stronger the temptation to solve new requirements simply by adding more components. Need API authentication? Put an API gateway in front of it. Need protection from Layer 7 attacks? Add a web application firewall. Need automatic certificate renewal? Integrate an ACME client. Each decision on its own is perfectly reasonable. Over time, however, the resulting architecture becomes complex not because of the individual components, but because of the transitions between them. This may describe one of the most fundamental problems of modern platforms. It is not merely the number of systems that grows. It is the number of dependencies between them.
Take a seemingly simple request.
Browser
│
▼
DNS
│
▼
Edge
│
▼
TLS
│
▼
WAF
│
▼
Layer 7
│
▼
Ingress
│
▼
Kubernetes
│
▼
Application
At first glance, this diagram looks linear. In reality, additional information flows exist between almost every component. The web application firewall needs information about which applications exist. The Layer 7 proxy needs to know certificates. Health checks need information about backend state. Routing needs to know which sites are productive. Monitoring then wants to correlate all of this information even though it comes from completely different systems. A chain gradually becomes a graph. And that is where the real complexity begins.
Perhaps it is useful to look at a principle that has proven itself repeatedly not only in software development, but also in the architecture of distributed systems.
Good systems rarely emerge by trying to solve as many problems as possible at once.
Good systems emerge when each subsystem solves exactly one problem.
The statement sounds almost trivial. Its consequences are significant. A routing protocol should solve routing problems. Not authentication. A web application firewall should analyze HTTP. Not orchestrate Kubernetes. A Kubernetes cluster should schedule containers. Not calculate global network paths. As soon as individual components start assuming the responsibilities of other systems, dependencies emerge that are often much harder to understand than the original problems themselves.
This idea maps surprisingly well to modern edge platforms. Edge infrastructure often appears particularly complex because it provides many functions. In reality, much of its complexity comes from another source. It sits at the intersection of almost every other system. It knows DNS. It knows routing. It terminates TLS. It evaluates HTTP. It distributes traffic. It knows the health of backends. And yet it should know as little as possible about the actual application. That separation is what determines whether a platform remains manageable over the long term.
Let us therefore look at the same architecture not in terms of products, but in terms of responsibilities.
+--------------------------------------------------+
| Edge Platform |
+--------------------------------------------------+
- Accept connections
- Determine network paths
- Terminate TLS
- Validate requests
- Distribute traffic
- Select healthy backends
│
▼
+--------------------------------------------------+
| Compute Platform |
+--------------------------------------------------+
- Run containers
- Scale applications
- Orchestrate services
- Process data
- Provide business logic
Interestingly, almost every product name disappears from this diagram. Not because products are unimportant. But because they are merely implementations of a responsibility. Whether Layer 7 routing is provided by HAProxy, Envoy, or NGINX hardly changes the architecture. Neither does whether Kubernetes uses containerd or CRI-O. Architecture begins where responsibilities are defined. Not where products are selected.
Perhaps this also explains why platforms often outlive individual technologies by a wide margin. Products change. Protocols evolve. New software replaces old software. Responsibilities remain remarkably constant. A platform will still need to accept connections ten years from now. Distribute traffic. Run applications. Store data. Provide observability. The concrete implementation may change. The architecture behind it changes surprisingly little.
This realization also changes how we evaluate infrastructure. The central question is no longer: "Which software do we use?" Instead, it becomes: "What responsibility does this system have – and does it actually possess all the information required to fulfill that responsibility reliably?" Perhaps that is the real definition of good platform architecture. Not as many features as possible. Not as many products as possible. But boundaries that are as clear as possible. Platforms do not become maintainable simply because they contain fewer components. They become maintainable because every component knows exactly which problem it is supposed to solve. And which problem it deliberately does not solve.