Escaping the Cloud Complexity Tax

Software Ideas from the 1970s

Aaron Stannard / February 26, 2026

AWS Houston Meetup

About Me

Petabridge Sdkbin
  • Aaron Stannard, CEO of Petabridge
  • 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

Old Sdkbin billing architecture with competing cron jobs, webhooks, and optimistic concurrency

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.

IoT Rube Goldberg architecture: IoT Core to Kinesis to competing consumers to DB to aggregation to alert

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.

Sdkbin actor hierarchy: SubscriptionActor communicating with InvoicingActor, PaymentIntentActor, and InvoiceSequenceActor

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

Billing sequence flow: Reminder to SubscriptionActor to InvoiceSequence to Invoicing to PaymentIntent to Stripe and back

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

  • Durable Reminders - cluster-wide scheduled messages

    Survive node failures, actor restarts, shard rebalancing

We still use databases — but the role is different.

Actors are the source of truth. Databases are cold storage.

Running on AWS

Actor frameworks exist for every major platform:

  • Akka.NET (.NET) / Akka (JVM - Scala, Java)
  • Erlang / Elixir (BEAM VM)
  • Microsoft Orleans (.NET) / Proto.Actor (Go, .NET)

Deploy on ECS, EKS, or Fargate — standard containerized apps.

  • Cluster forms automatically via service discovery or DNS
  • Rolling updates with zero downtime
  • No special AWS services required

Get Started

Akka.NET - getakka.net

Open source, production-grade actor framework for .NET

Petabridge Bootcamp - petabridge.com/bootcamp

Free self-paced training to learn actor model fundamentals

These Slides + Blog Posts - aaronstannard.com

The complexity tax is optional.
You just need to know there's another way.

Thank You!

Questions?

https://aaronstannard.com
@aaronstannard