<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:cc="http://cyber.law.harvard.edu/rss/creativeCommonsRssModule.html">
    <channel>
        <title><![CDATA[Stories by Engin UNAL on Medium]]></title>
        <description><![CDATA[Stories by Engin UNAL on Medium]]></description>
        <link>https://medium.com/@enginunal?source=rss-f2da39f0544c------2</link>
        <image>
            <url>https://cdn-images-1.medium.com/fit/c/150/150/1*dXuqQuy_wbV6zUdL6e9mew.jpeg</url>
            <title>Stories by Engin UNAL on Medium</title>
            <link>https://medium.com/@enginunal?source=rss-f2da39f0544c------2</link>
        </image>
        <generator>Medium</generator>
        <lastBuildDate>Sun, 15 Nov 2026 04:53:27 GMT</lastBuildDate>
        <atom:link href="https://medium.com/@enginunal/feed" rel="self" type="application/rss+xml"/>
        <webMaster><![CDATA[yourfriends@medium.com]]></webMaster>
        <atom:link href="http://medium.superfeedr.com/" rel="hub"/>
        <item>
            <title><![CDATA[Tasarım Kalıpları — Decorator Tasarım Kalıbı]]></title>
            <link>https://enginunal.medium.com/tasar%C4%B1m-kal%C4%B1plar%C4%B1-decorator-tasar%C4%B1m-kal%C4%B1b%C4%B1-dd6f6d12e70d?source=rss-f2da39f0544c------2</link>
            <guid isPermaLink="false">https://medium.com/p/dd6f6d12e70d</guid>
            <category><![CDATA[structural]]></category>
            <category><![CDATA[design-patterns]]></category>
            <category><![CDATA[patterns]]></category>
            <category><![CDATA[csharp]]></category>
            <category><![CDATA[decorator-pattern]]></category>
            <dc:creator><![CDATA[Engin UNAL]]></dc:creator>
            <pubDate>Mon, 11 Dec 2023 16:11:02 GMT</pubDate>
            <atom:updated>2023-12-11T16:11:02.286Z</atom:updated>
            <content:encoded><![CDATA[<h3>Tasarım Kalıpları — Decorator Tasarım Kalıbı</h3><p><em>Yapısal Tasarım Kalıplarından(Structural Patterns) olan Decorator Tasarım Kalıbı(Decorator Design Pattern) ile devam ediyoruz. Nedir? Nasıl kullanılır? C# kod örneğiyle inceleyeceğiz.</em></p><p>Decorator pattern, mevcut bir sınıfın yapısını, kodlamasını değiştirmeden çalışma zamanında yeni işlevler eklenmesine olanak tanır.</p><p>Yazının devamında aslında yukarıdaki cümlenin altını dolduracağız. Varolan bir sınıfın herhangi bir metodunu değiştirmeden yani kodlamasına müdahale etmeden çalışma davranışını değiştirmek istediğimiz durumlar olabilir. Aynı şekilde iç yapıyı etkilemeden yeni özellikler de eklemek isteyebiliriz. Decorator pattern bu noktada yardımcı olur.</p><p>Birazdan detayına gireceğiz ama mantığın kolay anlaşılabilmesi için görsel bir örnek üzerinden gidelim. Client ve Service isminde iki sınıfımız var. Client, Service’e çağrı atıyor ve bir sonuç dönüyor. Aşağıdaki şeklin ilk kısmında bunu görebiliriz. Sistem bu şekilde sorunsuz çalışırken Service sınıfına ek özellikler talep ediliyor. Şeklin ikinci kısmında buna çözüm olarak decorator uygulanıyor(Decorator1). Service kodlamasında herhangi bir değişiklik yapılmadan decorator sınıfı ile istenen düzenlemeler sağlanıyor. Devamında bir düzenleme daha isteniyor ve çözüm olarak şeklin üçüncü kısmında yeni bir decorator ekleniyor. Böylece sistemimizde iki adet decorator nesnesi oluyor(Decorator1 ve Decorator2).</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/638/1*1r6hIGRvIoLueR02JCo6eQ.png" /><figcaption>İlk şekilde Client ve Service arasındaki standart çağrı örneği. İkinci şekilde decorator kullanılarak zenginleştirilmiş örnek var. Üçüncü şekilde ise öncekine bir decorator daha uygulanmıştır. Buradaki decorator’ı wrapper gibi düşünebiliriz. Service sınıfında herhangi bir değişiklik yapmadan sarmalamış. Yeni sınıf ile bazı düzenlemeleri kullanıma sunmuş.</figcaption></figure><p>Şekilden açıkça görüldüğü üzere decorator nesnesi, referansı verilen nesneye işlem uygular. Decorator2 nesnesi Decorator1 nesnesine, Decorator1 nesnesi de Service nesnesine işlem uygular veya dekore eder diyelim. Bu şekilde çalışma zamanında Service nesnesine istediğimiz kadar decorator uygulayabilir ve davranışını değiştirebiliriz. Bunu nasıl yaparız? Kod üzerinden daha net göreceğiz.</p><p>Peki OOP prensiplerinden inheritance(kalıtım) ile bunun ne farkı var? Service class’ını(superclass) alır ondan yeni bir class türetirim(subclass) neden bunu kullanayım? Sorularına cevap arayalım.</p><p>Evet bu gibi işleri subclass tanımlayarak ta yapabiliriz decorator pattern çalışma zamanında işlevselliğin uygulanması açısından çok iyi bir alternatif sunar.</p><p>Yukarıdaki örnekten devam edersek, Service nesnesine çalışma zamanında(runtime) decorator tarafından müdahale edilmektedir. Diğer ifadeyle Service nesnesine decorator dinamik olarak uygulanmakta. <strong>Yani öncelikli şart elimizde bir nesne olmalıdır ve bu nesne referansı decorator’a parametre olarak geçilir. Decorator içinde bu nesneye müdahale edilir.</strong></p><p>Diğer önemli nokta ise <strong>her yeni özellik için kalıtım kullanmaya çalışırsak bu özelliklerin kombinasyonunu sağlamak için çok fazla türemiş sınıfımız olacaktır.</strong> Bu da sınıf sayısında patlamaya neden olacaktır. Oysa her bir özelliği tanımlayan decorator’ı ard arda uygulayarak bunu kolayca sağlayabiliriz.</p><h4>Problem Tanımı</h4><p>Şimdi daha somut örnek üzerinden gidelim. Pizza sınıfımız olsun, sınıf yapımızı aşağıdaki gibi kurmaya başlayalım.</p><pre>public abstract class Pizza<br>{<br>    public abstract string Description();<br>    public abstract double GetCost();<br>}<br><br>public class Pepperoni : Pizza<br>{<br>    public override string Description() =&gt; $&quot;{GetType().Name}&quot;;<br>    public override double GetCost() =&gt; 100;<br>}<br><br>public class Margherita : Pizza<br>{<br>    public override string Description() =&gt; $&quot;{GetType().Name}&quot;;<br>    public override double GetCost() =&gt; 200;<br>}</pre><p>Şimdilik iki ana tip pizzamız olsun Pepperoni ve Margherita. Mantarlı seçeneklerini ekleyelim.</p><pre>public class PepperoniWithMushroom : Pepperoni<br>{<br>    public override string Description() =&gt; $&quot;{GetType().Name}&quot;;<br>    public override double GetCost() =&gt; base.GetCost() + 10;    <br>}<br><br>public class MargheritaWithMushroom : Margherita<br>{<br>    public override string Description() =&gt; $&quot;{GetType().Name}&quot;;<br>    public override double GetCost() =&gt; base.GetCost() + 10;<br>}</pre><p>Şimdi de ekstra mozarella seçeneği geldi.</p><pre>public class PepperoniWithMushroomWithExtraMozarella : PepperoniWithMushroom<br>{<br>    public override string Description() =&gt; $&quot;{GetType().Name}&quot;;<br>    public override double GetCost() =&gt; base.GetCost() + 5;<br>}<br><br>public class MargheritaWithMushroomWithExtraMozarella : MargheritaWithMushroom<br>{<br>    public override string Description() =&gt; $&quot;{GetType().Name}&quot;;<br>    public override double GetCost() =&gt; base.GetCost() + 5;<br>}</pre><p>Eklenen her özellik nedeniyle yeni sınıflar ekledik ve sınıf sayısı daha şimdiden hızla artış göstermeye başladı.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/935/1*8m8MR2kLx8pkeno1dgM8Zw.png" /></figure><p>Yeni sınıf eklemek yerine property ekleyerek çözebilir miyiz? Buna bakalım.</p><pre>public abstract class Pizza<br>{<br>    public bool WithMushroom { get; set; }<br>    public bool WithExtraMozarella { get; set; }<br>    public bool WithSweetCorn { get; set; }<br><br>    public abstract string Description();<br>    public abstract double GetCost();<br>}<br><br>public class Pepperoni : Pizza<br>{<br>    public override string Description() =&gt; $&quot;{GetType().Name}&quot;;<br>    public override double GetCost()<br>    {<br>        double cost = 100;<br><br>        if (WithMushroom)<br>        {<br>            cost += 10;<br>        }<br><br>        if (WithExtraMozarella)<br>        {<br>            cost += 5;<br>        }<br><br>        if (WithSweetCorn)<br>        {<br>            cost += 7;<br>        }<br><br>        //new ?<br>        return cost;<br>    }<br>}<br><br>public class Margherita : Pizza<br>{<br>    public override string Description() =&gt; $&quot;{GetType().Name}&quot;;<br>    public override double GetCost()<br>    {<br>        double cost = 200;<br><br>        if (WithMushroom)<br>        {<br>            cost += 10;<br>        }<br><br>        if (WithExtraMozarella)<br>        {<br>            cost += 5;<br>        }<br><br>        if (WithSweetCorn)<br>        {<br>            cost += 7;<br>        }<br><br>        return cost;<br>    }<br>}</pre><p>Bu çözümde ise ana sınıflarımız olan Pepperoni ve Margherita içerisine if kodları eklemek durumunda kaldık. Yeni eklenen her bir özellik için kodlara müdahale edilip hesaplama yapılıyor. Bu da iyi bir çözüm olmadı. Çünkü sınıflarımızın değişime kapalı fakat genişlemeye açık olmasını isteriz. Değişiklik istekleri her zaman yukarıdaki örnekteki gibi basit olmayacaktır. Daha önceden geliştirmesi tamamlanmış ve kullanıma sunulan sınıfların kodlarında değişiklikler yapılması önerilmez.</p><p>Decorator pattern tarafından sunulan çözüm de bu durumlara yöneliktir. Şimdi çözümü uygulayalım.</p><h4>Önerilen Çözüm</h4><p>Ana sınıflarımız olduğu gibi kalsın.</p><pre>public abstract class Pizza<br>{<br>    public abstract string Description();<br>    public abstract double GetCost();<br>}<br><br>public class Pepperoni : Pizza<br>{<br>    public override string Description() =&gt; $&quot;{GetType().Name}&quot;;<br>    public override double GetCost() =&gt; 100;<br>}<br><br>public class Margherita : Pizza<br>{<br>    public override string Description() =&gt; $&quot;{GetType().Name}&quot;;<br>    public override double GetCost() =&gt; 200;<br>}</pre><p>Decorator’larda kullanacağımız base decorator sınıfını ekleyelim.</p><pre>public abstract class PizzaDecorator : Pizza<br>{<br>    protected Pizza _pizza;<br><br>    public override string Description() =&gt; _pizza.Description();    <br>    public override double GetCost() =&gt; _pizza.GetCost();<br>}</pre><p><em>PizzaDecorator</em> sınıfı <em>Pizza </em>sınıfından türemekle birlikte kendi içinde <em>Pizza </em>tipinde bir nesne barındırmakta. Bundan sonraki tüm decorator’larımızı <em>PizzaDecorator’</em>dan türeteceğiz ve dışarıdan <em>Pizza </em>tipinde nesneyi referans olarak alacaklar. Böylece <em>Pizza </em>nesnesinin kullanıldığı yerlerde decorator nesnemizi kullanabilirken bir yandan da <em>Pizza </em>nesnesini kullanarak ürettiği sonuca müdahale imkanımız olacak.</p><p>Decorator’larımız artık tanımlayabiliriz. Şimdilik iki adet yeterli.</p><pre>public class WithMushroomDecorator : PizzaDecorator<br>{<br>    public override double GetCost() =&gt; base.GetCost() + 10;    <br>    public WithMushroomDecorator(Pizza pizza) =&gt; _pizza = pizza;    <br>}<br><br>public class WithExtraMozarellaDecorator : PizzaDecorator<br>{<br>    public override double GetCost() =&gt; base.GetCost() + 5;<br>    public WithExtraMozarellaDecorator(Pizza pizza) =&gt; _pizza = pizza;<br>}</pre><p>Deneyelim:</p><pre>Pizza pizza = new Pepperoni();<br>pizza = new WithMushroomDecorator(pizza); //decorator<br>pizza = new WithExtraMozarellaDecorator(pizza); //decorator<br>pizza = new WithExtraMozarellaDecorator(pizza); //decorator<br>Console.WriteLine(pizza.Description() + &quot;:&quot; + pizza.GetCost());<br>//Pepperoni:120<br><br>//another example<br>pizza = new WithMushroomDecorator(new Margherita()); //decorator<br>Console.WriteLine(pizza.Description() + &quot;:&quot; + pizza.GetCost());<br>//Margherita:210</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/607/1*Gcf7W45KT3IbwdUM2dq3qQ.png" /></figure><p><em>Pizza</em> sınıfımıza dokunmadan base decorator sınıfı oluşturduk ve tipi yine <em>Pizza</em> olabilecek şekilde türettik. Aynı zamanda bu base decorator sınıfının içinde <em>Pizza</em> nesnesine referans ekledik. Yeni eklenen decorator sınıflarını(concrete) base decorator sınıfından türeterek işlemleri bunlar içinde gerçekleştirdik.</p><p>Decorator tasarım kalıbının UML şeması aşağıdaki gibidir.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/949/1*kOrNqskEPeYNgjYeRrdJ5w.png" /><figcaption>Decorator pattern UML şeması. (Kaynak : <a href="https://en.wikipedia.org/wiki/Decorator_pattern">wikipedia</a>)</figcaption></figure><p>Şimdi yukarıdaki UML şemasını daha iyi anlayabilmek için aynı isimleri kullanarak sınıf yapısını ve decorator tasarımını yapalım. Öncelikle <em>Component </em>tanımını yapalım.</p><pre>public interface IComponent<br>{<br>    void Operation();<br>}<br><br>public class ConcreteComponent : IComponent<br>{<br>    public void Operation() =&gt; Console.WriteLine(GetType().Name + &quot;.Operation()&quot;);<br>}</pre><p><em>ConcreteComponent </em>sınıfına yeni bir özellik eklenmesi gerekti diyelim ve bunu decorator ile gerçekleştireceğiz. Bu durumda öncelikle decorator sınıfını eklemeliyiz.</p><pre>public class Decorator : IComponent<br>{<br>    readonly IComponent _component;<br><br>    public Decorator(IComponent component) =&gt; _component = component;<br><br>    public virtual void Operation() =&gt; _component.Operation();<br>}</pre><p><em>Decorator</em> sınıfımız da <em>IComponent</em>’ten türedi yani aynı arabirimi sağlıyor. Ek olarak <em>Component </em>nesnesine de sahip. Decorator sınıfından türeyen decorator örneğimiz ile yani <em>ConcreteDecorator </em>tanımını da yapalım.</p><pre>public class ConcreteDecorator : Decorator<br>{<br>    public ConcreteDecorator(IComponent component) : base(component) { }<br>    <br>    public override void Operation()<br>    {<br>        base.Operation();<br>        Console.WriteLine(GetType().Name + &quot;.Operation()&quot;);<br>    }<br>}</pre><p>Deneyelim:</p><pre>IComponent component = new ConcreteComponent();<br>component = new ConcreteDecorator(component);<br>component.Operation();<br>// Console:<br>//ConcreteComponent.Operation()<br>//ConcreteDecorator.Operation()</pre><p><em>ConcreteDecorator</em>’ı istediğimiz kadar arka arkaya çağırabilir veya yeni decorator’lar ekleyebiliriz. <em>ConcreteDecoratorA </em>ve <em>ConcreteDecoratorB</em> ekleyelim ve son olarak bunları da çalıştıralım.</p><pre>public class ConcreteDecoratorA : Decorator<br>{<br>    public ConcreteDecoratorA(IComponent component) : base (component) { }<br>    public override void Operation()<br>    {<br>        base.Operation();<br>        Console.WriteLine(GetType().Name + &quot;.Operation()&quot;);<br>    }<br>}<br><br>public class ConcreteDecoratorB : Decorator<br>{<br>    public ConcreteDecoratorB(IComponent component) : base(component) { }<br>    public override void Operation()<br>    {<br>        base.Operation();<br>        Console.WriteLine(GetType().Name + &quot;.Operation()&quot;);<br>    }<br>}</pre><p>Deneyelim:</p><pre>IComponent component = new ConcreteDecoratorA(new ConcreteDecoratorB(new ConcreteComponent()));<br>component.Operation();<br>//Console:<br>//ConcreteComponent.Operation()<br>//ConcreteDecoratorB.Operation()<br>//ConcreteDecoratorA.Operation()</pre><p>Bu iki örnek konunun anlaşılması için sanırım yeterli olmuştur. Okuduğunuz için teşekkürler.</p><p>Engin ÜNAL</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=dd6f6d12e70d" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[C# — RabbitMQ Ve Kullanım Örneği]]></title>
            <link>https://enginunal.medium.com/c-rabbitmq-ve-kullan%C4%B1m-%C3%B6rne%C4%9Fi-1116d7c87343?source=rss-f2da39f0544c------2</link>
            <guid isPermaLink="false">https://medium.com/p/1116d7c87343</guid>
            <category><![CDATA[message-broker]]></category>
            <category><![CDATA[csharp]]></category>
            <category><![CDATA[rabbitmq]]></category>
            <category><![CDATA[rpc]]></category>
            <category><![CDATA[pub-sub]]></category>
            <dc:creator><![CDATA[Engin UNAL]]></dc:creator>
            <pubDate>Thu, 09 Nov 2023 20:16:44 GMT</pubDate>
            <atom:updated>2023-11-09T20:16:44.041Z</atom:updated>
            <content:encoded><![CDATA[<h3>C# — RabbitMQ Ve Kullanım Örneği</h3><p><em>Bu yazıda RabbitMQ nedir? Nasıl kullanılır? Neden tercih ediliyor? Genel kullanım alanları nelerdir? Sorularına cevaplar arayacağız. Devamında C# ile RabbitMQ erişimi ve yönetimine değinip kodlama örnekleri ile bitireceğiz.</em></p><p>RabbitMQ, açık kaynaklı, çok kullanılan bir mesajlaşma aracısıdır. <em>AMQP</em>, <em>MQTT</em>, <em>STOMP</em> gibi mesajlaşma protokollerini destekler. Mesajların alım ve dağıtımında hızlı, ölçeklenebilir, dağıtık yapıların kurulmasına imkan tanımaktadır. Esasında ondan beklenen ve iyi yaptığı iş ise temel olarak ona iletilen mesajların alıcısına iletilmek üzere bir kuyrukta saklanması ve kuyruktan sırayla alıcıya/alıcılara iletilmesidir. Bu işleri yapan başka uygulamalar da bulunmaktadır. Örneğin <em>ActiveMQ, MSMQ, Azure Service Bus, Amazon SQS, Google Cloud Pub/Sub</em> bunlardan bazıları olarak sıralanabilir.</p><p>RabbitMQ bir mesaj kuyruğu yönetim uygulamasıdır veya mesajlaşma aracısıdır demiştik. Çeşitli protokolleri destekler, tek bir protokole bağımlı değildir fakat biz bu yazıda geniş kullanımdaki <em>AMQP</em>(<em>Advanced Message Queuing Protocol</em>) ile devam edeceğiz. AMQP bir mesajlaşma protokolü ve standardıdır. Mesajlaşmanın nasıl bir mimari ve kurallar bütünlüğü içinde olacağını belirleyen standartları tanımlar.</p><p>Burada karşımıza iki versiyon çıkar. AMQP 1.0 ve AMQP 0.9.1 versiyonları. 1.0 olan versiyonu 0.9.1&#39;den oldukça farklıdır ve sanki yeni bir protokol tanımlar gibi yapısal değişiklikler içerir. RabbitMQ’nun desteklediği temel protokol 0.9.1 versiyonudur, geliştirilirken bu versiyon baz alınmıştır. Ayrıca sitesinde geliştiriciler için paylaşılan kod örneklerinde de 0.9.1 versiyonu kullanılmıştır. Yazıda da yaygın kullanımda olan bu versiyon yani AMQP 0–9–1 kullanılacaktır.</p><h4>Peki RabbitMQ neden ve hangi ihtiyaçlar için kullanılır?</h4><p>Bekleme gerektiren veya uzun süren işlemlerin sonuçlarını o anda almak istemeyebiliriz. Uygulamamızın bir işlemi tamamlaması ve kullanıcıya sonucunu dürmesi uzun sürebilir ve kullanıcının bu işlemi beklemesine gerek olmayabilir. Sonucu daha sonra bildirim, mesaj veya e-mail ile iletmek yeterli olabilir. Örneğin e-ticaret uygulamamız olduğunu düşünelim. Kullanıcı alışveriş yapmış olsun ve ödeme sonrasında kesilen faturanın kullanıcı e-mail adresine gönderilmesi gereksin. Faturanın satış anında gönderilmesiyle birkaç dakika sonra gönderilmesi arasında kullanıcı açısından bir fark olmayacaktır. Satış işlemi yapılır, e-mail mesajı bir mesaj kuyruğuna iletilir ve oradan e-mail gönderen servis gönderim işlemini gerçekleştirir. Bu şekilde arka plandaki servislerin asenkron olarak çalışmaları sağlanmış olur.</p><p>Servislerin birbirlerine sıkı bağlı çalışmaları da istenmeyen bir durumdur. Özellikle mikroservis projelerinde servisler arası gevşek bağlı (loosely coupled) ilişkiler tercih edilir. Bu tip tasarımlarda araya bir broker koyarak mimariye esneklik sağlanabilir. Örnekler ve kullanım alanları çok çeşitlidir. Birkaç örnek ile devam edelim. Arkaplandaki mikro-servislerin birbirlerini beklemeden asenkron olarak çalışmaları gerekebilir. Örneğin A servisi, B servisine kaydedilmek üzere bir veri göndersin ama kayıt sonucunu beklemesi gerekmesin. Bunu yapmak için RabbitMQ’daki kuyruğa mesaj gönderir ve B servisi bu mesajı aldığında işler. B servisi işini bitirdiğinde kuyruğa tamamlandı mesajını gönderir ve A servisi bu işin tamamlandığı bilgisini alır.</p><p>Veya yük dağılımı yapılarak bazı servislerin aşırı yük altında çalışmalarını önlemek isteyebiliriz. Örneğin fatura hazırlayan servisimiz çok yoğun çalışıyor ve aynı işi yapan ikinci, üçüncü servisleri açmak gerekli. Eğer fatura servisimiz yapacağı işleri RabbitMQ kuyruğundan alıyorsa bu süreç gayet kolay bir şekilde çözülür. Kuyruğu dinleyen ve birbirinden bağımsız çalışabilen fatura servisleri açabilir ve yük dağılımını ayarlayabiliriz.</p><p>Veya bazı servislerin kritik önemde olduğu ve üzerinde çalıştığı işleri kaybetmek istemediğimiz durumlar olabilir. Servis çöktüğünde sistemin aksamaması ve işlerin kesintiye uğramadan devam etmesi istenebilir. Bu işleri bir kuyruğa atıp ayakta olan servislerin devralması ve devam etmesine ihtiyaç olabilir. RabbitMQ bu konuda önemli kolaylıklar sağlamaktadır. Kuyruktan çekilen bir mesajın <em>ack </em>bilgisini sonradan verebilme imkanı sunarak servis çökmelerinde mesaj kaybolmasına karşı kolayca önlem alınmasını sağlar. Buna ileriki konularda değineceğim. Yukarıda verilen kullanım örnekleri çeşitlendirilebilir. Genel bakış açısı sağladığını umuyorum.</p><h4>RabbitMQ nasıl çalışıyor?</h4><p>Artık kavramlara geçebiliriz. Mesaj gönderme/alma işlemleri en temel olarak aşağıdaki gibi olmaktadır.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/961/1*84UuIrfgDtXuc7q3v5yqew.png" /></figure><ul><li><strong>Publisher:</strong> Mesajı RabbitMQ’ya gönderen uygulamadır. (Producer)</li><li><strong>Consumer:</strong> RabbitMQ’daki mesajları okuyan uygulamadır. (Subscriber)</li><li><strong>Queue:</strong> Mesajların saklandığı kuyruk. İlk gönderilen mesaj ilk okunur. (<em>FIFO — First In First Out</em>).</li></ul><p>Mesajı gönderecek uygulama(publisher/producer) RabbitMQ’ya bağlanır ve gönderim yapmak istediği mesajı iletir. Mesajları okuyan uygulama(consumer/subscriber) ise RabbitMQ’ya bağlanır ve almak istediği mesajları içeren kuyruğu dinlemeye başlar, gelen mesajları okur.</p><p>Bu işlemleri ve ihtiyaca göre daha gelişmişlerini de gerçekleştirebilmek için RabbitMQ tarafında <em>Exchange</em> yapıları bulunmaktadır, şimdi bunları görelim.</p><h3>Exchange</h3><p>Dağıtıcı görevi görürler. Gelen postaları ilgili alıcılara dağıtan postacı gibi düşünülebilir. <em>Publisher/Producer</em> tarafından <em>RabbitMQ</em> sistemine gönderilen tüm mesajlar öncelikle <em>Exchange</em> tarafından karşılanırlar. Exchange, kendisine iletilen mesajların hangi kuyruğa/kuyruklara yönlendirileceğine(<em>routing</em>) karar verir.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/875/1*mN14Uv_5CZ9DRdLSMHSoXw.png" /></figure><p>Bunu yaparken <em>binding</em>, <em>routing key</em> ve <em>header</em> özelliklerini kullanmaktadırlar.</p><ul><li><strong>Binding : </strong><em>Exchange </em>ile <em>Queue </em>arasındaki bağlantıdır.</li><li><strong>Routing Key :</strong> Mesajın kuyruğa nasıl yönlendirileceğine karar vermek için Exchange’in baktığı alandır.</li><li><strong>Header Attributes : </strong>Mesajları routing key değerine göre değil header’daki değerlere göre kuyruğa yönlendirmek için kullanılır. Sadece <em>Headers Exchange</em> tipi için kullanılır. Gereken bilgi exchange tipleri altında verilecektir.</li></ul><h3>Exchange Tipleri</h3><p><em>Direct, fanout, topic, headers</em> isminde temel dört exchange tipi bulunmaktadır. Bunlar gelen mesajların <em>routing key, header</em> veya <em>binding</em> parametrelerine göre yönlendirme işlemini yaparlar. Bir de <em>default exchange</em> bulunmaktadır, <em>direct exchange</em> tipindedir, rabbitmq tarafından ön tanımlıdır ve kullanılırken ismi boş geçilir. Genellikle exchange tanımlama ile uğraşmadan doğrudan kuyruğa mesaj göndermek için kullanılmaktadır. Tüm kuyruklar kendi isimleriyle bu exchange’e bağlıdır, eğer routing key değerine kuyruk adı verilirse mesaj o kuyruğa yönlendirilir. Okuma için de aynı şekilde routing key değerine kuyruk adı verilerek exchange adı boş geçilerek okuma yapılır. Şimdi exchange tiplerini inceleyelim.</p><h4>Direct Exchange</h4><p>Gelen mesajların <em>Routing Key</em> değerlerine göre kuyruklara dağıtılması için kullanılan exchange çeşididir, bir çeşit <em>unicast </em>yapar. <em>Queue</em> ile <em>Exchange</em> arasında bağlantı kurmak gereklidir. Bunun için <em>binding </em>tanımı yapılır. Routing key ile ilişkisi ise şöyledir; binding tanımı yapılırken verilen routing key ile gelen mesajdaki routing key ile eşleşen kuyruğa mesaj iletilir.</p><p>Örneğin <em>ex-direct</em> isminde bir exchange oluşturalım. Bunun için rabbitmq kurulumu yapıp yönetim ekranına giriyoruz. Buradaki Exchanges sayfasından aşağıdaki gibi yeni bir exchange ekliyoruz.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/622/1*zsIac2idjpabteIZSyg_2g.png" /><figcaption>Exchanges sayfasından ex-direct isminde exchange ekliyoruz.</figcaption></figure><p>Queue eklemek için ise <em>Queues and Streams</em> sayfasına girip “<em>Add a new queue”</em> kısmında yeni queue tanımlarımızı yapıyoruz. <em>q1, q2, q3, q4 </em>isminde dört adet queue oluşturalım.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/733/1*KhuFxdgZYbLI8qjKfXGHQQ.png" /><figcaption>q1, q2, q3 ve q4 kuyruklarını ekliyoruz.</figcaption></figure><p>Tanımladığımız <em>ex-direct</em> isimli exchange tanım ekranına girip(<em>Exchanges</em> sayfasından <em>ex-direct</em> seçilir) aşağıdaki gibi binding tanımlarını yapıyoruz.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/671/1*5DKxhXK686nmwhh3aEwWdw.png" /><figcaption>ex-direct &gt; Bindings altında q1, q2, q3 ve q4 için binding tanımı yapıyoruz. q3 ve q4 için Routing key değeri olarak renk:mavi veriyoruz.</figcaption></figure><p>Buraya kadar direct exchange tanımladık, q1, q2, q3, q4 kuyruklarına bağladık. Şimdi deneyelim. Aynı ekranda “<em>Publish message”</em> kısmından <em>Payload</em>’una birşeyler yazarak ve Routing key değerini boş bırakarak “<em>Publish message”</em> ile mesaj yayınlayalım.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/753/1*6VcOEiAdxqnaMCBdu9ZBpw.png" /></figure><p><em>“Message published”</em> mesajı çıkacaktır. <em>Queues </em>ekranından kontrol edersek mesajın q1 ve q2 kuyruklarına yayınladığını görebiliriz.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/986/1*DC9hhX6CZVChgeUkQV4krA.png" /></figure><p>Routing key değerine <em>renk:mavi</em> yazarak mesaj yayınlaması yaparsak sadece q3 ve q4 kuyruklarına mesaj yönlendirecektir.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/755/1*xPOuD_tBQbyouq1HNUdVzw.png" /></figure><p>Kuyrukları kontrol ettiğimizde gönderilen mesajdaki routing key ile eşleşen q3 ve q4 kuyruğuna mesajın iletildiğini görebiliriz.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*1HWtG6vziB0Lf-6Xg4Afaw.png" /><figcaption>ex-direct örneğinde Routing key değerine göre routing yapar. Gelen mesaj verilen “renk:mavi” routing key’i ile eşleşen q3 ve q4 kuyruklarına iletilir.</figcaption></figure><p>Özetle <em>direct exchange</em> tipinde bir exchange tanımladığımızda <em>routing key</em> değeri kullanılarak gelen mesajın hangi kuyruklara yönlendirileceğini tanımlayabiliriz. Mesajdaki <em>routing key</em> değeri ile exchange-queue binding tanımındaki <em>routing key</em> değeri eşleşen kuyruklara mesaj iletilmektedir.</p><h4>Fanout Exchange</h4><p>Gelen mesajların bağlı tüm kuyruklara dağıtılması için kullanılan exchange çeşididir. Mesajlar tüm alıcılara iletilir yani <em>broadcast </em>yapar. <em>Queue</em> ile <em>Exchange</em> arasında bağlantı kurmak gereklidir. Bunun için binding tanımı yapılır. Routing key dikkate alınmaz. Gelen mesaja routing key verilsin veya verilmesin bind edilmiş tüm kuyruklara bunu iletir.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*nkIbySbBkKlsKRNASfUl6A.png" /><figcaption>Routing key ne olursa olsun bind edilmiş tüm kuyruklara gelen mesajı iletir.</figcaption></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*OXFmc9OWKbgt7Ad4cJCepA.png" /><figcaption>Bu exchange tipinde amaç gelen mesajı bağlı tüm kuyruklara iletmektir.</figcaption></figure><h4>Topic Exchange</h4><p>Gelen mesajların hangi kuyruklara dağıtılacağına karar verirken, mesajın routing key değeri ile bağlı kuyrukların routing key desenlerini(pattern) karşılaştırır. Yani gelen mesajın routing key değerini alır, bind edilmiş kuyruğun routing key tanımlarını da alır ve aşağıda açıklayacağım kurallara göre mesajın hangi kuyruklara dağıtılacağına karar verir. Özetle <em>multicast</em> yapar diyebiliriz.</p><p>Topic exchange kullanımlarında kuyrukların exchange ile bağlantısında(bind) Routing key tanımlaması yaparken * ve # karakterlerini kullanabiliriz. Bunları regular expression pattern’leri gibi düşünebilirsiniz. Eğer * kullanılırsa sadece bir kelimeyi kıyaslar ve herhangi bir kelime olabilir anlamına gelir. Ve # kullanılırsa öncesi veya sonrası tüm kelimeler için herhangi biri olabilir anlamına gelir. Birazdan örnekte daha ne göreceğiz.</p><p>Öncelikle kelime kıyaslaması nedir? Buradan devam edelim. Mesajın routing key değeri . işareti ile ayrılmış kelimeler içermelidir. Kıyaslama yapılabilmesi için kelimelerin arasına . işareti konulur.</p><p>Örnek olarak kirmizi.kalem değerini routing key olarak gönderdiğimizi düşünelim. Routing işleminde ilk kelime olan “kırmızı” ve ikinci kelime olan “kalem” ayrı ayrı işleme alınıp hangi kuyruktaki pattern ile uyuşuyor kıyaslaması yapılacaktır. q1 adında bir kuyruğu <em>ex-topic</em> isminde tipi <em>topic </em>olan bir exchange açıp bağlayalım. Bağlarken routing key olarak *.kalem verelim. Bu tanımda ilk kelime ne olursa olsun ikinci kelimesi kalem olan mesajlar q1 kuyruğuna düşer. Şimdi q2 kuyruğunu oluşturup routing key değerini kirmizi.* verelim. Böylece ilk kelimesi kırmızı olan tüm mesajlar q2 kuyruğuna düşer. Gelen mesajdaki routing key değerikirmizi.kalem ise mesajımız her iki q1 ve q2 kuyruklarına da düşer. Eğer gelen mesaj tukenmez.kalem ise sadece q1 kuyruğuna düşecektir.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*6f1ZrYPusyiyQBIL-_bjQQ.png" /></figure><p>Diğer kıyaslama karakteri # demiştik. Bu da öncesinde veya sonrasındaki kelimelerin tümüne geçiş izni verir. Başka bir örnekle devam edelim. Yine <em>ex-topic</em> isminde bir topic exchange’imiz olsun. Ve isimleri <em>“tshirt”, “pamuklu-tshirt”, “mavi”, “sentetik”</em> isimlerinde dört kuyruğumuz olsun. Amacımız gelen mesajdaki routing key değerine göre tshirt ile başlayan tüm mesajları “tshirt” kuyruğuna, pamuklu tshirt’leri “pamuklu-tshirt” kuyruğuna, mavi renkteki ürünleri “mavi” isimli kuyruğa vs. yönlendirmek olsun. Binding tanımı yaparken örneğin tshirt ile başlayanların tümünü “tshirt” kuyruğuna göndermek için routing pattern olarak tshirt.# veririz. Veya tüm sentetik ürünleri “sentetik” kuyruğuna atmak için #.sentetik veririz.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/478/1*wVSriH9VUEAaJmv6iRjt1Q.png" /><figcaption>Örnekteki queue’ler için binding tanımları.</figcaption></figure><p>Gelen mesajdaki routing key değeri <em>“tshirt.mavi.pamuklu”</em> olduğunu düşünelim. Mesajı karşılayan <em>ex-topic</em>, bağlı olan kuyruklardaki routing key pattern’lerini alır ve kıyaslama sonucunda <em>“tshirt”</em> kuyruğuna, <em>“pamuklu-tshirt”</em> kuyruğuna ve <em>“mavi”</em> kuyruğuna mesajı iletir.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*uriBuN0okMUUrkcg7JHvfg.png" /><figcaption>Gelen örnek mesajların yönlendirmeleri.</figcaption></figure><h4>Headers Exchange</h4><p>Mesaj başlığındaki(headers) tanımları kullanarak yönlendirme yapan exchange tipidir. Routing key tanımları dikkate alınmaz/kullanılmaz. Headers exchange’in baktığı mesaj başlığı tanımlarına <em>headers attribute</em> denir. Headers attribute, birden çok olabilir. Gelen mesaj başlığındaki tanımların(header attribute) tümünün uyuştuğu veya en az birinin uyuştuğu durumları kurala bağlamak mümkündür. Eğer headers attribute tanımlarının tümü karşılansın isteniyorsa headers tanımlarına <em>x-match = all</em> değeri eklenir. Veya sadece biri bile karşılansa yeterli ise <em>x-match = any </em>değeri eklenir.</p><p>Örnek üzerinden daha açık görebiliriz. Öncelikle <em>ex-headers</em> isminde tipi headers olan bir exchange oluşturalım. Devamında <em>binek-satis</em>, <em>satilanlar</em> ve <em>ticari-satis</em> isminde üç kuyruk oluşturalım. Bunları <em>ex-headers</em> ile bind edeceğiz. Queue ile bind işlemlerinde <em>Arguments</em> kısmında her queue için aşağıdaki tanımları yapalım.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/405/1*G0GIgY-NzzD7BDAugkAe4Q.png" /><figcaption>Headers exchange örneği, queue ve exchange binding tanımları.</figcaption></figure><p>Yukarıdaki tanımlar şöyle çalışacaktır: Gelen mesajın header özelliklerinde <em>islem:satis</em> ve <em>tip:binek</em> değerlerinin ikisini de sağlayanlar <em>binek-satis </em>kuyruğuna gönderilir. Header’da <em>durum:satildi</em> veya <em>islem:satis </em>değerlerinden birini içerenler <em>satilanlar </em>kuyruğuna gönderilir. Header’da <em>islem:satis </em>ve <em>tip:ticari </em>değerlerinin ikisini de içerenler <em>ticari-satis </em>kuyruğuna gönderilir.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*S3L7u-Sdunw1VJpdM_DPvQ.png" /><figcaption>Headers exchange örneği.</figcaption></figure><h4>Özet</h4><p>Exchange tipleri ilgili buraya kadar anlatılanların kısa bir özetini yapmak gerekirse. Direct exchange, gelen mesajların routing key değerleri ile bağlı kuyrukların routing key değerlerini kıyaslar ve eşleşenlere mesajları iletir. Fanout exchange, gelen mesajları bağlı tüm kuyruklara iletir. Topic exchange, gelen mesajları routing key değerleri ile bağlı kuyrukların routing key desenleri arasında karşılaştırma yapar fakat burada * ve # karakterlerini kullanarak dağıtımı kurallara göre yapar. Headers exchange, gelen mesajların header tanımlardan yola çıkarak bağlı kuyruklardaki header tanımlarının değerlerini karşılaştırır ve iletimi bu kurallara göre yapar.</p><h3>Kodlama Örneği</h3><p>Öncelikle mesaj gönderen <em>Publisher </em>örneğini devamında ise kuyruğu dinleyen <em>Consumer </em>örneğini kodlayacağız. Kodlama örneğini ilk olarak <em>Direct Exchange</em> için yapacağız. Projemizi oluşturalım ve <a href="https://www.nuget.org/packages/RabbitMQ.Client"><em>RabbitMQ.Client</em></a> paketini ekleyerek başlayalım.</p><pre>mkdir RabbitMQ<br>dotnet new console -n Publisher<br>cd Publisher<br>dotnet add package RabbitMQ.Client </pre><p><em>Publisher</em> projesindeki <em>Program.cs</em> içindeki kod aşağıdaki gibi olsun.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/d1311e94908a0cc9e05b1d7f994e7e43/href">https://medium.com/media/d1311e94908a0cc9e05b1d7f994e7e43/href</a></iframe><p>Direct exchange konusunda verdiğim örneğin koda dökülmüş hali, görüldüğü gibi <em>RabbitMQ.Client</em> ile exchange, queue vs. tanım yapmak veya silmek oldukça basit. Yukarıda exchange tanımını yapıp iki queue bind ettik ve iki mesaj gönderip test ettik ve sonra <em>Console</em>’dan bir tuşa basılmasını bekleyip oluşturduğumuz exchange ve queue’leri sildik. (Not: <em>VS Code</em> kullanıyorsanız ve <em>ReadKey</em> için hata alıyorsanız <em>launch.json</em> içerisinde <em>“console”: “integratedTerminal”</em> tanımını yapınız.)</p><p>Consumer örneğiyle devam edelim. RabbitMQ klasörümüze dönelim ve aşağıdaki komutlarla projeyi oluşturalım.</p><pre>cd ..<br>dotnet new console -n Consumer<br>cd Consumer<br>dotnet add package RabbitMQ.Client</pre><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/00b26d4bd516152fb5ba0338a4d32219/href">https://medium.com/media/00b26d4bd516152fb5ba0338a4d32219/href</a></iframe><p>Yukarıdaki kod örneğinde <em>BasicConsume</em> ile <em>q1</em> isimli kuyruk dinlemeye alınır ve bu kuyruğa bir mesaj ulaştığında <em>consumer </em>nesnesindeki <em>Received</em> event handler’ı içinde konsola mesaj içeriği yazdırılır. <em>Publisher </em>ve <em>Consumer </em>projelerini çalıştıralım, her biri için bir terminal window açıp dotnet run komutu ile çalıştıracağız.</p><pre>cd Publisher<br>dotnet run<br><br>cd Consumer<br>dotnet run</pre><p>Kod kalabalıklığı olmaması için tüm<em> exchange </em>ler için kod örneği eklemeyeceğim. <em>Fanout Exchange</em> için benzer kodlar kullanılabilir, bilindiği gibi orada sadece bind edilen kuyruklara yönlendirme yapıyor ve <em>routing key</em>’in bir önemi yoktu. Bu nedenle <em>BasicPublish</em> içinde <em>routingKey</em> değerini <em>string.Empty</em> ile boş geçiyoruz. <em>Topic Exchange</em> için de benzer bir durum sözkonusu. Orada da <em>BasicPublish</em> <em>routingKey</em> değeri . (nokta) ile ayrılmış olmalı ve <em>queue</em> tanımlarken pattern( * ve # ile) verebiliyorduk. Yani kodlama olarak değil içerik olarak değişenler olduğundan onları sizin denemenizi tavsiye ederim.</p><p>Headers Exchange ile devam edelim. Öncelikle projemizi oluşturalım.</p><pre>cd ..<br>dotnet new console -n HeadersPublisher<br>cd HeadersPublisher<br>dotnet add package RabbitMQ.Client</pre><p><em>QueueBind</em> ile header key-value tanımlarını taşıyan <em>Dictionary</em> ekliyoruz. Artık BasicPublish yaparken properties parametresine geçeceğimiz header değerleri bu tanımlarla karşılaştırılarak ilgili queue’ya iletilecektir.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/55ef1194c95fce0adf17afd17a14dd7d/href">https://medium.com/media/55ef1194c95fce0adf17afd17a14dd7d/href</a></iframe><h4>Mesajların Okundu Olarak İşaretlenmesi (ACK:Acknowledgment)</h4><p>Şimdiye kadar verdiğim örneklerde consumer tarafında <em>autoAck </em>parametresini <em>true</em> olarak verdik. Bu kullanımda <em>RabbitMQ</em>, mesajı consumer’a iletir ve işlenmesini beklemeden okundu olarak işaretleyerek kuyruktan siler. Kuyruktan silmesi için işlenmesini beklemez iletilmesi yeterlidir. Fakat bu her zaman istenilen bir çalışma şekli olmayabilir.</p><p>Olası çökme veya hata durumlarında veri kaybı yaşanmaması önemlidir. Örneğin consumer sunucusu çökerse, ona iletilen ve o anda işlenmekte olan veya işlenmeyi bekleyen mesajların tümü kaybedilebilir. Böyle durumlara karşı manuel ack yöntemi önerilir.</p><p>Veya bir consumer problem yaşadığında yeni bir consumer açıp mesajların kayıp yaşanmadan yeni açılan consumer’dan işlenmeye devam etmesini isteyebiliriz. O nedenle RabbitMQ tarafındaki mesajın silinmesi sürecinin iyi analiz edilip tanımlanması gerekir.</p><p>Mesajların consumer tarafından işlenmesini bekleyip sonrasında bir onay ile kuyruktan silinmesini sağlamak için ack işlemini manuel yapabiliriz demiştik. Bunun için <em>autoAck </em>parametresini <em>false </em>verip mesaj işlendikten sonra <em>BasicAck </em>metoduyla okundu olarak işaretleriz.</p><p>Aşağıda uygulama kod örneği verilmiştir.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/07191890355ffe2fde0c68a8e841a960/href">https://medium.com/media/07191890355ffe2fde0c68a8e841a960/href</a></iframe><p>Kod örneğinde <em>BasicAck</em> metoduna gelinceye kadar mesaj kuyrukta bekletilir.</p><h3>Sık Kullanılan Tasarımlar</h3><p><em>RabbitMQ </em>tarafından sunulan teknik çeşitliliğin farklı şekillerde kullanılması mümkün. Bu konuda tasarım kalıplarına uygun ve genel kullanımdaki birkaç örnekle devam edeceğiz.</p><h4>Work Queue / Task Queue / Competing Consumers Pattern</h4><p>Mesaj kuyruğundaki mesajların birden çok consumer tarafından paylaşılarak işlenmesine olanak tanıyan tasarımdır. Mesajları paralel olarak olarak işleyebilmek iş yükünün dengelenmesi, yatay olarak ölçeklenebilirlik(yeni consumer’lar ekleyerek), kaynakların optimize kullanılabilmesi gibi avantajlar sunar. Bu tasarım deseninde her mesaj boştaki bir consumer tarafından işlenmektedir.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/871/1*4tUZJNDuuZD-wzeDAqV_Cg.png" /><figcaption>Mesaj yoğunluğu ve iş yüküne göre consumer sayısı ayarlanabilir.</figcaption></figure><p>Mesaj kuyruğundaki birikmelere karşı hızlı ve ölçeklenebilir çözüm üretebilmek, gerekmediği durumlarda da fazladan consumer bulundurmamak yani kaynakların verimli kullanımı bu tasarımın avantajlarıdır.</p><p>Örnek üzerinde gidelim. Diyelim ki e-ticaret uygulamanız var ve indirim günlerinde çok fazla işlem gerçekleşiyor. Arkaplanda kurguladığınız RabbitMQ kuyruğunda çok fazla mesaj birikiyor ve bunların eritilmesi gerekiyor ama bir adet consumer bağlamışsınız ve yetişemiyor. Böyle senaryolarda sisteminizi tasarlarken consumer sayılarının artırılıp azaltılabilmesine imkan verecek esnekliği uygulayabilirseniz iş yükünün fazla olduğu zamanlarda yeni consumer’ları devreye alarak işlemlerin tamamlanma sürelerini kısaltabilirsiniz.</p><p>RabbitMQ kuyruktaki mesajları consumer’lara dağıtırken standart olarak <em>Round Robin</em> algoritması kullanır yani sırayla dağıtır. Detayına gireceğiz, şimdi kod örneği üzerinden devam edelim. Publisher ve Consumer projesi oluşturup aşağıdaki kısa kodlamayı yapalım.</p><p><em>Publisher </em>projesi <em>Program.cs</em> içeriği:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/f529ad59b8c005f92427bee1a8d7cc47/href">https://medium.com/media/f529ad59b8c005f92427bee1a8d7cc47/href</a></iframe><p><em>Publisher </em>projesindeki <em>Program.cs</em> içerisindeki döngüde her saniye <em>q1</em> isimli kuyruğa yeni bir mesaj gönderiliyor.</p><p><em>Consumer</em> projesindeki <em>Program.cs</em> :</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/07191890355ffe2fde0c68a8e841a960/href">https://medium.com/media/07191890355ffe2fde0c68a8e841a960/href</a></iframe><p><em>Consumer </em>projesinde ise <em>q1</em> isimli kuyruk dinlenerek gelen mesajlar ekrana yazdırılıyor. Dışarıdan argüman olarak saniye bilgisi verilirse o kadar saniye bekleme yapıyor sonrasın mesaj işlemeye devam ediyor.</p><p>Şimdi bu kodlamayı kullanarak bir publisher ve iki adet consumer çalıştıralım. Publisher her saniye bir mesaj üretip kuyruğa göndersin. Consumer’lar da her işlemde 2 saniye beklesin. Komut satırından aşağıdaki gibi çalıştıralım.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*Tgp5CqJrKzjuPLkZWF_G9A.png" /><figcaption>İki consumer da 2sn beklemeyle mesajı işler.</figcaption></figure><p>İlk consumer 1,3,5,… no’lu mesajları alırken ikincisi 2,4,6,.. no’lu mesajları işleme almış. <em>RabbitMQ </em>kuyruktaki mesajları dağıtırken <em>Round Robin</em> uyguladığı için sıralı dağıtıldı. Şimdi ilk consumer’ın bekleme süresini 5sn yapalım, ikincisi 2sn olsun. Tekrar çalıştıralım.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*4yhd31f-5C1p1-kZkwGyuA.png" /><figcaption>İlk consumer 5sn, ikincisi ise 2sn beklemeyle mesajı işler.</figcaption></figure><p>Görüldüğü gibi mesajların dağıtılmasında bir değişiklik yok. Sırayla dağıtılıyor. Fakat ilk consumer yavaş olduğundan yetişemiyor, geride kalıyor. Bu nedenle ilk consumer’a atanan fakat yavaş olmasından dolayı işlenemeyen mesajlar var bunlar kuyrukta birikiyor.</p><p>Yavaş olan consumer’ı beklediğimiz için kuyrukta birikme yaşanıyor, oysa işini daha kısa sürede bitirip boşta kalan ikinci consumer’a mesajları yönlendirmek daha verimli olacaktır. <em>RabbitMQ</em>’ya mesajı işleyip onaylayana kadar(<em>ack</em>) yeni bir mesaj göndermemesini söyleyebiliriz. Böylece <em>RabbitMQ </em>kuyruktan sıradaki mesajı meşgul olmayan bir sonraki consumer’a gönderecektir. Bunu da <em>BasicQos </em>metodu ile sağlarız. <em>PrefetchCount: 1</em> vererek <em>RabbitMQ</em>’ya bir consumer’a aynı anda birden fazla mesaj iletmemesini söyleriz.</p><p>Consumer kodumuzun son durumu aşağıdaki gibi olur.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/3e9fa5e7bfd986d7775d76361aaba91d/href">https://medium.com/media/3e9fa5e7bfd986d7775d76361aaba91d/href</a></iframe><p>Tekrar çalıştırıp denersek aşağıdaki çıktıyı üretecektir.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*l57h28PMu54JZUkFBaI_Mg.png" /><figcaption>BasicQos ve <em>PrefetchCount </em>ile consumer’ın aynı anda tek mesaj iletilmesi.</figcaption></figure><h3>Publish/Subscribe Pattern</h3><p>Bu tasarım kalıbında, kuyruktaki bir mesaj birden çok consumer’a iletilmektedir. Önemli nokta kuyruğa gönderilen bir mesajın o sırada bağlı olan tüm consumer’lara iletilmesidir. Kaç adet consumer varsa o kadar kuyruk oluşur ve mesaj kopyalanarak kuyruklara iletilir. Bunu sağlamak için <em>Fanout Exchange</em> kullanılır.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/851/1*MaTDnBeXEN9_lnC2zofVJg.png" /></figure><p>Kullanım alanlarına geçersek. Örneğin mikroservislerin çalıştığı bir ortamda gelen bir mesajın tüm mikroservislere yayınlanması istenebilir. Daha somut örnekle; Web sitenizde yeni bir kullanıcı açma işlemi gerçekleşiyor olsun. Yeni kullanıcı açıldıktan sonra bir dizi işlem yapılacaktır ve bu işlemler birbirlerinden bağımsız gerçekleşebilir. Açılan kullanıcıya şifresini gönderen bir mikroservis, işlem loglarını kaydeden bir mikroservis vb. servislere yayınlanan mesajın iletilmesi gerekecektir. Bu örnekteki gibi aynı mesajın birden çok consumer’a yayınlanması ihtiyacı için pub/sub pattern kullanılabilir. Böylece producer’ın consumer’lara herhangi bir bağlılığı olmadan(decoupling) mesaj iletimini sağlayabilir, yeni consumer’ların sisteme kolayca eklenip çıkarılması esnekliğine sahip olabiliriz.</p><p>Kod örneği ile devam edelim. Önceki örneklerdeki basit yapımızı tekrar kurup <em>Publisher </em>ve <em>Consumer </em>projeleri oluşturalım. <em>Publisher </em>projesindeki <em>Program.cs</em> içeriği aşağıdaki gibi olsun.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/13e2f27b7c694b53dc52f3ed1fbbd18f/href">https://medium.com/media/13e2f27b7c694b53dc52f3ed1fbbd18f/href</a></iframe><p><em>Publisher</em> sadece “<em>ex-pubsub</em>” isminde bir <em>Fanout Exchange</em> oluşturup her saniye bir mesaj gönderiyor. <em>Consumer</em> projesindeki <em>Program.cs</em> kodları aşağıdaki gibi olsun.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/fd94098bbfef0cfd42185e1a87dd10a8/href">https://medium.com/media/fd94098bbfef0cfd42185e1a87dd10a8/href</a></iframe><p>Burada ise consumer adını argümandan aldık ve “<em>ex-pubsub</em>” exchange’ine bağlanıp consumer adıyla bir kuyruk oluşturduk. Çalıştırıp deneyelim. iki consumer açalım ve isimleri A ve B olsun.(dotnet run Ave dotnet run Bile).</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*rfMjgvnPEgrk7L55zMKO5g.png" /></figure><p>Görüldüğü gibi producer tarafından exchange’e gönderilen mesajlar iki consumer’a da dağıtılmakta. Örnekteki iki consumer yerine daha fazla consumer açarak deneyebilirsiniz. Kod satırında bıraktığım açıklamaya dikkat edilirse consumer’ların kapanması durumunda kuyrukların otomatik silinmesi istenilirse queueName = channel.QueueDeclare().QueueNameile tanım yapılabilir. Bu şekilde kuyruk tanımlandığında consumer ile kuyruk bağlantısı koptuğunda kuyruk kalıcı olmayacak ve silinecektir.</p><h3>Request Reply Pattern</h3><p>Diğer adıyla <em>Request/Response Pattern</em> olan bu tasarım kalıbında bir servis başka bir servise mesaj gönderir ve yanıtını aldıktan sonra işleme devam eder. Bu tasarım kalıbının RabbitMQ ile uygulanışında producer/consumer ayrımını yapmayacağız bunun yerine client/server olarak ayıracağız. Mesajı gönderen aynı zamanda sonucunu da bekleyeceğinden producer aynı zamanda bir consumer olmak durumunda veya tersi consumer aynı zamanda bir producer olacak. Şekil üzerinden daha açık görülebilir.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/904/1*9ewKdbaTxGeeI-4_cKEqlg.png" /><figcaption>Request-Reply, Request-Response, RPC.</figcaption></figure><p>Bir client yani istemci, sunucudan yanıtını beklediği bir mesaj gönderir ve mesaj kuyruğa eklenir. Dinleme yapan sunucu mesajı kuyruktan alır ve sonucunu mesaj sonuçlarının olduğu başka bir kuyruğa ekler. İstemci sonucu kuyruktan alır ve süreç tamamlanır.</p><p>Server gelen bir mesajı işledikten sonra sonucunu hani kuyruğa atacağına nasıl karar veriyor? Client ise kuyruğa gelen mesajlar arasında gönderdiği mesajın sonucunu nasıl ilişkilendiriyor? Gelen mesaj hangi mesajın sonucu bunu nasıl bulabiliriz? Sorularına cevap vermemiz gerekir. Mesajlaşmayı başlatan yani örneğimizdeki client, gönderdiği mesaj içerisine tanımlayıcı bir değer koyar buna <em>CorrelationId</em> denir. <em>CorrelationId</em> değeri server tarafından alınır ve sonuç olarak üretilen mesaj içerisine de eklenir bu şekilde hangi mesajın sonucu olduğu ayrılabilir. Client tarafından gönderilen mesaj içerisine ayrıca bu mesajın sonucunun hangi kuyruğa atılacağı bilgisi de eklenir. Bu da <em>ReplyTo</em> ismiyle verilir.</p><p>Kod örneğiyle devam edelim. <em>Server</em> ve <em>Client</em> isminde iki projemiz olsun. <em>Server </em>projesindeki <em>Program.cs</em> kodları aşağıdaki gibi olsun.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/b4ef3b3911e215c796b5d061b782be37/href">https://medium.com/media/b4ef3b3911e215c796b5d061b782be37/href</a></iframe><p>Server, “<em>request_queue</em>” isimli kuyruğu dinler. Mesaj geldiğinde <em>CorrelationId</em> ve <em>ReplyTo</em> parametrelerini okur. <em>ReplyTo</em> ile verilen kuyruğa <em>“This is reply message”</em> mesajını gönderir. Kod açıklamalarından detay takip edilebilir.</p><p><em>Client </em>projesindeki <em>Program.cs</em> kodları:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/b2c7b71d9f221e2ad6a378eeeae66b9a/href">https://medium.com/media/b2c7b71d9f221e2ad6a378eeeae66b9a/href</a></iframe><p>Client, <em>“request_queue”</em> kuyruğuna “**hi from client**” mesajını gönderir. Bu mesaj parametrelerine <em>CorrelationId</em> ve <em>ReplyTo</em> değerlerini ekler. <em>ReplyTo</em> parametresinde verdiği isimle açtığı kuyruğu dinlemeye alır. Server tarafından <em>ReplyTo</em> kuyruğuna bir mesaj gönderildiğinde bu mesajı alır ve ekrana yazdırır.</p><p>Yukarıdaki Client ve Server kodlarını ayrı terminal ekranlarında çalıştırdığımızda aşağıdaki gibi çıktı üretecektir.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*9A_CyurWfEMsJ1Ub_ArFww.png" /></figure><p><em>RabbitMQ</em>, kullanım alanlarının çeşitliliği, ihtiyaçlara yönelik üretilen çözümlerin ve teknik detayların fazla olması nedeniyle geniş bir konu. Bu yazıdaki amacım genel bir bakışı vermek ve teknik detaya boğmadan anlaşılabilir olmaktı. Umarım verdiğim bilgiler faydalı olur.</p><p>Okuduğunuz için teşekkürler.</p><p>Engin Ünal</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=1116d7c87343" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[C# — JSON Web Token(JWT) Ve WebApi Örneği]]></title>
            <link>https://enginunal.medium.com/c-json-web-token-jwt-ve-webapi-%C3%B6rne%C4%9Fi-ca16a67f3cf6?source=rss-f2da39f0544c------2</link>
            <guid isPermaLink="false">https://medium.com/p/ca16a67f3cf6</guid>
            <category><![CDATA[jwt]]></category>
            <category><![CDATA[json-web-token]]></category>
            <category><![CDATA[rsa]]></category>
            <category><![CDATA[webapi]]></category>
            <category><![CDATA[csharp]]></category>
            <dc:creator><![CDATA[Engin UNAL]]></dc:creator>
            <pubDate>Tue, 29 Aug 2023 18:07:00 GMT</pubDate>
            <atom:updated>2023-08-29T18:07:00.118Z</atom:updated>
            <content:encoded><![CDATA[<h3>C# — JSON Web Token(JWT) Ve WebApi Örneği</h3><p><em>JSON Web Token Nedir? Detaylı inceleyip WebApi ve .Net tarafındaki kullanım örneği ile yazıyı sonlandıracağız.</em></p><p>JWT yani JSON Web Token, verilerin taraflar arasında(alıcı-verici veya istemci-sunucu vb. olabilir) güvenli iletilmesi için tanımlanmış açık bir standarttır. Bu bilgiler dijital olarak imzalandığı için doğrulanabilir ve değiştirilmeye karşı güvenlidir. İmzalama için simetrik bir anahtar — Secret Key — (HMAC algoritmasıyla) veya Özel/Genel Anahtar Çifti — Private/Public Key Pair — (RSA veya ECDSA algoritmasıyla) kullanılabilir.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/663/1*sYGWM0aYmFAI3NtENWEIwQ.png" /><figcaption>JWT kullanılan bir servis çağrısı örneği</figcaption></figure><p>Ağırlıklı olarak kullanım alanları; Kimlik doğrulama, servis güvenliği, kaynak erişimi yetki kontrolü, servisler arası iletişimde güvenli iletişim ve mesajın bütünlüğünün garanti edilmesi durumu olarak özetlenebilir.</p><p>Bu yazıda örnek olarak seçeceğim kullanım ise bir webapi projesindeki kimlik doğrulama ve api erişim kontrolü olacaktır.</p><h3>JWT İç Yapısı</h3><p>JSON Web Token üç ana parçadan oluşmaktadır. Bunlar <em>Header</em>, <em>Payload </em>ve <em>Signature </em>olarak sıralanabilir.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/314/1*6RIoawJCOlZmOWbqRJ4zDw.png" /><figcaption>JWT içeriği</figcaption></figure><p>Bu üç veri katmanı <em>Base64 </em>formatındadır ve aralarında “.” (nokta) ayracı vardır. Aşağıdaki örnekte <a href="http://jwt.io/">jwt.io</a> sitesindeki decoder ile token ve içeriği gösterilmiştir.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*kq9_sWMBCqdywQ2qpTwIQA.png" /><figcaption>jwt.io</figcaption></figure><h4>Header</h4><p>Header verisi için genel olarak iki alan ön plana çıkmaktadır.</p><ul><li><strong>alg :</strong> İmzalama veya şifreleme algoritması. (<em>HS256, RS256, ES256</em>)</li><li><strong>typ :</strong> Token’ın tipi yani JWT.</li></ul><pre>{<br> “alg”: “HS256”,<br> “typ”: “JWT”<br>}</pre><p>Algoritma seçimi nasıl yapılır? Bunu Signature konusu devamında inceleyeceğiz.</p><h4>Payload</h4><p>Payload, token içindeki verinin taşındığı alandır. İçinde claim yapıları bulunur. Aynı header gibi JSON formatındadır. Payload içindeki claim’lerin bazıları ön tanımlı veya kayıtlı olmakla birlikte ihtiyaca göre kullanıcı tanımlı claim’ler de eklenebilir. Önemli olan kayıtlı bir claim ile aynı isimde kullanmamaya dikkat edilmelidir(<a href="https://datatracker.ietf.org/doc/html/rfc7519#section-4.1">detay</a>). Kayıtlı claim’lerden bazıları ve açıklamaları aşağıda listelenmiştir. Bunlar zorunlu değildir fakat kullanılması standardizasyon açısından önerilir.</p><ul><li><strong>iss :</strong> Issuer — Token’ı oluşturan veya yayınlayıcının adı.</li><li><strong>sub :</strong> Subject — Alıcı. Token’ın kim için oluşturulduğu bilgisini tutar.</li><li><strong>aud :</strong> Audience — Hedef kullanıcı, alıcı. Sadece buradaki kullanıcılar tarafından kullanılabilir.</li><li><strong>exp :</strong> Expiration Time — Son kullanım tarihi.</li><li><strong>nbf :</strong> Not Before — Verilen süreden öncesinde geçerli değil bilgisini tutar.</li><li><strong>iat :</strong> Issued At — Token oluşturulma zamanı.</li><li><strong>jti :</strong> JWT ID — Benzersiz kimlik değeri.</li></ul><h4>Signature</h4><p>JWT verisinin imzasını taşıyan bölümdür. Burada token’ın imzası bulunur. Token’ın üreticisinin ve token bütünlüğünün kontrolünün yapılabilmesi signature verisi ile mümkün olmaktadır. Örneğin HMAC algoritması ile imzalama yapmak istersek aşağıdaki gibi bir yol izleriz.</p><pre>HMACSHA256(<br>  base64UrlEncode(header) + &quot;.&quot; +<br>  base64UrlEncode(payload),<br>  SECRET_KEY<br>)</pre><p>Şimdi biraz algoritma detaylarına ve seçiminin nasıl yapılabileceğine değinelim.</p><h3>Algoritma Seçimi</h3><p>İmzalama işlemlerinde, jwt içindeki header ve payload verileri alınır ve hash işleminden geçirilir devamıda bu veri imzalanır. Hash nedir? Öncelikle bunu hatırlayarak başlayalım.</p><p>Hash, bir veriyi boyutuna bağlı olmadan hep aynı uzunlukta bir çıktıya çeviren yani verinin parmak izini alan matematiksel fonksiyonlardır. Tek yönlü şifreleme yapar. Hash değerinden orjinal veriye ulaşmak mümkün değildir. Bu nedenle genellikle veri bütünlüğü kontrollerinde tercih edilir. Hash değerinin alınmasının önemli nedenlerinden biri de büyük boyutlu verinin tamamının şifrelenmesi yerine hash değerinin hesaplanarak çok daha verimli kontrollerin yapılabilmesini sağlamaktır.</p><p>Örneğin bir dosyanın değişmemesini istiyoruz ve bunu kontrol etmemiz gerekiyor. Bunun için hash değerini alıp saklarız. Eğer dosyada değişiklik olursa dosyanın güncel hash değerini hesaplatıp eskisiyle kıyaslayarak dosyanın orjinal olup olmadığını anlayabiliriz. Hash verisi hep aynı uzunlukta olacağından yani hash’i alınmak istenen verinin boyutundan bağımsız olacağından oldukça verimli bir yöntemdir.</p><p>Hash algoritması için JWT tarafında genellikle <em>SHA-256, SHA-384, SHA-512</em> gibi seçenekler vardır bunlar <em>SHA-2</em> hash algoritma ailesindendir. Buradaki numaralar üretilen hash verisinin boyutunu verir.<br>Örneğin <em>SHA-256, 256-bit</em>’lik bir hash üretirken <em>SHA-512</em> ise <em>512-bit</em>’lik hash üretmektedir.</p><p>JWT tarafında da hash işlemleri token bütünlüğünün kontrol edilmesi ve token üreticisinin doğrulanması için kullanılır. Konunun başında belirttiğim gibi token hash değeri alınır ve bu veri şifreleme algoritmasından geçirilerek şifrelenir ve token imzası yani Signature bölümüne yazılır.</p><p>İmzalama için kullanılabilecek algoritmalar kullanım alanlarına ve güvenlik seviyelerine göre farklılıklar göstermektedir.</p><h4>HMAC</h4><p>Simetirk şifreleme algoritmasıdır. <em>Hash-based Message Authentication Codes</em> kelimelerinin kısaltılmışıdır. Hash algoritması olarak <em>HS256 </em>için <em>SHA-256, HS384</em> içi <em>SHA-384</em> ve <em>HS512 </em>için <em>SHA-512</em> kullanılır. Simetrik kriptografi ile anahtar(Secret Key) ve hash fonksiyonunu alarak mesajın güvenlik kodunu oluşturur.</p><p>İmzayı oluşturmak ve doğrulamak için aynı anahtarı kullanır. Anahtarın, imzalayıcı ve doğrulayıcı arasında paylaşılması güvenli olmadığından genellikle iç kullanımlarda tercih edilir.</p><h4>RSA</h4><p>Asimetrik şifreleme algoritmasıdır, yaygın olarak kullanılır. Adını bulucularının(Rivest–Shamir–Adleman) soyisimlerinin baş harflerinden almıştır. Temelindeki mantık bir anahtar ile şifrelenen verinin tekrar aynı anahtar ile çözülmesine ihtiyaç duymamasıdır. Bu anahtarlar private key ve public key çiftidir. Public key ile şifrelenen veri private key ile çözülebilir.<br>İmzalama işlemlerinde ise private key ile imzalanan veriler public key ile doğrulanır. Böylece private key’in karşı tarafa verilmesi gerekemez.</p><p>Genel anahtar kriptografi standardı(<em>Public Key Cryptography Standard 1- PKCS #1</em>) <em>RSA </em>algoritması kullanımına ilişkin şifreleme şemaları, formatları vs. bir çok standart belirlemiştir.<br><em>RSASSA</em>’da bu tanımlamalardan biridir, RSA imzalarının ve doğrulamasının standartlarını belirler. <em>RSASSA — RSA Signature Scheme With Appendix</em>.</p><blockquote>Burada önemli nokta <em>RSASSA PKCS1 v1.5</em> ile deterministik imzalama yapılmaktadır.</blockquote><p>Bu ne demek? Yani aynı header ve payload değerlerine sahip JWT imzaları aynı olacaktır.</p><p>JWT için seçenekler:</p><ul><li>RS256: RSASSA PKCS1 v1.5 — SHA-256</li><li>RS384: RSASSA PKCS1 v1.5 — SHA-384</li><li>RS512: RSASSA PKCS1 v1.5 — SHA-512</li></ul><p>İmzaların daha güvenli olmasına imkan tanımak için <em>RSASSA-PSS</em> seçeneği de mevcuttur. <em>RSASSA-PSS</em>, RSA’nın olasılıksal versiyonudur. Buradaki <em>PSS, Probabilistic Signature Scheme</em> kelimelerinin kısaltılmışıdır.<br>Kriptografik olarak rastlantısal numara üreteci(RNG-Random Number Generator) kullanır. Bu nedenle imzalanan veri içeriği değişmese de imza değişir. Yani aynı header ve payload verisine sahip JWT her imzalamada farklı bir imza değeri üretir.</p><p>Daha güvenli olması nedeniyle RSASSA-PSS’in kullanımı önerilmektedir.</p><p>JWT için seçenekler:</p><ul><li>PS256: RSASSA-PSS — SHA-256</li><li>PS384: RSASSA-PSS — SHA-384</li><li>PS512: RSASSA-PSS — SHA-512</li></ul><h4>ECDSA</h4><p><em>RSA </em>gibi asimetrik şifreleme algoritmalarından biridir. Adını <em>Elliptic-Curve Digital Signature Algorithm</em> kelimelerinin ilk harfnden alır. <em>RSA</em>’daki gibi asal çarpanlarını kullanmak yerine eliptik eğriler(<em>y2 = x3 + ax + b</em>) üzerinde inşa edilmiştir.</p><p><em>RSA</em>’ya göre avantajları ise kırılma olasılığı daha düşük olmakla birlikte daha kısa şifre anahtarı kullanır bu nedenle imza boyutu küçüktür. Örneğin <em>256-bit</em>’lik bir <em>Elliptic-Curve</em> anahtarının sağladığı güvenlik için <em>RSA</em> tarafında <em>3072-bit</em> gereklidir. Artısı yanında eksisi de var. İmzalamada hızlı olsa da imza doğrulamada <em>RSA</em>’a göre daha yavaştır. Ek olarak rastlantısal sayı üretimi imzanın güvenliği açısından kritiktir. Bu noktada <em>RSA </em>daha iyidir.</p><p>JWT için seçenekler:</p><ul><li>ES256: ECDSA — SHA-256</li><li>ES384: ECDSA — SHA-384</li><li>ES512: ECDSA — SHA-512</li></ul><h4>Özet</h4><p>Eğer JWT ile güvenlik gerektiren işlemler yapılmakta ise yani iç kullanım gibi daha düşük güvenlik seviyesi gerektiren bir kullanım değilse ES veya PS tercih edilmelidir. Bu noktada algoritmaların arasında seçim için daha detaylı düşünmek gerekir. Örneğin imzalama işleminin yavaş olması önemli değil fakat doğrulama işleminin hızlı olması önemli bir durum ise PS daha iyi bir seçim olacaktır. Veya imza boyutu küçük olmalı ise ES iyi bir seçim olabilir. HMAC gibi seçimler yani simetrik anahtar ile şifreleme çözümleri dış kullanımlar için önerilmez.</p><p>Kodlama örneğimizle devam edelim.</p><h3>JWT &amp; WebApi Örneği</h3><p>Kodlama örneğimizde daha önceden hazırda tuttuğum temel bir webapi projesini kullanacağım. İsterseniz aşağıda paylaştığım linkten siz de aynı şekilde ilerleyebilirsiniz.</p><p><a href="https://github.com/enginunal/AspNetJWT/tree/webapi-base">https://github.com/enginunal/AspNetJWT/tree/webapi-base</a></p><figure><img alt="" src="https://cdn-images-1.medium.com/max/282/1*NPWIa7Tl07Ge-ocppsJSBQ.png" /><figcaption>Temel webapi projesinde sadece <strong>Item </strong>CRUD işlemi bulunuyor.</figcaption></figure><p>Bu <em>webapi </em>projesinde item tablosu var ve servisteki <em>CRUD(Create, Read, Update, Delete) </em>fonksiyonları ile tablodan okuma ve tabloya yazma gibi temel işlemler bulunuyor. Bunu da <em>ItemsController </em>ile yapıyoruz. Veritabanı olarak <em>SqLite </em>kullandım. Bu kısa tanıtımdan sonra amacımıza geçelim.</p><p><em>Item </em>servisimize sadece izin verdiğimiz ve <em>token </em>sahibi olanların erişmesini istiyoruz yani güvenlik katmanı ekleyeceğiz. Bunu da <em>JWT </em>ile sağlayacağız. Öncelikle <em>JWT </em>kütüphanelerimiz ekleyelim.</p><pre>dotnet add package Microsoft.AspNetCore.Authentication.JwtBearer --version 7.0.10<br>dotnet add package System.IdentityModel.Tokens.Jwt --version 6.32.2</pre><p><em>appsettings.json</em> dosyasında <em>jwt </em>tanımlarımızı yapalım. Bunları <em>token </em>üretirken kullanacağız.</p><pre>&quot;JwtTokenOptions&quot;: {<br>    &quot;Issuer&quot;: &quot;AspNetJWT&quot;,<br>    &quot;Audience&quot;: &quot;ItemsAud&quot;,<br>    &quot;SigningKey&quot;: &quot;simetrik sifreleme anahtari kullanacagiz&quot;,<br>    &quot;Expiration&quot;: 3600<br>  }</pre><p><em>Program.cs</em> dosyasına aşağıdaki tanımları ekleyelim.</p><pre>builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)<br>    .AddJwtBearer(opts =&gt;<br>    {<br>        byte[] signingKey = Encoding.UTF8<br>         .GetBytes(builder.Configuration.GetSection(&quot;JwtTokenOptions&quot;)[&quot;SigningKey&quot;]);<br><br>        opts.TokenValidationParameters = new TokenValidationParameters<br>        {            <br>            ValidIssuer = builder.Configuration.GetSection(&quot;JwtTokenOptions&quot;)[&quot;Issuer&quot;],<br>            ValidAudience = builder.Configuration.GetSection(&quot;JwtTokenOptions&quot;)[&quot;Audience&quot;],<br>            IssuerSigningKey = new SymmetricSecurityKey(signingKey)<br>        };<br>    });<br>builder.Services.AddAuthorization();<br>//Swagger için gerekli tanımları da ekleyelim. Bunu swagger üzerinden<br>//token denemesini yapmak için ekledim. Postman kullanıyorsanız gerek yok.<br>builder.Services.AddSwaggerGen(c =&gt;<br>{<br>    c.SwaggerDoc(&quot;v1&quot;, new OpenApiInfo { Title = &quot;AspNetJWT&quot;, Version = &quot;1.0&quot; });<br>    c.AddSecurityDefinition(&quot;Bearer&quot;, new OpenApiSecurityScheme<br>    {<br>        Name = &quot;Authorization&quot;,<br>        Type = SecuritySchemeType.ApiKey,<br>        Scheme = &quot;Bearer&quot;,<br>        BearerFormat = &quot;JWT&quot;,<br>        In = ParameterLocation.Header,<br>        Description =<br>          &quot;JWT Authorization header using the Bearer scheme. \r\n\r\n Enter &#39;Bearer&#39; [space] and then your token in the text input below.\r\n\r\nExample: \&quot;Bearer 12345abcdef\&quot;&quot;<br>    });<br>    c.AddSecurityRequirement(new OpenApiSecurityRequirement<br>    {<br>        {<br>            new OpenApiSecurityScheme<br>            {<br>                Reference = new OpenApiReference<br>                {<br>                    Type = ReferenceType.SecurityScheme,<br>                    Id = &quot;Bearer&quot;<br>                }<br>            },<br>            new string[] { }<br>        }<br>    });<br>});<br>//...<br>//...<br>//bunlar mutlaka aşağıdaki sırayla olmalı<br>app.UseAuthentication();<br>app.UseAuthorization();</pre><p>Tanımlar bu kadar. Şimdi <em>token </em>üretimi yapabilmek için bir <em>controller </em>ekleyelim. Bunun adı <em>TokenController</em> olsun.</p><pre>dotnet aspnet-codegenerator controller -name TokenController -async -api -outDir Controllers</pre><p>TokenController.cs dosyamıza aşağıdaki metodları ekleyelim. (Not: Tabi burada constructor içinde <em>IConfiguration config</em> de enjekte edilmeli).</p><pre>[HttpGet(&quot;{pwd}&quot;)]<br>public async Task&lt;IActionResult&gt; GetToken(string pwd)<br>{<br>    if (pwd != &quot;123&quot;) return Unauthorized(&quot;Şifre geçersiz.&quot;);<br>    string token = GenerateToken();<br>    return Ok(token);<br>}<br><br>private string GenerateToken()<br>{<br>    var keyBytes = Encoding.UTF8.GetBytes(_config.GetSection(&quot;JwtTokenOptions&quot;)[&quot;SigningKey&quot;]);<br>    var symmetricKey = new SymmetricSecurityKey(keyBytes);<br><br>    var signingCredentials = new SigningCredentials(<br>        symmetricKey,<br>        SecurityAlgorithms.HmacSha256);<br><br>    var claims = new List&lt;Claim&gt;()<br>    {<br>        new Claim(&quot;sub&quot;, &quot;eunal&quot;),<br>        new Claim(&quot;name&quot;, &quot;engin unal&quot;)<br>    };<br><br>    var roleClaims = new List&lt;Claim&gt;()<br>    {<br>        new Claim(&quot;role&quot;, &quot;readers&quot;),<br>        new Claim(&quot;role&quot;, &quot;writers&quot;),<br>    };<br><br>    claims.AddRange(roleClaims);<br><br>    var token = new JwtSecurityToken(<br>        issuer: _config.GetSection(&quot;JwtTokenOptions&quot;)[&quot;Issuer&quot;],<br>        audience: _config.GetSection(&quot;JwtTokenOptions&quot;)[&quot;Audience&quot;],<br>        claims: claims,<br>        expires: DateTime.Now.Add(TimeSpan.FromSeconds(double.Parse(_config.GetSection(&quot;JwtTokenOptions&quot;)[&quot;Expiration&quot;]))),<br>        signingCredentials: signingCredentials);<br><br>    var tokenData = new JwtSecurityTokenHandler().WriteToken(token);<br>    return tokenData;<br>}</pre><p>Son durumda swagger görüntüsü aşağıdaki gibi olur.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*f4Y6J_QbeUL7wd1m6NiVlA.png" /></figure><p>Şimdi JWT üretimini denemek için <em>GetToken</em> metodunu çağıralım.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*RbWYPDJc5YSiXBe0aC1kxA.png" /></figure><p>Gelen token’ı <a href="http://jwt.io/"><em>jwt.io</em></a> da incelediğimizde:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*1SwoYEkPJTMauCJnlQ3QeQ.png" /></figure><p>Şimdi <em>ItemsController</em> class tanımının üzerine <strong>[Authorize]</strong> atribute tanımını ekleyelim. Artık token olmadan doğrudan <em>ItemsController</em>’daki herhangi bir metodu çağırdığımızda <strong><em>401</em></strong><em> Unauthorized </em>hatası alacağız. Amacımız <em>GetToken</em> metodundan token üretip çağırmaktı, yukarıda aldığımız token ile deneyelim. Bunun için swagger sağ üst köşedeki <strong>Authorize</strong>’a tıklayıp açılan pencereden <em>Value </em>kısmına -&gt; Bearer tokenbilgisiniyapıştırıyoruz.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/632/1*eEzhJHWtdJ29-fThnZPRCQ.png" /></figure><p>Ve <em>GetItems</em> metodunu çağırdığımızda:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*F8dWv153n_s-LDFQWUy5Tg.png" /></figure><p>Sonuçları geliyor. Buraya kadar olan kısımda <em>JWT </em>kütüphanesini ekledik, tanımlarını gördük ve devamında simetrik anahtar kullanan <em>HS256 </em>algoritması ile şifrelenmiş token üretimini gerçekleştirdik ve son olarak denedik. Yazının sonuna doğru kullanım pratiğinde genelde merak edilenlerle devam edeceğim.</p><h4>Audience Nasıl Çalışıyor?</h4><p>Tanım dosyamızda yani <em>appsettings.json</em> dosyamızda <em>issuer</em> ve <em>audience</em> tanımlarımız vardı. <em>Issuer</em>, token üreticisinin bilgisini taşırken <em>audience</em> ise token’ın kullanıcısını belirlemekteydi. Örneğin bir token ürettik ve bunun her serviste değil de sadece bazı servislerde kullanılabilmesini istemekteyiz. Bu durumda audience tanımını kullanırız. Somut örnek üzerinden gidelim. Örnek projemizde mevcut tanımlarla token üretelim.</p><p>Tanımlar:</p><pre> &quot;JwtTokenOptions&quot;: {<br>    &quot;Issuer&quot;: &quot;AspNetJWT&quot;,<br>    &quot;Audience&quot;: &quot;ItemsAud&quot;,<br>    &quot;SigningKey&quot;: &quot;simetrik sifreleme anahtari kullanacagiz&quot;,<br>    &quot;Expiration&quot;: 3600<br>  }</pre><p>Üretittiğimiz token:</p><pre>eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJldW5hbCIsIm5hbWUiOiJlbmdpbiB1bmFsIiwicm9sZSI6WyJyZWFkZXJzIiwid3JpdGVycyJdLCJleHAiOjE2OTMyMzQ4MjYsImlzcyI6IkFzcE5ldEpXVCIsImF1ZCI6Ikl0ZW1zQXVkIn0.FjQkT5i7EDPQ1_2KsFI7i3YkL9hhB5HKSrnLeCRtwMs<br><br>Token içinde aud değeri:<br>&quot;aud&quot;: &quot;ItemsAud&quot;</pre><p>Bu token ile <em>GetItems</em> servisini çağırdığımızda çalışacaktır. Projeyi kapatıp <em>appsettings.json</em> içindeki <em>Audience</em> değerini “DenemeServis” olarak değiştirelim ve projeyi tekrar başlatalım. Mevcut token’ı <em>Swagger UI</em>’daki <strong>Authorize</strong> ekranından aşağıdaki gibi girelim ve <em>GetItems</em> servisini çağıralım.</p><pre>Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJldW5hbCIsIm5hbWUiOiJlbmdpbiB1bmFsIiwicm9sZSI6WyJyZWFkZXJzIiwid3JpdGVycyJdLCJleHAiOjE2OTMyMzQ4MjYsImlzcyI6IkFzcE5ldEpXVCIsImF1ZCI6Ikl0ZW1zQXVkIn0.FjQkT5i7EDPQ1_2KsFI7i3YkL9hhB5HKSrnLeCRtwMs</pre><p>Aşağıdaki hatayı alırız:</p><pre>www-authenticate: Bearer error=&quot;invalid_token&quot;,<br>error_description=&quot;The audience &#39;ItemsAud&#39; is invalid&quot; </pre><p>Servisimiz “DenemeServis” <em>aud </em>değerini bekliyor ama token içindeki değer “ItemsAud” olarak geldiğinden bu token bu servis için üretilmemiş ve <em>ValidateAudience</em> kontrolünden geçemiyor. (Not: Biz <em>Program.cs</em> içinde <em>TokenValidationParameters</em> altında <em>ValidateAudience </em>parametresini tanımlamadık bu nedenle default tanımıyla yani <em>ValidateAudience=true </em>olarak çalıştı). Yukarıdaki örnekte tek proje üzerinden anlatmaya çalıştım ama size önerim iki farklı web api projesi oluşturup birbiri ile haberleşmesinde bu parametreleri değiştirerek denemenizdir.</p><p>Şimdiye kadar anlatılanlarla ilgili kod örneğini aşağıdaki repo’dan inceleyebilirsiniz.</p><p><a href="https://github.com/enginunal/AspNetJWT/tree/webapi-jwt-symmetric-hmac">https://github.com/enginunal/AspNetJWT/tree/webapi-jwt-symmetric-hmac</a></p><h4>Asimetrik Anahtar İle JWT Üretimi Nasıl Yapılır?</h4><p>Simetrik anahtara göre çok daha güvenli bir yöntem olan asimetrik anahtar şifrelemesinin nasıl yapılacağını inceleyelim. Asimetrik şifreleme algoritması olarak PS256 (RSASSA-PSS — SHA-256) kullanacağım. Öncelikle Private/Public Key çiftine ihtiyacımız olacak. Bunu üretip appsettings.json içerisinde aşağıdaki değişikliği yapıyorum. (Not: Private Key en üst düzeyde korunması gereken bir veridir config dosyaları, proje dosyaları, kodlar vb. ortamlarda tutmayınız.)</p><pre>{<br>  &quot;Logging&quot;: {<br>    &quot;LogLevel&quot;: {<br>      &quot;Default&quot;: &quot;Information&quot;,<br>      &quot;Microsoft.AspNetCore&quot;: &quot;Warning&quot;<br>    }<br>  },<br>  &quot;AllowedHosts&quot;: &quot;*&quot;,<br>  &quot;ConnectionStrings&quot;: {<br>    &quot;ItemsConnection&quot;: &quot;Data Source=ItemsDb.db&quot;<br>  },<br>  &quot;JwtTokenOptions&quot;: {<br>    &quot;Issuer&quot;: &quot;AspNetJWT&quot;,<br>    &quot;Audience&quot;: &quot;ItemsAud&quot;,<br>    &quot;Expiration&quot;: 10,<br>    &quot;PrivateKey&quot;: &quot;MIIEpAIBAAKCAQEA42x8DXg7KuqaIiQVAktrUpSBfnHfNpZBZrVqhk676oF0V9Jo4R9S/ML+t6W6/3jLfYriwmJAUK6i5XihKijYcGsgqbDnk7ymD9sX6JQi+7YviQdNiPPHlbd6ULxwaFRp8VRoeHkJwz7cb1S46I4KXjSDLzg7V9UoILllvfwkw2g5HmukQCwirOPTfJU4X6W0F8Cc6KYWILGMWoL3L965JxXrSBLUAynMw2XiuudWbAirS5H6aVM58QP5bhO884azLCsgTmQr78EHEtY3KuopMR3h607n8UB0SsaBXc+suN/FMFStLlIktej1KpZjPOQYplFVb1Za4qm550W3+vA3uQIDAQABAoIBAAe/tYZb92Ah+Dh7lD+sxC5fIv5k8N8SRY4zVjrXSe3WlVk8sRgikhhMqJMnUXTM79oKNmm7IUfee2xoLM2b8Kv76nP9tBZDkkDuDSV+jqaW8Y2wswKck8tVIhTIJuhXH/j6EEkyjpOZe7dLYrWByXamQWYFe3glqiVbS80qXzJNNVehJZKWE4C0p95zylLeve87z6ESRqEFEGGajg1fJ1y8HiBFLLIeo8PhXVQb+12Lli4nt4J5e26vGftZbEBc2hgHmy+z03lUIwlLjHtnSIMncdVNoDxpIGrk000tan77RMbUtEtoJDrDlT31nUihxghDKCo40M6dWIAn/mYF5QUCgYEA/BLInz+lgE3XLxVjDoTl5oln4d1iLfzmAx/D+AKeIsg+t4wEVFhJ8z35t9+Rgge2+0vdFIXt0cmCxT6xgvDwPB4tzRgihowierGs/LBA3ALChw87Km7ZhmZPNi0N1ZKOOQUpIEUVqquMRBuIPjGhX+/zrV9HD1EYgrhxllxnLbMCgYEA5vdnRFDufdMqYj274GgZBLPdRlmT9m+5fbBDczRzhRJuxwH0enmJ/pswpKtDi/233eLUAUQoFT9ncn/umLsJvpsmknXEjJu5j/+lhy5zMv37Zj0DZiV/tkmrNH2Z3fuVVHC0MRGKee/JQkAjq3498epkfHRgHallSzAlzxLdhuMCgYEAyffK4z29v2WfCkyah6GYCvUBVQlqhupg6RPkkqyWQ8wp5Cq9tU7gQZLgqKDrF0JTLFoGk1ET0ckhjCTFWeLuuVx82h2CXEefwKrnrbcS4LUdY9WiVEdo5rTGtuO2d+7rIWivIPn5eDvnMRLWQO7HG/VISxtR9kXF28coy47R5N0CgYB5IXmsT5G0tcG9wki9WXr9h2NeWO6LyW6L55yIWBLZ0J/+iRzx+roPqM5rHlMPoWID4cl0XI11D0qutz+oJDfpGi+xkkghwi4gwl/KtOa/8IofxZH5yWLgdGRw5AqFbEOWBIHMVjm817BCqabLcysDq/1FdDdQdx3jv/2kS22ZJwKBgQD6ZZc5imJ3R78wJt/PXkYXlqF/pvN83ImwSTGhyyDi+sLdhwqXhxGOoD9MlhR2Cv5bufzxYzsrRvpLt4szu6lE/9b+MjIjCjQqkGVxOcQjlTGuQg8Xd/pkhNePghD0aFKvzVThsNclhntiGkRvroG7eGW19gCWa71o54RzF6I5LQ==&quot;,<br>    &quot;PublicKey&quot;: &quot;MIIBCgKCAQEA42x8DXg7KuqaIiQVAktrUpSBfnHfNpZBZrVqhk676oF0V9Jo4R9S/ML+t6W6/3jLfYriwmJAUK6i5XihKijYcGsgqbDnk7ymD9sX6JQi+7YviQdNiPPHlbd6ULxwaFRp8VRoeHkJwz7cb1S46I4KXjSDLzg7V9UoILllvfwkw2g5HmukQCwirOPTfJU4X6W0F8Cc6KYWILGMWoL3L965JxXrSBLUAynMw2XiuudWbAirS5H6aVM58QP5bhO884azLCsgTmQr78EHEtY3KuopMR3h607n8UB0SsaBXc+suN/FMFStLlIktej1KpZjPOQYplFVb1Za4qm550W3+vA3uQIDAQAB&quot;<br>  }<br>}</pre><p><em>Program.cs</em> dosyamızda aşağıdaki düzenlemeyi yapıyoruz.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/47e52cf889d717aa01251eec56c21962/href">https://medium.com/media/47e52cf889d717aa01251eec56c21962/href</a></iframe><p>Yukarıdaki değişkliğimiz gelen token’ı doğrulayabilmek için Public Key verisini geçmekten ibaret. Bunu da <em>IssuerSigningKey</em> ile sağlıyoruz. Son olarak <em>TokenController.cs</em> içinde aşağıdaki düzenlemeyi yapıyoruz.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/1242394874f2a8c410df400a5d674546/href">https://medium.com/media/1242394874f2a8c410df400a5d674546/href</a></iframe><p>Buradaki düzenleme de çok farklı değil. Token üretirken imzalama için kullanılacak Private Key verisini <em>SigningCredentials</em>’a geçmek gerekiyor bunu sağladık. Şimdi deneyelim. GetToken metodunu çağırdığımızda aşağıdaki gibi imzalı token verisini üretecektir.</p><pre>eyJhbGciOiJQUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJldW5hbCIsIm5hbWUiOiJlbmdpbiB1bmFsIiwicm9sZSI6WyJyZWFkZXJzIiwid3JpdGVycyJdLCJhdWQiOiJJdGVtc0F1ZCIsImV4cCI6MTY5MzI5ODczMiwiaXNzIjoiQXNwTmV0SldUIiwiaWF0IjoxNjkzMjk4MTMyLCJuYmYiOjE2OTMyOTgxMzJ9.QTKs7obfYKON2NmXsaiuRw3VIg4H5SxU00leTc1SGPgL5F8yyMxSOyPtBq7J-ITDl1mo1SQMQzUcWeikazmwj-qrCmw4kBTZpvpVQIaI2q6fTvjbJor0ORhrvOCv8Cq9IMhRUry2FDeIi240Jwjtq0FNdKQ23uM_UAmp9x0jp5hIfgDkefSpZYqEvydPYubz7e-VbaVrE3j4txXZy-zzf9ACsHsceMTDlzCm95kHuE4cm1i2REVWNoOkuNONDk5fxicEWdww_iENB84s5au62yx28wnpd1jsmcPj0aipiiOY75tkGn5FNkktsnHCztONMK64XWGrZ5KlM7EAoPBkOA</pre><p>Tekrar çağıralım.</p><pre>eyJhbGciOiJQUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJldW5hbCIsIm5hbWUiOiJlbmdpbiB1bmFsIiwicm9sZSI6WyJyZWFkZXJzIiwid3JpdGVycyJdLCJhdWQiOiJJdGVtc0F1ZCIsImV4cCI6MTY5MzI5ODc5OSwiaXNzIjoiQXNwTmV0SldUIiwiaWF0IjoxNjkzMjk4MTk5LCJuYmYiOjE2OTMyOTgxOTl9.aRz89LOLmAjx16us2El96zrWt2ZFhDBQSy8MYme35m8v4va0Ke2-0-vY5n1QEQcZ-NpHP5st77U0Y5IUDFroJoObhfiPTtpe5Z34um_QsE8yunbwDWt4jT00M6YTuXyUw3AcXd77COMaiW67-omM4aqdZVAF6BEQ4cxspb2t7ozaBFGHLImtrLHOGkd6wwTV-Y_aKzdGqQTwFtZtn7VnE8CZW9aX_PmruX3gX61T38yWlpjbQG60euGA1m25Zv_uCuYp79rW4jSm_F9HAYYcoxQtAgmOk4TVT1fHsxW_CZYMwiaXE5Y4hdGteJZdI894ZmRpMmHiI7ir16Kq9TX40A</pre><p>İkincisinde farklı imza üretti.</p><pre>// ilk token imzası<br>QTKs7obfYKON2NmXsaiuRw3VIg4H5SxU00leTc1SGPgL5F8yyMxSOyPtBq7J-ITDl1mo1SQMQzUcWeikazmwj-qrCmw4kBTZpvpVQIaI2q6fTvjbJor0ORhrvOCv8Cq9IMhRUry2FDeIi240Jwjtq0FNdKQ23uM_UAmp9x0jp5hIfgDkefSpZYqEvydPYubz7e-VbaVrE3j4txXZy-zzf9ACsHsceMTDlzCm95kHuE4cm1i2REVWNoOkuNONDk5fxicEWdww_iENB84s5au62yx28wnpd1jsmcPj0aipiiOY75tkGn5FNkktsnHCztONMK64XWGrZ5KlM7EAoPBkOA<br>// ikinci token imzası<br>aRz89LOLmAjx16us2El96zrWt2ZFhDBQSy8MYme35m8v4va0Ke2-0-vY5n1QEQcZ-NpHP5st77U0Y5IUDFroJoObhfiPTtpe5Z34um_QsE8yunbwDWt4jT00M6YTuXyUw3AcXd77COMaiW67-omM4aqdZVAF6BEQ4cxspb2t7ozaBFGHLImtrLHOGkd6wwTV-Y_aKzdGqQTwFtZtn7VnE8CZW9aX_PmruX3gX61T38yWlpjbQG60euGA1m25Zv_uCuYp79rW4jSm_F9HAYYcoxQtAgmOk4TVT1fHsxW_CZYMwiaXE5Y4hdGteJZdI894ZmRpMmHiI7ir16Kq9TX40A</pre><p>Kullandığımız algoritma RSASSA-PSS olması nedeniyle imzalanan veri değişmese de imza değişir ve daha güvenli imzalar üretebilir. Bununla ilgili bilgiyi bu yazının başlarında vermiştim. Şimdi token ile GetItems metodunu çağırarak testi bitirelim.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*eCQEU-fnukhxYb2cx7W81g.png" /></figure><p>Herşey istediğimiz gibi çalışıyor. Asimetrik anahtar ile imzalama örneği için aşağaki github repo’dan kodları inceleyebilirsiniz.</p><p><a href="https://github.com/enginunal/AspNetJWT/tree/webapi-jwt-asymmetric-ps256">https://github.com/enginunal/AspNetJWT/tree/webapi-jwt-asymmetric-ps256</a></p><p>Yazının sonuna geldik. Okuduğunuz için teşekkürler.</p><p>Engin Ünal</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=ca16a67f3cf6" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[C# Expression Tree Ve Dinamik Filtreleme Örneği]]></title>
            <link>https://enginunal.medium.com/c-expression-tree-ve-dinamik-filtreleme-%C3%B6rne%C4%9Fi-e95108d3735c?source=rss-f2da39f0544c------2</link>
            <guid isPermaLink="false">https://medium.com/p/e95108d3735c</guid>
            <category><![CDATA[entity-framework-core]]></category>
            <category><![CDATA[dotnet-core]]></category>
            <category><![CDATA[c-sharp-programming]]></category>
            <category><![CDATA[expression-tree]]></category>
            <category><![CDATA[expression]]></category>
            <dc:creator><![CDATA[Engin UNAL]]></dc:creator>
            <pubDate>Wed, 16 Aug 2023 18:57:51 GMT</pubDate>
            <atom:updated>2023-08-16T18:57:51.275Z</atom:updated>
            <content:encoded><![CDATA[<p><em>Expression Tree nedir? Nasıl kullanabiliriz? Bu konuya giriş yapıp dinamik sorgu kodu üretimi örneğiyle inceleyeceğiz.</em></p><p>Expression Tree konusuna girmeden önce bazı temel kavramların üzerinden geçmemiz faydalı olacak. Yazdığımız kodların yapı taşları olan bu kavramlar <em>expression</em>, <em>statement </em>ve <em>block </em>olarak sıralanabilir.</p><p><strong>Expression</strong>, bir değer döndüren kod parçasıdır. Değişkenler, değişmezler, fonksiyon çağrıları ve operatörlerden veya bunların kombinasyonundan oluşabilir. Bir expression başka bir expression’ı içerebilir birazdan değineceğiz.</p><p><strong>Statement</strong>, uygulamanın çalıştırılması için gereken temel kod birimleridir. Örneğin C# için “;” arasındaki ifadeler birer statement örneğidir veya if/else, for/do/while, break/continue, throw/try/catch gibi yapılar statement örnekleridir.</p><p><strong>Block </strong>ise “{}” arasındaki bölümlerdir. İçerisinde hiç statement olmayabilir veya birden çok statement içerebilir.</p><p>Asıl konumuz olan Expression Tree ile ilgili bilgiler vermeden önce Expression konusunu daha derinden incelemeliyiz. Expression ve örnekleriyle devam edelim.</p><h3>Expression</h3><p>.Net expression yapılarının karşılığı olarak <em>System.Linq.Expressions</em> namespace altında <em>Expression</em> base class bulunmaktadır. Bu kütüphaneyi kullanarak expression oluşturabilir veya mevcut bir expression’ı inceleyebiliriz. Bu class içerisinde <em>NodeType</em> ve <em>Type</em> property’leri bulunmaktadır.</p><p><em>NodeType</em>, kullanılan expression’ın hangi tipte olduğunu verir. Expression kütüphanesi oldukça geniş bir tipte expression oluşturmaya imkan tanımakta. Oluşturulan expression’ın tipine ulaşmak için bu property kullanılır. Neler var denilirse örnek olarak <em>BinaryExpression, ConstantExpression, ConditionalExpression, LambdaExpression</em> ve daha bir çoğu sayılabilir. Bu özel tipler kullanılarak amaca yönelik özelleştirilmiş expression’lar oluşturulabilir.</p><p><em>Type</em>, expression’ın taşıdığı tipi döndürür. Örneğin expression içinde int tipinde bir değer varsa Type dönüşü int olacaktır. Aşağıdaki örneğimizle bir expression oluşturarak özelliklerini inceleyeceğiz.</p><p><strong>Örnek :</strong> İki sayıyı toplayalım.</p><pre>int a = 1 + 2;</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/553/1*J_yTDZKzhMEboKozG0owNg.png" /><figcaption>Toplama işleminin expression karşılığı.</figcaption></figure><p>1 + 2 işleminde 1 ve 2 değerleri sabit değer expression, toplama işlemi ise ikili işlem(binary) yapan bir expression olarak tanımlanır.</p><pre>ConstantExpression exp1 = Expression.Constant(1, typeof(int));<br>ConstantExpression exp2 = Expression.Constant(2, typeof(int));<br>BinaryExpression expA = Expression.Add(exp1, exp2);</pre><p>Yukarıdaki üç satırlık kodda 1 ve 2 değerlerini taşıyan <em>exp1 </em>ve <em>exp2 </em>expressionlarını aldık, toplama işlemi(<em>Expression.Add</em>) uyguladık ve bu işlemin sonucunda <em>BinaryExpression </em>tipinde expA ismini verdiğimiz expression’ımızı ürettik.</p><p>BinaryExpression’lar parametre olarak iki expression alır(<em>left, right</em>) ve bunlara uygulanacak işlemin operatör bilgisini(<em>NodeType</em>) tutar. Aritmetik işlemler (toplama, çarpma, bölme, üs alma vs.), kıyaslama işlemleri(eşit, eşit değil, büyük, küçük), bit seviyesi işlemleri(and, or, exor) gibi işlemler gerçekleştirilebilir.</p><p>Örneğin yukarıdaki kodlamadaki <em>expA.NodeType</em> değerine bakıldığında <em>Add </em>görülecektir. <em>Expression.Add</em> yerine <em>Expression.Multipy</em> olsaydi yani toplama yerine çarpma yapsaydık <em>NodeType</em> değeri <em>Multiply</em> olarak gelecekti.</p><p><strong>Örnek :</strong> Diğer örnekle devam edelim, “<em>merhaba</em>” kelimesini büyük harfe çevireceğiz(<em>string.ToUpper</em>).</p><pre>MethodCallExpression toUpperExp = Expression.Call(<br>  Expression.Constant(&quot;merhaba&quot;), <br>  typeof(String).GetMethod(nameof(String.ToUpper), new Type[] { }));</pre><p><em>MethodCallExpression</em> kullandık, bunun nedeni <em>String </em>sınıfı içindeki <em>ToUpper </em>metodunu çağırmaktır. Expression içerisinden başka bir sınıftaki veya nesnedeki metodların çağrılması ihtiyacı olduğunda <em>MethodCallExpression </em>kullanılmaktadır.</p><p>Şimdi “merhaba” string ifadesinin uzunluğunu nasıl alabiliriz onu görelim. Normalde kodlarken <em>String.Length</em> kullanarak alabiliyoruz, expression kullandığımızda da <em>String.Length</em> property’sini çağıracağız. Fakat bu bir method değil bir property. MethodCall ile yapamayız. Buna çözüm olarak <em>Property </em>ismiyle çağıracağımız factory metodumuz bize <em>MemberExpression </em>tipinde yeni bir expression üretecek.</p><pre>MemberExpression lenExp = Expression.Property(<br>    Expression.Constant(&quot;merhaba&quot;), <br>    typeof(String).GetProperty(nameof(String.Length)));</pre><p><strong>Örnek :</strong> <em>Console.WriteLine(“merhaba”)</em> işlemini Expression api ile yapalım.</p><pre>ConstantExpression constExp = Expression.Constant(&quot;merhaba&quot;, typeof(string));<br>MethodCallExpression methodCallExp = Expression.Call(<br>  typeof(Console).GetMethod(&quot;WriteLine&quot;, new Type[] { typeof(string) }), <br>  constExp);<br><br>//methodCallExp System.Console.WriteLine(&quot;merhaba&quot;) üretir </pre><p><em>MethodCallExpression</em> içinde <em>Console</em> class altındaki <em>WriteLine</em> metodunu <em>constExp</em> ile hazırladığımız parametreyi vererek çağırdık.</p><p>Bu örnekler expression konusunda bir miktar fikir vermiştir. Expression Tree ile devam edeceğiz.</p><h3>Expression Tree</h3><p>Buraya kadar expression yapılarını ve Api ile expression üretme yöntemini gördük. Aslında iç içe expression üretirken expression tree konusuna giriş yaptık. Expression’ların birbiri ile bağlantıları expression tree ile sağlanır. Expression tree içindeki node’lar yani expression’lar ağacın en alt dalından başlanarak hesaplanır ve en üst dalında(root) işlem sona erer. Örnekler üzerinden daha net görelim.</p><p><strong>Örnek :</strong> 3 &gt; 2 işlemini yapan kod parçamız olsun.</p><pre>bool res = 3 &gt; 2;</pre><p>Bunun expresion Api kodlaması:</p><pre>ConstantExpression exp1 = Expression.Constant(3);<br>ConstantExpression exp2 = Expression.Constant(2);<br>BinaryExpression expRes = Expression.GreaterThan(exp1, exp2);</pre><p>Expression tree yapısı:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/671/1*xSYaLozS41LtRVv8eFWCSw.png" /><figcaption>3 &gt; 2 Expression Tree görüntüsü.</figcaption></figure><p><strong>Örnek :</strong> Şimdi ağacı biraz daha büyütüp (5 &gt; 4) &amp;&amp; (3 &gt; 2) ifadesi için yapalım.</p><pre>bool res = (5 &gt; 4) &amp;&amp; (3 &gt; 2);</pre><p>Expression Api kodlaması:</p><pre>ConstantExpression exp1 = Expression.Constant(5);<br>ConstantExpression exp2 = Expression.Constant(4);<br>BinaryExpression exp12 = Expression.GreaterThan(exp1, exp2);<br><br>ConstantExpression exp3 = Expression.Constant(3);<br>ConstantExpression exp4 = Expression.Constant(2);<br>BinaryExpression exp34 = Expression.GreaterThan(exp3, exp4);<br><br>BinaryExpression exp1234 = Expression.AndAlso(exp12, exp34);</pre><p>Expression tree yapısı:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*FVAagyYBDp68zbA9EfAy0A.png" /><figcaption>(5 &gt; 4) &amp;&amp; (3 &gt; 2) Expression Tree görüntüsü.</figcaption></figure><p>Buraya kadar olan kısımda expression tanımlama konusunu gördük fakat parametre geçmek gerektiğinde nasıl yapacağız? Bu expression’ları nasıl çalıştırıyoruz? Şimdi bu soruların cevaplarıyla devam edelim.</p><p>Bir expression’a dışarıdan parametre göndermek isteyebiliriz. Bunun için <em>ParameterExpression </em>sınıfını kullanacağız. Örneğin aşağıdaki gibi bir lambda expression kodlamış olalım. Lambda expression konusuna bu yazıda girmeyeceğim, bildiğinizi varsayarak devam edeceğim.</p><pre>Func&lt;int, bool&gt; func1 = (i) =&gt; i &gt; 2;<br><br>//Expression karşılığı<br>ParameterExpression expParam = Expression.Parameter(typeof(int), &quot;i&quot;);<br>ConstantExpression expConst = Expression.Constant(2);<br>BinaryExpression expRes = Expression.GreaterThan(expParam, expConst);</pre><p><em>ParameterExpression</em> oluştururken parametrenin taşıdığı tipi ve opsiyonel olarak parametre adını veriyoruz. Bu şekilde oluşturduğumuz expression nesnesine dışarıdan parametre geçebiliyoruz. Örneğimizde <em>func1 </em>lambda expression’ı bulunmakta ve girdi olarak <em>int </em>tipinde parametre alırken çıkı olarak <em>bool</em> tipinde dönüş yapıyor.</p><p>Lambda expression’ın sağ tarafındaki ifadeyi üreterek başladık ve <em>int</em> tipinde parameter expression oluşturduk buna <em>“i”</em> ismini verdik, 2 değeriyle ConstantExpression oluşturduk ve bunları büyüktür işlemini yapacak <em>BinaryExpression</em> içine ekledik.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/528/1*B68xIjwJ7KWy1Nnyj2ipsg.png" /><figcaption>i &gt; 2 Expression Tree görüntüsü.</figcaption></figure><p>Oluşturduğumuz expression’ın çalıştırılması nasıl yapılıyor? Öncelikle <em>func1 </em>ismiyle yazdığımız lambda expression bildiğiniz üzere doğrudan çalıştırılabiliyor.</p><pre>Func&lt;int, bool&gt; func1 = (i) =&gt; i &gt; 2;<br>//func1 lambda expression çağırımı <br>Console.WriteLine(func1(3)); // true<br>Console.WriteLine(func1(1)); // false</pre><p>Expression için de durum farklı değil. Üretilen expression nesnesini çalıştırılabilir yapabilmek için öncelikle Lambda expression’a çevirmemiz gerekiyor. Bunu aşağıdaki gibi yapıyoruz.</p><pre>Expression&lt;Func&lt;int, bool&gt;&gt; lambdaExp = Expression.Lambda&lt;Func&lt;int, bool&gt;&gt;(expRes, expParam);<br>Func&lt;int, bool&gt; func1FromExp = lambdaExp.Compile();<br>Console.WriteLine(func1FromExp(3)); // true<br>Console.WriteLine(func1FromExp(1)); // false</pre><p>Örneğimizde ürettiğimiz <em>expRes’i </em>önce Lambda Expression’a çeviriyoruz.</p><pre>Expression&lt;Func&lt;int, bool&gt;&gt; lambdaExp = <br>            Expression.Lambda&lt;Func&lt;int, bool&gt;&gt;(expRes, expParam);</pre><p><em>Expression.Lambda</em> metoduyla <em>int</em> girdi alan ve <em>bool</em> dönen bir fonksiyon oluşturacağımızı bildiriyoruz. Parametre olarak alınacak <em>expParam </em>bilgisini de veriyoruz. Yani lambda expression oluştururken <em>expression body</em> ve parametre bilgisini kullandık. Bunun sonucunda elimizde <em>lambdaExp </em>isminde bir nesnemiz var bunu çalıştırmak için derlememiz gerekiyor. Bunun için ise <em>Compile</em> metodunu kullanarak derleme yapıyoruz ve çalıştırılabilir fonksiyonumuz hazır oluyor.</p><pre>Func&lt;int, bool&gt; func1FromExp = lambdaExp.Compile();<br>Console.WriteLine(func1FromExp(3)); // true<br>Console.WriteLine(func1FromExp(1)); // false</pre><p>Özetle, oluşturduğumuz expression’ları çalıştırılabilir yapabilmek için Lambda Expression’a çevirmemiz ve derlememiz gerekli bunun için de Expression kütüphanesinin <em>Expression.Lambda</em> metodunu ve sonrasında derleme için <em>Expression.Compile</em> metodunu kullanıyoruz.</p><p>Microsoft dokümanlarındaki açıklama:</p><blockquote>Only expression trees that represent lambda expressions can be executed. Expression trees that represent lambda expressions are of type <a href="https://learn.microsoft.com/en-us/dotnet/api/system.linq.expressions.lambdaexpression">LambdaExpression</a> or <a href="https://learn.microsoft.com/en-us/dotnet/api/system.linq.expressions.expression-1">Expression&lt;TDelegate&gt;</a>. To execute these expression trees, call the <a href="https://learn.microsoft.com/en-us/dotnet/api/system.linq.expressions.lambdaexpression.compile">Compile</a> method to create an executable delegate, and then invoke the delegate.</blockquote><p><a href="https://learn.microsoft.com/en-us/dotnet/csharp/advanced-topics/expression-trees/expression-trees-execution">Executing Expression Trees</a></p><p>Yukarıdaki örnekte bir expression ürettik ve bunu lambda expression’a çevirip çalıştırdık. Tersi de geçerlidir. Lambda expression’dan expression tree oluşturmak mümkündür. Örnek:</p><pre>Expression&lt;Func&lt;int, bool&gt;&gt; expFunc1 = (i) =&gt; i &gt; 2;</pre><p>Derleyici <em>Expression&lt;TDelegate&gt;</em> ifadesi ile başlayan lambda expression’ları expression tree olarak işleyecektir. Böylece üretilen expression içerisi görülebilir veya bu kullanılarak yeni expression tree’ler üretilebilir.</p><pre>Expression&lt;Func&lt;int, bool&gt;&gt; expTree = i =&gt; i &gt; 2;<br>Func&lt;int, bool&gt; func1 = expTree.Compile();<br>func1(3); // true<br><br>// expTree içeriği incelenebilir veya bunlar kullanılarak<br>// yeni bir expression tree üretilebilir ve bununla değiştirilebilir.<br>ParameterExpression param = (ParameterExpression)expTree.Parameters[0];<br>BinaryExpression operation = (BinaryExpression)expTree.Body;<br>ParameterExpression left = (ParameterExpression)operation.Left;<br>ConstantExpression right = (ConstantExpression)operation.Right;</pre><p>Expression tree node’ları değiştirilemezdir, eğer değiştirmek gerekiyorsa yeni expression node oluşturulur. Expression tree düzenleme işlemleri <em>ExpressionVisitor</em> class yardımıyla yapılır. Dinamik filtreleme konusuna artık geçebiliriz. Oradaki örnek kodlarda kullanım alanları hakkında daha net bilgi sahibi olabileceğiz.</p><h3>Dinamik Filtre Üretimi</h3><p>Veritabanı sorgulamalarında LINQ kullanıyorsanız mutlaka <em>expression tree</em> yapılarını kullanmak durumunda kaldınız demektir. Örneğin <em>IQueryable</em> interface’ini miras alan bir nesnenin <em>Where</em> sorgusunda expression kullanmışsınızdır. Bu noktada <em>IEnumarable </em>ve <em>IQueryable</em> farkını da incelemenizi tavsiye ederim. Kısaca değinmek gerekirse, <em>IEnumarable</em> kullanıldığında Entity Framework(EF) tarafından veritabanından tüm kayıtlar çekilir ve bunlar üzerinde sorgu çalıştırılır yani filtreleme işlemini kod üstlenir. <em>IQueryable </em>kullanıldığında ise filtre sorgusu veritabanına gönderilir ve sonucu çekilir. Bu iki yöntemin de kullanım alanları vardır. Expression tree yapılarının temel kullanımı bu noktada olmaktadır. Yazılan LINQ kodları epxression tree’ye çevrilir.</p><pre>//Aşağıdaki gibi bir sorgu ifademiz olsun. <br>Expression&lt;Func&lt;int, bool&gt;&gt; func1 = i =&gt; i &gt; 2;<br>//Derleyici bu ifadeyi okur ve aşağıdaki gibi bir expression tree üretir<br>ParameterExpression exp1 = Expression.Parameter(typeof(int), &quot;i&quot;);<br>ConstantExpression exp2 = Expression.Constant(2);<br>BinaryExpression expRes = Expression.GreaterThan(exp1, exp2);<br>Expression&lt;Func&lt;int, bool&gt;&gt; expLambda = Expression.Lambda&lt;Func&lt;int, bool&gt;&gt;(expRes, exp1);</pre><p>Bu sorgu nasıl işlenir? EF, gelen sorguyu database provider sınıfına gönderir ve database provider bu sorguyu alarak veritabanına gönderilecek örneğin SQL sorgusuna(<em>SELECT Id,Name FROM Users</em>..) çevirir. Bu işleme <em>query translation</em> denir. Translation işlemine başlanmadan araya girmek istenirse <em>IQueryExpressionInterceptor </em>gibi bir interceptor tanımı yapabilirsiniz veya <em>IQueryTranslationPreprocessorFactory </em>‘den türeyen bir sınıf yazarak doğrudan müdahale edebilirsiniz. Yukarıdaki kısım burada not olarak bulunsun. Expression tree’nin işlenmesi ve sonra veritabanının anlayacağı sorgulara çevrilme kısmına şu aşamada ihtiyacımız olmayacak. Bize lazım olan bilgi: Entity Framework, LINQ sorgusunu expression tree’ye dönüştürür.</p><p>Dolayısıyla çalışma zamanında expression tree üretebiliyorsak dinamik sorgulama kapısını açabiliyoruz demektir. Temel çalışma mekanizması hakkında bazı bilgiler edindik şimdi bu bilgileri kullanarak dinamik filtreleme yöntemine geçebiliriz. Örneğimizin tanımını yapalım ve veri yapısını kodlayalım.</p><p><strong>Örnek : </strong>Örneğimizde araç bilgilerini tutacak <em>Car</em> isminde bir sınıfımız var <em>Id, Year, Make </em>alanlarını içeriyor. Veritabanı ile ilgili tanım kodları kalabalık yapmasın diye sanki veritabanı varmış gibi bir class yapacağız adı <em>DemoDb</em> olacak. DemoDb içinde tüm araçları içeren <em>_cars</em> listemiz olacak. Bu listeyi sorgulayabilmek için <em>Cars</em> metoduyla <em>IQueryable </em>erişimini döndüreceğiz. Kısacası <em>DemoDb</em> veritabanında <em>Cars</em> isminde bir tablo olduğunu düşünelim.</p><pre>public class Car<br>{<br>    public int Id { get; set; }<br>    public int Year { get; set; }<br>    public string Make { get; set; }<br><br>    public Car(int id, int year, string make)<br>    {<br>        Id = id;<br>        Year = year;<br>        Make = make;<br>    }<br><br>    public override string ToString()<br>    {<br>        return Id + &quot; &quot; + Year + &quot; &quot; + Make;<br>    }<br>}<br><br><br>public class DemoDb<br>{<br>    List&lt;Car&gt; _cars = new List&lt;Car&gt;();<br><br>    public DemoDb()<br>    {<br>        //seed<br>        _cars.Add(new Car(1, 2023, &quot;Volvo&quot;));<br>        _cars.Add(new Car(2, 2022, &quot;Mercedes&quot;));<br>        _cars.Add(new Car(3, 2021, &quot;Tesla&quot;));<br>        _cars.Add(new Car(4, 2023, &quot;Toyota&quot;));<br>        _cars.Add(new Car(5, 2022, &quot;Audi&quot;));<br>    }<br><br>    public IQueryable&lt;Car&gt; Cars()<br>    {<br>        return _cars.AsQueryable();<br>    }<br>}</pre><p>Cars tablosuna sorgu atalım, örneğin 2023 yılına ait araçları çekelim.</p><pre>var cars2023 = context.Cars().Where(x =&gt; x.Year == 2023).ToList();<br>cars2023.ForEach(x =&gt; Console.WriteLine(x.ToString()));<br><br>// Sonuçlar:<br>// 1 2023 Volvo<br>// 4 2023 Toyota</pre><p>Bunu daha taşınabilir yapabiliriz. Örneğin sorgu expression parametresini Where içinde değil dışarıda hazırlayıp parametre olarak geçelim.</p><pre>// Expression&#39;ı önceden üretip kullanabiliriz<br>Expression&lt;Func&lt;Car, bool&gt;&gt; predicateYear2023 = car =&gt; car.Year == 2023;<br>cars2023 = context.Cars().Where(predicateYear2023).ToList();<br>cars2023.ForEach(x =&gt; Console.WriteLine(x.ToString()));<br><br>// Sonuçlar:<br>// 1 2023 Volvo<br>// 4 2023 Toyota</pre><p>Taşınabilir olmasına çalıştık ama <em>Year </em>kolon adını koda gömmek gerekti. Bundan nasıl kurtulabiliriz? Dışarıdan kolon isimleri ve değerler gelsin biz içeride dinamik olarak <em>Where </em>içerisinde kullanacağımız expression’ı üretelim. Şöyle yapabiliriz:</p><pre>Expression&lt;Func&lt;Car, bool&gt;&gt; predicateDynamic = ExpressionGenerator(&quot;Year&quot;, &quot;==&quot;, 2023);<br>cars2023 = context.Cars().Where(predicateDynamic).ToList();<br>cars2023.ForEach(x =&gt; Console.WriteLine(x.ToString()));</pre><p>Bir tane <em>ExpressionGenerator </em>yazalım ve bu tablomuzdaki kolon ve değerlerden bağımsız olarak expression üretsin böylece sorgulama katmanında kolon bağımlılığından kurtulmuş olur ve daha az kodla daha çok iş çıkarabiliriz.</p><pre>Expression&lt;Func&lt;Car, bool&gt;&gt; ExpressionGenerator(string col, string opr, object val)<br>{   <br>}</pre><p>ExpressionGenerator fonksiyonu <em>col, opr, val</em> parametrelerini yani kolon adı, karşılaştırma operatörü ve değer parametrelerini alarak expression tree üretecek. Böylece amacımız olan <em>Where</em> sorgusundaki <em>LINQ</em> ifadesini dinamik olarak üretebileceğiz. Aşağıda ExpressionGenerator’ın çalışan kodunu inceleyebilirsiniz. Kısa ve anlaşılabilir olması nedeniyle içinde herhangi bir kontrol vs. rutinler yok. Sadece örnek için yeterli olacak şekilde yazdım.</p><pre>Expression&lt;Func&lt;Car, bool&gt;&gt; ExpressionGenerator(string col, string opr, object val)<br>{<br>    //x =&gt; x.Year == 2023, buradaki x parametre olarak geliyor, parameter expression olarak tanımlıyoruz.<br>    ParameterExpression paramExp = Expression.Parameter(typeof(Car), &quot;x&quot;);<br>    //Year,Make alanlarının tipini alan adından alıyoruz.<br>    Type propType = typeof(Car).GetProperty(col).PropertyType;<br>    //x.Year diyebilmek için x&#39;i tanımladık bu da alanın expression tanımı. <br>    MemberExpression propExp = Expression.Property(paramExp, col);<br>    //x.Year == 2023 sorgusundaki 2023 değeri yani filtre değeri(val).<br>    ConstantExpression valExp = Expression.Constant(val, propType);<br>    Expression expRes;<br><br>    //Burada gelen operatör değerine göre farklı expression&#39;lar üretmek gerekiyor. <br>    //==, contains, startswith gibi bunlar çoğaltılabilir. <br>    //Örneğin contains gelirse methodcall expression kullanılırken, == geldiğinden binary expression kullanılır.<br>    switch (opr)<br>    {<br>        case &quot;StartsWith&quot;:<br>            expRes = Expression.Call(propExp, nameof(string.StartsWith), Type.EmptyTypes, valExp);<br>            break;<br>        case &quot;Contains&quot;:<br>            expRes = Expression.Call(propExp, nameof(string.Contains), Type.EmptyTypes, valExp);<br>            break;<br>        case &quot;==&quot;:<br>            expRes = Expression.Equal(propExp, valExp);<br>            break;<br>        case &quot;!=&quot;:<br>            expRes = Expression.NotEqual(propExp, valExp);<br>            break;<br>        default:<br>            expRes = Expression.Equal(propExp, valExp);<br>            break;<br>    }<br>    <br>    Expression&lt;Func&lt;Car,bool&gt;&gt; lambdaExp = Expression.Lambda&lt;Func&lt;Car, bool&gt;&gt;(expRes, paramExp);<br>    return lambdaExp;    <br>}</pre><p>Fonksiyon içerisinde neler yapıldı? Öncelikle <em>ParameterExpression </em>oluşturduk. Bunun nedeni <em>x =&gt; x.Year == 2023 </em>gibi bir sorgu oluşturabilmek için buradaki x’in <em>Car</em> tipinde olması gerekliydi ve <em>x.Year</em> ifadesi için ise <em>Car </em>sınıfının <em>Year </em>property’sine erişmek gerekliydi bunun için ise <em>MemberExpression </em>tanımladık. Bu noktaya kadar ifademizin sol tarafı hazır oldu. Yani <em>x.Year</em> veya <em>x.Make</em> tamam. Sağ tarafı için yani <em>val</em> değeri için ise <em>ConstantExpression </em>tanımladık. Bu noktada alanın tip bilgisi de gerekliydi ve alan adından tipini alarak <em>ConstantExpression</em>’a geçtik. Sağ ve sol taraflar hazır sadece kıyaslama veya işlem tanımı kaldı yani <em>x.Year == 2023</em> ifadesindeki <em>==</em> işlemi. Bunun için ise gelen <em>opr </em>parametresine göre expression ürettik. Eğer contains işlemi olacaksa <em>MethodCallExpression </em>ile <em>String.Contains</em> metodunu çağırdık veya <em>==</em> işlemi olacaksa <em>BinaryExpression </em>ürettik ve <em>Equals </em>kullandık.</p><p>Son olarak lambda expression üretiyoruz <em>Expression.Lambda</em>’ya <em>expRes </em>ve <em>paramExp </em>parametrelerini geçiyoruz. Bunlar <em>body</em> ve <em>parameter</em> olarak adlandırılır. x =&gt; (x.Year == 2023) örneği üzerinden gidelim buradaki body, <em>x.Year == 2023</em> olan bölüm iken parameter ise <em>x</em>’i temsil eder.</p><p>Şimdi deneyelim:</p><pre>DemoDb context = new DemoDb();<br>List&lt;Car&gt; carsRes = new List&lt;Car&gt;();<br><br>Console.WriteLine(&quot;Filter: Year == 2023&quot;);<br>Expression&lt;Func&lt;Car, bool&gt;&gt; predicateDynamicYear2023 = ExpressionGenerator(&quot;Year&quot;, &quot;==&quot;, 2023);<br>carsRes = context.Cars().Where(predicateDynamicYear2023).ToList();<br>carsRes.ForEach(x =&gt; Console.WriteLine(x.ToString()));<br><br>Console.WriteLine(&quot;Filter: Make StartsWith &#39;T&#39;&quot;);<br>Expression&lt;Func&lt;Car, bool&gt;&gt; predicateDynamicStartsWithT = ExpressionGenerator(&quot;Make&quot;, &quot;StartsWith&quot;, &quot;T&quot;);<br>carsRes = context.Cars().Where(predicateDynamicStartsWithT).ToList();<br>carsRes.ForEach(x =&gt; Console.WriteLine(x.ToString()));<br><br>//Çıktıları:<br>//Filter: Year == 2023<br>//1 2023 Volvo<br>//4 2023 Toyota<br>//Filter: Make StartsWith &#39;T&#39;<br>//3 2021 Tesla<br>//4 2023 Toyota</pre><p>Buraya kadar expression tree oluşturma ve çalıştırma konusunda bazı bilgiler verdim. Expression tree içeriğini okumak veya değiştirmek gerekebilir bu gibi işlemler için <a href="https://learn.microsoft.com/en-us/dotnet/api/system.linq.expressions.expressionvisitor?view=net-7.0"><em>ExpressionVisitor</em></a> sınıfı kullanılır.</p><p>Tahmin edileceği gibi Expression Tree kütüphanesi, kullanım alanları ve kısıtları konularında pek çok detay bulunmakta konunun daha iyi anlaşılabilmesi için somut örnekler yaparak ilerlemek faydalı olacaktır.</p><p>Teşekkürler.</p><p>Engin Ünal</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=e95108d3735c" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Tasarım Kalıpları — Proxy Tasarım Kalıbı]]></title>
            <link>https://enginunal.medium.com/tasar%C4%B1m-kal%C4%B1plar%C4%B1-proxy-tasar%C4%B1m-kal%C4%B1b%C4%B1-2cd831d94b16?source=rss-f2da39f0544c------2</link>
            <guid isPermaLink="false">https://medium.com/p/2cd831d94b16</guid>
            <category><![CDATA[csharp]]></category>
            <category><![CDATA[tasarım-kalıpları]]></category>
            <category><![CDATA[proxy-design-pattern]]></category>
            <category><![CDATA[design-patterns]]></category>
            <category><![CDATA[proxy]]></category>
            <dc:creator><![CDATA[Engin UNAL]]></dc:creator>
            <pubDate>Tue, 02 May 2023 21:45:31 GMT</pubDate>
            <atom:updated>2023-12-08T12:24:31.758Z</atom:updated>
            <content:encoded><![CDATA[<h3>Tasarım Kalıpları — Proxy Tasarım Kalıbı</h3><p><em>Yapısal Tasarım Kalıplarından(Structural Patterns) olan Proxy Tasarım Kalıbı(Proxy Design Pattern) nedir? Nasıl kullanılır? C# kod örneğiyle inceleyeceğiz.</em></p><p>Proxy Tasarım Kalıbı, mevcutta kullanılan bir nesnenin <strong>yerine geçen</strong>, o nesneye erişimi sınırlayan, öncesinde veya sonrasında yapılmak istenilen işlemlerin eklenmesine imkan tanıyan yapısal bir tasarım kalıbıdır.</p><p>Adından anlaşılacağı üzere vekil görevini üstlenir. Bir nesnenin yaptığı tüm işi başka bir nesneye devreder, bu şekilde yeni nesne üzerinden gereken kontroller veya işlemler yapılır. Asıl nesnenin çalışma davranışını değiştirmek için kullanılmaz. Böyle bir gereksinim varsa başka bir tasarım kalıbı kullanılmalıdır(ör: Decorator Pattern). Asıl nesne ile proxy nesnesi yer değiştirebilir olmalıdır.</p><p>Basitçe konunun mantığını gösterebilmek için aşağıda iki şema bulunmakta. İlkinde proxy pattern kullanmadan bir servis çağrısı yapılıyor, ikincisinde ise araya proxy nesnesi konularak aynı servis çağırılıyor.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/582/1*PJ-uM07muVb-3X0M8_4hQA.png" /><figcaption>Proxy kullanılmadan doğrudan servis çağrısı yapılıyor.</figcaption></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/760/1*IDD5wa9YFHN1eZ-UMi_u-Q.png" /><figcaption>Client ile Service arasında Proxy nesnesi ekleyerek çağrıları proxy üzerinden karşılar.</figcaption></figure><p>Neden böyle bir tasarıma ihtiyaç duyulur? Yukarıdaki şemada servis nesnemize erişip kullanıyoruz. Servis nesnesinin oluşturulması uzun sürebilir bu durumda her çağrıda tekrar oluşturmak performans açısından olumsuz bir durumdur. Çözüm olarak araya proxy nesnesi kullanarak bunun içinde servis nesnesini üretir ve cache’leyebiliriz.</p><p>Veya servis nesnemizin performansını ölçmek istedik, bunun için servis kodlarında değişiklik yapılması da istediğimiz bir durum değil, test edilmiş ve sorunsuz çalışan kodlara müdahale etmeyi istemeyiz. Çözüm olarak bir proxy yazarak bunun içinden servis nesnemizi çağırırken ölçüm işlemini gerçekleştirebiliriz.</p><p>Veya servis çağrısı başında ve sonunda log atmak istiyoruz. Bu işlem için de servis kodlarına ekleme yapmak yerine proxy kullanarak servis çağrısını proxy içinden yapıp log işlemini buraya ekleyebiliriz.</p><p>Bu ve benzeri durumlar çoğaltılabilir, bunlara çözüm olarak araya proxy nesnesi ekleriz. Yukarıdaki örnekten devam edersek ServiceProxy içerisinden Service nesnemizi çağırabiliriz ve bu çağrı öncesi ve sonrasında istenilen düzenlemeleri yapabiliriz. Service kodlarında değişiklik gerekliliği olmayacaktır.</p><p>Yaklaşım yalın ve uygulaması kolay bir yaklaşımdır. Şimdi bunu C# kodlama örneği ile inceleyelim.</p><h4>Kod Örneği</h4><p>Örnek olarak veritabanı sorgusu için kullanılan bir servisimiz olsun ve serviste geçen süreyi loglayan bir değişiklik istenmiş olsun. Bunu proxy pattern ile nasıl yapabiliriz? Öncelikle servis metodumuzu görelim.</p><pre>public interface IQueryDatabase<br>{<br>    string ExecuteQuery(string query);<br>}</pre><pre>class QueryDatabase : IQueryDatabase<br>{<br>    public string ExecuteQuery(string query)<br>    {<br>        Random rnd = new Random();<br>        System.Threading.Thread.Sleep(1000 * rnd.Next(3));<br>        return &quot;some result&quot;;<br>    }<br>}</pre><p><em>Not: </em>Örnekteki kodların sade olması için veritabanı işlemleri yapılıyormuş gibi yapalım, 3 saniye bekleyip devam etsin.</p><p><em>QueryDatabase</em> içindeki <em>ExecuteQuery</em> metodunu çağırıp sorgu sonucunu çektiğimizi düşünelim. Bizden istenen bu sürenin loglanmasıydı. Ama istenen log özelliğinin eklenmesini <em>ExecuteQuery</em> içinde yapmak istemeyiz.</p><pre>IQueryDatabase dbQery = new QueryDatabase();<br>string res = dbQery.ExecuteQuery(&quot;test&quot;);<br>Console.WriteLine(res);</pre><p>Bu durumda yeni bir proxy class oluşturup bunun içinden <em>QueryDatabase </em>nesnemize erişmeliyiz. Proxy class’ımızı tanımlayalım.</p><pre>class QueryDatabaseMeasureProxy : IQueryDatabase<br>{<br>    IQueryDatabase _queryDatabase;<br><br>    public string ExecuteQuery(string query)<br>    {<br>        if(_queryDatabase == null)<br>          _queryDatabase = new QueryDatabase();<br>        Stopwatch stopwatch = new Stopwatch();<br>        stopwatch.Start();        <br>        string res = _queryDatabase.ExecuteQuery(query);<br>        stopwatch.Stop();<br>        Console.WriteLine($&quot;Elapsed: {stopwatch.Elapsed}&quot;);<br>        return res;<br>    }<br>}</pre><p><em>QueryDatabaseMeasureProxy </em>class’ımız içinde <em>QueryDatabase </em>nesnesini oluşturup <em>ExecuteQuery </em>öncesinde ve sonrasında ne kadar süre aldığını ölçen işlemler yapıyoruz. Şimdi deneyelim.</p><pre>IQueryDatabase dbQProxy = new QueryDatabaseMeasureProxy();<br>dbQProxy.ExecuteQuery(&quot;test&quot;);</pre><p>Şu ana kadar ne yapmış olduk? Veritabanı sorgusu atan servisimize proxy yazdık. Aşağıdaki şekilde ilk satırda verilen yapıdan ikinci sıradakine geçiş yaptık. Bizden istenen süre loglama işlemini veritabanı kodlarımızda herhangi bir değişikliğe gitmeden sağladık.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1007/1*ewNN3I1jfUhim_WYMTSPjA.png" /></figure><p>Başka bir örnekle devam edelim. <em>QueryDatabase</em> servisimiz için yetki kontrolü yapan düzenleme istenmiş olsun. Aynı mantıkla bunun için de bir proxy yazıp servisimize erişelim.</p><pre>class QueryDatabaseProtectionProxy : IQueryDatabase<br>{<br>    IQueryDatabase _queryDatabase;<br>    <br>    public string ExecuteQuery(string query)<br>    {<br>        if(_queryDatabase == null)<br>          _queryDatabase = new QueryDatabase();<br>        if (!CheckProtection()) <br>          return &quot;Protection error!&quot;;<br>        string res = _queryDatabase.ExecuteQuery(query);<br>        return res;<br>    }<br><br>    private bool CheckProtection()<br>    {<br>        return true;<br>    }<br>}</pre><p>Bunun için de <em>QueryDatabaseProtectionProxy</em> isminde bir proxy yazıp içinde yetki kontrolü ekledik. Aşağıdaki gibi çağırarak servis yetki kontrolü içeren düzenlemeyi kullanabiliriz.</p><pre>IQueryDatabase dbQProxy = new QueryDatabaseProtectionProxy();<br>dbQProxy.ExecuteQuery(&quot;test&quot;);</pre><p>Örnekler çoğaltılabilirken iç içe proxy’ler de tanımlayabilirsiniz. Bu durumda üretilen çözümün karmaşıklığı artacaktır.</p><h4>Kullanım Alanları</h4><p>Bu tasarım kalıbının yaygın kullanım alanlarına değinerek yazıyı sonlandıralım.</p><ul><li><strong>Caching Proxy:</strong> Cache yani ön belleğe alma amacıyla kullanılır. Yapılan çağrı için dönen sonuçların ön belleğe alınması ihtiyacı olabilir. Genelde bu tip durumlar performans gerektiren, sık sorgulanan yapılar, fazla veri dönülen sorgulamakar için sözkonusudur. Çözümlerden biri de Caching Proxy yapısı uygulamaktır.</li><li><strong>Protection Proxy:</strong> Erişim kontrolü yapılması için kullanılabilir. İstekte bulunan nesnenin bu isteğe yetkili mi değil mi kontrol edilmesi gerekebilir. Veya birtakım kurallar zinciri kontrolünden geçirilmesi istenebilir. Çözüm olarak Protection Proxy uygulanabilir.</li><li><strong>Remote Proxy:</strong> Uzaktaki bir nesneye erişimde kullanılır. Uzaktaki bir nesneye erişimde bazı network işlemleri veya erişim için gereken fazladan kodlamalar gerekebilir(protokol kodları vs.) işte bu tip işlemleri mevcut koddan ayırıp proxy nesnesi içinde yapmak mümkündür. Böylece çağırılan nesne uzaktan erişiliyor olsa bile sanki lokalde kullanılıyor gibi kolayca kullanılabilir.</li><li><strong>Logging Proxy:</strong> Çağrıları ve/veya dönüş verisini kayıt altına alma amacıyla kullanılabilir. Logging işlemleri için servis kodlarınızı değiştirmenize gerek kalmadan proxy yapısıyla loglama yaptırabilirsiniz. Tabi ki loglama yapan kodlar sadece servis metoduna giriş öncesi ve çıkış sonrası için eklenebilirler.</li><li><strong>Virtual Proxy:</strong> Geç yükleme(lazy loading) amaçlı kullanılabilir. Bazı nesnelerin ilk aşamada yüklenmesi sistemin performansı açısından gereksiz olabilir. İlgili nesnenin çağrı geldiğinde yüklenmesi veya gerekli olduğu anda yüklenmesi tercih edilebilir. Bu tip durumlarda Virtual Proxy kullanılabilir.</li></ul><p>Yukarıda bu tasarım kalıbının kullanım alanlarından bazıları listelenmiştir. İhtiyaca göre çeşitlendirmek mümkündür.</p><p>Teşekkürler.</p><p>Engin ÜNAL</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=2cd831d94b16" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Solidity 10 — ERC-20 Token Standardı Nedir? ERC-20 Token Nasıl Yapılır?]]></title>
            <link>https://enginunal.medium.com/solidity-10-erc-20-token-standard%C4%B1-nedir-erc-20-token-nas%C4%B1l-yap%C4%B1l%C4%B1r-c64b51112f1a?source=rss-f2da39f0544c------2</link>
            <guid isPermaLink="false">https://medium.com/p/c64b51112f1a</guid>
            <category><![CDATA[erc20]]></category>
            <category><![CDATA[smart-contracts]]></category>
            <category><![CDATA[erc-20-tokens]]></category>
            <category><![CDATA[ethereum]]></category>
            <category><![CDATA[solidity]]></category>
            <dc:creator><![CDATA[Engin UNAL]]></dc:creator>
            <pubDate>Tue, 29 Nov 2022 18:16:26 GMT</pubDate>
            <atom:updated>2022-11-29T18:16:26.226Z</atom:updated>
            <content:encoded><![CDATA[<h3>Solidity 10 — ERC-20 Token Standardı Nedir? ERC-20 Token Nasıl Yapılır?</h3><p>ERC-20 Token standardı anlatımı ve devamında kod örneğiyle kendi token’ımızı kodlayacağımız bir yazıyla devam ediyoruz.</p><p>Blockchain projeleri sürekli gelişmekte ve değişkmekte olan, standartları ihtiyaca göre güncellenen ve yeniliklere açık projelerdir. Merkezi olmayan projelerde yeniliklerin hayata geçirilmesi geliştiricilerin, validatörlerin veya kullanıcıların önerileri üzerinden ilerlemektedir.</p><p>Ethereum ekosisteminde yeniliklerin veya değişiklik isteklerinin önerilmesi süreci EIP’ler ile sağlanır. EIP, Ethereum Improvement Proposals ( ~ Ethereum İyileştirme Önerileri ) kelimelerinin kısaltılmışıdır. Örneğin platform temel mimarisi, api, kontrat, network veya diğer konularda EIP hazırlanarak öneri sunulabilir. Devamında öneriler tartışılır ve kabul görürse finalde standartlaşır. <a href="https://eips.ethereum.org/">eips.ethereum.org</a> adresinden EIP hazırlama kuralları, standartlaşmış EIP’ler ve halihazırdaki öneriler incelenebilir.</p><p>Bu kısa özetten sonra konumuz olan EIP-20 veya ERC-20 token standardına geçelim. ERC ise uygulama ve kontrat konularıyla ilgili EIP kategorisi için kullanılan alt başlıktır. ERC-20 dediğimizde EIP-20 önerisinin kontrat, token, URI, library vb. standartlarla ilgili olduğu anlaşılır.</p><p>EIP-20, Fabian Vogelsteller ve Vitalik Buterin’in 2015 yılında ERC kategorisinde önerdikleri bir token arayüz standardıdır. Bu standart ile ilgili detaya <a href="https://eips.ethereum.org/EIPS/eip-20">https://eips.ethereum.org/EIPS/eip-20</a> adresinden ulaşılabilir. ERC-20 token standardı, akıllı kontratlar kullanarak token oluşturma ve transfer işlemlerinin yönetilebilmesi için ortak bir arabirim/interface kuralları koyar. Böylece blockchain dışından da erişilebilen(örneğin cüzdan) özelleştirilmiş ve harcanabilir varlık oluşturma yolu açılmıştır. Devamında ekosistemde token patlaması yaşanmış, harcanabilir varlıklar konusunda ilerlemeler ve tartışmalar sonrasında alınan yol ile harcanamaz varlık yani NFT’ye giden sürece önemli katkıları olmuştur. İleriki yazılarda imkan olursa buna da değineceğim.</p><p>Şimdi ERC-20 token hangi standartları şart koşar? Bunları nasıl sağlayabiliriz? Ve kendimize minimun gereksinimleri sağlayan token yapmak istesek nasıl yapabiliriz? Sorularına sırayla cevaplarla devam edelim.</p><h3>ERC-20 Token Standardı</h3><p><a href="https://eips.ethereum.org/EIPS/eip-20">ERC-20</a> token standardını incelediğimizde geliştirmek istediğimiz akıllı kontrat içerisinde en az aşağıdaki fonksiyon ve tanımların olması gerektiği görülmektedir. Eğer kodlayacağımız kontrat içerisinde aşağıdaki gereksinimler tam olarak sağlanamaz ise veya isimler aynı değilse token’ımız ERC-20 standardına uymayacaktır.</p><pre>//fonksiyonlar<br>function totalSupply() public view returns (uint256)<br>function balanceOf(address _owner) public view returns (uint256 balance)<br>function transfer(address _to, uint256 _value) public returns (bool success)<br>function transferFrom(address _from, address _to, uint256 _value) public returns (bool success)<br>function approve(address _spender, uint256 _value) public returns (bool success)<br>function allowance(address _owner, address _spender) public view returns (uint256 remaining)<br><br>//eventler<br>event Transfer(address indexed _from, address indexed _to, uint256 _value)<br>event Approval(address indexed _owner, address indexed _spender, uint256 _value)</pre><p>Yukarıdaki tanımlar kontratınız içerisinde mutlaka olmalıdır. Dikkat çekmek istediğim <em>name</em> ve <em>symbol</em> tanımlarının zorunlu olmaması. EVM tarafında veya validatörlerde bunun önemi yoktur. Aynı isme sahip token’lar oluşturmak mümkündür. Örneğin ABC isimli bir token olsun aynı isimde bir token daha yapılabilir. Güvenli olmayan bir alım satım işleminde orjinal ABC yerine sahtesine ödeme yapma mümkün olabilir. Bu nedenle <em>name </em>ve <em>symbol </em>değil kontrat adresi kontrol edilir. Yukarıdaki fonksiyon tanımları standart içerisindeki mutlaka olması gereken tanımlardır. Aşağıda ise standartta tanımlı fakat opsiyonel olarak eklenebilen fonksiyonlar verilmiştir.</p><pre>function name() public view returns (string)<br>function symbol() public view returns (string)<br>function decimals() public view returns (uint8)</pre><h4>ERC-20 içerisindeki fonksiyonların açıklamaları:</h4><ul><li><strong><em>name() :</em></strong> Token’a verilen ismi döndürür. Örnek: Istanbul Token.</li><li><strong><em>symbol() :</em></strong> Verilen kısa ismi döndürür. Örnek: IST.</li><li><strong><em>decimals() :</em></strong> Ondalık hane sayısını döndürür. Ethereum’da minimum birim Wei’dir. 1 eth = 10¹⁸ wei’dir. Ondalık işlemler için özel bir tip bulunmaz. İşlemler wei’e çevrilerek yapılır. Örneğin 2.5 eth = 2.5 * 10¹⁸ olacaktır. ERC-20 token için de ondalıklı yapı kullanılmak isteniyorsa decimals tanımı yapılarak bu sağlanabilir. Genellikle 10¹⁸ kullanılır veya isteğe göre değiştirilebilir.</li><li><strong><em>totalSupply() :</em></strong> Toplamdaki token arzıdır.</li><li><strong><em>balanceOf(address _owner) :</em></strong> <em>_owner </em>ile belirtilen hesaptaki bakiye bilgisini dönmektedir.</li><li><strong><em>transfer(address _to, uint256 _value) :</em></strong> Çağıran hesaptan belirtilen <em>_to </em>adresine <em>_value </em>kadar token’ı gönderen fonksiyondur. Bu işlem sonucunda <em>Transfer </em>event’i fırlatılmalıdır. Hesapta yeterki kadar token yoksa hata fırlatmalıdır.</li><li><strong><em>transferFrom(address _from, address _to, uint256 _value) :</em></strong> <em>_from </em>adresinden <em>_to </em>adresine <em>_value </em>kadar token’ı transfer eder. <em>transfer </em>fonksiyonundaki mekanizmaya benzerdir.</li><li><strong><em>approve(address _spender, uint256 _value) :</em></strong> Belirtilen <em>_spender </em>adresine sahip hesabın toplamda <em>_value </em>değeri kadar çekme işlemi(withdraw) yapabilmesini tanımlamak için kullanılır.</li><li><strong><em>allowance(address _owner, address _spender) :</em></strong> <em>_owner </em>tarafından <em>_spender </em>adresine verilmiş çekme işlemi izni kalan miktarını döner.</li></ul><p>Genel mekanizmayı gördüğümüze göre artık kodlama ve uygulamaya geçebiliriz. Öncelikle temel özelliklerde bir token yapalım.</p><h4>Kendi Token’ımızı Yapalım</h4><p>Geliştireceğimiz token sembolü ve adı <em>GIST</em>:Guzel Istanbul Token olsun. Token kontrat kodu aşağıdaki gibi olabilir.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/3fa81133be2965237bb244ab9ba74283/href">https://medium.com/media/3fa81133be2965237bb244ab9ba74283/href</a></iframe><p>Yukarıdaki kontratı yayınladığınızda artık ERC-20 standardına sahip bir token olan GIST token’ı test ortamında olsa da yayınladınız.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/392/1*xSS9n_xxF7KUE8yFY7EHBA.png" /></figure><p>Artık bu token kontratı ile on-chain veya off-chain iletişime geçip bakiye kontrolü, ödünç verme ve transfer gibi işlemleri gerçekleştirebilirsiniz.</p><p>Görüldüğü üzere karmaşık işlemler içeren bir yapı değil. Karmaşık gelebilecek tek fonksiyon <em>transferFrom </em>ve onun işleyişi olabilir. Aşağıda onunla ilgili yeterli açıklamayı vermeye çalıştım.</p><p>Bir hesaptan(<em>_from</em>) başka bir hesaba(<em>_to</em>) istenilen miktarda(<em>_value</em>) token transferi yapmak için <em>transferFrom</em> kullanılır. Token’ı gönderen hesap token’a sahip olmak zorunda değildir. Peki birinin token’ını nasıl başka birine transfer edebiliyoruz? Bunun için <em>allowance</em> mekanizması kullanılır. Token sahibi, <em>approve</em> fonksiyonu yardımıyla istediği hesaba kendi token’ından kullanma yetkisi verebilir. <em>approve</em> fonksiyonu iki parametre alır. Birincisi yetki verilecek hesap bilgisi diğeri ise ne kadar token için yetki verileceği. Bu yetkilendirme sonrası yetkiyi alan hesap artık kendisinin olmayan token’lardan kendisine izin verilen kadarını başka hesaplara transfer edebilir.</p><p>İşte bu transfer işlemi için de <em>transferFrom</em> fonksiyonu kullanılır. Neden böyle birşey var? Aslında bu yetenek sayesinde Defi ve staking konularında oldukça hızlı gelişmeler yaşandı. Şöyle düşünebiliriz. Hesabınızdaki token’ları transfer etmeden bir Defi uygulaması hesabına veya staking hesabına <em>approve</em> ile yetki verebilir bu sayede token’larınız yerinde kalırken gelir elde edebileceğiniz modeller kullanabilirsiniz.</p><p><a href="https://eips.ethereum.org/EIPS/eip-20">ERC-20</a> token geliştirmek için her defasında sıfırdan kodlama yapmak yerine hazır şablonları da kullanabilirsiniz. Bunun için seçenekler çeşitli fakat ben en çok tercih edileni <a href="https://www.openzeppelin.com/">OpenZeppelin</a> ile devam edeceğim. Şimdi ERC-20 token geliştirmenin daha kolay yöntemi olan <a href="https://www.openzeppelin.com/"><em>OpenZeppelin</em></a> hazır kontratlarıyla devam edelim.</p><h4>OpenZeppelin İle ERC-20 Token Üretimi</h4><p>OpenZeppelin, kripto siber güvenlik ve hizmetleri konusunda uzmanlaşmış bir şirket olmakla beraber güvenli akıllı kontratlar ve ortak kullanılan kontratların hazır hallerini alıp kullanabileceğimiz kütüphaneleri ve kod örnekleri bulunmaktadır. Token kontrat geliştiriciler için de örnek ERC-20 kod örnekleri bulunmaktadır. <a href="https://www.openzeppelin.com/contracts">https://www.openzeppelin.com/contracts</a> adresinden ERC20 başlığı altındaki wizard yardımı ile manuel olarak yazdığımız token olan GIST için openzeppelin kütüphanesini kullanalım.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*NAO3iK5bazBlFBdMObw2rg.png" /></figure><p>Remix ide ile açıp derleyelim ve devamında yayınlayalım.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/964/1*ftN8aiRA8Z37jcozX29TLQ.png" /></figure><p>Openzeppelin tarafından hazırlanmış ve test edilmiş ERC20 kontratından miras alarak kendi token kontratlarımızı çok kolay türetebildik.</p><pre>// SPDX-License-Identifier: MIT<br>pragma solidity ^0.8.9;<br><br>import &quot;@openzeppelin/contracts@4.8.0/token/ERC20/ERC20.sol&quot;;<br><br>contract GuzelIstanbulToken is ERC20 {<br>    constructor() ERC20(&quot;Guzel Istanbul Token&quot;, &quot;GIST&quot;) {<br>        _mint(msg.sender, 100 * 10 ** decimals());<br>    }<br>}</pre><p>Buna ek olarak arz miktarını değiştirebileceğimiz mint ve burn özelliklerini de kolayca ekleyebiliriz. Aynı şekilde sık kullanılan fonksiyonlar için de hazırlanmış kütüphaneler bulunmaktadır.</p><p>Openzeppelin sitesindeki <a href="https://docs.openzeppelin.com/contracts">dokümanlar</a> kısmını incelemenizi ve ERC-20 veya diğer kontrat örneklerini de uygulamanızı önerebilirim.</p><p>Teşekkürler.</p><p><strong><em>Serideki diğer yazılar:</em></strong></p><p><a href="https://enginunal.medium.com/solidity-1-temel-bilgiler-40ab2ac36878"><em>Temel Bilgiler</em></a></p><p><a href="https://enginunal.medium.com/solidity-2-i%CC%87lk-smart-contract-ve-remix-ide-4606d2bfb5d4"><em>İlk </em>Akıllı Kontrat<em> ve Remix</em></a></p><p><a href="https://enginunal.medium.com/solidity-3-tipler-460d3eeaf168"><em>Tipler</em></a></p><p><a href="https://enginunal.medium.com/solidity-4-contract-ve-function-88fab4b92d4c"><em>Contract ve Function</em></a></p><p><a href="https://enginunal.medium.com/solidity-5-interface-ve-library-4fbd5f6c1793"><em>Interface ve Library</em></a></p><p><a href="https://enginunal.medium.com/solidity-6-veri-y%C3%B6netimi-dc795e5d87b5"><em>Veri Yönetimi</em></a></p><p><a href="https://enginunal.medium.com/solidity-7-logs-ve-events-4fefc3f81e5a">Logs ve Events</a></p><p><a href="https://medium.com/@enginunal/solidity-8-%C3%B6deme-i%CC%87%C5%9Flemleri-54031d9ef27f">Ödeme İşlemleri</a></p><p><a href="https://medium.com/@enginunal/solidity-9-contract-%C3%A7a%C4%9Fr%C4%B1lar%C4%B1-16454060c5c4">Akıllı Kontrat Çağrıları</a></p><p>Engin Ünal</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=c64b51112f1a" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Solidity 9 — Contract Çağrıları]]></title>
            <link>https://enginunal.medium.com/solidity-9-contract-%C3%A7a%C4%9Fr%C4%B1lar%C4%B1-16454060c5c4?source=rss-f2da39f0544c------2</link>
            <guid isPermaLink="false">https://medium.com/p/16454060c5c4</guid>
            <category><![CDATA[delegate]]></category>
            <category><![CDATA[smart-contracts]]></category>
            <category><![CDATA[solidity]]></category>
            <category><![CDATA[ethereum]]></category>
            <category><![CDATA[call]]></category>
            <dc:creator><![CDATA[Engin UNAL]]></dc:creator>
            <pubDate>Mon, 14 Nov 2022 18:12:40 GMT</pubDate>
            <atom:updated>2022-11-16T12:08:04.216Z</atom:updated>
            <content:encoded><![CDATA[<h3>Solidity 9 — Akıllı Kontrat Çağrıları</h3><p>Bir akıllı kontrattan diğerini çağırmanın yollarını ve nasıl yapıldığını inceleyeceğiz.</p><p>Bazı durumlarda geliştirdiğiniz bir akıllı kontrat içinden başka bir kontrat çağırmanız ve bu işleme göre bazı süreçleri tetiklemeniz gerekebilir. Bu tip çağrı işlemlerine yönelik Solidity tarafında çeşitli çözümler bulunmaktadır. Geliştiriciler, kullanacakları çağrı mekanizmalarını doğru seçebilmek ve güvenlik risklerini minimize edebilmek için bu konularda yeterli bilgiye sahip olmalıdır.</p><p>Bazı durumlarda gelişmiş çağrı yöntemlerine ihtiyaç duyulmadan doğrudan diğer kontrata erişimle işler çözülebilir. Yazıda Solidity ekosisteminde yaygın olarak kullanılan çağrı yöntemleri ve farklarını göreceğiz.</p><p>Yazıda çağrı yöntemlerini iki temel başlıkta gruplamaya çalıştım. İlk bölümde kodlarına erişilebilen bir kontratı çağırma yöntemlerini inceleyeceğim. İkinci bölümde ise direkt olarak fonksiyon çağrısı yapma yöntemlerini inceleyeceğim.</p><h3>Kodlarına Erişilebilen Kontrat Çağrıları</h3><p>Bir akıllı kontrat içinden başka bir akıllı kontratı çağırmanın farklı yöntemleri olduğundan söz etmiştim. Çağıracağımız kontratın kodlarına veya türediği interface kodlarına erişebiliyorsak aşağıdaki yöntemlerle o kontratı çalıştırabiliriz.</p><h4><strong>1 — Kontrat İçinden Yeni Kontrat Oluşturmak Ve Çağırmak</strong></h4><p>Madde başlığı uzun olmasına rağmen konuyu özetlemekte. Bir akıllı kontrat içinden başka bir akıllı kontrat oluşturmak ve bunun fonksiyonlarını doğrudan çağırmak mümkündür. Kontrat içinden oluşturulan yeni kontrat da blockchain’e yayınlanır ve bir adrese sahip olur.</p><p>Aşağıdaki örnek üzerinden inceleyelim.</p><pre>// SPDX-License-Identifier: MIT<br>pragma solidity &gt;=0.8.7;</pre><pre><strong>contract A</strong> {<br>  function Hi() public pure returns(string memory) {<br>    return &quot;Hi from contract A&quot;;<br>  }<br>}</pre><pre><strong>contract B</strong> {<br>  A public a;</pre><pre>  function createContractA() public returns(string memory) {<br>    a = new A();<br>    return a.Hi();<br>  }<br>}</pre><p>A ve B isminde iki akıllı kontrat kodlaması yaptık. B içinden A oluşturuluyor ve <em>Hi </em>fonksiyonu çağırılıyor.</p><blockquote><strong>Önemsiz Not:</strong> A kontratını başka bir sol dosyası içine da alabiliriz, eğer böyle yaparsak A kontratını import etmemiz gerekir. B kontratımızın dosyası <em>BContract.sol</em> olursa ve A kontrat dosyası da <em>AContract.sol</em> olursa bu durumda B içinden <em>import “./AContract.sol”</em> tanımı ile A kontratını <em>import </em>ederek kullanabiliriz. Yukarıdaki ile aynı şeyi ifade eder sadece kontrat dosyalarını ayırmış oluruz. Daha sade olması açısından yukarıdaki örnek üzerinden devam edelim.</blockquote><p><em>a = new A();</em> kodlaması ile yeni bir <em>A</em> kontrat oluşturuluyor ve devamında <em>a.Hi()</em> çağrısı ile yeni kontratın <em>Hi </em>fonksiyonu çağırılıp döndürdüğü string değer <em>createContractA </em>fonksiyonunun dönüş değeri oluyor.</p><p>Bu şekildeki kullanımda A ve B iki farklı akıllı kontrat olarak farklı adreslere yayınlanmış olur. A kontratını yayınlayan B kontratıdır. <em>createContractA </em>fonksiyonu çalıştırıldığında transaction(internal transaction) ile A kontratı oluşturulur.</p><p>Yukarıdaki örnekte A’nın yayınlandığı adresi alalım sonraki örnekte çağrı için kullanacağız.</p><h4><strong>2 — Mevcut Bir Kontratı Adresi İle Çağırmak</strong></h4><p>Bunu iki maddede inceleyebiliriz. Biri kontrat kodları bizimle paylaşılmış ise diğeri ise kontratın türediği interface kodları paylaşılmış ise.</p><h4><strong>a) Kontrat Kodları Paylaşılmış İse</strong></h4><p>Adresini bildiğimiz yani daha önce yayınlanmış ve kodlarına ulaşabileceğimiz bir kontrata erişmek ve fonksiyonlarını çalıştırabilmek mümkündür.</p><p>Önceki örnekte kontratı yayınlayıp doğrudan erişiyorduk şimdi ise yayınlanmış bir kontrata erişeceğiz. Bunun için adresinin ve kodlarının paylaşılmış olması gerekmektedir. Önceki örnekteki B akıllı kontrat kodlamasında aşağıdaki değişikliği yapalım. A olduğu gibi kalacak ve önceden yayınlandığı için adresini bilmemiz yeterli.</p><pre><strong>contract B</strong> {<br>  function callContractA(address _contractA) public pure returns(string memory) {<br>    A a = A(_contractA);<br>    return a.Hi();<br>  }<br>}</pre><p>Şimdi B kontratı deploy edelim. A’nın adresini B contract içindeki <em>createContractA </em>fonksiyonuna geçmek için kullanacağız.</p><p>A’nın adresi: 0xD7B8da9bC7dbe4E5A…..</p><p><em>callContractA </em>fonksiyonuna bu adresi parametre olarak geçip kontratı çalıştırdığımızda A kontrat içindeki <em>Hi </em>fonksiyonu çalışır ve “Hi from contract A” değerini döndürür.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/290/1*JgKJVV_dcdAB5VMn-L_PeQ.png" /></figure><p>Özetle, bu örnekte daha önce bir adrese yayınlanmış akıllı kontrata eriştik ve çalıştırdık.</p><p>Bu noktada önemli bir ayrıma yaklaştık. Dikkat edilirse şimdiye kadar kodlarına erişebildiğimiz akıllı kontratları çağırmak için kullanılabilecek yöntemleri gördük. Bu yöntemlerin eksi tarafı gereksiz kodları da barındırmamız gereksinimidir. Belki de hiç kullanmayacağımız fonksiyonlar olmasına rağmen bir kontratı saklamak verimsiz bir yöntemdir. Oysa bazen gerekli olan tüm kontrat değil daha dar kapsamlı bölümleri olabilir. Örneğin sadece bir fonksiyonu çağırmak yeterli olabilir fakat bunun için tüm kontrat kodlarını saklamak istemeyiz. Bu duruma çözümler mevcuttur. Devam ederek interface ile erişimi inceleyelim.</p><h4><strong>b) Kontrat Interface Kodları Paylaşılmış İse</strong></h4><p>Interface tanımlayarak daha dar kapsamdaki bölümleri parçalara ayırıp kullanabiliriz. Örnek üzerinden görelim. Aşağıdaki örnekte iki adet interface tanımı bulunmakta. A kontratı bunlardan ikisini de implemente etmekte ve B kontrat içerisinden sadece biri kullanılmakta.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/1ab7a922891cb13536d3a805c6b2d514/href">https://medium.com/media/1ab7a922891cb13536d3a805c6b2d514/href</a></iframe><p>B kontrat içindeki <em>callContractA</em> fonksiyonunda ihtiyacımız olan şey sadece A kontratta tanımlı <em>Hi</em> isimli fonksiyonu çağırmaktır. A kontrat içerisindeki diğer fonksiyonlar ile şu aşamada bir işimiz yoktur. A kontrat çeşitli fonksiyonlar içerebilir fakat B kontrat bunlardan ihtiyacı kadarına erişmek ve kullanmak isteyebilir. Bu tip durumlarda sadece gereken fonksiyonların tanımlandığı interface’ler kullanılarak çağrı yapılabilmektedir.</p><p><em>IHelloFunc </em>interface içinde sadece <em>Hi </em>fonksiyonu tanımlı ve B kontrat için de bu yeterli. Böylece <em>IHelloFunc </em>interface ile birlikte A kontrat adresi verildiğinde sadece <em>Hi </em>fonksiyonuna erişebilir ve kullanabiliriz.</p><pre>IHelloFunc a = IHelloFunc(_contractA);<br>val = a.Hi();</pre><p>Yukarıda görüleceği üzere <em>a.Hi</em> çağrısı ile A kontratına erişilip <em>Hi </em>fonksiyonu çağırılır ve fonksiyondan dönen “<em>Hi from contract A</em>” değeri B kontrat içinde tanımlı olan <em>val</em> değişkenine atanır.</p><p>Örneği verilen bu yöntemle de diğer akıllı kontratların interface tanımlarındaki fonksiyonlarına erişip çalıştırmak mümkün olmaktadır.</p><h3>Doğrudan Fonksiyon Çağrıları</h3><p>İlk bölümdeki örneklerde çağıracağımız kontratın kodlarına veya interface kodlarına erişimimiz olduğunda nasıl çağrı yapabileceğimizi inceledik. Bu bölümde çağırmak istediğimiz akıllı kontratın adresini ve ilgili fonksiyon ve parametrelerini bildiğimiz durumda doğrudan fonksiyon çağrısı yapmanın yöntemlerini inceleyeceğiz.</p><p>Bu yöntemlerde çağrı oluşturmak için karşı kontratın adresi, fonksiyon adı ve parametreleri bilinmelidir. Interface veya kod içeriği gibi başka verilere ihtiyaç duyulmadan karşı kontrat fonksiyonlarının çağrılması için düşük seviye çağrılar(low level call) kullanılır. Bunlar call, delegatecall ve staticcall çağrılarıdır. Birazdan bunları inceleyeceğiz. Peki çağırmak istediğimiz fonksiyonu ve parametrelerini çağrıya hazır hale nasıl getirebiliriz? Öncelikle bunu görelim.</p><p>Bu noktada <em>Function Signature </em>ve <em>Function Selector </em>kavramları önümüzde çıkar. Solidity, bir fonksiyon çağrısı yapılmak istendiğinde çağrılmak istenen fonksiyonun <em>Function Selector</em> verisini ister. Bu 4-byte’lık bir veridir ve ilgili akıllı kontrat içindeki function’ı işaret eder, oluşturmak için <em>Function Signature</em> değerinin bilinmesi gerekir.</p><h4>Function Signature</h4><p>Kontrat içindeki fonksiyonun adı ve parametre tiplerinin verildiği ve parametre isimlerinin olmadığı tanıma <em>function signature</em> denir. Aşağıdaki gibi deneme isminde bir fonksiyonumuz olsun:</p><pre>function deneme(int8 a) public pure returns(int8) { return a; }</pre><p>Function Signature değeri:</p><pre>deneme(int8)</pre><h4>Function Selector</h4><p>Function Signature <em>hash</em> değerinin ilk 4-byte’lık kısmıdır. Yani <em>bytes4(keccak256(signatureDeğeri))</em> olacaktır. Yukarıdaki <em>deneme</em> isimli fonksiyonumuz için signature değerini hesaplarsak.</p><pre>bytes4 denemeSelector;<br>denemeSelector = bytes4(keccak256(bytes(&quot;deneme(int8)&quot;)));<br>//sonuc =  0x80a2fb15</pre><p>Selector değeri farklı yöntemlerle kolayca hesaplanabilir. Aşağıdaki kod örneğinde signature ve selector için hesaplama örneği verilmiştir.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/01d14a6fa2ac791abf143e28219d0b4d/href">https://medium.com/media/01d14a6fa2ac791abf143e28219d0b4d/href</a></iframe><p>Çağrılarda sadece fonksiyon selector değeri elbette yeterli olmayacaktır. Eğer çağırdığımız fonksiyon parametre alıyorsa bunu da çağrımıza eklemeliyiz. Yukarıdaki örnekte <em>deneme(int8 a)</em> fonksiyonu <em>int8 a</em> parametresi alıyordu. Function selector ve parametre değerini birleştiren ve çağrıya hazır duruma getiren Solidity fonksiyonları bulunmaktadır.</p><p>Bunlar<em> encodeWithSignature</em> ve <em>encodeWithSelector </em>fonksiyonlarıdır. Bu iki fonksiyonun ilk parametreleri çağırılacak fonksiyon tanımı ile ilgili olmakla birlikte sonraki parametreler ise çağırılacak fonksiyona geçilecek parametre değerleridir. Özetle bunların yaptığı işlem, selector ve fonksiyona gönderilecek parametreleri byte tipinde hazırlamaktır. Yukarıdaki kod örneğindeki son iki fonksiyon buna örnektir.</p><p>Kod örneğindeki <em>getCallParamsSig </em>fonksiyonunu veya <em>getCallParamsSel </em>fonksiyonunu çağırdığımızda aşağıdaki çıktıyı verecektir.</p><pre>0x80a2fb150000000000000000000000000000000000000000000000000000000000000064</pre><p>Görüleceği üzere ilk 4-byte’lık <em>0x80a2fb15</em> değeri deneme fonksiyonunun <em>function selector</em> değeridir. Sonraki 32-byte’lık kısımda 0x64 yani decimal 100 değeri tutulmaktadır.</p><p>Fonksiyon çağrıları için bilmemiz gereken temel bilgileri tamamladık. Çağrı nasıl yapılıyor? Farkları nelerdir? Sorularına yanıtlarla ve örnek kodlamalarla call, delegatecall ve staticcall çağrılarını görelim.</p><h4>Call</h4><p>Önceki yazıda call fonksiyonunun ödeme süreçlerindeki kullanımından bahsetmiştim bu nedenle bu yazıda sadece fonksiyon çağrısı yapılması ile ilgili özelliklerini göreceğiz. Call fonksiyonu, adresi bilinen bir kontrattaki fonksiyon tanımı ve parametreleri bililen bir fonksiyonu çağırmak için kullanılan düşük seviye bir çağrı fonksiyonudur. Önerilen bir fonksiyon çağrı yöntemi değildir, kullanmadan önce durum değerlendirmesi yapılıp eğer diğer yöntemlerle çağırma imkanı yoksa kullanılması önerilmektedir.</p><p>Neden önerilmez? Type checking yapmaz, fonksiyon var mı kontrolü yapmaz vs. Bunun gibi kontrolleri yapmadan doğrudan çağırılan kontrattaki fonksiyonu ve parametreleri varmış varsayarak çağırmaya çalışır. Bu da sorunlara neden olabilir. Bunun yerine eğer imkan varsa fonksiyonu çağırılacak kontratın interface’i import edilmeli ve bu şekilde çağrı yapılması önerilmektedir.</p><p>Kullanımı ve örnek kodlaması aşağıda verilmiştir.</p><pre>(bool success, bytes memory result) = address.call(abi.encodeWithSignature(&quot;funcToCall(type)&quot;, &quot;valParam&quot;));<br>veya<br>(bool success, bytes memory result) = address.call{value: 1 ether}(abi.encodeWithSignature(&quot;funcToCall(type)&quot;, &quot;valParam&quot;)));<br>veya<br>(bool success, bytes memory result) = address.call{gas: 1000000, value: 1 ether}(abi.encodeWithSignature(&quot;funcToCall(type)&quot;, &quot;valParam&quot;)));</pre><p>Çağırılacak fonksiyon <em>payable </em>ise doğrudan o fonksiyona ödeme yapılabilir. Çağırılacak fonksiyon eğer adresi verilen kontrat içinde yoksa veya tanımı aynı değilse <em>fallback </em>fonksiyonu tetiklenir.</p><p>Dönüş değeri olarak işlemin başarılı olup olmadığını belirten <em>bool </em>tipinde bir dönüş değeri ve <em>bytes </em>tipinde dönüş verisini içeren değişken döner. Çağırılan fonksiyon bir dönüş değeri döndürüyorsa bu <em>bytes </em>tipindeki değişkende depolanır. Çağırılan fonksiyonun kaç adet dönüş değeri ve hangi tiplerde döndürdüğünü bilmek gerekir bu bilgiyle decode fonksiyonu çağırılarak dönüş verisi alınır.</p><p>Aşağıdaki kodlama örneğinde deneme isimli bir fonksiyonumuz var int8 tipinde bir parametre alıp yine int8 tipinde değer döndürüyor. Diğer kontrattaki <em>denemeCall </em>fonksiyonu ise bu fonksiyonumuzu çağırarak parametre olarak verilen değeri geçiyor ve dönüş değerini decode ederek <em>int8 </em>tipinde bir değişkene atıyor.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/f19ae27b7b38d0a193543fccf213d741/href">https://medium.com/media/f19ae27b7b38d0a193543fccf213d741/href</a></iframe><p>Bazı notlarla call konusunu bitirelim. Call ile çağırdığınız kontrat fonksiyonu, çağıran kontratın context’inde çalışmaz. Başka bir ifadeyle çağırılan fonksiyon ait olduğu kontrat içinde çalışır ve o kontratın state değişkenlerine erişebilir. Yukarıdaki örnekteki <em>Deneme </em>kontratı içindeki deneme fonksiyonu <em>CallSamples </em>kontratından çağırılıyor. Yapılan bu çağırma işleminde <em>deneme </em>fonksiyonu sadece <em>Deneme </em>kontratındaki değişkenlere müdahale edebilir çünkü o kontrat içinde çalışır. Bu bilgi <em>delegatecall </em>konusuna geçmeden önce önemli, delegatecall farklı şekilde çalışmaktadır.</p><h4>Delegatecall</h4><p>Buraya kadar akıllı kontratlarla ilgili hep aynı çalışma mantığını takip ettik. Buna göre her kontrat kendi çalışma alanında çalışır, her kontrat fonksiyonu sadece kendi kontratındaki state değişkenlerine erişebilir ve işlem yapabilir. Böylece bir kontrattan diğerini çağırdığımızda her ikisi de farklı adreslerde farklı storage alanlarında çalışıyordu ve doğal olarak fonksiyonları da birbirlerinin çalışma alanlarına müdahale edemiyordu. Özetle farklı context’lerde çalışmaktaydılar.</p><p>Bu durumun ihlal edildiği bir yapı mümkündür. Bunu delegatecall çağrıları sağlar. Bu tip çağrılarda çağırılan fonksiyon, çağıran kontrat içinde çalışıyormuş gibi davranır. Yani çağıran kontratın context’inde çalışır. Ve state değişkenlerine müdahale imkanına sahip olur.</p><p>Bunu başka bir kontrattan fonksiyon ödünç almak gibi düşünebiliriz. Ödünç alınan fonksiyon bizim kontratımız içinde sanki bizim yazdığımız fonksiyonmıuş gibi çalışarak işlemini tamamlar.</p><p>Tabi böyle çalışma imkanı verilen bir fonksiyon çağrısı da beraberinde yüksek güvenlik risklerini de birlikte getirir. Doğrudan kontratınıza müdahale edebilen ve sizin yazmadığınız bir fonksiyona güvenerek onu çağırmanız kontrolün kaybedilmesi riskini doğurabilir.</p><p>Kullanım alanlarına bakılırsa çoğunlukla library fonksiyonları kullanımında ve proxy kontratlar tarafından kullanılmaktadır.</p><p>Kod örneğiyle inceleyelim. Önceki kodlama örneğimize aşağıdaki <em>denemeDelegatecall </em>fonksiyonunu ekleyelim.</p><pre>function denemeDelegatecall(int8 _newVal) public returns(int8) {<br>  (bool success, bytes memory result) = s_addr.delegatecall(abi.encodeWithSignature(&quot;deneme(int8)&quot;, _newVal));<br>  require(success, &quot;call failed&quot;);<br>  int8 a = abi.decode(result, (int8));<br>  return a;<br>}</pre><p>Çalıştırdığımızda farklı bir çalışma şeklini göreceğiz. <em>Deneme </em>kontrat içindeki <em>s_val </em>değerine baktığımızda değişmediğini görürüz fakat <em>CallSamples </em>kontrat içindeki <em>s_val </em>değişkeni ise değişmiştir. <em>deneme </em>fonksiyonu bizim <em>CallSamples </em>kontrat içinde olmamasına rağmen <em>CallSamples </em>kontrat içinde tanımlı <em>s_val </em>state değişkenini değiştirebildi ve <em>Deneme </em>kontrat içindeki değişkene müdahale edemedi.</p><p>Şu sonucu çıkarabiliriz: <em>delegatecall </em>ile çağırılan fonksiyon tanımlandığı kontrat içinde değil çağıran kontrat içinde çalışmaktadır. Yani <em>deneme </em>fonksiyonunu <em>CallSamples </em>içinde çalışması için ödünç almış olduk.</p><p>Peki bu çağrı yöntemi neden var? Ağırlıklı olarak library kullanırken yapılan çağrılar veya proxy pattern ile geliştirilen kontratlarda kullanılmaktadır. Özellikle dikkat edilmesi gereken bir yöntemdir, kontrolün başka bir kontrat içindeki fonksiyona verilmesi doğal olarak önceden tahmin edilmesi zor sorunlara neden olabilir.</p><p>Buna örnek olarak aşağıdaki kodlamayı inceleyebilirsiniz. İki kontrat tanımlandı. A ve B akıllı kontratları. A içerisindeki deneme fonksiyonu kontratın owner’ını değiştirebiliyor. B içinde ise owner değişimi engellenmiş sadece görüntülenebiliyor. B içinden A’daki deneme fonksiyonu <em>delegatecall </em>ile çağırıldığında B’nin owner’ı değiştirilerek B kontratı hackleniyor ve sahibi değiştirilebiliyor.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/3433fcbe4d5f8e6da5688b294d69735c/href">https://medium.com/media/3433fcbe4d5f8e6da5688b294d69735c/href</a></iframe><p><em>B </em>kontratı owner’ını korumaya aldığını düşünerek kodlama yapmasına rağmen çağırdığı A kontratındaki deneme fonksiyonu owner’a erişebildiği için herhangi bir koruma kalmıyor ve <em>B </em>kontratı güvensiz hale düşüyor. Bunun gibi güvenlik açıkları oluşturabileceğinden kullanıma dikkat edilmelidir.</p><h4>Staticcall</h4><p>staticcall çağrıları call ile aynı şekilde çalışmaktadır. Aralarındaki fark staticcall ile çağrı yaptığınız fonksiyonun içerisinde state değişimi olamaz. Yani <em>view </em>ve <em>pure </em>fonksiyon çağrısı gibi çalışmalıdır. Eğer çağırılan fonksiyon içerisinde state değişikliği oluyorsa hata üretilir ve işlem revert edilir.</p><p>Kod örneğiyle yazıyı bitirelim. Önceki kod örneğine <em>deneme2 </em>fonksiyonu eklendi ve bu fonksiyon view olduğundan state üzerinde değişiklik yapmıyor. <em>A </em>kontratı çağıracağımız fonksiyonların tanımlandığı kontrat. <em>CallAFunctions </em>kontratı ise çağrıları başlattığımız akıllı kontrattır.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/f875ad0f68d090b16a32890da43f7489/href">https://medium.com/media/f875ad0f68d090b16a32890da43f7489/href</a></iframe><p>Yukarıdaki <em>denemeStaticcall </em>fonksiyonunda <em>A</em> kontrat içindeki <em>deneme2 </em>fonksiyonu çağırılıyor ve dönüş değeri alınıyor. <em>staticcall </em>ile <em>deneme </em>fonksiyonunu çağırsaydık işlem gerçekleşmeyecekti ve <em>revert </em>edilecekti.</p><p>Solidity çağrıları ile ilgili bu yazımızın sonuna geldik. Okuduğunuz için teşekkürler.</p><p><strong><em>Serideki diğer yazılar:</em></strong></p><p><a href="https://enginunal.medium.com/solidity-1-temel-bilgiler-40ab2ac36878"><em>Temel Bilgiler</em></a></p><p><a href="https://enginunal.medium.com/solidity-2-i%CC%87lk-smart-contract-ve-remix-ide-4606d2bfb5d4"><em>İlk </em>Akıllı Kontrat<em> ve Remix</em></a></p><p><a href="https://enginunal.medium.com/solidity-3-tipler-460d3eeaf168"><em>Tipler</em></a></p><p><a href="https://enginunal.medium.com/solidity-4-contract-ve-function-88fab4b92d4c"><em>Contract ve Function</em></a></p><p><a href="https://enginunal.medium.com/solidity-5-interface-ve-library-4fbd5f6c1793"><em>Interface ve Library</em></a></p><p><a href="https://enginunal.medium.com/solidity-6-veri-y%C3%B6netimi-dc795e5d87b5"><em>Veri Yönetimi</em></a></p><p><a href="https://enginunal.medium.com/solidity-7-logs-ve-events-4fefc3f81e5a">Logs ve Events</a></p><p><a href="https://medium.com/@enginunal/solidity-8-%C3%B6deme-i%CC%87%C5%9Flemleri-54031d9ef27f">Ödeme İşlemleri</a></p><p>Engin Ünal</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=16454060c5c4" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Solidity 8 — Ödeme İşlemleri]]></title>
            <link>https://enginunal.medium.com/solidity-8-%C3%B6deme-i%CC%87%C5%9Flemleri-54031d9ef27f?source=rss-f2da39f0544c------2</link>
            <guid isPermaLink="false">https://medium.com/p/54031d9ef27f</guid>
            <category><![CDATA[call]]></category>
            <category><![CDATA[smart-contracts]]></category>
            <category><![CDATA[ethereum]]></category>
            <category><![CDATA[transfers]]></category>
            <category><![CDATA[solidity]]></category>
            <dc:creator><![CDATA[Engin UNAL]]></dc:creator>
            <pubDate>Sat, 05 Nov 2022 21:24:37 GMT</pubDate>
            <atom:updated>2022-11-16T12:07:07.596Z</atom:updated>
            <content:encoded><![CDATA[<h3>Solidity 8 — Ödeme İşlemleri</h3><p>Akıllı kontrat ödeme işlemleri konulu bu yazıyla seriye devam ediyoruz. Yazıda <em>transfer</em>, <em>send </em>ve <em>call </em>fonksiyonları incelenecektir.</p><h3>Ödeme İşlemleri</h3><p>İlk yazılardan hatırlarsınız Ethereum sisteminde varlık transfer işlemleri iki tip hesaba da yapılabilmekteydi; Biri akıllı kontrat diğeri ise EOA ismiyle bilinen cüzdan hesaplarına. Akıllı kontrata ödeme işlemlerinde alıcı kontratın <em>receive </em>veya <em>fallback</em> fonksiyonları otomatik olarak çalışmakta ve gelen miktarı algılayabilmekteydi. Akıllı kontrattan karşıdaki bir adrese ödeme işleminde temel kural karşı adresin <em>payable </em>olmasıdır. Ödeme almayan adreslere ödeme fonksiyonları işlem yapmazlar. Bir akıllı kontrattan diğerine veya akıllı kontrattan cüzdan hesabına ödeme işlemi için üç fonksiyon vardır.</p><ul><li>transfer</li><li>send</li><li>call</li></ul><p>Bunları ve farklarını inceleyelim. Sonrasında örnek kodlarla farklı senaryoların çalışma mantığını göreceğiz.</p><h4>Transfer Fonksiyonu</h4><p>Akıllı kontrat içerisinden adresi verilmiş bir alıcıya ödeme yapılması için kullanılan fonksiyondır. Kullanımı:</p><pre>aliciAdresi.transfer(wei_olarak_miktar);</pre><p>Bu işlem sonrasında eğer kontrat bakiyesi yeterli ise ve alıcı ile ilgili bir sorun yoksa ve reddetmediyse <em>transfer </em>işlemi başarıyla gerçekleşir. Bir hata durumunda <em>transfer </em>fonksiyonu transaction’ı iptal eder ve işlemi <em>revert </em>eder.</p><p>Ödeme işleminde alıcı bir akıllı kontrat ise ödemeyi alan akıllı kontratın <em>receive </em>veya <em>fallback </em>fonksiyonu tetiklenir. Transfer işleminde <em>2300 gas </em>alıcı kontrata aktarılarak <em>receive</em>/<em>fallback </em>fonksiyonundaki işlemi için kullanılır. 2300 gas alıcı kontrat <em>receive</em>/<em>fallback </em>fonksiyonuna yeterli gelmiyorsa işlem iptale düşer.</p><h4>Send Fonksiyonu</h4><p>Transfer fonksiyonuna benzer, hata üretip transaction’ı iptale düşürmez. Bunun yerine <em>bool </em>dönüş tipi verir, dönüş değeri kontrolü ile tüm kontrol kontrata bırakılmıştır. Kullanımı:</p><pre>bool basarili = aliciAdresi.send(wei_olarak_miktar);</pre><p>Bu işlem sonrasında eğer kontrat bakiyesi yeterli ise ve alıcı ile ilgili bir sorun yoksa ve reddetmediyse <em>send </em>işlemi başarıyla gerçekleşir ve sonuç olarak <em>true </em>döner. Bir hata durumunda <em>send </em>fonksiyonu <em>false </em>döner transaction’ı iptal etmez veya <em>revert </em>etmez.</p><p>Ödeme işleminde alıcı bir kontrat ise ödemeyi alan kontratın <em>receive </em>veya <em>fallback </em>fonksiyonu tetiklenir. <em>Send </em>işleminde 2300 <em>gas </em>alıcı kontrata aktarılarak <em>receive</em>/<em>fallback </em>fonksiyonundaki işlem için kullanılır. <em>2300 gas </em>alıcı kontratın <em>receive</em>/<em>fallback </em>fonksiyonuna yeterli değil ise <em>send </em>fonksiyonu geriye <em>false </em>döner.</p><p>Görüldüğü üzere <em>send </em>fonksiyonunun dönüşü kontrol edilerek ödemenin başarılı olup olmadığı kontrol edilmelidir.</p><h4>Call Fonksiyonu</h4><p>Önceki başlıklarda bahsedilen <em>transfer </em>ve <em>send </em>fonksiyonlarının bazı kısıtlarına sahip değildir ve daha esnek bir kullanım sunar.</p><p><em>Transfer </em>ve <em>send </em>fonksiyonlarından ödeme yapılan kontrata(<em>receive</em>/<em>fallback </em>çalıştırılmasına) <em>2300 gas</em> gönderimi yapılıyordu. <em>Call </em>fonksiyonunda böyle bir limit yoktur. Bu nedenle komplike receive işlemlerinin çalışmasına imkan tanır. Bu süreç konu sonundaki örneklerle daha net anlaşılacaktır. Toparlamak gerekirse; Transfer ve send fonksiyonları kullanılırken ödeme yapılan kontratın <em>receive</em>/<em>fallback </em>fonksiyonu çalıştırmalarında kullanılmak üzere <em>2300 gas</em> gönderilir. Bu <em>gas </em>neredeyse <em>event </em>fırlatılmasına yetecek kadarıdır. Yani <em>state </em>değişkenine bir değer atanmak istendiğinde yetmeyecektir. Bu nedenle <em>receive </em>fonksiyonlarını geliştirirken bu limit düşünülerek gas tüketimi minimum tutulmalıdır.</p><p>Call fonksiyonunun farkı <em>2300 gas</em> limiti yoktur ve gereken <em>gas </em>gönderilir. Böylece <em>call </em>fonksiyonları kullanıldığında çağrılan kontrat <em>receive </em>fonksiyonunun <em>gas </em>tüketimine takılmaz ve işlem iptale düşmez. Kullanımı:</p><pre>(bool ok, bytes memory data) = alici.call{value: miktar}(&quot;&quot;);</pre><p>value değişkenine wei olarak gönderilen miktar verilir. (“”) ifadesinin detayına şimdilik girmeyeceğim temel görevi karşıdaki fonksiyonu çağırabilmesidir.</p><p>Bu işlem sonrasında eğer kontrat bakiyesi yeterli ise ve alıcı ile ilgili bir sorun yoksa ve reddetmediyse <em>call </em>işlemi başarıyla gerçekleşir ve sonuç <em>true </em>döner. Bir hata durumunda <em>call </em>fonksiyonu <em>false </em>döner, transaction’ı iptal etmez veya <em>revert </em>etmez.</p><p>Ödeme işleminde alıcı bir kontrat ise ödemeyi alan kontratın <em>receive/fallback </em>fonksiyonu tetiklenir.</p><p>Getirdiği esneklik ve gas kısıtı olmaması nedeniyle call kullanılması önerilmekte. Ayrıca transfer ve call fonksiyonlarındaki gas kısıtlaması sebebiyle ileride yapılacak bazı gas tüketim hesaplama değişikliklerinde ödeme işlemi yapan fonksiyonlarınız çalışamaz hale gelebilir. Bunun gibi kısıtlamalardan kurtulmak ve call kullanmak daha pratik olabilir fakat call kullanırken bazı güvenlik açıkları vermeniz ve saldırıya açık duruma düşme ihtimaliniz artar. Bu fonksiyonu kullanmadan önce Solidity güvenli kod geliştirme ile ilgili konuları incelemeniz önerilir, reentrancy atak gibi en bilinen saldırı tiplerine karşı önlem alınması gerekir.</p><h4>Örnek:</h4><p>Şimdi bu üç fonksiyon ile ilgili teorik bilgiyi pekiştireceğimiz kod örneği ile devam edelim.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/f76d32822100a5b17821c1cb1a3ea752/href">https://medium.com/media/f76d32822100a5b17821c1cb1a3ea752/href</a></iframe><p>İki kontrat örneğimiz var. <em>SendEther1 </em>ve <em>GetEther1</em>. <em>SendEther1 </em>içerisinde <em>transfer</em>, <em>send </em>ve <em>call </em>fonksiyonları ile ödeme işlemi örnekleri bulunmakta. <em>SendEther1 </em>kontrat içindeki transferTest, sendTest, callTest fonksiyonları iki parametre almakta. Bunlardan ilki ödemenin yapılacağı adres bilgisi(address payable _to) diğeri ise ne kadar ödeme yapılacağını ifade eden değer(uint256 _amount). Bildiğiniz gibi kullanılan birim <em>wei</em>.</p><p><em>GetEther1 </em>ise ödemeyi alacak olan kontratımız olacak. Bu nedenle içerisinde receive fonksiyonu tanımlı ve bu fonksiyon içerisinde gas tüketimi düşük birkaç işlem yapılmakta. Her iki kontrat içerisinde de log üreten event tanımları da bulunmakta.</p><p>Akıllı kontratları çalıştırmadan önce deploy edelim. Ödemeyi yapacak kontrat <em>SendEther1 </em>olduğundan onu deploy ederken istediğimiz kadar <em>Ether </em>yükleyeceğiz, tabi bunun için <em>constructor</em>’ını <em>payable </em>yapıyoruz. Bu örnekte <em>SendEther1</em>&#39;i 1000 <em>Wei </em>ile deploy edeceğim.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*_fyn9qJUP1oHM4hQnWk2Hg.png" /></figure><p><em>SendEther1 </em>kontratını hesabında 1000 <em>Wei </em>olacak şekilde deploy ettik. Devamında <em>GetEther1</em>&#39;i deploy edelim. İkisi de deploy edildi.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*37QEtJiKIxQvZ2Zc9xq4Qg.png" /></figure><p>SendEther1 adresi : 0xd9145CCE52D386f254917e481eB44e9943F39138</p><p>GetEther1 adresi : 0xd8b934580fcE35a11B58C6D73aDeE468a2833fa8</p><p>Şimdi ödeme fonksiyonlarımızı test edelim.</p><h4>transfer</h4><p>İlk olarak transfer fonksiyonunu deneyeceğimiz <em>SendEther1 </em>kontrat içindeki <em>transferTest </em>ile başlayalım. <em>GetEther1 </em>kontratına 100 <em>wei </em>gönderelim. Bunun için <em>GetEther1</em>&#39;in adresini alacağız ve <em>transferTest </em>fonksiyonuna parametre olarak vereceğiz.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/340/1*1NuCV7yirTVRobwBWPj_HQ.png" /></figure><p>Transact ile işlemi gerçekleştiriyoruz ve başarılı olarak tamamlanıyor. Log içeriğine baktığımızda <em>SendEther1&gt;transferTest</em> ve <em>GetEther1&gt;receive</em> fonksiyonları içindeki event’ler çalışıyor ve log üretiliyor. Log içeriğini incelediğimizde her ikisinin de bilgilerini görebiliriz.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/999/1*Ii0vapKOaDDqRND-6s81aQ.png" /></figure><p>Başarılı bir senaryoyu denedik. Peki hesabımızda şu anda yeterli olmayan bir rakamı örneğin 1000 <em>Wei </em>gönderelim. Tahmin edileceği üzere transaction başarılı olmayacak ve revert edilecektir.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/963/1*eiSZKt1sFeYxpKh0lqMV6w.png" /></figure><p>transfer fonksiyonu kullanıldığında ve işlem başarılı sonlanmadığında hata üretir ve transaction revert edilir.</p><h4>send</h4><p>Transfer fonksiyonunda yaptığımız işlemin aynısını burada da denediğimizde başarılı durumlarda send fonksiyonu true dönecektir ve transaction tamamlanacaktır. Hata üretecek bir işlem deneyelim. 1000 Wei gönderimini send fonksiyonuyla denediğimizde ne olacak? Transaction başarılı olarak sonlanacak mı? Hata üretip işlem iptale mi düşecek?</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/350/1*YqBsEPwMfcKbBI6YxbmD3g.png" /></figure><p>İşlemi başlattığımızda yeterli bakiye olmamasına rağmen transaction’ın başarılı olarak tamamlandığını görürüz. Fakat <em>GetEther1 </em>kontratına bir ödeme yapılmadığı da görülecektir.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/616/1*GfKZcuTPk_dfT81ckjZ5XQ.png" /></figure><p>Peki hem transaction başarılı hem de ödeme gitmedi neden? Bunun cevabını <em>sendTest </em>kodlarını incelediğimizde görebiliriz.</p><pre>function sendTest(address payable _to, uint256 _amount) external {<br>  bool ok = _to.send(_amount);<br>  emit SendLog(_to, _amount, ok);<br>}</pre><p><em>Send</em> fonksiyonu, çalışma tanımı gereği geriye sadece <em>bool </em>bir değer döner. Yani ödeme sonucu kontrolünü siz yapmalısınız, revert işlemine karar vermek tamamen size bırakılmıştır. Örneğimizde ödeme yapılamamasına rağmen transaction’ın başarılı olmasının nedeni budur. Burada <em>send </em>fonksiyonu bize <em>false </em>dönmüş fakat kodlamada <em>false </em>alındığında ne yapılacağı tanımlı olmadığından işlemler başarılı sonlanmıştır.</p><p><em>Logs </em>içeriği incelendiğinde alıcı kontratın log üretmediği ve gönderici kontrat loglarında <em>send </em>fonksiyonunun <em>false </em>döndüğü görülebilir.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/969/1*CgthKFup1qPK39mx8KvttA.png" /></figure><p>Send fonksiyonu kullanımlarında fonksiyon dönüş kontrolünün yapılması önemlidir. Burada yapılacak mantık hataları veya kontrol süreci sorunları büyük problemler doğurabilir.</p><h4>call</h4><p>Son örneğimizdeki <em>call </em>fonksiyonu kullanımında başarılı senaryo <em>transfer </em>veya <em>send </em>ile aynı olacaktır. Bir hata durumunda ise aynı <em>send </em>fonksiyonundaki gibi geriye <em>bool </em>değer döneceğinden dönüş değeri kontrol edilerek işlemin başarılı olup olmayacağına karar verilmelidir.</p><p>Kod örneğimizdeki gibi dönüş değeri kontrol edilmezse transaction başarılı olarak tamamlanır fakat ödeme işlemi gerçekleşmez.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/352/1*atVm4URr5l8zPAzKFWoc7Q.png" /></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/670/1*qw2O_bzTmLpzyhAGvz86kg.png" /></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/976/1*ur2BrgJwbzpb4rT3XTxeKw.png" /></figure><h4>transfer, send ve call Farkı</h4><p>Yukarıdaki örneklerde <em>GetEther1 </em>içerisindeki receive fonksiyonunda 2300 gas tüketmeyecek işlemler yapılmaktaydı. Peki <em>receive </em>içerisinde state değişkeni set edersek ne olur? Bildiğiniz gibi state değişkenlerinin gas tüketimleri yüksek olacaktır. Kodlamadaki değişikliği aşağıdaki gibi yapalım.</p><pre>contract GetEther1 {<br>  uint256 val;<br>  event LogIt(address, uint, uint);</pre><pre>  receive() external payable {<br>    val++;<br>    emit LogIt(msg.sender, msg.value, gasleft());<br>  }<br>}</pre><p>Ve tekrar deploy edelim.</p><p>Yeni adresi : 0x358AA13c52544ECCEF6B0ADD0f801012ADAD5eE3</p><p>Bu adresi <em>transferTest </em>fonksiyonuna geçip 100 <em>Wei </em>gönderimi yapmak istediğimizde aşağıdaki çıktıyı verecektir.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/988/1*RxgkFuiY4WnP3uYVOF_g7g.png" /></figure><p>Aynı şekilde send fonksiyonu da hata verir. Bunun nedeni alıcı kontratın <em>receive </em>fonksiyonuna ayrılan 2300 gas’lık rakamın yeterli olmamasıdır.</p><p>Fakat bu işlemi <em>call </em>fonksiyonuyla yaptığımızda bu limit olmadığından işlem başarılı gerçekleşir. Bu işlemin sonunda loglar oluşur. Bunu incelediğimizde 100 Wei’lik ödemenin gönderildiği ve karşı taraftan da alındığı listelenir.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/961/1*8ycbV_rD-_ropqmgbTus1A.png" /></figure><p>Bu tip esneklikler sağlaması ve geri dönüş verisi (çağrılan fonksiyonun dönüş verisini) dönebilmesi gibi imkanlar sunması nedeniyle genellikle <em>call </em>fonksiyonu kullanımı tercih ediliyor. Fakat reentrancy ataklar gibi ataklara da açık hale gelinebiliyor. Bunlara karşı önlemler alınarak kullanıma dikkat edilmelidir.</p><p>Üç ödeme fonksiyonunu ve önemli detaylarını örnekle açıklamaya çalıştım umarım faydalı olmuştur. Okuduğunuz için teşekkürler.</p><p><strong><em>Serideki diğer yazılar:</em></strong></p><p><a href="https://enginunal.medium.com/solidity-1-temel-bilgiler-40ab2ac36878"><em>Temel Bilgiler</em></a></p><p><a href="https://enginunal.medium.com/solidity-2-i%CC%87lk-smart-contract-ve-remix-ide-4606d2bfb5d4"><em>İlk </em>Akıllı Kontrat<em> ve Remix</em></a></p><p><a href="https://enginunal.medium.com/solidity-3-tipler-460d3eeaf168"><em>Tipler</em></a></p><p><a href="https://enginunal.medium.com/solidity-4-contract-ve-function-88fab4b92d4c"><em>Contract ve Function</em></a></p><p><a href="https://enginunal.medium.com/solidity-5-interface-ve-library-4fbd5f6c1793"><em>Interface ve Library</em></a></p><p><a href="https://enginunal.medium.com/solidity-6-veri-y%C3%B6netimi-dc795e5d87b5"><em>Veri Yönetimi</em></a></p><p><a href="https://enginunal.medium.com/solidity-7-logs-ve-events-4fefc3f81e5a">Logs ve Events</a></p><p>Engin Ünal</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=54031d9ef27f" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Solidity 7 — Logs ve Events]]></title>
            <link>https://enginunal.medium.com/solidity-7-logs-ve-events-4fefc3f81e5a?source=rss-f2da39f0544c------2</link>
            <guid isPermaLink="false">https://medium.com/p/4fefc3f81e5a</guid>
            <category><![CDATA[logs]]></category>
            <category><![CDATA[blockchain]]></category>
            <category><![CDATA[events]]></category>
            <category><![CDATA[solidity]]></category>
            <category><![CDATA[ethereum]]></category>
            <dc:creator><![CDATA[Engin UNAL]]></dc:creator>
            <pubDate>Tue, 01 Nov 2022 18:56:56 GMT</pubDate>
            <atom:updated>2022-11-16T12:06:28.956Z</atom:updated>
            <content:encoded><![CDATA[<h3>Solidity 7 — Logs ve Events</h3><p>Veri yönetimi konusunun devamında ve onunla da ilgili bir konu olan Logs ve Events yapılarını göreceğiz. Öncelikle Logs devamında Event başlıklarını inceleyeceğiz. Birbiriyle ilişkili konular olduğundan kavramlara değinilip devamında iki konu birleştirilip örnekle yazı sonlanacaktır.</p><h3>Logs</h3><p>EVM, logs isminde özel veri yapısına sahiptir. Blockchain üzerinde gerçekleşen işlemleri bu özel veri yapısına kaydeder. İndeksli bir veri yapısı olan logs’a kaydedilen verilere akıllı kontratlar erişemez.</p><p>Ethereum node’ları üzerinden logs verilerine erişilebilir ve arama yapılabilir. Logs verileri front-End uygulamaları veya blockchain dışındaki uygulamalar için değerlidir. Logs yayınlarını takip ederek değişiklikleri dinlerler ve yapılan işlem ile ilgili bilgilere ulaşabilirler.</p><p>Bu neden önemli? Örneğin bir web uygulaması yazdınız ve burada bir transfer işlemi gerçekleşiyor olsun. Transfer sonucu sonunda ekran güncellenmeli. Tam bu aşamada logs kayıtları önemlidir bu kayıtlar dinlenerek transaction işleminin tamamlandığı bilgisi alındığında sayfa güncellenebilir. Başka bir örnekle devam edelim. Bir web servis yazdınız ve bunun görevi bir akıllı kontratı takibe almak ve onda yapılan işlemlere göre başka bir kontratı çalıştırmak olsun. Temel olarak web servisimizin yapması gereken en kritik iş takip ettiği kontrat işlemlerini dinlemek ve buna göre aksiyon almaktır. İşte bu dinleme işlemleri logs yapısı ile gerçekleşir.</p><p>Ethereum node’undan çekilmiş bir logs kaydı örneği:</p><pre>{<br>  &quot;address&quot;: &quot;0xb59f67a8bff5d8cd03f...&quot;,<br>  &quot;blockHash&quot;: &quot;0x8243343df08b9751f5ca0c5f8c9c0460d83...&quot;,<br>  &quot;blockNumber&quot;: &quot;0x117a05&quot;,<br>  &quot;data&quot;: &quot;0x0000000000...0000000012a05f200&quot;,<br>  &quot;logIndex&quot;: &quot;0x56&quot;,<br>  &quot;removed&quot;: false,<br>  &quot;topics&quot;: [<br>    &quot;0xddf252ad1be2c89b69c2b068fc378daa...&quot;,<br>    &quot;0x00000000000000000000000000b46c...&quot;,<br>    &quot;0x00000000000000000000000054a2d42a40f512...&quot;<br>  ],<br>  &quot;transactionHash&quot;: &quot;0xab059a62e22e230fe0f56d8555340a2...&quot;,<br>  &quot;transactionIndex&quot;: &quot;0xac&quot;<br>}</pre><p>Topics değerleri indekslenmiş event verisini tutar. Data içerisinde ise indekslenmemiş event verilerini içerir. Buna events konusunda değineceğim.</p><p>Logs yapıları storage ile kıyaslandığında çok daha ekonomiktir. Bu nedenle eğer storage’da saklanması şart olmayan veya daha sonra okunmasına gerek olmayan veri saklanıyorsa bunu logs üzerinde almak gas ücretini önemli oranda düşürecektir.</p><p>Konuyu şu şekilde de özetleyebilirim: Web3 uygulamalarının veya merkezi olmayan uygulamaların(dApps) gereksinimlerinden biri de blockchain üzerinde gerçekleşen olaylardan haberdar olmaktır. Böylece uygulama kapsamına göre kullanıcıyla veya diğer uygulamalarla etkileşime girebilirler. Blockchain içerisinde gerçekleşen olayları logs yapılarından takip etmek mümkündür. Akıllı kontratlar bu yapılara veri gönderebilir ama okuyamaz. Okuma ve sorgulama işlemi blockchain node’ları üzerinden yapılmaktadır.</p><h3>Events</h3><p>Logs yapıları içinde tutulan veriler event verileridir. Logs yapısını bir veritabanı gibi düşünürsek events ise veri taşıyan satır gibi düşünülebilir. Solidity kodları ile event hazırlayıp gönderebiliriz yani logs’a event yazabiliriz fakat okuma yapamayız.</p><p>Peki bu ne işe yarıyor? Event yapılarıyla blockchain dışındaki yani dış dünyadaki uygulamaları tetikleme imkanı olmaktadır. Akıllı kontrat içinde yapılan bir işlemin herhangi bir web uygulamasına erişme şansı yok. Fakat event dinleyen bir web uygulamasının kontrat içindeki işlem ve değişiklikleri algılama imkanı olmaktadır. Bu köprü event ile kurulmaktadır. Kontrat kodlarında tanımlanan event ile istenilen veri gönderilir bu veri logs veritabanına kaydedilir. Yayınlanan event verisini herhangi bir uygulama dinleyebilir ve buna göre aksiyon alabilir.</p><p>Event içerisinde tanımlanan verinin indeksleme seçeneği bulunmaktadır. Indekslenmiş olarak işaretlenen veriler logs yapısında aranabilir ve filtrelenebilir tutulur. Bu şekilde logs üzerinde ilgili event verisini dinleyen veya arama yapan uygulama hızlıca indekslenmiş verilere erişebilir. İndekslenmemiş veriler abi encoded olarak saklanır ve decode edilmeleri gerekir. Indekslenmiş parametre tanımı limiti vardır, en fazla 3 adet yapılabilir.</p><p>Bu kadar teorik bilgi yeterli, events konusunu kod örneği ile daha net anlayabiliriz.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/3cd70e3592a5b6ff7f7eb195fd3d4fd8/href">https://medium.com/media/3cd70e3592a5b6ff7f7eb195fd3d4fd8/href</a></iframe><p>Bu örnek kodlamada <em>AmountChanged </em>isimli bir event tanımladık. Daha sonra <em>changeAmount </em>fonksiyonu içinden bu event’i kullandık. <em>Emit </em>ile event gönderimi yapılmakta, tanımlı olan parametre tiplerine göre event verisi dolurulup emit ile yayınlanmaktadır. Tanımladığımız event içine eski ve yeni state değerleri bulunmakta. Artık kontrat içindeki state değişkenimizin değişimleri dış dünya tarafından takip edilebilir ve buna göre aksiyon alınabilir.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/520/1*w3GSqdrcVm_VnUnDliEbyA.jpeg" /></figure><p>Yukarıdaki kontrat kodu yayınlanıp çalıştırıldığında log’a yazılacak çıktı aşağıdaki gibi olacaktır. Örnek olarak <em>changeAmount </em>fonksiyonunu 123 değeri ile çağıracağım(HEX değeri : 0x7B). Üretilen log verisi aşağıdaki gibi olacaktır.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/658/1*wEXZhk5Z-hMc6VzDBBRnlg.png" /></figure><p><em>Topics[0]</em>, event tanımı hash değeridir. Buradaki örnekte <em>AmountChanged </em>event tanımladık bunun hash değeri : keccak256(“<em>AmountChanged(uint, uint )</em>”).</p><p><em>Topics[1], oldAmount</em> değerini gösterir. İlk değeri 0 idi.</p><p><em>Topics[2], newAmount</em> değerini yani 0x7b(123) gösterir.</p><p>Şimdi <em>indexed </em>tanımını bir parametreden kaldırıp tekrar yayınlayalım. Event tanımı aşağıdaki gibi olsun.</p><pre>event AmountChanged(uint oldAmount, uint indexed newAmount);</pre><p><em>changeAmount </em>fonksiyonunu yine aynı değerle çağırdığımızda log kaydı aşağıdaki gibi olacaktır.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/658/1*O0St5TmRVDjpDF54J-zNYg.png" /></figure><p>Görüleceği üzere artık üç adet <em>Topics </em>yok. Indekslemediğimiz <em>oldAmount </em>topics içine eklenmedi. Fakat <em>Data </em>içerisinde bulunmakta.</p><p>Son örnekle konuyu sonlandıralım.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/ed5efc59f3505a24bcb14286da755888/href">https://medium.com/media/ed5efc59f3505a24bcb14286da755888/href</a></iframe><p>Bu örnekte <em>AccountCreated </em>isimli bir event tanımladık. Kontratın amacı bir banka hesabı oluşturmak ve hesap oluşma işlemi sonrasında bir event yayınlamaktır. Kodlamada herhangi bir güvenlik kontrolü, önlem vs. yoktur, sadece event örneği içindir. <em>ceateAccount </em>fonksiyonu içinde <em>BankAccount </em>nesnesi yaratılır ve <em>accounts </em>dizisine eklenir. Devamında <em>AccountCreated </em>event emit edilerek işlem sonlanır.</p><p>Bu kodlamada görüleceği üzere <em>BankAccount </em>tipinde bir değişken ve <em>uint256 </em>tipinde bir değişken verisinden event oluşturduk. Bu şekilde event yayınlaması da mümkündür. Bunun logs tarafındaki karşılığı aşağıdaki gibi olacaktır.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/683/1*aV3jT5YpFNanczBDr8cwkw.png" /></figure><p><em>Topics[0]</em>, <em>BankAccount </em>içindeki veri yerine hash değeri geldi. Bu nedenle kompleks tiplerle çalışmak yerine sistem tipleri kullanılması önerilir.</p><p><em>Topics[1]</em>, <em>uint256 </em>tipinde dizi uzunluğunu ifade eden veri(tek eleman ekledik bu durumda : 1).</p><p>Örnekler kullanıma göre artırılabilir. Tanım ve kullanımı gayet kolaydır.</p><p>Tanım :</p><pre>event EventAdı(tip tipAdı, …);</pre><p>Event yayınlama :</p><pre>emit EventAdı(değer1, …);</pre><p>Event dinleyen web3 uygulamaları örneklerine sonraki yazılarda değinmek üzere okuduğunuz için teşekkürler.</p><p><strong><em>Serideki diğer yazılar:</em></strong></p><p><a href="https://enginunal.medium.com/solidity-1-temel-bilgiler-40ab2ac36878"><em>Temel Bilgiler</em></a></p><p><a href="https://enginunal.medium.com/solidity-2-i%CC%87lk-smart-contract-ve-remix-ide-4606d2bfb5d4"><em>İlk </em>Akıllı Kontrat<em> ve Remix</em></a></p><p><a href="https://enginunal.medium.com/solidity-3-tipler-460d3eeaf168"><em>Tipler</em></a></p><p><a href="https://enginunal.medium.com/solidity-4-contract-ve-function-88fab4b92d4c"><em>Contract ve Function</em></a></p><p><a href="https://enginunal.medium.com/solidity-5-interface-ve-library-4fbd5f6c1793"><em>Interface ve Library</em></a></p><p><a href="https://enginunal.medium.com/solidity-6-veri-y%C3%B6netimi-dc795e5d87b5"><em>Veri Yönetimi</em></a></p><p>Engin Ünal</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=4fefc3f81e5a" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Solidity 6— Veri Yönetimi]]></title>
            <link>https://enginunal.medium.com/solidity-6-veri-y%C3%B6netimi-dc795e5d87b5?source=rss-f2da39f0544c------2</link>
            <guid isPermaLink="false">https://medium.com/p/dc795e5d87b5</guid>
            <category><![CDATA[evm]]></category>
            <category><![CDATA[solidity]]></category>
            <category><![CDATA[smart-contracts]]></category>
            <category><![CDATA[ethereum]]></category>
            <category><![CDATA[storage]]></category>
            <dc:creator><![CDATA[Engin UNAL]]></dc:creator>
            <pubDate>Fri, 21 Oct 2022 22:03:57 GMT</pubDate>
            <atom:updated>2022-11-16T12:05:13.383Z</atom:updated>
            <content:encoded><![CDATA[<p>EVM veri saklama ve okuma konusu ile devam ediyoruz. Önceki yazıların linkleri: <a href="https://enginunal.medium.com/solidity-1-temel-bilgiler-40ab2ac36878"><em>Temel Bilgiler</em></a>, <a href="https://enginunal.medium.com/solidity-2-i%CC%87lk-smart-contract-ve-remix-ide-4606d2bfb5d4"><em>İlk </em>Akıllı Kontrat<em> ve Remix</em></a>, <a href="https://enginunal.medium.com/solidity-3-tipler-460d3eeaf168">Tipler</a>, <a href="https://enginunal.medium.com/solidity-4-contract-ve-function-88fab4b92d4c">Contract ve Function</a>, <a href="https://enginunal.medium.com/solidity-5-interface-ve-library-4fbd5f6c1793">Interface ve Library</a>.</p><p>Ethereum Virtual Machine (EVM), veri saklama ve yönetimini kullanım yerine göre farklı şekillerde sağlamaktadır. Bunlara başlıklar halinde değinmeye çalışacağım. Takip edeceğim sıralama <em>Storage</em>, <em>Memory</em>, <em>Calldata</em>, <em>Code</em>. Bu başlıklardan sonra bu konuları toparlayıp özet geçerek kullanım örneği ile açıklamaya çalışıp bitireceğim. İşin detayına girmek istemeyenler için özet başlığı altında yeterli açıklama bulunmaktadır.</p><p>Konuyu bir cümle ile özetlemeye çalışırsam şöyle bir tanım yapabilirim: EVM bir sanal makine ve akıllı kontratı bir bilgisayar programı olarak varsayalım bu durumda storage’ı veritabanı, memory’i RAM, logs yapısını sistem log dosyaları gibi düşünmek daha kolay anlamayı sağlayabilir. Şimdi bunları daha yakından inceleyelim.</p><h3>Storage</h3><p>Akıllı kontrat içinde tanımlanmış tüm <em>state </em>değişkenleri kalıcı saklama alanında yani blockchain üzerinde depolanır. Bu tip saklama tipine <em>storage </em>ismi verilmektedir. Her bir kontrat için ayrı olarak oluşturulan, state değişkenlerinin saklandığı, okuma/yazma yapılabilen bir veri saklama alanıdır. Bilgisayarlardaki sabit disk gibi düşünülebilir, bilgisayar kapatılıp açıldığında sabit diskteki kaydedilmiş verilerin kaybolmaması gibi EVM tarafından kontrat her çalıştırıldığında state verilerine son kaydedildiği değerlerden kayıpsız olarak erişilebilir. Biraz daha detayına bakalım.</p><p><em>Storage </em>kalıcı bir saklama alanıdır tanımı yapmıştık, <em>storage </em>iç yapısı <em>slot</em>’lardan oluşmaktadır. Her <em>slot </em>32-byte(256-bit) uzunluktadır. Maksimum <em>slot </em>sayısı ise 2²⁵⁶ olarak tanımlıdır.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/592/1*VXVPgh6AzK9BUsReP45YfA.png" /><figcaption>Storage slot yapısı</figcaption></figure><p>State değişkenleri tanımlandığı sıraya göre slot’lara referans edilirler. Kontrat üzerinde inceleyelim. Aşağıdaki kod örneğinde <em>Storage1 </em>isimli bir kontratımız var ve içerisinde <em>uint256 </em>tipinde a,b,c,d,e,f,g,h,i,j,k state değişkenleri bulunmakta. <em>uint256 </em>veya <em>uint</em>, <em>256-bit</em> yani <em>32-byte</em>’tır.</p><pre>contract Storage1 {<br>  uint256 a;<br>  uint256 b;<br>  uint256 c;<br>  uint256 d;<br>  uint256 e;<br>  uint256 f;<br>  uint256 g;<br>  uint256 h;<br>  uint256 i;<br>  uint256 j;<br>  uint256 k;</pre><pre>  constructor() {<br>    f = 0xfafafa;<br>    k = 0xababab;<br>  }<br>}</pre><p>Örnek kontrat içindeki değişkenlerin <em>storage </em>slotları sıfırdan başlayarak yerleşecektir. <em>uint256 </em>32-byte’lık bir veri tipi olduğundan her bir state değişkeni bir slot kaplar. Bu durumda slot görüntüsü:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/896/1*j_qNg-EJ-_HFF6J8UHrhPg.png" /><figcaption>Örnekteki state değişkenlerin slot yerleşimi.</figcaption></figure><p>Konunun daha detayını merak edenler için bu yerleşimin nasıl gerçekleştirildiğini ve değer ataması yapılırken nasıl bir mekanizma çalışyor? Gas tüketimi ile ilgisi nasıl? İnceleyelim. Yukarıdaki <em>Storage1 </em>kontratı derleyip <em>opcode </em>içindeki komutlara baktığımızda:</p><pre>PUSH1 0x80 PUSH1 0x40 MSTORE CALLVALUE DUP1 ISZERO PUSH1 0xF JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST POP PUSH3 <strong>0xFAFAFA</strong> PUSH1 <strong>0x5 SSTORE</strong> PUSH3 <strong>0xABABAB</strong> PUSH1 <strong>0xA SSTORE</strong> PUSH1 0x3F DUP1 PUSH1 0x2B PUSH1 0x0 CODECOPY PUSH1 0x0 RETURN INVALID PUSH1 0x80 PUSH1 0x40 MSTORE PUSH1 0x0 DUP1 REVERT INVALID LOG2 PUSH5 0x6970667358 0x22 SLT KECCAK256 0xEE 0xCC SAR 0xE3 SDIV 0x1E SWAP8 0xC6 NUMBER PUSH5 0x1D00C2E1F7 CALLVALUE 0x4A 0xC7 CALLCODE 0xD3 0x2D TIMESTAMP 0x2B CODESIZE LOG1 0x26 DUP10 PUSH16 0x4B794CFB64736F6C6343000807003300</pre><p>0xFAFAFA<em> </em>değeri 0x5 deki slot’a yazılacak, 0xABABAB değeri 0xA daki slot’a yazılacak. Slot yerleşimine bakarsak 0x5 no’lu slotta f değişkeni var, 0xA yani 10 no’lu slotta ise k değişkeni bulunmakta. SSTORE komutu ile storage kaydı işlemi gerçekleştirilmekte. Yani bu kontrat için EVM opcodes incelendiğinde çıkarılan sonuç: f ve k değişkenlerine değer atamaları yapılmış ve storage’a kaydedilmiş.</p><p>Diğer state değişkenleri için bir <em>sstore </em>komutu bulunmamakta. Bu da demektir ki bir değer atanmadığı sürece storage üzerinde bir işlem yapılmamakta yani maliyeti olmamaktadır. Storage üzerinde yazma ve yazılmış bir state değişkeninin okuma işlemlerinin maliyeti bulunur. SSTORE ve SLOAD komutları storage’a yazan ve oradan okuma yapan komutlardır.</p><p>Her bir slot için ön tanımlı değer sıfır olarak atanmaktadır. Yani değişkene ilk değer ataması yaparken sıfır değer verilmesine gerek yoktur. Eğer sıfırdan farklı bir değer atanmamış ise state değişkeninin maliyeti yoktur yani gas tüketimi yoktur.</p><p>Başka bir örnek üzerinden gidelim.</p><pre>contract Storage2 {<br>  uint256 a;<br>  uint256[3] b;</pre><pre>  struct StructTest {<br>    uint256 x;<br>    uint256 y;<br>    uint256 z;<br>  }</pre><pre>  StructTest c;</pre><pre>  constructor() {<br>    a = 0xAAAAAA;<br>    b[0] = 0xBBBBBB;<br>    c.x = 0xC0C0C0;<br>    c.y = 0xC1C1C1;<br>    c.z = 0xC2C2C2;<br>  }<br>}</pre><p>opcode çıktısı:</p><pre>PUSH1 0x80 PUSH1 0x40 MSTORE CALLVALUE DUP1 ISZERO PUSH1 0xF JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST POP <strong>PUSH3 0xAAAAAA PUSH1 0x0 SSTORE PUSH3 0xBBBBBB PUSH1 0x1 SSTORE PUSH3 0xC0C0C0 PUSH1 0x4 SSTORE PUSH3 0xC1C1C1 PUSH1 0x5 SSTORE PUSH3 0xC2C2C2 PUSH1 0x6 SSTORE</strong> PUSH1 0x3F DUP1 PUSH1 0x40 PUSH1 0x0 CODECOPY PUSH1 0x0 RETURN INVALID PUSH1 0x80 PUSH1 0x40 MSTORE PUSH1 0x0 DUP1 REVERT INVALID LOG2 PUSH5 0x6970667358 0x22 SLT KECCAK256 BALANCE BLOCKHASH SWAP13 PUSH22 0x3DDF8CF6E74D025ADD302076288B6F9DB1E99BBBD758 0x25 0x2A 0x1F CODESIZE STATICCALL 0xD2 PUSH5 0x736F6C6343 STOP ADDMOD SMOD STOP CALLER</pre><p>Slot yerleşimi:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/731/1*M8RjcNh1aI8VCMvM3gxSLg.png" /></figure><p>Dikkat edilirse b[1] ve b[2] değişkenlerinin değerlerinde bir değişiklik yapılmadığı için storage üzerinde bir tüketimleri yok. Peki değişkenleri storage bilgimizi kullanarak daha verimli nasıl tanımlayabilirdik? Örnek üzerinden görelim.</p><pre>contract Storage3 {<br>  uint64 a;<br>  uint64 b;<br>  uint64 c;<br>  uint64 d;</pre><pre>  constructor() {<br>    a = 0xA0A0A;<br>    b = 0xB0B0B0;<br>    c = 0xC0C0C0;<br>    d = 0xD0D0D0;<br>  }<br>}</pre><p>Yukarıdaki şekildeki tanımlamada sadece bir slot kullanılır. Slot uzunluğunun 32-byte olduğunu düşündüğümüzde eğer değişkenlerimiz slot içerisine sığabilecek boyutlara indirilebiliyorsa fazladan slot rezerve etmeden kullanmak mümkündür. Derleme sonrasında opcode çıktısındaki komut listesi incelenirse bir adet SSTORE görülecektir.</p><p>Kendi örneklerinizi de yazıp slot kullanımını inceleyebilirsiniz. Burada küçük bir detay bulunur, statik bir array tanımlandığında önceki slot içinde boş yer olmasına rağmen ve array buna sığabilecek görünmesine rağmen yeni slot açılıyor. Veya array sonrasında yapılan bir tip değişkeni tanımı array’in bittiği slotta kalan yere sığabiliyor görünmesine rağmen yeni slot açılmakta. Örneğin</p><pre>uint32 a;<br>uint32[2] b;</pre><p>veya</p><pre>uint32[2] b;<br>uint32 a;</pre><p>Gibi tanımlamada iki slot kullanıldığı görülecektir. Oysa toplam 32-byte etmeyen bir alan ayırmak istiyoruz. a : 4-byte, b : 8-byte toplamda 12-byte için iki slot rezerve ediliyor. Burada garip bir durum var neden bu şekilde? Sorunun cevabı slot rezervasyonundaki algoritmanın bazı kurallar uygulamasıdır.</p><p>Dikkat edilmesi gereken bazı slot yerleşim kuralları :</p><ul><li>Struct ve array yapıları her zaman yeni bir slot ayırarak başlar.</li><li>Struct veya array sonrasındaki tipler her zaman yeni bir slot’tan başlar.</li></ul><p>Statik olarak tanımlanan değişkenlerin storage slotlarındaki yerleşimlerini gördük, şimdi dinamik tanımlanan değişkenleri inceleyelim. Statik değişkenlerde slot yerleşimlerinin hesaplanması çok daha kolay algoritmalarla sağlanabilmekte fakat iş dinamik değişkenlere geldiğinde karmaşıklaşabiliyor. Bunun nedeni de rezerve edilecek alanın önceden bilinmemesi ve dinamik olarak değişebilmesidir. Konunun çok fazla uzaması nedeniyle dinamik array ve mapping yapılarındaki slot rezervasyon işlemine iki cümle ile değinip geçeceğim.</p><p>Dinamik array yapılarında array’in başladığı slot numarasında array uzunluğu tutulur. Array elemanlarına ise <em>hash(slot numarası) + (indeks * tip uzunluğu)</em> formulüyle hesaplanarak erişilebilir. Mapping için de buna benzer bir algoritma ile slot yerleşimleri hesaplanmaktadır. Farkı ise mapping için bir indeks olmadığı için mapping ilk slot adresinde length tutulmaz. İlk slot numarası ve key hash bilgisi hesaplanarak okuma /yazma yapılacak slot numarası hesaplanır.</p><p>Storage mekanizmanın nasıl çalıştığını genel olarak anlamak daha verimli akıllı kontrat yazmayı kolaylaştıracağından önemlidir. Bir örnekle konuyu bitirelim. Diyelim ki kontrat içinde üç adet state değişkenimiz olsun.</p><pre>uint128 a;<br>uint256 b;<br>uint128 c;</pre><p>Herhangi bir kodlama düzenlemesi yapmadan ve sadece değişkenlerin kontrat içindeki yerleşimiyle oynayarak bile daha az gas tüketen kontratlar geliştirilebilir. Yukarıdaki örnekte 3 slot rezerve edilecektir.</p><pre>uint128 a;<br>uint128 c;<br>uint256 b;</pre><p>Oysa bunu yukarıdaki gibi değiştirdiğimizde 2 slot ile aynı işi gerçekleştirebiliriz. Benzer şekilde struct kullanımları ile değişkenlerin paketlenerek daha az slot rezerve etmesi ile daha az gas tüketen kontrat kodları üretilebilir.</p><p>Storage pahalı bir yapıdır, blockchain gibi dağıtık sistemler üzerinde veri saklamak, merkezi yapılarda veri saklamaya göre oldukça maliyetlidir. Bu nedenle gereksiz veya fazla verilerin storage’da saklanması önerilmez.</p><h3>Memory</h3><p>Memory, verinin kontrat çalışma zamanında saklandığı ve sonrasında silindiği yani veriyi geçici süreyle tutan yapılardır. RAM gibi de düşünülebilir. Akıllı kontrat, çalıştığı sürece memory içinde tutulan veriye erişerek her türlü işlemi gerçekleştirebilir, işlem sonlandığında rezerve edilen memory serbest bırakılır ve içindeki veriler silinir. Kontrat tekrar çalıştığında memory sıfırdan tekrar rezerve edilecek ve çalışma anı için oluşturulacaktır. Bu nedenle memory kullanımında bu alanın geçici olarak oluşturulduğunu ve içindeki verinin saklanmayacağını bilerek kodlama yapılmalıdır.</p><p>Aşağıdaki örnek ile devam edelim. s_ ile başlayan değişkenler storage’da saklanırken m_ ile başlayanlar memory’de saklanmaktadır.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/07b271afc62128c64cb80d234ba016e9/href">https://medium.com/media/07b271afc62128c64cb80d234ba016e9/href</a></iframe><p>Solidity compiler, özel tipler için storage, memory veya calldata tanımının yapılmasını ister. Bunlar struct, array, string ve mapping tanımlarıdır. Mapping memory ile kullanılamaz. Bu tiplerin yüksek boyutlara ulaşabilecek yer ihtiyaçları olabilir veya dinamik tanımlandıklarında çalışma sırasında değişken boyutlarda olabilirler. Solidity bunların veri yönetimlerini hangi veri ortamında(storage, memory) yapacağını bilmek ister. Fakat bundan önemlisi doğrudan storage değişkenleri üzerinde işlem yapmak maliyetlidir. Memory değişkenleri ile çalışmak gas tüketimini düşürecektir.</p><p>Şöyle de düşünebiliriz storage’da tanımlı 1000 elemanlı bir array olsun. Bunun elemanlarını gezmek yüksek maliyetler çıkarabilir. Oysa bunu bir memory’e atıp memory’deki array üzerinde çalışmak çok daha verimli olacaktır.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/9d09f78485c870ce272b98c41284b368/href">https://medium.com/media/9d09f78485c870ce272b98c41284b368/href</a></iframe><p>Memory konusunu anlayabilmek için şimdi yukarıdaki örnek üzerinden gidelim. <em>User </em>isimli struct tanımladık ve <em>user1 </em>isimli state değişkeni storage’da saklanır, <em>AssignTest1 </em>çağırıldığında storage üzerinde güncelleme işlemi yapar ve state değişkeni değişir.</p><p><em>AssignTest2 </em>içinde <em>user2 </em>değişkeni tanımladık ve memory anahtar kelimesini kullandık. Bu şekildeki kullanımda <em>user2 </em>memory’de tanımlanmış olur sadece bu fonksiyon içinde geçerlidir ve fonksiyon çalışmasını bitirdiğinde erişilemez.</p><p><em>AssignTest3 </em>içinde <em>user3 </em>değişkenini tanımlarken storage anahtar kelimesi kullanıldı ve <em>user1</em>&#39;den atama yapıldı, böylece <em>user2 </em>üzerinde yapılan her değişiklik aynı zamanda <em>user1 </em>üzerinde yapılmış olacaktır. Yani <em>user3 </em>değişkeni <em>user1 </em>state değişkeninin storage adres referansını alacaktır.</p><p><em>AssignTest4 </em>içinde <em>user4 </em>değişkenini tanımlarken memory anahtar kelimesi kullanıldı ve <em>user1</em>&#39;den atama yapıldı, bu şekilde <em>user4 </em>üzerinde yapılan değişiklikler <em>user1 </em>state değişkenini etkilemez. Yani <em>user4 </em>değişkeni <em>user1 </em>state değişkeninin içeriğinin kopyalanmış halini alacaktır. Memory ile tanımlı değişkenler için yeni memory alanı ayrılarak değişken içeriği buraya kopyalanır. Yapılan güncelleme işlemleri sadece ilgili memory alanını etkiler.</p><p>Memory tanımları state değişkenleri gibi kontrat seviyesinde yapılamaz, fonksiyon parametreleri ve fonksiyon içerisinde kullanılabilir.</p><h3>Calldata</h3><p>Calldata, memory yapılarına benzer ancak değiştirilemezdir. Yani salt okunurdur. Transaction aracılığı ile yapılan fonksiyon çağrılarındaki verileri içerir. Adı üstünde çağrı verisini ifade eder yani <em>msg.data</em> içeriğidir.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/2b58853738c4ba2defabc1cb8d2d61b9/href">https://medium.com/media/2b58853738c4ba2defabc1cb8d2d61b9/href</a></iframe><p>Yukarıdaki örnek üzerinden gidelim. <em>CallDataSample1 </em>fonksiyonunun <em>strCallData </em>parametresi <em>msg.data</em>’dan gelen değiştirilemez <em>calldata </em>verisidir. Bunu sadece okuyabiliriz fakat yazamayız. Aynı fonksiyon içinde yeni bir <em>string </em>değişken olan <em>newData </em>değişkeni oluşturuluyor ve içerisine diğer fonksiyonların da ürettiği <em>string </em>değerler eklenip dönüş yapılıyor.</p><p>Örnekteki <em>CallDataSample2 </em>fonksiyonu aynı şekilde <em>calldata </em>özelliğinde parametre alarak buna ekler yapar ve yeni bir <em>string </em>üreterek döner.</p><p><em>CallDataSample3 </em>ise <em>memory string</em> alır. Çağırıldığı yer incelendiğinde parametre olarak <em>calldata </em>geçildiği görülecektir. Bu bir yanlışlık gibi görünse de değildir. <em>strMemData </em>parametresi <em>memory </em>olarak tanımlandığı için <em>calldata </em>olarak tanımlanan <em>strCallData </em>parametresini alır ve yeni bir <em>string </em>üreterek içeriğini kopyalar. Böylece elimizde değiştirilebilir <em>strMemData </em>değişkenimiz olur. Solidity’in alt tarafta yaptığı işlem aşağıdaki mantıkta olacaktır.</p><pre>string memory strMemData = strCallData;</pre><p>Tekrar belirtmek gerekirse calldata sadece fonksiyon parametreleri için kullanılabilir, değiştirilemez sadece okuma yapılabilir.</p><h3>Code</h3><p>Akıllı kontrat içinde tanımlanan state değişkenlerinin storage’a kaydedildiğini, fonksiyon içindeki veya parametrelerinde memory tanımı yapılan değişkenlerin memory’e kaydedildiğini gördük. Calldata ise <em>msg.data</em>’dan gelen değişmez çağrı verisiydi. Bunların dışında code alanında saklanan veri de mevcuttur. Code isminden kasıt aslında akıllı kontrat kodlarıdır. Örnekle açıklarsak.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/782a6f2575d8e17556c6502a0de5e6cf/href">https://medium.com/media/782a6f2575d8e17556c6502a0de5e6cf/href</a></iframe><p>Akıllı kontrat içerisindeki <em>constant </em>ve <em>immutable </em>olarak tanımlanan veriler code içerisinde yani kontrat bytecode’unda saklanır. Bunlar aslında değişken gibi davranmaz. Solidity compiler, <em>constant </em>kullanılan yerlerde ilgili <em>constant </em>adı karşılığına onun değerini koyar.</p><h3>Özet</h3><p>Storage, blockchain’e kaydedilen verilerdir, hard disk gibi düşünülebilir. Memory, akıllı kontrat çalışırken fonksiyonlar tarafından kullanılan verilerdir, RAM gibi düşünülebilir. Calldata, transaction ile bir fonksiyon çağırılırken transaction.data veya <em>msg.data </em>içerisinde gelen fonksiyon parametre verileridir, değiştirilemez.</p><p>Storage ve memory kullanımı ile ilgili aşağıdaki kodlama örneği incelenebilir. Kod içerisinde açıklamalar eklenmiştir.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/3499da173488bd5039ac0bfebbcc09d1/href">https://medium.com/media/3499da173488bd5039ac0bfebbcc09d1/href</a></iframe><p>Calldata kullanım örneği:</p><pre>function CalldataTest(string calldata cdString) external pure {<br>  //calldata degeri a degiskenine kopyalanır.<br>  string memory a = cdString;</pre><pre>  //a degiskeni memory tanımlandigindan degistirilebilir,<br>  //bu degisiklik cdString degiskenini etkilemez.<br>  a = &quot;123abc&quot;; </pre><pre>  //calldata tanımı yapılırsa icerik degistirilemez.<br>  string calldata b = cdString; <br>  //derleme hatası verir --&gt; b = &quot;aaa&quot;;<br>}</pre><p>Yazının sonuna geldik. Umarım yeteri kadar açıklayıcı olmuştur. Okuduğunuz için teşekkürler.</p><p><strong><em>Serideki diğer yazılar:</em></strong></p><p><a href="https://enginunal.medium.com/solidity-1-temel-bilgiler-40ab2ac36878"><em>Temel Bilgiler</em></a></p><p><a href="https://enginunal.medium.com/solidity-2-i%CC%87lk-smart-contract-ve-remix-ide-4606d2bfb5d4"><em>İlk </em>Akıllı Kontrat<em> ve Remix</em></a></p><p><a href="https://enginunal.medium.com/solidity-3-tipler-460d3eeaf168">Tipler</a></p><p><a href="https://enginunal.medium.com/solidity-4-contract-ve-function-88fab4b92d4c">Contract ve Function</a></p><p><a href="https://enginunal.medium.com/solidity-5-interface-ve-library-4fbd5f6c1793">Interface ve Library</a></p><p>Engin Ünal</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=dc795e5d87b5" width="1" height="1" alt="">]]></content:encoded>
        </item>
    </channel>
</rss>