Skip to content

Instantly share code, notes, and snippets.

@justinko
Created January 9, 2012 22:38
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save justinko/1585371 to your computer and use it in GitHub Desktop.
Save justinko/1585371 to your computer and use it in GitHub Desktop.
The value of contexts in DCI
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
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
@rkh
Copy link

rkh commented Jan 11, 2012

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.

@judofyr
Copy link

judofyr commented Jan 11, 2012

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.

@justinko
Copy link
Author

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.

@rkh
Copy link

rkh commented Jan 12, 2012

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.

@justinko
Copy link
Author

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.

@rkh
Copy link

rkh commented Jan 12, 2012

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"?

@justinko
Copy link
Author

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment