We started a new project at shutl last week. For the past few months, we've been discussing in the office various different ideas that sound like they would make sense to try out. Amongst those ideas have been the following:

  • RESTful APIs (including the HATEOAS constraint)
  • (X)HTML Hypermedia APIs
  • JSON Hypermedia APIs (i.e. with some kind of links and forms/templates embedded in the response)
  • SOA - non monolithic apps, separate services, possibly a combination of Sinatra/Padrino/Rails apps.
  • Forms as bonafide objects in Rails (Django like - and not procedural code like), thanks @pithyless
  • DCI (Data, Context, Interaction)

It's the last point I want to talk about. We considered some of the other points when thinking about how we will architect the new system and a lot of the conversations went round all the trade-offs/benefits and never quite came to a real conclusion. We may well have HATEOAS compliant APIs in our new system, we may well break the app into separate services, but these seem to be approaches to solve problems that we're simply not having right now.

After watching Jim Gay's talk at wroc_love.rb last month and following some of the buzz about DCI that has been going around recently, I was quite excited by the concept of trying this approach out. There's a few things that have generally been niggling at me with respect to Rails development and I've not quite been able to put my finger on them. Steve Klabnik wrote an excellent article that gets at the gist of a lot of these niggles, but didn't quite offer up a hatful of magic solutions.

But of all the ideas that we've been bandying around for the last few months, the one that seemed like it would have the most business benefit to us, apart from a coffee machine or pool table was the idea of DCI. Of completing the loop and breaking the slight impedance mismatch that comes with mapping the real world to fit into M's, V's, and C's.

Anyway, after starting off with a Rails app, we kind of also were struggling with the 'how' and 'where' to put all the D's, C's, and I's.

There's a few mentions of simple use-cases but that I've seen but not a great deal Rails specific, so we were conceptually struggling to break out of the BDD feature/routes/view/Rspec controller spec/model spec/wherever next kind of cycle of the so called 'London School of Testing' of growing object oriented software.

Some of the first questions were: "Well, where do we put our contexts? Conceptually, and literally in the file system." "How do we instantiate them?" "Where in this feature cycle does it make sense to start thinking of a role/interaction?"

And what about the recent ideas of fast tests and Avdi's Objects-on-Rails approach of decoupling entirely from the shackles of the Rails/ActiveRecord prison?

A lot of things were pointing us in the direction of breaking away from Rails completely and writing plain old ruby objects that do the stuff of the business. You know, the domain logic, the separation of concerns and all that.

So we went back to a place I feel more comfortable with. We threw away Rails and starting writing a new project.

At first it looked like:

$ls -ah
.   .gitignore      .rspec      Gemfile     Gemfile.lock      README.md

Ah, refreshing. README driven development. We added the bare minimum. Which this time was RSpec and Turnip.

We started writing our acceptance tests. We mocked everything. Or where we need persistence we used OpenStructs in replacement of ActiveRecord. Our tests were insanely fast. We added fabricator for an easier way of building up our simple dumb data storing objects.

We didn't think about this

We didn't think about Javascript. Or web requests. Or REST. Or Hypermedia. Or data storage. Or queries. Or optimisation. We didn't think about all the stuff that business people glaze over whenever you mention it. We didn't think about HTML or templates. Or URLs. Or Servers. Or Clients. We didn't think about WebMock. Or using vagrant to setup a cluster of apps and databases and gubbins and stuff that can be used to run tests against a distributed system of services. We didn't have to talk about click-ing or fill_in-ing, or jangling some magic combination of keys to a spooky ethereal database configuration, that would pseudo-transmogrify your API mcthingummy to just make the things come on the screen.

We didn't think about spotting the difference in timestamps between webmock requests. There's a shitload of not thinking that we did.

So what the hell did we do?

Well, we spent all of our time talking about business concepts, drawing diagrams, scribbling on paper and coding solutions to business problems and talking to the people about the optimal way of describing the existing problems.

We wrote tests that were focused on user inputs to a system at the highest level. We spoke solely in domain logic about the problem domain and everyone in the office technical or not could be involved in a lot of the discussions.

Sounds good, what else?

Our test suite. Blazing. Did I mention our test suite? It's fast. It's only about 150 tests, but these are unit tests and acceptance tests that we'd got done in the first two-week sprint. I'd already been converted fully over to mocking vs factories and had been noticing the benefits.

We'd broken out of the mindset that tests take of the order of minutes and had gotten a previous project down from that magnitude to around 20 seconds. So that felt good. But this. About 0.5 seconds. Literally for the whole suite. Maybe we can get it faster, but we haven't even thought about any optimsing or spork or any of the other hacks that people need to do to get their tests fast.

That's all very well, but you haven't mentioned DCI yet

Sorry, I haven't I was excited about the tests. And about just coding in ruby, without all the hackery that's involved in web app development.

So what about DCI?

Well following from some of the ideas in Avdi Grimm's book about Objects, we wanted to make sure we could do all this abstracting away from Rails, but make it easy to swap it in.

This might not be the optimal solution and it's possibly a little bit astronauty for some, but we found that we could use POROs if we follow the idea of passing your dependencies in. So we came up with the idea of an abstract kind of data_store that just responds to methods returning things that will make your objects for you. e.g.

SudoSandwichMakerContext.new params, data_store

#then inside the context.call

user_fetcher = data_store.user_fetcher
user = user_fetcher.get params[:user_id]
user.extend SandwichMaker

user.make_sandwich params[:ingredients]

rescue SandwichMakingRefusal
  user.make_sandwich! params[:ingredients]

That's your bread-and-butter DCI stuff, and it's pretty easy to follow. In our fast test suite the user is literally just an OpenStruct.

At times, it's very much felt like we've been in danger of getting carried away to Java-land and lost in a sea of boilerplate to add a whole bunch of abstractions that we don't necessarily need, but bear with me, here's the gist of the fabrication stuff:

if ENV['fast_specs']
  #has a bunch of 'class User < OpenStruct; end' type declarations
  require 'open_struct_models'

  Fabricator :user_factory do
    find {Fabricate(:user)}

  Fabricator :ingredients_factory do
    all {[Fabricate(:ingredients) ]}

  Fabricator :data_store do
    user_fetcher        {Fabricate(:user_factory)}
    ingredients_fetcher {Fabricate(:ingredients_factory)}
  #inside Rails - so AR models or whatever already loaded
  Fabricator :data_store do
    user_fetcher        User
    ingredients_fetcher Ingredients

So why the hell is all that necessary?

I'm open to ideas. I'm not saying this is the way to do it. We could maybe simplify things. I must admit I found the idea of a fabricator to fabricate a data store that fabricates factories that create the instances I want to test seemed insane. But it seemed a natural consequence of completely decoupling the domain logic from the actual app and real world where all the messy, hard bits live.

And you do only have to do this stuff approximately once. There might be a way to write a DSL that simplifies this stuff too.

What's the benefit?

Well we made working software. All ready for a demo on Monday that should exceed expectations.

How about integrating with Rails again? Well as you can see with the snippet above. Our user factory is back to just being the User class. Plain old familiar for most, Rails and ActiveRecord. So we can have our cucumber/turnip test that live in the old world, and our fast_specs that describe our almost completely transplantable domain logic. The number of places we couple to anything using DCI is absolutely minimal.


#N.B. ActiveRecord class methods are actually factories in disguise.
class DataStore
  def self.user_fetcher

class ApplicationController
  def data_store

class SandwichController
  def create
    SudoSandwichMakerContext.new params, data_store #this line is the seam

Anyway, would love to receive feedback / field any questions. The topic is new to me, but quite exciting in that it seems to simplify a lot of Rails development, and I look forward to ushering in yet another era of improved software development.