Uma API ruby com AWS Lambda e Serverless Framework
Tags: serverless, aws, ruby
Published: 2020-04-19 22:26:35

O [AWS Lambda](https://aws.amazon.com/lambda/) nos permite que com alguns poucos passos possamos ter uma aplicação no ar, sem ter que configurar ou gerenciar servidores. Além disso, você só paga por tempo de computação, então podemos entregar pequenos serviços com velocidade e baixo custo.

Os principais casos de uso do AWS Lambda são: Processamento de arquivos e streams em tempo real e backend de aplicações web, iot ou mobile.

Quero mostrar como podemos criar uma pequena API em ruby e disponibilizá-la para consumo em poucos minutos e com todo um ambiente robusto e autoescalável para realização de suas tarefas.

### O que é o [Serverless Framework](https://serverless.com/)?
É uma ferramenta Open Source que te ajuda a fazer deploy e configuração de aplicações serverless em várias plataformar diferentes (AWS, GCloud, Azure, etc). Com um simples arquivo de configuração `.yml` podemos descrever tudo que nossa aplicação precisa.

## Vamos ao código!
Iremos desenvolver uma aplicação que você passa como entrada um CEP qualquer e a API retornará o endereço completo.

Aqui utilizarei Ruby 2.7.0 e minha conta da AWS. Dado o nosso uso, nada será cobrado, pois o Lambda tem um [nível de uso gratuito](https://aws.amazon.com/pt/lambda/pricing/).

> O nível de uso gratuito do AWS Lambda inclui 1 milhão de solicitações gratuitas por mês e 400.000 GB/segundos de tempo de computação por mês.

### Instalando o Serverless Framework
Recomendo que você tenha Node 6 ou maior instalado e o `npm`, pois facilita a instalação de alguns plugins que o serverless nos fornece.  
Feito isso, basta executar o comando `npm install -g serverless` e estamos prontos.
    
### Criando o projeto
Crie um repositório `lambda-ruby-api` e então execute `bundle init`.  
Para o nosso projeto, vamos precisar da gem [correios-cep](https://github.com/prodis/correios-cep), então adicione ela ao `Gemfile` e execute `bundle install`

```ruby
# frozen_string_literal: true

source 'https://rubygems.org'

gem 'correios-cep'
```

Agora vamos criar nossa função em um arquivo chamado `handler.rb`
```ruby
# frozen_string_literal: true

require 'correios-cep'

def handler(event:, context:)
  cep = event['pathParameters']['cep']

  address = Correios::CEP::AddressFinder.get(cep)

  { statusCode: 200, body: JSON.generate(address) }
end

```
Nossa função recebe o `event`, que é um JSON que conterá os dados de entrada para o processamento de nossa função. O `context` contem informações sobre o nome da função, versão, arn, limite de memória, etc. Não utilizaremos o `context` nessa aplicação.

No corpo do `event`, iremos receber o `cep` e então chamaremos o `AddressFinder` da gem para buscar o endereço que precisamos.

Por fim, retornamos um statusCode 200 para indicar que tudo deu certo e o endereço que foi processado pela gem, em JSON.

> **Observações!!**
> Nosso endpoint da api será da forma https://.../cep_aqui
> Para acessar isso via lambda, usamos o pathParameters. A nossa key será `cep`.
> Isso será definido nos próximos passos.

### Configurando Serverless Framework
Agora precisamos fazer algumas configurações para fazer o deploy da nossa aplicação.

Para que o Serverless possa fazer uso da AWS precisamos criar algumas credenciais para ele usar. 

1. Acesse sua conta da AWS e então acesse Serviços > IAM > Usuários > Adicionar Usuário.
2. Dê um nome. Eu coloquei *serverless-test*.
3. Habilite acesso programático e vá para a aba anexar políticas existentes de forma direta.
4. Selecione `AdministratorAccess` e finalize a criação do usuário.
5. Acesse a página do usuário que você criou, vá em credenciais de segurança.
6. Clique em criar chave de acesso e guarde essas chaves.

Voltando agora para nosso terminal, basta digitar o seguinte comando para dar permissão de acesso a AWS ao serverless.  
```serverless config credentials --provider aws --key=key_id --secret=key_secret```
Substitua o `key_id` pela sua chave e `key_secret` por sua chave secreta.

> :warning: Atenção!
> Aqui estamos habilitando acesso máximo a AWS, já que estamos apenas testando isso agiliza nosso processo de entendimento. Entretanto, recomendo que depois você configure um acesso mais específico para o serverless. Veja mais [aqui](https://serverless.com/framework/docs/providers/aws/guide/credentials/).

### serverless.yml
Para que o deploy seja feito, devemos criar um arquivo .yml que define os requisitos da nossa aplicação. O nosso arquivo será o seguinte:

```yaml
service: ruby-cep # nome da nossa função lambda

provider:
  name: aws # onde vamos fazer o deploy
  runtime: ruby2.7 # linguagem que vamos utilizar

plugins:
  - serverless-ruby-package # plugin para empacotamento de dependencias

package: # o que queremos que seja enviado para o AWS Lambda
  include: # tudo que precisamos para que função funcione
    - handler.rb
    - vendor/**

functions: # podemos definir várias funções em uma configuração só
  api: # vamos definir apenas nosso endpoint api para ceps
    handler: handler.handler # onde está nossa função
    events: # eventos no qual ela será disparada
      - http: # chamando via http
          path: /{cep} # caminho e o parâmetro cep
          method: get
          cors: true # habilita chamada de todas as origens 
```

> Sobre o **serverless-ruby-package**
> Precisamos que nossas dependencias sejam enviadas para o Lmabda também, a fim de que possam ser utilizadas. Esse plugin nos ajuda com esse processo removendo todos os arquivos e nos forçando a definir quais queremos que sejam enviados (em `package`).
>
> Para instalar o plugin basta executar `npm i serverless-ruby-package --save`

### Fazendo o deploy
Temos que fazer uma pequena alteração para que possamos carregar as dependencias da nossa função.
1. Execute `bundle install --standalone --path vendor/bundle`
2. Modifique `handler.rb`
```ruby
# frozen_string_literal: true

load 'vendor/bundle/bundler/setup.rb' # adicionado

require 'correios-cep'

def handler(event:)
  cep = event['pathParameters']['cep']

  address = Correios::CEP::AddressFinder.get(cep)

  { statusCode: 200, body: JSON.generate(address) }
end

```
Agora é só ir no terminal e executar `serverless deploy`, aguarde alguns minutos e pronto! Você terá um output similar a este.
```
Serverless: Stack update finished...
Service Information
service: ruby-cep
stage: dev
region: us-east-1
stack: ruby-cep-dev
resources: 11
api keys:
  None
endpoints:
  GET - https://some-id.execute-api.us-east-1.amazonaws.com/dev/
functions:
  api: ruby-cep-dev-api
layers:
  None
Serverless: Run the "serverless" command to setup monitoring, troubleshooting and testing.
```

Veja que ele já nos deu o endpoint que configuramos! Vamos lá testar se funciona :smile: 

Copie e cole no seu navegador o caminho com seu CEP para testar.
`Exemplo: https://some-id.execute-api.us-east-1.amazonaws.com/dev/00000-000`

E aí? Deu certo :interrobang:

### Atualizando a função
Agora queremos adicionar mais funcionalidades, como retornar diferentes respostas quando recebermos um CEP inválido.

O CEP pode ser inválido tanto por estar escrito em um formato inválido (Ex: 00-0) ou por não existir. Fazemos algumas modificações:

```ruby
# frozen_string_literal: true

load 'vendor/bundle/bundler/setup.rb'

require 'correios-cep'

def handler(event:, context:)
  cep = event['pathParameters']['cep']

  address = Correios::CEP::AddressFinder.get(cep)

  if address.empty?
    { statusCode: 404 }
  else
    { statusCode: 200, body: JSON.generate(address) }
  end
rescue ArgumentError
  { statusCode: 422 }
end

```

A gem dos correios faz uma validação no formato do cep, se for inválido ele dispara uma exceção do tipo `ArgumentError` e nós a capturamos. :smile: 

> 404 significa `não encontrado`
> 422 significa `entidade não processável`

Para fazer o deploy rápido dessas modificações basta rodar o comando:

`serverless deploy function -f api`
Nesse caso `api` é o nome da função que declaramos dentro da chave `functions` do nosso `serverless.yml`
```yaml
# ...
functions: 
  api: # aqui
    handler: handler.handler 
    events:
      - http: 
          path: /{cep} 
          method: get
          cors: true 
```

Rapidamente a função será atualizada e agora podemos verificar se está tudo ok. Teste um cep inválido e veja as mensagens retornadas.

> Exemplos inválidos:
> 404: `https://some-id.execute-api.us-east-1.amazonaws.com/dev/00000-000`
> 422: `https://some-id.execute-api.us-east-1.amazonaws.com/dev/0`


## Conclusão

Com AWS Lambda e Serverless Framework podemos construir e entregar pequenas e úteis aplicações rapidamente. O que mostrei aqui foi só um pequeno exemplo, mas podemos criar aplicações bem grandes para executar nessa infraestrutura e economizar alguns gastos.



---
Código fonte: https://github.com/mtayllan/lambda-ruby-api