Scala/akka discrete event simulation

2019-08-14 20:50发布

问题:

I am considering migrating a large code base to Scala and leveraging the Akka Actor model. I am running into the following conceptual issue almost immediately:

In the existing code base, business logic is tested using a discrete event simulation (DES) like SimPy with deterministic results. While the real-time program runs for hous, the DES takes a few minutes. The real time system can be asynchronous and need not follow the exact ordering of the testing setup. I'd like to leverage the same Akka code for both the testing setup and the real-time setup. Can this be achieved in Scala + Akka?

I have toyed with the idea of a central message queue Actor -- but feel this is not the correct approach.

回答1:

A general approach to solving the problem you stated is to isolate your "business logic" from Akka code. This allows the business code to be unit tested, and event tested, independently of Akka which also allows you to write very lean Akka code.

As an example, say you're business logic is to process some Data:

object BusinessLogic {
  type Data = ???
  type Result = ???

  def processData(data : Data) : Result = ???
}

This is a clean implementation that can be run in any concurrency environment, not just Akka (Scala Futures, Java threads, ...).

Historic Simulation

The core business logic can then be run in your discrete event simulation:

import BusinessLogic.processData

val someDate : Date = ???

val historicData : Iterable[Data] = querySomeDatabase(someDate)

//discrete event simulation
val historicResults : Iterable[Result] = historicData map processData

If concurrency is capable of making the event simulator faster then it is possible to use a non-Akka approach:

val concurrentHistoricResults : Future[Iterable[Result]] = 
  Future sequence {
    historicData.map(data => Future(processData(data)))
  }

Akka Realtime

At the same time the logic can be incorporated into Akka Actors. In general it is very helpful to make your receive method nothing more than a "data dispatcher", there shouldn't be any substantive code residing in the Actor definition:

class BusinessActor extends Actor {

  override def receive = {
    case data : Data => sender ! processData(data)
  }
}

Similarly the business logic can be placed inside of akka streams for back-pressured stream processing:

val dataSource : Source[Data, _] = ???

val resultSource : Source[Result, _] = 
  dataSource via (Flow[Data] map processData)