Files
entscheidomat/posts/06-bezos-decision-matrix-architecture-code.md
2026-08-05 19:33:11 +02:00

244 lines
9.9 KiB
Markdown

---
title: "Architecting Low-Latency Decision Engines: One-Way vs. Two-Way Door Metrics in Software Architecture"
description: "How to design low-latency software decision engines using Feature Flags, State Machines, and Bezos' reversible decision framework in TypeScript."
tags: ["architecture", "typescript", "systemdesign", "devops"]
canonical_url: "https://entscheidomat.com/ratgeber/entscheidung-treffen-wenn-zwei-optionen-gleich-gut-sind"
target_keywords: ["entscheidungsfinder", "entscheidungsgenerator", "entweder oder generator", "feature flag architecture", "state machine typescript"]
---
# Architecting Low-Latency Decision Engines: One-Way vs. Two-Way Door Metrics in Software Architecture
In high-throughput distributed systems, decision-making happens millions of times per second. Whether an application is routing API traffic between microservices, executing A/B testing variations, evaluating user entitlements, or powering a lightweight decision tool like an [Entscheidungsgenerator](https://entscheidomat.com/entweder-oder), how decision engines are architected directly impacts **latency, system resilience, and deployment velocity**.
Amazon founder Jeff Bezos famously divided all decisions into two distinct categories in his 1997 Shareholder Letter:
* **Type 1 Decisions (One-Way Doors):** Irreversible, high-consequence architectural bets (e.g. primary database migration, multi-region cloud strategy).
* **Type 2 Decisions (Two-Way Doors):** Reversible, low-risk operational choices (e.g. UI micro-interactions, algorithm tweaks, feature toggles).
In this article, we will translate this decision framework into concrete software architecture patterns. We will build a production-grade, zero-dependency **Type 2 Decision Engine with Feature Flag Rollouts and Deterministic Finite State Machines (FSM)** in TypeScript.
---
## 1. Architectural Taxonomy: Type 1 vs. Type 2 Decisions in Code
Software architecture mistakes occur when engineering organizations treat Type 2 decisions with Type 1 rigor (paralyzing release cycles) or treat Type 1 decisions as Type 2 shortcuts (causing catastrophic outage vulnerabilities).
```text
┌────────────────────────┐
│ Software Decision Flow │
└───────────┬────────────┘
Is the change easily reversible?
┌──────────────┴──────────────┐
▼ ▼
YES (Type 2 / Two-Way) NO (Type 1 / One-Way)
┌───────────────────────────┐ ┌──────────────────────────┐
│ • Feature Toggles │ │ • DB Schema Migrations │
│ • Dynamic API Routing │ │ • Monolith to Serverless │
│ • Progressive Rollouts │ │ • Protocol Format Shifts │
└───────────────────────────┘ └──────────────────────────┘
```
| Decision Metric | Type 1 (One-Way Door) | Type 2 (Two-Way Door) |
| :--- | :--- | :--- |
| **Reversibility Cost** | Extremely High ($O(\text{Weeks/Months})$) | Near Zero ($O(\text{Milliseconds})$) |
| **Evaluation Speed Target** | Weeks (RFCs, Architecture Reviews) | $< 1 \text{ms}$ (Runtime Evaluator) |
| **Verification Strategy** | Formal proofs, load testing | Feature flags, canary rollouts, A/B metrics |
| **Code Implementation** | Immutable schema contracts | Dynamic state machine / Config evaluation |
---
## 2. Designing a Sub-Millisecond Type 2 Decision Engine
Let's build a sub-millisecond, zero-dependency **Type 2 Feature Flag & Routing Engine** in TypeScript. This engine allows teams to make instant, reversible runtime decisions without re-deploying code.
```typescript
export interface UserContext {
id: string;
email: string;
country: string;
isBetaTester: boolean;
}
export interface FeatureFlagRule {
id: string;
enabled: boolean;
rolloutPercentage: number; // 0 to 100
allowedCountries?: string[];
requiresBeta?: boolean;
}
export class DecisionEngine {
private rules: Map<string, FeatureFlagRule> = new Map();
public registerRule(rule: FeatureFlagRule): void {
this.rules.set(rule.id, rule);
}
/**
* Deterministic hash calculation (MurmurHash3 variant) to ensure
* a user consistently receives the same feature bucket.
*/
private hashUser(userId: string, flagId: string): number {
const key = `${userId}:${flagId}`;
let hash = 0x811c9dc5; // FNV-1a offset basis
for (let i = 0; i < key.length; i++) {
hash ^= key.charCodeAt(i);
hash += (hash << 1) + (hash << 4) + (hash << 7) + (hash << 8) + (hash << 24);
}
return (hash >>> 0) % 100; // Returns consistent integer 0..99
}
/**
* Evaluates a decision rule in under 0.1 milliseconds.
*/
public evaluate(flagId: string, context: UserContext): boolean {
const rule = this.rules.get(flagId);
if (!rule || !rule.enabled) return false;
// Rule 1: Beta Check
if (rule.requiresBeta && !context.isBetaTester) return false;
// Rule 2: Geo-location Filter
if (rule.allowedCountries && !rule.allowedCountries.includes(context.country)) {
return false;
}
// Rule 3: Deterministic Percentage Rollout
const userBucket = this.hashUser(context.id, flagId);
return userBucket < rule.rolloutPercentage;
}
}
```
### Performance & Determinism Test
```typescript
const engine = new DecisionEngine();
engine.registerRule({
id: "new_checkout_flow",
enabled: true,
rolloutPercentage: 25, // 25% rollout
allowedCountries: ["DE", "AT", "CH"]
});
const user: UserContext = {
id: "usr_94821",
email: "dev@example.com",
country: "DE",
isBetaTester: false
};
const startTime = performance.now();
const isEnabled = engine.evaluate("new_checkout_flow", user);
const duration = performance.now() - startTime;
console.log(`Decision Evaluation Result: ${isEnabled} (Executed in ${duration.toFixed(4)} ms)`);
```
---
## 3. Reversible Decision Workflows via Finite State Machines (FSM)
For complex multi-step application states (such as checkout flows, loan applications, or interactive decision tools like [Entscheidomat Entweder-Oder](https://entscheidomat.com/entweder-oder)), representing state transitions as a **Finite State Machine** ensures strict type safety and zero invalid transitions.
```typescript
export type DecisionState = "IDLE" | "EVALUATING" | "RESOLVED" | "CANCELLED";
export type DecisionEvent = "SUBMIT" | "APPROVE" | "REJECT" | "RESET";
export class ReversibleDecisionFSM {
private currentState: DecisionState = "IDLE";
private history: DecisionState[] = [];
private readonly transitions: Record<DecisionState, Partial<Record<DecisionEvent, DecisionState>>> = {
IDLE: { SUBMIT: "EVALUATING" },
EVALUATING: { APPROVE: "RESOLVED", REJECT: "CANCELLED", RESET: "IDLE" },
RESOLVED: { RESET: "IDLE" },
CANCELLED: { RESET: "IDLE" }
};
public transition(event: DecisionEvent): DecisionState {
const allowedNextState = this.transitions[this.currentState][event];
if (!allowedNextState) {
throw new Error(`Invalid FSM transition: Cannot trigger '${event}' from state '${this.currentState}'`);
}
this.history.push(this.currentState);
this.currentState = allowedNextState;
return this.currentState;
}
/**
* Reverts to previous state (Two-Way Door behavior)
*/
public rollback(): DecisionState {
const previous = this.history.pop();
if (!previous) {
throw new Error("No previous state to rollback to");
}
this.currentState = previous;
return this.currentState;
}
public getState(): DecisionState {
return this.currentState;
}
}
```
---
## 4. Benchmark & Architectural Checklist
When designing modern web applications and decision utilities:
| Principle | Technical Implementation |
| :--- | :--- |
| **Sub-Millisecond Evaluation** | Use in-memory FNV-1a hashing instead of remote DB network calls on every request. |
| **Zero Deployment Rollbacks** | Wrap all Type 2 changes in feature flag evaluation blocks. |
| **Reversibility (Two-Way)** | Implement FSM history stacks to allow single-click state rollbacks. |
| **Stateless Scalability** | Derive user buckets deterministically using `hash(userId + flagId) % 100`. |
---
## Conclusion & Practical Takeaway
1. Software teams must explicitly tag changes as **Type 1 (One-Way)** or **Type 2 (Two-Way)** in RFCs and PR reviews.
2. Type 2 decisions should never require code deployments; evaluate them using in-memory feature flags and state machines.
3. For interactive consumer tools, use clean TypeScript state engines to keep execution fast, predictable, and reversible.
Test an interactive decision engine live on [Entscheidomat Entweder-Oder](https://entscheidomat.com/entweder-oder).
---
## FAQ (Schema Structured Data)
```json
{
"@context": "https://schema.org",
"@type": "FAQPage",
"mainEntity": [
{
"@type": "Question",
"name": "What is Jeff Bezos' One-Way vs. Two-Way Door decision framework?",
"acceptedAnswer": {
"@type": "Answer",
"text": "One-Way Door (Type 1) decisions are irreversible and high-stakes, requiring slow evaluation. Two-Way Door (Type 2) decisions are easily reversible and should be executed rapidly using feature flags and runtime evaluators."
}
},
{
"@type": "Question",
"name": "How fast should a feature flag decision engine evaluate?",
"acceptedAnswer": {
"@type": "Answer",
"text": "A well-architected in-memory decision engine using deterministic hashing should evaluate in under 0.1 milliseconds per request without hitting external network databases."
}
}
]
}
```