So.. the more I've been toying with Scala actors in my spare time, the better feel I have for how they are used in an application and how to go about designing said applications. The key here is to rethink your application layout.
Actors allow us to bring service-oriented-architecture to a much lower layer in the application. I an now design "micro-servers" that do very small amounts of behavior, and provide a mechanism to wire in to other micro-servers. On top of this, you also need a method of documenting your service so others know how/what messages they can pass to you.
Here's an example of creating a "microserver". This contrived example is a server that will make sure all input meets a certain filter before passing it on.
First off, let's define what a "filter" means:
trait Filter[A] {
/** Determines if the given input "passes" a filter */
def pass(input : A) : Boolean
}
it's a very simple implementation, but works for us. Notice that Filter has a type parameter. Our final actor will also have a type parameter.
Now... Let's define the messages we want to send back and forth. We need a method of adding filters to a "FilterActor", a method of removing filters, a method of querying the filter list and finally a method of performing filtering action. I'm also going to add a method of telling the "FilterActor" whom to send messages onto if they successful pass the filters.
Here's our API for FilterActor
/** Adds a filter to the filter Actor's list */
case class AddFilter[A](filter : Filter[A])
/** Removes a filter from the filter Actor's list*/
case class RemoveFitler[A](filter : Filter[A])
/** Retreives the list of filters fro a FilterActor (BLOCKING-RETURN) */
case class GetFilters[A]
/** Performs Filtering on a given input */
case class PerformFilter[A](data : A)
/** Sets some listener who will be called if a given input data passes all filters */
case class SetListener(listener : => A)
Ok, so we now have a (documented) API for calling our filter manager. Writing the code should be easy! One thing I like to do (to help other developers find things) is encapsulate my FilterActor/FilterManager in an object. We'll also define the actor in the FilterManager object.
/** Contains the FilterManager/FilterActor API */
object FilterManager {
/** Adds a filter to the filter Actor's list */
case class AddFilter[A](filter : Filter[A])
/** Removes a filter from the filter Actor's list*/
case class RemoveFitler[A](filter : Filter[A])
/** Retreives the list of filters fro a FilterActor (BLOCKING-RETURN) */
case class GetFilters[A]
/** Performs Filtering on a given input */
case class PerformFilter[A](data : A)
/** Sets some listener who will be called if a given input data passes all filters */
case class SetListener(listener : A => Unit)
import scala.actors.Actor._
/** Creates a Scala actor that uses the API defined above. */
def makeActor[A] = actor {
var listener : A => Unit = {tmp =>()}
var filters : List[Filter[A]] = Nil
loop {
react {
case PerformFilter[A](data) =>
//pass if no filters
if(filters.isEmpty) {
listener(data)
} else {
if( filters.forall(f => f.pass(data))) {
listener(msg);
}
}
case SetListener[A](l) => listener = l
case AddFilter[A](f) => filters = f :: filters;
case RemoveFilter[A](f) => filters = filters filter { _ != item }
case GetFilters[A] => reply(filters)
}
}
}
}
As you can see, we now have a very self-contained, documented "micro-service". You can see, due to the "SetListener[A]" message, that the FilterActor does not need to know who it has for a final destination, this can be wired at runtime.
Here's an example usage where we will print all lines in a file that match a given regular expression. I've removed the regex filter / file reading code as an excercise for the reader (and due to my laziness).
object MySimpleGrepApp extends Application {
import FilterManager._
val fileReader = ...
val regexFilter = ...
val grepFilterer = FilterManager.makeActor[String]
grepFilterer ! AddFilter(regexFilter)
grepFilterer ! SetListener({ line : String => Console.println(line) })
fileReader.getLines.foreach {
line => grepFilterer ! PerformFilter(line)
}
}
As you can see, it's really simple for us to have nicely threaded results. With a few minor changes, we could even read multiple files at the same time and pipe all the results through the filter.
One thing I'd like to attempt in the future is defining Scala actors in such a way that you can generically wire up inputs and outputs. In this way you could define some abstract way of scripting "micro-services" together. Even more interesting would be a way to architect an actor-based system using some kind of Inversion of Control container like Guice or Spring. If you could reliable define message passing interfaces and enforce them somehow, you could have a very powerful, scalable architecture tool.
I'm thinking that I may have jumped a lot of my audience in terms of Scala understanding for using the actor-api. Once again I think I may do a post detailing pattern match (as I understand it).