9.9 KiB
title, description, tags, canonical_url, target_keywords
| title | description | tags | canonical_url | target_keywords | |||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Architecting Low-Latency Decision Engines: One-Way vs. Two-Way Door Metrics in Software Architecture | How to design low-latency software decision engines using Feature Flags, State Machines, and Bezos' reversible decision framework in TypeScript. |
|
https://entscheidomat.com/ratgeber/entscheidung-treffen-wenn-zwei-optionen-gleich-gut-sind |
|
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, 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).
┌────────────────────────┐
│ 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.
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
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), representing state transitions as a Finite State Machine ensures strict type safety and zero invalid transitions.
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
- Software teams must explicitly tag changes as Type 1 (One-Way) or Type 2 (Two-Way) in RFCs and PR reviews.
- Type 2 decisions should never require code deployments; evaluate them using in-memory feature flags and state machines.
- 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.
FAQ (Schema Structured Data)
{
"@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."
}
}
]
}