Skip to content

my-study-area/zupedu-plano-estudo-api-idempotente

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

18 Commits
 
 
 
 

Repository files navigation

zupedu-plano-estudo-api-idempotente

Plano de estudo - Desenhando REST APIs Idempotentes e Tolerantes a falhas

Links

Exemplos práticos

Aplicação utilizando primary key com etag e If-None-Match

cd exemplos-praticos/etag-header
curl --location 'localhost:8080/hello' -v
# *   Trying 127.0.0.1...
# * TCP_NODELAY set
# * Connected to localhost (127.0.0.1) port 8080 (#0)
# > GET /hello HTTP/1.1
# > Host: localhost:8080
# > User-Agent: curl/7.58.0
# > Accept: */*
# > 
# < HTTP/1.1 200 
# < ETag: "07d793b78c60fb2b2c265e8c3114bc321"
# < Content-Type: text/plain;charset=UTF-8
# < Content-Length: 17
# < Date: Thu, 10 Aug 2023 00:47:36 GMT
# < 
# * Connection #0 to host localhost left intact
# Hello etag Header

curl --header 'If-None-Match: "07d793b78c60fb2b2c265e8c3114bc321"' --location 'http://localhost:8080/hello' -v
# *   Trying 127.0.0.1...
# * TCP_NODELAY set
# * Connected to localhost (127.0.0.1) port 8080 (#0)
# > GET /hello HTTP/1.1
# > Host: localhost:8080
# > User-Agent: curl/7.58.0
# > Accept: */*
# > If-None-Match: "07d793b78c60fb2b2c265e8c3114bc321"
# > 
# < HTTP/1.1 304 
# < ETag: "07d793b78c60fb2b2c265e8c3114bc321"
# < Date: Thu, 10 Aug 2023 00:47:46 GMT
# < 
# * Connection #0 to host localhost left intact

Obs: o teste também pode ser realizado utilizando um browser com Developer tools habilitado, na aba Network, para visualizar as informações do header da requisição.

Aplicação utilizando primary key, Secondary-Key e Idempotency-Key

# entra no diretório
cd exemplos-praticos/nossa-biblioteca/

# inicia o redis
docker-compose up -d

Requisição com primary-key utilizando If-Match:

curl --location 'localhost:8080/api/payments1' \
--header 'If-Match: b79b29bf-5e3a-4e69-a39d-7cd523409cf9' \
--header 'Content-Type: application/json' \
--data '{
    "paymentAmount": "100.00",
    "transactionId": "b79b29bf-5e3a-4e69-a39d-7cd523409cf9",
    "currency": "BRL"
}' -v
# *   Trying 127.0.0.1...
# * TCP_NODELAY set
# * Connected to localhost (127.0.0.1) port 8080 (#0)
# > POST /api/payments1 HTTP/1.1
# > Host: localhost:8080
# > User-Agent: curl/7.58.0
# > Accept: */*
# > If-Match: b79b29bf-5e3a-4e69-a39d-7cd523409cf9
# > Content-Type: application/json
# > Content-Length: 117
# > 
# * upload completely sent off: 117 out of 117 bytes
# < HTTP/1.1 201 
# < Location: http://localhost:8080/api/payments1/1
# < Content-Length: 0
# < Date: Tue, 22 Aug 2023 01:03:11 GMT
# < 
# * Connection #0 to host localhost left intact

curl --location 'localhost:8080/api/payments1' \
--header 'If-Match: b79b29bf-5e3a-4e69-a39d-7cd523409cf9' \
--header 'Content-Type: application/json' \
--data '{
    "paymentAmount": "100.00",
    "transactionId": "b79b29bf-5e3a-4e69-a39d-7cd523409cf9",
    "currency": "BRL"
}' -v
# *   Trying 127.0.0.1...
# * TCP_NODELAY set
# * Connected to localhost (127.0.0.1) port 8080 (#0)
# > POST /api/payments1 HTTP/1.1
# > Host: localhost:8080
# > User-Agent: curl/7.58.0
# > Accept: */*
# > If-Match: b79b29bf-5e3a-4e69-a39d-7cd523409cf9
# > Content-Type: application/json
# > Content-Length: 117
# > 
# * upload completely sent off: 117 out of 117 bytes
# < HTTP/1.1 412 
# < Content-Type: application/json
# < Transfer-Encoding: chunked
# < Date: Tue, 22 Aug 2023 01:04:20 GMT
# < 
# * Connection #0 to host localhost left intact
# {"timestamp":"2023-08-22T01:04:20.828+00:00","status":412,"error":"Precondition Failed","path":"/api/payments1"}

Realizando uma requisição utilizando um método com Secondary-Key

curl --location 'localhost:8080/api/payments2' \
--header 'Content-Type: application/json' \
--data '{
    "paymentAmount": "100.00",
    "transactionId": "63673167-0b3c-41c6-9642-6cae67ce5303",
    "currency": "BRL"
}' -v
# *   Trying 127.0.0.1...
# * TCP_NODELAY set
# * Connected to localhost (127.0.0.1) port 8080 (#0)
# > POST /api/payments2 HTTP/1.1
# > Host: localhost:8080
# > User-Agent: curl/7.58.0
# > Accept: */*
# > Content-Type: application/json
# > Content-Length: 117
# > 
# * upload completely sent off: 117 out of 117 bytes
# < HTTP/1.1 201 
# < Location: http://localhost:8080/api/payments2/4
# < Content-Length: 0
# < Date: Tue, 22 Aug 2023 01:09:32 GMT
# < 
# * Connection #0 to host localhost left intact

curl --location 'localhost:8080/api/payments2' \
--header 'Content-Type: application/json' \
--data '{
    "paymentAmount": "100.00",
    "transactionId": "63673167-0b3c-41c6-9642-6cae67ce5303",
    "currency": "BRL"
}' -v
# *   Trying 127.0.0.1...
# * TCP_NODELAY set
# * Connected to localhost (127.0.0.1) port 8080 (#0)
# > POST /api/payments2 HTTP/1.1
# > Host: localhost:8080
# > User-Agent: curl/7.58.0
# > Accept: */*
# > Content-Type: application/json
# > Content-Length: 117
# > 
# * upload completely sent off: 117 out of 117 bytes
# < HTTP/1.1 409 
# < Content-Type: application/json
# < Transfer-Encoding: chunked
# < Date: Tue, 22 Aug 2023 01:06:45 GMT
# < 
# * Connection #0 to host localhost left intact
# {"timestamp":"2023-08-22T01:06:45.889+00:00","status":409,"error":"Conflict","path":"/api/payments2"}

Realizando uma requisição utilizando Idempotency-Key

curl --location 'localhost:8080/api/payments3' \
--header 'Idempotency-Key: 6d683b56-a6bf-4fcd-89d7-9d5e1c7848d6' \
--header 'Content-Type: application/json' \
--data '{
    "paymentAmount": "100.00",
    "transactionId": "6d683b56-a6bf-4fcd-89d7-9d5e1c7848d6",
    "currency": "BRL"
}' -v
# *   Trying 127.0.0.1...
# * TCP_NODELAY set
# * Connected to localhost (127.0.0.1) port 8080 (#0)
# > POST /api/payments3 HTTP/1.1
# > Host: localhost:8080
# > User-Agent: curl/7.58.0
# > Accept: */*
# > Idempotency-Key: 6d683b56-a6bf-4fcd-89d7-9d5e1c7848d6
# > Content-Type: application/json
# > Content-Length: 117
# > 
# * upload completely sent off: 117 out of 117 bytes
# < HTTP/1.1 201 
# < Location: http://localhost:8080/api/payments3/5
# < Content-Length: 0
# < Date: Tue, 22 Aug 2023 01:14:05 GMT
# < 
# * Connection #0 to host localhost left intact

Resumo sobre idempotência

Idempotência é uma propriedade comum da matemática que encontramos na potenciação com expoente “0” (zero) e em algumas outras operações que, independente da quantidade de vezes que são executadas, sempre retornam o mesmo valor. Por exemplo, 2 ^ 0 (Lê-se dois elevado a zero) sempre terá como resultado 1. Outro exemplo é a multiplicação de um número por zero, veja alguns exemplos abaixo: 2 x 0 = 0 92 x 0 = 0 2546 x 0 = 0

Como exemplificado acima, podemos comprovar que a multiplicação por zero é operação idempotente. Não importa quantas vezes sejam executadas as operações acima, sempre terão o mesmo resultado, o resultado final zero.

O mesmo se aplica na ciência da computação através das requisições HTTP, onde, por padrão, temos alguns métodos idempotentes e outros não, mas podemos modificar esse comportamento através de algumas técnicas que serão abordadas adiante. Para entendermos um pouco melhor, é interessante conhecermos duas propriedades dos métodos HTTP: safe e idempotency.

Um método safe é aquele que não altera o estado do servidor, como por exemplo, os métodos de leitura como GET, HEAD, OPTIONS, and TRACE. Já um método idempotente é aquele que pode ser executado mais de uma vez e mesmo assim retorna o mesmo resultado como se fosse uma única requisição, como no caso do PUT, DELETE e os métodos safe. Vale lembrar que todos os métodos HTTP que possuem a propriedade safe são, também, idempotentes.

Agora vamos falar dos métodos PUT e DELETE que acabam gerando algumas dúvidas em relação a idempotência. Isso ocorre, em algumas situações, quando imaginamos um cenário de deleção de um recurso que na primeira requisição, onde o recurso deixa de existir e ao realizar uma segunda requisição não encontra o recurso, e dependendo da forma que foi desenvolvido, pode retornar um status code 404 (NOT FOUND). Por mais estranho que isto pareça, está correto, o método não deixa de ser idempotente quando a primeira e segunda requisições tem status code diferente. O que torna um método idempotente é quando uma ou mais requisições causam o mesmo efeito no servidor, no caso, é não existir o recurso independente do status code retornado. Algo similar ocorre no método PUT onde diversas requisições com os mesmos dados de entrada sempre causam o mesmo efeito.

Então, se eu precisar utilizar idempotência nos métodos PATCH e POST, é possível? A resposta é SIM, através da utilização de conditional key, secondary key e idempotency key. Com essas estratégias é possível realizar uma ou diversas requisições, com os mesmo dados de entrada, sem causar um efeito colateral no servidor.

Através da conditional key temos duas formas de aplicar a estratégia: etag + if-none-match e if-match. Na primeira estratégia o servidor deve enviar o campo etag na resposta de uma requisição e o cliente deve armazená-la para enviar em sua request passando o valor da etag no header da requisição, no campo if-none-match, e em caso de sucesso pode responder com um status code 200, caso contrário responde com um 412 Precondition Failed. Na segunda estratégia, por exemplo, em uma requisição POST, enviamos uma requisição com o com o campo if-match no header e utilizamos essa informação para retornar um 412 Precondition Failed caso o campo seja um identificador do recurso.

A estratégia de uso de uma secondary key é bem comum, utilizamos alguma informação da request com o identificador do recurso e caso de encontrarmos um recurso numa requisição POST podemos responder com um status code 409 Conflito.

No caso da estratégia utilizando um idempotency key utilizamos o campo Idempontecy-Key, no header da requisição, para identificarmos se o recurso já existe ao realizarmos uma requisição POST. Assim podemos utilizar um banco de dados em memória para armazenar as respostas e retornar ao usuário a resposta da primeira requisição, podendo ser uma resposta de sucesso ou até mesmo uma resposta de erro.

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages