- Type-safe interfaces for actors
- wiring up actors (decoupling them)
The basic idea follows this pattern. Say, for instance, I have the rather unhelpful "EmailService" interface defined in my spring application:
public interface EmailService {
public void sendEmail(String address, String subject, String message);
public boolean isValid(String address);
}
Let's say (initially) I want to migrate all my service calls to scala actors. The goal here is to start pipelining my business logic and because I read a white paper about Erlang/OTP and wanted to "get some" into my J2EE application. Well, since I already have a java interface, and I already have a spring configuration file (or many), then I should be able to swap out implementations of the EmailService. This is the whole reason for dependency injection right?
Here's our first cut at a implementation using scala actors:
import scala.actors.Actor._
class ScalaActorEmailService extends EmailService {
//Here we define the actor API (based on the interface)
case class IsValid(address : String)
case class SendEmail(address : String, subject : String, message : String)
//Here we define the actor that actually performs service calls
private val worker = actor {
loop {
react {
case IsValid(address) => reply(false) //TODO Implement
case SendEmail(address, subject, message) => Console.println("TODO -Implement send");
}
}
}
//Now we implement the API
override def isValid(address : String) : Boolean = {
val response = Some(worker !? IsValid(address))
response.getOrElse(false) match {
case b : Boolean => return b
case _ => return false
}
}
override def sendEmail(address : String, subject : String, message : String) : Boolean = {
worker ! SendEmail(address, subject, message)
}
}
Notice how all API calls are actually being sent as messages over actors. If we can ensure immutable arguments to our services... (which may be hard after the fact), you can see how we can add rich concurrent behavior in a piecemeal fashion. Another good part about this technique is that you can NOT pass messages to an actor in the case of "simple" operations.
Let's pretend we profiled and found that the isValid API call was slowing our application down significantly due to the return value. We want to inline in it to see what kind of performance gain we might make. Here's what our service would change into.
import scala.actors.Actor._
class ScalaActorEmailService extends EmailService {
//Here we define the actor API (based on the interface)
case class SendEmail(address : String, subject : String, message : String)
//Here we define the actor that actually performs service calls
private val worker = actor {
loop {
react {
case SendEmail(address, subject, message) => Console.println("TODO -Implement send");
}
}
}
//We inline this method for performance
override def isValid(address : String) : Boolean = {
//TODO - Implement
return false
}
//This method calls out to the actor to make sure we don't block on send
override def sendEmail(address : String, subject : String, message : String) : Boolean = {
worker ! SendEmail(address, subject, message)
}
}
Now we're starting to have some power in how we can architect our system. Not only that, we're dealing with type safe interfaces, so we don't have to search through documentation to figure out what messages can be sent to what actors. We can also remove/add actor calls as necessary for our app.
Another thing I'd like to say, and this is totally my preference. When in Java land and initially learning spring, a *lot* of examples show dependency injection using setters. I actually am starting to think this is a bad idea, as it makes mis-constructing an object (or mis-configuring it within the spring container) *much* easier. It seems to be a safer (and perhaps more functional?) way of doing things to inject everything in the constructor. In this manner we can inject type-safe interfaces for other actor-based-services into our actor-based service! Instantly we start talking pipelines and scalability based on hardware (i.e. # of CPUs).
The last thing to do, of course would be to attempt to unify choice of languages into scala by creating a trait instead of an interface that can be used.
trait EmailService {
//Perfectly abstract method
def sendEmail(address : String, subject : String, message : String) : Unit
def isValid(String address) : Boolean = {
//We could implement the "basic" version here and override it later if desired
false
}
}
5 comments:
Would you please show the build script/maven for compilation setup (scalac ?) and dependencies required (pom.xml, etc.)
Thanks!
I'll post an entire sample project to github. If you caught my latest post, you might notice I'm a bit busy right now. Hopefully I can get something up for you in the next two weeks.
Thanks.
And congratulations on the little one! Enjoy! But be aware that time flies. I just had a second BD for my daughter and it feels like she was born yesterday!
I finally had a chance to look over my old project. After a few "WTF"s, I decided to try to clean it up. after finally finding *time* to clean it up, I have your sample project. It gave me another idea for a blog posting about spring, so we'll see what comes of it.
The sample code is here:
http://github.com/jsuereth/scala-spring-actors-sample/tree/master
I'll try to spruce it up with more real-life samples. Right now it's too "simple" for my taste. I want to show some real interaction between DAOs, Services + Controllers.
Also, if you need combined Java/Scala compilation, I'll update the example to show that as well.
Josh, thanks for taking the time and posting a sample. Yes, the combined Scala/Java compilation example would also help!
Post a Comment