Skip to content

Sarmkadan/dotnet-actor-framework

Repository files navigation

Build License .NET

DotNet Actor Framework

A lightweight, production-ready actor model framework for .NET with mailboxes, supervision trees, clustering support, and message persistence. Built on modern .NET 10 with async/await throughout.

Table of Contents

Project Overview

The DotNet Actor Framework brings the actor model pattern to .NET, providing a robust foundation for building distributed, fault-tolerant, and highly scalable systems. The actor model is a proven pattern used by frameworks like Akka and is ideal for systems that need to handle concurrent message processing with minimal resource overhead.

Key Characteristics

  • Message-Driven: Actors communicate exclusively through asynchronous messages
  • Stateful: Each actor maintains isolated internal state
  • Resilient: Hierarchical supervision enables self-healing systems
  • Observable: Built-in metrics collection and health monitoring
  • Durable: Optional message and state persistence
  • Distributed: Clustering support for multi-node deployments
  • Type-Safe: Strongly-typed message inheritance hierarchy

When to Use

The actor framework is ideal for:

  • Real-time data processing pipelines
  • Distributed job processing systems
  • WebSocket/SignalR server implementations
  • Game servers and MMO backends
  • Event-driven microservices
  • Long-running background workers
  • Systems requiring self-healing capabilities

Architecture

Core Components

┌─────────────────────────────────────────────────────────┐
│                    Actor System                          │
│                                                          │
│  ┌──────────────┐  ┌──────────────┐  ┌──────────────┐  │
│  │ Actor        │  │ Actor        │  │ Supervisor   │  │
│  │ Registry     │  │ System       │  │ Service      │  │
│  └──────────────┘  └──────────────┘  └──────────────┘  │
│                                                          │
│  ┌──────────────┐  ┌──────────────┐  ┌──────────────┐  │
│  │ Mailbox      │  │ Message      │  │ Persistence  │  │
│  │ Service      │  │ Dispatcher   │  │ Layer        │  │
│  └──────────────┘  └──────────────┘  └──────────────┘  │
│                                                          │
│  ┌──────────────┐  ┌──────────────┐  ┌──────────────┐  │
│  │ Metrics      │  │ Caching      │  │ Event Bus    │  │
│  │ Collector    │  │ Service      │  │              │  │
│  └──────────────┘  └──────────────┘  └──────────────┘  │
│                                                          │
└─────────────────────────────────────────────────────────┘

Component Responsibilities

Component Responsibility
ActorSystem Overall system lifecycle and coordination
ActorRegistry Actor creation, lookup, and lifecycle management
MailboxService FIFO queue management per actor
MessageDispatcher Message routing and delivery
SupervisionService Failure detection and recovery strategies
ActorStateRepository Snapshot persistence for actor state
MessagePersistenceRepository Durable message log storage
ActorMetricsRepository Performance metrics aggregation
ActorCacheService In-memory caching of frequently accessed actors
EventBus Pub/sub for system events

Actor Lifecycle States

Created → Initializing → Started ⟷ Suspended
                          ↓
                      Stopping → Terminated
                          ↑
                        Error (with recovery)
  1. Created: Actor instantiated but not yet initialized
  2. Initializing: OnInitializeAsync() in progress
  3. Started: Ready to process messages
  4. Suspended: Temporarily paused (e.g., during error recovery)
  5. Stopping: OnStopAsync() in progress
  6. Terminated: Shut down and removed from system
  7. Error: Encountered an unhandled exception

Installation

NuGet Package (Recommended)

dotnet add package DotNetActorFramework

Or through Visual Studio Package Manager:

Install-Package DotNetActorFramework

From Source

git clone https://github.com/sarmkadan/dotnet-actor-framework.git
cd dotnet-actor-framework
dotnet build
dotnet pack

Docker

Building the Image

docker build -t dotnet-actor-framework .

Running with Docker Compose

To start the actor system cluster with the database:

docker-compose up -d

Running a Container

docker run -it dotnet-actor-framework:latest

Quick Start

Basic Actor System Setup

using Microsoft.Extensions.DependencyInjection;
using DotNetActorFramework.Configuration;
using DotNetActorFramework.Models;

// 1. Configure dependency injection
var services = new ServiceCollection();
services.AddActorFramework(options =>
{
    options.SystemName = "MySystem";
    options.MaxActorCount = 10000;
});

var serviceProvider = services.BuildServiceProvider();

// 2. Initialize the actor system
var config = ActivatorUtilities.CreateInstance<ActorSystemConfiguration>(serviceProvider);
var actorSystem = await config.InitializeAsync();

// 3. Create an actor
var path = new ActorPath("/user/worker");
var workerRef = await config.CreateActorAsync(path);

// 4. Send a message
var message = new ControlMessage("process", new Dictionary<string, object>
{
    { "data", "example" }
});

var dispatcher = serviceProvider.GetRequiredService<MessageDispatcher>();
await dispatcher.SendAsync(workerRef, message);

// 5. Graceful shutdown
await actorSystem.ShutdownAsync();

Usage Examples

Example 1: Simple Echo Actor

public class EchoActor : Actor
{
    public EchoActor(ActorPath path) : base(path) { }

    public override async Task ReceiveAsync(Message message)
    {
        if (message is ControlMessage cm)
        {
            Console.WriteLine($"Echo: {cm.Command}");
            await Task.CompletedTask;
        }
    }
}

// Usage
var path = new ActorPath("/user/echo");
var echoRef = await config.CreateActorAsync(path);
await dispatcher.SendAsync(echoRef, new ControlMessage("hello"));

Example 2: Request-Response Pattern

public class CalculatorActor : Actor
{
    public CalculatorActor(ActorPath path) : base(path) { }

    public override async Task ReceiveAsync(Message message)
    {
        if (message is ControlMessage cm && cm.Command == "add")
        {
            var a = (int)cm.Parameters["a"];
            var b = (int)cm.Parameters["b"];
            var result = a + b;

            // Send response back to sender
            if (message.Sender != null)
            {
                var response = new ResponseMessage(result, isSuccess: true);
                // Response handling
            }
        }
        await Task.CompletedTask;
    }
}

Example 3: Stateful Actor

public class CounterActor : Actor
{
    private int _count = 0;

    public CounterActor(ActorPath path) : base(path) { }

    public override async Task ReceiveAsync(Message message)
    {
        if (message is ControlMessage cm)
        {
            switch (cm.Command)
            {
                case "increment":
                    _count++;
                    break;
                case "decrement":
                    _count--;
                    break;
                case "get":
                    // Return current count
                    break;
            }
        }
        await Task.CompletedTask;
    }
}

Example 4: Parent-Child Hierarchy

public class SupervisorActor : Actor
{
    private List<ActorRef> _children = new();

    public SupervisorActor(ActorPath path) : base(path) { }

    public override async Task OnInitializeAsync()
    {
        // Create child actors
        for (int i = 0; i < 5; i++)
        {
            var childPath = new ActorPath($"{Path}/worker-{i}");
            var childRef = await ActorSystem.CreateActorAsync(childPath, this.Ref);
            _children.Add(childRef);
        }
    }

    public override async Task ReceiveAsync(Message message)
    {
        // Distribute work to children
        if (message is ControlMessage cm && cm.Command == "process")
        {
            foreach (var child in _children)
            {
                await _dispatcher.SendAsync(child, message);
            }
        }
        await Task.CompletedTask;
    }
}

Example 5: Error Handling and Supervision

services.AddActorFramework(options =>
{
    options.SystemName = "RobustSystem";
    options.DefaultSupervisionStrategy = SupervisionStrategy.Restart;
    options.BackoffInitialDelayMs = 100;
    options.BackoffMaxDelayMs = 10000;
});

public class ResilientActor : Actor
{
    public ResilientActor(ActorPath path) : base(path) { }

    public override async Task ReceiveAsync(Message message)
    {
        try
        {
            // Processing logic that might fail
            await ProcessMessageAsync(message);
        }
        catch (Exception ex)
        {
            Metrics.RecordError(ex);
            throw; // Supervision will handle recovery
        }
    }

    private async Task ProcessMessageAsync(Message message)
    {
        // Implementation
        await Task.CompletedTask;
    }
}

Example 6: Message Batching

public class BatchProcessorActor : Actor
{
    private readonly List<Message> _batch = new();
    private readonly int _batchSize = 100;
    private readonly Timer? _flushTimer;

    public BatchProcessorActor(ActorPath path) : base(path) { }

    public override async Task ReceiveAsync(Message message)
    {
        _batch.Add(message);
        if (_batch.Count >= _batchSize)
        {
            await ProcessBatchAsync(_batch);
            _batch.Clear();
        }
    }

    private async Task ProcessBatchAsync(List<Message> batch)
    {
        // Bulk processing
        await Task.CompletedTask;
    }
}

Example 7: Metrics and Monitoring

var stats = config.GetStatistics();

Console.WriteLine($"=== Actor System Health ===");
Console.WriteLine($"Total Actors: {stats.Health?.TotalActors}");
Console.WriteLine($"Running: {stats.Health?.RunningActors}");
Console.WriteLine($"Terminated: {stats.Health?.TerminatedActors}");
Console.WriteLine($"Health Percentage: {stats.Health?.GetHealthPercentage()}%");
Console.WriteLine($"Error Rate: {stats.Health?.GetErrorRate()}%");

Console.WriteLine($"\n=== Message Dispatcher ===");
Console.WriteLine($"Total Processed: {stats.DispatcherStats?.TotalProcessed}");
Console.WriteLine($"Success Rate: {stats.DispatcherStats?.SuccessRate}%");
Console.WriteLine($"Average Latency: {stats.DispatcherStats?.AverageLatency}ms");

Console.WriteLine($"\n=== Mailbox ===");
Console.WriteLine($"Total Enqueued: {stats.MailboxStats?.TotalEnqueued}");
Console.WriteLine($"Current Queue Size: {stats.MailboxStats?.CurrentQueueSize}");

Example 8: Middleware Pipeline

services.AddActorFramework(options =>
{
    options.EnableMetricsCollection = true;
    options.EnableLogging = true;
});

// Middleware is registered and automatically applied
// Built-in middleware:
// - LoggingMiddleware: logs all message activity
// - MetricsCollectionMiddleware: collects performance metrics
// - AuthenticationMiddleware: validates sender credentials
// - RateLimitingMiddleware: enforces message rate limits
// - ErrorHandlingMiddleware: catches exceptions

Example 9: Persistence

services.AddActorFrameworkReliable("Server=localhost;Database=ActorFramework");

public class PersistentActor : Actor
{
    private readonly ActorStatePersistence _persistence;

    public PersistentActor(ActorPath path, ActorStatePersistence persistence) 
        : base(path)
    {
        _persistence = persistence;
    }

    public override async Task OnStopAsync()
    {
        // Save state before shutdown
        var snapshot = new ActorSnapshot
        {
            ActorPath = Path,
            State = GetState(),
            Timestamp = DateTime.UtcNow
        };
        await _persistence.SaveSnapshotAsync(snapshot);
    }

    public override async Task OnInitializeAsync()
    {
        // Restore state from previous session
        var snapshot = await _persistence.GetLatestSnapshotAsync(Path);
        if (snapshot != null)
        {
            RestoreState(snapshot.State);
        }
    }
}

Example 10: Clustering

services.AddActorFrameworkCluster(options =>
{
    options.NodeId = "node-1";
    options.BindAddress = "127.0.0.1";
    options.BindPort = 8080;
    options.SeedNodes = new[] { "127.0.0.1:8080" };
});

// Actors automatically participate in the cluster
var remoteActorPath = new ActorPath("/user/remote-actor@node-2");
var remoteRef = await config.ResolveActorAsync(remoteActorPath);
await dispatcher.SendAsync(remoteRef, message);

API Reference

ActorSystem

public class ActorSystem
{
    // Lifecycle
    public Task StartAsync();
    public Task ShutdownAsync();
    
    // Queries
    public ActorRef? GetActor(ActorPath path);
    public IEnumerable<ActorRef> GetAllActors();
    public Task<SystemStatistics> GetStatisticsAsync();
    public HealthSummary GetHealthSummary();
}

ActorRegistry

public interface IActorRegistry
{
    Task<ActorRef> CreateActorAsync(ActorPath path, ActorRef? supervisor = null);
    Task TerminateActorAsync(ActorRef actorRef);
    ActorRef? GetActorByPath(ActorPath path);
    IEnumerable<ActorRef> GetActorsByPath(ActorPath parentPath);
    Task<ActorMetrics> GetActorMetricsAsync(ActorRef actorRef);
}

MessageDispatcher

public interface IMessageDispatcher
{
    Task SendAsync(ActorRef recipient, Message message);
    Task SendAsync(ActorRef recipient, Message message, ActorRef? sender);
    Task<bool> TrySendAsync(ActorRef recipient, Message message, TimeSpan timeout);
    Task PublishAsync(Message message);
}

SupervisionService

public interface ISupervisionService
{
    Task ApplySupervisionStrategyAsync(ActorRef actor, Exception exception);
    void RegisterSupervisionHandler(Func<ActorRef, Exception, Task> handler);
}

Message Types

public abstract record Message
{
    public Guid Id { get; init; }
    public DateTime CreatedAt { get; init; }
    public ActorRef? Sender { get; init; }
}

public record ControlMessage(string Command, 
    Dictionary<string, object>? Parameters = null) : Message;

public record ResponseMessage(object? Data, bool IsSuccess, 
    string? Error = null) : Message;

public record FailureMessage(string Reason, Exception? Exception = null) : Message;

Configuration Reference

ActorSystemOptions

public class ActorSystemOptions
{
    // System identity
    public string SystemName { get; set; } = "ActorSystem";
    
    // Limits
    public int MaxActorCount { get; set; } = 10000;
    public int MaxMessageQueueSize { get; set; } = 100000;
    
    // Persistence
    public bool EnableMessagePersistence { get; set; } = false;
    public string? ConnectionString { get; set; }
    
    // Supervision
    public SupervisionStrategy DefaultSupervisionStrategy { get; set; } = 
        SupervisionStrategy.Restart;
    public int BackoffInitialDelayMs { get; set; } = 100;
    public int BackoffMaxDelayMs { get; set; } = 30000;
    
    // Monitoring
    public bool EnableMetricsCollection { get; set; } = true;
    public bool EnableLogging { get; set; } = true;
    public int MetricsFlushIntervalMs { get; set; } = 5000;
    
    // Clustering
    public bool EnableClustering { get; set; } = false;
    public string? ClusterNodeId { get; set; }
    public string? ClusterBindAddress { get; set; }
}

Predefined Configuration Profiles

// Default: balanced configuration
services.AddActorFramework();

// High Performance: optimized for throughput
services.AddActorFrameworkHighPerformance();

// Reliable: emphasizes durability and fault-tolerance
services.AddActorFrameworkReliable("connection-string");

// Cluster: distributed multi-node setup
services.AddActorFrameworkCluster(options => {
    options.NodeId = "node-1";
    options.BindAddress = "0.0.0.0";
});

// Custom: full control
services.AddActorFramework(options => {
    options.SystemName = "Custom";
    options.EnableMessagePersistence = true;
    // ... more options
});

Message Types

Control Message

For general commands and parameters:

var msg = new ControlMessage("startProcessing", new Dictionary<string, object>
{
    { "dataPath", "/data/input" },
    { "batchSize", 1000 },
    { "timeout", 60 }
});

Response Message

For request-response patterns:

var response = new ResponseMessage(
    data: new { Count = 42, Status = "complete" },
    isSuccess: true
);

// Or error response
var errorResponse = new ResponseMessage(
    data: null,
    isSuccess: false,
    error: "Processing failed"
);

Failure Message

For exception propagation:

var failure = new FailureMessage(
    reason: "Database connection timeout",
    exception: ex
);

Custom Typed Messages

public record OrderMessage(string OrderId, decimal Amount) : Message;
public record UserRegistrationMessage(string Email, string Name) : Message;

// Usage
var order = new OrderMessage("ORD-123", 99.99m);
await dispatcher.SendAsync(processorRef, order);

Supervision Strategies

Restart

Automatically restart the actor after a brief delay:

options.DefaultSupervisionStrategy = SupervisionStrategy.Restart;
options.BackoffInitialDelayMs = 100;
options.BackoffMaxDelayMs = 30000;

Stop

Terminate the actor on failure (no restart):

options.DefaultSupervisionStrategy = SupervisionStrategy.Stop;

Resume

Ignore the error and continue processing:

options.DefaultSupervisionStrategy = SupervisionStrategy.Resume;

Escalate

Forward the failure to the parent supervisor:

options.DefaultSupervisionStrategy = SupervisionStrategy.Escalate;

Backoff

Restart with exponential backoff delays:

options.DefaultSupervisionStrategy = SupervisionStrategy.Backoff;
options.BackoffInitialDelayMs = 100;
options.BackoffMaxDelayMs = 60000; // 1 minute max

Monitoring & Metrics

Health Summary

var health = config.GetHealthSummary();

health.TotalActors        // Total actor count
health.RunningActors      // Currently active
health.TerminatedActors   // Shut down count
health.ErroredActors      // In error state
health.SuspendedActors    // Paused actors

health.GetHealthPercentage()  // 0-100%
health.GetErrorRate()         // 0-100%

Comprehensive Statistics

var stats = await config.GetStatisticsAsync();

// Actor Registry stats
stats.ActorRegistryStats.TotalCreated
stats.ActorRegistryStats.TotalTerminated

// Dispatcher stats
stats.DispatcherStats.TotalProcessed
stats.DispatcherStats.SuccessRate
stats.DispatcherStats.AverageLatency
stats.DispatcherStats.P95Latency
stats.DispatcherStats.P99Latency

// Mailbox stats
stats.MailboxStats.TotalEnqueued
stats.MailboxStats.CurrentQueueSize
stats.MailboxStats.AverageQueueLength
stats.MailboxStats.PeakQueueLength

// Supervision stats
stats.SupervisionStats.TotalRecoveries
stats.SupervisionStats.RestartCount
stats.SupervisionStats.StopCount
stats.SupervisionStats.EscalateCount

Metrics Export

// Get all metrics as JSON
var metricsJson = stats.ToJson();

// Export to file
await System.IO.File.WriteAllTextAsync(
    "metrics.json", 
    metricsJson
);

Persistence

Actor State Snapshots

public class MyActor : Actor
{
    private string _state;

    public override async Task OnStopAsync()
    {
        var snapshot = new ActorSnapshot
        {
            ActorPath = Path,
            State = new { State = _state },
            Timestamp = DateTime.UtcNow
        };
        await persistence.SaveSnapshotAsync(snapshot);
    }

    public override async Task OnInitializeAsync()
    {
        var snapshot = await persistence.GetLatestSnapshotAsync(Path);
        if (snapshot?.State is Dictionary<string, object> state)
        {
            _state = state["State"]?.ToString() ?? "";
        }
    }
}

Message Persistence

Messages are automatically persisted when enabled:

services.AddActorFrameworkReliable(
    "Server=localhost;Database=ActorFramework"
);

Event Sourcing

Store all state changes as events:

public class EventSourcingActor : Actor
{
    private readonly Queue<object> _events = new();

    public override async Task ReceiveAsync(Message message)
    {
        // Record event
        _events.Enqueue(message);
        
        // Process event
        await ApplyEventAsync(message);
    }

    private async Task ApplyEventAsync(Message message)
    {
        // Update state based on event
        await Task.CompletedTask;
    }
}

Clustering

Single Node Setup

services.AddActorFramework();
var config = new ActorSystemConfiguration(...);
var system = await config.InitializeAsync();

Multi-Node Cluster

// Node 1
services.AddActorFrameworkCluster(options =>
{
    options.NodeId = "node-1";
    options.BindAddress = "192.168.1.10";
    options.BindPort = 8080;
    options.SeedNodes = new[] { "192.168.1.10:8080" };
});

// Node 2
services.AddActorFrameworkCluster(options =>
{
    options.NodeId = "node-2";
    options.BindAddress = "192.168.1.11";
    options.BindPort = 8080;
    options.SeedNodes = new[] { "192.168.1.10:8080" };
});

Remote Actor Invocation

// Resolve remote actor
var remotePath = new ActorPath("/user/service@node-2");
var remoteRef = await config.ResolveActorAsync(remotePath);

// Send message (works transparently)
var msg = new ControlMessage("process");
await dispatcher.SendAsync(remoteRef, msg);

// Response handling
var response = await dispatcher.SendAndWaitAsync(remoteRef, msg, timeout: 5000);

Troubleshooting

Issue: Messages not being processed

Symptoms: Actors receive messages but don't process them

Solutions:

  • Verify OnInitializeAsync() completed successfully
  • Check actor state with actor.State (should be Started)
  • Review logs for middleware errors
  • Ensure ReceiveAsync() doesn't throw unhandled exceptions
public override async Task ReceiveAsync(Message message)
{
    try
    {
        // Processing
    }
    catch (Exception ex)
    {
        // Log and handle
        Console.WriteLine($"Error: {ex.Message}");
        throw; // Allow supervision to handle
    }
}

Issue: High memory usage

Symptoms: Memory grows unbounded over time

Solutions:

  • Reduce MaxMessageQueueSize option
  • Implement message batching
  • Enable periodic actor cleanup
  • Monitor queue lengths with metrics
services.AddActorFramework(options =>
{
    options.MaxMessageQueueSize = 50000; // Reduce from default
    options.MaxActorCount = 5000;
});

Issue: Actors not recovering from failures

Symptoms: Actor enters error state and stays there

Solutions:

  • Check supervision strategy configuration
  • Verify BackoffMaxDelayMs is not too high
  • Review exception logs
  • Implement proper error handling in ReceiveAsync()
services.AddActorFramework(options =>
{
    options.DefaultSupervisionStrategy = SupervisionStrategy.Restart;
    options.BackoffInitialDelayMs = 100;
    options.BackoffMaxDelayMs = 10000;
});

Issue: Slow message processing

Symptoms: Latency is high, throughput is low

Solutions:

  • Use high-performance configuration preset
  • Implement message batching
  • Reduce actor lifecycle overhead
  • Profile with metrics collection
// Enable monitoring to identify bottleneck
var stats = await config.GetStatisticsAsync();
Console.WriteLine($"P99 Latency: {stats.DispatcherStats?.P99Latency}ms");

Issue: Database connection errors

Symptoms: Persistence operations fail, connection timeouts

Solutions:

  • Verify connection string
  • Check database accessibility
  • Increase connection pool size
  • Enable retry logic
services.AddActorFrameworkReliable(
    "Server=localhost;Database=ActorFramework;" +
    "Max Pool Size=100;Connection Timeout=30;"
);

Performance

The framework is designed for high-throughput, low-latency message processing on modern .NET hardware.

Running Benchmarks

To run the performance benchmarks, execute the following command from the project root:

dotnet run -c Release --project benchmarks/DotNetActorFramework.Benchmarks/DotNetActorFramework.Benchmarks.csproj

Benchmark Results

Measured on AMD EPYC-Rome Processor 2.45GHz, .NET 10.0

Method Mean Error StdDev Allocated
CreateActorAsync 4,016.39 ns 79.030 ns 110.788 ns 1136 B
GetActorRef 12.22 ns 0.242 ns 0.288 ns 0 B
GetHealthSummary 37.84 ns 0.816 ns 1.429 ns 80 B

Performance Tips

  • Use AddActorFrameworkHighPerformance() for throughput-critical paths
  • Enable message batching for bulk operations (MessageBatcher)
  • Pass identifiers in messages and load data inside actors rather than embedding large payloads
  • Monitor P95Latency / P99Latency from dispatcher stats to detect bottlenecks early

Related Projects

  • dotnet-event-bus - In-process and distributed event bus for .NET - pub/sub, request/reply, dead letter, polymorphic handlers

Integration Examples

Publish actor output to the event bus

Actors emit results as domain events so downstream subscribers react without tight coupling:

public class OrderProcessorActor : Actor
{
    private readonly IEventBus _eventBus;

    public OrderProcessorActor(ActorPath path, IEventBus eventBus) : base(path)
        => _eventBus = eventBus;

    public override async Task ReceiveAsync(Message message)
    {
        if (message is ControlMessage { Command: "process" } cm)
        {
            var orderId = cm.Parameters!["orderId"].ToString();
            // ... process order ...
            await _eventBus.PublishAsync(new OrderProcessedEvent(orderId!));
        }
    }
}

Bridge event bus messages into an actor mailbox

Subscribe to external domain events and forward them into the actor system for stateful processing:

eventBus.Subscribe<PaymentReceivedEvent>(async evt =>
{
    var actorRef = registry.GetActorByPath(new ActorPath("/user/payment-handler"));
    var msg = new ControlMessage("handlePayment", new() { ["amount"] = evt.Amount });
    await dispatcher.SendAsync(actorRef!, msg);
});

Testing

# Run all tests
dotnet test

# Run with coverage
dotnet test /p:CollectCoverage=true /p:CoverletOutputFormat=lcov

# Run a specific test project
dotnet test tests/dotnet-actor-framework.Tests/dotnet-actor-framework.Tests.csproj

# Run with verbose output
dotnet test --logger "console;verbosity=detailed"

The test suite covers actor lifecycle, path resolution, metrics collection, and the middleware pipeline. Target minimum 80% code coverage for contributions.

Contributing

Contributions are welcome! Please follow these guidelines:

Development Setup

git clone https://github.com/sarmkadan/dotnet-actor-framework.git
cd dotnet-actor-framework
dotnet build
dotnet test

Code Style

  • Follow C# naming conventions (PascalCase for classes, camelCase for fields)
  • Write XML documentation comments for public APIs
  • Keep methods under 30 lines when possible
  • Use meaningful variable names

Commit Messages

<type>: <subject>

<body>

<footer>

Types: feat, fix, docs, style, refactor, test, chore

Pull Request Process

  1. Fork the repository
  2. Create a feature branch (git checkout -b feature/amazing-feature)
  3. Make changes and write tests
  4. Commit with meaningful messages
  5. Push to your fork
  6. Open a Pull Request with clear description

License

MIT License - Copyright (c) 2026 Vladyslav Zaiets

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

Author

Vladyslav Zaiets


Built by Vladyslav Zaiets \n- Supervision Guide

About

Lightweight actor model framework for .NET - mailboxes, supervision trees, clustering, persistence

Topics

Resources

License

Code of conduct

Contributing

Security policy

Stars

Watchers

Forks

Packages

 
 
 

Contributors