Idempotency ve Idempotent Api Tasarlama

IDEMPOTENCY ve IDEMPOTENT API TASARLAMA

Bir api’a request attıgınızı düşünün, bu api requesti ile yüklü miktarda ödeme çıkacaksınız. Requesti attınız ve time-out ya da internal server error hatası aldınız. Bu durumda ne yaparsınız? pek tabii başarılı resultı alana kadar requesti tekrar tekrar gönderirsiniz. Ama ya request atılan api, ödeme işlemini gerçekleştirdikten sonraki aşamalarda hata aldığı için error dönüyor ise ve biz de erroru gördüğümüzde tekrar deneme yaparak tekrar bir ödeme çıkıyorsak?

Api’a bir istek atılması sonucunda time out ya da bir hata alındığında, client bu noktada serverin requesti alıp almadığı ya da aldıysa ne aşamaya kadar işlediği noktasında emin olamaz. Acaba server requesti alıp işledi ve response dönerken mi crash oldu yoksa requesti hiç mi alamadı. Bu gibi durumlarda client’ın yapacağı en doğal hareket başarılı bir response alana kadar requesti tekrar tekrar yollamaktır. Peki Api’nin bu gibi retry senaryoları sonucunda oluşacak side effectlere karşı nasıl tasarlanması gerekir?

Bu gibi retrylara ve resent senaryolarına karşı api tarafında side effectleri engellemenin yolu idempotent apiler tasarlamaktır. Idempotent demek bir işlemin ya da operasyonun sıfır side effect ile retry edilebilmesi demektir. Yani, bir ödeme apisi tasarladığınızda client tekrar tekrar aynı ödemeyi request olarak göndermeye de çalışsa, servisiniz kendi içerisinde retry’a da girse işlem tamamlandığında hiç bir side effect olmayacak ve bu işlem bir kez yapılmış olarak doğru sonuca ulaşacak. Özetlemek gerekirse, idempotent bir API, hatalara karşı robust client uygulanmasını çok daha kolay hale getirir; çünkü API, tüm olası edge case’lar hakkında endişelenmeden isteklerin başarısızlık durumunda kullanımdan kaldırılabileceğini varsayabilir.

HTTP metotlarından olan GET, PUT ve DELETE doğası gereği idempotenttir. HTTP put metodu üzerinden bir datayı update etmek için request attığınızda aynı requesti defalarca kez de atsanız ilgili data ilk requestte update olmus olacak ve diğer requestlerde de aynı sonucu verecek şekilde ilerleyecek ve bir side effect olmayacaktır. Ancak POST metodu bu şekilde değildir. Örneğin, bir product yaratmak için post metodunu call ettiğinizde ve bir timeout sebebiyle tekrar post isteği attıgınızda, 2 aynı productın yaratılmasını istemezsiniz. Bu gibi caselerle nasıl handle edebiliriz. Yani post endpointini nasıl idempotent hale getirebiliriz ki kaç tane identical request gelirse gelisin tek bir kez execute edileceğini ya da tek bir kez execute edilmiş gibi görüneceğini garanti edebilelim.

Peki bir api nasıl idempontent bir şekilde tasarlanabilir? Bunun için her requestin bir idempotency keyi olması idealdir. Bu key ile birlikte gelen requestin bir öncekiyle aynı request mi yoksa yepyeni bir request mi olduğu ayrıştırılabilir duruma gelir. Bu headerin bir parçası olabilir. Server da bu idempotency keyleri store etmelidir. Yeni bir request geldiğinde eğer bu requestin idempotency key’i önceden store edilmiş ise, bu request sisteme alınmamalıdır.

Ancak bu noktada da bazı sıkıntılar çıkabilir. Diyelim ki api’ye request geldi, api idempontency key store edildi ancak key store edildikten sonra işlemler yapılamadan servis crash oldu. Bu noktada server tarafında idempoteny key store edilmiş olacağı için, client tarafının retrylerinin bir etkisi olmayacak ancak ürün yaratma işlemi de tamamlanmamış olacaktır. Buna çözüm olarak idempontecy keyi store etme ve servisin ana işi aynı transaction içerisinde atomik olarak değerlendirmek gerekebilir. Yani işlemler doğru bir şekilde tamamlanınca idempotency key persist olur ya da fail olur ve idempotency key store edilmez. Idempotency key ve ana işlemler aynı databasede store edilirse bu kolaylaşır ve atomikliği garanti edebiliriz.  Ancak işler her zaman bu kadar kolay olmayabilir. Proje içerisinde çok fazla external call varsa ya da farklı databaseler kullanılıyorsa işler daha zorlu hale gelir. Çok daha fazla koordinasyon gerekir.

Peki idempontecy key seçimi ve idempotency key’i yaratma işlemi kimde olmalıdır? Bunu client taraf mı ele almalıdır yoksa server mı konusuna biraz daha bakalım. Idempotency key’i yaratma işlemi genel olarak client taraftında ele alınmalıdır çünkü server tarafı arka arkaya gelen iki requestin identical mı yoksa yoksa iki farklı request mi olduğunu anlayacak yeterli bilgiye sahip olmayabilir. Örnek olarak yine ödeme çıktığınız bir endpoint düşünelim. Ödemeyi çıkarken yollanan parametreler; iban, alıcı ismi, açıklama, ödenecek tutar ve benzeri bir kaç alan olsun. Bu noktada, server tarafı yani ilgili api ikinci kez gelen isteğin bir retry mı yoksa gerçekten aynı hesaba aynı miktarda ödenecek ikinci request mi olduğunu bilemez.

Bir network network kopukluğundan dolayı client tarafında bir fail alındı ise, client bunu gördüğünde aynı requesti tekrar yollayacaktır. Server ise bu requestle ilk kez karşılaştığı için normal bir şekilde alıp işleyecektir. Serverin requesti aldıktan sonra içeride yaptığı operasyonlar sırasında bir fail alması durumunda, serverın vereceği tepki tamamen iç implementasyonuna bağlıdır.

Server requesti doğru bir şekilde aldı işledi ancak response dönme aşamasıda bir fail aldı ise ve client beklediği resultı alamadıysa, clientin ikinci denemesinde server cachlenmiş success response’u dönebilir.

Şimdi Idempotecy key’i bir kaç case ile daha detaylı bir şekilde ele alalım.

CASE 1

Şuana kadar bahsettiğimiz case’ler hep single client üzerinden ilerleyen caselerdi. Şimdi bir de birden fazla client olduğu bir case düşünelim. Client A ve Client B.

Client A bir product yaratmak için request atar. Server bu requesti alır. Idempotency key’i kaydeder. Product’ı olması gerektiği gibi create eder ancak response dönmesi gereken noktada bir şekilde crash olur ve response döneme

Client B’ de akabinde gelip bu product için bir delete işlemi yapar ve productı siler.

Client A response alamadığı için requesti tekrar gönderirir ve product’ı tekrar yaratır.

CASE 2 

Server clienttan bir idempotency key talep etmek yerine, gelen alanlardan birini idempotency key olarak kullanabilir mi?

Diyelim ki banka hesabı açan bir api tasarlanacak ve bu api’yi idempotent olarak tasarlayacaksınız. İlk düşünceniz, api’ye gelen requestte bulunan TCKN’yı idempotency key olarak kullanmak. Request geldiğinde ilgili TCKN’yi kaydetmek ve aynı TCKN ile request geldiğinde bu kişi adına hesap açıldığı için tekrar açmamak. Ancak, biliyoruz ki aynı kişinin aynı bankada birden fazla hesabı bulunabilir. Bu noktada TCKN’yi idempotency key olarak kullanmak mantıklı olmayacaktır.

Diğer bir örnek de bir e-ticaret sitesi için hesap açan bir servisiniz olduğunu varsayın. Burada da idempotency’i sağlaması için kullanıcı e-mailini kullanmak istiyorsunuz. Aynı e-mail ile bir request geldiğinde gerekli işlemleri yapıyorsunuz ancak ikinci bir request geldiğinde bunu kabul etmiyorsunuz.

Eventually ve strongly consistency sistemler’de idempotency yaklaşımları farklı olabilir. Işlemleri ACID bazında mı değerlendirmeliyiz bunun cevabı sistemlerimizin strongly mi yoksa eventually consistenyi mi destekleyeceğidir çünkü eventually durumda consumerların kendi içinde retryları devreye girer. Fonksiyon ve consumer bazında idempontecy gerekir. Ancan strongly consistency sistemlerde bir hata alındığı zaman işlemlerin ACID bazında değerlendirilip rollback olması gerekliliği doğar.

Idempotency hakkında faydalı linkler

1. https://serverlessland.com/event-driven-architecture/visuals/understanding-idempotency

2. https://www.lpalmieri.com/posts/idempotency/

3. https://wiki.c2.com/?DoesBeingStatelessImplyBeingIdempotent

4. https://martinfowler.com/articles/patterns-of-distributed-systems/idempotent-receiver.html

5. https://www.youtube.com/watch?v=m6DtqSb1BDM

6. https://www.youtube.com/watch?v=J2IcD9FZvZU

7. https://www.youtube.com/watch?v=Hxja4crycBg

8. https://blog.hubspot.com/website/idempotent-api#:~:text=DELETE%20is%20an%20idempotent%20method,is%20no%20change%20of%20state.

You may also like...

Bir yanıt yazın

E-posta adresiniz yayınlanmayacak. Gerekli alanlar * ile işaretlenmişlerdir