Skip to content

Instantly share code, notes, and snippets.

@capotej
Forked from nkallen/MODULARITY_OLYMPICS.markdown
Created February 16, 2010 07:02
Show Gist options
  • Save capotej/305364 to your computer and use it in GitHub Desktop.
Save capotej/305364 to your computer and use it in GitHub Desktop.
# 2010 Modularity Olympics
# ===
# This is a contest, open to programming languages from all over the world, to write modular and
# extensible code to solve the following problem: Implement a service that can run queries on a database.
# 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
# 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.
# ===
# You can use any programming language you like (functional, OO, stack-based) and any techniques you like.
# Your main goal should be clarity and flexibility. Terseness is valuable where it adds to clarity.
# Creative and unusual solutions are welcome, though.
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
def run_test(query_evaluator)
query_evaluator.transaction do |t|
t.select("SELECT ... FROM ... FOR UPDATE ...")
t.execute("INSERT ...")
t.execute("INSERT ...")
end
end
puts "Forward:"
run_test(QueryEvaluator.new(ConnectionPool.new(20), query_factory))
puts
puts "Backward:"
run_test(QueryEvaluator.new(ConnectionPool.new(20), ReversingQueryFactory.new(query_factory)))
# 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!
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
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 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 MemoizingQueryFactory
def initialize(query_factory)
@memo = Hash.new do |h, k|
puts "Instantiating Query Object"
h[k] = query_factory.new(*k)
end
end
def new(connection, query_string, *args)
@memo[[connection, query_string, args]]
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 = @query.query
@query = inner
reverse.query = self
reverse
else
self
end
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 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 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
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment