Creator of Akka.NET - open source distributed systems framework
I've spent 15 years watching apps get complicated
And I've built the same mess myself
A Personal Story
We built a NuGet marketplace called Sdkbin. It started simple.
Then COVID hit. Revenue dropped 50% overnight.
Hired a remote developer. 7 time zones apart.
I was too busy scrambling for revenue to review the code.
"I committed the biggest engineering management mistake I've ever made: I didn't get into the details."
When I finally opened the codebase and turned on nullable... 100% of unit tests failed.
How We Solved Subscription Billing
A typical .NET architecture
Nobody Owns Anything
The subscription record has no owner.
The cron job thinks it owns it
The webhook handler thinks it owns it
The email sender is reading state that may already be stale
Add a second server and you have two cron jobs racing each other
Transient ownership. Constant coordination.
Everyone is fighting over the same data at the same time.
It's Not Just App Code
A real customer in the IoT space wanted alerts when sensor readings hit a threshold.
Time from sensor reading to alert: ~2 minutes
This is safety-critical infrastructure.
The Cost of "Nobody Owns Anything"
Application Complexity
Distributed locks
Optimistic concurrency conflicts
Race conditions between services
Stale reads and inconsistent state
Cloud Complexity
5+ services just to move data around
Expensive managed services
Coordination overhead between services
Minutes of latency for simple decisions
State is everywhere. Ownership is nowhere.
Every developer I've talked to built this incrementally. Each step made sense at the time.
"A complex system that works is invariably found to have evolved from a simple system that worked."
- John Gall, Systemantics (1975)
Nobody woke up and decided to build a Rube Goldberg machine.
They added one queue. Then one lock. Then one coordination service. Repeat.
That's how we got here. But there's a way out.
The way out was described in a 1973 computer science paper.
The 1973 Paper
Ideas that were 50 years ahead of their time
Carl Hewitt, 1973
"A Universal Modular ACTOR Formalism for Artificial Intelligence"
He described a model of computation built around:
Isolated entities that each own their own state
They communicate only through messages
They process messages one at a time
He called them: Actors
What Is an Actor?
An actor is like a person with a dedicated mailbox.
Incoming Messages
msg 3 → msg 2 → msg 1
→
Actor
Processes one message at a time
Owns its state
→
Outgoing Messages
to other actors
Reads one message at a time
Handles it completely before reading the next
Can send messages to other actors
Nobody else touches its state
Who Uses This Today?
WhatsApp - 2 billion users, ~50 engineers
Discord - millions of concurrent voice and chat users
Financial systems - stock exchanges, clearing houses, payment processors
Telecom - Ericsson built Erlang (the original actor language) to run phone switches
And we're using it right now to rewrite Sdkbin's billing system.
The .NET implementation is called Akka.NET.
Benefit 1: Single Ownership
No more distributed locks
Ownership: Before and After
Before: Shared State
Cron Job →
Webhook →
HTTP Request →
Background Job →
Database Record (locks, retries, conflicts)
After: Single Owner
Cron Job →
Webhook →
HTTP Request →
Background Job →
Actor (one queue, one owner)
Same callers. Same operations. One entry point instead of four competing ones.
Single Ownership = No Distributed Locks
Every read and write for an entity passes through one globally-unique actor
The built-in message queue serializes writes automatically
Ordering guarantees without coordination mechanisms
"Distributed locks aren't a word in your vocabulary anymore."
Benefit 2: State Machines That Make Sense
Business rules you can actually read
State Management: The Old Way
Business rules are scattered across:
HTTP Controller if (status == Active)
Webhook Handler if (status == Active)
Background Job if (status == Active)
Queue Consumer if (status == Active)
Add a new state? Find and update all of them.
The Actor Way: Behaviors
public enum SubscriptionBehavior
{
PendingPayment,
Active,
Suspended,
Cancelled
}
private void SwitchBehavior()
{
switch (_state.CurrentBehavior)
{
case PendingPayment: BecomePendingPayment(); break;
case Active: BecomeActive(); break;
case Suspended: BecomeSuspended(); break;
case Cancelled: BecomeCancelled(); break;
}
}
Business Logic as Pure Functions
// Event in → new state out. No database. No dependencies.
public static SubscriptionState Apply(
this SubscriptionState state, ISubscriptionEvent @event)
{
return @event switch
{
InvoicePaid => state with { Behavior = Active },
PaymentFailed => state with { Behavior = Suspended },
SubscriptionCancelled => state with { Behavior = Cancelled },
BillingAdvanced => state with { Behavior = Active },
};
}
State transitions are pure functions. Testable in isolation.
No database. No dependencies. Just: event in → new state out.
Benefit 3: Entities That Talk to Each Other
Modeling real-world relationships with messages
The New Sdkbin Billing System
Each entity is its own actor. Each has one job.
One actor per subscription, per invoice, per payment attempt, per publisher
Actors Calling Actors
// Look up any actor by type - cluster-sharded, globally unique
private IActorRef SubscriptionRegion =>
ActorRegistry.For(Context.System)
.Get<SubscriptionActor>();
// After payment succeeds - tell the subscription it got paid
SubscriptionRegion.Tell(
new NotifyInvoicePaid(subscriptionId, invoiceId));
No shared database writes
No distributed transactions
Just: tell the subscription it got paid
The Full Billing Flow
Benefit 4: Performance
Stateful = fast
Why Stateful = Fast
State lives in memory - no database round-trips for reads
Single-digit millisecond response times
Millions of actors running in parallel on modest hardware
Remember that IoT customer?
Before
~2 min
5 services, DB writes, aggregation consumer
After (Actors)
<100 ms
Device actor owns state, evaluates threshold directly
But Actors Are In-Memory...
What about crashes?
Event Sourcing - persist events, replay to rebuild state
Actor recovers its state automatically on restart
CQRS - reads bypass actors entirely
Query the read model for dashboards, reports, search