Diseño de APIs con Ruby Edwin Cruz @soſtr8 #SGRuby Friday, June 15, 2012
Diseño de APIs con Ruby
Edwin Cruz @softr8
#SGRuby
Friday, June 15, 2012
PLAN
•Que es una API
• Como implementar una buena API
• Usando Ruby on Rails para implementar una API
• Patrones de diseño
Friday, June 15, 2012
“
“
ApplicationProgrammingInterface
Friday, June 15, 2012
API
Es una manera para comunicar dos aplicaciones entre ellas.
Friday, June 15, 2012
TIPOS DE API
• Library
• SDK
•Web services
Friday, June 15, 2012
PRIMERAS API - SOAP
SimpleObjectAccessProtocol
Friday, June 15, 2012
REST
REpresentationStateTransfer
Friday, June 15, 2012
CRUD
CreateReadUpdateDelete
AltasBajasCambiosConsultas
Friday, June 15, 2012
REST ⋍ CRUD
Friday, June 15, 2012
Recursos a través de URIUso de verbos HTTP
REST ⋍ CRUD
Friday, June 15, 2012
REST-ISH + JSON
Friday, June 15, 2012
REST-ISH + JSON
=
Friday, June 15, 2012
REST-ISH + JSON
=Cosas Increibles
Friday, June 15, 2012
RECURSO
Friday, June 15, 2012
RECURSO
Cualquier cosa expuesta mediante web
Friday, June 15, 2012
RECURSO
Cualquier cosa expuesta mediante webTienen una representación en datos
Friday, June 15, 2012
RECURSO
Cualquier cosa expuesta mediante webTienen una representación en datosCon un servicio web intercambiamos representaciones de recursos
Friday, June 15, 2012
REQUISITOS REST
Friday, June 15, 2012
REQUISITOS REST
Separación de responsabilidades
Friday, June 15, 2012
REQUISITOS REST
Separación de responsabilidades Cliente/Servidor
Friday, June 15, 2012
REQUISITOS REST
Separación de responsabilidades Cliente/ServidorSin estado
Friday, June 15, 2012
REQUISITOS REST
Separación de responsabilidades Cliente/ServidorSin estado“Cacheable”
Friday, June 15, 2012
REQUISITOS REST
Separación de responsabilidades Cliente/ServidorSin estado“Cacheable”Sistema a capas
Friday, June 15, 2012
REQUISITOS REST
Separación de responsabilidades Cliente/ServidorSin estado“Cacheable”Sistema a capasInterface uniforme
Friday, June 15, 2012
¿PORQUÉ UN API?
Friday, June 15, 2012
¿PORQUÉ UN API?
Aumenta la flexibilidad de un servicio
Friday, June 15, 2012
¿PORQUÉ UN API?
Aumenta la flexibilidad de un servicioAumenta la utilidad de un servicio
Friday, June 15, 2012
¿PORQUÉ UN API?
Aumenta la flexibilidad de un servicioAumenta la utilidad de un servicio
Libera los datos de usuario
Friday, June 15, 2012
¿PORQUÉ UN API?
Aumenta la flexibilidad de un servicioAumenta la utilidad de un servicio
Libera los datos de usuarioProporciona valor de negocio
Friday, June 15, 2012
¿QUÉ CARACTERÍSTICAS?
Friday, June 15, 2012
¿QUÉ CARACTERÍSTICAS?
Fácil de implementar y mantener
Friday, June 15, 2012
¿QUÉ CARACTERÍSTICAS?
Fácil de implementar y mantenerBuen rendimiento
Friday, June 15, 2012
¿QUÉ CARACTERÍSTICAS?
Fácil de implementar y mantenerBuen rendimientoEscalable
Friday, June 15, 2012
¿QUÉ CARACTERÍSTICAS?
Fácil de implementar y mantenerBuen rendimientoEscalableFácil de entender y usar
Friday, June 15, 2012
¿CUÁLES SON LOS RETOS?
Friday, June 15, 2012
¿CUÁLES SON LOS RETOS?
La red es un eslabón débil
Friday, June 15, 2012
¿CUÁLES SON LOS RETOS?
La red es un eslabón débilAPI incompleta pone estrés en el cliente
Friday, June 15, 2012
¿CUÁLES SON LOS RETOS?
La red es un eslabón débilAPI incompleta pone estrés en el clienteAdministrar Cambios
Friday, June 15, 2012
DISEÑAR UNA BUENA API
Friday, June 15, 2012
CONVENCIONES REST
Friday, June 15, 2012
GET /api/products #Listado
REST ⋍ CRUD
{"products" : [ {"product" : { "id" : 1, "name" : "Producto 1", "status" : "archived"} }, {"product" : { "id" : 2, "name" : "Producto 2", "status" : "active" } } ]}
Friday, June 15, 2012
REST ⋍ CRUD
GET /api/products/2 #Ver
{"product" : { "id" : 2, "name" : "Producto 2", "status" : "active" } }
Friday, June 15, 2012
REST ⋍ CRUD
POST /api/products #Crear
{ "name" : "Producto 2"}
Friday, June 15, 2012
REST ⋍ CRUD
PUT /api/products/2 #Actualizar
{ "name" : "Producto dos"}
Friday, June 15, 2012
REST ⋍ CRUD
DELETE /api/products/2 #Eliminar
Friday, June 15, 2012
VERSIONANDO TU API
• API Interna (entre aplicaciones)
• API Externa (aplicacion movil ? )
• API Usuarios (publico en general)
Friday, June 15, 2012
VERSIONANDO TU API
/int/api/products/ext/api/products/pub/api/products
Friday, June 15, 2012
VERSIONANDO TU API
/int/api/products?version=2/ext/api/products?version=2/pub/api/products?version=2
Friday, June 15, 2012
VERSIONANDO TU API
/v2/int/api/products/v2/ext/api/products/v2/pub/api/products
Friday, June 15, 2012
MUNDO IDEAL
Friday, June 15, 2012
MUNDO IDEAL
Uso de: Accept Header
Friday, June 15, 2012
MUNDO IDEAL
Uso de: Accept Header
Accept: application/vnd.mycompany.com;version=2,application/json
Friday, June 15, 2012
CODIGOS HTTP200 OK201 Created202 Accepted
400 Bad Request401 Unauthorized402 Payment Required404 Not Found409 Conflict422 Unprocessable Entity
500 Internal Server Error503 Service Unavailable
Friday, June 15, 2012
CODIGOS HTTP
HTTP/1.1 401 Unauthorized
{ “errors”: [ “api_key not found” ]}
Friday, June 15, 2012
CODIGOS HTTPHTTP/1.1 401 Unauthorized
{ “errors”: [ “api_key no valida”, “api_key no puede contener espacios” ]}
Friday, June 15, 2012
CODIGOS HTTP
HTTP/1.1 401 Unauthorized
{ “errors”: [ “api_key no encontrada, por favor visita http://account.myapp.com/api para obtenerla” ]}
Friday, June 15, 2012
CODIGOS HTTP
HTTP/1.1 400 Bad Request
{ “errors”: [ “Estructura JSON no valida”, “unexpected TSTRING, expected ‘}’ at line 2” ]}
Friday, June 15, 2012
DOCUMENTACIONHTTP/1.1 422 Unprocessable Entity
{ “errors”: [ “vendor_code: no puede estar vacio” ], “documentacion”: [ “vendor_code”: [ “descripcion” : “Codigo asignado por proveedor”, “formato” : “Combinacion de tres letras seguidas de 4 numeros”, “ejemplo” : “SOL1234” ] ]}
Friday, June 15, 2012
CODIGOS HTTP
HTTP/1.1 503 Service Unavailable
{ “messages”: [ “En mantenimiento” ]}
Friday, June 15, 2012
OPCIONES AVANZADAS
• Simuladores
• Autenticación
• Validadores
• Limite de uso
• Rapidez
• Balanceo
Friday, June 15, 2012
USANDO RUBY ON RAILS PARA
IMLEMENTAR UNA API
Friday, June 15, 2012
APIS ON RAILS - REST
products GET /products(.:format) products#index POST /products(.:format) products#create new_product GET /products/new(.:format) products#newedit_product GET /products/:id/edit(.:format) products#edit product GET /products/:id(.:format) products#show PUT /products/:id(.:format) products#update DELETE /products/:id(.:format) products#destroy
MyApp::Application.routes.draw do resources :productsend
Friday, June 15, 2012
APIS ON RAILS - REST
products GET /api/products(.:format) products#index POST /api/products(.:format) products#create new_product GET /api/products/new(.:format) products#newedit_product GET /api/products/:id/edit(.:format) products#edit product GET /api/products/:id(.:format) products#show PUT /api/products/:id(.:format) products#update DELETE /api/products/:id(.:format) products#destroy
MyApp::Application.routes.draw do scope ‘/api’ do resources :products endend
Friday, June 15, 2012
APIS ON RAILS - VERSIONES
v2_products GET /v2/products(.:format) V1/products#index POST /v2/products(.:format) V1/products#create new_v2_product GET /v2/products/new(.:format) V1/products#newedit_v2_product GET /v2/products/:id/edit(.:format) V1/products#edit v2_product GET /v2/products/:id(.:format) V1/products#show PUT /v2/products/:id(.:format) V1/products#update DELETE /v2/products/:id(.:format) V1/products#destroy
gem 'versionist'MyApp::Application.routes.draw do api_version(:module => 'V1', :path => 'v2') do resources :products endend
Friday, June 15, 2012
APIS ON RAILS, CONTROLADOR
class V2::ProductsController < ApplicationController respond_to :json def index @products = V2::Product.paginate(:page => (params[:page] || 1), :per_page => (params[:per_page] || 100)).all respond_with @products end def show @product = V2::Product.find(params[:id]) respond_with @product end def update @product = V2::Product.find(params[:id]) @product.update_attributes(params[:product]) respond_with @product end def destroy @product = V2::Product.find(params[:id]) respond_with @product.destroy endend
Friday, June 15, 2012
APIS ON RAILS - MODELO
class V2::Product < Product
JSON_ATTRIBUTES = { properties: [ :id, :upc, :sku, :list_cost, :color, :dimension, :size, :created_at, :updated_at, ], methods: [ :units_on_hand ] }
end
Friday, June 15, 2012
APIS ON RAILS, CONTROLADOR
gem ‘rabl’class V3::ProductsController < ApplicationController respond_to :json, :xml def index @products = V3::Product.paginate(:page => (params[:page] || 1), :per_page => (params[:per_page] || 100)).all end def show @product = V3::Product.find(params[:id]) end def update @product = V3::Product.find(params[:id]) @product.update_attributes(params[:product]) end def destroy @product = V3::Product.find(params[:id]) render json: {}, status: @product.destroy ? :ok : :unprocessable_entity endend
Friday, June 15, 2012
APIS ON RAILSVISTAS
#app/views/api/v3/products/index.rabl
collection @productsattributes :id, :name, :statusnode(:url) {|product| product_url(product) }node(:current_stock) {|product| product.variants.map(&:on_hand).sum }child :variants do attributes :upc, :color, :size, :on_handend
{"products" : [ {"product" : { "id" : 1, "name" : "Producto 1", "status" : "archived", “current_stock” : 10, “variants” : [ {“upc” : “ASDFS”, “color” : “negro”, “size” : “M”, “on_hand” : 10} ] } } ]}
Friday, June 15, 2012
APIS ON RAILSVISTAS
gem ‘jbuilder’Jbuilder.encode do |json| json.content format_content(@message.content) json.(@message, :created_at, :updated_at)
json.author do |json| json.name @message.creator.name.familiar json.email_address @message.creator.email_address_with_name json.url url_for(@message.creator, format: :json) end
if current_user.admin? json.visitors calculate_visitors(@message) end
json.comments @message.comments, :content, :created_at
end
Friday, June 15, 2012
APIS ON RAILSVISTAS
gem ‘active_model_serializer’class PostSerializer < ActiveModel::Serializer attributes :id, :body attribute :title, :key => :name
has_many :comments
def tags tags.order :name endend
Friday, June 15, 2012
APIS ON RAILSSEGURIDAD
DeviseAutenticacion Flexible para aplicaciones RailsCompuesta de 12 modulos: database authenticable, token authenticable, omniauthable, confirmable, recoverable, registerable, trackable, timeoutable, validatable, lockable
Friday, June 15, 2012
APIS ON RAILSSEGURIDAD
DeviseAutenticacion Flexible para aplicaciones RailsCompuesta de 12 modulos: database authenticable, token authenticable, omniauthable, confirmable, recoverable, registerable, trackable, timeoutable, validatable, lockable
Friday, June 15, 2012
APIS ON RAILSSEGURIDAD
gem ‘rabl’class V3::ProductsController < ApplicationController before_filter :authenticate_user! respond_to :json, :xml def index @products = V3::Product.paginate(:page => (params[:page] || 1), :per_page => (params[:per_page] || 100)).all end def show @product = V3::Product.find(params[:id]) end def update @product = V3::Product.find(params[:id]) @product.update_attributes(params[:product]) end def destroy @product = V3::Product.find(params[:id]) render json: {}, status: @product.destroy ? :ok : :unprocessable_entity endend
Friday, June 15, 2012
APIS ON RAILSPRUEBAS
describe V3::ProductsController do
before do @request.env["HTTP_ACCEPT"] = "application/json" end
describe "#index" do context "cuando no se pasa ningun atributo" do it "regresa los registros en paginas" do get :index response.should be_success data = JSON.parse(response.body) Product.count.should > 0 data['products'].length.should == Product.count end end endend
Friday, June 15, 2012
APIS ON RAILSPRUEBAS
describe V3::ProductsController do
before do @request.env["HTTP_ACCEPT"] = "application/json" end
describe "#show" do context "pasando un id inexistente" do it "responde con http 404 y un mensaje de error" do get :show, id: -1 response.code.should == "404" JSON.parse(response.body)['error'].should == "ActiveRecord::RecordNotFound" JSON.parse(response.body)['message'].should == "Couldn't find Product with id=-1" end end endend
Friday, June 15, 2012
APIS ON RAILSPRUEBAS
describe V3::ProductsController do
before do @request.env["HTTP_ACCEPT"] = "application/json" end
describe "#create" do context "con malos atributos" do it "responde con un error" do post :create, product: {bad_key: "foo"} response.code.should == "400" json_response = JSON.parse(response.body) json_response['error'].should == "ActiveRecord::UnknownAttributeError" json_response['message'].should == "unknown attribute: bad_key" end end endend
Friday, June 15, 2012
APIS ON RAILSPRUEBAS
describe V3::ProductsController do
before do @request.env["HTTP_ACCEPT"] = "application/json" end
describe "#create" do context "con atributos correctos" do it "responde correctamente y crea el producto" do expect { post :create, product: {name: "productito"} }.to change(Product, :count).by(1) end end endend
Friday, June 15, 2012
RAILS A DIETA
Friday, June 15, 2012
RAILS A DIETA
Rails es modular
Friday, June 15, 2012
RAILS A DIETA
Rails es modularPara crear APIs, algunos Middlewares no son
necesarios
Friday, June 15, 2012
RAILS A DIETA
Rails es modularPara crear APIs, algunos Middlewares no son
necesariosrails-api
Friday, June 15, 2012
<Module:0x007ff271221e40>, ActionDispatch::Routing::Helpers, #<Module:0x007ff2714ad268>, ActionController::Base, ActionDispatch::Routing::RouteSet::MountedHelpers, HasScope, ActionController::Compatibility, ActionController::ParamsWrapper, ActionController::Instrumentation, ActionController::Rescue, ActiveSupport::Rescuable, ActionController::HttpAuthentication::Token::ControllerMethods, ActionController::HttpAuthentication::Digest::ControllerMethods, ActionController::HttpAuthentication::Basic::ControllerMethods, ActionController::RecordIdentifier, ActionController::DataStreaming, ActionController::Streaming, ActionController::ForceSSL, ActionController::RequestForgeryProtection, AbstractController::Callbacks, ActiveSupport::Callbacks, ActionController::Flash, ActionController::Cookies, ActionController::MimeResponds, ActionController::ImplicitRender, ActionController::Caching, ActionController::Caching::Fragments, ActionController::Caching::ConfigMethods, ActionController::Caching::Pages, ActionController::Caching::Actions, ActionController::ConditionalGet, ActionController::Head, ActionController::Renderers::All, ActionController::Renderers, ActionController::Rendering, ActionController::Redirecting, ActionController::RackDelegation, ActiveSupport::Benchmarkable, AbstractController::Logger, ActionController::UrlFor, AbstractController::UrlFor, ActionDispatch::Routing::UrlFor, ActionDispatch::Routing::PolymorphicRoutes, ActionController::HideActions, ActionController::Helpers, AbstractController::Helpers, AbstractController::AssetPaths, AbstractController::Translation, AbstractController::Layouts, AbstractController::Rendering, AbstractController::ViewPaths, ActionController::Metal, AbstractController::Base, ActiveSupport::Configurable, Object, ActiveSupport::Dependencies::Loadable, Mongoid::Extensions::Object::Yoda, Mongoid::Extensions::Object::Substitutable, Mongoid::Extensions::Object::Reflections, Mongoid::Extensions::Object::DeepCopy, Mongoid::Extensions::Object::Checks, JSON::Ext::Generator::GeneratorMethods::Object, PP::ObjectMixin, Kernel, BasicObject
Friday, June 15, 2012
<Module:0x007ff271221e40>, ActionDispatch::Routing::Helpers, #<Module:0x007ff2714ad268>, ActionController::Base, ActionDispatch::Routing::RouteSet::MountedHelpers, HasScope, ActionController::Compatibility, ActionController::ParamsWrapper, ActionController::Instrumentation, ActionController::Rescue, ActiveSupport::Rescuable, ActionController::HttpAuthentication::Token::ControllerMethods, ActionController::HttpAuthentication::Digest::ControllerMethods, ActionController::HttpAuthentication::Basic::ControllerMethods, ActionController::RecordIdentifier, ActionController::DataStreaming, ActionController::Streaming, ActionController::ForceSSL, ActionController::RequestForgeryProtection, AbstractController::Callbacks, ActiveSupport::Callbacks, ActionController::Flash, ActionController::Cookies, ActionController::MimeResponds, ActionController::ImplicitRender, ActionController::Caching, ActionController::Caching::Fragments, ActionController::Caching::ConfigMethods, ActionController::Caching::Pages, ActionController::Caching::Actions, ActionController::ConditionalGet, ActionController::Head, ActionController::Renderers::All, ActionController::Renderers, ActionController::Rendering, ActionController::Redirecting, ActionController::RackDelegation, ActiveSupport::Benchmarkable, AbstractController::Logger, ActionController::UrlFor, AbstractController::UrlFor, ActionDispatch::Routing::UrlFor, ActionDispatch::Routing::PolymorphicRoutes, ActionController::HideActions, ActionController::Helpers, AbstractController::Helpers, AbstractController::AssetPaths, AbstractController::Translation, AbstractController::Layouts, AbstractController::Rendering, AbstractController::ViewPaths, ActionController::Metal, AbstractController::Base, ActiveSupport::Configurable, Object, ActiveSupport::Dependencies::Loadable, Mongoid::Extensions::Object::Yoda, Mongoid::Extensions::Object::Substitutable, Mongoid::Extensions::Object::Reflections, Mongoid::Extensions::Object::DeepCopy, Mongoid::Extensions::Object::Checks, JSON::Ext::Generator::GeneratorMethods::Object, PP::ObjectMixin, Kernel, BasicObject
#<Module:0x007f9211d5cd70>, ActionDispatch::Routing::Helpers, #<Module:0x007f9211f7b5e8>, ActionController::API, ActiveRecord::Railties::ControllerRuntime, ActionDispatch::Routing::RouteSet::MountedHelpers, ActionController::Instrumentation, ActionController::Rescue, ActiveSupport::Rescuable, ActionController::DataStreaming, ActionController::ForceSSL, AbstractController::Callbacks, ActiveSupport::Callbacks, ActionController::ConditionalGet, ActionController::Head, ActionController::Renderers::All, ActionController::Renderers, ActionController::Rendering, AbstractController::Rendering, AbstractController::ViewPaths, ActionController::Redirecting, ActionController::RackDelegation, ActiveSupport::Benchmarkable, AbstractController::Logger, ActionController::UrlFor, AbstractController::UrlFor, ActionDispatch::Routing::UrlFor, ActionDispatch::Routing::PolymorphicRoutes, ActionController::HideActions, ActionController::Metal, AbstractController::Base, ActiveSupport::Configurable, Object, JSON::Ext::Generator::GeneratorMethods::Object, ActiveSupport::Dependencies::Loadable, PP::ObjectMixin, Kernel, BasicObject
Friday, June 15, 2012
use ActionDispatch::Staticuse Rack::Lockuse #<ActiveSupport::Cache::Strategy::LocalCache::Middleware:0x007fd3b32928c0>use Rack::Runtimeuse Rack::MethodOverrideuse ActionDispatch::RequestIduse Rails::Rack::Loggeruse ActionDispatch::ShowExceptionsuse ActionDispatch::DebugExceptionsuse ActionDispatch::RemoteIpuse ActionDispatch::Reloaderuse ActionDispatch::Callbacksuse ActionDispatch::Cookiesuse ActionDispatch::Session::CookieStoreuse ActionDispatch::Flashuse ActionDispatch::ParamsParseruse ActionDispatch::Headuse Rack::ConditionalGetuse Rack::ETaguse ActionDispatch::BestStandardsSupportuse Rack::Mongoid::Middleware::IdentityMap
Friday, June 15, 2012
use ActionDispatch::Staticuse Rack::Lockuse #<ActiveSupport::Cache::Strategy::LocalCache::Middleware:0x007fd3b32928c0>use Rack::Runtimeuse Rack::MethodOverrideuse ActionDispatch::RequestIduse Rails::Rack::Loggeruse ActionDispatch::ShowExceptionsuse ActionDispatch::DebugExceptionsuse ActionDispatch::RemoteIpuse ActionDispatch::Reloaderuse ActionDispatch::Callbacksuse ActionDispatch::Cookiesuse ActionDispatch::Session::CookieStoreuse ActionDispatch::Flashuse ActionDispatch::ParamsParseruse ActionDispatch::Headuse Rack::ConditionalGetuse Rack::ETaguse ActionDispatch::BestStandardsSupportuse Rack::Mongoid::Middleware::IdentityMap
use ActionDispatch::Staticuse Rack::Lockuse #<ActiveSupport::Cache::Strategy::LocalCache::Middleware:0x007fe74448cf50>use Rack::Runtimeuse ActionDispatch::RequestIduse Rails::Rack::Loggeruse ActionDispatch::ShowExceptionsuse ActionDispatch::DebugExceptionsuse ActionDispatch::RemoteIpuse ActionDispatch::Reloaderuse ActionDispatch::Callbacksuse ActiveRecord::ConnectionAdapters::ConnectionManagementuse ActiveRecord::QueryCacheuse ActionDispatch::ParamsParseruse ActionDispatch::Headuse Rack::ConditionalGetuse Rack::ETag
Friday, June 15, 2012