release responsibly (maintaining backwards compatibility)

45
Release Responsibly (backwards compatibility) Emily Stolfo Ruby Engineer at @EmStolfo

Upload: emstolfo

Post on 13-Jun-2015

223 views

Category:

Technology


0 download

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

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