My aproach for doing “DDD on Ruby” – Introduction and Part I
Introduction
We all know that in Domain-Driven Design it is good to express our domain with simplicity and “pureness” and for that we usually go for a POxO (Plain Old {fill_with_your_preffered_language} Object) approach. The problem is that the ORM solutions I’ve found for Ruby make us either inherit from some base class or do the mappings directly into our classes. By no means am I saying that they aren’t good, but we are talking about DDD and the idea of having a pure domain layer without distractions is something we should embrace.
My first thought was to write about a full blown example, comparing side by side a common C# / Java implementations to my Ruby implementation but then I realized it would probably have some unnecessary and “boring” information. It’s better to focus on writing about my approach and have some comments here and there to explain how things would be done in other platforms. What I’m going to show here is something that is really common in other languages but not in Ruby I believe, all classes from my domain will be implemented as POROs – Plain Old Ruby Objects, totaly decoupled from any tool that we might use.
There was a talk by Eric Evans called “What I’ve learned about DDD since the book” that has been commented out here and here (slides here). Even though the building blocks of Model-Driven Design mentioned in the book (and the other one mentioned on the talk) are not essential, they are important and will be the subject of this series.
So, to illustrate my ideas I’ll use the common Customer/Order/Product for my examples with some requirements from Jimmy Nilsson – Applying Domain-Driven Design and Patterns:
- List the orders when looking at a specific customer
- Orders have an acceptance status that is changed by the user
- The total value of an order must be lower or equal than one million US$
- A Customer has a credit limit and cannot owe us more than a specified amount of money
Part I – Customer and Products
If you are not familiar with DDD and its building blocks I suggest that have a look at this glossary of DDD terms.
This is our really simple model for this part:

Entities
There’s no big deal here so I’ll just provide some code:
class Customer attr_reader :id attr_accessor :name attr_accessor :credit_limit def initialize(name, credit_limit) @name = name @credit_limit = credit_limit end end class Product attr_reader :id attr_accessor :price attr_accessor :description def initialize(price, description) @price = price @description = description end end
Repositories
Statically typed languages like Java and C# would have an interface in order to decouple our domain from infrastructure but Ruby doesn’t have interfaces. But still… I believe that we need some kind of contract for the repositories and instead of having a class with empty methods I’ll create in-memory repositories as the pattern definition:
class CustomerRepository def self.store(customer) customer.instance_variable_set(:@id, next_id) if customer.id.nil? customers[customer.id] = customer end def self.find(id) customers[id] end def self.all customers.values end def self.delete(customer) customers.delete(customer.id) end private def self.customers if !defined?(@@customers) @@customers = {} else @@customers end end def self.next_id if !defined?(@@next_id) @@next_id = 1 else @@next_id += 1 end end end class ProductRepository # ... similar code ... end
Maybe this code could be factored out somewhere but let’s just leave as it is for now.
Right now you might be thinking “This guy is crazy, static methods are evil!!”. Well, I think that it’s not true when it comes to Ruby class methods. Just below I will show you how we can easily add persistence to our domain.
Even if we want to set up a IoC container I don’t think it would be a problem since Ruby classes are objects as well. For example these repositories could be easily injected into a Rails controller instance variable.
Adding persistence
If we use (N)Hibernate, we would probably have the mappings file into a different package / assembly, but in Ruby I think it would be enough to just have persistence related code in another file that redefines the classes.
Mappings
class Customer include Clipper::Model orm.map(self, "customers") do |customers| customers.key(customers.field("id", Clipper::Types::Serial)) customers.field("name", Clipper::Types::String.new(200)) customers.field("credit_limit", Clipper::Types::Float(8, 2)) end end class Product include Clipper::Model orm.map(self, "products") do |products| products.key(products.field("id", Clipper::Types::Serial)) products.field("description", Clipper::Types::String.new(200)) products.field("price", Clipper::Types::Float(8, 2)) end end
Repositories
module ClipperRepository def self.included(klass) klass.instance_eval do include Singleton include Clipper::Session::Helper def self.orm instance.orm end end end end class CustomerRepository include ClipperRepository def self.store(customer) orm.save(customer) end def self.delete(customer) orm.delete(customer) end def self.find(id) orm.get(Customer, id) end def self.all orm.all(Customer) end end class ProductRepository # ... similar code ... end
Here I’ve used Clipper but for these examples we could probably rewrite it to use DataMapper as well. Unfortunately, with this approach both DataMapper and Clipper will redefine our already defined accessors and that would be a problem if we have some business logic there. For this example this wouldn’t be a problem but I’ve checked with one of Clipper developers and they are willing to have this feature implemented in the library, I’ll have to check if DataMapper folks have something like this in mind as well. I’ve already hacked Clipper in order to have something like Hibernate property access config that defines the strategy for accessing the property value but it still lacks the feature for associations.
Testing
With this approach I believe testing becomes more simple. I’ve created tests for the pure domain and just by including a different file I can test everything hooked up with infrastructure without rewriting the tests. To make things easier, I created two Rake tasks for testing: ‘rake test:domain’ and ‘rake test:infrastructure’. I think they look a bit ugly at the moment but here we go:
namespace :test do task :enviroment_domain do |t| ENV['DDD_EXAMPLE_HELPER'] = Pathname(__FILE__).dirname + 'tests' + 'domain_helper' end task :enviroment_infrastructure do |t| ENV['DDD_EXAMPLE_HELPER'] = Pathname(__FILE__).dirname + 'tests' + 'infrastructure_helper' end Rake::TestTask.new(:domain => :enviroment_domain) do |t| t.libs << "tests" t.test_files = FileList["tests/domain/**/*_test.rb"] t.verbose = true end Rake::TestTask.new(:infrastructure => :enviroment_infrastructure) do |t| t.libs << "tests" t.test_files = FileList["tests/domain/**/*_test.rb"] + FileList["tests/infrastructure/**/*_test.rb"] t.verbose = true end end>>
Test Case
require ENV['DDD_EXAMPLE_HELPER'] class CustomerRepositoryTest < Test::Unit::TestCase # ... def test_create customer = Customer.new('customer', 1500) CustomerRepository.store(customer) assert_not_nil(customer.id) end # ... end
I believe this approach increases experimentation because we are free to work with our domain without worrying about persistence issues as if we were using Rails ActiveRecord. Rails AR need us to set up DB tables by running migrations in order to use the entities and have access to the properties.
Project Structure

Conclusion
This whole thing might seem weird if you are used to Rails AR, but the fact that we have our domain totaly decoupled from infrastructure issues is important because as technology changes and shifts, and as our domain layer is burdened with complex computer science problems, things will most certainly change. Keeping the heart of the software decoupled from tools allows those changes to be easier (XP ?).
That’s it! I’d like to thank Michael Brennan and Guilherme Chapiewski for reviewing and contributing with ideas for this post and Sam Smoot for also reviewing and helping me out with Clipper.
- Operating System:
- Person:
- Programming Language:
- Tags:
- Technology:



Recent comments
25 weeks 9 hours ago
25 weeks 1 day ago
26 weeks 6 days ago
29 weeks 1 day ago
43 weeks 6 days ago
46 weeks 6 days ago
47 weeks 5 days ago
47 weeks 5 days ago
48 weeks 4 hours ago
50 weeks 1 day ago