Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save baldmountain/307073 to your computer and use it in GitHub Desktop.
Save baldmountain/307073 to your computer and use it in GitHub Desktop.

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!
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.
import scala.testing.Benchmark
import scala.actors._
import Actor._
import scala.collection.mutable.Map
class SecondsHelper(sec: Int) {
def seconds: Int = sec*1000
def second: Int = sec*1000
def minutes: Int = sec*1000*60
def minute: Int = sec*1000*60
def hours: Int = sec*1000*60*60
def hour: Int = sec*1000*60*60
}
implicit def convertInt2Seconds(sec: Int) = new SecondsHelper(sec)
class Connection
object Connection {
def apply() = new Connection
}
class Stats {
def measure(name : String)(f: => Any): Any = {
var answer: Any = None
val bm = new Benchmark {
def run = {
answer = f
None
}
}
val results = bm.runBenchmark(1)
val duration = results(0) / 1000.0
printf("Measured %s at %.2f seconds\n", name, duration)
answer
}
}
object Stats {
def apply() = new Stats
}
class ConnectionPool(val size: Int) {
def with_connection(f:(Connection) => Unit): Any = {
f(new Connection)
}
}
object ConnectionPool {
def apply(size :Int) :ConnectionPool = new ConnectionPool(size)
}
trait Query {
def select: Any
def execute: Any
}
class BaseQuery (val connection :Connection , val query_string :String, args :Any*) extends Query {
def select = {
Thread.sleep(1000)
println("Selecting "+query_string+" on "+connection)
List(1, 2, 3)
}
def execute = {
Thread.sleep(1000)
println("Executing "+query_string+" on "+connection)
1
}
def reverse = this
}
object BaseQuery {
def apply(connection :Connection , query_string :String, args :Any*): Query =
new BaseQuery(connection, query_string, args)
}
trait QueryProxy extends Query {
val query :Query
def delegate(name :String)(f: => Any): Any
def select = {
delegate("select") { query.select }
}
def execute = {
delegate("execute") { query.execute }
}
}
class TimingOutQueryFactory(val query_factory: QueryFactory, val timeout: Int) extends QueryFactory {
override def create(connection: Connection, query_string: String, args: Any*): QueryProxy = {
new TimingOutQueryProxy(query_factory.create(connection, query_string, args), timeout)
}
}
class TimingOutQueryProxy(override val query :Query, val timeout: Int) extends QueryProxy {
override def delegate(name :String)(f: => Any): Any = {
val caller = self
actor {
caller ! f
}
receiveWithin(timeout) {
case TIMEOUT => println("timed out")
case r: Any => { println("Did not timeout! Yay fast database!") ; r }
}
}
}
class QueryFactory {
def create (connection: Connection, query_string: String, args: Any*): Query = {
BaseQuery(connection, query_string, args)
}
}
class MemoizingQueryFactory(val query_factory: QueryFactory) extends QueryFactory {
override def create(connection: Connection, query_string: String, args: Any*): QueryProxy = {
new MemoizingQueryProxy(query_factory.create(connection, query_string, args))
}
}
class MemoizingQueryProxy(override val query: Query) extends QueryProxy {
val memo = Map[String, Any]() // remember results based on query string
println("Instantiating Query Object")
override def delegate(name :String)(f: => Any): Any = {
// look up result in memo using query string return it if we find it
val query_string = query.asInstanceOf[BaseQuery].query_string
var answer = memo.getOrElse(query_string, None)
if (answer == None) {
answer = f
// store result in memo if we had to calculate it
memo += (query_string -> answer)
}
answer
}
}
class StatsCollectingQueryFactory(val query_factory: QueryFactory, val stats: Stats) extends QueryFactory {
override def create(connection: Connection, query_string: String, args: Any*): QueryProxy = {
new StatsCollectingQueryProxy(query_factory.create(connection, query_string, args), stats)
}
}
class StatsCollectingQueryProxy(override val query: Query, val stats: Stats) extends QueryProxy {
override def delegate(name :String)(f: => Any): Any = {
stats.measure(name) { f }
}
}
class ReversingQueryFactory(val query_factory: QueryFactory) extends QueryFactory {
override def create(connection: Connection, query_string: String, args: Any*): QueryProxy = {
new ReversingQueryProxy(query_factory.create(connection, query_string, args))
}
}
class ReversingQueryProxy(override val query: Query) extends QueryProxy {
override def delegate(name :String)(f: => Any): Any = {
val answer = f
answer match {
case l: Seq[_] => l.reverse
case _ => answer
}
}
}
class TransactionalQueryFactory(val query_factory: QueryFactory) extends QueryFactory {
override def create(connection: Connection, query_string: String, args: Any*): QueryProxy = {
new TransactionalQueryProxy(query_factory.create(connection, query_string, args))
}
}
class TransactionalQueryProxy(override val query: Query) extends QueryProxy {
override def delegate(name :String)(f: => Any): Any = {
// begin transaction
val answer = f
// end transaction
answer
}
}
class QueryEvaluator(val connection_pool: ConnectionPool, val query_factory: QueryFactory) {
def select(query_string: String, args: Any*): Any = {
connection_pool.with_connection { connection =>
query_factory.create(connection, query_string, args).select
}
}
def execute(query_string: String, args: Any*): Any = {
connection_pool.with_connection { connection =>
query_factory.create(connection, query_string, args).execute
}
}
def transaction(f:(TransactionalQueryEvaluator) => Any): Any = {
connection_pool.with_connection { connection =>
val tqe = new TransactionalQueryEvaluator(connection, query_factory)
tqe.transaction { f(tqe) }
}
}
}
class TransactionalQueryEvaluator(val connection: Connection, val query_factory: QueryFactory) {
def select(query_string: String, args: Any*): Any ={
query_factory.create(connection, query_string, args).select
}
def execute(query_string: String, args: Any*): Any = {
query_factory.create(connection, query_string, args).execute
}
def transaction(f: => Any): Any = {
// begin transaction
val answer = f
// end transaction
answer
}
}
val qf = new QueryFactory
val config = List (("MemoizingQueryFactory", None), ("TimingOutQueryFactory", 2.seconds), ("StatsCollectingQueryFactory", Stats()))
val query_factory = (qf /: config) { (factory, item) =>
var (decorator_name, settings) = item
decorator_name match {
case "MemoizingQueryFactory" => new MemoizingQueryFactory(factory)
case "TimingOutQueryFactory" => new TimingOutQueryFactory(factory, settings.asInstanceOf[Int])
case "StatsCollectingQueryFactory" => new StatsCollectingQueryFactory(factory, settings.asInstanceOf[Stats])
}
}
List(query_factory, new ReversingQueryFactory(query_factory)).foreach { qf =>
val query_evaluator = new QueryEvaluator(ConnectionPool(20), qf)
query_evaluator.transaction { t: TransactionalQueryEvaluator =>
t.select("SELECT ... FROM ... FOR UPDATE ...")
t.execute("INSERT ...")
t.execute("INSERT ...")
}
println
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment