construindo apis usando rails
DESCRIPTION
Essa apresentação aconteceu no 17 encontro dos usuários de Rails de PE - Frevo on Rails. Nela, eu abordo 4 tópicos importantes na hora de implementar APIs: Rest, Consistencia, Versionamento e Segurança.TRANSCRIPT
Criando APIs - Usando RailsFernando KakimotoDesenvolvedor @ ThoughtWorks
O que é uma API mesmo?
Application Programming Interface
Biblioteca que inclui especificações para rotinas, estruturas de dados, objetos, classes e
variáveis
Características de boas APIs
Fácil de aprender e memorizar
Dificilmente usada de maneira errada
Minimalista
Completa
Tópicos para hoje
REST
Consistência
Versionamento
Segurança
REST
REpresentational State Transfer
Um padrão arquitetural para sistemas hipermídias distribuídos
REST
URL base
Tipo de mídia
Operações (Método HTTP)
REST
GET https://api.twitter.com/1.1/statuses/ment
ions_timeline.json
REST
GET https://api.twitter.com/1.1/statuses/ment
ions_timeline.json
URL base
REST
GET https://api.twitter.com/1.1/statuses/ment
ions_timeline.jsonTipo de mídia
REST
GET https://api.twitter.com/1.1/statuses/ment
ions_timeline.json
Método
2 URLs base por recurso
• GET /tickets - recupera conjunto de tickets
• GET /tickets/12 - recupera o ticket #12
• POST /tickets - cria um novo ticket
• PUT /tickets/12 - atualiza ticket #12
• DELETE /tickets/12 - remove ticket #12
Restrições REST
Cliente-Servidor
Stateless
Cacheable
Sistema em camadas
Interface Uniforme
API RESTful
API implementada usando princípios HTTP e REST
> nandokakimoto@Development$ rails new blog
> nandokakimoto@blog$ rails generate scaffold Post name:string title:string content:text
> nandokakimoto@blog$ rake routes
posts GET /posts(.:format) posts#index POST /posts(.:format) posts#create new_post GET /posts/new(.:format) posts#new edit_post GET /posts/:id/edit(.:format) posts#edit post GET /posts/:id(.:format) posts#show PUT /posts/:id(.:format) posts#update DELETE /posts/:id(.:format) posts#destroy
Consistência
Métodos Safe
Métodos que nunca modificam um recurso
Dados não devem ser alterados como resultado de uma requisição GET
Métodos Idempotentes
Métodos com o mesmo resultado mesmo que requisição seja repetida
diversas vezesPOST é o único método não idempotente
Código de Resposta
Respostas HTTP padronizam como informar o cliente sobre o resultado da
sua requisição
Código de Resposta
1xx - Informacional
2xx - Sucesso
3xx - Redirecionamento
4xx - Erro de cliente
5xx - Erro de servidor
> nandokakimoto@blog$ curl -v http://localhost:3000/posts.json
* Connected to localhost (127.0.0.1) port 3000 (#0)> GET /posts.json HTTP/1.1<< HTTP/1.1 200 OK < Server: WEBrick/1.3.1 (Ruby/1.9.3/2013-06-27)[{"content": "Melhor evento ever <3 <3 <3", "created_at": "2013-09-03T02:12:26Z", "id": 1, "name": "Frevo on Rails", "title": "Evento 14 Setembro", "updated_at": "2013-09-03T02:12:26Z"}]
> nandokakimoto@blog$ curl -v -d "post[name]=Geek Night&post[title]=Scala Dojo&post[content]=Come and learn Scala" http://localhost:3000/posts.json
* Connected to localhost (127.0.0.1) port 3000 (#0)> POST /posts.json HTTP/1.1> Host: localhost:3000<< HTTP/1.1 201 Created < Location: http://localhost:3000/posts/2< Server: WEBrick/1.3.1 (Ruby/1.9.3/2013-06-27){"content": "Come and learn Scala", "created_at": "2013-09-04T01:19:33Z", "id”: 2, "name": "Geek Night", "title":"Scala Dojo", "updated_at": "2013-09-04T01:19:33Z"}
> nandokakimoto@blog$ curl -v -d "post[name]=Geek Night&post[content]=Come and learn Scala" http://localhost:3000/posts.json
* Connected to localhost (127.0.0.1) port 3000 (#0)> POST /posts.json HTTP/1.1> Host: localhost:3000> Content-Length: 56> < HTTP/1.1 422 < Server: WEBrick/1.3.1 (Ruby/1.9.3/2013-06-27)< Content-Length: 28{"title":["can't be blank"]}
Versionamento
Versionamento
Sempre versione a sua APIDiferentes opniões sobre se a versão deve estar na URL ou no
cabeçalho
Versionamento
Versionist - https://github.com/bploetz/versionist
RocketPants - https://github.com/filtersquad/rocket_pants
config/routes.rb
> nandokakimoto@blog$ rake routes
(…)
GET /api/v1/products(.:format) api/v1/posts#indexPOST /api/v1/products(.:format) api/v1/posts#createGET /api/v1/products/new(.:format) api/v1/posts#newGET /api/v1/products/:id/edit(.:format) api/v1/posts#editGET /api/v1/products/:id(.:format) api/v1/posts#showPUT /api/v1/products/:id(.:format) api/v1/posts#updateDELETE /api/v1/products/:id(.:format) api/v1/posts#destroy
app/controllers/api/v1/posts_controller.rb
> nandokakimoto@blog$ curl -v http://localhost:3000/api/v1/posts.json
* Connected to localhost (127.0.0.1) port 3000 (#0)> GET /api/v1/posts HTTP/1.1> Host: localhost:3000<< HTTP/1.1 200 OK < Server: WEBrick/1.3.1 (Ruby/1.9.3/2013-06-27)< Content-Length: 330[{"content": "Melhor evento ever <3 <3 <3", "created_at":"2013-09-03T02:12:26Z", "id": 1, "name": "Frevo on Rails", "title": "Evento 14 Setembro", "updated_at": "2013-09-03T02:12:26Z"}, {"content": "Come and learn Scala", "created_at": "2013-09-04T01:19:33Z", "id": 2, "name": "Geek Night", "title": "Scala Dojo", "updated_at": "2013-09-04T01:19:33Z"}]
> nandokakimoto@blog$ curl -v http://localhost:3000/api/v1/posts/15.json
* Connected to localhost (127.0.0.1) port 3000 (#0)> GET /api/v1/posts/15.json HTTP/1.1> Host: localhost:3000> < HTTP/1.1 404 Not Found < Server: WEBrick/1.3.1 (Ruby/1.9.3/2013-06-27)< Content-Length: 1<
Versionamento
Novo RequisitoMostrar apenas id, nome e título do post
config/routes.rb
> nandokakimoto@blog$ rake routes
(…)
GET /api/v2/posts(.:format) api/v2/posts#index POST /api/v2/posts(.:format) api/v2/posts#create GET /api/v2/posts/new(.:format) api/v2/posts#new GET /api/v2/posts/:id/edit(.:format) api/v2/posts#edit GET /api/v2/posts/:id(.:format) api/v2/posts#show PUT /api/v2/posts/:id(.:format) api/v2/posts#update DELETE /api/v2/posts/:id(.:format) api/v2/posts#destroy
app/controllers/api/v2/posts_controller.rb
RABL
RABL (Ruby API Builder Language) consiste num sistema de template RAILS para geração de JSON
app/views/api/v2/show.rabl.json
app/views/api/v2/index.rabl.json
> nandokakimoto@blog$ curl -v http://localhost:3000/api/v2/posts
* Connected to localhost (127.0.0.1) port 3000 (#0)> GET /api/v2/posts HTTP/1.1> Host: localhost:3000<< HTTP/1.1 200 OK < Server: WEBrick/1.3.1 (Ruby/1.9.3/2013-06-27)< Content-Length: 113[{"id":1,"name":"Frevo on Rails","title":"Evento 14 Setembro"},{"id”:2,"name":"Geek Night","title":"Scala Dojo"}]
Segurança
HTTP Basic Authentication
Solução mais simples
Fácil de implementar
Maioria dos clientes suportam
app/controllers/api/v2/posts_controller.rb
> nandokakimoto@blog$ curl -v http://localhost:3000/api/v2/posts
* Connected to localhost (127.0.0.1) port 3000 (#0)> GET /api/v2/posts HTTP/1.1> Host: localhost:3000<< HTTP/1.1 200 OK < Server: WEBrick/1.3.1 (Ruby/1.9.3/2013-06-27)< Content-Length: 113[{"id":1,"name":"Frevo on Rails","title":"Evento 14 Setembro"},{"id”:2,"name":"Geek Night","title":"Scala Dojo"}]
> nandokakimoto@blog$ curl -v -d "post[name]=RubyConf&post[title]=RubyConf Details&post[content]=RubyConf was awesome" http://localhost:3000/api/v2/posts.json
* Connected to localhost (127.0.0.1) port 3000 (#0)> POST /api/v2/posts.json HTTP/1.1> Host: localhost:3000> Content-Length: 82> < HTTP/1.1 401 Unauthorized < WWW-Authenticate: Basic realm="Application"< Server: WEBrick/1.3.1 (Ruby/1.9.3/2013-06-27)< Content-Length: 27< HTTP Basic: Access denied.
> nandokakimoto@blog$ curl -v -d "post[name]=RubyConf&post[title]=RubyConf Details&post[content]=RubyConf was awesome" http://localhost:3000/api/v2/posts.json -u "admin:secret"
* Connected to localhost (127.0.0.1) port 3000 (#0)* Server auth using Basic with user 'admin'> POST /api/v2/posts.json HTTP/1.1> Authorization: Basic YWRtaW46c2VjcmV0> Host: localhost:3000> Content-Length: 83> < HTTP/1.1 201 Created< Location: http://localhost:3000/api/v2/posts/3 < Server: WEBrick/1.3.1 (Ruby/1.9.3/2013-06-27)< Content-Length: 159{"id":3,"name":"RubyConf","title":"RubyConf Details”}
Token de Acesso
Maior entropia
Token é salvo no servidor
Data de expiração
> nandokakimoto@blog$ rails g model api_key access_tokeninvoke active_record
create db/migrate/20130907211645_create_api_keys.rb create app/models/api_key.rb invoke test_unit create test/unit/api_key_test.rb create test/fixtures/api_keys.yml
app/model/api_key.rb
app/controllers/api/v2/posts_controller.rb
> nandokakimoto@blog$ curl -v http://localhost:3000/api/v2/posts
curl -v http://localhost:3000/api/v2/posts* Connected to localhost (127.0.0.1) port 3000 (#0)> GET /api/v2/posts HTTP/1.1> Host: localhost:3000> < HTTP/1.1 401 Unauthorized < WWW-Authenticate: Token realm="Application"< Server: WEBrick/1.3.1 (Ruby/1.9.3/2013-06-27)< Content-Length: 27< HTTP Token: Access denied.
> nandokakimoto@blog$ curl -v http://localhost:3000/api/v2/posts -H 'Authorization: Token token=”8219a125816b331d0e478eeab566bf7c”'
* Connected to localhost (127.0.0.1) port 3000 (#0)> GET /api/v2/posts HTTP/1.1> Host: localhost:3000> Authorization: Token token="8219a125816b331d0e478eeab566bf7c"> < HTTP/1.1 200 OK < Server: WEBrick/1.3.1 (Ruby/1.9.3/2013-06-27)< Content-Length: 168[{"id":1,"name":"Frevo on Rails","title":"Evento 14 Setembro"},{"id":2,"name":"Geek Night","title":"Scala Dojo"},{"id":3,"name":"RubyConf","title":"RubyConf Details"}]
OAuth
“An open protocol to allow secure authorization in a simple and standard method from web, mobile and desktop application”
- http://oauth.net -
OAuth
Doorkeeper
Oauth2
Tópicos Futuros
Filtros, ordenação, busca
Paginação
Documentação
Limitação de uso
Pra Terminar
API é a intefarce de usuário para os desenvolvedores
Trabalhe para garantir que ela seja funcional e prazerosa de usar