"DCI" in Ruby is completely broken

  • I was curious what Tony's graph would look like using a SimpleDelegator as he suggested.

        (using ruby 1.9.3p194)
        Calculating -------------------------------------
                 without dci     68507 i/100ms
                    with dci     24409 i/100ms
              with delegator     46945 i/100ms
        -------------------------------------------------
                 without dci  2240202.4 (±3.2%) i/s -   11235148 in   5.020463s
                    with dci   412445.3 (±3.7%) i/s -    2074765 in   5.037113s
              with delegator  1018928.4 (±1.6%) i/s -    5117005 in   5.023310s
    
    
    RUBY:

        require 'rubygems'
        require 'benchmark/ips'
        require 'delegate'
    
        class ExampleClass
          def foo; 42; end
        end
    
        module ExampleMixin
          def foo; 43; end
        end
    
        class ExampleProvisioner < SimpleDelegator
          def foo; 44; end
    
        Benchmark.ips do |bm|
          bm.report("without dci") { ExampleClass.new.foo }
          bm.report("with dci") do
            obj = ExampleClass.new
            obj.extend(ExampleMixin)
            obj.foo
          end
          bm.report("with delegator") do
            ExampleProvisioner.new(ExampleClass.new).foo
          end
        end

  • I've had similar experiences profiling Ruby code, replacing dynamic constructions with static ones, and seeing orders of magnitude bumps in performance.

    One thing I've learned never to do is to "customize" short-lived objects at runtime with new methods. If you create an object in response to user input, it should be born with all the methods it's going to need.

  • You know, I've been thinking about this kind of thing lately. All of this mixin, traits, monkey patching hoopla is completely nonexistent in functional programming languages. To add a new method to an object you just..write a function that takes that piece of data.

    Imagine a language where a call looked like this:

        user.doSomething(1, 2, 3)
    
    But it's just syntax sugar for this function application:

        doSomething(user, 1, 2, 3)
    
    ("user" would be just the data..like a struct type thing)

    That way you get the flexibility of loose functions but you get the (imo) superior aesthetics of oop languages.

  • From my experience the main benefit comes from the alignment of particular scenarios, with contexts, the result of which is a single place to reason about the algorithm(s) behind that scenario.

    I see very little reason for the dogma behind the way role injection is typically handled, yet everyone seems to be attached to doing this in Ruby. This is odd because there are DCI examples in almost every language, each one has it's awkwardness when it comes to roles, but they all manage to get it done with some workaround. I don't see why Ruby should be any different, if it wasn't possible to call #extend, we'd just do it another way.

    Either way, I wouldn't dismiss DCI just because the canonical approach some academics cooked up is completely senseless in practice. In fact the author seems to agree in the end so the title is a bit exaggerated.

  • I'm "thinking in DCI" for 2 years now.

    At GameBoxed (http://gameboxed.com/) we've got a Rails app that uses a lot of .extend on every request. It's in production for 1.5 year now.

    It's a backend for multiple social games (one backend app for many CoffeeScript frontends).

    It's not huge in terms of traffic and performance needs. We implemented it with a DCI-like architecture in mind. It was helpful at the beginning. DCI wasn't the only way to implement it, but it was the simplest way for us at that time.

    We've had some performance problems, but they were never related to the usage of .extend. If it was a performance problem, we would probably consider removing the .extends (not a big deal), switch to Java, do more caching, there are so many options.

    Again, we don't have huge performance needs, we've maybe 40 req/s at peak times.

    DCI is much more than .extend, it's a huge shift in thinking about the OOP architecture.

    If we (as Ruby community) agree that it's an architecture worth trying, then maybe it makes sense to introduce better optimisations techniques to the current Ruby implementations?

    Anyway, it's great to see the discussion happening. DCI may not be ready to use right now, but it's more of a change in thinking than in the implementation.

    I definitely wouldn't call it completely broken in Ruby.

    Here are some of my posts on DCI in Ruby:

    http://andrzejonsoftware.blogspot.com/2011/02/dci-and-rails....

    http://andrzejonsoftware.blogspot.com/2012/01/dci-and-rails-...

    http://andrzejonsoftware.blogspot.com/2011/08/dci-patterns-h...

  • I'm not a Ruby programmer, so maybe I'm misunderstanding the article -- are we talking about changing an object's class hierarchy after it's already been instantiated and then complaining that it's the performance aspect that is pathological? That sounds like a completely insane way of writing software to me.

  • In practice, we've been using DCI (with object.extend) in a production Rails app and have seen no worse performance degradation than in other apps with comparable complexity (at least subjectively--of course it's very difficult to measure objectively). Typically plenty of production apps suffer more from simple things like missing an index on a db column, N+1 queries, iterating over objects in Ruby, or poorly-thought out design. DCI in Rails is a pretty good trade off for when you need to use it.

  • The title of the post is unnecessarily inflammatory. It should be "Using extend to do DCI in Ruby is completely broken." The author even admits this in the article, and points out other ways of doing DCI in Ruby.

  • Erm what? Changing the class hierarchy invalidates all method caches? That sounds a bit brutal, and totally unnecessary.

    Can't you just have a method cache per metaclass (or class; I'm not very familiar with how Ruby factors this stuff)? Since mixing in a role into an instances creates a new metaclass anyway, there's no need to affect instances of the original class.

  • DCI is a paradigm related to design and architecture. Computers are so fast today that does this benchmarking business really matter?

    If DCI helps to keep your code maintainable, readable, and less buggy, is that a better tradeoff? I would think that 95-99% of the time, the answer is yes.

    On a side note, if speed is mission critical, then would ruby really be the language of choice to use?

  • There's more than one way to skin a cat. Besides mixins, You don't even have to use SimpleDelegator to do delegation in Ruby. For example, here is a demonstration of another approach to DCI in Ruby (http://github.com/rubyworks/dci).

  • Ruby is a language, not an implementation. DCI in ruby seems just fine, but all implementations are not friendly to the dynamic mixin hell hole.

  • I thought the same thing when I came across the DCI pattern. It is very useful for beautifying and simplifying use cases that span lots of objects.

    I do something slightly different. I create a class directly for the context and then fire an object into it.

    class TheContext def initialize # ... end

      def execute(the_object)
        # ...
      end
    end

  • Just to note tha NetBeans RCP uses DCI as well, quite successfully.

    http://wiki.apidesign.org/wiki/DCI

    http://www.antonioshome.net/kitchen/nbdci/

  • Extending is expensive.

    Delegating is usually cheaper, just be careful how you do it.

    https://gist.github.com/4441557

    I guess the upside of `extend` is that you can do it at the call site, delegation has to be configured upfront.

  • You can do DCI without singleton methods in Ruby.