Questions on DCI and role injection

39 views
Skip to first unread message

candlerb

unread,
Feb 9, 2010, 11:59:43 AM2/9/10
to object-composition
After watching http://architects.dzone.com/videos/dci-architecture-coplien
- which was very interesting by the way - I'm going to ask a couple of
naive questions if that's OK.

(1) When setting up a TransferMoneyContext to transfer money from
account A to account B, why would you inject the logic for doing this
into the account A role, and then ask account A to do it for you? (Or
account B, for that matter?)

It seems to me that in this simple example, the TransferMoneyContext
could orchestrate the transfer itself - in which case it would just be
a 'controller'. The only difference I can see is in how the
participants are named. The TransferMoneyContext knows about 'self',
'source_account', 'sink_account' and 'amount'. If the code runs inside
account A then it sees 'context', 'self', 'context.sink_account' and
'context.amount' respectively.

So I'm sure I'm missing some of the bigger picture here. Can you point
me to a larger example which shows the benefits of injecting the
procedural code into the object(s) themselves rather than talking to
them from outside?

(2) At the end of the presentation, the point is made that an Account
isn't really an object at all, but it's a role. However all the code
shown so far certainly *looks* like Account is an object (such as
"@balance += amount" for adding to the balance)

Does this statement invalidate the previous code examples? If so, what
should an Account role *really* look like in code? Does the
TransferMoneyContext still call Account.find(source_id) to find the
relevant account role? Is this object likely to be stateless?

(3) ISTM that composing stateless objects is little more than function
composition. Is there any truth in that, and are there any materials
which talk about DCI from a view of functional programming?

Thanks,

Brian.

James O. Coplien

unread,
Feb 10, 2010, 4:00:24 PM2/10/10
to object-co...@googlegroups.com
On Feb 9, 2010, at 5:59 , candlerb wrote:

After watching http://architects.dzone.com/videos/dci-architecture-coplien
- which was very interesting by the way - I'm going to ask a couple of
naive questions if that's OK.

Those are the best kind :-)


(1) When setting up a TransferMoneyContext to transfer money from
account A to account B, why would you inject the logic for doing this
into the account A role, and then ask account A to do it for you? (Or
account B, for that matter?)

Because each one has a job to do. One gives, and another takes. Each one has to be able to handle failures that can occur at its end. It also provides decoupling. If there is a SourceAccount and DestinationAccount, it makes it possible for SourceAccount to think of the DestinationAccount in terms of its role as a DestinationAccount instead of its class as a... well, a CheckingAccount or whatever. As a DestinationAccount it might be your phone bill, and the transfer may pay your phone bill. The role can still know the essentials of being a destination account (being able to increase balance and to affect the right kind of record-keeping) independent of whether it is a SavingsAccount or a PhoneBill. In DDD-speak, being a DestinationAccount is a different domain than being a SavingsAccount or a PhoneBill.


It seems to me that in this simple example, the TransferMoneyContext
could orchestrate the transfer itself - in which case it would just be
a 'controller'. The only difference I can see is in how the
participants are named. The TransferMoneyContext knows about 'self',
'source_account', 'sink_account' and 'amount'. If the code runs inside
account A then it sees 'context', 'self', 'context.sink_account' and
'context.amount' respectively.


Controllers have a role in separating user interaction from the representation of information. DCI components have a role in unifying the programmer mental model with the code. I think you are mixing two ideas here.

But let's go with it. Let's say that the knowledge is in the controller. It is like having a global procedure that has omniscience about the objects on which it operates. That's bad coupling and cohesion. You instead want to localize the logic to roles and to inject that logic into the objects that need it, when they need it.


So I'm sure I'm missing some of the bigger picture here. Can you point
me to a larger example which shows the benefits of injecting the
procedural code into the object(s) themselves rather than talking to
them from outside?


Trygve, how about your planning example?


(2) At the end of the presentation, the point is made that an Account
isn't really an object at all, but it's a role. However all the code
shown so far certainly *looks* like Account is an object (such as
"@balance += amount" for adding to the balance)


Right. For the time being (for pedagogical purposes—that's all one can do in a one-hour talk), we stay with the illusion that an Account is an object. So it is in the end user (Account Holder) mental model. The problem is that real systems have to accommodate many stakeholders other than Account Holders, and a single implementation has to accommodate all the stakeholders. Over time, that has led to a pattern in the financial field that leaves accounts as loci of computation rather than encapsulations of state. If you translate that to the DCI context, an Account looks more like a Context than like a domain object. See my posting here of 20 January where I include some of the code for the Account.


Does this statement invalidate the previous code examples? If so, what
should an Account role *really* look like in code?


See my posting of 20 January. Or, if you're a domain expert in banking, try it out yourself!


Does the TransferMoneyContext still call Account.find(source_id) to find the
relevant account role? Is this object likely to be stateless?


Yes and yes!


(3) ISTM that composing stateless objects is little more than function
composition. Is there any truth in that, and are there any materials
which talk about DCI from a view of functional programming?


There is definitely an element of that, except we still have a program counter. It's a bit more like applicative programming (à la SASL) than functional (à la pure Lisp or FP), but the partial ordering of instructions and the explicit role of time progression still leave it in Von Neumann / Turing machine land, albeit with fewer side-effects than in a classic Turing machine. All that said, DCI is squarely within the Turing paradigm. There are much more powerful things one can do if you through Turing to the wind and have dataflow hardware, and in fact we did many of those things back in Bell Labs in the 1980s. (See, for example, Coplien et al: ISHMAEL: An Integrated Software/Hardware Maintenance and Evaluation Environment, AT&T Technical Journal, 70(1):52-63, Short Hills, New Jersey, January/February 1991). Most of the core ideas of DCI were present in that work, except that it took custom hardware to realize it. (The relationship from roles to objects was done through an adjunct reference; the role/object association was rebound on every major machine cycle, and those machine cycle corresponded to changes in real-world state rather than any cycle internal to the machine.) The paradigm was called applicative state transition logic (Astra), and it owes largely to Tom Burrows.

Trygve Reenskaug

unread,
Feb 11, 2010, 4:19:58 AM2/11/10
to object-co...@googlegroups.com

On 2010.02.10 22:00, James O. Coplien wrote:
On Feb 9, 2010, at 5:59 , candlerb wrote:
+++++

So I'm sure I'm missing some of the bigger picture here. Can you point
me to a larger example which shows the benefits of injecting the
procedural code into the object(s) themselves rather than talking to
them from outside?
Trygve, how about your planning example?
This planning example illustrates the combination of MVC with DCA and is my best example for illustrating the combination of these two paradigms.
��� http://heim.ifi.uio.no/~trygver/2009/bb4plan.pdf
The document's� 21 pages give a detailed description of the problem, the architecture of the solution, and the actual code. The code is commented to make it easily readable for people who are not familiar with the Smalltalk syntax.

There are two implementations; one with DCI and one without. The problem statement is simple, but not trivial. Readers familiar with activity network planning can easily see how the DCI implementation can be extended without increasing its program complexity. (And how increased functionality will increase code complexity in the non-DCI implementation).

Enjoy
--Trygve

--

Trygve Reenskaug������ mailto: try...@ifi.uio.no

Morgedalsvn. 5A ������� http://heim.ifi.uio.no/~trygver

N-0378 Oslo�������������� Tel: (+47) 22 49 57 27

Norway

candlerb

unread,
Feb 15, 2010, 11:28:57 AM2/15/10
to object-composition
> This planning example illustrates the combination of MVC with DCA and is
> my best example for illustrating the combination of these two paradigms.
>    http://heim.ifi.uio.no/~trygver/2009/bb4plan.pdf
> The document's  21 pages give a detailed description of the problem, the

> architecture of the solution, and the actual code. The code is commented
> to make it easily readable for people who are not familiar with the
> Smalltalk syntax.

Thank you. I note the method "reselectObjectsForRoles" is not defined,
and I imagine this comes from BB1Context which is from an earlier part
of the book.

However I can identify what I think are some critical pieces of DCI
code. One is this:

Frontloader>>frontloadFrom: startWeek
AllActivities do: [:act | act earlyStart: nil].
[ Context reselectObjectsForRoles.
Activity notNil
] whileTrue:
[ Activity earlyStart: startWeek.
Predecessors do:
[ :pred |
(pred earlyFinish > Activity earlyStart)

In the PDF, AllActivities, Context, Activity and Predecessors are
underlined to highlight that these are role names (which might not be
clear from a regular listing)

If I understand rightly, after role selection "Activity" and
"Predecessors" must be related objects (the latter being the
predecessors of the former). In reality an individual activity object
doesn't have any notion of its predecessors. Rather, this information
is pulled out from the system's set of activities and dependencies to
fill the Predecessors role. This is also an example of how the roles
in a context may be changing during execution of a single method.

Now, the code for picking an object for the Activity role is clear
(lines 342-348), and so are AllActivities (349-350) and FrontLoader
(351). The Context is presumably inherited. But I can't at the moment
see how the Predecessors role is filled, in such a way that it aligns
with the selected Activity. Have I missed something, or is it possible
that a few lines of listing are missing? I was expecting to see
something like

BB4bFrontloadCtx>>Predecessors
data predecessorsOf Activity

(except in valid Smalltalk of course :-)

Regards,

Brian.

Trygve Reenskaug

unread,
Feb 16, 2010, 4:47:55 AM2/16/10
to object-co...@googlegroups.com
Brian,
It's great to see that you have actually read and understood the many pages of the planning example report. Thank you for pointing out the missing reselectObjectsForRoles and other methods. (They are also missing in the main report). The actual implementation utilizes some of the more powerful features of Smalltalk.

The secret is in writing a selection method for each role in the Context, e.g. binding the Activity role to an object: (from the report on p. 43):
    342. BB4bFrontloadCtx>>Activity
    343. ^ data allActivities
    344.         detect:
    345.                 [:act |
    346.                 act earlyStart isNil
    347.                    and: [(data predecessorsOf: act) noneSatisfy: [:pred | pred earlyStart isNil]]]
    348.     ifNone: [nil]

You are right in assuming that reselectObjectsForRoles is is in the superclass:
  1. BB1Context>>reselectObjectsForRoles
  2.     | messName |
  3.     roleMap := IdentityDictionary new.
  4.     self class roleNames
  5.         do: [:rNam |
  6.             messName := rNam asString asSymbol.
  7.             self roleMap
  8.                 at: messName
  9.                 put: (self perform: messName ifNotUnderstood: [nil])].
line 3: Runtime binding of role->object is a lookup in the roleNames dictionary.
line 9: In Smalltalk, we can compute a message name and the send it as a message to the object.
          Here, the message name is the role name.

I agree that Frontloader>>frontloadFrom: startWeek is a critical piece of code. The underlining is not important in actual coding of role methods because they are stateless. Further, I capitalize the names while instance and local variable nams start with a lower case letter in Smalltalk.
Yes, yes. Quite powerful, isn't it?

Now, the code for picking an object for the Activity role is clear
(lines 342-348), and so are AllActivities (349-350) and FrontLoader
(351). The Context is presumably inherited. But I can't at the moment
see how the Predecessors role is filled, in such a way that it aligns
with the selected Activity. Have I missed something, or is it possible
that a few lines of listing are missing? I was expecting to see
something like

BB4bFrontloadCtx>>Predecessors
   data predecessorsOf Activity

(except in valid Smalltalk of course :-)
  
You've got it! The proof is that you have written the missing method (shame on me and full points to you)
    BB4bFrontloadCtx>>Predecessors
        ^data predecessorsOf: (self at: #Activity)
The method predecessorsOf: is in code line 245 in the report.

Also, as you assume:
    BB1Context>>Context
        ^self
Regards,

Brian.
  
Cheers
--Trygve


--

Trygve Reenskaug       mailto: try...@ifi.uio.no

Morgedalsvn. 5A         http://heim.ifi.uio.no/~trygver

N-0378 Oslo               Tel: (+47) 22 49 57 27

Norway

candlerb

unread,
Feb 16, 2010, 10:47:40 AM2/16/10
to object-composition
On Feb 16, 9:47 am, Trygve Reenskaug <tryg...@ifi.uio.no> wrote:
> You are right in assuming that /reselectObjectsForRoles /is is in the
> superclass:
>
>    1. *BB1Context>>reselectObjectsForRoles*
>    2.     | messName |
>    3.     roleMap := IdentityDictionary new.
>    4.     self class roleNames
>    5.         do: [:rNam |
>    6.             messName := rNam asString asSymbol.
>    7.             self roleMap
>    8.                 at: messName
>    9.                 put: (self perform: messName ifNotUnderstood: [nil])].

Thank you. So if I understand this correctly,

BB4bFrontloadCtx>>Activity

is called once by reselectObjectsForRoles, and is memoized in roleMap,
so that references to Activity within the context get the value last
chosen.

Does the call "self at: #Activity" resolve by looking up #Activity as
a key in the roleMap object? If so, I think I'm getting there :-)

In that case it translates very literally to the following Ruby. Ruby
lets me define the subscripting 'operator' x[y] as a method, which
I've chosen instead of x.at(y)

class Context
def [](key)
@role_map[key]
end

def reselect_objects_for_roles
@role_map = {}
self.class::ROLE_NAMES.each do |rnam|
@role_map[rnam] = send(rnam) rescue nil
end
end

def context
self
end
end

class FrontloaderCtx < Context
ROLE_NAMES =
[:context, :activity, :predecessors, :all_activities, :front_loader]

def initialize(data)
@data = data
end

def activity
@data.all_activities.detect { |act|
act.early_start.nil? && !@data.precessors_of(act).detect { |
pred| pred.early_start.nil? }
}
end

def all_activities
@data.all_activities
end

def predecessors
@data.predecessors_of(self[:activity])
end

def front_loader
@data
end
end

The bits I'm missing now are executeInContext and the inherited
roleStructure method. I'm guessing that it allows a subset of this
roleMap to be given to the role objects, so that their communication
with other objects is on a "need to know" basis. Is that more or less
right?

A few observations from the above code to check my understanding and
my translation.

(1) It looks like there is a hidden dependency here: the 'activity'
role must be filled before the 'predecessors' role, otherwise the
'predecessors' role-selection method will fail. That is, the ordering
of roleNames is critical to correct behaviour.

(2) It seems slightly odd to me that 'data' (the instance of the data
model) is just an instance variable of the FrontloaderCtx, whereas the
other objects are roles in the roleMap. This means that there is a
design decision to be made, for each participating object, whether to
make it a role or not.

(3) Furthermore, the frontLoader role is actually played by 'data' -
lines 356-357. I guess you need *some* object to play the role of the
front loader, and maybe it's convenient for the model data to do this,
rather than instantiating a fresh object.

The references to other role objects are injected dynamically in line
332, but it looks like the frontLoader role methods are injected
statically in line 237. Is that true? Was that done just because of
implementation limitations? I'd have thought you would want to avoid
polluting the model with methods from the front loader algorithm if at
all possible.

Thanks again for your patience,

Brian.

Trygve Reenskaug

unread,
Feb 17, 2010, 2:23:23 PM2/17/10
to object-co...@googlegroups.com
Brian,
Comments inline below.


On 2010.02.16 16:47, candlerb wrote:
On Feb 16, 9:47 am, Trygve Reenskaug <tryg...@ifi.uio.no> wrote:
  
You are right in assuming that /reselectObjectsForRoles /is is in the
superclass:

   1. *BB1Context>>reselectObjectsForRoles*
   2.     | messName |
   3.     roleMap := IdentityDictionary new.
   4.     self class roleNames
   5.         do: [:rNam |
   6.             messName := rNam asString asSymbol.
   7.             self roleMap
   8.                 at: messName
   9.                 put: (self perform: messName ifNotUnderstood: [nil])].
    
Thank you. So if I understand this correctly,

BB4bFrontloadCtx>>Activity

is called once by reselectObjectsForRoles, and is memoized in roleMap,
so that references to Activity within the context get the value last
chosen.
  
That's right.

Does the call "self at: #Activity" resolve by looking up #Activity as
a key in the roleMap object? If so, I think I'm getting there :-)
  
That's also right.
  1. BB1Context>>executeInContext: aBlock
  2.     self reselectObjectsForRoles.
  3.     self class pushContextStack: self.
  4.     aBlock ensure: [self class popContextStack].
Code line 4: aBlock is first executed, then 'self class popContextStack' will be executed, even if the execution of aBlock failed.
With languages missing the executable block feature, one could put all this inline in the calling code.

As to the roleStructure method, the superclass returns an empty dictionary. The BB4bFrontloadCtx class adds the structure to the dictionary.  The keys in this dictionary are the roles (role names) in the BB4bFrontloadCtx. The values specify visibility, i.e. the roles that are visible from a given role. This is used in my modified BabyIDE compiler; a role method can only reference visible roles.

A few observations from the above code to check my understanding and
my translation.

(1) It looks like there is a hidden dependency here: the 'activity'
role must be filled before the 'predecessors' role, otherwise the
'predecessors' role-selection method will fail. That is, the ordering
of roleNames is critical to correct behaviour.
  
Right.

(2) It seems slightly odd to me that 'data' (the instance of the data
model) is just an instance variable of the FrontloaderCtx, whereas the
other objects are roles in the roleMap. This means that there is a
design decision to be made, for each participating object, whether to
make it a role or not.
  
The data variable contains the pool of objects available to the Context. Role playing objects are selected from this pool (or created on the fly from information available in the pool)

(3) Furthermore, the frontLoader role is actually played by 'data' -
lines 356-357. I guess you need *some* object to play the role of the
front loader, and maybe it's convenient for the model data to do this,
rather than instantiating a fresh object.
  
True. An instance of any class could play the Frontloader role since the role method does not call 'self'. (But in my implementation, the role method would be injected into that class)

The references to other role objects are injected dynamically in line
332, but it looks like the frontLoader role methods are injected
statically in line 237. Is that true? Was that done just because of
implementation limitations? I'd have thought you would want to avoid
polluting the model with methods from the front loader algorithm if at
all possible.
  
The purpose of line 332 is to define role visibility. It means that a role method for Frontloader can acces the Context, AllActivities, Activity, and Predecessors roles. It will not necessarily do so. (The BB4bFrontloadCtx class>>roleStructure method is actually a static method. The class word in the specification is Smalltalk's way of saying this. )

Thanks again for your patience,

Brian.
  
No need to thank me. I not only enjoy this conversation but I also learn from it.

Sean DeNigris

unread,
Feb 18, 2010, 2:42:39 PM2/18/10
to object-composition
> > (2) At the end of the presentation, the point is made that an Account
> > isn't really an object at all, but it's a role. However all the code
> > shown so far certainly *looks* like Account is an object (such as
> > "@balance += amount" for adding to the balance)
>
> <snip> If you translate that to the DCI context, an Account looks more like a Context than like a domain object. See my posting here of 20 January where I include some of the code for the Account.

>
> > Does this statement invalidate the previous code examples? If so, what
> > should an Account role *really* look like in code?
I think the word "role" here threw the conversation off track. Above,
JOC said that Account would be a context.

> See my posting of 20 January. Or, if you're a domain expert in banking, try it out yourself!

I searched for this post, but only saw ...Source role code. I would
love to see an example of Account as Context.

> > Does the TransferMoneyContext still call Account.find(source_id) to find the
> > relevant account role? Is this object likely to be stateless?
>
> Yes and yes!

So is Account an alias here for a Context object? If so, wouldn't it
be called something like "WorkWithBalanceContext" (the use case from
the TransferMoneyContext perspective)? How do you keep it in domain
terms at this level (which seems intuitively to want to relate to an
account from the customer perspective)?

Sean

Reply all
Reply to author
Forward
0 new messages