İçindekiler
Geleneksel nesne yönelimli programlamada bir nesne diğerinin metodunu çağırır ve çoğu zaman aynı belleği paylaşır. Actor modelinde ise bağımsız aktörler birbirlerine mesaj gönderir. Böylece kilitler, yarış koşulları ve hata yayılımı daha yönetilebilir hâle gelir. Elixir ile Akka bu fikri farklı araçlarla uygular; biri Erlang VM’nin doğal yeteneklerine, diğeri JVM ve Scala ekosistemine yaslanır.
``
Actor modeli nasıl çalışır?
Bir aktör; özel duruma, mesaj kutusuna ve davranışa sahip hesaplama birimidir. Aktörler doğrudan birbirlerinin durumuna erişmez. Her aktör sırayla bir mesaj işler ve sonuç olarak durumunu değiştirebilir, başka aktörlere mesaj gönderebilir veya yeni aktörler oluşturabilir.
Aktörleri gerçekten birbirinden tamamen bağımsız işletim sistemi iş parçacıkları sanmamak gerekir. Genellikle çok sayıda hafif aktör, daha küçük bir thread havuzu üzerinde zamanlanır. Basitçe:
\[N_{actor} \gg N_{thread}\]Bu ayrım sayesinde yüz binlerce aktör oluşturmak mümkün olabilir. Mesajların sırayla işlenmesi de aktörün kendi durumu için kilit ihtiyacını azaltır. Ancak farklı aktörlerden gelen mesajların küresel sırası garanti edilmez.
| Özellik | Elixir | Akka Typed (Scala) |
|---|---|---|
| Çalışma ortamı | BEAM | JVM |
| Mesaj biçimi | Dinamik Elixir terimleri | Tip güvenli protokoller |
| Temel soyutlama | Process, GenServer | Actor, Behavior |
| Hata yaklaşımı | Supervisor ve bağlantılar | Supervision stratejileri |
| Dağıtık çalışma | Dil ve VM ile bütünleşik | Cluster modülleriyle |
| Ekosistem | OTP merkezli | JVM ve Reactive ekosistemi |
Elixir: süreçler ve OTP
Elixir’de aktör fikrinin karşılığı BEAM süreçleridir. Bunlar işletim sistemi process’i değil, VM tarafından yönetilen hafif yürütme birimleridir. Üretim uygulamalarında ham süreçler yerine çoğunlukla GenServer kullanılır.
defmodule Counter do
use GenServer
def start_link(initial) do
GenServer.start_link(__MODULE__, initial, name: __MODULE__)
end
def increment, do: GenServer.cast(__MODULE__, :increment)
def value, do: GenServer.call(__MODULE__, :value)
def init(initial), do: {:ok, initial}
def handle_cast(:increment, state), do: {:noreply, state + 1}
def handle_call(:value, _from, state), do: {:reply, state, state}
end
Bu sayaçta durum yalnızca Counter sürecindedir. cast yanıt beklemeyen, call ise yanıt bekleyen mesajlaşmayı temsil eder. Süreç çökerse bir supervisor onu yeniden başlatabilir. OTP’nin meşhur yaklaşımı, hatayı saklamak yerine kontrollü biçimde çöküp toparlanmaktır: Let it crash!
Akka Typed: tip güvenli mesajlar
Akka Typed, aktörün kabul edebileceği mesajları Scala türleriyle tanımlar. Böylece yanlış mesajların önemli bir bölümü derleme sırasında yakalanır.
import akka.actor.typed.{ActorSystem, Behavior}
import akka.actor.typed.scaladsl.Behaviors
object Counter {
sealed trait Command
final case object Increment extends Command
final case class GetValue(replyTo: akka.actor.typed.ActorRef[Int]) extends Command
def apply(value: Int = 0): Behavior[Command] =
Behaviors.receiveMessage {
case Increment => apply(value + 1)
case GetValue(replyTo) =>
replyTo ! value
Behaviors.same
}
}
val system = ActorSystem(Counter(), "counter")
system ! Counter.Increment
Burada Behavior[Command], aktör protokolünü açıkça sınırlar. Değişmez durum her mesajdan sonra yeni bir davranış üretilerek taşınır. Bu yaklaşım, büyük Scala projelerinde güçlü derleyici denetimi sağlar.
Hangisini seçmeli?
Elixir; yüksek bağlantı sayısı, düşük gecikmeli mesajlaşma ve hata toleransı gereken sohbet, telekomünikasyon veya gerçek zamanlı sistemlerde son derece doğaldır. Akka ise JVM kütüphaneleriyle bütünleşmesi, güçlü tip sistemi ve mevcut Scala altyapısı nedeniyle kurumsal dağıtık sistemlerde öne çıkar.
Actor modeli bütün eşzamanlılık sorunlarını sihirli biçimde çözmez. Mesaj kutusunun sınırsız büyümesi, teslim garantileri, tekrar işleme ve ağ bölünmeleri yine tasarlanmalıdır. Seçim yaparken sözdiziminden çok ekosistem, operasyon bilgisi ve hata modelini değerlendirmek gerekir.
Yorumlar