The following implementation does not yet take into account events, however this will be covered.
First we have a trait to mark a command
trait Command
Individual command will implement this trait, such as
case class NewUserCommand(string:Login, string:Password) extends Command
For each command there should be a handler which will extend the following:
trait Handler[T <: Command] {
def handle(command:T):Unit
}
The command handlers I use follow the cake pattern, and have state wired in. For example
trait NewUserCommandHandler extends Handler[NewUserCommand] {
this:SecurityService with PersistencyService =>
def handle(command:NewUserCommand) { ... }
}
In this example state is a document cache required by the PersistencyService:
trait DocumentPersistencyService extends PersistencyService {
def state:DocumentCache
...
I have created a registry for holding the constructor of the command handlers. Here state is a type parameter, though I don't think that's entirely necessary. The registry is mutable only until first use, I have followed the same approach that I used here.
trait CommandRegistry[State] {
def getHandler[T <: Command:TypeTag](implicit state:State):Handler[T]
}
trait CommandConstructorRegistry[State] extends CommandRegistry[State] {
private val registryBuffer = new ListBuffer[(TypeSymbol, (State) => Handler[_])]
private lazy val registry = registryBuffer.toMap
def getHandler[T <: Command:TypeTag](implicit state:State):Handler[T] = registry(typeOf[T].typeSymbol.asType)(state).asInstanceOf[Handler[T]]
def register[T <: Command:TypeTag](constructor:((State) => Handler[T])) {
registryBuffer += ((typeOf[T].typeSymbol.asType, constructor))
}
}
A command dispatcher wraps this all up, so that all commands can be issued via the dispatcher.
trait CommandDispatcher[State] {
def dispatch[T <: Command:TypeTag](command:T)(implicit state:State):Unit
}
trait DefaultCommandDispatcher[State] extends CommandDispatcher[State] {
this:CommandRegistry[State] =>
def dispatch[T <: Command:TypeTag](command:T)(implicit state:State) {
val handler = getHandler[T]
handler.handle(command)
}
}
So tying it all together looks something like this:
val commandDispatcher = new DefaultCommandDispatcher[DocumentCache] with CommandConstructorRegistry[DocumentCache] {
register(s => new NewUserCommandHandler with DefaultSecurityService with DocumentPersistencyService { val state = s })
}
implicit val state = new DocumentCache()
val command = NewUserCommand("login","password")
commandDispatcher.dispatch(command)
You could also enrich the dispatcher through the use of mixins, say if you wanted to log the commands content:
trait CommandLogger[State] extends CommandDispatcher[State] {
abstract override def dispatch[T <: Command:TypeTag](command:T)(implicit state:State) {
println(command)
super.dispatch(command)
}
}
No comments:
Post a Comment