# Murphy Architecture This document describes the internal architecture of Murphy for Delphi, design principles, and extension points. ## Table of Contents - [Design Principles](#design-principles) - [Architecture Overview](#architecture-overview) - [Core Components](#core-components) - [Policy Lifecycle](#policy-lifecycle) - [Extension Points](#extension-points) - [Design Patterns](#design-patterns) ## 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 │ ... │ │ └─────────────┘ └─────────────┘ └─────────────┘ │ └────────────┬────────────────────────────────────────────────┘ │ │ Creates and configures ▼ ┌─────────────────────────────────────────────────────────────┐ │ Policy Layer │ │ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ │ │ IRetryPolicy│ │ICircuitBreak│ │IFallbackPolic│ │ │ │ │ │erPolicy │ │y │ ... │ │ └─────────────┘ └─────────────┘ └─────────────┘ │ │ │ │ │ │ │ ▼ ▼ ▼ │ │ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ │ │TRetryPolicy │ │TCircuitBreak│ │TFallbackPolic│ │ │ │ │ │erPolicy │ │y │ ... │ │ └─────────────┘ └─────────────┘ └─────────────┘ │ └────────────┬────────────────────────────────────────────────┘ │ │ 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: ```pascal 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: ```pascal type TPolicy = class abstract(TInterfacedObject, IPolicy) private FHandledExceptions: TList; 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: ```pascal type TPolicyBuilder = class abstract private FHandledExceptions: TList; 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 ```pascal // 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) 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`: ```pascal 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**: ```pascal function Scheduler: IScheduler; begin if MurphyTestModeEnabled then Result := TTestScheduler.Create else Result := TConcreteScheduler.Create; end; ``` #### Global Configuration Located in `Murphy.Globals.pas`: ```pascal var MurphyTestModeEnabled: Boolean = False; ``` **Purpose**: Control library behavior for testing scenarios. ## Policy Lifecycle ### 1. Construction Phase ```pascal // Builder is created var Builder := TRetryBuilder.Create; ``` Builder initializes with default values. ### 2. Configuration Phase ```pascal // Fluent configuration Builder .Handle(EIdHTTPProtocolException) .Handle(EIdSocketError) .Retry(3) .Wait(TTimeSpan.FromSeconds(2)); ``` Builder accumulates configuration. ### 3. Build Phase ```pascal // 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 ```pascal // Policy executes user code Policy.Execute( procedure begin // User code end); ``` Policy applies its logic around user code. ### 5. Cleanup Phase ```pascal // 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**: ```pascal type IYourPolicy = interface(IPolicy) procedure Execute(AProc: TProc); // Configuration methods end; ``` 3. **Implement policy class**: ```pascal type TYourPolicy = class(TPolicy, IYourPolicy) private // Internal state public procedure Execute(AProc: TProc); // Implementation end; ``` 4. **Create builder**: ```pascal type TYourPolicyBuilder = class(TPolicyBuilder) 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: ```pascal 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`: ```pascal 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 - **Implement your own policies**: Follow the extension guide above - **Review specific patterns**: See [Pattern Guides](Home.md#pattern-guides) - **Explore API details**: Check [API Reference](API-Reference/README.md) - **Learn best practices**: Read [Best Practices](Advanced/Best-Practices.md) --- [← Chaos Engineering](03-Chaos-Engineering.md) | [Back to Index](Home.md) | [API Reference →](API-Reference/README.md)