Interesting but I cant keep all the customers in memory active as an actor.
I would need thousands of memory resident objects, many of which would
never be used. The customer is a domain object that contains data and is
loaded on demand. Furthermore, the Customer object is a Java object, not a
Scala one and could not be a case class for a dozen different issues, not
the least of which is that i cant throw out a half a million line code base
for programatic purity. The actor either has to manage the customer object
or not have an actor at all. And as for the Order, that one is even worse.
What am I supposed to do, start an actor for all 2 million orders in the
system, on the off chance that one of them might be called? No. The domain
models are reflections of what is in the data store and contain lots of
objects. In our case the domain models are stored in hazelcast backed by
MySQL.
And I will emphasize again that you will have at least as many message
classes as you have "things that can be done" to customer. Furthermore,
your example case is O(n) performance and in a system where millions of
these messages are being sent, that is LETHAL to performance. Our customers
dont care to wait 2 minutes for the web page to load. Methods that are O(n)
complexity are fine perhaps if you have a small number of messages or a
small number of invocations but combine LARGE amount of functionality and
large volume and such a design will require you to run 50 nodes to handle
mediocre load.
I am a little confused why people think this kind of O(n) logic is a good
thing and why contained logic in a message is a bad thing. The message of
this thread is "huge pattern matches and code duplication is GOOD. Object
oriented performance oriented code is just not proper." That confuses the
heck out of me.
The more I hear the more it seems that Akka is not compatible with anything
other than a finite state machine. I wanted to introduce it in our project
and now I am having second thoughts. Serious second thoughts.
On Wednesday, August 26, 2015 at 2:02:06 PM UTC-5, Patrick Mahoney wrote:
>
> Hello Robert,
>
> "Furthermore since a user could call the methods anyway on the domain
> object why would a newer developer bother going through the actor anyway.
> Oh yeah you can say "code reviews" but in a large team I don't have time to
> review every line of code. "
>
> This is kind of the point- there is nothing precluding the developer from
> doing this, so don't write the Customer class and actor this way. Making
> the Customer class an Actor would make it threadsafe immediately. Basically
> consolidate the Customer and CustomerActor, and halve the duplication above
> by removing the methods in favour of messages that result in safe
> alteration of your Customer's (now an Actor) mutable state:
>
> class Customer(_id: Int, _name: String) extends Actor {
> import Customer._
>
> var id: Int = _id
> var name: String = _name
>
> override def receive: Receive = {
> case ChangeId(newId) =>
> id = newId
> case ChangeName(newName) =>
> name = newName
> }
>
> }
>
> object Customer {
> final case class ChangeId(id: Int)
> final case class ChangeName(id: Int)
>
> def props(id: Int, name: String): Props = Props(new Customer(id, name))
> }
>
> Don't start by domain modelling using non-threadsafe abstractions, then
> trying to make them threadsafe with akka. Use the tools provided
> (immutability, actors) and start with threadsafe domain models.
>
> What precludes representing the Customer as a case class here? You are
> forcing mutability right down into the leaves of your domain model. An
> actor might not be suitable for representing any given domain element-an
> Actor might be more suitable for representing something stateful, like the
> customer cache (a cache of immutable Customer instances) for instance.
>
> I am not sure how you arrived at this model based on what I read in this
> thread - you didn't fix the closure issue involving ChangeNameMsg, and went
> backwards in creating a Customer class that would require
> synchronization/locks if ever used outside the actor.
>
> final case class Customer(id: Int, name: String /* and 35 other
> properties, each that can be adjusted using the case classes' 'copy' method
> or a lens */)
>
> class CustomerCache extends Actor {
> var cache: Map[Int, Customer] = Map.empty[Int, Customer]
>
> override def receive: Receive = {
> case GetCustomer(id, recipient) => recipient ! cache.get(id)
> case CacheCustomer(customer) =>
> cache += (customer.id -> customer)
> }
> }
>
> object CustomerCache {
> def props: Props = Props(new CustomerCache)
>
> final case class GetCustomer(id: Int, recipient: ActorRef)
> final case class CacheCustomer(customer: Customer)
>
> }
>
> "Is this actually the pattern considered best practice?" - I haven't seen
> this recommended anywhere, including Roland's post.
>
> "That sort of system would be just lethal and would preclude any use of
> Akka whatsoever in our domain. Id be sacrificing business goals to achieve
> some version of programmer purity. No way I can sell that to management. If
> thats the real deal then Akka jsut isnt a good fit for this system. And in
> fact I cant see a system i have worked on in 22 years of professional
> development where it WOULD fit. The number of Finite state machine projects
> outside of academia is VANISHINGLY small. " -> it seems like you finish
> knocking down the straw-man you erected here. I wouldn't recommend writing
> this sort of system in akka, it won't take your non-threadsafe classes and
> make them threadsafe. It provides a set of tools that aid in producing
> threadsafe models. akka may not be the silver bullet here.
>
> -Patrick
>
> On 26 August 2015 at 12:01, kraythe <[email protected] <javascript:>>
> wrote:
>
> It seems from your response that you see Akka as only applicable to the
> Finite State Machine use cases. Well I don't have any of those in our
> system. Nothing is that simple in the system I am working on. There are big
> domain objects that have hundreds of possible things that can be done to
> them. If I have to declare an actor and manage each message individually
> its going to be a HORRENDOUS amount of code duplication. I would need to
> increase the code base size by 20 to 30% just to accommodate it. I can see
> it now:
>
> /** A Domain-Driven-Design object. */
> class Customer {
> private var _id: Int = 0
> def id = _id
> private var _name: String = null
> def name = _name
>
> // and 35 other properties
>
> def changeName(name: String): Unit = {
> // apply logic for checking legal names.
> _name = name
> }
>
> // and LITERALLY 100 other domain methods, some QUITE complicated
> }
>
> class CustomerActor extends Actor {
> val log = Logging(context.system, this)
>
> override def receive: Receive = {
> case msg: ChangeNameMsg =>
> Customer.findInCacheById(msg.customerId).changeName(msg.newName)
> // ... AND LITERALLY 100 OTHER CASES.
> case msg =>
> val data = ToStringBuilder.reflectionToString(msg)
> log.error(s"received unknown message of type ${msg.getClass} with data
> $data")
> unhandled(msg)
> }
>
> case class ChangeNameMsg(customerId: Int, newName: String)
>
> // and LITERALLY 100 other messages
> }
>
>
> Oh my god! I can see about 50 things wrong with this, not the least of
> which is that the case statement would be huge and have O(n) performance
> (which is lethal to high volume systems). Is this actually the pattern
> considered best practice? Seems more than a bit crazy to me. The only thing
> crazier would be embedding the logic of the 100 domain methods into the
> receive method. And at least 1/2 the code in the 1/2 million line code base
> is in java so forget fancy Scala tricks like mixins. Even if I could use
> them I wouldn't because of a host of other reasons.
>
> Furthermore since a user could call the methods anyway on the domain
> object why would a newer developer bother going through the actor anyway.
> Oh yeah you can say "code reviews" but in a large team I don't have time to
> review every line of code.
>
> That sort of system would be just lethal and would preclude any use of
> Akka whatsoever in our domain. Id be sacrificing business goals to achieve
> some version of programmer purity. No way I can sell that to management. If
> thats the real deal then Akka jsut isnt a good fit for this system. And in
> fact I cant see a system i have worked on in 22 years of professional
> development where it WOULD fit. The number of Finite state machine projects
> outside of academia is VANISHINGLY small.
>
> -- Robert
>
> On Wednesday, August 26, 2015 at 2:41:19 AM UTC-5, rkuhn wrote:
>
>
> 25 aug 2015 kl. 20:59 skrev kraythe <[email protected]>:
>
> No I dont mean they close over the state at all. I mean they are nested. I
> fail to see how it is any more than an organizational strategy and why it
> would be considered bad. Nor do I see how it is a single threaded executor.
> Messages are still being sent. You are just taking advantage of the fact
> that any nested class has access to the members of its enclosing class.
>
> class Customer extends Actor {
> val log = Logging(context.system, this)
> private var id : Int = 0
> private var name : String = null;
>
> override def receive: Receive = {
> case msg : CustomerMessage => msg match {
> case f : Function0[_] =>
> sender ! f.apply()
> }
> case msg =>
> val data = ToStringBuilder.reflectionToString(msg)
> log.error(s"received unknown message of type ${msg.getClass} with data
> $data")
> unhandled(msg)
> }
>
> sealed trait CustomerMessage
>
> case class ChangeName(customerId: Int, newName : String) extends
> CustomerMessage with Function0[Unit] {
> override def apply(): Unit = {
> Customer.findCustomerById(customerId).name = newName
> }
> }
> }
>
> object Customer {
> def findCustomerById(id: Int) : Customer = {
> // in reality we would look in cache for this.
> new Customer()
>
>
> This won’t work: instantiating an Actor is only possible via `actorOf(…)`.
> This is the main reason why it is a bad idea to identify an Actor with a
> domain object (by inheritance) instead of having an Actor manage a domain
> object (by composition).
>
> Composition has the additional advantage that you won’t need to perform
> any asynchronous Actor-based tests for your domain logic since it will be
> completely decoupled from the Actor layer. Doing that, I concur that
> modeling a domain object as a State and a set of Operations makes a lot of
> sense (where an Operation type contains the logic, i.e. it has the
> signature State => State). However, I would still not send the Operation
> directly as a message but carry it inside a PerformOp() type that can then
> also hold messaging-related information that has no relation to the
> business logic (like sequence numbers for reliable delivery).
>
> Note that this signature implies the use of side-effects as only the next
> State can be returned; the next step of purification would then be to
> change the signature of an Operation to State => Seq[Event] and place the
> definition of the effects in each event—both for state changes and external
> effects like sending a message. It is no coincidence that this then allows
> seamless use of Akka Persistence for storing the domain object’s state.
>
> Regards,
>
> Roland
>
> }
> }
>
>
> How would you qualify that as a single threaded executor. Everything is
> quite well encapsulated and protected and no mutable state has been closed
> over.
>
>
> On Tuesday, August 25, 2015 at 1:18:35 PM UTC-5, rkuhn wrote:
>
> Hi Robert,
>
> 25 aug 2015 kl. 19:51 skrev kraythe <[email protected]>:
>
> Roland,
>
> All good points but if you look at large systems there are a large set of
> messages that have a relatively static implementation. For example, go
> fetch X from all objects of type Y in the system. There is little to be
> interpreted in that message. With the "book" based examples, we could have
> a receive method with hundreds of these messages in a huge case statement.
> That is the reality of real, vs Academic, examples. With a code base in the
> hundreds of thousands, we have classes that do hundreds of things. To have
> to write a dispatcher even to call those methods separately is a lot of
> code duplication for not much gain. Further it seems that there are cases
> where such "mutable behavior" reaction to a message can be coded into the
> callable. If the message is defined inside the class, when its run it will
> have access to the data of the class.
>
> Consider a Customer class with over 100 things to do to it. We can write
> these 100 things into methods and then create 100 messages to encapsulate
> these events and then have a receive with a pattern matching case on 100
> types of messages. Sounds nasty, verbose and error prone (especially for
> more junior programmers). Now lets say I have a different paradigm. I
> define the 95 of the 100 messages and wrap up their implementation in a
> callable. The 5 remaining methods are special, they transition the actor to
> some state, change the behavior or the actor via become() or something like
> that. These special messages are largely just messages without
> implementation. For the actor implementation, all of the code of the 95
> methods is hidden from direct invocation (which is a good thing) but since
> the messages are defined as nested classes they can still do what needs to
> be done to the customer object. As for the receive() method, I just need 6
> cases. 1 to handle all 95 possible messages (just calls the callable and
> sends the sender back the result) and 5 for the special messages.
> Furthermore, if the customer needs to convert one of the 95 messages or
> alter behavior, that is going to be a rare circumstance and can be
> programmed directly into the message or actor class. For example if an
> attempt is made to grant a gift card to a customer whose account is
> suspended, the code can do that in the call. If some reason the actor needs
> to interpret a message in a different manner, that can be coded right into
> the receive method for that message or the message can be made to implement
> Function1[State, Result] and have the condition passed to it.
>
> In fact this would make the customer actor MUCH easier to understand and
> increase the performance dramatically. In high volume systems and O(n)
> solution to anything should be called into question. I have a prototype
> running and its working nicely with even a base class that other actors can
> extend:
>
> class SmartMessageActor extends Actor {
> val log = Logging(context.system, this)
>
> override def receive: Receive = {
> case f: Function0[_] =>
> sender ! f.apply()
> case f: Callable[_] =>
> sender ! f.call()
> case f: Runnable =>
> // no reply to sender, just run the message.
> f.run()
> case msg =>
> val data = ToStringBuilder.reflectionToString(msg)
> log.error(s"received unknown message of type ${msg.getClass} with data
> $data")
> unhandled(msg)
> }
> }
>
>
> Interesting concept ?
>
>
> Yes, and a very old one: you just implemented a single-threaded Executor
> with quite a bit of overhead that you don’t use—an Actor is meant to
> encapsulate state and none of the functions you pass can access or modify
> that state. If you implied by “nested classes” that the messages close over
> the actual state of the Actor then that is obviously a big no-go, it
> completely breaks the encapsulation to share this object outside of the
> Actor itself (which would be needed to instantiate these nested classes in
> the first place).
>
> As I said, for a real Actor the behavior does not belong to the message;
> as long as you treat the code accordingly it does not matter where you
> place it (i.e. you could place it into the message type, but then you’ll
> have to pass in all required context so that the Actor itself decides what
> it exposes to the processing and when it does it—and it is strongly
> preferred to not modify the state from within the message but instead
> return the new state, in our collective experience this approach saves you
> lots of head scratching).
>
> Note how this is incompatible with just sending a raw
> Runnable/Callable/etc. to an Actor, you’ll still need to define a proper
> message (super) type with appropriate method signatures.
>
> Regards,
>
> Roland
>
>
> -- Robert
>
>
> On Tuesday, August 25, 2015 at 9:07:34 AM UTC-5, rkuhn wrote:
>
> Hi Robert,
>
> this proposal shares some commonality with another experiment of mine, see
> https://github.com/akka/akka/pull/18147#discussion_r37143815. The biggest
> conceptual issue here is that messages are deliberately separated from
> behavior in the Actor Model: an Actor reacts to messages when it wants and
> in whichever way it wants, this is key to achieve protocol compositionality
> (e.g. forwarding, aggregating, caching, etc.). What you describe is no
> longer an Actor, it is just a queue of functions to be run by an Executor.
>
> Another angle is that placing the implementation within the message type
> means that this message is bound to a particular Actor type (and its
> internal state representation). How would you handle this message within
> another Actor that happens to speak (part of) the same language? The
> freedom to interpret the message at the receiving end is precisely what
> achieves the loose coupling in message-oriented systems.
>
> So, while it is certainly possible to place the code wherever you want, in
> general—or conceptually—the behavior belongs to the processing entity, not
> to the message.
>
> Regards,
>
> Roland
>
> 23 aug 2015 kl. 16:39 skrev kraythe <[email protected]>:
>
> Sure I could do all of that. If the whole codebase was Scala. Its not. Its
> a hybrid of scala and java. Even with just java I could do method
> delegation. However, that kind of misses the point.
>
> Say we have domain object with the name Customer. There are certain
> messages we send customer to debit their balance, change name, etc. But
> there are other messages that we send to generate JSON specific to a
> particular rest endpoint. It seems problematic at best to mix together the
> customer implementation with the implementation to get the JSON data for
> that particular endpoint. I would rather put the JSON creation in a
> callable and pass it to the CustomerActor and then inside there we extract
> the relevant fields for the JSON to return to the user.
>
> But even excluding this use case, consider a large domain object with 60
> different methods that can be done with it. You can order supplies, change
> relevant information and so on. Now in order to implement this with an
> actor I would have to make one HUGE 60 case message switch. Now, other than
> the fact that it wouldnt be an efficient method (pattern matching 60 cases)
> it would be really super annoying to see this massive method just to call
> some other method. It would seem to be a lot clearer if the message is
> smart. So when user calls Customer.placeOrder(Integer, Order) it would
> generate a Smart message that has the implementation of the message
> processing in the code.
>
> Naturally you would have to watch out for closing over, mutable state and
> all the other potential pitfalls of messsaging systems. However, the
> implementation would be clean. Inside the method, the message is generated
> and dispatched. The user gets back a future if needed and the actor for
> that domain object realizes it is a smart message and simply runs the apply
> function inside the smart message.
>
> In that paradigm the actor receive method is smaller, the message
> implementation is isolated to the PlaceOrder method and we dont have a 60
> case dispatch.
>
> -- Robert
>
> On Sunday, August 23, 2015 at 2:28:54 AM UTC-5, Martin Senne wrote:
>
> typo: "ctor" has the "a" missing. should be "actor" of course.
> Am 23.08.2015 09:26 schrieb "Martin Senne" <[email protected]>:
>
> Hi kraythe,
>
> first, you can use method delegation to untangle the "large" receive
> method.
>
> Second, you can wrap all these methods including the relevant part of the
> receive method into a trait and mix that into your concrete ctor
> implementation.
>
> Cheers, Martin
> Am 23.08.2015 09:14 schrieb "Patrik Nordwall" <[email protected]>:
>
> How would it look like when the actor has state? How is the state updated?
> How do you handle the message differently depending on the state?
>
> Regards,
> Patrik
> sön 23 aug 2015 kl. 04:56 skrev kraythe <[email protected]>:
>
> Actually I am not talking about that. It's a simple concept of putting the
> implementation of the message processor in the message itself. It's just a
> different way to organize the code.
>
> --
> >>>>>>>>>> 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.
>
> --
> /Patrik
>
> --
> >>>>>>>>>> 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.
>
>
> --
> >>>>>>>>>> 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.
>
>
>
>
> *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.
>
> ...
>
> --
> >>>>>>>>>> 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
>
> ...
--
>>>>>>>>>> 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.