APIS DO JEITO CERTO
RAVAN SCAFI
• Ravan Scafi
• Web Developer @ Leroy
Merlin
• Co-organizador do Meetup
do Laravel-SP
• twitter.com/ravanscafi
SOBRE MIM
SOBRE A APRESENTAÇÃO
• Motivação
• Boas práticas na construção de APIs
• Lições Aprendidas
• Laravel / Lumen ao resgate!
APIS REST EM 1 MINUTO
• Recursos como substantivos
• Verbos HTTP especificam a ação
$ curl -XGET localhost/api/users/123
MOTIVAÇÃO
• Por que escrever uma API?
• O que implica ter uma API?
# DOCUMENTAÇÃO
“Uma API é apenas tão boa quanto
sua documentação”
apiary.io
SWAGGER / OPEN API
• Uma forma de documentar sua API
• Único arquivo swagger.json
• Consumível por Humanos e por Máquinas
• Swagger-UI: ❤️ ❤️ ❤️ ❤️
DOCUMENTAÇÃO: DICAS
• Mantenha a documentação próxima ao código
• Regras de bom código também valem para
documentação
• https://github.com/zircote/swagger-php
<?php
class UserController
{
/**
* @SWGPost(path="/user",
* tags={"user"},
* summary="Create user",
* description="This can only be done by the logged in user.",
* operationId="createUser",
* produces={"application/xml", "application/json"},
* @SWGParameter(
* in="body",
* name="body",
* description="Created user object",
* required=false,
* @SWGSchema(ref="#/definitions/User")
* ),
* @SWGResponse(response="default", description="successful operation")
* )
*/
public function createUser()
{
}
}
Swagger-PHP: Exemplo
<?php
$basePath = __DIR__ . '/..';
require_once $basePath . '/vendor/autoload.php';
// grabbing info from .env file
$beforeEnv = $_ENV;
(new DotenvDotenv($basePath))->load();
$envVars = array_diff($_ENV, $beforeEnv);
// grabbing info from composer.json file
$composerFile = json_decode(file_get_contents($basePath . '/composer.json'));
$composerInfo = [
'COMPOSER_NAME' => $composerFile->name ?? null,
'COMPOSER_DESCRIPTION' => $composerFile->description ?? null,
'COMPOSER_VERSION' => $composerFile->version ?? '0.0.0',
'COMPOSER_LICENSE' => $composerFile->license ?? null,
];
// defining grabbed info as constants
$desiredConstants = array_filter(array_merge($envVars, $composerInfo));
array_map('define', array_keys($desiredConstants), $desiredConstants);
bootstrap/swagger.php
{
...
"autoload": {
"psr-4": {
"App": "app/"
}
},
"scripts": {
"api-docs": "swagger app -o public/docs.json -b bootstrap/swagger.php",
}
}
composer.json
$ composer api-docs
# DESIGN
VERSIONAMENTO
• Internamente: Semantic Versioning
• Publicamente: só mude versões cheias (v1, v2)
• Evite Breaking Changes (BC) o máximo que
conseguir
O QUE CONFIGURA UMA BC?
• Remover campos: SIM
• Renomear campos: SIM
• Adicionar campos: NÃO
• Adicionar recursos / endpoints: NÃO
PROTEJA-SE COM MUTATORS
• Apresentação e transformação dos dados
• Uma “barreira” entre os recursos e a API
• Typecasting, relacionamentos
<?php
use AcmeModelBook;
use LeagueFractal;
$books = Book::all();
$resource = new FractalResourceCollection($books, function(Book $book) {
return [
'id' => (int) $book->id,
'title' => $book->title,
'year' => $book->yr,
'author' => [
'name' => $book->author_name,
'email' => $book->author_email,
],
'links' => [
[
'rel' => 'self',
'uri' => '/books/'.$book->id,
]
]
];
});
Transformer: Exemplo
NEGOCIAÇÃO DE CONTEÚDO
• JSON é JavaScript, portanto use camelCase
• /api/user/123.json, /api/user/123.csv
• Recursos embedados: trazer dinamicamente ou não
• Metadados: URI, página atual, count total
• Verbo HTTP: override com _method
RESPOSTAS
• Padronize campos como: datas, floats, moedas
• Padronize códigos HTTP de resposta
• Padronize paginação, metadados, etc.
RESPOSTAS
• Faça um “wrap” da resposta em uma chave “data”
• Mostre o erro, ao invés de um código de erro
• Nunca exponha exceptions para os usuários
• Não reinvente a roda, seja coerente com padrões
AUTENTICAÇÃO
• Baseados em Sessão: NÃO
• APIs devem ser sem estados (stateless)
• Implementação em mobile é custosa
• Baseados em Token: SIM
• OAuth? OAuth2? JWT? Http-Basic?
OAUTH VS JWT
• Não são “concorrentes” diretos
• É até mesmo possível utilizar os dois em conjunto
• OAuth: concebido para autorização (pseudo
autenticação)
• 3-legged vs 2-legged
• JWT: concebido para claims / autenticação
Como se parece um token JWT
SEGURANÇA
• Evite expor IDs sequenciais
• Limite as requisições (Throttling), porém configurável
por conta
• Não utilize sessões ou logins por senhas (exceto em
aplicativos próprios)
• API somente em HTTPS
• Verifique sempre vulnerabilidades com OAuth / JWT
# LARAVEL VS LUMEN
LARAVEL VS LUMEN
• Já tenho um app em Laravel: Laravel!
• Já tenho um app em Lumen: Lumen!
• Vou começar: Lumen!
• Use e abuse de Middlewares
• Agrupe rotas por versão
<?php
$app->group(['prefix' => 'api'], function () use ($app) {
$app->group(['prefix' => 'v1'], function () use ($app) {
$app->get('users', function () {});
$app->get('products', function () {});
});
$app->group(['prefix' => 'v2'], function () use ($app) {
$app->get('users', function () {});
$app->get('products', function () {});
});
});
Grupos de Rotas por Versão
PACOTES ÚTEIS
• Dingo/API
• Zircote/Swagger-PHP
• TymonDesigns/JWT-Auth
• League - OAuth2 Server
• League - Fractal
• Ramsey - UUID
DINGO/API
• Negociação de Conteúdo
• Múltiplos Adaptadores de Autenticação
• Versionamento da API
• Limitação de Requisições
DINGO/API
• Transformers and Formatters de Respostas
• Handling de Exceptions e Erros
• Requests Internos
• Documentação Blueprint
• Build APIs You Won't Hate
• Heroku's HTTP API
Design
• Open API Initiative
• Petstore (demo swagger)
• 2-legged OAuth
LIVROS E RECURSOS
LEMBREM-SE: EMPATIA É A CHAVE
OBRIGADO!
@ravanscafi
ESTAMOS CONTRATANDO :)
rscafi@leroymerlin.com.br

APIs do Jeito Certo

  • 1.
    APIS DO JEITOCERTO RAVAN SCAFI
  • 2.
    • Ravan Scafi •Web Developer @ Leroy Merlin • Co-organizador do Meetup do Laravel-SP • twitter.com/ravanscafi SOBRE MIM
  • 3.
    SOBRE A APRESENTAÇÃO •Motivação • Boas práticas na construção de APIs • Lições Aprendidas • Laravel / Lumen ao resgate!
  • 4.
    APIS REST EM1 MINUTO • Recursos como substantivos • Verbos HTTP especificam a ação $ curl -XGET localhost/api/users/123
  • 5.
    MOTIVAÇÃO • Por queescrever uma API? • O que implica ter uma API?
  • 6.
  • 7.
    “Uma API éapenas tão boa quanto sua documentação” apiary.io
  • 9.
    SWAGGER / OPENAPI • Uma forma de documentar sua API • Único arquivo swagger.json • Consumível por Humanos e por Máquinas • Swagger-UI: ❤️ ❤️ ❤️ ❤️
  • 11.
    DOCUMENTAÇÃO: DICAS • Mantenhaa documentação próxima ao código • Regras de bom código também valem para documentação • https://github.com/zircote/swagger-php
  • 12.
    <?php class UserController { /** * @SWGPost(path="/user", *tags={"user"}, * summary="Create user", * description="This can only be done by the logged in user.", * operationId="createUser", * produces={"application/xml", "application/json"}, * @SWGParameter( * in="body", * name="body", * description="Created user object", * required=false, * @SWGSchema(ref="#/definitions/User") * ), * @SWGResponse(response="default", description="successful operation") * ) */ public function createUser() { } } Swagger-PHP: Exemplo
  • 13.
    <?php $basePath = __DIR__. '/..'; require_once $basePath . '/vendor/autoload.php'; // grabbing info from .env file $beforeEnv = $_ENV; (new DotenvDotenv($basePath))->load(); $envVars = array_diff($_ENV, $beforeEnv); // grabbing info from composer.json file $composerFile = json_decode(file_get_contents($basePath . '/composer.json')); $composerInfo = [ 'COMPOSER_NAME' => $composerFile->name ?? null, 'COMPOSER_DESCRIPTION' => $composerFile->description ?? null, 'COMPOSER_VERSION' => $composerFile->version ?? '0.0.0', 'COMPOSER_LICENSE' => $composerFile->license ?? null, ]; // defining grabbed info as constants $desiredConstants = array_filter(array_merge($envVars, $composerInfo)); array_map('define', array_keys($desiredConstants), $desiredConstants); bootstrap/swagger.php
  • 14.
    { ... "autoload": { "psr-4": { "App":"app/" } }, "scripts": { "api-docs": "swagger app -o public/docs.json -b bootstrap/swagger.php", } } composer.json $ composer api-docs
  • 15.
  • 16.
    VERSIONAMENTO • Internamente: SemanticVersioning • Publicamente: só mude versões cheias (v1, v2) • Evite Breaking Changes (BC) o máximo que conseguir
  • 17.
    O QUE CONFIGURAUMA BC? • Remover campos: SIM • Renomear campos: SIM • Adicionar campos: NÃO • Adicionar recursos / endpoints: NÃO
  • 18.
    PROTEJA-SE COM MUTATORS •Apresentação e transformação dos dados • Uma “barreira” entre os recursos e a API • Typecasting, relacionamentos
  • 19.
    <?php use AcmeModelBook; use LeagueFractal; $books= Book::all(); $resource = new FractalResourceCollection($books, function(Book $book) { return [ 'id' => (int) $book->id, 'title' => $book->title, 'year' => $book->yr, 'author' => [ 'name' => $book->author_name, 'email' => $book->author_email, ], 'links' => [ [ 'rel' => 'self', 'uri' => '/books/'.$book->id, ] ] ]; }); Transformer: Exemplo
  • 20.
    NEGOCIAÇÃO DE CONTEÚDO •JSON é JavaScript, portanto use camelCase • /api/user/123.json, /api/user/123.csv • Recursos embedados: trazer dinamicamente ou não • Metadados: URI, página atual, count total • Verbo HTTP: override com _method
  • 21.
    RESPOSTAS • Padronize camposcomo: datas, floats, moedas • Padronize códigos HTTP de resposta • Padronize paginação, metadados, etc.
  • 22.
    RESPOSTAS • Faça um“wrap” da resposta em uma chave “data” • Mostre o erro, ao invés de um código de erro • Nunca exponha exceptions para os usuários • Não reinvente a roda, seja coerente com padrões
  • 23.
    AUTENTICAÇÃO • Baseados emSessão: NÃO • APIs devem ser sem estados (stateless) • Implementação em mobile é custosa • Baseados em Token: SIM • OAuth? OAuth2? JWT? Http-Basic?
  • 24.
    OAUTH VS JWT •Não são “concorrentes” diretos • É até mesmo possível utilizar os dois em conjunto • OAuth: concebido para autorização (pseudo autenticação) • 3-legged vs 2-legged • JWT: concebido para claims / autenticação
  • 25.
    Como se pareceum token JWT
  • 26.
    SEGURANÇA • Evite exporIDs sequenciais • Limite as requisições (Throttling), porém configurável por conta • Não utilize sessões ou logins por senhas (exceto em aplicativos próprios) • API somente em HTTPS • Verifique sempre vulnerabilidades com OAuth / JWT
  • 27.
  • 28.
    LARAVEL VS LUMEN •Já tenho um app em Laravel: Laravel! • Já tenho um app em Lumen: Lumen! • Vou começar: Lumen! • Use e abuse de Middlewares • Agrupe rotas por versão
  • 29.
    <?php $app->group(['prefix' => 'api'],function () use ($app) { $app->group(['prefix' => 'v1'], function () use ($app) { $app->get('users', function () {}); $app->get('products', function () {}); }); $app->group(['prefix' => 'v2'], function () use ($app) { $app->get('users', function () {}); $app->get('products', function () {}); }); }); Grupos de Rotas por Versão
  • 30.
    PACOTES ÚTEIS • Dingo/API •Zircote/Swagger-PHP • TymonDesigns/JWT-Auth • League - OAuth2 Server • League - Fractal • Ramsey - UUID
  • 31.
    DINGO/API • Negociação deConteúdo • Múltiplos Adaptadores de Autenticação • Versionamento da API • Limitação de Requisições
  • 32.
    DINGO/API • Transformers andFormatters de Respostas • Handling de Exceptions e Erros • Requests Internos • Documentação Blueprint
  • 33.
    • Build APIsYou Won't Hate • Heroku's HTTP API Design • Open API Initiative • Petstore (demo swagger) • 2-legged OAuth LIVROS E RECURSOS
  • 34.
  • 35.
  • 36.

Notas do Editor

  • #2 Questionário Já usou? Já desenvolveu? Quer desenvolver / está desenvolvendo? Explicação do tamanho da palestra Abelhas: APIcultura
  • #4 Tudo mesclado, não em ordem
  • #6 Principal skill ao desenvolver uma API: Empatia
  • #8 APIARY: api design tools
  • #9 UPS: United Parcel System maiores empresas de logística
  • #10 RAML, Blueprint Arquivo contém rotas, como fazer requisições e como são as respostas Demonstração >>> H <<< minimiza
  • #12 1- ou não atualizará e logo servirá pra nada 2 - Clareza, evitar duplicação 3 - doctrine annotations!
  • #13 virará o arquivo swagger.json ao rodar o comando
  • #14 pensando em não duplicar info, fiz o script
  • #15 facilidade de geração
  • #18 Sempre use o bom senso Fallbacks (volta o campo velho e o novo) Marcar deprecated nos metadados
  • #19 ao invés de loopar no model castando pra bool / int
  • #20 virará o arquivo swagger.json ao rodar o comando
  • #21 _method para os navegadores, que só usam GET e POST Preciso suportar XML? CSV? etc? Não! somente deixe documentado
  • #22 se quiser, ponha um link para uma descrição do erro em alguma documentação API BUC boolean ativo
  • #23 se quiser, ponha um link para uma descrição do erro em alguma documentação API BUC boolean ativo
  • #24 API SPTrans (404, 405 (post), login) Problemas em auth com cookie: Android / iOS se alguém do sptrans ver essa talk, melhora isso! (só lembra de fazer na v2, pra não quebrar quem já tá usando)
  • #25 Problemas em auth com cookie: Android / iOS se alguém do sptrans ver essa talk, melhora isso! (só lembra de fazer na v2, pra não quebrar quem já tá usando)
  • #27 UUID
  • #29 Facilidade migração lumen > laravel
  • #30 virará o arquivo swagger.json ao rodar o comando
  • #34 1- Phil Sturgeon - escrevendo v2 (twitter) 2 - Começou como um trabalho do Heroku 3 - Open API / Swagger