Roland, 

As is the case with 90% of software out of academia, there isnt much purity 
of any pholosophy in the system. We have to deal with legacy code, code 
implemented badly, code implemented by other teams and as a practical 
matter of reality I cant advocate a complete refactor of an enormous system 
to any particular strategy without having some sort of business ROI reason 
to do so. So any changes made have to be incremental. The system stores the 
objects in a hazelcast map and looks the data up in that map before 
operating on it. Is it pure academic DDD ? Maybe not. Fairly close though. 
Definitely its not MVC. The objects are intelligent and know how to do 
things to themselves, not managed as pojos by external code. The MVC pojo 
approach has major problems in my opinion, one I share with M. Fowler. The 
anemic domain model is ... bad. 

As a result I cant start up an actor per domain model. That would be 
problematic because i don't know which objects will be needed and I would 
have to constantly bring the actors up and down. The approach I have to 
take is to use a generic CustomerActor that can look up, find and access 
the actual customer objects from the cache and call the functionality in 
the domain object. I can make the actor a nested class and make all of the 
implementation of the methods private so it must be accessed through the 
actor though. That is an interesting idea. 

As for accessing mutliple objects, most use cases involve changing more 
than one object at a time in a transactional manner. Should something go 
wrong all needs to be rolled back. So a message "CancelAllOrders" and you 
have to change the customer after all the orders are cancelled that all has 
to be done in a transaction. There are probably some ways it could be 
changed to be better but again ROI from management will be the limiting 
factor. 

-- Robert


On Thursday, August 27, 2015 at 12:08:37 PM UTC-5, rkuhn wrote:
>
> Hi Robert!
>
> 27 aug 2015 kl. 16:01 skrev kraythe <[email protected] <javascript:>>:
>
> Roland, 
>
> Sorry I got frustrated. Perhaps we I am just having problems communicating.
>
>
> That might well be, thanks for being open!
>
>
> Back to the issue at hand. Your code sample is fine, and I like how its 
> implemented, but here is what I wonder. What is the difference between what 
> you wrote and having a message that implements Callable? Only think you are 
> doing is implementing a different interface (trait in this case).
>
>
> The difference is that the message type is specific and descriptive, it is 
> clear that this Actor only responds to operations on the particular domain 
> entity type that it represents. An Actor that just executes any Callable it 
> gets is no longer an Actor, it is just an Executor for arbitrary code.
>
> In this particular use pattern the primary class is the domain object with 
> all its operations, these are tightly coupled together and form the 
> business logic that you are expressing. The Actor that wraps one such 
> domain object again is tightly coupled to this particular type of entity, 
> it is not a generic DomainEntityActor but a CustomerActor. While 
> generalizing the apply() method across all Operations allows elegant code 
> for the main use-case it remains important that individual operation types 
> can be handled differently or reinterpreted—the Actor needs to stay in 
> control of what it executes and when. For example you might limit the 
> update rate or limit certain operations for some clients, which can then be 
> implemented in the Actor without affecting the domain model at all.
>
> Something I am missing here ? I do see that you are passing the object to 
> be operated on to your operation and that would work fine if the operation 
> doesn't depend on multiple domain objects. If so then I would still have to 
> look up those inside the operation. However that is a minor detail.
>
>
> Normally, at least according to my layman’s understanding of DDD, no 
> command shall affect more than one aggregate root, where each aggregate 
> root owns those parts of the entity graph that need to maintain some 
> invariants (i.e. be in some way “consistent”). Therefore the usual mapping 
> from a bounded context onto an Actor system is that each Actor manages one 
> aggregate root including its dependencies and then it is also clear that 
> each command in the system only effects exactly one Actor—no distributed 
> transactions needed. I guess you knew this already, just wanted to state it 
> here for completeness. For an expert opinion I’d defer to Vaughn Vernon or 
> Greg Young.
>
>
> I also have the need to migrate the system slowly to the paradigm, not 
> boil the whole code base into a FSM all at once. I take your point about 
> everything being *able* to be a FSM but its not implemented like that 
> *now* and I have to do it in steps. 
>
>
> Since you are already dealing with in-memory representations of your 
> domain objects that you modify (including the generation of side effects) 
> I’d say that wrapping such an object in an Actor makes the FSM nature of 
> this process visible already. While I proposed immutable objects and 
> transformation functions those are not fundamental necessities, the 
> functional approach just makes testing and reasoning about your software a 
> lot easier. If that path is not yet open to you due to external constraints 
> then just go with mutable objects for now. This is something that can 
> easily be changed incrementally once the mechanism is completely hidden 
> behind a message-driven façade.
>
> Regards,
>
> Roland
>
>
> Thanks in advance. 
>
> -- Robert
>
> On Wednesday, August 26, 2015 at 3:42:57 PM UTC-5, rkuhn wrote:
>
> Hi Robert,
>
> 26 aug 2015 kl. 21:32 skrev kraythe <[email protected]>:
>
> 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.
>
>
> Please, take a step back, maybe come back to this thread tomorrow and 
> reread my last email. There is no “O(n)” (which is a red herring anyway as 
> n is not going towards infinity by any stretch—which is the only domain 
> where O-notation is meaningful) and I described exactly how you can get rid 
> of all code duplication and case matching while still decoupling the domain 
> model and the messaging abstraction.
>
> Hint:
>
> case class PerformOp(op: Operation, id: Long)
> trait Operation {
>   def apply(c: Customer): Customer
> }
> case class ChangeName(n: String) extends Operation {
>   def apply(c: Customer): Customer = c.copy(name = n) // plus whatever 
> complicated other things shall happen
> }
>
> class CustomerActor extends Actor {
>   var customer = Customer.default
>   def receive = {
>     case PerformOp(op, id) =>
>       customer = op(customer)
>   }
> }
>
> (This is the version for step one, I’m sure you’ll see how to switch on 
> event sourcing from here.)
>
>
> The more I hear the more it seems that Akka is not compatible with 
> anything other than a finite state machine.
>
>
> This, BTW, is of course true, because everything is a finite state 
> machine, no matter whether you like to call it that way or not. Your code 
> samples are no different.
>
> I wanted to introduce it in our project and now I am having second 
> thoughts. Serious second thoughts.
>
>
> To be frank, my impression is that you are having some deep misconceptions 
> of what Akka is and you are trying desperately to have them confirmed so 
> that you won’t have to use it. That’s fine and I won’t argue, I reply 
> mostly for the benefit of all the other readers (and because there is a 
> good probability that my impression is wrong, of course—my sincere 
> apologies in that case!).
>
> Regards,
>
> Roland
>
>
> 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]> 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
>
> ...

-- 
>>>>>>>>>>      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.

Reply via email to