Hi Roland,
Yes that makes sense, especially since the class is effectively an actor :)
Does the same rule apply with code that just performs some behaviour on
behalf of an actor? I.e. code that doesn't actually represent an actor in
its own right?
In my example, I have isolated the 'chatter' involved in determining email
address availability into its own type:
trait EmailAddressAvailabilityQuery {
def apply(emailAddress: EmailAddress)(consumer: Boolean => Unit)(implicit
context: ActorContext): Unit
}
class EmailAddressAvailabilityQueryImpl(emailCredentialsView: ActorRef)
extends EmailAddressAvailabilityQuery {
override def apply(emailAddress: EmailAddress)(consumer: Boolean =>
Unit)(implicit context: ActorContext): Unit = {
queryAvailabilityOf(emailAddress)
yieldResultTo(consumer)
}
private def queryAvailabilityOf(emailAddress: EmailAddress): Unit =
emailCredentialsView ! EmailAddressAvailabilityQueried(emailAddress)
private def yieldResultTo(consumer: Boolean => Unit)(implicit context:
ActorContext): Unit =
context become {
case EmailAddressAvailabilityDetermined(emailAddressAvailable) =>
context unbecome()
consumer(emailAddressAvailable)
}
}
This produces a very nice level of abstraction for the calling code (which
is within an actor). It also isolates all the message type details to one
place, making the code much more maintainable if I were to ever change
email address availability lookup.
emailAddressAvailabilityQuery(emailAddress) { emailAddressAvailable =>
if (emailAddressAvailable)
allowRegistration()
else
denyRegistration()
}
Do you suggest that I inline the implementation of the
EmailAddressAvailabilityQueryImpl and remove the class?
Thanks,
Lawrence
On Thursday, May 29, 2014 7:18:08 AM UTC+1, rkuhn wrote:
>
> Hi Lawrence,
>
> you don’t need to pass around ActorContext: just return the Props to the
> actor and call context.actorOf() there. Passing around ActorContext is not
> something that should be handled with caution, it is a recipe for disaster
> and will result in weird failures eventually; the reason is that because of
> the Actor Model you or your team will think that low-level synchronization
> is not an issue, and keeping only exactly this one type separate is tedious
> and error-prone.
>
> When it comes to testing I recommend functional tests only (message in,
> message(s) out), no verification of meaningless details such as passing an
> implementation detail to some mock—that will only break once you improve
> your implementation and thereby create extra work and impede progress. In
> the case at hand you would give a Props-factory to your Actor (the
> FulfilRegistration class with my suggested modification) and during test
> you can have that inject the right stub actors quite naturally.
>
> Regards,
>
> Roland
>
> 28 maj 2014 kl. 18:00 skrev Lawrence Wagerfield <
> [email protected] <javascript:>>:
>
> I had an Actor whose code grew to be quite large, so this is the result of
> a refactoring.
>
> The new class performs actions on behalf of the actor, impersonating it,
> so to speak. It caries out various send operations, sets up state with
> un/become, etc.
>
> The refactor produces clearer and more testable code, although I do
> appreciate why ActorContext (or any other internal state) should be handled
> with caution.
>
> My code is as follows:
> *(note: CheckEmailAddressAvailability follows a similar pattern - code
> omitted as you'll already get the idea!)*
>
> class UserRegistry(fulfilRegistration: FulfilRegistration) extends
> EventsourcedProcessor {
> override def receiveRecover: Receive = {
> case UserSubmittedRegistration(registration) =>
> fulfilRegistration(registration)
> }
>
> override def receiveCommand: Receive = {
> case registrationEvent: UserSubmittedRegistration =>
> persist(registrationEvent) { event =>
> fulfilRegistration(event.registration)
> }
> }
> }
>
> trait FulfilRegistration {
> def apply(registration: Registration)(implicit context: ActorContext):
> Unit
> }
>
> class FulfilRegistrationImpl(implicit checkEmailAddressAvailability:
> CheckEmailAddressAvailability)
> extends FulfilRegistration{
> def apply(registration: Registration)(implicit context: ActorContext):
> Unit =
> FulfilRegistration
> .beginTransaction(registration)
> }
>
> object FulfilRegistration {
> def beginTransaction(registration: Registration)(implicit context:
> ActorContext, checkEmailAddressAvailability:
> CheckEmailAddressAvailability): Unit = {
> val name = transactionActorName(registration.newUserId)
> val actorRef = context.actorOf(transactionProps, name)
> actorRef forward registration
> }
>
> def transactionActorName(userId: UserId): String =
> userId.toString
>
> private def transactionProps(implicit checkEmailAddressAvailability:
> CheckEmailAddressAvailability): Props =
> Props(new RecoverableActor {
> override protected def initialState: Receive = notRegistered
>
> override def receiveRecover: Receive = {
> case UserRegistered() =>
> context stop self
>
> case RegistrationDeniedDueToDuplicateEmail() =>
> context stop self
> }
>
> private def notRegistered: Receive = {
> case Registration(newUserId, _, EmailCredential(emailAddress, _))
> =>
> val registrant = sender()
>
> def allowRegistration(): Unit = {
> val event = UserRegistered()
> registrant ! event // Informing registrant is part of the
> registration so occurs before committing the event.
> persist(event) { _ =>
> context stop self
> }
> }
>
> def denyRegistration(): Unit = {
> val event = RegistrationDeniedDueToDuplicateEmail()
> registrant ! event // Informing registrant is part of the
> denial so occurs before committing the event.
> persist(event) { _ =>
> context stop self
> }
> }
>
> checkEmailAddressAvailability(emailAddress) {
> emailAddressAvailable =>
> if (emailAddressAvailable)
> allowRegistration()
> else
> denyRegistration()
> }
> }
> })
> }
>
>
> On Wednesday, May 28, 2014 4:45:52 PM UTC+1, Konrad Malawski wrote:
>>
>> We strongly advertise against exposing your ActorContext - it's scary
>> what could happen with it being accessed from outside of the respective
>> actor.
>>
>> What's your use case? Perhaps there is a better way to test / abstract it?
>>
>>
>> On Wed, May 28, 2014 at 4:56 PM, Lawrence Wagerfield <
>> [email protected]> wrote:
>>
>>> Hi Konrad,
>>>
>>> I'm currently using TestActorRef to obtain the underlying ActorContext.
>>> I am using this to assert the actor is passing its own context as an
>>> argument to a mocked-out dependency.
>>>
>>> Can you think of another way using existing TestKit probes, or is this
>>> the best way?
>>>
>>> Thanks,
>>> Lawrence
>>>
>>>
>>>
>>> On Wednesday, May 28, 2014 3:48:26 PM UTC+1, Konrad Malawski wrote:
>>>
>>>> Hello Lawrence,
>>>> Because of the processor’s inner workings (how it interacts with the
>>>> Journal) it does not make sense to test it using the *synchronous*test
>>>> utilities (such as
>>>> TestActorRef).
>>>> Please do not use TestActorRef with Akka-Persistence - use the plain
>>>> TestKit and it’s probes and other utils.
>>>>
>>>> // We had such issue reported, and marked as won’t fix
>>>> https://github.com/akka/akka/issues/15293
>>>>
>>>>
>>>>
>>>> On Wed, May 28, 2014 at 4:20 PM, Lawrence Wagerfield <
>>>> [email protected]> wrote:
>>>>
>>>>> Using TestActorRef on implementations of Processor (and
>>>>> EventsourcedProcessor) appears to introduce a deadlock at the point
>>>>> of calling into the snapshot store actor.
>>>>>
>>>>> The result is an actor-under-test that never completes recovery.
>>>>>
>>>>> I have worked-around this issue by providing .withDispatcher(
>>>>> Dispatchers.DefaultDispatcherId) on the Props object I construct the
>>>>> TestActorRef with.
>>>>>
>>>>> Should I raise this as an issue? Currently using Akka 2.3.3.
>>>>>
>>>>> --
>>>>> >>>>>>>>>> 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 http://groups.google.com/group/akka-user.
>>>>> For more options, visit https://groups.google.com/d/optout.
>>>>>
>>>>
>>>>
>>>>
>>>> --
>>>> Cheers,
>>>> Konrad 'ktoso' Malawski
>>>> hAkker - Typesafe, Inc
>>>>
>>>> <http://scaladays.org/>
>>>>
>>>
>>> --
>>> >>>>>>>>>> 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 http://groups.google.com/group/akka-user.
>>> For more options, visit https://groups.google.com/d/optout.
>>>
>>
>>
>>
>> --
>> Cheers,
>> Konrad 'ktoso' Malawski
>> hAkker - Typesafe, Inc
>>
>> <http://scaladays.org/>
>>
>
> --
> >>>>>>>>>> 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] <javascript:>.
> To post to this group, send email to [email protected]<javascript:>
> .
> Visit this group at http://groups.google.com/group/akka-user.
> For more options, visit https://groups.google.com/d/optout.
>
>
>
>
> *Dr. Roland Kuhn*
> *Akka Tech Lead*
> Typesafe <http://typesafe.com/> – Reactive apps on the JVM.
> twitter: @rolandkuhn
> <http://twitter.com/#!/rolandkuhn>
>
>
--
>>>>>>>>>> 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 http://groups.google.com/group/akka-user.
For more options, visit https://groups.google.com/d/optout.