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

Beautiful APIs with Faceted

Beautiful APIs with Faceted

Rails as a framework is famous for helping you get your application up and running quickly, but the very paradigms that make it so easy at the start can lead to maintenance nightmares down the road. Successful applications grow rapidly larger, more complex, and harder to extend and maintain.

One way to approach refactoring a monolithic application is dividing it up into a series of smaller applications that organize the work of the system through internal APIs. Faceted is a new Ruby gem that makes it easy to build these APIs using a flexible, declarative, and opinionated set of modules. In this presentation you will be introduced to Faceted, see examples of how to use it, and get a glimpse of the guiding philosophy behind the API-driven refactoring philosophy.

Coraline Ada Ehmke

November 14, 2012
Tweet

More Decks by Coraline Ada Ehmke

Other Decks in Technology

Transcript

  1. 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
  2. IDIOMATIC RAILS: RAPID DEVELOPMENT MADE EASY •Convention over configuration •Built-in

    MVC support •Persistence built in to models •RESTful routing
  3. 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
  4. UNCLE BOB MARTIN “Most software eventually degrades to the point

    where someone will declare the design to be unsound.”
  5. 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
  6. HOW DID WE GET HERE? •Dozens of developers have touched

    the code •Explosion of methods •“God Models” •Eternal test suite •Idiomatic Rails
  7. 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?
  8. 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
  9. 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
  10. HEXAGONAL ARCHITECTURE •Objects have “faces” or “sides” •Each represents an

    interface •These interfaces taken together define an API
  11. 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
  12. FACETED: A HEXAGONAL TOOLKIT FOR APIS •Presenters, collectors, interfaces, and

    controllers •Ready to use with minimal wiring •Convention over configuration FTW
  13. GUIDING PRINCIPLES FOR A BEAUTIFUL API •Easy to maintain •Easy

    to scale •Easy to extend •DRY •Suitable for use in new and novel ways
  14. 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
  15. module  MyApi    class  Musician        include  Faceted::Presenter

           presents  :musician        field  :name        field  :genre        field  :random_song_title    end end PRESENTERS IN ACTION
  16. >  m  =  Musician.create(:name  =>  'Johnny  Cash') >  m.id =>

     13 >  presenter  =  MyApi::Musician.new(:id  =>  13) >  presenter.name =>  "Johnny  Cash" PRESENTERS IN ACTION
  17. >  presenter.genre  =  nil >  presenter.save =>  false >  presenter.errors

    =>  [“Genre  cannot  be  blank”] PRESENTERS IN ACTION
  18. >  presenter.genre  =  “Western” >  presenter.save =>  true >  m

     =  Musician.find(13) >  m.genre =>  “Western” PRESENTERS IN ACTION
  19. 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
  20. module  MyApi    class  Birthplace        include  Faceted::Presenter

           presents  :birthplace,  :class_name  =>  ‘Location’        field  :city        field  :state    end end PRESENTERS IN ACTION
  21. 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
  22. 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
  23. 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
  24. 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
  25. module  MyApi    class  Playlist        include  Faceted::Collector

           collects  :musicians,  :find_by  =>  :genre_id    end end COLLECTORS
  26. >  l  =  MyApi::Playlist.new(:genre_id  =>  3) >  l.musicians.count =>  14

    >  l.musicians.first.name =>  "American  Music  Club" COLLECTORS
  27. You’ve got your beautiful API fully stocked with presenters and

    collectors. Now it’s time to serve them up. CONTROLLERS
  28. 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
  29. class  MyApi::PlaylistsController  <  MyApi::BaseController    def  create      

     @playlist  =  MyApi::Playlist.new(params)        render_response  @playlist    end end CONTROLLERS
  30. 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
  31. class  MyApi::MusicianSearchController  <  MyApi::BaseController    def  create      

     @musicians  =  MyApi::Musician.where(params)        render_response_with_collection(:musicians,  @musicians)    end end CONTROLLERS
  32. 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
  33. INTERFACE DSL    class  MemberWrapper        include  Faceted::Interface

           wraps  :member        schema  do            field  :name            field  :email            field  :password            ...        end        exposes  :full_name    end
  34. 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
  35. 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!