Post on 18-Jan-2017
APIS DO JEITO CERTORAVAN 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
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{ /** * @SWG\Post(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"}, * @SWG\Parameter( * in="body", * name="body", * description="Created user object", * required=false, * @SWG\Schema(ref="#/definitions/User") * ), * @SWG\Response(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 Dotenv\Dotenv($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
<?phpuse Acme\Model\Book;use League\Fractal;
$books = Book::all();
$resource = new Fractal\Resource\Collection($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 swagge
r)• 2-legged OAuth
LIVROS E RECURSOS
LEMBREM-SE: EMPATIA É A CHAVE
OBRIGADO!
@ravanscafi
ESTAMOS CONTRATANDO :)
rscafi@leroymerlin.com.br