← Back to list

Akka.NET Kullanımı

Akka.NET, yüksek performanslı, ölçeklenebilir ve eşzamanlı (concurrent) sistemler yazmayı kolaylaştıran bir framework’tür. Temelinde…

Cihan Asan · 2025-10-18 11:20 · 0 claps · 11.3 min read
#akka #akka-net
Open on Medium ↗

Akka.NET

Akka.NET, yüksek performanslı, ölçeklenebilir ve eşzamanlı (concurrent) sistemler yazmayı kolaylaştıran bir framework’tür. Temelinde “actor model” (aktör modeli) denilen bir yaklaşım vardır.

Normalde çok sayıda işlem veya thread yönetmek zordur. Akka.NET bunu “actor” denen küçük birimler aracılığıyla çözer.

  • Her actor, kendi küçük göreviyle ilgilenir.
  • Diğer actor’larla mesaj göndererek iletişim kurar.
  • Bu sayede thread-safe, asenkron ve dağıtık sistemler kolayca kurulabilir.

Dağıtık Sistem Nedir?

Basitçe söylemek gerekirse:

“Birden fazla bilgisayarın veya sunucunun birlikte çalışarak tek bir sistem gibi davranmasıdır.”

Yani, iş yükü tek bir makinede değil, birden fazla makineye dağıtılmıştır ve hepsi birbiriyle iletişim kurar.

Temel Özellikleri

Çoklu düğüm (Node)

  • Sistem birden fazla bilgisayarda çalışır.
  • Örneğin: bir web sitesi, farklı sunucularda çalışan veri tabanı ve cache sunucularından oluşabilir.

İletişim

  • Düğümler birbirleriyle mesajlaşır (HTTP, gRPC, Akka mesajları gibi).
  • Her düğüm diğer düğümlerin durumunu bilmek zorunda değildir; sadece mesaj alır/gönderir.

Hata toleransı

  • Bir düğüm kapanırsa sistem tamamen çökmez.
  • Mesajlar başka düğümler üzerinden yönlendirilebilir.

Paralel ve eşzamanlı işlem

  • İşler aynı anda birden fazla düğümde yürütülür.
  • Büyük veriler veya yoğun işlemler daha hızlı tamamlanır.

Akka.NET ve Dağıtık Sistemler

Akka.NET’in actor modeli, dağıtık sistemler için birebir uygundur:

  • Her actor bağımsızdır ve mesaj ile iletişim kurar.
  • Actor’lar farklı makinelerde çalışabilir (Akka.Cluster ile).
  • Sistem hata toleranslı ve ölçeklenebilir olur.
using System;
using Akka.Actor;
using System.Threading.Tasks;

// Mesaj modeli
public class StartCargo
{
    public string CargoId { get; set; }
    public string Destination { get; set; }
}

// Yükleme aktörü
public class LoadingActor : ReceiveActor
{
    public LoadingActor()
    {
        ReceiveAsync<StartCargo>(async msg =>
        {
            Console.WriteLine($"📦 Kargo {msg.CargoId} yükleniyor...");
            await Task.Delay(2000); // işlem süresi
            Console.WriteLine($"✅ Kargo {msg.CargoId} başarıyla yüklendi. Teslimata gönderiliyor...");

            // Teslimat aktörünü oluştur ve mesajı gönder
            var deliveryActor = Context.ActorOf<DeliveryActor>("deliveryActor");
            deliveryActor.Tell(msg);
        });
    }
}

// Teslimat aktörü
public class DeliveryActor : ReceiveActor
{
    public DeliveryActor()
    {
        ReceiveAsync<StartCargo>(async msg =>
        {
            Console.WriteLine($"🚚 Kargo {msg.CargoId}, {msg.Destination} adresine yola çıktı...");
            await Task.Delay(3000);
            Console.WriteLine($"🎉 Kargo {msg.CargoId}, {msg.Destination} adresine teslim edildi!");
        });
    }
}

// Koordinatör aktör
public class CargoCoordinatorActor : ReceiveActor
{
    private readonly IActorRef _loadingActor;

    public CargoCoordinatorActor()
    {
        _loadingActor = Context.ActorOf<LoadingActor>("loadingActor");

        Receive<StartCargo>(msg =>
        {
            Console.WriteLine($"🔔 Yeni kargo süreci başlatıldı: {msg.CargoId}");
            _loadingActor.Tell(msg);
        });
    }
}

// Ana program
public class Program
{
    public static async Task Main(string[] args)
    {
        var system = ActorSystem.Create("CargoSystem");

        var coordinator = system.ActorOf<CargoCoordinatorActor>("cargoCoordinator");

        // Yeni kargo oluştur
        coordinator.Tell(new StartCargo { CargoId = "CARGO-001", Destination = "Ankara" });

        Console.WriteLine("\nKargo işlemi başlatıldı. Çıkmak için Enter'a bas...");
        Console.ReadLine();

        await system.Terminate();
    }
}

ActorSystem nedir?

ActorSystem, Akka.NET’te tüm actor’ların içinde çalıştığı üst düzey ortamdır. Bunu şöyle düşünebilirsin:

“Actor’lar bir şehirdeki insanlar gibidir, ActorSystem ise o şehrin kendisidir.”

Yani:

  • Actor’lar ActorSystem içinde yaşar.
  • Mesajlar ActorSystem üzerinden akar.
  • Sistem kapanana kadar actor’lar varlığını sürdürür.

Kod örneğimizde:

var system = ActorSystem.Create("CargoSystem");

Bunun anlamı:

  1. ActorSystem.Create("CargoSystem") → Yeni bir actor sistemi oluştur.
  2. "CargoSystem" → Bu sistemin adı. Konsolda veya loglarda bu isimle görünür.
  3. var system = ... → Oluşturulan ActorSystem’in referansını system değişkenine ata.

Bu system sayesinde:

  • Actor’lar oluşturabilirsin (system.ActorOf(...)).
  • Sistemi sonlandırabilirsin (await system.Terminate();).
  • Actor’lar arası mesajlaşmayı yönetirsin.

ReceiveActor sınıfı Akka.NET’in kalbidir. Bunu iyi anlarsan, Akka dünyasında neredeyse her şeyi çözersin.

Akka.NET’te her actor kendi küçük evreninde yaşar.

Bu evrenin bilgilerini, araçlarını, alt aktörlerini yönetmek için bir bağlam (context) kullanır.

İşte o yüzden Akka.NET bize her actor içinde Context adında bir özellik verir.

Context nedir?

Context, actor’un çalışma ortamına (yani “ben kimim, kimden mesaj alıyorum, kimlere mesaj gönderebilirim” bilgilerine) erişmeni sağlayan bir yardımcı nesnedir.

Context tür olarak aslında:

IActorContext

Nereden geliyor?

Her ReceiveActor, ActorBase sınıfından türetilir. ActorBase sınıfında bir korumalı (protected) özellik vardır:

protected IActorContext Context { get; }

Yani sen ekstra bir şey tanımlamıyorsun, çünkü ReceiveActor’dan miras alıyorsun. Akka.NET bunu otomatik sağlar.

Ne işe yarar?

Context sayesinde actor içinde birçok önemli işlemi yapabilirsin:

Bizim örnekte nasıl kullanıldı?

var deliveryActor = Context.ActorOf<DeliveryActor>("deliveryActor");
deliveryActor.Tell(msg);

Bu satırda:

  • Context.ActorOf<DeliveryActor>("deliveryActor") → Yani “benim altımda bir DeliveryActor oluştur”.
  • Tell(msg) → Ona mesaj gönder.

Yani Context, LoadingActor’a “benim altımda yeni bir aktör oluşturma gücü” veriyor.

Özetle

*Context, actor’un çalışma ortamını temsil eder. Her actor kendi Context’i içinde yaşar. Bu context sayesinde alt aktörler oluşturabilir, mesaj gönderebilir, kendi kimliğini ve sistem bilgilerini alabilir.*

Actor Nedir?

Akka.NET’te her şey actor kavramı etrafında döner. Actor’u şöyle düşünebilirsin:

“Kendi işiyle ilgilenen, küçük, bağımsız bir işçi.”

Bu işçi:

  • Sadece mesaj alır,
  • Sadece kendi durumu (state) üzerinde çalışır,
  • Asla başka actor’ün verisine dokunmaz,
  • Her mesajı tek tek (sıralı) işler.

Böylece hiçbir thread karışıklığı olmaz.

ReceiveActor Ne İşe Yarar?

ReceiveActor, actor oluşturmanın en kolay yoludur. Normalde bir actor yazmak için ActorBase sınıfından türetip OnReceive metodunu override edersin. Ama bu biraz uzundur.

ReceiveActor ise bunu basitleştirir.

*ReceiveActor, gelen mesajları dinleyip uygun mesaj türüne göre hangi işlemi yapacağını tanımlamana yarar.*

Yani, “Bu actor şu türde bir mesaj alırsa, şunu yap” diyorsun.

public class LoadingActor : ReceiveActor
{
    public LoadingActor()
    {
        ReceiveAsync<StartCargo>(async msg =>
        {
            Console.WriteLine($"📦 Kargo {msg.CargoId} yükleniyor...");
            await Task.Delay(2000);
            Console.WriteLine($"✅ Kargo {msg.CargoId} başarıyla yüklendi. Teslimata gönderiliyor...");

            var deliveryActor = Context.ActorOf<DeliveryActor>("deliveryActor");
            deliveryActor.Tell(msg);
        });
    }
}

Bunu şöyle okuyabilirsin 👇

“Ben bir LoadingActor’üm. Eğer bana StartCargo tipinde bir mesaj gelirse, şu işlemleri yapacağım…”

Yani burada:

  • ReceiveAsync<StartCargo> → sadece StartCargo mesajlarını dinliyor.
  • msg → gelen mesaj (örneğin CargoId = "CARGO-001")
  • Task.Delay(2000) → yükleme işlemini 2 saniye sürüyormuş gibi yapıyor.
  • deliveryActor.Tell(msg) → iş bittikten sonra başka bir aktöre (Teslimat aktörü) mesaj gönderiyor.

Akka Mantığına Göre Akış

  1. Coordinator aktör, LoadingActor’a mesaj gönderir (Tell() ile).
  2. LoadingActor, StartCargo mesajını alır (çünkü o türü dinliyor).
  3. ReceiveAsync içinde ne yazdıysan onu yapar.
  4. İş bitince, sonucu başka bir aktöre aktarabilir (DeliveryActor).

Kısaca Özetle

  • ReceiveActor: mesajları dinleyip, hangi mesaj geldiğine göre çalışır.
  • Receive<T>() veya ReceiveAsync<T>(): belirli bir mesaj türü için davranış tanımlamanı sağlar.
  • Bu sayede her aktör tek bir işi yapar ve birbirleriyle mesajlaşarak sistemi çalıştırırlar.

**IActorRef → Bu, Akka.NET’te bir actor’a referans** demektir.

  • Actor’a direkt erişemezsin, sadece referansı üzerinden mesaj gönderebilirsin (Tell() gibi).
  • Actor’un kendisi başka bir thread’de çalışıyor olabilir; sen onunla güvenli bir şekilde IActorRef üzerinden konuşursun.

Akka.NET’te Hata Toleransı

Akka.NET actor modeli, hata toleransı için özel bir yaklaşım kullanır:

Supervisor Strategy (Gözetici Strateji)

  • Her actor’un bir parent’ı vardır (koordinatör gibi).
  • Eğer bir child actor hata yaparsa, parent karar verir:
  • Restart (yeniden başlat)
  • Stop (durdur)
  • Resume (devam et)
  • Escalate (hata üst seviyeye taşınır)

Mesaj bazlı izolasyon

  • Bir actor’un hatası diğer actor’ları etkilemez.
  • Mesajlar queue (kuyruk) üzerinden yönetilir, kaybolmaz.

Cluster ile dağıtık tolerans

  • Actor’lar farklı node’larda olabilir.
  • Bir node down olsa bile, cluster diğer node’larda çalışmaya devam eder.

Şimdi hata toleransını Cargo örneğimizde simüle edelim.

Amacımız:

  • LoadingActor bazen hata versin,
  • Ama DeliveryActor ve diğer actor’lar çalışmaya devam etsin.
  • Parent actor (coordinator) hatayı yakalayıp restart yapsın.

Supervisor Strategy Tanımlayalım

Akka.NET’te parent actor, child actor’ların hatalarını yönetmek için supervisor strategy kullanır.

CargoCoordinatorActor’a ekleyeceğiz:

public class CargoCoordinatorActor : ReceiveActor
{
    private readonly IActorRef _loadingActor;
    private readonly IActorRef _deliveryActor;

    public CargoCoordinatorActor()
    {
        _deliveryActor = Context.ActorOf<DeliveryActor>("deliveryActor");
        _loadingActor = Context.ActorOf(Props.Create(() => new LoadingActor(_deliveryActor)), "loadingActor");

        Receive<StartCargo>(msg =>
        {
            Console.WriteLine($"🔔 Yeni kargo süreci başlatıldı: {msg.CargoId}");
            SendToLoadingActor(msg);
        });

        Receive<RestartedCargo>(msg =>
        {
            Console.WriteLine($"🔄 Restart sonrası kargo mesajı tekrar gönderiliyor: {msg.CargoId}");
            SendToLoadingActor(new StartCargo { CargoId = msg.CargoId, Destination = msg.Destination });
        });
    }

    private void SendToLoadingActor(StartCargo msg)
    {
        _loadingActor.Tell(msg, Self);
    }

    protected override SupervisorStrategy SupervisorStrategy()
    {
        return new OneForOneStrategy(
            maxNrOfRetries: 3,
            withinTimeRange: TimeSpan.FromSeconds(10),
            localOnlyDecider: ex =>
            {
                Console.WriteLine($"⚠️ Hata yakalandı: {ex.Message}, LoadingActor restart edilecek!");

                var originalMsg = new RestartedCargo { CargoId = "CARGO-001", Destination = "Ankara" };
                Self.Tell(originalMsg);

                return Directive.Restart;
            });
    }
}

LoadingActor ve DeliveryActor’ları yaratır.

StartCargo mesajını alır → yükleme actor’ına gönderir.

Supervisor stratejisi:

  • LoadingActor hata verirse restart edilir.
  • Coordinator restart sonrası mesajı tekrar gönderir (RestartedCargo mesajı ile).

SendToLoadingActor metodu mesaj gönderme işini kolaylaştırıyor.

LoadingActor’u hata verecek şekilde değiştirelim

public class LoadingActor : ReceiveActor
{
    private static bool simulateError = true; 
    private readonly IActorRef _deliveryActor;

    public LoadingActor(IActorRef deliveryActor)
    {
        _deliveryActor = deliveryActor;

        ReceiveAsync<StartCargo>(async msg =>
        {
            if (simulateError)
            {
                simulateError = false;
                throw new Exception("❌ Yükleme sırasında beklenmeyen hata!");
            }

            Console.WriteLine($"📦 Kargo {msg.CargoId} yükleniyor...");
            await Task.Delay(1000);
            Console.WriteLine($"✅ Kargo {msg.CargoId} başarıyla yüklendi.");

            _deliveryActor.Tell(msg);
        });
    }
}
  • İlk mesaj geldiğinde LoadingActor hata verecek.
  • Parent actor (CargoCoordinatorActor) hatayı yakalayacak ve restart edecek.
  • İkinci denemede yükleme düzgün çalışacak. simulateError static olduğu için ilk denemede hata fırlatıyor, restart sonrası tekrar hata vermez.

Böylece hata toleransını simüle etmiş olduk:

  • Actor hata yaptı,
  • Parent actor restart etti,
  • Sistem kesintisiz çalıştı.

OneForOneStrategy

new OneForOneStrategy(
    maxNrOfRetries: 3,
    withinTimeRange: TimeSpan.FromSeconds(10),
    localOnlyDecider: ex => { ... }
)
  • OneForOneStrategy: Bu strateji sadece hata veren aktörü restart eder.
  • Alternatif olarak AllForOneStrategy da var, o zaman aynı parent altındaki tüm actorlar restart edilir.

maxNrOfRetries: 3

Aynı actor max 3 kez restart edilebilir.

withinTimeRange: TimeSpan.FromSeconds(10)

Bu 3 restart 10 saniye içinde gerçekleşirse sayılır. Eğer actor 10 saniyeden daha uzun süre boyunca hata verirse, restart sayısı sıfırlanır ve tekrar 3 deneme hakkı olur.

Eğer 3 kez içinde tekrar hata verirse, actor artık restart edilmez ve ölür.

  • 0s: hata 1 → restart 1
  • 3s: hata 2 → restart 2
  • 7s: hata 3 → restart 3 artık max deneme tamam
  • 12s: hata 4 → önceki 10 saniye geçti → counter sıfırlandı, restart 1 sayılır

veya actor 3 saniye içinde 3 hata üretirse, o zaman:

  1. hata → restart edilir
  2. hata → restart edilir
  3. hata → restart edilir
  4. hata (hala 10 saniye içinde olursa) → artık hakkı biter → actor “ölür” (Stop edilir)

localOnlyDecider: ex => { ... }

  • Bu bir lambda veya fonksiyon: actor hata verdiğinde çalışır.
  • Hata objesi (ex) parametre olarak gelir.
  • Fonksiyon bir Directive döndürür: Restart, Resume, Stop, Escalate.

Tam Çalışan Kod :

using System;
using Akka.Actor;
using System.Threading.Tasks;

// Mesaj modeli
public class StartCargo
{
    public string CargoId { get; set; }
    public string Destination { get; set; }
}

// Teslimat aktörü
public class DeliveryActor : ReceiveActor
{
    public DeliveryActor()
    {
        ReceiveAsync<StartCargo>(async msg =>
        {
            Console.WriteLine($"🚚 Kargo {msg.CargoId}, {msg.Destination} adresine yola çıktı...");
            await Task.Delay(1000);
            Console.WriteLine($"🎉 Kargo {msg.CargoId}, {msg.Destination} adresine teslim edildi!");
        });
    }
}

// Yükleme aktörü
public class LoadingActor : ReceiveActor
{
    private static bool simulateError = true; // STATIC olmalı

    private readonly IActorRef _deliveryActor;

    public LoadingActor(IActorRef deliveryActor)
    {
        _deliveryActor = deliveryActor;

        ReceiveAsync<StartCargo>(async msg =>
        {
            if (simulateError)
            {
                simulateError = false; // restart sonrası da false kalır
                throw new Exception("❌ Yükleme sırasında beklenmeyen hata!");
            }

            Console.WriteLine($"📦 Kargo {msg.CargoId} yükleniyor...");
            await Task.Delay(1000);
            Console.WriteLine($"✅ Kargo {msg.CargoId} başarıyla yüklendi.");

            _deliveryActor.Tell(msg);
        });
    }
}

// Koordinatör aktör
public class CargoCoordinatorActor : ReceiveActor
{
    private readonly IActorRef _loadingActor;
    private readonly IActorRef _deliveryActor;

    public CargoCoordinatorActor()
    {
        // Delivery actor oluşturulur
        _deliveryActor = Context.ActorOf<DeliveryActor>("deliveryActor");

        // Loading actor oluşturulur, delivery actor referansı verilir
        _loadingActor = Context.ActorOf(Props.Create(() => new LoadingActor(_deliveryActor)), "loadingActor");

        // Başlatılan kargo mesajları
        Receive<StartCargo>(msg =>
        {
            Console.WriteLine($"🔔 Yeni kargo süreci başlatıldı: {msg.CargoId}");
            SendToLoadingActor(msg);
        });

        // Supervisor restart sonrası mesajları yeniden gönder
        Receive<RestartedCargo>(msg =>
        {
            Console.WriteLine($"🔄 Restart sonrası kargo mesajı tekrar gönderiliyor: {msg.CargoId}");
            SendToLoadingActor(new StartCargo { CargoId = msg.CargoId, Destination = msg.Destination });
        });
    }

    private void SendToLoadingActor(StartCargo msg)
    {
        _loadingActor.Tell(msg, Self);
    }

    // Supervisor strategy override
    protected override SupervisorStrategy SupervisorStrategy()
    {
        return new OneForOneStrategy(
            maxNrOfRetries: 3,
            withinTimeRange: TimeSpan.FromSeconds(10),
            localOnlyDecider: ex =>
            {
                Console.WriteLine($"⚠️ Hata yakalandı: {ex.Message}, LoadingActor restart edilecek!");

                // Restart sonrası mesajı coordinator tekrar gönderecek
                var originalMsg = new RestartedCargo { CargoId = "CARGO-001", Destination = "Ankara" };
                Self.Tell(originalMsg);

                return Directive.Restart;
            });
    }
}

// Restart sonrası mesaj
public class RestartedCargo
{
    public string CargoId { get; set; }
    public string Destination { get; set; }
}

// Program
public class Program
{
    public static async Task Main(string[] args)
    {
        var system = ActorSystem.Create("CargoSystem");

        var coordinator = system.ActorOf<CargoCoordinatorActor>("cargoCoordinator");

        // Yeni kargo oluştur
        coordinator.Tell(new StartCargo { CargoId = "CARGO-001", Destination = "Ankara" });

        Console.WriteLine("\nKargo işlemi başlatıldı. Çıkmak için Enter'a bas...");
        Console.ReadLine();

        await system.Terminate();
    }
}
var deliveryActor = Context.ActorOf<DeliveryActor>($"deliveryActor-{msg.CargoId}");

Bu kullanım parametresiz constructor olan aktörler için geçerli. DeliveryActor sınıfında constructor parametresi yok, dolayısıyla direkt olarak sınıf ismiyle oluşturabiliyoruz.

var loadingActor = Context.ActorOf(Props.Create(() => new LoadingActor(deliveryActor)), $"loadingActor-{msg.CargoId}");

Burada actor constructor’ına parametre geçiriyoruz. LoadingActor’ın constructor’ı IActorRef deliveryActor istiyor. Akka.NET doğrudan ActorOf<LoadingActor>() ile parametreli constructor’ı desteklemez, bu yüzden Props.Create ile lambda kullanıyoruz.

O zaman bir sonraki adımda paralel kargo işleme yapacağız. Her kargo bağımsız actor’larda çalışacak ve supervisor stratejisi her biri için ayrı ayrı işleyecek. Böylece gerçek bir dağıtık sistem mantığını görebileceğiz.

Aşağıdaki örnek, 3 kargo paralel olarak yüklenecek ve teslim edilecek, her kargo için LoadingActor ve DeliveryActor ayrı actor instance’ları olacak:

using System;
using Akka.Actor;
using System.Threading.Tasks;

// Mesaj modeli
public class StartCargo
{
    public string CargoId { get; set; }
    public string Destination { get; set; }
}

// Restart sonrası mesaj
public class RestartedCargo
{
    public string CargoId { get; set; }
    public string Destination { get; set; }
}

// Teslimat aktörü
public class DeliveryActor : ReceiveActor
{
    public DeliveryActor()
    {
        ReceiveAsync<StartCargo>(async msg =>
        {
            Console.WriteLine($"🚚 Kargo {msg.CargoId}, {msg.Destination} adresine yola çıktı...");
            await Task.Delay(1000);
            Console.WriteLine($"🎉 Kargo {msg.CargoId}, {msg.Destination} adresine teslim edildi!");
        });
    }
}

// Yükleme aktörü
public class LoadingActor : ReceiveActor
{
    private static bool simulateError = true;
    private readonly IActorRef _deliveryActor;
    private readonly string _cargoId;

    public LoadingActor(IActorRef deliveryActor, string cargoId)
    {
        _deliveryActor = deliveryActor;
        _cargoId = cargoId;

        ReceiveAsync<StartCargo>(async msg =>
        {
            if (simulateError)
            {
                simulateError = false; // restart sonrası tekrar hata çıkmaz
                throw new Exception($"❌ Kargo {_cargoId} yükleme sırasında beklenmeyen hata!");
            }

            Console.WriteLine($"📦 Kargo {_cargoId} yükleniyor...");
            await Task.Delay(1000);
            Console.WriteLine($"✅ Kargo {_cargoId} başarıyla yüklendi.");

            _deliveryActor.Tell(msg);
        });
    }
}

// Koordinatör aktör
public class CargoCoordinatorActor : ReceiveActor
{
    public CargoCoordinatorActor()
    {
        Receive<StartCargo>(msg =>
        {
            Console.WriteLine($"🔔 Yeni kargo süreci başlatıldı: {msg.CargoId}");

            // Her kargo için bağımsız delivery ve loading actor oluştur
            var deliveryActor = Context.ActorOf<DeliveryActor>($"deliveryActor-{msg.CargoId}");
            var loadingActor = Context.ActorOf(Props.Create(() => new LoadingActor(deliveryActor, msg.CargoId)), $"loadingActor-{msg.CargoId}");

            // Mesajı gönder
            loadingActor.Tell(msg, Self);
        });

        Receive<RestartedCargo>(msg =>
        {
            Console.WriteLine($"🔄 Restart sonrası kargo mesajı tekrar gönderiliyor: {msg.CargoId}");
            var deliveryActor = Context.Child($"deliveryActor-{msg.CargoId}");
            var loadingActor = Context.Child($"loadingActor-{msg.CargoId}");

            if (loadingActor.IsNobody())
            {
                // actor yeniden oluştur
                var newDelivery = deliveryActor.IsNobody() ? Context.ActorOf<DeliveryActor>($"deliveryActor-{msg.CargoId}") : deliveryActor;
                var newLoading = Context.ActorOf(Props.Create(() => new LoadingActor(newDelivery, msg.CargoId)), $"loadingActor-{msg.CargoId}");
                newLoading.Tell(new StartCargo { CargoId = msg.CargoId, Destination = msg.Destination });
            }
            else
            {
                loadingActor.Tell(new StartCargo { CargoId = msg.CargoId, Destination = msg.Destination });
            }
        });
    }

    protected override SupervisorStrategy SupervisorStrategy()
    {
        return new OneForOneStrategy(
            maxNrOfRetries: 3,
            withinTimeRange: TimeSpan.FromSeconds(10),
            localOnlyDecider: ex =>
            {
                var actorName = Sender.Path.Name; // örn: loadingActor-CARGO-001
                var cargoId = string.Join("-", actorName.Split('-')[1..]); // "CARGO-001" alır
                Console.WriteLine($"⚠️ Hata yakalandı: {ex.Message}, {cargoId} restart edilecek!");

                Self.Tell(new RestartedCargo { CargoId = cargoId, Destination = "Ankara" });
                return Directive.Restart;
            });
    }
}

// Program
public class Program
{
    public static async Task Main(string[] args)
    {
        var system = ActorSystem.Create("CargoSystem");

        var coordinator = system.ActorOf<CargoCoordinatorActor>("cargoCoordinator");

        // Paralel kargoları başlat
        coordinator.Tell(new StartCargo { CargoId = "CARGO-001", Destination = "Ankara" });
        coordinator.Tell(new StartCargo { CargoId = "CARGO-002", Destination = "İstanbul" });
        coordinator.Tell(new StartCargo { CargoId = "CARGO-003", Destination = "İzmir" });

        Console.WriteLine("\nKargo işlemleri başlatıldı. Çıkmak için Enter'a bas...");
        Console.ReadLine();

        await system.Terminate();
    }
}

($"deliveryActor-{msg.CargoId}")

Bu kısım aktörün benzersiz adını belirliyor. String interpolasyonu ($"...") sayesinde, her kargoya özel bir isim oluşturuluyor.

Bu sayede her kargo için kendi DeliveryActor örneği oluşturulmuş olur. Yani sistemde şu şekilde aktörler bulunabilir:

/user/cargoCoordinator/deliveryActor-CARGO-001
/user/cargoCoordinator/deliveryActor-CARGO-002
/user/cargoCoordinator/deliveryActor-CARGO-003

Sender ne anlama geliyor?

Akka.NET’te her mesaj gönderimi Tell() veya Ask() ile yapılır ve her mesajın bir göndereni (sender) vardır. Yani bir actor başka bir actore mesaj gönderdiğinde, alıcı actor mesajı aldığında Sender özelliği üzerinden “beni kim çağırdı?” bilgisini görebilir.

Kısaca:

loadingActor.Tell(msg, Self);

dediğimizde —

  • Self: mesajı gönderen actor’ü (örneğin CargoCoordinatorActor) temsil eder.
  • loadingActor: mesajı alan actor’dür.
  • Sender: alıcı actor’ün gözünden bakıldığında, mesajın kimden geldiğini (CargoCoordinatorActor) gösterir.

Ama burada farklı bir durum var

Burada kod SupervisorStrategy içinde çalışıyor:

localOnlyDecider: ex =>
{
    var actorName = Sender.Path.Name;
    ...
}

Buradaki Sender — bu hatayı fırlatan alt actor’ü (örneğin LoadingActor) temsil ediyor. Yani bu blok, hangi actor hata fırlattıysa onun referansını Sender üzerinden görebiliyor.

Böylece şu satır:

var actorName = Sender.Path.Name;

örneğin "loadingActor-CARGO-001" gibi bir isim döndürüyor. Sen de hemen ardından bu ismi parçalayarak "CARGO-001" kısmını alıyorsun:

var cargoId = string.Join("-", actorName.Split('-')[1..]);

Bu ifade C# 8.0 ile gelen range (aralık) operatörüdür. [1..] demek, dizinin 1. indeksinden itibaren sonuna kadar olan elemanları al anlamına gelir.

Yani:

["loadingActor", "CARGO", "001"][1..]

şuna dönüşür:

["CARGO", "001"]

SupervisorStrategy içinde localOnlyDecider sadece şunu biliyor:

  • Hata (Exception)
  • Hata atan aktör (Sender)

Ama o aktörün işlem yaptığı mesaj (StartCargo) bu scope’ta artık mevcut değil. Yani burada Destination bilgisi yok.

Cargo bilgilerini bir dictionary’de tutmak

CargoCoordinatorActor içinde her kargo için CargoIdDestination eşlemesini saklarsan, hata oluştuğunda cargoId üzerinden doğru Destination’ı bulabilirsin.

Aşağıdaki şekilde basitçe çözülebilir 👇

public class CargoCoordinatorActor : ReceiveActor
{
    private readonly Dictionary<string, string> _cargoDestinations = new();

    public CargoCoordinatorActor()
    {
        Receive<StartCargo>(msg =>
        {
            Console.WriteLine($"🔔 Yeni kargo süreci başlatıldı: {msg.CargoId}");

            // bilgiyi dictionary'e kaydet
            _cargoDestinations[msg.CargoId] = msg.Destination;

            var deliveryActor = Context.ActorOf<DeliveryActor>($"deliveryActor-{msg.CargoId}");
            var loadingActor = Context.ActorOf(
                Props.Create(() => new LoadingActor(deliveryActor, msg.CargoId)),
                $"loadingActor-{msg.CargoId}"
            );

            loadingActor.Tell(msg, Self);
        });

        Receive<RestartedCargo>(msg =>
        {
            Console.WriteLine($"🔄 Restart sonrası kargo mesajı tekrar gönderiliyor: {msg.CargoId}");
            var deliveryActor = Context.Child($"deliveryActor-{msg.CargoId}");
            var loadingActor = Context.Child($"loadingActor-{msg.CargoId}");

            if (loadingActor.IsNobody())
            {
                var newDelivery = deliveryActor.IsNobody()
                    ? Context.ActorOf<DeliveryActor>($"deliveryActor-{msg.CargoId}")
                    : deliveryActor;

                var newLoading = Context.ActorOf(
                    Props.Create(() => new LoadingActor(newDelivery, msg.CargoId)),
                    $"loadingActor-{msg.CargoId}"
                );

                newLoading.Tell(new StartCargo
                {
                    CargoId = msg.CargoId,
                    Destination = msg.Destination
                });
            }
            else
            {
                loadingActor.Tell(new StartCargo
                {
                    CargoId = msg.CargoId,
                    Destination = msg.Destination
                });
            }
        });
    }

    protected override SupervisorStrategy SupervisorStrategy()
    {
        return new OneForOneStrategy(
            maxNrOfRetries: 3,
            withinTimeRange: TimeSpan.FromSeconds(10),
            localOnlyDecider: ex =>
            {
                var actorName = Sender.Path.Name;
                var cargoId = string.Join("-", actorName.Split('-')[1..]); // "CARGO-001"

                var destination = _cargoDestinations.ContainsKey(cargoId)
                    ? _cargoDestinations[cargoId]
                    : "Bilinmiyor";

                Console.WriteLine($"⚠️ Hata yakalandı: {ex.Message}, {cargoId} ({destination}) restart edilecek!");

                Self.Tell(new RestartedCargo
                {
                    CargoId = cargoId,
                    Destination = destination
                });

                return Directive.Restart;
            });
    }
}

İyi Çalışmalar.


메타데이터
post_id
d8f0c0c00fdc
slug
akka-net-kullanımı-d8f0c0c00fdc
url
https://medium.com/@cihanasan1/akka-net-kullan%C4%B1m%C4%B1-d8f0c0c00fdc
canonical_url
https://medium.com/@cihanasan1/akka-net-kullan%C4%B1m%C4%B1-d8f0c0c00fdc
author_url
https://medium.com/@cihanasan1
status
ok
fetched_at
2026-08-21 13:24:54