Slide 1

Slide 1 text

BEAUTIFUL APIS WITH FACETED Corey Ehmke Senior Engineer @ Trunk Club

Slide 2

Slide 2 text

WHO IS THIS “COREY” PERSON? •A developer with nearly 20 years of experience •An active Open Source contributor •A veteran of the web application platform wars •Senior engineer at Trunk Club

Slide 3

Slide 3 text

IDIOMATIC RAILS: RAPID DEVELOPMENT MADE EASY •Convention over configuration •Built-in MVC support •Persistence built in to models •RESTful routing

Slide 4

Slide 4 text

IDIOMATIC RAILS: RAPID DEVELOPMENT HAS A COST •Persistence mixed with business logic •Complex object graphs •Non-CRUD controller actions •Route exceptions become the rule •/lib overflow

Slide 5

Slide 5 text

UNCLE BOB MARTIN “Most software eventually degrades to the point where someone will declare the design to be unsound.”

Slide 6

Slide 6 text

WATCH FOR THESE WARNING SIGNS •Business logic leaking through the layers •Raw SQL in models •Overly-complex object graphs •Controllers with non-CRUD actions •Route exceptions become the rule •/lib overflow

Slide 7

Slide 7 text

•Rigidity •Fragility •Immobility •Feature friction FEATURES OF A DEGRADED SYSTEM

Slide 8

Slide 8 text

•Rigidity •Fragility •Immobility •Feature friction FEATURES OF A DEGRADED SYSTEM

Slide 9

Slide 9 text

HOW DID WE GET HERE? •Dozens of developers have touched the code •Explosion of methods •“God Models” •Eternal test suite •Idiomatic Rails

Slide 10

Slide 10 text

GET READY TO REFACTOR •Refactor your test suite first! •Identify and separate groupings of functionality •Think outside the traditional way of modeling •Distribute functionality horizontally •Did I mention refactoring your test suite?

Slide 11

Slide 11 text

MARTIN FOWLER “Refactoring without good test coverage is just changing shit.”

Slide 12

Slide 12 text

TOWARD A DIVERSE APPLICATION ECOSYSTEM •Group related functionality into distinct client applications •Redesign the models •Provide a central data store •Implement caching for performance •Bind the client applications together via solid APIs

Slide 13

Slide 13 text

Web Browser Rake Task API Client Testing Framework API Server Database Mocks iOS Application Command Line Domain Objects Persistence HTTP Command Line Messaging Default Logger External Monitoring Service Email Service HEXAGONAL ARCHITECTURE

Slide 14

Slide 14 text

HEXAGONAL ARCHITECTURE •Objects have “faces” or “sides” •Each represents an interface •These interfaces taken together define an API

Slide 15

Slide 15 text

IMPLEMENTATION GUIDELINES •Build your application in Ruby, not Rails •Think in ports, not layers •Users, programs, tests, and scripts access ports •Everything has an API

Slide 16

Slide 16 text

FACETED: A HEXAGONAL TOOLKIT FOR APIS •Presenters, collectors, interfaces, and controllers •Ready to use with minimal wiring •Convention over configuration FTW

Slide 17

Slide 17 text

FACETED: A HEXAGONAL TOOLKIT FOR APIS

Slide 18

Slide 18 text

FACETED: A HEXAGONAL TOOLKIT FOR APIS

Slide 19

Slide 19 text

GUIDING PRINCIPLES FOR A BEAUTIFUL API •Easy to maintain •Easy to scale •Easy to extend •DRY •Suitable for use in new and novel ways

Slide 20

Slide 20 text

LET’S BUILD SOMETHING ALREADY.

Slide 21

Slide 21 text

THE PRESENTER PATTERN •A stand-in for an ActiveRecords or other models •Presents only the interface you need •Retrieves data and formats it for presentation •Extremely useful for APIs

Slide 22

Slide 22 text

module  MyApi    class  Musician        include  Faceted::Presenter        presents  :musician        field  :name        field  :genre        field  :random_song_title    end end PRESENTERS IN ACTION

Slide 23

Slide 23 text

>  m  =  Musician.create(:name  =>  'Johnny  Cash') >  m.id =>  13 >  presenter  =  MyApi::Musician.new(:id  =>  13) >  presenter.name =>  "Johnny  Cash" PRESENTERS IN ACTION

Slide 24

Slide 24 text

>  presenter.genre  =  nil >  presenter.save =>  false >  presenter.errors =>  [“Genre  cannot  be  blank”] PRESENTERS IN ACTION

Slide 25

Slide 25 text

>  presenter.genre  =  “Western” >  presenter.save =>  true >  m  =  Musician.find(13) >  m.genre =>  “Western” PRESENTERS IN ACTION

Slide 26

Slide 26 text

module  MyApi    class  Musician        include  Faceted::Presenter        presents  :musician        field  :name        field  :genre        field  :random_song_title        field  :birthplace_id    end end PRESENTERS IN ACTION

Slide 27

Slide 27 text

module  MyApi    class  Birthplace        include  Faceted::Presenter        presents  :birthplace,  :class_name  =>  ‘Location’        field  :city        field  :state    end end PRESENTERS IN ACTION

Slide 28

Slide 28 text

>  presenter  =  MyApi::Musician.new(:id  =>  13) >  presenter.birthplace.class.name =>  “MyApi::Birthplace” >  presenter.birthplace.city =>  "Kingsland" PRESENTERS IN ACTION

Slide 29

Slide 29 text

module  MyApi    class  Musician        include  Faceted::Presenter        presents  :musician        field  :name        field  :genre        field  :random_song_title        field  :birthplace_id        field  :record_label_id,  :skip_association  =>  true    end end PRESENTERS IN ACTION

Slide 30

Slide 30 text

module  MyApi    class  Musician        include  Faceted::Presenter        presents  :musician        field  :name        field  :genre        field  :random_song_title        field  :birthplace_id        field  :record_label_id,  :skip_association  =>  true        field  :genre_id,  :class_name  =>  'MusicalGenre'    end end PRESENTERS IN ACTION

Slide 31

Slide 31 text

Now your API is ready to serve up a playlist comprising musicians and their top hits. Don’t reach for that #index method just yet. Try a Collector instead. COLLECTORS

Slide 32

Slide 32 text

In the CRUD world, you would have a heavy query embedded in your #index method. This isn’t tenable in an API, where every kb matters. Rather than deliver a serialized enumerable of objects, deliver a resource that happens to include a collection of light-weight objects. COLLECTORS

Slide 33

Slide 33 text

module  MyApi    class  Playlist        include  Faceted::Collector        collects  :musicians,  :find_by  =>  :genre_id    end end COLLECTORS

Slide 34

Slide 34 text

>  l  =  MyApi::Playlist.new(:genre_id  =>  3) >  l.musicians.count =>  14 >  l.musicians.first.name =>  "American  Music  Club" COLLECTORS

Slide 35

Slide 35 text

You’ve got your beautiful API fully stocked with presenters and collectors. Now it’s time to serve them up. CONTROLLERS

Slide 36

Slide 36 text

class  MyApi::BaseController  <  ActionController::Base    require  'faceted'    include  Faceted::Controller    before_filter  :authenticate_user!    respond_to  :json    rescue_from  Exception,  :with  =>  :render_500    rescue_from  ActiveRecord::RecordNotFound,  :with  =>  :render_404 end CONTROLLERS

Slide 37

Slide 37 text

class  MyApi::PlaylistsController  <  MyApi::BaseController    def  create        @playlist  =  MyApi::Playlist.new(params)        render_response  @playlist    end end CONTROLLERS

Slide 38

Slide 38 text

class  MyApi::MusiciansController  <  MyApi::BaseController    def  show        @musician  =  MyApi::Musician.new(params)        render_response  @musician    end    def  update        @musician  =  MyApi::Musician.new(params)        @musician.save        render_response  @musician    end end CONTROLLERS

Slide 39

Slide 39 text

class  MyApi::MusicianSearchController  <  MyApi::BaseController    def  create        @musicians  =  MyApi::Musician.where(params)        render_response_with_collection(:musicians,  @musicians)    end end CONTROLLERS

Slide 40

Slide 40 text

INTERNAL APIS Good OO design is about classes and messages Our Member model understands 2,287 messages Difficult to refactor, impossible to prune Business logic co-mingles with persistence Declarative interfaces may be the answer

Slide 41

Slide 41 text

INTERFACE DSL    class  MemberWrapper        include  Faceted::Interface        wraps  :member        schema  do            field  :name            field  :email            field  :password            ...        end        exposes  :full_name    end

Slide 42

Slide 42 text

USING APIS AS A REFACTORING TOOL Step 1: implement Faceted for a lightweight API

Slide 43

Slide 43 text

USING APIS AS A REFACTORING TOOL Step 1: implement Faceted for a lightweight API Step 2: Let the API design drive your refactoring efforts Extract concerns Consider implementing internal interfaces Separate persistence from business logic

Slide 44

Slide 44 text

USING APIS AS A REFACTORING TOOL Step 1: implement Faceted for a lightweight API Step 2: Let the API design drive your refactoring efforts Extract concerns Consider implementing internal interfaces Separate persistence from business logic Step 3: Rule the world!

Slide 45

Slide 45 text

KEEP IN TOUCH! [email protected] @bantik on twitter/app.net github.com/bantik Happy hours @ Trunk Club