Transcript

Release Responsibly (backwards compatibility)

Emily Stolfo Ruby Engineer at @EmStolfo

The lifetime of our Gemfile.

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

Innovation and

Stability are in conflict in the Ruby community.

“Not part of the official API”

“My bad, sorry”

yank the gem, release a new one

a gem version breaks backwards compatibility…

Release Responsibly (backwards compatibility)

“What’s the deal with the Ruby driver?

!

Do newer versions of the driver support all older versions of the server?”

“Not quite…”

Your code will be backwards

compatible if you stick to 3 things.

SIMPLE API CLEAR COMMUNICATION SEMANTIC VERSIONING

SIMPLE API

Configurability and

Stability are at odds.

www.gov.uk

Some ways to safely make API

changes.

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.

Modularize (hide all the gory details)

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

Have a Compatibility

module.

Use Polymorphism

instead of conditionals.

Backport only when necessary.

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

CLEAR COMMUNICATION

Compatible with what? !

1. Your older API 2. Older system(s) you

integrate with

Define the

scope of what you support.

Ex: Ruby driver

Test everything you say you support.

Changelog is important.

keepachangelog.com

User trust should be a

priority.

“Not part of the official API”

“My bad, sorry”

yank the gem, release a new one

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.

Have a deprecation

strategy.

www.mongodb.com/support-policy

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

Anticipate deprecations

when designing.

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

SEMANTIC VERSIONING

Why don’t people follow SemVer?

SemVer is a double-edged

sword. !

- a Bundler maintainer

Gemfiles could be cleaner.

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

It’s a culture/community thing.

end


Recommended