Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up

Actors on Scala.js

License

NotificationsYou must be signed in to change notification settings

CodeMettle/jsactor

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

78 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Actors on Scala.js

Check out a rough example project athttps://github.com/CodeMettle/jsactor-spa-example

Disclaimer

Much of this code (the good parts) are straight fromTypesafe/Akka and I don't claimauthorship. Most of the code that isn't straight from Akka was at least written while looking at Akka code and usestheir concepts and techniques. Anything broken is my fault, everything else is thanks to their work. My hope is thatthey create and maintain an official Scala.js version of Akka; until thenjsactor seems to mostly work.

While this projects works, somewhat, it's not close to production-quality.

Why?

I'm used to working with the Actor model on the backend, with Scala.js it feels natural to use Actors and havehierarchy, a well-defined lifecycle, anEventStream, and so on.

Import

libraryDependencies++=Seq("com.codemettle.jsactor"%%%"jsactor"%"0.8.0","com.codemettle.jsactor"%%%"jsactor-bridge-client"%"0.8.0"// if using jsactor-bridge"com.codemettle.jsactor"%%%"jsactor-loglevel"%"0.8.0"// to use the LogLevel logging adapter)

Usage

(deleted JsActor section since it's now gone in favor of Akka.JS)

Server-client Interop

This is the part we care about, right? Why use Actors on the client if we're not interacting with a backend ActorSystem?

To that end, I'm including some extra projects that make interaction pretty seamless, at least in our usage. We havemany µservices running on the backend, one of which consolidates large amounts of data from many different sources andtransforms to model objects and pushes changes to the GUI. No pull, no REST, no client-side polling for changes - thebackend reacts to changes it cares about, based on the current client view (what it cares about), and when the modelobjects change then the server pushes them.

Shared model

// project with model classes shared between client & server, cross-compiled to JVM & JS ...libraryDependencies+="com.codemettle.jsactor"%%%"jsactor-bridge-shared"%"0.8.0" ...

Currently this project (so, by extension, client and server bridge projects) depends onuPickle, with no way to change that.

This whole readme is out of date, but nobody uses this library anyway, so NBD. But there now are*-upickle projectsthat are based on uPickle. I aim to addBooPickle and/orCirce implementations of client-server bridges.

uPickle was written, I assume, withautowire in mind - all types are known inadvance. That presents an issue forjsactor since the Akka model is typeless and depends on pattern matching.I worked around this with inspiration from (or, really, by copying)scala-js-pickling'sPicklerRegistry. The top-level bridge actor on client and server need an implicitBridgeProtocol in scope. Theprotocol has to register every top-level message that is going to be sent across the bridge.

objectMyProjectProtocolextendsBridgeProtocol {overridedefregisterMessages(registry:MessageRegistry):Unit= {defadd[A:Reader:Writer:ClassTag]= registry.add[A]defaddObj[A<:Singleton:Reader:Writer:ClassTag](obj:A)= registry.addObj(obj)    addObj(SessionApi.SubscribeToUserAndAlarmPrefs)    add[SessionApi.UserUpdate]    add[SessionApi.PreferenceUpdate]    add[SessionApi.UpdatePreferences]    add[SessionApi.PreferenceUpdateResult]  }}

Server-side

// server project; also needs to depend on the shared model project ...libraryDependencies+="com.codemettle.jsactor"%%%"jsactor-bridge-server"%"0.8.0" ...

The server-side of the bridge takes a reference to an Actor which sends/receives Strings to/from the client,presumably over a WebSocket.

classServerBridgeActor(clientWebSocket:ActorRef)(implicitbridgeProtocol:BridgeProtocol)extendsActor

Play 2.3 happens to give us an Actor representing a WebSocket, so this is pretty trivial in Play:

objectMyControllerextendsController {  ...privatedefgetSession(implicitrequest:RequestHeader):Future[Option[ClientSession]]=???defws=WebSocket.tryAcceptWithActor[String,String] { request    getSession(request) map {caseNoneLeft(Forbidden)caseSome(session)implicitvalbridgeProtocol=MyProjectProtocolRight((wsActor)ServerBridgeActor.props(wsActor))    }  }  ...}

Client-side

// client project; also needs to depend on the shared model project ...libraryDependencies+="com.codemettle.jsactor"%%%"jsactor-bridge-client"%"0.8.0" ...

jsactor-bridge-client provides building blocks for creating the client-side of a bridge, any/all of the providedclasses can be replaced with your own, but it's easiest to usejsactor.bridge.client.{ClientBridgeActor, SocketManager, WebSocketActor}.

valactorSystem:ActorSystem=???privateimplicitvalbridgeProtocal=MyProjectProtocolvalwsManager= actorSystem.actorOf(SocketManager.props(SocketManager.Config("ws://localhost:9000/ws"),"socketManager")classMyActorextendsActor {overridedefpreStart()= {super.preStart()      wsManager!SocketManager.Events.SubscribeToEvents    }overridedefreceive= {caseSocketManager.Events.WebSocketConnected(socket)        socket!WebSocketActor.Messages.SendMessageToServer("/user/MyServerActor",MessageThatIHaveRegistered())        socket!WebSocketActor.Messages.SendMessageToServer("akka.tcp://ActorSystem@address:port/user/MyServerActor",MessageThatIHaveRegistered())// could receive a jsactor.bridge.protocol.ServerActorNotFound message,// otherwise the server-side actor will have a proxy representing this client-side actor, any messages the// server sends to that actor will come to this actor; the server can DeathWatch that proxy to be notified// that this actor has shutdown (or the WebSocket was interrupted)        socket!WebSocketActor.Messages.IdentifyServerActor("/user/MyServerActor")// this actor will receive a jsactor.bridge.protocol.ServerActorNotFound message, or will receive a// jsactor.bridge.protocol.ServerActorFound message (sent from the client-side proxy of that server-side actor).// Any message sent to this client-side proxy will be sent to the corresponding server-side actor, and the// client-side proxy can be DeathWatched to notify that the server actor has shutdown (or WebSocket was interrupted).    }  }

I've also included a rough utility trait,jsactor.bridge.client.util.RemoteActorListener, that can be used toautomatically start trying toIdentify a server-side actor when the websocket connects (or reconnects):

importjsactor.bridge.client.util.RemoteActorListenerclassMyClientActor(valwsManager:ActorRef)extendsRemoteActorListener {defactorPath="/user/MyServerActor"defonConnect(serverActor:ActorRef)= {    serverActor!MyRegisteredMessage  }defwhenConnected(serverActor:ActorRef)= {caseRegisteredMessageFromServer(param)=> log.info(s"got$param from server")case msg@RegisteredMessageFromClient=> serverActor forward msg  }}

[8]ページ先頭

©2009-2025 Movatter.jp