public
Created

The value of contexts in DCI

  • Download Gist
post_creation_context.rb
Ruby
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
class PostCreationContext
def initialize(post)
@post = post
@post.extend PostNotifier
@post.extend PostIndexer
@post.extend PostCacher
end
 
def execute
@post.save
@post.notify
@post.index
@post.cache
end
end
posts_controller.rb
Ruby
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
class PostsController < ApplicationController
def create
post = Post.new
post.extend PostNotifier
post.extend PostIndexer
post.extend PostCacher
post.save
post.notify
post.index
post.cache
end
end
 
# VS
 
class PostsController < ApplicationController
def create
context = PostCreationContext.new(Post.new)
context.execute
end
end

I haven't really looked into DCI, but wouldn't it be better to use composition (i.e. to wrap post rather than to use extend?). This would play nicer with call site caches and you would not have to worry about Post instances being reused, say by an identity map or whatever.

Well, there's two things here really: First we have the way we think about DCI and the second is how do we implement it in this language.

There's really not a big difference between:

class SomeFeature < BasicObject
  def initialize(data)
    @data = datta
  end

  def method_missing(*a,&b) @data.send(*a,&b) end

  # my custom methods
end

foo = SomeFeature.new(bar)

and:

module SomeFeature
  # my custom methods
end

bar.extend SomeFeature

Both captures the basic idea in DCI: That you have plain data objects (with core methods; e.g. push, pop on Array) and when you need to do something more complex with it, you put that in another place and include it when you need it. Two different implementations that solves the same problem.

Of course, one thing that is possible with composition is storing separate state. That's something that's very unlike DCI. State belongs in Data or Context.

Not all OO languages have the capabilites to implement the DCI pattern well. You need to be able to inject code into existing classes: try doing this in Java. In C++ it is possible using templates, but only in a static manner. The devil is really in the detail.

http://larskurth.wordpress.com/2009/11/11/not-your-grandfathers-oo-data-context-interaction/

Here is the article on DCI. All of the examples inject methods into the data object. For Scala, they use "traits". For C++, they use "templates":
http://www.artima.com/articles/dci_vision.html

In Ruby, we're going to have to use extend.

My comment was more on architecture in general, not on how to best implement DCI.

The example on that page looks exactly like the original LISP mixins.

What I like about composition rather than DCI is the better separation of logic layers. For instance, on business logic level, the semantics of delete might be different from the semantics of delete on persistance layer (remove from database vs. mark as removed). DCI does not solve this issue.

What I like about composition rather than DCI is the better separation of logic layers.

By "separation", you mean classes vs modules? Regarding "layer", if you mean a separate Ruby object, then yes it is more of a separation. But there is overhead to that approach...

the semantics of delete might be different from the semantics of delete on persistence layer

Define your own delete method in a "role" (module). Use super to hit the persistence layer delete method.

By "separation", you mean classes vs modules?

I mean composition vs inheritance.

But there is overhead to that approach...

Agreed. But you avoid leaky abstractions.

Define your own delete method in a "role" (module). Use super to hit the persistence layer delete method.

What if some code on the persistent layer wants to call delete and by that means "remove from the database" vs. some business logic code that calls delete and means "remove from the user interface"?

Agreed. But you avoid leaky abstractions.

The leakiness potential is worth not having an extra layer.

What if some code on the persistent layer wants to call delete and by that means "remove from the database" vs. some business logic code that calls delete and means "remove from the user interface"?

This is a scenario that I just don't think will come up. When would you ever need to call a delete method on the extended object and then actually need to delete it in the db? A more real life scenario would be calling an archive method then the delete method.

This is all just experimentation for me. I might switch to composition if it gets painful - we'll see.

Please sign in to comment on this gist.

Something went wrong with that request. Please try again.