Why Good Systems Need Both Flow and Form
Hatched by tfc
May 10, 2026
9 min read
2 views
86%
The hidden question behind every scalable system
Why do some systems feel effortless as they grow, while others become brittle the moment more people depend on them? The usual answer is technical: better infrastructure, cleaner code, more automation. But the deeper answer is organizational and philosophical. A system scales when it can absorb change without forcing every change to become everyone else’s problem.
That idea connects two worlds that are often treated separately: event driven architecture and project configuration. One governs how work moves through a system. The other governs how work is shaped before it ever begins. At first glance, they seem unrelated. One is about messages, queues, routing, and orchestration. The other is about code, workflows, and managing configuration across people and teams. But both are really about the same challenge: how do you coordinate complexity without centralizing everything?
The most interesting systems do not eliminate coordination. They redesign it. They replace constant negotiation with shared structure, explicit boundaries, and mechanisms that tolerate independence. That is the real common thread.
The trap of making everything synchronous
When people first build systems, they often favor immediacy. A request comes in, a response comes back, and everyone feels reassured because the connection is visible. Synchronous communication has the psychological comfort of a conversation: you ask, I answer, we know where we stand. In software, that comfort is seductive, especially for critical tasks.
But synchronous design has a hidden tax: it couples the pace of one part of the system to the availability of another. The caller cannot move on until the receiver answers. That means a slow dependency is not just slow. It becomes contagious. A busy service, a temporary outage, or a network hiccup can ripple outward and turn a local delay into a system wide bottleneck.
This is why asynchronous patterns matter so much. They do not merely improve scalability in the abstract. They create temporal decoupling, which is a fancy way of saying that one component can keep going without waiting for another one to catch up. A fire and forget pattern embodies this idea neatly. You send an event to a queue or event bus, then you continue working. The sender does not have to carry the emotional burden of knowing exactly when, or even whether, the receiver will finish immediately.
A useful analogy is a restaurant kitchen. Synchronous architecture is like a diner refusing to sit down until the chef finishes every dish in front of them. Asynchronous architecture is like placing an order ticket and letting the kitchen batch, route, and prepare work independently. The diner gets speed and the kitchen gets room to organize itself.
Scalability is often just delayed dependency.
That phrase matters because it reveals something counterintuitive. Many teams think scalability comes from making things faster. Often it comes from making them less entangled.
Why decoupling alone is not enough
Here is the catch: decoupling is powerful, but it can also become chaos if it is not paired with discipline. Once you let events flow freely, you introduce new problems. Messages can be duplicated. Processing may happen more than once. Consumers may arrive out of order. A system that is loosely coupled in time can become tightly coupled in correctness unless you design for failure.
That is where idempotency enters, and it is one of the most underrated design principles in modern systems. If an event can be processed twice without changing the result, then duplication stops being catastrophic. You have turned a fragile action into a safe one.
This matters because real distributed systems are messy. Networks retry. Services crash. Queues deliver more than once. Human assumptions about “exactly once” often break down the moment production traffic arrives. Idempotency is the engineering equivalent of emotional maturity: it assumes repetition, absorbs it, and still behaves correctly.
Consider an order system. If an event saying “charge customer” arrives twice, the result should not be two charges. If an event saying “mark invoice paid” arrives again, the second processing should detect that the work is already done and do nothing harmful. In practice, this often means designing operations around stable identifiers, state checks, and write logic that can safely confirm whether the intended transition has already happened.
The broader lesson is that robustness comes from expecting the system to lie to you a little. Events may duplicate, arrive late, or trigger side effects more than once. Idempotency is how you make those lies harmless.
This is where the connection to project configuration starts to become visible. A growing project also lives in a world of repetition. Teams copy workflows, replicate settings, and inherit conventions. If those conventions are brittle, every new team member becomes a source of accidental divergence. If they are idempotent, if they can be applied repeatedly without changing the intended outcome, then growth becomes safer.
Event routing is not just plumbing, it is governance
Once a system is asynchronous and resilient to duplicates, the next question is: how do events find the right place? This is not merely a routing problem. It is a governance problem.
Event routing services like EventBridge matter because they let producers and consumers evolve independently. Producers emit facts about what happened. Consumers decide which facts they care about. The relationship is no longer a direct handshake between two tightly bound services. Instead, the system gains a shared nervous system: events flow through a central mechanism that can fan out, filter, and direct them to multiple targets.
That sounds like an infrastructure detail, but it is really a statement about organizational design. Event routing makes the system less like a chain of command and more like a marketplace of responsibilities. Producers do not need to know every consumer. Consumers do not need to ask permission from every producer. The routing layer becomes the place where rules of visibility and relevance are encoded.
The same pattern appears in project management through code. A tool like projen lets people, teams, and organizations manage project configuration through code, adding workflows and files as the project grows. This is not only a convenience. It is a way to formalize governance. Instead of every repository drifting toward its own private snowflake configuration, teams encode shared patterns that can be applied across projects, updated centrally, and extended without manual reassembly.
This is the deeper symmetry:
- Event routing determines where information should go.
- Code driven configuration determines how structure should spread.
In both cases, the point is not central control for its own sake. The point is to create a system where independence does not produce fragmentation. A well designed routing layer keeps events discoverable without forcing producers to know the whole topology. A well designed configuration layer keeps workflows consistent without forcing humans to copy and paste ceremony everywhere.
Think of a large orchestra. The musicians are autonomous, but they are not improvising in isolation. The score, the conductor, and the shared timing create coherence. Event routing is the score for runtime behavior. Code driven configuration is the score for how the orchestra is built, maintained, and expanded.
Orchestration is the art of knowing when to coordinate and when to let go
There is still a final tension. If asynchronous systems reduce coupling, and routing distributes responsibility, what holds the whole thing together? The answer is orchestration.
Step Functions are a useful model here because they make workflows explicit. Rather than burying process logic across many services, you can visualize a sequence, coordinate multiple AWS services, and manage complex branching in a structured way. This is not about returning to the old world of rigid central control. It is about acknowledging that some processes really do need a conductor.
The key insight is that orchestration should be selective. Not every interaction needs a workflow engine. Not every business event should become a state machine. Over orchestration can be as harmful as under orchestration, because it recreates the very coupling that asynchronous design was meant to escape.
The best mental model is this: use events for facts, routing for relevance, and orchestration for commitments.
- A fact says something happened.
- Routing decides who should care.
- Orchestration coordinates what must happen next when the chain of actions matters.
For example, imagine a customer onboarding flow. A user signs up, a verification event is emitted, a welcome email is sent, a CRM record is created, and a provisioning step initializes resources. Some of these steps can happen independently. Others need explicit sequence or conditional branching. A workflow engine can hold the line where the process truly needs coordination, while events keep the surrounding system flexible.
This principle applies to project configuration too. A code based system for project setup should not hardcode every one off choice. It should orchestrate the stable parts, the workflows, release steps, linting, test conventions, and dependency patterns, while leaving room for local variation where it matters. The result is a platform that feels opinionated without becoming suffocating.
Good orchestration does not eliminate autonomy. It protects the few moments when autonomy would be costly.
That is the synthesis. The healthiest systems are not purely decentralized and not purely centralized. They are composed of layers that each answer a different coordination question.
A practical framework for building systems that survive growth
The deepest connection between these ideas is that both runtime architecture and project configuration are ways of answering the same question: How do we keep a growing system understandable when more people and more processes enter it?
Here is a simple framework that can guide decisions:
-
Use synchronous paths for immediate necessity, not habit. If a user truly needs an instant answer, synchronous communication is appropriate. Otherwise, default to decoupling.
-
Design every asynchronous boundary as if duplication will happen. Assume retries, repeats, and partial failures. Make operations idempotent by default.
-
Treat routing as policy, not plumbing. Decide explicitly which events matter to which consumers. Do not let accidental dependencies harden into architecture.
-
Orchestrate only the parts of the workflow that require commitment. Use state machines for business critical sequences, not as a substitute for all coordination.
-
Manage project structure through code so growth stays reproducible. Shared workflows and configuration should be versioned, reviewable, and repeatable, just like application logic.
The bigger lesson is that scale is less about size than about repetition with integrity. A system is healthy when the hundredth event looks as safe as the first, and when the twentieth repository can inherit structure without inventing its own dialect of maintenance.
When you start seeing these patterns together, a useful discipline emerges. Ask not just whether a design is fast, but whether it is repeatable. Ask not just whether a workflow works, but whether it can be re applied safely. Ask not just whether a team can ship today, but whether their process can survive ten more teams like them.
Key Takeaways
- Prefer decoupling when the caller does not need an immediate answer. Temporal independence is a scalability feature, not an afterthought.
- Assume events can be duplicated. Build idempotent operations so retries and repeats become harmless.
- Treat routing as a form of governance. Decide who should react to which facts, and encode that policy centrally.
- Use orchestration sparingly but intentionally. Reserve workflow engines for sequences that truly need coordination and state.
- Manage project configuration through code to preserve consistency at scale. Reproducible structure is the organizational version of reliable event handling.
The final insight is simple but easy to miss: the same principles that make distributed software resilient also make teams resilient. In both cases, the goal is not to remove coordination. It is to make coordination explicit, repeatable, and resilient to failure. Once you see that, architecture stops being just a technical choice. It becomes a design for how complexity itself is allowed to grow.
Sources
Hatch New Ideas with Glasp AI 🐣
Glasp AI allows you to hatch new ideas based on your curated content. Let's curate and create with Glasp AI :)
Start Hatching 🐣