<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Berk Selvi | Software Developer</title><link>https://berkselvi.dev/tr/</link><description>Recent content on Berk Selvi | Software Developer</description><generator>Hugo -- gohugo.io</generator><language>tr-TR</language><copyright>Made by Berk Selvi. &lt;br&gt; Bu websitesi &lt;a href='https://github.com/berkslv/berkselvi.dev' target='_blank'&gt;open source&lt;/a&gt; olarak yayınlanmıştır.</copyright><lastBuildDate>Sun, 02 Aug 2026 00:00:00 +0300</lastBuildDate><atom:link href="https://berkselvi.dev/tr/index.xml" rel="self" type="application/rss+xml"/><item><title>IEnumerable'dan IAsyncEnumerable'a: .NET'te Belleği Öldürmeden Büyük Veri Setlerini Streaming ile İşlemek</title><link>https://berkselvi.dev/tr/posts/from-ienumerable-to-iasyncenumerable-streaming-large-datasets-in-dotnet-without-killing-memory/</link><pubDate>Sun, 02 Aug 2026 00:00:00 +0300</pubDate><guid>https://berkselvi.dev/tr/posts/from-ienumerable-to-iasyncenumerable-streaming-large-datasets-in-dotnet-without-killing-memory/</guid><description>&lt;p&gt;Birkaç ay önce, gece çalışan export job&amp;rsquo;larımızdan biri &lt;code&gt;OutOfMemoryException&lt;/code&gt; fırlatmaya başladı. Kodda hiçbir şey değişmemişti. Değişen tek şey, okuduğu tablonun boyutuydu—sessizce 200 bin satırdan 4 milyon satıra çıkmıştı. Job tam olarak söylediğimiz şeyi yapıyordu: tüm tabloyu &lt;code&gt;.ToListAsync()&lt;/code&gt; ile çekip üzerinde döngü kuruyordu. Sadece önce tüm veritabanı tablosunu RAM&amp;rsquo;e alıyordu ve RAM kaybediyordu.&lt;/p&gt;
&lt;p&gt;Bu, neredeyse her .NET geliştiricisinin er ya da geç düştüğü bir tuzak: aslında ihtiyacınız olan şey kayıt kayıt streaming yapan &lt;code&gt;IAsyncEnumerable&amp;lt;T&amp;gt;&lt;/code&gt; iken &lt;code&gt;IEnumerable&amp;lt;T&amp;gt;&lt;/code&gt;&amp;lsquo;ye (ya da onun async kuzeni &lt;code&gt;Task&amp;lt;List&amp;lt;T&amp;gt;&amp;gt;&lt;/code&gt;&amp;lsquo;ye) yönelmek. &lt;code&gt;IEnumerable&amp;lt;T&amp;gt;&lt;/code&gt; ve &lt;code&gt;IAsyncEnumerable&amp;lt;T&amp;gt;&lt;/code&gt; yüzeysel olarak birbirine benzer ama çok farklı problemleri çözerler, ve yanlışını seçmek sizi gece 2&amp;rsquo;de 4 milyon satırlık bir tabloyu bellekte sayfalamaya götüren tam olarak budur.&lt;/p&gt;</description><content>&lt;p&gt;Birkaç ay önce, gece çalışan export job&amp;rsquo;larımızdan biri &lt;code&gt;OutOfMemoryException&lt;/code&gt; fırlatmaya başladı. Kodda hiçbir şey değişmemişti. Değişen tek şey, okuduğu tablonun boyutuydu—sessizce 200 bin satırdan 4 milyon satıra çıkmıştı. Job tam olarak söylediğimiz şeyi yapıyordu: tüm tabloyu &lt;code&gt;.ToListAsync()&lt;/code&gt; ile çekip üzerinde döngü kuruyordu. Sadece önce tüm veritabanı tablosunu RAM&amp;rsquo;e alıyordu ve RAM kaybediyordu.&lt;/p&gt;
&lt;p&gt;Bu, neredeyse her .NET geliştiricisinin er ya da geç düştüğü bir tuzak: aslında ihtiyacınız olan şey kayıt kayıt streaming yapan &lt;code&gt;IAsyncEnumerable&amp;lt;T&amp;gt;&lt;/code&gt; iken &lt;code&gt;IEnumerable&amp;lt;T&amp;gt;&lt;/code&gt;&amp;lsquo;ye (ya da onun async kuzeni &lt;code&gt;Task&amp;lt;List&amp;lt;T&amp;gt;&amp;gt;&lt;/code&gt;&amp;lsquo;ye) yönelmek. &lt;code&gt;IEnumerable&amp;lt;T&amp;gt;&lt;/code&gt; ve &lt;code&gt;IAsyncEnumerable&amp;lt;T&amp;gt;&lt;/code&gt; yüzeysel olarak birbirine benzer ama çok farklı problemleri çözerler, ve yanlışını seçmek sizi gece 2&amp;rsquo;de 4 milyon satırlık bir tabloyu bellekte sayfalamaya götüren tam olarak budur.&lt;/p&gt;
&lt;h2 id="bir-bakışta-ienumerable-vs-iasyncenumerable"&gt;Bir Bakışta IEnumerable vs IAsyncEnumerable&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Özellik&lt;/th&gt;
&lt;th&gt;&lt;code&gt;IEnumerable&amp;lt;T&amp;gt;&lt;/code&gt; / &lt;code&gt;List&amp;lt;T&amp;gt;&lt;/code&gt;&lt;/th&gt;
&lt;th&gt;&lt;code&gt;IAsyncEnumerable&amp;lt;T&amp;gt;&lt;/code&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Materialization&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Iterasyon başlamadan önce tamamen gerçekleştirilir&lt;/td&gt;
&lt;td&gt;Talep üzerine, tek tek çekilir&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Bellek ayak izi&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Sonuç kümesinin boyutuyla birlikte büyür&lt;/td&gt;
&lt;td&gt;Boyuttan bağımsız olarak kabaca sabit&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;İlk öğeye kadar geçen süre&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Tüm sorgu/koleksiyonu bekler&lt;/td&gt;
&lt;td&gt;İlk öğe hazır olur olmaz kullanılabilir&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Cancellation&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Sadece döngünüzü durdurur; zaten yapılmış iş yapılmış kalır&lt;/td&gt;
&lt;td&gt;Altta yatan sorguyu akış ortasında durdurabilir&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Iterasyon&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Tekrarlanabilir, indekslenebilir&lt;/td&gt;
&lt;td&gt;Sadece ileri yönlü, tek geçişli&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Tipik kullanım&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Küçük, sınırlı koleksiyonlar&lt;/td&gt;
&lt;td&gt;Export&amp;rsquo;lar, ETL, sayfalanmış API&amp;rsquo;ler, log işleme&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2 id="eski-yöntem-önce-her-şeyi-yükle-sonra-iterasyon-yap"&gt;Eski Yöntem: Önce Her Şeyi Yükle, Sonra Iterasyon Yap&lt;/h2&gt;
&lt;p&gt;İşte bizi belaya sokan pattern buydu. Order&amp;rsquo;ları export etmek için okuyan tipik bir .NET servisi:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-csharp" data-lang="csharp"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;async&lt;/span&gt; Task&amp;lt;List&amp;lt;Order&amp;gt;&amp;gt; GetAllOrdersAsync()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;await&lt;/span&gt; _dbContext.Orders
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; .AsNoTracking()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; .ToListAsync();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;async&lt;/span&gt; Task ExportOrdersAsync()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; orders = &lt;span style="color:#66d9ef"&gt;await&lt;/span&gt; GetAllOrdersAsync();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;foreach&lt;/span&gt; (&lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; order &lt;span style="color:#66d9ef"&gt;in&lt;/span&gt; orders)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;await&lt;/span&gt; _exportWriter.WriteLineAsync(order.ToCsvRow());
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Bu tamamen makul görünüyor, ve küçük tablolar için de öyle. Ama &lt;code&gt;ToListAsync()&lt;/code&gt;&amp;lsquo;in aslında ne yaptığına yakından bakın: sorgunun döndürdüğü &lt;strong&gt;her bir satırı&lt;/strong&gt;, kodunuz ilk öğeye dokunmadan önce bellekte duran bir &lt;code&gt;List&amp;lt;Order&amp;gt;&lt;/code&gt; içinde materialize eder. Sorgu 4 milyon satır döndürüyorsa, &lt;code&gt;foreach&lt;/code&gt; döngüsünde her seferinde sadece bir satır işlese bile, aynı anda 4 milyon &lt;code&gt;Order&lt;/code&gt; nesnesini tutacak kadar belleğe ihtiyacınız var.&lt;/p&gt;
&lt;p&gt;Belirtiler yavaşça ortaya çıkar:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Bellek sıçramaları:&lt;/strong&gt; GC baskısı artar, process&amp;rsquo;in working set&amp;rsquo;i şişer, ve Kubernetes&amp;rsquo;te container bellek limitlerine takılınır.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;İlk sonuçtan önce gecikme:&lt;/strong&gt; Caller, tek bir satır görmeden önce &lt;em&gt;tüm&lt;/em&gt; sorgunun tamamlanmasını bekler, sadece sonuçları bir client&amp;rsquo;a stream etmek istese bile.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Cancellation&amp;rsquo;da boşa giden iş:&lt;/strong&gt; Caller yarı yolda işlemi iptal ederse, her şeyi yükleme maliyetinin tamamını zaten ödemişsinizdir.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Backpressure yok:&lt;/strong&gt; &amp;ldquo;Yavaşla, son batch&amp;rsquo;i hâlâ işliyorum&amp;rdquo; demenin bir yolu yoktur—veritabanı, driver&amp;rsquo;ın buffer&amp;rsquo;layabildiği hızda satır üretmeye devam eder.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Export yapan bir background job, bir CSV stream eden API endpoint&amp;rsquo;i, ya da bir veri migration script&amp;rsquo;i için bu pattern ölçeklenmez. Local development&amp;rsquo;ta 50 satırlık seed veri setinizle çalışır, ve sessizce production&amp;rsquo;da bozulur.&lt;/p&gt;
&lt;h2 id="neden-iasyncenumerable"&gt;Neden IAsyncEnumerable?&lt;/h2&gt;
&lt;p&gt;.NET Core 3.0, tam olarak bu tür problemi çözmek için &lt;code&gt;await foreach&lt;/code&gt; syntax&amp;rsquo;ıyla birlikte &lt;code&gt;IAsyncEnumerable&amp;lt;T&amp;gt;&lt;/code&gt;&amp;lsquo;yi tanıttı. Tamamen gerçekleştirilmiş bir koleksiyon döndüren bir method yerine, consumer bir sonraki elemanı her istediğinde asenkron olarak tek tek &lt;strong&gt;çekilen&lt;/strong&gt; bir sequence döndürür.&lt;/p&gt;
&lt;p&gt;Mental model değişimi önemli:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;Task&amp;lt;List&amp;lt;T&amp;gt;&amp;gt;&lt;/code&gt;&lt;/strong&gt; şu anlama gelir: &amp;ldquo;her şey hazır olana kadar bekle, sonra bana tüm şeyi ver.&amp;rdquo;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;IAsyncEnumerable&amp;lt;T&amp;gt;&lt;/code&gt;&lt;/strong&gt; şu anlama gelir: &amp;ldquo;bir sonrakini istediğimde bana bir öğe ver, hazır değilse &lt;code&gt;await&lt;/code&gt; etmeme izin ver.&amp;rdquo;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Bu şunları mümkün kılar:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Sabit bellek kullanımı:&lt;/strong&gt; Kaynağın 10 satırı ya da 10 milyon satırı olması fark etmeksizin, herhangi bir anda bellekte sadece bir öğenin (ya da küçük bir buffer&amp;rsquo;ın) olması yeterlidir.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Time-to-first-byte iyileştirmeleri:&lt;/strong&gt; Bir consumer, tüm sonuç kümesini beklemek yerine ilk satır hazır olur olmaz işlemeye (ya da bir HTTP client veri almaya) başlayabilir.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Doğal cancellation desteği:&lt;/strong&gt; &lt;code&gt;IAsyncEnumerable&amp;lt;T&amp;gt;&lt;/code&gt;, &lt;code&gt;WithCancellation&lt;/code&gt; üzerinden &lt;code&gt;CancellationToken&lt;/code&gt; ile compose olur, böylece bir stream&amp;rsquo;i iptal etmek, sadece zaten yüklenmiş bir listeyi atmak yerine gerçekten altta yatan sorguyu durdurur.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;LINQ ile composability:&lt;/strong&gt; &lt;code&gt;System.Linq.Async&lt;/code&gt; paketiyle, async stream&amp;rsquo;ler üzerinde &lt;code&gt;Select&lt;/code&gt;, &lt;code&gt;Where&lt;/code&gt; ve benzerlerini kullanabilirsiniz, böylece &lt;code&gt;IEnumerable&amp;lt;T&amp;gt;&lt;/code&gt; ile alışık olduğunuz ifade gücünü kaybetmezsiniz.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Trade-off ise, random access&amp;rsquo;ten ve ayrı bir sorgu yapmadan sayıyı önceden bilme yeteneğinden vazgeçmenizdir—&lt;code&gt;IAsyncEnumerable&amp;lt;T&amp;gt;&lt;/code&gt; tasarım gereği sadece ileri yönlü, tek geçişlidir. Streaming senaryoları için bu tam olarak doğru kısıtlamadır.&lt;/p&gt;
&lt;h2 id="yield-return-ile-iasyncenumerable-üretmek"&gt;yield return ile IAsyncEnumerable Üretmek&lt;/h2&gt;
&lt;p&gt;Bir &lt;code&gt;IAsyncEnumerable&amp;lt;T&amp;gt;&lt;/code&gt; üretmenin en basit yolu, &lt;code&gt;async&lt;/code&gt; bir method içinde &lt;code&gt;yield return&lt;/code&gt; kullanmaktır. İşte simüle edilmiş bir I/O gecikmesiyle sayıları stream eden, EF Core&amp;rsquo;u işin içine katmadan önce mekaniği anlamak için kullanışlı, el yazımı bir örnek:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-csharp" data-lang="csharp"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;async&lt;/span&gt; IAsyncEnumerable&amp;lt;&lt;span style="color:#66d9ef"&gt;int&lt;/span&gt;&amp;gt; GenerateNumbersAsync(
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;int&lt;/span&gt; count,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt; [EnumeratorCancellation]&lt;/span&gt; CancellationToken cancellationToken = &lt;span style="color:#66d9ef"&gt;default&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;for&lt;/span&gt; (&lt;span style="color:#66d9ef"&gt;int&lt;/span&gt; i = &lt;span style="color:#ae81ff"&gt;0&lt;/span&gt;; i &amp;lt; count; i++)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; cancellationToken.ThrowIfCancellationRequested();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// Simulate an async I/O operation, e.g. a network call per item&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;await&lt;/span&gt; Task.Delay(&lt;span style="color:#ae81ff"&gt;10&lt;/span&gt;, cancellationToken);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;yield&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; i;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;[EnumeratorCancellation]&lt;/code&gt; attribute&amp;rsquo;u gözden kaçması kolay ama önemli bir detay: bu olmadan, &lt;code&gt;WithCancellation&lt;/code&gt;&amp;lsquo;a geçirilen bir &lt;code&gt;CancellationToken&lt;/code&gt; aslında method body&amp;rsquo;sine akmaz, ve &lt;code&gt;ThrowIfCancellationRequested&lt;/code&gt; caller&amp;rsquo;ın token&amp;rsquo;ına karşı no-op haline gelir.&lt;/p&gt;
&lt;p&gt;Bunu tüketmek şöyle görünür:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-csharp" data-lang="csharp"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;await&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;foreach&lt;/span&gt; (&lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; number &lt;span style="color:#66d9ef"&gt;in&lt;/span&gt; GenerateNumbersAsync(&lt;span style="color:#ae81ff"&gt;1000&lt;/span&gt;, cts.Token))
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Console.WriteLine(number);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Burada &lt;em&gt;olmayan&lt;/em&gt; şeye dikkat edin: 1000 sayının tamamını tutan ara bir &lt;code&gt;List&amp;lt;int&amp;gt;&lt;/code&gt; yok. Her &lt;code&gt;number&lt;/code&gt; üretilir, tüketilir, ve bir sonraki üretilmeden önce garbage collect edilebilir.&lt;/p&gt;
&lt;h2 id="gerçek-hayat-örneği-ef-core-sorgu-sonuçlarını-streaming-yapmak"&gt;Gerçek Hayat Örneği: EF Core Sorgu Sonuçlarını Streaming Yapmak&lt;/h2&gt;
&lt;p&gt;Gece 2&amp;rsquo;de 4 milyon &lt;code&gt;Order&lt;/code&gt; satırını belleğe sayfalayan export job&amp;rsquo;a geri dönelim ve doğru şekilde düzeltelim. Entity Framework Core, &lt;code&gt;AsAsyncEnumerable()&lt;/code&gt; üzerinden &lt;code&gt;DbSet&amp;lt;T&amp;gt;&lt;/code&gt; ve &lt;code&gt;IQueryable&amp;lt;T&amp;gt;&lt;/code&gt; üzerinde doğrudan &lt;code&gt;IAsyncEnumerable&amp;lt;T&amp;gt;&lt;/code&gt; sunar, bu yüzden veritabanı sorguları için kendi &lt;code&gt;yield return&lt;/code&gt; wrapper&amp;rsquo;ınızı yazmanıza gerek yoktur:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-csharp" data-lang="csharp"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; IAsyncEnumerable&amp;lt;Order&amp;gt; StreamOrdersAsync(CancellationToken cancellationToken)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; _dbContext.Orders
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; .AsNoTracking()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; .OrderBy(o =&amp;gt; o.Id)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; .AsAsyncEnumerable();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;async&lt;/span&gt; Task ExportOrdersAsync(CancellationToken cancellationToken)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;await&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;foreach&lt;/span&gt; (&lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; order &lt;span style="color:#66d9ef"&gt;in&lt;/span&gt; StreamOrdersAsync(cancellationToken).WithCancellation(cancellationToken))
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;await&lt;/span&gt; _exportWriter.WriteLineAsync(order.ToCsvRow());
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Fark kodda ince ama runtime&amp;rsquo;da devasa. Sorgu tamamen çalışıp 4 milyon &lt;code&gt;Order&lt;/code&gt; satırını bir &lt;code&gt;List&amp;lt;Order&amp;gt;&lt;/code&gt;&amp;lsquo;a buffer&amp;rsquo;lamak yerine, EF Core altta yatan &lt;code&gt;DbDataReader&lt;/code&gt;&amp;lsquo;ı açık tutar ve &lt;code&gt;await foreach&lt;/code&gt; döngüsü istedikçe veritabanı bağlantısından satırları stream eder. Sorgunun kendisi için bellek kullanımı, tablo boyutundan bağımsız olarak kabaca sabit kalır—tüm sonuç kümesi için değil, her seferinde bir satır (artı küçük bir internal buffer) için ödeme yaparsınız.&lt;/p&gt;
&lt;p&gt;Burada gözden kaçması kolay iki detay önemli. Birincisi, &lt;code&gt;.AsNoTracking()&lt;/code&gt; dekoratif değildir—olmadan, EF Core&amp;rsquo;un change tracker&amp;rsquo;ı, &lt;code&gt;DbContext&lt;/code&gt;&amp;lsquo;in ömrü boyunca size verdiği her entity&amp;rsquo;nin bir snapshot&amp;rsquo;ını tutar, bu da sorgu kendisi stream ediyor olsa bile, ortadan kaldırmaya çalıştığınız tam bellek büyümesini sessizce geri getirir. İkincisi, yukarıdaki el yazımı &lt;code&gt;yield return&lt;/code&gt; örneğinin aksine, &lt;code&gt;AsAsyncEnumerable()&lt;/code&gt; kendi kodunuzda herhangi bir yerde &lt;code&gt;[EnumeratorCancellation]&lt;/code&gt; eklemenizi gerektirmez—EF Core, &lt;code&gt;WithCancellation&lt;/code&gt;&amp;lsquo;a geçirdiğiniz &lt;code&gt;CancellationToken&lt;/code&gt;&amp;lsquo;ı internal olarak kendi sorgu pipeline&amp;rsquo;ına bağlar. Attribute sadece &lt;code&gt;async IAsyncEnumerable&amp;lt;T&amp;gt;&lt;/code&gt; method&amp;rsquo;unu kendiniz yazdığınızda önem kazanır.&lt;/p&gt;
&lt;p&gt;Bu ayrıca ASP.NET Core ile de temiz bir şekilde compose olur. Bunu minimal bir API endpoint&amp;rsquo;i olarak sunarsanız, framework, tüm JSON array&amp;rsquo;ini önce server-side&amp;rsquo;da buffer&amp;rsquo;lamak yerine, öğeler hazır oldukça stream&amp;rsquo;i doğrudan response body&amp;rsquo;sine serialize eder:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-csharp" data-lang="csharp"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;app.MapGet(&lt;span style="color:#e6db74"&gt;&amp;#34;/orders/stream&amp;#34;&lt;/span&gt;, (AppDbContext dbContext, CancellationToken cancellationToken) =&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; dbContext.Orders
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; .AsNoTracking()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; .OrderBy(o =&amp;gt; o.Id)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; .AsAsyncEnumerable();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;});
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;.NET 6&amp;rsquo;dan beri, ASP.NET Core&amp;rsquo;un minimal API&amp;rsquo;leri &lt;code&gt;IAsyncEnumerable&amp;lt;T&amp;gt;&lt;/code&gt; dönüş tipini algılar ve altta yatan enumerable öğeleri ürettikçe JSON array&amp;rsquo;ini, tam listeyi return etmeden önce materialize etmenizi gerektirmek yerine, chunk chunk stream eder. Bilinmeye değer bir tuzak: eğer response compression middleware&amp;rsquo;i endpoint&amp;rsquo;in önünde duruyorsa, etkili bir şekilde sıkıştırmak için response&amp;rsquo;u internal olarak buffer&amp;rsquo;layabilir, bu da az önce kurduğunuz streaming davranışını sessizce boşa çıkarır—uçtan uca çalıştığını varsaymadan önce gerçek bir client ve &lt;code&gt;Transfer-Encoding: chunked&lt;/code&gt; ile kontrol etmeye değer.&lt;/p&gt;
&lt;h3 id="batching-üzerine-bir-not"&gt;Batching Üzerine Bir Not&lt;/h3&gt;
&lt;p&gt;EF Core&amp;rsquo;dan bir seferde bir satır stream etmek bellek dostudur, ama her satırı harici bir sisteme yazıyorsanız (bir HTTP call, bir message queue, ikinci bir veritabanı), bunu satır bazında yapmak, öğe başına overhead nedeniyle yavaş olabilir. Yaygın bir orta yol, stream&amp;rsquo;i chunk&amp;rsquo;lara batch&amp;rsquo;lemektir:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-csharp" data-lang="csharp"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;static&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;async&lt;/span&gt; IAsyncEnumerable&amp;lt;List&amp;lt;T&amp;gt;&amp;gt; ChunkAsync&amp;lt;T&amp;gt;(
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;this&lt;/span&gt; IAsyncEnumerable&amp;lt;T&amp;gt; source,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;int&lt;/span&gt; chunkSize,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt; [EnumeratorCancellation]&lt;/span&gt; CancellationToken cancellationToken = &lt;span style="color:#66d9ef"&gt;default&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; buffer = &lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; List&amp;lt;T&amp;gt;(chunkSize);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;await&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;foreach&lt;/span&gt; (&lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; item &lt;span style="color:#66d9ef"&gt;in&lt;/span&gt; source.WithCancellation(cancellationToken))
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; buffer.Add(item);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; (buffer.Count == chunkSize)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;yield&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; buffer;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; buffer = &lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; List&amp;lt;T&amp;gt;(chunkSize);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; (buffer.Count &amp;gt; &lt;span style="color:#ae81ff"&gt;0&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;yield&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; buffer;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Şöyle kullanılır:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-csharp" data-lang="csharp"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;await&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;foreach&lt;/span&gt; (&lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; batch &lt;span style="color:#66d9ef"&gt;in&lt;/span&gt; StreamOrdersAsync(cancellationToken).ChunkAsync(&lt;span style="color:#ae81ff"&gt;500&lt;/span&gt;, cancellationToken))
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;await&lt;/span&gt; _messageBus.PublishBatchAsync(batch);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Yine de belleği sınırlı tutarsınız—4 milyon yerine aynı anda uçuşta sadece 500 order—aynı zamanda downstream sistemin çağrı başına overhead&amp;rsquo;ini bir batch üzerinden amortize edersiniz.&lt;/p&gt;
&lt;h2 id="iasyncenumerable-doğru-araç-olmadığında"&gt;IAsyncEnumerable Doğru Araç Olmadığında&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;IAsyncEnumerable&amp;lt;T&amp;gt;&lt;/code&gt;, &lt;code&gt;List&amp;lt;T&amp;gt;&lt;/code&gt; ya da &lt;code&gt;IEnumerable&amp;lt;T&amp;gt;&lt;/code&gt; için evrensel bir replacement değildir. Şu durumlarda yanlış seçimdir:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Sayıya ihtiyacınız varsa, yükledikten sonra sort etmeniz gerekiyorsa, ya da random access&amp;rsquo;e ihtiyacınız varsa (örn. &lt;code&gt;orders[42]&lt;/code&gt;)—bunlar zaten veriyi materialize etmeyi gerektirir.&lt;/li&gt;
&lt;li&gt;Veri seti gerçekten küçük ve sınırlıysa (20 kategoriden oluşan bir dropdown listesi streaming semantiğine ihtiyaç duymaz).&lt;/li&gt;
&lt;li&gt;Aynı sequence&amp;rsquo;ı birden fazla kez iterate etmeniz gerekiyorsa—&lt;code&gt;IAsyncEnumerable&amp;lt;T&amp;gt;&lt;/code&gt; convention gereği tek geçişlidir; yeniden enumerate etmek genellikle altta yatan sorguyu yeniden çalıştırır.&lt;/li&gt;
&lt;li&gt;Zaten tüm koleksiyona ihtiyaç duyan in-memory dönüşümler yapıyorsanız (örn. median hesaplamak), bu durumda streaming, bellek kullanımını azaltmadan sadece &lt;code&gt;await&lt;/code&gt; overhead&amp;rsquo;i ekler.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Problemin şekli &amp;ldquo;potansiyel olarak büyük bir sequence&amp;rsquo;ı, hepsini bir anda tutmaya ihtiyaç duymadan, tek tek (ya da küçük batch&amp;rsquo;ler halinde) işlemek&amp;rdquo; olduğunda özellikle kullanın. Export&amp;rsquo;lar, ETL pipeline&amp;rsquo;ları, sayfalanmış API sonuçları, log işleme, ve büyük rapor üretimi klasik durumlardır.&lt;/p&gt;
&lt;h2 id="özet"&gt;Özet&lt;/h2&gt;
&lt;p&gt;Bu araştırmayı başlatan &lt;code&gt;OutOfMemoryException&lt;/code&gt;, geleneksel anlamda bir bug değildi—kod, &lt;code&gt;ToListAsync()&lt;/code&gt;&amp;lsquo;in vaat ettiği şeyi tam olarak yapıyordu. Gerçek çözüm daha akıllı bir allocation stratejisi ya da daha büyük bir container bellek limiti değildi; job&amp;rsquo;un aslında hiçbir zaman bellekte duran bir &lt;code&gt;List&amp;lt;Order&amp;gt;&lt;/code&gt;&amp;lsquo;a ihtiyaç duymadığını fark etmekti. İhtiyacı olan bir stream&amp;rsquo;di. O export job&amp;rsquo;ında &lt;code&gt;ToListAsync()&lt;/code&gt;&amp;lsquo;i &lt;code&gt;AsAsyncEnumerable()&lt;/code&gt; ile değiştirdiğimizde, aynı 4 milyon satırlık tablo düz, öngörülebilir bir bellek kullanımıyla işlendi, ve &lt;code&gt;OutOfMemoryException&lt;/code&gt; bir daha geri gelmedi.&lt;/p&gt;
&lt;p&gt;Büyük veri setlerini &lt;code&gt;IAsyncEnumerable&amp;lt;T&amp;gt;&lt;/code&gt; ile stream etmek her sorguyu daha hızlı yapmaz, ve karmaşıklık ekler—cancellation token&amp;rsquo;ları, &lt;code&gt;[EnumeratorCancellation]&lt;/code&gt;, ve random access ile tekrarlanabilir iterasyonun kaybı bedava değildir. Ama probleminiz gerçekten &amp;ldquo;çok fazla veri okuyup sıralı olarak işlemek&amp;rdquo; olduğunda, &lt;code&gt;IAsyncEnumerable&amp;lt;T&amp;gt;&lt;/code&gt;, sınırsız bir bellek problemini sabit bir probleme dönüştürür. Bir dahaki sefere, beklediğinizden daha fazla satır döndürebilecek bir sorguda alışkanlıktan &lt;code&gt;.ToListAsync()&lt;/code&gt; yazdığınızda, gerçekten listeye mi ihtiyacınız var, yoksa sadece bir sonraki öğeye mi ihtiyacınız var diye durup sormaya değer.&lt;/p&gt;
&lt;h2 id="sonuç"&gt;Sonuç&lt;/h2&gt;
&lt;p&gt;Okuduğunuz için teşekkürler! 🎉 Yazılım geliştirme alanındaki araştırmalarımı kaçırmamak için &lt;a href="https://x.com/berkslv"&gt;@berkslv&lt;/a&gt; adresinden takipte kalabilirsiniz.
&lt;/content&gt;&lt;/p&gt;</content></item><item><title>.NET'te Idempotency Key Pattern: Ödeme API'niz İki Kere Çağrılırsa Ne Olur?</title><link>https://berkselvi.dev/tr/posts/the-idempotency-key-pattern-in-dotnet-what-happens-when-your-payment-api-gets-called-twice/</link><pubDate>Wed, 22 Jul 2026 00:00:00 +0300</pubDate><guid>https://berkselvi.dev/tr/posts/the-idempotency-key-pattern-in-dotnet-what-happens-when-your-payment-api-gets-called-twice/</guid><description>&lt;p&gt;Müşteri &amp;ldquo;Öde&amp;rdquo; butonuna basıyor. İstek API&amp;rsquo;nize düşüyor, ödeme sağlayıcınız kartı çekiyor ve sonra&amp;hellip; response timeout&amp;rsquo;a düşüyor. Mobil uygulama ödemenin başarılı olup olmadığını bilmiyor, bu yüzden bildiği tek şeyi yapıyor: retry atıyor. Şimdi tek bir sipariş için iki başarılı çekim, sinirli bir müşteri ve &amp;ldquo;iki kere para çekildi&amp;rdquo; diye başlayan bir destek talebiniz var.&lt;/p&gt;
&lt;p&gt;Parayla, kuyruklarla veya üçüncü parti API&amp;rsquo;lerle ilgilenen herhangi bir şey geliştirdiyseniz, bu senaryonun hipotetik olmadığını zaten biliyorsunuzdur. Ağ kesintileri, client timeout&amp;rsquo;ları ve load balancer retry&amp;rsquo;ları distributed system&amp;rsquo;lerin gerçeği. &lt;strong&gt;Idempotency key pattern&lt;/strong&gt;, retry sayısı ne olursa olsun aynı mantıksal isteğin yalnızca bir kez uygulanmasını garanti eden, production seviyesindeki ödeme API&amp;rsquo;lerinin kullandığı yöntem. Bu yazıda bunun neden önemli olduğunu, önlem alınmazsa genelde nelerin ters gittiğini ve .NET API&amp;rsquo;de nasıl doğru şekilde uygulanacağını anlatacağım.&lt;/p&gt;</description><content>&lt;p&gt;Müşteri &amp;ldquo;Öde&amp;rdquo; butonuna basıyor. İstek API&amp;rsquo;nize düşüyor, ödeme sağlayıcınız kartı çekiyor ve sonra&amp;hellip; response timeout&amp;rsquo;a düşüyor. Mobil uygulama ödemenin başarılı olup olmadığını bilmiyor, bu yüzden bildiği tek şeyi yapıyor: retry atıyor. Şimdi tek bir sipariş için iki başarılı çekim, sinirli bir müşteri ve &amp;ldquo;iki kere para çekildi&amp;rdquo; diye başlayan bir destek talebiniz var.&lt;/p&gt;
&lt;p&gt;Parayla, kuyruklarla veya üçüncü parti API&amp;rsquo;lerle ilgilenen herhangi bir şey geliştirdiyseniz, bu senaryonun hipotetik olmadığını zaten biliyorsunuzdur. Ağ kesintileri, client timeout&amp;rsquo;ları ve load balancer retry&amp;rsquo;ları distributed system&amp;rsquo;lerin gerçeği. &lt;strong&gt;Idempotency key pattern&lt;/strong&gt;, retry sayısı ne olursa olsun aynı mantıksal isteğin yalnızca bir kez uygulanmasını garanti eden, production seviyesindeki ödeme API&amp;rsquo;lerinin kullandığı yöntem. Bu yazıda bunun neden önemli olduğunu, önlem alınmazsa genelde nelerin ters gittiğini ve .NET API&amp;rsquo;de nasıl doğru şekilde uygulanacağını anlatacağım.&lt;/p&gt;
&lt;h2 id="problem-at-least-once-delivery-varsayılan-davranıştır"&gt;Problem: &amp;ldquo;At least once delivery&amp;rdquo; Varsayılan Davranıştır&lt;/h2&gt;
&lt;p&gt;Rahatsız edici bir gerçek var: güvenilir olmayan bir ağ üzerinden bir isteğin &amp;ldquo;tam olarak bir kez&amp;rdquo; teslim edildiğini garanti edemezsiniz. TCP bağlantıları response ortasında kopabilir, HTTP client&amp;rsquo;lar timeout&amp;rsquo;a düşebilir, load balancer&amp;rsquo;lar veya mobil uygulamalar 5xx hatalarında ve timeout&amp;rsquo;larda retry atabilir. Gerçekçi olarak inşa edebileceğiniz en iyi şey &lt;strong&gt;en az bir kez teslimat&lt;/strong&gt; (at-least-once delivery), yani endpoint&amp;rsquo;iniz tamamen aynı mantıksal isteği iki, üç, hatta on kez alabilir.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;GET /orders/123&lt;/code&gt; isteği için bu zararsız. Veriyi iki kez okumak hiçbir şeyi değiştirmez. Ama &lt;code&gt;POST /payments/charge&lt;/code&gt; için, bu retry&amp;rsquo;ların her biri yepyeni bir çekim tetikleyebilir, çünkü server açısından bu, yeni ve alakasız bir istek gibi görünür.&lt;/p&gt;
&lt;h2 id="eski-yöntem-mükerrer-isteklere-karşı-hiçbir-koruma-yok"&gt;Eski Yöntem: Mükerrer İsteklere Karşı Hiçbir Koruma Yok&lt;/h2&gt;
&lt;p&gt;Çoğu ekip hiçbir koruma olmadan başlar. Tipik, naif bir ödeme endpoint&amp;rsquo;i şöyle görünür:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-csharp" data-lang="csharp"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;[HttpPost(&amp;#34;charge&amp;#34;)]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;async&lt;/span&gt; Task&amp;lt;IActionResult&amp;gt; ChargeCard([FromBody] ChargeRequest request)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; result = &lt;span style="color:#66d9ef"&gt;await&lt;/span&gt; _paymentGateway.ChargeAsync(request.CardToken, request.Amount);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;await&lt;/span&gt; _orderRepository.MarkAsPaidAsync(request.OrderId, result.TransactionId);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; Ok(result);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Bu, happy path&amp;rsquo;te gayet iyi çalışır. Ama client, aslında başarılı olmak üzere olan bir response&amp;rsquo;u beklerken timeout&amp;rsquo;a düşerse neler olduğuna bakalım:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Client, &lt;code&gt;#4711&lt;/code&gt; numaralı sipariş için &lt;code&gt;POST /payments/charge&lt;/code&gt; isteği gönderir.&lt;/li&gt;
&lt;li&gt;Server payment gateway&amp;rsquo;i çağırır, kart başarıyla çekilir.&lt;/li&gt;
&lt;li&gt;Server response dönmeden önce, client&amp;rsquo;ın HTTP timeout&amp;rsquo;u devreye girer.&lt;/li&gt;
&lt;li&gt;Client, standart retry mantığını izleyerek aynı isteği tekrar gönderir.&lt;/li&gt;
&lt;li&gt;Server ilk denemeyi hiç hatırlamadığı için kartı ikinci kez çeker.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Bunu &amp;ldquo;timeout olmasın yeter&amp;rdquo; diyerek çözemezsiniz, çünkü timeout&amp;rsquo;lar, kopan bağlantılar ve process crash&amp;rsquo;leri &lt;strong&gt;microservices&lt;/strong&gt; ve diğer &lt;strong&gt;distributed system&lt;/strong&gt;&amp;rsquo;lerde ağ üzerinden çalışan bir servisi işletmenin normal bir parçası. Çözüm retry&amp;rsquo;ları önlemek değil, doğru tasarlanmış bir &lt;strong&gt;idempotent API&lt;/strong&gt; ile retry&amp;rsquo;ları güvenli hale getirmek.&lt;/p&gt;
&lt;h2 id="neden-idempotency-key"&gt;Neden Idempotency Key?&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Idempotency key&lt;/strong&gt;, genellikle client tarafında üretilen bir UUID olan, çağıranın isteğe eklediği benzersiz bir tanımlayıcıdır. API&amp;rsquo;nize şunu söyler: &amp;ldquo;bu belirli deneme, kaç kere görürsen gör, tek bir mantıksal işlemi temsil ediyor.&amp;rdquo; Server&amp;rsquo;ın görevi şudur:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;İlk başarılı isteğin sonucunu bu key&amp;rsquo;e karşılık saklamak.&lt;/li&gt;
&lt;li&gt;Aynı key&amp;rsquo;i taşıyan herhangi bir mükerrer istekte, işlemi yeniden çalıştırmak yerine &lt;strong&gt;saklanan sonucu&lt;/strong&gt; döndürmek.&lt;/li&gt;
&lt;li&gt;Aynı key&amp;rsquo;i farklı bir payload ile yeniden kullanan istekleri reddetmek (client&amp;rsquo;ın key&amp;rsquo;i yanlış kullandığının bir işareti).&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="bir-apiyi-gerçekten-idempotent-yapan-nedir"&gt;Bir API&amp;rsquo;yi Gerçekten Idempotent Yapan Nedir&lt;/h3&gt;
&lt;p&gt;Sıradan bir endpoint&amp;rsquo;i idempotent bir API&amp;rsquo;ye dönüştürmek, client kaç kere aynı key ile çağırırsa çağırsın, endpoint&amp;rsquo;in şu üç özelliği sağlaması demektir:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Side effect&amp;rsquo;lerin tek seferde çalışması:&lt;/strong&gt; payment gateway, envanter sistemi veya veritabanı yazma işlemi yalnızca bir kez gerçekleşir.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Tutarlı response:&lt;/strong&gt; her retry, orijinal denemeyle tamamen aynı response body ve status code&amp;rsquo;u geri alır.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Sınırlı yaşam süresi:&lt;/strong&gt; key belirli bir süre sonra expire olur, böylece store sonsuza kadar büyümez ve bir client&amp;rsquo;ın yanlışlıkla yeniden kullandığı bir key yüzünden meşru yeni istekler engellenmez.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Gezegendeki en çok retry atılan API&amp;rsquo;lerden biri olan Stripe, &lt;a href="https://stripe.com/docs/api/idempotent_requests"&gt;tam olarak bu pattern&amp;rsquo;i dokümante ediyor&lt;/a&gt; ve durum değiştiren tüm isteklerde bir &lt;code&gt;Idempotency-Key&lt;/code&gt; header&amp;rsquo;ı zorunlu kılıyor. Bu bir &amp;ldquo;olsa iyi olur&amp;rdquo; özelliği değil; retry ağırlıklı SDK&amp;rsquo;larını bir ödeme API&amp;rsquo;sine karşı güvenle kullanılabilir kılan temel yapı.&lt;/p&gt;
&lt;h2 id="idempotency-storeu-tasarlamak"&gt;Idempotency Store&amp;rsquo;u Tasarlamak&lt;/h2&gt;
&lt;p&gt;Herhangi bir middleware yazmadan önce, idempotency kayıtlarını saklayacak bir yere ihtiyacınız var. Basit bir relational tablo gayet iyi iş görür:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-sql" data-lang="sql"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;CREATE&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;TABLE&lt;/span&gt; IdempotencyKeys (
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;Key&lt;/span&gt; VARCHAR(&lt;span style="color:#ae81ff"&gt;64&lt;/span&gt;) &lt;span style="color:#66d9ef"&gt;NOT&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;NULL&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;PRIMARY&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;KEY&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; RequestHash VARCHAR(&lt;span style="color:#ae81ff"&gt;64&lt;/span&gt;) &lt;span style="color:#66d9ef"&gt;NOT&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;NULL&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ResponseStatus INT &lt;span style="color:#66d9ef"&gt;NULL&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ResponseBody TEXT &lt;span style="color:#66d9ef"&gt;NULL&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Status VARCHAR(&lt;span style="color:#ae81ff"&gt;20&lt;/span&gt;) &lt;span style="color:#66d9ef"&gt;NOT&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;NULL&lt;/span&gt;, &lt;span style="color:#75715e"&gt;-- &amp;#39;Processing&amp;#39; | &amp;#39;Completed&amp;#39;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; CreatedAt &lt;span style="color:#66d9ef"&gt;TIMESTAMP&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;NOT&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;NULL&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ExpiresAt &lt;span style="color:#66d9ef"&gt;TIMESTAMP&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;NOT&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;NULL&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Burada önemli olan birkaç tasarım kararı var:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;RequestHash&lt;/code&gt;&lt;/strong&gt;: request body&amp;rsquo;nin hash&amp;rsquo;i, böylece bir client&amp;rsquo;ın aynı key&amp;rsquo;i &lt;em&gt;farklı&lt;/em&gt; bir payload için yeniden kullanıp kullanmadığını tespit edebilirsiniz (bu bir client bug&amp;rsquo;ı ve saklanan yanlış response&amp;rsquo;u sessizce döndürmek yerine &lt;code&gt;422&lt;/code&gt; dönmelisiniz).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;Status&lt;/code&gt;&lt;/strong&gt;: sadece &amp;ldquo;Completed&amp;rdquo; değil, bir &amp;ldquo;Processing&amp;rdquo; state&amp;rsquo;ine de ihtiyacınız var, böylece ikinci, eşzamanlı bir istek ilkinin hâlâ devam ettiğini anlayabilir. Mükerrer çekime karşı asıl garanti, veritabanının &lt;code&gt;Key&lt;/code&gt; üzerindeki unique constraint&amp;rsquo;inin ikinci insert&amp;rsquo;i reddetmesinden gelir, sadece &lt;code&gt;Status&lt;/code&gt; kolonundan değil — bunu aşağıdaki store implementasyonunda göreceksiniz.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;ExpiresAt&lt;/code&gt;&lt;/strong&gt;: idempotency key&amp;rsquo;ler sonsuza kadar yaşamamalı. Stripe bunları 24 saat sonra expire ediyor; client&amp;rsquo;larınızın gerçekçi olarak ne kadar süre retry attığına uyan bir pencere seçin ve &lt;code&gt;ExpiresAt&lt;/code&gt;&amp;lsquo;i geçmiş satırları silen bir background cleanup job&amp;rsquo;ı çalıştırın, böylece tablo sınırsızca büyümesin.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Bu şemanın Postgres tarzı SQL (&lt;code&gt;ON CONFLICT&lt;/code&gt;) kullandığını unutmayın. SQL Server kullanıyorsanız, karşılığı &lt;code&gt;MERGE&lt;/code&gt; veya &lt;code&gt;Key&lt;/code&gt; üzerinde unique index ile birleştirilmiş bir &lt;code&gt;IF NOT EXISTS&lt;/code&gt; guard&amp;rsquo;ıdır — concurrency güvenliği prensibi aynı, sadece syntax farklı.&lt;/p&gt;
&lt;h2 id="nette-uygulamak"&gt;.NET&amp;rsquo;te Uygulamak&lt;/h2&gt;
&lt;p&gt;Bunu bir &lt;code&gt;ActionFilter&lt;/code&gt; olarak inşa edelim, böylece business logic&amp;rsquo;i kirletmeden idempotency korumasına ihtiyaç duyan herhangi bir endpoint&amp;rsquo;e deklaratif olarak uygulanabilir.&lt;/p&gt;
&lt;h3 id="1-idempotency-store"&gt;1. Idempotency Store&lt;/h3&gt;
&lt;p&gt;Bu örnek raw SQL erişimi için &lt;a href="https://github.com/DapperLib/Dapper"&gt;Dapper&lt;/a&gt; kullanıyor, önce onu ekleyin:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;dotnet add package Dapper
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-csharp" data-lang="csharp"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;interface&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;IIdempotencyStore&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Task&amp;lt;IdempotencyRecord?&amp;gt; GetAsync(&lt;span style="color:#66d9ef"&gt;string&lt;/span&gt; key, CancellationToken ct);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Task&amp;lt;&lt;span style="color:#66d9ef"&gt;bool&lt;/span&gt;&amp;gt; TryStartProcessingAsync(&lt;span style="color:#66d9ef"&gt;string&lt;/span&gt; key, &lt;span style="color:#66d9ef"&gt;string&lt;/span&gt; requestHash, CancellationToken ct);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Task CompleteAsync(&lt;span style="color:#66d9ef"&gt;string&lt;/span&gt; key, &lt;span style="color:#66d9ef"&gt;int&lt;/span&gt; statusCode, &lt;span style="color:#66d9ef"&gt;string&lt;/span&gt; responseBody, CancellationToken ct);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;record&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;IdempotencyRecord&lt;/span&gt;(&lt;span style="color:#66d9ef"&gt;string&lt;/span&gt; RequestHash, &lt;span style="color:#66d9ef"&gt;string&lt;/span&gt; Status, &lt;span style="color:#66d9ef"&gt;int?&lt;/span&gt; ResponseStatus, &lt;span style="color:#66d9ef"&gt;string?&lt;/span&gt; ResponseBody);
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;TryStartProcessingAsync&lt;/code&gt; metodu kritik parça. Bu, atomik bir &amp;ldquo;yoksa insert et&amp;rdquo; işlemi olmalı, aksi halde aynı key&amp;rsquo;e sahip iki eşzamanlı istek de kontrolü geçip ikisi de kartı çekmeye devam edebilir:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-csharp" data-lang="csharp"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;class&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;SqlIdempotencyStore&lt;/span&gt; : IIdempotencyStore
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;private&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;readonly&lt;/span&gt; IDbConnection _db;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; SqlIdempotencyStore(IDbConnection db) =&amp;gt; _db = db;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;async&lt;/span&gt; Task&amp;lt;&lt;span style="color:#66d9ef"&gt;bool&lt;/span&gt;&amp;gt; TryStartProcessingAsync(&lt;span style="color:#66d9ef"&gt;string&lt;/span&gt; key, &lt;span style="color:#66d9ef"&gt;string&lt;/span&gt; requestHash, CancellationToken ct)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// Relies on the PRIMARY KEY constraint on `Key` to fail on duplicates&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;const&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;string&lt;/span&gt; sql = &lt;span style="color:#e6db74"&gt;@&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt; INSERT INTO IdempotencyKeys (Key, RequestHash, Status, CreatedAt, ExpiresAt)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt; VALUES (@Key, @RequestHash, &amp;#39;Processing&amp;#39;, @Now, @Expires)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt; ON CONFLICT (Key) DO NOTHING
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt; RETURNING Key;&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; inserted = &lt;span style="color:#66d9ef"&gt;await&lt;/span&gt; _db.QueryFirstOrDefaultAsync&amp;lt;&lt;span style="color:#66d9ef"&gt;string&lt;/span&gt;&amp;gt;(sql, &lt;span style="color:#66d9ef"&gt;new&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Key = key,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; RequestHash = requestHash,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Now = DateTime.UtcNow,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Expires = DateTime.UtcNow.AddHours(&lt;span style="color:#ae81ff"&gt;24&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; });
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; inserted != &lt;span style="color:#66d9ef"&gt;null&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;async&lt;/span&gt; Task&amp;lt;IdempotencyRecord?&amp;gt; GetAsync(&lt;span style="color:#66d9ef"&gt;string&lt;/span&gt; key, CancellationToken ct)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;const&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;string&lt;/span&gt; sql = &lt;span style="color:#e6db74"&gt;&amp;#34;SELECT RequestHash, Status, ResponseStatus, ResponseBody FROM IdempotencyKeys WHERE Key = @Key&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;await&lt;/span&gt; _db.QueryFirstOrDefaultAsync&amp;lt;IdempotencyRecord&amp;gt;(sql, &lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; { Key = key });
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;async&lt;/span&gt; Task CompleteAsync(&lt;span style="color:#66d9ef"&gt;string&lt;/span&gt; key, &lt;span style="color:#66d9ef"&gt;int&lt;/span&gt; statusCode, &lt;span style="color:#66d9ef"&gt;string&lt;/span&gt; responseBody, CancellationToken ct)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;const&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;string&lt;/span&gt; sql = &lt;span style="color:#e6db74"&gt;@&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt; UPDATE IdempotencyKeys
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt; SET Status = &amp;#39;Completed&amp;#39;, ResponseStatus = @Status, ResponseBody = @Body
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt; WHERE Key = @Key&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;await&lt;/span&gt; _db.ExecuteAsync(sql, &lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; { Key = key, Status = statusCode, Body = responseBody });
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;ON CONFLICT (Key) DO NOTHING ... RETURNING Key&lt;/code&gt; numarası, veritabanının unique constraint&amp;rsquo;inin concurrency kontrolünü sizin yerinize yapmasını sağlar, böylece application seviyesinde lock&amp;rsquo;lara ihtiyacınız kalmaz.&lt;/p&gt;
&lt;h3 id="2-action-filter"&gt;2. Action Filter&lt;/h3&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-csharp" data-lang="csharp"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;class&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;IdempotentAttribute&lt;/span&gt; : Attribute, IAsyncActionFilter
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;async&lt;/span&gt; Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; httpContext = context.HttpContext;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// Let model validation fail first - an invalid request should never consume a key.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; (!context.ModelState.IsValid)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;await&lt;/span&gt; next();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; (!httpContext.Request.Headers.TryGetValue(&lt;span style="color:#e6db74"&gt;&amp;#34;Idempotency-Key&amp;#34;&lt;/span&gt;, &lt;span style="color:#66d9ef"&gt;out&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; keyHeader) ||
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;string&lt;/span&gt;.IsNullOrWhiteSpace(keyHeader) || keyHeader.ToString().Length &amp;gt; &lt;span style="color:#ae81ff"&gt;64&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; context.Result = &lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; BadRequestObjectResult(&lt;span style="color:#e6db74"&gt;&amp;#34;A valid Idempotency-Key header (max 64 chars) is required.&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; store = httpContext.RequestServices.GetRequiredService&amp;lt;IIdempotencyStore&amp;gt;();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; key = keyHeader.ToString();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; requestHash = &lt;span style="color:#66d9ef"&gt;await&lt;/span&gt; ComputeRequestHashAsync(httpContext.Request);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; existing = &lt;span style="color:#66d9ef"&gt;await&lt;/span&gt; store.GetAsync(key, httpContext.RequestAborted);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; (existing &lt;span style="color:#66d9ef"&gt;is&lt;/span&gt; not &lt;span style="color:#66d9ef"&gt;null&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; (existing.RequestHash != requestHash)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; context.Result = &lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; UnprocessableEntityObjectResult(
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;Idempotency-Key was reused with a different request payload.&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; (existing.Status == &lt;span style="color:#e6db74"&gt;&amp;#34;Completed&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; context.Result = &lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; ContentResult
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; StatusCode = existing.ResponseStatus,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Content = existing.ResponseBody,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ContentType = &lt;span style="color:#e6db74"&gt;&amp;#34;application/json&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; };
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// Still processing - a concurrent duplicate arrived while the first one is in flight.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; context.Result = &lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; ConflictObjectResult(&lt;span style="color:#e6db74"&gt;&amp;#34;Request with this Idempotency-Key is already being processed.&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; acquired = &lt;span style="color:#66d9ef"&gt;await&lt;/span&gt; store.TryStartProcessingAsync(key, requestHash, httpContext.RequestAborted);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; (!acquired)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// Lost the race to a concurrent request that inserted first.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; context.Result = &lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; ConflictObjectResult(&lt;span style="color:#e6db74"&gt;&amp;#34;Request with this Idempotency-Key is already being processed.&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; executedContext = &lt;span style="color:#66d9ef"&gt;await&lt;/span&gt; next();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// Persist whatever the action produced so retries get an identical replay.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// Anything other than ObjectResult (exceptions, StatusCodeResult, etc.) should&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// still release the key - otherwise it stays stuck in &amp;#34;Processing&amp;#34; until it expires.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; (executedContext.Result &lt;span style="color:#66d9ef"&gt;is&lt;/span&gt; ObjectResult objectResult)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; statusCode = objectResult.StatusCode ?? &lt;span style="color:#ae81ff"&gt;200&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; body = JsonSerializer.Serialize(objectResult.Value, httpContext.RequestServices
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; .GetRequiredService&amp;lt;IOptions&amp;lt;JsonOptions&amp;gt;&amp;gt;().Value.JsonSerializerOptions);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;await&lt;/span&gt; store.CompleteAsync(key, statusCode, body, httpContext.RequestAborted);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;else&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; statusCode = executedContext.Exception &lt;span style="color:#66d9ef"&gt;is&lt;/span&gt; not &lt;span style="color:#66d9ef"&gt;null&lt;/span&gt; ? &lt;span style="color:#ae81ff"&gt;500&lt;/span&gt; : (httpContext.Response.StatusCode);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;await&lt;/span&gt; store.CompleteAsync(key, statusCode, &lt;span style="color:#66d9ef"&gt;string&lt;/span&gt;.Empty, httpContext.RequestAborted);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;private&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;static&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;async&lt;/span&gt; Task&amp;lt;&lt;span style="color:#66d9ef"&gt;string&lt;/span&gt;&amp;gt; ComputeRequestHashAsync(HttpRequest request)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; request.EnableBuffering();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;using&lt;/span&gt; var reader = &lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; StreamReader(request.Body, leaveOpen: &lt;span style="color:#66d9ef"&gt;true&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; body = &lt;span style="color:#66d9ef"&gt;await&lt;/span&gt; reader.ReadToEndAsync();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; request.Body.Position = &lt;span style="color:#ae81ff"&gt;0&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; bytes = Encoding.UTF8.GetBytes(body);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; hash = SHA256.HashData(bytes);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; Convert.ToHexString(hash);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Değinilmesi gereken iki detay var: &lt;code&gt;JsonSerializerOptions&lt;/code&gt;, uygulamanın gerçek &lt;code&gt;AddJsonOptions&lt;/code&gt; konfigürasyonundan &lt;code&gt;IOptions&amp;lt;JsonOptions&amp;gt;&lt;/code&gt; üzerinden alınıyor, böylece replay edilen body, client&amp;rsquo;ın zaten beklediği casing ve converter&amp;rsquo;larla eşleşiyor — burada default serializer&amp;rsquo;ı kullanmak sessizce orijinal response&amp;rsquo;tan farklı bir payload üretirdi. Ve hatalar dahil her code path artık &lt;code&gt;CompleteAsync&lt;/code&gt;&amp;lsquo;i çağırıyor, böylece bir key hiçbir zaman kalıcı olarak &lt;code&gt;&amp;quot;Processing&amp;quot;&lt;/code&gt;&amp;lsquo;de takılı kalmıyor; bunu, bir şekilde yine de takılı kalan herhangi bir kaydın otomatik olarak geri alınması için &lt;code&gt;ExpiresAt&lt;/code&gt; cleanup job&amp;rsquo;ı ile birleştirin.&lt;/p&gt;
&lt;h3 id="3-idempotent-apiyi-endpointe-uygulamak"&gt;3. Idempotent API&amp;rsquo;yi Endpoint&amp;rsquo;e Uygulamak&lt;/h3&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-csharp" data-lang="csharp"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;[HttpPost(&amp;#34;charge&amp;#34;)]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;[Idempotent]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;async&lt;/span&gt; Task&amp;lt;IActionResult&amp;gt; ChargeCard([FromBody] ChargeRequest request)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; result = &lt;span style="color:#66d9ef"&gt;await&lt;/span&gt; _paymentGateway.ChargeAsync(request.CardToken, request.Amount);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;await&lt;/span&gt; _orderRepository.MarkAsPaidAsync(request.OrderId, result.TransactionId);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; Ok(result);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Artık client, her mantıksal ödeme denemesi için bir UUID üretmekten ve aynı denemenin her retry&amp;rsquo;ında bunu &lt;code&gt;Idempotency-Key&lt;/code&gt; header&amp;rsquo;ı olarak göndermekten sorumlu:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;$ curl -X POST https://api.example.com/payments/charge &lt;span style="color:#ae81ff"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; -H &lt;span style="color:#e6db74"&gt;&amp;#34;Idempotency-Key: 8f14e45f-ceea-467e-bb9d-8f7c1a1c1b1a&amp;#34;&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; -H &lt;span style="color:#e6db74"&gt;&amp;#34;Content-Type: application/json&amp;#34;&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; -d &lt;span style="color:#e6db74"&gt;&amp;#39;{&amp;#34;orderId&amp;#34;:&amp;#34;4711&amp;#34;,&amp;#34;cardToken&amp;#34;:&amp;#34;tok_visa&amp;#34;,&amp;#34;amount&amp;#34;:4999}&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Response gelmeden ağ koparsa ve client aynı header ile retry atarsa, ikinci istek payment gateway&amp;rsquo;e bir daha hiç dokunmadan ilk denemede saklanan sonucu döndürür.&lt;/p&gt;
&lt;h2 id="bunun-çözmediği-şeyler"&gt;Bunun Çözmediği Şeyler&lt;/h2&gt;
&lt;p&gt;Idempotency key&amp;rsquo;ler sihirli bir değnek değil, sınırlarını açıkça belirtmekte fayda var:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Outbox pattern&amp;rsquo;inin yerini tutmaz.&lt;/strong&gt; Charge handler&amp;rsquo;ınız aynı zamanda bir &lt;code&gt;OrderPaidEvent&lt;/code&gt; yayınlaması gerekiyorsa, klasik dual-write problemini önlemek için o event için hâlâ transactional garantilere ihtiyacınız var.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Client işbirliği gerektirir.&lt;/strong&gt; Mobil client&amp;rsquo;ınız aynı UUID&amp;rsquo;yi yeniden kullanmak yerine her retry&amp;rsquo;da &lt;em&gt;yeni&lt;/em&gt; bir UUID üretirse, tüm pattern boşa çıkar. Bunu API kontratınızda açıkça dokümante edin ve key&amp;rsquo;i her HTTP denemesinde değil, kullanıcı &amp;ldquo;Öde&amp;quot;ye bastığı anda client tarafında üretmeyi düşünün.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Her istekte bir yazma işlemi ekler.&lt;/strong&gt; Aşırı yüksek throughput&amp;rsquo;lu, düşük değerli endpoint&amp;rsquo;ler için ekstra veritabanı round-trip&amp;rsquo;i buna değmeyebilir. Bu pattern&amp;rsquo;i, mükerrerliğin gerçekten pahalıya mal olduğu işlemler için saklayın: ödemeler, sipariş oluşturma ve para veya envanter hareket ettiren her şey.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Expiration bir trade-off.&lt;/strong&gt; Çok kısa olursa, meşru ama yavaş retry&amp;rsquo;lar pencerenin dışında kalır ve yine de mükerrer çekim olur. Çok uzun olursa, sürekli büyüyen bir ölü kayıt tablosu biriktirirsiniz. Expire olmuş key&amp;rsquo;leri temizleyen bir background cleanup job&amp;rsquo;ı ilk günden inşa edilmeye değer.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="özet"&gt;Özet&lt;/h2&gt;
&lt;p&gt;Retry&amp;rsquo;lar distributed system&amp;rsquo;lerde bir edge case değil; payment gateway&amp;rsquo;lerle, webhook&amp;rsquo;larla ve birbiriyle konuşan microservices&amp;rsquo;lerin normal işleyiş koşuludur. Idempotency key pattern retry&amp;rsquo;ları önlemeye çalışmaz, &amp;ldquo;bu ödeme çekildi mi?&amp;rdquo; sorusunu bir tahmin yerine bir lookup&amp;rsquo;a çevirerek onları zararsız hale getirir. Yazının başındaki, iki kere ücretlendirilip ücretlendirilmediğini merak eden o müşteriyi hatırlayın? Idempotent bir API yerinde olsaydı, o retry sessizce tamamen aynı başarı response&amp;rsquo;unu döndürürdü ve müşteri bir ağ sorunu yaşandığından haberdar bile olmazdı. Bir ödeme API&amp;rsquo;si, bir webhook receiver&amp;rsquo;ı veya mükerrer çalışmanın gerçek bir maliyeti olan başka bir şey inşa ediyorsanız, idempotency key&amp;rsquo;leri sonradan eklenen bir yama değil, birinci sınıf bir gereksinim olarak ele alın.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="sonuç"&gt;Sonuç&lt;/h2&gt;
&lt;p&gt;Okuduğunuz için teşekkürler! 🎉 Yazılım geliştirme alanındaki araştırmalarımı kaçırmamak için &lt;a href="https://x.com/berkslv"&gt;@berkslv&lt;/a&gt; adresinden takipte kalabilirsiniz.&lt;/p&gt;</content></item><item><title>Yazılım Mimarisinde Events vs Commands: Kapsamlı Bir .NET Rehberi</title><link>https://berkselvi.dev/tr/posts/events-vs-commands-in-software-architecture-a-complete-dotnet-guide/</link><pubDate>Mon, 22 Jun 2026 14:55:00 +0300</pubDate><guid>https://berkselvi.dev/tr/posts/events-vs-commands-in-software-architecture-a-complete-dotnet-guide/</guid><description>&lt;p&gt;Modern yazılım mühendisliği dünyasında, monolitik sistemlerden dağıtık &lt;strong&gt;microservices&lt;/strong&gt; mimarilerine geçiş yaygın bir evrimsel yoldur. Sistemler karmaşıklaştıkça, farklı bileşenlerin, modüllerin ve servislerin iletişim kurma şekli; sistemin dayanıklılığını, ölçeklenebilirliğini ve sürdürülebilirliğini belirleyen kritik faktör haline gelir. Eğer sağlam, cloud-native uygulamalar geliştiriyorsanız, bileşenlerinizi birbirinden ayırma (decouple) ihtiyacıyla kaçınılmaz olarak karşılaşırsınız.&lt;/p&gt;
&lt;p&gt;Bu iletişim ve decoupling stratejisinin merkezinde iki temel mesajlaşma deseni (pattern) yatar: &lt;strong&gt;Commands&lt;/strong&gt; ve &lt;strong&gt;Events&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;İlk bakışta birbirlerine benzeseler de —her ikisi de özünde sistemin bir parçasından diğerine gönderilen ve veri içeren mesajlardır— amaçları, işlenme şekilleri (handling) ve mimari etkileri birbirinden çok farklıdır. &lt;strong&gt;Software architecture içinde events vs commands&lt;/strong&gt; ayrımını yanlış anlamak, bir &lt;strong&gt;Event-Driven Architecture (EDA)&lt;/strong&gt; tasarlarken veya &lt;strong&gt;Command Query Responsibility Segregation (CQRS)&lt;/strong&gt; uygularken düşülen en yaygın tuzaklardan biridir.&lt;/p&gt;</description><content>&lt;p&gt;Modern yazılım mühendisliği dünyasında, monolitik sistemlerden dağıtık &lt;strong&gt;microservices&lt;/strong&gt; mimarilerine geçiş yaygın bir evrimsel yoldur. Sistemler karmaşıklaştıkça, farklı bileşenlerin, modüllerin ve servislerin iletişim kurma şekli; sistemin dayanıklılığını, ölçeklenebilirliğini ve sürdürülebilirliğini belirleyen kritik faktör haline gelir. Eğer sağlam, cloud-native uygulamalar geliştiriyorsanız, bileşenlerinizi birbirinden ayırma (decouple) ihtiyacıyla kaçınılmaz olarak karşılaşırsınız.&lt;/p&gt;
&lt;p&gt;Bu iletişim ve decoupling stratejisinin merkezinde iki temel mesajlaşma deseni (pattern) yatar: &lt;strong&gt;Commands&lt;/strong&gt; ve &lt;strong&gt;Events&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;İlk bakışta birbirlerine benzeseler de —her ikisi de özünde sistemin bir parçasından diğerine gönderilen ve veri içeren mesajlardır— amaçları, işlenme şekilleri (handling) ve mimari etkileri birbirinden çok farklıdır. &lt;strong&gt;Software architecture içinde events vs commands&lt;/strong&gt; ayrımını yanlış anlamak, bir &lt;strong&gt;Event-Driven Architecture (EDA)&lt;/strong&gt; tasarlarken veya &lt;strong&gt;Command Query Responsibility Segregation (CQRS)&lt;/strong&gt; uygularken düşülen en yaygın tuzaklardan biridir.&lt;/p&gt;
&lt;p&gt;Bu kapsamlı rehberde, events ve commands arasındaki teorik farkları keşfedecek, hangisinin ne zaman kullanılacağını analiz edecek ve &lt;strong&gt;.NET&lt;/strong&gt; ve C# kullanarak hikaye tadında pratik bir uygulamayı adım adım inceleyeceğiz.&lt;/p&gt;
&lt;h2 id="dağıtık-sistemlerde-temel-iletişim-problemi"&gt;Dağıtık Sistemlerde Temel İletişim Problemi&lt;/h2&gt;
&lt;p&gt;Tanımlara dalmadan önce, çözmeye çalıştığımız problemi anlayalım.&lt;/p&gt;
&lt;p&gt;Bir e-ticaret uygulaması hayal edin. Kullanıcı &amp;ldquo;Ödeme Yap&amp;rdquo; (Checkout) butonuna tıklar. Geleneksel, sıkı sıkıya bağlı (tightly coupled) monolitik bir mimaride, sistem aşağıdaki adımları tek bir senkron akış içinde sırayla yürütebilir:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Sepeti doğrula.&lt;/li&gt;
&lt;li&gt;Stoğu düş.&lt;/li&gt;
&lt;li&gt;Ödemeyi işle.&lt;/li&gt;
&lt;li&gt;Fatura oluştur.&lt;/li&gt;
&lt;li&gt;Onay e-postası gönder.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Eğer e-posta sunucusu çökerse, tüm ödeme süreci başarısız olabilir, ödeme işlemi geri alınabilir ve kullanıcı hüsrana uğrayabilir. Bu sıkı bağ (tight coupling), kırılgan sistemler yaratır.&lt;/p&gt;
&lt;p&gt;Bunu çözmek için mimarlar &lt;strong&gt;asynchronous messaging&lt;/strong&gt; kavramını devreye sokarlar. Servisler metotları doğrudan çağırmak yerine, bir mediator veya bir message broker (RabbitMQ, Apache Kafka veya AWS ve Google Cloud üzerindeki cloud-native çözümler gibi) aracılığıyla birbirlerine mesaj gönderirler. Peki bu mesajlar nasıl yapılandırılmalıdır? Ödeme servisi, e-posta servisine &lt;em&gt;bir e-posta göndermesini&lt;/em&gt; mi söylemeli, yoksa sadece &lt;em&gt;bir siparişin verildiğini&lt;/em&gt; mi belirtmeli?&lt;/p&gt;
&lt;p&gt;İşte Commands ve Events arasındaki ayrım bu noktada kritik bir hal alır.&lt;/p&gt;
&lt;h2 id="command-nedir"&gt;Command Nedir?&lt;/h2&gt;
&lt;p&gt;Bir &lt;strong&gt;Command&lt;/strong&gt;, bir &lt;em&gt;niyetin&lt;/em&gt; (intent) ifadesidir. Sistemin belirli bir eylemi gerçekleştirmesi veya durumunu (state) değiştirmesi için yapılan bir istektir.&lt;/p&gt;
&lt;p&gt;Bir command&amp;rsquo;i emir kipiyle verilmiş bir talimat olarak düşünün. Sisteme tam olarak ne yapmasını istediğinizi söylüyorsunuz. Bir istek olduğu için, bir command reddedilebilir. Sistem command&amp;rsquo;i doğrulayabilir ve iş kuralları, izinler veya geçersiz veriler nedeniyle çalıştırılamayacağına karar verebilir.&lt;/p&gt;
&lt;h3 id="bir-commandin-temel-özellikleri"&gt;Bir Command&amp;rsquo;in Temel Özellikleri:&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Imperative Naming (Emir Kipiyle İsimlendirme):&lt;/strong&gt; Command&amp;rsquo;ler emir kipindeki fiillerle isimlendirilir (örneğin, &lt;code&gt;PlaceOrderCommand&lt;/code&gt;, &lt;code&gt;CreateUserCommand&lt;/code&gt;, &lt;code&gt;ProcessPaymentCommand&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Targeted (Hedefe Yönelik):&lt;/strong&gt; Bir command tam olarak &lt;strong&gt;bir&lt;/strong&gt; handler&amp;rsquo;a yönlendirilir (routed). Bir command&amp;rsquo;i tüm sisteme yayınlamazsınız (broadcast); onu o eylemden sorumlu olan belirli bileşene gönderirsiniz.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Can Fail (Başarısız Olabilir):&lt;/strong&gt; Durumu (state change) değiştirmeye yönelik bir istek olduğundan, alıcı handler doğrulama başarısız olursa bu isteği reddedebilir (ve genellikle reddeder) ve gönderene bir hata veya exception döndürür.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;High Coupling (Behavioral):&lt;/strong&gt; Command&amp;rsquo;i gönderen taraf ne yapılması gerektiğini bilmek zorundadır. Fiziksel bağ bir mesaj veriyolu ile soyutlanmış olsa da, bu durum events&amp;rsquo;e kıyasla daha sıkı bir davranışsal bağ anlamına gelir.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="event-nedir"&gt;Event Nedir?&lt;/h2&gt;
&lt;p&gt;Öte yandan bir &lt;strong&gt;Event&lt;/strong&gt;, bir &lt;em&gt;gerçeğin&lt;/em&gt; (fact) ifadesidir. Geçmişte bir şeyin zaten gerçekleşmiş olduğunun duyurusudur.&lt;/p&gt;
&lt;p&gt;Bir event&amp;rsquo;i reddedemezsiniz çünkü geçmişi değiştiremezsiniz. Eğer bir event yayınlanmışsa (published), state change çoktan gerçekleşmiştir. Sistemdeki bileşenler, bu olaylara tepki vermek ve kendi ardışık süreçlerini tetiklemek için events&amp;rsquo;i dinlerler.&lt;/p&gt;
&lt;h3 id="bir-eventin-temel-özellikleri"&gt;Bir Event&amp;rsquo;in Temel Özellikleri:&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Past-Tense Naming (Geçmiş Zamanla İsimlendirme):&lt;/strong&gt; Events, geçmiş zamanlı fiiller kullanılarak isimlendirilir (örneğin, &lt;code&gt;OrderPlacedEvent&lt;/code&gt;, &lt;code&gt;UserCreatedEvent&lt;/code&gt;, &lt;code&gt;PaymentProcessedEvent&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Broadcasted (Yayınlanan - Publish/Subscribe):&lt;/strong&gt; Bir event, bir event bus veya message broker&amp;rsquo;a yayınlanır. &lt;strong&gt;Sıfır, bir veya birden fazla&lt;/strong&gt; handler&amp;rsquo;ı olabilir. Yayınlayan taraf (publisher), kimin dinlediğini bilmez ve umursamaz.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Cannot Be Rejected (Reddedilemez):&lt;/strong&gt; Olay zaten gerçekleşmiştir. Abone olanlar (subscribers) bunu işlemek zorundadır. Eğer bir subscriber bir event&amp;rsquo;i işlemekte başarısız olursa, kendi retry (yeniden deneme) mantığını yürütmelidir; publisher&amp;rsquo;a bu gerçeği &amp;ldquo;geri almasını&amp;rdquo; söyleyemez.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Loose Coupling (Gevşek Bağ):&lt;/strong&gt; Gönderici (publisher), alıcılardan (subscribers) tamamen izole edilmiştir (decoupled). Publisher sadece bir durum değişikliğini duyurur, bu da mevcut koda hiç dokunmadan sisteme yeni işlevsellikler eklemeyi inanılmaz derecede kolaylaştırır.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="temel-farklar-events-vs-commands"&gt;Temel Farklar: Events vs Commands&lt;/h2&gt;
&lt;p&gt;Kavramları pekiştirmek için yan yana bir karşılaştırma yapalım.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Özellik&lt;/th&gt;
&lt;th&gt;Command&lt;/th&gt;
&lt;th&gt;Event&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Tanım&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Bir eylemi gerçekleştirmek için yapılan istek.&lt;/td&gt;
&lt;td&gt;Bir şeyin gerçekleştiğine dair bildirim.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;İsimlendirme Standardı&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Emir Kipi (örn. &lt;code&gt;ShipOrder&lt;/code&gt;)&lt;/td&gt;
&lt;td&gt;Geçmiş Zaman (örn. &lt;code&gt;OrderShipped&lt;/code&gt;)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Yönlendirme / Handlers&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Tam olarak bir handler (1:1).&lt;/td&gt;
&lt;td&gt;Sıfırdan çoka kadar handlers (1:N).&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Yön&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Noktadan noktaya (Point-to-point). Gönderen bir alıcıyı hedefler.&lt;/td&gt;
&lt;td&gt;Publish/Subscribe. Gönderen yayınlar.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Hata Yönetimi&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Reddedilebilir (doğrulama hatası).&lt;/td&gt;
&lt;td&gt;Reddedilemez. Geçmiş olaylar değiştirilemez (immutable).&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Gönderenin Bilgisi&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;em&gt;Ne&lt;/em&gt; yapılması gerektiğini bilir.&lt;/td&gt;
&lt;td&gt;Event ile &lt;em&gt;kimin&lt;/em&gt; ilgilendiğini bilmez.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2 id="net-uygulama-hikayesi-hayata-geçirmek"&gt;.NET Uygulama Hikayesi: Hayata Geçirmek&lt;/h2&gt;
&lt;p&gt;Bu kavramların gerçek dünya uygulamalarında nasıl bir araya geldiğini göstermek için &lt;strong&gt;.NET&lt;/strong&gt; kullanarak bir hikaye anlatalım. .NET ekosisteminde in-memory (bellek içi) mesajlaşma ve &lt;strong&gt;CQRS&lt;/strong&gt; pattern&amp;rsquo;leri için standart uygulama olan, oldukça popüler &lt;strong&gt;MediatR&lt;/strong&gt; kütüphanesini kullanacağız.&lt;/p&gt;
&lt;p&gt;Bir Sipariş Yönetimi microservice&amp;rsquo;i inşa ettiğimizi hayal edelim.&lt;/p&gt;
&lt;h3 id="adım-1-commandi-tanımlamak"&gt;Adım 1: Command&amp;rsquo;i Tanımlamak&lt;/h3&gt;
&lt;p&gt;Yolculuk, bir kullanıcının sepetini onaylamasıyla başlar. API controller HTTP isteğini alır ve bir command oluşturur. Emir kipiyle isimlendirmeye dikkat edin. Mesajlar değiştirilemez (immutable) olması gerektiği için C# record&amp;rsquo;larını kullanıyoruz.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-csharp" data-lang="csharp"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;using&lt;/span&gt; MediatR;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;namespace&lt;/span&gt; ECommerce.Domain.Commands;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// The Command: A request that returns a strongly typed result&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;record&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;PlaceOrderCommand&lt;/span&gt;(
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Guid CustomerId,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; List&amp;lt;CartItem&amp;gt; Items,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;string&lt;/span&gt; ShippingAddress
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;) : IRequest&amp;lt;OrderResult&amp;gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;record&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;CartItem&lt;/span&gt;(Guid ProductId, &lt;span style="color:#66d9ef"&gt;int&lt;/span&gt; Quantity, &lt;span style="color:#66d9ef"&gt;decimal&lt;/span&gt; Price);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;record&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;OrderResult&lt;/span&gt;(&lt;span style="color:#66d9ef"&gt;bool&lt;/span&gt; IsSuccess, &lt;span style="color:#66d9ef"&gt;string&lt;/span&gt; Message, Guid? OrderId = &lt;span style="color:#66d9ef"&gt;null&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="adım-2-commandi-işlemek-handling"&gt;Adım 2: Command&amp;rsquo;i İşlemek (Handling)&lt;/h3&gt;
&lt;p&gt;Command tam olarak &lt;em&gt;bir&lt;/em&gt; handler&amp;rsquo;a yönlendirilir. Bu handler temel iş mantığını içerir. İsteği doğrular, veritabanı durumunu günceller ve eğer başarılı olursa bir Event üretir.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-csharp" data-lang="csharp"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;using&lt;/span&gt; MediatR;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;namespace&lt;/span&gt; ECommerce.Application.Handlers.Commands;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;class&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;PlaceOrderCommandHandler&lt;/span&gt; : IRequestHandler&amp;lt;PlaceOrderCommand, OrderResult&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;private&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;readonly&lt;/span&gt; IOrderRepository _repository;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;private&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;readonly&lt;/span&gt; IMediator _mediator;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; PlaceOrderCommandHandler(IOrderRepository repository, IMediator mediator)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; _repository = repository;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; _mediator = mediator;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;async&lt;/span&gt; Task&amp;lt;OrderResult&amp;gt; Handle(PlaceOrderCommand request, CancellationToken cancellationToken)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// 1. Validation (The command can be rejected!)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; (request.Items == &lt;span style="color:#66d9ef"&gt;null&lt;/span&gt; || !request.Items.Any())
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; OrderResult(&lt;span style="color:#66d9ef"&gt;false&lt;/span&gt;, &lt;span style="color:#e6db74"&gt;&amp;#34;Cart cannot be empty.&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// 2. State Change&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; totalAmount = request.Items.Sum(i =&amp;gt; i.Price * i.Quantity);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; order = &lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; Order(Guid.NewGuid(), request.CustomerId, totalAmount, request.ShippingAddress);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;await&lt;/span&gt; _repository.SaveAsync(order);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// 3. Announcing the Fact (The Event)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; orderPlacedEvent = &lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; OrderPlacedEvent(order.Id, request.CustomerId, totalAmount);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// Notice we are Publishing the event, not Sending a command.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;await&lt;/span&gt; _mediator.Publish(orderPlacedEvent, cancellationToken);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; OrderResult(&lt;span style="color:#66d9ef"&gt;true&lt;/span&gt;, &lt;span style="color:#e6db74"&gt;&amp;#34;Order successfully placed.&amp;#34;&lt;/span&gt;, order.Id);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="adım-3-eventi-tanımlamak"&gt;Adım 3: Event&amp;rsquo;i Tanımlamak&lt;/h3&gt;
&lt;p&gt;Durum (state) değişti. Sipariş güvenli bir şekilde veritabanına kaydedildi. Artık sistem sadece bu gerçeği uygulamanın geri kalanına duyurur. Geçmiş zamanlı isimlendirmeye dikkat edin.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-csharp" data-lang="csharp"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;using&lt;/span&gt; MediatR;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;namespace&lt;/span&gt; ECommerce.Domain.Events;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// The Event: A notification that something happened. Does not return a response.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;record&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;OrderPlacedEvent&lt;/span&gt;(
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Guid OrderId,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Guid CustomerId,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;decimal&lt;/span&gt; TotalAmount
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;) : INotification;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="adım-4-evente-tepki-vermek-multiple-handlers"&gt;Adım 4: Event&amp;rsquo;e Tepki Vermek (Multiple Handlers)&lt;/h3&gt;
&lt;p&gt;İşte &lt;strong&gt;Event-Driven Architecture&lt;/strong&gt;&amp;lsquo;ın büyüsünün parladığı yer burasıdır. Bir event yayınladığımız için, çekirdek &lt;code&gt;PlaceOrderCommandHandler&lt;/code&gt; sınıfımızı değiştirmeden, birbirinden bağımsız birden fazla handler&amp;rsquo;ın bu olaya tepki vermesini sağlayabiliriz.&lt;/p&gt;
&lt;p&gt;Diyelim ki pazarlama ekibi bir &amp;ldquo;Teşekkürler&amp;rdquo; e-postası göndermek istiyor ve lojistik ekibinin stoğu ayırması gerekiyor. Basitçe iki ayrı event handler oluşturuyoruz.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Handler A: E-posta Servisi&lt;/strong&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-csharp" data-lang="csharp"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;using&lt;/span&gt; MediatR;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;namespace&lt;/span&gt; ECommerce.Application.Handlers.Events;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;class&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;SendOrderConfirmationEmailHandler&lt;/span&gt; : INotificationHandler&amp;lt;OrderPlacedEvent&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;private&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;readonly&lt;/span&gt; IEmailService _emailService;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; SendOrderConfirmationEmailHandler(IEmailService emailService)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; _emailService = emailService;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;async&lt;/span&gt; Task Handle(OrderPlacedEvent notification, CancellationToken cancellationToken)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// React to the fact&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;await&lt;/span&gt; _emailService.SendEmailAsync(
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; notification.CustomerId,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;$&amp;#34;Thank you for your order! Your total is ${notification.TotalAmount}.&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; );
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;strong&gt;Handler B: Envanter Servisi&lt;/strong&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-csharp" data-lang="csharp"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;using&lt;/span&gt; MediatR;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;namespace&lt;/span&gt; ECommerce.Application.Handlers.Events;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;class&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;ReserveInventoryHandler&lt;/span&gt; : INotificationHandler&amp;lt;OrderPlacedEvent&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;private&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;readonly&lt;/span&gt; IInventoryService _inventoryService;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; ReserveInventoryHandler(IInventoryService inventoryService)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; _inventoryService = inventoryService;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;async&lt;/span&gt; Task Handle(OrderPlacedEvent notification, CancellationToken cancellationToken)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// React to the same fact independently&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;await&lt;/span&gt; _inventoryService.ReserveItemsForOrderAsync(notification.OrderId);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;strong&gt;.NET&lt;/strong&gt; kodumuzu bu şekilde yapılandırarak harika bir loosely coupled (gevşek bağlı) sistem elde ettik. Yarın analiz ekibi satışları takip etmek isterse, &lt;code&gt;PlaceOrderCommandHandler&lt;/code&gt;&amp;lsquo;a dokunmayız. Sadece &lt;code&gt;OrderPlacedEvent&lt;/code&gt;&amp;lsquo;i dinleyen bir &lt;code&gt;TrackSalesAnalyticsHandler&lt;/code&gt; ekleriz.&lt;/p&gt;
&lt;h2 id="ileri-düzey-mimari-değerlendirmeler"&gt;İleri Düzey Mimari Değerlendirmeler&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;MediatR&lt;/strong&gt; örneği in-memory kavramları mükemmel bir şekilde gösterse de, bu pattern&amp;rsquo;leri bellekten çıkarıp dağıtık (distributed) bir bulut ortamına taşımak karmaşık zorlukları beraberinde getirir. &lt;strong&gt;Microservices&lt;/strong&gt;&amp;rsquo;leri çalıştırmak için Google Cloud, AWS veya Azure gibi cloud platformları kullanıldığında, mesajlaşma yapısı da dağıtık hale gelir.&lt;/p&gt;
&lt;p&gt;İşte &lt;strong&gt;Events vs Commands&lt;/strong&gt; yapılarını ölçeklendirirken göz önünde bulundurmanız gereken üç ileri düzey pattern:&lt;/p&gt;
&lt;h3 id="1-the-outbox-pattern-garantili-teslimat"&gt;1. The Outbox Pattern (Garantili Teslimat)&lt;/h3&gt;
&lt;p&gt;C# örneğimizde siparişi veritabanına kaydettik ve ardından event&amp;rsquo;i MediatR&amp;rsquo;a yayınladık. Peki ya veritabanı kaydı başarılı olur ancak uygulama tam olarak &lt;code&gt;await _mediator.Publish()&lt;/code&gt; satırı çalıştırılmadan bir milisaniye önce çökerse?&lt;/p&gt;
&lt;p&gt;Durum değişti, ancak event hiçbir zaman fırlatılmadı. Müşterinin kartından para çekildi, ancak deponun haberi olmadı. Bu, dağıtık sistemlerde &amp;ldquo;&lt;strong&gt;Dual Write Problem&lt;/strong&gt;&amp;rdquo; (Çift Yazma Problemi) olarak bilinen yıkıcı bir hatadır.&lt;/p&gt;
&lt;p&gt;Bunu çözmek için mimarlar &lt;strong&gt;Outbox Pattern&lt;/strong&gt; (veya Transactional Outbox Pattern) kullanırlar. Command handler, event&amp;rsquo;i doğrudan yayınlamak yerine, siparişin kendisiyle &lt;em&gt;aynı veritabanı transaction&amp;rsquo;ı&lt;/em&gt; içinde &lt;code&gt;OrderPlacedEvent&lt;/code&gt;&amp;lsquo;i bir &amp;ldquo;Outbox&amp;rdquo; tablosuna yazar. Ayrı bir arka plan süreci (bir .NET BackgroundService gibi) sürekli olarak bu Outbox tablosunu okur ve mesajları harici broker&amp;rsquo;ınıza (örn. RabbitMQ veya Google Cloud Pub/Sub) yayınlar. Bu, event&amp;rsquo;lerinizin en az bir kere teslim edilmesini garanti eder.&lt;/p&gt;
&lt;h3 id="2-idempotency-yinelenen-eventleri-işlemek"&gt;2. Idempotency (Yinelenen Event&amp;rsquo;leri İşlemek)&lt;/h3&gt;
&lt;p&gt;Dağıtık mesajlaşma sistemleri genellikle &amp;ldquo;en az bir kere&amp;rdquo; teslimat garantisi sunduğundan, event handler&amp;rsquo;larınız aynı &lt;code&gt;OrderPlacedEvent&lt;/code&gt;&amp;lsquo;i iki kez alabilir.&lt;/p&gt;
&lt;p&gt;Eğer &lt;code&gt;SendOrderConfirmationEmailHandler&lt;/code&gt; dikkatlice tasarlanmazsa, müşteri iki e-posta alabilir. Daha da kötüsü, bir event ödeme alımını tetikliyorsa, müşteriden iki kez ücret alınabilir.&lt;/p&gt;
&lt;p&gt;Event handler&amp;rsquo;lar &lt;strong&gt;idempotent&lt;/strong&gt; olmalıdır. Bu, aynı event&amp;rsquo;i birden fazla kez uygulamanın, bir kez uygulamakla aynı etkiye sahip olması anlamına gelir. .NET&amp;rsquo;te bu genellikle işlenmiş &lt;code&gt;EventId&lt;/code&gt;&amp;lsquo;lerin tutulduğu bir tablo aracılığıyla uygulanır. Bir handler bir event&amp;rsquo;i işlemeden önce kontrol eder: &lt;em&gt;&amp;ldquo;Bu Event ID&amp;rsquo;yi daha önce gördüm mü?&amp;rdquo;&lt;/em&gt; Eğer evetse, onu güvenli bir şekilde yoksayar ve başarılı bir yanıt döner.&lt;/p&gt;
&lt;h3 id="3-event-sourcing-vs-event-driven-architecture"&gt;3. Event Sourcing vs Event-Driven Architecture&lt;/h3&gt;
&lt;p&gt;Bir &lt;strong&gt;Event-Driven Architecture&lt;/strong&gt;&amp;lsquo;ı (EDA), &lt;strong&gt;Event Sourcing&lt;/strong&gt; ile karıştırmamak önemlidir.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;EDA&lt;/strong&gt;, servisler arasında iletişim kurmak için events kullanır (yukarıdaki örneğimizde olduğu gibi). Durum (state) hala geleneksel bir veritabanı tablosunda (örneğin, bir &lt;code&gt;Status&lt;/code&gt; sütunu olan &lt;code&gt;Orders&lt;/code&gt; tablosunda) saklanır.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Event Sourcing&lt;/strong&gt; ise events&amp;rsquo;in veritabanının &lt;em&gt;ta kendisi&lt;/em&gt; olduğu çok daha karmaşık bir paradigmadır. Bir siparişin mevcut durumunu kaydetmek yerine, sadece ekleme yapılabilen (append-only) bir olaylar günlüğü (log) kaydedersiniz: &lt;code&gt;OrderCreated&lt;/code&gt;, &lt;code&gt;AddressUpdated&lt;/code&gt;, &lt;code&gt;ItemAdded&lt;/code&gt;, &lt;code&gt;OrderCheckedOut&lt;/code&gt;. Sistemin mevcut durumu bulabilmesi için events başlangıç zamanından itibaren yeniden oynatılır. Denetim izleri için güçlü olsa da, devasa bir karmaşıklık getirir ve finansal defterler gibi katı tarihsel doğruluk gerektiren özel alanlara saklanmalıdır.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="hangisini-ne-zaman-kullanmalı"&gt;Hangisini Ne Zaman Kullanmalı?&lt;/h2&gt;
&lt;p&gt;Yazılım tasarımının güzelliği, iş için doğru aracı seçmektir. İşte .NET uygulamalarınızda Commands vs Events tercihini ne zaman yapacağınıza dair kısa bir kopya kağıdı:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Şu durumlarda Command kullanın:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Sistemin belirli bir eylemi gerçekleştirmesine ihtiyacınız varsa ve bunu hangi bileşenin işlemesi (handle) gerektiğini tam olarak biliyorsanız.&lt;/li&gt;
&lt;li&gt;Kullanıcı girdisini doğrulamanız ve potansiyel olarak isteği reddetmeniz gerekiyorsa.&lt;/li&gt;
&lt;li&gt;Geriye bir sonuç bekliyorsanız (başarı, başarısızlık veya yeni oluşturulan bir entity&amp;rsquo;nin ID&amp;rsquo;si).&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Şu durumlarda Event kullanın:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Önemli bir durum değişikliği (state change) gerçekleştiyse ve sistemin diğer parçalarını bilgilendirmek istiyorsanız.&lt;/li&gt;
&lt;li&gt;Farklı, bağımsız modüllerin sıkı bir bağ (tight coupling) yaratmadan tek bir eyleme tepki vermesine izin vermek istiyorsanız.&lt;/li&gt;
&lt;li&gt;Microservice sınırları arasında entegrasyon yapıyorsanız ve servislerin otonom kalmasını sağlamak istiyorsanız.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="özet"&gt;Özet&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Software architecture&lt;/strong&gt; içinde &lt;strong&gt;events vs commands&lt;/strong&gt; arasındaki farkta uzmanlaşmak, bir geliştiricinin &lt;strong&gt;system design&lt;/strong&gt; mükemmelliğine giden yolculuğunda önemli bir kilometre taşıdır. Commands uygulamanızın niyetini yönlendirir ve iş kurallarını korur, durum değişikliklerinin sadece geçerli olduğunda gerçekleşmesini sağlar. Events ise sistemin sinir ağı gibi davranır, gerçekleri yayınlar ve yüksek ölçeklenebilir, loosely coupled (gevşek bağlı) bir microservices ekosistemini mümkün kılar.&lt;/p&gt;
&lt;p&gt;.NET&amp;rsquo;teki &lt;strong&gt;MediatR&lt;/strong&gt; gibi güçlü kütüphanelerden yararlanarak ve &lt;strong&gt;asynchronous messaging&lt;/strong&gt; inceliklerini anlayarak, sadece günümüz iş mantığının karmaşıklığıyla başa çıkabilen değil, aynı zamanda yarının gereksinimlerine uyum sağlayacak kadar esnek ve dayanıklı sistemler inşa edebilirsiniz. Command&amp;rsquo;lerinizi emir kipiyle isimlendirmeyi, event&amp;rsquo;lerinizi geçmişin değiştirilemez gerçekleri olarak ele almayı ve decouple mimarinin gücünü kucaklamayı unutmayın.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="sonuç"&gt;Sonuç&lt;/h2&gt;
&lt;p&gt;Okuduğunuz için teşekkürler! 🎉 Yazılım geliştirme konusundaki araştırmalarımı kaçırmamak için beni &lt;a href="https://x.com/berkslv"&gt;@berkslv&lt;/a&gt; adresinden takip edebilirsiniz.&lt;/p&gt;</content></item><item><title>.NET'te Anemic'ten Rich Domain Model'e Refactoring</title><link>https://berkselvi.dev/tr/posts/refactoring-from-an-anemic-to-a-rich-domain-model-in-dotnet/</link><pubDate>Tue, 16 Jun 2026 00:00:00 +0300</pubDate><guid>https://berkselvi.dev/tr/posts/refactoring-from-an-anemic-to-a-rich-domain-model-in-dotnet/</guid><description>&lt;p&gt;.NET&amp;rsquo;te kurumsal uygulamalar geliştirmek için önemli bir zaman harcadıysanız, Domain-Driven Design (DDD) kavramıyla karşılaşmış olmanız muhtemeldir. DDD, kodunuzu temsil ettiği gerçek dünya iş domain&amp;rsquo;iyle hizalayarak yazılım karmaşıklığıyla başa çıkmanın bir yolunu vaat eder. Ancak, geliştiricilerin DDD&amp;rsquo;yi—veya sadece temel Object-Oriented Programming&amp;rsquo;i (OOP)—benimserken düştükleri en yaygın tuzaklardan biri &lt;strong&gt;Anemic Domain Model&lt;/strong&gt; yaratmaktır.&lt;/p&gt;
&lt;p&gt;Birçok .NET mimarisinde tanıdık bir pattern görürsünüz: yalnızca public getter ve setter&amp;rsquo;lardan oluşan torbalar olan &amp;ldquo;entity&amp;quot;ler ve bunlara eşlik eden, tüm iş kurallarını (business rules), validation ve state manipulation mantığını içeren devasa &amp;ldquo;service&amp;rdquo; sınıfları. Bu yapı, özellikle veritabanı odaklı bir geçmişten gelenler için rahat hissettirse de, object-oriented design ve encapsulation prensiplerine aktif olarak ters düşer.&lt;/p&gt;</description><content>&lt;p&gt;.NET&amp;rsquo;te kurumsal uygulamalar geliştirmek için önemli bir zaman harcadıysanız, Domain-Driven Design (DDD) kavramıyla karşılaşmış olmanız muhtemeldir. DDD, kodunuzu temsil ettiği gerçek dünya iş domain&amp;rsquo;iyle hizalayarak yazılım karmaşıklığıyla başa çıkmanın bir yolunu vaat eder. Ancak, geliştiricilerin DDD&amp;rsquo;yi—veya sadece temel Object-Oriented Programming&amp;rsquo;i (OOP)—benimserken düştükleri en yaygın tuzaklardan biri &lt;strong&gt;Anemic Domain Model&lt;/strong&gt; yaratmaktır.&lt;/p&gt;
&lt;p&gt;Birçok .NET mimarisinde tanıdık bir pattern görürsünüz: yalnızca public getter ve setter&amp;rsquo;lardan oluşan torbalar olan &amp;ldquo;entity&amp;quot;ler ve bunlara eşlik eden, tüm iş kurallarını (business rules), validation ve state manipulation mantığını içeren devasa &amp;ldquo;service&amp;rdquo; sınıfları. Bu yapı, özellikle veritabanı odaklı bir geçmişten gelenler için rahat hissettirse de, object-oriented design ve encapsulation prensiplerine aktif olarak ters düşer.&lt;/p&gt;
&lt;p&gt;Bu kapsamlı rehberde, Anemic Domain Model&amp;rsquo;in tam olarak ne olduğunu, endüstri uzmanlarının bunu neden bir anti-pattern olarak gördüğünü ve C# uygulamalarınızı nasıl &lt;strong&gt;Rich Domain Model&lt;/strong&gt;&amp;rsquo;e refactor edebileceğinizi inceleyeceğiz. Pratik kod örneklerine bakacak, Entity Framework Core (EF Core) ile persistence işlemlerini nasıl halledeceğimizi tartışacak ve mantığı entity&amp;rsquo;lerinizin içine taşımanın unit testing&amp;rsquo;i nasıl çocuk oyuncağı haline getirdiğini göreceğiz.&lt;/p&gt;
&lt;h2 id="1-anemic-domain-model-tuzağı"&gt;1. Anemic Domain Model Tuzağı&lt;/h2&gt;
&lt;h3 id="anemic-domain-model-nedir"&gt;Anemic Domain Model Nedir?&lt;/h3&gt;
&lt;p&gt;&amp;ldquo;Anemic Domain Model&amp;rdquo; terimi ilk olarak 2003 yılında Martin Fowler tarafından meşhur edilmiştir. O, bu durumu nesnelerin gerçek domain entity&amp;rsquo;leri gibi göründüğü (genellikle iş alanındaki &lt;code&gt;Customer&lt;/code&gt;, &lt;code&gt;Order&lt;/code&gt; veya &lt;code&gt;Invoice&lt;/code&gt; gibi isimlerle adlandırıldığı) ancak neredeyse hiçbir davranışa (behavior) sahip olmadığı bir anti-pattern olarak tanımlamıştır. Bunun yerine, onlar sadece birer veri konteyneridir (data containers)—public property&amp;rsquo;lerle dolu C# sınıflarıdır.&lt;/p&gt;
&lt;p&gt;C#&amp;rsquo;ta anemic bir entity&amp;rsquo;nin klasik bir örneğine bakalım:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-csharp" data-lang="csharp"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;class&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Order&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; Guid Id { &lt;span style="color:#66d9ef"&gt;get&lt;/span&gt;; &lt;span style="color:#66d9ef"&gt;set&lt;/span&gt;; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; Guid CustomerId { &lt;span style="color:#66d9ef"&gt;get&lt;/span&gt;; &lt;span style="color:#66d9ef"&gt;set&lt;/span&gt;; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; DateTime OrderDate { &lt;span style="color:#66d9ef"&gt;get&lt;/span&gt;; &lt;span style="color:#66d9ef"&gt;set&lt;/span&gt;; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; OrderStatus Status { &lt;span style="color:#66d9ef"&gt;get&lt;/span&gt;; &lt;span style="color:#66d9ef"&gt;set&lt;/span&gt;; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;decimal&lt;/span&gt; TotalAmount { &lt;span style="color:#66d9ef"&gt;get&lt;/span&gt;; &lt;span style="color:#66d9ef"&gt;set&lt;/span&gt;; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; List&amp;lt;OrderItem&amp;gt; Items { &lt;span style="color:#66d9ef"&gt;get&lt;/span&gt;; &lt;span style="color:#66d9ef"&gt;set&lt;/span&gt;; } = &lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; List&amp;lt;OrderItem&amp;gt;();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;string&lt;/span&gt; ShippingAddress { &lt;span style="color:#66d9ef"&gt;get&lt;/span&gt;; &lt;span style="color:#66d9ef"&gt;set&lt;/span&gt;; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;enum&lt;/span&gt; OrderStatus
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Pending,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Paid,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Shipped,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Cancelled
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;İlk bakışta bu tamamen normal görünebilir. Entity Framework eğitimlerinin büyük çoğunluğu sınıfları bu şekilde oluşturmanızı öğretir. Ancak bu sınıf davranıştan (behavior) tamamen yoksundur. Bu &amp;ldquo;aptal&amp;rdquo; (dumb) bir nesnedir.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;Order&lt;/code&gt; kendi state&amp;rsquo;ini koruyamadığı için, uygulamanın herhangi bir kısmı onu doğrudan değiştirebilir:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-csharp" data-lang="csharp"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; order = &lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; Order();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// Bekle, müşterisi olmayan, ürün içermeyen ve çoktan kargolandı olarak işaretlenmiş bir sipariş mi?&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;order.Status = OrderStatus.Shipped;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;order.TotalAmount = -&lt;span style="color:#ae81ff"&gt;500&lt;/span&gt;m; &lt;span style="color:#75715e"&gt;// Negatif tutar mı? Tabii, neden olmasın!&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="service-katmanı-şişkinliği"&gt;&amp;ldquo;Service&amp;rdquo; Katmanı Şişkinliği&lt;/h3&gt;
&lt;p&gt;Entity hiçbir mantığa sahip olmadığı için, iş kuralları (business rules) başka bir yerde yaşamalıdır. Bu durum genellikle Transaction Scripts olarak adlandırılan &amp;ldquo;God class&amp;quot;ların veya devasa service katmanlarının oluşmasına yol açar.&lt;/p&gt;
&lt;p&gt;İşte anemic bir modelle uğraşırken bir &lt;code&gt;OrderService&lt;/code&gt;&amp;lsquo;in nasıl göründüğü:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-csharp" data-lang="csharp"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;class&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;OrderService&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;void&lt;/span&gt; AddItemToOrder(Guid orderId, OrderItem item)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; order = _repository.Get(orderId);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; (order == &lt;span style="color:#66d9ef"&gt;null&lt;/span&gt;) &lt;span style="color:#66d9ef"&gt;throw&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; Exception(&lt;span style="color:#e6db74"&gt;&amp;#34;Order not found&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; (order.Status == OrderStatus.Shipped || order.Status == OrderStatus.Cancelled)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;throw&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; InvalidOperationException(&lt;span style="color:#e6db74"&gt;&amp;#34;Cannot modify a shipped or cancelled order.&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; order.Items.Add(item);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// Recalculate total&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; order.TotalAmount = order.Items.Sum(i =&amp;gt; i.Price * i.Quantity);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; _repository.Save(order);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Bu kod çalışmasına rağmen, object-oriented programming kılığına girmiş prosedürel programlamayı (procedural programming) temsil eder. Bir &lt;code&gt;Order&lt;/code&gt;&amp;lsquo;ı neyin geçerli (valid) kıldığıyla ilgili mantık &lt;code&gt;Order&lt;/code&gt;&amp;lsquo;dan çıkarılıp &lt;code&gt;OrderService&lt;/code&gt; içine atılmıştır.&lt;/p&gt;
&lt;h3 id="aneminin-maliyetleri"&gt;Anemi&amp;rsquo;nin Maliyetleri&lt;/h3&gt;
&lt;p&gt;Domain modeliniz anemic olduğunda, birkaç önemli mimari maliyetle karşılaşırsınız:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Eksik Encapsulation:&lt;/strong&gt; Herhangi bir yerdeki herhangi bir kod bir entity&amp;rsquo;nin state&amp;rsquo;ini değiştirebilir. Bir &lt;code&gt;Order&lt;/code&gt;&amp;lsquo;ın geçerli (valid) bir state&amp;rsquo;te olduğunu garanti etmek imkansızdır.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Tekrarlanan Logic:&lt;/strong&gt; Farklı bir use case&amp;rsquo;te bir siparişin toplam tutarını yeniden hesaplamanız gerekirse (örneğin indirim uygularken), muhtemelen bu hesaplama mantığını başka bir service içinde kopyalayacaksınız.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Düşük Cohesion:&lt;/strong&gt; Veri (&lt;code&gt;Order&lt;/code&gt;) ve o veri üzerinde işlem yapan davranış (&lt;code&gt;OrderService&lt;/code&gt;) birbirinden ayrılmıştır. Bir siparişin yaşam döngüsünü anlamak için birden fazla katman arasında gezinmeniz gerekir.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Kırılganlık (Fragility):&lt;/strong&gt; Uygulama büyüdükçe, karmaşık iş mantığı onlarca service&amp;rsquo;e dağılır ve bu da bug&amp;rsquo;ların izini sürmeyi veya sistemi refactor etmeyi inanılmaz derecede zorlaştırır.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="2-çözüm-rich-domain-modele-giriş"&gt;2. Çözüm: Rich Domain Model&amp;rsquo;e Giriş&lt;/h2&gt;
&lt;p&gt;Bir &lt;strong&gt;Rich Domain Model&lt;/strong&gt; bu paradigmayı tam tersine çevirir. Rich bir modelde entity&amp;rsquo;ler zeki nesnelerdir. Hem kendi verilerini (state) hem de o verilerin nasıl mutate edilebileceğini yöneten iş kurallarını (behavior) encapsulate ederler.&lt;/p&gt;
&lt;p&gt;Rich bir domain model üç temel sütun üzerine inşa edilir:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Her Zaman Geçerli State (Invariants):&lt;/strong&gt; Bir entity asla geçersiz bir state&amp;rsquo;te bulunmamalıdır. Yaratılması ve mutate edilmesi sıkı bir şekilde kontrol edilir.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Encapsulation:&lt;/strong&gt; Property&amp;rsquo;ler dışarıdan keyfi olarak değiştirilemez. State mutation&amp;rsquo;ları sadece açıkça isimlendirilmiş methodlar aracılığıyla gerçekleşir.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Behavior-Driven (Davranış Odaklı):&lt;/strong&gt; Bir nesneden verilerini isteyip onun adına kararlar vermek yerine, nesneye ne yapacağını söylersiniz (&lt;em&gt;Tell, Don&amp;rsquo;t Ask&lt;/em&gt; prensibi).&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Anemic &lt;code&gt;Order&lt;/code&gt; entity&amp;rsquo;mizi C#&amp;rsquo;ta behavior-driven, rich bir domain entity&amp;rsquo;sine refactor etme sürecine başlayalım.&lt;/p&gt;
&lt;h2 id="3-cta-adım-adım-rich-domain-modele-refactoring"&gt;3. C#&amp;rsquo;ta Adım Adım Rich Domain Model&amp;rsquo;e Refactoring&lt;/h2&gt;
&lt;h3 id="adım-1-statei-kilitleyin-private-setterlar"&gt;Adım 1: State&amp;rsquo;i Kilitleyin (Private Setter&amp;rsquo;lar)&lt;/h3&gt;
&lt;p&gt;Anemic bir modeli iyileştirmenin ilk adımı kanamayı durdurmaktır. Dışarıdaki kodun property&amp;rsquo;leri keyfi olarak değiştirmesini engellemeliyiz. Bunu tüm property setter&amp;rsquo;larını &lt;code&gt;private&lt;/code&gt; veya &lt;code&gt;protected&lt;/code&gt; yaparak başarıyoruz.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-csharp" data-lang="csharp"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;class&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Order&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; Guid Id { &lt;span style="color:#66d9ef"&gt;get&lt;/span&gt;; &lt;span style="color:#66d9ef"&gt;private&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;set&lt;/span&gt;; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; Guid CustomerId { &lt;span style="color:#66d9ef"&gt;get&lt;/span&gt;; &lt;span style="color:#66d9ef"&gt;private&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;set&lt;/span&gt;; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; DateTime OrderDate { &lt;span style="color:#66d9ef"&gt;get&lt;/span&gt;; &lt;span style="color:#66d9ef"&gt;private&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;set&lt;/span&gt;; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; OrderStatus Status { &lt;span style="color:#66d9ef"&gt;get&lt;/span&gt;; &lt;span style="color:#66d9ef"&gt;private&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;set&lt;/span&gt;; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;decimal&lt;/span&gt; TotalAmount { &lt;span style="color:#66d9ef"&gt;get&lt;/span&gt;; &lt;span style="color:#66d9ef"&gt;private&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;set&lt;/span&gt;; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;string&lt;/span&gt; ShippingAddress { &lt;span style="color:#66d9ef"&gt;get&lt;/span&gt;; &lt;span style="color:#66d9ef"&gt;private&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;set&lt;/span&gt;; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// We don&amp;#39;t want external code adding to the list directly&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;private&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;readonly&lt;/span&gt; List&amp;lt;OrderItem&amp;gt; _items = &lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; List&amp;lt;OrderItem&amp;gt;();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; IReadOnlyCollection&amp;lt;OrderItem&amp;gt; Items =&amp;gt; _items.AsReadOnly();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Burada ne olduğuna dikkat edin:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;set&lt;/code&gt; accessor&amp;rsquo;ları artık &lt;code&gt;private&lt;/code&gt;. Siparişin state&amp;rsquo;ini okuyabilirsiniz, ancak doğrudan değiştiremezsiniz.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Items&lt;/code&gt; koleksiyonu artık &lt;code&gt;IReadOnlyCollection&amp;lt;OrderItem&amp;gt;&lt;/code&gt; olarak dışarı sunuluyor. Gerçek liste, &lt;code&gt;private readonly&lt;/code&gt; bir backing field&amp;rsquo;ın arkasına gizlenmiştir. Bu, birisinin dışarıdan &lt;code&gt;order.Items.Add(...)&lt;/code&gt; çağırmasını engeller.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="adım-2-anlamlı-constructorlar"&gt;Adım 2: Anlamlı Constructor&amp;rsquo;lar&lt;/h3&gt;
&lt;p&gt;Setter&amp;rsquo;lar private ise, bir nesneyi nasıl oluştururuz? Başından itibaren geçerli (valid) bir entity oluşturmak için gerekli tüm bilgileri talep eden parametreli bir constructor kullanırız.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-csharp" data-lang="csharp"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;class&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Order&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// ... properties ...&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; Order(Guid id, Guid customerId, &lt;span style="color:#66d9ef"&gt;string&lt;/span&gt; shippingAddress)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; (id == Guid.Empty) &lt;span style="color:#66d9ef"&gt;throw&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; ArgumentException(&lt;span style="color:#e6db74"&gt;&amp;#34;Order ID cannot be empty.&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; (customerId == Guid.Empty) &lt;span style="color:#66d9ef"&gt;throw&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; ArgumentException(&lt;span style="color:#e6db74"&gt;&amp;#34;Customer ID cannot be empty.&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; (&lt;span style="color:#66d9ef"&gt;string&lt;/span&gt;.IsNullOrWhiteSpace(shippingAddress)) &lt;span style="color:#66d9ef"&gt;throw&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; ArgumentException(&lt;span style="color:#e6db74"&gt;&amp;#34;Shipping address is required.&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Id = id;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; CustomerId = customerId;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ShippingAddress = shippingAddress;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// Initial defaults&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; OrderDate = DateTime.UtcNow;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Status = OrderStatus.Pending;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; TotalAmount = &lt;span style="color:#ae81ff"&gt;0&lt;/span&gt;m;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Artık bir Customer ID veya Shipping Address olmadan bir &lt;code&gt;Order&lt;/code&gt; oluşturmak tam anlamıyla imkansızdır. Derleyici (compiler) sizin iş kurallarınızı zorunlu kılar.&lt;/p&gt;
&lt;h3 id="adım-3-domain-methodları-ekleyin-behavior"&gt;Adım 3: Domain Method&amp;rsquo;ları Ekleyin (Behavior)&lt;/h3&gt;
&lt;p&gt;State korunduğuna göre, state mutation&amp;rsquo;larına izin vermek için açık ve amacını belli eden methodlar eklememiz gerekiyor. Mantığı &lt;code&gt;OrderService&lt;/code&gt;&amp;lsquo;ten çıkarıp &lt;code&gt;Order&lt;/code&gt; entity&amp;rsquo;sinin içine taşıdığımız yer burasıdır.&lt;/p&gt;
&lt;p&gt;Bir öğe ekleme ve siparişi kargolandı olarak işaretleme mantığını implement edelim.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-csharp" data-lang="csharp"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;class&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Order&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// ... constructor and properties ...&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;void&lt;/span&gt; AddItem(Product product, &lt;span style="color:#66d9ef"&gt;int&lt;/span&gt; quantity)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; (Status != OrderStatus.Pending)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;throw&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; InvalidOperationException(&lt;span style="color:#e6db74"&gt;&amp;#34;Can only add items to a pending order.&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; (quantity &amp;lt;= &lt;span style="color:#ae81ff"&gt;0&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;throw&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; ArgumentException(&lt;span style="color:#e6db74"&gt;&amp;#34;Quantity must be greater than zero.&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; existingItem = _items.SingleOrDefault(i =&amp;gt; i.ProductId == product.Id);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; (existingItem != &lt;span style="color:#66d9ef"&gt;null&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; existingItem.IncreaseQuantity(quantity);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;else&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; _items.Add(&lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; OrderItem(product.Id, product.Price, quantity));
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; RecalculateTotal();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;void&lt;/span&gt; Ship()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; (Status != OrderStatus.Paid)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;throw&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; InvalidOperationException(&lt;span style="color:#e6db74"&gt;&amp;#34;Cannot ship an order that has not been paid.&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; (!_items.Any())
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;throw&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; InvalidOperationException(&lt;span style="color:#e6db74"&gt;&amp;#34;Cannot ship an empty order.&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Status = OrderStatus.Shipped;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;private&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;void&lt;/span&gt; RecalculateTotal()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; TotalAmount = _items.Sum(item =&amp;gt; item.Price * item.Quantity);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="sonuçta-ortaya-çıkan-application-layer"&gt;Sonuçta Ortaya Çıkan Application Layer&lt;/h3&gt;
&lt;p&gt;Bu refactoring&amp;rsquo;den sonra &lt;code&gt;OrderService&lt;/code&gt;&amp;lsquo;imizin (Clean Architecture&amp;rsquo;da genellikle Command Handler veya Application Service olarak adlandırılır) nasıl göründüğüne bakalım:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-csharp" data-lang="csharp"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;class&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;OrderService&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;void&lt;/span&gt; AddItemToOrder(Guid orderId, Product product, &lt;span style="color:#66d9ef"&gt;int&lt;/span&gt; quantity)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; order = _repository.Get(orderId);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; (order == &lt;span style="color:#66d9ef"&gt;null&lt;/span&gt;) &lt;span style="color:#66d9ef"&gt;throw&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; Exception(&lt;span style="color:#e6db74"&gt;&amp;#34;Order not found&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// The logic is no longer here! We simply delegate to the domain.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; order.AddItem(product, quantity);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; _repository.Save(order);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Application Service artık incedir ve tamamen bir orkestratör (orchestrator) görevi görür. Veritabanından entity&amp;rsquo;yi çeker, entity üzerindeki davranışı (behavior) çağırır ve yeni state&amp;rsquo;i tekrar veritabanına kaydeder. Tüm o ağır işler, business rule&amp;rsquo;lar ve invariant&amp;rsquo;lar &lt;code&gt;Order&lt;/code&gt; sınıfının içine düzgün bir şekilde encapsulate edilmiştir. Bu, Domain-Driven Design prensipleriyle mükemmel bir şekilde uyum sağlar.&lt;/p&gt;
&lt;h2 id="4-entity-framework-core-rich-modelleri-veritabanına-map-etmek"&gt;4. Entity Framework Core: Rich Model&amp;rsquo;leri Veritabanına Map Etmek&lt;/h2&gt;
&lt;p&gt;Geliştiricilerin Anemic Domain Model&amp;rsquo;lere bağlı kalmalarının tarihi ve büyük bir nedeni, Object-Relational Mapper&amp;rsquo;ların (ORM) bunları zorunlu kılmasıydı. Entity Framework&amp;rsquo;ün erken sürümleri, tabloları nesnelere map etmek için public setter&amp;rsquo;lar, parametresiz public constructor&amp;rsquo;lar ve public collection&amp;rsquo;lar talep ediyordu.&lt;/p&gt;
&lt;p&gt;Neyse ki, modern &lt;strong&gt;Entity Framework Core (EF Core)&lt;/strong&gt; inanılmaz derecede güçlüdür ve Domain-Driven Design pattern&amp;rsquo;lerini aktif olarak destekler. Persistence endişelerini domain&amp;rsquo;inize sızdırmadan tamamen encapsulate edilmiş bir Rich Domain Model&amp;rsquo;i koruyabilirsiniz.&lt;/p&gt;
&lt;p&gt;İşte Rich Domain Model&amp;rsquo;leri veritabanına kaydetmek (persist) için ihtiyacınız olan temel EF Core özellikleri:&lt;/p&gt;
&lt;h3 id="1-ef-core-için-parametresiz-constructorlar"&gt;1. EF Core için Parametresiz Constructor&amp;rsquo;lar&lt;/h3&gt;
&lt;p&gt;EF Core veritabanından veri okuduğunda, property&amp;rsquo;lerini ayarlamadan önce nesnelerinizi instance haline getirmek için bir yola ihtiyaç duyar. Public bir parametresiz constructor istemezsiniz çünkü bu encapsulation&amp;rsquo;ı bozar. Bunun yerine, özellikle EF Core için &lt;strong&gt;private&lt;/strong&gt; bir parametresiz constructor sağlayabilirsiniz.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-csharp" data-lang="csharp"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;class&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Order&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// Public constructor for your application code&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; Order(Guid id, Guid customerId, &lt;span style="color:#66d9ef"&gt;string&lt;/span&gt; shippingAddress) { ... }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// Private constructor for EF Core&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;private&lt;/span&gt; Order() { }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;EF Core nesneyi oluşturmak için reflection kullanır, bu yüzden private bir constructor mükemmel çalışırken API&amp;rsquo;nizi diğer geliştiricilerden de korur.&lt;/p&gt;
&lt;h3 id="2-private-backing-fieldları-map-etmek"&gt;2. Private Backing Field&amp;rsquo;ları Map Etmek&lt;/h3&gt;
&lt;p&gt;EF Core, private backing field&amp;rsquo;ları otomatik olarak bulacak ve onlara bağlanacak (bind) kadar akıllıdır. Eğer bir &lt;code&gt;IReadOnlyCollection&amp;lt;OrderItem&amp;gt; Items&lt;/code&gt; property&amp;rsquo;niz ve bir &lt;code&gt;_items&lt;/code&gt; backing field&amp;rsquo;ınız varsa, veritabanından veri materialize ederken EF Core ilişkileri doğrudan &lt;code&gt;_items&lt;/code&gt; alanına map edecektir.&lt;/p&gt;
&lt;p&gt;Eğer isimlendirme kuralınız farklıysa, bunu &lt;code&gt;OnModelCreating&lt;/code&gt; methodunda veya &lt;code&gt;IEntityTypeConfiguration&lt;/code&gt; sınıfınızda açıkça yapılandırabilirsiniz:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-csharp" data-lang="csharp"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;class&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;OrderConfiguration&lt;/span&gt; : IEntityTypeConfiguration&amp;lt;Order&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;void&lt;/span&gt; Configure(EntityTypeBuilder&amp;lt;Order&amp;gt; builder)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; builder.HasKey(o =&amp;gt; o.Id);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// Tell EF Core to use the private backing field for the collection&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; builder.Metadata.FindNavigation(nameof(Order.Items))
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; .SetPropertyAccessMode(PropertyAccessMode.Field);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="3-owned-entityler-aracılığıyla-value-objectler"&gt;3. Owned Entity&amp;rsquo;ler Aracılığıyla Value Object&amp;rsquo;ler&lt;/h3&gt;
&lt;p&gt;Bir Rich Domain Model&amp;rsquo;de genellikle birbiriyle ilişkili property&amp;rsquo;leri &lt;strong&gt;Value Object&lt;/strong&gt;&amp;rsquo;ler halinde gruplamak istersiniz. Örneğin, &lt;code&gt;Currency&lt;/code&gt; ve &lt;code&gt;Amount&lt;/code&gt; değerlerini bir entity üzerinde ayrı ayrı tutmak yerine, bir &lt;code&gt;Money&lt;/code&gt; value object&amp;rsquo;i oluşturabilirsiniz.&lt;/p&gt;
&lt;p&gt;EF Core bunu &lt;strong&gt;Owned Types&lt;/strong&gt; (&lt;code&gt;OwnsOne&lt;/code&gt;) aracılığıyla destekler:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-csharp" data-lang="csharp"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// The Value Object&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;class&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Money&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;string&lt;/span&gt; Currency { &lt;span style="color:#66d9ef"&gt;get&lt;/span&gt;; &lt;span style="color:#66d9ef"&gt;private&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;set&lt;/span&gt;; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;decimal&lt;/span&gt; Amount { &lt;span style="color:#66d9ef"&gt;get&lt;/span&gt;; &lt;span style="color:#66d9ef"&gt;private&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;set&lt;/span&gt;; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; Money(&lt;span style="color:#66d9ef"&gt;string&lt;/span&gt; currency, &lt;span style="color:#66d9ef"&gt;decimal&lt;/span&gt; amount)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Currency = currency;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Amount = amount;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;private&lt;/span&gt; Money() { } &lt;span style="color:#75715e"&gt;// For EF Core&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// Inside your Entity Configuration:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;builder.OwnsOne(o =&amp;gt; o.Price, priceBuilder =&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; priceBuilder.Property(p =&amp;gt; p.Currency).HasColumnName(&lt;span style="color:#e6db74"&gt;&amp;#34;PriceCurrency&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; priceBuilder.Property(p =&amp;gt; p.Amount).HasColumnName(&lt;span style="color:#e6db74"&gt;&amp;#34;PriceAmount&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;});
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Bu, son derece anlamlı, object-oriented bir domain modeli korumanıza olanak tanırken, verileri tek bir SQL tablosuna düzgün bir şekilde düzleştirmenizi (flatten) sağlar.&lt;/p&gt;
&lt;h2 id="5-rich-domain-modelleri-unit-test-etmenin-büyüsü"&gt;5. Rich Domain Model&amp;rsquo;leri Unit Test Etmenin Büyüsü&lt;/h2&gt;
&lt;p&gt;Mantığı entity&amp;rsquo;lerinizin içine taşımanın en derin faydalarından biri de unit testing süreçlerinizin ne kadar kolaylaştığıdır.&lt;/p&gt;
&lt;p&gt;Mantığınız devasa bir &lt;code&gt;OrderService&lt;/code&gt; içinde yaşadığında, unit test yazmak acı vericidir. &lt;code&gt;IOrderRepository&lt;/code&gt;&amp;lsquo;yi mock&amp;rsquo;lamanız, &lt;code&gt;IProductRepository&lt;/code&gt;&amp;lsquo;yi mock&amp;rsquo;lamanız, loglama servisini mock&amp;rsquo;lamanız, veritabanı state&amp;rsquo;ini hazırlamanız (arrange) ve son olarak mantığı test etmeniz gerekir. Test kurulumu genellikle testin kendisinden daha büyüktür.&lt;/p&gt;
&lt;p&gt;Ancak bir Rich Domain Model kullandığınızda, business logic tamamen entity&amp;rsquo;nin state&amp;rsquo;ine dayanır. &lt;code&gt;Order&lt;/code&gt; sınıfınızın içinde harici bağımlılıklar, veritabanları veya API&amp;rsquo;ler yoktur. Bu saf (pure) bir C# kodudur.&lt;/p&gt;
&lt;p&gt;Domain mantığını test etmek inanılmaz derecede basit ve ışık hızında hale gelir:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-csharp" data-lang="csharp"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;class&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;OrderTests&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt; [Fact]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;void&lt;/span&gt; AddItem_Should_ThrowException_When_OrderIsNotPending()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// Arrange&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; order = &lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; Order(Guid.NewGuid(), Guid.NewGuid(), &lt;span style="color:#e6db74"&gt;&amp;#34;123 Main St&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// Use reflection or a test-specific setup to transition state to Shipped &lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// (Assuming we have a Pay() then Ship() method)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; order.Pay();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; order.Ship();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; product = &lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; Product(Guid.NewGuid(), &lt;span style="color:#e6db74"&gt;&amp;#34;Laptop&amp;#34;&lt;/span&gt;, &lt;span style="color:#ae81ff"&gt;1000&lt;/span&gt;m);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// Act &amp;amp; Assert&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; exception = Assert.Throws&amp;lt;InvalidOperationException&amp;gt;(() =&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; order.AddItem(product, &lt;span style="color:#ae81ff"&gt;1&lt;/span&gt;));
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Assert.Equal(&lt;span style="color:#e6db74"&gt;&amp;#34;Can only add items to a pending order.&amp;#34;&lt;/span&gt;, exception.Message);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt; [Fact]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;void&lt;/span&gt; AddItem_Should_IncreaseTotalAmount()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// Arrange&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; order = &lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; Order(Guid.NewGuid(), Guid.NewGuid(), &lt;span style="color:#e6db74"&gt;&amp;#34;123 Main St&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; product = &lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; Product(Guid.NewGuid(), &lt;span style="color:#e6db74"&gt;&amp;#34;Laptop&amp;#34;&lt;/span&gt;, &lt;span style="color:#ae81ff"&gt;1000&lt;/span&gt;m);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// Act&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; order.AddItem(product, &lt;span style="color:#ae81ff"&gt;2&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// Assert&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Assert.Equal(&lt;span style="color:#ae81ff"&gt;2000&lt;/span&gt;m, order.TotalAmount);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Assert.Single(order.Items);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Bu testte &lt;strong&gt;sıfır mock&lt;/strong&gt; olduğuna dikkat ettiniz mi? Rich Domain Model, iş mantığını altyapı (infrastructure) endişelerinden izole ettiği için domain katmanınız yüksek test edilebilirliğe (testability) ulaşır. Sadece in-memory C# sınıflarını çalıştırdığınız için binlerce karmaşık business varyasyonunu milisaniyeler içinde kapsayabilirsiniz.&lt;/p&gt;
&lt;h2 id="özet"&gt;Özet&lt;/h2&gt;
&lt;p&gt;Anemic Domain Model&amp;rsquo;den Rich Domain Model&amp;rsquo;e refactoring yapmak zihniyette bir değişim gerektirir. Entity&amp;rsquo;lerinizi sadece veritabanı tablolarının (veri yapıları / data structures) birer yansıması olarak görmekten uzaklaşmalı ve onları açık davranışları (behavior) olan yaşayan business konseptleri olarak görmeye başlamalısınız.&lt;/p&gt;
&lt;p&gt;Private setter&amp;rsquo;lar, anlamlı constructor&amp;rsquo;lar, açık domain method&amp;rsquo;ları gibi prensipleri uygulayarak ve &lt;em&gt;Tell, Don&amp;rsquo;t Ask&lt;/em&gt; kuralına bağlı kalarak, iş mantığınızı (business logic) tam olarak ait olduğu yerde, yani entity&amp;rsquo;nin içinde merkezileştirebilirsiniz.&lt;/p&gt;
&lt;p&gt;Bu geçiş bir öğrenme eğrisi gerektirse de, EF Core&amp;rsquo;un private backing field&amp;rsquo;lar ve owned entity&amp;rsquo;ler gibi gelişmiş mapping yeteneklerinin entegrasyonu, artık veritabanınız uğruna object-oriented design&amp;rsquo;dan ödün vermek zorunda olmadığınız anlamına gelir. Uzun vadeli ödüller inkar edilemez: bug&amp;rsquo;lara ve technical debt&amp;rsquo;e aktif olarak direnen, yüksek düzeyde cohesive, derinlemesine encapsulate edilmiş ve kolayca test edilebilir bir kod tabanı.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="sonuç"&gt;Sonuç&lt;/h2&gt;
&lt;p&gt;Okuduğunuz için teşekkürler! 🎉 Yazılım geliştirme alanındaki araştırmalarımı kaçırmamak için beni &lt;a href="https://x.com/berkslv"&gt;@berkslv&lt;/a&gt; adresinden takip edebilirsiniz.&lt;/p&gt;</content></item><item><title>Legacy WCF/SOAP'tan Modern .NET'e: Taviz Vermeden Migration</title><link>https://berkselvi.dev/tr/posts/legacy-wcf-soap-to-modern-dotnet-migration/</link><pubDate>Fri, 12 Dec 2025 00:00:00 +0300</pubDate><guid>https://berkselvi.dev/tr/posts/legacy-wcf-soap-to-modern-dotnet-migration/</guid><description>&lt;h1 id="legacy-wcfsoaptan-modern-nete-taviz-vermeden-migration"&gt;Legacy WCF/SOAP&amp;rsquo;tan Modern .NET&amp;rsquo;e: Taviz Vermeden Migration&lt;/h1&gt;
&lt;p&gt;Mükemmel migration planını hazırladınız. Yol haritası harika görünüyor: O yaşlanmış, bitik monolitik .NET Framework 4.8 WCF uygulamasını alıp dönüştüreceksiniz. Arayüzü .NET 10 üzerinde şık, state tutmayan bir REST API olarak yeniden yazacak ve sonunda o eski IIS sunucusunu kapatacaksınız.&lt;/p&gt;
&lt;p&gt;Planı paydaşlara sunuyorsunuz. İç ekipler alkışlarla tezahürat yapıyor. DevOps ekibi çoktan Dockerfile&amp;rsquo;ları yazmaya başladı bile. Herkes çok heyecanlı.&lt;/p&gt;
&lt;p&gt;Ve sonra o e-posta geliyor.&lt;/p&gt;
&lt;p&gt;Büyük bir iş ortağının &amp;ldquo;Eski Entegrasyonlar&amp;rdquo; ekibinden veya belki de 2014&amp;rsquo;ten beri yazılım güncellemesi almamış bir donanım cihazını yöneten bir tedarikçiden geliyor. Mesajları kibar ama net:&lt;/p&gt;</description><content>&lt;h1 id="legacy-wcfsoaptan-modern-nete-taviz-vermeden-migration"&gt;Legacy WCF/SOAP&amp;rsquo;tan Modern .NET&amp;rsquo;e: Taviz Vermeden Migration&lt;/h1&gt;
&lt;p&gt;Mükemmel migration planını hazırladınız. Yol haritası harika görünüyor: O yaşlanmış, bitik monolitik .NET Framework 4.8 WCF uygulamasını alıp dönüştüreceksiniz. Arayüzü .NET 10 üzerinde şık, state tutmayan bir REST API olarak yeniden yazacak ve sonunda o eski IIS sunucusunu kapatacaksınız.&lt;/p&gt;
&lt;p&gt;Planı paydaşlara sunuyorsunuz. İç ekipler alkışlarla tezahürat yapıyor. DevOps ekibi çoktan Dockerfile&amp;rsquo;ları yazmaya başladı bile. Herkes çok heyecanlı.&lt;/p&gt;
&lt;p&gt;Ve sonra o e-posta geliyor.&lt;/p&gt;
&lt;p&gt;Büyük bir iş ortağının &amp;ldquo;Eski Entegrasyonlar&amp;rdquo; ekibinden veya belki de 2014&amp;rsquo;ten beri yazılım güncellemesi almamış bir donanım cihazını yöneten bir tedarikçiden geliyor. Mesajları kibar ama net:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;&amp;ldquo;Bir REST API tüketemeyiz. Sistemlerimiz bir SOAP gerektiriyor. WSDL&amp;rsquo;in olduğu gibi kalmasına ihtiyacımız var. Eğer kontratı değiştirirseniz, size entegre olamayız.&amp;rdquo;&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Aniden, migration planınız bir tuğla duvara çarpıyor. İki kötü seçenekle karşı karşıyasınız:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;migrationü iptal etmek:&lt;/strong&gt; Sadece tek bir arayüzü memnun etmek için tüm projeyi .NET Framework üzerinde tutmak.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&amp;ldquo;Sidecar&amp;rdquo; Kabusu:&lt;/strong&gt; Yeni sistemi .NET Core&amp;rsquo;da kurmak, ancak sadece SOAP isteklerini yeni API&amp;rsquo;ye proxy etmek için çalışan bir zombi .NET Framework sunucusunu ayakta tutmak.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Ancak üçüncü bir seçenek daha var. Bir köprü.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;CoreWCF.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;CoreWCF, barındırma modelini, Dependency Injection ve performans özelliklerini modern .NET kullanarak tamamen yeniden yazarken, dış kontratı (SOAP) eski sistemle aynı tutmamıza olanak tanır.&lt;/p&gt;
&lt;p&gt;Bu yazıda, bir &lt;code&gt;InvoiceService&lt;/code&gt;&amp;lsquo;i adım adım taşıyacağız. Ham bir uç noktayla (endpoint) başlayacak, onu modern yetkilendirme ile güvenli hale getirecek ve son olarak RESTful &lt;code&gt;ProblemDetails&lt;/code&gt; ile SOAP Hataları (Faults) arasındaki boşluğu dolduran sofistike bir hata yönetimi uygulayacağız.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="adım-1-migrationün-merhaba-dünyası"&gt;Adım 1: migrationün &amp;ldquo;Merhaba Dünya&amp;quot;sı&lt;/h2&gt;
&lt;p&gt;İlk hedefimiz sadece ışıkları yakmak. &lt;code&gt;IInvoiceService&lt;/code&gt; kontratını IIS yerine Kestrel web sunucusu kullanarak sunabileceğimizi kanıtlamak istiyoruz.&lt;/p&gt;
&lt;p&gt;Henüz güvenlik veya hata yönetimi hakkında endişelenmiyoruz. Sadece XML&amp;rsquo;in aktığını görmek istiyoruz.&lt;/p&gt;
&lt;h3 id="kontrat-ve-servis"&gt;Kontrat ve Servis&lt;/h3&gt;
&lt;p&gt;Servis Kontratımızı eski sistemdekiyle neredeyse tamamen aynı tutuyoruz. Tek fark, eski WCF uygulamasına kıyasla büyük bir performans kazancı sağlayan gerçek asenkron işlemeyi yani &lt;code&gt;Task&amp;lt;T&amp;gt;&lt;/code&gt; yapısını benimsiyor olmamız. Ayrıca, Mediator desenine daha iyi uyması için mevcut istek ve yanıt modellerini birazcık yeniden adlandırdık.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-csharp" data-lang="csharp"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// Sample.API.WCF/IInvoiceService.cs&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;using&lt;/span&gt; CoreWCF;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;using&lt;/span&gt; Sample.Business.Features.Invoices.CreateInvoice;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;namespace&lt;/span&gt; Sample.API.WCF;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;[ServiceContract]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;interface&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;IInvoiceService&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt; [OperationContract]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Task&amp;lt;CreateInvoiceCommandResponse&amp;gt; CreateInvoice(CreateInvoiceCommand command);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// Dikkat: Henüz burada hiçbir attribute yok, sadece saf mantık.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;class&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;InvoiceService&lt;/span&gt;(ISender mediator) : IInvoiceService
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;async&lt;/span&gt; Task&amp;lt;CreateInvoiceCommandResponse&amp;gt; CreateInvoice(CreateInvoiceCommand command)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;await&lt;/span&gt; mediator.Send(command);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="konfigürasyon-1-adım"&gt;Konfigürasyon (1. Adım)&lt;/h3&gt;
&lt;p&gt;İşte en temel kurulum için &lt;code&gt;Program.cs&lt;/code&gt;. Gereksiz her şeyi çıkarıyoruz.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-csharp" data-lang="csharp"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// Program.cs - Adım 1: Temel WCF Barındırma&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;using&lt;/span&gt; CoreWCF;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;using&lt;/span&gt; CoreWCF.Configuration;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;using&lt;/span&gt; CoreWCF.Description;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;using&lt;/span&gt; Sample.API.WCF;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;using&lt;/span&gt; CoreWCF.Description;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; builder = WebApplication.CreateBuilder(args);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// 1. CoreWCF temellerini DI container&amp;#39;a ekle&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;builder.Services.AddTransient&amp;lt;InvoiceService&amp;gt;();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;builder.Services.AddServiceModelServices();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;builder.Services.AddServiceModelMetadata();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;services.AddSingleton&amp;lt;IServiceBehavior, UseRequestHeadersForMetadataAddressBehavior&amp;gt;();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// 2. MediatR ve Servisimizi Kaydet&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;builder.Services.AddMediatR(cfg =&amp;gt; cfg.RegisterServicesFromAssembly(&lt;span style="color:#66d9ef"&gt;typeof&lt;/span&gt;(Program).Assembly));
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; app = builder.Build();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// 3. CoreWCF Middleware&amp;#39;ini Yapılandır&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;app.UseServiceModel(serviceBuilder =&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; serviceBuilder.AddService&amp;lt;InvoiceService&amp;gt;()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; .AddServiceEndpoint&amp;lt;InvoiceService, IInvoiceService&amp;gt;(
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; BasicHttpBinding(), &lt;span style="color:#e6db74"&gt;&amp;#34;/Services/InvoiceService.svc&amp;#34;&lt;/span&gt; &lt;span style="color:#75715e"&gt;// Uç nokta URL&amp;#39;si&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; );
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// İnatçı istemcinin referansını güncellemesi gerekirse diye WSDL üretimini etkinleştir&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; serviceMetadataBehavior = app.Services.GetRequiredService&amp;lt;ServiceMetadataBehavior&amp;gt;();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; serviceMetadataBehavior.HttpGetEnabled = &lt;span style="color:#66d9ef"&gt;true&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;});
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;app.Run();
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Bu noktada, taşıma katmanında zafer kazandık. Entegratör &lt;code&gt;/Services/InvoiceService.svc&lt;/code&gt; adresine gelebilir, bir XML/SOAP zarfı gönderebilir ve modern MediatR işleyicilerimiz iş kurallarını işler.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="adım-2-yetkilendirme-authorization-ekleme"&gt;Adım 2: Yetkilendirme (Authorization) Ekleme&lt;/h2&gt;
&lt;p&gt;&amp;ldquo;Sade&amp;rdquo; servis çalışıyor ama tehlikeli. Eski WCF günlerinde, geliştiriciler çoğu zaman uç noktalara herhangi bir yetkilendirme desteği eklemezdi, güvenliği yapılandırmak güvenlik sertifikalarıyla uğraşmak demekti.&lt;/p&gt;
&lt;p&gt;CoreWCF&amp;rsquo;de, &lt;strong&gt;güvenlik sadece ASP.NET Core güvenliğidir&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Eğer REST API&amp;rsquo;niz JWT Bearer tokenları veya Cookie Auth kullanıyorsa, CoreWCF uç noktalarınız da bunları kullanabilir. Tekerleği yeniden icat etmemize gerek yok. Servisimizi basitçe &lt;code&gt;[Authorize]&lt;/code&gt; ile süslüyoruz.&lt;/p&gt;
&lt;h3 id="güncellenmiş-servis"&gt;Güncellenmiş Servis&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;[Authorize]&lt;/code&gt; ekliyoruz ve kullanıcı bağlamına erişebildiğimizi kanıtlamak için &lt;code&gt;IHttpContextAccessor&lt;/code&gt; enjekte ediyoruz.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-csharp" data-lang="csharp"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;using&lt;/span&gt; CoreWCF;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;using&lt;/span&gt; MediatR;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;using&lt;/span&gt; Microsoft.AspNetCore.Authorization;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;using&lt;/span&gt; Sample.Shared.Services.Interfaces;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;namespace&lt;/span&gt; Sample.API.WCF;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;[Authorize]&lt;/span&gt; &lt;span style="color:#75715e"&gt;// &amp;lt;--- Sihirli kelime. Bu standart ASP.NET Core yetkilendirmesidir.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;class&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;InvoiceService&lt;/span&gt;(ISender mediator, IHttpContextAccessor httpContextAccessor) : IInvoiceService
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;async&lt;/span&gt; Task&amp;lt;Response&amp;gt; CreateInvoice(CreateInvoiceCommand command)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; context = httpContextAccessor.HttpContext;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Console.WriteLine(context?.User.Identity?.Name);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;await&lt;/span&gt; mediator.Send(command);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="konfigürasyon-2-adım"&gt;Konfigürasyon (2. Adım)&lt;/h3&gt;
&lt;p&gt;ASP.NET Core Auth ara yazılımını (middleware), CoreWCF ara yazılımından &lt;em&gt;önce&lt;/em&gt; katmanlamamız gerekiyor.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-csharp" data-lang="csharp"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// Program.cs - Adım 2: Yetkilendirme Ekleme&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;using&lt;/span&gt; CoreWCF.Configuration;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;using&lt;/span&gt; CoreWCF.Description;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;using&lt;/span&gt; Sample.API.WCF;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;using&lt;/span&gt; Microsoft.AspNetCore.Authentication.JwtBearer; &lt;span style="color:#75715e"&gt;// JWT varsayıyoruz&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; builder = WebApplication.CreateBuilder(args);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// 1. Standart ASP.NET Core Kimlik Doğrulama Kurulumu&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; .AddJwtBearer(JwtBearerDefaults.AuthenticationScheme, options =&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; options.MapInboundClaims = &lt;span style="color:#66d9ef"&gt;false&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; options.TokenValidationParameters = &lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; TokenValidationParameters
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ValidateIssuer = &lt;span style="color:#66d9ef"&gt;true&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ValidateAudience = &lt;span style="color:#66d9ef"&gt;true&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ValidateLifetime = &lt;span style="color:#66d9ef"&gt;true&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ValidateIssuerSigningKey = &lt;span style="color:#66d9ef"&gt;true&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ValidIssuer = configuration[&lt;span style="color:#e6db74"&gt;&amp;#34;Jwt:Issuer&amp;#34;&lt;/span&gt;],
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ValidAudience = configuration[&lt;span style="color:#e6db74"&gt;&amp;#34;Jwt:Audience&amp;#34;&lt;/span&gt;],
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; IssuerSigningKey = &lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; SymmetricSecurityKey(Encoding.UTF8.GetBytes(configuration[&lt;span style="color:#e6db74"&gt;&amp;#34;Jwt:SecretKey&amp;#34;&lt;/span&gt;]!))
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; };
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; });
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;builder.Services.AddAuthorization(options =&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; options.DefaultPolicy = &lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; AuthorizationPolicyBuilder(JwtBearerDefaults.AuthenticationScheme)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; .RequireAuthenticatedUser()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; .Build();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;});
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;builder.Services.AddTransient&amp;lt;InvoiceService&amp;gt;();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;builder.Services.AddServiceModelServices();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;builder.Services.AddServiceModelMetadata();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;services.AddSingleton&amp;lt;IServiceBehavior, UseRequestHeadersForMetadataAddressBehavior&amp;gt;();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;builder.Services.AddMediatR(cfg =&amp;gt; cfg.RegisterServicesFromAssembly(&lt;span style="color:#66d9ef"&gt;typeof&lt;/span&gt;(Program).Assembly));
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; app = builder.Build();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// 2. ÖNEMLİ: Auth middleware WCF middleware&amp;#39;den önce çalışmalıdır&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;app.UseAuthentication();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;app.UseAuthorization();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;app.UseServiceModel(serviceBuilder =&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; serviceBuilder.AddService&amp;lt;InvoiceService&amp;gt;()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; .AddServiceEndpoint&amp;lt;InvoiceService, IInvoiceService&amp;gt;(
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; BasicHttpBinding(), &lt;span style="color:#e6db74"&gt;&amp;#34;/Services/InvoiceService.svc&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; );
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; serviceMetadataBehavior = app.Services.GetRequiredService&amp;lt;ServiceMetadataBehavior&amp;gt;();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; serviceMetadataBehavior.HttpGetEnabled = &lt;span style="color:#66d9ef"&gt;true&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;});
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;app.Run();
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Şimdi, eğer entegratör geçerli bir standart HTTP &lt;code&gt;Authorization: Bearer&lt;/code&gt; header&amp;rsquo;ı olmadan bir istek gönderirse, CoreWCF bunu kodumuza daha ulaşmadan 401 ile reddedecektir. Elbette müşterimizin istek yapmadan önce ek bir adım uygulaması gerekir ancak authn &amp;amp; authz olmadan herhangi bir uygulama saldırılara açık olacaktır.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="adım-3-evrensel-çevirmen-birleşik-hata-yönetimi"&gt;Adım 3: Evrensel Çevirmen (Birleşik Hata Yönetimi)&lt;/h2&gt;
&lt;p&gt;Hikayenin en zor kısmı burası.&lt;/p&gt;
&lt;p&gt;Modern bir REST API&amp;rsquo;de, bir şeyler ters gittiğinde (örneğin &amp;ldquo;Fatura bulunamadı&amp;rdquo;), özel bir &lt;code&gt;BadRequestException&lt;/code&gt; fırlatırız ve bir JSON &lt;code&gt;ProblemDetails&lt;/code&gt; (RFC 7807) yanıtı döndürürüz.&lt;/p&gt;
&lt;p&gt;WCF&amp;rsquo;de ise, XML içine sarılmış bir &lt;code&gt;FaultException&lt;/code&gt; döndürmemiz beklenir.&lt;/p&gt;
&lt;p&gt;Eğer bunu yönetmezsek, güzelim &lt;code&gt;ValidationException&lt;/code&gt;&amp;lsquo;ımız servisi çökertecek ve istemciye genel, çirkin bir &amp;ldquo;Internal Server Error&amp;rdquo; dönecektir. Modern Exception&amp;rsquo;larımızı yakalayan ve onları hala tüm zengin verileri (doğrulama hata alanları gibi) içeren SOAP Hatalarına (Faults) çeviren bir tercümana ihtiyacımız var.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-csharp" data-lang="csharp"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;using&lt;/span&gt; System.Diagnostics;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;using&lt;/span&gt; System.Xml.Linq;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;using&lt;/span&gt; CoreWCF;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;using&lt;/span&gt; CoreWCF.Channels;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;using&lt;/span&gt; CoreWCF.Dispatcher;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;using&lt;/span&gt; Sample.Domain.Constants;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;using&lt;/span&gt; Sample.Domain.Exceptions;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;using&lt;/span&gt; Microsoft.AspNetCore.Mvc;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;using&lt;/span&gt; Microsoft.Extensions.Localization;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;namespace&lt;/span&gt; Sample.API.Filters;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;/// &amp;lt;summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;/// Exception&amp;#39;ları yakalayan ve ProblemDetails&amp;#39;i SOAP Fault olarak döndüren CoreWCF Hata İşleyicisi.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;/// Bu, modern Exception tanımları ile eski SOAP hataları arasında bir köprü görevi görür.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;/// &amp;lt;/summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;class&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;ExceptionHandlerErrorHandler&lt;/span&gt; : IErrorHandler
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;private&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;const&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;string&lt;/span&gt; HttpRfcBadRequest = &lt;span style="color:#e6db74"&gt;&amp;#34;https://tools.ietf.org/html/rfc7231#section-6.5.1&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;private&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;const&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;string&lt;/span&gt; ProblemDetailsNamespace = &lt;span style="color:#e6db74"&gt;&amp;#34;urn:ietf:rfc:7807&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;private&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;readonly&lt;/span&gt; ILogger&amp;lt;ExceptionHandlerErrorHandler&amp;gt; _logger;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;private&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;readonly&lt;/span&gt; IHttpContextAccessor _httpContextAccessor;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;private&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;readonly&lt;/span&gt; IStringLocalizer&amp;lt;ExceptionHandleMiddleware&amp;gt; _localizer;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;private&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;readonly&lt;/span&gt; Dictionary&amp;lt;Type, Func&amp;lt;Exception, ProblemDetails&amp;gt;&amp;gt; _exceptionHandlers;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; ExceptionHandlerErrorHandler(
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ILogger&amp;lt;ExceptionHandlerErrorHandler&amp;gt; logger,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; IHttpContextAccessor httpContextAccessor,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; IStringLocalizer&amp;lt;ExceptionHandleMiddleware&amp;gt; localizer)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; _logger = logger;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; _httpContextAccessor = httpContextAccessor;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; _localizer = localizer;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// Domain exception&amp;#39;larını belirli ProblemDetails işleyicilerine eşle&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; _exceptionHandlers = &lt;span style="color:#66d9ef"&gt;new&lt;/span&gt;()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; { &lt;span style="color:#66d9ef"&gt;typeof&lt;/span&gt;(BadRequestException), HandleBadRequestException },
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// İstediğiniz kadar ekleyin&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; };
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;bool&lt;/span&gt; HandleError(Exception error)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// Hatayı işlediğimizi ve oturumun hatalı olmadığını belirtmek için true döndür&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;true&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;void&lt;/span&gt; ProvideFault(Exception error, MessageVersion version, &lt;span style="color:#66d9ef"&gt;ref&lt;/span&gt; Message fault)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// 1. Exception&amp;#39;ı ProblemDetails&amp;#39;e dönüştür&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; problemDetails = CreateProblemDetails(error);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// 2. ProblemDetails&amp;#39;i SOAP Body için XML Elementine dönüştür&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; problemDetailsElement = TransformToXml(problemDetails);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// 3. Problem detaylarını içeren bir FaultException oluştur&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; faultException = &lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; FaultException&amp;lt;XElement&amp;gt;(
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; problemDetailsElement,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; FaultReason(problemDetails.Title ?? &lt;span style="color:#e6db74"&gt;&amp;#34;An error occurred&amp;#34;&lt;/span&gt;),
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; FaultCode(GetFaultCodeFromStatus(problemDetails.Status ?? &lt;span style="color:#ae81ff"&gt;500&lt;/span&gt;)),
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;ProblemDetails&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; messageFault = faultException.CreateMessageFault();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; fault = Message.CreateMessage(version, messageFault, faultException.Action);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// 4. Temel HTTP durum kodunu ayarla (izleme/gözlemlenebilirlik için önemli)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; SetHttpStatusCode(problemDetails.Status ?? StatusCodes.Status500InternalServerError);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;private&lt;/span&gt; ProblemDetails CreateProblemDetails(Exception exception)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; exceptionType = exception.GetType();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; (_exceptionHandlers.TryGetValue(exceptionType, &lt;span style="color:#66d9ef"&gt;out&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; handler))
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; _logger.LogWarning(&lt;span style="color:#e6db74"&gt;&amp;#34;Handled Exception {ExceptionMessage}: Time {Time}&amp;#34;&lt;/span&gt;, exception.Message, DateTime.UtcNow);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; pd = handler(exception);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; EnrichProblemDetails(pd);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; pd;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; _logger.LogError(exception, &lt;span style="color:#e6db74"&gt;&amp;#34;Unhandled Exception: Time {Time}&amp;#34;&lt;/span&gt;, DateTime.UtcNow);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; unknownPd = HandleUnknownException(exception);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; EnrichProblemDetails(unknownPd);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; unknownPd;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// --- Özel Exception İşleyicileri ---&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;private&lt;/span&gt; ProblemDetails HandleBadRequestException(Exception ex)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; exception = (BadRequestException)ex;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; ProblemDetails
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Status = StatusCodes.Status400BadRequest,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Type = HttpRfcBadRequest,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Title = _localizer[Localized.BadRequestExceptionTitle],
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Detail = _localizer[exception.LocalizedMessage, exception.Arguments]
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; };
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;private&lt;/span&gt; ProblemDetails HandleValidationException(Exception ex)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; exception = (ValidationException)ex;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; ValidationProblemDetails(exception.Errors)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Status = StatusCodes.Status400BadRequest,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Type = HttpRfcBadRequest,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Title = _localizer[Localized.ValidationExceptionTitle]
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; };
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;private&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;static&lt;/span&gt; ProblemDetails HandleUnknownException(Exception ex)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; ProblemDetails
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Status = StatusCodes.Status500InternalServerError,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Type = HttpRfcInternalError,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Title = &lt;span style="color:#e6db74"&gt;&amp;#34;Beklenmeyen bir hata oluştu&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Detail = ex.Message
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; };
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// --- Yardımcılar ---&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;private&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;static&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;void&lt;/span&gt; EnrichProblemDetails(ProblemDetails result)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// Hata raporlaması için dağıtık izleme (distributed tracing) ID&amp;#39;lerini ekle&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; correlation = AsyncStorage&amp;lt;Correlation&amp;gt;.Retrieve();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; (correlation &lt;span style="color:#66d9ef"&gt;is&lt;/span&gt; not &lt;span style="color:#66d9ef"&gt;null&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; result.Extensions.Add(&lt;span style="color:#e6db74"&gt;&amp;#34;correlationId&amp;#34;&lt;/span&gt;, correlation.Id);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; traceId = Activity.Current?.Id;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; (traceId &lt;span style="color:#66d9ef"&gt;is&lt;/span&gt; not &lt;span style="color:#66d9ef"&gt;null&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; result.Extensions.Add(nameof(traceId), traceId);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;private&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;static&lt;/span&gt; XElement TransformToXml(ProblemDetails problemDetails)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; ns = XNamespace.Get(ProblemDetailsNamespace);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; element = &lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; XElement(ns + &lt;span style="color:#e6db74"&gt;&amp;#34;problem&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; XElement(ns + &lt;span style="color:#e6db74"&gt;&amp;#34;type&amp;#34;&lt;/span&gt;, problemDetails.Type),
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; XElement(ns + &lt;span style="color:#e6db74"&gt;&amp;#34;title&amp;#34;&lt;/span&gt;, problemDetails.Title),
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; XElement(ns + &lt;span style="color:#e6db74"&gt;&amp;#34;status&amp;#34;&lt;/span&gt;, problemDetails.Status),
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; XElement(ns + &lt;span style="color:#e6db74"&gt;&amp;#34;detail&amp;#34;&lt;/span&gt;, problemDetails.Detail ?? &lt;span style="color:#66d9ef"&gt;string&lt;/span&gt;.Empty));
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;foreach&lt;/span&gt; (&lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; extension &lt;span style="color:#66d9ef"&gt;in&lt;/span&gt; problemDetails.Extensions)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; element.Add(&lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; XElement(ns + extension.Key, extension.Value?.ToString() ?? &lt;span style="color:#66d9ef"&gt;string&lt;/span&gt;.Empty));
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; (problemDetails &lt;span style="color:#66d9ef"&gt;is&lt;/span&gt; ValidationProblemDetails validationProblemDetails &amp;amp;&amp;amp;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; validationProblemDetails.Errors.Count &amp;gt; &lt;span style="color:#ae81ff"&gt;0&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; errorsElement = &lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; XElement(ns + &lt;span style="color:#e6db74"&gt;&amp;#34;errors&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;foreach&lt;/span&gt; (&lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; error &lt;span style="color:#66d9ef"&gt;in&lt;/span&gt; validationProblemDetails.Errors)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; errorElement = &lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; XElement(ns + &lt;span style="color:#e6db74"&gt;&amp;#34;error&amp;#34;&lt;/span&gt;, &lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; XAttribute(&lt;span style="color:#e6db74"&gt;&amp;#34;field&amp;#34;&lt;/span&gt;, error.Key));
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;foreach&lt;/span&gt; (&lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; message &lt;span style="color:#66d9ef"&gt;in&lt;/span&gt; error.Value)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; errorElement.Add(&lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; XElement(ns + &lt;span style="color:#e6db74"&gt;&amp;#34;message&amp;#34;&lt;/span&gt;, message));
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; errorsElement.Add(errorElement);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; element.Add(errorsElement);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; element;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;private&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;static&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;string&lt;/span&gt; GetFaultCodeFromStatus(&lt;span style="color:#66d9ef"&gt;int&lt;/span&gt; status)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; status &lt;span style="color:#66d9ef"&gt;switch&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#ae81ff"&gt;400&lt;/span&gt; =&amp;gt; &lt;span style="color:#e6db74"&gt;&amp;#34;BadRequest&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#ae81ff"&gt;401&lt;/span&gt; =&amp;gt; &lt;span style="color:#e6db74"&gt;&amp;#34;Unauthorized&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#ae81ff"&gt;403&lt;/span&gt; =&amp;gt; &lt;span style="color:#e6db74"&gt;&amp;#34;Forbidden&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#ae81ff"&gt;404&lt;/span&gt; =&amp;gt; &lt;span style="color:#e6db74"&gt;&amp;#34;NotFound&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; _ =&amp;gt; &lt;span style="color:#e6db74"&gt;&amp;#34;InternalServerError&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; };
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;private&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;void&lt;/span&gt; SetHttpStatusCode(&lt;span style="color:#66d9ef"&gt;int&lt;/span&gt; statusCode)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; httpContext = _httpContextAccessor.HttpContext;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; (httpContext &lt;span style="color:#66d9ef"&gt;is&lt;/span&gt; not &lt;span style="color:#66d9ef"&gt;null&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; httpContext.Response.StatusCode = statusCode;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="konfigürasyon-3-adım"&gt;Konfigürasyon (3. Adım)&lt;/h3&gt;
&lt;p&gt;Bu hata işleyicisini bir &lt;code&gt;IServiceBehavior&lt;/code&gt; olarak bağlamamız gerekiyor. Bu, servise yapılan her bir çağrı için geçerli olmasını sağlar.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-csharp" data-lang="csharp"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// Program.cs - Adım 3: Hata Yönetimi ile Üretime Hazır&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;using&lt;/span&gt; CoreWCF;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;using&lt;/span&gt; CoreWCF.Channels;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;using&lt;/span&gt; CoreWCF.Configuration;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;using&lt;/span&gt; CoreWCF.Description;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;using&lt;/span&gt; CoreWCF.Dispatcher;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;using&lt;/span&gt; Sample.API.Filters; &lt;span style="color:#75715e"&gt;// ErrorHandler&amp;#39;ımızın olduğu yer&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;using&lt;/span&gt; Sample.API.WCF;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;using&lt;/span&gt; Microsoft.Extensions.DependencyInjection.Extensions;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; builder = WebApplication.CreateBuilder(args);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; .AddJwtBearer(JwtBearerDefaults.AuthenticationScheme, options =&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; options.MapInboundClaims = &lt;span style="color:#66d9ef"&gt;false&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; options.TokenValidationParameters = &lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; TokenValidationParameters
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ValidateIssuer = &lt;span style="color:#66d9ef"&gt;true&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ValidateAudience = &lt;span style="color:#66d9ef"&gt;true&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ValidateLifetime = &lt;span style="color:#66d9ef"&gt;true&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ValidateIssuerSigningKey = &lt;span style="color:#66d9ef"&gt;true&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ValidIssuer = configuration[&lt;span style="color:#e6db74"&gt;&amp;#34;Jwt:Issuer&amp;#34;&lt;/span&gt;],
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ValidAudience = configuration[&lt;span style="color:#e6db74"&gt;&amp;#34;Jwt:Audience&amp;#34;&lt;/span&gt;],
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; IssuerSigningKey = &lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; SymmetricSecurityKey(Encoding.UTF8.GetBytes(configuration[&lt;span style="color:#e6db74"&gt;&amp;#34;Jwt:SecretKey&amp;#34;&lt;/span&gt;]!))
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; };
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; });
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;builder.Services.AddAuthorization(options =&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; options.DefaultPolicy = &lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; AuthorizationPolicyBuilder(JwtBearerDefaults.AuthenticationScheme)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; .RequireAuthenticatedUser()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; .Build();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;});
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;builder.Services.AddTransient&amp;lt;InvoiceService&amp;gt;();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;builder.Services.AddServiceModelServices();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;builder.Services.AddServiceModelMetadata();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;builder.Services.AddSingleton&amp;lt;IServiceBehavior, UseRequestHeadersForMetadataAddressBehavior&amp;gt;();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;builder.Services.AddSingleton&amp;lt;IErrorHandler, ExceptionHandlerErrorHandler&amp;gt;(); &lt;span style="color:#75715e"&gt;// Bu satırların&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;builder.Services.AddSingleton&amp;lt;IServiceBehavior, ExceptionHandlerServiceBehaviour&amp;gt;(); &lt;span style="color:#75715e"&gt;// eklenmesi gerek.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; app = builder.Build();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;app.UseAuthentication();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;app.UseAuthorization();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;app.UseServiceModel(serviceBuilder =&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; serviceBuilder.AddService&amp;lt;InvoiceService&amp;gt;()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; .AddServiceEndpoint&amp;lt;InvoiceService, IInvoiceService&amp;gt;(
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; BasicHttpBinding(), &lt;span style="color:#e6db74"&gt;&amp;#34;/Services/InvoiceService.svc&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; );
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; serviceMetadataBehavior = app.Services.GetRequiredService&amp;lt;ServiceMetadataBehavior&amp;gt;();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; serviceMetadataBehavior.HttpGetEnabled = &lt;span style="color:#66d9ef"&gt;true&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;});
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;app.Run();
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="mutlu-son"&gt;Mutlu Son&lt;/h3&gt;
&lt;ol start="3"&gt;
&lt;li&gt;
&lt;p&gt;Adım itibarıyla dikkate değer bir şey başardık:&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Entegratör&lt;/strong&gt; mutlu çünkü hala &lt;code&gt;/Services/InvoiceService.svc&lt;/code&gt; adresine SOAP istekleri gönderiyorlar. Entegrasyon desenlerini değiştirmek zorunda kalmadılar.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Geliştirici&lt;/strong&gt; mutlu çünkü &lt;code&gt;InvoiceService&lt;/code&gt; tertemiz. &lt;code&gt;MediatR&lt;/code&gt; kullanıyor. İş mantığını kirleten &lt;code&gt;try/catch&lt;/code&gt; blokları yok. Güvenlik için &lt;code&gt;[Authorize]&lt;/code&gt; kullanıyor.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Operasyon Ekibi&lt;/strong&gt; mutlu çünkü loglar, istek bir REST istemcisinden de gelse SOAP istemcisinden de gelse tutarlı &lt;code&gt;ProblemDetails&lt;/code&gt; yapılarını gösteriyor.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Sadece kodu &amp;ldquo;port&amp;rdquo; etmedik; etrafındaki ekosistemi modernize ettik ve WCF arayüzünü bir pranga yerine bir uyumluluk katmanı olarak bıraktık.&lt;/p&gt;</content></item><item><title>.NET Mikroservislerinde gRPC: REST ile Karşılaştırma</title><link>https://berkselvi.dev/tr/posts/grpc-in-net-microservices-comparison-with-rest/</link><pubDate>Wed, 08 Oct 2025 00:00:00 +0300</pubDate><guid>https://berkselvi.dev/tr/posts/grpc-in-net-microservices-comparison-with-rest/</guid><description>&lt;p&gt;.NET mikroservislerinin birbirleriyle senkron iletişim kurması gerektiğinde, genellikle iki ana seçenek önümüze çıkar: REST API ve gRPC API.&lt;/p&gt;
&lt;p&gt;REST, HTTP/1.1 protokolü üzerinde JSON ile çalışır; kolay okunur, tüm tarayıcılarda desteklenir ve debug işlemi basittir. Ancak REST API’lerde veri paketleri gRPC’ye kıyasla daha büyüktür ve her istek kendi TCP bağlantısını açar. gRPC ise veri iletimini Protocol Buffers ile sıkıştırır ve tüm trafiği tek bir HTTP/2 bağlantısı üzerinden aktararak gecikmeyi minimuma indirir. Bu yazımda hem REST hem gRPC teknolojilerinin çalışma mantığını, .NET 9 üzerinde örneklerle karşılaştırmasını ve hangi senaryolarda hangi çözümün daha avantajlı olduğunu inceleyeceğiz. Haydi başlayalım.&lt;/p&gt;</description><content>&lt;p&gt;.NET mikroservislerinin birbirleriyle senkron iletişim kurması gerektiğinde, genellikle iki ana seçenek önümüze çıkar: REST API ve gRPC API.&lt;/p&gt;
&lt;p&gt;REST, HTTP/1.1 protokolü üzerinde JSON ile çalışır; kolay okunur, tüm tarayıcılarda desteklenir ve debug işlemi basittir. Ancak REST API’lerde veri paketleri gRPC’ye kıyasla daha büyüktür ve her istek kendi TCP bağlantısını açar. gRPC ise veri iletimini Protocol Buffers ile sıkıştırır ve tüm trafiği tek bir HTTP/2 bağlantısı üzerinden aktararak gecikmeyi minimuma indirir. Bu yazımda hem REST hem gRPC teknolojilerinin çalışma mantığını, .NET 9 üzerinde örneklerle karşılaştırmasını ve hangi senaryolarda hangi çözümün daha avantajlı olduğunu inceleyeceğiz. Haydi başlayalım.&lt;/p&gt;
&lt;h2 id="nedir-bu-rpc"&gt;Nedir bu RPC?&lt;/h2&gt;
&lt;p&gt;Uzun süredir .NET ekosisteminde uygulama geliştiriyorsanız RPC kavramıyla büyük ihtimal ilk kez karşılaşmıyorsunuz. SOAP tabanlı, IIS üzerinden WCF servisleri kullanarak servisler arası iletişim .NET Framework zamanlarındada yapılıyordu, ancak bu framework ile XML tipinkedi mesajlarla boğuşup, .NET ekosisteminde sıkışıp kalabiliyorduk.&lt;/p&gt;
&lt;p&gt;Bunun yerine HTTP/2 üzerinden çok daha performanslı, cross-platform ve cross-language çalışabilen gRPC, yeni projelerde seçebileceğimiz en mantıklı RPC framework’ü haline geliyor.&lt;/p&gt;
&lt;h2 id="rest-ile-grpc-arasındaki-farklar"&gt;REST ile gRPC arasındaki farklar&lt;/h2&gt;
&lt;p&gt;Mikroservis mimarisinde, servisler arası iletişim için kullanabileceğimiz tek seçenek RPC değildir. REST API yaklaşımı da, servislerin birbirleriyle kolayca ve yaygın şekilde iletişim kurmasını sağlar. Ancak, REST ve gRPC arasında hem mimari tasarım hem de teknik uygulama açısından önemli farklar bulunmaktadır. Bu farklılıkları tasarım felsefesi ve teknik farklar altında inceleyelim.&lt;/p&gt;
&lt;h3 id="1-tasarım-felsefesi"&gt;1. Tasarım Felsefesi&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;REST&lt;/strong&gt; ve &lt;strong&gt;gRPC&lt;/strong&gt;, API tasarımında farklı bakış açılarına sahiptir. REST’te temel yaklaşım, her şeyi bir “kaynak” (resource) olarak görmek ve bu kaynaklar üzerinde işlem yapmaktır. HTTP metodları (GET, POST, PUT, DELETE) yapılan işlemi temsil eder, URL ise üzerinde çalışılan kaynağı belirtir. Örneğin &lt;code&gt;/api/books&lt;/code&gt; adresi bir kitaplar kaynağıdır, bu şekilde bir endpointe sahipseniz onun üzerinde yapabileceğiniz işlemleri az çok tahmin edebilirsiniz;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;GET /api/books&lt;/code&gt; isteği kitap listesini döndürür,&lt;/li&gt;
&lt;li&gt;&lt;code&gt;POST /api/books&lt;/code&gt; ile yeni bir kitap eklersiniz,&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;gRPC’de ise bakış açısı daha çok “eylem” (action) veya “metot” (method) odaklıdır. Yani her bir işlem, bir fonksiyon çağrısı gibi tanımlanır. gRPC’de doğrudan hangi işlemi yapmak istediğinizi belirtirsiniz ve URL tasarımına veya HTTP metodlarına bağlı kalmazsınız.&lt;/p&gt;
&lt;p&gt;Örneğin,&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;BookService.GetBooks()&lt;/code&gt; ile kitapları listelersiniz,&lt;/li&gt;
&lt;li&gt;&lt;code&gt;BookService.CreateBook()&lt;/code&gt; ile yeni bir kitap eklersiniz.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;REST’te de işlemsel endpoint’ler tanımlamak teknik olarak mümkün olsa da, bu genellikle best practice’lere ve standart REST felsefesine uygun değildir.&lt;/p&gt;
&lt;h3 id="2-teknik-farklılıklar"&gt;2. Teknik Farklılıklar&lt;/h3&gt;
&lt;p&gt;REST ve gRPC arasında, iletişimden veri formatına, bağlantı yönetiminden hata kontrolüne kadar birçok teknik fark bulunur. Aşağıda en önemli başlıklar ve farklar özetlenmiştir:&lt;/p&gt;
&lt;h3 id="transport-layer"&gt;&lt;strong&gt;Transport Layer&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;REST&lt;/strong&gt; HTTP/1.1 kullanır. Her bir REST API isteği için ayrı bir TCP/TLS bağlantısı kurulur. Bu, özellikle yüksek istek hacminde &lt;strong&gt;head-of-line blocking&lt;/strong&gt; (bir isteğin yavaşlaması diğerlerini de etkiler) gibi performans darboğazlarına neden olabilir. Ayrıca, REST’te çift yönlü sürekli iletişim (örneğin canlı veri akışı veya anlık bildirimler) gerektiren durumlarda WebSocket veya Server-Sent Events gibi ek protokoller kullanmak gerekir. Yani REST, temelinde request-response modeline dayanır ve streaming (akış) desteği doğal değildir.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;gRPC&lt;/strong&gt; ise modern iletişim protokolü olan HTTP/2 ile çalışır. HTTP/2’nin &lt;strong&gt;multiplexing&lt;/strong&gt; özelliği sayesinde tek bir TCP bağlantısı üzerinden eşzamanlı birçok istek ve yanıt taşınabilir. Bu, yüksek performans ve düşük gecikme (latency) sağlar. gRPC, HTTP/2’nin sunduğu native stream ve header compression gibi özelliklerle, gerçek zamanlı ve çift yönlü iletişimi çok daha kolay ve verimli şekilde gerçekleştirir.&lt;/p&gt;
&lt;p&gt;gRPC, hizmetlerin arayüzlerini ve mesaj yapısını genellikle &lt;strong&gt;Protocol Buffers&lt;/strong&gt; (Protobuf) ile tanımlar. Servis tanımı, uzaktan çağrılabilecek metodları ve mesaj şemalarını açıkça belirtir.&lt;/p&gt;
&lt;p&gt;Aşağıda bir örnek görebilirsin&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-protobuf" data-lang="protobuf"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;service&lt;/span&gt; HelloService {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;rpc&lt;/span&gt; SayHello (HelloRequest) &lt;span style="color:#66d9ef"&gt;returns&lt;/span&gt; (HelloResponse);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;message&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;HelloRequest&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;string&lt;/span&gt; greeting &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;1&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;message&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;HelloResponse&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;string&lt;/span&gt; reply &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;1&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;gRPC ile 4 farklı iletişim (RPC) yöntemi mümkündür:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Unary&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;En klasik yöntemdir: istemci bir istek gönderir, sunucu bir yanıt döner. REST’teki request-response modeline benzer. Aşağıdaki şekilde tanımlanabilir.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-protobuf" data-lang="protobuf"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;rpc&lt;/span&gt; SayHello(HelloRequest) &lt;span style="color:#66d9ef"&gt;returns&lt;/span&gt; (HelloResponse);
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;strong&gt;Server Streaming&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;İstemci tek bir istek gönderir, sunucu ise birden fazla yanıt mesajı akışı (stream) dönebilir. İstemci, sunucudan gelen veri akışını tek tek okur.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-protobuf" data-lang="protobuf"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;rpc&lt;/span&gt; LotsOfReplies(HelloRequest) &lt;span style="color:#66d9ef"&gt;returns&lt;/span&gt; (&lt;span style="color:#66d9ef"&gt;stream&lt;/span&gt; HelloResponse);
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;strong&gt;Client Streaming&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;İstemci, sunucuya birden fazla mesaj gönderir (stream). Tüm mesajları gönderdikten sonra sunucudan tek bir yanıt alır. Büyük veri yükleme işlemleri, toplu güncellemeler gibi alanlarda kullanılabilir.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-protobuf" data-lang="protobuf"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;rpc&lt;/span&gt; LotsOfGreetings(&lt;span style="color:#66d9ef"&gt;stream&lt;/span&gt; HelloRequest) &lt;span style="color:#66d9ef"&gt;returns&lt;/span&gt; (HelloResponse);
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;strong&gt;Bidirectional Streaming RPC&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Hem istemci hem sunucu, aynı anda birbirlerine bağımsız şekilde veri akışı gönderebilirler. Akışlar birbirinden bağımsız çalışır; sıralama her bir akış için korunur. Gerçek zamanlı sohbet uygulamaları, canlı veri transferlerinde tercih edilebilir.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-protobuf" data-lang="protobuf"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;rpc&lt;/span&gt; BidiHello(&lt;span style="color:#66d9ef"&gt;stream&lt;/span&gt; HelloRequest) &lt;span style="color:#66d9ef"&gt;returns&lt;/span&gt; (&lt;span style="color:#66d9ef"&gt;stream&lt;/span&gt; HelloResponse);
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="veri"&gt;&lt;strong&gt;Veri&lt;/strong&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;REST:&lt;/strong&gt; Mesajlar genellikle insan tarafından okunabilir &lt;strong&gt;JSON&lt;/strong&gt; formatında taşınır. JSON, okunabilir ve esnek ama büyük veri boyutuna ve yüksek ayrıştırma maliyetine sahiptir. Ayrıca kesin bir şema zorunlu değildir. Genellikle OpenAPI/Swagger ile dokümantasyon eklenir, ancak uygulama sırasında tip uyuşmazlıkları yaşanabilir.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;gRPC:&lt;/strong&gt; &lt;strong&gt;Protocol Buffers (Protobuf)&lt;/strong&gt; adı verilen ikili (binary) ve sıkıştırılmış bir format kullanır. Protobuf mesajları küçük ve hızlıdır, ağ ve CPU maliyeti düşüktür. Tüm veri tipleri ve metodlar &lt;code&gt;.proto&lt;/code&gt; dosyasında tanımlanır. Sunucu ve istemci kodları otomatik olarak üretilir, tip güvenliği yüksektir.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="performans-ve-verimlilik"&gt;&lt;strong&gt;Performans ve Verimlilik&lt;/strong&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;REST:&lt;/strong&gt; JSON’un büyük boyutu ve HTTP/1.1’in sınırlamaları nedeniyle yüksek trafik altında gecikme ve kaynak tüketimi artar.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;gRPC:&lt;/strong&gt; Küçük paket boyutu ve HTTP/2 ile tek bağlantı üzerinden çoklu istek sayesinde daha düşük gecikme, daha fazla RPS (istek/saniye) ve daha verimli ağ kullanımı sağlar.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="tarayıcı-ve-platform-uyumluluğu"&gt;&lt;strong&gt;Tarayıcı ve Platform Uyumluluğu&lt;/strong&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;REST:&lt;/strong&gt; Doğrudan tarayıcıdan çağrılabilir, fetch/AJAX ile kolayca entegre edilir.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;gRPC:&lt;/strong&gt; Tarayıcıdan doğrudan kullanılamaz; gRPC-Web veya JSON Transcoding gibi ek katmanlara ihtiyaç duyacağı için tarayıcı içeren senaryolarda tercih edilmezler.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="hangi-senaryolarda-rest-veya-grpc-seçilmeli"&gt;Hangi Senaryolarda REST veya gRPC Seçilmeli?&lt;/h2&gt;
&lt;p&gt;REST ve gRPC arasında seçim yaparken sadece teknik detaylara bakmak yeterli olmayabilir; iş ihtiyaçları, hedef kullanıcı kitlesi ve entegrasyon gereksinimleri de en az performans kadar önemlidir. İşte bazı tipik senaryolar ve öneriler:&lt;/p&gt;
&lt;h3 id="ne-zaman-rest-kullanılmalı"&gt;Ne Zaman REST Kullanılmalı?&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Tarayıcı veya üçüncü parti entegrasyonu&lt;/strong&gt;: Eğer API’nız doğrudan web tarayıcıları veya harici uygulamalar tarafından tüketilecekse REST her zaman daha yaygın ve erişilebilir bir çözüm sunar.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;public API’ler&lt;/strong&gt;: JSON’un okunabilirliği ve HTTP/1.1’in yaygın desteği sayesinde REST tabanlı API’ler, geliştiriciler tarafından kolayca test edilebilir ve dökümante edilebilir.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Basit CRUD işlemleri&lt;/strong&gt;: Kaynak odaklı (ör. ürünler, siparişler) ve klasik GET/POST/PUT/DELETE işlemleri için REST yalın ve anlaşılırdır.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Cache/Proxy ihtiyaçları&lt;/strong&gt;: CDN veya reverse proxy üzerinden önbellekleme ve yönlendirme yapmak gerekiyorsa, HTTP standardı ile tam uyumlu olduğu için REST daha avantajlıdır.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="ne-zaman-grpc-kullanılmalı"&gt;Ne Zaman gRPC Kullanılmalı?&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Mikroservisler arası iletişim&lt;/strong&gt;: Aynı altyapı içinde, yüksek hacimli ve düşük gecikmeli servis-servis trafiği için gRPC ciddi avantaj sağlar.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Mobil ve IoT cihazları&lt;/strong&gt;: Küçük paket boyutu ve ikili format sayesinde veri tüketimi azalır.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Gerçek zamanlı akış gereksinimi&lt;/strong&gt;: Sunucudan istemciye ya da çift yönlü veri akışının gerektiği chat, canlı skor, bildirim gibi uygulamalarda gRPC doğal akış desteği sunar.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Dil bağımsızlığı&lt;/strong&gt;: Birden çok farklı programlama dili arasında tip güvenli bir iletişim kurmak istiyorsanız, Protobuf sayesinde uyumluluk kolayca sağlanır.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Daha güçlü tip güvenliği&lt;/strong&gt;: Derlenmiş sözleşmeler ve otomatik kod üretimi sayesinde API tüketiminde hata oranı düşer.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="net-ile-kapsamlı-uygulama-örneği-books-servisi"&gt;.NET ile Kapsamlı Uygulama Örneği: Books Servisi&lt;/h2&gt;
&lt;p&gt;Hem REST hem de gRPC ile kitap yönetim servisi oluşturalım. Bu örnekte, gRPC&amp;rsquo;nin sunduğu farklı streaming modlarını da göreceğiz.&lt;/p&gt;
&lt;h3 id="proje-kurulumu"&gt;Proje Kurulumu&lt;/h3&gt;
&lt;p&gt;İlk olarak gerekli paketleri ekleyelim:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;dotnet new web -n BookService
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;cd BookService
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;dotnet add package Grpc.AspNetCore
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;dotnet add package Grpc.AspNetCore.Server.Reflection
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;dotnet add package Grpc.Tools
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="rest-api-implementasyonu"&gt;REST API İmplementasyonu&lt;/h3&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-csharp" data-lang="csharp"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// Models/Book.cs&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;namespace&lt;/span&gt; BookService.Models;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;class&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Book&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;int&lt;/span&gt; Id { &lt;span style="color:#66d9ef"&gt;get&lt;/span&gt;; &lt;span style="color:#66d9ef"&gt;set&lt;/span&gt;; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;string&lt;/span&gt; Title { &lt;span style="color:#66d9ef"&gt;get&lt;/span&gt;; &lt;span style="color:#66d9ef"&gt;set&lt;/span&gt;; } = &lt;span style="color:#66d9ef"&gt;string&lt;/span&gt;.Empty;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;string&lt;/span&gt; Author { &lt;span style="color:#66d9ef"&gt;get&lt;/span&gt;; &lt;span style="color:#66d9ef"&gt;set&lt;/span&gt;; } = &lt;span style="color:#66d9ef"&gt;string&lt;/span&gt;.Empty;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;int&lt;/span&gt; Year { &lt;span style="color:#66d9ef"&gt;get&lt;/span&gt;; &lt;span style="color:#66d9ef"&gt;set&lt;/span&gt;; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;decimal&lt;/span&gt; Price { &lt;span style="color:#66d9ef"&gt;get&lt;/span&gt;; &lt;span style="color:#66d9ef"&gt;set&lt;/span&gt;; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-csharp" data-lang="csharp"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// Controllers/BooksController.cs&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;using&lt;/span&gt; Microsoft.AspNetCore.Mvc;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;using&lt;/span&gt; BookService.Models;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;namespace&lt;/span&gt; BookService.Controllers;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;[ApiController]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;[Route(&amp;#34;api/books&amp;#34;)]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;class&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;BooksController&lt;/span&gt; : ControllerBase
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;private&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;static&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;readonly&lt;/span&gt; List&amp;lt;Book&amp;gt; Books = Book.GenerateBooks();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt; [HttpGet]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; IActionResult GetBooks()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; Ok(Books);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt; [HttpGet(&amp;#34;{id}&amp;#34;)]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; IActionResult GetBook(&lt;span style="color:#66d9ef"&gt;int&lt;/span&gt; id)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; book = Books.FirstOrDefault(b =&amp;gt; b.Id == id);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; (book == &lt;span style="color:#66d9ef"&gt;null&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; NotFound();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; Ok(book);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt; [HttpPost]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; IActionResult CreateBook([FromBody] Book book)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; book.Id = Books.Max(b =&amp;gt; b.Id) + &lt;span style="color:#ae81ff"&gt;1&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Books.Add(book);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; CreatedAtAction(nameof(GetBook), &lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; { id = book.Id }, book);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="grpc-servisi---protobuf-tanımları"&gt;gRPC Servisi - Protobuf Tanımları&lt;/h3&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-protobuf" data-lang="protobuf"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// Protos/books.proto
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;syntax&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;proto3&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;option&lt;/span&gt; csharp_namespace &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;BookService.Grpc&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;package&lt;/span&gt; books;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// Ana servis tanımı
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;service&lt;/span&gt; BookService {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// Unary RPC - Tüm kitapları getir
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;rpc&lt;/span&gt; GetBooks (GetBooksRequest) &lt;span style="color:#66d9ef"&gt;returns&lt;/span&gt; (BookList);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// Server Streaming - Kitapları tek tek stream et
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;rpc&lt;/span&gt; StreamBooks (StreamBooksRequest) &lt;span style="color:#66d9ef"&gt;returns&lt;/span&gt; (&lt;span style="color:#66d9ef"&gt;stream&lt;/span&gt; Book);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// Client Streaming - Toplu kitap ekleme
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;rpc&lt;/span&gt; AddBooksStream (&lt;span style="color:#66d9ef"&gt;stream&lt;/span&gt; Book) &lt;span style="color:#66d9ef"&gt;returns&lt;/span&gt; (AddBooksResponse);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// Bidirectional Streaming - Gerçek zamanlı kitap arama
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;rpc&lt;/span&gt; SearchBooksStream (&lt;span style="color:#66d9ef"&gt;stream&lt;/span&gt; SearchRequest) &lt;span style="color:#66d9ef"&gt;returns&lt;/span&gt; (&lt;span style="color:#66d9ef"&gt;stream&lt;/span&gt; Book);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;message&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Book&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;int32&lt;/span&gt; id &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;1&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;string&lt;/span&gt; title &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;2&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;string&lt;/span&gt; author &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;3&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;int32&lt;/span&gt; year &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;4&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;double&lt;/span&gt; price &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;5&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;message&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;GetBooksRequest&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// Boş istek mesajı
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;message&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;BookList&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;repeated&lt;/span&gt; Book books &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;1&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;message&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;StreamBooksRequest&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;int32&lt;/span&gt; delay_ms &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;1&lt;/span&gt;; &lt;span style="color:#75715e"&gt;// Her kitap arasında gecikme (ms)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;message&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;AddBooksResponse&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;int32&lt;/span&gt; count &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;1&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;string&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;message&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;2&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;message&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;SearchRequest&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;string&lt;/span&gt; query &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;1&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="grpc-servisi---implementasyon"&gt;gRPC Servisi - İmplementasyon&lt;/h3&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-csharp" data-lang="csharp"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// Services/GrpcBookService.cs&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;using&lt;/span&gt; Grpc.Core;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;using&lt;/span&gt; BookService.Grpc;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;namespace&lt;/span&gt; BookService.Services;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;class&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;GrpcBookService&lt;/span&gt; : Grpc.BookService.BookServiceBase
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;private&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;static&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;readonly&lt;/span&gt; List&amp;lt;BookService.Grpc.Book&amp;gt; Books = BookService.Models.Book.GenerateBooks()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; .Select(b =&amp;gt; &lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; BookService.Grpc.Book { Id = b.Id, Title = b.Title, Author = b.Author, Year = b.Year, Price = (&lt;span style="color:#66d9ef"&gt;double&lt;/span&gt;)b.Price })
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; .ToList();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// Unary RPC - Klasik request-response&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;override&lt;/span&gt; Task&amp;lt;BookList&amp;gt; GetBooks(GetBooksRequest request, ServerCallContext context)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; response = &lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; BookList();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; response.Books.AddRange(Books);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; Task.FromResult(response);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// Server Streaming - Sunucu kitapları tek tek stream eder&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;override&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;async&lt;/span&gt; Task StreamBooks(StreamBooksRequest request,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; IServerStreamWriter&amp;lt;BookService.Grpc.Book&amp;gt; responseStream,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ServerCallContext context)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;foreach&lt;/span&gt; (&lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; book &lt;span style="color:#66d9ef"&gt;in&lt;/span&gt; Books)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// İptal kontrolü&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; (context.CancellationToken.IsCancellationRequested)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;break&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;await&lt;/span&gt; responseStream.WriteAsync(book);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// Belirtilen süre kadar bekle (streaming&amp;#39;i göstermek için)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; (request.DelayMs &amp;gt; &lt;span style="color:#ae81ff"&gt;0&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;await&lt;/span&gt; Task.Delay(request.DelayMs);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// Client Streaming - İstemci birden fazla kitap gönderir&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;override&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;async&lt;/span&gt; Task&amp;lt;AddBooksResponse&amp;gt; AddBooksStream(
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; IAsyncStreamReader&amp;lt;BookService.Grpc.Book&amp;gt; requestStream,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ServerCallContext context)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;int&lt;/span&gt; count = &lt;span style="color:#ae81ff"&gt;0&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;int&lt;/span&gt; maxId = Books.Any() ? Books.Max(b =&amp;gt; b.Id) : &lt;span style="color:#ae81ff"&gt;0&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;await&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;foreach&lt;/span&gt; (&lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; book &lt;span style="color:#66d9ef"&gt;in&lt;/span&gt; requestStream.ReadAllAsync())
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; book.Id = ++maxId;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Books.Add(book);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; count++;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; AddBooksResponse
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Count = count,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Message = &lt;span style="color:#e6db74"&gt;$&amp;#34;Successfully added {count} books&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; };
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// Bidirectional Streaming - İki yönlü gerçek zamanlı arama&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;override&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;async&lt;/span&gt; Task SearchBooksStream(
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; IAsyncStreamReader&amp;lt;SearchRequest&amp;gt; requestStream,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; IServerStreamWriter&amp;lt;BookService.Grpc.Book&amp;gt; responseStream,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ServerCallContext context)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;await&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;foreach&lt;/span&gt; (&lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; searchRequest &lt;span style="color:#66d9ef"&gt;in&lt;/span&gt; requestStream.ReadAllAsync())
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; results = Books.Where(b =&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; b.Title.Contains(searchRequest.Query, StringComparison.OrdinalIgnoreCase) ||
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; b.Author.Contains(searchRequest.Query, StringComparison.OrdinalIgnoreCase)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ).ToList();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;foreach&lt;/span&gt; (&lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; book &lt;span style="color:#66d9ef"&gt;in&lt;/span&gt; results)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;await&lt;/span&gt; responseStream.WriteAsync(book);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="programcs-konfigürasyonu"&gt;Program.cs Konfigürasyonu&lt;/h3&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-csharp" data-lang="csharp"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// Program.cs&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;using&lt;/span&gt; BookService.Services;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; builder = WebApplication.CreateBuilder(args);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// REST API servisleri&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;builder.Services.AddControllers();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;builder.Services.AddEndpointsApiExplorer();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;builder.Services.AddSwaggerGen();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// gRPC servisleri&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;builder.Services.AddGrpc();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; app = builder.Build();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// Swagger (REST için)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; (app.Environment.IsDevelopment())
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; app.UseSwagger();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; app.UseSwaggerUI();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// gRPC reflection (development için)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; app.MapGrpcReflectionService();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// REST endpoints&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;app.MapControllers();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// gRPC endpoints&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;app.MapGrpcService&amp;lt;GrpcBookService&amp;gt;();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;app.Run();
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="csproj-dosyası"&gt;.csproj Dosyası&lt;/h3&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-xml" data-lang="xml"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;&amp;lt;Project&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Sdk=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;Microsoft.NET.Sdk.Web&amp;#34;&lt;/span&gt;&lt;span style="color:#f92672"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;lt;PropertyGroup&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;lt;TargetFramework&amp;gt;&lt;/span&gt;net9.0&lt;span style="color:#f92672"&gt;&amp;lt;/TargetFramework&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;lt;Nullable&amp;gt;&lt;/span&gt;enable&lt;span style="color:#f92672"&gt;&amp;lt;/Nullable&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;lt;ImplicitUsings&amp;gt;&lt;/span&gt;enable&lt;span style="color:#f92672"&gt;&amp;lt;/ImplicitUsings&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;lt;/PropertyGroup&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;lt;ItemGroup&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;lt;PackageReference&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Include=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;Grpc.AspNetCore&amp;#34;&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Version=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;2.60.0&amp;#34;&lt;/span&gt; &lt;span style="color:#f92672"&gt;/&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;lt;PackageReference&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Include=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;Grpc.AspNetCore.Server.Reflection&amp;#34;&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Version=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;2.60.0&amp;#34;&lt;/span&gt; &lt;span style="color:#f92672"&gt;/&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;lt;PackageReference&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Include=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;Grpc.Tools&amp;#34;&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Version=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;2.60.0&amp;#34;&lt;/span&gt;&lt;span style="color:#f92672"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;lt;PrivateAssets&amp;gt;&lt;/span&gt;all&lt;span style="color:#f92672"&gt;&amp;lt;/PrivateAssets&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;lt;IncludeAssets&amp;gt;&lt;/span&gt;runtime; build; native; contentfiles; analyzers; buildtransitive&lt;span style="color:#f92672"&gt;&amp;lt;/IncludeAssets&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;lt;/PackageReference&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;lt;PackageReference&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Include=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;Swashbuckle.AspNetCore&amp;#34;&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Version=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;6.5.0&amp;#34;&lt;/span&gt; &lt;span style="color:#f92672"&gt;/&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;lt;/ItemGroup&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;lt;ItemGroup&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;lt;Protobuf&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Include=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;Protos\\books.proto&amp;#34;&lt;/span&gt; &lt;span style="color:#f92672"&gt;/&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;lt;/ItemGroup&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;&amp;lt;/Project&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Ayrıca lokalden çalışan uygulamanız https ile çalışmayacaksa HTTP/1 protundan ayrı HTTP/2 için ayrı bir port açmamız gerekiyor. Bunun için appsettings.json dosyasında aşağıdaki tanım eklenerek ilgili portlar açılır.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-json" data-lang="json"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;Kestrel&amp;#34;&lt;/span&gt;&lt;span style="color:#960050;background-color:#1e0010"&gt;:&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;Endpoints&amp;#34;&lt;/span&gt;: {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;Http&amp;#34;&lt;/span&gt;: {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;Protocols&amp;#34;&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;Http1&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;Url&amp;#34;&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;http://*:5000&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; },
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;gRPC&amp;#34;&lt;/span&gt;: {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;Protocols&amp;#34;&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;Http2&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;Url&amp;#34;&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;http://*:5001&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }&lt;span style="color:#960050;background-color:#1e0010"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="grpc-client-örnekleri"&gt;gRPC Client Örnekleri&lt;/h2&gt;
&lt;p&gt;Şimdi farklı streaming modlarını kullanan client örnekleri görelim:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-csharp" data-lang="csharp"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// Client/GrpcClientExamples.cs&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;using&lt;/span&gt; Grpc.Net.Client;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;using&lt;/span&gt; BookService.Grpc;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;namespace&lt;/span&gt; BookService.Client;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;class&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;GrpcClientExamples&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;private&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;readonly&lt;/span&gt; GrpcChannel _channel;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;private&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;readonly&lt;/span&gt; Grpc.BookService.BookServiceClient _client;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; GrpcClientExamples(&lt;span style="color:#66d9ef"&gt;string&lt;/span&gt; serverAddress = &lt;span style="color:#e6db74"&gt;&amp;#34;http://localhost:5001&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; _channel = GrpcChannel .ForAddress(serverAddress, &lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; GrpcChannelOptions
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// HTTPS tanımı disable ediliyor. Production ortamda dikkatlı kullanınız&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; HttpHandler = &lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; HttpClientHandler
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ServerCertificateCustomValidationCallback = HttpClientHandler.DangerousAcceptAnyServerCertificateValidator
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; });
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; _client = &lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; Grpc.BookService.BookServiceClient(_channel);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// Unary RPC Örneği&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;async&lt;/span&gt; Task UnaryCallExample()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Console.WriteLine(&lt;span style="color:#e6db74"&gt;&amp;#34;=== Unary Call Example ===&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; request = &lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; GetBooksRequest();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; response = &lt;span style="color:#66d9ef"&gt;await&lt;/span&gt; _client.GetBooksAsync(request);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Console.WriteLine(&lt;span style="color:#e6db74"&gt;$&amp;#34;Received {response.Books.Count} books:&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;foreach&lt;/span&gt; (&lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; book &lt;span style="color:#66d9ef"&gt;in&lt;/span&gt; response.Books)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Console.WriteLine(&lt;span style="color:#e6db74"&gt;$&amp;#34;- {book.Title} by {book.Author} ({book.Year})&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// Server Streaming Örneği&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;async&lt;/span&gt; Task ServerStreamingExample()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Console.WriteLine(&lt;span style="color:#e6db74"&gt;&amp;#34;\\n=== Server Streaming Example ===&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; request = &lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; StreamBooksRequest { DelayMs = &lt;span style="color:#ae81ff"&gt;500&lt;/span&gt; };
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;using&lt;/span&gt; var call = _client.StreamBooks(request);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;await&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;foreach&lt;/span&gt; (&lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; book &lt;span style="color:#66d9ef"&gt;in&lt;/span&gt; call.ResponseStream.ReadAllAsync())
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Console.WriteLine(&lt;span style="color:#e6db74"&gt;$&amp;#34;Received: {book.Title} - ${book.Price}&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Console.WriteLine(&lt;span style="color:#e6db74"&gt;&amp;#34;Streaming completed&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// Client Streaming Örneği&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;async&lt;/span&gt; Task ClientStreamingExample()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Console.WriteLine(&lt;span style="color:#e6db74"&gt;&amp;#34;\\n=== Client Streaming Example ===&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;using&lt;/span&gt; var call = _client.AddBooksStream();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; booksToAdd = &lt;span style="color:#66d9ef"&gt;new&lt;/span&gt;[]
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; Book { Title = &lt;span style="color:#e6db74"&gt;&amp;#34;Design Patterns&amp;#34;&lt;/span&gt;, Author = &lt;span style="color:#e6db74"&gt;&amp;#34;Gang of Four&amp;#34;&lt;/span&gt;, Year = &lt;span style="color:#ae81ff"&gt;1994&lt;/span&gt;, Price = &lt;span style="color:#ae81ff"&gt;59.99&lt;/span&gt; },
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; Book { Title = &lt;span style="color:#e6db74"&gt;&amp;#34;Head First Design Patterns&amp;#34;&lt;/span&gt;, Author = &lt;span style="color:#e6db74"&gt;&amp;#34;Eric Freeman&amp;#34;&lt;/span&gt;, Year = &lt;span style="color:#ae81ff"&gt;2004&lt;/span&gt;, Price = &lt;span style="color:#ae81ff"&gt;44.99&lt;/span&gt; },
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; Book { Title = &lt;span style="color:#e6db74"&gt;&amp;#34;Microservices Patterns&amp;#34;&lt;/span&gt;, Author = &lt;span style="color:#e6db74"&gt;&amp;#34;Chris Richardson&amp;#34;&lt;/span&gt;, Year = &lt;span style="color:#ae81ff"&gt;2018&lt;/span&gt;, Price = &lt;span style="color:#ae81ff"&gt;49.99&lt;/span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; };
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;foreach&lt;/span&gt; (&lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; book &lt;span style="color:#66d9ef"&gt;in&lt;/span&gt; booksToAdd)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;await&lt;/span&gt; call.RequestStream.WriteAsync(book);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Console.WriteLine(&lt;span style="color:#e6db74"&gt;$&amp;#34;Sent: {book.Title}&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;await&lt;/span&gt; Task.Delay(&lt;span style="color:#ae81ff"&gt;300&lt;/span&gt;); &lt;span style="color:#75715e"&gt;// Simüle edilmiş gecikme&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;await&lt;/span&gt; call.RequestStream.CompleteAsync();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; response = &lt;span style="color:#66d9ef"&gt;await&lt;/span&gt; call;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Console.WriteLine(&lt;span style="color:#e6db74"&gt;$&amp;#34;Result: {response.Message}&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// Bidirectional Streaming Örneği&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;async&lt;/span&gt; Task BidirectionalStreamingExample()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Console.WriteLine(&lt;span style="color:#e6db74"&gt;&amp;#34;\\n=== Bidirectional Streaming Example ===&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;using&lt;/span&gt; var call = _client.SearchBooksStream();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// Background&amp;#39;da sonuçları oku&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; readTask = Task.Run(&lt;span style="color:#66d9ef"&gt;async&lt;/span&gt; () =&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;await&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;foreach&lt;/span&gt; (&lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; book &lt;span style="color:#66d9ef"&gt;in&lt;/span&gt; call.ResponseStream.ReadAllAsync())
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Console.WriteLine(&lt;span style="color:#e6db74"&gt;$&amp;#34;Found: {book.Title} by {book.Author}&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; });
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// Arama sorgularını gönder&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; queries = &lt;span style="color:#66d9ef"&gt;new&lt;/span&gt;[] { &lt;span style="color:#e6db74"&gt;&amp;#34;Clean&amp;#34;&lt;/span&gt;, &lt;span style="color:#e6db74"&gt;&amp;#34;Design&amp;#34;&lt;/span&gt;, &lt;span style="color:#e6db74"&gt;&amp;#34;Refactoring&amp;#34;&lt;/span&gt;, &lt;span style="color:#e6db74"&gt;&amp;#34;Domain&amp;#34;&lt;/span&gt; };
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;foreach&lt;/span&gt; (&lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; query &lt;span style="color:#66d9ef"&gt;in&lt;/span&gt; queries)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Console.WriteLine(&lt;span style="color:#e6db74"&gt;$&amp;#34;Searching for: {query}&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;await&lt;/span&gt; call.RequestStream.WriteAsync(&lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; SearchRequest { Query = query });
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;await&lt;/span&gt; Task.Delay(&lt;span style="color:#ae81ff"&gt;1000&lt;/span&gt;); &lt;span style="color:#75715e"&gt;// Her aramadan sonra bekle&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;await&lt;/span&gt; call.RequestStream.CompleteAsync();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;await&lt;/span&gt; readTask;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Console.WriteLine(&lt;span style="color:#e6db74"&gt;&amp;#34;Bidirectional streaming completed&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;async&lt;/span&gt; ValueTask DisposeAsync()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;await&lt;/span&gt; _channel.ShutdownAsync();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; _channel.Dispose();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// Kullanım örneği&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;class&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Program&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;static&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;async&lt;/span&gt; Task Main(&lt;span style="color:#66d9ef"&gt;string&lt;/span&gt;[] args)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;await&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;using&lt;/span&gt; var client = &lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; GrpcClientExamples();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;await&lt;/span&gt; client.UnaryCallExample();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;await&lt;/span&gt; client.ServerStreamingExample();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;await&lt;/span&gt; client.ClientStreamingExample();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;await&lt;/span&gt; client.BidirectionalStreamingExample();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="grpcurl-ile-test-etme"&gt;grpcurl ile Test Etme&lt;/h2&gt;
&lt;p&gt;grpcurl, gRPC servisleri için curl benzeri bir komut satırı aracıdır. Servisimizi test edelim:&lt;/p&gt;
&lt;h3 id="grpcurl-kurulumu"&gt;grpcurl Kurulumu&lt;/h3&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# macOS (Homebrew)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;brew install grpcurl
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# Linux&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;go install github.com/fullstorydev/grpcurl/cmd/grpcurl@latest
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# Windows (Chocolatey)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;choco install grpcurl
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="grpcurl-kullanım-örnekleri"&gt;grpcurl Kullanım Örnekleri&lt;/h3&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# 1. Serviste hangi servisler var? (Reflection gerektirir)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;grpcurl -plaintext localhost:5001 list
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# Çıktı:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# books.BookService&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# grpc.reflection.v1alpha.ServerReflection&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# 2. BookService&amp;#39;in metodlarını listele&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;grpcurl -plaintext localhost:5001 list books.BookService
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# Çıktı:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# books.BookService.AddBooksStream&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# books.BookService.GetBooks&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# books.BookService.SearchBooksStream&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# books.BookService.StreamBooks&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# 3. Unary RPC çağrısı - Tüm kitapları getir&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;grpcurl -plaintext localhost:5001 books.BookService/GetBooks
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# Çıktı:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# {&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# &amp;#34;books&amp;#34;: [&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# {&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# &amp;#34;id&amp;#34;: 1,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# &amp;#34;title&amp;#34;: &amp;#34;Dune&amp;#34;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# &amp;#34;author&amp;#34;: &amp;#34;Frank Herbert&amp;#34;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# &amp;#34;year&amp;#34;: 1965,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# &amp;#34;price&amp;#34;: 29.99&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# },&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# ...&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# ]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# }&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="performans-karşılaştırması-rest-vs-grpc-benchmark"&gt;&lt;strong&gt;Performans Karşılaştırması: REST vs gRPC Benchmark&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;Teorik avantajları görmek güzel, ancak gerçek performans farkını ölçmek için kapsamlı bir benchmark yapalım. BenchmarkDotNet kullanarak REST ve gRPC&amp;rsquo;nin farklı senaryolardaki performansını karşılaştıracağız.&lt;/p&gt;
&lt;h3 id="benchmark-kurulumu"&gt;Benchmark Kurulumu&lt;/h3&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-csharp" data-lang="csharp"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// BookService.Benchmark/BookServiceBenchmark.cs&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;using&lt;/span&gt; BenchmarkDotNet.Attributes;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;using&lt;/span&gt; Grpc.Net.Client;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;using&lt;/span&gt; BookService.Grpc;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;using&lt;/span&gt; System.Net.Http.Json;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;namespace&lt;/span&gt; BookService.Benchmark;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;[MemoryDiagnoser]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;[SimpleJob(warmupCount: 3, iterationCount: 10)]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;class&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;BookServiceBenchmark&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;private&lt;/span&gt; HttpClient? _httpClient;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;private&lt;/span&gt; GrpcChannel? _grpcChannel;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;private&lt;/span&gt; Grpc.BookService.BookServiceClient? _grpcClient;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;private&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;const&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;string&lt;/span&gt; RestUrl = &lt;span style="color:#e6db74"&gt;&amp;#34;http://localhost:5000&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;private&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;const&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;string&lt;/span&gt; GrpcUrl = &lt;span style="color:#e6db74"&gt;&amp;#34;http://localhost:5001&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt; [GlobalSetup]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;void&lt;/span&gt; Setup()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// REST client kurulumu&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; _httpClient = &lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; HttpClient { BaseAddress = &lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; Uri(RestUrl) };
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// gRPC client kurulumu - HTTP/2 optimizasyonları ile&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; _grpcChannel = GrpcChannel.ForAddress(GrpcUrl, &lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; GrpcChannelOptions
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; MaxReceiveMessageSize = &lt;span style="color:#ae81ff"&gt;4&lt;/span&gt; * &lt;span style="color:#ae81ff"&gt;1024&lt;/span&gt; * &lt;span style="color:#ae81ff"&gt;1024&lt;/span&gt;, &lt;span style="color:#75715e"&gt;// 4MB&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; MaxSendMessageSize = &lt;span style="color:#ae81ff"&gt;4&lt;/span&gt; * &lt;span style="color:#ae81ff"&gt;1024&lt;/span&gt; * &lt;span style="color:#ae81ff"&gt;1024&lt;/span&gt;, &lt;span style="color:#75715e"&gt;// 4MB&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; });
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; _grpcClient = &lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; Grpc.BookService.BookServiceClient(_grpcChannel);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt; [Benchmark(Baseline = true)]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;async&lt;/span&gt; Task&amp;lt;&lt;span style="color:#66d9ef"&gt;int&lt;/span&gt;&amp;gt; REST_GetAllBooks()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; books = &lt;span style="color:#66d9ef"&gt;await&lt;/span&gt; _httpClient!.GetFromJsonAsync&amp;lt;List&amp;lt;BookDto&amp;gt;&amp;gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;/api/books&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; books?.Count ?? &lt;span style="color:#ae81ff"&gt;0&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt; [Benchmark]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;async&lt;/span&gt; Task&amp;lt;&lt;span style="color:#66d9ef"&gt;int&lt;/span&gt;&amp;gt; gRPC_GetAllBooks()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; response = &lt;span style="color:#66d9ef"&gt;await&lt;/span&gt; _grpcClient!.GetBooksAsync(&lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; GetBooksRequest());
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; response.Books.Count;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt; [Benchmark]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;async&lt;/span&gt; Task&amp;lt;&lt;span style="color:#66d9ef"&gt;int&lt;/span&gt;&amp;gt; REST_GetAllBooks_10Times()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;int&lt;/span&gt; totalCount = &lt;span style="color:#ae81ff"&gt;0&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;for&lt;/span&gt; (&lt;span style="color:#66d9ef"&gt;int&lt;/span&gt; i = &lt;span style="color:#ae81ff"&gt;0&lt;/span&gt;; i &amp;lt; &lt;span style="color:#ae81ff"&gt;10&lt;/span&gt;; i++)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; books = &lt;span style="color:#66d9ef"&gt;await&lt;/span&gt; _httpClient!.GetFromJsonAsync&amp;lt;List&amp;lt;BookDto&amp;gt;&amp;gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;/api/books&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; totalCount += books?.Count ?? &lt;span style="color:#ae81ff"&gt;0&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; totalCount;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt; [Benchmark]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;async&lt;/span&gt; Task&amp;lt;&lt;span style="color:#66d9ef"&gt;int&lt;/span&gt;&amp;gt; gRPC_GetAllBooks_10Times()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;int&lt;/span&gt; totalCount = &lt;span style="color:#ae81ff"&gt;0&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;for&lt;/span&gt; (&lt;span style="color:#66d9ef"&gt;int&lt;/span&gt; i = &lt;span style="color:#ae81ff"&gt;0&lt;/span&gt;; i &amp;lt; &lt;span style="color:#ae81ff"&gt;10&lt;/span&gt;; i++)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; response = &lt;span style="color:#66d9ef"&gt;await&lt;/span&gt; _grpcClient!.GetBooksAsync(&lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; GetBooksRequest());
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; totalCount += response.Books.Count;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; totalCount;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt; [GlobalCleanup]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;async&lt;/span&gt; Task Cleanup()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; _httpClient?.Dispose();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; (_grpcChannel != &lt;span style="color:#66d9ef"&gt;null&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;await&lt;/span&gt; _grpcChannel.ShutdownAsync();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; _grpcChannel.Dispose();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="benchmark-sonuçları"&gt;Benchmark Sonuçları&lt;/h3&gt;
&lt;p&gt;Optimizasyonlar sonrasında elde edilen gerçek performans sonuçları:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Method&lt;/th&gt;
&lt;th&gt;Mean&lt;/th&gt;
&lt;th&gt;Error&lt;/th&gt;
&lt;th&gt;StdDev&lt;/th&gt;
&lt;th&gt;Ratio&lt;/th&gt;
&lt;th&gt;RatioSD&lt;/th&gt;
&lt;th&gt;Allocated&lt;/th&gt;
&lt;th&gt;Alloc Ratio&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;REST_GetAllBooks&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;332.3 μs&lt;/td&gt;
&lt;td&gt;42.42 μs&lt;/td&gt;
&lt;td&gt;28.06 μs&lt;/td&gt;
&lt;td&gt;1.00&lt;/td&gt;
&lt;td&gt;0.00&lt;/td&gt;
&lt;td&gt;22.91 KB&lt;/td&gt;
&lt;td&gt;1.00&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;gRPC_GetAllBooks&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;296.9 μs&lt;/td&gt;
&lt;td&gt;18.10 μs&lt;/td&gt;
&lt;td&gt;10.77 μs&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;0.90&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;0.06&lt;/td&gt;
&lt;td&gt;25.66 KB&lt;/td&gt;
&lt;td&gt;1.12&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;REST_GetAllBooks_10Times&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;3,257.1 μs&lt;/td&gt;
&lt;td&gt;364.16 μs&lt;/td&gt;
&lt;td&gt;240.87 μs&lt;/td&gt;
&lt;td&gt;9.85&lt;/td&gt;
&lt;td&gt;0.99&lt;/td&gt;
&lt;td&gt;227.61 KB&lt;/td&gt;
&lt;td&gt;9.94&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;gRPC_GetAllBooks_10Times&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;2,883.9 μs&lt;/td&gt;
&lt;td&gt;338.63 μs&lt;/td&gt;
&lt;td&gt;223.99 μs&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;8.74&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;1.05&lt;/td&gt;
&lt;td&gt;255.17 KB&lt;/td&gt;
&lt;td&gt;11.14&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 id="sonuçların-analizi"&gt;Sonuçların Analizi&lt;/h3&gt;
&lt;p&gt;Tek istek performansı incelendiğinde, gRPC&amp;rsquo;nin REST&amp;rsquo;e kıyasla %10 daha hızlı çalıştığı görülüyor (296.9 μs vs 332.3 μs). Bu fark, Protocol Buffers&amp;rsquo;ın binary serializasyon formatının JSON&amp;rsquo;a gösterdiği avantajdan kaynaklanmaktadır. Çoklu istek senaryolarında ise gRPC&amp;rsquo;nin performans üstünlüğü daha da belirginleşiyor ve %11.5&amp;rsquo;e çıkıyor (2,883.9 μs vs 3,257.1 μs). Bu artış, HTTP/2 protokolünün connection reuse özelliğinin devreye girmesiyle açıklanabilir.&lt;/p&gt;
&lt;p&gt;Bellek kullanımı açısından bakıldığında, gRPC&amp;rsquo;nin REST&amp;rsquo;e göre %12 daha fazla bellek tükettiği görülmektedir (25.66 KB vs 22.91 KB). Bu ek bellek kullanımı, HTTP/2 bağlantı yönetimi için gerekli olan overhead&amp;rsquo;dan kaynaklanmaktadır. Ancak elde edilen performans iyileştirmeleri göz önüne alındığında, bu bellek artışı kabul edilebilir bir trade-off olarak değerlendirilebilir.&lt;/p&gt;
&lt;h2 id="özet"&gt;Özet&lt;/h2&gt;
&lt;p&gt;Bu yazı, .NET 9 ekosisteminde mikroservisler arası &lt;strong&gt;senkron&lt;/strong&gt; iletişimde REST ve gRPC’yi mimari yaklaşım, teknik altyapı ve performans boyutlarıyla karşılaştırıyor. REST, HTTP/1.1 üzerinde JSON’la kolay erişilebilirlik, tarayıcı uyumluluğu ve cache/proxy avantajı sunarken; gRPC, HTTP/2’nin multiplexing ve doğal &lt;strong&gt;streaming&lt;/strong&gt; yetenekleri ile Protobuf sayesinde daha düşük gecikme ve daha güçlü tip güvenliği sağlıyor. Yazıda, aynı “Books” alanında hem REST Controller hem de gRPC servis (unary, server/client/bidirectional streaming) örnekleri ve &lt;strong&gt;grpcurl&lt;/strong&gt; ile test adımları bulunuyor. BenchmarkDotNet sonuçları, tekil çağrıda gRPC’nin REST’e kıyasla yaklaşık &lt;strong&gt;%10&lt;/strong&gt;, art arda çağrılarda &lt;strong&gt;%11&lt;/strong&gt; civarı daha hızlı olduğunu; buna karşılık az miktarda ek bellek kullandığını gösteriyor.&lt;/p&gt;
&lt;p&gt;Kaynak koduna erişmek isterseniz projenin tamamını GitHub hesabımda bulabilirsiniz:&lt;/p&gt;
&lt;p&gt;&lt;a href="https://github.com/berkslv/lecture-grpc"&gt;https://github.com/berkslv/lecture-grpc&lt;/a&gt;&lt;/p&gt;
&lt;h2 id="sonuç"&gt;Sonuç&lt;/h2&gt;
&lt;p&gt;Okuduğunuz için teşekkürler! 🎉 Yazılım geliştirme alanındaki araştırmalarımı kaçırmamak için &lt;a href="https://x.com/berkslv"&gt;@berkslv&lt;/a&gt; adresinden takipte kalabilirsiniz.&lt;/p&gt;</content></item><item><title>.NET’te Kod Kalitesini Zorunlu kılın: .editorconfig &amp; Analyzers &amp; Pre-Commit Hooks</title><link>https://berkselvi.dev/tr/posts/enforce-code-quality-in-net-editorconfig-analyzers-pre-commit-hooks/</link><pubDate>Thu, 24 Jul 2025 00:00:00 +0300</pubDate><guid>https://berkselvi.dev/tr/posts/enforce-code-quality-in-net-editorconfig-analyzers-pre-commit-hooks/</guid><description>&lt;p&gt;Kod kalitesini korumak ve iyileştirmek yazdığımız her satırda hem kendimize hem de takım arkadaşlarımıza karşı bir sorumluluğumuz olarak düşünüyorum. Bu sorumluluğu uygularken kimi zaman acele yazılan kodlarla, yetişmesi gereken deadline’larla uygulanması gereken pratikleri göz ardı edebiliyoruz. Ayrıca, bu pratikleri takım arkadaşlarımıza yaygınlaştırırken de zorluklar yaşayabiliyoruz.&lt;/p&gt;
&lt;p&gt;Bu tip sorunları aşıp, Kod kalitesini developer’ın inisiyatifine bırakmadan bir zorunluluk haline getirmenin birkaç adımdan oluşan ve 20+ developer’ın çalıştığı, 10+ servisten oluşan projeden çıkarılan deneyimlerle ispatlanan yöntemini paylaşmak istiyorum.&lt;/p&gt;</description><content>&lt;p&gt;Kod kalitesini korumak ve iyileştirmek yazdığımız her satırda hem kendimize hem de takım arkadaşlarımıza karşı bir sorumluluğumuz olarak düşünüyorum. Bu sorumluluğu uygularken kimi zaman acele yazılan kodlarla, yetişmesi gereken deadline’larla uygulanması gereken pratikleri göz ardı edebiliyoruz. Ayrıca, bu pratikleri takım arkadaşlarımıza yaygınlaştırırken de zorluklar yaşayabiliyoruz.&lt;/p&gt;
&lt;p&gt;Bu tip sorunları aşıp, Kod kalitesini developer’ın inisiyatifine bırakmadan bir zorunluluk haline getirmenin birkaç adımdan oluşan ve 20+ developer’ın çalıştığı, 10+ servisten oluşan projeden çıkarılan deneyimlerle ispatlanan yöntemini paylaşmak istiyorum.&lt;/p&gt;
&lt;p&gt;İlk olarak TreatWarningsAsErrors flag’i ile projenin 1000+ warning ile production’a çıkmasını engelleyeceğiz.&lt;/p&gt;
&lt;p&gt;Devamında .editorconfig dosyaları ile .NET tarafından default olarak sağlanan kurallarla örneğin property isimlendirmeleri takıma yaygınlaştırılması gereken genel-geçer bir kural değil bir zorunluluk haline getireceğiz.&lt;/p&gt;
&lt;p&gt;Bu kuralları SonarAnalyzer ve Roslynator gibi paketlerle geliştirip, sonar taramalarına daha girmeden ilgili sorunları çözüp, daha performanslı ve güvenli kodlar yazacağız.&lt;/p&gt;
&lt;p&gt;Son olarakta pre-commit hook ile, build olmayan projenin ve fail eden testlerin CI/CD pipelinelarında hataya sebep olmadan önce, daha commit atmadan bu testleri görüp, düzelteceğiz. Haydi başlayalım.&lt;/p&gt;
&lt;h2 id="treatwarningsaserrors-ile-warning-içermeyen-proje"&gt;TreatWarningsAsErrors ile warning içermeyen proje&lt;/h2&gt;
&lt;p&gt;.NET ile proje geliştirirken en çok warning “Possible null reference” ile ortaya çıkıyor. Bu warningle sınırlı olmadan projemizde birçok farklı warning ortaya çıkabilir ve bunlara saygı duymazsak runtime’da her yerde NullReferenceException almamız kadar doğal birşey olamaz.&lt;/p&gt;
&lt;p&gt;Bu tip hatalar daha ortaya çıkmadan çözmek için Warning’lere Error olarak muamele edip, projenin build olmasını engelleyebiliriz. Bunun içinde .csproj dosyasına aşağıdaki flag’i eklememiz yeterlidir.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-xml" data-lang="xml"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;&amp;lt;PropertyGroup&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;lt;TreatWarningsAsErrors&amp;gt;&lt;/span&gt;true&lt;span style="color:#f92672"&gt;&amp;lt;/TreatWarningsAsErrors&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;&amp;lt;!-- Sadece gerçekten gerekli ise --&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;lt;WarningsNotAsErrors&amp;gt;&lt;/span&gt;CS0108;NU1901;NU1902;NU1903;NU1904;NU1701&lt;span style="color:#f92672"&gt;&amp;lt;/WarningsNotAsErrors&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;&amp;lt;/PropertyGroup&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Ancak bu flag ile .NET platformu tarafından verilen tüm Warning’ler Error olarak görüleceği için örneğin Nuget paketleri tarafından verilen ve bizim tarafımızca çözülemeyecek warningler başımızı çokça ağrıtabilir. Bu tür sorunların üstesinden gelmek içinde WarningsNotAsErrors flag’ini kullanabiliyoruz. Ancak bunu sadece çözülemez hatalar için yapmak önemli.&lt;/p&gt;
&lt;h2 id="editorconfig-ile-projenin-kurallarını-belirleyelim"&gt;.editorconfig ile projenin kurallarını belirleyelim&lt;/h2&gt;
&lt;p&gt;EditorConfig, aynı proje üzerinde çalışan birden fazla geliştiricinin farklı IDE&amp;rsquo;ler aracılığıyla tutarlı kodlama stilleri korumasına yardımcı olur. .NET projelerine özel bir pratik değildir, dil bağımsızdır ve ortak kuralları olduğu gibi dil özelinde kurallarıda mevcuttur.&lt;/p&gt;
&lt;p&gt;Bu sayede sınıflar &lt;a href="https://learn.microsoft.com/en-us/dotnet/fundamentals/code-analysis/categories"&gt;.NET&lt;/a&gt; tarafından standart sağlanan kurallar ile örneğin PascalCase, değişkenler camelCase, sabitler LOWER_CASE olmalı gibi kuralları proje seviyesinde tanımlayabiliriz.&lt;/p&gt;
&lt;p&gt;Bu kurallara ek olarak &lt;a href="https://rules.sonarsource.com/csharp/"&gt;SonarAnalyzer&lt;/a&gt; ile örneğin &lt;code&gt;name == “”&lt;/code&gt; gibi facia bir kullanımı sonar taramasına girmeden yakalayıp &lt;code&gt;string.IsNullOrEmpty(name)&lt;/code&gt; önerisini Visual Studio kullanıyorsanız Alt+Enter ile direk çözüm önerisini olarak alıp uygulayabilirsiniz. Bu sayede CI/CD adımında sonar taramasına giren kodunuz çok daha az hatayla taramaya başlayacaktır.&lt;/p&gt;
&lt;p&gt;Harika bir diğer code analyzer kütüphanesi olan &lt;a href="https://josefpihrt.github.io/docs/roslynator/"&gt;Roslynator&lt;/a&gt; sayesinde de çok daha spesifik kuralları projemizde uygulayabiliriz. Örneğin class property’leri arasında bir boşluk olmalı gibi kod okunaklığını arttıran kuralları bu sayede uygulayabiliriz.&lt;/p&gt;
&lt;p&gt;Bu tür third party analyzer kütüphanelerini repo içerisindeki tüm projelere uygulamak için her projeye tek tek uygulamaktansa root dizinde Directory.Build.Props isminde bir dosya oluşturup içeriğini aşağıdaki şekilde oluşturursak build adımında bu paketler tüm projelere otomatik eklenecektir.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-xml" data-lang="xml"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;&amp;lt;Project&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;lt;ItemGroup&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;lt;PackageReference&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;Include=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;Roslynator.Analyzers&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;Version=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;4.12.4&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;PrivateAssets=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;all&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;Condition=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;$(MSBuildProjectExtension) == &amp;#39;.csproj&amp;#39;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;/&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;lt;PackageReference&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;Include=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;Roslynator.Formatting.Analyzers&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;Version=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;4.12.4&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;PrivateAssets=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;all&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;Condition=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;$(MSBuildProjectExtension) == &amp;#39;.csproj&amp;#39;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;/&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;lt;PackageReference&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;Include=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;SonarAnalyzer.CSharp&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;Version=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;9.32.0.97167&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;PrivateAssets=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;all&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;Condition=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;$(MSBuildProjectExtension) == &amp;#39;.csproj&amp;#39;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;/&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;lt;/ItemGroup&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;&amp;lt;/Project&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Örnek olarak aşağıdaki .editorconfig dosyasına göz atabiliriz. Burada IDE ile tanımlanan kurallar .NET tarafından sağlanan standart kurallar, rcs ile başlayanlar roslynator ve S ile başlayanlar sonar kurallarıdır. Bu kurallar suggestion, warning ve error seviyesiyle tanımlanıp her bir proje için özelleştirilebilir.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# top-most EditorConfig file&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;root &lt;span style="color:#f92672"&gt;=&lt;/span&gt; true
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# Don&amp;#39;t use tabs for indentation.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;[&lt;/span&gt;*&lt;span style="color:#f92672"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;indent_style &lt;span style="color:#f92672"&gt;=&lt;/span&gt; space
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# (Please don&amp;#39;t specify an indent_size here; that has too many unintended consequences.)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;spelling_exclusion_path &lt;span style="color:#f92672"&gt;=&lt;/span&gt; SpellingExclusions.dic
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# Code files&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;[&lt;/span&gt;*.&lt;span style="color:#f92672"&gt;{&lt;/span&gt;cs,csx,vb,vbx&lt;span style="color:#f92672"&gt;}]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;indent_size &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;4&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;insert_final_newline &lt;span style="color:#f92672"&gt;=&lt;/span&gt; true
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;charset &lt;span style="color:#f92672"&gt;=&lt;/span&gt; utf-8-bom
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# XML project files&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;[&lt;/span&gt;*.&lt;span style="color:#f92672"&gt;{&lt;/span&gt;csproj,vbproj,vcxproj,vcxproj.filters,proj,projitems,shproj&lt;span style="color:#f92672"&gt;}]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;indent_size &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;2&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# XML config files&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;[&lt;/span&gt;*.&lt;span style="color:#f92672"&gt;{&lt;/span&gt;props,targets,ruleset,config,nuspec,resx,vsixmanifest,vsct&lt;span style="color:#f92672"&gt;}]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;indent_size &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;2&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# JSON files&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;[&lt;/span&gt;*.json&lt;span style="color:#f92672"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;indent_size &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;2&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# Powershell files&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;[&lt;/span&gt;*.ps1&lt;span style="color:#f92672"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;indent_size &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;2&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# Shell script files&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;[&lt;/span&gt;*.sh&lt;span style="color:#f92672"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;end_of_line &lt;span style="color:#f92672"&gt;=&lt;/span&gt; lf
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;indent_size &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;2&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# Dotnet code style settings:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;[&lt;/span&gt;*.&lt;span style="color:#f92672"&gt;{&lt;/span&gt;cs,vb&lt;span style="color:#f92672"&gt;}]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# Sort using and Import directives with System.* appearing first&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;dotnet_sort_system_directives_first &lt;span style="color:#f92672"&gt;=&lt;/span&gt; true
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;dotnet_separate_import_directive_groups &lt;span style="color:#f92672"&gt;=&lt;/span&gt; false
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# IDE0005: Remove unnecessary using directives&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;dotnet_diagnostic.IDE0005.severity &lt;span style="color:#f92672"&gt;=&lt;/span&gt; warning
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# IDE0010: Add missing cases to switch statement &lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;dotnet_diagnostic.IDE0010.severity &lt;span style="color:#f92672"&gt;=&lt;/span&gt; warning
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# Add blank line after using directive list&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;dotnet_diagnostic.rcs0003.severity &lt;span style="color:#f92672"&gt;=&lt;/span&gt; error
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# Place new line after/before &amp;#39;?:&amp;#39; operator&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;dotnet_diagnostic.rcs0028.severity &lt;span style="color:#f92672"&gt;=&lt;/span&gt; error
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# Track uses of &amp;#34;TODO&amp;#34; tags&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;dotnet_diagnostic.S1135.severity &lt;span style="color:#f92672"&gt;=&lt;/span&gt; suggestion
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Son olarak bu pratiklerle aşağıdaki gibi bir öneri alarak kodumuzun daha tutarlı ve okunaklı olmasını sağlayabiliyoruz.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;csharp_prefer_braces &lt;span style="color:#f92672"&gt;=&lt;/span&gt; true:suggestion
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-csharp" data-lang="csharp"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// Good:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; (isActive)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; DoSomething();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// Bad:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; (isActive)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; DoSomething();
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Ayrıca GitHub&amp;rsquo;ın bol yıldızlı repolarında &lt;a href="https://github.com/dotnet/roslyn/blob/main/.editorconfig"&gt;dotnet / roslyn&lt;/a&gt; ve &lt;a href="https://github.com/christianhelle/refitter/blob/main/.editorconfig"&gt;christianhelle / refitter&lt;/a&gt; gibi .editorconfig referansları bulabilirsiniz.&lt;/p&gt;
&lt;h2 id="pre-commit-hook-ile-sorunları-lokalde-yakalayalım"&gt;pre-commit hook ile sorunları lokalde yakalayalım&lt;/h2&gt;
&lt;p&gt;Pre-commit hook’lar, Git’in her commit’ten önce çalıştırdığı scriptlerdir. Kod formatlama, linting veya testlerin çalıştırılması gibi kontroller için oldukça uygundur, sorunlu kodların commitlenmesini engellerler. Testlerde hata alan bir kodu commitlemeyi engellemek kısa vadede sinir bozucu olsa da uzun vadede testleri canlı tutmak adına çok önemli. Eğer CI/CD pipeline’ınızda test adımı varsa o adıma gelmeden önce lokalde sorunları görmemizi sağlar.&lt;/p&gt;
&lt;p&gt;.NET projelerinde bu pre-commit hook’larını register etmek için ek paketlerden yardım alınabilir veya manuel süreçler ile her developer’ın &lt;code&gt;git config core.hooksPath ./.git-hooks&lt;/code&gt; komudunu çalıştırması istenebilir. Biz burada projemizi ek bir pakete bağımlı hale getirmeden .NET platformunun bize sağladığı yeteneklerden faydalanacağız.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;.csproj&lt;/code&gt; dosyanıza aşağıdaki gibi ekleyeceğiniz basit bir MSBuild target’ı ile, her dotnet restore adımında, dolayısıylada build adımında Git hook dizininizin kurulmasını sağlayabilirsiniz. Böylece, projenizde çalışan kim olursa olsun, projeyi build ettiği sürece, ekstra adımlara gerek kalmadan pre-commit hook otomatik olarak yüklenecektir. Ayrıca Production ortamında bu adımı atlıyoruz, böylece CI/CD pipeline’larında ya da prod ortamlarda gereksiz kurulumlar yapılmıyor.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-xml" data-lang="xml"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;&amp;lt;Target&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Name=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;register git hooks&amp;#34;&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;BeforeTargets=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;Restore;CollectPackageReferences&amp;#34;&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Condition=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;&amp;#39;$(ASPNETCORE_ENVIRONMENT)&amp;#39; != &amp;#39;Production&amp;#39;&amp;#34;&lt;/span&gt;&lt;span style="color:#f92672"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;lt;Exec&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Command=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;git config core.hooksPath ./.git-hooks&amp;#34;&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;StandardOutputImportance=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;Low&amp;#34;&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;StandardErrorImportance=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;High&amp;#34;&lt;/span&gt; &lt;span style="color:#f92672"&gt;/&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;&amp;lt;/Target&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Projenizin root dizininde &lt;code&gt;.git-hooks&lt;/code&gt; adlı bir klasör oluşturun. İçine &lt;code&gt;pre-commit&lt;/code&gt; isminde bir dosya oluşturun:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;./git-hooks/pre-commit&lt;/code&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;#!/bin/sh
echo Running tests...
dotnet format
dotnet test
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Dosyanızı çalıştırılabilir yapmayı unutmayın:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;chmod +x .git-hooks/pre-commit
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Bu akış sayesinde testlerin hata aldığı durumda commit atamayacağız&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;$ git commit -m &lt;span style="color:#e6db74"&gt;&amp;#39;failed commit&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Running tests...
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Determining projects to restore...
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; All projects are up-to-date &lt;span style="color:#66d9ef"&gt;for&lt;/span&gt; restore.
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Committer.API -&amp;gt; C:&lt;span style="color:#ae81ff"&gt;\P&lt;/span&gt;rojects&lt;span style="color:#ae81ff"&gt;\P&lt;/span&gt;ersonal&lt;span style="color:#ae81ff"&gt;\l&lt;/span&gt;ecture-net-pre-commit&lt;span style="color:#ae81ff"&gt;\s&lt;/span&gt;rc&lt;span style="color:#ae81ff"&gt;\C&lt;/span&gt;ommitter.API&lt;span style="color:#ae81ff"&gt;\b&lt;/span&gt;in&lt;span style="color:#ae81ff"&gt;\D&lt;/span&gt;ebug&lt;span style="color:#ae81ff"&gt;\n&lt;/span&gt;et9.0&lt;span style="color:#ae81ff"&gt;\C&lt;/span&gt;ommitter.API.dll
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Committer.Tests -&amp;gt; C:&lt;span style="color:#ae81ff"&gt;\P&lt;/span&gt;rojects&lt;span style="color:#ae81ff"&gt;\P&lt;/span&gt;ersonal&lt;span style="color:#ae81ff"&gt;\l&lt;/span&gt;ecture-net-pre-commit&lt;span style="color:#ae81ff"&gt;\t&lt;/span&gt;ests&lt;span style="color:#ae81ff"&gt;\C&lt;/span&gt;ommitter.Tests&lt;span style="color:#ae81ff"&gt;\b&lt;/span&gt;in&lt;span style="color:#ae81ff"&gt;\D&lt;/span&gt;ebug&lt;span style="color:#ae81ff"&gt;\n&lt;/span&gt;et9.0&lt;span style="color:#ae81ff"&gt;\C&lt;/span&gt;ommitter.Tests.dll
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Test run &lt;span style="color:#66d9ef"&gt;for&lt;/span&gt; C:&lt;span style="color:#ae81ff"&gt;\P&lt;/span&gt;rojects&lt;span style="color:#ae81ff"&gt;\P&lt;/span&gt;ersonal&lt;span style="color:#ae81ff"&gt;\l&lt;/span&gt;ecture-net-pre-commit&lt;span style="color:#ae81ff"&gt;\t&lt;/span&gt;ests&lt;span style="color:#ae81ff"&gt;\C&lt;/span&gt;ommitter.Tests&lt;span style="color:#ae81ff"&gt;\b&lt;/span&gt;in&lt;span style="color:#ae81ff"&gt;\D&lt;/span&gt;ebug&lt;span style="color:#ae81ff"&gt;\n&lt;/span&gt;et9.0&lt;span style="color:#ae81ff"&gt;\C&lt;/span&gt;ommitter.Tests.dll &lt;span style="color:#f92672"&gt;(&lt;/span&gt;.NETCoreApp,Version&lt;span style="color:#f92672"&gt;=&lt;/span&gt;v9.0&lt;span style="color:#f92672"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;VSTest version 17.14.0 &lt;span style="color:#f92672"&gt;(&lt;/span&gt;x64&lt;span style="color:#f92672"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Starting test execution, please wait...
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;A total of &lt;span style="color:#ae81ff"&gt;1&lt;/span&gt; test files matched the specified pattern.
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;[&lt;/span&gt;xUnit.net 00:00:00.14&lt;span style="color:#f92672"&gt;]&lt;/span&gt; Committer.Tests.Services.CalculatorServiceTests.Add_ShouldReturnCorrectSum&lt;span style="color:#f92672"&gt;(&lt;/span&gt;a: 5, b: 3, expected: 7&lt;span style="color:#f92672"&gt;)&lt;/span&gt; &lt;span style="color:#f92672"&gt;[&lt;/span&gt;FAIL&lt;span style="color:#f92672"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Failed Committer.Tests.Services.CalculatorServiceTests.Add_ShouldReturnCorrectSum&lt;span style="color:#f92672"&gt;(&lt;/span&gt;a: 5, b: 3, expected: 7&lt;span style="color:#f92672"&gt;)&lt;/span&gt; &lt;span style="color:#f92672"&gt;[&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;1&lt;/span&gt; ms&lt;span style="color:#f92672"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Error Message:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Assert.Equal&lt;span style="color:#f92672"&gt;()&lt;/span&gt; Failure: Values differ
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Expected: &lt;span style="color:#ae81ff"&gt;7&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Actual: &lt;span style="color:#ae81ff"&gt;8&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Stack Trace:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; at Committer.Tests.Services.CalculatorServiceTests.Add_ShouldReturnCorrectSum&lt;span style="color:#f92672"&gt;(&lt;/span&gt;Double a, Double b, Double expected&lt;span style="color:#f92672"&gt;)&lt;/span&gt; in C:&lt;span style="color:#ae81ff"&gt;\P&lt;/span&gt;rojects&lt;span style="color:#ae81ff"&gt;\P&lt;/span&gt;ersonal&lt;span style="color:#ae81ff"&gt;\l&lt;/span&gt;ecture-net-pre-commit&lt;span style="color:#ae81ff"&gt;\t&lt;/span&gt;ests&lt;span style="color:#ae81ff"&gt;\C&lt;/span&gt;ommitter.Tests&lt;span style="color:#ae81ff"&gt;\S&lt;/span&gt;ervices&lt;span style="color:#ae81ff"&gt;\C&lt;/span&gt;alculatorServiceTests.cs:line &lt;span style="color:#ae81ff"&gt;25&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; at InvokeStub_CalculatorServiceTests.Add_ShouldReturnCorrectSum&lt;span style="color:#f92672"&gt;(&lt;/span&gt;Object, Span&lt;span style="color:#e6db74"&gt;`&lt;/span&gt;1&lt;span style="color:#f92672"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; at System.Reflection.MethodBaseInvoker.InvokeWithFewArgs&lt;span style="color:#f92672"&gt;(&lt;/span&gt;Object obj, BindingFlags invokeAttr, Binder binder, Object&lt;span style="color:#f92672"&gt;[]&lt;/span&gt; parameters, CultureInfo culture&lt;span style="color:#f92672"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Failed! - Failed: 1, Passed: 18, Skipped: 0, Total: 19, Duration: &lt;span style="color:#ae81ff"&gt;39&lt;/span&gt; ms - Committer.Tests.dll &lt;span style="color:#f92672"&gt;(&lt;/span&gt;net9.0&lt;span style="color:#f92672"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;$ git log --oneline
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;9a89b16 &lt;span style="color:#f92672"&gt;(&lt;/span&gt;HEAD -&amp;gt; main, origin/main&lt;span style="color:#f92672"&gt;)&lt;/span&gt; test commit
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;10367df README.md added
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#ae81ff"&gt;0051724&lt;/span&gt; Inital commit
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Testler başarılı ise commit işlemi gerçekleşecektir:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;$ git commit -m &lt;span style="color:#e6db74"&gt;&amp;#39;succeded commit&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Running tests...
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Determining projects to restore...
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; All projects are up-to-date &lt;span style="color:#66d9ef"&gt;for&lt;/span&gt; restore.
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Committer.API -&amp;gt; C:&lt;span style="color:#ae81ff"&gt;\P&lt;/span&gt;rojects&lt;span style="color:#ae81ff"&gt;\P&lt;/span&gt;ersonal&lt;span style="color:#ae81ff"&gt;\l&lt;/span&gt;ecture-net-pre-commit&lt;span style="color:#ae81ff"&gt;\s&lt;/span&gt;rc&lt;span style="color:#ae81ff"&gt;\C&lt;/span&gt;ommitter.API&lt;span style="color:#ae81ff"&gt;\b&lt;/span&gt;in&lt;span style="color:#ae81ff"&gt;\D&lt;/span&gt;ebug&lt;span style="color:#ae81ff"&gt;\n&lt;/span&gt;et9.0&lt;span style="color:#ae81ff"&gt;\C&lt;/span&gt;ommitter.API.dll
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Committer.Tests -&amp;gt; C:&lt;span style="color:#ae81ff"&gt;\P&lt;/span&gt;rojects&lt;span style="color:#ae81ff"&gt;\P&lt;/span&gt;ersonal&lt;span style="color:#ae81ff"&gt;\l&lt;/span&gt;ecture-net-pre-commit&lt;span style="color:#ae81ff"&gt;\t&lt;/span&gt;ests&lt;span style="color:#ae81ff"&gt;\C&lt;/span&gt;ommitter.Tests&lt;span style="color:#ae81ff"&gt;\b&lt;/span&gt;in&lt;span style="color:#ae81ff"&gt;\D&lt;/span&gt;ebug&lt;span style="color:#ae81ff"&gt;\n&lt;/span&gt;et9.0&lt;span style="color:#ae81ff"&gt;\C&lt;/span&gt;ommitter.Tests.dll
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Test run &lt;span style="color:#66d9ef"&gt;for&lt;/span&gt; C:&lt;span style="color:#ae81ff"&gt;\P&lt;/span&gt;rojects&lt;span style="color:#ae81ff"&gt;\P&lt;/span&gt;ersonal&lt;span style="color:#ae81ff"&gt;\l&lt;/span&gt;ecture-net-pre-commit&lt;span style="color:#ae81ff"&gt;\t&lt;/span&gt;ests&lt;span style="color:#ae81ff"&gt;\C&lt;/span&gt;ommitter.Tests&lt;span style="color:#ae81ff"&gt;\b&lt;/span&gt;in&lt;span style="color:#ae81ff"&gt;\D&lt;/span&gt;ebug&lt;span style="color:#ae81ff"&gt;\n&lt;/span&gt;et9.0&lt;span style="color:#ae81ff"&gt;\C&lt;/span&gt;ommitter.Tests.dll &lt;span style="color:#f92672"&gt;(&lt;/span&gt;.NETCoreApp,Version&lt;span style="color:#f92672"&gt;=&lt;/span&gt;v9.0&lt;span style="color:#f92672"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;VSTest version 17.14.0 &lt;span style="color:#f92672"&gt;(&lt;/span&gt;x64&lt;span style="color:#f92672"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Starting test execution, please wait...
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;A total of &lt;span style="color:#ae81ff"&gt;1&lt;/span&gt; test files matched the specified pattern.
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Passed! - Failed: 0, Passed: 19, Skipped: 0, Total: 19, Duration: &lt;span style="color:#ae81ff"&gt;31&lt;/span&gt; ms - Committer.Tests.dll &lt;span style="color:#f92672"&gt;(&lt;/span&gt;net9.0&lt;span style="color:#f92672"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;[&lt;/span&gt;main 0d9f3cf&lt;span style="color:#f92672"&gt;]&lt;/span&gt; succeded commit
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#ae81ff"&gt;1&lt;/span&gt; file changed, &lt;span style="color:#ae81ff"&gt;1&lt;/span&gt; insertion&lt;span style="color:#f92672"&gt;(&lt;/span&gt;+&lt;span style="color:#f92672"&gt;)&lt;/span&gt;, &lt;span style="color:#ae81ff"&gt;1&lt;/span&gt; deletion&lt;span style="color:#f92672"&gt;(&lt;/span&gt;-&lt;span style="color:#f92672"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;$ git log --oneline
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;0d9f3cf &lt;span style="color:#f92672"&gt;(&lt;/span&gt;HEAD -&amp;gt; main&lt;span style="color:#f92672"&gt;)&lt;/span&gt; succeded commit
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;9a89b16 &lt;span style="color:#f92672"&gt;(&lt;/span&gt;origin/main&lt;span style="color:#f92672"&gt;)&lt;/span&gt; test commit
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;10367df README.md added
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#ae81ff"&gt;0051724&lt;/span&gt; Inital commit
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="özet"&gt;Özet&lt;/h2&gt;
&lt;p&gt;Özetle, .NET projelerinde kod kalitesini geliştirici insiyatifinden çıkarıp, takım genelinde zorunlu hale getirmek için uyguladığımız pratiklere göz attık. &lt;code&gt;TreatWarningsAsErrors&lt;/code&gt;, .editorconfig kuralları ve gelişmiş analyzer paketleriyle kodun hem okunabilirliğini hem de güvenliğini arttırdık. Pre-commit hook’ları sayesinde ise hatalı veya testten geçmeyen kodun repoya girmesini baştan engelledik. Bu yaklaşımlarla, takımda sürdürülebilir ve yüksek kaliteli bir yazılım geliştirme kültürü oturtmak mümkün oldu.&lt;/p&gt;
&lt;p&gt;Kaynak koduna erişmek isterseniz projenin tamamını GitHub hesabımda bulabilirsiniz:&lt;/p&gt;
&lt;p&gt;&lt;a href="https://github.com/berkslv/lecture-net-pre-commit"&gt;github.com/berkslv/lecture-net-pre-commit&lt;/a&gt;&lt;/p&gt;
&lt;h2 id="kapanış"&gt;Kapanış&lt;/h2&gt;
&lt;p&gt;Okuduğunuz için teşekkürler! 🎉 Yazılım geliştirme alanındaki araştırmalarımı kaçırmamak için &lt;a href="https://x.com/berkslv"&gt;@berkslv&lt;/a&gt; adresinden beni takip edebilirsiniz.&lt;/p&gt;</content></item><item><title>Feature Flagging in .NET with OpenFeature and flagd</title><link>https://berkselvi.dev/tr/posts/feature-flagging-in-dotnet-with-openfeature-and-flagd/</link><pubDate>Tue, 08 Jul 2025 00:00:00 +0300</pubDate><guid>https://berkselvi.dev/tr/posts/feature-flagging-in-dotnet-with-openfeature-and-flagd/</guid><description>&lt;p&gt;Feature flagging (özellik bayrakları), modern yazılım geliştirme pratiklerinin vazgeçilmezlerinden biri haline geldi. Sık sık release alıyorsanız, belirli kullanıcılarla özellikleri test etmek istiyorsanız, daha güvenli sürümler arıyorsanız, feature flag’ler kesinlikle araç kutunuzda olmalı. Bu yazıda, vendor bağımsız bir feature flag standardı olan OpenFeature’ı anlatacağım ve .NET projelerinde &lt;strong&gt;flagd&lt;/strong&gt; ile nasıl entegre edilebileceğini göstereceğim.&lt;/p&gt;
&lt;h2 id="feature-flagging-nedir"&gt;Feature Flagging Nedir?&lt;/h2&gt;
&lt;p&gt;Feature flag’ler (ya da diğer adıyla feature toggle’lar), yeni bir kod release almadan uygulamanızdaki özellikleri runtime sırasında anlık olarak açıp kapatmanızı sağlar. Hatta bir özelliği kullanıcı, ortam veya istediğiniz herhangi bir kritere göre segmente edebilirsiniz. Bu sayede:&lt;/p&gt;</description><content>&lt;p&gt;Feature flagging (özellik bayrakları), modern yazılım geliştirme pratiklerinin vazgeçilmezlerinden biri haline geldi. Sık sık release alıyorsanız, belirli kullanıcılarla özellikleri test etmek istiyorsanız, daha güvenli sürümler arıyorsanız, feature flag’ler kesinlikle araç kutunuzda olmalı. Bu yazıda, vendor bağımsız bir feature flag standardı olan OpenFeature’ı anlatacağım ve .NET projelerinde &lt;strong&gt;flagd&lt;/strong&gt; ile nasıl entegre edilebileceğini göstereceğim.&lt;/p&gt;
&lt;h2 id="feature-flagging-nedir"&gt;Feature Flagging Nedir?&lt;/h2&gt;
&lt;p&gt;Feature flag’ler (ya da diğer adıyla feature toggle’lar), yeni bir kod release almadan uygulamanızdaki özellikleri runtime sırasında anlık olarak açıp kapatmanızı sağlar. Hatta bir özelliği kullanıcı, ortam veya istediğiniz herhangi bir kritere göre segmente edebilirsiniz. Bu sayede:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Kademeli yaygınlaştırma (progressive delivery)&lt;/li&gt;
&lt;li&gt;A/B testleri ve denemeler&lt;/li&gt;
&lt;li&gt;Problemli özellikler için anında kill switch&lt;/li&gt;
&lt;li&gt;Dağıtım sırasında daha düşük risk&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Peki ya en önemli faydası nedir? &lt;strong&gt;Kontrol.&lt;/strong&gt; Artık yayına aldığınız kod, sadece ekibinize ya da küçük bir beta grubuna görünür olabilir. Bir problem çıktığında ise, panikle rollback yapmak yerine, sadece flag’i kapatırsınız.&lt;/p&gt;
&lt;h2 id="eski-yöntem-environment-variables"&gt;Eski Yöntem: Environment Variables&lt;/h2&gt;
&lt;p&gt;OpenFeature ve flagd’dan önce biraz gerçek dünyada yaşanan acılardan bahsetmek istiyorum. Pek çok ekip gibi, ben de başlangıçta feature flag yönetimini &lt;strong&gt;environment variable&lt;/strong&gt; (veya &lt;code&gt;appsettings.json&lt;/code&gt;) üzerinden yaptım. Şu yapı eminim size de tanıdık gelecektir:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-json" data-lang="json"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;EnableNewDashboard&amp;#34;&lt;/span&gt;: &lt;span style="color:#66d9ef"&gt;true&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Kodda da şöyle bir kontrol:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-csharp" data-lang="csharp"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; (Configuration[&lt;span style="color:#e6db74"&gt;&amp;#34;EnableNewDashboard&amp;#34;&lt;/span&gt;] == &lt;span style="color:#e6db74"&gt;&amp;#34;true&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// Yeni dashboard’u göster&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Tabiki ilk başta işe yarıyor… ama bir süre sonra sorunlar başlıyor:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Hedefleme yok:&lt;/strong&gt; Ya hep ya hiç. Sadece iç kullanıcılar için mi açmak istiyorsunuz? Mümkün değil.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Anlık değişiklik yok:&lt;/strong&gt; Değişiklik için çoğu zaman yeniden deploy veya restart gerekiyor.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Config karmaşası:&lt;/strong&gt; Zamanla onlarca flag, diğer konfigürasyonlarla karışıyor.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Audit veya geçmiş yok:&lt;/strong&gt; Kim ne zaman değiştirdi, iz yok.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Standart yok:&lt;/strong&gt; Her ekip, her uygulama kendince yapıyor.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Ekipler büyüdükçe bu yaklaşım kırılganlaşıyor. Progressive delivery, canary deployment veya A/B testleri için dinamik kontrol şart. İşte bu noktada feature flag platformları ve OpenFeature gibi standartlar devreye giriyor.&lt;/p&gt;
&lt;h2 id="neden-openfeature"&gt;Neden OpenFeature?&lt;/h2&gt;
&lt;p&gt;OpenFeature dışındaki çoğu feature flag çözümü (LaunchDarkly, Unleash, vs.) sizi kendi SDK’sına ve API’sine bağımlı kılar. OpenFeature burada devreye giriyor:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Vendor bağımsız:&lt;/strong&gt; Sağlayıcı değiştirmek için kodunuzu yeniden yazmanız gerekmez.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Standart API:&lt;/strong&gt; Farklı dil ve platformlarda aynı yaklaşımı kullanabilirsiniz.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Genişletilebilir:&lt;/strong&gt; Loglama, telemetry gibi hook’lar ekleyebilirsiniz.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Cloud-native uyumlu:&lt;/strong&gt; Modern mimariler için tasarlanmıştır.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;OpenFeature ile uygulamanız sadece OpenFeature SDK’ya bağımlı olur; belli bir sağlayıcıya değil. Zamanla farklı bir sağlayıcıya geçmek isterseniz, provider’ı değiştirmeniz yeterli olacaktır.&lt;/p&gt;
&lt;h2 id="neden-flagd"&gt;Neden flagd?&lt;/h2&gt;
&lt;p&gt;&lt;a href="https://github.com/open-feature/flagd"&gt;flagd&lt;/a&gt;, açık kaynak ve hafif bir feature flag backend’idir. OpenFeature ile doğrudan çalışmak için tasarlanmıştır ve flag’leri JSON dosyalarından, HTTP endpoint veya gRPC üzerinden yönetilebilir. Fakat ne yazık ki şu anda flag’leri yönetmek için official bir kullanıcı arayüzü (UI) bulunmuyor, bunun yerine GitOps pratikleri ile yönetim sağlanıyor. flagd, hiçbir vendor-lockin yaşamadan, self host edebileceğiniz, bulut tabanlı bir feature flag backendi istiyorsanız idealdir.&lt;/p&gt;
&lt;h2 id="gerçek-hayattan-örnek-envden-feature-flage"&gt;Gerçek Hayattan Örnek: ENV’den Feature Flag’e&lt;/h2&gt;
&lt;p&gt;Gerçek hayattan bir örnekle, bir .NET web API’niz var diyelim. Yeni bir özelliği yayına alıyorsunuz; ama önce sadece iç kullanıcılarınıza açmak istiyorsunuz. Bir environment variable tanımlayıp, bunu &lt;code&gt;isInternalUser&lt;/code&gt; gibi bir değişkene bağlayarak feature flag pratiklerini kısmen uygulayabilirsiniz. Ancak bu değişkende her değişiklikte kodu tekrar deploy etmeniz gerekiyor ve belirli bir kullanıcı grubu için değil, servisi kullanan tüm kullanıcılar için bu işlemi yapabiliyorsunuz.&lt;/p&gt;
&lt;p&gt;Enviroment variable ile yönetilen bir feature flag ortamında Product Manager, bir özelliği kullanıcıların %10’u ile A/B test yapmak istiyorsa özel kod ve yeniden deploy gerekecektir.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Feature flag’ler&lt;/strong&gt; bu tabloyu değiştiriyor:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Özelliği anında, spesifik kullanıcı/gruplar için açıp kapatın.&lt;/li&gt;
&lt;li&gt;Herkese açmaya hazır olduğunuzda deploy gerekmeden canlıya çıkın.&lt;/li&gt;
&lt;li&gt;Sorun çıkarsa, tek tuşla anında kapatın.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Modern ürün ekipleri bu sayede daha hızlı iterasyon yapabiliyor, riski azaltıyor ve sürekli değer sunabiliyor.&lt;/p&gt;
&lt;h2 id="nette-openfeature-ve-flagd-entegrasyonu"&gt;.NET’te OpenFeature ve flagd Entegrasyonu&lt;/h2&gt;
&lt;p&gt;Şimdi kurulumun nasıl yapılacağına bakalım.&lt;/p&gt;
&lt;h3 id="1-nuget-paketlerini-ekleyin"&gt;1. NuGet Paketlerini Ekleyin&lt;/h3&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;dotnet add package OpenFeature
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;dotnet add package OpenFeature.Contrib.Providers.Flagd
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="2-flagdi-docker-compose-ile-çalıştırın"&gt;2. flagd’i docker-compose ile çalıştırın&lt;/h3&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;services&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;flagd&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;image&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;ghcr.io/open-feature/flagd:latest&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;volumes&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#ae81ff"&gt;./flags:/etc/flagd&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;command&lt;/span&gt;: [
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#39;start&amp;#39;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#39;--uri&amp;#39;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#39;file:/etc/flagd/flags.json&amp;#39;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#39;--debug&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ]
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;ports&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#e6db74"&gt;&amp;#39;8013:8013&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Örnek &lt;code&gt;flags.json&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-json" data-lang="json"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;$schema&amp;#34;&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;https://flagd.dev/schema/v0/flags.json&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;flags&amp;#34;&lt;/span&gt;: {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;new-greeting&amp;#34;&lt;/span&gt;: {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;state&amp;#34;&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;ENABLED&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;variants&amp;#34;&lt;/span&gt;: {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;on&amp;#34;&lt;/span&gt;: &lt;span style="color:#66d9ef"&gt;true&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;off&amp;#34;&lt;/span&gt;: &lt;span style="color:#66d9ef"&gt;false&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; },
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;defaultVariant&amp;#34;&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;off&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;targeting&amp;#34;&lt;/span&gt;: {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;if&amp;#34;&lt;/span&gt;: [
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;$ref&amp;#34;&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;userAgentIsFirefox&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; },
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;on&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;null&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ]
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; },
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;$evaluators&amp;#34;&lt;/span&gt;: {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;userAgentIsFirefox&amp;#34;&lt;/span&gt;: {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;in&amp;#34;&lt;/span&gt;: [
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;firefox&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;var&amp;#34;&lt;/span&gt;: [
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;userAgent&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ]
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ]
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="3-net-uygulamanızda-openfeatureı-konfigüre-edin"&gt;3. .NET Uygulamanızda OpenFeature’ı Konfigüre Edin&lt;/h3&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-csharp" data-lang="csharp"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;using&lt;/span&gt; OpenFeature.Contrib.Providers.Flagd;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;using&lt;/span&gt; OpenFeature.Model;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; builder = WebApplication.CreateBuilder(args);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;await&lt;/span&gt; OpenFeature.Api.Instance.SetProviderAsync(&lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; FlagdProvider(&lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; Uri(&lt;span style="color:#e6db74"&gt;&amp;#34;http://localhost:8013&amp;#34;&lt;/span&gt;)));
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; app = builder.Build();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;app.MapGet(&lt;span style="color:#e6db74"&gt;&amp;#34;/greet&amp;#34;&lt;/span&gt;, &lt;span style="color:#66d9ef"&gt;async&lt;/span&gt; context =&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; client = OpenFeature.Api.Instance.GetClient();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// Get User-Agent header&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; userAgent = context.Request.Headers[&lt;span style="color:#e6db74"&gt;&amp;#34;User-Agent&amp;#34;&lt;/span&gt;].FirstOrDefault() ?? &lt;span style="color:#e6db74"&gt;&amp;#34;&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; evalContext = EvaluationContext.Builder().Set(&lt;span style="color:#e6db74"&gt;&amp;#34;userAgent&amp;#34;&lt;/span&gt;, userAgent).Build();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; useNewGreeting = &lt;span style="color:#66d9ef"&gt;await&lt;/span&gt; client.GetBooleanValueAsync(&lt;span style="color:#e6db74"&gt;&amp;#34;new-greeting&amp;#34;&lt;/span&gt;, &lt;span style="color:#66d9ef"&gt;false&lt;/span&gt;, evalContext);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;string&lt;/span&gt; greeting = useNewGreeting
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ? &lt;span style="color:#e6db74"&gt;&amp;#34;Hey there! Welcome to the new greeting experience! 🎉&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; : &lt;span style="color:#e6db74"&gt;&amp;#34;Hello, World!&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;await&lt;/span&gt; context.Response.WriteAsync(greeting);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;});
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;app.Run();
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="4-test-edin"&gt;4. Test Edin&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;flags.json&lt;/code&gt; dosyanızdan flag’i değiştirin. .NET uygulamanız değişikliği anında yakalayacaktır. API ucunu test ettiğimizde aşağıdaki gibi bir sonuçla karşılaşırız.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;$ curl http://localhost:5050/greet
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Hello, World!
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;$ curl http://localhost:5050/greet -H &lt;span style="color:#e6db74"&gt;&amp;#39;User-Agent: firefox&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Hey there! Welcome to the new greeting experience! 🎉
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Eğer flagd backendine direk olarak istek atıp flagin durumunu görmek istersek ise aşağıdaki şekilde sorgulama yapabiliriz&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;$ curl -X POST &lt;span style="color:#e6db74"&gt;&amp;#34;http://localhost:8013/flagd.evaluation.v1.Service/ResolveBoolean&amp;#34;&lt;/span&gt; -d &lt;span style="color:#e6db74"&gt;&amp;#39;{&amp;#34;flagKey&amp;#34;:&amp;#34;new-greeting&amp;#34;,&amp;#34;context&amp;#34;:{}}&amp;#39;&lt;/span&gt; -H &lt;span style="color:#e6db74"&gt;&amp;#34;Content-Type: application/json&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;{&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;value&amp;#34;&lt;/span&gt;:false, &lt;span style="color:#e6db74"&gt;&amp;#34;reason&amp;#34;&lt;/span&gt;:&lt;span style="color:#e6db74"&gt;&amp;#34;DEFAULT&amp;#34;&lt;/span&gt;, &lt;span style="color:#e6db74"&gt;&amp;#34;variant&amp;#34;&lt;/span&gt;:&lt;span style="color:#e6db74"&gt;&amp;#34;off&amp;#34;&lt;/span&gt;, &lt;span style="color:#e6db74"&gt;&amp;#34;metadata&amp;#34;&lt;/span&gt;:&lt;span style="color:#f92672"&gt;{}}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;$ curl -X POST &lt;span style="color:#e6db74"&gt;&amp;#34;http://localhost:8013/flagd.evaluation.v1.Service/ResolveBoolean&amp;#34;&lt;/span&gt; -d &lt;span style="color:#e6db74"&gt;&amp;#39;{&amp;#34;flagKey&amp;#34;:&amp;#34;new-greeting&amp;#34;,&amp;#34;context&amp;#34;:{&amp;#34;userAgent&amp;#34;: &amp;#34;firefox&amp;#34;}}&amp;#39;&lt;/span&gt; -H &lt;span style="color:#e6db74"&gt;&amp;#34;Content-Type: application/json&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;{&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;value&amp;#34;&lt;/span&gt;:true, &lt;span style="color:#e6db74"&gt;&amp;#34;reason&amp;#34;&lt;/span&gt;:&lt;span style="color:#e6db74"&gt;&amp;#34;TARGETING_MATCH&amp;#34;&lt;/span&gt;, &lt;span style="color:#e6db74"&gt;&amp;#34;variant&amp;#34;&lt;/span&gt;:&lt;span style="color:#e6db74"&gt;&amp;#34;on&amp;#34;&lt;/span&gt;, &lt;span style="color:#e6db74"&gt;&amp;#34;metadata&amp;#34;&lt;/span&gt;:&lt;span style="color:#f92672"&gt;{}}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="özet"&gt;Özet&lt;/h2&gt;
&lt;p&gt;Feature flag’ler riski azaltır, daha hızlı dağıtım sağlar ve kullanıcı hedeflemesini kolaylaştırır. OpenFeature, .NET’te feature flag yönetimi için size standart ve geleceğe dönük bir API sunar. flagd ise kendi kendinize barındırabileceğiniz, Kubernetes ile uyumlu açık kaynak bir backend’dir.&lt;/p&gt;
&lt;p&gt;Hâlâ environment variable ya da config dosyaları ile özellik yönetimi yapıyorsanız, OpenFeature ve flagd’ye bir şans verin. Gelecekte kendinize teşekkür edeceksiniz, özellikle bir cuma akşamı bir özelliği acil kapatmanız gerekirse.&lt;/p&gt;
&lt;p&gt;Kaynak koduna erişmek isterseniz projenin tamamını GitHub hesabımda bulabilirsiniz:&lt;/p&gt;
&lt;p&gt;&lt;a href="https://github.com/berkslv/lecture-net-openfeature"&gt;https://github.com/berkslv/lecture-net-pre-commit&lt;/a&gt;&lt;/p&gt;
&lt;h2 id="sonuç"&gt;Sonuç&lt;/h2&gt;
&lt;p&gt;Okuduğunuz için teşekkürler! 🎉 Yazılım geliştirme alanındaki araştırmalarımı kaçırmamak için &lt;a href="https://x.com/berkslv"&gt;@berkslv&lt;/a&gt; adresinden beni takip edebilirsiniz.&lt;/p&gt;</content></item><item><title>SignalR ile Gerçek Zamanlı İletişim: Derinlemesine İnceleme</title><link>https://berkselvi.dev/tr/posts/realtime-communication-with-signalr-a-deep-dive/</link><pubDate>Fri, 29 Nov 2024 00:00:00 +0300</pubDate><guid>https://berkselvi.dev/tr/posts/realtime-communication-with-signalr-a-deep-dive/</guid><description>&lt;p&gt;Günümüz dünyasında verilerin hızla hareket ettiği bir ortamda, modern uygulamaların kullanıcılarına sorunsuz deneyimler sunabilmesi için gerçek zamanlı iletişim bir zorunluluk haline gelmiştir. SignalR, .NET uygulamaları için gerçek zamanlı iletişim özelliklerini kolayca hayata geçirmenize yardımcı olan temel bir kütüphanedir. SignalR kullanırken tüm iletişim ve network gereksinimleri kütüphane tarafından soyutlanır; sadece bir RPC çağrısı yapar gibi yöntem çağrısında bulunmanız yeterlidir. SignalR sayesinde canlı panolar, sohbet uygulamaları veya web uygulamalarında sıkça ihtiyaç duyulan basit bildirim özelliklerini kolayca oluşturabilirsiniz. Haydi başlayalım.&lt;/p&gt;</description><content>&lt;p&gt;Günümüz dünyasında verilerin hızla hareket ettiği bir ortamda, modern uygulamaların kullanıcılarına sorunsuz deneyimler sunabilmesi için gerçek zamanlı iletişim bir zorunluluk haline gelmiştir. SignalR, .NET uygulamaları için gerçek zamanlı iletişim özelliklerini kolayca hayata geçirmenize yardımcı olan temel bir kütüphanedir. SignalR kullanırken tüm iletişim ve network gereksinimleri kütüphane tarafından soyutlanır; sadece bir RPC çağrısı yapar gibi yöntem çağrısında bulunmanız yeterlidir. SignalR sayesinde canlı panolar, sohbet uygulamaları veya web uygulamalarında sıkça ihtiyaç duyulan basit bildirim özelliklerini kolayca oluşturabilirsiniz. Haydi başlayalım.&lt;/p&gt;
&lt;h2 id="neden-signalr"&gt;Neden SignalR?&lt;/h2&gt;
&lt;p&gt;Geleneksel istemci-sunucu iletişim modelleri, yüksek etkileşim ihtiyaçları için daha az verimli ve sınırlı olan istek-cevap modeline dayanır ve veriye ihtiyaç duyulduğunda istemcinin bir istek göndermesini gerektirir. SignalR, istemci ile sunucu arasındaki aynı bağlantıyı sürekli açık tutarak bu zorlukları ortadan kaldırmak için tasarlanmıştır.&lt;/p&gt;
&lt;h3 id="signalrın-temel-avantajları"&gt;SignalR’ın Temel Avantajları:&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Gerçek Zamanlı İletişim:&lt;/strong&gt; İstemciler, yeni bir istek göndermeye gerek kalmadan anında güncellemeleri alır.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Çapraz Platform Desteği:&lt;/strong&gt; SignalR, platform ve tarayıcıdan bağımsızdır.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Karmaşık Protokollerin Soyutlanması:&lt;/strong&gt; SignalR, WebSocket, Server-Sent Events ve Long Polling protokollerini kullanma yeteneğine sahiptir. Bu, istemci ve sunucu arasında daha verimli bir ilişki sağlar ve en uygun protokolün seçilmesine olanak tanır. WebSocket protokolü istemci tarafından desteklenmiyorsa, SignalR kendi iç algoritmasına göre diğer yöntemleri seçerek kullanır.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;.NET Uygulamaları ile Entegrasyon:&lt;/strong&gt; .NET ile başarılı bir şekilde entegre olarak modern .NET uygulamalarında sorunsuz bir kullanım sağlar.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="signalr-ile-başlangıç"&gt;SignalR ile Başlangıç&lt;/h3&gt;
&lt;p&gt;.NET projenize SignalR’ı entegre etmek için aşağıdaki adımları takip edebilirsiniz:&lt;/p&gt;
&lt;h3 id="1-signalr-nuget-paketi"&gt;1. SignalR NuGet Paketi&lt;/h3&gt;
&lt;p&gt;SignalR, .NET sistem kütüphaneleriyle birlikte gelir, bu nedenle ek bir paket yüklemenize gerek yoktur. Ancak Visual Studio saçmalamaya başlarsa &lt;code&gt;Microsoft.AspNetCore.SignalR&lt;/code&gt; paketini yüklemeyi deneyebilirsiniz.&lt;/p&gt;
&lt;h3 id="2-hub-sınıfı-oluşturma"&gt;2. Hub Sınıfı Oluşturma&lt;/h3&gt;
&lt;p&gt;Hub, bağlantıları yöneten ve istemcilere mesaj gönderen merkezi bir bileşen görevi görür. Bu sınıfı, HTTP isteklerini kabul ettiğiniz Controller&amp;rsquo;lara benzetebilirsiniz, ancak burada istemciden gelen istekleri kabul edebilir veya istemciye doğrudan istek gönderebilirsiniz. Basit bir örnek:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-csharp" data-lang="csharp"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;using&lt;/span&gt; Microsoft.AspNetCore.SignalR;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;class&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;NotificationsHub&lt;/span&gt; : Hub
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;async&lt;/span&gt; Task SendNotification(&lt;span style="color:#66d9ef"&gt;string&lt;/span&gt; content)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;await&lt;/span&gt; Clients.All.SendAsync(&lt;span style="color:#e6db74"&gt;&amp;#34;ReceiveNotification&amp;#34;&lt;/span&gt;, content);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="3-programcs-dosyasında-hubı-yapılandırma"&gt;3. Program.cs Dosyasında Hub’ı Yapılandırma&lt;/h3&gt;
&lt;p&gt;SignalR servislerini kaydedin ve hub uç noktasını şu şekilde eşleyin:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-csharp" data-lang="csharp"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; builder = WebApplication.CreateBuilder(args);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;//...&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;builder.Services.AddSignalR();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; app = builder.Build();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;//...&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;app.MapHub&amp;lt;NotificationsHub&amp;gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;hubs/notification&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;app.Run();
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="4-signalr-istemcisi"&gt;4. SignalR İstemcisi&lt;/h3&gt;
&lt;p&gt;SignalR, Javascript, .NET ve Java gibi çeşitli istemci platformlarını destekler. Eğer bu platformlarda SignalR kütüphanesini uygulamak istemiyorsanız, uç noktanızı test etmek için basitçe Postman kullanabilirsiniz. Ancak JSON sonuna 0x1E karakterini eklemeniz gerekir. SignalR’ın dahili protokol gerekliliği budur. Javascript kütüphanesi kullanıyorsanız bu detayla uğraşmanıza gerek yoktur.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-json" data-lang="json"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;protocol&amp;#34;&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;json&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;version&amp;#34;&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;SignalR Hub’a mesaj göndermek ve almak için şu şekilde bir JSON kullanabilirsiniz:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-json" data-lang="json"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;arguments&amp;#34;&lt;/span&gt; : [&lt;span style="color:#e6db74"&gt;&amp;#34;hello world&amp;#34;&lt;/span&gt;],
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;target&amp;#34;&lt;/span&gt; : &lt;span style="color:#e6db74"&gt;&amp;#34;SendNotification&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;type&amp;#34;&lt;/span&gt; : &lt;span style="color:#ae81ff"&gt;1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Kısa bir Postman ve SignalR demosu:&lt;/p&gt;
&lt;img src="https://berkselvi.dev/img/realtime-communication-with-signalr-a-deep-dive/postman-signalr-demo.gif" alt="SignalR ve Postman demo" loading="lazy" /&gt;
&lt;p class="image-sub-title"&gt;SignalR ve Postman demo&lt;/p&gt;
&lt;h2 id="gelişmiş-özellikler"&gt;Gelişmiş Özellikler&lt;/h2&gt;
&lt;h3 id="1-tip-güvenli-hublar"&gt;1. Tip Güvenli Hub’lar&lt;/h3&gt;
&lt;p&gt;Bakımı daha kolay hale getirmek için hub arayüzünü belirterek tip güvenli hub’lar kullanabilirsiniz:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-csharp" data-lang="csharp"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;interface&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;INotificationsClient&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Task ReceiveNotification(&lt;span style="color:#66d9ef"&gt;string&lt;/span&gt; content);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;class&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;NotificationsHub&lt;/span&gt; : Hub&amp;lt;INotificationsClient&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;async&lt;/span&gt; Task SendNotification(&lt;span style="color:#66d9ef"&gt;string&lt;/span&gt; content)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;await&lt;/span&gt; Clients.All.ReceiveNotification(content);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="2-belirli-istemcilere-mesaj-gönderme"&gt;2. Belirli İstemcilere Mesaj Gönderme&lt;/h3&gt;
&lt;p&gt;SignalR, belirli kullanıcılar veya gruplara mesaj gönderme yeteneğine sahiptir. Kullanıcıyı bir grup adına ekledikten sonra yalnızca ilgili kullanıcıya bildirim gönderebilirsiniz:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-csharp" data-lang="csharp"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;await&lt;/span&gt; Clients.User(userId).ReceiveNotification(&lt;span style="color:#e6db74"&gt;&amp;#34;Hello, User!&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;await&lt;/span&gt; Clients.Group(groupName).ReceiveNotification(&lt;span style="color:#e6db74"&gt;&amp;#34;Hello, Group!&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Kullanıcı kimliği (userId) ile hedefleme yapmak için IUserIdProvider uygulamanız gerekir:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-csharp" data-lang="csharp"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;class&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;UserIdProvider&lt;/span&gt; : IUserIdProvider
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;string&lt;/span&gt; GetUserId(HubConnectionContext connection)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; connection.User?.FindFirst(ClaimTypes.NameIdentifier)?.Value;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-csharp" data-lang="csharp"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// Program.cs&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;services.AddSingleton&amp;lt;IUserIdProvider, CustomUserIdProvider&amp;gt;();
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="3-hub-dışından-ihubcontext-kullanımı"&gt;3. Hub Dışından IHubContext Kullanımı&lt;/h3&gt;
&lt;p&gt;Hub dışında istemcilere mesaj göndermek için aşağıdaki şekilde bir yaklaşım izleyebilirsiniz. IHubContext türünü enjekte edin ve mesajınızı istemcilere gönderin.&lt;/p&gt;
&lt;p&gt;Sisteminizde bir olay gerçekleştiğinde bu özelliği kullanarak hub&amp;rsquo;ı tetikleyebilirsiniz. Örneğin, uzun süren bir toplu işlem tamamlandığında kullanıcılarınıza sonuç hakkında bildirim gönderebilirsiniz.&lt;/p&gt;
&lt;p&gt;Kısa bir not: Kodunuzu daha temiz hale getirmek için bu IHubContext arayüzünü, örneğin &lt;code&gt;INotificationDispatcher&lt;/code&gt; gibi başka bir arayüzle soyutlamanız iyi bir fikir olabilir.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-csharp" data-lang="csharp"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;class&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;NotificationDispatcher&lt;/span&gt;(IHubContext&amp;lt;NotificationsHub&amp;gt; hubContext) : INotificationDispatcher
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;async&lt;/span&gt; Task SendNotification(&lt;span style="color:#66d9ef"&gt;string&lt;/span&gt; content)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;await&lt;/span&gt; hubContext.Clients.All.SendAsync(&lt;span style="color:#e6db74"&gt;&amp;#34;ReceiveNotification&amp;#34;&lt;/span&gt;, content);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;async&lt;/span&gt; Task SendNotificationToUser(&lt;span style="color:#66d9ef"&gt;string&lt;/span&gt; userId, &lt;span style="color:#66d9ef"&gt;string&lt;/span&gt; content)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;await&lt;/span&gt; hubContext.Clients.User(userId).SendAsync(&lt;span style="color:#e6db74"&gt;&amp;#34;ReceiveNotification&amp;#34;&lt;/span&gt;, content);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;async&lt;/span&gt; Task SendNotificationToGroup(&lt;span style="color:#66d9ef"&gt;string&lt;/span&gt; groupName, &lt;span style="color:#66d9ef"&gt;string&lt;/span&gt; content)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;await&lt;/span&gt; hubContext.Clients.Group(groupName).SendAsync(&lt;span style="color:#e6db74"&gt;&amp;#34;ReceiveNotification&amp;#34;&lt;/span&gt;, content);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;ol start="4"&gt;
&lt;li&gt;SignalR Güvenliğini Sağlama&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;SignalR hub’larını [Authorize] özniteliği ile güvence altına alabilirsiniz:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-csharp" data-lang="csharp"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;[Authorize]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;class&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;NotificationsHub&lt;/span&gt; : Hub
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// Hub methods&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;ol start="5"&gt;
&lt;li&gt;Redis Backplane ile SignalR Ölçekleme&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;SignalR’ı birden fazla sunucuya ölçeklendirmek, birden çok instance üzerinde çalışan yüksek talep gören uygulamalar için güvenilir gerçek zamanlı iletişim sağlar. Redis backplane, SignalR sunucuları arasındaki mesajları senkronize ederek, farklı sunuculara bağlı istemcilerin sorunsuz bir şekilde mesaj almasını sağlar. İşte Microsoft belgelerinden bir alıntı:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;SignalR Redis backplane, mesajları diğer sunuculara iletmek için pub/sub özelliğini kullanır. Bir istemci bağlantı kurduğunda, bağlantı bilgileri backplane’e iletilir. Bir sunucu tüm istemcilere mesaj göndermek istediğinde, bunu backplane’e iletir. Backplane, tüm bağlı istemcileri ve hangi sunucularda olduklarını bilir. Mesajı ilgili sunucular aracılığıyla tüm istemcilere gönderir.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;dotnet add package Microsoft.AspNetCore.SignalR.StackExchangeRedis
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-csharp" data-lang="csharp"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// Program.cs&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;builder.Services.AddSignalR()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; .AddStackExchangeRedis(&lt;span style="color:#e6db74"&gt;&amp;#34;localhost:6379&amp;#34;&lt;/span&gt;, options =&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; options.Configuration.ChannelPrefix = &lt;span style="color:#e6db74"&gt;&amp;#34;SignalR&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; });
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Eğer bana inanmıyorsanız, SignalR uygulamanızı yerel test ortamında birden fazla instance ile çalıştırmayı Docker Compose kullanarak deneyebilirsiniz:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;services&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;signalr&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;image&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;your-app&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;ports&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#e6db74"&gt;&amp;#34;5000:5000&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;depends_on&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#ae81ff"&gt;redis&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;redis&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;image&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;redis:latest&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;ports&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#e6db74"&gt;&amp;#34;6379:6379&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;docker-compose up --scale signalr&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;3&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="signalr-iletişim-modelleri"&gt;SignalR İletişim Modelleri&lt;/h2&gt;
&lt;p&gt;SignalR, uyumluluk ve performansı sağlamak için birden fazla taşıma yöntemini soyutlar:&lt;/p&gt;
&lt;h3 id="websocket-websockets"&gt;&lt;strong&gt;WebSocket (&lt;/strong&gt;&lt;code&gt;webSockets&lt;/code&gt;&lt;strong&gt;)&lt;/strong&gt;:&lt;/h3&gt;
&lt;p&gt;WebSockets, SignalR için en verimli ve tercih edilen gerçek zamanlı, çift yönlü iletişim yöntemidir. Tek bir TCP kanalı üzerinden kalıcı, tam çift yönlü bir bağlantı kurarak istemci ve sunucu arasında eşzamanlı mesaj alışverişine olanak tanır. Bu, canlı sohbetler, ortak düzenleme araçları, çevrimiçi oyunlar ve finansal veri akışları gibi düşük gecikme ve yüksek hız gerektiren uygulamalar için idealdir.&lt;/p&gt;
&lt;p&gt;WebSockets, ilk HTTP el sıkışmasının ardından kendi hafif protokolüne (&lt;code&gt;ws://&lt;/code&gt; veya &lt;code&gt;wss://&lt;/code&gt;) geçerek geleneksel HTTP tabanlı iletişimin getirdiği yükü azaltır. Ancak, WebSockets&amp;rsquo;in ölçeklendirilmesi, WebSocket uyumlu yük dengeleyiciler ve yapışkan oturumlar gibi özel altyapılar gerektirir. SignalR, bağlantı sürekliliğini, otomatik yeniden bağlantıları ve WebSockets kullanılamadığında alternatif protokollere geçişi yöneterek bu zorlukları basitleştirir.&lt;/p&gt;
&lt;h3 id="server-sent-events-serversentevents"&gt;&lt;strong&gt;Server-Sent Events (&lt;/strong&gt;&lt;code&gt;serverSentEvents&lt;/code&gt;&lt;strong&gt;):&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;SSE (Server-Sent Events), uzun ömürlü bir bağlantı üzerinden sunucudan istemciye gerçek zamanlı güncellemeler sağlayan hafif bir HTTP tabanlı mekanizmadır. Standartlaştırılmış EventSource API&amp;rsquo;sini kullanır ve verileri &lt;code&gt;text/event-stream&lt;/code&gt; formatında iletir. Bu, canlı panolar, bildirimler ve haber akışları gibi yalnızca sunucudan istemciye veri aktarımı gerektiren durumlar için basit ve etkilidir.&lt;/p&gt;
&lt;p&gt;SSE, standart HTTP üzerinde çalıştığı için mevcut HTTP altyapısı ile kolayca entegre olur ve WebSockets&amp;rsquo;e kıyasla daha kolay ölçeklenebilir. Ancak, SSE Internet Explorer tarafından desteklenmez ve istemciden sunucuya iletişim gerektiren durumlar için ayrı HTTP istekleri gerektirir. SignalR, WebSockets kullanılamadığında otomatik olarak SSE&amp;rsquo;yi tercih ederek kesintisiz bir deneyim sunar.&lt;/p&gt;
&lt;h3 id="long-polling-longpolling"&gt;&lt;strong&gt;Long Polling (&lt;/strong&gt;&lt;code&gt;longPolling&lt;/code&gt;&lt;strong&gt;):&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;Long Polling, sürekli olarak HTTP bağlantıları açıp kapatarak gerçek zamanlı iletişimi taklit eder. İstemci, sunucuya bir istek gönderir ve sunucu veri gönderene kadar bağlantıyı açık tutar. Veriler iletildikten sonra istemci hemen yeni bir istek oluşturur. Bu yöntem, güncellemeler arasındaki gecikmeyi en aza indirir ve bir tür push tabanlı bağlantı hissi yaratır.&lt;/p&gt;
&lt;p&gt;Long Polling, tüm tarayıcılarla çalışabilir ve uygulanması kolaydır. Ancak, sık HTTP istekleri ve yanıtları nedeniyle daha fazla yük oluşturur. Bu, özellikle yüksek frekanslı güncellemelerde, WebSockets veya SSE&amp;rsquo;ye kıyasla daha az verimlidir. SignalR, WebSockets veya SSE desteklenmediğinde Long Polling’i bir yedek çözüm olarak kullanır.&lt;/p&gt;
&lt;h3 id="forever-frame-foreverframe"&gt;&lt;strong&gt;Forever Frame (&lt;/strong&gt;&lt;code&gt;foreverFrame&lt;/code&gt;&lt;strong&gt;):&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;Forever Frame, eski Internet Explorer sürümlerine özgü bir protokoldür ve gerçek zamanlı, tek yönlü iletişim sağlar. İstemci tarayıcısında gizli bir iframe oluşturur ve sunucu bu iframe’e sürekli olarak çalıştırılabilir komut dosyaları gönderir.&lt;/p&gt;
&lt;p&gt;Forever Frame, çift yönlü iletişimi desteklememesi ve yüksek ek yük getirmesi gibi sınırlamalara sahiptir. İstemciden sunucuya iletişim, ayrı standart HTTP istekleri gerektirir ve bu da uygulamayı daha az verimli ve zahmetli hale getirir. SignalR, yalnızca eski Internet Explorer tarayıcılarına özgü ortamlar için bir son çare olarak Forever Frame’i kullanır.&lt;/p&gt;
&lt;h3 id="websockets-ve-server-sent-events-karşılaştırması"&gt;WebSockets ve Server-Sent Events Karşılaştırması&lt;/h3&gt;
&lt;p&gt;WebSockets ve Server-Sent Events (SSE), farklı gerçek zamanlı iletişim ihtiyaçlarına hitap eder. &lt;strong&gt;WebSockets&lt;/strong&gt;, düşük gecikmeli, çift yönlü iletişim sunarak sohbet, oyun ve ortak çalışma araçları için idealdir. &lt;strong&gt;SSE&lt;/strong&gt; ise hafif, tek yönlü güncellemeler sağlayarak canlı panolar ve bildirimler gibi durumlar için mükemmeldir.&lt;/p&gt;
&lt;p&gt;WebSockets, etkili iki yönlü mesajlaşma için özel bir protokol (&lt;code&gt;ws://&lt;/code&gt; veya &lt;code&gt;wss://&lt;/code&gt;) kullanır ancak ölçeklendirme için WebSocket uyumlu altyapı gerektirir. SSE ise standart HTTP (&lt;code&gt;text/event-stream&lt;/code&gt;) üzerinde çalışır ve geleneksel HTTP araçları ile daha kolay ölçeklenebilir. Ancak, yalnızca sunucudan istemciye iletişim sağlar ve Internet Explorer desteği bulunmaz.&lt;/p&gt;
&lt;p&gt;SignalR, bu farklılıkları soyutlayarak performans için WebSockets&amp;rsquo;i önceliklendirir ve kesintisiz gerçek zamanlı işlevsellik sağlamak için SSE veya diğer yöntemlere otomatik olarak geçiş yapar.&lt;/p&gt;
&lt;h2 id="protokolü-belirleme"&gt;Protokolü Belirleme&lt;/h2&gt;
&lt;p&gt;SignalR, gerçek zamanlı iletişim için kullanılacak taşıma protokolünü açıkça belirtmenize olanak tanır. Varsayılan olarak, SignalR dahili algoritmasıyla mevcut en iyi protokolü otomatik olarak seçer. Ancak, bu davranışı uygulamanızın ihtiyaçlarına göre özelleştirebilirsiniz.&lt;/p&gt;
&lt;p&gt;WebSockets’in verimliliğinden yararlanmak ve WebSockets kullanılamadığında Long Polling’e geçiş yapmak için &lt;code&gt;Program.cs&lt;/code&gt; dosyasına şu kodu ekleyebilirsiniz:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-csharp" data-lang="csharp"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;app.MapHub&amp;lt;NotificationsHub&amp;gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;/hubs/notification&amp;#34;&lt;/span&gt;, opt =&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; opt.Transports = HttpTransportType.WebSockets | HttpTransportType.LongPolling;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;});
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Veya istemci tarafında JavaScript ile şu şekilde yapılandırabilirsiniz:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-js" data-lang="js"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;let&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;connection&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;signalR&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;HubConnectionBuilder&lt;/span&gt;()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; .&lt;span style="color:#a6e22e"&gt;withUrl&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;/hubs/notification&amp;#34;&lt;/span&gt;, {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;transport&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;signalR&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;HttpTransportType&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;WebSockets&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;|&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;signalR&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;HttpTransportType&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;LongPolling&lt;/span&gt; })
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; .&lt;span style="color:#a6e22e"&gt;build&lt;/span&gt;();
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="özet"&gt;Özet&lt;/h2&gt;
&lt;p&gt;SignalR, .NET uygulamalarına gerçek zamanlı özellikler eklemeyi kolay ve verimli hale getirir. Karmaşık ağ protokollerini soyutlayarak, alt yapı detaylarıyla uğraşmadan canlı panolar, sohbet uygulamaları veya bildirimler oluşturmanıza olanak tanır. WebSockets, Server-Sent Events (SSE) ve Long Polling gibi protokolleri destekleyen SignalR, ortamınıza en uygun protokolü otomatik olarak seçerek kesintisiz bir deneyim sunar.&lt;/p&gt;
&lt;p&gt;Kaynak koduna erişmek isterseniz projenin tamamını GitHub hesabımda bulabilirsiniz:&lt;/p&gt;
&lt;p&gt;&lt;a href="https://github.com/berkslv/lecture-signalr-deep-dive"&gt;GitHub - berkslv/lecture-signalr-deep-dive&lt;/a&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="sonuç"&gt;Sonuç&lt;/h2&gt;
&lt;p&gt;Okuduğunuz için teşekkürler! 🎉 Yazılım geliştirme alanındaki araştırmalarımı kaçırmamak için &lt;a href="https://x.com/berkslv"&gt;@berkslv&lt;/a&gt; adresinden takipte kalabilirsiniz.&lt;/p&gt;</content></item><item><title>Event-Driven Architecture: Mikroservis mimarisinde başka servislerin verilerine mi ihtiyacınız var</title><link>https://berkselvi.dev/tr/posts/do-you-need-other-services-data-in-microservice-architecture-event-driven-architecture/</link><pubDate>Sat, 07 Sep 2024 00:00:00 +0300</pubDate><guid>https://berkselvi.dev/tr/posts/do-you-need-other-services-data-in-microservice-architecture-event-driven-architecture/</guid><description>&lt;p&gt;Microservice mimarisinde geliştirilen uygulamalarda domainler genellikle birbirlerinden sıkı bir şekilde ayrılmaya çalışılıyor, inceleyeceğimiz örnekte ürün ile ilgili tüm logic ve data product servisinde yer alırken, bu ürün ile sipariş oluşturabilecek satış danışmanlarını yöneten organization serviside kendi logic ve datasını barındırıyor.&lt;/p&gt;
&lt;p&gt;Ancak organization servisinde satış danışmanı oluşturmak için yapacağımız &lt;code&gt;createUnit()&lt;/code&gt; isteği validasyon sebepleri için product servisinin verisine ihtiyaç duyacaktır. Var olmayan bir ürün için oluşturma izni unit’e verilmemeli değil mi? Bu durumda organization servisi kendi domain’inde yer almayan ve kendi sorumluluğunda olmayan product servisinin verisine ihtiyaç duyacak.&lt;/p&gt;</description><content>&lt;p&gt;Microservice mimarisinde geliştirilen uygulamalarda domainler genellikle birbirlerinden sıkı bir şekilde ayrılmaya çalışılıyor, inceleyeceğimiz örnekte ürün ile ilgili tüm logic ve data product servisinde yer alırken, bu ürün ile sipariş oluşturabilecek satış danışmanlarını yöneten organization serviside kendi logic ve datasını barındırıyor.&lt;/p&gt;
&lt;p&gt;Ancak organization servisinde satış danışmanı oluşturmak için yapacağımız &lt;code&gt;createUnit()&lt;/code&gt; isteği validasyon sebepleri için product servisinin verisine ihtiyaç duyacaktır. Var olmayan bir ürün için oluşturma izni unit’e verilmemeli değil mi? Bu durumda organization servisi kendi domain’inde yer almayan ve kendi sorumluluğunda olmayan product servisinin verisine ihtiyaç duyacak.&lt;/p&gt;
&lt;p&gt;Bu veriye senkron veya asenkron olarak erişebiliriz. ilk olarak senkron olarak erişecek ve yaşayacağımız sorunları ele aldıktan sonra, asenkron methodu kullanıp yaşayabileceğimiz sorunları ele alacağız, hadi başlayalım.&lt;/p&gt;
&lt;h2 id="synchronous-communication"&gt;Synchronous communication&lt;/h2&gt;
&lt;p&gt;Organization servisimize rest ile göndereceğimiz istek ile, createUnit() methodunu tetikletecek ve unit oluşturacağız, ancak bu servis product verisine validasyon sebebiyle için ihtiyaç duyacağı için bu veriyi senkron olarak, HTTP REST yardımıyla yapacağı &lt;code&gt;getProduct()&lt;/code&gt; isteği ile getirmesi gerekecektir.&lt;/p&gt;
&lt;p&gt;runtime sırasında, createUnit methodunun işlenebilmesi için organization servisine ek olarak product servisininde ayakta ve erişilebilir olması, aradaki network bağlantısında bir sorun olmaması gerekiyor. Eğer product servisi çalışmıyorsa, organization serviside çalışmayacaktır. Her iki serviste ayakta ve erişilebilir olduğu durumlarda ise product servisi yavaş çalışıyorsa, senkron yapacağımız istek akışı bloklayacağı için, organization serviside yavaş çalışacaktır. Bu sorunu aşmak için organization servisinin yoğun istek aldığı durumlarda scale edilecektir. Ancak runtime bağımlılığı bulunan product servisininde organizationdan gelecek bol miktarda isteği karşılayabilmek için aynı şekilde scale etmemiz gerekecektir. Bu pratiği tüm servislere uyguladığımız senaryoda, tüm servislerin beraber scale edilmesi gerekecektir. Bu sebeple Organization servisi, Product servisine son derece bağımlı olup, yaşamlarına kendi başlarına değil, beraber devam etmek zorunda kalacaklardır. Eğer microservice mimarimizdeki tüm servislerimiz birbirlerine bu şekilde bağımlı olursa tek bir servisin erişilemez olması diğer tüm servislerin erişilemez olmasına yol açabilir.&lt;/p&gt;
&lt;p&gt;design time sırasında ise product servisinin getProduct API’ında yapılacak olan bir geliştirme sonrası Organization servisinin geliştirici takımıda aynı değişikliği kendi Product HTTP Client entegrasyonlarında yapmaları gerekecektir. Bu değişiklik büyük olasılıkla organization servisinin iş akışlarındada kod değişikliğe yol açacaktır.&lt;/p&gt;
&lt;p&gt;Sonuç olarak hem runtime’da hemde design time’da coupling ortaya çıkacağı için iki servis birbirine göbekten bağlı olacaktır, Jonathan Tower’ın tabiriyle &lt;a href="https://www.youtube.com/watch?v=p2GlRToY5HI&amp;amp;t=1203s"&gt;distributed monolith&lt;/a&gt; geliştirmiş olacağız. Bir servisimizde yapılacak olan değişiklik, diğer serviside direk olarak etkileyeceğinden ötürü, sürekli değişen iş gereksinimlerine hızlıca cevap veremeyeceğiz. Product servisinin getProduct() methodunda yapılan herhangi bir geliştirmeye istinaden, bu ucu kullanan diğer servislerinde aynı geliştirmeyi kendi servislerinde yapması gerekecektir. Geliştirmeyi bir şekilde tamamladıktan sonrada son kullanıcıların uygulamamızı kullanabilmesi için senkron olarak birbirlerine bağlı olan tüm servislerin erişilebilir olması gerekecektir.&lt;/p&gt;
&lt;img src="https://berkselvi.dev/img/do-you-need-other-services-data-in-microservice-architecture-event-driven-architecture/synchronous-communication.png" alt="Synchronous communication" loading="lazy" /&gt;
&lt;p class="image-sub-title"&gt;Synchronous communication&lt;/p&gt;
&lt;p&gt;Müşterimize bir SLA garantisi vermek istediğimiz senaryoda, iyimser olarak Product ve Organization servislerimiz için %99.5 uptime garantisi verecek olursak yıllık 43 saat downtime yaşanabileceğini kabul ediyoruz. product ve organization servislerinin runtime’da bağımlı olacağı senaryoda $0.995 * 0,995 = 0.990$ hesabıyla senkron iletişimle beraber çalışan servislerimiz ile yaşanan bağımlılık sonucunda, her bir servisimiz iki kat daha kötü uptime garanti ederek, yıllık 87 saat downtime yaşayabileceklerini görüyoruz.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Product&lt;/th&gt;
&lt;th&gt;Organization&lt;/th&gt;
&lt;th&gt;Product &amp;amp; Organization&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Uptime percentege&lt;/td&gt;
&lt;td&gt;99.5&lt;/td&gt;
&lt;td&gt;99.5&lt;/td&gt;
&lt;td&gt;99.0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Month&lt;/td&gt;
&lt;td&gt;716&lt;/td&gt;
&lt;td&gt;716&lt;/td&gt;
&lt;td&gt;712&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Downtime in month&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;7&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Year&lt;/td&gt;
&lt;td&gt;8716&lt;/td&gt;
&lt;td&gt;8716&lt;/td&gt;
&lt;td&gt;8672&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Downtime in year&lt;/td&gt;
&lt;td&gt;43&lt;/td&gt;
&lt;td&gt;43&lt;/td&gt;
&lt;td&gt;87&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Peki başımızı bu kadar ağrıtabileceğini ön gördüğümüz bu yöntemden nasıl kaçınacağız? Servisler arası iletişimdeki diğer çözüm önerimize bir bakalım.&lt;/p&gt;
&lt;h2 id="asynchronous-communication"&gt;Asynchronous communication&lt;/h2&gt;
&lt;p&gt;Senkron iletişim, servisler arası bağımlılığı mümkün olan her yönde arttırdığı için bu seçeneği tercih etmek istemezsek, diğer seçeneğimiz olan asenkron iletişimle devam edebiliriz. Asenkron yöntemle okları tersine çevirerek Product servisinin Product tablosunda bir güncelleme yaşanması durumunda ProductCreatedEvent isminde bir domain event publish ediyoruz. Bu eventi dinleyen tüm servisler, kendi veritabanlarında tuttukları products tablolarını güncelliyorlar.&lt;/p&gt;
&lt;p&gt;Organization servisi product verisine ihtiyaç duyduğunda kendi veritabanında yer alan products tablosundan okuma yapacaktır. Bu tablo organization’ın iş akışlarından etkilenmeden, sadece okuma yapılacaktır. Bu şekilde createUnit() methodumuzda ihtiyacımız olacak ürün bilgisini sorgulamak için unit verisini kaydedeceğimiz aynı veritabanından okuma yaparak ilgili veriye ulaşmış oluruz.&lt;/p&gt;
&lt;img src="https://berkselvi.dev/img/do-you-need-other-services-data-in-microservice-architecture-event-driven-architecture/asynchronous-communication.png" alt="Asynchronous communication" loading="lazy" /&gt;
&lt;p class="image-sub-title"&gt;Asynchronous communication&lt;/p&gt;
&lt;p&gt;Bu yöntemle createUnit() akışımızda product verisine organization’ın kendi veritabanından erişerek, Product servisimize runtime sırasındaki bağımlılığımızı ortadan kaldırıyoruz.&lt;/p&gt;
&lt;p&gt;Bu sayede, run time sırasındaki bağımlılık ortadan kalkıyor, çünkü product servisinin verdiği getProduct() ucunu senkron bir şekilde sorgulamayı bıraktık. design time sırasındada geliştirme yaparken yaşadığımız bağımlılıkta ortadan kalkmış oluyor. Çünkü Product servisinin getProduct() methodunda yaptığımız bir değişiklik sonrasında Organization servisine sahip olan takımın kendi servislerinde bir değişiklik yapması gerekmiyor.&lt;/p&gt;
&lt;p&gt;Design time sırasındada asenkron bir bağımlılığımız var, sadece ihtiyacımız olduğu durumlarda ilgili değişiklikleri yapmamız gerekiyor. Eğer Product servisinin product domain modelinde bir değişiklik yapılarak yeni bir kolon eklenirse, bu kolon direk olarak event’e yeni bir field olarak eklenebilir. Eğer product tablosunda bir kolon silindiyse, bu kolon, event tipinde silinmeden default değerleri ile gönderilmeye başlanıp, obsolete olarakta işaretlenebilir. Bu eklenip çıkartılan kolonlara diğer servisler diledikleri gibi cevap verebilir veya hiç bir değişiklik yapmadan kendi hayatlarına devam edebilirler.&lt;/p&gt;
&lt;p&gt;Publish edilen eventin, kimler tarafından işlendiği publish eden taraf olan Product servisini ilgilendirmez. Artık bu veriye ihtiyacı olan herkes bu eventi consume edip, kendi veritabanlarında product verisinin bir kopyasını tutarak, Product servisiyle senkron bir iletişime geçmeden kendi işlerini kendi başlarına halledebiliyorlar. Bu şekilde daha scale edilebilir ve daha erişilebilir uygulamalar ortaya çıkarmış oluyoruz, çünkü artık uygulamalarımızın farklı bir servise bağımlılığı bulunmadan kendi başlarına hareket edilebiliyorlar.&lt;/p&gt;
&lt;p&gt;Eğer tablolarımızın domain bazında karışacağını düşünüyorsak farklı domainlerden gelecek olan tabloları aşağıdaki örnekteki gibi farklı bir veritabanı şemasında tutabiliriz.&lt;/p&gt;
&lt;img src="https://berkselvi.dev/img/do-you-need-other-services-data-in-microservice-architecture-event-driven-architecture/asynchronous-communication-2.png" alt="Asynchronous communication" loading="lazy" /&gt;
&lt;p class="image-sub-title"&gt;Asynchronous communication 2&lt;/p&gt;
&lt;h2 id="eventual-consistency"&gt;Eventual consistency&lt;/h2&gt;
&lt;p&gt;Bu yöntemin karşımıza çıkartacağı en büyük sorun, eventual consistency yaratacak olmasıdır. Product servisine eklenen bir ürünün, organization servisindeki ilgili tabloya yansıması aynı t anında değil, aradaki message broker’ın erişilebilirlik durumuna göre, birkaç milisaniye veya saniye gecikme ile yaşanacaktır.&lt;/p&gt;
&lt;p&gt;Bu durumu CAP teorisiylede açıklayabiliriz. CAP teorisinin 3 bacağından biri olan Partition tolarence bacağını sabit tutarsak, Consistency ve Availability bacaklarından birisi artarken diğeri azalacaktır. Dolayısıyla bu üç seçeneği aynı anda sağlayamayacağız. Buradada çalıştığımız iş akışına göre karar vermemiz gerekiyor.&lt;/p&gt;
&lt;p&gt;Örneğin bir eticaret sisteminde sepete ekleme özelliğinin highly avaliable olması gerekebilir, çünkü sepete ekleme özelliği çalışmıyorsa, lütfen daha sonra tekrar deneyin gibi bir hata alıyorsak, o sitede değil rakip sitede alışverişe devam etmemiz olasıdır. Ancak sepete devam ettiğimizde istediğimiz üründen 1 değil 2 adet görürsek ilgili düzeltmemizi yapıp ödemeye devam edebiliriz.&lt;/p&gt;
&lt;p&gt;Fakat aynı durumu otel rezervasyonlarında uygulayamayabiliriz. Sepet örneğimizin tersine consistency muhtemelen daha önemli olacaktır. Aynı odayı birden çok kişiye kiralamak yerine, lütfen daha sonra tekrar deneyin gibi bir hata dönmek çok daha mantıklı olabilir.&lt;/p&gt;
&lt;p&gt;Sonuç olarak senkron iletişimin uygun olduğu immediate consistency isteyen akışlar için senkron iletişim, high avalability ihtiyacı olan akışlar için ise asenkron iletişimden faydalanabiliriz. Burada küçük bir yorum olarak microservice mimarisindeki akışlarda genellikle high avalability tercih edilmesi gerekiyor, çünkü diğer senkron iletişim yöntemiyle tüm servislerimiz göbekten birbirlerine bağımlı olacaklardır.&lt;/p&gt;
&lt;img src="https://berkselvi.dev/img/do-you-need-other-services-data-in-microservice-architecture-event-driven-architecture/high-avalability-vs-immidiate-consistency.png" alt="High avalability vs Immidiate consistency" loading="lazy" /&gt;
&lt;p class="image-sub-title"&gt;High avalability vs Immidiate consistency&lt;/p&gt;
&lt;h2 id="edge-cases-in-asynchronous-communication"&gt;Edge cases in asynchronous communication&lt;/h2&gt;
&lt;p&gt;Eğer servisleriniz arasındaki iletişimde, event driven architecture ile ilerlemeye karar verdiyseniz sistemdeki veri tutarlılığını sağlamak içinde bir şeyler yapmanız gerekecektir. Bir servisimize create isteği gelir ve CreatedEvent publish edersek, ilk önce veritabanını güncelleyip, sornasında event publish ettiğimiz durumda, eğer veritabanında yürüttüğümüz transaction hatayla sonuçlanır, ve bunu takip etmeden eventi publish edersek, veya veritabanında yürüttüğümüz transaction başarıyla sonuçlanır, ancak sonrasında event bus’a erişilemezse, uygulamalarımızda dağıtık olarak duran veri, insonsistent state’e düşebilir. Bu durumun önüne geçmek için product servisi kendi veritabanındaki product verisini güncellerken aynı zamanda aynı veritabanında yer alan outbox tablosunada ilgili eventi ekledikten sonra, farklı bir thread bu tabloyu devamlı olarak tarayarak yeni gelen eventleri publish eder. Veritabanına yaptığımız kayıt atomic olarak işleneceği için product verisi oluşturulurken event verisininde outbox tablosunda oluşturulacağı garanti edilir. Fakat birden çok instance olarak çalışan bir uygulamada bir eventin aynı outbox tablosundan 2 defa okuyup, 2 defa publish etmesi gibi durumlarla karşılaşılabilir. Bu durumda zaten idempotency sorununa çıkıyor.&lt;/p&gt;
&lt;p&gt;Bir metodun bir defa çağrıldığında alınan sonuç ile birden fazla kez çağrıldığında alınan sonuç aynı ise bu bir idempotent metottur. Bir eventin, farklı sebeplerden ötürü, birden çok kez işlenip sistemdeki veri tutarlılığını bozulmasını önlemek için, eventlerin benzersiz message id değerlerini consume edildikten sonra veritabanına kaydedip, yeni bir event consume edileceği zaman, message id değeri veritabanında yoksa işleme devam etmemiz gerekecektir. Chris Richardson (microservices.io) bu konuya Pattern: Idempotent Consumer isimli yazısındada değiniyor.&lt;/p&gt;
&lt;img src="https://berkselvi.dev/img/do-you-need-other-services-data-in-microservice-architecture-event-driven-architecture/without-outbox-idempotency.png" alt="Without Outbox &amp; Idempotency" loading="lazy" /&gt;
&lt;p class="image-sub-title"&gt;Without Outbox &amp; Idempotency&lt;/p&gt;
&lt;img src="https://berkselvi.dev/img/do-you-need-other-services-data-in-microservice-architecture-event-driven-architecture/outbox-idempotency.png" alt="Outbox &amp; Idempotency" loading="lazy" /&gt;
&lt;p class="image-sub-title"&gt;Outbox &amp; Idempotency&lt;/p&gt;
&lt;p&gt;Sonuç olarak Computer Science alanındaki çoğu problemle sunulan çözüm önerilerinde olduğu gibi elimizde bir silver bullet bulunmuyor. Her iki yönteminde avantajları ve dezavantajlarını kapsamlı bir şekilde, çalıştığımız domain’inde aklımızda bulundurup değerlendirdikten sonra dilediğimiz yöntemle devam edebiliriz. Ancak benim önerim, servisler arasındaki bağımlılığı mümkün olduğu durumlarda asenkron iletişim kullanarak ortadan kaldırmak olacaktır.&lt;/p&gt;
&lt;p&gt;Asenkron ve senkron veri iletişimiyle çalışırken yaşanbilecek durumları simule etmek için geliştirdiğim .NET projesine aşağıdaki github reposundan ulaşabilirsiniz. Örnekte outbox yapısı için MassTransit tarafından sağlanan altyapı kullanılarak bu özellik için özel bir geliştirme yapılmamıştır.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://github.com/berkslv/lecture-fetch-other-service-data-with-event-driven-architecture"&gt;GitHub - berkslv/lecture-fetch-other-service-data-with-event-driven-architecture&lt;/a&gt;&lt;/p&gt;
&lt;h2 id="resources"&gt;Resources&lt;/h2&gt;
&lt;p&gt;&lt;a href="https://www.youtube.com/watch?v=p2GlRToY5HI&amp;amp;t=1203s"&gt;Don’t Build a Distributed Monolith - Jonathan &amp;ldquo;J.&amp;rdquo; Tower - NDC London 2023&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="https://www.youtube.com/watch?v=STKCRSUsyP0"&gt;The Many Meanings of Event-Driven Architecture • Martin Fowler • GOTO 2017&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="https://www.youtube.com/watch?v=AEbJgpamZ4w&amp;amp;t=898s"&gt;Solving distributed data problems in a microservice architecture | Microservices.io&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="https://www.youtube.com/watch?v=rZxIzrjvSGg&amp;amp;t=716s"&gt;You Keep Using That Word • Sam Newman • GOTO 2023&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="https://microservices.io/patterns/communication-style/idempotent-consumer.html"&gt;Microservices Pattern: Pattern: Idempotent Consumer&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="https://www.reactivemanifesto.org/en"&gt;The Reactive Manifesto&lt;/a&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="sonuç"&gt;Sonuç&lt;/h2&gt;
&lt;p&gt;Okuduğunuz için teşekkürler! 🎉 Yazılım geliştirme alanındaki araştırmalarımı kaçırmamak için &lt;a href="https://x.com/berkslv"&gt;@berkslv&lt;/a&gt; adresinden takipte kalabilirsiniz.&lt;/p&gt;</content></item><item><title>HTTP ve MassTransit ile .NET Mikroservislerinde CorrelationId Takibi</title><link>https://berkselvi.dev/tr/posts/correlationid-tracing-net-microservices-with-http-and-rabbitmq/</link><pubDate>Sun, 14 Jul 2024 00:00:00 +0300</pubDate><guid>https://berkselvi.dev/tr/posts/correlationid-tracing-net-microservices-with-http-and-rabbitmq/</guid><description>&lt;p&gt;Microservice mimarisinde dağıtık olarak çalışan uygulamaların kendi aralarında yaptıkları iletişimler sırasında devam eden işlemin takip edilebilirliği ve eğer bir serviste performans sebepli veya akışsal bir sorun varsa hangi adımlardan sonra buraya geldiğini anlamlandırabilmek için Distributed tracing başlığı altında CorrelationId çok önemli bir yere sahiptir.&lt;/p&gt;
&lt;p&gt;CorrelationId, bir isteğin tüm yaşam döngüsü boyunca taşınarak, farklı servisler arasındaki bağıntıyı sağlamalıdır. Bu yazıda, .NET tabanlı bir microservice mimarisinde HTTP ve RabbitMQ istekleri kullanarak CorrelationId değerinin nasıl gezdirilebileceğini inceleyeceğiz. Bu çözüm sorunumuza net bir şekilde cevap olmak için tasarlandı ancak her türlü yoruma ve geliştirmeye açıktır, eğer aklınızda daha iyi bir çözüm gelirse lütfen bana ulaşın.&lt;/p&gt;</description><content>&lt;p&gt;Microservice mimarisinde dağıtık olarak çalışan uygulamaların kendi aralarında yaptıkları iletişimler sırasında devam eden işlemin takip edilebilirliği ve eğer bir serviste performans sebepli veya akışsal bir sorun varsa hangi adımlardan sonra buraya geldiğini anlamlandırabilmek için Distributed tracing başlığı altında CorrelationId çok önemli bir yere sahiptir.&lt;/p&gt;
&lt;p&gt;CorrelationId, bir isteğin tüm yaşam döngüsü boyunca taşınarak, farklı servisler arasındaki bağıntıyı sağlamalıdır. Bu yazıda, .NET tabanlı bir microservice mimarisinde HTTP ve RabbitMQ istekleri kullanarak CorrelationId değerinin nasıl gezdirilebileceğini inceleyeceğiz. Bu çözüm sorunumuza net bir şekilde cevap olmak için tasarlandı ancak her türlü yoruma ve geliştirmeye açıktır, eğer aklınızda daha iyi bir çözüm gelirse lütfen bana ulaşın.&lt;/p&gt;
&lt;img src="https://berkselvi.dev/img/correlationid-tracing-net-microservices-with-http-and-rabbitmq/big-picture.png" alt="Big picture" loading="lazy" /&gt;
&lt;p class="image-sub-title"&gt;Big picture&lt;/p&gt;
&lt;p&gt;CorrelationId değerini HTTP ve MassTransit kullanarak servisler arasındaki iletişim sırasında Middleware ve Filter yapılarıyla gelen ve giden isteklerde araya girerek header değerleri üzerinde manipulasyonda bulunarak CorrelationId değerini servisler arasında gezdireceğiz. Bu gezdirme sırasındada gelen isteklerle başlatılan contextlerin, verilen CorrelationId değeri ile loglanması için Serilog tarafından sağlanan LogContext sınıfından faydalanacağız. Tüm örneklere aşağıdaki repodan ulaşabilirsiniz.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://github.com/berkslv/lecture-correlation-id-microservices"&gt;GitHub - berkslv/lecture-correlation-id-microservices&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Serivisimize gelen isteklerde aldığımız CorrelationId değerini, giden isteklerde göndermek için Scoped olarak tanımlayabileceğimiz Correlation sınıfımızdan yararlanabiliriz. Ancak bu şekilde Dependency Injection yöntemleri ile sınıflar arasında gezdirdiğimiz state değerimiz async olarak ilerleyen isteğimizde özel bir durumda erişilemez oluyor: DelegatingHandler sınıfı kullanarak HttpClient ile yapacağımız isteklerde araya girecek olan &lt;code&gt;CorrelationHeaderHandler&lt;/code&gt; sınıfımız uygulamamızdan ayrı bir DI scope içerisinde çalışacağı için Scoped olarak tanımlayabileceğimiz Correlation sınıfımızın değerine bu özel durumda erişemeyeceğiz. Bu konu microsoft&amp;rsquo;un kendi dökümantasyonundada şöyle aktarılıyor&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;When IHttpClientFactory creates a new delegating handler, it uses DI to fulfill the handler&amp;rsquo;s constructor parameters. IHttpClientFactory creates a separate DI scope for each handler, which can lead to surprising behavior when a handler consumes a scoped service.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Bu soruna çözüm olarak &lt;code&gt;AsyncStorage&lt;/code&gt; isminde bir sınıf oluşturup Microsoft tarafından sağlanan &lt;code&gt;AsyncLocal&lt;/code&gt; sınıfını kullanarak CorrelationId değerini async olarak ilerleyen isteğimizde aynı thread içerisinde dilediğimiz zaman erişebileceğimiz bir yapı oluşturacağız:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-csharp" data-lang="csharp"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;namespace&lt;/span&gt; Order.API.Filters.Correlation;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;/// &amp;lt;summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;/// Stores and retrieves values in an async context.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;/// &amp;lt;/summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;/// &amp;lt;typeparam name=&amp;#34;T&amp;#34;&amp;gt;What should be stored&amp;lt;/typeparam&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;static&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;class&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;AsyncStorage&lt;/span&gt;&amp;lt;T&amp;gt; &lt;span style="color:#66d9ef"&gt;where&lt;/span&gt; T : &lt;span style="color:#66d9ef"&gt;new&lt;/span&gt;()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;private&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;static&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;readonly&lt;/span&gt; AsyncLocal&amp;lt;T&amp;gt; _asyncLocal = &lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; AsyncLocal&amp;lt;T&amp;gt;();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;static&lt;/span&gt; T Store(T val)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; _asyncLocal.Value = val;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; _asyncLocal.Value;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;static&lt;/span&gt; T? Retrieve()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; _asyncLocal.Value;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;AsyncStorage sınıfına type olarak vereceğimiz Correlation sınıfını, aşağıdaki gibi tanımlayarak Id property&amp;rsquo;sinde CorrelationId değerini barındıracağız.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-csharp" data-lang="csharp"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;namespace&lt;/span&gt; Order.API.Filters.Correlation;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;/// &amp;lt;summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;/// It holds the CorrelationId value that comes with HTTP requests and events handled via MassTransit.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;/// &amp;lt;/summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;class&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Correlation&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; Guid Id { &lt;span style="color:#66d9ef"&gt;get&lt;/span&gt;; &lt;span style="color:#66d9ef"&gt;init&lt;/span&gt;; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;CorrelationId değerini yapılan istek boyunca erişebileceğimiz bir konumda sakladığımıza göre, şimdi gelen isteklerde araya girerek header&amp;rsquo;lardan CorrelationId değerini çekelim. Microservice mimarisinde geliştirilen uygulamalarda servisler arasında sadece HTTP değil, aynı zamanda event tabanlı yöntemlerle iletişim kurulabiliyor. Ancak bu şekilde başlatılan isteklerde, CorrelationId değerini HTTP header&amp;rsquo;larında değil, MassTransit tarafından sağlanan header değerlerinde taşıyacağız.&lt;/p&gt;
&lt;img src="https://berkselvi.dev/img/correlationid-tracing-net-microservices-with-http-and-rabbitmq/filters-for-correlationid.png" alt="Filters for CorrelationId" loading="lazy" /&gt;
&lt;p class="image-sub-title"&gt;Filters for CorrelationId&lt;/p&gt;
&lt;h1 id="correlationmiddleware"&gt;CorrelationMiddleware&lt;/h1&gt;
&lt;p&gt;Gelen HTTP isteklerinde araya girecek ve CorrelationId HTTP header&amp;rsquo;ından değeri alacak olan CorrelationMiddleware sınıfımızı aşağıdaki gibi tanımlıyoruz. Eğer headerda değer varsa ilk önce Serilog tarafından sağlanan &lt;code&gt;LogContext&lt;/code&gt; sınıfına bu değeri ayrı bir field olarak koyarak bir nevi enrich işlemi yapıyoruz. Daha sonra &lt;code&gt;AsyncStorage&lt;/code&gt; sınıfımıza Correlation sınıfını vererek CorrelationId değerini saklıyoruz. Bu sayede gelen isteklerde CorrelationId değerini alıp, giden isteklerde bu değeri kullanabileceğiz.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-csharp" data-lang="csharp"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;namespace&lt;/span&gt; Order.API.Filters.Correlation;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;/// &amp;lt;summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;/// When the Http request is made, it takes the CorrelationId value from the HttpContext Header and sets the Correlation.Id value.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;/// &amp;lt;/summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;class&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;CorrelationMiddleware&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;private&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;readonly&lt;/span&gt; RequestDelegate _next;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; CorrelationMiddleware(RequestDelegate next)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; _next = next;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;async&lt;/span&gt; Task InvokeAsync(HttpContext context, Correlation correlation)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; correlationIdHeader = context.Request.Headers[&lt;span style="color:#e6db74"&gt;&amp;#34;CorrelationId&amp;#34;&lt;/span&gt;];
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; (!&lt;span style="color:#66d9ef"&gt;string&lt;/span&gt;.IsNullOrWhiteSpace(correlationIdHeader))
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; correlationId = Guid.Parse(correlationIdHeader.ToString());
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; LogContext.PushProperty(&lt;span style="color:#e6db74"&gt;&amp;#34;CorrelationId&amp;#34;&lt;/span&gt;, &lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; ScalarValue(correlationId));
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; AsyncStorage&amp;lt;Correlation&amp;gt;.Store(&lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; Correlation
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Id = correlationId
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; });
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;await&lt;/span&gt; _next(context);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Bu middleware&amp;rsquo;i uygulamamıza eklemek için Progam.cs içerisinde aşağıdaki gibi düzenliyoruz.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-csharp" data-lang="csharp"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; builder = WebApplication.CreateBuilder(args);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;//...&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; app = builder.Build();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;app.UseMiddleware&amp;lt;CorrelationMiddleware&amp;gt;();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;//...&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;app.Run();
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h1 id="correlationconsumefilter"&gt;CorrelationConsumeFilter&lt;/h1&gt;
&lt;p&gt;Eğer servisimize gelen istek HTTP değil, event ile tetiklenen bir istek ise ortada bir HTTP headerı olmayacaktır. Bu sebeple MassTransit tarafından sağlanan header değerlerinden CorrelationId değerini almak için CorrelationConsumeFilter sınıfımızı aşağıdaki gibi tanımlıyoruz. Bu sınıfımızda IConsumer ile imzalanan sınıflarda consume işlemi gerçekleştirilmeden önce, gelen eventlerde araya girerek CorrelationId değerini alıp, &lt;code&gt;LogContext&lt;/code&gt; ve &lt;code&gt;AsyncStorage&amp;lt;Correlation&amp;gt;&lt;/code&gt; içerisine ekliyoruz.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-csharp" data-lang="csharp"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;namespace&lt;/span&gt; Order.API.Filters.Correlation;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;/// &amp;lt;summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;/// It is triggered when there is an event consumed by MassTransit and sets the CorrelationId value to the Correlation class.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;/// &amp;lt;/summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;class&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;CorrelationConsumeFilter&lt;/span&gt;&amp;lt;T&amp;gt; : IFilter&amp;lt;ConsumeContext&amp;lt;T&amp;gt;&amp;gt; &lt;span style="color:#66d9ef"&gt;where&lt;/span&gt; T : &lt;span style="color:#66d9ef"&gt;class&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; Task Send(ConsumeContext&amp;lt;T&amp;gt; context, IPipe&amp;lt;ConsumeContext&amp;lt;T&amp;gt;&amp;gt; next)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; correlationIdHeader = context.CorrelationId;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; (correlationIdHeader.HasValue)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; correlationId = correlationIdHeader.Value;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; LogContext.PushProperty(&lt;span style="color:#e6db74"&gt;&amp;#34;CorrelationId&amp;#34;&lt;/span&gt;, &lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; ScalarValue(correlationId));
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; AsyncStorage&amp;lt;Correlation&amp;gt;.Store(&lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; Correlation
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Id = correlationId
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; });
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; next.Send(context);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;void&lt;/span&gt; Probe(ProbeContext context)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Bu şekilde gelen isteklerde &lt;code&gt;AsyncStorage&amp;lt;Correlation&amp;gt;&lt;/code&gt; içerisinde tuttuğumuz Correlation değerimizi, yapılacak olan event isteklerinde ilgili header alanlarına eklemek için neler yapabileceğimizi inceleyelim.&lt;/p&gt;
&lt;h1 id="correlationpublishfilter"&gt;CorrelationPublishFilter&lt;/h1&gt;
&lt;p&gt;MassTransit tarafından sağlanan &lt;code&gt;IPublishEndpoint&lt;/code&gt; ile publish edilen eventlerde araya girecek olan &lt;code&gt;CorrelationPublishFilter&lt;/code&gt; sınıfımızı aşağıdaki gibi tanımlıyoruz. Bu sınıfımız, publish edilen eventlerde araya girerek &lt;code&gt;AsyncStorage&amp;lt;Correlation&amp;gt;&lt;/code&gt; içerisinden CorrelationId değerini alıp, MassTransit&amp;rsquo;in CorrelationId header değerine ekleyecektir.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-csharp" data-lang="csharp"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;namespace&lt;/span&gt; Order.API.Filters.Correlation;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;/// &amp;lt;summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;/// Sets the CorrelationId value of events published via MassTransit.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;/// &amp;lt;/summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;class&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;CorrelationPublishFilter&lt;/span&gt;&amp;lt;T&amp;gt; : IFilter&amp;lt;PublishContext&amp;lt;T&amp;gt;&amp;gt; &lt;span style="color:#66d9ef"&gt;where&lt;/span&gt; T : &lt;span style="color:#66d9ef"&gt;class&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; Task Send(PublishContext&amp;lt;T&amp;gt; context, IPipe&amp;lt;PublishContext&amp;lt;T&amp;gt;&amp;gt; next)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; correlation = AsyncStorage&amp;lt;Correlation&amp;gt;.Retrieve();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; (correlation &lt;span style="color:#66d9ef"&gt;is&lt;/span&gt; not &lt;span style="color:#66d9ef"&gt;null&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; context.CorrelationId = Guid.Parse(correlation.Id.ToString()!);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; next.Send(context);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;void&lt;/span&gt; Probe(ProbeContext context)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h1 id="correlationsendfilter"&gt;CorrelationSendFilter&lt;/h1&gt;
&lt;p&gt;Eğer MassTransit&amp;rsquo;e gönderilen event publish edilmemiş ve request/response yapısı ile &lt;code&gt;IRequestClient&amp;lt;T&amp;gt;&lt;/code&gt; kullanılarak gönderildiyse &lt;code&gt;CorrelationPublishFilter&lt;/code&gt; değil &lt;code&gt;CorrelationSendFilter&lt;/code&gt; sınıfımız araya girecek. Bu sınıfımızda send edilen eventlerde araya girerek &lt;code&gt;AsyncStorage&amp;lt;Correlation&amp;gt;&lt;/code&gt; içersinden CorrelationId değerini alıp, MassTransit&amp;rsquo;in CorrelationId header değerininin içerisine ekliyoruz.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-csharp" data-lang="csharp"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;namespace&lt;/span&gt; Order.API.Filters.Correlation;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;/// &amp;lt;summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;/// Sets the CorrelationId value of events sent via MassTransit.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;/// &amp;lt;/summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;class&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;CorrelationSendFilter&lt;/span&gt;&amp;lt;T&amp;gt; : IFilter&amp;lt;SendContext&amp;lt;T&amp;gt;&amp;gt; &lt;span style="color:#66d9ef"&gt;where&lt;/span&gt; T : &lt;span style="color:#66d9ef"&gt;class&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; Task Send(SendContext&amp;lt;T&amp;gt; context, IPipe&amp;lt;SendContext&amp;lt;T&amp;gt;&amp;gt; next)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; correlation = AsyncStorage&amp;lt;Correlation&amp;gt;.Retrieve();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; (correlation &lt;span style="color:#66d9ef"&gt;is&lt;/span&gt; not &lt;span style="color:#66d9ef"&gt;null&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; context.CorrelationId = Guid.Parse(correlation.Id.ToString());
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; next.Send(context);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;void&lt;/span&gt; Probe(ProbeContext context)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;CorrelationConsumeFilter&lt;/code&gt;, &lt;code&gt;CorrelationPublishFilter&lt;/code&gt; ve &lt;code&gt;CorrelationSendFilter&lt;/code&gt; sınıflarımızı uygulamamıza eklemek için aşağıdaki gibi ConfigureServices sınıfımızı tanımlayıp bu methodu Program.cs içerisinde kullanıyoruz.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-csharp" data-lang="csharp"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;static&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;class&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;ConfigureServices&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;static&lt;/span&gt; WebApplicationBuilder AddMassTransit(&lt;span style="color:#66d9ef"&gt;this&lt;/span&gt; WebApplicationBuilder builder, IConfiguration configuration)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; messageBroker = builder.Configuration.GetSection(&lt;span style="color:#e6db74"&gt;&amp;#34;MessageBroker&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; builder.Services.AddMassTransit(cfg =&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; cfg.SetKebabCaseEndpointNameFormatter();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; cfg.AddConsumers(Assembly.GetExecutingAssembly());
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; cfg.UsingRabbitMq((context, config) =&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; config.UseSendFilter(&lt;span style="color:#66d9ef"&gt;typeof&lt;/span&gt;(CorrelationSendFilter&amp;lt;&amp;gt;), context);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; config.UsePublishFilter(&lt;span style="color:#66d9ef"&gt;typeof&lt;/span&gt;(CorrelationPublishFilter&amp;lt;&amp;gt;), context);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; config.UseConsumeFilter(&lt;span style="color:#66d9ef"&gt;typeof&lt;/span&gt;(CorrelationConsumeFilter&amp;lt;&amp;gt;), context);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; config.Host(messageBroker[&lt;span style="color:#e6db74"&gt;&amp;#34;Host&amp;#34;&lt;/span&gt;], messageBroker[&lt;span style="color:#e6db74"&gt;&amp;#34;VirtualHost&amp;#34;&lt;/span&gt;], h =&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; h.Username(messageBroker[&lt;span style="color:#e6db74"&gt;&amp;#34;Username&amp;#34;&lt;/span&gt;]!);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; h.Password(messageBroker[&lt;span style="color:#e6db74"&gt;&amp;#34;Password&amp;#34;&lt;/span&gt;]!);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; });
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; config.ConfigureEndpoints(context);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; });
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; });
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; builder;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-csharp" data-lang="csharp"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; builder = WebApplication.CreateBuilder(args);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;builder.AddMassTransit(builder.Configuration);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// ...&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; app = builder.Build();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// ...&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;app.Run();
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h1 id="correlationheaderhandler"&gt;CorrelationHeaderHandler&lt;/h1&gt;
&lt;p&gt;Eğer servisimizden yapılan istek event değil HTTP isteği ise &lt;code&gt;IHttpClientFactory&lt;/code&gt; ile oluşturacağımız HttpClient sınıfımızdan yapacağımız isteklerde araya girecek olan &lt;code&gt;CorrelationHeaderHandler&lt;/code&gt; sınıfımızı aşağıdaki gibi tanımlıyoruz. Bu sınıfımızda uygulamamızdan yapılan HTTP isteklerinde araya girerek &lt;code&gt;AsyncStorage&amp;lt;Correlation&amp;gt;&lt;/code&gt; içersinden CorrelationId değerini alıp, HTTP header olarak ekliyoruz.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-csharp" data-lang="csharp"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;/// &amp;lt;summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;/// Middleware to be used in requests made with HttpClient. Adds the CorrelationId header to the requests made.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;/// &amp;lt;/summary&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;class&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;CorrelationHeaderHandler&lt;/span&gt; : DelegatingHandler
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;protected&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;override&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;async&lt;/span&gt; Task&amp;lt;HttpResponseMessage&amp;gt; SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; correlation = AsyncStorage&amp;lt;Correlation&amp;gt;.Retrieve();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; (correlation &lt;span style="color:#66d9ef"&gt;is&lt;/span&gt; not &lt;span style="color:#66d9ef"&gt;null&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; request.Headers.Add(&lt;span style="color:#e6db74"&gt;&amp;#34;CorrelationId&amp;#34;&lt;/span&gt;, correlation.Id.ToString());
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;await&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;base&lt;/span&gt;.SendAsync(request, cancellationToken);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Bu middleware&amp;rsquo;i uygulamamıza eklemek için Progam.cs içerisinde aşağıdaki gibi Named Http Client tanımlayıp &lt;code&gt;AddHttpMessageHandler&lt;/code&gt; ile yapılacak isteklerde araya girecek olan middleware&amp;rsquo;i ekliyoruz.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-csharp" data-lang="csharp"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; builder = WebApplication.CreateBuilder(args);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;builder.Services.AddTransient&amp;lt;CorrelationHeaderHandler&amp;gt;();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;builder.Services.AddHttpClient(&lt;span style="color:#e6db74"&gt;&amp;#34;Inventory&amp;#34;&lt;/span&gt;, c =&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; c.BaseAddress = &lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; Uri(&lt;span style="color:#e6db74"&gt;&amp;#34;http://localhost:5053&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; })
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; .AddHttpMessageHandler&amp;lt;CorrelationHeaderHandler&amp;gt;();;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// ...&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; app = builder.Build();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// ...&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;app.Run();
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Oluşturduğumuz Named Http Client için kullanımı aşağıdaki gibi yapıyoruz. Burada yapacağımız isteklerin async olmazsa middleware araya giremiyor.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-csharp" data-lang="csharp"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;class&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;InventoryService&lt;/span&gt; : IInventoryService
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;private&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;readonly&lt;/span&gt; IHttpClientFactory _httpClientFactory;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; InventoryService(IHttpClientFactory httpClientFactory)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; _httpClientFactory = httpClientFactory;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;async&lt;/span&gt; Task RemoveStockAsync(&lt;span style="color:#66d9ef"&gt;string&lt;/span&gt; productId, &lt;span style="color:#66d9ef"&gt;int&lt;/span&gt; quantity)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; httpClient = _httpClientFactory.CreateClient(&lt;span style="color:#e6db74"&gt;&amp;#34;Inventory&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; response = &lt;span style="color:#66d9ef"&gt;await&lt;/span&gt; httpClient.PostAsync(&lt;span style="color:#e6db74"&gt;$&amp;#34;remove-stock/{productId}/{quantity}&amp;#34;&lt;/span&gt;, &lt;span style="color:#66d9ef"&gt;null&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; response.EnsureSuccessStatusCode();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Bu makalede, HTTP ve RabbitMQ kullanarak .NET microservice mimarisinde dağıtık izlenebilirliği artırmak için CorrelationId&amp;rsquo;yi nasıl etkili bir şekilde kullanabileceğimizi inceledik. Middleware ve Filter yapılarından yararlanarak, farklı servisler arasında CorrelationId&amp;rsquo;yi yayarak header değerlerini manipüle edebildik ve Serilog&amp;rsquo;un LogContext framework&amp;rsquo;ü ile kapsamlı loglama ve izleme sağladık.&lt;/p&gt;
&lt;p&gt;CorrelationId&amp;rsquo;yi hem HTTP isteklerinde hem de MassTransit tarafından sağlanan event tabanlı iletişimde nasıl ele alacağımızı gösterdik. HTTP için &lt;code&gt;CorrelationMiddleware&lt;/code&gt; ve &lt;code&gt;CorrelationHeaderHandler&lt;/code&gt; sınıflarını; event tabanlı iletişim için ise &lt;code&gt;CorrelationConsumeFilter&lt;/code&gt;, &lt;code&gt;CorrelationPublishFilter&lt;/code&gt; ve &lt;code&gt;CorrelationSendFilter&lt;/code&gt; sınıflarını uygulayarak, CorrelationId&amp;rsquo;nin bir isteğin tüm yaşam döngüsü boyunca tutarlı bir şekilde taşınmasını sağladık. Bu, işlem akışının net bir şekilde izlenmesini sağladı ve performans veya akış sorunlarını belirlemeye yardımcı oldu.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;AsyncLocal&lt;/code&gt; sınıfıyla birlikte &lt;code&gt;AsyncStorage&amp;lt;Correlation&amp;gt;&lt;/code&gt; kullanımı, CorrelationId değerini farklı kapsamlar arasında saklamak ve almak için güvenilir bir yol sağladı ve Dependency Injection ve asenkron işlemeyle ilgili zorlukları ele aldı.&lt;/p&gt;
&lt;p&gt;Umarım bu rehber, kendi microservice mimarinizde sağlam bir dağıtık izleme çözümü uygulamanıza yardımcı olur. Daha detaylı örnekler için, GitHub reposunu inceleyebilirsiniz:&lt;/p&gt;
&lt;p&gt;&lt;a href="https://github.com/berkslv/lecture-correlation-id-microservices"&gt;GitHub - berkslv/lecture-correlation-id-microservices&lt;/a&gt;&lt;/p&gt;
&lt;h1 id="kaynaklar"&gt;Kaynaklar&lt;/h1&gt;
&lt;p&gt;AsyncLocal sınıfnın doğru kullanımı için yararlandığım kaynak:&lt;/p&gt;
&lt;p&gt;&lt;a href="https://medium.com/@mbearz/how-to-log-everything-using-middleware-and-httpclient-handler-42b8f628fe84"&gt;https://medium.com/@mbearz/how-to-log-everything-using-middleware-and-httpclient-handler-42b8f628fe84&lt;/a&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="sonuç"&gt;Sonuç&lt;/h2&gt;
&lt;p&gt;Okuduğunuz için teşekkürler! 🎉 Yazılım geliştirme alanındaki araştırmalarımı kaçırmamak için &lt;a href="https://x.com/berkslv"&gt;@berkslv&lt;/a&gt; adresinden takipte kalabilirsiniz.&lt;/p&gt;</content></item><item><title>.NET Geliştiricisi Perspektifinden API Gateway Alternatifleri: YARP, Ocelot, Kong, APISIX ve KrakenD</title><link>https://berkselvi.dev/tr/posts/api-gateway-alternatives-from-a-net-developer-standpoint/</link><pubDate>Mon, 20 May 2024 00:00:00 +0300</pubDate><guid>https://berkselvi.dev/tr/posts/api-gateway-alternatives-from-a-net-developer-standpoint/</guid><description>&lt;p&gt;API Gateway servislerini microservice mimarisinde servislerimizi dış dünyaya açarken bir ara katman olarak kullanabiliyoruz. Bu yazımızda kısaca API Gateway nedirden bahsedip sonrasında kullanabileceğimiz .NET, OpenResty ve Go tabanlı API Gateway alternatiflerinden performans testleri ile beraber bahsedip sonlandıracağız.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://github.com/berkslv/lecture-api-gateway-comparison"&gt;GitHub - berkslv/lecture-api-gateway-comparison&lt;/a&gt;&lt;/p&gt;
&lt;h2 id="api-gateway-nedir"&gt;API Gateway nedir?&lt;/h2&gt;
&lt;p&gt;API Gateway, sistemlerimize aşağıdaki gibi bir giriş noktası sağlarak dış dünyadan izole eder. İçerisinde her bir alt serviste tekrar tekrar implemente edilmesi gereken yetkilendirme, loglama, Rate limiting gibi ortak sorunları tek bir servis ile çözer.&lt;/p&gt;</description><content>&lt;p&gt;API Gateway servislerini microservice mimarisinde servislerimizi dış dünyaya açarken bir ara katman olarak kullanabiliyoruz. Bu yazımızda kısaca API Gateway nedirden bahsedip sonrasında kullanabileceğimiz .NET, OpenResty ve Go tabanlı API Gateway alternatiflerinden performans testleri ile beraber bahsedip sonlandıracağız.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://github.com/berkslv/lecture-api-gateway-comparison"&gt;GitHub - berkslv/lecture-api-gateway-comparison&lt;/a&gt;&lt;/p&gt;
&lt;h2 id="api-gateway-nedir"&gt;API Gateway nedir?&lt;/h2&gt;
&lt;p&gt;API Gateway, sistemlerimize aşağıdaki gibi bir giriş noktası sağlarak dış dünyadan izole eder. İçerisinde her bir alt serviste tekrar tekrar implemente edilmesi gereken yetkilendirme, loglama, Rate limiting gibi ortak sorunları tek bir servis ile çözer.&lt;/p&gt;
&lt;p&gt;İstekleri yönlendirirken url ve body bilgilerinde hatta protokollerinde dönüşümler uygulayabilirler, http bir isteği alt servislere daha performanslı olması için gRPC protokolü ile iletebilirler. Gelen istekleri yönlendirecekleri servislerin adresini bulmak için flat file veya veritabanlarından yararlanabilirler.&lt;/p&gt;
&lt;img src="https://berkselvi.dev/img/api-gateway-alternatives-from-a-net-developer-standpoint/what-is-api-gateway.png" alt="What is API Gateway" loading="lazy" /&gt;
&lt;br/&gt;
&lt;h2 id="seçenekler-nelerdir"&gt;Seçenekler nelerdir&lt;/h2&gt;
&lt;p&gt;Sistemlerimizde kullanabileceğimiz API Gateway alternatiflerini değerlendirirken, gereken özelleştirme miktarı, performans, dağıtık mimariye uygunluk ve admin paneli üzerinden route bilgilerini güncelleme gibi ihtiyaçları değerlendirip uygun kararı vermemiz gerekir. Tüm bu özellikleri içerisinde barındıran, tüm ihtiyaçlarımızı tek seferde çözen bir gateway ürünü maalesef yok, bu değerlendirmeleri sistemimizin özel ihtiyaçlarına göre yapmamız gerekiyor.&lt;/p&gt;
&lt;p&gt;İlk olarak .NET tabanlı, C# ile özelleştirilebilen YARP ve Ocelot API gateway ürünlerini inceleyip, OpenResty yani ngnix ve lua ikilisini beraber kullanan Kong ve Apache APISIX’den bahsettikten sonra son olarak cloud native ortamdada sıklıkla kullanılan Go ile geliştirilmiş KrakenD’yi inceleyeceğiz. Tüm bu seçenekler open source olarak ücretsiz bir şekilde kullanılabilir, ancak Kong, Apache APISIX ve KrakenD seçeneklerinde gerektiğinde enterprise desteğide alabiliriz.&lt;/p&gt;
&lt;p&gt;Seçeneklerimizi değerlendirmeye geçmeden önce 1 saniye bekleyerek veritabanı sorgularını simule eden çok basit bir .NET API servisi geliştirdim, bu servisi API Gateway’in arkasına aldıktan sonra isteklerimizi yönlendirmeye çalışacak ve basit bir performans aracı olan Apache Benchmark kullanarak performans testlerimizi yapacağız. Kaynak tüketimini daha yakından inceleyebilmek için bu uygulamayı diğer gateway’lerle beraber docker compose ile çalıştırıp service:8080 adresinden isteklerimize cevap alacağız.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-csharp" data-lang="csharp"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// Program.cs&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; builder = WebApplication.CreateBuilder(args);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;builder.Services.AddEndpointsApiExplorer();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;builder.Services.AddSwaggerGen();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; app = builder.Build();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; (app.Environment.IsDevelopment())
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; app.UseSwagger();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; app.UseSwaggerUI();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;app.MapGet(&lt;span style="color:#e6db74"&gt;&amp;#34;/weather&amp;#34;&lt;/span&gt;, () =&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Task.Delay(&lt;span style="color:#ae81ff"&gt;1000&lt;/span&gt;).Wait();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;Weather is OK!&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;})
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;.WithName(&lt;span style="color:#e6db74"&gt;&amp;#34;GetWeather&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;.WithOpenApi();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;app.Run();
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="net-based"&gt;.NET Based&lt;/h2&gt;
&lt;p&gt;.NET developerları olarak genellikle aklımıza ilk gelen çözüm yine .NET teknolojileri ile geliştirilebilen YARP ve Ocelot API Gateway’den bahsederek seçeneklerimizi incelemeye başlayabiliriz. Bu seçenekler performans olarak çok basit senaryolarda diğer rakipleriyle yarışabilirken, kaynak tüketimi ve admin dashboard gibi kompleks ihtiyaçlara cevap veremiyorlar.&lt;/p&gt;
&lt;h2 id="yarp"&gt;YARP&lt;/h2&gt;
&lt;p&gt;Microsoft&amp;rsquo;un geliştirdiği YARP (Yet Another Reverse Proxy), bir reverse proxy ürünü olarak öne çıkıyor ve API Gateway olarak da kullanılabiliyor. Microsoft&amp;rsquo;un GitHub&amp;rsquo;da yayınladığı eShop örnek microservice projelerinde API Gateway olarak kullanıldığı için benim dikkatimi çekmişti. YARP, load balancing desteği sunuyor ve birden fazla servis olduğunda bu servislerin adreslerini service discovery yöntemleriyle alarak istekleri round robin gibi algoritmalarla bu servislere iletebiliyor. .NET tabanlı olması sebebiyle standart .NET API projelerinde kullanılabilen yetkilendirme, rate limiting, distributed tracing, loglama gibi işlemler de kolayca ve aşina olduğumuz yöntemlerle uygulanabiliyor. Ayrıca, middleware eklemesi ile özelleştirmeler yapılabiliyor, HTTP isteklerini gRPC isteklerine dönüştürebiliyor.&lt;/p&gt;
&lt;p&gt;YARP projesi oluşturmak için bir .NET web api projesi oluşturulduktan sonra Yarp.ReverseProxy paketini uygulamaya dahil edip, gerekli konfigürasyonları yaptıktan sonra istekleri yönlendirmek için hazırız.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-csharp" data-lang="csharp"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// Program.cs&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; builder = WebApplication.CreateBuilder(args);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;builder.Services.AddReverseProxy()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; .LoadFromConfig(builder.Configuration.GetSection(&lt;span style="color:#e6db74"&gt;&amp;#34;ReverseProxy&amp;#34;&lt;/span&gt;));
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; app = builder.Build();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;app.MapReverseProxy();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;app.Run();
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-json" data-lang="json"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// appsettings.json
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;ReverseProxy&amp;#34;&lt;/span&gt;: {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;Routes&amp;#34;&lt;/span&gt;: {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;weaher_route&amp;#34;&lt;/span&gt;: {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;ClusterId&amp;#34;&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;weather_api&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;Match&amp;#34;&lt;/span&gt;: {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;Path&amp;#34;&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;/weather&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; },
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;Clusters&amp;#34;&lt;/span&gt;: {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;weather_api&amp;#34;&lt;/span&gt;: {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;Destinations&amp;#34;&lt;/span&gt;: {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;destination1&amp;#34;&lt;/span&gt;: {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;Address&amp;#34;&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;http://service:8080&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; },
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;Logging&amp;#34;&lt;/span&gt;: {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;LogLevel&amp;#34;&lt;/span&gt;: {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;Default&amp;#34;&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;Information&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;Microsoft.AspNetCore&amp;#34;&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;Warning&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; },
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;AllowedHosts&amp;#34;&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;*&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="ocelot"&gt;Ocelot&lt;/h2&gt;
&lt;p&gt;Ocelot, YARP ile birçok ortak özelliğe sahip bir API Gateway çözümüdür. .NET API projelerinde kullanılabilecek tüm özellikleri yine buradada kullanabiliriz, bağımsız bir open source projesi olarak geliştiriliyor. Ocelot, YARP’a kıyasla direk olarak API Gateway ürünü olarak kendisini konumlandırıyor. Farklı olarak, gRPC desteği bulunmamakla birlikte request aggregation gibi özellikler sunuyor. Bu özellik, backend for frontend yapılarında tercih edilebiliyor.&lt;/p&gt;
&lt;p&gt;Ocelot uygulaması oluşturmak için benzer şekilde bir .NET API projesi oluşturulur ve Program.cs içerisindeki konfigurasyonlarla birlikte servis konfigürasyonları appsettings.json değil ayrı bir ocelot.json dosyasından okunacak şekilde ayarlanır. Ocelot ile route tanımlarını yaparken servis adreslerini tekrar tekrar vermemiz gerekiyor, YARP ile bunu tek bir yerden verip oluşturduğumuz servisin bilgisini router içerisinde belirtmemiz yeterliydi. Request aggregation işleminide route tanımlamalarından sonra aggregates kısmında yapabiliriz. Daha kompleks ihtiyaçlar için ise IDefinedAggregator interface&amp;rsquo;inden yararlanılarak özel aggregate&amp;rsquo;ler tanımlayabiliyoruz.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-csharp" data-lang="csharp"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// Program.cs&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;using&lt;/span&gt; Ocelot.DependencyInjection;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;using&lt;/span&gt; Ocelot.Middleware;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; builder = WebApplication.CreateBuilder(args);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;builder.Configuration.AddJsonFile(&lt;span style="color:#e6db74"&gt;&amp;#34;ocelot.json&amp;#34;&lt;/span&gt;, optional: &lt;span style="color:#66d9ef"&gt;false&lt;/span&gt;, reloadOnChange: &lt;span style="color:#66d9ef"&gt;true&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;builder.Services.AddOcelot(builder.Configuration);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; app = builder.Build();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;await&lt;/span&gt; app.UseOcelot();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;await&lt;/span&gt; app.RunAsync();
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-json" data-lang="json"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// ocelot.json
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;Routes&amp;#34;&lt;/span&gt;: [
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;DownstreamPathTemplate&amp;#34;&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;/weather&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;DownstreamScheme&amp;#34;&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;http&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;DownstreamHostAndPorts&amp;#34;&lt;/span&gt;: [
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;Host&amp;#34;&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;service&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;Port&amp;#34;&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;8080&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ],
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;UpstreamPathTemplate&amp;#34;&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;/weather&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;UpstreamHttpMethod&amp;#34;&lt;/span&gt;: [ &lt;span style="color:#e6db74"&gt;&amp;#34;Get&amp;#34;&lt;/span&gt; ]
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ],
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;GlobalConfiguration&amp;#34;&lt;/span&gt;: {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;BaseUrl&amp;#34;&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;https://service:8080&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="yarp-vs-ocelot"&gt;YARP vs Ocelot&lt;/h2&gt;
&lt;p&gt;Ocelot ve YARP teknolojilerinin karşılaştırılması için hazırladığım tabloda görebileceğiniz üzere çoğu özellikte ortaklar. Burada iki teknoloji arasında tercih sebebimiz destekledikleri özellikler, community desteği ve konfigurasyon dosyasının yapısı olabilir diye düşünüyorum.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Özellik&lt;/th&gt;
&lt;th&gt;YARP&lt;/th&gt;
&lt;th&gt;Ocelot&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Routing &amp;amp; Request mapping&lt;/td&gt;
&lt;td&gt;Var&lt;/td&gt;
&lt;td&gt;Var&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Authn &amp;amp; Authz&lt;/td&gt;
&lt;td&gt;Var&lt;/td&gt;
&lt;td&gt;Var&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Rate limiting&lt;/td&gt;
&lt;td&gt;Var&lt;/td&gt;
&lt;td&gt;Var&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Load balancing&lt;/td&gt;
&lt;td&gt;Var&lt;/td&gt;
&lt;td&gt;Var&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;gRPC&lt;/td&gt;
&lt;td&gt;Var&lt;/td&gt;
&lt;td&gt;Yok, Eski paketler mevcut&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Request aggregation&lt;/td&gt;
&lt;td&gt;Yok, Özel geliştirme gerekiyor&lt;/td&gt;
&lt;td&gt;Var, Basit ise konfigurasyon ile yapılabilir&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Distributed tracing&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Var&lt;/td&gt;
&lt;td&gt;Var&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Mac M1 8GB özelliklere sahip makinede, docker üzerinde çalışan ortamda, sadece tek bir endpointin olduğu çok basit bir senaryoda performans testlerimiz için 10 bin adet isteği, 50 concurrent channel üzerinden Apache Benchmark aracı ile ilettim. Ocelot ortalama 1030 ms, YARP ortalama 1029 ms cevap süresi ile hemen hemen aynı sonucu verdiler. Tüketim için 20MB hafıza tüketimi ile başlarken, istekler gelmeye başlayınca maksimum 100MB tüketime kadar yükseldi, CPU için ise %10 - %20 arasında değişkenli gösteren bir tüketim oldu. Bu oranlar YARP ve Ocelot için hemen hemen aynı. Testleri yaptığım komutlarda şu şekilde:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# YARP&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# 100 MB RAM | %10 - %20 CPU&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;ab -k -n &lt;span style="color:#ae81ff"&gt;10000&lt;/span&gt; -c &lt;span style="color:#ae81ff"&gt;50&lt;/span&gt; http://localhost:9090/weather
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Server Software: Kestrel
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Server Hostname: localhost
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Server Port: &lt;span style="color:#ae81ff"&gt;9090&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Document Path: /weather
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Document Length: &lt;span style="color:#ae81ff"&gt;14&lt;/span&gt; bytes
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Concurrency Level: &lt;span style="color:#ae81ff"&gt;50&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Time taken &lt;span style="color:#66d9ef"&gt;for&lt;/span&gt; tests: 207.561 seconds
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Complete requests: &lt;span style="color:#ae81ff"&gt;10000&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Failed requests: &lt;span style="color:#ae81ff"&gt;0&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Keep-Alive requests: &lt;span style="color:#ae81ff"&gt;0&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Total transferred: &lt;span style="color:#ae81ff"&gt;1470000&lt;/span&gt; bytes
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;HTML transferred: &lt;span style="color:#ae81ff"&gt;140000&lt;/span&gt; bytes
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Requests per second: 48.18 &lt;span style="color:#f92672"&gt;[&lt;/span&gt;&lt;span style="color:#75715e"&gt;#/sec] (mean)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Time per request: 1037.804 &lt;span style="color:#f92672"&gt;[&lt;/span&gt;ms&lt;span style="color:#f92672"&gt;]&lt;/span&gt; &lt;span style="color:#f92672"&gt;(&lt;/span&gt;mean&lt;span style="color:#f92672"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Time per request: 20.756 &lt;span style="color:#f92672"&gt;[&lt;/span&gt;ms&lt;span style="color:#f92672"&gt;]&lt;/span&gt; &lt;span style="color:#f92672"&gt;(&lt;/span&gt;mean, across all concurrent requests&lt;span style="color:#f92672"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Transfer rate: 6.92 &lt;span style="color:#f92672"&gt;[&lt;/span&gt;Kbytes/sec&lt;span style="color:#f92672"&gt;]&lt;/span&gt; received
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Connection Times &lt;span style="color:#f92672"&gt;(&lt;/span&gt;ms&lt;span style="color:#f92672"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; min mean&lt;span style="color:#f92672"&gt;[&lt;/span&gt;+/-sd&lt;span style="color:#f92672"&gt;]&lt;/span&gt; median max
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Connect: &lt;span style="color:#ae81ff"&gt;0&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;0&lt;/span&gt; 0.1 &lt;span style="color:#ae81ff"&gt;0&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;2&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Processing: &lt;span style="color:#ae81ff"&gt;1002&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;1029&lt;/span&gt; 122.9 &lt;span style="color:#ae81ff"&gt;1015&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;2961&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Waiting: &lt;span style="color:#ae81ff"&gt;1002&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;1028&lt;/span&gt; 122.9 &lt;span style="color:#ae81ff"&gt;1015&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;2961&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Total: &lt;span style="color:#ae81ff"&gt;1002&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;1029&lt;/span&gt; 123.0 &lt;span style="color:#ae81ff"&gt;1016&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;2962&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Percentage of the requests served within a certain time &lt;span style="color:#f92672"&gt;(&lt;/span&gt;ms&lt;span style="color:#f92672"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; 50% &lt;span style="color:#ae81ff"&gt;1016&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; 66% &lt;span style="color:#ae81ff"&gt;1019&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; 75% &lt;span style="color:#ae81ff"&gt;1021&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; 80% &lt;span style="color:#ae81ff"&gt;1023&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; 90% &lt;span style="color:#ae81ff"&gt;1028&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; 95% &lt;span style="color:#ae81ff"&gt;1033&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; 98% &lt;span style="color:#ae81ff"&gt;1052&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; 99% &lt;span style="color:#ae81ff"&gt;1517&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; 100% &lt;span style="color:#ae81ff"&gt;2962&lt;/span&gt; &lt;span style="color:#f92672"&gt;(&lt;/span&gt;longest request&lt;span style="color:#f92672"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# Ocelot&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# 100 MB RAM | %10 - %20 CPU&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;ab -k -n &lt;span style="color:#ae81ff"&gt;10000&lt;/span&gt; -c &lt;span style="color:#ae81ff"&gt;50&lt;/span&gt; http://localhost:9090/weather
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Server Software: Kestrel
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Server Hostname: localhost
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Server Port: &lt;span style="color:#ae81ff"&gt;9090&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Document Path: /weather
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Document Length: &lt;span style="color:#ae81ff"&gt;14&lt;/span&gt; bytes
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Concurrency Level: &lt;span style="color:#ae81ff"&gt;50&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Time taken &lt;span style="color:#66d9ef"&gt;for&lt;/span&gt; tests: 207.739 seconds
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Complete requests: &lt;span style="color:#ae81ff"&gt;10000&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Failed requests: &lt;span style="color:#ae81ff"&gt;0&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Keep-Alive requests: &lt;span style="color:#ae81ff"&gt;0&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Total transferred: &lt;span style="color:#ae81ff"&gt;1470000&lt;/span&gt; bytes
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;HTML transferred: &lt;span style="color:#ae81ff"&gt;140000&lt;/span&gt; bytes
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Requests per second: 48.14 &lt;span style="color:#f92672"&gt;[&lt;/span&gt;&lt;span style="color:#75715e"&gt;#/sec] (mean)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Time per request: 1038.695 &lt;span style="color:#f92672"&gt;[&lt;/span&gt;ms&lt;span style="color:#f92672"&gt;]&lt;/span&gt; &lt;span style="color:#f92672"&gt;(&lt;/span&gt;mean&lt;span style="color:#f92672"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Time per request: 20.774 &lt;span style="color:#f92672"&gt;[&lt;/span&gt;ms&lt;span style="color:#f92672"&gt;]&lt;/span&gt; &lt;span style="color:#f92672"&gt;(&lt;/span&gt;mean, across all concurrent requests&lt;span style="color:#f92672"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Transfer rate: 6.91 &lt;span style="color:#f92672"&gt;[&lt;/span&gt;Kbytes/sec&lt;span style="color:#f92672"&gt;]&lt;/span&gt; received
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Connection Times &lt;span style="color:#f92672"&gt;(&lt;/span&gt;ms&lt;span style="color:#f92672"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; min mean&lt;span style="color:#f92672"&gt;[&lt;/span&gt;+/-sd&lt;span style="color:#f92672"&gt;]&lt;/span&gt; median max
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Connect: &lt;span style="color:#ae81ff"&gt;0&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;0&lt;/span&gt; 0.1 &lt;span style="color:#ae81ff"&gt;0&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;2&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Processing: &lt;span style="color:#ae81ff"&gt;1001&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;1030&lt;/span&gt; 120.0 &lt;span style="color:#ae81ff"&gt;1016&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;3347&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Waiting: &lt;span style="color:#ae81ff"&gt;1001&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;1030&lt;/span&gt; 119.9 &lt;span style="color:#ae81ff"&gt;1016&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;3347&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Total: &lt;span style="color:#ae81ff"&gt;1001&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;1030&lt;/span&gt; 120.0 &lt;span style="color:#ae81ff"&gt;1016&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;3349&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Percentage of the requests served within a certain time &lt;span style="color:#f92672"&gt;(&lt;/span&gt;ms&lt;span style="color:#f92672"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; 50% &lt;span style="color:#ae81ff"&gt;1016&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; 66% &lt;span style="color:#ae81ff"&gt;1020&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; 75% &lt;span style="color:#ae81ff"&gt;1023&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; 80% &lt;span style="color:#ae81ff"&gt;1025&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; 90% &lt;span style="color:#ae81ff"&gt;1032&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; 95% &lt;span style="color:#ae81ff"&gt;1040&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; 98% &lt;span style="color:#ae81ff"&gt;1061&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; 99% &lt;span style="color:#ae81ff"&gt;1359&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; 100% &lt;span style="color:#ae81ff"&gt;3349&lt;/span&gt; &lt;span style="color:#f92672"&gt;(&lt;/span&gt;longest request&lt;span style="color:#f92672"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="openresty-tabanlı"&gt;OpenResty tabanlı&lt;/h2&gt;
&lt;p&gt;Temellerinden yüksek performansı ile adını duyuran ngnix ve onun üzerine LuaJIT ile script dili olarak esnek bir geliştirme ortamı sunan ve JIT desteği yeniden başlatmaya gerek kalmadan hot reload özelliğini destekleyen Lua kullanılabiliyor. Lua dili çoğu senaryoda içerdiği C API’ı sayesinde C kadar yüksek performans sunarken aynı zamanda script dili olduğu için daha yüksek seviyede bir programlama ile, daha geliştirici dostu yapısıyla daha esnek geliştirme yapmaya olanak sağlıyor. C kodundan Lua kodunu veya Lua kodundan C kodunu çağırmakta mümkün, bu sebeplerden ötürü, beraber kullanılabilen güzel bir ikili ortaya çıkmış oluyor. Hatta C ve Lua ikilisi Cloudflare&amp;rsquo;in kendi araçları içinde kullandığı bir stack, yani networking uygulamarındada sıklıkla kullanılıyor.&lt;/p&gt;
&lt;p&gt;Bahsettiğim OpenResty temeline sahip gatewaylerdende sektörde çok sık tercih edilen ve ismini duyuran Kong, sonrasında tamamen open source olduğu için dikkat çeken ve kong&amp;rsquo;dan birkaç güzel özellikle sıyrılmayı başaran Apache APISIX&amp;rsquo;den bahsedip Go tabanlı gatewaylerle devam edeceğiz.&lt;/p&gt;
&lt;h2 id="kong"&gt;Kong&lt;/h2&gt;
&lt;p&gt;Kong, API gateway çözümleri arasında öne çıkan bir araçtır. Ocelot ve YARP gibi araçlarda bulunan tüm özellikleri içermesinin yanı sıra, 60&amp;rsquo;tan fazla plugin desteği ile ek özellikler sunar. Ayrıca, Lua veya diğer programlama dilleri ile kendi pluginlerinizi geliştirebilirsiniz. Enterprise versiyonu, GraphQL desteği, request validation, body güncelleme ve secret manager gibi ekstra özellikler sağlar.&lt;/p&gt;
&lt;p&gt;Kong, bir API gateway’den beklenebilecek yetkilendirme, loglama, request transformation ve rate limiting gibi çoğu özelliği içerir. Ancak, request aggregation pattern (tek bir istekle birden fazla servisten dönen cevabı birleştirme) gibi bazı özellikler için ek geliştirme yapılması gerekir. Native olarak kullanılabilen ve diğer alternatiflerden daha iyi çalışacak olan Lua dışında Go, JavaScript veya Python ile IPC (Inter Process Communication) desteği sayesindede geliştirme yapabiliriz ancak daha düşük bir performansla karşılaşacağız. Bununla birlikte, WebAssembly desteği sayesinde Go ve Rust dilleri ile IPC yöntemlerine gerek kalmadan aşina olduğumuz dillerle yüksek performanslı pluginler oluşturabiliyoruz.&lt;/p&gt;
&lt;p&gt;Ocelot ve YARP gibi .NET tabanlı gateway&amp;rsquo;lerden farklı olarak, Kong farklı deployment seçenekleri sunuyor. &amp;ldquo;DB-less&amp;rdquo; mod ile, konfigurasyon dosyaları üzerinden yönetim yapılabilir. Bu mod, CI/CD tabanlı deployment senaryolarında oldukça kullanışlıdır ve state farklılıklarından kaynaklanabilecek sorunları önler.&lt;/p&gt;
&lt;img src="https://berkselvi.dev/img/api-gateway-alternatives-from-a-net-developer-standpoint/kong-config-deployment.png" alt="Kong config deployment" loading="lazy" /&gt;
&lt;br/&gt;
&lt;p&gt;Kong, ayrıca traditional olarak adlandırılan yöntemle veritabanı kullanarak da yapılandırılabilir. Bu yöntemde, PostgreSQL, Cassandra, Redis, InfluxDB ve Kafka gibi veritabanları kullanılabilir. Admin API üzerinden route ve service bilgileri üzerinde değişiklikler yapılabilir. Ancak, bu senaryo veritabanına ek bir bağımlılık yaratır ve control plane ile data plane aynı instance üzerinde çalıştığı için saldırılara karşı daha korunaksız olabilir.&lt;/p&gt;
&lt;img src="https://berkselvi.dev/img/api-gateway-alternatives-from-a-net-developer-standpoint/kong-traditional-deployment.png" alt="Kong traditional deployment" loading="lazy" /&gt;
&lt;br/&gt;
&lt;p&gt;Konnect servisi sayesinde uygulayabildiğimiz deployment senaryosu ile data plane ve control plane kısımlarını birbirinden ayırarak daha güvenli bir yapıya sahip oluyoruz. Konnect, Kong tarafından software as a service olarak sunuluyor ve state yönetimi için ekstra bir veritabanı bağımlılığı gerektirmiyor.&lt;/p&gt;
&lt;img src="https://berkselvi.dev/img/api-gateway-alternatives-from-a-net-developer-standpoint/kong-konnect-deployment.png" alt="Kong konnect deployment" loading="lazy" /&gt;
&lt;br/&gt;
&lt;p&gt;Eğer Kong tarafından yönetilen bir ürünü kullanmak istemiyorsak, tüm sürecin kendi iç network ağımız üzerinde yapılmasını istiyorsak Hybrid mod olarak isimlendirilen yöntem ile control plane ve veritabanı, data plane&amp;rsquo;den ayrı olarak sistemimizin, birbiri ile iletişime geçebilecekleri farklı bir konumuna deploy edilerek, data plane&amp;rsquo;i kontrol etmek için kullanılabiliyor&lt;/p&gt;
&lt;img src="https://berkselvi.dev/img/api-gateway-alternatives-from-a-net-developer-standpoint/kong-hybrid-deployment.png" alt="Kong hybrid deployment" loading="lazy" /&gt;
&lt;br/&gt;
&lt;p&gt;Local olarak geliştirme yapmak için, Kong&amp;rsquo;un sunduğu Docker Compose dosyası sayesinde PostgreSQL ile beraber çalışabilirsiniz ve ilk çalıştırmada bu dosyadan okuma yapılır. Eğer Db-less modda çalışacaksak, tüm route&amp;rsquo;larınızı bir konfigurasyon dosyasında tanımlamanız gerekir. Aşağıdaki gibi konfigurasyon tanımını yaptıktan sonra Docker compose dosyamız ile gateway’imizi ayağa kaldırıyoruz.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# kong.yaml&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# a very minimal declarative config file&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;_format_version&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;2.1&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;_transform&lt;/span&gt;: &lt;span style="color:#66d9ef"&gt;true&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;services&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#f92672"&gt;name&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;weather-service&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;url&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;http://host.docker.internal:8080&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;routes&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#f92672"&gt;name&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;get-weather&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;paths&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#ae81ff"&gt;/weather&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# docker-compose.yml&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;# ...&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;kong&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;image&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;${KONG_DOCKER_TAG:-kong:latest}&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;user&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;${KONG_USER:-kong}&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;environment&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;lt;&amp;lt;&lt;/span&gt;: &lt;span style="color:#75715e"&gt;*kong-env&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;KONG_ADMIN_ACCESS_LOG&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;/dev/stdout&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;KONG_ADMIN_ERROR_LOG&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;/dev/stderr&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;KONG_PROXY_LISTEN&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;${KONG_PROXY_LISTEN:-0.0.0.0:8000}&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;KONG_ADMIN_LISTEN&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;${KONG_ADMIN_LISTEN:-0.0.0.0:8001}&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;KONG_ADMIN_GUI_LISTEN&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;${KONG_ADMIN_GUI_LISTEN:-0.0.0.0:8002}&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;KONG_PROXY_ACCESS_LOG&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;/dev/stdout&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;KONG_PROXY_ERROR_LOG&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;/dev/stderr&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;KONG_PREFIX&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;${KONG_PREFIX:-/var/run/kong}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;KONG_DECLARATIVE_CONFIG&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;/opt/kong/kong.yaml&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;secrets&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#ae81ff"&gt;kong_postgres_password&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;networks&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#ae81ff"&gt;kong-net&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;ports&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;# The following two environment variables default to an insecure value (0.0.0.0)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;# according to the CIS Security test.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#e6db74"&gt;&amp;#34;${KONG_INBOUND_PROXY_LISTEN:-0.0.0.0}:8000:8000/tcp&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#e6db74"&gt;&amp;#34;${KONG_INBOUND_SSL_PROXY_LISTEN:-0.0.0.0}:8443:8443/tcp&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;# Making them mandatory but undefined, like so would be backwards-breaking:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;# - &amp;#34;${KONG_INBOUND_PROXY_LISTEN?Missing inbound proxy host}:8000:8000/tcp&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;# - &amp;#34;${KONG_INBOUND_SSL_PROXY_LISTEN?Missing inbound proxy ssl host}:8443:8443/tcp&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;# Alternative is deactivating check 5.13 in the security bench, if we consider Kong&amp;#39;s own config to be enough security here&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#e6db74"&gt;&amp;#34;127.0.0.1:8001:8001/tcp&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#e6db74"&gt;&amp;#34;127.0.0.1:8444:8444/tcp&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#e6db74"&gt;&amp;#34;127.0.0.1:8002:8002/tcp&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;healthcheck&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;test&lt;/span&gt;: [ &lt;span style="color:#e6db74"&gt;&amp;#34;CMD&amp;#34;&lt;/span&gt;, &lt;span style="color:#e6db74"&gt;&amp;#34;kong&amp;#34;&lt;/span&gt;, &lt;span style="color:#e6db74"&gt;&amp;#34;health&amp;#34;&lt;/span&gt; ]
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;interval&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;10s&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;timeout&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;10s&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;retries&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;10&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;restart&lt;/span&gt;: &lt;span style="color:#66d9ef"&gt;on&lt;/span&gt;-&lt;span style="color:#ae81ff"&gt;failure:5&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;read_only&lt;/span&gt;: &lt;span style="color:#66d9ef"&gt;true&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;volumes&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#ae81ff"&gt;kong_prefix_vol:${KONG_PREFIX:-/var/run/kong}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#ae81ff"&gt;kong_tmp_vol:/tmp&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#ae81ff"&gt;./config:/opt/kong&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;security_opt&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#66d9ef"&gt;no&lt;/span&gt;-&lt;span style="color:#ae81ff"&gt;new-privileges&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;# ...&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;KONG_DATABASE&lt;span style="color:#f92672"&gt;=&lt;/span&gt;postgres docker compose --profile database up -d
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Kong ile başarılı bir deployment yaptıktan sonra, belirttiğiniz route ve service bilgilerine yapılan istekler yönlendirilir. Tradational veya hybrid mod ile deployment yaptıysanız, bir admin arayüzü üzerinden servis ve endpoint bilgilerinizi yönetebilirsiniz.&lt;/p&gt;
&lt;img src="https://berkselvi.dev/img/api-gateway-alternatives-from-a-net-developer-standpoint/kong-admin-gui.png" alt="Kong admin gui" loading="lazy" /&gt;
&lt;br/&gt;
&lt;h2 id="apache-apisix"&gt;Apache APISIX&lt;/h2&gt;
&lt;p&gt;Apache APISIX, Kong gibi OpenResty üzerine geliştirilmiş bir gateway ürünüdür. Ancak, Kong&amp;rsquo;un sadece enterprise versiyonunda bulunan özellikleri open source olarak sunarak öne çıkıyor. Bu özellikler arasında GraphQL, Canary release ve secret management gibi özellikler yer alıyor.&lt;/p&gt;
&lt;p&gt;APISIX, yüksek sayıda route içeren sistemlerde Kong&amp;rsquo;a kıyasla daha yüksek performans sunuyor. Kong, route&amp;rsquo;lar arasında traversal search algoritması kullanırken, APISIX radixtree algoritması ile arama yapar ve plugin kullanılmadığında %140 daha yüksek performans sağlar. Ayrıca, APISIX, konfigurasyonlarını depolamak için etcd veritabanını kullanır. Bu veritabanı, cloud native çalışabilen ve kubernetes ortamında yaygın olarak kullanılan bir veritabanıdır.&lt;/p&gt;
&lt;p&gt;APISIX, üç farklı deployment senaryosu sunar:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Traditional Yöntem&lt;/strong&gt;: Control plane ve data plane aynı instance içerisinde veritabanına bağımlı olarak deploy edilir.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Decoupled Yöntem&lt;/strong&gt;: Control plane ve data plane farklı instancelar ile deploy edilir ve sadece control plane veritabanına bağımlıdır.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Standalone Yöntem&lt;/strong&gt;: Veritabanı kullanmadan konfigurasyon dosyasından okuma yaparak çalışır.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Local testlerimde, etcd veritabanı ile traditional yöntem kullanarak Docker Compose üzerinden deployment işlemi gerçekleştirdim. APISIX&amp;rsquo;in docker repositorysinde bulunan Docker Compose dosyasını küçük değişiklikler yaparak kullandım. Admin key&amp;rsquo;leri kullanarak Admin API&amp;rsquo;ya istek atabiliyoruz. Eğer standalone yöntemle deploy edecek olsaydım, route&amp;rsquo;ları yaml dosyasında tanımlamam gerekecekti.&lt;/p&gt;
&lt;p&gt;Eğer enterprise destek almak istenirse, APISIX&amp;rsquo;in geliştirici takımı tarafından yürütülen &lt;a href="http://api7.ai/"&gt;API7&lt;/a&gt; üzerinden destek alınabilir.&lt;/p&gt;
&lt;p&gt;Docker Compose dosyalarını ve kullandığım kodları blog yazısının sonunda paylaşacağım, böylece siz de kendi localinizde hızlıca deneyimleyebilirsiniz.&lt;/p&gt;
&lt;img src="https://berkselvi.dev/img/api-gateway-alternatives-from-a-net-developer-standpoint/kong-vs-apisix.png" alt="Kong vs APISIX" loading="lazy" /&gt;
&lt;br/&gt;
&lt;p&gt;APISIX&amp;rsquo;in performans testlerinde, 1025 milisaniye ortalama cevap süresiyle, 1032 milisaniye ortalama cevap süresine sahip olan Kong&amp;rsquo;dan daha performanslı olduğu görülebiliyor. Tüketim olarak, Kong çalışır çalışmaz 280MB civarında bellek kullanırken, APISIX 100MB civarında bellek kullanmaktadır. Her iki gateway de %5-10 arasında değişen CPU tüketim oranına sahip.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# Kong&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# 280MB RAM | %5 - %10 CPU&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;ab -k -n &lt;span style="color:#ae81ff"&gt;10000&lt;/span&gt; -c &lt;span style="color:#ae81ff"&gt;50&lt;/span&gt; http://localhost:8000/weather
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Server Software: Kestrel
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Server Hostname: localhost
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Server Port: &lt;span style="color:#ae81ff"&gt;8000&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Document Path: /weather
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Document Length: &lt;span style="color:#ae81ff"&gt;14&lt;/span&gt; bytes
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Concurrency Level: &lt;span style="color:#ae81ff"&gt;50&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Time taken &lt;span style="color:#66d9ef"&gt;for&lt;/span&gt; tests: 207.499 seconds
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Complete requests: &lt;span style="color:#ae81ff"&gt;10000&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Failed requests: &lt;span style="color:#ae81ff"&gt;0&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Keep-Alive requests: &lt;span style="color:#ae81ff"&gt;0&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Total transferred: &lt;span style="color:#ae81ff"&gt;2730028&lt;/span&gt; bytes
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;HTML transferred: &lt;span style="color:#ae81ff"&gt;140000&lt;/span&gt; bytes
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Requests per second: 48.19 &lt;span style="color:#f92672"&gt;[&lt;/span&gt;&lt;span style="color:#75715e"&gt;#/sec] (mean)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Time per request: 1037.494 &lt;span style="color:#f92672"&gt;[&lt;/span&gt;ms&lt;span style="color:#f92672"&gt;]&lt;/span&gt; &lt;span style="color:#f92672"&gt;(&lt;/span&gt;mean&lt;span style="color:#f92672"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Time per request: 20.750 &lt;span style="color:#f92672"&gt;[&lt;/span&gt;ms&lt;span style="color:#f92672"&gt;]&lt;/span&gt; &lt;span style="color:#f92672"&gt;(&lt;/span&gt;mean, across all concurrent requests&lt;span style="color:#f92672"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Transfer rate: 12.85 &lt;span style="color:#f92672"&gt;[&lt;/span&gt;Kbytes/sec&lt;span style="color:#f92672"&gt;]&lt;/span&gt; received
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Connection Times &lt;span style="color:#f92672"&gt;(&lt;/span&gt;ms&lt;span style="color:#f92672"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; min mean&lt;span style="color:#f92672"&gt;[&lt;/span&gt;+/-sd&lt;span style="color:#f92672"&gt;]&lt;/span&gt; median max
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Connect: &lt;span style="color:#ae81ff"&gt;0&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;0&lt;/span&gt; 0.2 &lt;span style="color:#ae81ff"&gt;0&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;4&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Processing: &lt;span style="color:#ae81ff"&gt;1002&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;1032&lt;/span&gt; 120.5 &lt;span style="color:#ae81ff"&gt;1018&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;3369&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Waiting: &lt;span style="color:#ae81ff"&gt;1002&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;1031&lt;/span&gt; 120.5 &lt;span style="color:#ae81ff"&gt;1018&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;3369&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Total: &lt;span style="color:#ae81ff"&gt;1002&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;1032&lt;/span&gt; 120.7 &lt;span style="color:#ae81ff"&gt;1019&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;3373&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Percentage of the requests served within a certain time &lt;span style="color:#f92672"&gt;(&lt;/span&gt;ms&lt;span style="color:#f92672"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; 50% &lt;span style="color:#ae81ff"&gt;1019&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; 66% &lt;span style="color:#ae81ff"&gt;1023&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; 75% &lt;span style="color:#ae81ff"&gt;1026&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; 80% &lt;span style="color:#ae81ff"&gt;1028&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; 90% &lt;span style="color:#ae81ff"&gt;1034&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; 95% &lt;span style="color:#ae81ff"&gt;1043&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; 98% &lt;span style="color:#ae81ff"&gt;1066&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; 99% &lt;span style="color:#ae81ff"&gt;1326&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; 100% &lt;span style="color:#ae81ff"&gt;3373&lt;/span&gt; &lt;span style="color:#f92672"&gt;(&lt;/span&gt;longest request&lt;span style="color:#f92672"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# APISIX&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# 100MB RAM | %5 - %10 CPU&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;ab -k -n &lt;span style="color:#ae81ff"&gt;10000&lt;/span&gt; -c &lt;span style="color:#ae81ff"&gt;50&lt;/span&gt; http://localhost:9080/weather
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Server Software: APISIX/3.9.0
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Server Hostname: localhost
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Server Port: &lt;span style="color:#ae81ff"&gt;9090&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Document Path: /weather
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Document Length: &lt;span style="color:#ae81ff"&gt;14&lt;/span&gt; bytes
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Concurrency Level: &lt;span style="color:#ae81ff"&gt;50&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Time taken &lt;span style="color:#66d9ef"&gt;for&lt;/span&gt; tests: 206.130 seconds
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Complete requests: &lt;span style="color:#ae81ff"&gt;10000&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Failed requests: &lt;span style="color:#ae81ff"&gt;0&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Keep-Alive requests: &lt;span style="color:#ae81ff"&gt;0&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Total transferred: &lt;span style="color:#ae81ff"&gt;1520000&lt;/span&gt; bytes
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;HTML transferred: &lt;span style="color:#ae81ff"&gt;140000&lt;/span&gt; bytes
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Requests per second: 48.51 &lt;span style="color:#f92672"&gt;[&lt;/span&gt;&lt;span style="color:#75715e"&gt;#/sec] (mean)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Time per request: 1030.650 &lt;span style="color:#f92672"&gt;[&lt;/span&gt;ms&lt;span style="color:#f92672"&gt;]&lt;/span&gt; &lt;span style="color:#f92672"&gt;(&lt;/span&gt;mean&lt;span style="color:#f92672"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Time per request: 20.613 &lt;span style="color:#f92672"&gt;[&lt;/span&gt;ms&lt;span style="color:#f92672"&gt;]&lt;/span&gt; &lt;span style="color:#f92672"&gt;(&lt;/span&gt;mean, across all concurrent requests&lt;span style="color:#f92672"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Transfer rate: 7.20 &lt;span style="color:#f92672"&gt;[&lt;/span&gt;Kbytes/sec&lt;span style="color:#f92672"&gt;]&lt;/span&gt; received
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Connection Times &lt;span style="color:#f92672"&gt;(&lt;/span&gt;ms&lt;span style="color:#f92672"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; min mean&lt;span style="color:#f92672"&gt;[&lt;/span&gt;+/-sd&lt;span style="color:#f92672"&gt;]&lt;/span&gt; median max
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Connect: &lt;span style="color:#ae81ff"&gt;0&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;0&lt;/span&gt; 0.2 &lt;span style="color:#ae81ff"&gt;0&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;7&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Processing: &lt;span style="color:#ae81ff"&gt;921&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;1025&lt;/span&gt; 126.2 &lt;span style="color:#ae81ff"&gt;1012&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;3376&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Waiting: &lt;span style="color:#ae81ff"&gt;921&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;1024&lt;/span&gt; 126.2 &lt;span style="color:#ae81ff"&gt;1011&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;3375&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Total: &lt;span style="color:#ae81ff"&gt;921&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;1025&lt;/span&gt; 126.3 &lt;span style="color:#ae81ff"&gt;1012&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;3378&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Percentage of the requests served within a certain time &lt;span style="color:#f92672"&gt;(&lt;/span&gt;ms&lt;span style="color:#f92672"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; 50% &lt;span style="color:#ae81ff"&gt;1012&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; 66% &lt;span style="color:#ae81ff"&gt;1015&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; 75% &lt;span style="color:#ae81ff"&gt;1017&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; 80% &lt;span style="color:#ae81ff"&gt;1018&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; 90% &lt;span style="color:#ae81ff"&gt;1023&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; 95% &lt;span style="color:#ae81ff"&gt;1029&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; 98% &lt;span style="color:#ae81ff"&gt;1047&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; 99% &lt;span style="color:#ae81ff"&gt;1327&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; 100% &lt;span style="color:#ae81ff"&gt;3378&lt;/span&gt; &lt;span style="color:#f92672"&gt;(&lt;/span&gt;longest request&lt;span style="color:#f92672"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h1 id="go-tabanlı-alternatifler"&gt;Go Tabanlı alternatifler&lt;/h1&gt;
&lt;p&gt;Go tabanlı API Gateway alternatifi olarakta en çok öne çıkan, KrakenD ürününü ele alacağız, ancak Go ile ikinci bir alternatif olarak Tyk veya Traefik API Gateway gibi seçeneklerde tercih edilebilir, ancak bu ikiside ücretli olarak sunulduğu için deneme yapamadığımdan bu anlatımda yer veremiyorum.&lt;/p&gt;
&lt;h2 id="krakend"&gt;KrakenD&lt;/h2&gt;
&lt;p&gt;KrakenD, Go ile geliştirilmiş ve hem community hem de enterprise versiyonlarına sahip bir API gateway ürünü. 2021 yılında Linux Foundation&amp;rsquo;a bağışlanmış olan Lura engine üzerinde çalışır. Diğer API gateway ürünlerine kıyasla güçlü bir aggregation sistemi sunar. Kod yazmadan, sadece konfigurasyon dosyaları ile tüm aggregation işlemlerini gerçekleştirebilirsiniz.&lt;/p&gt;
&lt;p&gt;KrakenD, veritabanı kullanmadığı için, deployment seçenekleri arasında karar verirken kafa karışıklığı yaşatmaz, sadece konfigurasyon dosyalarını kullanarak işlemlerimizi gerçekleştirebiliriz. Ancak, veritabanı kullanmadığı için bir admin paneli bulunmamaktadır. Veritabanı olmaması, ek hata kaynaklarını ortadan kaldırır ve daha kolay ölçeklenebilirlik sağlar.&lt;/p&gt;
&lt;p&gt;Tüm konfigurasyonlar, git ile takip edilebilecek bir konfigurasyon dosyası üzerinden yönetilir. JSON, YAML, TOML gibi farklı dosya formatlarını destekliyor. KrakenD&amp;rsquo;nin kendi websitesindeki arayüz ile konfigurasyonları hızlıca oluşturabiliriz. Local geliştirme ortamında, sadece konfigurasyon dosyası üzerinden çalıştığı için hızlıca ayağa kaldırabilir ve servisinize istek atabiliriz. Hot reload özelliği ile kısa bir downtime ile konfigurasyon dosyasını güncelleyebilirsiniz. Ancak, production ortamlarında bu yöntem önerilmez, GitOps yöntemleri ile yeni bir release almanız tavsiye edilir. Enterprise versiyonunda OpenAPI importer ve exporter, gzip sıkıştırma ve response validation gibi ek özellikler bulunur.&lt;/p&gt;
&lt;img src="https://berkselvi.dev/img/api-gateway-alternatives-from-a-net-developer-standpoint/krakend-designer.png" alt="KrakenD designer" loading="lazy" /&gt;
&lt;br/&gt;
&lt;p&gt;KrakenD, Kong ve APISIX&amp;rsquo;ten çok daha basit bir Docker Compose içeriğine sahiptir, çünkü herhangi bir ek bağımlılığı yoktur. Sadece konfigurasyon dosyasının path&amp;rsquo;ini volume olarak vermeniz yeterlidir. Örnek olması açısından weather servisini Docker Compose içerisine ekledim ve krakend.json dosyasını hızlıca oluşturarak servisi ayağa kaldırdım. Performans testinde çok iyi sonuçlar verdi.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-json" data-lang="json"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// krakend.json
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;$schema&amp;#34;&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;https://www.krakend.io/schema/krakend.json&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;version&amp;#34;&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;3&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;name&amp;#34;&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;KrakenD - API Gateway&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;timeout&amp;#34;&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;3000ms&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;cache_ttl&amp;#34;&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;300s&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;output_encoding&amp;#34;&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;json&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;port&amp;#34;&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;9090&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;endpoints&amp;#34;&lt;/span&gt;: [
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;endpoint&amp;#34;&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;/weather&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;method&amp;#34;&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;GET&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;output_encoding&amp;#34;&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;string&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;backend&amp;#34;&lt;/span&gt;: [
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;url_pattern&amp;#34;&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;/weather&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;encoding&amp;#34;&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;string&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;sd&amp;#34;&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;static&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;method&amp;#34;&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;GET&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;host&amp;#34;&lt;/span&gt;: [
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;http://service:8080&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ],
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;disable_host_sanitize&amp;#34;&lt;/span&gt;: &lt;span style="color:#66d9ef"&gt;false&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ]
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ]
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# docker-compose.yml&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;version&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#39;3&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;services&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;krakend&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;image&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;devopsfaith/krakend&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;ports&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#ae81ff"&gt;9090&lt;/span&gt;:&lt;span style="color:#ae81ff"&gt;9090&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;volumes&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#ae81ff"&gt;./krakend.json:/etc/krakend/krakend.json&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;networks&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#ae81ff"&gt;krakend-net&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;weather-service&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;container_name&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;service&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;build&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;../service/Service.API&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;networks&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#ae81ff"&gt;krakend-net&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;ports&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#e6db74"&gt;&amp;#34;8080:8080&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;networks&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;krakend-net&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;external&lt;/span&gt;: &lt;span style="color:#66d9ef"&gt;false&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;KrakenD, kendi websitesinde de belirttiği gibi, Kong ve APISIX&amp;rsquo;ten daha hızlı çalışıyor. Benzer test sonuçlarında, Kong ortalama 1032 milisaniye, APISIX ortalama 1025 milisaniye ile cevap verirken, KrakenD ortalama 1024 milisaniye ile en iyi performansı sunuyor. CPU tüketimi %4-6 civarında sabit kalırken en yüksek 30MB memory tüketiyor. Bu sonuçlar, KrakenD&amp;rsquo;nin diğer rakiplerinden daha az kaynak tükettiğini ve buna rağmen daha hızlı olduğunu gösteriyor.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# KrakenD&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# 30MB RAM | %4 - %6 CPU&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;ab -k -n &lt;span style="color:#ae81ff"&gt;10000&lt;/span&gt; -c &lt;span style="color:#ae81ff"&gt;50&lt;/span&gt; http://localhost:9090/weather
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Server Software:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Server Hostname: localhost
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Server Port: &lt;span style="color:#ae81ff"&gt;9090&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Document Path: /weather
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Document Length: &lt;span style="color:#ae81ff"&gt;14&lt;/span&gt; bytes
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Concurrency Level: &lt;span style="color:#ae81ff"&gt;50&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Time taken &lt;span style="color:#66d9ef"&gt;for&lt;/span&gt; tests: 206.359 seconds
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Complete requests: &lt;span style="color:#ae81ff"&gt;10000&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Failed requests: &lt;span style="color:#ae81ff"&gt;9&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;(&lt;/span&gt;Connect: 0, Receive: 0, Length: 9, Exceptions: 0&lt;span style="color:#f92672"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Non-2xx responses: &lt;span style="color:#ae81ff"&gt;9&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Keep-Alive requests: &lt;span style="color:#ae81ff"&gt;10000&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Total transferred: &lt;span style="color:#ae81ff"&gt;2439352&lt;/span&gt; bytes
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;HTML transferred: &lt;span style="color:#ae81ff"&gt;139874&lt;/span&gt; bytes
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Requests per second: 48.46 &lt;span style="color:#f92672"&gt;[&lt;/span&gt;&lt;span style="color:#75715e"&gt;#/sec] (mean)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Time per request: 1031.795 &lt;span style="color:#f92672"&gt;[&lt;/span&gt;ms&lt;span style="color:#f92672"&gt;]&lt;/span&gt; &lt;span style="color:#f92672"&gt;(&lt;/span&gt;mean&lt;span style="color:#f92672"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Time per request: 20.636 &lt;span style="color:#f92672"&gt;[&lt;/span&gt;ms&lt;span style="color:#f92672"&gt;]&lt;/span&gt; &lt;span style="color:#f92672"&gt;(&lt;/span&gt;mean, across all concurrent requests&lt;span style="color:#f92672"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Transfer rate: 11.54 &lt;span style="color:#f92672"&gt;[&lt;/span&gt;Kbytes/sec&lt;span style="color:#f92672"&gt;]&lt;/span&gt; received
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Connection Times &lt;span style="color:#f92672"&gt;(&lt;/span&gt;ms&lt;span style="color:#f92672"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; min mean&lt;span style="color:#f92672"&gt;[&lt;/span&gt;+/-sd&lt;span style="color:#f92672"&gt;]&lt;/span&gt; median max
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Connect: &lt;span style="color:#ae81ff"&gt;0&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;0&lt;/span&gt; 0.1 &lt;span style="color:#ae81ff"&gt;0&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;3&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Processing: &lt;span style="color:#ae81ff"&gt;1000&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;1024&lt;/span&gt; 122.7 &lt;span style="color:#ae81ff"&gt;1010&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;3021&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Waiting: &lt;span style="color:#ae81ff"&gt;1000&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;1024&lt;/span&gt; 122.7 &lt;span style="color:#ae81ff"&gt;1010&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;3021&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Total: &lt;span style="color:#ae81ff"&gt;1000&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;1024&lt;/span&gt; 122.8 &lt;span style="color:#ae81ff"&gt;1010&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;3023&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Percentage of the requests served within a certain time &lt;span style="color:#f92672"&gt;(&lt;/span&gt;ms&lt;span style="color:#f92672"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; 50% &lt;span style="color:#ae81ff"&gt;1010&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; 66% &lt;span style="color:#ae81ff"&gt;1012&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; 75% &lt;span style="color:#ae81ff"&gt;1013&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; 80% &lt;span style="color:#ae81ff"&gt;1015&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; 90% &lt;span style="color:#ae81ff"&gt;1018&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; 95% &lt;span style="color:#ae81ff"&gt;1023&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; 98% &lt;span style="color:#ae81ff"&gt;1037&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; 99% &lt;span style="color:#ae81ff"&gt;1520&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; 100% &lt;span style="color:#ae81ff"&gt;3023&lt;/span&gt; &lt;span style="color:#f92672"&gt;(&lt;/span&gt;longest request&lt;span style="color:#f92672"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="sonuç"&gt;Sonuç&lt;/h2&gt;
&lt;p&gt;İncelediğimiz 5 API gateway ürününü karşılaştırmak için aşağıdaki gibi bir tablo hazırladım. Bu tablo, sunumun içeriğin özetini oluşturuyor ve araştırmalarım sonucunda birkaç önemli noktayı vurgulamak istiyorum.&lt;/p&gt;
&lt;h3 id="kong-ve-apisix"&gt;&lt;strong&gt;Kong ve APISIX&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;Eğer farklı projelere sahip takımlar, tek bir API gateway üzerinden internete açılacaksa ve canlı olarak güncellenebilen bir admin arayüzüne ihtiyaç duyuyorsa, Kong veya APISIX tercih edilebilir. Her iki gateway de güçlü özelliklere sahip ve genişletilebilir yapıları ile öne çıkıyorlar. Ayrıca, admin arayüzü sayesinde, konfigurasyonları yönetmek ve güncellemeleri canlı olarak yapmak mümkün.&lt;/p&gt;
&lt;h3 id="krakend-1"&gt;&lt;strong&gt;KrakenD&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;Eğer API gateway projesi, takımların ortak sorumluluğunda ise ve geliştiriciler konfigurasyon dosyasını güncelleyip pipeline çalıştırabilirlerse, yüksek performans ve düşük kaynak tüketimi hedeflenen durumlarda, KrakenD iyi bir seçim olabilir. KrakenD, Go ile geliştirilmiş olup, güçlü bir aggregation sistemi sunar ve veritabanı kullanmadığı için daha kolay ölçeklenebilir.&lt;/p&gt;
&lt;h3 id="ocelot-ve-yarp"&gt;&lt;strong&gt;Ocelot ve YARP&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;Performans ve kaynak tüketimi kritik değilse ve aşina olduğumuz bir teknoloji yeterliyse, .NET tabanlı Ocelot veya YARP ile yolumuza devam edebiliriz. Bu araçlar, benzer özellikleri farklı şekillerde destekliyorlar ve .NET ekosistemine entegrasyonları kolaydır.&lt;/p&gt;
&lt;h3 id="diğer-kriterler"&gt;&lt;strong&gt;Diğer Kriterler&lt;/strong&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Deployment Yöntemleri&lt;/strong&gt;: Hangi gateway’in nasıl deploy edileceği, konfigurasyon dosyalarının yapısı ve düzenlenebilirliği önemlidir.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Community ve Enterprise Desteği&lt;/strong&gt;: Gateway&amp;rsquo;in ne kadar geniş bir kullanıcı kitlesine sahip olduğu ve destek alabileceğimiz kaynakların bulunup bulunmadığı da karar verirken göz önünde bulundurulmalıdır.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Tüm ihtiyaçlarımıza tek başına kusursuz bir çözüm sunacak bir ürün maalesef yok. Bu nedenle, kendi ihtiyaçlarımızı değerlendirerek, seçeceğimiz aracın özelliklerine göre karar vermeliyiz. Doğru API gateway seçimi, projenizin başarısı için kritik bir adımdır ve ihtiyaçlarınıza en uygun olanı tercih etmek her zaman en iyi yaklaşımdır.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Feature&lt;/th&gt;
&lt;th&gt;YARP&lt;/th&gt;
&lt;th&gt;Ocelot&lt;/th&gt;
&lt;th&gt;Kong&lt;/th&gt;
&lt;th&gt;APISIX&lt;/th&gt;
&lt;th&gt;KrakenD&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Request aggregation&lt;/td&gt;
&lt;td&gt;NO&lt;/td&gt;
&lt;td&gt;Simple, can be done with configuration&lt;/td&gt;
&lt;td&gt;NO&lt;/td&gt;
&lt;td&gt;YES&lt;/td&gt;
&lt;td&gt;Powerful,, can be done with configuration&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;OAuth &amp;amp; OpenID&lt;/td&gt;
&lt;td&gt;YES&lt;/td&gt;
&lt;td&gt;YES&lt;/td&gt;
&lt;td&gt;YES&lt;/td&gt;
&lt;td&gt;YES&lt;/td&gt;
&lt;td&gt;YES&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Request validation&lt;/td&gt;
&lt;td&gt;Needs custom logic&lt;/td&gt;
&lt;td&gt;Needs custom logic&lt;/td&gt;
&lt;td&gt;Comes with enterprise&lt;/td&gt;
&lt;td&gt;YES&lt;/td&gt;
&lt;td&gt;YES&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;HTTP to gRPC&lt;/td&gt;
&lt;td&gt;YES&lt;/td&gt;
&lt;td&gt;NO&lt;/td&gt;
&lt;td&gt;YES&lt;/td&gt;
&lt;td&gt;YES&lt;/td&gt;
&lt;td&gt;YES&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Websocket&lt;/td&gt;
&lt;td&gt;YES&lt;/td&gt;
&lt;td&gt;YES&lt;/td&gt;
&lt;td&gt;?&lt;/td&gt;
&lt;td&gt;YES&lt;/td&gt;
&lt;td&gt;Comes with enterprise&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;State&lt;/td&gt;
&lt;td&gt;File&lt;/td&gt;
&lt;td&gt;File&lt;/td&gt;
&lt;td&gt;File, PostgreSQL, Cassandra, Redis, InfluxDB, Kafka&lt;/td&gt;
&lt;td&gt;File, etcd&lt;/td&gt;
&lt;td&gt;File&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Plugin&lt;/td&gt;
&lt;td&gt;C#&lt;/td&gt;
&lt;td&gt;C#&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Native&lt;/strong&gt;: Lua &amp;amp; &lt;strong&gt;IPC&lt;/strong&gt;: Go, Javascript, Python &amp;amp; &lt;strong&gt;WASM&lt;/strong&gt;: Go, Rust vs.&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Native&lt;/strong&gt;: Lua &amp;amp; &lt;strong&gt;IPC&lt;/strong&gt;: Go, Javascript, Python &amp;amp; &lt;strong&gt;WASM&lt;/strong&gt;: Go, Rust vs.&lt;/td&gt;
&lt;td&gt;Go &amp;amp; Lua&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Performance&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Mean response&lt;/strong&gt;: 1029ms, &lt;strong&gt;Memory&lt;/strong&gt;: 100MB, &lt;strong&gt;CPU&lt;/strong&gt;: %10 - %20&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Mean response&lt;/strong&gt;: 1030ms, &lt;strong&gt;Memory&lt;/strong&gt;: 100MB, &lt;strong&gt;CPU&lt;/strong&gt;: %10 - %20&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Mean response&lt;/strong&gt;: 1035ms, &lt;strong&gt;Memory&lt;/strong&gt;: 280MB, &lt;strong&gt;CPU&lt;/strong&gt;: %5 - %10&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Mean response&lt;/strong&gt;: 1025ms, &lt;strong&gt;Memory&lt;/strong&gt;: 100MB &lt;strong&gt;CPU&lt;/strong&gt;: %5 - %10&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Mean response&lt;/strong&gt;: 1024ms, &lt;strong&gt;Memory&lt;/strong&gt;: 30MB, &lt;strong&gt;CPU&lt;/strong&gt;: %4 - %6&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Kaynak koduna erişmek isterseniz projenin tamamını GitHub hesabımda bulabilirsiniz:&lt;/p&gt;
&lt;p&gt;&lt;a href="https://github.com/berkslv/lecture-api-gateway-comparison"&gt;GitHub - berkslv/lecture-api-gateway-comparison&lt;/a&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="sonuç-1"&gt;Sonuç&lt;/h2&gt;
&lt;p&gt;Okuduğunuz için teşekkürler! 🎉 Yazılım geliştirme alanındaki araştırmalarımı kaçırmamak için &lt;a href="https://x.com/berkslv"&gt;@berkslv&lt;/a&gt; adresinden takipte kalabilirsiniz.&lt;/p&gt;</content></item><item><title>RSA ile JavaScript'te Şifreleme ve .NET'te Şifre Çözme Nasıl Yapılır</title><link>https://berkselvi.dev/tr/posts/how-to-use-rsa-for-encryption-in-javascript-and-decryption-in-net/</link><pubDate>Thu, 07 Mar 2024 00:00:00 +0300</pubDate><guid>https://berkselvi.dev/tr/posts/how-to-use-rsa-for-encryption-in-javascript-and-decryption-in-net/</guid><description>&lt;p&gt;Dijital dünyada, farklı sistemler arasındaki iletişimin güvence altına alınması çok önemlidir. RSA şifreleme, iki adet anahtar kullanarak şifreleme ve şifre çözme için sağlam bir yol sağlar. Öte yandan, AES gibi simetrik şifreleme algoritmaları, şifreleme ve şifre çözme için tek bir anahtar kullanır. Bu nedenlerle, anahtarı halka açık olarak kullanmak ve hatta bir saldırganın eline geçse bile sorunları önlemek için asimetrik şifreleme algoritmalarından olan RSA tercih edilebilir.&lt;/p&gt;
&lt;p&gt;Bu blog yazısında, OpenSSL ile RSA anahtar oluşturmayı, JavaScript&amp;rsquo;te şifrelemeyi ve .NET&amp;rsquo;te şifre çözmeyi, bir frontend uygulaması ile bir backend servisi arasında güvenli veri alışverişi sağlayacak uygulamayı ele alacağım.&lt;/p&gt;</description><content>&lt;p&gt;Dijital dünyada, farklı sistemler arasındaki iletişimin güvence altına alınması çok önemlidir. RSA şifreleme, iki adet anahtar kullanarak şifreleme ve şifre çözme için sağlam bir yol sağlar. Öte yandan, AES gibi simetrik şifreleme algoritmaları, şifreleme ve şifre çözme için tek bir anahtar kullanır. Bu nedenlerle, anahtarı halka açık olarak kullanmak ve hatta bir saldırganın eline geçse bile sorunları önlemek için asimetrik şifreleme algoritmalarından olan RSA tercih edilebilir.&lt;/p&gt;
&lt;p&gt;Bu blog yazısında, OpenSSL ile RSA anahtar oluşturmayı, JavaScript&amp;rsquo;te şifrelemeyi ve .NET&amp;rsquo;te şifre çözmeyi, bir frontend uygulaması ile bir backend servisi arasında güvenli veri alışverişi sağlayacak uygulamayı ele alacağım.&lt;/p&gt;
&lt;h2 id="understanding-rsa-encryption"&gt;Understanding RSA Encryption&lt;/h2&gt;
&lt;p&gt;RSA (Rivest-Shamir-Adleman), güvenli veri iletimi için yaygın olarak kullanılan ilk açık anahtarlı kriptosistemlerden biridir. İki anahtarı içerir: açık anahtar, mesajları şifrelemek için açıkça paylaşılabilen ve onları şifrelemek için gizli tutulması gereken bir özel anahtar. RSA&amp;rsquo;nın gücü, iki büyük asal sayının çarpımını faktörleme zorluğunda yatar, bu da güvenliğini destekler.&lt;/p&gt;
&lt;h2 id="generation-of-publicprivate-key-pairs-with-openssl"&gt;Generation of Public/Private key pairs with OpenSSL&lt;/h2&gt;
&lt;p&gt;RSA şifreleme ve şifre çözmenin uygulanmasındaki temel adımlardan biri, açık ve özel anahtar çiftlerinin oluşturulmasıdır. OpenSSL, kriptografi için sağlam bir açık kaynaklı araç olan bu anahtarları oluşturmanın basit bir yolunu sunar. Ancak, bu adım isteğe bağlıdır. .NET kütüphaneleri başarılı bir şekilde RSA anahtarları oluşturur. Ancak, bu gibi açık kaynaklı araçları kullanmak bize standartların güvencesini sağlar. Bu bölüm, OpenSSL&amp;rsquo;i kullanarak RSA açık/özel anahtar çifti oluşturma sürecini adım adım ele alacaktır.&lt;/p&gt;
&lt;p&gt;Private anahtar oluşturma işlemiyle başlayabiliriz:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;$ openssl genrsa -out privatekey.pem &lt;span style="color:#ae81ff"&gt;2048&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;$ cat privatekey.pem
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;-----BEGIN PRIVATE KEY-----
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDuOitcIPd5UpGN
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;/4GEwNhc4fAmGP6fREJJk/byRgiLiWcyXwdoPwXdXjKSjkSIcETdz/hLAlb6+zNY
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Gi4Ap0S5flZSRiKh60xWRA4vZVgsLqhEi2IsclSlWu3R7KD5dkBGsyf5xlCfNSs4
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;DbYYMgrXXZxXQ0c4qCikpnEB6OpqSzm19Cwrdq9GwophoAPxnf00d7S+y5QpRa+l
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Ca70xQ42FxBpMuK76a1g6i+JtmPgTYqXZ0FCMnUEzBq+u3N8LKoNzcSNdrLgvmHQ
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;oOLFA/9BPe34YgLxoCUi+HhEiLSnvgz/Kn1yprvsz37baYizU/YWFG8s80SPmFsY
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;JARxvZiJAgMBAAECggEAEboc42Jln+7Lu34NmIAUKZc7fE5EVjwpVZnP2Lfq510+
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Y2JsZe64pEAf8cVp5qA6E6pn3scKC2uZZr8t+Kj5xXbX/A/RgGyGte2jA5ZeQQ1o
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;wo7/q3CHiXVyeHpMATwyLMCkoyLFeY9mi6mDiRniMiP6YAj22gmzWoszIhH/rZCO
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;NjdpWRsIkFp8jIiyRhhoaBoJtZ1qV3twk8HH590rgjS7QUGJWK9uUsi5SDh69MsR
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;r/SJ00gTMYFozFd6dB29aKj/p8fSk6qzh2noxU37AuiVc1A2H8Z6SAfRUDSvPkxA
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;FdqVr62iLWmypBX2VYQ2p1pH1N0v8oG7NTxtrxgRTQKBgQD3YdxKNT0dWZJRGPFC
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;DIRZVn86UgDAHRw29aToLARnepE5XOrLSmlGMqbK8vzRyUnlctJyG7x5ure2BMp4
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;gKSLuhz48n7ZvpHyCVnNsqqkc21iM/whL/yKiI11Jz5koXmRDltz9IioQH/PwXvD
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;RJbjxISHjG5YEKlVmztWtTup/QKBgQD2hqouE8JLePAUzK3SlfgVTlmaHDUeJev4
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;u95RWPnMNOOwLUWbGYiKBoJ8uc2nSHmxIvEvEPGNwafR6etf0YocpwDfF5pnPDjk
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;yA4hOdZrCyYa/UDqkB5pHTOZj5iVct1hBNbiANz6kL7Pl+H221U+HxD9rhaDPDez
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;gdYPhLF4fQKBgQCdi2QomlkufOHS5eiXoLMU1iI2eQzjTGawlaYM+iNf503fU05w
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;YxZCT3WroC3kSKXYC1T8uK9CcugWclyje4HPPpq+7GhesZ/unYfkmRlVm/EYbnTu
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;icnyS96Ssohou/FYsOULJrt1M4ZyQA1aoS7bJUclsAiB6zJ8Q1z57ndt3QKBgEwa
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;9p6S6wBxrWw+Y8sHETdCoNa6rotIGbkIBnIGjddE3KWe1EY1c7lomQ/4LEzgSvEs
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;YFivWmLwzeY32LoT7hc6V0KH/tqv9MBsIjCPsmoJXxIl7Mx9AWZh5XQaqHg6pa01
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;2UCcE5wm40EjGlcjDwXGgXBPNhP9mxSHmJXh2QfhAoGANouUrF3qv8ofb1HGrUXx
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;DO/WkGMzwbkNmwwdgcGYVsGr3Dv/zx1y3TJEvqNh0ViVgmfdYveCo3VrSawq890U
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;GJZXld2KFE1icEJpNoURkM9okZMzTDIMs+r4vq5Or2jB04mMuoCbG6rjfqUMtkll
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;oAIFOSKH+F3TrXjNgy4/juo&lt;span style="color:#f92672"&gt;=&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;-----END PRIVATE KEY-----
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Başarıyla private anahtarı oluşturduktan sonra, oluşturduğumuz private anahtar ile public anahtarı oluşturabiliriz.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;$ openssl rsa -in privatekey.pem -outform PEM -pubout -out publickey.pem
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;$ cat privatekey.pem
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;-----BEGIN PUBLIC KEY-----
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA7jorXCD3eVKRjf+BhMDY
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;XOHwJhj+n0RCSZP28kYIi4lnMl8HaD8F3V4yko5EiHBE3c/4SwJW+vszWBouAKdE
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;uX5WUkYioetMVkQOL2VYLC6oRItiLHJUpVrt0eyg+XZARrMn+cZQnzUrOA22GDIK
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;112cV0NHOKgopKZxAejqaks5tfQsK3avRsKKYaAD8Z39NHe0vsuUKUWvpQmu9MUO
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;NhcQaTLiu+mtYOovibZj4E2Kl2dBQjJ1BMwavrtzfCyqDc3EjXay4L5h0KDixQP/
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;QT3t+GIC8aAlIvh4RIi0p74M/yp9cqa77M9+22mIs1P2FhRvLPNEj5hbGCQEcb2Y
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;iQIDAQAB
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;-----END PUBLIC KEY-----
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="setting-up-the-environment-for-net-rsa-decryption"&gt;Setting Up the Environment for .NET RSA Decryption&lt;/h2&gt;
&lt;p&gt;.NET şifre çözme için .NET 8&amp;rsquo;i kullanacağım ve System.Security.Cryptography ad alanınından faydalanacağım. RSA şifre çözme için başka bir nuget paketine ihtiyacımız yoktur, bunun için şöyle bir Helper sınıf oluşturdum.&lt;/p&gt;
&lt;p&gt;RsaKey&amp;rsquo;i appsettings.json dosyasından okuyoruz. RsaKey alanında yeni satırlar için \n eklemeniz gerekir. Bu anahtarı appsettings.json dosyasına koyduk çünkü her istekte aynı anahtarı kullanmak istiyoruz. Ancak .NET kütüphanesinin sorumluluğuna bırakırsanız, her başlangıçta yeni bir anahtar oluşturulacaktır.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-json" data-lang="json"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// appsettings.json
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;Logging&amp;#34;&lt;/span&gt;: {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;LogLevel&amp;#34;&lt;/span&gt;: {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;Default&amp;#34;&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;Information&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;Microsoft.AspNetCore&amp;#34;&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;Warning&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; },
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;AllowedHosts&amp;#34;&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;*&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;RsaKey&amp;#34;&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;-----BEGIN PRIVATE KEY-----\nMIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQCuan+y++Eqc2Vx3QIt9gv0r6rzfcxpsBRubcWCmI+8tqb40oflv5dViPSiCSWNgg5xKk7K8WTeEQQ6NDg1IJ2OwoQ2dfzat5qlpfV9EeF3u8iTY/hyQaaYFwB77cV9t5Czb8oG/+IIOByorJVds9tAoKjssKUZ3W9IU7ffElZjZrbjoiy/H4z8u9fOq8IL9Zf3pHgzv2FxF4BPJamqr4s1VtMqGJ5g18wV1OD9gcz9pJOHHUVieZ0+xP4WD4+1wCv/uwEgIcqmEs0os6birHZL1X/CSqBSPc8e/+kkZyyzoF+MBWPzvAwmW32alIxz2ZV0Z+jJtoOrh/qVqbrGAf+RAgMBAAECggEAEWYu4IJHg0ZZOZtgRwj7RtjKXzluraFi3GRHdoB1IFCBRiOsamMrO91qeAqdDCmL+saLbyvXEd8VMqA4djZPeWkWqt8ozwHPY9RzMZuZyCm7t9Zad71sWtI6mmJNF/46qWfOudWHbSX51+rFiMAzMFaGm3wAsFyaaBbv6gkohIhVrrMvpIuV8X8JNI8/VNlOR6vExHd/3uWKi8vPrFEFTNQ1UE+WJDkVojcmx0t63jlP8C/O4ofaJeCYRkAaM9+FXnM+jVNf0qZU+3JGYmy1R/B7L+LyrbG2uYTaBa1Ba9zGsXD1HQ4dctK+MltBd4p+MWgHAQV+xuVmGGDkylpb8QKBgQDP9zY4UzLmnt0werJF9SGmAJU51Y/+gannUC/+XYqnK/NA5iUDR86rlYCJbqQCceyMYJSYuePumygWOVL9rHnu1/fTMG/sXnRktmTSFemgeCYfPyn8/KeZKn6E5xw8GbtfLJLA8uM4DakDf9vSpilKDYT4UbZyKgq7/yodPXbgCQKBgQDWs4gL4yOtDLAY/OQKZvoyXClapldkVFRzf97pBJQGLYfdTzkVFviWsgI/yrA+t5tlzaWtZieuv2KsSmazVyO2W5ei9HILx4yRFK7K56OM5o7wqjN62ZmF0R+422YNaAyqYAd9nhLjgdsrmZ9FTxhQ91asBSH1AO3j6xaZUvh1SQKBgAZuo/ur/xgI89hrAxaM1WSYAgWO6Gw7wHCKF2HrrL0s69InDCAE2YyPDDGz/ViiA2n4FsB+h2E65UuCrGFyMzdC8MRUbDHIXhs7VPT2fopbDPrMblUHz3s6SD1+FG57cUMpUsSq/oIeUgrsqnTidMZ4kpNHm7f+OuTDqJ7M5t9ZAoGAAfm2570YR/BU8nXpNztJVAtLCh17sl2gRUvI5kX3grMKi/u9n7cNZH2Qzbt0sa8Iy///ZUAKX249Xy50EXRczMG8/G/ZWMhmP7N8BDvrYlGAwTAftyKnafbJnu7N2pO5ghvOFdbNf7BjLtyD/aRDqgMMlhqZ/GIczjsMgy6jQJkCgYATxkjdW/mnWLDDZAYm1V10Gnm7YDMwX3gKdhpoYleeeapZiDlurkR3YAEvQezcnhv3NpjaKQtF1/3Q0fZJ0wBc5FflaevHRjHTa0IrU5QIa48dLDx+jxn+cUCFx9kirkb7JcfJLZWtToPMLfCZt324P2ogZgWBwweVPiz1/voEXw==\n-----END PRIVATE KEY-----&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-cs" data-lang="cs"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// Helpers/RsaEncryptionHelper.cs &lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;class&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;RsaEncryptionHelper&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;private&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;readonly&lt;/span&gt; RSA _rsa = RSA.Create();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; RsaEncryptionHelper(IConfiguration configuration)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; privateKeyPem = configuration[&lt;span style="color:#e6db74"&gt;&amp;#34;RsaKey&amp;#34;&lt;/span&gt;];
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; (&lt;span style="color:#66d9ef"&gt;string&lt;/span&gt;.IsNullOrEmpty(privateKeyPem)) &lt;span style="color:#66d9ef"&gt;throw&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; ArgumentNullException(nameof(privateKeyPem));
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ImportKey(privateKeyPem);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;void&lt;/span&gt; ImportKey(&lt;span style="color:#66d9ef"&gt;string&lt;/span&gt; privateKey)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; _rsa.ImportFromPem(privateKey.ToCharArray());
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;string&lt;/span&gt; GetPublicKey()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; publicKey = _rsa.ExportSubjectPublicKeyInfoPem();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; publicKey;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;string&lt;/span&gt; GetPrivateKey()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; privateKey = _rsa.ExportPkcs8PrivateKeyPem();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; privateKey;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;string&lt;/span&gt; Encrypt(&lt;span style="color:#66d9ef"&gt;string&lt;/span&gt; data)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; dataBytes = Encoding.UTF8.GetBytes(data);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; encryptedData = _rsa.Encrypt(dataBytes, RSAEncryptionPadding.OaepSHA256);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; Convert.ToBase64String(encryptedData);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;string&lt;/span&gt; Decrypt(&lt;span style="color:#66d9ef"&gt;string&lt;/span&gt; data)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; dataBytes = Convert.FromBase64String(data);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; decryptedData = _rsa.Decrypt(dataBytes, RSAEncryptionPadding.OaepSHA256);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; Encoding.UTF8.GetString(decryptedData);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Ve bu helper sınıfı EncryptionController içinde kullanarak helper sınıfın methodlarını kullanın. Production ortamlarında, PrivateKey&amp;rsquo;in kimseye açığa çıkarılmaması gerekir. Bu gizli tutulmalıdır.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-cs" data-lang="cs"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// Controllers/EncryptionController.cs &lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;[ApiController]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;[Route(&amp;#34;api/[controller]&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;)]
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;class&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;EncryptionController&lt;/span&gt; : ControllerBase
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;private&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;readonly&lt;/span&gt; RsaEncryptionHelper _rsaEncryptionHelper;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; EncryptionController(RsaEncryptionHelper rsaEncryptionHelper)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; _rsaEncryptionHelper = rsaEncryptionHelper;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt; [HttpPost(&amp;#34;Encrypt&amp;#34;)]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;string&lt;/span&gt; Encrypt([FromBody] EncryptionRequest request)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; _rsaEncryptionHelper.Encrypt(request.Data);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt; [HttpPost(&amp;#34;Decrypt&amp;#34;)]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;string&lt;/span&gt; Decrypt([FromBody] EncryptionRequest request)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; _rsaEncryptionHelper.Decrypt(request.Data);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt; [HttpGet(&amp;#34;PublicKey&amp;#34;)]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;string&lt;/span&gt; PublicKey()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; _rsaEncryptionHelper.GetPublicKey();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt; [HttpGet(&amp;#34;PrivateKey&amp;#34;)]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;string&lt;/span&gt; PrivateKey()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; _rsaEncryptionHelper.GetPrivateKey();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt; [HttpPost(&amp;#34;ImportKey&amp;#34;)]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;void&lt;/span&gt; ImportKey([FromBody] ImportKeyRequest request)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; _rsaEncryptionHelper.ImportKey(request.PrivateKey);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;record&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;ImportKeyRequest&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;string&lt;/span&gt; PrivateKey { &lt;span style="color:#66d9ef"&gt;get&lt;/span&gt;; &lt;span style="color:#66d9ef"&gt;init&lt;/span&gt;; } = &lt;span style="color:#66d9ef"&gt;null&lt;/span&gt;!;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;record&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;EncryptionRequest&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;string&lt;/span&gt; Data { &lt;span style="color:#66d9ef"&gt;get&lt;/span&gt;; &lt;span style="color:#66d9ef"&gt;init&lt;/span&gt;; } = &lt;span style="color:#66d9ef"&gt;null&lt;/span&gt;!;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Bu backend tarafında yaptığımız geliştirmelerden sonra JavaScript tarafına geçerek devam edebiliriz. Yaptıklarımız sayesinde, public anahtarımızı istemciyle endpoint&amp;rsquo;ten paylaşabiliriz, ardından istemciler bu public anahtarı kullanarak verilerini güvenli iletişim için şifreleyebilirler.&lt;/p&gt;
&lt;h2 id="setting-up-the-development-environment-for-javascript-rsa-encryption"&gt;Setting Up the Development environment for JavaScript RSA Encryption&lt;/h2&gt;
&lt;p&gt;rnekte, modern tarayıcılara yerleşik olan ve RSA şifrelemeyi içeren kriptografik işlemleri sağlayan Web Crypto API&amp;rsquo;sini kullanacağız. Bu nedenle, herhangi bir npm paketi indirmenize gerek yoktur.&lt;/p&gt;
&lt;p&gt;JavaScript ile tarayıcı uygulaması geliştireceksem genellikle Vite ile başlarım. Bunu yapmak için, aşağıdaki komutu girerek vanilla js veya React ile bir proje başlatabiliriz. Gösterim amaçlı küçük bir React projesi geliştireceğim, ancak vanilla js ile devam etmeniz durumda bile yeterli olacaktır.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;npm create vite@latest
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Bundan sonra src/utils/RsaEncryptionHelper.js dosyasını oluşturup o dosyayı aşağıdaki gibi dolduracağım. Bu dosyanın method&amp;rsquo;larıyla RSA algoritması ile şifreleyebilir ve şifresini çözebilirsiniz, ancak şifre çözme genellikle backend sorumluluğundadır çünkü şifre çözme private anahtar gerektirir ve güvenlik nedenlerinden dolayı javascript istemcisinin private anahtarları herkese açık olarak saklamaması veya private anahtarlara erişmemesi gerekir. Ancak SSL&amp;rsquo;deki gibi iki yönlü anahtar şifrelemeye benzer bir şey denemek isteyen olursa diye bunuda paylaşıyorum.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-js" data-lang="js"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// src/utils/RsaEncryptionHelper.js
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;const&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;encryptAlgorithm&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;name&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;RSA-OAEP&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;modulusLength&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;2048&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;publicExponent&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Uint8Array&lt;/span&gt;([&lt;span style="color:#ae81ff"&gt;1&lt;/span&gt;, &lt;span style="color:#ae81ff"&gt;0&lt;/span&gt;, &lt;span style="color:#ae81ff"&gt;1&lt;/span&gt;]),
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;extractable&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;true&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;hash&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;name&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;SHA-256&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; },
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;};
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;function&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;arrayBufferToBase64&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;arrayBuffer&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;const&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;byteArray&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Uint8Array&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;arrayBuffer&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;let&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;byteString&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;for&lt;/span&gt; (&lt;span style="color:#66d9ef"&gt;let&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;i&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;0&lt;/span&gt;; &lt;span style="color:#a6e22e"&gt;i&lt;/span&gt; &lt;span style="color:#f92672"&gt;&amp;lt;&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;byteArray&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;byteLength&lt;/span&gt;; &lt;span style="color:#a6e22e"&gt;i&lt;/span&gt;&lt;span style="color:#f92672"&gt;++&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;byteString&lt;/span&gt; &lt;span style="color:#f92672"&gt;+=&lt;/span&gt; String.&lt;span style="color:#a6e22e"&gt;fromCharCode&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;byteArray&lt;/span&gt;[&lt;span style="color:#a6e22e"&gt;i&lt;/span&gt;]);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;const&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;b64&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; window.&lt;span style="color:#a6e22e"&gt;btoa&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;byteString&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;b64&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;function&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;base64StringToArrayBuffer&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;b64str&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;const&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;byteStr&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;atob&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;b64str&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;const&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;bytes&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Uint8Array&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;byteStr&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;length&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;for&lt;/span&gt; (&lt;span style="color:#66d9ef"&gt;let&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;i&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;0&lt;/span&gt;; &lt;span style="color:#a6e22e"&gt;i&lt;/span&gt; &lt;span style="color:#f92672"&gt;&amp;lt;&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;byteStr&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;length&lt;/span&gt;; &lt;span style="color:#a6e22e"&gt;i&lt;/span&gt;&lt;span style="color:#f92672"&gt;++&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;bytes&lt;/span&gt;[&lt;span style="color:#a6e22e"&gt;i&lt;/span&gt;] &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;byteStr&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;charCodeAt&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;i&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;bytes&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;buffer&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;function&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;convertPemToArrayBuffer&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;pem&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;const&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;lines&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;pem&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;split&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;\n&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;let&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;encoded&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;for&lt;/span&gt; (&lt;span style="color:#66d9ef"&gt;let&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;i&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;0&lt;/span&gt;; &lt;span style="color:#a6e22e"&gt;i&lt;/span&gt; &lt;span style="color:#f92672"&gt;&amp;lt;&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;lines&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;length&lt;/span&gt;; &lt;span style="color:#a6e22e"&gt;i&lt;/span&gt;&lt;span style="color:#f92672"&gt;++&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; (
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;lines&lt;/span&gt;[&lt;span style="color:#a6e22e"&gt;i&lt;/span&gt;].&lt;span style="color:#a6e22e"&gt;trim&lt;/span&gt;().&lt;span style="color:#a6e22e"&gt;length&lt;/span&gt; &lt;span style="color:#f92672"&gt;&amp;gt;&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;0&lt;/span&gt; &lt;span style="color:#f92672"&gt;&amp;amp;&amp;amp;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;lines&lt;/span&gt;[&lt;span style="color:#a6e22e"&gt;i&lt;/span&gt;].&lt;span style="color:#a6e22e"&gt;indexOf&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;-----BEGIN RSA PRIVATE KEY-----&amp;#34;&lt;/span&gt;) &lt;span style="color:#f92672"&gt;&amp;lt;&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;0&lt;/span&gt; &lt;span style="color:#f92672"&gt;&amp;amp;&amp;amp;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;lines&lt;/span&gt;[&lt;span style="color:#a6e22e"&gt;i&lt;/span&gt;].&lt;span style="color:#a6e22e"&gt;indexOf&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;-----BEGIN PRIVATE KEY-----&amp;#34;&lt;/span&gt;) &lt;span style="color:#f92672"&gt;&amp;lt;&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;0&lt;/span&gt; &lt;span style="color:#f92672"&gt;&amp;amp;&amp;amp;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;lines&lt;/span&gt;[&lt;span style="color:#a6e22e"&gt;i&lt;/span&gt;].&lt;span style="color:#a6e22e"&gt;indexOf&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;-----BEGIN PUBLIC KEY-----&amp;#34;&lt;/span&gt;) &lt;span style="color:#f92672"&gt;&amp;lt;&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;0&lt;/span&gt; &lt;span style="color:#f92672"&gt;&amp;amp;&amp;amp;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;lines&lt;/span&gt;[&lt;span style="color:#a6e22e"&gt;i&lt;/span&gt;].&lt;span style="color:#a6e22e"&gt;indexOf&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;-----END RSA PRIVATE KEY-----&amp;#34;&lt;/span&gt;) &lt;span style="color:#f92672"&gt;&amp;lt;&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;0&lt;/span&gt; &lt;span style="color:#f92672"&gt;&amp;amp;&amp;amp;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;lines&lt;/span&gt;[&lt;span style="color:#a6e22e"&gt;i&lt;/span&gt;].&lt;span style="color:#a6e22e"&gt;indexOf&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;-----END PRIVATE KEY-----&amp;#34;&lt;/span&gt;) &lt;span style="color:#f92672"&gt;&amp;lt;&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;0&lt;/span&gt; &lt;span style="color:#f92672"&gt;&amp;amp;&amp;amp;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;lines&lt;/span&gt;[&lt;span style="color:#a6e22e"&gt;i&lt;/span&gt;].&lt;span style="color:#a6e22e"&gt;indexOf&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;-----END PUBLIC KEY-----&amp;#34;&lt;/span&gt;) &lt;span style="color:#f92672"&gt;&amp;lt;&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;0&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ) {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;encoded&lt;/span&gt; &lt;span style="color:#f92672"&gt;+=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;lines&lt;/span&gt;[&lt;span style="color:#a6e22e"&gt;i&lt;/span&gt;].&lt;span style="color:#a6e22e"&gt;trim&lt;/span&gt;();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;base64StringToArrayBuffer&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;encoded&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;export&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;const&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;encryptRsa&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;async&lt;/span&gt; (&lt;span style="color:#a6e22e"&gt;str&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;pemString&lt;/span&gt;) =&amp;gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;try&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// convert string into ArrayBuffer
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;const&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;encodedPlaintext&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;TextEncoder&lt;/span&gt;().&lt;span style="color:#a6e22e"&gt;encode&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;str&lt;/span&gt;).&lt;span style="color:#a6e22e"&gt;buffer&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;const&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;keyArrayBuffer&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;convertPemToArrayBuffer&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;pemString&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// import public key
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;const&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;secretKey&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;await&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;crypto&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;subtle&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;importKey&lt;/span&gt;(
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;spki&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;keyArrayBuffer&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;encryptAlgorithm&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;true&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; [&lt;span style="color:#e6db74"&gt;&amp;#34;encrypt&amp;#34;&lt;/span&gt;]
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; );
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// encrypt the text with the public key
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;const&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;encrypted&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;await&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;crypto&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;subtle&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;encrypt&lt;/span&gt;(
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;name&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;RSA-OAEP&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; },
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;secretKey&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;encodedPlaintext&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; );
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// store data into base64 string
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;arrayBufferToBase64&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;encrypted&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; } &lt;span style="color:#66d9ef"&gt;catch&lt;/span&gt; (&lt;span style="color:#a6e22e"&gt;error&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;console&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;error&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;Encryption Error:&amp;#34;&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;error&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;};
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;export&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;const&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;decryptRsa&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;async&lt;/span&gt; (&lt;span style="color:#a6e22e"&gt;str&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;pemString&lt;/span&gt;) =&amp;gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;try&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// convert base64 encoded input string into ArrayBuffer
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;const&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;encodedPlaintext&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;base64StringToArrayBuffer&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;str&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;const&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;keyArrayBuffer&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;convertPemToArrayBuffer&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;pemString&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// import private key
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;const&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;secretKey&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;await&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;crypto&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;subtle&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;importKey&lt;/span&gt;(
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;pkcs8&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;keyArrayBuffer&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;encryptAlgorithm&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;true&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; [&lt;span style="color:#e6db74"&gt;&amp;#34;decrypt&amp;#34;&lt;/span&gt;]
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; );
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// decrypt the text with the public key
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;const&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;decrypted&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;await&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;crypto&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;subtle&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;decrypt&lt;/span&gt;(
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;name&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;RSA-OAEP&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; },
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;secretKey&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;encodedPlaintext&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; );
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// decode the decrypted ArrayBuffer output
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;const&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;uint8Array&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Uint8Array&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;decrypted&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;const&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;textDecoder&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;TextDecoder&lt;/span&gt;();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;const&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;decodedString&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;textDecoder&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;decode&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;uint8Array&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;decodedString&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; } &lt;span style="color:#66d9ef"&gt;catch&lt;/span&gt; (&lt;span style="color:#a6e22e"&gt;error&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;console&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;error&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;Decryption Error:&amp;#34;&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;error&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;};
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Bu yardımcı yöntemleri kullanarak küçük bir React projesi geliştirdim. Verileri şifrelemek ve şifresini çözmek için .NET API&amp;rsquo;den paylaşılacak genel ve özel anahtarlarınızı kullanabilirsiniz, ancak şifre çözme işleminin arka uçta gizli tutulan özel anahtarla yapılması gerektiğini unutmayın.&lt;/p&gt;
&lt;a href="https://lecture-rsa-dotnet-javascript.vercel.app" target="_blank"&gt;
&lt;img src="https://berkselvi.dev/img/how-to-use-rsa-for-encryption-in-javascript-and-decryption-in-net/rsa-encryption.webp" alt="RSA Encryption (lecture-rsa-dotnet-javascript.vercel.app)" loading="lazy" /&gt;
&lt;p class="image-sub-title"&gt;RSA Encryption (lecture-rsa-dotnet-javascript.vercel.app)&lt;/p&gt;
&lt;/a&gt;
&lt;p&gt;Kaynak koduna erişmek isterseniz projenin tamamını GitHub hesabımda bulabilirsiniz:&lt;/p&gt;
&lt;p&gt;&lt;a href="https://github.com/berkslv/lecture-rsa-dotnet-javascript"&gt;GitHub - berkslv/lecture-rsa-dotnet-javascript&lt;/a&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="sonuç"&gt;Sonuç&lt;/h2&gt;
&lt;p&gt;Okuduğunuz için teşekkürler! 🎉 Yazılım geliştirme alanındaki araştırmalarımı kaçırmamak için &lt;a href="https://x.com/berkslv"&gt;@berkslv&lt;/a&gt; adresinden takipte kalabilirsiniz.&lt;/p&gt;</content></item><item><title>Ocelot ve Keycloak'i birlikte kullanarak API Gateway'den Mikroservis güvenliğini nasıl sağlarız?</title><link>https://berkselvi.dev/tr/posts/how-to-use-ocelot-and-keycloak-together-to-secure-microservices-from-api-gateway/</link><pubDate>Tue, 30 Jan 2024 00:00:00 +0300</pubDate><guid>https://berkselvi.dev/tr/posts/how-to-use-ocelot-and-keycloak-together-to-secure-microservices-from-api-gateway/</guid><description>&lt;p&gt;Mikroservis mimarisinin dinamik dünyasında, güçlü bir güvenliğe olan ihtiyaç çok daha önemli hale gelmiştir. Kuruluşlar uygulamalarını daha küçük, bağımsız bir şekilde deploy edilebilir servislere böldükçe, bu servisler arasında alınan ve gönderilen verilerin bütünlüğünü ve gizliliğini sağlamak kritik bir önem haline gelir.&lt;/p&gt;
&lt;p&gt;Bu blog yazısında, Ocelot&amp;rsquo;u kullanan ve güçlü bir açık kaynak kimlik ve erişim yönetimi çözümü olan Keycloak ile mikroservisleri güvence altına almak için kapsamlı bir çözümü keşfedeceğiz. Keycloak&amp;rsquo;u API Gateway&amp;rsquo;in arkasına yerleştirerek, bu entegrasyonun kaynakları koruduğunu, diğer hizmetlere yönelik istekleri kimlik doğruladığını ve taleplere erişimi yetkilendirdiğini claim yapılarını kullanarak sunar. Bu, mikroservis ekosisteminiz için sorunsuz ve güvenli bir iletişim çerçevesi sunar. Başlayalım.&lt;/p&gt;</description><content>&lt;p&gt;Mikroservis mimarisinin dinamik dünyasında, güçlü bir güvenliğe olan ihtiyaç çok daha önemli hale gelmiştir. Kuruluşlar uygulamalarını daha küçük, bağımsız bir şekilde deploy edilebilir servislere böldükçe, bu servisler arasında alınan ve gönderilen verilerin bütünlüğünü ve gizliliğini sağlamak kritik bir önem haline gelir.&lt;/p&gt;
&lt;p&gt;Bu blog yazısında, Ocelot&amp;rsquo;u kullanan ve güçlü bir açık kaynak kimlik ve erişim yönetimi çözümü olan Keycloak ile mikroservisleri güvence altına almak için kapsamlı bir çözümü keşfedeceğiz. Keycloak&amp;rsquo;u API Gateway&amp;rsquo;in arkasına yerleştirerek, bu entegrasyonun kaynakları koruduğunu, diğer hizmetlere yönelik istekleri kimlik doğruladığını ve taleplere erişimi yetkilendirdiğini claim yapılarını kullanarak sunar. Bu, mikroservis ekosisteminiz için sorunsuz ve güvenli bir iletişim çerçevesi sunar. Başlayalım.&lt;/p&gt;
&lt;h2 id="create-keycloak-instance"&gt;Create Keycloak instance&lt;/h2&gt;
&lt;p&gt;Docker içinde Keycloak&amp;rsquo;u çalıştırmak çok kolaydır. Bu Docker dosyasını projenizin /Identity dizinine yerleştirin. Tüm kodları yazının sonundaki GitHub reposunda bulabilirsiniz.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-Dockerfile" data-lang="Dockerfile"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;FROM&lt;/span&gt; &lt;span style="color:#e6db74"&gt;quay.io/keycloak/keycloak:latest&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;as&lt;/span&gt; &lt;span style="color:#e6db74"&gt;builder&lt;/span&gt; &lt;span style="color:#960050;background-color:#1e0010"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#960050;background-color:#1e0010"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# Enable health and metrics support &lt;/span&gt;&lt;span style="color:#960050;background-color:#1e0010"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;ENV&lt;/span&gt; KC_HEALTH_ENABLED&lt;span style="color:#f92672"&gt;=&lt;/span&gt;true
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;ENV&lt;/span&gt; KC_METRICS_ENABLED&lt;span style="color:#f92672"&gt;=&lt;/span&gt;true
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# Configure a database vendor &lt;/span&gt;&lt;span style="color:#960050;background-color:#1e0010"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;ENV&lt;/span&gt; KC_DB&lt;span style="color:#f92672"&gt;=&lt;/span&gt;postgres
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;WORKDIR&lt;/span&gt; &lt;span style="color:#e6db74"&gt;/opt/keycloak&lt;/span&gt; &lt;span style="color:#960050;background-color:#1e0010"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# for demonstration purposes only, please make sure to use proper certificates in production instead &lt;/span&gt;&lt;span style="color:#960050;background-color:#1e0010"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;RUN&lt;/span&gt; keytool -genkeypair -storepass password -storetype PKCS12 -keyalg RSA -keysize &lt;span style="color:#ae81ff"&gt;2048&lt;/span&gt; -dname &lt;span style="color:#e6db74"&gt;&amp;#34;CN=server&amp;#34;&lt;/span&gt; -alias server -ext &lt;span style="color:#e6db74"&gt;&amp;#34;SAN:c=DNS:localhost,IP:127.0.0.1&amp;#34;&lt;/span&gt; -keystore conf/server.keystore &lt;span style="color:#960050;background-color:#1e0010"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;RUN&lt;/span&gt; /opt/keycloak/bin/kc.sh build &lt;span style="color:#960050;background-color:#1e0010"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#960050;background-color:#1e0010"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;FROM&lt;/span&gt; &lt;span style="color:#e6db74"&gt;quay.io/keycloak/keycloak:latest&lt;/span&gt; &lt;span style="color:#960050;background-color:#1e0010"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;COPY&lt;/span&gt; --from&lt;span style="color:#f92672"&gt;=&lt;/span&gt;builder /opt/keycloak/ /opt/keycloak/ &lt;span style="color:#960050;background-color:#1e0010"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#960050;background-color:#1e0010"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# change these values to point to a running postgres instance &lt;/span&gt;&lt;span style="color:#960050;background-color:#1e0010"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;ENTRYPOINT&lt;/span&gt; [&lt;span style="color:#e6db74"&gt;&amp;#34;/opt/keycloak/bin/kc.sh&amp;#34;&lt;/span&gt;]&lt;span style="color:#960050;background-color:#1e0010"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;And create docker-compose.yml file that access /Identity/Dockerfile and builds image from it. We can use base image rather than custom Dockerfile but with this approach you can customize much more features of Keycloak like frontend theme and secret keys.&lt;/p&gt;
&lt;p&gt;/Identity/Dockerfile dosyasına erişen ve ondan bir image oluşturan docker-compose.yml dosyasını oluşturun. Özel Docker dosyası yerine bir temel image kullanabiliriz, ancak bu yaklaşımla Keycloak&amp;rsquo;un frontend teması ve secret key bilgileri gibi çok daha fazla özelliğini özelleştirebilirsiniz.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;version&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;3&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;services&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;# PostgreSQL for keycloak &lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;secured-identity-db&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;container_name&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;secured-identity-db &lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;image&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;postgres:16-alpine &lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;ports&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#ae81ff"&gt;6063&lt;/span&gt;:&lt;span style="color:#ae81ff"&gt;5432&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;expose&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#ae81ff"&gt;6063&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;volumes&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#ae81ff"&gt;./data/secured-identity-db:/var/lib/postgresql/data &lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;restart&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;always &lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;environment&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#ae81ff"&gt;POSTGRES_PASSWORD=myStrongPassword123 &lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#ae81ff"&gt;POSTGRES_USER=keycloak &lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#ae81ff"&gt;POSTGRES_DB=keycloak &lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;networks&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#ae81ff"&gt;secured-network &lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;# Keycloak &lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;secured-identity&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;container_name&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;secured-identity &lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;build&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;./Keycloak &lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;command&lt;/span&gt;: [&lt;span style="color:#e6db74"&gt;&amp;#34;start-dev&amp;#34;&lt;/span&gt;]
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;ports&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#ae81ff"&gt;5053&lt;/span&gt;:&lt;span style="color:#ae81ff"&gt;8080&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;expose&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#ae81ff"&gt;5053&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;environment&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#ae81ff"&gt;KEYCLOAK_ADMIN=admin &lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#ae81ff"&gt;KEYCLOAK_ADMIN_PASSWORD=admin &lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#ae81ff"&gt;KC_HOSTNAME_URL=http://localhost:5050/identity &lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#ae81ff"&gt;KC_DB=postgres &lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#ae81ff"&gt;KC_DB_USERNAME=keycloak &lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#ae81ff"&gt;KC_DB_PASSWORD=myStrongPassword123 &lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#ae81ff"&gt;KC_DB_URL=jdbc:postgresql://secured-identity-db:5432/keycloak &lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;depends_on&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#ae81ff"&gt;secured-identity-db &lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;networks&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#ae81ff"&gt;secured-network &lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;networks&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;secured-network&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;driver&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;bridge &lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;volumes&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;secured-data&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;driver&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;local&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Ve sadece bu docker-compose.yml dosyasından docker&amp;rsquo;ı çalıştırmamız yeterlidir.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;docker compose build
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;docker compose up -d
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="put-keycloak-behind-ocelot-api-gateway-to-protect-resources"&gt;Put keycloak behind Ocelot API Gateway to protect resources&lt;/h2&gt;
&lt;img src="https://berkselvi.dev/img/how-to-use-ocelot-and-keycloak-together-to-secure-microservices-from-api-gateway/Microservice-architecture-with-Ocelot-and-Keycloak.webp" alt="Microservice architecture with Ocelot and Keycloak" loading="lazy" /&gt;
&lt;p class="image-sub-title"&gt;Microservice architecture with Ocelot and Keycloak&lt;/p&gt;
&lt;p&gt;Kimlik hizmetinizin (keycloak) güvenliğini sağlamak ve diğer hizmetler gibi istek alabilecek uç noktaları sınırlandırarak saldırı vektörünü azaltmak önemlidir. Keycloak admin paneline harici bir ağdan erişilmemelidir, bu özelliğe yalnızca bu ağdaki kişiler erişebilir. Daha fazla bilgi edinmek için &lt;a href="https://www.keycloak.org/server/reverseproxy"&gt;reverse proxy kullanma&lt;/a&gt; hakkındaki Keycloak dökümantasyonunu göz atın.&lt;/p&gt;
&lt;img src="https://berkselvi.dev/img/how-to-use-ocelot-and-keycloak-together-to-secure-microservices-from-api-gateway/Exposed-path-recommendations.webp" alt="Exposed path recommendations" loading="lazy" /&gt;
&lt;p class="image-sub-title"&gt;Exposed path recommendations&lt;/p&gt;
&lt;p&gt;Öncelikle Ocelot projesini oluşturalım. Burada .NET 8 kullanıyorum&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;dotnet new sln -n Secured
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;dotnet new webapi -o Secured.ApiGateway
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;cd Secured.ApiGateway/
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;dotnet add package Ocelot --version 22.0.1
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Projeyi başarıyla oluşturduktan sonra Program.cs dosyasını aşağıdaki gibi değiştirip root dizinde ocelot.json dosyasını oluşturun.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-cs" data-lang="cs"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; builder = WebApplication.CreateBuilder(args);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;builder.Configuration.AddJsonFile(&lt;span style="color:#e6db74"&gt;&amp;#34;ocelot.json&amp;#34;&lt;/span&gt;, optional: &lt;span style="color:#66d9ef"&gt;false&lt;/span&gt;, reloadOnChange: &lt;span style="color:#66d9ef"&gt;true&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;builder.Services.AddOcelot(builder.Configuration);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; app = builder.Build();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;await&lt;/span&gt; app.UseOcelot();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;await&lt;/span&gt; app.RunAsync();
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Bundan sonra bu ocelot.json dosyasını localhost:5053 üzerinde çalışan keycloak uygulamamıza erişimi sınırlamak için kullanabiliriz.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-json" data-lang="json"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;Routes&amp;#34;&lt;/span&gt;: [
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;DownstreamPathTemplate&amp;#34;&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;/realms/{everything}&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;DownstreamScheme&amp;#34;&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;http&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;DownstreamHostAndPorts&amp;#34;&lt;/span&gt;: [
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;Host&amp;#34;&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;localhost&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;Port&amp;#34;&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;5053&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ],
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;UpstreamPathTemplate&amp;#34;&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;/identity/realms/{everything}&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;UpstreamHttpMethod&amp;#34;&lt;/span&gt;: [ &lt;span style="color:#e6db74"&gt;&amp;#34;Get&amp;#34;&lt;/span&gt;, &lt;span style="color:#e6db74"&gt;&amp;#34;Post&amp;#34;&lt;/span&gt;, &lt;span style="color:#e6db74"&gt;&amp;#34;Put&amp;#34;&lt;/span&gt;, &lt;span style="color:#e6db74"&gt;&amp;#34;Delete&amp;#34;&lt;/span&gt; ]
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; },
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;DownstreamPathTemplate&amp;#34;&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;/resources/{everything}&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;DownstreamScheme&amp;#34;&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;http&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;DownstreamHostAndPorts&amp;#34;&lt;/span&gt;: [
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;Host&amp;#34;&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;localhost&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;Port&amp;#34;&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;5053&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ],
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;UpstreamPathTemplate&amp;#34;&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;/identity/resources/{everything}&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;UpstreamHttpMethod&amp;#34;&lt;/span&gt;: [ &lt;span style="color:#e6db74"&gt;&amp;#34;Get&amp;#34;&lt;/span&gt; ]
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; },
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;DownstreamPathTemplate&amp;#34;&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;/js/{everything}&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;DownstreamScheme&amp;#34;&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;http&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;DownstreamHostAndPorts&amp;#34;&lt;/span&gt;: [
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;Host&amp;#34;&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;localhost&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;Port&amp;#34;&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;5053&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ],
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;UpstreamPathTemplate&amp;#34;&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;/identity/js/{everything}&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;UpstreamHttpMethod&amp;#34;&lt;/span&gt;: [ &lt;span style="color:#e6db74"&gt;&amp;#34;Get&amp;#34;&lt;/span&gt; ]
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ],
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;GlobalConfiguration&amp;#34;&lt;/span&gt;: {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;BaseUrl&amp;#34;&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;https://localhost:5050&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Bu yapılandırmalardan sonra, API Gateway&amp;rsquo;in adresi olan localhost:5050&amp;rsquo;den keycloak&amp;rsquo;a erişebilirsiniz. Ancak, bu adres aracılığıyla Keycloak yönetici paneline erişemezsiniz çünkü erişimi sınırladık.&lt;/p&gt;
&lt;p&gt;Keycloak yapılandırmalarımızı yapmak için, hala local olarak erişilebilen keycloak adresinde, yani localhost:5053&amp;rsquo;te yönetici paneline giriş yapabiliriz. Deployment senaryosunda, yalnızca Ocelot&amp;rsquo;un harici internete erişimi olduğundan emin olacağız.&lt;/p&gt;
&lt;p&gt;localhost:5053 adresine admin kullanıcı adı ve admin şifresiyle giriş yapabiliriz. Bu kimlik bilgilerini docker-compose.yml dosyasından değiştirebiliriz.&lt;/p&gt;
&lt;img src="https://berkselvi.dev/img/how-to-use-ocelot-and-keycloak-together-to-secure-microservices-from-api-gateway/keycloak-1.webp" alt="Keycloak" loading="lazy" /&gt;
&lt;p&gt;Giriş yaptıktan sonra, kullanıcıları yönetmek için yeni bir alan (realm) oluşturmak ve uygulamamız için istemci oluşturmak için bu açılır menüden yeni alan (realm) oluştur seçeneğine tıklıyoruz. &amp;ldquo;Yeni alan (realm) oluştur&amp;rdquo; düğmesine tıkladıktan sonra, basitçe &lt;strong&gt;secured&lt;/strong&gt;&amp;lsquo;ı alan (realm) adı olarak girin ve diğer her şeyi olduğu gibi bırakın.&lt;/p&gt;
&lt;img src="https://berkselvi.dev/img/how-to-use-ocelot-and-keycloak-together-to-secure-microservices-from-api-gateway/keycloak-2.webp" alt="Keycloak" loading="lazy" /&gt;
&lt;p&gt;Clients sekmesinde client oluştur&amp;rsquo;a tıklayın. Client ID alanını &lt;strong&gt;postman&lt;/strong&gt; olarak adlandırın ve geçerli redirect URL&amp;rsquo;sini &lt;a href="https://oauth.pstmn.io/v1/callback"&gt;&lt;strong&gt;https://oauth.pstmn.io/v1/callback&lt;/strong&gt;&lt;/a&gt; olarak adlandırın. diğer şeyleri olduğu gibi bırakın.&lt;/p&gt;
&lt;img src="https://berkselvi.dev/img/how-to-use-ocelot-and-keycloak-together-to-secure-microservices-from-api-gateway/keycloak-3.webp" alt="Keycloak" loading="lazy" /&gt;
&lt;p&gt;Kullanıcılar sekmesinde Keycloak&amp;rsquo;ta oturum açacak kullanıcıları ekleyin.&lt;/p&gt;
&lt;img src="https://berkselvi.dev/img/how-to-use-ocelot-and-keycloak-together-to-secure-microservices-from-api-gateway/keycloak-4.webp" alt="Keycloak" loading="lazy" /&gt;
&lt;p&gt;Bu adımlardan sonra, yeni oluşturulan kullanıcıyla Keycloak&amp;rsquo;a giriş yaparak erişim belirteci alabiliriz. API gateway&amp;rsquo;e erişmek için Postman kullanıyorum. Postman&amp;rsquo;de Keycloak ile oturum açabilirsiniz. Yetkilendirme bölümünde OAuth 2.0&amp;rsquo;ı seçin ve aşağıdaki gibi yapılandırın ve Yeni Erişim Belgesi Al&amp;rsquo;a tıklayın. Postman, tarayıcı penceresini açacak ve sizi Keycloak giriş sayfasına yönlendirecektir, yeni kullanıcınızın kimlik bilgilerini girin. Admin kimlik bilgilerini girmeyin, bu kimlik bilgileri yalnızca ana (master) alan için geçerlidir.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-txt" data-lang="txt"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Grant type: Authorization code
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Auth URL: http://localhost:5050/identity/realms/secured/protocol/openid-connect/auth
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Access Token URL: http://localhost:5050/identity/realms/secured/protocol/openid-connect/token
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Client ID: postman
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Scope: openid profile roles
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Her şeyi doğru yaptıysanız, Postman ile API Gateway&amp;rsquo;in arkasındaki Keycloak&amp;rsquo;a başarıyla kimlik doğrulaması yaptınız 🎉&lt;/p&gt;
&lt;h2 id="authenticate-requests-to-other-services"&gt;Authenticate requests to other services&lt;/h2&gt;
&lt;p&gt;Ocelot&amp;rsquo;ta bir isteğin kimliğini doğrulamak istiyorsanız Program.cs ve ocelot.json&amp;rsquo;da aşağıdaki güncellemeleri yapın. MetadataAddress, JWT doğrulaması için gerekli anahtarları alır. Ancak ekstra network isteklerinden kaçınmak istiyorsanız anahtarları appsettings.json dosyasına yerleştirip kullanabilirsiniz.&lt;/p&gt;
&lt;p&gt;Ocelot.json dosyasındaki AuthenticationOptions ile kimlik doğrulama tamamlandı, burada JWT tokenımızı Bearer önekiyle birlikte göndereceğimizi ve JWT token doğrulamasını Program.cs&amp;rsquo;den yapılandırma ile yapacağımızı söylüyoruz.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-cs" data-lang="cs"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; builder = WebApplication.CreateBuilder(args);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;builder.Services
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; .AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; .AddJwtBearer(JwtBearerDefaults.AuthenticationScheme, o =&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; o.MetadataAddress = &lt;span style="color:#e6db74"&gt;&amp;#34;http://localhost:5050/identity/realms/secured/.well-known/openid-configuration&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; o.RequireHttpsMetadata = &lt;span style="color:#66d9ef"&gt;false&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; o.Authority = &lt;span style="color:#e6db74"&gt;&amp;#34;http://localhost:5050/realms/secured&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; o.Audience = &lt;span style="color:#e6db74"&gt;&amp;#34;account&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; });
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;builder.Configuration.AddJsonFile(&lt;span style="color:#e6db74"&gt;&amp;#34;ocelot.json&amp;#34;&lt;/span&gt;, optional: &lt;span style="color:#66d9ef"&gt;false&lt;/span&gt;, reloadOnChange: &lt;span style="color:#66d9ef"&gt;true&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;builder.Services.AddOcelot(builder.Configuration);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; app = builder.Build();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;app.UseAuthentication();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;app.UseAuthorization();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;await&lt;/span&gt; app.UseOcelot();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;await&lt;/span&gt; app.RunAsync();
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-json" data-lang="json"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;Routes&amp;#34;&lt;/span&gt;: [
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;DownstreamPathTemplate&amp;#34;&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;/get&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;DownstreamScheme&amp;#34;&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;https&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;DownstreamHostAndPorts&amp;#34;&lt;/span&gt;: [
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;Host&amp;#34;&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;httpbin.org&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;Port&amp;#34;&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;443&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ],
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;UpstreamPathTemplate&amp;#34;&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;/test&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;UpstreamHttpMethod&amp;#34;&lt;/span&gt;: [ &lt;span style="color:#e6db74"&gt;&amp;#34;Get&amp;#34;&lt;/span&gt; ],
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;AuthenticationOptions&amp;#34;&lt;/span&gt;: {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;AuthenticationProviderKey&amp;#34;&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;Bearer&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; },
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;//...
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="authorize-requests-to-other-services"&gt;Authorize requests to other services&lt;/h2&gt;
&lt;p&gt;Yetkilendirme biraz daha zor ve ek çalışma gerektirir çünkü Keycloak, kullanıcı rollerini JWT yükünde nested yani iç içe geçmiş olarak yerleştirir. Aşağıdaki gibi bir şeydir. Görüldüğü gibi, roller alanı realm_access alanı altına yerleştirilir. Bu, Ocelot&amp;rsquo;ta karışıklığa neden olur, çünkü Ocelot, talepleri string veya object değerleri olarak okur ve nested alanları değerlendirmez. Bu sorunu önlemek için Keycloak token eşlemesini yapılandırırız ve nested biçimi kullanmayız.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-json" data-lang="json"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;//...
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;exp&amp;#34;&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;1706600524&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;realm_access&amp;#34;&lt;/span&gt;: {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;roles&amp;#34;&lt;/span&gt;: [
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;offline_access&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;default-roles-microcommerce&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;uma_authorization&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;customer&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ]
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; },
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;resource_access&amp;#34;&lt;/span&gt;: {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;account&amp;#34;&lt;/span&gt;: {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;roles&amp;#34;&lt;/span&gt;: [
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;manage-account&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;manage-account-links&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;view-profile&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ]
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; },
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;scope&amp;#34;&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;openid email profile&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;preferred_username&amp;#34;&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;berkslv&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Bu özel eşleme için sol menüden Client scopes&amp;rsquo;a tıklayın ve rolleri seçin.&lt;/p&gt;
&lt;img src="https://berkselvi.dev/img/how-to-use-ocelot-and-keycloak-together-to-secure-microservices-from-api-gateway/keycloak-5.webp" alt="Keycloak" loading="lazy" /&gt;
&lt;p&gt;Rollerin altında, Mappers bölümünü seçin ve ardından realm roles&amp;rsquo;a tıklayın.&lt;/p&gt;
&lt;img src="https://berkselvi.dev/img/how-to-use-ocelot-and-keycloak-together-to-secure-microservices-from-api-gateway/keycloak-6.webp" alt="Keycloak" loading="lazy" /&gt;
&lt;p&gt;Açılan menüde Token Claim Name seçeneğine realm_access.roles hali hazırda girilidir. Bunu &lt;strong&gt;realm_roles&lt;/strong&gt; olarak güncelliyoruz.&lt;/p&gt;
&lt;img src="https://berkselvi.dev/img/how-to-use-ocelot-and-keycloak-together-to-secure-microservices-from-api-gateway/keycloak-7.webp" alt="Keycloak" loading="lazy" /&gt;
&lt;p&gt;Bu konfigürasyonları yaptıktan sonra Postman üzerinden tekrar giriş yaparak tokenımızı güncelliyoruz. Güncellenen token ile yaptığımız isteklerin Ocelot tarafından talep kontrolüne tabi olması için ocelot.json dosyasında aşağıdaki güncellemeyi yapıyoruz. Kullanıcımızda &lt;strong&gt;customer&lt;/strong&gt; rolü yoksa 403 yanıtı alıyoruz. Keycloak yönetici panelinde özel rollerinizi oluşturabilir ve atayabilirsiniz.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-json" data-lang="json"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;Routes&amp;#34;&lt;/span&gt;: [
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;DownstreamPathTemplate&amp;#34;&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;/get&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;DownstreamScheme&amp;#34;&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;https&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;DownstreamHostAndPorts&amp;#34;&lt;/span&gt;: [
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;Host&amp;#34;&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;httpbin.org&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;Port&amp;#34;&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;443&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ],
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;UpstreamPathTemplate&amp;#34;&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;/test&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;UpstreamHttpMethod&amp;#34;&lt;/span&gt;: [ &lt;span style="color:#e6db74"&gt;&amp;#34;Get&amp;#34;&lt;/span&gt; ],
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;AuthenticationOptions&amp;#34;&lt;/span&gt;: {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;AuthenticationProviderKey&amp;#34;&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;Bearer&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; },
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;RouteClaimsRequirement&amp;#34;&lt;/span&gt;: {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;realm_roles&amp;#34;&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;customer&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; },
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;//...
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Kaynak koduna erişmek isterseniz projenin tamamını GitHub hesabımda bulabilirsiniz:&lt;/p&gt;
&lt;p&gt;&lt;a href="https://github.com/berkslv/lecture-ocelot-and-keycloak"&gt;GitHub - berkslv/lecture-ocelot-and-keycloak&lt;/a&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="sonuç"&gt;Sonuç&lt;/h2&gt;
&lt;p&gt;Okuduğunuz için teşekkürler! 🎉 Yazılım geliştirme alanındaki araştırmalarımı kaçırmamak için &lt;a href="https://x.com/berkslv"&gt;@berkslv&lt;/a&gt; adresinden takipte kalabilirsiniz.&lt;/p&gt;</content></item><item><title>.NET ile Background jobs ve Hangfire</title><link>https://berkselvi.dev/tr/posts/background-jobs-and-hangife-in-net/</link><pubDate>Sun, 17 Sep 2023 00:00:00 +0300</pubDate><guid>https://berkselvi.dev/tr/posts/background-jobs-and-hangife-in-net/</guid><description>&lt;p&gt;.NET ekosisteminde bir uygulama geliştirirken, işler karmaşıklaştığında, bazı yöntemlerimizin birden fazla servise gitmesi, yanıtlarını değerlendirmesi ve bu sonuçları farklı servislere bildirmesi gerekebilir ve bu uzun zaman alabilir! Bu tür uzun süre çalışan yöntemleri bir endpoint&amp;rsquo;in arkasına yerleştirerek ve TCP bağlantımızı o HTTP isteğinin yanıtı için açık tutarak kaynakları boşa harcamak istemeyiz. Bu kullanımı on demand job olarak adlandırabiliriz çünkü bir istek yapıldığında çalışacaktır. Ayrıca, belirli günler veya haftanın belirli zamanlarında otomatik olarak çalışmasını isteyebiliriz, bunu bir recurring job olarak adlandırabiliriz; Bu tür durumlarda, işimizi uygulamanın çalıştığı main thread&amp;rsquo;den farklı bir thread başlatarak &amp;ldquo;background job&amp;rdquo; kullanabiliriz.&lt;/p&gt;</description><content>&lt;p&gt;.NET ekosisteminde bir uygulama geliştirirken, işler karmaşıklaştığında, bazı yöntemlerimizin birden fazla servise gitmesi, yanıtlarını değerlendirmesi ve bu sonuçları farklı servislere bildirmesi gerekebilir ve bu uzun zaman alabilir! Bu tür uzun süre çalışan yöntemleri bir endpoint&amp;rsquo;in arkasına yerleştirerek ve TCP bağlantımızı o HTTP isteğinin yanıtı için açık tutarak kaynakları boşa harcamak istemeyiz. Bu kullanımı on demand job olarak adlandırabiliriz çünkü bir istek yapıldığında çalışacaktır. Ayrıca, belirli günler veya haftanın belirli zamanlarında otomatik olarak çalışmasını isteyebiliriz, bunu bir recurring job olarak adlandırabiliriz; Bu tür durumlarda, işimizi uygulamanın çalıştığı main thread&amp;rsquo;den farklı bir thread başlatarak &amp;ldquo;background job&amp;rdquo; kullanabiliriz.&lt;/p&gt;
&lt;p&gt;.NET&amp;rsquo;te arka plan işlerini yönetmenin birçok farklı yöntemi bulunmaktadır. Bu blog yazısında adım adım bu yöntemlerden bahsedeceğim, nasıl sorunları çözdüklerini ve bize hangi sorunları getirdiklerini anlatacağım ve background job&amp;rsquo;larıyla uğraşırken en çok sevdiğim paket olan Hangfire&amp;rsquo;da detaylı kullanım örnekleri sunacağım. Ayrıca, tüm kodlara repodan erişebilirsiniz:&lt;/p&gt;
&lt;p&gt;&lt;a href="https://github.com/berkslv/lecture-dotnet-background-jobs"&gt;GitHub - berkslv/lecture-dotnet-background-jobs&lt;/a&gt;&lt;/p&gt;
&lt;h2 id="taskrun"&gt;Task.Run()&lt;/h2&gt;
&lt;p&gt;Background job&amp;rsquo;larına ihtiyacım olduğunda aklıma ilk gelen çözüm, Task.Run() yöntemiyle yeni bir thread oluşturmak ve bir methodu onun üzerinde çalıştırmak oldu. Birçok dış servise bağımlılığı olan ve tamamlanması 5 dakikaya kadar sürebilen bir yöntemim var ve bu yöntemi oluşturduğum bir controller aracılığıyla bir HTTP isteği üzerinden çağırıyordum, ancak bu isteğe yanıt vermek sorunluydu çünkü TCP bağlantısının 5 dakika boyunca açık kalması gerekiyordu, bu yüzden isteği çağırdığımda, işlem başarıyla başlatıldıysa, istemciye işlemin başarıyla başlatıldığını 200 durum koduyla bildirmem gerekiyor.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-cs" data-lang="cs"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// TestService.cs &lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;class&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;TestService&lt;/span&gt; : ITestService
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;private&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;readonly&lt;/span&gt; ILogger&amp;lt;TestService&amp;gt; _logger;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; TestService(ILogger&amp;lt;TestService&amp;gt; logger)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; _logger = logger;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;bool&lt;/span&gt; RunTests()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; _logger.LogInformation(&lt;span style="color:#e6db74"&gt;$&amp;#34;{DateTime.Now} RunTests is started&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// ... &lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Thread.Sleep(&lt;span style="color:#ae81ff"&gt;5000&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// ... &lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; _logger.LogInformation(&lt;span style="color:#e6db74"&gt;$&amp;#34;{DateTime.Now} RunTests is finished&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;true&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-cs" data-lang="cs"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// Program.cs &lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// ... &lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;builder.Services.AddTransient&amp;lt;ITestService, TestService&amp;gt;();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// ...&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Task.Run() kullanmadan işlemi çağırmazsam, bu örnekte tarayıcıda /job endpoint&amp;rsquo;ine yönelik istek 3 saniye boyunca yükleniyor olacak.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-cs" data-lang="cs"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// JobController.cs &lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;[ApiController]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;[Route(&amp;#34;[controller]&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;)]
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;class&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;JobController&lt;/span&gt; : ControllerBase
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;private&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;readonly&lt;/span&gt; ITestService _testService;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; JobController(ITestService testService)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; _testService = testService;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt; [HttpGet]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; IActionResult Get()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; _testService.RunTests();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; Ok(&lt;span style="color:#e6db74"&gt;&amp;#34;Ok&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Task.Run() ile aşağıdaki gibi kullandığımda, controller başarılı bir şekilde yanıt verecek ve RunTests yöntemim arka planda çalışmaya devam edecek.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-cs" data-lang="cs"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;[HttpGet]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; IActionResult Get()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Task.Run(() =&amp;gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; _testService.RunTests();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; });
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; Ok(&lt;span style="color:#e6db74"&gt;&amp;#34;Ok&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="artılar"&gt;Artılar&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;İsteğe bağlı olarak arka planda bir işlem çalıştırabiliriz.&lt;/li&gt;
&lt;li&gt;Ek bir paket gerektirmez.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="eksiler"&gt;Eksiler&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Mevcut durumda recurring job desteği bulunmamaktadır, özel bir sistem geliştirilmesi gerekmektedir.&lt;/li&gt;
&lt;li&gt;Dependency Injection kullanıldığında, enjekte edilen interface&amp;rsquo;ler main thread üzerinde kalacağından, gereken interface&amp;rsquo;ü Service scope aracılığıyla yeniden oluşturmamız gerekebilir.&lt;/li&gt;
&lt;li&gt;Yöntem çalışırken bir hata oluşursa ne olur?&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="hosted-service"&gt;Hosted Service&lt;/h2&gt;
&lt;p&gt;Önceki örneğimizde uygulayamadığımız yinelenen işleri yönetmek için kendi sistemimizi geliştirmemiz gerekiyordu, ancak Hosted servis ile bu yönetimi kendimiz geliştirmemize gerek kalmaz, bunun yerine Program.cs dosyasında &lt;code&gt;AddHostedService&lt;/code&gt; yöntemimizi aşağıdaki gibi çağırırız ve TestService sınıfımızı BackgroundService sınıfından türetiriz. Bu örnekte, RunTests yöntemimiz her 10 saniyede bir çalışacaktır. Bu zaman aralığı, BackgroundService abstract sınıfından miras alınan ExecuteAsync yönteminden ayarlanır.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-cs" data-lang="cs"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// Program.cs &lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// ... &lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;builder.Services.AddHostedService&amp;lt;TestService&amp;gt;();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// ...&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-cs" data-lang="cs"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// TestService.cs &lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;class&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;TestService&lt;/span&gt; : BackgroundService, ITestService
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;private&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;readonly&lt;/span&gt; ILogger&amp;lt;TestService&amp;gt; _logger;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; TestService(ILogger&amp;lt;TestService&amp;gt; logger)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; _logger = logger;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;bool&lt;/span&gt; RunTests(TestType testType)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; type = Enum.GetName(&lt;span style="color:#66d9ef"&gt;typeof&lt;/span&gt;(TestType), testType);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; _logger.LogInformation(&lt;span style="color:#e6db74"&gt;$&amp;#34;{DateTime.Now} {type} RunTests is started&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// ... &lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Thread.Sleep(&lt;span style="color:#ae81ff"&gt;5000&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// ... &lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; _logger.LogInformation(&lt;span style="color:#e6db74"&gt;$&amp;#34;{DateTime.Now} {type} RunTests is finished&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;true&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;protected&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;override&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;async&lt;/span&gt; Task ExecuteAsync(CancellationToken stoppingToken)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; timer = &lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; PeriodicTimer(TimeSpan.FromSeconds(&lt;span style="color:#ae81ff"&gt;10&lt;/span&gt;));
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;while&lt;/span&gt; (&lt;span style="color:#66d9ef"&gt;await&lt;/span&gt; timer.WaitForNextTickAsync(stoppingToken))
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; RunTests(TestType.Recurring);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Ancak, yöntemimizi on demand olarak çalıştırmak istiyorsak, Task.Run() yönteminin yardımıyla yeni bir thread üzerinden çalışırız, bu nedenle JobController.cs&amp;rsquo;de herhangi bir değişiklik yapmamıza gerek yoktur.&lt;/p&gt;
&lt;h3 id="artılar-1"&gt;Artılar&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Recurring job yönetimi sağlar&lt;/li&gt;
&lt;li&gt;Ek paket kurmaya gerek yoktur&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="eksiler-1"&gt;Eksiler&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Talep üzerine çalışma için bir sistem bulunmamaktadır.&lt;/li&gt;
&lt;li&gt;Yöntem çalışırken bir hata alınırsa ne olur?&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="hangfire"&gt;Hangfire&lt;/h2&gt;
&lt;p&gt;Hangfire, job&amp;rsquo;ımızı tek bir sistem aracılığıyla talep üzerine ve recurring job&amp;rsquo;ları yönetmeyi çok daha kolay hale getirir. Diğer iki yöntemde bulunmayan job storage sistemi sayesinde, uygulama o anda çalışmıyor olsa bile cron job ile süresi gelmişse ilgili job&amp;rsquo;ı otomatik olarak çalıştırır. Belirli job&amp;rsquo;ları çalıştırabilir ve job oluşturulurken sağlanan id bilgileri ile o job&amp;rsquo;ı silme yeteneğine sahip oluruz. Ayrıca, /hangfire adresindeki bir dashboard üzerinden şu anda çalışan job&amp;rsquo;larımızı izleyebiliriz.&lt;/p&gt;
&lt;img src="https://berkselvi.dev/img/background-jobs-and-hangife-in-net/hangfire-dashboard.webp" alt="Hangfire dashboard" loading="lazy" /&gt;
&lt;p class="image-sub-title"&gt;Hangfire dashboard&lt;/p&gt;
&lt;p&gt;Hangfire resmi olarak Sql Server veritabanını destekler, ancak açık kaynaklı bir uzantı ile Sqlite ve Postgresql gibi sık tercih edilen veritabanları da kullanılabilir. Ayrıca, Hangfire, ücretli sürümüyle Redis veritabanı desteği ve batch job&amp;rsquo;larının çalıştırılması gibi enterprise ihtiyaçları da karşılar.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://www.hangfire.io/extensions.html"&gt;Hangfire – Background Jobs for .NET and .NET Core&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Bu kısa Hangfire girişten sonra, Hangfire&amp;rsquo;ı uygulamamızda nasıl kullanabileceğimiz ve yeteneklerinden kısaca bahsedelim. Örneğimizde job storage olarak Postgresql kullanacağız, ancak yukarıdaki link üzerinden istediğiniz veritabanını seçebilirsiniz. Uygulamamıza aşağıdaki paketleri yükledikten ve Docker ile bir Postgre SQL veritabanı kurduktan sonra, kodlarımıza geçelim.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;dotnet add package Hangfire
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;dotnet add package Hangfire.Core
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;dotnet add package Hangfire.PostgreSql
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;dotnet add package TimeZoneConverter
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;docker run -d --name postgres_db -e POSTGRES_USER&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;root&amp;#34;&lt;/span&gt; -e POSTGRES_PASSWORD&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;1234&amp;#34;&lt;/span&gt; -e POSTGRES_DB&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;postgres&amp;#34;&lt;/span&gt; -v postgres_data:/var/lib/postgresql/data -p 5432:5432 postgres
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;İlk olarak, appsettings.json dosyasında Postgresql için connection string bilgilerini aşağıdaki gibi sağlıyoruz.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-json" data-lang="json"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;ConnectionStrings&amp;#34;&lt;/span&gt;&lt;span style="color:#960050;background-color:#1e0010"&gt;:&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;HangfireConnection&amp;#34;&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;Host=localhost;Port=5432;Password=1234;Username=root;Database=postgres;Pooling=true;Integrated Security=true;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Program.cs&amp;rsquo;de ilgili yapılandırmaları aşağıdaki gibi yapıyoruz.&lt;/p&gt;
&lt;p&gt;TZConvert.GetTimeZoneInfo yöntemiyle gerekli zaman dilimi bilgisini işletim sisteminden alırsınız. Bu kod satırı, local makinenizin, frontend uygulamanızın ve cloud makinenizin farklı zaman dilimlerine sahip olabileceği için gereklidir.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-cs" data-lang="cs"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; builder = WebApplication.CreateBuilder(args);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// ... &lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;builder.Services.AddHangfire(config =&amp;gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; config
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; .UseSimpleAssemblyNameTypeSerializer()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; .UseRecommendedSerializerSettings()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; .UsePostgreSqlStorage(builder.Configuration.GetConnectionString(&lt;span style="color:#e6db74"&gt;&amp;#34;HangfireConnection&amp;#34;&lt;/span&gt;));
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; cronEveryMinute = &lt;span style="color:#e6db74"&gt;&amp;#34;*/1 * * * *&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; recurringJobOptions = &lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; RecurringJobOptions
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; TimeZone = TZConvert.GetTimeZoneInfo(&lt;span style="color:#e6db74"&gt;&amp;#34;Etc/GMT+3&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; };
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; RecurringJob.AddOrUpdate&amp;lt;ITestService&amp;gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;id-run-and-wait&amp;#34;&lt;/span&gt;, x =&amp;gt; x.RunTests(Guid.NewGuid(), TestType.Recurring, CancellationToken.None), cronEveryMinute, recurringJobOptions);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;});
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;builder.Services.AddHangfireServer();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; app = builder.Build();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// ... &lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;app.UseHangfireDashboard();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;app.MapHangfireDashboard();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;app.Run();
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;To showcase Hangfire’s capabilities, we add a few more endpoints to our controller class.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;/run,&lt;/strong&gt; we can start a job and that method returns us a job id&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;/stop,&lt;/strong&gt; we can stop the job related to the job id given to us by Hangfire.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;/continue,&lt;/strong&gt; if many different jobs are to be run but they are dependent on each other, another job can be run after the parent job is finished with the given job id.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;/reschedule,&lt;/strong&gt; the job’s working intervals can be dynamically adjusted by cron or TimeSpan.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;/deschedule,&lt;/strong&gt; recurring jobs can be deleted by their unique id.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;/trigger,&lt;/strong&gt; we can manually trigger a recurring job.&lt;/p&gt;
&lt;p&gt;Hangfire&amp;rsquo;nin yeteneklerini sergilemek için, controller sınıfımıza birkaç ek endpoint ekliyoruz.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;/run,&lt;/strong&gt; bir job başlatabilir ve bu yöntem bize job&amp;rsquo;ın id bilgisini döndürür.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;/stop,&lt;/strong&gt; bize Hangfire tarafından verilen job id ile ilişkili job sürecini durdurabiliriz.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;/continue,&lt;/strong&gt; birçok farklı job&amp;rsquo;ın çalıştırılması gerekiyorsa, ama bunlar birbirlerine bağımlıysa, verilen job id ile ana job tamamlandıktan sonra başka bir iş çalıştırılabilir.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;/reschedule,&lt;/strong&gt; job&amp;rsquo;un çalışma aralıkları cron veya TimeSpan ile dinamik olarak ayarlanabilir.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;/deschedule,&lt;/strong&gt; recurring job&amp;rsquo;lar, job id bilgileriyle silinebilir.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;/trigger,&lt;/strong&gt; recurring bir job&amp;rsquo;ları manuel olarak tetikleyebiliriz.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-cs" data-lang="cs"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// JobController.cs &lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;[ApiController]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;[Route(&amp;#34;[controller]&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;)]
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;class&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;JobController&lt;/span&gt; : ControllerBase
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt; [HttpGet(&amp;#34;/run&amp;#34;)]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; IActionResult Run()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; jobId = BackgroundJob.Enqueue&amp;lt;ITestService&amp;gt;(x =&amp;gt; x.RunTests(Guid.NewGuid(), TestType.OnDemand, CancellationToken.None));
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; Ok(jobId);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt; [HttpGet(&amp;#34;/stop&amp;#34;)]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; IActionResult Stop(&lt;span style="color:#66d9ef"&gt;string&lt;/span&gt; jobId)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; BackgroundJob.Delete(jobId);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; Ok(&lt;span style="color:#e6db74"&gt;&amp;#34;Stopped&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt; [HttpGet(&amp;#34;/continue&amp;#34;)]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; IActionResult Continue(&lt;span style="color:#66d9ef"&gt;string&lt;/span&gt; jobId)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; BackgroundJob.ContinueJobWith&amp;lt;ITestService&amp;gt;(jobId, x =&amp;gt; x.RunTests(Guid.NewGuid(), TestType.OnDemand, CancellationToken.None));
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; Ok(&lt;span style="color:#e6db74"&gt;&amp;#34;Continued&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt; [HttpGet(&amp;#34;/reschedule&amp;#34;)]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; IActionResult Reschedule(&lt;span style="color:#66d9ef"&gt;string&lt;/span&gt; cron)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; recurringJobOptions = &lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; RecurringJobOptions
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; TimeZone = TZConvert.GetTimeZoneInfo(&lt;span style="color:#e6db74"&gt;&amp;#34;Etc/GMT+3&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; };
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; RecurringJob.AddOrUpdate&amp;lt;ITestService&amp;gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;id-run-and-wait&amp;#34;&lt;/span&gt;, x =&amp;gt; x.RunTests(Guid.NewGuid(), TestType.Recurring, CancellationToken.None), cron, recurringJobOptions);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; Ok(&lt;span style="color:#e6db74"&gt;&amp;#34;Rescheduled&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt; [HttpGet(&amp;#34;/deschedule&amp;#34;)]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; IActionResult Deschedule(&lt;span style="color:#66d9ef"&gt;string&lt;/span&gt; id)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; (String.IsNullOrEmpty(id))
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; id = &lt;span style="color:#e6db74"&gt;&amp;#34;id-run-and-wait&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; RecurringJob.RemoveIfExists(id);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; Ok(&lt;span style="color:#e6db74"&gt;&amp;#34;Descheduled&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt; [HttpGet(&amp;#34;/trigger&amp;#34;)]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; IActionResult Trigger(&lt;span style="color:#66d9ef"&gt;string&lt;/span&gt; id)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; (String.IsNullOrEmpty(id))
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; id = &lt;span style="color:#e6db74"&gt;&amp;#34;id-run-and-wait&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; RecurringJob.TriggerJob(id);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; Ok(&lt;span style="color:#e6db74"&gt;&amp;#34;Triggered&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Finally, error management, which is not in our toolkit previously with Task.Run and Hosted service but with Hangfire if an error occurs while running a method, Hangfire runs that method 10 more times with the same parameters at certain time intervals. As an example, we add a method called ThrowRandomly to our TestService class. With this method, we simply add a system that will throw an exception from the method that works with probability 1/2, but Hangfire will try to get successful results by re-running the methods that get errors for us. But errors that catches successfully cannot trigger the retry system. Therefore in the end of catch block we throw again.&lt;/p&gt;
&lt;p&gt;Son olarak, önceki Task.Run() ve Hosted servis ile sahip olmadığımız hata yönetimi sayesinde Hangfire ile bir yöntem çalışırken bir hata oluşursa, Hangfire belirli zaman aralıklarında aynı parametrelerle bu yöntemi 10 kez daha çalıştırır. Bir örnek olarak, TestService sınıfımıza ThrowRandomly adında bir method ekliyoruz. Bu method ile, 1/2 olasılıkla gerçekleşen bir exception fırlatan yapı oluşturuyoruz, ancak Hangfire, bizim için hata alan methodları tekrar çalıştırarak başarılı sonuçlar elde etmeye çalışır. Ancak başarılı bir şekilde catch&amp;rsquo;lenen exception&amp;rsquo;lar, yeniden deneme sistemini tetiklemez. Bu nedenle catch bloğunun sonunda tekrar bir exception fırlatıyoruz.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-cs" data-lang="cs"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// TestService.cs &lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;class&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;TestService&lt;/span&gt; : ITestService
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;private&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;readonly&lt;/span&gt; ILogger&amp;lt;TestService&amp;gt; _logger;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; TestService(ILogger&amp;lt;TestService&amp;gt; logger)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; _logger = logger;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;bool&lt;/span&gt; RunTests(Guid id, TestType testType, CancellationToken cancellationToken)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; type = Enum.GetName(&lt;span style="color:#66d9ef"&gt;typeof&lt;/span&gt;(TestType), testType);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;try&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; _logger.LogInformation(&lt;span style="color:#e6db74"&gt;$&amp;#34;{DateTime.Now} {type} RunTests is started. Id: {id}&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; cancellationToken.ThrowIfCancellationRequested();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// ... &lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Thread.Sleep(&lt;span style="color:#ae81ff"&gt;5000&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ThrowRandomly();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// ... &lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; _logger.LogInformation(&lt;span style="color:#e6db74"&gt;$&amp;#34;{DateTime.Now} {type} RunTests is finished. Id: {id}&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;true&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;catch&lt;/span&gt; (OperationCanceledException exception)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; _logger.LogError(&lt;span style="color:#e6db74"&gt;$&amp;#34;{DateTime.Now} {type} RunTests is failed. Exception: {exception.Message} Id: {id}&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;throw&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;catch&lt;/span&gt;(Exception exception)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; _logger.LogError(&lt;span style="color:#e6db74"&gt;$&amp;#34;{DateTime.Now} {type} RunTests is failed. Exception: {exception.Message} Id: {id}&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;throw&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;private&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;void&lt;/span&gt; ThrowRandomly()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; random = &lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; Random();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; number = random.Next(&lt;span style="color:#ae81ff"&gt;1&lt;/span&gt;, &lt;span style="color:#ae81ff"&gt;3&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; (number == &lt;span style="color:#ae81ff"&gt;2&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;throw&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; Exception(&lt;span style="color:#e6db74"&gt;&amp;#34;Error is throwed!&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Ayrıca, Hangfire ayrı servis üzerinde çalışır, bu ihtiyaçlarınıza bağlı olarak iyi veya kötü olabilir. Ölçeklendirme gerektiğinde uygulama ve Hangfire sunucularını ayırabilir ve bunları farklı makinelerde çalıştırabiliriz.&lt;/p&gt;
&lt;h3 id="artılar-2"&gt;Artılar&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Güçlü bir soyutlama ile talep üzerine ve yinelenen işleri birlikte yönetebilir.&lt;/li&gt;
&lt;li&gt;Cron iş zamanlamasını dinamik olarak ayarlayabiliriz ve zamanlaması çok hassastır.&lt;/li&gt;
&lt;li&gt;Çalışan ve cron job&amp;rsquo;larımızı Dashboard ile izleyebiliriz.&lt;/li&gt;
&lt;li&gt;Zorunlu interface uygulaması veya başka özel bir uygulama yoktur, yalnızca Hangfire tarafından sağlanan yöntemleri kullanarak job&amp;rsquo;larımızı yönetebiliriz.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="eksiler-2"&gt;Eksiler&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Harici depolama gereklidir, varsayılan olarak SQL Server ile çalışır.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id="sonuç"&gt;Sonuç&lt;/h2&gt;
&lt;p&gt;Okuduğunuz için teşekkürler! 🎉 Yazılım geliştirme alanındaki araştırmalarımı kaçırmamak için &lt;a href="https://x.com/berkslv"&gt;@berkslv&lt;/a&gt; adresinden takipte kalabilirsiniz.&lt;/p&gt;</content></item><item><title>Dotnet ve Vue.js uygulamasını Keycloak ile nasıl güvenliği sağlanır?</title><link>https://berkselvi.dev/tr/posts/how-to-secure-dotnet-vue-application-with-keycloak/</link><pubDate>Mon, 21 Aug 2023 00:00:00 +0300</pubDate><guid>https://berkselvi.dev/tr/posts/how-to-secure-dotnet-vue-application-with-keycloak/</guid><description>&lt;p&gt;Bu yazıda, dotnet ve vue uygulamalarımızı keycloak kullanarak nasıl güvence altına alabileceğimizi konuşacağım.&lt;/p&gt;
&lt;p&gt;Öncelikle, Keycloak uygulamamızı docker kullanarak çalıştırıyoruz, bu uygulamayı Azure veya benzeri bir bulut sağlayıcısında yayınlarsanız, production uygulamalarınızda kolayca kullanabilirsiniz.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;docker run -p 8080:8080 -e KEYCLOAK_ADMIN&lt;span style="color:#f92672"&gt;=&lt;/span&gt;admin -e KEYCLOAK_ADMIN_PASSWORD&lt;span style="color:#f92672"&gt;=&lt;/span&gt;admin quay.io/keycloak/keycloak:22.0.1 start-dev
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Keycloak çalıştıktan sonra, localhost:8080/admin adresine kullanıcı adı: admin ve şifre: admin ile giriş yaptıktan sonra sol taraftan uygulamamız için bir alan (realm) oluştururuz. Ana (master) alan keycloak&amp;rsquo;un kendi kullanımına atandığından, başka bir realm üzerinde çalışmamız gerekmektedir.&lt;/p&gt;</description><content>&lt;p&gt;Bu yazıda, dotnet ve vue uygulamalarımızı keycloak kullanarak nasıl güvence altına alabileceğimizi konuşacağım.&lt;/p&gt;
&lt;p&gt;Öncelikle, Keycloak uygulamamızı docker kullanarak çalıştırıyoruz, bu uygulamayı Azure veya benzeri bir bulut sağlayıcısında yayınlarsanız, production uygulamalarınızda kolayca kullanabilirsiniz.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;docker run -p 8080:8080 -e KEYCLOAK_ADMIN&lt;span style="color:#f92672"&gt;=&lt;/span&gt;admin -e KEYCLOAK_ADMIN_PASSWORD&lt;span style="color:#f92672"&gt;=&lt;/span&gt;admin quay.io/keycloak/keycloak:22.0.1 start-dev
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Keycloak çalıştıktan sonra, localhost:8080/admin adresine kullanıcı adı: admin ve şifre: admin ile giriş yaptıktan sonra sol taraftan uygulamamız için bir alan (realm) oluştururuz. Ana (master) alan keycloak&amp;rsquo;un kendi kullanımına atandığından, başka bir realm üzerinde çalışmamız gerekmektedir.&lt;/p&gt;
&lt;img src="https://berkselvi.dev/img/how-to-secure-dotnet-vue-application-with-keycloak/create-realm.webp" alt="Create realm" loading="lazy" /&gt;
&lt;p class="image-sub-title"&gt;Create realm&lt;/p&gt;
&lt;img src="https://berkselvi.dev/img/how-to-secure-dotnet-vue-application-with-keycloak/create-realm-confirmation.webp" alt="Create realm confirmation" loading="lazy" /&gt;
&lt;p class="image-sub-title"&gt;Create realm confirmation&lt;/p&gt;
&lt;p&gt;Realm’i oluşturduktan sonra client kısmından vue adında bir client oluşturuyoruz. Yönlendirme URL&amp;rsquo;lerimiz Vue uygulamamızın URL&amp;rsquo;sidir. production ortamı için ek güncellemeler yapmanız gerekebilir.&lt;/p&gt;
&lt;p&gt;Vue uygulamamızda kullanmak üzere Kullanıcılar menüsünden dotnet-vue realm altında kullanıcılar oluşturabiliriz.&lt;/p&gt;
&lt;p&gt;Uygulamamız için kullanıcı oluşturmak istiyorsak realm ayarlarında bu seçeneği açarak kullanıcılarımızın keycloak&amp;rsquo;a kayıt olmalarını sağlayabiliriz.&lt;/p&gt;
&lt;h2 id="vue"&gt;Vue&lt;/h2&gt;
&lt;p&gt;Bu konfigürasyonları tamamladıktan sonra keycloak admin paneli ile işimiz bitiyor, vue uygulamamıza yetkisiz bir istek geldiğinde onu keycloak giriş sayfamıza yönlendirmemiz gerekiyor. Bunun için &lt;a href="https://medium.com/keycloak/secure-vue-js-app-with-keycloak-94814181e344"&gt;Secure Vue.js app with Keycloak&lt;/a&gt; makalesinden yararlanıyoruz.&lt;/p&gt;
&lt;p&gt;Vue uygulamamızı vite kullanarak şu şekilde oluşturabiliriz, sorulan sorulardan Vue ve Javascript seçip devam edebilirsiniz.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;npm create vite@latest vue
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;cd vue
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;npm install
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;npm run dev
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Daha sonra main.js içeriğini aşağıdaki gibi güncelliyoruz. Bu komut dosyası, yetkisiz oturum açma isteklerini yeniden yönlendirir ve token&amp;rsquo;ın süresinin onTokenExpired ile sona ermesi durumunda token&amp;rsquo;ı refrest token kullanarak günceller. Bu işlem sonrasında uygulamayı npm run dev ile çalıştırıp http://localhost:5173 adresine gidersek keycloak giriş url’si yönlendirilecektir. Oluşturduğumuz kullanıcı bilgileriyle giriş yaparsak sistemimize giriş yapmış olacağız.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-js" data-lang="js"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;import&lt;/span&gt; { &lt;span style="color:#a6e22e"&gt;createApp&lt;/span&gt; } &lt;span style="color:#a6e22e"&gt;from&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;vue&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;import&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;./style.css&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;import&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;App&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;from&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;./App.vue&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;import&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Keycloak&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;from&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;keycloak-js&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;let&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;initOptions&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;url&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;http://localhost:8080&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;realm&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;dotnet-vue&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;clientId&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;vue&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;onLoad&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;login-required&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;};
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;let&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;keycloak&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Keycloak&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;initOptions&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;keycloak&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; .&lt;span style="color:#a6e22e"&gt;init&lt;/span&gt;({ &lt;span style="color:#a6e22e"&gt;onLoad&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;initOptions&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;onLoad&lt;/span&gt; })
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; .&lt;span style="color:#a6e22e"&gt;then&lt;/span&gt;((&lt;span style="color:#a6e22e"&gt;auth&lt;/span&gt;) =&amp;gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; (&lt;span style="color:#f92672"&gt;!&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;auth&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; window.&lt;span style="color:#a6e22e"&gt;location&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;reload&lt;/span&gt;();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; } &lt;span style="color:#66d9ef"&gt;else&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;console&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;log&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;Authenticated&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;createApp&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;App&lt;/span&gt;).&lt;span style="color:#a6e22e"&gt;mount&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;#app&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; })
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; .&lt;span style="color:#66d9ef"&gt;catch&lt;/span&gt;((&lt;span style="color:#a6e22e"&gt;error&lt;/span&gt;) =&amp;gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;console&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;log&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;error&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;console&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;error&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;Authenticated Failed&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; });
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;keycloak&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;onTokenExpired&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; () =&amp;gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;console&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;log&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;Token expired&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;keycloak&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; .&lt;span style="color:#a6e22e"&gt;updateToken&lt;/span&gt;(&lt;span style="color:#ae81ff"&gt;30&lt;/span&gt; &lt;span style="color:#f92672"&gt;*&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;60&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; .&lt;span style="color:#a6e22e"&gt;then&lt;/span&gt;(() =&amp;gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;console&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;log&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;Token renewed&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; })
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; .&lt;span style="color:#66d9ef"&gt;catch&lt;/span&gt;(() =&amp;gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;keycloak&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;login&lt;/span&gt;()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; })
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;export&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;default&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;keycloak&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="net"&gt;.NET&lt;/h2&gt;
&lt;p&gt;.NET 6 ile aşağıdaki gibi bir webapi projesi oluşturabiliriz.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;cd dotnet
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;dotnet new sln -n Secured
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;dotnet new webapi -o Secured.API
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;cd Secured.API
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;dotnet watch run
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Keycloak ve .NET&amp;rsquo;in desteklediği standartlar sayesinde Program.cs&amp;rsquo;de yapacağımız konfigürasyonlarla özel bir kod yazmadan sadece yetkilendirebiliyoruz. Bu kodda, ihraççı ile tokenın nereden geldiğini doğruluyoruz ve IssuerSigningKey sayesinde keycloak&amp;rsquo;a gitmeden oluşturulan tokenı doğrulayabiliyoruz.&lt;/p&gt;
&lt;p&gt;RequireHttpsMetadata yalnızca geliştirme ortamı için kullanılmalıdır; production ortamında çalışan bir uygulamanız varsa bu satırı silebilirsiniz; değeri true olarak değerlendirilecektir.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-cs" data-lang="cs"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;builder.Services.AddSwaggerGen();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// ... &lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; issuer = builder.Configuration[&lt;span style="color:#e6db74"&gt;&amp;#34;Jwt:Issuer&amp;#34;&lt;/span&gt;];
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; key = builder.Configuration[&lt;span style="color:#e6db74"&gt;&amp;#34;Jwt:Key&amp;#34;&lt;/span&gt;];
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;builder.Services.AddAuthentication(options =&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; })
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; .AddJwtBearer(o =&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; o.Authority = issuer;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; o.RequireHttpsMetadata = &lt;span style="color:#66d9ef"&gt;false&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; o.TokenValidationParameters = &lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; Microsoft.IdentityModel.Tokens.TokenValidationParameters
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ValidateIssuer = &lt;span style="color:#66d9ef"&gt;true&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ValidateAudience = &lt;span style="color:#66d9ef"&gt;false&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ValidateLifetime = &lt;span style="color:#66d9ef"&gt;true&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ValidateIssuerSigningKey = &lt;span style="color:#66d9ef"&gt;true&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ValidIssuer = issuer,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; IssuerSigningKey = &lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; SymmetricSecurityKey(Encoding.UTF8.GetBytes(key))
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; };
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; });
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// ... &lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; app = builder.Build();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// ... &lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;app.UseAuthentication();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;app.UseAuthorization();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// ... &lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;app.Run();
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Appsettings.json dosyasındaki verileri aşağıdaki gibi tutarak builder.Configuration adresinden erişebiliriz. RSA256 anahtarına keycloak admin panelinde ilgili realm altında Realm ayarları menüsündeki Keys sekmesindeki RSA256 Kid değerinden ulaşabilirsiniz.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-json" data-lang="json"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;Jwt&amp;#34;&lt;/span&gt;&lt;span style="color:#960050;background-color:#1e0010"&gt;:&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;Issuer&amp;#34;&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;https://localhost:8080/realms/dotnet-vue&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;Key&amp;#34;&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;secret-rsa-key&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}&lt;span style="color:#960050;background-color:#1e0010"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="consuming-net-api-from-vue-app"&gt;Consuming .NET API from Vue app&lt;/h2&gt;
&lt;p&gt;Öncelikle .NET projemize CORS ayarlarını ve Authorize niteliklerini ekliyoruz. Bunun için &lt;code&gt;Program.cs&lt;/code&gt;yi aşağıdaki gibi düzenliyoruz.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-cs" data-lang="cs"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// ... &lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;builder.Services.AddCors(options =&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; options.AddDefaultPolicy(
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; policy =&amp;gt; policy.WithOrigins(&lt;span style="color:#e6db74"&gt;&amp;#34;http://localhost:5137&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; .AllowAnyHeader()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; .AllowAnyMethod()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; .AllowAnyOrigin()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; );
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;});
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// ... &lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; app = builder.Build();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// app.UseHttpsRedirection(); &lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;app.UseCors();
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;WeatherForecastController.cs dosyası aşağıdaki attirbute ile yetkilendirme gerektirir.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-cs" data-lang="cs"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// ... &lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;[Authorize]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;public&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;class&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;WeatherForecastController&lt;/span&gt; : ControllerBase
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// ...&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Daha sonra aşağıdaki komutla vue uygulamamıza axios kütüphanesini kuruyoruz.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;npm install axios
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Kurulumdan sonra services isimli bir klasör oluşturup &lt;code&gt;base.api.js&lt;/code&gt; isimli dosyamızda axios konfigürasyonlarını yapıyoruz.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-js" data-lang="js"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;import&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;axios&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;from&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;axios&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;import&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;keycloak&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;from&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;../main&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;const&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;http&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;axios&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;create&lt;/span&gt;({
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;baseURL&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;http://localhost:5050&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;headers&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;Content-Type&amp;#34;&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;application/json&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; },
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;});
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;http&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;interceptors&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;request&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;use&lt;/span&gt;(
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;async&lt;/span&gt; (&lt;span style="color:#a6e22e"&gt;config&lt;/span&gt;) =&amp;gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;const&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;token&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;localStorage&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;getItem&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;vue-token&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;config&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;headers&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;Authorization&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;`Bearer &lt;/span&gt;&lt;span style="color:#e6db74"&gt;${&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;keycloak&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;token&lt;/span&gt;&lt;span style="color:#e6db74"&gt;}&lt;/span&gt;&lt;span style="color:#e6db74"&gt;`&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;Accept&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;application/json&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; };
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;config&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; },
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; (&lt;span style="color:#a6e22e"&gt;error&lt;/span&gt;) =&amp;gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Promise.&lt;span style="color:#a6e22e"&gt;reject&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;error&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;export&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;default&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;http&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Örnek bir endpoint olan WeatherForecast&amp;rsquo;e erişmek için, aynı dizine &lt;code&gt;weather.api.js&lt;/code&gt; dosyasını oluşturuyoruz.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-js" data-lang="js"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;import&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;axios&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;from&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;./base.api&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;export&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;const&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;getWeather&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;async&lt;/span&gt; () =&amp;gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;const&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;response&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;await&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;axios&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;get&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;`/WeatherForecast`&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;response&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;data&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;App.vue&lt;/code&gt;da API isteğimizi yaparak verileri görüntülüyoruz, burada logout yöntemi ile çıkış yapabiliyoruz.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-vue" data-lang="vue"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&amp;lt;&lt;span style="color:#f92672"&gt;script&lt;/span&gt;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;import&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Keycloak&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;from&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#39;./main&amp;#39;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;import&lt;/span&gt; { &lt;span style="color:#a6e22e"&gt;getWeather&lt;/span&gt; } &lt;span style="color:#a6e22e"&gt;from&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#39;./service/weather.api&amp;#39;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;export&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;default&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;name&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#39;App&amp;#39;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;data&lt;/span&gt;() {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;weathers&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; []
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; },
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;async&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;mounted&lt;/span&gt;() {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;this&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;weathers&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;await&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;getWeather&lt;/span&gt;();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; },
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;methods&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;logout&lt;/span&gt;() {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;Keycloak&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;logout&lt;/span&gt;();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; },
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&amp;lt;/&lt;span style="color:#f92672"&gt;script&lt;/span&gt;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&amp;lt;&lt;span style="color:#f92672"&gt;template&lt;/span&gt;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &amp;lt;&lt;span style="color:#f92672"&gt;button&lt;/span&gt; &lt;span style="color:#f92672"&gt;@click&lt;/span&gt;&lt;span style="color:#e6db74"&gt;=&amp;#34;logout&amp;#34;&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;style&lt;/span&gt;&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;margin-bottom: 30px;&amp;#34;&lt;/span&gt;&amp;gt;&lt;span style="color:#a6e22e"&gt;Logout&lt;/span&gt;&amp;lt;/&lt;span style="color:#f92672"&gt;button&lt;/span&gt;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &amp;lt;&lt;span style="color:#f92672"&gt;table&lt;/span&gt;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &amp;lt;&lt;span style="color:#f92672"&gt;tr&lt;/span&gt;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &amp;lt;&lt;span style="color:#f92672"&gt;th&lt;/span&gt;&amp;gt;Date&amp;lt;/&lt;span style="color:#f92672"&gt;th&lt;/span&gt;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &amp;lt;&lt;span style="color:#f92672"&gt;th&lt;/span&gt;&amp;gt;&lt;span style="color:#a6e22e"&gt;Summary&lt;/span&gt;&amp;lt;/&lt;span style="color:#f92672"&gt;th&lt;/span&gt;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &amp;lt;/&lt;span style="color:#f92672"&gt;tr&lt;/span&gt;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &amp;lt;&lt;span style="color:#f92672"&gt;tr&lt;/span&gt; &lt;span style="color:#f92672"&gt;v-for&lt;/span&gt;&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;weather in weathers&amp;#34;&lt;/span&gt;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &amp;lt;&lt;span style="color:#f92672"&gt;td&lt;/span&gt;&amp;gt;{{ &lt;span style="color:#a6e22e"&gt;weather&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;date&lt;/span&gt; }}&amp;lt;/&lt;span style="color:#f92672"&gt;td&lt;/span&gt;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &amp;lt;&lt;span style="color:#f92672"&gt;td&lt;/span&gt;&amp;gt;{{ &lt;span style="color:#a6e22e"&gt;weather&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;summary&lt;/span&gt; }}&amp;lt;/&lt;span style="color:#f92672"&gt;td&lt;/span&gt;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &amp;lt;/&lt;span style="color:#f92672"&gt;tr&lt;/span&gt;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &amp;lt;/&lt;span style="color:#f92672"&gt;table&lt;/span&gt;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&amp;lt;/&lt;span style="color:#f92672"&gt;template&lt;/span&gt;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Kaynak koduna erişmek isterseniz projenin tamamını GitHub hesabımda bulabilirsiniz:&lt;/p&gt;
&lt;p&gt;&lt;a href="https://github.com/berkslv/lecture-dotnet-vue-keycloak"&gt;GitHub - berkslv/lecture-dotnet-vue-keycloak&lt;/a&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="sonuç"&gt;Sonuç&lt;/h2&gt;
&lt;p&gt;Okuduğunuz için teşekkürler! 🎉 Yazılım geliştirme alanındaki araştırmalarımı kaçırmamak için &lt;a href="https://x.com/berkslv"&gt;@berkslv&lt;/a&gt; adresinden takipte kalabilirsiniz.&lt;/p&gt;</content></item><item><title>Zero downtime ile Deployment: Azure DevOps Deployment Slots Kullanarak Azure App Service Deployment işlemi</title><link>https://berkselvi.dev/tr/posts/achieving-zero-downtime-azure-app-service-deployment-using-azure-devops-and-deployment-slots/</link><pubDate>Sun, 21 May 2023 00:00:00 +0300</pubDate><guid>https://berkselvi.dev/tr/posts/achieving-zero-downtime-azure-app-service-deployment-using-azure-devops-and-deployment-slots/</guid><description>&lt;p&gt;Selamlar! Azure App Service&amp;rsquo;te tek instance olarak çalışan uygulamamızı Azure DevOps işlem hatlarını kullanarak dağıtırken büyük olasılıkla birkaç saniyelik kesinti yaşanacaktır. Çünkü tek instance olarak çalışan uygulamanın yeni sürüme güncellenmesi için yeniden başlatılması gerekecektir. En kötü senaryoda, uygulama sürümlerimizin geçişinde bir hata oluşması durumunda geri alma nedeniyle kesinti süresi uzayacaktır.&lt;/p&gt;
&lt;p&gt;Bu özel sorunu app servisi için deployment slot özelliğini kullanarak çözebiliriz. Bu özellikle, genellikle ayrı örnekler olarak aynı app servis planında çalışan iki farklı örnek, genellikle staging ve production uygulamaları, birbirinden ayrı örnekler olarak çalışır ve production ortamına geçiş yapıldığında app servisi tarafından değiştirme işlemi yönetilir ve kesinti yaşanmaz.&lt;/p&gt;</description><content>&lt;p&gt;Selamlar! Azure App Service&amp;rsquo;te tek instance olarak çalışan uygulamamızı Azure DevOps işlem hatlarını kullanarak dağıtırken büyük olasılıkla birkaç saniyelik kesinti yaşanacaktır. Çünkü tek instance olarak çalışan uygulamanın yeni sürüme güncellenmesi için yeniden başlatılması gerekecektir. En kötü senaryoda, uygulama sürümlerimizin geçişinde bir hata oluşması durumunda geri alma nedeniyle kesinti süresi uzayacaktır.&lt;/p&gt;
&lt;p&gt;Bu özel sorunu app servisi için deployment slot özelliğini kullanarak çözebiliriz. Bu özellikle, genellikle ayrı örnekler olarak aynı app servis planında çalışan iki farklı örnek, genellikle staging ve production uygulamaları, birbirinden ayrı örnekler olarak çalışır ve production ortamına geçiş yapıldığında app servisi tarafından değiştirme işlemi yönetilir ve kesinti yaşanmaz.&lt;/p&gt;
&lt;p&gt;Bu yazıda, çok basit bir Dotnet Web API projesi oluşturacak, Azure DevOps Repository kullanarak barındıracak, Azure DevOps pipeline ile CI/CD pipeline oluşturarak Azure App Service&amp;rsquo;e dağıtacak ve deployment slot özelliği ile kesintisiz deployment yapacağız. Aşağıdaki adımları takip ederek süreci 5 adımda tamamlayacağız. Başlayalım.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Uygulamanın Oluşturulması&lt;/li&gt;
&lt;li&gt;Azure DevOps Repo&lt;/li&gt;
&lt;li&gt;Azure DevOps build pipeline&lt;/li&gt;
&lt;li&gt;Azure App service&lt;/li&gt;
&lt;li&gt;Zero downtime testing&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;## Uygulamanın Oluşturulması&lt;/p&gt;
&lt;p&gt;Deploy etmek için basit bir Dotnet Web API projesi oluşturuyoruz. Bunun için aşağıdaki komutları kullanıyoruz.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;mkdir backend
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;cd ./backend
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;dotnet new sln -n Slot
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;dotnet new webapi -n Slot.API
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;dotnet sln add ./Slot.API/
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;properties/launchSetting.json dosyasındaki &lt;em&gt;profiles.http&lt;/em&gt; özelliğini aşağıdaki gibi güncelliyoruz. Burada yalnızca &lt;em&gt;applicationUrl&lt;/em&gt; ve &lt;em&gt;launchBrowser&lt;/em&gt; özelliklerini güncelledik.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-json" data-lang="json"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// ...
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;profiles&amp;#34;&lt;/span&gt;&lt;span style="color:#960050;background-color:#1e0010"&gt;:&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;http&amp;#34;&lt;/span&gt;: {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;commandName&amp;#34;&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;Project&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;dotnetRunMessages&amp;#34;&lt;/span&gt;: &lt;span style="color:#66d9ef"&gt;true&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;launchBrowser&amp;#34;&lt;/span&gt;: &lt;span style="color:#66d9ef"&gt;false&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;launchUrl&amp;#34;&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;swagger&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;applicationUrl&amp;#34;&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;http://localhost:5050&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;environmentVariables&amp;#34;&lt;/span&gt;: {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;ASPNETCORE_ENVIRONMENT&amp;#34;&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;Development&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; },
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// ...
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Aşağıdaki güncellemeyi Program.cs dosyasında yaparak, uygulamanın erişilebilir ve /health uç noktasına yapılan isteklere yanıt verebileceğini test edeceğiz. Uygulamanızda bir veritabanı kullanıyorsanız, &lt;a href="https://learn.microsoft.com/en-us/dotnet/api/microsoft.extensions.dependencyinjection.entityframeworkcorehealthchecksbuilderextensions.adddbcontextcheck?view=dotnet-plat-ext-8.0"&gt;AddDbContextCheck&lt;/a&gt; yöntemi ile veritabanına erişimde bir sorun olup olmadığını da test edebilirsiniz.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-cs" data-lang="cs"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; builder = WebApplication.CreateBuilder(args);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// ... &lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;builder.Services.AddHealthChecks();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;var&lt;/span&gt; app = builder.Build();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// ... &lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// app.UseHttpsRedirection(); &lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;app.MapControllers();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;app.UseHealthChecks(&lt;span style="color:#e6db74"&gt;&amp;#34;/health&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;app.Run();
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Uygulamamız için bu kadar! Şimdi şu komutları çalıştırarak localhost:5050/health adresine erişebiliriz.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;cd ./Slot.API
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;dotnet run
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;curl -4 http://localhost:5050/health
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# Healthy&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Daha sonra uygulamamızı dağıtmak için Docker kullanacağız, bu yüzden Dockerfile dosyamızı sln dosyamızla aynı dizine koyuyoruz.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-Dockerfile" data-lang="Dockerfile"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# Build Stage &lt;/span&gt;&lt;span style="color:#960050;background-color:#1e0010"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;FROM&lt;/span&gt; &lt;span style="color:#e6db74"&gt;mcr.microsoft.com/dotnet/aspnet:7.0-alpine&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;AS&lt;/span&gt; &lt;span style="color:#e6db74"&gt;base&lt;/span&gt; &lt;span style="color:#960050;background-color:#1e0010"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;WORKDIR&lt;/span&gt; &lt;span style="color:#e6db74"&gt;/app&lt;/span&gt; &lt;span style="color:#960050;background-color:#1e0010"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;EXPOSE&lt;/span&gt; &lt;span style="color:#e6db74"&gt;8080&lt;/span&gt; &lt;span style="color:#960050;background-color:#1e0010"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#960050;background-color:#1e0010"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#960050;background-color:#1e0010"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# Publish Stage &lt;/span&gt;&lt;span style="color:#960050;background-color:#1e0010"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;FROM&lt;/span&gt; &lt;span style="color:#e6db74"&gt;mcr.microsoft.com/dotnet/sdk:7.0-alpine&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;AS&lt;/span&gt; &lt;span style="color:#e6db74"&gt;build&lt;/span&gt; &lt;span style="color:#960050;background-color:#1e0010"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;COPY&lt;/span&gt; &lt;span style="color:#f92672"&gt;[&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;Slot.API/Slot.API.csproj&amp;#34;&lt;/span&gt;, &lt;span style="color:#e6db74"&gt;&amp;#34;Slot.API/&amp;#34;&lt;/span&gt;&lt;span style="color:#f92672"&gt;]&lt;/span&gt; &lt;span style="color:#960050;background-color:#1e0010"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;RUN&lt;/span&gt; dotnet restore &lt;span style="color:#e6db74"&gt;&amp;#34;Slot.API/Slot.API.csproj&amp;#34;&lt;/span&gt; &lt;span style="color:#960050;background-color:#1e0010"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;COPY&lt;/span&gt; . . &lt;span style="color:#960050;background-color:#1e0010"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;WORKDIR&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;/Slot.API&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34; &lt;/span&gt;&lt;span style="color:#960050;background-color:#1e0010"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;RUN&lt;/span&gt; dotnet build &lt;span style="color:#e6db74"&gt;&amp;#34;Slot.API.csproj&amp;#34;&lt;/span&gt; -c Release -o /app/build &lt;span style="color:#960050;background-color:#1e0010"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#960050;background-color:#1e0010"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;FROM&lt;/span&gt; &lt;span style="color:#e6db74"&gt;build&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;AS&lt;/span&gt; &lt;span style="color:#e6db74"&gt;publish&lt;/span&gt; &lt;span style="color:#960050;background-color:#1e0010"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;RUN&lt;/span&gt; dotnet publish &lt;span style="color:#e6db74"&gt;&amp;#34;Slot.API.csproj&amp;#34;&lt;/span&gt; -c Release -o /app/publish /p:UseAppHost&lt;span style="color:#f92672"&gt;=&lt;/span&gt;false &lt;span style="color:#960050;background-color:#1e0010"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#960050;background-color:#1e0010"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;FROM&lt;/span&gt; &lt;span style="color:#e6db74"&gt;base&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;AS&lt;/span&gt; &lt;span style="color:#e6db74"&gt;final&lt;/span&gt; &lt;span style="color:#960050;background-color:#1e0010"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;WORKDIR&lt;/span&gt; &lt;span style="color:#e6db74"&gt;/app&lt;/span&gt; &lt;span style="color:#960050;background-color:#1e0010"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;COPY&lt;/span&gt; --from&lt;span style="color:#f92672"&gt;=&lt;/span&gt;publish /app/publish . &lt;span style="color:#960050;background-color:#1e0010"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;ENV&lt;/span&gt; ASPNETCORE_URLS&lt;span style="color:#f92672"&gt;=&lt;/span&gt;http://*:8080 &lt;span style="color:#960050;background-color:#1e0010"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;ENV&lt;/span&gt; ASPNETCORE_ENVIRONMENT&lt;span style="color:#f92672"&gt;=&lt;/span&gt;Production
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;ENTRYPOINT&lt;/span&gt; [&lt;span style="color:#e6db74"&gt;&amp;#34;dotnet&amp;#34;&lt;/span&gt;, &lt;span style="color:#e6db74"&gt;&amp;#34;Slot.API.dll&amp;#34;&lt;/span&gt;]&lt;span style="color:#960050;background-color:#1e0010"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Aşağıdaki iki komutu kullanarak imajı başarıyla oluşturup container&amp;rsquo;ı build edip çalıştırabiliriz.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;docker build -t deployment-slots-demo .
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;docker run -it -p 80:8080 deployment-slots-demo -n deployment-slots-demo-container
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="uygulamanın-çalışıyor-olduğunu-doğrulayın"&gt;Uygulamanın Çalışıyor Olduğunu Doğrulayın&lt;/h2&gt;
&lt;p&gt;Önceki adımda sağlık kontrolü özelliği ile uygulamamızı oluşturduktan sonra, bu adrese her saniye bir istek yaparak uygulamanın erişilebilir olduğunu doğrulayabiliriz. Bu şekilde, Azure&amp;rsquo;a dağıtıldığında erişilebilir olduğunu doğrulamış olacağız.&lt;/p&gt;
&lt;p&gt;Bu bölümde, uygulamanın her 50 milisaniyede bir istek yaparak erişilebilir olup olmadığını doğrulamak için basit bir Node.js script&amp;rsquo;i yazacağız. Bunun için aşağıdaki komutları kullanıyoruz.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;mkdir health-check
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;npm init -y
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;touch index.js
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;npm install node-fetch
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Aşağıdaki kısmı package.json dosyasına ekliyoruz.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-json" data-lang="json"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// ...
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;scripts&amp;#34;&lt;/span&gt;&lt;span style="color:#960050;background-color:#1e0010"&gt;:&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;start&amp;#34;&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;node index.js&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}&lt;span style="color:#960050;background-color:#1e0010"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;type&amp;#34;&lt;/span&gt;&lt;span style="color:#960050;background-color:#1e0010"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;module&amp;#34;&lt;/span&gt;&lt;span style="color:#960050;background-color:#1e0010"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// ...
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;index.js dosyamızı aşağıdaki gibi oluşturabiliriz. Bu kod ile belirtilen url&amp;rsquo;e her 50 milisaniyede bir istek yapacak ve yanıtı konsola yazacaktır.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-js" data-lang="js"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;import&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;fetch&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;from&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;node-fetch&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;const&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;check&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;async&lt;/span&gt; (&lt;span style="color:#a6e22e"&gt;url&lt;/span&gt;) =&amp;gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;try&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;const&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;response&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;await&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;fetch&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;url&lt;/span&gt;, {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;method&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;GET&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; });
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;const&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;result&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;await&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;response&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;text&lt;/span&gt;();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; (&lt;span style="color:#a6e22e"&gt;result&lt;/span&gt; &lt;span style="color:#f92672"&gt;!==&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;Healthy&amp;#34;&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;console&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;log&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;`&lt;/span&gt;&lt;span style="color:#e6db74"&gt;${&lt;/span&gt;&lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; Date().&lt;span style="color:#a6e22e"&gt;toISOString&lt;/span&gt;()&lt;span style="color:#e6db74"&gt;}&lt;/span&gt;&lt;span style="color:#e6db74"&gt;, &lt;/span&gt;&lt;span style="color:#e6db74"&gt;${&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;url&lt;/span&gt;&lt;span style="color:#e6db74"&gt;}&lt;/span&gt;&lt;span style="color:#e6db74"&gt; result is not OK`&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; } &lt;span style="color:#66d9ef"&gt;catch&lt;/span&gt; (&lt;span style="color:#a6e22e"&gt;error&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;console&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;log&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;`&lt;/span&gt;&lt;span style="color:#e6db74"&gt;${&lt;/span&gt;&lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; Date().&lt;span style="color:#a6e22e"&gt;toISOString&lt;/span&gt;()&lt;span style="color:#e6db74"&gt;}&lt;/span&gt;&lt;span style="color:#e6db74"&gt;, &lt;/span&gt;&lt;span style="color:#e6db74"&gt;${&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;url&lt;/span&gt;&lt;span style="color:#e6db74"&gt;}&lt;/span&gt;&lt;span style="color:#e6db74"&gt; error is &lt;/span&gt;&lt;span style="color:#e6db74"&gt;${&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;error&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;message&lt;/span&gt;&lt;span style="color:#e6db74"&gt;}&lt;/span&gt;&lt;span style="color:#e6db74"&gt;`&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;};
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;(() =&amp;gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;setInterval&lt;/span&gt;(() =&amp;gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;check&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;http://localhost:5050/health&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }, &lt;span style="color:#ae81ff"&gt;50&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;setInterval&lt;/span&gt;(() =&amp;gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;check&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;http://localhost:5050/health&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }, &lt;span style="color:#ae81ff"&gt;50&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;})();
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;API projemizde UseHttpsRedirection middleware&amp;rsquo;ini kapatmazsanız, geçersiz bir SSL sertifikası hatası alabilirsiniz. Bunu aşağıdaki gibi düzeltebilirsiniz.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-js" data-lang="js"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;import&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;fetch&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;from&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;node-fetch&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;import&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;https&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;from&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;https&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;const&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;httpsAgent&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;https&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Agent&lt;/span&gt;({
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;rejectUnauthorized&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;false&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;});
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;const&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;check&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;async&lt;/span&gt; (&lt;span style="color:#a6e22e"&gt;url&lt;/span&gt;) =&amp;gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;try&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;const&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;response&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;await&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;fetch&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;url&lt;/span&gt;, {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;method&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;GET&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;agent&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;httpsAgent&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; });
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;const&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;result&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;await&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;response&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;text&lt;/span&gt;();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; (&lt;span style="color:#a6e22e"&gt;result&lt;/span&gt; &lt;span style="color:#f92672"&gt;!==&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;Healthy&amp;#34;&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;console&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;log&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;`&lt;/span&gt;&lt;span style="color:#e6db74"&gt;${&lt;/span&gt;&lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; Date().&lt;span style="color:#a6e22e"&gt;toISOString&lt;/span&gt;()&lt;span style="color:#e6db74"&gt;}&lt;/span&gt;&lt;span style="color:#e6db74"&gt;, &lt;/span&gt;&lt;span style="color:#e6db74"&gt;${&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;url&lt;/span&gt;&lt;span style="color:#e6db74"&gt;}&lt;/span&gt;&lt;span style="color:#e6db74"&gt; result is not OK`&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; } &lt;span style="color:#66d9ef"&gt;catch&lt;/span&gt; (&lt;span style="color:#a6e22e"&gt;error&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;console&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;log&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;`&lt;/span&gt;&lt;span style="color:#e6db74"&gt;${&lt;/span&gt;&lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; Date().&lt;span style="color:#a6e22e"&gt;toISOString&lt;/span&gt;()&lt;span style="color:#e6db74"&gt;}&lt;/span&gt;&lt;span style="color:#e6db74"&gt;, &lt;/span&gt;&lt;span style="color:#e6db74"&gt;${&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;url&lt;/span&gt;&lt;span style="color:#e6db74"&gt;}&lt;/span&gt;&lt;span style="color:#e6db74"&gt; error is &lt;/span&gt;&lt;span style="color:#e6db74"&gt;${&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;error&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;message&lt;/span&gt;&lt;span style="color:#e6db74"&gt;}&lt;/span&gt;&lt;span style="color:#e6db74"&gt;`&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;};
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;(() =&amp;gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;setInterval&lt;/span&gt;(() =&amp;gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;check&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;https://localhost:5051/health&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }, &lt;span style="color:#ae81ff"&gt;50&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;setInterval&lt;/span&gt;(() =&amp;gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;check&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;https://localhost:5051/health&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }, &lt;span style="color:#ae81ff"&gt;50&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;})();
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="azure-devops-repo"&gt;Azure DevOps Repo&lt;/h2&gt;
&lt;p&gt;Azure DevOps hesabımıza giriş yapıyoruz ve aşağıdaki gibi yeni bir repo oluşturuyoruz.&lt;/p&gt;
&lt;img src="https://berkselvi.dev/img/achieving-zero-downtime-azure-app-service-deployment-using-azure-devops-and-deployment-slots/azure-deployment-1.webp" alt="Azure deployment" loading="lazy" /&gt;
&lt;br/&gt;
&lt;img src="https://berkselvi.dev/img/achieving-zero-downtime-azure-app-service-deployment-using-azure-devops-and-deployment-slots/azure-deployment-2.webp" alt="Azure deployment" loading="lazy" /&gt;
&lt;p&gt;Bu repo&amp;rsquo;yu bilgisayarımıza klonluyoruz, yazdığımız kodu bu repo klasörüne taşıyoruz ve kodları origin&amp;rsquo;e iletiyoruz.&lt;/p&gt;
&lt;img src="https://berkselvi.dev/img/achieving-zero-downtime-azure-app-service-deployment-using-azure-devops-and-deployment-slots/azure-deployment-3.webp" alt="Azure deployment" loading="lazy" /&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;git add .
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;git commit -m &lt;span style="color:#e6db74"&gt;&amp;#34;inital commit&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;git push origin
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;img src="https://berkselvi.dev/img/achieving-zero-downtime-azure-app-service-deployment-using-azure-devops-and-deployment-slots/azure-deployment-4.webp" alt="Azure deployment" loading="lazy" /&gt;
&lt;h2 id="azure-devops-build-pipeline"&gt;Azure DevOps build pipeline&lt;/h2&gt;
&lt;p&gt;Pipeline ekranında, en sağ üstteki New pipeline düğmesine tıklıyoruz. Ardından aşağıdaki adımları izleyerek docker dosyasını oluşturup ve Azure Container Registry&amp;rsquo;e iteleriyoruz.&lt;/p&gt;
&lt;img src="https://berkselvi.dev/img/achieving-zero-downtime-azure-app-service-deployment-using-azure-devops-and-deployment-slots/azure-deployment-5.webp" alt="Azure deployment" loading="lazy" /&gt;
&lt;br/&gt;
&lt;img src="https://berkselvi.dev/img/achieving-zero-downtime-azure-app-service-deployment-using-azure-devops-and-deployment-slots/azure-deployment-6.webp" alt="Azure deployment" loading="lazy" /&gt;
&lt;br/&gt;
&lt;img src="https://berkselvi.dev/img/achieving-zero-downtime-azure-app-service-deployment-using-azure-devops-and-deployment-slots/azure-deployment-7.webp" alt="Azure deployment" loading="lazy" /&gt;
&lt;br/&gt;
&lt;img src="https://berkselvi.dev/img/achieving-zero-downtime-azure-app-service-deployment-using-azure-devops-and-deployment-slots/azure-deployment-8.webp" alt="Azure deployment" loading="lazy" /&gt;
&lt;br/&gt;
&lt;img src="https://berkselvi.dev/img/achieving-zero-downtime-azure-app-service-deployment-using-azure-devops-and-deployment-slots/azure-deployment-9.webp" alt="Azure deployment" loading="lazy" /&gt;
&lt;br/&gt;
&lt;img src="https://berkselvi.dev/img/achieving-zero-downtime-azure-app-service-deployment-using-azure-devops-and-deployment-slots/azure-deployment-10.webp" alt="Azure deployment" loading="lazy" /&gt;
&lt;br/&gt;
&lt;img src="https://berkselvi.dev/img/achieving-zero-downtime-azure-app-service-deployment-using-azure-devops-and-deployment-slots/azure-deployment-11.webp" alt="Azure deployment" loading="lazy" /&gt;
&lt;br/&gt;
&lt;img src="https://berkselvi.dev/img/achieving-zero-downtime-azure-app-service-deployment-using-azure-devops-and-deployment-slots/azure-deployment-12.webp" alt="Azure deployment" loading="lazy" /&gt;
&lt;br/&gt;
&lt;img src="https://berkselvi.dev/img/achieving-zero-downtime-azure-app-service-deployment-using-azure-devops-and-deployment-slots/azure-deployment-13.webp" alt="Azure deployment" loading="lazy" /&gt;
&lt;br/&gt;
&lt;img src="https://berkselvi.dev/img/achieving-zero-downtime-azure-app-service-deployment-using-azure-devops-and-deployment-slots/azure-deployment-14.webp" alt="Azure deployment" loading="lazy" /&gt;
&lt;br/&gt;
&lt;img src="https://berkselvi.dev/img/achieving-zero-downtime-azure-app-service-deployment-using-azure-devops-and-deployment-slots/azure-deployment-15.webp" alt="Azure deployment" loading="lazy" /&gt;
&lt;br/&gt;
&lt;img src="https://berkselvi.dev/img/achieving-zero-downtime-azure-app-service-deployment-using-azure-devops-and-deployment-slots/azure-deployment-16.webp" alt="Azure deployment" loading="lazy" /&gt;
&lt;br/&gt;
&lt;img src="https://berkselvi.dev/img/achieving-zero-downtime-azure-app-service-deployment-using-azure-devops-and-deployment-slots/azure-deployment-17.webp" alt="Azure deployment" loading="lazy" /&gt;
&lt;br/&gt;
&lt;img src="https://berkselvi.dev/img/achieving-zero-downtime-azure-app-service-deployment-using-azure-devops-and-deployment-slots/azure-deployment-18.webp" alt="Azure deployment" loading="lazy" /&gt;
&lt;br/&gt;
&lt;img src="https://berkselvi.dev/img/achieving-zero-downtime-azure-app-service-deployment-using-azure-devops-and-deployment-slots/azure-deployment-19.webp" alt="Azure deployment" loading="lazy" /&gt;
&lt;br/&gt;
&lt;img src="https://berkselvi.dev/img/achieving-zero-downtime-azure-app-service-deployment-using-azure-devops-and-deployment-slots/azure-deployment-20.webp" alt="Azure deployment" loading="lazy" /&gt;
&lt;p&gt;Şimdi Azure Container Registry&amp;rsquo;de image deployment işlemimizin başarılı olduğunu kontrol ediyoruz.&lt;/p&gt;
&lt;img src="https://berkselvi.dev/img/achieving-zero-downtime-azure-app-service-deployment-using-azure-devops-and-deployment-slots/azure-deployment-21.webp" alt="Azure deployment" loading="lazy" /&gt;
&lt;br/&gt;
&lt;img src="https://berkselvi.dev/img/achieving-zero-downtime-azure-app-service-deployment-using-azure-devops-and-deployment-slots/azure-deployment-22.webp" alt="Azure deployment" loading="lazy" /&gt;
&lt;br/&gt;
&lt;img src="https://berkselvi.dev/img/achieving-zero-downtime-azure-app-service-deployment-using-azure-devops-and-deployment-slots/azure-deployment-23.webp" alt="Azure deployment" loading="lazy" /&gt;
&lt;p&gt;Görüldüğü gibi, boru hattı çalıştırıldığında, Docker image&amp;rsquo;i başarıyla Azure Container Registry&amp;rsquo;de oluşturulmuş ve kullanılmak üzere bizi bekliyor. Bu ayarların ardından, main branch&amp;rsquo;de /backend dizininde yapılan her değişiklik ile tetiklenerek yeni bir Docker image&amp;rsquo;i oluşturulacak.&lt;/p&gt;
&lt;h2 id="azure-app-service"&gt;Azure App service&lt;/h2&gt;
&lt;p&gt;Azure App servisi, güvenlik, yük dengeleme, otomatik ölçeklendirme gibi özellikleriyle Azure tarafından yönetilen web uygulamalarını dağıtmamıza olanak tanır. Azure app servisleri ekranı üzerinden yeni bir app servisi oluşturabiliriz.&lt;/p&gt;
&lt;img src="https://berkselvi.dev/img/achieving-zero-downtime-azure-app-service-deployment-using-azure-devops-and-deployment-slots/azure-deployment-24.webp" alt="Azure deployment" loading="lazy" /&gt;
&lt;br/&gt;
&lt;img src="https://berkselvi.dev/img/achieving-zero-downtime-azure-app-service-deployment-using-azure-devops-and-deployment-slots/azure-deployment-25.webp" alt="Azure deployment" loading="lazy" /&gt;
&lt;br/&gt;
&lt;img src="https://berkselvi.dev/img/achieving-zero-downtime-azure-app-service-deployment-using-azure-devops-and-deployment-slots/azure-deployment-26.webp" alt="Azure deployment" loading="lazy" /&gt;
&lt;br/&gt;
&lt;img src="https://berkselvi.dev/img/achieving-zero-downtime-azure-app-service-deployment-using-azure-devops-and-deployment-slots/azure-deployment-27.webp" alt="Azure deployment" loading="lazy" /&gt;
&lt;br/&gt;
&lt;img src="https://berkselvi.dev/img/achieving-zero-downtime-azure-app-service-deployment-using-azure-devops-and-deployment-slots/azure-deployment-28.webp" alt="Azure deployment" loading="lazy" /&gt;
&lt;p&gt;App servisini oluşturduktan sonra, Container Registry şifresini yapılandırma sekmesinden güncellemeniz gerekebilir.&lt;/p&gt;
&lt;p&gt;## Azure DevOps release pipeline&lt;/p&gt;
&lt;p&gt;release pipeline ile, build pipeline tarafından oluşturulan Docker image&amp;rsquo;i kullanarak App servisine deploy edilir ve uygulamamızı yayınlamış oluruz.&lt;/p&gt;
&lt;img src="https://berkselvi.dev/img/achieving-zero-downtime-azure-app-service-deployment-using-azure-devops-and-deployment-slots/azure-deployment-29.webp" alt="Azure deployment" loading="lazy" /&gt;
&lt;br/&gt;
&lt;img src="https://berkselvi.dev/img/achieving-zero-downtime-azure-app-service-deployment-using-azure-devops-and-deployment-slots/azure-deployment-30.webp" alt="Azure deployment" loading="lazy" /&gt;
&lt;br/&gt;
&lt;img src="https://berkselvi.dev/img/achieving-zero-downtime-azure-app-service-deployment-using-azure-devops-and-deployment-slots/azure-deployment-31.webp" alt="Azure deployment" loading="lazy" /&gt;
&lt;br/&gt;
&lt;img src="https://berkselvi.dev/img/achieving-zero-downtime-azure-app-service-deployment-using-azure-devops-and-deployment-slots/azure-deployment-32.webp" alt="Azure deployment" loading="lazy" /&gt;
&lt;br/&gt;
&lt;img src="https://berkselvi.dev/img/achieving-zero-downtime-azure-app-service-deployment-using-azure-devops-and-deployment-slots/azure-deployment-33.webp" alt="Azure deployment" loading="lazy" /&gt;
&lt;br/&gt;
&lt;img src="https://berkselvi.dev/img/achieving-zero-downtime-azure-app-service-deployment-using-azure-devops-and-deployment-slots/azure-deployment-34.webp" alt="Azure deployment" loading="lazy" /&gt;
&lt;br/&gt;
&lt;img src="https://berkselvi.dev/img/achieving-zero-downtime-azure-app-service-deployment-using-azure-devops-and-deployment-slots/azure-deployment-35.webp" alt="Azure deployment" loading="lazy" /&gt;
&lt;p&gt;Staging dağıtımına girdiğimizde, staging versiyonumuz için dağıtım alırız ve Docker image&amp;rsquo;inde yaptığımız değişikliklerin canlı hale gelmesini sağlamak için uygulama servisimizi yeniden başlatırız.&lt;/p&gt;
&lt;img src="https://berkselvi.dev/img/achieving-zero-downtime-azure-app-service-deployment-using-azure-devops-and-deployment-slots/azure-deployment-36.webp" alt="Azure deployment" loading="lazy" /&gt;
&lt;br/&gt;
&lt;img src="https://berkselvi.dev/img/achieving-zero-downtime-azure-app-service-deployment-using-azure-devops-and-deployment-slots/azure-deployment-37.webp" alt="Azure deployment" loading="lazy" /&gt;
&lt;br/&gt;
&lt;img src="https://berkselvi.dev/img/achieving-zero-downtime-azure-app-service-deployment-using-azure-devops-and-deployment-slots/azure-deployment-38.webp" alt="Azure deployment" loading="lazy" /&gt;
&lt;br/&gt;
&lt;img src="https://berkselvi.dev/img/achieving-zero-downtime-azure-app-service-deployment-using-azure-devops-and-deployment-slots/azure-deployment-39.webp" alt="Azure deployment" loading="lazy" /&gt;
&lt;br/&gt;
&lt;img src="https://berkselvi.dev/img/achieving-zero-downtime-azure-app-service-deployment-using-azure-devops-and-deployment-slots/azure-deployment-40.webp" alt="Azure deployment" loading="lazy" /&gt;
&lt;br/&gt;
&lt;img src="https://berkselvi.dev/img/achieving-zero-downtime-azure-app-service-deployment-using-azure-devops-and-deployment-slots/azure-deployment-41.webp" alt="Azure deployment" loading="lazy" /&gt;
&lt;p&gt;production aşamasında, app service&amp;rsquo;de bir deployment yapmıyoruz, bunun yerine staging aşamasıyla değişim işlemini gerçekleştiriyoruz ve bu şekilde, production erişiminde herhangi bir kesinti yaşamadan staging&amp;rsquo;de çalışan uygulamamızı production ile değiştirdiğimiz için production uygulamamıza erişebiliyoruz.&lt;/p&gt;
&lt;img src="https://berkselvi.dev/img/achieving-zero-downtime-azure-app-service-deployment-using-azure-devops-and-deployment-slots/azure-deployment-42.webp" alt="Azure deployment" loading="lazy" /&gt;
&lt;br/&gt;
&lt;img src="https://berkselvi.dev/img/achieving-zero-downtime-azure-app-service-deployment-using-azure-devops-and-deployment-slots/azure-deployment-43.webp" alt="Azure deployment" loading="lazy" /&gt;
&lt;br/&gt;
&lt;img src="https://berkselvi.dev/img/achieving-zero-downtime-azure-app-service-deployment-using-azure-devops-and-deployment-slots/azure-deployment-44.webp" alt="Azure deployment" loading="lazy" /&gt;
&lt;br/&gt;
&lt;img src="https://berkselvi.dev/img/achieving-zero-downtime-azure-app-service-deployment-using-azure-devops-and-deployment-slots/azure-deployment-45.webp" alt="Azure deployment" loading="lazy" /&gt;
&lt;p&gt;Şimdi, main bracnh&amp;rsquo;te yapılan herhangi bir değişiklik önce build pipeline (CI) ile geçecek, ardından release pipeline (CD) geçecek ve release, staging ortamına yapılacak. Production ortamına geçmek isterseniz, production deployment işlemi manuel olarak release ekranından tetiklenmelidir.&lt;/p&gt;
&lt;img src="https://berkselvi.dev/img/achieving-zero-downtime-azure-app-service-deployment-using-azure-devops-and-deployment-slots/azure-deployment-46.webp" alt="Azure deployment" loading="lazy" /&gt;
&lt;h2 id="zero-downtime-testing"&gt;Zero downtime testing&lt;/h2&gt;
&lt;p&gt;Uygulamamız için gerekli pipeline&amp;rsquo;ları başarıyla oluşturduk, bundan sonra release sırasında herhangi bir kesinti olup olmadığını test etmemiz gerekecek. Bunun için &lt;em&gt;health-check/index.js&lt;/em&gt; dosyamızı aşağıdaki gibi güncelliyorum ve uygulamayı npm run start komutuyla çalıştırıp pipeline&amp;rsquo;ı tetikliyorum. Ardından, konsolda herhangi bir hata mesajı almadan, yani herhangi bir kesinti olmadan deployment sürecini tamamlıyorum!&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-js" data-lang="js"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;import&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;fetch&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;from&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;node-fetch&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;const&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;check&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;async&lt;/span&gt; (&lt;span style="color:#a6e22e"&gt;url&lt;/span&gt;) =&amp;gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;try&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;const&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;response&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;await&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;fetch&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;url&lt;/span&gt;, {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;method&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;GET&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; });
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;const&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;result&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;await&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;response&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;text&lt;/span&gt;();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; (&lt;span style="color:#a6e22e"&gt;result&lt;/span&gt; &lt;span style="color:#f92672"&gt;!==&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;Healthy&amp;#34;&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;console&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;log&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;`&lt;/span&gt;&lt;span style="color:#e6db74"&gt;${&lt;/span&gt;&lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; Date().&lt;span style="color:#a6e22e"&gt;toISOString&lt;/span&gt;()&lt;span style="color:#e6db74"&gt;}&lt;/span&gt;&lt;span style="color:#e6db74"&gt;, &lt;/span&gt;&lt;span style="color:#e6db74"&gt;${&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;url&lt;/span&gt;&lt;span style="color:#e6db74"&gt;}&lt;/span&gt;&lt;span style="color:#e6db74"&gt; result is not OK`&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; } &lt;span style="color:#66d9ef"&gt;catch&lt;/span&gt; (&lt;span style="color:#a6e22e"&gt;error&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;console&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;log&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;`&lt;/span&gt;&lt;span style="color:#e6db74"&gt;${&lt;/span&gt;&lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; Date().&lt;span style="color:#a6e22e"&gt;toISOString&lt;/span&gt;()&lt;span style="color:#e6db74"&gt;}&lt;/span&gt;&lt;span style="color:#e6db74"&gt;, &lt;/span&gt;&lt;span style="color:#e6db74"&gt;${&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;url&lt;/span&gt;&lt;span style="color:#e6db74"&gt;}&lt;/span&gt;&lt;span style="color:#e6db74"&gt; error is &lt;/span&gt;&lt;span style="color:#e6db74"&gt;${&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;error&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;message&lt;/span&gt;&lt;span style="color:#e6db74"&gt;}&lt;/span&gt;&lt;span style="color:#e6db74"&gt;`&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;};
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;(() =&amp;gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;setInterval&lt;/span&gt;(() =&amp;gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;check&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;https://deployment-slot-demo.azurewebsites.net/health&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }, &lt;span style="color:#ae81ff"&gt;50&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;setInterval&lt;/span&gt;(() =&amp;gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;check&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;https://deployment-slot-demo-staging.azurewebsites.net/health&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }, &lt;span style="color:#ae81ff"&gt;50&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;})();
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Benzer bir işlemi deployment slots özelliği uygulamayan bir App Service ile denersek, aşağıdaki gibi bir hata alırız.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;2023-11-14T12:28:39.506Z, &lt;span style="color:#f92672"&gt;[&lt;/span&gt;some-url&lt;span style="color:#f92672"&gt;]&lt;/span&gt;/health error is request to &lt;span style="color:#f92672"&gt;[&lt;/span&gt;some-url&lt;span style="color:#f92672"&gt;]&lt;/span&gt;/health failed, reason: connect ETIMEDOUT 100.100.100.100:443
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;hr&gt;
&lt;h2 id="sonuç"&gt;Sonuç&lt;/h2&gt;
&lt;p&gt;Okuduğunuz için teşekkürler! 🎉 Yazılım geliştirme alanındaki araştırmalarımı kaçırmamak için &lt;a href="https://x.com/berkslv"&gt;@berkslv&lt;/a&gt; adresinden takipte kalabilirsiniz.&lt;/p&gt;</content></item><item><title>React-router-dom kullanarak bir React uygulamasını Docker ile Nginx'e nasıl deploy ederiz?</title><link>https://berkselvi.dev/tr/posts/how-to-deploy-a-react-app-with-nginx-using-docker-with-react-router-dom/</link><pubDate>Sat, 21 Jan 2023 00:00:00 +0300</pubDate><guid>https://berkselvi.dev/tr/posts/how-to-deploy-a-react-app-with-nginx-using-docker-with-react-router-dom/</guid><description>&lt;p&gt;Bir React uygulamasını deploy etmek istiyorsanız, web sunucusuna deployment veya Docker gibi bir container aracı kullanma gibi birkaç seçenek mevcuttur. Bu kılavuzda, bir React uygulamasını Nginx kullanarak Docker ile nasıl deploy edebileceğimizi göreceğiz. Nginx, yüksek performansı ve düşük kaynak kullanımı ile tanınan popüler bir açık kaynaklı web sunucusudur. React uygulamamız için bir Docker dosyası oluşturacağız ve uygulamayı sunmak için Nginx&amp;rsquo;i kullanacağız. Ayrıca, Nginx&amp;rsquo;e uygulamayı nasıl sunacağını söylemek için bir Nginx yapılandırma dosyası da oluşturacağız. Son olarak, uygulamamızın bir Docker image&amp;rsquo;ini oluşturacak ve uygulamayı deploy etmek için bir Docker container çalıştıracağız.&lt;/p&gt;</description><content>&lt;p&gt;Bir React uygulamasını deploy etmek istiyorsanız, web sunucusuna deployment veya Docker gibi bir container aracı kullanma gibi birkaç seçenek mevcuttur. Bu kılavuzda, bir React uygulamasını Nginx kullanarak Docker ile nasıl deploy edebileceğimizi göreceğiz. Nginx, yüksek performansı ve düşük kaynak kullanımı ile tanınan popüler bir açık kaynaklı web sunucusudur. React uygulamamız için bir Docker dosyası oluşturacağız ve uygulamayı sunmak için Nginx&amp;rsquo;i kullanacağız. Ayrıca, Nginx&amp;rsquo;e uygulamayı nasıl sunacağını söylemek için bir Nginx yapılandırma dosyası da oluşturacağız. Son olarak, uygulamamızın bir Docker image&amp;rsquo;ini oluşturacak ve uygulamayı deploy etmek için bir Docker container çalıştıracağız.&lt;/p&gt;
&lt;h2 id="önkoşullar"&gt;Önkoşullar:&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Docker hakkında temel bir bilgi&lt;/li&gt;
&lt;li&gt;Deploy edilecek bir React uygulaması&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="step-1-create-a-dockerfile"&gt;Step 1: Create a Dockerfile&lt;/h2&gt;
&lt;p&gt;İlk adım, React uygulamamız için bir Docker dosyası oluşturmaktır. Bu dosya, uygulamamızın bir Docker görüntüsünü nasıl oluşturacağımıza dair talimatlar içerecektir. İşte Nginx ile bir React uygulaması için bir Docker dosyası örneği:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-Dockerfile" data-lang="Dockerfile"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# Use an official Node runtime as a parent image&lt;/span&gt;&lt;span style="color:#960050;background-color:#1e0010"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;FROM&lt;/span&gt; &lt;span style="color:#e6db74"&gt;node:19-alpine&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;as&lt;/span&gt; &lt;span style="color:#e6db74"&gt;build&lt;/span&gt;&lt;span style="color:#960050;background-color:#1e0010"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#960050;background-color:#1e0010"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# Set the working directory to /app&lt;/span&gt;&lt;span style="color:#960050;background-color:#1e0010"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;WORKDIR&lt;/span&gt; &lt;span style="color:#e6db74"&gt;/app&lt;/span&gt;&lt;span style="color:#960050;background-color:#1e0010"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#960050;background-color:#1e0010"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# Copy the package.json and package-lock.json to the container&lt;/span&gt;&lt;span style="color:#960050;background-color:#1e0010"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;COPY&lt;/span&gt; package*.json ./&lt;span style="color:#960050;background-color:#1e0010"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#960050;background-color:#1e0010"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# Install dependencies&lt;/span&gt;&lt;span style="color:#960050;background-color:#1e0010"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;RUN&lt;/span&gt; npm install&lt;span style="color:#960050;background-color:#1e0010"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#960050;background-color:#1e0010"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# Copy the rest of the application code to the container&lt;/span&gt;&lt;span style="color:#960050;background-color:#1e0010"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;COPY&lt;/span&gt; . .&lt;span style="color:#960050;background-color:#1e0010"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#960050;background-color:#1e0010"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# Build the React app&lt;/span&gt;&lt;span style="color:#960050;background-color:#1e0010"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;RUN&lt;/span&gt; npm run build&lt;span style="color:#960050;background-color:#1e0010"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#960050;background-color:#1e0010"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# Use an official Nginx runtime as a parent image&lt;/span&gt;&lt;span style="color:#960050;background-color:#1e0010"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;FROM&lt;/span&gt; &lt;span style="color:#e6db74"&gt;nginx:1.21.0-alpine&lt;/span&gt;&lt;span style="color:#960050;background-color:#1e0010"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#960050;background-color:#1e0010"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# Copy the ngnix.conf to the container&lt;/span&gt;&lt;span style="color:#960050;background-color:#1e0010"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;COPY&lt;/span&gt; ngnix.conf /etc/nginx/conf.d/default.conf&lt;span style="color:#960050;background-color:#1e0010"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#960050;background-color:#1e0010"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# Copy the React app build files to the container&lt;/span&gt;&lt;span style="color:#960050;background-color:#1e0010"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;COPY&lt;/span&gt; --from&lt;span style="color:#f92672"&gt;=&lt;/span&gt;build /app/build /usr/share/nginx/html&lt;span style="color:#960050;background-color:#1e0010"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#960050;background-color:#1e0010"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# Expose port 80 for Nginx&lt;/span&gt;&lt;span style="color:#960050;background-color:#1e0010"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;EXPOSE&lt;/span&gt; &lt;span style="color:#e6db74"&gt;80&lt;/span&gt;&lt;span style="color:#960050;background-color:#1e0010"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#960050;background-color:#1e0010"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# Start Nginx when the container starts&lt;/span&gt;&lt;span style="color:#960050;background-color:#1e0010"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;CMD&lt;/span&gt; [&lt;span style="color:#e6db74"&gt;&amp;#34;nginx&amp;#34;&lt;/span&gt;, &lt;span style="color:#e6db74"&gt;&amp;#34;-g&amp;#34;&lt;/span&gt;, &lt;span style="color:#e6db74"&gt;&amp;#34;daemon off;&amp;#34;&lt;/span&gt;]&lt;span style="color:#960050;background-color:#1e0010"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Bu Docker dosyası, offical Node runtime ana image&amp;rsquo;i olarak kullanarak React uygulamasını kurmak ve derlemek için kullanır. Daha sonra React uygulamasını sunmak için resmi Nginx runtime ana image&amp;rsquo;ini base olarak kullanır.&lt;/p&gt;
&lt;h1 id="step-2-create-an-nginx-configuration-file"&gt;Step 2: Create an Nginx configuration file&lt;/h1&gt;
&lt;p&gt;Sonraki adım olarak, Nginx&amp;rsquo;e uygulamamızı nasıl sunacağını söylemek için bir Nginx yapılandırma dosyası oluşturmamız gerekiyor. İşte bir örnek yapılandırma dosyası:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code class="language-conf" data-lang="conf"&gt;server {
listen 80;
server_name example.com;
root /usr/share/nginx/html;
index index.html;
location / {
try_files $uri $uri/ /index.html;
}
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;strong&gt;Bu &lt;code&gt;nginx.conf&lt;/code&gt; dosyasını Dockerfile ile aynı dizine oluşturmalısınız.&lt;/strong&gt; Bu yapılandırma dosyası, Nginx&amp;rsquo;in port 80&amp;rsquo;de dinlemesini ve React uygulamasını sunmasını sağlar. try_files yönergesi, Nginx&amp;rsquo;e istenen dosyayı sunmaya çalışmasını, ardından dosyayı içeren dizini ve son olarak istenen dosya veya dizin mevcut değilse index.html dosyasını sunmaya çalışmasını söyler.&lt;/p&gt;
&lt;h1 id="step-3-build-the-docker-image"&gt;Step 3: Build the Docker image&lt;/h1&gt;
&lt;p&gt;Docker dosyası ve Nginx yapılandırma dosyası oluşturulduktan sonra, şimdi Docker image&amp;rsquo;i oluşturabiliriz. Docker dosyasını içeren dizine gidin ve aşağıdaki komutu çalıştırın:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;docker build -t my-react-app .
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Bu komut, Docker&amp;rsquo;a mevcut dizindeki Docker dosyasını kullanarak bir image oluşturmasını ve ona my-react-app adını etiketlemesini söyler. Komutun sonundaki nokta, Docker&amp;rsquo;ın oluşturma bağlamı olarak mevcut dizini kullanmasını sağlar.&lt;/p&gt;
&lt;h1 id="step-4-run-the-docker-container"&gt;Step 4: Run the Docker container&lt;/h1&gt;
&lt;p&gt;Oluşturulan Docker görüntüsüyle şimdi aşağıdaki komutu kullanarak bir container çalıştırabiliriz:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;docker run -p 80:80 my-react-app
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Bu komut, my-react-app Docker görüntüsünden bir konteyner oluşturur ve konteynerdeki 80 numaralı bağlantı noktasını ana makinedeki 80 numaralı bağlantı noktasına eşler. Ardından, web tarayıcınızda http://localhost adresine giderek React uygulamanıza erişebilirsiniz.&lt;/p&gt;
&lt;p&gt;Tebrikler, bir React uygulamasını Nginx kullanarak Docker ile başarıyla dağıttınız! Artık bu yöntemi kullanarak React uygulamalarınızı Docker&amp;rsquo;ı destekleyen herhangi bir sunucuya veya sunucu platformuna kolayca deploy edebilirsiniz.&lt;/p&gt;
&lt;h1 id="step-5-use-docker-compose-to-deploy-the-app"&gt;Step 5: Use Docker Compose to deploy the app&lt;/h1&gt;
&lt;p&gt;Uygulamanızı deploy etmek için daha basit bir yaklaşımı tercih ediyorsanız, container&amp;rsquo;larınızı yönetmek için Docker Compose&amp;rsquo;u kullanabilirsiniz. Docker Compose, çoklu container Docker uygulamalarını tanımlamanıza ve çalıştırmanıza olanak tanıyan bir araçtır.&lt;/p&gt;
&lt;p&gt;Docker Compose&amp;rsquo;u kullanmak için, projenizin root dizininde bir docker-compose.yml dosyası oluşturmanız gerekecektir. İşte önceki Docker dosyasıyla kullanabileceğiniz bir örnek docker-compose.yml dosyası:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;version&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#39;3&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;services&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;my-react-app&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;build&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;context&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;dockerfile&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;Dockerfile&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;ports&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#e6db74"&gt;&amp;#34;80:80&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;volumes&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#ae81ff"&gt;./ngnix.conf:/etc/nginx/conf.d/default.conf&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Bu docker-compose.yml dosyasında, my-react-app adında tek bir servis tanımlıyoruz. Oluşturma bağlamını (.) ve Docker dosyasının adını Dockerfile olarak belirtiyoruz. Ayrıca, konteynerdeki 80 numaralı bağlantı noktasını ana makinedeki 80 numaralı bağlantı noktasına ports yapılandırması kullanarak eşleriz.&lt;/p&gt;
&lt;p&gt;Son olarak, ana makinedeki ngnix.conf dosyasını konteynerin Nginx yapılandırma dizinindeki default.conf dosyasına eşleyen bir volume bağlarız. Bu, Docker görüntüsünü yeniden oluşturmadan Nginx yapılandırmasını özelleştirmemizi sağlar.&lt;/p&gt;
&lt;p&gt;Bu docker-compose.yml dosyasını kullanmak için, sadece dosyayı içeren dizine gidin ve aşağıdaki komutu çalıştırın:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;docker-compose up -d
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Bu komut, Docker Compose&amp;rsquo;un my-react-app servisini Docker dosyasını kullanarak oluşturmasını ve bir konteyner çalıştırmasını söyler. -d bayrağı, konteynerin arka planda çalışmasını sağlayan detached modunda çalıştırır. Ardından, web tarayıcınızda http://localhost adresine giderek React uygulamanıza erişebilirsiniz.&lt;/p&gt;
&lt;p&gt;Tebrikler! Bir React uygulamasını Nginx kullanarak Docker ve Docker Compose ile başarıyla dağıttınız. Artık React uygulamalarınızı Docker&amp;rsquo;ı destekleyen herhangi bir sunucuya veya barındırma platformuna kolayca dağıtabilirsiniz.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="sonuç"&gt;Sonuç&lt;/h2&gt;
&lt;p&gt;Okuduğunuz için teşekkürler! 🎉 Yazılım geliştirme alanındaki araştırmalarımı kaçırmamak için &lt;a href="https://x.com/berkslv"&gt;@berkslv&lt;/a&gt; adresinden takipte kalabilirsiniz.&lt;/p&gt;</content></item><item><title>Bir şey nasıl çok iyi öğrenilir</title><link>https://berkselvi.dev/tr/posts/how-to-learn-something-very-well/</link><pubDate>Wed, 09 Mar 2022 10:01:10 +0300</pubDate><guid>https://berkselvi.dev/tr/posts/how-to-learn-something-very-well/</guid><description>&lt;p&gt;Nasıl öğreneceğinizi öğrendiğinizde, bilgi ile dünyadaki hemen hemen tüm olasılıkların kilidini açarsınız. Bazılarınız pratiğin bilgiden daha önemli olduğunu düşünebilir, ama inanın bana, çalışma alanınız hakkında bir şey bilmiyorsanız, pratik yapamazsınız ve yapabilirseniz de işe yaramaz çünkü altta yatan süreçleri bilmiyorsunuzdur.&lt;/p&gt;
&lt;p&gt;Bu kılavuz basit ve sonuç odaklıdır, &lt;a href="https://www.goodreads.com/book/show/18770267-make-it-stick"&gt;bu harika kitapla&lt;/a&gt; konu hakkında daha fazla bilgi edinebilirsiniz.&lt;/p&gt;
&lt;h2 id="öğrenmeden-önce"&gt;Öğrenmeden önce&lt;/h2&gt;
&lt;p&gt;Zihin sabit değildir ve geliştirilebilir, sadece zihin ve IQ değil, entelektüel beceriler de çalışarak geliştirilebilir. Her şeyden önce sağlam bir öz disipline sahip olmalısınız, bu farklı şekillerde kazanılabilir, ancak kendi yolunuzu bulmalı ve izlemelisiniz. Dilerseniz askeri disiplin yöntemlerini takip edebilir, dilerseniz Uzakdoğu felsefelerine sadık kalabilirsiniz. Tamamen size kalmış.&lt;/p&gt;</description><content>&lt;p&gt;Nasıl öğreneceğinizi öğrendiğinizde, bilgi ile dünyadaki hemen hemen tüm olasılıkların kilidini açarsınız. Bazılarınız pratiğin bilgiden daha önemli olduğunu düşünebilir, ama inanın bana, çalışma alanınız hakkında bir şey bilmiyorsanız, pratik yapamazsınız ve yapabilirseniz de işe yaramaz çünkü altta yatan süreçleri bilmiyorsunuzdur.&lt;/p&gt;
&lt;p&gt;Bu kılavuz basit ve sonuç odaklıdır, &lt;a href="https://www.goodreads.com/book/show/18770267-make-it-stick"&gt;bu harika kitapla&lt;/a&gt; konu hakkında daha fazla bilgi edinebilirsiniz.&lt;/p&gt;
&lt;h2 id="öğrenmeden-önce"&gt;Öğrenmeden önce&lt;/h2&gt;
&lt;p&gt;Zihin sabit değildir ve geliştirilebilir, sadece zihin ve IQ değil, entelektüel beceriler de çalışarak geliştirilebilir. Her şeyden önce sağlam bir öz disipline sahip olmalısınız, bu farklı şekillerde kazanılabilir, ancak kendi yolunuzu bulmalı ve izlemelisiniz. Dilerseniz askeri disiplin yöntemlerini takip edebilir, dilerseniz Uzakdoğu felsefelerine sadık kalabilirsiniz. Tamamen size kalmış.&lt;/p&gt;
&lt;p&gt;Ve unutmayın, para gibi değerli şeyler birikir. Buna bilgi de dahildir ve ne kadar çok yeni şeyler öğrenirseniz, yeni şeyleri öğrenmek ve anlamlandırmak o kadar kolay olur.&lt;/p&gt;
&lt;h2 id="öğrenmek-değil"&gt;Öğrenmek değil&lt;/h2&gt;
&lt;p&gt;Aynı konuyu tekrar tekrar çalışmak, aynı şeyleri tekrar tekrar okumak öğrenme hissi yaratabilir ama bu büyük ihtimalle yanlış bir duygudur. öğrenme duygusu ve akıcılık birbiriyle kolayca karıştırılabilir.&lt;/p&gt;
&lt;p&gt;Kendinizi test etmeden devam etmeyin, bu şekilde öğrendiğinizi düşünebilirsiniz, ancak büyük olasılıkla bu da yanlış bir duygu olacaktır.&lt;/p&gt;
&lt;h2 id="yapılacak-şeyler"&gt;Yapılacak şeyler&lt;/h2&gt;
&lt;p&gt;Hafızadan hatırlama, tekrarlı okuma gibi yöntemlere göre çok daha harika bir yöntemdir. Makaleleri okurken cümlelerin veya paragrafların başında durun ve “Ana fikir nedir? Vermek istediğiniz mesaj nedir?” Bunun gibi sorular sormak, geri arama yöntemimiz için harika bir uygulamadır. Bu sayede öğrenilen konularla ilgili zihinsel bağlantılar güçlendirilecektir.&lt;/p&gt;
&lt;p&gt;Bölüm sonu soruları ve kendi oluşturduğunuz sorular harika öğrenme yöntemleridir. Burada quizlerde ve sınavlarda kullanılabilir, Quizleri bir öğrenme aracı olarak kullanmak tehlike açısından alışılmışın dışında olabilir, ancak hatırlama için çok iyi çalışır ve sorulara verdiğiniz cevaplar önemlidir. Eksiklerinizi bu şekilde ortaya koyabilirsiniz ve bu alanları tamamlamak sizi ileriye taşıyacaktır.&lt;/p&gt;
&lt;p&gt;Bu iki yöntem tamamen geri arama (callback) ile ilgilidir. Geri çağırma konusundaki son söz, ne kadar zorlayıcı olursa, etkilerinin o kadar kalıcı olacağıdır.&lt;/p&gt;
&lt;p&gt;Blok çalışmanın kısa vadeli faydalarını kullanmak yerine, konuya aralıklı çalışma ile yaklaşmak bilgiyi daha kalıcı hale getirecektir. Gerekli aralık, çalışılan konuya ve kişiye bağlıdır. 1 gün, 1 hafta, 1 ay gibi. Bir konuyu periyodik olarak hatırlamaya çalışmak, onu hafızada kalıcı kılar ve artık bir refleks gibi olur.&lt;/p&gt;
&lt;p&gt;Bir matematik konusuyla ilgili benzer soruları arka arkaya çözmek aşinalık hissi yaratır, ancak uygulama sırasında aynı konudaki diğer sorularla farklılıkları ve benzerlikleri görmenize yardımcı olmaz. Soruları çözerken soru tekrarı yaptığınızı fark ederseniz hemen farklı soru türlerine geçin.&lt;/p&gt;
&lt;p&gt;Öğrendiğiniz konuya yeni anlam katmanları eklemek detaylandırmadır. Buna bir örnek, bir konuyu not alırken kendi cümlelerinizle ifade etmek ve gerçek hayatla ilişkisini incelemektir. Yeni öğrenilen konunun daha önce öğrendiklerinizle ilişkisini incelemek öğrenmeyi artırır. Konuyu kendiniz ve hayatınız ile ilişkilendirin.&lt;/p&gt;
&lt;p&gt;Öğrenmeden önce üretin. Bir matematik sorusunu cevap verilmeden önce bildiklerinizle çözmeye çalışmak, çözüm anlatıldıktan sonra çözümü kavramanızı ve hatırlamanızı kolaylaştırır.&lt;/p&gt;
&lt;p&gt;Geri çağırma ve detaylandırmayı harmanlayın. Bir konu veya dersten sonra kendinize sorun, “Nasıl gitti? Daha iyi olabilir mi? Bu sana geçmişten bir şey hatırlatıyor mu?” Bunun gibi sorular sormak öğrenmeye yardımcı olur ve buna &lt;strong&gt;yansıtma&lt;/strong&gt; denir.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="sonuç"&gt;Sonuç&lt;/h2&gt;
&lt;p&gt;Okuduğunuz için teşekkürler! 🎉 Yazılım geliştirme alanındaki araştırmalarımı kaçırmamak için &lt;a href="https://x.com/berkslv"&gt;@berkslv&lt;/a&gt; adresinden takipte kalabilirsiniz.&lt;/p&gt;</content></item><item><title>Her Geliştiricinin Sahip Olması Gereken 5 Temel Felsefe</title><link>https://berkselvi.dev/tr/posts/5-core-philosophies-that-every-developer-should-have/</link><pubDate>Sun, 26 Sep 2021 00:00:00 +0300</pubDate><guid>https://berkselvi.dev/tr/posts/5-core-philosophies-that-every-developer-should-have/</guid><description>&lt;p&gt;Ben kendim, yazılımın bir işten önce bir yaşam felsefesi olduğunu düşünüyorum ve çoğu sorunu bir yazılım geliştirici olarak yaklaşarak ve bu şekilde çözerek büyük keyif alıyorum. Bu makalede, bu felsefenin temel taşlarından bahsedeceğim.&lt;/p&gt;
&lt;h2 id="içindekiler"&gt;İçindekiler&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Sorumlu bir kişi olun&lt;/li&gt;
&lt;li&gt;Kodunuzda hiçbir şeyi mahvetmeyin&lt;/li&gt;
&lt;li&gt;Mükemmeliyetçi olmayın&lt;/li&gt;
&lt;li&gt;Varlığınız bilginizdir&lt;/li&gt;
&lt;li&gt;Topluluğunuzu geliştirin!&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="sorumlu-bir-kişi-olun"&gt;Sorumlu bir kişi olun&lt;/h2&gt;
&lt;p&gt;Bir programcı olarak, yazdığınız JavaScript kodunda bir hata varsa, bu hatanın nedeninin C++ derleyicisi olduğunu söyleyemezsiniz. Bu saçma gelebilir, ancak bu durumu kendinize uyarlamayı düşünün!&lt;/p&gt;</description><content>&lt;p&gt;Ben kendim, yazılımın bir işten önce bir yaşam felsefesi olduğunu düşünüyorum ve çoğu sorunu bir yazılım geliştirici olarak yaklaşarak ve bu şekilde çözerek büyük keyif alıyorum. Bu makalede, bu felsefenin temel taşlarından bahsedeceğim.&lt;/p&gt;
&lt;h2 id="içindekiler"&gt;İçindekiler&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Sorumlu bir kişi olun&lt;/li&gt;
&lt;li&gt;Kodunuzda hiçbir şeyi mahvetmeyin&lt;/li&gt;
&lt;li&gt;Mükemmeliyetçi olmayın&lt;/li&gt;
&lt;li&gt;Varlığınız bilginizdir&lt;/li&gt;
&lt;li&gt;Topluluğunuzu geliştirin!&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="sorumlu-bir-kişi-olun"&gt;Sorumlu bir kişi olun&lt;/h2&gt;
&lt;p&gt;Bir programcı olarak, yazdığınız JavaScript kodunda bir hata varsa, bu hatanın nedeninin C++ derleyicisi olduğunu söyleyemezsiniz. Bu saçma gelebilir, ancak bu durumu kendinize uyarlamayı düşünün!&lt;/p&gt;
&lt;p&gt;Bir iş almadan önce, o işin gereksinimlerini iyi planlayın çünkü projede uzun bir yol kat ettiğinizde ve bir müşteriye veya patronunuza gittiğinizde size geçerli bir soru sorarlar, &amp;ldquo;neden bunu önceden düşünmediniz&amp;rdquo; Bu tür durumları göz önünde bulundurarak işe koyulursanız, karşılaşabileceğiniz herhangi bir soruya hazırlıklı olun. Ancak verdiğiniz cevap kesinlikle &amp;ldquo;bu imkansız yapılacak bir şey değil!&amp;rdquo; olmamalıdır. O problem için uygun farklı çözüm alternatifleri sunmaya çalışmalısınız.&lt;/p&gt;
&lt;h2 id="kodunuzda-hiçbir-şeyi-mahvetmeyin"&gt;Kodunuzda hiçbir şeyi mahvetmeyin&lt;/h2&gt;
&lt;p&gt;Yazılımı bir bina gibi düşünün, bir apartmandaki bir pencere kırıldığında ve onarılmadığında, başka bir pencere kırıldığında ve kimse ilgilenmezse, bu böyle devam eder ve sonunda duvarlara grafiti yazılmaya başlar ve bina tamamen düzensiz hale gelir. Sonra; asla kırık bir camla yaşamayın. Bu yazılım için kırık cam örneği, gelecekteki hatalara yol açabilecek kötü tasarımı ve kötü bir kodu içerebilir.&lt;/p&gt;
&lt;p&gt;Aslında bu durum oldukça bilimsel, fizikte entropiyi düşünürseniz, düzensizlik her zaman artmak ister. Bunu engellememek size bağlıdır. Yazılımda bir hata varsa, yeni bir özellik eklemeye odaklanmaktan önce onu düzeltmeye odaklanın ve devam edin. Bunu yapamam dediğinizde değil, yaptım dediğinizde durun.&lt;/p&gt;
&lt;h2 id="mükemmeliyetçi-olmayın"&gt;Mükemmeliyetçi olmayın&lt;/h2&gt;
&lt;p&gt;Harika bir fikriniz varsa, üzerinde bir yıl veya iki yıl çalıştıktan ve yayınladıktan sonra, geri bildirimlerden öğrenmek çok hayal kırıklığı olabilir ve gerçekten değersiz bir fikir olduğunu öğrenmek. Herhangi bir projede kusursuz ilerlemeden önce, o projenin temel özelliklerine sahip bir MVP&amp;rsquo;ye sahip olmak ve onu yayınlamak, geri bildirimleri değerlendirmek ve her şey yolundaysa ilerlemek çok daha mantıklıdır.&lt;/p&gt;
&lt;p&gt;Kısacası, mükemmeliyetçilik genellikle yazılım için işe yaramaz. Üründen emin olmak ve sonra onu mükemmelleştirmek daha iyi bir fikirdir. Sonuç olarak, dünya her gün değişiyor ve her dakika yeni teknolojiler ve ürünler ortaya çıkıyor!&lt;/p&gt;
&lt;h2 id="varlığınız-bilginizdir"&gt;Varlığınız bilginizdir&lt;/h2&gt;
&lt;p&gt;Bilginiz ve deneyiminiz en önemli varlığınızdır. Ancak bu varlıklar zamanla eskimiş olabilir, özellikle teknoloji dünyasında, her şey çok hızlı bir şekilde değişiyor. Bir yıl önce sahip olduğunuz değerli varlıklar şimdi değersiz olabilir. Bu yüzden güncel kalmaya odaklanmalısınız.&lt;/p&gt;
&lt;p&gt;Bir bilgi ve deneyim portföyünü yönetmek, finansal varlıkları yönetmek gibi belirli gereksinimlere sahiptir;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Ciddi yatırımcılar alışkanlık olarak düzenli olarak yatırım yaparlar: Düzenli olarak yatırım yaparsanız, ne kadar az veya çok kazanırsanız kazanın, toplamda büyük bir kar elde edersiniz.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Çeşitlendirme uzun vadeli başarının anahtarıdır: Ne kadar fazla teknolojiye hakim olursanız, değişime o kadar iyi adapte olabilirsiniz.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Akıllı yatırımcılar portföylerini düşük risk, düşük getiri ve yüksek risk, yüksek getiri yatırımları arasında dengelemektedir: Tüm varlığınızı (zaman ve çaba) gelecekte değerli olacağını düşündüğünüz yüksek getirili bir varlığa yatırmak yerine, varlığınızı güvenli dall&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;ara dalın ve varlığınızı aynı kutuya koymayın.&lt;/p&gt;
&lt;ol start="4"&gt;
&lt;li&gt;
&lt;p&gt;Yatırımcılar maksimum getiri için düşük al ve yüksek satın: Gelecekte değer kazanacağını düşündüğünüz varlıklara yatırım yapmak büyük getiriler sağlayabilir.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Portföyler periyodik olarak gözden geçirilmeli ve yeniden dengelenmelidir: Çok dalgalı bir endüstride iş yapıyoruz, bu yüzden yatırımlarınızı düzenli olarak gözden geçirmeli ve değer kaybeden varlıklara dikkat etmelisiniz.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Ancak tüm bu hedeflerin en önemlisi ve vazgeçilmezi düzenli olarak yatırım yapmaktır. Yatırım yapmazsanız, nereye yatırım yaparsanız yapmanızın bir önemi yoktur. Bu hedeflere ulaşmanın bir aracı olarak kullanabileceğiniz birkaç araç aşağıda verilmiştir.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Her yıl en az bir yeni dil öğrenin. Farklı diller aynı sorunları farklı yollarla çözer. Farklı yaklaşımları öğrenmek düşünme yeteneğinizi genişletebilir ve rutine sıkışıp kalmaktan kaçınmanıza yardımcı olabilir.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Her çeyrek teknik bir kitap okuyun. Alışkanlık kazandıktan sonra, ayda bir kitap okuyun. Kullanmakta olduğunuz teknolojileri öğrendikten sonra, farklı dallarda çalışın ve ilgisiz dallarda çalışın.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Teknik olmayan kitaplar da okuyun. Bilgisayarlar insanlar içindir ve insanları daha iyi anlarsanız, onların çözümlerine çok daha iyi yaklaşabilirsiniz.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Yerel kullanıcı gruplarına katılın. Sadece gitmeyin ve dinlemeyin, aktif olarak katılın. İzolasyon kariyerinize ölümcül olabilir.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Bu teknolojilerden herhangi birini bir projede kullansanız veya hatta özgeçmişinize koyduğunuzda önemli değil. Bunları öğrenmek öğrenme yeteneğinizi artırır ve problem çözme yeteneğinize katkı sağlar.&lt;/p&gt;
&lt;p&gt;Bir sorunla karşılaşırsanız ve bunun cevabını bilmiyorsanız, bu sorunu bir hedef olarak seçin ve öğrenmeye çalışın. Etrafınızda bir guruya sorun, etrafınızda yoksa çevrimiçi bir tane bulun. Bu hem sorunu yanıtlayacak hem de ağınızı genişletecektir. Sürekli olarak öğrenmeye devam edin. Otobüste seyahat ederken okuyacak bir şey taşıyın ve değerlendirin.&lt;/p&gt;
&lt;p&gt;Bir kitapçının size pazarladığı ürünün en iyi ürünü olmadığı gibi, arama motorlarında ilk sıralara çıkan içeriğe güvenmeyin, doğruluğunu düşünün ve kafanızda tartın ve sorgusuz sualsiz kabul etmeyin.&lt;/p&gt;
&lt;h2 id="topluluğunuzu-geliştirin"&gt;Topluluğunuzu geliştirin!&lt;/h2&gt;
&lt;p&gt;Etkili iletişiminiz olmadığında iyi bir fikir yetimdir. Geliştiriciler olarak, son kullanıcılarla iletişim kurarken veya belgeler oluştururken, patronlarımız veya üstlerimizle iletişim kurarken iletişim becerilerimizi kullanıyoruz. İletişim becerilerinizi güçlendirmenin birkaç yolu şunlardır;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Bir programcı olarak göreviniz kelime oyunları oynamak değil, istediğiniz bilgileri doğru ve net bir şekilde iletmektir. Ne demek istediğinizi planlayın. Bir taslak yazın. Sonra kendinize sorun, &amp;ldquo;Bu söylemek istediğim her şeyi kapsıyor mu?&amp;rdquo; Bu gereksinimi karşılayana kadar tekrarlayın. Bu planı sadece bir belge yazarken değil, toplantılar ve konuşmalar öncesinde, hatta önemsediğiniz müşterilerle buluşmadan önce yapın.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Bilgi aktarabiliyorsanız, doğru iletişimi sağlayabilirsiniz. Bu nedenle, diğer kişinin karşılayabileceği bilgi seviyesini belirlemelisiniz.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;İletişimin çeşitli yolları vardır. Bazı müşteriler e-posta ile bilgilendirilmek isteyebilirken, diğerleri telefonla aranmak isteyebilir.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;İnsanların sizi dinlemesini istiyorsanız, yapmanız gereken ilk şey onları dinlemektir. Bir toplantıyı diyaloga dönüştürebilirseniz, bundan daha iyisi yoktur.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;İnsanları bekletmeyin, size geri döneceğim, en kısa sürede cevaplayacağım gibi şeyler söyleyerek insanları bekletmeyin.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;hr&gt;
&lt;h2 id="sonuç"&gt;Sonuç&lt;/h2&gt;
&lt;p&gt;Okuduğunuz için teşekkürler! 🎉 Yazılım geliştirme alanındaki araştırmalarımı kaçırmamak için &lt;a href="https://x.com/berkslv"&gt;@berkslv&lt;/a&gt; adresinden takipte kalabilirsiniz.&lt;/p&gt;</content></item></channel></rss>