-
Notifications
You must be signed in to change notification settings - Fork 1
04 Architecture
This document describes the internal architecture of Murphy for Delphi, design principles, and extension points.
- Design Principles
- Architecture Overview
- Core Components
- Policy Lifecycle
- Extension Points
- Design Patterns
Murphy follows these architectural principles:
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
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
Clear separation between:
- Configuration (builders)
- Execution (policies)
- Infrastructure (schedulers, helpers)
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)
Leverage Delphi's type system:
- Generic types for type-safe results (Fallback)
- Strong exception type checking
- Compile-time validation where possible
┌─────────────────────────────────────────────────────────────┐
│ 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) │ │
│ └────────────────────┘ └──────────────────────┘ │
└─────────────────────────────────────────────────────────────┘
Located in Murphy.Base.Policy.pas:
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.
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
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
Each policy follows a consistent structure:
- Context Type (if needed) - Holds execution context data
- Policy Interface - Defines the policy contract
- Policy Implementation - Implements the pattern logic
- Builder Class - Fluent configuration API
// 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;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:
-
TConcreteScheduler - Production implementation:
-
Now: ReturnsSystem.SysUtils.Now -
WaitFor: UsesTThread.Sleep
-
-
TTestScheduler - Test implementation:
-
Now: ReturnsSystem.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;Located in Murphy.Globals.pas:
var
MurphyTestModeEnabled: Boolean = False;Purpose: Control library behavior for testing scenarios.
// Builder is created
var Builder := TRetryBuilder.Create;Builder initializes with default values.
// Fluent configuration
Builder
.Handle(EIdHTTPProtocolException)
.Handle(EIdSocketError)
.Retry(3)
.Wait(TTimeSpan.FromSeconds(2));Builder accumulates configuration.
// 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)
// Policy executes user code
Policy.Execute(
procedure
begin
// User code
end);Policy applies its logic around user code.
// When Policy goes out of scope
end;Interface reference counting automatically cleans up.
To add a new pattern (e.g., Timeout, Bulkhead):
-
Create new unit:
Murphy.Policy.YourPattern.pas -
Define policy interface:
type IYourPolicy = interface(IPolicy) procedure Execute(AProc: TProc); // Configuration methods end;
-
Implement policy class:
type TYourPolicy = class(TPolicy, IYourPolicy) private // Internal state public procedure Execute(AProc: TProc); // Implementation end;
-
Create builder:
type TYourPolicyBuilder = class(TPolicyBuilder<IYourPolicy>) public function SomeConfig(AValue: Integer): IYourPolicy; function Build: IYourPolicy; override; end;
-
Add tests: Create unit tests in
Tests/Murphy.Tests.YourPattern.pas
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 somehowAdd 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;Murphy leverages several design patterns:
Where: All policy builders (TRetryBuilder, etc.)
Why: Separate construction from representation, enable fluent configuration
Where: Different policy implementations (IRetryPolicy, ICircuitBreakerPolicy, etc.)
Why: Encapsulate different resilience algorithms, make them interchangeable
Where: TPolicy base class
Why: Define skeleton of exception handling, let subclasses override specifics
Where: Scheduler service
Why: Enable testing without real delays, allow custom implementations
Where: Each policy has its own interface
Why: Clients depend only on methods they use
Where: Policies wrap user code execution
Why: Add behavior (retry, circuit breaking) transparently
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.
- Policies: Lightweight, minimal memory footprint
- Builders: Temporary, discarded after build
- Interfaces: Reference-counted, automatic cleanup
- 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
- Reuse policies: Create once, use multiple times
- Avoid excessive retries: More retries = more delay
- Use appropriate timeouts: Don't wait indefinitely
- Test mode: Use in unit tests to avoid delays
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
- Implement your own policies: Follow the extension guide above
- Review specific patterns: See Pattern Guides
- Explore API details: Check API Reference
- Learn best practices: Read Best Practices