Skip to content

04 Architecture

Marco Breveglieri edited this page Jul 20, 2026 · 2 revisions

Murphy Architecture

This document describes the internal architecture of Murphy for Delphi, design principles, and extension points.

Table of Contents

Design Principles

Murphy follows these architectural principles:

1. Interface-Based Design

All policies are accessed through interfaces (IRetryPolicy, ICircuitBreakerPolicy, etc.), enabling:

  • Flexibility: Easy to swap implementations
  • Testability: Simple to mock for unit testing
  • Lifecycle management: Automatic reference counting

2. Fluent Builder Pattern

Policies are constructed using fluent builders:

  • Readability: Configuration reads like natural language
  • Discoverability: IDE autocomplete guides usage
  • Immutability: Built policies are configured and ready to use

3. Separation of Concerns

Clear separation between:

  • Configuration (builders)
  • Execution (policies)
  • Infrastructure (schedulers, helpers)

4. Fail-Safe Defaults

All patterns have sensible defaults:

  • Retry: 1 retry, no wait
  • Circuit Breaker: 2 failures, 30-second recovery
  • Rate Limit: 20 calls per second
  • Fallback: No default (must be configured)

5. Type Safety

Leverage Delphi's type system:

  • Generic types for type-safe results (Fallback)
  • Strong exception type checking
  • Compile-time validation where possible

Architecture Overview

┌─────────────────────────────────────────────────────────────┐
│                        Client Code                           │
└────────────┬────────────────────────────────────────────────┘
             │
             │ Uses fluent builders
             ▼
┌─────────────────────────────────────────────────────────────┐
│                    Builder Layer                             │
│  ┌─────────────┐  ┌─────────────┐  ┌─────────────┐         │
│  │ TRetryBuilder│  │TCircuitBreak│  │TFallbackBuild│         │
│  │             │  │erBuilder    │  │er<T>        │  ...    │
│  └─────────────┘  └─────────────┘  └─────────────┘         │
└────────────┬────────────────────────────────────────────────┘
             │
             │ Creates and configures
             ▼
┌─────────────────────────────────────────────────────────────┐
│                     Policy Layer                             │
│  ┌─────────────┐  ┌─────────────┐  ┌─────────────┐         │
│  │ IRetryPolicy│  │ICircuitBreak│  │IFallbackPolic│         │
│  │             │  │erPolicy     │  │y<T>         │  ...    │
│  └─────────────┘  └─────────────┘  └─────────────┘         │
│         │                │                 │                 │
│         ▼                ▼                 ▼                 │
│  ┌─────────────┐  ┌─────────────┐  ┌─────────────┐         │
│  │TRetryPolicy │  │TCircuitBreak│  │TFallbackPolic│         │
│  │             │  │erPolicy     │  │y<T>         │  ...    │
│  └─────────────┘  └─────────────┘  └─────────────┘         │
└────────────┬────────────────────────────────────────────────┘
             │
             │ Inherits from
             ▼
┌─────────────────────────────────────────────────────────────┐
│                      Base Layer                              │
│  ┌──────────────────────┐    ┌───────────────────────┐     │
│  │   IPolicy interface  │    │  TPolicy base class   │     │
│  │                      │    │                       │     │
│  │ - IsHandled()        │    │ - Exception filtering │     │
│  └──────────────────────┘    │ - Common behavior     │     │
│                               └───────────────────────┘     │
└────────────┬────────────────────────────────────────────────┘
             │
             │ Uses
             ▼
┌─────────────────────────────────────────────────────────────┐
│                   Support Services                           │
│  ┌────────────────────┐    ┌──────────────────────┐        │
│  │   IScheduler       │    │  Murphy.Globals      │        │
│  │                    │    │                      │        │
│  │ - Now              │    │ - Test mode flag     │        │
│  │ - WaitFor          │    └──────────────────────┘        │
│  └────────────────────┘                                     │
│           │                                                  │
│           ▼                                                  │
│  ┌────────────────────┐    ┌──────────────────────┐        │
│  │ TConcreteScheduler │    │  TTestScheduler      │        │
│  │ (Production)       │    │  (Testing)           │        │
│  └────────────────────┘    └──────────────────────┘        │
└─────────────────────────────────────────────────────────────┘

Core Components

Base Policy Infrastructure

Located in Murphy.Base.Policy.pas:

IPolicy Interface

The root interface for all policies:

type
  IPolicy = interface
    ['{8B2E5F6A-1D3C-4E8B-9A7F-2B4C6D8E9F0A}']
    function IsHandled(AException: Exception): Boolean;
  end;

Purpose: Define the contract for exception handling across all policies.

TPolicy Base Class

Abstract base class providing common functionality:

type
  TPolicy = class abstract(TInterfacedObject, IPolicy)
  private
    FHandledExceptions: TList<ExceptClass>;
  protected
    function IsHandled(AException: Exception): Boolean;
  public
    constructor Create;
    destructor Destroy; override;
  end;

Responsibilities:

  • Maintain list of handled exception types
  • Determine if an exception should be handled by the policy
  • Provide common initialization/cleanup

TPolicyBuilder Generic Base

Generic base class for all builders:

type
  TPolicyBuilder<TPolicy: IPolicy> = class abstract
  private
    FHandledExceptions: TList<ExceptClass>;
  protected
    function GetPolicy: TPolicy; virtual; abstract;
  public
    constructor Create;
    destructor Destroy; override;
    function Handle(AExceptionType: ExceptClass): TPolicy;
    function Build: TPolicy;
  end;

Responsibilities:

  • Collect exception types to handle
  • Transfer configuration to policy on build
  • Provide fluent interface foundation

Policy Implementations

Each policy follows a consistent structure:

  1. Context Type (if needed) - Holds execution context data
  2. Policy Interface - Defines the policy contract
  3. Policy Implementation - Implements the pattern logic
  4. Builder Class - Fluent configuration API

Example: Retry Policy Structure

// 1. Context
type
  TRetryContext = record
    Attempts: Integer;
    Exception: Exception;
    WaitDelay: TTimeSpan;
  end;

// 2. Interface
type
  IRetryPolicy = interface(IPolicy)
    procedure Execute(AProc: TProc);
    function Retry(ATimes: Integer): IRetryPolicy;
    function Wait(ADelay: TTimeSpan): IRetryPolicy;
    // ... more methods
  end;

// 3. Implementation
type
  TRetryPolicy = class(TPolicy, IRetryPolicy)
  private
    FRetryCount: Integer;
    FWaitDelay: TTimeSpan;
    // ... more fields
  public
    procedure Execute(AProc: TProc);
    // ... method implementations
  end;

// 4. Builder
type
  TRetryBuilder = class(TPolicyBuilder<IRetryPolicy>)
  private
    FRetryCount: Integer;
    FWaitDelay: TTimeSpan;
  public
    function Retry(ATimes: Integer): IRetryPolicy;
    function Wait(ADelay: TTimeSpan): IRetryPolicy;
    function Build: IRetryPolicy; override;
  end;

Support Services

Scheduler Abstraction

Located in Murphy.Services.Schedulers.pas:

type
  IScheduler = interface
    function Now: TDateTime;
    procedure WaitFor(const ATimeSpan: TTimeSpan);
  end;

Purpose: Abstract time-based operations for testability.

Implementations:

  1. TConcreteScheduler - Production implementation:

    • Now: Returns System.SysUtils.Now
    • WaitFor: Uses TThread.Sleep
  2. TTestScheduler - Test implementation:

    • Now: Returns System.SysUtils.Now (real time)
    • WaitFor: Returns immediately (no delay)

Usage:

function Scheduler: IScheduler;
begin
  if MurphyTestModeEnabled then
    Result := TTestScheduler.Create
  else
    Result := TConcreteScheduler.Create;
end;

Global Configuration

Located in Murphy.Globals.pas:

var
  MurphyTestModeEnabled: Boolean = False;

Purpose: Control library behavior for testing scenarios.

Policy Lifecycle

1. Construction Phase

// Builder is created
var Builder := TRetryBuilder.Create;

Builder initializes with default values.

2. Configuration Phase

// Fluent configuration
Builder
  .Handle(EIdHTTPProtocolException)
  .Handle(EIdSocketError)
  .Retry(3)
  .Wait(TTimeSpan.FromSeconds(2));

Builder accumulates configuration.

3. Build Phase

// Build creates policy instance
var Policy: IRetryPolicy := Builder.Build;
  • Policy instance is created
  • Configuration is transferred from builder to policy
  • Builder's job is complete (can be discarded)

4. Execution Phase

// Policy executes user code
Policy.Execute(
  procedure
  begin
    // User code
  end);

Policy applies its logic around user code.

5. Cleanup Phase

// When Policy goes out of scope
end;

Interface reference counting automatically cleans up.

Extension Points

Adding a New Policy Pattern

To add a new pattern (e.g., Timeout, Bulkhead):

  1. Create new unit: Murphy.Policy.YourPattern.pas

  2. Define policy interface:

    type
      IYourPolicy = interface(IPolicy)
        procedure Execute(AProc: TProc);
        // Configuration methods
      end;
  3. Implement policy class:

    type
      TYourPolicy = class(TPolicy, IYourPolicy)
      private
        // Internal state
      public
        procedure Execute(AProc: TProc);
        // Implementation
      end;
  4. Create builder:

    type
      TYourPolicyBuilder = class(TPolicyBuilder<IYourPolicy>)
      public
        function SomeConfig(AValue: Integer): IYourPolicy;
        function Build: IYourPolicy; override;
      end;
  5. Add tests: Create unit tests in Tests/Murphy.Tests.YourPattern.pas

Customizing Scheduler Behavior

You can provide custom scheduler implementations:

type
  TCustomScheduler = class(TInterfacedObject, IScheduler)
  public
    function Now: TDateTime;
    procedure WaitFor(const ATimeSpan: TTimeSpan);
  end;

// Use in policy or inject somehow

Extending Exception Handling

Add custom exception filtering logic by overriding IsHandled:

type
  TCustomPolicy = class(TPolicy, ICustomPolicy)
  protected
    function IsHandled(AException: Exception): Boolean; override;
  end;

function TCustomPolicy.IsHandled(AException: Exception): Boolean;
begin
  // Custom logic
  Result := inherited IsHandled(AException) and SomeCondition;
end;

Design Patterns Used

Murphy leverages several design patterns:

1. Builder Pattern

Where: All policy builders (TRetryBuilder, etc.)

Why: Separate construction from representation, enable fluent configuration

2. Strategy Pattern

Where: Different policy implementations (IRetryPolicy, ICircuitBreakerPolicy, etc.)

Why: Encapsulate different resilience algorithms, make them interchangeable

3. Template Method Pattern

Where: TPolicy base class

Why: Define skeleton of exception handling, let subclasses override specifics

4. Dependency Injection

Where: Scheduler service

Why: Enable testing without real delays, allow custom implementations

5. Interface Segregation

Where: Each policy has its own interface

Why: Clients depend only on methods they use

6. Decorator Pattern (Conceptual)

Where: Policies wrap user code execution

Why: Add behavior (retry, circuit breaking) transparently

Thread Safety

Current Status: Murphy policies are not inherently thread-safe.

Recommendations:

  • Create separate policy instances per thread
  • Or protect shared policy instances with synchronization primitives
  • Circuit breaker state is instance-specific

Future: Thread-safe implementations may be added in future versions.

Performance Considerations

Memory

  • Policies: Lightweight, minimal memory footprint
  • Builders: Temporary, discarded after build
  • Interfaces: Reference-counted, automatic cleanup

CPU

  • No overhead when not triggered: If no exceptions occur, minimal overhead
  • Retry: Overhead proportional to retry count
  • Circuit Breaker: Minimal state tracking
  • Rate Limit: Token bucket algorithm, O(1) per call

Best Practices

  1. Reuse policies: Create once, use multiple times
  2. Avoid excessive retries: More retries = more delay
  3. Use appropriate timeouts: Don't wait indefinitely
  4. Test mode: Use in unit tests to avoid delays

Code Organization

Source/
├── Murphy.Base.Policy.pas           # Base interfaces and classes
├── Murphy.Policy.Retry.pas          # Retry pattern
├── Murphy.Policy.CircuitBreaker.pas # Circuit Breaker pattern
├── Murphy.Policy.Fallback.pas       # Fallback pattern
├── Murphy.Policy.RateLimit.pas      # Rate Limit pattern
├── Murphy.Services.Schedulers.pas   # Time abstraction
├── Murphy.Globals.pas               # Global configuration
└── Murphy.Resources.Strings.pas     # Resource strings

Next Steps


← Chaos Engineering | Back to Index | API Reference →

Clone this wiki locally