release responsibly (maintaining backwards compatibility)
DESCRIPTION
The Ruby community is notorious for favoring innovation over stability. The reality is that software usually has other dependencies not able to keep up with our pace. So how can we keep our code backwards compatible? Releasing responsibly is critical, whether you maintain an open source library, your company's codebase, or a personal project. On the MongoDB driver team, we recognize that it's easier to upgrade a driver than to upgrade a database. Our code must therefore hide all the gory details of backwards compatibility and continue to expose the simplest possible API version-to-version. I'll talk about some best practices we follow to make sure our users never have unexpected API inconsistencies, regardless of the underlying database version.TRANSCRIPT
source :rubygems !# Generic gem “rake” if RUBY_VERSION < “1.9.3” gem “activesupport”, “~>3.0” else gem “activesupport” end !# Deploy gem “redcarpet”, “2.2.0” !# Testing gem “mocha”, “0.13.0”, :require => ”mocha/setup” gem “shoulda”, “3.3.2” gem “shoulda-matchers”, “~>1.0” gem “test-unit”, “2.5.0” !# JRuby platforms :jruby do gem “jruby-jars”, “1.7.13” end
“Not part of the official API”
“My bad, sorry”
yank the gem, release a new one
a gem version breaks backwards compatibility…
“What’s the deal with the Ruby driver?
!
Do newer versions of the driver support all older versions of the server?”
DO • Add arguments with default values, so
old method calls still work. • Add methods. • Add subclasses. !
DON’T • Add arguments without default values. • Rename methods or classes. • Remove arguments from method
signatures.
def add_user(username, password=nil, read_only=false, opts={}) begin user_info = command(:usersInfo => username) # MongoDB >= 2.5.3 requires the use of commands to manage users. # "Command not found" error didn't return an error code (59) # before MongoDB 2.4.7 so we assume that a nil error code means # the usersInfo command doesn't exist and we should fall back to # the legacy add user code. rescue OperationFailure => ex raise ex unless COMMAND_NOT_FOUND_CODES.include?(ex.error_code) return legacy_add_user(username, password, read_only, opts) end ! if user_info.key?('users') && !user_info['users'].empty? create_or_update_user(:updateUser, username, password, read_only, opts) else create_or_update_user(:createUser, username, password, read_only, opts) end end
Ex: Gory details
module BSON class OrderedHash < Hash ! def ==(other) begin case other when BSON::OrderedHash keys == other.keys && values == other.values else super end rescue false end end ... end end
Ex: OrderedHash
Ex: Gem released breaking 1.8.7 support
Q: Maybe you can change the gemspec to official say you don’t support 1.8.7?
A: We don’t use the gemspec because we only support alive Ruby versions.
Ex: strict mode# lib/mongo/db.rb ... ! # @deprecated Support for strict will be removed in version 2.0 of the driver. def strict=(value) warn "Support for strict mode has been “ + “deprecated and will be removed “ + “in version 2.0 of the driver." @strict = value end
Ex: Insert operation# lib/mongo/operation/write/insert.rb ... ! def execute(context) if context.write_command_enabled? op = Command::Insert.new(spec) Response.new(op.execute(context)) else context.with_connection do |connection| Response.new(connection.dispatch([ message, gle ])) end end end
Ex: Insert operation
# lib/mongo/operation/write/insert.rb ... ! def execute(context) op = Command::Insert.new(spec) Response.new(op.execute(context)) end
source :rubygems !# Generic gem “rake” if RUBY_VERSION < “1.9.3” gem “activesupport”, “~>3.0” else gem “activesupport” end !# Deploy gem “redcarpet”, “2.2.0” !# Testing gem “mocha”, “0.13.0”, :require => ”mocha/setup” gem “shoulda”, “3.3.2” gem “shoulda-matchers”, “~>1.0” gem “test-unit”, “2.5.0” !# JRuby platforms :jruby do gem “jruby-jars”, “1.7.13” end