SEO blogpost
This commit is contained in:
243
posts/06-bezos-decision-matrix-architecture-code.md
Normal file
243
posts/06-bezos-decision-matrix-architecture-code.md
Normal file
@@ -0,0 +1,243 @@
|
||||
---
|
||||
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."
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
Reference in New Issue
Block a user