2 Kasım 2020 Pazartesi

Yazılım Mimarisi - Idempotency (Denkgüçlülük) Nedir

Giriş
Idempotency kelimesinin Türkçe karşılığı da biraz tuhaf aslında :)

Idempotency şu anlama gelir. Bir metodun başarıyla çalıştıktan sonra tekrar çağrılması durumunda, yeni bir değişiklik yapmamasıdır. Sistemin Idempotency özelliği mikroservis (mikroservice) mimarisi ile daha çok konuşulur oldu.

En çok kullanılan örneklerden birisi şöyle
In more complex micro service processes, the code executing when a client clicks the "Purchase button", typically invokes several micro services. Imagine something as follows.

1. User clicks the purchase button
2. Money is deducted from the client's account
3. Money is transferred to the manufacturer's account
4. The order is handled internally, and shipping is started

If step 3 or step 4 fails in the above process, you cannot simply rerun the process, because this would result in deducting the client's bank account twice. If all of the above invocations are idempotent though, the process can easily be re-animated by simply running it in its entirety once more.
Idempotency Yöntemleri
Idempotency için iki tane yöntem var.

1. Her şeyin en baştan yapılması
Bu yöntemin kullanılabilmesi için her şeyin en baştan yapılmasının zararsız olması gerekir. Örneğin bir web sayfası, sayfadaki bir bileşen değiştikçe tekrar tekrar render edilebilir. Sayfa en baştan render edildiği için zararsızdır.

2. Sadece başarısız adımların baştan yapılması - Tekil Numara İle Takip Edilir
Örneğin banka hesabından para çekilip, e-posta gönderilmesi işlemi olsun. Hesaptan para çekme işlemi başarılı, ancak e-posta gönderme işlemi başarısız ise, metod tekrar çağrıldığında sadece e-posta gönderme işlemi yapılmalıdır. Bu yöntemi gerçekleştirmek için kullanılacak yöntemlerden birisi de işlem başına bir tekil numara kullanılması. Açıklaması şöyle
Idempotency is actually very easy to implement. Just create a new Guid or Uuid, and associate with every invocation to your micro service invocations, and if the micro service endpoint encounters an identifier it has previously executed, it returns early, and doesn't apply any state changes to its internal states. This of course requires persisting the identifier for the execution somehow, but that's fairly simple in practice. If the client calling the endpoint requires access to the returned value, you can also persist the return value of some micro service process, and return the previous result, looking it up using the invocation identification as a criteria.
Örnek
Açıklaması şöylee
... we can maintain an INBOX table inside the consumer service’s database. It simply keeps track of what events were processed by recording their UUIDs.
After processing an event for the first time, the consumer marks the event as processed in the INBOX table. That should be transactional — making it possible to trap any rollbacks at the consumer level so that it can retry receiving the event.
Şeklen şöyle

Örnek
Kod olarak şuna benzer
function decrementInventoryStockCount(txid, pid, offset) {
  transaction {
    tx_executed = check transaction table record where id=txid
      if not tx_executed {
        prod_count = select count from inventory where product=pid
        prod_count += offset
        set count=prod_count in inventory where product=pid
        insert to transaction table with id=txid
      }
  }
}
Burada dikkat edilmesi gerek şeyler şöyle
1. Metod bir txid  yan işlem numarasını parametre olarak alıyor
2. Metod bir transaction içinde çalışıyor

txid kullanılmasının açıklaması şöyle
Sometimes it’s not feasible to make data operations idempotent due to transaction isolation issues we get in a microservices communication. This means we can’t blindly retry operations in a transaction if we are not sure whether an operation was executed in a remote microservice. This can be solved by having a unique identifier such as a transaction ID for the operation of the microservice call, so the target microservice will create a history of transactions executed against it. In this manner, for each microservice operation call, it can do a local transaction to check the history and see if this transaction has already been executed. If not, it will execute the database operation, and still in the local transaction, update the transaction history table as well.
HTTP ve Idempotency
Açıklaması şöyle. HTTP için dikkat edilmesi gereken şey POST istekleri.
RFC 2616 says that methods GET, PUT and DELETE should be idempotent. That means aside from error or expiration issues, the side-effects for multiple requests are the same as for a single request. So if I issue 1 GET request or 2 or 3 of them with the same request headers and body, the net result is going to be the exactly the same. The same idea applies to PUT and DELETE requests.
..
But if you read RFC 2616 you’ll notice that they left out the POST method. This method is intended to have side-effects since the intention was to use it to create new objects.

Idempotency'nin Önem Kazandığı Bazı Örnekler

1. Observer Tasarım Örüntüsü ve Idempotency
Observer tetiklenince idempotent olarak tabir edilen ve her şeyi baştan yapan bir metod çağırır. Açıklaması şöyle
Let's say you have an HTML page that is fairly complicated-- if you pick something in one dropdown, another control might appear, or the values in a third control might change. There's two ways you could approach this:

  1. Write a separate handler, for each and every control, that respond to events on that control, and updates other controls as needed.
  2. Write a single handler that looks at the state of all the controls on the page and just fixes everything.
The second call is "idempotent" because you can call it over and over again and the controls will always be arranged properly. Whereas the first call(s) may have issues if a call is lost or repeated, e.g. if one of the handlers performs a toggle.

The logic for the second call would be a bit more obscure, but you only have to write one handler.

And you can always use both solutions, calling the "fix everything" function as needed "just to be on the safe side."
2. Retry ve Idempotency
Bir işlem başarısız olduğunda tekrar denemek (retry) isteyebilir. Tekrar deneme yönteminde karşı tarafın Idempotent Receiver olması gerekir. Açıklaması şöyle.
One thing that you need to be mindful when retrying is message idempotency. What happens if we get an HTTP timeout when calling the Fan Courier HTTP API, but our shipment request was actually processed successfully, we just didn't get the response back? When we retry, we don't want to send a new shipment. This is why the Fan Courier Gateway needs to be an Idempotent Receiver. This means that it doesn't matter if it processes the same message only once or 5 times, the result will always be the same: a single shipment request.

Hiç yorum yok:

Yorum Gönder