Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Building an Organic API

Building an Organic API

The Why, What, and How of a better approach to API design.

Johnny Winn

June 10, 2013
Tweet

Other Decks in Programming

Transcript

  1. Building an Organic API JOHNNY WINN Why, What, & How

    of APIs Text Text • Why should we care about our interfaces? 5 Monday, June 10, 13
  2. Building an Organic API JOHNNY WINN Why, What, & How

    of APIs Text Text • Why should we care about our interfaces? • What can we learn about APIs from a DSL? 6 Monday, June 10, 13
  3. Building an Organic API JOHNNY WINN Why, What, & How

    of APIs Text Text • Why should we care about our interfaces? • What can we learn about APIs from a DSL? • How do we use APIs without thinking? 7 Monday, June 10, 13
  4. Building an Organic API JOHNNY WINN Every Object Communicates through

    Its Public Interfaces Doctor TARDIS 9 Monday, June 10, 13
  5. Building an Organic API JOHNNY WINN Every Object Communicates through

    Its Public Interfaces Doctor TARDIS go_to(‘Manzanillo’, 2013) 10 Monday, June 10, 13
  6. Building an Organic API JOHNNY WINN Abstracting Implementation Details Insulates

    Our Code Doctor TARDIS go_to(‘Manzanillo’, 2013) go_to(location, year) 11 Monday, June 10, 13
  7. Building an Organic API JOHNNY WINN Abstracting Implementation Details Insulates

    Our Code Doctor TARDIS go_to(‘Manzanillo’, 2013) go_to(location, year) start_engines() 12 Monday, June 10, 13
  8. Building an Organic API JOHNNY WINN Abstracting Implementation Details Insulates

    Our Code Doctor TARDIS go_to(‘Manzanillo’, 2013) go_to(location, year) start_engines() travel_to(location, year) 13 Monday, June 10, 13
  9. Building an Organic API JOHNNY WINN Abstracting Implementation Details Insulates

    Our Code Doctor TARDIS go_to(‘Manzanillo’, 2013) go_to(location, year) start_engines() travel_to(location, year) touchdown() 14 Monday, June 10, 13
  10. Building an Organic API JOHNNY WINN Intuitive Interfaces Facilitate Communication

    Doctor TARDIS go_to(‘Manzanillo’, 2013) 15 Monday, June 10, 13
  11. Building an Organic API JOHNNY WINN Unintuitive Interface Obscure Communication

    Doctor TARDIS do_stuff(‘Manzanillo’, 2013) 16 Monday, June 10, 13
  12. Building an Organic API JOHNNY WINN Unintuitive Interface Impede Communication

    Doctor TARDIS do_stuff(‘Manzanillo’, 2013) Do What “Stuff”? 17 Monday, June 10, 13
  13. Building an Organic API JOHNNY WINN What is the TARDIS?

    • An Adaptive Domain Language Interpreter 21 Monday, June 10, 13
  14. Building an Organic API JOHNNY WINN What is the TARDIS?

    • An Adaptive Domain Language Interpreter • Focus is shifted to the messages 22 Monday, June 10, 13
  15. Building an Organic API JOHNNY WINN What is the TARDIS?

    • An Adaptive Domain Language Interpreter • Focus is shifted to the messages • Implementation changes based on a dialect 23 Monday, June 10, 13
  16. Building an Organic API JOHNNY WINN What is the TARDIS?

    • An Adaptive Domain Language Interpreter • Focus is shifted to the messages • Implementation changes based on a dialect • Messages are transformed 24 Monday, June 10, 13
  17. Building an Organic API JOHNNY WINN What is the TARDIS?

    • An Adaptive Domain Language Interpreter • Focus is shifted to the messages • Implementation changes based on a dialect • Messages are transformed Message-Driven Design 25 Monday, June 10, 13
  18. Building an Organic API JOHNNY WINN How does the TARDIS

    work? Message-Driven Design 27 Monday, June 10, 13
  19. Building an Organic API JOHNNY WINN How does the TARDIS

    work? • Language adaptation with Dependency Injection Message-Driven Design 28 Monday, June 10, 13
  20. Building an Organic API JOHNNY WINN How does the TARDIS

    work? • Language adaptation with Dependency Injection • Abstraction of external dependencies via proxy Message-Driven Design 29 Monday, June 10, 13
  21. Building an Organic API JOHNNY WINN How does the TARDIS

    work? • Language adaptation with Dependency Injection • Abstraction of external dependencies via proxy • Visitor pattern to apply commands Message-Driven Design 30 Monday, June 10, 13
  22. Building an Organic API JOHNNY WINN Dependency Injection module Tardis

    class DefaultDialect < Dialect configure do |c| c.command_alias :select, "show" c.command_alias :create, "new" c.define :order, { desc: "most_recent" } c.define :business_objects, ["articles", "posts"] end end end Teach the TARDIS your dialect... 32 Monday, June 10, 13
  23. Building an Organic API JOHNNY WINN Dependency Injection module Tardis

    class Dialect def self.configure &block @@config = Config.new yield @@config end def self.config @@config end end end Learn through configuration... 33 Monday, June 10, 13
  24. Building an Organic API JOHNNY WINN Dependency Injection module Tardis

    class Tardis attr_reader :dialect_klass def initialize(dialect = "Tardis::DefaultDialect") self.dialect = dialect end def namespace Module.nesting.last end def dialect=(klass_string) if namespace.const_defined?(klass_string.split("::").last) @dialect_klass = klass_string.constantize end end def dialect @dialect_klass end def config dialect_klass.config end end end ...pass the knowledge in 34 Monday, June 10, 13
  25. Building an Organic API JOHNNY WINN Dependency Injection module Tardis

    class Config ## Trimmed to fit def includes_command?(key) @commands.include?(key) end def includes_definition?(key) @dictionary.include?(key) end # Handle requests for configuration values by key def method_missing(meth, *args, &block) if includes_definition?(meth) return @dictionary[meth] elsif includes_command?(meth) return @commands[meth] else super end end end end ...and ask for a translation 35 Monday, June 10, 13
  26. Building an Organic API JOHNNY WINN Dependency Injection module Tardis

    class Config ## Trimmed to fit def includes_command?(key) @commands.include?(key) end def includes_definition?(key) @dictionary.include?(key) end # Handle requests for configuration values by key def method_missing(meth, *args, &block) if includes_definition?(meth) return @dictionary[meth] elsif includes_command?(meth) return @commands[meth] else super end end end end > t = Tardis.new("Tardis::DefaultDialect") > puts t.config.select => "show" 36 Monday, June 10, 13
  27. Building an Organic API JOHNNY WINN Proxy Pattern module Tardis

    module Arel class Table attr_accessor :proxy_table def initialize(business_object) self.proxy_table = ::Arel::Table.new(business_object) end def method_missing(meth, *args, &block) if block_given? proxy_table.send(meth, args, block) else proxy_table.send(meth, args) end end def [](key) proxy_table.send(:[], key) end end end end Wrap the dependency and... 38 Monday, June 10, 13
  28. Building an Organic API JOHNNY WINN Proxy Pattern describe Tardis::Arel

    do Arel::Table.engine = Arel::Sql::Engine.new(FakeConnection::Base.new) Given(:table) { Tardis::Arel::Table.new("articles") } context "initialize Table with business object" do Then { expect(table.proxy_table).to be_a(Arel::Table) } And { expect(table.proxy_table.name).to eq("articles") } describe "#order" do When(:query) { table.order(:created_date) } Then { query.to_sql.should =~ /^*ORDER BY 'created_date'$/ } end describe "#take" do When(:query) { table.take(3) } Then { query.to_sql.should =~ /^*LIMIT 3$/ } end end context "chain methods to build query" do When(:query) { table.order(:created_date).take(3) } Then { query.to_sql.should =~ /^*ORDER BY 'created_date' LIMIT 3$/ } end end Maintain API through the proxy 39 Monday, June 10, 13
  29. Building an Organic API JOHNNY WINN Visitor Pattern “The Doctor

    is curious, that means we stay” 40 Monday, June 10, 13
  30. Building an Organic API JOHNNY WINN Visitor Pattern Visitor Pattern

    allows us to ... add new functionality to an existing object without modifying the original object 41 Monday, June 10, 13
  31. Building an Organic API JOHNNY WINN Visitor Pattern module Tardis

    class TableCommand attr_accessor :command, :config def initialize(command, config) @command = command @config = config end def accept(visitor) visitor.visit(self) end def method_missing(meth, *args, &block) if meth =~ /^is_(.+)$/ self.is_a?("Tardis::#{$1.gsub("?","").classify}".constantize) else super end end end end A command accepts visitors... 42 Monday, June 10, 13
  32. Building an Organic API JOHNNY WINN Visitor Pattern module Tardis

    class TableVisitor def self.visit(visitor) extract_table_from_command(visitor) do |table| visitor.table = ::Tardis::Arel::Table.new(table) end end def self.extract_table_from_command(visitor, &block) visitor.config.business_objects.detect do |table| if visitor.command =~ Regexp.new("(#{table})") && visitor.is_table_command? block_given? ? yield(table) : table end end end end end but only affected when needed 43 Monday, June 10, 13
  33. Building an Organic API JOHNNY WINN Visitor Pattern class Tardis

    ## Trimmed to fit def apply_visitors_to(command) namespace.constants.each do |visitor| command_accepts_visitor(command, visitor_constant(visitor)) end end def visitor_constant(visitor) "Tardis::#{visitor}".constantize end def command_accepts_visitor(command, visitor) command.accept(visitor) if visitor.respond_to?(:visit) end def method_missing(meth, *args, &block) if meth =~ /#{config.commands[:select]}/ cmd = TableCommand.new(meth, config) apply_visitors_to(cmd) else super end end end The Implementation 44 Monday, June 10, 13
  34. Building an Organic API JOHNNY WINN Rails Magic? You don’t

    want to believe everything you see 47 Monday, June 10, 13
  35. Building an Organic API JOHNNY WINN Rails Magic? You don’t

    want to believe everything you see ActiveRecord is an intuitive interface 48 Monday, June 10, 13
  36. Building an Organic API JOHNNY WINN Rails Magic? You don’t

    want to believe everything you see ActiveRecord is an intuitive interface • Business Objects are decoupled from data 49 Monday, June 10, 13
  37. Building an Organic API JOHNNY WINN Rails Magic? You don’t

    want to believe everything you see ActiveRecord is an intuitive interface • Business Objects are decoupled from data • It responds to a common dialect 50 Monday, June 10, 13