public

  • Download Gist
MODULARITY_OLYMPICS.markdown
Markdown

2010 Modularity Olympics

This is a contest, open to programming languages from all nations, to write modular and extensible code to solve the following problem: Implement a service that can run queries on a database.

The Challenge

Sounds simple right? Wrong! A programmer without control over the source-code of that service must be able to later add enhancements such as statistics collecting, timeouts, memoization, and so forth. There are a few more requirements:

  1. the “enhancements” must be specified in a configuration object which is consumed at run-time (e.g., it could be based on user-input).
  2. The enhancements are ordered (stats collecting wraps timeouts, not the other way around) but it must be possible to reverse the order of the enhancements at run-time.
  3. The enhancements must be “surgical” and not “global”. That is, it must be possible to simultaneously have two query services, one reversed and one not reversed, and even have a query service without any enhancements.
  4. Your code must be thread-safe or at least support concurrency using some technique. Please bear in mind the use of connection pools and such.

Most programming contests emphasize things that are not modularity. This contest emphasizes things that are modularity. And its the olympics and don’t you want to be olympic? So go ahead and prove to the world that Design Patterns are no longer necessary, or that Haskell is the raddest thing since the movie Rad, or that Peanut Butter is better than Jelly. This is much more fun than inflamatory blog posts and angry reddit comments, yes? Yes.

How to Play

Fork this gist!! See my sample

http://magicscalingsprinkles.wordpress.com/2010/02/16/2010-modularity-olympics/

Output

Your program must produce exactly this output. Note the reversing order of the enhancements and the memoization of Query instantiation:

Forward:
Instantiating Query Object
Selecting SELECT ... FROM ... FOR UPDATE ... on #<Object:0x11f6750>
Did not timeout! Yay fast database!
Measured select at 1.00 seconds
Instantiating Query Object
Executing INSERT ... on #<Object:0x11f6750>
Did not timeout! Yay fast database!
Measured select at 1.00 seconds
Executing INSERT ... on #<Object:0x11f6750>
Did not timeout! Yay fast database!
Measured select at 1.00 seconds

Backward:
Instantiating Query Object
Selecting SELECT ... FROM ... FOR UPDATE ... on #<Object:0x11f4ea0>
Measured select at 1.00 seconds
Did not timeout! Yay fast database!
Instantiating Query Object
Executing INSERT ... on #<Object:0x11f4ea0>
Measured select at 1.00 seconds
Did not timeout! Yay fast database!
Executing INSERT ... on #<Object:0x11f4ea0>
Measured select at 1.00 seconds
Did not timeout! Yay fast database!
sample.rb
Ruby
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213
require 'rubygems'
require 'activesupport'
require 'timeout'
 
class MemoizingQueryFactory
def initialize(query_factory)
@memo = Hash.new do |h, k|
puts "Instantiating Query Object"
h[k] = query_factory.new(*k) # let's pretend this is thread-safe, kthx ruby!
end
end
 
def new(connection, query_string, *args)
@memo[[connection, query_string, args]]
end
end
 
class QueryEvaluator
def initialize(connection_pool, query_factory = Query)
@query_factory = query_factory
@connection_pool = connection_pool
end
def select(query_string, *args)
@connection_pool.with_connection do |connection|
@query_factory.new(connection, query_string, *args).select
end
end
def execute(query_string, *args)
@connection_pool.with_connection do |connection|
@query_factory.new(connection, query_string, *args).execute
end
end
def transaction
@connection_pool.with_connection do |connection|
yield TransactionalQueryEvaluator.new(connection, @query_factory)
end
end
end
class TransactionalQueryEvaluator
def initialize(connection, query_factory)
@connection = connection
@query_factory = query_factory
end
def select(query_string, *args)
@query_factory.new(@connection, query_string, *args).select
end
def execute(query_string, *args)
@query_factory.new(@connection, query_string, *args).execute
end
def transaction
yield self
end
end
 
class QueryProxy
attr_accessor :query
 
def initialize(query)
@query = query
end
 
def select
delegate("select") { @query.select }
end
 
def execute
delegate("execute") { @query.execute }
end
 
def reverse
case @query
when QueryProxy
reverse = @query.reverse
inner = reverse.query
clone = dup
clone.query = inner
reverse.query = clone
reverse
else
self
end
end
 
def dup
clone = super
clone.query = @query.dup
clone
end
end
 
class ReversingQueryFactory
def initialize(query_factory)
@query_factory = query_factory
end
 
def new(connection, query_factory, *args)
@query_factory.new(connection, query_factory, *args).reverse
end
end
 
class TimingOutQueryFactory
def initialize(query_factory, timeout)
@query_factory = query_factory
@timeout = timeout
end
def new(connection, query_string, *args)
TimingOutQuery.new(@query_factory.new(connection, query_string, *args), @timeout)
end
end
class TimingOutQuery < QueryProxy
def initialize(query, timeout)
super(query)
@timeout = timeout
end
 
def delegate(method)
result = Timeout.timeout(@timeout.to_i) { yield }
puts "Did not timeout! Yay fast database!"
result
end
end
 
class StatsCollectingQueryFactory
def initialize(query_factory, stats)
@query_factory = query_factory
@stats = stats
end
 
def new(connection, query_string, *args)
StatsCollectingQuery.new(@query_factory.new(connection, query_string, *args), @stats)
end
end
 
class StatsCollectingQuery < QueryProxy
def initialize(query, stats)
super(query)
@stats = stats
end
 
def delegate(method)
@stats.measure(method) { yield }
end
end
 
class Query
def initialize(connection, query_string, *args)
@connection = connection
@query_string = query_string
@args = args
end
 
def select
sleep 1
puts "Selecting #{@query_string} on #{@connection}"
[1, 2, 3]
end
 
def execute
sleep 1
puts "Executing #{@query_string} on #{@connection}"
1
end
end
 
class ConnectionPool
def initialize(size)
@size = size
end
 
def with_connection
yield Object.new
end
end
 
class Stats
def measure(name)
result = nil
bm = Benchmark.measure { result = yield }
puts "Measured #{name} at #{"%.2f" % bm.real} seconds"
result
end
end
 
config = [
[:memoizing, []],
[:timing_out, [2.seconds]],
[:stats_collecting, [Stats.new]]
]
query_factory = config.inject(Query) do |factory, (decorator_name, settings)|
"#{decorator_name.to_s.classify}QueryFactory".constantize.new(factory, *settings)
end
 
[query_factory, ReversingQueryFactory.new(query_factory)].each do |qf|
query_evaluator = QueryEvaluator.new(ConnectionPool.new(20), qf)
query_evaluator.transaction do |t|
t.select("SELECT ... FROM ... FOR UPDATE ...")
t.execute("INSERT ...")
t.execute("INSERT ...")
end
puts
end
 
# let me know if you see any bugs but I think this is good and thread-safe now (with caveats noted
# inline.

Please sign in to comment on this gist.

Something went wrong with that request. Please try again.