From: Intransition on 8 Feb 2010 18:43 I watched Part 1 of this great lecture, and I just had to share: http://architects.dzone.com/videos/dci-architecture-trygve You can read my brief post on it here: http://proutils.github.com/2010/02/dci-architecture/index.html I love the line "Code must be Chunkable". Reminds me of _why. Also some interesting counter arguments about TDD.
From: Brian Candler on 9 Feb 2010 11:40 Thomas Sawyer wrote: > http://architects.dzone.com/videos/dci-architecture-trygve Interesting. Part 2 explores some ideas in Ruby with a simple bank transfer example. What I don't get is why you'd want to inject the logic of transferring money between two accounts into one of the accounts and then call it there. Surely you could just do it all in the context object itself? In that case, the transfer-money context would just become what I'd call a 'controller'. So I'd find it useful to see a more extensive example which shows the benefits of working this way. Then at the end, it says that an account isn't really an object at all - but all the previous code has shown it as a concrete object (e.g. Account.find(id)). So an example of what an account role *should* look like in code would be good. -- Posted via http://www.ruby-forum.com/.
From: Intransition on 11 Feb 2010 03:29 On Feb 9, 11:40 am, Brian Candler <b.cand...(a)pobox.com> wrote: > Thomas Sawyer wrote: > > http://architects.dzone.com/videos/dci-architecture-trygve > > Interesting. > > Part 2 explores some ideas in Ruby with a simple bank transfer example. > What I don't get is why you'd want to inject the logic of transferring > money between two accounts into one of the accounts and then call it > there. Surely you could just do it all in the context object itself? In > that case, the transfer-money context would just become what I'd call a > 'controller'. > > So I'd find it useful to see a more extensive example which shows the > benefits of working this way. I just finished watching the 2nd video. I agree with you. Coplien does an awful job of explaining things. Trygve, despite his age, does a much better job. > Then at the end, it says that an account isn't really an object at all - > but all the previous code has shown it as a concrete object (e.g. > Account.find(id)). So an example of what an account role *should* look > like in code would be good. I don't know what he is talking about. It's as if he thinks, if something isn't solid it isn't an object. And his whole speel about logging-in is not a usecase because there's no business goal, is silly too. He's splitting hairs over words and as much as he thinks DCI is so cool, I'm not sure he actually "gets it" himself. However, at the very beginning he does point out the main point of the whole pursuit -- code readability. His Ruby code, btw, wasn't very well written, would not run and worse, I don't think represents DCI well either. So I threw together a fix that I think represents it at least a little better. Still a simple bank transfer, but it works, so that in it's self is an improvement ;) One thing I would point out, Coplien's TransferMoneyContext is a Command pattern --a class that encapsulates a single action. I don't think it's necessary to go that far. While my example follows his, if I were doing it otherwise, I would probably make it an AccountInteractions class and define methods within it for all the ways in which two accounts could interact. # class Account # simple account db def self.accounts @@accounts ||= {} end def self.find(accountID) accounts[accountID] end attr :accountID attr :balance def initialize(accountID, initialBalance) Account.accounts[accountID] = self @accountID = accountID @balance = initialBalance end end # class SavingsAccount < Account def initialize(accountID, initialBalance) super(accountID, initialBalance) end def availableBalance; @balance; end def decreaseBalance(amount); @balance -= amount; end def increaseBalance(amount); @balance += amount; end def updateLog(message, time, amount) puts "%s %s #%s $%.2f" % [message, time, accountID, amount.to_f] end end # Use Case (Context) class MoneyTransfer attr :amount attr :source_account attr :destination_account def initialize(amt, sourceID, destID) @amount = amt @source_account = Account.find(sourceID) @destination_account = Account.find(destID) end def execute source_account.extend TransferSource destination_account.extend TransferDestination source_account.withdraw(amount) destination_account.deposit(amount) #source_account.unextend TransferSource #destination_account.unextend TransferDestination end end # Account Role module TransferSource def withdraw(amount) raise "Insufficiant Funds" if balance < amount decreaseBalance(amount) updateLog "Transfer Out", Time.now, amount end end # Account Role module TransferDestination def deposit(amount) increaseBalance(amount) updateLog "Transfer In", Time.now, amount end end # try it out SavingsAccount.new(1, 500) SavingsAccount.new(2, 100) transfer_case = MoneyTransfer.new(50, 1, 2) transfer_case.execute Notice the remarked #unextend lines. For a real implementation of DCI, we would want to remove these roles once we used them, but Ruby's extend doesn't allow that, of course. So the bottom line I think is this. You work out usecases (i.e. contexts) for actually doing things. You make your objects pretty dumb --primarily state bags. You figure out the roles your objects must play to satisfy those use cases and code those. Then you code the usecases with the roles and objects so as to get the job done. The whole programs then becomes easier to read b/c you are reading usecases first, which explains things as the interaction of roles played by simple objects. And presto the "Code is Chunkable". (P.S. I also think this is much more like AOP then Coplien is willing to admit.)
From: Brian Candler on 11 Feb 2010 04:41 Thomas Sawyer wrote: > def execute > source_account.extend TransferSource > destination_account.extend TransferDestination > > source_account.withdraw(amount) > destination_account.deposit(amount) > > #source_account.unextend TransferSource > #destination_account.unextend TransferDestination > end > end Thank you. That was pretty much what I was thinking. After all, in a real bank transfer, the "source account" isn't responsible for carrying out the transfer, the bank clerk is. In a play, there's a single script. And if either Romeo or Juliet forgets their lines, it's the prompter at the front of the stage who tells them what to say next. (OK, perhaps that's taking the analogy too far :-) I can see a specific case where this context/role split would work well. In Rails-type apps, I've wondered before how best to implement logic which clearly belongs in the model, but which is affected by properties of the controller. Behaviour dependent on the user's timezone preference is one example; adding updated_by and updated_ip stamps is another. Rails solves the timezone problem by just stuffing it into a thread-local variable, which is horrible. Having a 'context' object available to the model at execution time makes total sense. And as long as you inject the context at the same time as you inject the methods which make use of that context, then you know the two are aligned; it's safe because you know that code can't be used elsewhere. In practice this might mean you eschew the model's own 'save' method in favour of a ModelUpdater context and a UpdatableModel role. -- Posted via http://www.ruby-forum.com/.
From: Brian Candler on 11 Feb 2010 04:45
Thomas Sawyer wrote: > Coplien does > an awful job of explaining things. Trygve, despite his age, does a > much better job. I'd say "Trygve, because of his age, does a much better job" :-) (I also started by toggling in binary machine-code. Admittedly that was switches and LEDs rather than switches and lamps) -- Posted via http://www.ruby-forum.com/. |