Hi everyone,
We'd like to share a pitch of a distributed actors feature we've been working on for a while now.
This proposal extends distributed actor capabilities in one important aspect: it allows a proper server/client split of distributed applications! When we first introduced distributed actors, many developers were asking about using them for situations where there's a clear distinction between "server" and "client". The initial work prioritized peer-to-peer systems where all nodes participating in it (e.g. a cluster), share the same code and this feature was not required there.
Previously, serving this use case was problematic as obtaining a remote reference to an actor required that the client also have the declaration of the concrete type that would be called on the server. Workarounds existed, but were not great.
With this proposal it is possible to write the following:
// API module
import Distributed
@DistributedProtocol
public protocol Greeter: DistributedActor
where ActorSystem: DistributedActorSystem<any Codable> {
distributed func greet(name: String) -> String
}
// Client module
import API // declared Greeter
import Distributed
let someActorSystem = // <the actor system you're using>
let remote = try $Greeter.resolve(id: id, using: someActorSystem)
let reply = try await remote.greet(name: "Caplin")
print(reply) // Dzień dobry, Caplin!
// oh, we didn't know the remote implementation is PolishGreeter!
This allows developers to publish an API package, that has protocol descriptions, and hide the server implementation types entirely from their client apps.
Do I understand it right that this is a major step in making away with client-server communication manually written by app developers who control both the client and server side? So no API documentation needed, no JSON encoding/decoding, just calling async functions on the client as if the server was actually just an application running in a different thread to be awaited for?
If you find a working time machine, you might enjoy a trip back to the late 90s when this was all the rage. DCOM, D’OLE, JMI… the implementations were numerous. OMG (the Object Management Group) released a thing called CORBA to try to unify them all. UML came out of this era.
Guess this is not only about server/client apps, but overall how two distributed systems can communicate while still natively using actors. Was playing a bit creating my own system based on web sockets and pretty early land into the situation "But what if I want one system for this purposes and another one for different? How do actors of different systems can communicate then?". Of course this could be solved with some effort, but glad there is solution now for that.
Props for an elegant solution with the use of macros!
Several small questions:
I guess on client side one can write some simplified version of distributed system just for the purpose of communication then?
Was thinking about API distribution, and now have overall question—for me this approach feels like more about monolith, where you can have client and server in one Swift codebase—then you can have one shared API with defined distributed protocols. What could be a solution for other clients/languages? Feels like source-generation based RPC systems have advantage here. (Thinking out loud, but maybe something like Swift -> C++ -> other languages? But don't know how distributed features are supported in interop...)
Is there any approaches/frameworks/languages that have that idea implemented? Or at least some research papers? Just curious to know, as this feels like something new and don't know what to compare even.
Technically since the entire distributed actors feature is just a "language tool to build RPC systems", yes you could build a system targeting non-Swift peers.
I will repeat though what I usually say when such questions come up. Distributed actors don't necessarily have to be the way you communicate any time there's a chance to do so. There's some systems where they fit, and some where they don't. Examples where they do great are anything real-time-ish, coupled, or deployed "together". There's many systems like this out there, just check success stories of Akka (Tesla Virtual Powerplant, game backends, energy management systems etc) or Orleans (again, game servers (the famous HALO use-case), and "Virtual Twin" systems where real-time-ness comes into play).
For example if you're talking about public APIs (e.g. RESTful APIs that people outside of "yourself" as in an internal to the dev team and "deployment unit") I'd rather suggest using Swift OpenAPI. What would be interesting indeed would be a bridge between distributed actors and OpenAPI (!), but we don't have this today.
Distributed actors currently are focused on use cases where both sides are Swift. With this proposal, we are very focused on serving some IPC (inter process communication) use cases. Although yes the "device to cloud" case is also enabled by this! This isn't to say you can't do multi language solutions, but this isn't currently a strong focus. It is definitely doable, but I believe we'll have to polish up more annotation and metadata information control before we can do this very well. In most Akka clustering powered production systems at customers in my previous life I supported, most of the time a system would have HTTP or WebSocket "entry points", into a then distributed compute cluster -- so it isn't "either actors OR ", but frequently it is both -- just depending on the "data on the inside / data on the outside" side of the equation.
--
I recently encountered this great paper from 2023, where researchers at Google make arguments for such distributed systems vs. the usual "everything is a public service and API". It's a great read and it is very actor system inspired (surprise!): Towards Modern Development of Cloud Applications I highly recommend anyone assuming "this is just corba" to give this a read, as as well designed location transparency system can do a lot of good - so let's not throw out the baby with the bathwater
Yeah, after thinking a bit more on weekend on topic—also think using distributed actors everywhere will be a bit heavy, especially where other simple tools will work.
Guess this was my overall question. Thx for response!
Looking forward to play with the implementation.
Gotcha. Yeah, for what it's worth I think we'd get "80%"™ of the benefits if we provided a nice OpenAPI "edge API -> bridge into my actor system" using either source generation or macros to map a specific "entry point" actor to an OpenAPI endpoint, once you're in there you're in actor land and can use all the binary weird protocols you want -- but it'd be very useful to be able to easily provide bridges into the "inside" though OpenAPI.
That ecosystem is primarily about "API First" design, so I think we'd want to keep that, and make sure the "inside" of a system can mesh well with it. Given that there's source generation involved in OpenAPI, I'm not sure if this proposal actually changes the equation a lot here, distributed actors already can "just" implement an API protocol genrated by OpenAPI after all...
I think this is worth investigating, but probably unrelated to the current pitch which focused on "swift on both ends, both sides under tight control of a team".
This is starting to feel like Java with URL class loaders downloading implementations just like Jini did in Java 20 years ago. If you don't know about that, please go look up River on Apache, and contemplate what was learned and implemented there to put native types and composite, user defined types, into a system that provided remote interfaces.