The DCI Architecture: Lean and Agile at the Code Level

Reference: The DCI Architecture: Lean and Agile at the Code Level

James Coplien's criticism of Java (that it cannot do proper object-oriented programming) is my essential criticism of static languages in general: what if the model of programs that your language enforces does not allow you to program well?

Coplien describes a style of programming that, although not especially encouraged by Smalltalk, was at least possible as an architectural convention. And listening to Alan Kay, I get the impression that the early Smalltalk group was programming with a concept like Roles in mind, though there was no explicit construct for it in the language.

That concept was what we now refer to as "duck typing". To fulfill a "methodful role", you simply needed to have all of the required methods defined.

Now, how would Clojure handle DCI? Well, quite naturally, actually. There are three building block concepts in DCI described in the video, each with a terrible name.

  1. Classes (data)
  2. Methodful Roles (interfaces)
  3. Methodless Roles (variables)

In Clojure, "classes" are simply data, perhaps validated to conform to a schema, perhaps wrapped in a ref to get state. "Methodful roles" are groups of functions that can operate on data. If you're feeling frisky, you can use protocols. "Methodless roles" are variables in your program, each assigned to the data which will play that role.

Let's look at the main example in Clojure.

    ;; money source role: debit and balance
    (defn debit [source amount]
      (update-in source [:balance] #(- % amount)))

    (defn balance [source]
      (:balance source))

    ;; money destination role: credit
    (defn credit [dest amount]
        (update-in dest [:balance] #(+ % amount)))

    ;; the use case for doing a transfer
    (defn transfer [amount source destination]
        (when (< (balance @source) amount)
          (throw (ex-info "Insufficient funds." {:account @source :amount amount})))
        (alter   source       debit amount) ;; debiting is not commutative if we check balance
        (commute destination credit amount)))

    ;; our two accounts
    (def savings  (ref {:balance 1000}))
    (def checking (ref {:balance 25}))

    ;; we can transfer 20 bucks from savings to checking
    (transfer 20 savings checking)

I wrote this just to make sure I was not kidding myself that "DCI" is dead-obvious if your language does not get in the way. Object-oriented conventions today make this complicated enough that you need to read books on it to understand. How many files would you need to write in Java to do this?

  1. MoneySource interface
  2. MondeyDestination interface
  3. Transfer Use Case
  4. Account Class (or two, depending on the differences between savings and checking)

If you can modify the account class(es) to implement the interfaces, you should be ok with those files. But if you can't, you'll have to use the "Bridge" pattern that I learned in school. Basically, you make a new class for each combination of class and interface. So you would have SavingsAccountMoneySource, SavingsAccountMoneyDestination, CheckingAccountMoneySource, and CheckingAccountMoneyDestination. And OOP was supposed to need less code!

This video tells me that object-oriented programming as it is taught today has been going way down the wrong track. The lessons the static object-oriented languages have learned from the dynamic ones are the wrong lessons. Duck typing allowed programmers to elegantly implement these ideas without restrictions from the compiler. And inheritance was about reuse more than subtyping. Modern OO style takes and does not give back.