Derinlemesine yazılım eğitimleri için kanalımı takip edebilirsiniz...

Asp.NET Core Uygulamalarında Idempotent REST API Uygulama

Merhaba,

Bu içeriğimizde önceki yazılarımızda yer yer vurguladığımız Idempotent kavramına bir de Asp.NET Core REST API açısından bakış atıyor olacak ve client’lardan gelen yinelenen request’ler neticesinde yaratılan mükerrer durumlara karşın nasıl önlemler alabileceğimizi ve bir yandan da distributed sistemlerde REST API üzerinden haberleşme süreçlerinde güvenliğin daha da nasıl artırılabileceğini değerlendiriyor olacağız. O halde buyurun başlayalım…

Idempotency Nedir?

Yukarıda referans ettiğim iki makalede idempotency kavramını farklı açılardan değerlendirmiştik. Hadi şimdi gelin farklı bir dille tekrar değerlendirelim 🙂 Idempotency, bir sistem için güvenilirliği ve tutarlılığı garanti eden kritik bir kavramdır.

Güvenilirliği garanti ediyor, çünkü; bir işlem yanlışlıkla veyahut ağ sorunları gibi türlü nedenlerle birden fazla kez gönderilir ya da yapılmaya çalışılırsa(ki bu tarz durumlar kullanıcı kaynaklı sürekli olağan şeylerdir) sistemin bu etkiyi yalnızca bir kez işlemesini sağlar. Böylece, sistemin öngörülemeyen hatalardan(örneğin; çift ödeme, aynı işlemin mükerrer tekrar edilmesi vs.) korunmasını sağlar.

Bunun yanında, ağ kesintileri veya client-server iletişim hataları gibi durumlarda işlemin ya da iletişimin gönül rahatlığınca tekrar tekrar denenebilmesinin güvencesini sağlar. Çünkü idempotency, bir işlemin tekrar denenmesi durumunda bile beklenmeyen yan etkilerin üretilmeyeceğinin garantisini vermektedir.

Bu duruma rasyonel bir örnek vermemiz gerekirse eğer; bir veritabanında kaydın güncellenmesi idempotent bir şekilde tasarlandıysa, aynı güncelleme talebi tekrar gönderildiğinde sistem ya aynı sonucu döndürecek ya da zaten güncellenmiş olduğunu bildirerek işlem gerçekleştirmeyecektir. Bir başka örnek vermemiz gerekirse eğer; herhangi bir e-ticaret sisteminde, sipariş verildiği taktirde stok işlemleri için message broker’a atılan mesajın türlü nedenlerden kaynaklı stok servisi tarafından tekrar tekrar işlenebilme ihtimali söz konusudur. İşte buradaki tasarım idempotent bir yaklaşım sergiliyorsa bu tarz durumlara karşın stok servisinde bir yan etki oluşmayacak ve önceden ilgili siparişe dair stok güncellemesi yapıldığı bilineceği için tekrar bir işlem yapılmayacaktır.

Tutarlılığı garanti ediyor, çünkü; aynı işlem birden fazla kez çalıştırıldığı durumlarda, sistemin durumu ilk çalıştırılmaya karşın yalnızca bir kez değişecek, sonrakilerde ise değişmeyecektir. Böylece veritabanında veya sistemde çelişkili durumların(örneğin; aynı kaydın birden fazla kopyasının oluşması vs. gibi) önlenmesi sağlanmış olacaktır.

Ayrıca, idempotency bir işlemin sonucunun her tekrarda her daim aynı olmasını garanti edeceği için hem sistemin hem de verisel anlamda işleyişin tutarlı ve öngörülebilir olmasını sağlayacaktır.

Buna da bir örnek vermemiz gerekirse; bir ödeme işlemi idempotent ise, aynı ödeme talebi tekrar tekrar gönderilse bile, sistem tek bir ödeme yapacak ve diğerlerinde zaten ödemenin yapıldığını bildirecektir.

Yani anlayacağınız, idempotent olarak tasarlanmış bir işlem, özellikle ağ hataları veya zaman aşımı sorunlarından kaynaklı yinelenen isteklerin gönül rahatlığıyla yapılabileceğini ve böylece ilk istek dışında sonucu değiştirmeden birden çok kez tekrarlanabileceğini garanti etmektedir.

REST API Bağlamında Idempotent Nedir?

REST API’ler bağlamında idempotent kavramı, yinelenen isteklerin(request) sistem üzerinde tek bir istekle aynı etkiyi yaratması anlamına gelmektedir. Bir başka deyişle, bir client tarafından herhangi bir işleve karşın olan istek kaç kez yinelenirse yinelensin, server tarafındaki etkisinin yalnızca bir kez gerçekleşmesidir.

HTTP Semantics(anlamsal kuralları) üzerine olan RFC 9110 standardı dahi bu konuda baz alınabilecek güzel bir tanım sunmakta ve idempotent işlevselliğini şu şekilde açıklamaktadır;

Bir istek metodunun ‘idempotent’ kabul edilebilmesi için, aynı metotla gerçekleştirilen birden çok özdeş isteğin sunucuda yarattığı etkinin tek bir isteğin etkisiyle aynı olması gerekir.

Bu açıklamadan yola çıkarak; PUT, DELETE, GET, HEAD, OPTIONS ve TRACE idempotent metotlar olduğunu söyleyebiliriz.

Tabi burada ayriyeten şunu da vurgulamakta fayda vardır ki; idempotent, yalnızca kullanıcının beklediği ana ya da bir başka deyişle sistemin o anki işleyiş bağlamına etki için geçerli olan bir yapılanmadır. Yani idempotent olan bir yapılanmada server, gelen request’i idempotent olarak işlemek dışında her isteği ayrı ayrı loglayabilir, bir sürüm kontrol geçmişi tutabilir ya da metrik toplamak gibi türlü side effect’ler oluşturarak sistemin gerektirdiği başka işlemleri rahatlıkla yürütebilir. Anlayacağınız, her istekte kullanıcı ve sistem açısından sonuç değişmeyecek(yani idempotent olacak) ancak arka planda server teknik olarak başka kayıtlar veya işlemler yaparak yaşam fonksiyonlarına devam edebilecektir.

Bu duruma günlük hayattan aşağıdaki gibi güzel bir örnek verebiliriz;
Yapılan bir alışveriş neticesinde kasiyerden aynı fişi kaç kez istersek isteyelim aynı içeriğe sahip fiş verecektir, bu kısım idempotent’tir. Ama bu süreçte kasiyer her fiş isteğini ayrıca deftere kaydedebilir ya da kendince not alabilir, işte bu da idempotent’a aykırı olmayan side effect’lerdir.

Hangi HTTP Metotları Doğası Gereği Idempotent’tir?

HTTP metotlarının idempotent olup olmaması, aynı isteğin birden fazla kez yapılmasının sunucu üzerinde aynı sonucu üretip üretmediğine bağlıdır. Bu açıdan olayı değerlendirdiğimizde birçok HTTP metodu doğası gereği idempotent’tır;

  • GET
    Her GET isteğinde sunucu, state’ini değiştirmeyeceği ve sadece aynı veriyi getireceği için idempotent’dir. Burada dikkat edilmesi gereken bir husus vardır; o da, pratikte bazı uygulamalarda GET isteklerinin yanlış şekilde implemente edilmesinden kaynaklı sunucuda kullanıcı ve sistem açısından state’i değiştirebilecek sayaç artırma, log yazma gibi side effect’ler mevzu bahis olabilir. Bu durumlarda, GET metodu teknik olarak idempotent olmaktan çıkabilir; şöyle ki, yukarıdaki satırlarda da bahsettiğimiz gibi kullanıcıya sunulan kaynak durumu değişmediği sürece bu metodun idempotent’liği etkilenmeyecektir. Aksi taktirde, bu side effect’ler neticesinde kullanıcıya sunulan sonuçta bir değişiklik olacaksa (ki bu değişiklik ‘bu profil (n) kere gösterildi’ şeklinde bir sayaç bilgisini etkiliyorsa), her istekte bu sayaç artacak ve kullanıcıya farklı sonuç sunulacaktır. Ee haliyle bu durumda da idempotent’ın doğasına aykırı bir durum söz konusu olacaktır.
  • HEAD
    GET ile benzer davranış sergileyen ancak sadece response headers’ları döndürerek, response body içermeyen bir metottur. GET’te bahsedilen kritik durumları aynı şekilde göz önüne alarak doğası gereği idempotent olduğunu söyleyebiliriz.
  • PUT
    Genellikle belirli kaynağı güncellemekte olan metot türüdür. Haliyle aynı PUT isteği her tekrarlandığında, kaynak zaten ilkinde güncellenmiş olacağı için aynı sonucu üretecektir. Bundan kaynaklı genellikle idempotent’tır. Amma velakin her PUT isteğinde kaynakta artırma, eksiltme ya da rastgelelik gibi sürekli bir değişim söz konusuysa bu taktirde idempotent’lik mevzu bahis olmayacaktır. Bunu şöyle örneklendirebiliriz; yapılan PUT isteğinde {‘adi’ : ‘Gençay’} şeklinde bir çalışma yapılıyorsa, evet, ilk istekten sonraki tüm istekler aynı neticeyle nihayete erecek ve ilgili kaydın adını ‘Gençay’ olarak değiştirecektirler. İşte bu durum idempotent’tir. Halbuki yapılan PUT isteğinde, {‘increment’: 5} minvalinde değer artırıcı ya da azaltıcı bir işlem yapılacaksa, her istekte bu işlem farklı bir netice doğuracağı için bu kullanım durumlarında da idempotent’lık söz konusu olmayacaktır.
  • DELETE
    DELETE ile belirli bir kaynağı(ya da kaydı/veriyi) sileceğimiz için ve zaten bu kaynak ilk istekte silinmiş olacağı için isteğin sonraki tekrarlarında aynı durum korunacak ve mutlak bir idempotent’lık durumu söz konusu olacaktır.
  • OPTIONS
    Bu istekte, sunucunun desteklediği metotları sorgulayacağı için idempotent bir davranışa sahiptir.

Ancak yapısı ve davranışı gereği net idempotent olmayan tek bir HTTP metodumuz vardır, o da POST‘tur. POST, her istekte yeni bir kaynak oluşturacağı için doğası gereği idempotent değildir! Tabi bizler özel mantık geliştirerek, kaynağı oluşturmadan önce mevcut mu değil mi kontrol edebilir ve böylece tekrarlanan POST isteklerinde idempotent’lik sağlayabiliriz.

Asp.NET Core’da Idempotency’nin Uygulanması

Asp.NET Core’da uygulamaya idempotency davranışını uygulayabilmek için request header’larında Idempotence-Key içeren özel bir strateji geliştireceğiz. Bu stratejinin teknik detayları şu şekilde olacaktır;

  1. Client, her işleme özel unique bir key üretecek ve bunu Idempotence-Key başlığında request’in header’ına ekleyerek gönderecektir.
  2. Server ise bu key’i kontrol edecek ve belirli bir vadeye karşın önceden işlenmiş bir key’in muadiliyse o işlemin result’ını döndürecek, yok eğer değilse de gelen istek doğrultusunda işlevini gerçekleştirecek ve bu key eşliğinde sonucu saklayacaktır.

Yani anlayacağınız, isteklerdeki idempotent değerlerini server’da adil bir süreliğine cache’leyerek store edeceğiz, ardından da bu süre zarfında ağ sorunları gibi durumlardan kaynaklı tekrar eden isteklerin yalnızca bir kez işlenmesini sağlıyor olacağız.

Bunun için uygulamada öncelikle bir cache mekanizması temellerinin atılmış olması gerekmektedir. Bizler bu içeriğimizde, bu işlem için Redis veritabanını tercih edeceğiz. Haliyle Microsoft.Extensions.Caching.StackExchangeRedis kütüphanesi eşliğinde aşağıdaki yapılandırmayla cache temellerini hızlıca atabiliriz;

builder.Services.AddStackExchangeRedisCache(options =>
{
    options.Configuration = "localhost:6379";
});

Bu kadar basit. Artık bu cache altyapısı üzerinden idempotency çalışmalarına başlayabiliriz.

Controller-Based Idempotency

Asp.NET Core’da geleneksel controller seviyesindeki çalışmalarda idempotency için aşağıdaki gibi bir Attribute ve IAsyncActionFilter olarak geliştirilmiş sınıf tasarlayabiliriz;

    [AttributeUsage(AttributeTargets.Method)]
    public sealed class IdempotentAttribute(int CacheTimeInMinutes = 60) : Attribute, IAsyncActionFilter
    {
        public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
        {
            //Gelen istektenten Idempotence-Key header'ını elde ediyoruz.
            if (
                !context.HttpContext.Request.Headers.TryGetValue("Idempotence-Key", out StringValues idempotenceKeyValue)
                ||
                !Guid.TryParse(idempotenceKeyValue, out Guid idempotenceKey)
                )
            {
                context.Result = new BadRequestObjectResult("Idempotence-Key değeri geçersiz veya eksiktir!");
                return;
            }

            //Bu isteğin daha önce işlenip işlenmediğini kontrol ediyoruz. Eğer işlendiyse, önbellekten sonucu alıp döndürüyoruz.
            IDistributedCache cache = context.HttpContext.RequestServices.GetRequiredService<IDistributedCache>();
            string cacheKey = $"Idempotence_{idempotenceKey}";
            string? cachedResult = await cache.GetStringAsync(cacheKey);

            if (cachedResult is not null)
            {
                IdempotentResponse response = JsonSerializer.Deserialize<IdempotentResponse>(cachedResult)!;

                var result = new ObjectResult(response.Value)
                {
                    StatusCode = response.StatusCode
                };

                context.Result = result;

                return;
            }

            // İstek daha önce işlenmemişse, işlemi gerçekleştiriyoruz. Ayrıca sonucu belirtilen süre boyunca önbelleğe kaydediyoruz.
            ActionExecutedContext executedContext = await next();

            if (executedContext.Result is ObjectResult { StatusCode: >= 200 and < 300 } objectResult)
            {
                int statusCode = objectResult.StatusCode ?? StatusCodes.Status200OK;
                IdempotentResponse response = new(statusCode, objectResult.Value);

                await cache.SetStringAsync(
                    cacheKey,
                    JsonSerializer.Serialize(response),
                    new DistributedCacheEntryOptions { AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(CacheTimeInMinutes) }
                    );
            }
        }
    }

Burada;
6 ile 15. satır aralığına göz atarsak eğer client’tan gelen istekteki Idempotence-Key header’ı kontrol edilmekte ve böyle bir header yoksa yahut olsa dahi içerisindeki veri uygun formatta değilse, işte bu taktirde ilgili istek BadRequestObjectResult olarak güncellenip, geri döndürülmektedir.

17 ile 34. satır aralığına bakarsak eğer, geçerli bir Idempotence-Key değerine karşılık bu isteğin daha önce işlenip işlenmediği kontrol edilmekte ve eğer işlendiyse cache’den önceki sonucu alınıp, geri gönderilmektedir.

Son olarak 36 ile 50. satır aralığına göz atarsak, ilgili isteğin daha önce(ya da adil cache süreci içerisinde) işlenmediğine kanaat getirildiği taktirde gerekli işlevsellik gösterilmekte, ayrıca yeni değer cache’lenerek süreç tamamlanmaktadır.

Tabi bu süreçte kullanılan IdempotentResponse türünü de aşağıdaki gibi oluşturabiliriz;

internal sealed class IdempotentResponse
{
    [JsonConstructor]
    public IdempotentResponse(int statusCode, object? value)
    {
        StatusCode = statusCode;
        Value = value;
    }

    public int StatusCode { get; }
    public object? Value { get; }
}

Ve bu çalışmadan sonra aşağıdaki gibi herhangi bir controller’da bu filter’ı devreye sokup, kullanabiliriz;

    [Route("api/[controller]")]
    [ApiController]
    public class OrdersController : ControllerBase
    {
        [HttpPost]
        [Idempotent(CacheTimeInMinutes: 60)]
        public IActionResult CreateOrder([FromBody] CreateOrderRequest createOrderRequest)
        {
            return Ok(new Random().NextDouble());
        }
    }

Burada dikkat ederseniz ‘mış’ gibi bir sipariş oluşturma işlemi gerçekleştirmekte ve esasında 0 ile 1 arasında rastgele bir değer üreterek geriye döndürmekteyiz. Birazdan idempotent olan bu sistemde, gelen tekrarlı istekler neticesinde client’a gönderilen verinin yeni üretilip üretilmediğini bu değerlerden kontrol edip, test edeceğiz. Ama tabi sanki bir sipariş kaydı oluşturuluyormuş gibi varsayacağız 🙂

Esasında controller tabanlı isteklerle ilgili çalışmamız bu noktada tamamlandığı için testlere geçip gerekli kontrollerde bulunabiliriz. Ancak ben minimal api’ler için de bu yapılandırmanın nasıl olacağına değinip, gün sonunda her iki yaklaşımında testini tek elden yapmayı tercih etmekteyim.

Minimal API Idempotency

Evet, minimal API’lerde de idempotency’i uygulayabilmek için aşağıdaki gibi bir IEndpointFilter tasarlayabiliriz;

    public sealed class IdempotencyFilter(int CacheTimeInMinutes = 60) : IEndpointFilter
    {
        public async ValueTask<object?> InvokeAsync(EndpointFilterInvocationContext context, EndpointFilterDelegate next)
        {
            //Gelen istektenten Idempotence-Key header'ını elde ediyoruz.
            if (
                !context.HttpContext.Request.Headers.TryGetValue("Idempotence-Key", out StringValues idempotenceKeyValue)
                ||
                !Guid.TryParse(idempotenceKeyValue, out Guid idempotenceKey)
                )
            {
                return Results.BadRequest("Idempotence-Key değeri geçersiz veya eksiktir!");
            }

            //Bu isteğin daha önce işlenip işlenmediğini kontrol ediyoruz. Eğer işlendiyse, önbellekten sonucu alıp döndürüyoruz.
            IDistributedCache cache = context.HttpContext.RequestServices.GetRequiredService<IDistributedCache>();
            string cacheKey = $"Idempotence_{idempotenceKey}";
            string? cachedResult = await cache.GetStringAsync(cacheKey);

            if (cachedResult is not null)
            {
                IdempotentResponse response = JsonSerializer.Deserialize<IdempotentResponse>(cachedResult)!;
                return response.Value;
            }

            // İstek daha önce işlenmemişse, işlemi gerçekleştiriyoruz. Ayrıca sonucu belirtilen süre boyunca önbelleğe kaydediyoruz.
            var result = await next(context);

            if (result is IStatusCodeHttpResult { StatusCode: >= 200 and < 300 } statusCodeResult and IValueHttpResult valueResult)
            {
                int statusCode = statusCodeResult.StatusCode ?? StatusCodes.Status200OK;
                IdempotentResponse response = new(statusCode, valueResult.Value);

                await cache.SetStringAsync(
                    cacheKey,
                    JsonSerializer.Serialize(response),
                    new DistributedCacheEntryOptions { AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(CacheTimeInMinutes) }
                    );
            }

            return result;
        }
    }

Burada da benzer mantıkla, 5 ile 13. satır aralığında request’teki Idempotence-Key değeri kontrol edilmekte, 15 ile 24. satır aralığında bu isteğin önceden yapılıp yapılmadığı değerlendirilmekte ve 26 ile 39. satır aralığında ise request işlenerek sonucu cache’lenmektedir.

Bu yaptığımız çalışmayı da aşağıdaki gibi herhangi bir endpoint’te AddEndpointFilter metodu ile çağırabiliriz;

.
.
.
app.MapPost("/api/orders_mini", (CreateOrderRequest createOrderRequest) =>
{
    return Results.Ok(new Random().NextDouble());
})
    .AddEndpointFilter<IdempotencyFilter>();

Evet, burada da yine sanki bir sipariş oluşturuluyormuş minvalinde olan, halbuki içerisinde 0 ile 1 arasında değer döndüren bir işlevselliğe sahip bir endpoint tasarlamış bulunmaktayız.

Her neyse… Ne de olsa yapmamız gerekenler esasında bunlardan ibarettir diyebiliriz.

Şimdi gelin test sürecine geçebiliriz;

Test Edelim

Yukarıda yaptığımız Controller-Based ve Minimal API odaklı iki idempotency çalışmasını aşağıdaki gibi bir .http dosyasıyla rahatlıkla test edebiliriz;

### Create Order Controller-Based Test Request
POST https://localhost:7152/api/orders
Content-Type: application/json
Accept: application/json
Idempotence-Key: {{$guid}}

{
  "BuyerId": 123
}

### Create Order Minimal API Test Request
POST https://localhost:7152/api/orders_mini
Content-Type: application/json
Accept: application/json
Idempotence-Key: {{$guid}}

{
  "BuyerId": 123
}

Burada tasarlanan isteklerin yapısına göz atarsak eğer Guid türünden olan Idempotence-Key verisi otomatik üretilerek-{{$guid}}– request oluşturulmakta ve böylece, client’ın her bir isteğe karşın yeni bir key üretebildiği bir durumu rahatlıkla simüle edebilmekteyiz.

Şimdi aşağıdaki görselde olduğu gibi içerik sürecinde oluşturduğumuz her iki endpoint’e üçerli istekte bulunduğumuzu varsayalım;Asp.NET Core Uygulamalarında Idempotent REST API UygulamaGörüldüğü üzere tüm istekler yeni bir Idempotence-Key değeriyle yapılacağı için sistem gereği bu isteklere karşın üretilen değerlerle(result) birlikte Idempotence-Key verileri Redis’te cache’lenecektir. Bu durum, ister controller-based olsun, ister minimal api olsun… hangi yaklaşım olursa olsun her ikisi için de geçerli olacaktır.

Tabi sürecin işleyişinin daha iyi sindirilebilmesi için herhangi bir request’in detayını screenshot olarak aşağıya alarak incelemekte fayda görmekteyim;Asp.NET Core Uygulamalarında Idempotent REST API UygulamaEvet, bu ekran alıntısından da görüldüğü üzere bir request eğer özgün bir Idempotence-Key değerine sahipse hem bu request’in işlenmesine müsade edilmekte hem de bir yandan Redis’e cache’lenerek tekrarlı bir istek geldiği taktirde cache’lenmiş veriyi döndürmektedir.

Ee hoca! tekrarlı istekleri de test ederek bi göster la! dediğinizi duyar gibiyim… Tamam, hiç merak etmeyin 🙂 Misal olarak, yukarıda yaptığımız son isteğin Idempotence-Key değerini alıp, aşağıdaki gibi kullanarak yinelenen istekleri hızlıca örneklendirebiliriz;

### Create Order Controller-Based Test Request
POST https://localhost:7152/api/orders
Content-Type: application/json
Accept: application/json
Idempotence-Key: 1817d553-4e2f-4d26-ac45-c9bc3fd54646

{
  "BuyerId": 123
}

### Create Order Minimal API Test Request
POST https://localhost:7152/api/orders_mini
Content-Type: application/json
Accept: application/json
Idempotence-Key: 1817d553-4e2f-4d26-ac45-c9bc3fd54646

{
  "BuyerId": 123
}

Asp.NET Core Uygulamalarında Idempotent REST API UygulamaGörüldüğü üzere cache’de ilgili Idempotence-Key değerine karşın bir request bilgisi tutulduğu için bu istek işlenmemekte, mevcut değer döndürülmektedir.

İşte bu kadar 🙂

İçeriğimizi sonlandırmadan, yazı boyunca olası aklınıza takılma riski olan bir kaç hususa değinmek istiyorum. Misal olarak bunlardan birinin cache’lenecek verilerin expiration süreleri olduğunu söyleyebilirim. Evet, cache expiration oldukça hassas bir parametredir diyebiliriz. Makul olmalı ama bir yandan da yinelenecek isteklerin işlevselliklerindeki kritik duruma göre zamansal uzunluğa sahip olmalıdır. Bunun dışında distributed çalışmalar ve daha da önemlisi distributed locking durumları için Redis oldukça idealdir diyebiliriz. O yüzden bu tarz çalışmalarda ben Redis’i mutlak tercih ediyorum diyebilirim. Sizlerin de bu konuda çok fazla alternatif aramanıza gerek olmadığını düşünüyorum.

Nihai olarak;
Bu içeriğimizde, REST API’lerimizin kalitesini, hizmet güvenilirliğini ve tutarlılığını artırabilmek için Idempotency yaklaşımının nasıl uygulanabileceğini Asp.NET Core çerçevesinde değerlendirmiş ve bir yandan da API’lerimizdeki kritik işlemlere ve özellikle sistem durumunu değiştiren veya önemli iş süreçlerini tetikleyen operasyonlara karşın ağ kesintileri yahut kullanıcı deneyimlerindeki problemlerden kaynaklı istemsiz yinelenebilen isteklere karşı nasıl önlem alınabileceğini idempotency’i pratik bir şekilde uygulayarak deneyimlemiş bulunuyoruz.

İlgilenenlerin faydalanması dileğiyle…
Sonraki yazılarımda görüşmek üzere…
İyi çalışmalar…

Not : Örnek çalışmaya aşağıdaki GitHub adresinden erişebilirsiniz.
https://github.com/gncyyldz/AspNetCore.Idempotency.Implementation

Bunlar da hoşunuza gidebilir...

1 Cevap

  1. Eyüp Kahraman dedi ki:

    Emeğinize sağlık

Bir yanıt yazın

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