I am going to describe a concrete scenario I have: "*an item can be removed
from the menu*"
class MenuActor extends PersistentActor {
var state: Option[Menu] = None
override def receiveCommand: Receive = {
case remove: RemoveItem =>
persist(MenuItemRemoved(remove.itemId)) {
event =>
state = state.map(_.remove(event.itemId))
sender ! state.get
}
// ... other handlers
}
}
The Menu aggregate root raises a *BusinessException("$itemId does not
exist") *if obviously the item identified by the given ID does not exist in
the Menu.
In the concrete case of an HTTP client this error would be map to a 400
status code containing the descriptive message as any other "breaking
aggregate invariant" scenario.
What I am doing right now is this:
class MenuActor extends PersistentActor {
var state: Option[Menu] = None
override def receiveCommand: Receive = {
case remove: RemoveItem =>
try {
val newState = state.map(_.remove(remove.itemId)) *// -> 1*
persist(MenuItemRemoved(remove.categoryId)) {
event =>
state = newState *// -> 2*
sender ! state.get
}
} catch {
case businessException: BusinessException => sender() !
Failure(businessException) *// -> 3*
}
// ... other handlers
}
}
1 -> I mutate the actor state before persisting the event since that is the
aggregate mutating behaviour that raise an exception when an invariant is
violated.
All the samples I saw mutates the internal actor state in the event handler
closure after persisting the event but if the aggregate consistency is
violated and it throws a business exception then we would have an
inconsistent state of the journal containing an event that should not be
there.
2 -> In order to prevent what I mentioned in the previous point I just
assign the actor state to be the new state (aggregate mutation before
persist)
3 -> If the aggregate raises an exception we don´t save the event in the
journal, instead we send back a Failure message that maps to a
Future.failed in the client who asked this actor and so we can give
feedback to any client source about this business exception.
A )Is this OK? If not... how is handled?
I´ve seen in the docs that after sending back the *Failure* message, the
exception is re-thrown so supervision takes place:
} catch {
case businessException: BusinessException => {
sender() ! Failure(businessException)
throw businessException // supervision take places
}
}
Why to do that? The default supervision strategy will Restart the actor,
but I don´t need the actor to be restarted, resumed would be OK, however...
I just leave the actor as it is since the code above prevented the state to
be mutated and the client who asked this actor is notified about the
business exception.
B) Should I throw the businessException after sending back the Failure to
the sender? Why? Why to restart the actor?
C) Last question, I´ve seen examples of supervision expecting
NullPointerException, arithmeticException and so on... those sound bugs to
me, unexpected exceptions other than network failures and so shouldn't´t be
supervised but fixed. I understand the situation where a Backoff supervisor
sounds reasonable to deal with network failures but I don´t see why to
supervise exceptions that are caused because of bugs like the ones
mentioned before (matching doc examples)
Thanks,
Sebastian
--
>>>>>>>>>> Read the docs: http://akka.io/docs/
>>>>>>>>>> Check the FAQ:
>>>>>>>>>> http://doc.akka.io/docs/akka/current/additional/faq.html
>>>>>>>>>> Search the archives: https://groups.google.com/group/akka-user
---
You received this message because you are subscribed to the Google Groups "Akka
User List" group.
To unsubscribe from this group and stop receiving emails from it, send an email
to [email protected].
To post to this group, send email to [email protected].
Visit this group at https://groups.google.com/group/akka-user.
For more options, visit https://groups.google.com/d/optout.