I want my Lagom subscriber-only service to subscribe to a Kafka Topic and stream the messages to a websocket. I have a service defined as follows using this documentation (https://www.lagomframework.com/documentation/1.4.x/scala/MessageBrokerApi.html#Subscribe-to-a-topic) as a guideline:
// service call
def stream(): ServiceCall[Source[String, NotUsed], Source[String, NotUsed]]
// service implementation
override def stream() = ServiceCall { req =>
req.runForeach(str => log.info(s"client: %str"))
kafkaTopic().subscribe.atLeastOnce(Flow.fromFunction(
// add message to a Source and return Done
))
Future.successful(//some Source[String, NotUsed])
However, I can't quite figure out how to handle my kafka message. The Flow.fromFunction returns [String, Done, _] and implies that I need to add those messages (strings) to a Source that has been created outside of the subscriber.
So my question is twofold:
1) How do I create an akka stream source to receive messages from a kafka topic subscriber at runtime?
2) How do I append kafka messages to said source while in the Flow?
You seem to be misunderstanding the service API of Lagom. If you're trying to materialize a stream from the body of your service call, there's no input to your call; i.e.,
def stream(): ServiceCall[Source[String, NotUsed], Source[String, NotUsed]]
implies that when the client provides a Source[String, NotUsed], the service will respond in kind. Your client is not directly providing this; therefore, your signature should likely be
def stream(): ServiceCall[NotUsed, Source[String, NotUsed]]
Now to your question...
This actually doesn't exist in the scala giter8 template, but the java version contains what they call an autonomous stream which does approximately what you want to do.
In Scala, this code would look something like...
override def autonomousStream(): ServiceCall[
Source[String, NotUsed],
Source[String, NotUsed]
] = ServiceCall { hellos => Future {
hellos.mapAsync(8, ...)
}
}
Since your call isn't mapping over the input stream, but rather a kafka topic, you'll want to do something like this:
override def stream(): ServiceCall[NotUsed, Source[String, NotUsed]] = ServiceCall {
_ =>
Future {
kafkaTopic()
.subscribe
.atMostOnce
.mapAsync(...)
}
}
Related
It seems that the http client request API is for classic actors only:
val responseFuture: Future[HttpResponse] = Http().singleRequest(HttpRequest(uri = "http://akka.io"))
responseFuture
.onComplete {
case Success(res) => println(res)
case Failure(_) => sys.error("something wrong")
}
I have a akka typed actor and not sure how to make external http requests to my API.
When I added the above inside of my typed actor, I got this error:
could not find implicit value for parameter system: akka.actor.ClassicActorSystemProvider
What options do I have?
Typed actor system can be easily converted to a classical one and used to send your http request via akka-http.
For example, if there is a val of type akka.actor.typed.ActorSystem, you can convert it to a classical one in following way
val system: akka.actor.typed.ActorSystem[_] = ???
implicit val classic = system.classicSystem
So now, any method requiring implicit actorSystem: akka.actor.ActorSystem method parameter will work.
And from within a typed actor, you can get reference to a context and then to typed actor system, which then converted to classical one
val behavior = Behaviors.setup[Int] { ctx =>
implicit val classic = ctx.system.classicSystem
???
}
This is rather a basic question but I couldn't find a satisfying answer after googling for hours. From the example in here, the way to make web socket is something like this:
Controller code:
import play.api.mvc._
import play.api.libs.streams.ActorFlow
import javax.inject.Inject
import akka.actor.ActorSystem
import akka.stream.Materializer
class Application #Inject()(cc:ControllerComponents) (implicit system: ActorSystem, mat: Materializer) extends AbstractController(cc) {
def socket = WebSocket.accept[String, String] { request =>
ActorFlow.actorRef { out =>
MyWebSocketActor.props(out)
}
}
}
Actor code:
import akka.actor._
object MyWebSocketActor {
def props(out: ActorRef) = Props(new MyWebSocketActor(out))
}
class MyWebSocketActor(out: ActorRef) extends Actor {
def receive = {
case msg: String =>
out ! ("I received your message: " + msg)
}
}
But how exactly do I send message from controller to the actor via web socket? Let's say in the controller code, I have an action code that handles when a button is pressed, it will send a block of string to the actor. How do I send this string to the actor above from the controller code?
I can provide you with some examples of websockets in Play. Essentially they use a Flow (akka-streams) to handle a websocket connection.
There is an official Play Websocket example: Lightbend's Websocket example
Based on that, I have several projects that use websockets, for example:
play-wsocket-scalajs
This is an example application showing how you can integrate a Play project with a Scala.js, Binding.scala project - using Web Sockets.
It is quite involved, so the easiest way is to check HomeController, UserParentActor, UserActor and AdapterActor how they work together.
scala-adapters
Is a framework that is based on the example above - that also shows how to register websocket clients.
Let's first understand what is already created, and what we need to add. The type of socket, is a WebSocket.
This WebSocket, reveals a single apply method:
def apply(request: RequestHeader): Future[Either[Result, Flow[Message, Message, _]]]
Therefore, as long as you did not send a message, the flow has not yet been created. Now, once a message is sent we can create the flow, and send it a meesage:
def index = Action.async(parse.json) { request =>
socket(request).map {
case Left(result) =>
Ok("Done: Left: " + result.body)
case Right(value) =>
Source.single(TextMessage(Json.stringify(request.body))).via(value).to(Sink.ignore).run()
Ok("Done: Right: ")
}
}
This sample app and related discussion might be helpful. Here's some code clipped/summarized from the linked sample app:
Flow.futureFlow(futureUserActor.map { userActor =>
val incomingMessages: Sink[Message, NotUsed] =
Flow[Message]
.map(...)
.to(...)
val outgoingMessages: Source[Message, NotUsed] =
ActorSource
.actorRef[User.OutgoingMessage](...)
.mapMaterializedValue { outActor =>
// give the user actor a way to send messages out
userActor ! User.Connected(outActor)
NotUsed
}
.map(...)
// then combine both to a flow
Flow.fromSinkAndSourceCoupled(incomingMessages, outgoingMessages)
})
There are at least two ways to approach this:
Customize play's ActorFlow.actorRef method to return the underlying actor. There was a similar discussion before, here's a gist. If you put the underlying actor into a (user, websocket) map, make sure to use a thread-safe implementation like the TrieMap.
What you're trying to do could be solved by creating an event bus & subscribing to it from within the actor. Then you could filter the events you're interested in & react accordingly. This solution is better, in that it actually scales - you can have more than one replica of your web app (the 1st approach wouldn't work in that case, because a replica that doesn't hold the reference to a user's WS actor could receive button clicked event). In pseudo code to illustrate the idea:
sealed trait AppEvent
final case class ButtonClicked(user: User.ID) extends AppEvent
// inside an action
system.eventStream.publish(ButtonClicked(request.identity.id))
// inside your actor
override def preStart =
context.system.eventStream.subscribe(self, classOf[AppEvent])
Please note that the idea of event bus is abstract. What I've demonstrated above is the most basic approach using akka's classic event bus, which works locally. For this approach to scale, you would need an actual message queue behind the scenes.
#Singleton
class EventPublisher #Inject() (#Named("rabbit-mq-event-update-actor") rabbitControlActor: ActorRef)
(implicit ctx: ExecutionContext) {
def publish(event: Event): Unit = {
logger.info("Publishing Event: {}", toJsObject(event), routingKey)
rabbitControlActor ! Message.topic(shipmentStatusUpdate, routingKey = "XXX")
}
}
I want to write a unit test to verify if this publish function is called
rabbitControlActor ! Message.topic(shipmentStatusUpdate, routingKey = "XXX")
is called only once.
I am using spingo to publish messages to Rabbit MQ.
I am using Playframework 2.6.x and scala 2.12.
You can create an TestProbe actor with:
val myActorProbe = TestProbe()
and get its ref with myActorProbe.ref
After, you can verify that it receives only one message with:
myActorProbe.expectMsg("myMsg")
myActorProbe.expectNoMsg()
You probably should take a look at this page: https://doc.akka.io/docs/akka/2.5/testing.html
It depends you want to check only the message is received by that actor or you want to test the functionality of that actor.
If you want to check message got delivered to the actor you can go with TestProbe. I.e.
val probe = TestProbe()
probe.ref ! Message
Then do :
probe.expectMsg[Message]
You can make use of TestActorRef in case where you have supervisor actor, which is performing some db operations so you can over ride its receive method and stop the flow to go till DB.
I.e.
val testActor =new TestActorRef(Actor.props{
override receive :Receive ={
case m:Message => //some db operation in real flow
//in that case you can return what your actor return from the db call may be some case class.
case _ => // do something }})
Assume your method return Future of Boolean.
val testResult=(testActor ? Message).mapTo[boolean]
//Then assert your result
I would like to have a consumer actor subscribe to a Kafka topic and stream data for further processing with Spark Streaming outside the consumer. Why an actor? Because I read that its supervisor strategy would be a great way to handle Kafka failures (e.g., restart on a failure).
I found two options:
The Java KafkaConsumer class: its poll() method returns a Map[String, Object]. I would like a DStream to be returned just like KafkaUtils.createDirectStream would, and I don't know how to fetch the stream from outside the actor.
Extend the ActorHelper trait and use actorStream() like shown in this example. This latter option doesn't display a connection to a topic but to a socket.
Could anyone point me in the right direction?
For handling Kafka failures, I used the Apache Curator framework and the following workaround:
val client: CuratorFramework = ... // see docs
val zk: CuratorZookeeperClient = client.getZookeeperClient
/**
* This method returns false if kafka or zookeeper is down.
*/
def isKafkaAvailable:Boolean =
Try {
if (zk.isConnected) {
val xs = client.getChildren.forPath("/brokers/ids")
xs.size() > 0
}
else false
}.getOrElse(false)
For consuming Kafka topics, I used the com.softwaremill.reactivekafka library. For example:
class KafkaConsumerActor extends Actor {
val kafka = new ReactiveKafka()
val config: ConsumerProperties[Array[Byte], Any] = ... // see docs
override def preStart(): Unit = {
super.preStart()
val publisher = kafka.consume(config)
Source.fromPublisher(publisher)
.map(handleKafkaRecord)
.to(Sink.ignore).run()
}
/**
* This method will be invoked when any kafka records will happen.
*/
def handleKafkaRecord(r: ConsumerRecord[Array[Byte], Any]) = {
// handle record
}
}
I'm playing with the akka-stream-and-http-experimental 1.0. So far, I've a user service that can accept and respond to HTTP requests. I'm also going to have an appointment service that can manage appointments. In order to make appointments, one must be an existing user. Appointment service will check with the user service if the user exists. Now this obviously can be done over HTTP but I'd rather have the appointment service send a message to the user service. Being new to this, I'm not clear how to use actors (as akka-http abstracts that) to send and receive messages. There's mention of ActorRef and ActorPublisher in the doc but no examples of the former and the later looks like an overkill for my need.
My code looks like the following and is on Github:
trait UserReadResource extends ActorPlumbing {
val userService: UserService
val readRoute = {
// route stuff
}
}
trait ActorPlumbing {
implicit val system: ActorSystem
implicit def executor: ExecutionContextExecutor
implicit val materializer: Materializer
def config: Config
val logger: LoggingAdapter
}
trait UserService { // Implemented by Slick and MongoDB in the backend
def findByFirstName(firstName: String): Future[immutable.Seq[User]]
}
object UserApp extends App with UserReadResource with UserWriteResource with ActorPlumbing {
override implicit val system = ActorSystem()
override implicit def executor = system.dispatcher
override implicit val materializer = ActorMaterializer()
override def config = ConfigFactory.load()
override val logger = Logging(system, getClass)
private val collection = newCollection("users")
val userRepository = new MongoDBUserRepository(collection)
val userService: UserService = new MongoDBUserRepositoryAdapter(userRepository) with UserBusinessDelegate {
// implicitly finds the executor in scope. Ain't that cute?
override implicit def executor = implicitly
}
Http().bindAndHandle(readRoute ~ writeRoute, config.getString("http.interface"), config.getInt("http.port"))
}
Edit:
I figured out how to send messages, which could be done using Source.actorRef. That only emits the messages into the stream. What I'd like to do is for the route handler class to receive the response. That way when I create the appointment service, it's actor can call the user service actor and receive the response in the same manner as the user route handler in my example does.
Pseudo code:
val src = Source.single(name) \\ How to send this to an actor and get the response
Edit 2:
Based on the #yardena answer, I came up with the following but the last line doesn't compile. My actor publisher returns a Future which I'm guessing will be wrapped in a Promise and then delivered as a Future to the route handler.
get {
parameters("firstName".?, "lastName".?).as(FindByNameRequest) { name =>
type FindResponse = Future[FindByNameResponse]
val src: Source[FindResponse, Unit] = Source.actorPublisher[FindResponse](businessDelegateProps).mapMaterializedValue {
_ ! name
}
val emptyResponse = Future.apply(FindByNameResponse(OK, Seq.empty))
val sink = Sink.fold(emptyResponse)((_, response: FindResponse) => response)
complete(src.runWith(sink)) // doesn't compile
}
}
I ended up with using Actor.ask. Simple.
This link may be helpful: http://zuchos.com/blog/2015/05/23/how-to-write-a-subscriber-for-akka-streams/ and this answer by #Noah Accessing the underlying ActorRef of an akka stream Source created by Source.actorRef
Basically you have 2 choices:
1) if you want a "simple" actor, which will forward into the stream all messages that it receives, you can use Source.actorRef. Then you can pipeline the messages into UserService by creating a processing stage using mapAsync.
2) Another option, in case you want the actor to have some custom behavior, is to write your own ActorPublisher.
HTH