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
>
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
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