Los distributed actors de Swift, introducidos en Swift 5.7, extienden el modelo de actores para que puedan vivir en procesos o máquinas distintas. La llamada a un método de un actor remoto se ve igual que la de uno local, con una única diferencia sintáctica: todas las llamadas deben ser async throws. El sistema de tipos garantiza que no puedes olvidarte de gestionar la red o la serialización.
Declarar un actor distribuido
La única diferencia con un actor normal es la palabra clave distributed. Los métodos que quieras llamar desde otro proceso también deben marcarse con distributed:
import Distributed
// Sistema de actores distribuidos (necesario para el runtime)
typealias DefaultDistributedActorSystem = LocalTestingDistributedActorSystem
distributed actor ContadorDistribuido {
var valor: Int = 0
// Este método puede llamarse desde otro proceso
distributed func incrementar() {
valor += 1
}
distributed func obtenerValor() -> Int {
valor
}
// Este método solo es accesible localmente
func resetear() {
valor = 0
}
}
// Uso:
let contador = ContadorDistribuido(actorSystem: sistema)
try await contador.incrementar()
let v = try await contador.obtenerValor() // siempre async throws en distributed
ClusterSystem de swift-distributed-actors
Para producción, el paquete oficial swift-distributed-actors proporciona un sistema de clústeres TCP:
import DistributedCluster
// Nodo 1 (servidor)
let sistema = await ClusterSystem("nodo1") { settings in
settings.bindHost = "127.0.0.1"
settings.bindPort = 2550
}
let inventario = InventarioActor(actorSystem: sistema)
// Nodo 2 (cliente)
let cliente = await ClusterSystem("nodo2") { settings in
settings.bindHost = "127.0.0.1"
settings.bindPort = 2551
}
// Conectar al primer nodo
await cliente.cluster.join(host: "127.0.0.1", port: 2550)
Serialización: Codable en distributed actors
Los parámetros y valores de retorno de los métodos distribuidos deben conformar Codable (o el serializador que configure el sistema). El compilador lo verifica en tiempo de compilación:
struct Pedido: Codable, Sendable {
let id: UUID
let producto: String
let cantidad: Int
let total: Double
}
distributed actor GestorPedidos {
private var pedidos: [UUID: Pedido] = [:]
// Codable verificado en compilación
distributed func crear(pedido: Pedido) throws {
guard pedido.cantidad > 0 else {
throw PedidoError.cantidadInvalida
}
pedidos[pedido.id] = pedido
}
distributed func listar() -> [Pedido] {
Array(pedidos.values)
}
}
Resolver actores remotos
Para obtener una referencia a un actor que vive en otro nodo, se usa el ID del actor:
// El actor se crea en el servidor y su ID se envÃa al cliente
// (por ejemplo, a través de un mensaje inicial o un registro de nombres)
let actorID = inventario.id
// En el cliente: resolver la referencia remota
let inventarioRemoto = try InventarioActor.resolve(id: actorID, using: clienteSistema)
// La llamada se ve igual que si fuera local
let stock = try await inventarioRemoto.stockActual(producto: "Swift Book")
Ejemplo real: juego multiplayer
distributed actor SalaDeJuego {
var jugadores: [String: Int] = [:] // nombre ? puntuación
distributed func unirse(jugador: String) {
jugadores[jugador] = 0
print("(jugador) se unió. Jugadores: (jugadores.count)")
}
distributed func anotar(jugador: String, puntos: Int) throws {
guard jugadores[jugador] != nil else {
throw JuegoError.jugadorNoRegistrado(jugador)
}
jugadores[jugador, default: 0] += puntos
}
distributed func clasificacion() -> [(nombre: String, puntos: Int)] {
jugadores.map { ($0.key, $0.value) }
.sorted { $0.puntos > $1.puntos }
}
}
Ejemplo real: microservicios en Swift puro
distributed actor ServicioInventario {
private var stock: [String: Int] = [:]
distributed func verificarDisponibilidad(producto: String) -> Bool {
(stock[producto] ?? 0) > 0
}
distributed func reservar(producto: String, cantidad: Int) throws {
guard let actual = stock[producto], actual >= cantidad else {
throw InventarioError.stockInsuficiente(producto)
}
stock[producto] = actual - cantidad
}
}
distributed actor ServicioPedidos {
let inventario: ServicioInventario
distributed func procesarPedido(_ pedido: Pedido) async throws {
// Llamada entre microservicios como si fueran locales
guard try await inventario.verificarDisponibilidad(producto: pedido.producto) else {
throw PedidoError.sinStock
}
try await inventario.reservar(producto: pedido.producto, cantidad: pedido.cantidad)
// ... procesar pago, notificar, etc.
}
}
Resumen
Los distributed actors de Swift convierten la distribución en una propiedad del tipo, no un detalle de implementación. La declaración es idéntica a un actor normal salvo el modificador distributed; el compilador verifica que todos los parámetros son serializables; y las llamadas remotas se ven sintácticamente iguales a las locales excepto por el async throws obligatorio. El paquete swift-distributed-actors proporciona el sistema de clústeres TCP para producción, y el LocalTestingDistributedActorSystem integrado en la librerÃa estándar facilita los tests sin infraestructura.
