Von Akka (JVM)
actor-ts ist der engste spirituelle Cousin von Akka im TypeScript-Umfeld. Die meisten Konzepte mappen 1:1; viele APIs sind identisch benannt. Dieser Guide führt durch die Übersetzung.
Konzept-Mapping
Abschnitt betitelt „Konzept-Mapping“| Akka (JVM) | actor-ts |
|---|---|
akka.actor.Actor | Actor<TMsg> |
akka.actor.ActorRef | ActorRef<T> |
akka.actor.Props | Props<TMsg> |
tell(msg) / ! | tell(msg) |
ask(msg).mapTo[T] | ref.ask<TRes>(msg, timeoutMs) |
context.spawnAnonymous(props) | context.spawnAnonymous(props) |
OneForOneStrategy | OneForOneStrategy |
AllForOneStrategy | AllForOneStrategy |
become(receive) | context.become(handler) |
stash() / unstashAll() | context.stash() / context.unstashAll() |
context.watch(ref) | context.watch(ref) |
Terminated(ref) | Terminated-Systemnachricht |
PoisonPill | PoisonPill.instance |
Kill | Kill.instance |
setReceiveTimeout(d) | context.setReceiveTimeout(ms) |
EventStream | system.eventStream |
Cluster.get(system).join(...) | Cluster.join(system, settings) |
ClusterSharding | ClusterSharding |
ClusterSingleton | ClusterSingletonManager / Proxy |
DistributedPubSub | DistributedPubSub |
DistributedData | DistributedData (per Extension) |
PersistentActor | PersistentActor |
persist(event)(cb) | this.persist(event, cb) |
Akka HTTP | HttpExtension + Route-DSL |
Akka Streams | NICHT verfügbar |
Before/After
Abschnitt betitelt „Before/After“// Akka Scala:class Counter extends Actor { var count = 0 def receive = { case "inc" => count += 1 case "get" => sender() ! count }}
// actor-ts:import { Actor, type ActorRef } from 'actor-ts';
type Cmd = { kind: 'inc' } | { kind: 'get'; replyTo: ActorRef<number> };
class Counter extends Actor<Cmd> { private count = 0; override onReceive(cmd: Cmd): void { if (cmd.kind === 'inc') this.count++; else cmd.replyTo.tell(this.count); }}Unterschiede:
- TypeScript braucht explizite Nachrichten-Typen (
Cmd) - AkkasAnylässt sich nicht übersetzen. sender()wird zu einer explizitenreplyTo-Ref in der Nachricht oder zuthis.sender(Option).- Keine Pattern-Matching-Syntax; nutze
if/elseoderts-pattern.
Supervisor
Abschnitt betitelt „Supervisor“// Akka Scala:override val supervisorStrategy = OneForOneStrategy() { case _: ArithmeticException => Resume case _: NullPointerException => Restart case _: Exception => Escalate}
// actor-ts:override supervisorStrategy = new OneForOneStrategy( decideBy([ { match: ArithmeticError, then: Directive.Resume }, { match: NullPointerError, then: Directive.Restart }, { match: Error, then: Directive.Escalate }, ]),);Benennung + Struktur identisch; die Direktiven sind dieselben.
Cluster + Sharding
Abschnitt betitelt „Cluster + Sharding“// Akka Scala:val cluster = Cluster(system)cluster.join(Address("akka", "MySystem", "host", port))
val region = ClusterSharding(system).start( typeName = "Counter", entityProps = Props[Counter], settings = ClusterShardingSettings(system), extractEntityId = ..., extractShardId = ...,)
// actor-ts:const cluster = await Cluster.join(system, { host, port, seeds });
const region = cluster.sharding.start({ typeName: 'Counter', entityProps: Props.create(() => new Counter()), extractEntityId: (msg) => msg.id, numShards: 100,});Größtenteils Drop-in. Unterschiede:
extractShardIdwird in actor-ts ausextractEntityId + numShardsabgeleitet (shardId = hash(entityId) % numShards). Keine separate Funktion.ClusterShardingSettingssteht inline als Optionen vonstart().
PersistentActor
Abschnitt betitelt „PersistentActor“// Akka Scala:class Account(val id: String) extends PersistentActor { override def persistenceId = s"account-$id" var balance = 0
override def receiveCommand = { case Deposit(amt) => persist(Deposited(amt))(e => balance += e.amount) }
override def receiveRecover = { case Deposited(amt) => balance += amt }}
// actor-ts:class Account extends PersistentActor<Cmd, Event, State> { constructor(public readonly id: string) { super(); } readonly persistenceId = `account-${this.id}`;
initialState(): State { return { balance: 0 }; }
onEvent(state: State, event: Event): State { if (event.kind === 'deposited') return { balance: state.balance + event.amount }; return state; }
onCommand(state: State, cmd: Cmd): void { if (cmd.kind === 'deposit') { this.persist({ kind: 'deposited', amount: cmd.amount }, () => {}); } }}Schlüssel-Unterschiede:
- Ein einziges
onEventstatt geteilterreceiveCommand/receiveRecover. Läuft sowohl beim Persistieren als auch bei Recovery. persist-Callback-Signatur -(newState) => voidstatt(event) => unit.- State ist expliziter Typ-Parameter - Akkas zustandsbehaftete Variable wird zu einer State-Form.
Cluster Singleton
Abschnitt betitelt „Cluster Singleton“// Akka Scala:val singleton = system.spawn( ClusterSingletonManager.props( singletonProps = Props[MyActor], terminationMessage = PoisonPill, settings = ClusterSingletonManagerSettings(system), ), name = "singletonManager",)
// actor-ts:system.spawn( ClusterSingletonManager.props({ cluster, typeName: 'my-singleton', singletonProps: Props.create(() => new MyActor()), }), 'singleton-manager-my-singleton',);Dasselbe Modell, leicht andere Konfigurations-Form.
Was fehlt
Abschnitt betitelt „Was fehlt“- Akka Streams - keine Portierung. Nutze Promise-basierte Patterns oder eine separate Streams-Library.
- Akka HTTPs typisierte Route-DSL - actor-ts hat eine eigene DSL, aber sie ist einfacher / weniger feature-reich.
- Akka Persistence Querys reaktive Stream-API - actor-ts hat
PersistenceQuery, aber alsAsyncIterable, nicht als Stream. - Einige fortgeschrittene Supervision-Features - Backoff-Supervision existiert; “watch a Future”-Patterns brauchen manuelles Wiring.
Was besser ist
Abschnitt betitelt „Was besser ist“- TypeScript-Typen - Nachrichten-Typen werden an der
Compile-Grenze geprüft. Keine
Any- /case class-Runtime-Checks. - Buns schneller Start - sub-100 ms statt 1+ Sekunde der JVM.
- Kleinere Binaries - keine JVM zum Ausliefern.
- Einfacheres operationelles Modell - Single-Process JS statt JVM-Tuning.
Migrations-Ansatz
Abschnitt betitelt „Migrations-Ansatz“Für eine bestehende Akka-App, die actor-ts erwägt:
- Nicht alles auf einmal umschreiben. Lass actor-ts in einem neuen Service laufen, der das Akka-System via HTTP/gRPC vorlagert.
- Einen Bounded Context nach dem anderen migrieren - wähle eine self-contained Domäne (Orders, Sessions) und portiere sie.
- Persistenz vorsichtig re-exportieren - von Akka geschriebene Events sind JSON, wenn du Jackson genutzt hast; actor-ts kann sie mit einem EventAdapter lesen.
Wohin als Nächstes
Abschnitt betitelt „Wohin als Nächstes“- Quickstart - actor-ts Hello-World.
- Fundamentals - Übersicht - Konzept-Landkarte.
- Migration - Übersicht - Framework-übergreifender Vergleich.
- from-pekko - für den Pekko-Fork.