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)