-
-
Save capotej/305364 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# 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! |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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