Ruby DDD Sample App
Goal
The goal of this sample app is to provide an idiomatic Ruby port of the DDD sample application. It should remain faithful to the intention of the purposes of the original DDD sample application.
Why a Ruby port?
Because Paul wanted to learn Ruby and see how well it supports DDD. Also, interested in questions like:
-
How does the choice of Ruby affect the implementation of the DDD building block patterns?
-
How well does an opinionated MVC framework like Rails support doing DDD?
-
What are implications of choosing a document store like MongoDB for aggregate design and eventual consistency?
Why a DDD Sample App?
-
Provide a how-to example for implementing a typical DDD application
-
Descriptive, not prescriptive
-
Idiomatic
-
Update sample app with latest ideas
-
-
Support discussion of implementation practices
-
Engage Ruby community in dialog and learning about DDD
-
Show design and implementation tradeoffs
-
Teach DDD (tend to be Java & .NET) community more about Ruby
-
-
Lab mouse for controlled experiments
-
Learn Ruby as a language and ecosystem
-
Can Rails + MongoDB carry the weight of a complex domain model?
-
Tradeoffs between Sinatra vs Rails for Ruby web apps
-
Problems with Sample Apps
-
Against DDD sample applications - Wise words from Jimmy Bogard
DDD Sample Application
History
-
Sep 2008 First public release: 1.0.
-
Jan 2009 Sample application tutorial at the JFokus conference in Stockholm.
-
Mar 2009 Sample application tutorial at the QCon conference in London.
-
Mar 2009 New public release: 1.1.0. See changelog for details.
-
2010/2011 Ported to .NET in several flavors.
-
May 8, 2013 Begin porting to Ruby at https://github.com/paulrayner/ddd_sample_app_ruby
-
May 13, 2013 Presentation of early work on port of sample app to Ruby at DDD Denver. Slides are available online.
The .NET port is being used as the primary basis for this Ruby port.
Implementation Stack
-
Persistence: MongoDB
-
Data access: Mongoid-backed repositories
-
Domain model: Plain Ruby objects
-
UI: Rails stack (w/ Twitter bootstrap)
-
Aggregate eventual consistency: Wisper-async leveraging Celluloid
-
GOAL: Before DDDx London - June 14 DONE!
_The focus of this version is to see how implementing a domain model within Rails affects the implementation.
Aggregates
The aggregate roots are:
-
Cargo
-
HandlingEvent
-
Location
-
Voyage
Design Decisions
Here you’ll find information on design choices made, and the relative tradeoffs. Plus resources for further reading. Actually, mostly resources right now.
Value Objects
Immutability in Ruby
-
Immutable Ruby presentation video (25 mins) - Michael Fairley @ MountainWest RubyConf 2013
-
Functional programming in object oriented languages - Blog post by Simon Harris, author of Hamster.
Enums in Ruby
Persistence
MongoDB
Mongo ORMs
-
Mongoid - Object-Document-Mapper (ODM) for MongoDB written in Ruby. Has Echo sample app - take a look at
application.rb
- it’s using Sidekiq and Kiqstand (not sure what for…​maybe could be used for aggregate updates?) -
Mongomatic - A MongoDB super-set that adds nice features over the traditional Ruby Driver. Map your Ruby objects to Mongo documents. It is designed to be fast and simple.
-
MongoMapper - ODM for MongoDB written in Ruby.
Repository Pattern in Ruby
-
http://mattbriggs.net/blog/2012/02/23/repository-pattern-in-ruby/
-
A Ruby implementation of the Repository Pattern - In memory only, developed from Repository Pattern for Ruby - 3 years old.
-
A set of interfaces for, and implementations of, the Repository pattern in Ruby. This one looks promising.
-
Collector is an implementation of the Repository Pattern for MongoDB
-
Curator is a model and repository framework for Ruby.Currently, curator supports Riak, MongoDB and an in-memory data store for persistence.
-
Good blog post by Paul Gross: "Untangle Domain and Persistence Logic with Curator"
-
Data migrations for NoSQL with Curator. "Curator migrations are lazy, so at any given time you might have documents with different versions in the data store."
-
ActiveRepository "Strawman" gist by David Bock. Proposal for what a good Repository pattern implementation should look like in Ruby. Comment thread is excellent value.
-
DataMapper 2 - goal is to create an ORM which is fast, thread-safe and feature rich. Last release was 1.2, but active development on v2 seems to be progressing.
-
Datamappify - is built using Virtus and existing ORMs (ActiveRecord and Sequel, etc). Compose and manage domain logic and data persistence separately and intelligently, Datamappify is loosely based on the Repository Pattern and Entity Aggregation. Datamappify is current in Proof-of-Concept stage, do NOT use it for anything other than experimentation.
Have not yet found a repository implementation that supports aggregates. Rather, each implementation follows a repository-per-object approach, which is not what we need.
There is an on issue for Curator regarding supporting foreign keys and embedded objects, and some experimentation in a branch with adding a mapping API which may do what I need.
Entrepot looks promising. It uses Virtus for the objects and has this kinda weird approach of referencing a repository from a repository:
class Address
include Virtus
include Entrepot::Model
attribute :street, String
attribute :city, String
attribute :country, String
end
class Person
include Virtus
include Entrepot::Mongo::Model
attribute :name, String
attribute :address, Address
end
class PersonRepository
include Entrepot::Repository
has_many :articles, :repository => :ArticleRepository
end
Aggregates
Concurrency in Ruby
-
Presentation on NOT using Eventmachine, advocates Celluloid
-
Pragmatic Concurrency With Ruby - great article, which also discusses how Celluloid uses
mutex
to thread-safe its mailboxes.
Eventual Consistency
Resources for implementing eventual consistency (i.e. performing asynchronous updates) between aggregate instances.
Worker Queues
-
Sidekiq - Simple, efficient message processing for Ruby, based on Celluloid actor model
Messaging
Celluloid
DDD and Rails
-
Entity Data Repository - Blog post describing hybrid ActiveRecord/DAO approach to building rich domain models in Rails, implemented in EDR library. Implements restricted version of DataMapper pattern. Datamapper 2 will be implementing the same pattern, but is not production-ready yet (see above)
-
DDD in Ruby article - recommends using to_s for UI concerns and structs for value objects, both of which seem problematic to me.
-
Does My Rails App Need a Service Layer? - blog post from Jan 2012 by Jared Carroll
-
Hexagonal Rails - Video of Matt Wynne’s Goruco 2012 presentation
-
Refactoring with Hexagonal Rails - blog post showing how to set up pub/sub eventing for use within Rails (inspired by Matt Wynne’s approach of passing controller object into domain object, so domain object can run a success/failure callback method on the controller)
-
Wisper - Ruby library for decoupling and managing the dependencies of your domain models]. See also this blog post on Wisper and this business case Gist.
-
Wisper-Async - Extends Wisper with async broadcasting of events. Each listener is transparently turned in to a Celluloid Actor.
Contributing
This is a learning experiment, pull requests are welcome! Bonus points for feature branches.
To get started, see milestones and issues. Use the vanilla .NET port version as the basis for any work.
Progress and learning will be shared after the DDD Exchange on June 14 through posts on Paul Rayner’s blog.
Copyright
Copyright © 2013 Paul Rayner. See LICENSE for details.