<?xml version="1.0" encoding="utf-8"?>
<feed xmlns:blogChannel="http://backend.userland.com/blogChannelModule" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:pingback="http://madskills.com/public/xml/rss/module/pingback/" xmlns:trackback="http://madskills.com/public/xml/rss/module/trackback/" xmlns:wfw="http://wellformedweb.org/CommentAPI/" xmlns:slash="http://purl.org/rss/1.0/modules/slash/" xmlns:geo="http://www.w3.org/2003/01/geo/wgs84_pos#" xmlns:betag="http://dotnetblogengine.net/schemas/tags" xmlns="http://www.w3.org/2005/Atom">
  <id>https://www.buraksenyurt.com/</id>
  <title>Burak Selim Şenyurt</title>
  <updated>2026-04-17T22:57:13+00:00</updated>
  <link href="https://www.buraksenyurt.com/" />
  <link rel="self" href="https://www.buraksenyurt.com/syndication.axd?format=atom" />
  <subtitle>Matematik Mühendisi Bir Bilgisayar Programcısının Notları</subtitle>
  <author>
    <name>Burak Selim Senyurt</name>
  </author>
  <generator uri="http://dotnetblogengine.net/" version="1.0.0.0">BlogEngine.Net Syndication Generator</generator>
  <blogChannel:blogRoll>https://www.buraksenyurt.com/opml.axd</blogChannel:blogRoll>
  <dc:creator>Burak Selim Senyurt</dc:creator>
  <dc:description>Matematik Mühendisi Bir Bilgisayar Programcısının Notları</dc:description>
  <dc:language>tr-TR</dc:language>
  <dc:title>Burak Selim Şenyurt</dc:title>
  <geo:lat>0.000000</geo:lat>
  <geo:long>0.000000</geo:long>
  <entry>
    <id>https://www.buraksenyurt.com/post/birlikte-ocaml-ogrenelim</id>
    <title>Birlikte OCaml Öğrenelim</title>
    <updated>2026-04-17T21:28:00+00:00</updated>
    <link rel="self" href="https://www.buraksenyurt.com/post.aspx?id=d80f930e-7339-4128-9eec-5aad5096f469" />
    <link href="https://www.buraksenyurt.com/post/birlikte-ocaml-ogrenelim" />
    <author>
      <name>bsenyurt</name>
    </author>
    <summary type="html">&lt;p&gt;İlk programlama dilinden bu zamanlara değişen &amp;ccedil;ok şey var. &amp;Uuml;niversite yıllarım kişisel bilgisayarların ve internetin yaygınlaştığı World Wide Web devrimine denk geliyor. O vakitler b&amp;ouml;l&amp;uuml;mde g&amp;ouml;sterilen bilgisayar programlama derslerini d&amp;uuml;ş&amp;uuml;n&amp;uuml;yorum da; &lt;a href="https://en.wikipedia.org/wiki/GW-BASIC" target="_blank"&gt;GW-Basic&lt;/a&gt;, &lt;a href="https://en.wikipedia.org/wiki/COBOL" target="_blank"&gt;Cobol&lt;/a&gt;, C ve C++ ... &amp;Ccedil;oğunda belli seviyeye kadar geldiğimizi anımsıyorum. Aynı yıllarda iş d&amp;uuml;nyasının hızlandırıcı etkisine de şahit olmuştuk. Sadece klavye ve 8 renkten oluşan siyah terminal ekranları &amp;ccedil;ok uzun zamandır mouse imle&amp;ccedil;leri ile renklenmişti. Dahası artık iş s&amp;uuml;re&amp;ccedil;lerinin internet ortamından y&amp;uuml;r&amp;uuml;t&amp;uuml;lebildiği bir d&amp;ouml;nemdi. Bu dalgayla birlikte ben ve bir&amp;ccedil;ok arkadaşım Delphi, Java, Visual Basic gibi dillere y&amp;ouml;neldi. Ben ağırlıklı olarak Delphi tarafına yakındım ama zamanla bu yakınlık yerini C# programlama diline bıraktı.&lt;/p&gt;
&lt;p&gt;Anılar bir kenara dursun, yıllarca pop&amp;uuml;ler dillerle uygulama geliştirmenin ardından gelen bir farkındalık, ara&amp;ccedil;lara değil, o ara&amp;ccedil;ları var eden felsefeye odaklanmam gerektiğini &amp;ouml;ğretti. Bazı programlama dillerini iş ama&amp;ccedil;lı kullanmak i&amp;ccedil;in değil, atası olduğu diğer dillere kattığı &amp;ouml;zellikleri d&amp;uuml;ş&amp;uuml;nerek &amp;ccedil;alışmak gerekiyor. &amp;Ouml;rneğin uzun s&amp;uuml;re uğraştığım ve sahada deneyimleme şansını &amp;ccedil;ok bulamadığım i&amp;ccedil;in şimdilerde paslandığım Rust programlama dili, atası sayılabilecek OCaml dilinden bir&amp;ccedil;ok &amp;ouml;zellik almıştı&lt;em&gt;(G&amp;uuml;&amp;ccedil;l&amp;uuml; tip sistemler, cebirsel veri tipleri - algebraic data types, hata payını azaltan &amp;ouml;r&amp;uuml;nt&amp;uuml; eşleştirme - pattern matching, options vb)&lt;/em&gt; Benzer şekilde Scala, F# ve bug&amp;uuml;nlerde dikkat &amp;ccedil;eken Rocq, Gleam gibi dillere de OCaml ilham vermişti. OCaml ile herhangi bir proje geliştirmeyeceğim veya onu iş yerinde kullanmayacağım ama fonksiyonel dil paradigmasını anlamak, bir derleyicinin ya da yorumlayıcının nasıl yazıldığını temelden &amp;ouml;ğrenmek ve bir&amp;ccedil;ok dilin OCaml &amp;uuml;zerinden aldığı kabiliyetleri kavramak i&amp;ccedil;in ge&amp;ccedil; de olsa &amp;ccedil;alışmalıyım&lt;em&gt;(Sen bu yazıyı okuyan bir &amp;uuml;niversite talebesi isen, bence sen de OCaml veya benzeri bazı dilleri iyice &amp;ouml;ğrenmeye &amp;ccedil;alışmalısın.)&lt;/em&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;F# programlama dilinin tam bir t&amp;uuml;revi olduğu, Rust'ın tip g&amp;uuml;venliği felsefesini benimsediği ve hatta ilk derleyicisinin OCaml ile yazıldığı d&amp;uuml;ş&amp;uuml;n&amp;uuml;ld&amp;uuml;ğ&amp;uuml;nde; OCaml &amp;ouml;ğrenmek modern programlama dillerinin genetik kodunu &amp;ccedil;&amp;ouml;zmek demektir.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;S&amp;ouml;z&amp;uuml;n &amp;ouml;z&amp;uuml; bu tamamen kendi zihinsel yatırımım ve itiraf etmeliyim ki bu yatırımı ellili yaşlarımda değil de yirmili yaşlarımda değerlendirmem gerekirdi.&lt;/p&gt;
&lt;h2&gt;Merak Ettiklerim&lt;/h2&gt;
&lt;p&gt;İşe bu dil ile ilgili merak ettiğim sorulara bulduğum yanıtlarla başlamak isterim.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;OCaml ismi nereden geliyor?: OCaml, "Objective Caml" ifadesinin kısaltması. Caml&lt;em&gt;(Categorical Abstract Machine Language)&lt;/em&gt; diline nesne y&amp;ouml;nelimli programlama &amp;ouml;zelliklerinin eklenmiş bir versiyonu olarak d&amp;uuml;ş&amp;uuml;nebiliriz ve evet, logosunda elbette ki deve var :D&lt;/li&gt;
&lt;li&gt;Geliştiricileri kim?: INRIA&lt;em&gt;(Institut National de Recherche en Informatique et en Automatique - Ulusal Bilgisayar Bilimi ve Otomasyon Araştırma Enstit&amp;uuml;s&amp;uuml;)&lt;/em&gt;'dan Xavier Leroy, J&amp;eacute;r&amp;ocirc;me Vouillon, Damien Doligez ve Didier R&amp;eacute;my tarafından geliştirilmiş. Fransızlar tarafından geliştirildiği i&amp;ccedil;in s&amp;ouml;z dizimine yer yer Fransız kaldığım da olmadı değil :D&lt;/li&gt;
&lt;li&gt;İlk versiyonu ne zaman &amp;ccedil;ıktı?: Kaynaklara g&amp;ouml;re ilk s&amp;uuml;r&amp;uuml;m 1996 yılında piyasaya s&amp;uuml;r&amp;uuml;lm&amp;uuml;ş. Dok&amp;uuml;manı yazdığım an itibariyle de son s&amp;uuml;r&amp;uuml;m&amp;uuml; 2025-10-09 tarihinde yayınlanmış olan 5.4.0 versiyonu. Son s&amp;uuml;r&amp;uuml;mde immutable diziler, labelled tuple t&amp;uuml;rleri, atomik record alanları gibi yeni &amp;ouml;zellikler eklenmiş.&lt;/li&gt;
&lt;li&gt;Dilin kullanım amacı:Genel ama&amp;ccedil;lı bir programlama dili olduğunu d&amp;uuml;ş&amp;uuml;nebiliriz zira nesne y&amp;ouml;nelimli olma hali ve fonksiyonel dil &amp;ouml;zellikleri ile birlikte pragmatik yaklaşımları i&amp;ccedil;eriyor. Genelleştirilmiş &amp;ccedil;&amp;ouml;p toplayıcısı&lt;em&gt;(Garbage Collector)&lt;/em&gt;, birinci sınıf fonksiyonlar&lt;em&gt;(First Citizen Functions)&lt;/em&gt;, statik t&amp;uuml;r sistemi&lt;em&gt;(Static Type System)&lt;/em&gt;, immutable programlama taktikleri, tip &amp;ccedil;ıkarımı&lt;em&gt;(Type Inference)&lt;/em&gt;, cebirsel veri t&amp;uuml;rleri&lt;em&gt;(Algebraic Data Types)&lt;/em&gt;, &amp;ouml;r&amp;uuml;nt&amp;uuml; eşleştirme&lt;em&gt;(pattern matching)&lt;/em&gt; ve daha bir&amp;ccedil;ok &amp;ouml;zelliği destekleyen bir dil.&lt;/li&gt;
&lt;li&gt;Hangi dillerden esinlenmiş: Sahip olduğu &amp;ouml;zellikler de d&amp;uuml;ş&amp;uuml;n&amp;uuml;ld&amp;uuml;ğ&amp;uuml;nde Caml başta olmak &amp;uuml;zere, C, Pascal, Modula-3 ve Standard ML dillerinden esinlenildiği belirtiliyor.&lt;/li&gt;
&lt;li&gt;Hangi dillere esin kaynağı olmuş: Bir tanesi&amp;nbsp;&lt;a href="https://rust-lang.org/" target="_blank"&gt;Rust&lt;/a&gt; ki ben de uğraştığım i&amp;ccedil;in biliyorum. Wikipedia kayıtlarına g&amp;ouml;re OCaml'dan etkilenen diğer diller arasında &lt;a href="https://rocq-prover.org/" target="_blank"&gt;Rocq&lt;/a&gt;, &lt;a href="https://fsharp.org/" target="_blank"&gt;F#&lt;/a&gt;, &lt;a href="https://www.scala-lang.org/" target="_blank"&gt;Scala&lt;/a&gt;,&amp;nbsp;&lt;a href="https://gleam.run/" target="_blank"&gt;Gleam&lt;/a&gt;&amp;nbsp;gibi pop&amp;uuml;ler diller de var.&lt;/li&gt;
&lt;li&gt;OCaml ile kendi programlama dilini yazabilir miyim?: Teorik olarak evet, OCaml g&amp;uuml;&amp;ccedil;l&amp;uuml; bir dil ve kendi dilinizi yazmak i&amp;ccedil;in gerekli ara&amp;ccedil;ları sağlayabilir. Zaten Rust'ın ilk s&amp;uuml;r&amp;uuml;m&amp;uuml; bildiğim kadarıyla OCaml ile yazılıyor.&lt;br /&gt;- Hangi kaynaklardan &amp;ouml;ğrenebilirim?:&amp;nbsp;&lt;a href="https://dev.realworldocaml.org/index.html" target="_blank"&gt;Real World OCaml, Functional Programming for the Masses, Anıl Madhavapeddy, Yaron Minsky, Cambridge University Press&lt;/a&gt;&amp;nbsp;heybetli bir kitap. Ger&amp;ccedil;ekten yirmili yaşlarımda olmam gerekiyor :D Bunun yanında Cornell &amp;Uuml;niversitesinden Michael Ryan Clarkson'ın 2021 yılında yayınladığı&amp;nbsp;&lt;a href="https://youtube.com/playlist?list=PLre5AT9JnKShBOPeuiD9b-I4XROIJhkIU&amp;amp;si=fqYdWGlXmQwy8c_b" target="_blank"&gt;OCaml Programming: Correct + Efficient + Beautiful&lt;/a&gt; kursunu da tavsiye ederim. Ben 25 yıl kadar ge&amp;ccedil; başlıyorum bazı şeylere doğrudur :D Ayrıca &lt;a href="https://cs3110.github.io/textbook/cover.html" target="_blank"&gt;bu yayına ait g&amp;uuml;zel bir kitap&lt;/a&gt; da var.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Kurulumlar&lt;/h2&gt;
&lt;p&gt;İlk olarak resmi &lt;a href="https://ocaml.org/docs/installing-ocaml" target="_blank"&gt;OCaml web sitesinden&lt;/a&gt; gerekli kurulumları yapmak lazım. Ayrıca VS Code edit&amp;ouml;r&amp;uuml;ne OCaml eklentisini y&amp;uuml;klemekte yarar var.&lt;/p&gt;
&lt;h3&gt;Windows 11 Tarafında Sorun&lt;/h3&gt;
&lt;p&gt;Windows 11 işletim sisteminde gerekli kurulumları yapmış olmama rağmen komut satırından ocaml ile kod &amp;ccedil;alıştırmakta sorun yaşadım. Bunun kalıcı &amp;ccedil;&amp;ouml;z&amp;uuml;m&amp;uuml; i&amp;ccedil;inse aşağıdaki komutu işlettim.&lt;/p&gt;
&lt;pre class="brush:bash;auto-links:false;toolbar:false" contenteditable="false"&gt;Add-Content $PROFILE "`n# Initialize opam environment`n(&amp;amp; opam env) -split '\r?\n' | ForEach-Object { Invoke-Expression `$_ }"

#Sonrasında PowerShell'i yeniden başlattım
#Kısa bir versiyon kontrol&amp;uuml; yaptım
ocaml -version

#ve &amp;ouml;rneğin hello-world.ml dosyasını doğrudan aşağıdaki komutla &amp;ccedil;alıştırabildim
ocaml hello-world.ml

#OCaml'ı interaktif modda kullanmak i&amp;ccedil;in ise aşağıdaki komutu kullanmak yeterli
ocaml&lt;/pre&gt;
&lt;p&gt;İşte ilk programın &amp;ccedil;ıktısı,&lt;/p&gt;
&lt;p&gt;&lt;img src="https://www.buraksenyurt.com/image.axd?picture=/2026/Nisan/hello_world.png" alt="" /&gt;&lt;/p&gt;
&lt;h3&gt;WSL Tarafında Ubuntu &amp;Uuml;zerinden &amp;Ccedil;alışmak&lt;/h3&gt;
&lt;p&gt;Nedense bu tip dilleri &amp;ccedil;alışmak i&amp;ccedil;in en uygun platform Linux ortamı sanırım&lt;em&gt;(Emektar Ubuntu sistemim Westworld tavan arasında ama Windows'ta WSL ile bir ubuntu &amp;uuml;zerinde &amp;ccedil;alışmak m&amp;uuml;mk&amp;uuml;n)&lt;/em&gt; Ben t&amp;uuml;m &amp;ccedil;alışmalardan sonra konu tekrarları i&amp;ccedil;in WSL &amp;uuml;zerinden ilerlemeye karar verdim. Hali hazırda WSL &amp;uuml;zerinde bir Ubuntu s&amp;uuml;r&amp;uuml;m&amp;uuml;m y&amp;uuml;kl&amp;uuml;. Ancak değilse de,&lt;/p&gt;
&lt;pre class="brush:bash;auto-links:false;toolbar:false" contenteditable="false"&gt;wsl --install -d Ubuntu
# ile ubuntu'yu y&amp;uuml;kleyebiliriz.&lt;/pre&gt;
&lt;p&gt;OCaml kurulumları i&amp;ccedil;inse aşağıdaki adımları takip etmek gerekiyor.&lt;/p&gt;
&lt;pre class="brush:bash;auto-links:false;toolbar:false" contenteditable="false"&gt;# &amp;Ouml;ncelikle apt paket y&amp;ouml;neticisini g&amp;uuml;ncelleyelim
sudo apt update
# Şimdi de apt software paketlerini g&amp;uuml;ncelleyelim
sudo apt upgrade -y
# İşimize yarayacak bazı paketleri de y&amp;uuml;kleyelim
sudo apt install -y zip unzip build-essential

sudo apt install opam
opam init --bare -a -y
# Bir ihtimal opam'ın update edilmesine dair bir uyarı gelebilir. O vakit,
opam update

# G&amp;uuml;ncel bir OCaml s&amp;uuml;r&amp;uuml;m&amp;uuml; ile &amp;ccedil;alışmak i&amp;ccedil;in aşağıdaki komutu kullanarak bir switch oluşturabiliriz
opam switch create ocaml-5.3 ocaml-base-compiler.5.3.0
# Terminalimizin yeni switch'i tanıması i&amp;ccedil;in aşağıdaki komutu &amp;ccedil;alıştırmakta fayda var
eval $(opam env)
# switch listesini g&amp;ouml;rmek i&amp;ccedil;in
opam switch list

# Şimdi opam i&amp;ccedil;in gerekli paketleri y&amp;uuml;kleyelim
opam install -y utop odoc ounit2 qcheck bisect_ppx menhir ocaml-lsp-server ocamlformat

# Şu noktada ocaml universal TopLevel aracı olan utop'u kullanarak interaktif bir şekilde OCaml kodu yazabiliriz
utop&lt;/pre&gt;
&lt;p&gt;Eğer her şey yolunda giderse ubuntu ortamında utop ile doğrudan ocaml kodlamaya başlanabilir. Mesela,&lt;/p&gt;
&lt;pre class="brush:bash;auto-links:false;toolbar:false" contenteditable="false"&gt;# 3.1415;;
- : float = 3.1415
# #quit;;&lt;/pre&gt;
&lt;p&gt;&lt;img src="https://www.buraksenyurt.com/image.axd?picture=/2026/Nisan/ocaml_16.png" alt="" /&gt;&lt;/p&gt;
&lt;h2&gt;Giriş Seviyesi&lt;/h2&gt;
&lt;p&gt;Aşağıdaki kod &amp;ouml;rnekleri i&amp;ccedil;in komut satırından ocaml komutu &amp;ccedil;alıştırılarak ilerlenebilir. Ayrıca utop aracı ile de &amp;ccedil;alışılabilir. Bu ikisi &amp;ouml;zellikle yazılan kodun anında &amp;ccedil;alıştırılması ve sonu&amp;ccedil;ların g&amp;ouml;r&amp;uuml;lmesi a&amp;ccedil;ısından faydalı ara&amp;ccedil;lar. utop, daha gelişmiş &amp;ouml;zelliklere sahip bir TopLevel aracı olarak d&amp;uuml;ş&amp;uuml;n&amp;uuml;lebilir ancak bir noktadan sonra ml uzantılı dosyalar &amp;uuml;zerinden &amp;ccedil;alışmaya d&amp;ouml;nd&amp;uuml;ğ&amp;uuml;m&amp;uuml; de belirtmek isterim. TopLevel bir nevi REPL&lt;em&gt;(Read-Eval-Print Loop)&lt;/em&gt; aracı. &amp;Ccedil;ok b&amp;uuml;y&amp;uuml;k &amp;ccedil;aplı olmayan kod par&amp;ccedil;alarını denemek i&amp;ccedil;in, &amp;ouml;zellikle dilin temel &amp;ouml;zelliklerini giriş seviyesinde &amp;ouml;ğrenirken olduk&amp;ccedil;a kullanışlı bir ara&amp;ccedil;. Yazılan bir ifadenin dil tarafından nasıl yorumlandığını anında g&amp;ouml;steriyor.&lt;/p&gt;
&lt;h3&gt;Bazı Yararlı Utop Komutları&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;#help;; : Utop'ta kullanılabilecek komutları g&amp;ouml;sterir.&lt;/li&gt;
&lt;li&gt;CTRL + L : Ekranı temizler. Bir noktadan sonra terminal ekranı &amp;ccedil;ok kirlenirse silmek i&amp;ccedil;in ;)&lt;/li&gt;
&lt;li&gt;#quit;; : Utop oturumunu sonlandırır.&lt;/li&gt;
&lt;li&gt;#show {module_name};; : Belirtilen mod&amp;uuml;l&amp;uuml;n i&amp;ccedil;eriğini g&amp;ouml;sterir. &amp;Ouml;rneğin #show List;; komutu ile List mod&amp;uuml;l&amp;uuml;n&amp;uuml;n i&amp;ccedil;eriği g&amp;ouml;r&amp;uuml;lebilir.&lt;/li&gt;
&lt;li&gt;#use "filename.ml";; : Belirtilen dosyayı y&amp;uuml;kler ve i&amp;ccedil;indeki kodu &amp;ccedil;alıştırır. Dosya uzantısı .ml olmalıdır. &amp;Ouml;rneğin aşağıdaki i&amp;ccedil;eriğe sahip bir ml dosyamız olduğunu d&amp;uuml;ş&amp;uuml;nelim. &lt;em&gt;(Clarkson'un &amp;ouml;ğretisine bağlı kalarak WSL ortamında 3110 isimli bir klas&amp;ouml;r oluşturup i&amp;ccedil;ine bu dosyayı koydum)&lt;/em&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class="brush:cpp;auto-links:false;toolbar:false" contenteditable="false"&gt;let x : int = 3110;;
print_int x;;
print_string "Hello, world!\n";;&lt;/pre&gt;
&lt;p&gt;&lt;img src="https://www.buraksenyurt.com/image.axd?picture=/2026/Nisan/ocaml_17.png" alt="" /&gt;&lt;/p&gt;
&lt;h3&gt;Basit aritmetik işlemler, değişken atamaları ve isimlendirmeler&lt;/h3&gt;
&lt;p&gt;&amp;Ouml;yleyse ders notlarımıza başlayalım. İlk olarak float değerler ile ilgili aritmetik birka&amp;ccedil; işleme bakalım. Deneyeceğim ifadeleri aşağıdaki kod bloğuna ekliyorum. Bunları utop aracından denersek &amp;ccedil;ok faydalı olacaktır.&lt;/p&gt;
&lt;pre class="brush:cpp;auto-links:false;toolbar:false" contenteditable="false"&gt;3.14 +. 2.1;;
10+2;;
10+.2;;
(* 
  Line 1, characters 0-2: 
  1 | 10+.2;;
      ^^
  Error: The constant 10 has type int but an expression was expected of type
          float
  Hint: Did you mean 10.?
*)
10. +. 2;;
(*
  Line 1, characters 7-8:
  1 | 10. +. 2;;
            ^
  Error: The constant 2 has type int but an expression was expected of type
          float
  Hint: Did you mean 2.?
*)
10. +. 2.;;
1_000_000 * 10_000;;
(2 * 5) &amp;lt;= 10;;
(2 * 6) &amp;lt;= 10;;
let xValue = 10;;
let y_value = 5;;
let result = xValue + y_value;;
let MaxUserCount = 8;;
(*
  Line 1, characters 4-16:
  1 | let MaxUserCount = 8;;
          ^^^^^^^^^^^^
  Error: Unbound constructor MaxUserCount
*)
let 7even = 7;;
(*
  Line 1, characters 4-9:
  1 | let 7even = 7;;
          ^^^^^
  Error: Invalid literal 7even
*)
let screen-width = 1024;;
(*
  Line 1, characters 11-16:
  1 | let screen-width = 1024;;
                ^^^^^
  Error: Syntax error
*)&lt;/pre&gt;
&lt;p&gt;&lt;img src="https://www.buraksenyurt.com/image.axd?picture=/2026/Nisan/ocaml_18.png" alt="" /&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;;; ile toplevel'a ilgili satırın bir ifade olarak ele alınması, yani hemen &amp;ccedil;alıştırılması gerektiğini belirtmiş oluyoruz. &lt;em&gt;(Evaluate Expression)&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;İki float değeri toplamak i&amp;ccedil;in +. operat&amp;ouml;r&amp;uuml; kullanılmalı. Ayrıca float ve int toplanacaksa k&amp;uuml;s&amp;uuml;rat olmasa bile . işareti ile sayının float olarak ele alınacağı ifade edilmeli.&lt;/li&gt;
&lt;li&gt;İfade &amp;ccedil;alıştırıldığında sadece sonu&amp;ccedil; değil t&amp;uuml;r bilgisi de d&amp;ouml;n&amp;uuml;l&amp;uuml;yor.&lt;/li&gt;
&lt;li&gt;B&amp;uuml;y&amp;uuml;k sayılar _ karakteri ile daha okunabilir yazılabilir.&lt;/li&gt;
&lt;li&gt;Değişkenleri let anahtar kelimesi ile tanımlayabilir, ilk değerleri atayabiliriz.&lt;/li&gt;
&lt;li&gt;Değişken isimlendirme kurallarına g&amp;ouml;re b&amp;uuml;y&amp;uuml;k harfle, sayıyla başlayan değişken adları verilemez&lt;em&gt;(MaxUserCount, 7even)&lt;/em&gt; gibi. B&amp;uuml;y&amp;uuml;k harf kullanılmama sebebi, mod&amp;uuml;l adlarının b&amp;uuml;y&amp;uuml;k harfle başlaması olabilir.&lt;/li&gt;
&lt;li&gt;Hatta değişken isimlendirmelerinde - operat&amp;ouml;r&amp;uuml; de kullanılamaz.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;let'in G&amp;uuml;c&amp;uuml; ve Fonksiyon Tanımlamaları&lt;/h3&gt;
&lt;p&gt;Başka işlemlerle devam edelim. let &amp;ccedil;ok g&amp;uuml;&amp;ccedil;l&amp;uuml; bir operat&amp;ouml;r. Değişkenleri değerlere bağlayabildiğimiz gibi, fonksiyonları da iş yapan kod bloklarına bağlayabiliriz.&lt;/p&gt;
&lt;pre class="brush:cpp;auto-links:false;toolbar:false" contenteditable="false"&gt;let total x y = x + y;;
total 1 5;;
total -5 5;;
(*
  Line 1, characters 0-5:
  1 | total -5 5;;
      ^^^^^
  Error: The value total has type int -&amp;gt; int -&amp;gt; int
        but an expression was expected of type int
*)
total (-5) 5;;
total 1.2 3.4;;
(*
  Line 1, characters 6-9:
  1 | total 1.2 3.4;;
            ^^^
  Error: The constant 1.2 has type float but an expression was expected of type
          int
*)
# total 128 (8 * 1024);;&lt;/pre&gt;
&lt;p&gt;&lt;img src="https://www.buraksenyurt.com/image.axd?picture=/2026/Nisan/ocaml_19.png" alt="" /&gt;&lt;/p&gt;
&lt;p&gt;Burada total isimli iki parametre alan ve varsayılan olarak int t&amp;uuml;r&amp;uuml;nden değerleri toplayan bir fonksiyon tanımladık. Fonksiyon &amp;ccedil;ağrılırken parametreler arasında parantez kullanımı &amp;ouml;nemli. Aksi halde eksi işareti operat&amp;ouml;r olarak algılanıyor. Ayrıca doğru t&amp;uuml;rlerde işlem yapmak lazım. Yeni ifadelerle devam edelim;&lt;/p&gt;
&lt;pre class="brush:cpp;auto-links:false;toolbar:false" contenteditable="false"&gt;let total_1 x y = x + y;;
let total_2 x y = (x * x) + (y * y);;
total_1 3 4 + total_2 5 1;;
let div x y = Float.from_int x / Float.from_int y;;
(*
  Line 1, characters 14-28:
  1 | let div x y = Float.from_int x / Float.from_int y;;
                    ^^^^^^^^^^^^^^
  Error: Unbound value Float.from_int
  Hint:   Did you mean Float.of_int or Float.to_int?
*)
let div x y = Float.of_int x / Float.of_int y;;
(*
  Line 1, characters 14-28:
  1 | let div x y = Float.of_int x / Float.of_int y;;
                    ^^^^^^^^^^^^^^
  Error: This expression has type float but an expression was expected of type
          int
*)
let div x y = Float.of_int x /. Float.of_int y;;
div 1 3 
;;
div 3.14 2. 
;;
(*
  Line 1, characters 4-8:
  1 | div 3.14 2.
          ^^^^
  Error: The constant 3.14 has type float
        but an expression was expected of type int
*)
div 3 2;;&lt;/pre&gt;
&lt;p&gt;&lt;img src="https://www.buraksenyurt.com/image.axd?picture=/2026/Nisan/ocaml_20.png" alt="" /&gt;&lt;/p&gt;
&lt;p&gt;div fonksiyonunun yorumlanma şekli dikkatinizi &amp;ccedil;ekmiştir. int -&amp;gt; int -&amp;gt; float. D&amp;uuml;ş&amp;uuml;n&amp;uuml;nce int,int -&amp;gt; float gibi bir şey yazar diye bekliyor insan değil mi?&lt;/p&gt;
&lt;p&gt;Yukarıdaki &amp;ouml;rnekte, div isimli fonksiyonu tanımlamaya &amp;ccedil;alışıyoruz. Fonksiyondan beklenti int t&amp;uuml;r&amp;uuml;nden gelen iki sayıyı b&amp;ouml;lmek ama bunları float t&amp;uuml;r&amp;uuml;nden ele almasını sağlamak. İlk denemede kitaptaki fonksiyon adını unuttum ve of_int yerine from_int yazdım. Rust g&amp;uuml;nl&amp;uuml;klerim geldi aklıma, yorumlayıcı "acaba şunu mu demek istedin" derdi. Fonksiyonları d&amp;uuml;zelttikten sonra / operat&amp;ouml;r&amp;uuml; ile /. arasındaki farka tosladım. float t&amp;uuml;rler arasında bir b&amp;ouml;lme işlemi s&amp;ouml;z konusu olacağı i&amp;ccedil;in /. operat&amp;ouml;r&amp;uuml;n&amp;uuml;n kullanılması gerekiyormuş. B&amp;ouml;lme operat&amp;ouml;r&amp;uuml;n&amp;uuml;n tipe &amp;ouml;zel versiyonlandığını ifade edebiliriz. Ayrıca Float bir OCaml mod&amp;uuml;l&amp;uuml;d&amp;uuml;r&lt;em&gt;(B&amp;uuml;y&amp;uuml;k harfle başlayan isimler mod&amp;uuml;lleri ifade eder)&lt;/em&gt; ve bu mod&amp;uuml;l&amp;uuml;n i&amp;ccedil;inde of_int isimli bir fonksiyon var. Bu fonksiyonun g&amp;ouml;revi int t&amp;uuml;r&amp;uuml;nden bir değeri float t&amp;uuml;r&amp;uuml;ne &amp;ccedil;evirmek.&lt;/p&gt;
&lt;p&gt;Burada rahatsız edici nokta belki de Float.of_int kullanımı olabilir ama bunu kolaylaştırmak i&amp;ccedil;in OCaml ekosisteminde yazılmış bir başka &lt;a href="https://ocaml.janestreet.com/ocaml-core/v0.13/doc/base/Base/Float/O/" target="_blank"&gt;mod&amp;uuml;l&lt;/a&gt; var. Bu mod&amp;uuml;ldeki ama&amp;ccedil;lardan birisi float değerler ile &amp;ccedil;alışırken +., /. operat&amp;ouml;rleri yerine +, / ve \ ile de &amp;ccedil;alışabilmek ve bunu float-safe modda yapabilmek. Biz şu an i&amp;ccedil;in standart k&amp;uuml;t&amp;uuml;phane ile devam edebiliriz. Ekosistemdeki diğer mod&amp;uuml;llere sonradan odaklanırız. Standart k&amp;uuml;t&amp;uuml;phane aynı fonksiyonu aşağıdaki gibi yazmamıza da izin veriyor.&lt;/p&gt;
&lt;pre class="brush:cpp;auto-links:false;toolbar:false" contenteditable="false"&gt;let div x y =
        float_of_int x /. float_of_int y
;;
div 1 5;;&lt;/pre&gt;
&lt;p&gt;&lt;img src="https://www.buraksenyurt.com/image.axd?picture=/2026/Nisan/ocaml_21.png" alt="" /&gt;&lt;/p&gt;
&lt;h4&gt;Yine de Float.0 ile &amp;Ccedil;alışmak Gerekirse&lt;/h4&gt;
&lt;p&gt;Bir noktada Float.O ile &amp;ccedil;alışmak gerekirse ş&amp;ouml;yle ilerlemek gerekiyor. &amp;Ouml;ncelikle komut satırından utop başlatılır. Ardından, toplevel, Base mod&amp;uuml;l&amp;uuml;n&amp;uuml; destekleyecek şekilde başlatılır. Bu işlemin ardından ilgili fonksiyon yazılabilir. Aşağıdaki ekran g&amp;ouml;r&amp;uuml;nt&amp;uuml;s&amp;uuml;n&amp;uuml; geleceğe not olarak bırakalım.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Burada dikkat edilmesi gereken bir nokta da Float.O ifadesindeki O'nun b&amp;uuml;y&amp;uuml;k harf O olduğudur. 0 (sıfır) değil :D&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;img src="https://www.buraksenyurt.com/image.axd?picture=/2026/Nisan/ocaml_01.png" alt="" /&gt;&lt;/p&gt;
&lt;p&gt;Gelecekten geldim :D WSL - Ubuntu tarafından da bir bakalım.&lt;/p&gt;
&lt;h3&gt;Zihin Yakan Bir Fonksiyon Kullanımı&lt;/h3&gt;
&lt;p&gt;Şimdi, int t&amp;uuml;r&amp;uuml;nden değer d&amp;ouml;nen bir fonksiyonu parametre olarak alan ve diğer parametreden gelen int değer ile toplayan bir fonksiyon tanımlayıp &amp;ccedil;alıştıralım.&lt;/p&gt;
&lt;pre class="brush:cpp;auto-links:false;toolbar:false" contenteditable="false"&gt;let more_add f x y = f * x + y;;
let square n = n * n;;
more_add (square 1) 1 1;;
more_add (square 2) 3 5;;&lt;/pre&gt;
&lt;p&gt;&lt;img src="https://www.buraksenyurt.com/image.axd?picture=/2026/Nisan/ocaml_23.png" alt="" /&gt;&lt;/p&gt;
&lt;p&gt;İlk olarak more_add fonksiyonuna bir bakalım. f harfinin bir fonksiyonu işaret ettiğini nereden anladı? Yorumlama kısmına baktığımızda int -&amp;gt; int -&amp;gt; int -&amp;gt; int şeklinde bir tanım var. `&amp;lt;fun\&amp;gt;` tabii ki bunun bir fonksiyon olduğunu ifade etmekte. f &amp;ccedil;ıktısını x ile &amp;ccedil;arpıp y ile toplatıyoruz. Sa&amp;ccedil;ma bir fonksiyon ancak dinamiğini &amp;ouml;ğrenmek a&amp;ccedil;ısından kayda değer. Sonrasında square isimli bir fonksiyon daha tanımlıyoruz. Bu fonksiyon tek parametre alıyor ve karesini d&amp;ouml;nd&amp;uuml;r&amp;uuml;yor. Şimdi more_add fonksiyonunu &amp;ccedil;ağırırken ilk parametre olarak square 2 ifadesini veriyoruz. Bu ifade 4 değerini d&amp;ouml;nd&amp;uuml;recek ve bu değer f parametresine bağlanacak. Sonrasında ise 3 ve 5 değerleri sırasıyla x ve y parametrelerine bağlanacak. Yani fonksiyonun işleyişi şu şekilde olacak, 4 * 3 + 5 = 12 + 5 = 17. Ancak asıl zihin yakıcı &amp;ouml;rnek kitaptaki &amp;ouml;rnekten esinlenilerek geliyor;&lt;/p&gt;
&lt;pre class="brush:cpp;auto-links:false;toolbar:false" contenteditable="false"&gt;let condition f first_arg second_arg =
      (if f first_arg then first_arg else 0)
      +
      (if f second_arg then second_arg else 0);;
let check_point value = value &amp;gt; 50;;
condition check_point 28 76;;&lt;/pre&gt;
&lt;p&gt;condition isimli fonksiyonun kullandığı f parametresi bir fonksiyonu işaret etmekte ve bu fonksiyonun t&amp;uuml;r&amp;uuml; int -&amp;gt; bool. Yani bir int alıp bool d&amp;ouml;nd&amp;uuml;ren bir fonksiyon. Peki yorumlayıcı buna nasıl karar verdi ya da bu t&amp;uuml;r tahminini&lt;em&gt;(type inference)&lt;/em&gt; neye g&amp;ouml;re yaptı? Bunu anlamak i&amp;ccedil;in if koşuluna odaklanmakta fayda var. Nitekim else kısımlarında 0 değeri kullanılmakta ki bu bir int t&amp;uuml;r&amp;uuml;. Buna g&amp;ouml;re then kısımlarında da int t&amp;uuml;r&amp;uuml; d&amp;ouml;nd&amp;uuml;ren ifadeler olmalı. Sonu&amp;ccedil; olarak f fonksiyonu int -&amp;gt; bool t&amp;uuml;r&amp;uuml;nde bir fonksiyon olmalı.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://www.buraksenyurt.com/image.axd?picture=/2026/Nisan/ocaml_24.png" alt="" /&gt;&lt;/p&gt;
&lt;p&gt;OCaml uzmanlarına g&amp;ouml;re bu yazım stiline ve yorumlayıcının tip tahmini mekanizmasına alışmak zaman alabilir. Diğer yandan dilin &amp;ccedil;ok g&amp;uuml;&amp;ccedil;l&amp;uuml; bir yanını ispat eden bu yazım stiline alışamayanlar i&amp;ccedil;in Annotations yani t&amp;uuml;r a&amp;ccedil;ıklamaları ile fonksiyonları tanımlamak da m&amp;uuml;mk&amp;uuml;n. Aynı fonksiyonu aşağıdaki gibi de yazabiliriz.&lt;/p&gt;
&lt;pre class="brush:cpp;auto-links:false;toolbar:false" contenteditable="false"&gt;let condition (f: int -&amp;gt; bool) (first_arg:int) (second_arg:int) : int =
      (if f first_arg then first_arg else 0)
      +
      (if f second_arg then second_arg else 0);;
let check_point value = value &amp;gt; 50;;
condition check_point 28 76;;&lt;/pre&gt;
&lt;p&gt;&lt;img src="https://www.buraksenyurt.com/image.axd?picture=/2026/Nisan/ocaml_25.png" alt="" /&gt;&lt;/p&gt;
&lt;h3&gt;Fonksiyonlarda Generic Parametre Kullanımı&lt;/h3&gt;
&lt;p&gt;OCaml t&amp;uuml;r tahmini yapma konusundaki h&amp;uuml;nerini generic t&amp;uuml;rler i&amp;ccedil;in de g&amp;ouml;sterir. Aşağıdaki ifadeleri deneyerek devam edelim.&lt;/p&gt;
&lt;pre class="brush:cpp;auto-links:false;toolbar:false" contenteditable="false"&gt;let identity value = value;;
identity 1001;;
identity "PRD-0001";;
let swap (left,right) = (right,left);;
swap (4,"four");;&lt;/pre&gt;
&lt;p&gt;&lt;img src="https://www.buraksenyurt.com/image.axd?picture=/2026/Nisan/ocaml_26.png" alt="" /&gt;&lt;/p&gt;
&lt;p&gt;identity ve swap isimli fonksiyonlar tanımlandıktan sonra yorumlayıcının verdiği &amp;ccedil;ıktılara dikkat edelim.&lt;em&gt;(A&amp;ccedil;ık&amp;ccedil;ası Rust'ı &amp;ouml;ğrenmeye başladığımda hem kavramsal olarak hem de sentaks olarak zorlandığım 'a - lifetime annotations konusu geldi aklıma)&lt;/em&gt; Her neyse, 'a ve 'b şeklinde yazılan ifadeler generic t&amp;uuml;rler. Generic kavramına aşina olmayanlar i&amp;ccedil;in a ve b yerine herhangi bir t&amp;uuml;r gelebilir ve bunun i&amp;ccedil;in her bir t&amp;uuml;re &amp;ouml;zel olacak şekilde bu fonksiyonun farklı versiyonlarını yazmanıza gerek yoktur diyelim. Şimdi biraz daha kafa karıştırabilecek bir &amp;ouml;rnek.&lt;/p&gt;
&lt;pre class="brush:cpp;auto-links:false;toolbar:false" contenteditable="false"&gt;let compare f arg_1 arg_2 = if f arg_1 then arg_1 else arg_2;;
let str_len string = String.length string &amp;gt; 8;;
compare str_len "Some..." "Something happens";;
let is_pass score = score &amp;gt; 70;;
compare is_pass 68 50;;
compare is_pass "Black" "And White";;
(*)
  1 | compare is_pass "Black" "And White";;
                      ^^^^^^^
  Error: This constant has type string but an expression was expected of type
          int
*)&lt;/pre&gt;
&lt;p&gt;&lt;img src="https://www.buraksenyurt.com/image.axd?picture=/2026/Nisan/ocaml_27.png" alt="" /&gt;&lt;/p&gt;
&lt;p&gt;compare isimli fonksiyonumuz bir fonksiyon alıp diğer iki arg&amp;uuml;manı da hesaba katarak bir if koşulu işletmekte. compare fonksiyonundaki parametrelerin generic 'a t&amp;uuml;r&amp;uuml; olarak yorumlandığına dikkat edelim. Sonraki adımlarda str_len ve is_pass isimli iki farklı fonksiyon daha tanımlanıyor. İlki, String mod&amp;uuml;l&amp;uuml;nden length fonksiyonunu kullanarak bir değer d&amp;ouml;nd&amp;uuml;rd&amp;uuml;ğ&amp;uuml; i&amp;ccedil;in string veri t&amp;uuml;r&amp;uuml; ile &amp;ccedil;alışacağı aşikar. Diğer fonksiyon ise sayısal bir karşılaştırma kullanıyor ve buna g&amp;ouml;re de int değerlerle &amp;ccedil;alışacağı anlaşılıyor. compare fonksiyonuna bu iki fonksiyonu parametre olarak verebiliriz ama devam eden arg&amp;uuml;manların da uygun tipler olması beklenir. Yani str_len kullanıyorsak diğer iki arg&amp;uuml;manın da string t&amp;uuml;r&amp;uuml;nden olması gerekiyor.&lt;/p&gt;
&lt;h3&gt;Tuple, List ve Options veri t&amp;uuml;rleri&lt;/h3&gt;
&lt;p&gt;İlk olarak tuple veri t&amp;uuml;r&amp;uuml; ile ilgili basit &amp;ouml;rneklerle ilerleyelim. Aşağıdaki ifadeleri deneyebiliriz.&lt;/p&gt;
&lt;pre class="brush:cpp;auto-links:false;toolbar:false" contenteditable="false"&gt;let config = ("He-Man, G&amp;ouml;lgelerin g&amp;uuml;c&amp;uuml; adına",1920,1080,true);;
let (title,width,height,is_active) = config;;
let move (x,y) speed = (x + speed , y + speed);;
move (10,15) 1;;
let (new_x,new_y) = move (11,16) 5;;&lt;/pre&gt;
&lt;p&gt;&lt;img src="https://www.buraksenyurt.com/image.axd?picture=/2026/Nisan/ocaml_28.png" alt="" /&gt;&lt;/p&gt;
&lt;p&gt;config isimli değişken bir tuple veri yapısını işaret ediyor. Tuple veri yapısı farklı t&amp;uuml;rden değerler i&amp;ccedil;erebilen zengin bir model. İstersek tanımladığımız config isimli tuple i&amp;ccedil;eriğini let ile başka değişkenlere &amp;ccedil;ıkarabiliriz(export) Burada pattern matching &amp;ouml;zelliğinin olduğunu da g&amp;ouml;rebiliriz. move isimli fonksiyon da dikkate değer. İki parametre alıyor ancak x ve y koordinatlarını ifade eden ilk parametreyi bir tuple olarak tanımlıyor. Ayrıca fonksiyondan geriye yine bir tuple t&amp;uuml;r&amp;uuml; d&amp;ouml;nmekte.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Kitapta tuple veri t&amp;uuml;r&amp;uuml; tanımında neden `*` şeklinde bir operat&amp;ouml;r kullanıldığı da vurgulanıyor. Yani bir tuple tanımlandığında yorumlayıcı bunu okurken string \* int \* int \* bool gibi bir ifade kullanıyor. T&amp;uuml;rlerin toplam k&amp;uuml;mesini işaret eden bir kartezyen &amp;ccedil;arpımı s&amp;ouml;z konusu olduğundan &amp;ccedil;arpım sembol&amp;uuml; kullanılıyor diyebiliriz. Kıssadan hisse bug&amp;uuml;n kullandığım Rust, C# ve Zig gibi dillerden &amp;ouml;nce belki de işe OCaml ile başlamak gerekiyordu...&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Eğer aynı t&amp;uuml;rde verilerden oluşan bir listeye ihtiyacımız varsa, pekala List veri yapısını kullanabiliriz :D Aşağıda yine yaptığım denemelerin peşi sıra gelen ifadeleri yer alıyor. &amp;Uuml;şenmeyip utop aracını a&amp;ccedil;ın, deneyin. &amp;Ouml;nemli olan ;; sonrasında OCaml yorumlayıcısının verdiği &amp;ccedil;ıktıları g&amp;ouml;rmek ve anlamaya &amp;ccedil;alışmak.&lt;/p&gt;
&lt;pre class="brush:cpp;auto-links:false;toolbar:false" contenteditable="false"&gt;let colors = ["Red" ; "Green" ; "Blue"];;
let numbers = [1;2;3;4;5];;
let points = [0.40;0.25;0.55;0.45];;
let illegal = ["One";"Two";3;"Four"];;
(*
  Line 1, characters 27-28:
  1 | let illegal = ["One";"Two";3;"Four"];;
                                ^
  Error: The constant 3 has type int but an expression was expected of type
          string
*)
List.length colors;;
"Black" :: "White" :: colors;;
colors;;
let extended = "Black" :: "White" :: colors;;
extended;;
let another_list = [1,2,3,4,5,6];;
let origin = 0,0;;
"R","G","B";;
let left_side = [1;2;3];;
let right_side = [4;5;6;7;8];;
let combine = left_side @ right_side;;&lt;/pre&gt;
&lt;p&gt;&lt;img src="https://www.buraksenyurt.com/image.axd?picture=/2026/Nisan/ocaml_29.png" alt="" /&gt;&lt;/p&gt;
&lt;p&gt;colors, numbers ve points kendi veri t&amp;uuml;rlerinde elemanlar taşıyan birer liste. illegal isimli liste ise farklı t&amp;uuml;rden elemanlardan oluşan bir liste yapısı oluşturmak istediğimizde alacağımız hatayı &amp;uuml;retiyor. OCaml'ın List mod&amp;uuml;l&amp;uuml;nde bazı yardımcı fonksiyonlar da bulunuyor. &amp;Ouml;rnek kodlarda listenin uzunluğunu bulmak i&amp;ccedil;in List.length, liste başına eleman eklemek i&amp;ccedil;in :: operat&amp;ouml;r&amp;uuml;(constructor operator) kullanılmakta. Dikkat edelim, orijinal liste değişmiyor! İlaveler sonrası yeni bir liste oluşuyor.&lt;/p&gt;
&lt;p&gt;&amp;Ccedil;alışırken yaptığım hatalardan birisi de liste elemanlarını tanımlarken arada virg&amp;uuml;l kullanmaktı. Bunu yapınca bir liste yerine tek elemanlı bir tuple listesi oluşmakta. Dolayısıyla ; ile , kullanımına dikkat etmeli. Hatta bir tuple tanımlanırken parantez kullanmazsak, virg&amp;uuml;l ile ayrılmış değerler bir tuple olarak algılanıyor. @, yani add operat&amp;ouml;r&amp;uuml;n&amp;uuml; kullanarak listeleri birleştirmek de m&amp;uuml;mk&amp;uuml;n.&lt;/p&gt;
&lt;p&gt;Peki bir liste veri yapısında pattern matching kullanabilir miyiz? Basit bir &amp;ouml;rnek &amp;uuml;st&amp;uuml;nden ele alalım.&lt;/p&gt;
&lt;pre class="brush:cpp;auto-links:false;toolbar:false" contenteditable="false"&gt;let first_or_default values =
      match values with
      | first :: the_rest -&amp;gt; first
      | [] -&amp;gt; 0;;
first_or_default [];;
first_or_default [12;0;23;9;14];;&lt;/pre&gt;
&lt;p&gt;&lt;img src="https://www.buraksenyurt.com/image.axd?picture=/2026/Nisan/ocaml_30.png" alt="" /&gt;&lt;/p&gt;
&lt;p&gt;Burada tanımladığımız first_or_default isimli fonksiyon int t&amp;uuml;r&amp;uuml;nden bir listenin ilk elemanını d&amp;ouml;nd&amp;uuml;r&amp;uuml;yor ancak pattern matching ile uyguladığımız bir koşul var. Boş bir liste verilirse varsayılan olarak 0 değerini d&amp;ouml;nd&amp;uuml;r&amp;uuml;yor; dolu bir liste gelirse, bunu first :: the_rest ifadesi ile eşleştirip&lt;em&gt;(ilk eleman ve kalanlar anlamında d&amp;uuml;ş&amp;uuml;nebiliriz)&lt;/em&gt; listenin ilk elemanını d&amp;ouml;nd&amp;uuml;r&amp;uuml;yor. Yorumlayıcının boş bir liste s&amp;ouml;z konusu ise 0 d&amp;ouml;nd&amp;uuml;r&amp;uuml;lmesinden yola &amp;ccedil;ıkarak fonksiyonun integer bir liste ile &amp;ccedil;alışacağına kanaat getirdiğine dikkat edelim. Dolayısıyla bu fonksiyonu aşağıdaki gibi yazarsak generic bir versiyon da &amp;ccedil;ıkarmış oluruz.&lt;/p&gt;
&lt;pre class="brush:cpp;auto-links:false;toolbar:false" contenteditable="false"&gt;let first_or default values =
      match values with
      | first :: the_rest -&amp;gt; first
      | [] -&amp;gt; default;;
first_or "" [];;
first_or 1 [];;
first_or 0 [12;2;6;9];;&lt;/pre&gt;
&lt;p&gt;&lt;img src="https://www.buraksenyurt.com/image.axd?picture=/2026/Nisan/ocaml_31.png" alt="" /&gt;&lt;/p&gt;
&lt;p&gt;Şimdi bir sayı listesindeki elemanların toplamını hesaplayan hem pattern matching i&amp;ccedil;eren hem de recursive olan bir fonksiyon yazalım. Eh, bir d&amp;ouml;ng&amp;uuml; ile listeyi dolaşmak vardı ama Real World OCaml kitabına g&amp;ouml;re &amp;ouml;z yinelemeli fonksiyonlar, fonksiyonel dillerin ger&amp;ccedil;ekten &amp;ouml;nemli bir par&amp;ccedil;ası. Doğrusu bundan g&amp;uuml;zel bir sınav sorusu olurmuş, "Herhangi bir sayı listesindeki elemanların toplamını bulacak bir fonksiyon yazın. D&amp;ouml;ng&amp;uuml; kullanmak yasak, recursive fonksiyonellik şart" :D&lt;/p&gt;
&lt;pre class="brush:cpp;auto-links:false;toolbar:false" contenteditable="false"&gt;let rec sum_of list =
      match list with
      | [] -&amp;gt; 0
      | head :: tail -&amp;gt; head + sum_of tail;;
sum_of [1;4;4;2;6;7];;
let numbers = [0;2;4;9;-4;-5];;
sum_of numbers;;&lt;/pre&gt;
&lt;p&gt;&lt;img src="https://www.buraksenyurt.com/image.axd?picture=/2026/Nisan/ocaml_32.png" alt="" /&gt;&lt;/p&gt;
&lt;p&gt;Bunu b&amp;uuml;y&amp;uuml;k ihtimalle unutacağım ve bakmadan yazamayacağım ama birka&amp;ccedil; &amp;ouml;nemli noktayı kayıt altına almak isterim. sum_of fonksiyonunun kendisini referans ettiğini belirttiğimiz bir yer var, rec anahtar kelimesi. Bir fonksiyonun recursive olduğunu belirtiyor. Boş liste veya dolu liste gelmesi ihtimallerine karşı bir pattern matching kullanımı da s&amp;ouml;z konusu. Eğer boş bir liste gelirse toplamın sıfır d&amp;ouml;neceğini belirtmek aynı zamanda bu fonksiyonu integer listelerle &amp;ccedil;alışacak bir t&amp;uuml;re d&amp;ouml;n&amp;uuml;şt&amp;uuml;r&amp;uuml;yor. İkinci match dalında head ve tail durumlarını ele alıyoruz ve fonksiyonu tekrar &amp;ccedil;ağırarak sayıları birbirlerine ekliyoruz. Yani ilk sayıdan başlarsak 1 + sum_of [4;4;2;6;7] gibi bir dizilim ortaya &amp;ccedil;ıkıyor. İkinci match kırılımı i&amp;ccedil;in t&amp;uuml;mevarımsal(inductive) yaklaşımın benimsendiğini vurgulayalım. Bu fonksiyonun işleyişine ait aşağıda bir &amp;ouml;rnekleme yer alıyor.&lt;/p&gt;
&lt;pre class="brush:plain;auto-links:false;toolbar:false" contenteditable="false"&gt;= 1 + sum_of [4;4;2;6;7]
= 1 + (4 + sum_of [4;2;6;7])
= 1 + (4 + (4 + sum_of [2;6;7]))
= 1 + (4 + (4 + (2 + sum_of [6;7])))
= 1 + (4 + (4 + (2 + (6 + sum_of [7]))))
= 1 + (4 + (4 + (2 + (6 + (7 + sum_of [])))))
= 1 + (4 + (4 + (2 + (6 + (7 + 0)))))
= 1 + (4 + (4 + (2 + (6 + 7))))
= 1 + (4 + (4 + (2 + 13)))
= 1 + (4 + (4 + 15))
= 1 + (4 + 19)
= 1 + 23
= 24&lt;/pre&gt;
&lt;p&gt;Piuvv! :D Parantezleri karıştırmış olabilirim. Kitapta 1;2;3 listesini toplamıştı.&lt;/p&gt;
&lt;p&gt;Bug&amp;uuml;nk&amp;uuml; terapide son olarak option veri yapısına bakıyorum. Bir değer vardır veya yoktur sorusuna cevap veren bir veri yapısı. Şahsen Rust dilinde Option t&amp;uuml;r&amp;uuml; &amp;ccedil;ok işe yarıyor&lt;em&gt;(Rust'ı geliştiren Graydon Hoare'un OCaml'den esinlendiği bir&amp;ccedil;ok yerde belirtiliyor)&lt;/em&gt; Aşağıdaki kod par&amp;ccedil;asında en basit kullanım şekli yer alıyor.&lt;/p&gt;
&lt;pre class="brush:cpp;auto-links:false;toolbar:false" contenteditable="false"&gt;let div x y = if y = 0 then None else Some (x/y);;
div 10 0;;
div 10 2;;&lt;/pre&gt;
&lt;p&gt;&lt;img src="https://www.buraksenyurt.com/image.axd?picture=/2026/Nisan/ocaml_33.png" alt="" /&gt;&lt;/p&gt;
&lt;p&gt;Eğer y sıfır ise None d&amp;ouml;n&amp;uuml;yor değilse b&amp;ouml;lme işlemi ger&amp;ccedil;ekleştiriliyor. Dikkat edileceği &amp;uuml;zere yorumlayıcı fonksiyonun d&amp;ouml;n&amp;uuml;ş t&amp;uuml;r&amp;uuml;n&amp;uuml; int option olarak belirledi. Bu son derece normal zira 7 değerinin 0 olup olmadığı kontrol ediliyor. Sıfırın varsayılan olarak int olarak kabul edildiği d&amp;uuml;ş&amp;uuml;n&amp;uuml;l&amp;uuml;rse int option olarak yorumlanması son derece doğal. Bu arada None ve Some ifadeleri rastgele isimlendirmeler değil birer constructor olarak kabul ediliyor.&lt;/p&gt;
&lt;h3&gt;Record Veri Yapısı ve Variant Tipler&lt;/h3&gt;
&lt;p&gt;Pek tabii var olan t&amp;uuml;rler dışında karma t&amp;uuml;rler de tanımlayabiliriz. Kendi veri yapılarımızı tasarlarken kullanabileceğimiz enstr&amp;uuml;manlardan birisi record t&amp;uuml;r&amp;uuml;d&amp;uuml;r.&lt;/p&gt;
&lt;pre class="brush:cpp;auto-links:false;toolbar:false" contenteditable="false"&gt;type address = {host:string; port:int; route:string};;

let cust_get ={host = "localhost"; port = 5001; route = "api/v1/customer/get"};;

type service = {name : string; is_active : bool; kind : string; path : address};;

let customer_service = {name = "Get customers"; is_active = true; kind = "REST"; path = cust_get};;&lt;/pre&gt;
&lt;p&gt;&lt;img src="https://www.buraksenyurt.com/image.axd?picture=/2026/Nisan/ocaml_34.png" alt="" /&gt;&lt;/p&gt;
&lt;p&gt;Yukarıdaki kod par&amp;ccedil;asında iki record t&amp;uuml;r&amp;uuml; yer alıyor, address ve service. Dikkat edileceği &amp;uuml;zere service record yapısındaki path alanı address veri yapısı t&amp;uuml;r&amp;uuml;nden. cust_get ve customer_service isimli değişkenler ise bu t&amp;uuml;rlere ait nesneleri işaret ediyor. A&amp;ccedil;ık&amp;ccedil;a belirtmesek de eşitliğin sağ tarafından yapılan atamalar otomatik olarak cust_get'in bir address t&amp;uuml;r&amp;uuml; olmasını sağlıyor. Benzer şekilde customer_service değişkeni de service t&amp;uuml;r&amp;uuml;nden bir nesne olarak tanımlanıyor. utop ekran g&amp;ouml;r&amp;uuml;nt&amp;uuml;s&amp;uuml;nde olduğu gibi &amp;ccedil;ıktılara mutlaka bakmak lazım. type inference mekanizmasının nasıl &amp;ccedil;alıştığını g&amp;ouml;rmek a&amp;ccedil;ısından &amp;ouml;nemli.&lt;/p&gt;
&lt;p&gt;Şimdi bir de variant tanımlamayı deneyelim. Bu t&amp;uuml;r ile birden fazla nesneyi(object) tek bir tip altında birleştirmek m&amp;uuml;mk&amp;uuml;n. Aşağıdaki &amp;ouml;rnek kod par&amp;ccedil;ası ile anlamaya &amp;ccedil;alışalım.&lt;/p&gt;
&lt;pre class="brush:cpp;auto-links:false;toolbar:false" contenteditable="false"&gt;type location = { x : float; y : float }
type button = { title: string; position: location }
type label = { title: string; position: location }
type drop_down = { items: string list; position: location; is_enabled: bool }

type component =
  | Button of button
  | Label of label
  | DropDown of drop_down
;;&lt;/pre&gt;
&lt;p&gt;Bu kod par&amp;ccedil;asında button, label, drop_down gibi farklı t&amp;uuml;rden nesneleri tek bir component t&amp;uuml;r&amp;uuml;nde birleştirdik. Bu sayede component t&amp;uuml;r&amp;uuml;nden bir değişken tanımladığımızda s&amp;ouml;z konusu değişken button, label veya drop_down t&amp;uuml;rlerinden herhangi birini işaret edebilir. Aralarda pipe işareti olduğuna dikkat etmemiz gerekiyor ve hatta `|` sonrası gelen isimlendirmede b&amp;uuml;y&amp;uuml;k harfle başlama zorunluluğu var, aksi halde syntax error hatası alınıyor. drop_down isimli record t&amp;uuml;r&amp;uuml;nde bir string list kullanılıyor. Dolayısıyla birden fazla string &amp;ouml;ğe barındırabilir. Kitaptaki &amp;ouml;rnekten de esinlenerek bu variant t&amp;uuml;r&amp;uuml;n&amp;uuml; bir fonksiyona parametre olarak ge&amp;ccedil;ebiliriz.&lt;/p&gt;
&lt;pre class="brush:cpp;auto-links:false;toolbar:false" contenteditable="false"&gt;let get_item_count (c : component) : int =
  match c with
  | Button _ -&amp;gt; 0
  | Label _ -&amp;gt; 0
  | DropDown d -&amp;gt; List.length d.items
;;

let left_menu = DropDown {
  items = ["Save"; "Load"; "Exit"];
  position = {x = 10.0; y = 20.0};
  is_enabled = true
}
;;

get_item_count left_menu;;&lt;/pre&gt;
&lt;p&gt;Bu fonksiyon component t&amp;uuml;r&amp;uuml;nden bir parametre alıyor ve bu parametrenin hangi t&amp;uuml;rde olduğunu pattern matching ile kontrol ediyor. Eğer button veya label ise 0 d&amp;ouml;nd&amp;uuml;r&amp;uuml;yor, ancak drop_down ise i&amp;ccedil;indeki items listesinin uzunluğunu d&amp;ouml;nd&amp;uuml;r&amp;uuml;yor.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://www.buraksenyurt.com/image.axd?picture=/2026/Nisan/ocaml_35.png" alt="" /&gt;&lt;/p&gt;
&lt;p&gt;Farklı bir fonksiyon daha yazalım. &amp;Ouml;rneğin bileşen detaylarını g&amp;ouml;steren bir versiyon.&lt;/p&gt;
&lt;pre class="brush:cpp;auto-links:false;toolbar:false" contenteditable="false"&gt;let show_component_details (c : component) : unit =
  match c with
  | Button b -&amp;gt; 
      Printf.printf "Button: %s\n" b.title
  | Label l -&amp;gt; 
      Printf.printf "Label: %s\n" l.title
  | DropDown d -&amp;gt;
      Printf.printf "DropDown containing:\n";
      List.iter (fun item -&amp;gt; Printf.printf " - %s\n" item) d.items
;;

show_component_details left_menu;;&lt;/pre&gt;
&lt;p&gt;Fonksiyonumuz parametre olarak component isimli variant t&amp;uuml;r&amp;uuml;nden bir nesne alıyor. Bu nesnenin hangi t&amp;uuml;rde olduğunu pattern matching ile kontrol ediyoruz. Eğer button veya label ise başlık bilgisini yazdırıyoruz. Ancak drop_down ise i&amp;ccedil;indeki items listesini dolaşıp her bir &amp;ouml;ğeyi yazdırıyoruz. List.iter fonksiyonu, verilen bir fonksiyonu listenin her bir elemanına uygulamak i&amp;ccedil;in kullanılmakta.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://www.buraksenyurt.com/image.axd?picture=/2026/Nisan/ocaml_36.png" alt="" /&gt;&lt;/p&gt;
&lt;h3&gt;Mutable Olma Hali&lt;/h3&gt;
&lt;p&gt;Varsayılan olarak immutable ama gerekirse mutable. Rust'ın a&amp;ccedil;ık bir şekilde benimsediği bir yaklaşım. Varsayılan olarak immutable olmak aslında bir şeylerin yanlışlıkla değiştirilmesini engellemek a&amp;ccedil;ısından anlamlı. Diğer yandan imperative yaklaşımın ele alındığı saya&amp;ccedil;lar(counters) ve durum otomatı(state machine) gibi kodlar yazmanın &amp;ouml;n&amp;uuml; de a&amp;ccedil;ık.&lt;/p&gt;
&lt;p&gt;OCaml'ın safkan bir fonksiyonel dil olduğu belirtiliyor. Yani, kodun &amp;ccedil;alışmasının bir par&amp;ccedil;ası olarak değişkenlerin değerlerini değiştirmek normalde m&amp;uuml;mk&amp;uuml;n değil. Programın durumu immutable veri yapılarıyla temsil ediliyor. Buna karşın imperative programlama paradigmasını da destekliyor. Bir başka deyişle mutable veri yapıları da mevcut. &amp;Ouml;rneğin Array veri yapısı bunlardan birisi. Bunun haricinde record t&amp;uuml;r&amp;uuml;n&amp;uuml;n kendisi immutable olsa dahi &amp;uuml;yeleri mutable olarak tanımlanabilir. Şimdi yine beni zorlayacak yazım stilleriyle bir array tanımlayalım ve kullanalım. Hatta sonrasında mutable &amp;uuml;yeler i&amp;ccedil;eren bir record yazalım.&lt;/p&gt;
&lt;pre class="brush:cpp;auto-links:false;toolbar:false" contenteditable="false"&gt;(* float sayılardan oluşan bir array tanımı*)
let points = [| 45.50; 30.25; 60.75; 48.90; 80.; 0.; |];;

(* array operat&amp;ouml;rlerine erişim *)
Printf.printf "First point: %.2f\n" points.(0);;
Printf.printf "Second point: %.2f\n" points.(1);;

(* Bir array elementini değiştirmek istersek ş&amp;ouml;yle yapabiliriz *)
points.(0) &amp;lt;- 51.00;;
Printf.printf "Updated first point to: %.2f\n" points.(0);;

(* Array'in tamamını g&amp;ouml;r&amp;uuml;nt&amp;uuml;lemek i&amp;ccedil;in *)
points;;

(* 
Belki bir d&amp;ouml;ng&amp;uuml; yardımıyla array elemanlarını g&amp;ouml;r&amp;uuml;nt&amp;uuml;lemek isteyebiliriz
Hatta d&amp;ouml;ng&amp;uuml; i&amp;ccedil;inde pattern match kullanıp dersten ge&amp;ccedil;ti, kaldı vs diyebiliriz
*)
for i = 0 to Array.length points - 1 do
  match points.(i) with
  | p when p &amp;gt;= 50.0 -&amp;gt; Printf.printf "Student %d passed with %.2f\n" (i + 1) p
  | p -&amp;gt; Printf.printf "Student %d failed with %.2f\n" (i + 1) p
done;;&lt;/pre&gt;
&lt;p&gt;&lt;img src="https://www.buraksenyurt.com/image.axd?picture=/2026/Nisan/ocaml_37.png" alt="" /&gt;&lt;/p&gt;
&lt;p&gt;Bu &amp;ouml;rnekte points isimli bir array tanımladık. Array elemanlarına erişmek i&amp;ccedil;in .(index), bir array elemanını değiştirmek i&amp;ccedil;in ise &amp;lt;- operat&amp;ouml;r&amp;uuml;n&amp;uuml; kullandık. Sonrasında array'in tamamını g&amp;ouml;r&amp;uuml;nt&amp;uuml;ledik ve bir d&amp;ouml;ng&amp;uuml; yardımıyla her bir elemanı kontrol ederek &amp;ouml;ğrencinin dersten ge&amp;ccedil;me/kalma durumunu ekrana yazdırdık.&lt;/p&gt;
&lt;p&gt;&amp;Ouml;yleyse bir de mutable &amp;uuml;yeler i&amp;ccedil;eren bir record tanımlayalım. Burada dikkat edilmesi gereken noktalardan birisi de &amp;lt;- operat&amp;ouml;r&amp;uuml;n&amp;uuml;n unit () d&amp;ouml;nd&amp;uuml;rmesidir. Bu, yapılan atama işleminin bir hesaplama(calculation) olmadığını, bir aksiyon(action) olduğunu belirtir. Yani, points.(0) &amp;lt;- 51.00 ifadesi bir değer d&amp;ouml;nd&amp;uuml;rmez, sadece points array'inin ilk elemanını 51.00 olarak g&amp;uuml;nceller.&lt;/p&gt;
&lt;pre class="brush:cpp;auto-links:false;toolbar:false" contenteditable="false"&gt;(* 
  Varsayılan olarak immutable olan record &amp;uuml;yeleri mutable yapılabilir.
  Ş&amp;ouml;yle anlamlı bir &amp;ouml;rnek d&amp;uuml;ş&amp;uuml;nelim. Bir oyuncunun adı genellikle oyun sırasında değiştirilmez
  ancak canı, bulunduğu konum gibi bilgiler anlık olarak değişebilir.
*)

type player = {
  name: string;
  mutable health: int;
  mutable position: (int * int);
};;

let she_ra = { name = "She-Ra"; health = 100; position = (0, 0) };;

(* Bir fonksiyon ile de &amp;ouml;rneğin oyuncu hasar aldığında health bilgisini g&amp;uuml;ncelleyebiliriz *)
let take_damage player amount = 
  player.health &amp;lt;- player.health - amount;
  Printf.printf "%s took %d damage and now has %d health.\n" player.name amount player.health
;;

take_damage she_ra 30;;

(* Oyuncunun pozisyonunu g&amp;uuml;ncellemek i&amp;ccedil;in de benzer şekilde bir fonksiyon yazabiliriz *)
let move_player player new_position =
  player.position &amp;lt;- new_position;
  Printf.printf "%s moved to position (%d, %d).\n" player.name (fst new_position) (snd new_position)
;;

move_player she_ra (5, 10);;&lt;/pre&gt;
&lt;p&gt;&lt;img src="https://www.buraksenyurt.com/image.axd?picture=/2026/Nisan/ocaml_38.png" alt="" /&gt;&lt;/p&gt;
&lt;p&gt;Şimdi burada durup OCaml dilinin bu varsayılan immutable felsefesini d&amp;uuml;ş&amp;uuml;nmek lazım. Normalde yukarıdaki gibi bir senaryo varsayılan olarak aşağıdaki gibi ifade edilir.&lt;/p&gt;
&lt;pre class="brush:cpp;auto-links:false;toolbar:false" contenteditable="false"&gt;(* 
  OCaml'ın immutable felsefesini anlamak i&amp;ccedil;in bu &amp;ouml;rneği varsayılan durumda ele alalım
  Aşağıda g&amp;ouml;r&amp;uuml;ld&amp;uuml;ğ&amp;uuml; gibi normal bir record tanımı yaptık.
*)
type player = {
  name: string;
  health: int;
  position: (int * int);
};;

let she_ra = { name = "She-Ra"; health = 100; position = (0, 0) };;

(* take_damage fonksiyonu artık player record'&amp;uuml;n&amp;uuml;n health &amp;uuml;yesini değiştiremez.
  Bu y&amp;uuml;zden yeni bir player record'&amp;uuml; oluşturarak g&amp;uuml;ncellenmiş bilgileri i&amp;ccedil;eren bir record d&amp;ouml;nd&amp;uuml;rmemiz gerekir.
  Tabii bu durumda var olan player record' unun bir kopyasını oluşturmuş oluruz.

  &amp;Ouml;rnekte update_player oluşturulurken health bilgisi g&amp;uuml;ncelleniyor,
  burada with keyword kullandığımıza dikkat edelim. in ise yeni record'&amp;uuml;n oluşturulacağı scope'u belirtiyor.
*)
let take_damage player amount = 
  let updated_player = { player with health = player.health - amount } in
  Printf.printf "%s took %d damage and now has %d health.\n" player.name amount updated_player.health;
  updated_player
;;

let she_ra = take_damage she_ra 8;;

(* 
  Bir fonksiyon tanımlamadan değer değiştirmek istersek bu durumda aşağıdaki gibi ilerleyebiliriz. 
  S&amp;ouml;z gelimi pozisyonu değiştirelim.
*)
Printf.printf "%s is currently at position (%d, %d).\n" she_ra.name (fst she_ra.position) (snd she_ra.position);;
let she_ra = { she_ra with position = (25, 50) };;

Printf.printf "%s moved to position (%d, %d).\n" she_ra.name (fst she_ra.position) (snd she_ra.position);;&lt;/pre&gt;
&lt;p&gt;&lt;img src="https://www.buraksenyurt.com/image.axd?picture=/2026/Nisan/ocaml_39.png" alt="" /&gt;&lt;/p&gt;
&lt;p&gt;Burada fst ve snd ifadeleri aslında birer fonksiyon. Bir tuple'ın ilk ve ikinci elemanına erişmek i&amp;ccedil;in kullanılırlar. Yani, fst new_position ifadesi new_position adlı tuple'ın ilk elemanını d&amp;ouml;nd&amp;uuml;r&amp;uuml;rken, snd new_position ifadesi ikinci elemanını d&amp;ouml;nd&amp;uuml;rmektedir. Yukarıdaki kod par&amp;ccedil;asında gerekli a&amp;ccedil;ıklamalar yer alıyor. Belki de hangisini ne zaman se&amp;ccedil;mek gerekir &amp;uuml;zerine d&amp;uuml;ş&amp;uuml;nmek lazım. Ne zaman immutable yerine mutable tercih edelim ya da tam tersi?&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Varsayılan olarak immutable olmak, concurrency ve karmaşık mantık i&amp;ccedil;eren kodlarda hataların &amp;ouml;n&amp;uuml;ne ge&amp;ccedil;mek a&amp;ccedil;ısından avantajlı olabilir. Zira değişken değerlerinin beklenmedik şekilde değişmesi engellenmiş olur. S&amp;ouml;z gelimi buradaki player'ın immutable olan versiyonunu bir fonksiyona ge&amp;ccedil;tiğimizde, onun ilgili fonksiyon i&amp;ccedil;inde değişmeyeceğinden emin oluruz.&lt;/li&gt;
&lt;li&gt;Mutable veri yapıları ise veriyi kopyalamadan değiştirme imkanı sağlar ve bazı durumlarda, &amp;ouml;rneğin state değiştirmek veya ger&amp;ccedil;ek zamanlı g&amp;uuml;ncellemeler yapmak istediğimizde daha performanslı olabilir. Ancak mutable veri yapılarını kullanırken dikkatli olmak gerekir, &amp;ccedil;&amp;uuml;nk&amp;uuml; yanlışlıkla veriyi değiştirmek veya beklenmedik yan etkiler oluşturmak m&amp;uuml;mk&amp;uuml;nd&amp;uuml;r. Bu konuda sanıyorum en sık verilen &amp;ouml;rnek saya&amp;ccedil; mekanizması. OCaml ile basit bir counter tasarlayalım.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class="brush:cpp;auto-links:false;toolbar:false" contenteditable="false"&gt;(*
  Bir saya&amp;ccedil; ger&amp;ccedil;ek zamanlı g&amp;uuml;ncellemeyi gerektirir. Bu nedenle immutable olarak kullanmak,
  s&amp;uuml;rekli yeni bir kopya oluşturmaya neden olabilir ve bu da performans a&amp;ccedil;ısından iyi değildir.
  Dolayısıyla OCaml gibi varsayılan olarak immutability felsefesini benimsemiş diller i&amp;ccedil;in,
  saya&amp;ccedil; mekanizması g&amp;uuml;zel bir mutable olma &amp;ouml;rneğidir.
*)
type counter = {
  mutable count: int;
};;

let tick_counter = { count = 0 };;

let increment (crt: counter) =
  crt.count &amp;lt;- crt.count + 1
;;

increment tick_counter;;
increment tick_counter;;
increment tick_counter;;

Printf.printf "Current count: %d\n" tick_counter.count;;&lt;/pre&gt;
&lt;p&gt;&lt;img src="https://www.buraksenyurt.com/image.axd?picture=/2026/Nisan/ocaml_40.png" alt="" /&gt;&lt;/p&gt;
&lt;h3&gt;Refs&lt;/h3&gt;
&lt;p&gt;Tekil bir mutable değişken oluşturmak i&amp;ccedil;in ref enstr&amp;uuml;manı da kullanılabilir. ref esasında standart k&amp;uuml;t&amp;uuml;phanede tanımlanmış bir tip ve hatta bir record t&amp;uuml;r&amp;uuml;. İ&amp;ccedil;inde contents isimli bir alan i&amp;ccedil;eriyor. Hatta stdlib.ml dosyasına bakarsak aşağıdaki gibi tanımlandığını g&amp;ouml;r&amp;uuml;r&amp;uuml;z.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://www.buraksenyurt.com/image.axd?picture=/2026/Nisan/ocaml_07.png" alt="" /&gt;&lt;/p&gt;
&lt;p&gt;! ve := şeklinde tanımlanmış fonksiyonlar dikkatinizi &amp;ccedil;ekmiştir. ! operat&amp;ouml;r&amp;uuml; bir ref'in i&amp;ccedil;indeki değere erişmek i&amp;ccedil;in kullanılırken, := operat&amp;ouml;r&amp;uuml; ise bir ref'in i&amp;ccedil;indeki değeri değiştirmek i&amp;ccedil;in kullanılır. Yine incr ve decr fonksiyonları yardımıyla değer artırma ve azaltma işlemleri de yapılabilir. OCaml komut satırından bir deneme yapabiliriz.&lt;/p&gt;
&lt;pre class="brush:cpp;auto-links:false;toolbar:false" contenteditable="false"&gt;let counter = ref 0;;
!counter;;
counter := !counter + 1;;
!counter;;
counter := !counter + 1;;
!counter;;
counter := !counter + 1;;
!counter;;&lt;/pre&gt;
&lt;p&gt;&lt;img src="https://www.buraksenyurt.com/image.axd?picture=/2026/Nisan/ocaml_41.png" alt="" /&gt;&lt;/p&gt;
&lt;p&gt;Bu arada istersek ref t&amp;uuml;r&amp;uuml;n&amp;uuml; kendimiz de tasarlayabiliriz. Hatta kitap bunu gayet g&amp;uuml;zel bir şekilde &amp;ouml;rnekliyor. Bir deneyelim.&lt;/p&gt;
&lt;pre class="brush:cpp;auto-links:false;toolbar:false" contenteditable="false"&gt;(*
  İstersek buradaki ref record yapısını kendimiz de yapabiliriz.
  Burada polimorfik bir record yapısı tanımlayarak herhangi bir t&amp;uuml;rdeki değeri mutable olarak tutabiliriz.

  'a ifadesi, OCaml'da polimorfik t&amp;uuml;r parametresini temsil eder. 
  Bu, mutable_ref t&amp;uuml;r&amp;uuml;n&amp;uuml;n herhangi bir t&amp;uuml;rdeki değeri tutabileceği anlamına gelir. 
  x ile başlatılan mutable_ref fonksiyonu, verilen değeri mutable_ref t&amp;uuml;r&amp;uuml;nde bir record olarak d&amp;ouml;nd&amp;uuml;r&amp;uuml;r.
*)
type 'a mutable_ref = {
  mutable value: 'a;
};;
let mutable_ref x = { value = x };;
let get r = r.value;;
let set r x = r.value &amp;lt;- x;;
let incr r = r.value &amp;lt;- r.value + 1;;
let decr r = r.value &amp;lt;- r.value - 1;;

(* Deneyelim bakalım *)
let my_counter = mutable_ref 0;;
Printf.printf "My Counter: %d\n" (get my_counter);;
incr my_counter;;
Printf.printf "My Counter: %d\n" (get my_counter);;
set my_counter 10;;
Printf.printf "My Counter: %d\n" (get my_counter);;
decr my_counter;;
Printf.printf "My Counter: %d\n" (get my_counter);;&lt;/pre&gt;
&lt;p&gt;&lt;img src="https://www.buraksenyurt.com/image.axd?picture=/2026/Nisan/ocaml_42.png" alt="" /&gt;&lt;/p&gt;
&lt;p&gt;ref t&amp;uuml;r&amp;uuml; iterasyonlarda değiştirilebilir(mutable) state tutarken de kullanışlı olabilir. &amp;Ouml;rneğin bir listedeki elemanların ortalamasını hesaplamak i&amp;ccedil;in aşağıdaki gibi bir fonksiyon geliştirelim.&lt;/p&gt;
&lt;pre class="brush:cpp;auto-links:false;toolbar:false" contenteditable="false"&gt;let avrage lst =
  let sum = ref 0 in
  let count = ref 0 in
  List.iter (fun x -&amp;gt; sum := !sum + x; count := !count + 1) lst;
  if !count = 0 then None else Some (!sum / !count)

let numbers = [1; 2; 3; 4; 5; 10;];;
Printf.printf "Average: %d\n" (match avrage numbers with Some avg -&amp;gt; avg | None -&amp;gt; 0);;&lt;/pre&gt;
&lt;p&gt;&lt;img src="https://www.buraksenyurt.com/image.axd?picture=/2026/Nisan/ocaml_43.png" alt="" /&gt;&lt;/p&gt;
&lt;p&gt;average isimli fonksiyon i&amp;ccedil;erisinde yer alan sum ve count değişkenleri mutable olarak tanımlanmıştır ve List.iter fonksiyonu kullanılarak listenin her bir elemanı &amp;uuml;zerinde işlem yaparken bu değişkenlerin değerleri g&amp;uuml;ncellenmektedir. Tabii iter fonksiyonuna verilen anonim fonksiyon i&amp;ccedil;erisinde ! operat&amp;ouml;r&amp;uuml;n&amp;uuml; kullanarak ref'lerin i&amp;ccedil;indeki değerlere erişiyoruz ve := operat&amp;ouml;r&amp;uuml;n&amp;uuml; kullanarak bu değerleri g&amp;uuml;ncelliyoruz. Bir de in operat&amp;ouml;r&amp;uuml; ile karşılaştık tabii ki. Bu operat&amp;ouml;r sum ve count değişkenlerinin bulundukları fonksiyon bloğunda ge&amp;ccedil;erli olduğunu belirtmek i&amp;ccedil;in kullanılmakta. Yani scope belirlemek i&amp;ccedil;in kullanılır. in kullanımının farkını anlamak i&amp;ccedil;in &amp;ouml;zellikle Utop ekranında aşağıdaki gibi bir deneme yapalım.&lt;/p&gt;
&lt;pre class="brush:cpp;auto-links:false;toolbar:false" contenteditable="false"&gt;let value = 12;;
let point = 90 in point + 10;;
value;;
point;;
(*
  Line 1, characters 0-5:
  1 | point;;
      ^^^^^
  Error: Unbound value point
*)&lt;/pre&gt;
&lt;p&gt;&lt;img src="https://www.buraksenyurt.com/image.axd?picture=/2026/Nisan/ocaml_44.png" alt="" /&gt;&lt;/p&gt;
&lt;p&gt;value isimli değişken global scope'ta tanımlanmış ve bu nedenle herhangi bir yerden erişilebilir durumda. Ancak point isimli değişken in operat&amp;ouml;r&amp;uuml;n&amp;uuml;n kullanıldığı fonksiyon bloğu i&amp;ccedil;erisinde tanımlanmış ve bu nedenle sadece o blok i&amp;ccedil;erisinde ge&amp;ccedil;erli. Dolayısıyla point değişkenine global scope'tan erişmeye &amp;ccedil;alıştığımızda Unbound value hatası alıyoruz.&lt;/p&gt;
&lt;h3&gt;D&amp;ouml;ng&amp;uuml;s&amp;uuml;z Olmaz Tabii(for, while loops)&lt;/h3&gt;
&lt;p&gt;En basit &amp;ouml;rneklerle başlayalım. Bir saya&amp;ccedil; fonksiyonunu hem for hem de while d&amp;ouml;ng&amp;uuml;s&amp;uuml; kullanarak yazalım.&lt;/p&gt;
&lt;pre class="brush:cpp;auto-links:false;toolbar:false" contenteditable="false"&gt;let count_for n =
  for i = 1 to n do
    Printf.printf "%d," i
  done;
  Printf.printf "\n"

let count_while n =
  let i = ref 1 in
  while !i &amp;lt;= n do
    Printf.printf "%d," !i;
    i := !i + 1
  done;
  Printf.printf "\n"
;;

count_for 5;;
count_while 10;;&lt;/pre&gt;
&lt;p&gt;&lt;img src="https://www.buraksenyurt.com/image.axd?picture=/2026/Nisan/ocaml_45.png" alt="" /&gt;&lt;/p&gt;
&lt;p&gt;Şimdi &amp;ouml;rneklerimizi biraz daha eğlenceli hale getirelim. &amp;Ouml;rneğin, tamsayılardan oluşan bir listeyi Random mod&amp;uuml;l&amp;uuml;nden de yararlanarak belli aralıktaki rastgele sayılarla dolduralım.&lt;/p&gt;
&lt;pre class="brush:cpp;auto-links:false;toolbar:false" contenteditable="false"&gt;let average arr = 
  let sum = ref 0 in
  for i = 0 to Array.length arr - 1 do
    sum := !sum + arr.(i)
  done;
  float_of_int !sum /. float_of_int (Array.length arr)

let arr = generate_random_list 10 |&amp;gt; Array.of_list;;

Printf.printf "Random numbers (while): %s\nAverage: %f\n" 
  (String.concat "; " (List.map string_of_int (Array.to_list arr))) 
  (average arr);;&lt;/pre&gt;
&lt;p&gt;&lt;img src="https://www.buraksenyurt.com/image.axd?picture=/2026/Nisan/ocaml_46.png" alt="" /&gt;&lt;/p&gt;
&lt;p&gt;generate_random_list fonksiyonu n değerine g&amp;ouml;re bir liste d&amp;ouml;nd&amp;uuml;rmekte. Bu listenin elemanları 0 ile 99 arasındaki rastgele sayılarla dolduruluyor. &amp;Ouml;rnekte Random isimli bir mod&amp;uuml;l kullanıyoruz(Galiba her dilde bu mod&amp;uuml;l mevcut :D) Dikkat edilmesi gereken noktalardan birisi self_init() &amp;ccedil;ağrısı. Bunu yapmadığımız takdirde her seferinde aynı rastgele sayıların &amp;uuml;retildiğini g&amp;ouml;r&amp;uuml;r&amp;uuml;z. &amp;Uuml;retilen rastgele sayılar :: operat&amp;ouml;r&amp;uuml; yardımıyla numbers isimli listeye ekleniyor. Sonrasında !numbers ifadesiyle de oluşturulan liste d&amp;ouml;nd&amp;uuml;r&amp;uuml;l&amp;uuml;yor. Kodun son satırında ise bu listeyi ekrana bastırmak i&amp;ccedil;in String.concat ve List.map fonksiyonlarından yararlanıyoruz. List.map fonksiyonu, verilen bir fonksiyonu listenin her bir elemanına uygulayarak yeni bir liste oluşturur. Bu &amp;ouml;rnekte, string_of_int fonksiyonunu kullanarak her bir tamsayıyı string'e d&amp;ouml;n&amp;uuml;şt&amp;uuml;r&amp;uuml;yoruz. Ardından String.concat fonksiyonu ile bu string'leri "; " ile birleştirerek tek bir string elde ediyoruz ve bunu ekrana yazdırıyoruz. Aynı fonksiyonu bir de while d&amp;ouml;ng&amp;uuml;s&amp;uuml; kullanarak yazalım.&lt;/p&gt;
&lt;pre class="brush:cpp;auto-links:false;toolbar:false" contenteditable="false"&gt;let generate_random_list_while n =
  Random.self_init ();
  let numbers = ref [] in
  let i = ref 1 in
  while !i &amp;lt;= n do
    let random_number = Random.int 100 in
    numbers := random_number :: !numbers;
    i := !i + 1
  done;
  !numbers

let random_numbers_while = generate_random_list_while 10;;

Printf.printf "Random numbers (while): %s\n" (String.concat "; " (List.map string_of_int random_numbers_while));;&lt;/pre&gt;
&lt;p&gt;Bu fonksiyon da aynı şekilde n değerine g&amp;ouml;re bir liste d&amp;ouml;nd&amp;uuml;r&amp;uuml;yor ancak bu kez while d&amp;ouml;ng&amp;uuml;s&amp;uuml;n&amp;uuml; kullandık. D&amp;ouml;ng&amp;uuml; i&amp;ccedil;erisinde i değişkeni 1'den başlayarak n'ye kadar artırılır ve her iterasyonda rastgele bir sayı &amp;uuml;retilerek numbers listesine eklenir. Her iki fonksiyonun &amp;ccedil;alışma zamanına ait bir &amp;ccedil;ıktıyı da ekleyelim.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://www.buraksenyurt.com/image.axd?picture=/2026/Nisan/ocaml_47.png" alt="" /&gt;&lt;/p&gt;
&lt;p&gt;&amp;Ouml;rneklerimize devam edelim. Parametre olarak gelen Array i&amp;ccedil;indeki sayıların ortalamasını bulup d&amp;ouml;nd&amp;uuml;ren bir fonksiyonu hem for hem de while d&amp;ouml;ng&amp;uuml;s&amp;uuml; kullanarak yazalım.&lt;/p&gt;
&lt;pre class="brush:cpp;auto-links:false;toolbar:false" contenteditable="false"&gt;let average arr = 
  let sum = ref 0 in
  for i = 0 to Array.length arr - 1 do
    sum := !sum + arr.(i)
  done;
  float_of_int !sum /. float_of_int (Array.length arr)

let arr = generate_random_list 10 |&amp;gt; Array.of_list;;
Printf.printf "Average: %f\n" (average arr);;&lt;/pre&gt;
&lt;p&gt;&lt;img src="https://www.buraksenyurt.com/image.axd?picture=/2026/Nisan/ocaml_48.png" alt="" /&gt;&lt;/p&gt;
&lt;p&gt;average isimli fonksiyon, arr isimli bir parametre almakta. Tabii bunun bir Array olduğunu varsayıyoruz. sum isimli değişkeni mutable olarak tanımladık zira bir toplam değerine ihtiyacımız var. Sonrasında bir for d&amp;ouml;ng&amp;uuml;s&amp;uuml; yardımıyla dizi elemanlarını arka arkaya toplatıyoruz. ! operat&amp;ouml;r&amp;uuml; ile sum dizisi i&amp;ccedil;indeki değere erişiyoruz ve := operat&amp;ouml;r&amp;uuml;n&amp;uuml; kullanarak bu değeri g&amp;uuml;ncelliyoruz. D&amp;ouml;ng&amp;uuml; tamamlandıktan sonra toplam değeri dizi uzunluğuna b&amp;ouml;lerek ortalamayı hesaplıyoruz. Dikkat edelim, b&amp;ouml;lme işlemi sırasında tam sayı b&amp;ouml;lmesi yapmamak i&amp;ccedil;in hem toplamı hem de dizi uzunluğunu float_of_int fonksiyonu ile float t&amp;uuml;r&amp;uuml;ne d&amp;ouml;n&amp;uuml;şt&amp;uuml;r&amp;uuml;yoruz. Sonrasında bu fonksiyonu kullanarak bir dizi oluşturup ortalamasını ekrana yazdırıyoruz.&lt;/p&gt;
&lt;p&gt;Peki bu fonksiyona alakasız bir veri t&amp;uuml;r&amp;uuml; g&amp;ouml;ndersek ne olur, &amp;ouml;rneğin metinsel bir ifade...&lt;/p&gt;
&lt;pre class="brush:cpp;auto-links:false;toolbar:false" contenteditable="false"&gt;let himm = "Bu &amp;ouml;rneklerde for ve while d&amp;ouml;ng&amp;uuml;lerini kullanarak listeler ve diziler &amp;uuml;zerinde işlemler yaptık.";;
let avg = average himm;;&lt;/pre&gt;
&lt;p&gt;VS Code arabiriminden baktığımızda da &amp;ccedil;alışma zamanında denediğimizde de bir hata ile karşılaşırız.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://www.buraksenyurt.com/image.axd?picture=/2026/Nisan/ocaml_49.png" alt="" /&gt;&lt;/p&gt;
&lt;p&gt;Sıradaki fonksiyonumuz iki boyutlu bir matris &amp;uuml;retiyor. Her bir elemanı 0 veya 1 olabilen bir matris. Basit bir oyun sahasının iki boyutlu g&amp;ouml;r&amp;uuml;n&amp;uuml;m&amp;uuml;nde duvar veya yol kararını vermeyi kolaylaştırabilecek &amp;ccedil;ok basit bir &amp;ouml;rnek.&lt;/p&gt;
&lt;pre class="brush:cpp;auto-links:false;toolbar:false" contenteditable="false"&gt;let generate_matrix row_count col_count =
  Random.self_init ();
  let matrix = Array.make_matrix row_count col_count 0 in
  for i = 0 to row_count - 1 do
    for j = 0 to col_count - 1 do
      matrix.(i).(j) &amp;lt;- Random.int 2
    done;
  done;
  matrix

let matrix = generate_matrix 5 8;;
Printf.printf "Generated Matrix:\n";
Array.iter (fun row -&amp;gt;
  Array.iter (fun value -&amp;gt; Printf.printf "%d " value) row;
  Printf.printf "\n"
) matrix;;&lt;/pre&gt;
&lt;p&gt;Burada yardımcı birka&amp;ccedil; fonksiyon da kullandık. &amp;Ouml;rneğin iki boyutlu bir matris dizisini oluşturmak i&amp;ccedil;in make_matrix fonksiyonuna başvurduk. İki boyutlu dizinin elemanlarını satır s&amp;uuml;tun bazında dolaşmak i&amp;ccedil;inse klasik i&amp;ccedil; i&amp;ccedil;e for d&amp;ouml;ng&amp;uuml;s&amp;uuml; kullandık. Doğrudan dizinin elemanlarına atama yapıldığından &amp;lt;- operat&amp;ouml;r&amp;uuml; ile 0 ve 1 şeklinde &amp;uuml;retilen rastgele sayıları atarız. Random.int fonksiyonuna 2 değerini verdiğimizde sadece 0 veya 1 değerleri &amp;uuml;retilebilir. Fonksiyon &amp;ccedil;ıktısı olan matrisi ekrana yazdırmak i&amp;ccedil;in yine i&amp;ccedil; i&amp;ccedil;e for d&amp;ouml;ng&amp;uuml;s&amp;uuml; kullanabiliriz ama fonksiyonel bir yaklaşımla ilerlemek de olduk&amp;ccedil;a şık. Nitekim Array mod&amp;uuml;l&amp;uuml;nde yer alan iter fonksiyonu ile dizinin her bir elemanına uygulanacak bir fonksiyon &amp;ccedil;alıştırabiliriz. Dolayısıyla dış iterasyon, row'u parametre olarak alan ve dolayısıyla kolonları dolaşmayı sağlayacak anonim bir fonksiyon kullanıyor. İ&amp;ccedil; iterasyon ise value'yu parametre olarak alan ve bu değeri ekrana yazdıran bir anonim fonksiyon. Her satırın sonunda ise yeni bir satır başlatmak i&amp;ccedil;in Printf.printf "\n" ifadesi yer almakta. Aşağıda &amp;ccedil;alışma zamanına ait &amp;ouml;rnek bir g&amp;ouml;r&amp;uuml;nt&amp;uuml; yer alıyor.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://www.buraksenyurt.com/image.axd?picture=/2026/Nisan/ocaml_50.png" alt="" /&gt;&lt;/p&gt;
&lt;h3&gt;Derleyerek &amp;Ccedil;alıştırmak&lt;/h3&gt;
&lt;p&gt;Real World OCaml kitabı bir sonraki b&amp;ouml;l&amp;uuml;me ge&amp;ccedil;meden &amp;ouml;nce "A Complete Program" başlığında basit bir program &amp;ouml;rneği anlatıyor. Bu &amp;ouml;rnekte ocaml kodunun derlenerek &amp;ccedil;alıştırılması s&amp;ouml;z konusu. Derleme işlemi i&amp;ccedil;in dune&lt;em&gt;(Gezegen olan değil :D)&lt;/em&gt; aracını kullanıyor. Burada temel ama&amp;ccedil; tek başına &amp;ccedil;alıştırılabilir(standalone) bir program oluşturmak. &amp;Ouml;ncelikle kodlarımızı oluşturalım. Bu ama&amp;ccedil;la standalone isimli bir klas&amp;ouml;r oluşturdum ve i&amp;ccedil;erisine rand_10.ml isimli bir dosya ekledim. Kolaya ka&amp;ccedil;arak daha &amp;ouml;nceden ele aldığımız bir fonksiyonu değerlendirebiliriz. Rastgele sayılardan oluşan 10 elemanlı bir liste oluşturuyoruz.&lt;/p&gt;
&lt;pre class="brush:cpp;auto-links:false;toolbar:false" contenteditable="false"&gt;let generate_random_list n =
  Random.self_init ();
  let numbers = ref [] in
  for _ = 1 to n do
    let random_number = Random.int 100 in
    numbers := random_number :: !numbers
  done;
  !numbers

let () = 
  let random_numbers = generate_random_list 10 in
  Printf.printf "Random numbers: %s\n" (String.concat "; " (List.map string_of_int random_numbers))&lt;/pre&gt;
&lt;p&gt;Tabii program kodunda dikkat etmemiz gereken şeyler de var. &amp;Ouml;ncelikle artık ;; operat&amp;ouml;r&amp;uuml;n&amp;uuml; kullanmadığımıza dikkat edelim. Diğer yandan bir de let () = ifadesi var. Bunu programın giriş noktası olarak d&amp;uuml;ş&amp;uuml;nebiliriz. Yani, program &amp;ccedil;alıştığında ilk olarak bu kısım &amp;ccedil;alışacaktır.&lt;/p&gt;
&lt;p&gt;Derleme işleminden &amp;ouml;nce bu klas&amp;ouml;rde oluşturmamız gereken iki dosya daha var; dune ve dune-project. İkisinin de uzantısı yoktur ve derlenecek programla ilgili birtakım konfig&amp;uuml;rasyon bilgilerini i&amp;ccedil;erirler(Tahmin edileceği &amp;uuml;zere). dune i&amp;ccedil;eriğini ş&amp;ouml;yle oluşturabiliriz.&lt;/p&gt;
&lt;pre class="brush:plain;auto-links:false;toolbar:false" contenteditable="false"&gt;(executable
 (name rand_10))&lt;/pre&gt;
&lt;p&gt;Kod dosyasının adı rand_10.ml olduğu i&amp;ccedil;in name kısmına da rand_10 yazdık. dune-project dosyasının i&amp;ccedil;eriği ise olduk&amp;ccedil;a basit.&lt;/p&gt;
&lt;pre class="brush:plain;auto-links:false;toolbar:false" contenteditable="false"&gt;(lang dune 3.0)&lt;/pre&gt;
&lt;p&gt;Bu dosya ile dune aracının hangi s&amp;uuml;r&amp;uuml;m&amp;uuml;n&amp;uuml;n kullanılacağını belirtiyoruz. Bu adımlardan sonra kodu derleyip &amp;ccedil;alıştırabiliriz. Normalde sadece dune build komutu yeterli olur ancak derleme sırasındaki detayları da g&amp;ouml;rmek istersek verbose arg&amp;uuml;manını kullanabiliriz. Programı &amp;ccedil;alıştırmak i&amp;ccedil;in yine dune aracından yararlanıyoruz.&lt;/p&gt;
&lt;pre class="brush:bash;auto-links:false;toolbar:false" contenteditable="false"&gt;# Derleme işlemi ve detaylar
dune build --display=verbose
# Programın &amp;ccedil;alıştırılması
dune exec ./rand_10.exe&lt;/pre&gt;
&lt;p&gt;ve işte &amp;ccedil;alışma zamanı &amp;ccedil;ıktılarımız.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://www.buraksenyurt.com/image.axd?picture=/2026/Nisan/ocaml_13.png" alt="" /&gt;&lt;/p&gt;
&lt;p&gt;&amp;Ouml;rneği ubuntu platformunda da benzer şekilde derleyebiliriz. Ben WSL &amp;uuml;zerinden denedim ve aşağıdaki gibi bir &amp;ccedil;ıktı aldım.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://www.buraksenyurt.com/image.axd?picture=/2026/Nisan/ocaml_51.png" alt="" /&gt;&lt;/p&gt;
&lt;h3&gt;Alcotest ile Birim Test Yazmak&lt;/h3&gt;
&lt;p&gt;OCaml kodlarını test etmek i&amp;ccedil;in birka&amp;ccedil; y&amp;ouml;ntem var. Bunlardan birisi () ile oluşturulan program giriş noktasında klasik terminal &amp;ccedil;ıktıları ile ilerlemek. Ancak birim test(unit test) yazmak elbette ki daha profesyonel bir yaklaşım ama daha da &amp;ouml;nemlisi bir standart. Bu ama&amp;ccedil;la dune ile entegre &amp;ccedil;alışabilen Alcotest isimli bir k&amp;uuml;t&amp;uuml;phane bulunuyor. &amp;Ouml;ncelikle bu aracı opam ile sisteme y&amp;uuml;klemek gerekiyor(Windows veya Linux fark etmez).&lt;/p&gt;
&lt;pre class="brush:cpp;auto-links:false;toolbar:false" contenteditable="false"&gt;opam install alcotest&lt;/pre&gt;
&lt;p&gt;&lt;img src="https://www.buraksenyurt.com/image.axd?picture=/2026/Nisan/ocaml_14.png" alt="" /&gt;&lt;/p&gt;
&lt;p&gt;Evet yanlış g&amp;ouml;rm&amp;uuml;yorsunuz, terminalde sevimli bir deve emojisi var :D&lt;/p&gt;
&lt;p&gt;Genel yaklaşım library haline getirilmiş kod dosyaları i&amp;ccedil;in test kelimesi ile başlayan ocaml dosyaları oluşturmak. &amp;Ouml;rneğin testing isimli bir klas&amp;ouml;r i&amp;ccedil;erisinde math.ml isimli bir mod&amp;uuml;l oluşturduğumuzu, bu mod&amp;uuml;l&amp;uuml; bir k&amp;uuml;t&amp;uuml;phane olarak tasarlayıp birim testlerini yazmak istediğimizi d&amp;uuml;ş&amp;uuml;nelim. &amp;Ouml;rnek olarak math mod&amp;uuml;l&amp;uuml;nde aşağıdaki iki basit fonksiyona yer verebiliriz.&lt;/p&gt;
&lt;pre class="brush:cpp;auto-links:false;toolbar:false" contenteditable="false"&gt;(* Fakt&amp;ouml;riyel hesaplama fonksiyonu *)
let rec factorial n =
  if n &amp;lt; 0 then failwith "Negative input not allowed for factorial"
  else if n = 0 then 1
  else n * factorial (n - 1)

(* &amp;Uuml;s alma fonksiyonu *)
let rec power base exp =
  if exp &amp;lt; 0 then failwith "Negative exponent not allowed"
  else if exp = 0 then 1
  else base * power base (exp - 1)&lt;/pre&gt;
&lt;p&gt;&amp;Ouml;ncelikle bu k&amp;uuml;t&amp;uuml;phanenin bir library olarak ele alınması lazım ve ayrıca test dosyalarının da bu k&amp;uuml;t&amp;uuml;phaneyi kullanabilmesi i&amp;ccedil;in yapılandırılması gerekiyor. Bu nedenle dune dosyasının i&amp;ccedil;eriğini aşağıdaki gibi hazırlamalıyız. Burada math mod&amp;uuml;l&amp;uuml;n&amp;uuml; bir k&amp;uuml;t&amp;uuml;phane olarak tanımlıyoruz. modules ile başlayan kısımlar k&amp;uuml;t&amp;uuml;phaneye dahil edilecek mod&amp;uuml;lleri de belirtmekte. Ayrıca Alcotest k&amp;uuml;t&amp;uuml;phanesini test kısmında kullanmak &amp;uuml;zere libraries kısmında bildiriyoruz.&lt;/p&gt;
&lt;pre class="brush:plain;auto-links:false;toolbar:false" contenteditable="false"&gt;(library
 (name math)
 (modules math))

(test
 (name test_math)
 (modules test_math)
 (libraries math alcotest))&lt;/pre&gt;
&lt;p&gt;Şimdi de birim testleri i&amp;ccedil;eren test_math.ml dosyasını oluşturalım. Burada olası t&amp;uuml;m durumları test etmekte yarar var elbette ki ancak ben &amp;ouml;rnek olması a&amp;ccedil;ısından birka&amp;ccedil; tanesine yer verdim.&lt;/p&gt;
&lt;pre class="brush:cpp;auto-links:false;toolbar:false" contenteditable="false"&gt;let test_factorial () =
  let value = 5 in
  let expected = 120 in
  let result = Math.factorial value in
  Alcotest.(check int) "factorial of 5" expected result

let test_power () =
  let base = 2 in
  let exp = 3 in
  let expected = 8 in
  let result = Math.power base exp in
  Alcotest.(check int) "power of 2^3" expected result

let test_factorial_negative () =
  let value = -1 in
  Alcotest.check_raises "factorial of negative number" (Failure "Negative input not allowed for factorial")
    (fun () -&amp;gt; ignore (Math.factorial value))

let () =
  let open Alcotest in
  run "Math Tests" [
    "Math Tests", [
       test_case "factorial of 5" `Quick test_factorial;
       test_case "power of 2^3" `Quick test_power;
       test_case "factorial of negative number" `Quick test_factorial_negative;   
    ];
  ];&lt;/pre&gt;
&lt;p&gt;Testleri &amp;ccedil;alıştırmak i&amp;ccedil;in tek yapmamız gereken aşağıdaki terminal komutunu işletmek.&lt;/p&gt;
&lt;pre class="brush:bash;auto-links:false;toolbar:false" contenteditable="false"&gt;dune runtest&lt;/pre&gt;
&lt;p&gt;&lt;img src="https://www.buraksenyurt.com/image.axd?picture=/2026/Nisan/ocaml_15.png" alt="" /&gt;&lt;/p&gt;
&lt;p&gt;İşte bu da Ubuntu &amp;ccedil;ıktısı.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://www.buraksenyurt.com/image.axd?picture=/2026/Nisan/ocaml_52.png" alt="" /&gt;&lt;/p&gt;
&lt;p&gt;Burada dikkate değer bir durum daha var. Dune, Incremental Build ve caching mekanizmaları sayesinde sadece değişen dosyaları derleyerek testleri &amp;ccedil;alıştırır. Dolayısıyla kod tabanında değişiklik olmadığında testler tekrardan &amp;ccedil;alıştırılmaz. Yani kodun aynı olması testlerin de aynı kalacağı anlamına gelir ki bu durumda kaynakları boşa israf etmenin de bir alemi yoktur. Burada dune kod dosyalarının imzalarını takip ederek bir karara varır. Ancak yine de testleri koşmaya zorlayabiliriz. Bunun i&amp;ccedil;in --force arg&amp;uuml;manını kullanmak yeterlidir.&lt;/p&gt;
&lt;pre class="brush:bash;auto-links:false;toolbar:false" contenteditable="false"&gt;dune runtest --force
# veya
dune runtest -f&lt;/pre&gt;
&lt;p&gt;&lt;img src="https://www.buraksenyurt.com/image.axd?picture=/2026/Nisan/ocaml_53.png" alt="" /&gt;&lt;/p&gt;
&lt;h2&gt;Biraz da Felsefe&lt;/h2&gt;
&lt;p&gt;En zor kısım burası. Ş&amp;ouml;yle bir soru soralım. Neden bazı diller Method Overloading kabiliyeti sunarken bazıları sunmuyor? Yazının bundan sonraki kısmında bu soruya cevap aramayacağız ama ger&amp;ccedil;ekten dilin genleri ve felsefesi ile alakalı konuları kavramaya &amp;ccedil;alışacağız. Tipler ile başlayalım.&lt;/p&gt;
&lt;h3&gt;Hata Yapmayı İmkansız Kılan Tip Desteği(Type Safety değil Type Expressiveness)&lt;/h3&gt;
&lt;p&gt;Verinin alabileceği t&amp;uuml;m durumlar ilişkili olduğu tip tarafından tanımlanır. &amp;Ccedil;ok klasik bir &amp;ouml;rnek &amp;uuml;zerinden ilerleyelim(Rust tarafında da kullandığım bir teori ki OCaml'dan geliyormuş :D )&lt;/p&gt;
&lt;pre class="brush:cpp;auto-links:false;toolbar:false" contenteditable="false"&gt;type payment_type =
  | Cash
  | CreditCard of string * float
  | Crypto of string * bool (*Vault adresi ile ağ onayını tutar*)

let process_payment pay_t =
  match pay_t with
  | Cash -&amp;gt; "Processing cash payment"
  | CreditCard (number, amount) -&amp;gt; Printf.sprintf "Processing credit card payment of %.2f for card %s" amount number
  | Crypto (address, confirmed) -&amp;gt;
      if confirmed then
        Printf.sprintf "Processing crypto payment to address %s" address
      else
        Printf.sprintf "Crypto payment to address %s is pending confirmation" address

let bills_payment = CreditCard ("1234-5678-9012-3456", 150.00);;
let () = 
  process_payment bills_payment
  |&amp;gt; print_endline&lt;/pre&gt;
&lt;p&gt;&amp;Ouml;nce &amp;ccedil;alışma zamanına bir bakalım.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://www.buraksenyurt.com/image.axd?picture=/2026/Nisan/ocaml_54.png" alt="" /&gt;&lt;/p&gt;
&lt;p&gt;payment_type i&amp;ccedil;erisinde kullandığımız CreditCard tipini ele alalım. Kredi kartından bahsedebilmemiz i&amp;ccedil;in string ve float t&amp;uuml;r&amp;uuml;nde iki bilgiye daha ihtiyacımız vardır. Bir başka deyişle CreditCard sadece bir etiket değil aynı zamanda bu iki bilgiyi de i&amp;ccedil;eren bir yapıdır. Dolayısıyla CreditCard'ı kullanarak bir &amp;ouml;deme işlemi ger&amp;ccedil;ekleştirebilmek i&amp;ccedil;in bu iki bilgiyi de sağlamamız gerekir. Sadece b&amp;ouml;yle bir durumda o veriye erişebiliriz. Bir başka mesele de pattern match kullanımıdır. &amp;Ouml;rneğin herhangi bir varyantı yazmazsak derleyici kızacaktır.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://www.buraksenyurt.com/image.axd?picture=/2026/Nisan/ocaml_55.png" alt="" /&gt;&lt;/p&gt;
&lt;p&gt;Bir başka deyişle derleyici tasarımımızın bir ortağı gibi hareket eder. Bir varyantı unutmamıza izin vermez. Rust dili a&amp;ccedil;ısından bakarsak bu yapının bence &amp;ccedil;ok daha şık bir şekli olan enum yapısı var. &amp;Uuml;stelik Options/Result gibi t&amp;uuml;rler de bu felsefeyi(anlatabildim mi veya anlayabildim mi işte b&amp;uuml;t&amp;uuml;n mesele bu :D) &amp;ccedil;ok g&amp;uuml;zel bir şekilde ortaya koyuyor. O zaman mottomuzu s&amp;ouml;yl&amp;uuml;yoruz; Tip g&amp;uuml;venliği değil tip ifade g&amp;uuml;c&amp;uuml;(type expressiveness).&lt;/p&gt;
&lt;h3&gt;Olabildiğince Fonksiyonel&lt;/h3&gt;
&lt;p&gt;OCaml m&amp;uuml;mk&amp;uuml;n olduğunca fonksiyonel olmayı hedefler. Yani her şeyi immutable yazmayı &amp;ouml;nerir. Lakin performans veya mantık gerektiren şeyler s&amp;ouml;z konusuysa imperative ara&amp;ccedil;ları da emrimize amade eder. Bu noktada Haskell gibi dillerden &amp;ouml;nemli &amp;ouml;l&amp;ccedil;&amp;uuml;de ayrıldığı s&amp;ouml;ylenir ki tartışmaya a&amp;ccedil;ıktır(Neden, &amp;ccedil;&amp;uuml;nk&amp;uuml; Haskell ile hi&amp;ccedil; tecr&amp;uuml;bem yok) &amp;Ouml;rnek kodlarda ele aldık ama saya&amp;ccedil; artırıcı meselesini tekrar masaya yatırabiliriz. Aşağıdaki kod par&amp;ccedil;asını ele alalım.&lt;/p&gt;
&lt;pre class="brush:cpp;auto-links:false;toolbar:false" contenteditable="false"&gt;(*Saf fonksiyonel yaklaşım*)
let rec sum list = function 
  | [] -&amp;gt; list 
  | x :: xs -&amp;gt; sum (list + x) xs

(* Pragmatik yaklaşım *)
let incrementer () =
  let count = ref 0 in
  fun () -&amp;gt; 
    count := !count + 1;
    !count

let () =
  let inc = incrementer () in
  print_endline (string_of_int (inc ()));
  print_endline (string_of_int (inc ()));
  print_endline (string_of_int (inc ()));&lt;/pre&gt;
&lt;p&gt;İlk &amp;ouml;nce &amp;ccedil;alışma zamanını bir değerlendirelim.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://www.buraksenyurt.com/image.axd?picture=/2026/Nisan/ocaml_56.png" alt="" /&gt;&lt;/p&gt;
&lt;p&gt;sum i&amp;ccedil;in tam bir fonksiyonel yaklaşımın s&amp;ouml;z konusu olduğunu s&amp;ouml;yleyebiliriz. Hatta tam anlamıyla matematiksel bir zarafet sunar. Zira mutable bir state yoktur, recursive &amp;ccedil;alışan fonksiyon her &amp;ccedil;ağrıda yeni bir değer d&amp;ouml;nd&amp;uuml;r&amp;uuml;r ve bunlar arka arkaya toplanır. incrementer fonksiyonunda kullanılan ref keyword bir referans kutusu oluşturur ve := operat&amp;ouml;r&amp;uuml; ile bu kutunun i&amp;ccedil;indeki değeri değiştirebiliriz. Bir başka deyişle bu fonksiyon state değiştiren bir fonksiyondur ve bu nedenle fonksiyonel değil, pragmatik bir yaklaşım sergiler.&lt;/p&gt;
&lt;p&gt;Aslında buradaki felsefeyi ş&amp;ouml;yle d&amp;uuml;ş&amp;uuml;nebiliriz. Bazı senaryolarda her şeyin saf bir fonksiyon ile yazılması m&amp;uuml;mk&amp;uuml;n değildir. &amp;Ouml;rneğin, milyonlarca finansal işlemin yapıldığı ger&amp;ccedil;ek zamanlı bir uygulamada(Bence tam bu noktada &lt;a href="https://ocaml.org/success-stories/large-scale-trading-system" target="_blank"&gt;Jane Street'in hikayesine&lt;/a&gt; bakılabilir) veya &amp;ccedil;ok oyunculu bir oyunda mutable state'lere ihtiya&amp;ccedil; duyar ve hatta performans ararız. OCaml b&amp;ouml;yle durumlara da hazırlıklıdır ve sağladığı mutable ara&amp;ccedil;ları kullanarak pragmatik bir şekilde ilerleyebiliriz. Ancak m&amp;uuml;mk&amp;uuml;n olduğunca fonksiyonel bir yaklaşım benimsemek kodun daha temiz, anlaşılır ve hatasız olmasına da yardımcı olabilir. Bu nedenle OCaml, fonksiyonel programlama paradigmalarını teşvik ederken aynı zamanda pragmatik ihtiya&amp;ccedil;lara da cevap verebilecek esneklikte tasarlanmıştır. Rust a&amp;ccedil;ısından bakarsak ger&amp;ccedil;ekten de benzer bir felsefeye sahiptir. Her şey varsayılan olarak immutable'dır ve mutable olması gerekiyorsa bu a&amp;ccedil;ık&amp;ccedil;a belirtilmelidir. Ancak Rust'ın sahip olduğu ownership ve borrowing mekanizmaları sayesinde mutable state'ler &amp;uuml;zerinde daha sıkı kontrol sağlanır ve bu da g&amp;uuml;venli bir şekilde mutable state'ler kullanmamıza olanak tanır ki bu Rust'ı &amp;ccedil;ekici kılan bir başka şeydir.&lt;/p&gt;
&lt;p&gt;Konuyu pekiştirmek adına bir başka &amp;ouml;rneğe bakalım.&lt;/p&gt;
&lt;pre class="brush:cpp;auto-links:false;toolbar:false" contenteditable="false"&gt;let big_data = [|10.4; 20.5; 30.6; 1.0; 3.14 |]

let scale_data factor data =
  for i = 0 to Array.length data - 1 do
    data.(i) &amp;lt;- data.(i) *. factor
  done

let () =
  print_endline "Original data:";
  Array.iter (Printf.printf "%.2f ") big_data;
  print_endline "\nScaling data by a factor of 2.0...";
  scale_data 2.0 big_data;
  print_endline "Scaled data:";
  Array.iter (Printf.printf "%.2f ") big_data;
  print_endline ""&lt;/pre&gt;
&lt;p&gt;&lt;img src="https://www.buraksenyurt.com/image.axd?picture=/2026/Nisan/ocaml_57.png" alt="" /&gt;&lt;/p&gt;
&lt;p&gt;Tabii bu &amp;ccedil;ok k&amp;uuml;&amp;ccedil;&amp;uuml;k bir veri k&amp;uuml;mesini ele alıyor. Elimizde milyon elemanlı bir veri de olabilirdi. Vekt&amp;ouml;rel sayıların olduğu bir dizi mesela. Tek bir değeri g&amp;uuml;ncellemek gerekiyorsa bile diziyi kopyalamak fonksiyonel yaklaşım a&amp;ccedil;ısından &amp;ccedil;ok maliyetlidir. Dolayısıyla dizinin elemanını olduğu yerde değiştirmek gerekir. Yukarıdaki kod par&amp;ccedil;asında OCaml'ın bunu nasıl sağladığını bir kere daha g&amp;ouml;r&amp;uuml;yoruz. OCaml, Array ve Bytes gibi yapıları doğrudan mutable olarak tasarlamıştır. &amp;lt;- operat&amp;ouml;r&amp;uuml; tam anlamıyla emirsel(imperative) bir şekilde &amp;ccedil;alışır ve dizinin elemanlarını doğrudan değiştirmemize olanak tanır. Yani yerinde veriyi değiştirmemiz m&amp;uuml;mk&amp;uuml;nd&amp;uuml;r.&lt;/p&gt;
&lt;p&gt;Buradan şu sonuca varabiliriz; belki de yazacağımız algoritma imperative yaklaşım gerekleri ile daha hızlı &amp;ccedil;alışıyordur. OCaml buna destek verir. Dolayısıyla elimizde y&amp;uuml;ksek seviyeli dillerin zarifliğine sahip(her ne kadar sentaksı zorlayıcı olsa da kavramsal olarak &amp;ouml;yle) ama gerektiğinde d&amp;uuml;ş&amp;uuml;k seviyeli dillerin sunduğu bellek performansına yakın destek veren bir programlama dili var ve Rust bence bu &amp;ouml;zellikleri bir &amp;uuml;st noktaya taşıyıp bellek tarafında ger&amp;ccedil;ekten g&amp;uuml;venli kalabilmenin yolunu da a&amp;ccedil;mış durumda.&lt;/p&gt;
&lt;h3&gt;Tony Hoare Anısına&lt;/h3&gt;
&lt;p&gt;Yazı yazmamdan kısa bir s&amp;uuml;re &amp;ouml;nce aramızdan ayrılan, bilgisayar bilimlerinin efsane ismi Tony Hoare'ın milyar dolarlık hata olarak da isimlendirdiği Null Pointer Exception, programlama dillerinde sık&amp;ccedil;a karşılaşılan ve ciddi sorunlara yol a&amp;ccedil;abilen bir durumu anlatır. Uğraştığımız pek &amp;ccedil;ok programlama dilinde null diye bir kavram var. Kısaca, bir değişkenin değeri yoksa ona null atayabiliriz şeklinde ifade etsek yeridir. Diğer yandan bu durum kodu yazarken bir null kontrol&amp;uuml; yapmamızı da gerektirir(null değer taşıyabilen bir referansın kullanıldığı her yerde null olup olmadığını kontrol ederek hareket etmek). OCaml bu konuya ş&amp;ouml;yle bir felsefe ile yaklaşıyor; "Eğer bir hata oluşacaksa &amp;ccedil;alışma zamanında değil derleme zamanında olmalıdır". Hımmm... Yani... O zaman null değer yoktur diyebiliriz. Evet, ger&amp;ccedil;ekten de null diye bir kavram **OCaml** dilinde yok. Bunun yerine programcıya sunulan bir se&amp;ccedil;enek var; Option...&lt;/p&gt;
&lt;p&gt;Ş&amp;ouml;yle bir senaryo &amp;uuml;zerinden ilerleyelim. Bir identity değerine g&amp;ouml;re kullanıcı aradığımızı varsayalım. Bunu ş&amp;ouml;yle yorumlamalıyız; "Kullanıcı ya vardır ya da yoktur"&lt;/p&gt;
&lt;pre class="brush:cpp;auto-links:false;toolbar:false" contenteditable="false"&gt;(*  Aboneleri bir Record tipi olarak tanımladık *)
type subscriber ={
  id:int;
  name:string;
  email:string;
}

(* 
  Liste t&amp;uuml;r&amp;uuml;nden hayali bir veritabanı ya da mock liste.
*)
let database = [
  {id=1001; name="John Doe"; email="john.doe@azon.com"};
  {id=1002; name="Jane Doe"; email="jane.doe@azon.com"};
  {id=1003; name="Mario"; email="mario@azon.com"};
]

(*
  Abone ID'sine g&amp;ouml;re abone arayan bir fonksiyon.
  Eğer abone bulunursa Some subscriber d&amp;ouml;ner, bulunmazsa None d&amp;ouml;ner.

  &amp;Ouml;zellikle fonksiyonun d&amp;ouml;n&amp;uuml;ş tipine dikkat edelim: subscriber option. 
  Bu, fonksiyonun ya bir subscriber d&amp;ouml;nd&amp;uuml;receği ya da hi&amp;ccedil;bir şey d&amp;ouml;nd&amp;uuml;rmeyeceği anlamına gelir.
*)
let rec find_subscriber_by_id id subscribers =
  match subscribers with
  | [] -&amp;gt; None
  | current :: rest -&amp;gt;
      if current.id = id then Some current
      else find_subscriber_by_id id rest

(*
  Burada derleyici bizi t&amp;uuml;m senaryolara bakmaya zorlar.
*)
let say_hello id = let result = find_subscriber_by_id id database in
  match result with
  | Some subscriber -&amp;gt; Printf.sprintf "Hello, %s!" subscriber.name
  | None -&amp;gt; "Subscriber not found."

(* Test *)
let () =
  let message1 = say_hello 1002 in
  let message2 = say_hello 9999 in
  print_endline message1;  (* Output: Hello, Jane Doe! *)
  print_endline message2   (* Output: Subscriber not found. *) &lt;/pre&gt;
&lt;p&gt;&amp;Ouml;ncelikle kodun &amp;ccedil;alışma zamanı &amp;ccedil;ıktısına bakalım.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://www.buraksenyurt.com/image.axd?picture=/2026/Nisan/ocaml_58.png" alt="" /&gt;&lt;/p&gt;
&lt;p&gt;Rust tarafından buraya ge&amp;ccedil;mek ger&amp;ccedil;ekten &amp;ccedil;ok enteresan bir deneyim. Zira yıllardır Rust dilinde Option, Result gibi &amp;ouml;nemli veri yapılarını hangi felsefeden geldiğini &amp;ccedil;ok da anlamadan kullanmışım. Pişmanım :D Neyse neyse... Kodda bir abone listesinden id bazlı kullanıcı araması yaptığımız recursive bir fonksiyon bulunuyor. Kodun sentaksına bakarken &amp;ccedil;ok fazla bir şey anlamayabiliriz ama VS Code ya da Utop fonksiyon imzasında option d&amp;ouml;nd&amp;uuml;ğ&amp;uuml;n&amp;uuml; a&amp;ccedil;ık&amp;ccedil;a ilan eder.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://www.buraksenyurt.com/image.axd?picture=/2026/Nisan/ocaml_59.png" alt="" /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src="https://www.buraksenyurt.com/image.axd?picture=/2026/Nisan/ocaml_60.png" alt="" /&gt;&lt;/p&gt;
&lt;p&gt;En &amp;ouml;nemli par&amp;ccedil;a say_hello fonksiyonunda yer alan match ifadesidir. Burada result değişkeninin ya bir abone i&amp;ccedil;erdiği ya da hi&amp;ccedil;bir şey i&amp;ccedil;ermediği durumlar ele alınır. Velev ki match ifadesini eksik yazdık. İşte gelen tepkiler...&lt;/p&gt;
&lt;p&gt;&lt;img src="https://www.buraksenyurt.com/image.axd?picture=/2026/Nisan/ocaml_61.png" alt="" /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src="https://www.buraksenyurt.com/image.axd?picture=/2026/Nisan/ocaml_62.png" alt="" /&gt;&lt;/p&gt;
&lt;p&gt;G&amp;ouml;r&amp;uuml;ld&amp;uuml;ğ&amp;uuml; &amp;uuml;zere ka&amp;ccedil;ma şansımız yok. None olasılığını da mutlaka kod i&amp;ccedil;erisinde değerlendirmemiz gerekiyor. Buradan hareketle bir değerin olmayışının aslında somut bir veri tipi olduğunu s&amp;ouml;yleyebiliriz. Yani bir değerin olmayışı da bir durumdur, bu durumun bir tipi vardır ve bu tipin adı option'dır. Diğer yandan, find_subscriber_by_id fonksiyonu bize bir abone d&amp;ouml;ndirmez esasında. Bunun yerine i&amp;ccedil;inde abone olabilecek bir kutu(Some veya None) d&amp;ouml;nd&amp;uuml;r&amp;uuml;r. Bir match bloğu kullanmadan bir başka deyişle None ihtimalini ele almadan kutunun i&amp;ccedil;indeki name bilgisine erişmemize derleyici fiziken m&amp;uuml;saade etmez. Bu da geliştiricilerin "burada Null gelmez, ı ıhhh, m&amp;uuml;mk&amp;uuml;n değil" diyerek hareket etmesini engeller(Burası ciddi bir kurum asker. İyimserliğe yer yok! Marş marş... :D) Derleyici olası t&amp;uuml;m ihtimalleri değerlendirmemizi bekler. Tabii bu yaklaşımın en g&amp;uuml;zel yanlarından birisi de huzurlu bir gece uykusudur. &amp;Ccedil;&amp;uuml;nk&amp;uuml; r&amp;uuml;yalarımıza girebilecek herhangi bir NullReferenceException &amp;ouml;c&amp;uuml;s&amp;uuml; yoktur.&lt;/p&gt;
&lt;p&gt;Rust programlama dilindeki `Option&amp;lt;T&amp;gt;` ve hata y&amp;ouml;netimi i&amp;ccedil;in kullanılan `Result&amp;lt;T,E&amp;gt;` kavramları bu felsefeden gelir. Diğer yandan &amp;ouml;rneğin C# programlama dili &amp;ccedil;ok sonradan Nullable Type yeteneği kazanmıştır fakat dilin temel felsefesinde halen null diye bir kavram olduğu i&amp;ccedil;in bu sonradan eklenmiş bir &amp;ouml;zellik olarak kabul edilir, bir başka deyişle dilin genlerine işlenmiş matematiksel bir g&amp;uuml;vence yoktur. Burada genel olarak ifade edilen bir sorunun cevabı da bulunabilir; Neden modern diller g&amp;uuml;n ge&amp;ccedil;tik&amp;ccedil;e OCaml'a benzemeye &amp;ccedil;alışıyor?&lt;/p&gt;
&lt;h3&gt;Y&amp;uuml;ksek Matematik Lisanslı Derleyici&lt;/h3&gt;
&lt;p&gt;Programlama dillerini bir&amp;ccedil;ok a&amp;ccedil;ıdan ayrıştırabiliriz. Performans ve hıza odaklanıp bazı g&amp;uuml;venli alanları kenara bırakanlar, iş modellerini ger&amp;ccedil;eğe yakın organize edip performanstan &amp;ouml;d&amp;uuml;n verenler gibi. Ancak bir de akademik ve end&amp;uuml;striyel olanlar şeklinde iki ana kategoriye de ayrılabilirler. S&amp;ouml;z gelimi &amp;ouml;ğrenmesi g&amp;ouml;rece daha zor olan Haskell, Lisp gibi diller matematiksel a&amp;ccedil;ıdan kusursuza yakındır ancak ger&amp;ccedil;ek d&amp;uuml;nya problemlerini modellemeye &amp;ccedil;alıştığımızda bizi daha da zorlayabilir. Diğer yandan C++, Java, Go gibi iş bitirici t&amp;uuml;rden yani end&amp;uuml;striyel &amp;ccedil;&amp;ouml;z&amp;uuml;mlere daha yatkın olan diller de vardır ancak bunlarda kritik hataların oluşmasına m&amp;uuml;sait dillerdir. Kaynaklar OCaml programlama dilinin akademik titizliğe sahip ve end&amp;uuml;striyel olarak da g&amp;uuml;&amp;ccedil;l&amp;uuml; olduğuna vurgu yaparlar.&lt;/p&gt;
&lt;p&gt;Konuyu biraz daha a&amp;ccedil;maya &amp;ccedil;alışalım. OCaml derleyicisi &lt;a href="https://en.wikipedia.org/wiki/Hindley%E2%80%93Milner_type_system" target="_blank"&gt;Hindley-Milner&lt;/a&gt; olarak bilinen bir tip sistemini kullanır. Bu aslında bazı matematiksel enstr&amp;uuml;manları ve ispatları barındıran bir yapıdır. Evet evet yanlış duymadınız, matematiksel ispatları dedim :D Hindley-Milner tip sistemini baz alan derleyici kodları okuduktan sonra bunları hemen makine diline &amp;ccedil;evirmez. &amp;Ouml;ncesinde sembolik mantık ve k&amp;uuml;me teorisine g&amp;ouml;re bazı denklemler &amp;ccedil;&amp;ouml;zer. Bu &amp;ccedil;&amp;ouml;z&amp;uuml;mler yazılan kodun mantıksal olarak tutarlı olduğunun matematiksel ispatı i&amp;ccedil;in işletilir. Dolayısıyla kod derleniyorsa matematiksel olarak doğrudur(Akademik anlamda g&amp;uuml;venilirdir). Bununla birlikte derleyici optimize edilmiş end&amp;uuml;striyel makine kodu &amp;uuml;retir.(Şu an i&amp;ccedil;in ne sizi ne de kendimi bu form&amp;uuml;llerle boğmak istemiyorum ama bir ara bu konuyu derinlemesine ele alacağım)&lt;/p&gt;
&lt;p&gt;Bu tip sistemi aklımızın bir k&amp;ouml;şesinde dursun ve gelin bir &amp;ouml;rnekle konuyu pekiştirmeye &amp;ccedil;alışalım. Finansal operasyonların her adımı son derece kritiktir. B&amp;uuml;y&amp;uuml;k bir finans sisteminde farklı t&amp;uuml;rden para birimlerinin olması da kesindir. &amp;Ouml;rneğin Dolar, Sterlin, Euro gibi para birimlerini g&amp;ouml;z &amp;ouml;n&amp;uuml;ne alalım. T&amp;uuml;m&amp;uuml; float t&amp;uuml;r&amp;uuml;nden olsalar da bunları birbiriyle yanlışlıkla toplamak faciaya neden olabilir. 1000 Dolar ile 1000 Euro'nun toplanabildiğini d&amp;uuml;ş&amp;uuml;n&amp;uuml;n, korkun&amp;ccedil;... OCaml ile bu sorunu nasıl aşabiliriz gelin &amp;ouml;rnek kod par&amp;ccedil;ası ile bakalım.&lt;/p&gt;
&lt;pre class="brush:cpp;auto-links:false;toolbar:false" contenteditable="false"&gt;(*
  CURRENCY isimli bir mod&amp;uuml;l tanımladık ama bunu bir s&amp;ouml;zleşme/contract gibi d&amp;uuml;ş&amp;uuml;nelim.

  Bu s&amp;ouml;zleşmeye g&amp;ouml;re var olan bir t tipi i&amp;ccedil;in,
  create fonksiyonu float t&amp;uuml;r&amp;uuml;nden bir değer alarak t t&amp;uuml;r&amp;uuml;nden bir değer d&amp;ouml;nd&amp;uuml;rmeli,
  value fonksiyonu t t&amp;uuml;r&amp;uuml;nden bir değer alarak float t&amp;uuml;r&amp;uuml;nden bir değer d&amp;ouml;nd&amp;uuml;rmeli,
  add fonksiyonu ise iki t t&amp;uuml;r&amp;uuml;nden değer alarak t t&amp;uuml;r&amp;uuml;nden bir değer d&amp;ouml;nd&amp;uuml;rmeli.

  Biraz generic constraint'leri hatırlatıyor gibi ;)
*)
module type CURRENCY = sig
  type t
  val create : float -&amp;gt; t
  val value : t -&amp;gt; float
  val add : t -&amp;gt; t -&amp;gt; t
end

(*
  Para birimi i&amp;ccedil;in CURRENCY isimli bir s&amp;ouml;zleşmemiz var.
  Buna g&amp;ouml;re Euro, Dolar ve Sterlin implementasyonları yapabiliriz.
*)
module Euro : CURRENCY = struct
  type t = float
  let create x = x
  let value x = x
  let add x y = x +. y
end

module Dollar : CURRENCY = struct
  type t = float
  let create x = x
  let value x = x
  let add x y = x +. y
end

module Sterlin : CURRENCY = struct
  type t = float
  let create x = x
  let value x = x
  let add x y = x +. y
end

(*
  Şimdi bu para birimlerinden birka&amp;ccedil; değer tanımlayalım
  birbirleriyle toplama işlemi yapmaya &amp;ccedil;alışalım.
*)
let payment_limit = Euro.create 1000.0
let payment_limit2 = Dollar.create 750.0 
let payment_limit3 = Sterlin.create 650.0

(* Aşağıdaki satır derlenmeyecektir &amp;ccedil;&amp;uuml;nk&amp;uuml; farklı t&amp;uuml;rler birbirleriyle toplanamaz *)
let total_payment = Euro.add payment_limit payment_limit2&lt;/pre&gt;
&lt;p&gt;Son satırda kasıtlı olarak farklı para birimleri toplanmaya &amp;ccedil;alışılmaktadır. Bakalım derleyici nasıl tepkiler vermiş.&lt;/p&gt;
&lt;p&gt;VS Code ortamından bir g&amp;ouml;r&amp;uuml;nt&amp;uuml;,&lt;/p&gt;
&lt;p&gt;&lt;img src="https://www.buraksenyurt.com/image.axd?picture=/2026/Nisan/ocaml_63.png" alt="" /&gt;&lt;/p&gt;
&lt;p&gt;ve terminalden derlemenin sonucu.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://www.buraksenyurt.com/image.axd?picture=/2026/Nisan/ocaml_64.png" alt="" /&gt;&lt;/p&gt;
&lt;p&gt;OCaml derleyicisi t&amp;uuml;m para birimleri float veri t&amp;uuml;r&amp;uuml;n&amp;uuml; kullanıyor olsalar da, CURRENCY mod&amp;uuml;l&amp;uuml;nden yapılan implementasyonlar sebebiyle farklı t&amp;uuml;rlerin toplanmasına izin vermeyecektir(Domain Driven Design tarafında Value Object t&amp;uuml;rleri ile de benzer bir tedbir alınabilir değil mi? Bi d&amp;uuml;ş&amp;uuml;n&amp;uuml;n ;) ) Buradaki felsefe şudur; hataları testler yazarak değil tip sistemini kullanarak derleme zamanında engelle. Endişe edeceğimiz noktalardan birisi belki de performans kaybıdır ancak burada Zero Cost Abstraction s&amp;ouml;z konusudur. Zira derleyici makine kodunu &amp;uuml;retirken Dollar.t, Euro.t gibi ayrımları silip doğrudan float toplama işlemini ele alır.&lt;/p&gt;
&lt;p&gt;Bu genler Rust diline de ge&amp;ccedil;miştir ve hatta &amp;ccedil;ok daha şık bir şekilde. Rust dili de Zero Cost Abstraction felsefesini benimser ve hatta parasal bir birimi şu şekilde yazmamıza izin veren Newtype desenini sunar.&lt;/p&gt;
&lt;pre class="brush:cpp;auto-links:false;toolbar:false" contenteditable="false"&gt;struct Dollar(f64);&lt;/pre&gt;
&lt;p&gt;C# tarafından olaya baktığımızda sanıyorum en yakın &amp;ccedil;&amp;ouml;z&amp;uuml;m record struct gibi bir t&amp;uuml;rden yararlanmak olacaktır. Nitekim C# ve Java gibi dillerde bu t&amp;uuml;r bir korumayı sağlamak i&amp;ccedil;in sınıflara başvurduğumuzda bellekte ekstra nesneler oluşmasına neden olup gereksiz GC d&amp;ouml;ng&amp;uuml;lerine sebebiyet verebiliriz. Ancak bu s&amp;ouml;ylediklerimi ispat edebilir miyim, ımmmm, hayır :D&lt;/p&gt;
&lt;h3&gt;Mod&amp;uuml;ller Birinci Sınıfı Vatandaştır(First Class Citizen)&lt;/h3&gt;
&lt;p&gt;Rahmetli babam &amp;ccedil;ok uzun yıllar Almanya'da &amp;ccedil;alışmıştı. Savaş sonrası kalkınmaya &amp;ccedil;alışan Almanya'ya erken d&amp;ouml;nem gidenler arasındaydı. Orada edindiği dostluklar yurda temelli d&amp;ouml;nd&amp;uuml;kten sonra da devam etmişti. &amp;Ouml;yle ki beni &amp;ccedil;ok seven bir&amp;ccedil;ok arkadaşı ne zaman onu ziyaret i&amp;ccedil;in &amp;uuml;lkeye gelse k&amp;uuml;&amp;ccedil;&amp;uuml;k ya da b&amp;uuml;y&amp;uuml;k lego setleri getirirdi. Taa o zamanlarda kalma bir Lego sevgisi vardır i&amp;ccedil;imde. Halen daha yapıyorum demek isterdim ama malum fiyatlar :|&lt;/p&gt;
&lt;p&gt;OCaml a&amp;ccedil;ısından olaya bakacak olursak dilin en g&amp;uuml;&amp;ccedil;l&amp;uuml; &amp;ouml;zelliklerinden birisi lego par&amp;ccedil;alarına benzettiğim mod&amp;uuml;lleri birinci sınıf vatandaş olarak ele almasıdır. Bu, bazı dillerde kullandığımız paket(package), isim alanı(namespace) gibi kavramlardan &amp;ccedil;ok farklı bir anlayıştır. Dahası var; mod&amp;uuml;ller birbirlerine birer değişken gibi bağlanabilir, i&amp;ccedil; i&amp;ccedil;e ge&amp;ccedil;ebilir ve functor adı verilen yapılarla bir mod&amp;uuml;lden başka bir mod&amp;uuml;l &amp;uuml;retilebilir. Bir başka deyişle mod&amp;uuml;l deyip ge&amp;ccedil;memek lazım :D&lt;/p&gt;
&lt;p&gt;Şimdi konuyu koda d&amp;ouml;k&amp;uuml;p felsefesine gelmeye &amp;ccedil;alışalım. C# ve Java gibi dillerde bileşenler arasındaki bağımlılıklar hep başa dert olmuştur. Bunları y&amp;ouml;netmek i&amp;ccedil;in Dependency Injection gibi desenler ortaya &amp;ccedil;ıkmıştır. Bağımlılıklar genellikle soyutlamalar &amp;uuml;zerinden(interface, abstract class) y&amp;ouml;netilir ve diğer bileşenlere yapıcı metotlar, &amp;ouml;zellikler veya servis sağlayıcılar aracılığıyla enjekte edilir. Ne var ki bu işlem &amp;ccedil;alışma zamanında ger&amp;ccedil;ekleşir ve doğal olarak bir ısınma maliyeti(Warm-up cost) vardır. Tahmin edin, OCaml'da bu nerede &amp;ccedil;&amp;ouml;z&amp;uuml;l&amp;uuml;r ;)&lt;/p&gt;
&lt;p&gt;Klasik bir kod loglama işlevini ele alalım.&lt;/p&gt;
&lt;pre class="brush:cpp;auto-links:false;toolbar:false" contenteditable="false"&gt;(*
  &amp;Ouml;ncelikle bir loglayıcının nasıl olması gerektiğini tarifleyelim.
  Bunu bir mod&amp;uuml;l tanımı aracılığı ile yapabiliriz.
  S&amp;ouml;zleşme &amp;uuml;&amp;ccedil; fonksiyonu i&amp;ccedil;eriyor: info, error ve warning.
  Ve &amp;uuml;retilen diğer mod&amp;uuml;llerin bu fonksiyonları yazması gerekiyor.
*)
module type LOGGER = sig (* sig kelimesi signature'ın kısaltması *)
  val info : string -&amp;gt; unit
  val error : string -&amp;gt; unit
  val warning : string -&amp;gt; unit
end

(*
  Şimdi bu loglayıcıdan &amp;ouml;rnek iki loglayıcı hazırlayalım.
  Aslında LOGGER mod&amp;uuml;l&amp;uuml;nden bir başka mod&amp;uuml;l t&amp;uuml;retiyoruz gibi.

  ConsoleLogger bir struct 
  İ&amp;ccedil;inde info, error ve warning isimli fonksiyonların asıl iş yapan s&amp;uuml;r&amp;uuml;mleri var.
*)
module ConsoleLogger : LOGGER = struct
  let info message = Printf.printf "[INFO] %s\n" message
  let error message = Printf.printf "[ERROR] %s\n" message
  let warning message = Printf.printf "[WARNING] %s\n" message

end

(*
  Aşağıdaki FileLogger mod&amp;uuml;l&amp;uuml; de LOGGER s&amp;ouml;zleşmesini uygulayan bir başka mod&amp;uuml;l
  ve bu sefer log mesajlarını bir dosyaya yazacak şekilde tasarlanmış durumda.
*)
module FileLogger : LOGGER = struct
  let log_file = "log.txt"

  let log message =
    let oc = open_out_gen [Open_append; Open_creat] 0o666 log_file in
    output_string oc (message ^ "\n");
    close_out oc

  let info message = log ("[INFO] " ^ message)
  let error message = log ("[ERROR] " ^ message)
  let warning message = log ("[WARNING] " ^ message)
end

(*
  Elimizde bir soyutlama mod&amp;uuml;l&amp;uuml; ve bunu uygulayan iki farklı mod&amp;uuml;l var.
  &amp;Ouml;yleyse başka bir mod&amp;uuml;le bu bağımlılığı enjekte edelim.

  AppTracer, FUNCTOR (Fabrika) mod&amp;uuml;l&amp;uuml;d&amp;uuml;r. Bir Logger mod&amp;uuml;l&amp;uuml;n&amp;uuml; 
  parametre olarak alır ve bir servis verir.
*)
module AppTracer (L : LOGGER) = struct
  let log_data message =
    L.info ("Processing data: " ^ message);
    (* Veri işleme kodları burada olabilir *)
    L.info "Data processed successfully."
end

(*
  Şimdi bu servisi ConsoleLogger ve FileLogger ile &amp;ccedil;alıştırabiliriz.
  Burada mod&amp;uuml;l bazında gerekli birleştirmeler yapılır ama &amp;ccedil;alışma zamanında değil
  derleme zamanında ger&amp;ccedil;ekleşir.
*)
module ConsoleAppTracer = AppTracer(ConsoleLogger)
module FileAppTracer = AppTracer(FileLogger)

let () =
  ConsoleAppTracer.log_data "This is a console log message.";
  FileAppTracer.log_data "This is a file log message."&lt;/pre&gt;
&lt;p&gt;&amp;Ouml;rnekte terminal ekranına ve log.txt dosyasına basit log mesajları bırakan bir kod akışına yer veriliyor. Bir &amp;ouml;nceki &amp;ouml;rnektekine benzer şekilde bir s&amp;ouml;zleşme tanımlayarak işe başlıyoruz. Bu s&amp;ouml;zleşme ger&amp;ccedil;ekten de bir imza(signature) tanımlıyor. ConsoleLogger ve FileLogger mod&amp;uuml;lleri bu s&amp;ouml;zleşmeyi uygulayan iki farklı mod&amp;uuml;l olarak ortaya &amp;ccedil;ıkıyor. Daha sonra AppTracer adında bir functor tanımlanıyor. Fabrika g&amp;ouml;revi &amp;uuml;stlenen bu mod&amp;uuml;l, bir LOGGER mod&amp;uuml;l&amp;uuml;n&amp;uuml; parametre olarak alıyor ve bu loglayıcıyı kullanarak veri işleme s&amp;uuml;recini izleyen bir servis sağlıyor. Yani bağımlılıkları enjekte ettiğimiz yer olarak d&amp;uuml;ş&amp;uuml;nebiliriz. Son olarak, bu servisi hem ConsoleLogger hem de FileLogger ile &amp;ccedil;alıştırmak i&amp;ccedil;in gerekli mod&amp;uuml;l bazında birleştirmeler yapılır. T&amp;uuml;m bu işlemler derleme zamanında ger&amp;ccedil;ekleşir. Ayrıca yeni bir loglama y&amp;ouml;netimi gerekirse, mevcut kodu bozmadan SOLID'in Open/Closed prensibine uygun olarak yeni bir mod&amp;uuml;l tanımlayıp onu da AppTracer'a enjekte edebiliriz. Bu esneklik ve genişletilebilirlik, mod&amp;uuml;llerin birinci sınıf vatandaş olarak ele alınmasının &amp;ouml;nemli avantajlarından biridir.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://www.buraksenyurt.com/image.axd?picture=/2026/Nisan/ocaml_65.png" alt="" /&gt;&lt;/p&gt;
&lt;p&gt;Bağımlılıkların derleme zamanında &amp;ccedil;&amp;ouml;z&amp;uuml;lmesi, &amp;ccedil;alışma zamanındaki maliyetten kurtulmamızı sağlar(Zero Cost Dependency Injection). Yani derleyici AppTracer(ConsoleLogger) ifadesini g&amp;ouml;rd&amp;uuml;ğ&amp;uuml;nde, ConsoleLogger mod&amp;uuml;l&amp;uuml;n&amp;uuml;n i&amp;ccedil;eriğini AppTracer'ın i&amp;ccedil;ine yerleştirerek makine kodu &amp;uuml;retir ve b&amp;ouml;ylece &amp;ccedil;alışma zamanında herhangi bir soyutlama ya da aray&amp;uuml;z &amp;ccedil;ağrısına gerek kalmaz. Rust dilinde benzer şekilde trait'ler aracılığıyla bağımlılıkların derleme zamanında &amp;ccedil;&amp;ouml;z&amp;uuml;lmesi sağlanır. Dolayısıyla Rust'ın OCaml dilindeki signature ve mod&amp;uuml;l sisteminden genetik izler taşıdığını s&amp;ouml;yleyebiliriz. Diğer yandan C# veya Java dillerindeki generic yapıların ve interface'lerin OCaml'daki functor'ların daha zayıf bir versiyonu olduğu ifade edilir. Bunu ş&amp;ouml;yle a&amp;ccedil;ıklamak m&amp;uuml;mk&amp;uuml;n; OCaml functor'ları kullanarak davranışlar b&amp;uuml;t&amp;uuml;n&amp;uuml;n&amp;uuml; soyutlarken C# daha &amp;ccedil;ok parametreleri soyutlar. &amp;Ouml;rneğin C# dilinde generic bir sınıf tanımlarken bu sınıfın hangi t&amp;uuml;rde &amp;ccedil;alışacağını belirtiriz(`&amp;lt;T&amp;gt;` kullanımı) ancak bu t&amp;uuml;r&amp;uuml;n hangi davranışlara sahip olması gerektiği konusunda daha az kontrol&amp;uuml;m&amp;uuml;z olur. OCaml'da ise functor'lar aracılığıyla sadece t&amp;uuml;rleri değil aynı zamanda bu t&amp;uuml;rlerin sahip olması gereken fonksiyonları da tanımlarız. Dolayısıyla daha g&amp;uuml;&amp;ccedil;l&amp;uuml; bir soyutlama ve daha sıkı bir tip g&amp;uuml;venliği sağlamış oluruz.&lt;/p&gt;
&lt;h2&gt;Sonu&amp;ccedil;&lt;/h2&gt;
&lt;p&gt;Bir merakla başladığım OCaml yolculuğumdaki hedefim bir programlama dili geliştirmek i&amp;ccedil;in gerekli becerileri &amp;ouml;ğrenmekti. Hen&amp;uuml;z bu noktanın &amp;ccedil;ok uzağında olmakla birlikte, severek kullandığım Rust'ın genlerini aldığı bu dille uğraşmak bir başka meydan okumaydı ama değdi. &amp;Ouml;ğrenmeye devam; umarım sizlere de yeni bir şeyler &amp;ouml;ğrenmek i&amp;ccedil;in ilham veren bir &amp;ccedil;alışma olmuştur. Bir başka &amp;ccedil;alışmada g&amp;ouml;r&amp;uuml;şmek &amp;uuml;zere, hoş&amp;ccedil;a kalın.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://github.com/buraksenyurt/learning-ocaml" target="_blank"&gt;Bu &amp;ccedil;alışmadaki &amp;ouml;rneklere ve biraz daha fazlasına github reposundan ulaşabilirsiniz&lt;/a&gt;&lt;/p&gt;</summary>
    <published>2026-04-17T21:28:00+00:00</published>
    <link rel="related" href="https://www.buraksenyurt.com/post/birlikte-ocaml-ogrenelim#comment" />
    <category term="OCaml" />
    <betag:tag>ocaml</betag:tag>
    <betag:tag>fonksiyonel programlama</betag:tag>
    <betag:tag>utop</betag:tag>
    <betag:tag>programming languages</betag:tag>
    <dc:publisher>bsenyurt</dc:publisher>
    <dc:description>İlk programlama dilinden bu zamanlara değişen çok şey var. Üniversite yıllarım kişisel bilgisayarların ve internetin yaygınlaştığı World Wide Web devrimine denk geliyor. O vakitler bölümde gösterilen bilgisayar programlama derslerini düşünüyorum da; GW-Basic, Cobol, C ve C++ ... Çoğunda belli seviyeye kadar geldiğimizi anımsıyorum. Aynı yıllarda iş dünyasının hızlandırıcı etkisine de şahit olmuştuk. Sadece klavye ve 8 renkten oluşan siyah terminal ekranları çok uzun zamandır mouse imleçleri ile renklenmişti. Dahası artık iş süreçlerinin internet ortamından yürütülebildiği bir dönemdi. Bu dalgayla birlikte ben ve birçok arkadaşım Delphi, Java, Visual Basic gibi dillere yöneldi. Ben ağırlıklı olarak Delphi tarafına yakındım ama zamanla bu yakınlık yerini C# programlama diline bıraktı.</dc:description>
    <pingback:server>https://www.buraksenyurt.com/pingback.axd</pingback:server>
    <pingback:target>https://www.buraksenyurt.com/post.aspx?id=d80f930e-7339-4128-9eec-5aad5096f469</pingback:target>
    <slash:comments>0</slash:comments>
    <trackback:ping>https://www.buraksenyurt.com/trackback.axd?id=d80f930e-7339-4128-9eec-5aad5096f469</trackback:ping>
    <wfw:comment>https://www.buraksenyurt.com/post/birlikte-ocaml-ogrenelim#comment</wfw:comment>
    <wfw:commentRss>https://www.buraksenyurt.com/syndication.axd?post=d80f930e-7339-4128-9eec-5aad5096f469</wfw:commentRss>
  </entry>
  <entry>
    <id>https://www.buraksenyurt.com/post/hangi-localization-teknigi</id>
    <title>Hangi Localization Tekniği</title>
    <updated>2026-04-11T08:47:00+00:00</updated>
    <link rel="self" href="https://www.buraksenyurt.com/post.aspx?id=afd27236-dbfd-40ac-9153-c003506fe13b" />
    <link href="https://www.buraksenyurt.com/post/hangi-localization-teknigi" />
    <author>
      <name>bsenyurt</name>
    </author>
    <summary type="html">&lt;p&gt;Tartışmanın konusu, &amp;ccedil;ooooook uzun zamandır d&amp;uuml;nyamızda var olan &amp;ccedil;oklu dil desteği&lt;em&gt;(Multi-Language Support)&lt;/em&gt;. Kimi zaman veritabanı &amp;uuml;zerinden, kimi zaman fiziki dosyalardan&lt;em&gt;(resx gibi)&lt;/em&gt; y&amp;ouml;netmeye &amp;ccedil;alıştığımız bir mevzu. S&amp;uuml;rekli değişip genişleyebilenler bir yana, nadiren değişip genellikle statik kalanlardan oluşan veri k&amp;uuml;meleri de cabası. Aslında temel ama&amp;ccedil;, bir program aray&amp;uuml;z&amp;uuml;n&amp;uuml;n veya kullanıcı ile etkileşimde olan taraflarının farklı dillerde destek vermesini sağlamak. Teori basit; değişmez sabit bir kavram&lt;em&gt;(key diyelim)&lt;/em&gt; karşılığında, kullanılan dile g&amp;ouml;re farklı değerler tutulmasını sağlamak.&lt;br /&gt;&lt;br /&gt;&amp;Ouml;rneğin m&amp;uuml;şteri bilgilerini kaydetme ekranında kullandığımız button kontrol&amp;uuml;n&amp;uuml;n başlığını save_button şeklinde bir key ile sabitleyip, değerlerini ana dilimizde kaydet, İngilizce'de save, İspanyolca'da Ahorrar şeklinde tutabiliriz. Bunun i&amp;ccedil;in ister tablo tasarımı kullanalım ister bir key:value koleksiyonu; verinin okunması, değiştirilmesi, aynı anda erişilmesi gibi konular başka soruları da g&amp;uuml;ndeme getirir. Nerede tutsak iyi olur, hangi teknik bizi ne kadar yavaşlatır ya da hızlandırır, eş zamanlı&lt;em&gt;(concurrent)&lt;/em&gt; &amp;ccedil;ağrılarda değişenlerin g&amp;uuml;ncelliğini nasıl koruruz, race-condition oluşur mu, &amp;ouml;n belleğe&lt;em&gt;(cache)&lt;/em&gt; alsak ne zaman tazelemek gerekir, koca veriyi &amp;ouml;n belleğe alma maliyeti nedir vb.&lt;br /&gt;&lt;br /&gt;&amp;Ccedil;oklu dil desteği aslında &amp;ccedil;&amp;ouml;z&amp;uuml;lmemiş bir problem değil. Bir&amp;ccedil;ok yazılım firması zaten &amp;ccedil;oktandır ideal &amp;ccedil;&amp;ouml;z&amp;uuml;mler &amp;uuml;zerinden ilerlemekte. Bu &amp;ccedil;alışmadaki amacım veritabanı&lt;em&gt;(kuvvetle muhtemel PostgreSQL)&lt;/em&gt;, Redis, in-memory cache veya hibrit &amp;ccedil;&amp;ouml;z&amp;uuml;mler arasında bir benchmark değerlendirmesi yapmak. Neticede aşağıdaki tabloda belirtilen sonu&amp;ccedil;ları ispatlamaya ya da ger&amp;ccedil;eği yansıtıp yansıtmadığını bulmaya &amp;ccedil;alışacağız.&lt;/p&gt;
&lt;table border="1"&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Yaklaşım&lt;/th&gt;
&lt;th&gt;Read&lt;/th&gt;
&lt;th&gt;Cold Start Maliyeti&lt;/th&gt;
&lt;th&gt;Hot Read Maliyeti&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;PostgreSQL (doğrudan kullanım)&lt;/td&gt;
&lt;td&gt;Network + Disk&lt;/td&gt;
&lt;td&gt;High&lt;/td&gt;
&lt;td&gt;High&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Redis&lt;/td&gt;
&lt;td&gt;Network + RAM&lt;/td&gt;
&lt;td&gt;Medium&lt;/td&gt;
&lt;td&gt;Low&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;IMemoryCache (in-process)&lt;/td&gt;
&lt;td&gt;RAM&lt;/td&gt;
&lt;td&gt;Low (after warmup)&lt;/td&gt;
&lt;td&gt;Lowest&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Hybrid (DB &amp;rarr; Redis &amp;rarr; MemCache)&lt;/td&gt;
&lt;td&gt;Layered&lt;/td&gt;
&lt;td&gt;Warm on demand&lt;/td&gt;
&lt;td&gt;Lowest after L1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;JSON (resx gibi dosyalarda)&lt;/td&gt;
&lt;td&gt;Disk&lt;/td&gt;
&lt;td&gt;Low&lt;/td&gt;
&lt;td&gt;Very Low&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2&gt;Hazırlıklar&lt;/h2&gt;
&lt;p&gt;&amp;Ouml;ncelikle birka&amp;ccedil; yaklaşımımız olduğunu belirtelim. &amp;Ccedil;oklu dil desteği i&amp;ccedil;in dikeyde b&amp;uuml;y&amp;uuml;yen bir tabloyu PostgreSQL &amp;uuml;zerinde tutacağız. Bir diğer yaklaşımda dağıtık sistemler d&amp;uuml;nyasının en karizma veri tabanlarından olan Redis ile ilerleyeceğiz. .NET'in dahili bellek kullanımı da yabana atılır t&amp;uuml;rden değil. Dolayısıyla o da işin i&amp;ccedil;erisine giriyor. Bir başka tercih de i&amp;ccedil;eriği disk &amp;uuml;zerinde JSON veya YAML gibi bir formatta tutmak ama bunu bu senaryoda ele almayacağız. Son olarak en hızlı teknikten en yavaşa doğru farklı seviyeleri ele alan hibrit bir yaklaşımımız da olacak.&lt;/p&gt;
&lt;h2&gt;Docker Setup&lt;/h2&gt;
&lt;p&gt;D&amp;uuml;zeneğimizi .NET 10 platformunda kurgulayabiliriz. PostgreSQL ve Redis enstr&amp;uuml;manları i&amp;ccedil;in her zaman olduğu gibi bir docker-compose dosyası iş g&amp;ouml;recektir. En azından aşağıdaki i&amp;ccedil;eriğe sahip olmasında yarar var.&lt;em&gt;(PgAdmin &amp;ccedil;ok şart değil zira community s&amp;uuml;r&amp;uuml;m&amp;uuml; bulunan DBeaver gibi ara&amp;ccedil;larla da veritabanına bağlanıp işlemler yapabiliriz ki ben onu tercih ettim)&lt;/em&gt;&lt;/p&gt;
&lt;pre class="brush:plain;auto-links:false;toolbar:false" contenteditable="false"&gt;services:

  postgres:
    image: postgres:latest
    container_name: locally-postgres
    environment:
      POSTGRES_USER: johndoe
      POSTGRES_PASSWORD: somew0rds
      POSTGRES_DB: postgres
    ports:
      - "5435:5432"
    volumes:
      - postgres_data:/var/lib/postgres/data
    networks:
      - locally-network

  pgadmin:
    image: dpage/pgadmin4:latest
    container_name: locally-pgadmin
    environment:
      PGADMIN_DEFAULT_EMAIL: scoth@tiger.com
      PGADMIN_DEFAULT_PASSWORD: 123456
    ports:
      - "5050:80"
    depends_on:
      - postgres
    networks:
      - locally-network

  redis:
    image: redis:latest
    container_name: locally-redis
    ports:
      - "6379:6379"
    # Burada persistance storage'ı devre dışı bırakıyoruz ve pure in-memory modda &amp;ccedil;alıştırıyoruz. 
    # Nitekim benchmark sırasında disk IO işlemleri performansı etkileyebilir.
    command: redis-server --save "" --appendonly no --maxmemory 256mb --maxmemory-policy allkeys-lru
    networks:
      - locally-network

volumes:
  postgres_data:

networks:
  locally-network:
    driver: bridge&lt;/pre&gt;
&lt;h2&gt;Solution İskeleti&lt;/h2&gt;
&lt;p&gt;Solution i&amp;ccedil;eriğinde birka&amp;ccedil; proje yer alacak. Farklı provider t&amp;uuml;rlerimiz olacağı i&amp;ccedil;in &amp;ccedil;ok basit soyutlamalar kullanmakta, benchmark &amp;ouml;l&amp;ccedil;&amp;uuml;mleri i&amp;ccedil;in ayrı bir proje a&amp;ccedil;makta ve testleri bir Web API &amp;uuml;zerinden icra etmekte yarar var. Yani farklı provider'lar i&amp;ccedil;in class library'ler, &amp;ccedil;oklu dil veri setlerine ulaşmak i&amp;ccedil;in bir Web API ve performans &amp;ouml;l&amp;ccedil;&amp;uuml;mleri i&amp;ccedil;in bir console uygulaması şimdilik işimizi g&amp;ouml;recektir. Son aşamada birde y&amp;uuml;k testi&lt;em&gt;(load test)&lt;/em&gt; i&amp;ccedil;in adım atacağız. Başlangı&amp;ccedil; senaryosuna g&amp;ouml;re solution i&amp;ccedil;eriği ve gerekli nuget paketlerini aşağıdaki gibi hazırlayabiliriz.&lt;/p&gt;
&lt;pre class="brush:bash;auto-links:false;toolbar:false" contenteditable="false"&gt;mkdir LocalizationChallenge
cd LocalizationChallenge

dotnet new sln

dotnet new classlib -n LocalizationChallenge.Core
dotnet sln add LocalizationChallenge.Core/

dotnet new classlib -n LocalizationChallenge.Infrastructure
dotnet add LocalizationChallenge.Infrastructure/LocalizationChallenge.Infrastructure.csproj package Npgsql
dotnet add LocalizationChallenge.Infrastructure/LocalizationChallenge.Infrastructure.csproj package StackExchange.Redis
dotnet add LocalizationChallenge.Infrastructure/LocalizationChallenge.Infrastructure.csproj package Microsoft.Extensions.Hosting.Abstractions
dotnet sln add LocalizationChallenge.Infrastructure/

dotnet new console -n LocalizationChallenge.Benchmarks
dotnet add LocalizationChallenge.Benchmarks/LocalizationChallenge.Benchmarks.csproj package BenchmarkDotNet
dotnet add LocalizationChallenge.Benchmarks/LocalizationChallenge.Benchmarks.csproj package Npgsql
dotnet add LocalizationChallenge.Benchmarks/LocalizationChallenge.Benchmarks.csproj package StackExchange.Redis
dotnet sln add LocalizationChallenge.Benchmarks/

dotnet new webapi -n LocalizationChallenge.Api
dotnet add LocalizationChallenge.Api/LocalizationChallenge.Api.csproj package Npgsql
dotnet add LocalizationChallenge.Api/LocalizationChallenge.Api.csproj package StackExchange.Redis  
dotnet sln add LocalizationChallenge.Api/

# Gerekli proje referansların eklenmesi
dotnet add LocalizationChallenge.Infrastructure/LocalizationChallenge.Infrastructure.csproj reference LocalizationChallenge.Core/LocalizationChallenge.Core.csproj
dotnet add LocalizationChallenge.Api/LocalizationChallenge.Api.csproj reference LocalizationChallenge.Core/LocalizationChallenge.Core.csproj
dotnet add LocalizationChallenge.Api/LocalizationChallenge.Api.csproj reference LocalizationChallenge.Infrastructure/LocalizationChallenge.Infrastructure.csproj
dotnet add LocalizationChallenge.Benchmarks/LocalizationChallenge.Benchmarks.csproj reference LocalizationChallenge.Core/LocalizationChallenge.Core.csproj
dotnet add LocalizationChallenge.Benchmarks/LocalizationChallenge.Benchmarks.csproj reference LocalizationChallenge.Infrastructure/LocalizationChallenge.Infrastructure.csproj&lt;/pre&gt;
&lt;p&gt;Kod tarafında ilerledik&amp;ccedil;e projelerimizin kullanım amacı biraz daha netleşecek.&lt;/p&gt;
&lt;h2&gt;PostgreSQL Tarafı&lt;/h2&gt;
&lt;p&gt;Veritabanı tarafında bir tabloya ve onu en azından &amp;ouml;rnek verilerle tohumlamaya&lt;em&gt;(seeding)&lt;/em&gt; ihtiyacımız var. İlk etapta aşağıdaki sql script'ini kullanabiliriz.&lt;em&gt;(İlerleyen aşamada bir trigger ekleyip olası değişimleri dış d&amp;uuml;nyaya push'layacağımız bir mekanizmayı da ele alacağız ki cache-invalidation noktasında gerekli olacak)&lt;/em&gt;&lt;/p&gt;
&lt;pre class="brush:sql;auto-links:false;toolbar:false" contenteditable="false"&gt;CREATE TABLE IF NOT EXISTS localizations (
    id           SERIAL       PRIMARY KEY,
    culture      VARCHAR(10)  NOT NULL,
    resource_key VARCHAR(255) NOT NULL,
    value        TEXT         NOT NULL,
    updated_at   TIMESTAMPTZ  NOT NULL DEFAULT NOW(),
    CONSTRAINT uq_culture_key UNIQUE (culture, resource_key)
);

CREATE INDEX IF NOT EXISTS idx_loc_culture_key ON localizations (culture, resource_key);

-- Sample data
INSERT INTO localizations (culture, resource_key, value) VALUES
  ('en-US', 'welcome_message',    'Welcome to our application'),
  ('en-US', 'farewell_message',   'Goodbye, see you soon'),
  ('en-US', 'error_not_found',    'The requested resource was not found'),
  ('en-US', 'error_unauthorized', 'You are not authorized to perform this action'),
  ('en-US', 'button_save',        'Save'),
  ('en-US', 'button_cancel',      'Cancel'),
  ('en-US', 'button_delete',      'Delete'),
  ('tr-TR', 'welcome_message',    'Uygulamamıza hoş geldiniz'),
  ('tr-TR', 'farewell_message',   'G&amp;uuml;le g&amp;uuml;le, yakında g&amp;ouml;r&amp;uuml;ş&amp;uuml;r&amp;uuml;z'),
  ('tr-TR', 'error_not_found',    'İstenen kaynak bulunamadı'),
  ('tr-TR', 'error_unauthorized', 'Bu işlemi ger&amp;ccedil;ekleştirme yetkiniz yok'),
  ('tr-TR', 'button_save',        'Kaydet'),
  ('tr-TR', 'button_cancel',      'İptal'),
  ('tr-TR', 'button_delete',      'Sil'),
  ('de-DE', 'welcome_message',    'Willkommen in unserer Anwendung'),
  ('de-DE', 'farewell_message',   'Auf Wiedersehen, bis bald'),
  ('de-DE', 'error_not_found',    'Die angeforderte Ressource wurde nicht gefunden'),
  ('de-DE', 'error_unauthorized', 'Sie sind nicht berechtigt, diese Aktion durchzuf&amp;uuml;hren'),
  ('de-DE', 'button_save',        'Speichern'),
  ('de-DE', 'button_cancel',      'Abbrechen'),
  ('de-DE', 'button_delete',      'L&amp;ouml;schen')
ON CONFLICT (culture, resource_key) DO NOTHING;&lt;/pre&gt;
&lt;p&gt;SQL i&amp;ccedil;eriğini pgadmin arabirimi &amp;uuml;zerinden ekleyebileceğimiz gibi komut satırından bu dosyayı container i&amp;ccedil;erisine alarak da &amp;ccedil;alıştırabiliriz. Bunun i&amp;ccedil;in kendi Windows 11 sistemimde izleyen komutlarla ilerledim.&lt;/p&gt;
&lt;pre class="brush:bash;auto-links:false;toolbar:false" contenteditable="false"&gt;# &amp;Ouml;ncelikle sql dosyasını container i&amp;ccedil;erisine kopyalayalım
docker cp seed.sql locally-postgres:/seed.sql
# Şimdi de &amp;ccedil;alıştırarak verileri ekleyelim
docker exec locally-postgres psql -U johndoe -d postgres -f /seed.sql
# Verilerin eklenip eklenmediğini kontrol edelim
docker exec locally-postgres psql -U johndoe -d postgres -c "SELECT * FROM localizations WHERE culture = 'tr-TR';"&lt;/pre&gt;
&lt;p&gt;&lt;img src="https://www.buraksenyurt.com/image.axd?picture=/2026/Nisan/LocallyBench_01.png" alt="" /&gt;&lt;/p&gt;
&lt;h2&gt;Redis&lt;/h2&gt;
&lt;p&gt;Redis tarafına da &amp;ouml;rnek veri tohumlarını aktarmakta yarar var. PostgreSQL i&amp;ccedil;in kullandığımız veri k&amp;uuml;mesinin aynısını Redis i&amp;ccedil;in de değerlendirebiliriz. Tabii verileri aktarmak i&amp;ccedil;in kullanabileceğimiz birka&amp;ccedil; yol var. Bunlardan birisi, docker ortamındaki Redis container'ına terminal a&amp;ccedil;mak ve &amp;ouml;rneğin aşağıdaki i&amp;ccedil;eriğe sahip bir shell script dosyasını kopyalayıp işletmek. Burada her bir dil k&amp;uuml;mesini bir HashSet olarak eklediğimizi g&amp;ouml;rebilirsiniz. Veritabanındaki tek tablo d&amp;uuml;zeneği yerine burada dil bazlı farklı hashset kullanımları s&amp;ouml;z konusu. &lt;em&gt;(Bu sadece bir tercih meselesi, tek bir hashset de kullanabiliriz ama bu şekilde daha d&amp;uuml;zenli duruyor diye d&amp;uuml;ş&amp;uuml;n&amp;uuml;yorum)&lt;/em&gt;&lt;/p&gt;
&lt;pre class="brush:bash;auto-links:false;toolbar:false" contenteditable="false"&gt;redis-cli -h localhost -p 6379 HSET loc:en-US \
  welcome_message    "Welcome to our application" \
  farewell_message   "Goodbye, see you soon" \
  error_not_found    "The requested resource was not found" \
  error_unauthorized "You are not authorized to perform this action" \
  button_save        "Save" \
  button_cancel      "Cancel" \
  button_delete      "Delete"

redis-cli -h localhost -p 6379 HSET loc:tr-TR \
  welcome_message    "Uygulamamıza hoş geldiniz" \
  farewell_message   "G&amp;uuml;le g&amp;uuml;le, yakında g&amp;ouml;r&amp;uuml;ş&amp;uuml;r&amp;uuml;z" \
  error_not_found    "İstenen kaynak bulunamadı" \
  error_unauthorized "Bu işlemi ger&amp;ccedil;ekleştirme yetkiniz yok" \
  button_save        "Kaydet" \
  button_cancel      "İptal" \
  button_delete      "Sil"

redis-cli -h localhost -p 6379 HSET loc:de-DE \
  welcome_message    "Willkommen in unserer Anwendung" \
  farewell_message   "Auf Wiedersehen, bis bald" \
  error_not_found    "Die angeforderte Ressource wurde nicht gefunden" \
  error_unauthorized "Sie sind nicht berechtigt, diese Aktion durchzuf&amp;uuml;hren" \
  button_save        "Speichern" \
  button_cancel      "Abbrechen" \
  button_delete      "L&amp;ouml;schen"

echo "Mission accomplished."&lt;/pre&gt;
&lt;p&gt;Yukarıdaki betiği &amp;ccedil;alıştırmak i&amp;ccedil;in aşağıdaki gibi bir yol izleyebiliriz &lt;em&gt;(Ben denemelerimi Windows 11 ortamında Command Prompt &amp;uuml;zerinden yaptım)&lt;/em&gt;&lt;/p&gt;
&lt;pre class="brush:bash;auto-links:false;toolbar:false" contenteditable="false"&gt;# &amp;Ouml;ncelikle redis-seed dosyasını container i&amp;ccedil;erisine kopyalayalım
docker cp redis-seed.sh locally-redis:/redis-seed.sh
# Ardından &amp;ccedil;alıştırarak verileri ekleyelim
docker exec locally-redis sh /redis-seed.sh

# Verilerin eklenip eklenmediğini kontrol edelim
docker exec locally-redis redis-cli -h localhost -p 6379 HGETALL loc:tr-TR&lt;/pre&gt;
&lt;p&gt;&lt;img src="https://www.buraksenyurt.com/image.axd?picture=/2026/Nisan/LocallyBench_00.png" alt="" /&gt;&lt;/p&gt;
&lt;h2&gt;Kod Tarafı&lt;/h2&gt;
&lt;p&gt;Şimdi adım adım kodlarımızı geliştirmeye başlayabiliriz. Birden fazla projede yapacağımız &amp;ouml;nemli değişiklikler var.&lt;/p&gt;
&lt;h2&gt;Core K&amp;uuml;t&amp;uuml;phanesi&lt;/h2&gt;
&lt;p&gt;LocalizationChallenge.Core isimli class library projesini ele alalım. Farklı t&amp;uuml;rden &amp;ccedil;oklu dil mekanizmaları kullanacağımız i&amp;ccedil;in bunu aşağıdaki aray&amp;uuml;z soyutlaması ile bir s&amp;ouml;zleşme&lt;em&gt;(contract)&lt;/em&gt; haline getirebilriz. B&amp;ouml;ylece web api gibi runtime'larda DI Container sistemiyle ilgili bileşenleri servislere enjekte edebiliriz. Hazırlıklı olmakta yarar var :D&lt;/p&gt;
&lt;pre class="brush:csharp;auto-links:false;toolbar:false" contenteditable="false"&gt;namespace LocalizationChallenge.Core;

public interface ILocalizationProvider
{
    string ProviderName { get; }
    ValueTask&amp;lt;string&amp;gt; GetLocalizedStringAsync(string key, string culture, CancellationToken cancellationToken = default);
}&lt;/pre&gt;
&lt;p&gt;ProviderName alanı&lt;em&gt;(field)&lt;/em&gt; adı &amp;uuml;st&amp;uuml;nde hangi tekniği kullandığımızı belirtmekte. GetLocalizedStringAsync metodu ise kobay olarak kullandığımız davranışı tanımlıyor. Amacımız bir terimin belirtilen dildeki karşılığını d&amp;ouml;nd&amp;uuml;recek fonksiyonu tanımlamak. Veri okuma ile ilgili operasyonlar i&amp;ccedil;in bu davranış şu an i&amp;ccedil;in yeterli. Dolayısıyla asıl provider nesnelerinin bu aray&amp;uuml;z&amp;uuml;&lt;em&gt;(interface)&lt;/em&gt; implemente etmesini bekliyoruz.&lt;/p&gt;
&lt;p&gt;Diğer yandan s&amp;uuml;re &amp;ouml;l&amp;ccedil;&amp;uuml;mlemelerini tutacağımız bir veri nesnesi de işimize yarar. BenchmarkResult isimli bu tipi aşağıdaki gibi t&amp;uuml;retilemez&lt;em&gt;(sealed)&lt;/em&gt; bir record t&amp;uuml;r&amp;uuml; şeklinde tasarlayabiliriz.&lt;/p&gt;
&lt;pre class="brush:csharp;auto-links:false;toolbar:false" contenteditable="false"&gt;namespace LocalizationChallenge.Core;

public sealed record BenchmarkResult(
    string ProviderName,
    string? Value,
    double ElapsedMicroseconds,
    bool CacheHit
);&lt;/pre&gt;
&lt;p&gt;Tipik olarak &amp;ouml;l&amp;ccedil;&amp;uuml;me konu olan provider enstr&amp;uuml;manını, elde edilen değeri&lt;em&gt;(veriyi kontrol etmek i&amp;ccedil;in)&lt;/em&gt;, mikro saniye t&amp;uuml;r&amp;uuml;nden &amp;ouml;l&amp;ccedil;&amp;uuml;m değerini ve cache &amp;uuml;zerinden tedarik edilip edilemediği bilgisini tutuyoruz.&lt;/p&gt;
&lt;h2&gt;Infrastructure K&amp;uuml;t&amp;uuml;phanesi&lt;/h2&gt;
&lt;p&gt;Hakiki provider bileşenlerimiz ise LocalizationChallenge.Infrastructure isimli class library projesinde yer alacak. Postgres, Redis, In-Memory ve hibrit teknikleri i&amp;ccedil;in birer provider sınıfı yazarak devam edelim. Tahmin edileceği &amp;uuml;zere bunlar Core k&amp;uuml;t&amp;uuml;phanesindeki ILocalizationProvider aray&amp;uuml;z&amp;uuml;n&amp;uuml; implemente etmeliler. İlk olarak postgres tarafı ile başlayalım.&lt;/p&gt;
&lt;pre class="brush:csharp;auto-links:false;toolbar:false" contenteditable="false"&gt;using LocalizationChallenge.Core;
using Npgsql;

namespace LocalizationChallenge.Infrastructure;

public sealed class PostgresLocalizationProvider(NpgsqlDataSource dataSource)
    : ILocalizationProvider
{
    public string ProviderName =&amp;gt; "Postgres";

    public async ValueTask&amp;lt;string&amp;gt; GetLocalizedStringAsync(string key, string culture, CancellationToken cancellationToken = default)
    {
        await using var command = dataSource.CreateCommand(
            "SELECT value FROM localizations WHERE resource_key = @key AND culture = @culture LIMIT 1"
        );
        command.Parameters.AddWithValue("key", key);
        command.Parameters.AddWithValue("culture", culture);

        var result = await command.ExecuteScalarAsync(cancellationToken);
        return result as string ?? string.Empty;
    }
}&lt;/pre&gt;
&lt;p&gt;Olduk&amp;ccedil;a basit bir sınıf. key ve culture bilgilerini parametre olarak alan bir SQL sorgusu işletiliyor ve sonu&amp;ccedil; geriye string olarak d&amp;ouml;n&amp;uuml;l&amp;uuml;yor. Tabii bir null check kontrol&amp;uuml;m&amp;uuml;z de var. Hi&amp;ccedil; vakit kaybetmeden redis bileşenimizi geliştirerek devam edelim.&lt;/p&gt;
&lt;pre class="brush:csharp;auto-links:false;toolbar:false" contenteditable="false"&gt;using LocalizationChallenge.Core;
using StackExchange.Redis;

namespace LocalizationChallenge.Infrastructure;

public sealed class RedisLocalizationProvider(IConnectionMultiplexer connectionMultiplexer)
    : ILocalizationProvider
{
    private readonly IDatabase database = connectionMultiplexer.GetDatabase();
    public string ProviderName =&amp;gt; "Redis";

    public async ValueTask&amp;lt;string&amp;gt; GetLocalizedStringAsync(string key, string culture, CancellationToken cancellationToken = default)
    {
        var value = await database.HashGetAsync($"loc::{culture}", key);
        return value.HasValue ? value.ToString() : string.Empty;
    }
}&lt;/pre&gt;
&lt;p&gt;Redis &amp;uuml;zerinden ilgili hashSet'e ulaşıp key değerini geri d&amp;ouml;nd&amp;uuml;r&amp;uuml;yoruz&lt;em&gt;(bulamazsak da boş bir değer)&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Sıradaki bileşen .NET'in in-memory cache &amp;ouml;zelliğini kullanıyor. &amp;Ouml;rneğimizde ele aldığımız &amp;ccedil;oklu dil verilerinin &amp;ccedil;ok sık değişmeyeceğini d&amp;uuml;ş&amp;uuml;n&amp;uuml;rsek yeni tip bir koleksiyon t&amp;uuml;r&amp;uuml;n&amp;uuml; de g&amp;ouml;z &amp;ouml;n&amp;uuml;ne alabiliriz. .NET 8 ile gelen ama .NET 10 tarafında &amp;ouml;nemli performans iyileştirmeleri i&amp;ccedil;eren ve &amp;ouml;zellikle lookup t&amp;uuml;r&amp;uuml;ndeki dictionary veri k&amp;uuml;melerinde *%20 ile %40* arasında daha hızlı olduğu iddia edilen FrozenDictionary sınıfını ele almak i&amp;ccedil;in iyi bir fırsat&lt;em&gt;(Bu koleksiyonu ve &amp;ouml;zelliklerini ayrıca &amp;ccedil;alışmam gerekiyor zira read-only ve performans kritik senaryolar i&amp;ccedil;in bi&amp;ccedil;ilmiş kaftan olduğu iddia edilmekte)&lt;/em&gt;&lt;/p&gt;
&lt;pre class="brush:csharp;auto-links:false;toolbar:false" contenteditable="false"&gt;using LocalizationChallenge.Core;
using Microsoft.Extensions.Hosting;
using Npgsql;
using System.Collections.Frozen;

namespace LocalizationChallenge.Infrastructure;

public sealed class MemoryCacheLocalizationProvider(NpgsqlDataSource dataSource)
    : ILocalizationProvider, IHostedService
{
    // Değişmeyen veri yapılarında, thread-safe olan ve hızlı erişim sağlayan FrozenDictionary bileşenini kullanarak &amp;ouml;nbellek oluşturuyoruz.
    // volatile bildirimi ile cache değişkenine yapılan atamaların t&amp;uuml;m thread'ler tarafından g&amp;ouml;r&amp;uuml;n&amp;uuml;r olması sağlanır.
    // B&amp;ouml;ylece StartAsync metodunda cache g&amp;uuml;ncellendiğinde, diğer thread'ler de bu g&amp;uuml;ncellemeyi g&amp;ouml;rebilir.
    private volatile FrozenDictionary&amp;lt;string, FrozenDictionary&amp;lt;string, string&amp;gt;&amp;gt; cache = FrozenDictionary&amp;lt;string, FrozenDictionary&amp;lt;string, string&amp;gt;&amp;gt;.Empty;
    public string ProviderName =&amp;gt; "InMemoryCache";

    public ValueTask&amp;lt;string&amp;gt; GetLocalizedStringAsync(string key, string culture, CancellationToken cancellationToken = default)
    {
        if (cache.TryGetValue(culture, out var dict) &amp;amp;&amp;amp; dict.TryGetValue(key, out var val))
            return ValueTask.FromResult&amp;lt;string?&amp;gt;(val);

        return ValueTask.FromResult&amp;lt;string?&amp;gt;(null);
    }

    // Bileşenimizi başlatırken, veritabanından t&amp;uuml;m lokalizasyon verilerini &amp;ccedil;ekip, hızlı erişim i&amp;ccedil;in &amp;ouml;nbelleğe y&amp;uuml;klememiz gerekiyor.
    // Bunun i&amp;ccedil;in IHostedService aray&amp;uuml;z&amp;uuml; implementasyonunu kullandık.
    // Api tarafındaki DI container'ına bu bileşeni singleton olarak eklerken, aynı zamanda IHostedService olarak da kaydedeceğiz.
    // B&amp;ouml;ylece uygulama başladığında StartAsync metodu tetiklenecek ve &amp;ouml;nbellek doldurulacak.
    public async Task StartAsync(CancellationToken cancellationToken)
    {
        await using var cmd = dataSource.CreateCommand(
            "SELECT culture, resource_key, value FROM localizations");
        await using var reader = await cmd.ExecuteReaderAsync(cancellationToken);

        var staging = new Dictionary&amp;lt;string, Dictionary&amp;lt;string, string&amp;gt;&amp;gt;(StringComparer.OrdinalIgnoreCase);

        while (await reader.ReadAsync(cancellationToken))
        {
            var culture = reader.GetString(0);
            var resourceKey = reader.GetString(1);
            var value = reader.GetString(2);

            if (!staging.TryGetValue(culture, out var dict))
            {
                dict = new Dictionary&amp;lt;string, string&amp;gt;(StringComparer.Ordinal);
                staging[culture] = dict;
            }
            dict[resourceKey] = value;
        }

        cache = staging.ToFrozenDictionary(
            kvp =&amp;gt; kvp.Key,
            kvp =&amp;gt; kvp.Value.ToFrozenDictionary(StringComparer.Ordinal),
            StringComparer.OrdinalIgnoreCase);
    }

    public Task StopAsync(CancellationToken cancellationToken) =&amp;gt; Task.CompletedTask;
}&lt;/pre&gt;
&lt;p&gt;Tabii bu sınıfın kod i&amp;ccedil;eriği diğerlerine g&amp;ouml;re biraz daha karmaşık. Nitekim sadece ILocalizationProvider s&amp;ouml;zleşmesini değil, IHostedService aray&amp;uuml;z&amp;uuml;n&amp;uuml; de uyguluyor. Buradaki ama&amp;ccedil; DIC&lt;em&gt;(Dependency Injection Container)&lt;/em&gt; tarafında bu bileşen ayağa kalkarken&lt;em&gt;(bir başka deyişle &amp;ouml;rneğimizdeki Web API canlanırken)&lt;/em&gt; Start metodunun devreye girmesi ve SQL tarafında tutulan &amp;ccedil;oklu dil veri setlerinin FrozenDictionary i&amp;ccedil;erisine alınmasını sağlamak. B&amp;ouml;ylece bir verinin belli bir dildeki karşılığı i&amp;ccedil;in bellekte tutulan y&amp;uuml;ksek performanslı dictionary kullanılacak.&lt;/p&gt;
&lt;p&gt;Son olarak hibrit &amp;ccedil;alışan provider sınıfımızı yazalım. Bu bileşenimiz diğer teknikleri harmanlar nitelikte ama &amp;ouml;nemli bir strateji de barındırıyor.&lt;/p&gt;
&lt;pre class="brush:csharp;auto-links:false;toolbar:false" contenteditable="false"&gt;using LocalizationChallenge.Core;
using StackExchange.Redis;

namespace LocalizationChallenge.Infrastructure;

public sealed class HybridLocalizationProvider(
    MemoryCacheLocalizationProvider level1,
    RedisLocalizationProvider level2,
    PostgresLocalizationProvider level3,
    IConnectionMultiplexer redis) : ILocalizationProvider
{
    private readonly IDatabase redisDb = redis.GetDatabase();

    public string ProviderName =&amp;gt; "Hybrid";

    public async ValueTask&amp;lt;string&amp;gt; GetLocalizedStringAsync(string key, string culture, CancellationToken cancellationToken = default)
    {
        var value = await level1.GetLocalizedStringAsync(culture, key, cancellationToken);
        if (value is not null) return value;

        value = await level2.GetLocalizedStringAsync(culture, key, cancellationToken);
        if (value is not null) return value;

        value = await level3.GetLocalizedStringAsync(culture, key, cancellationToken);
        if (value is not null)
        {
            _ = redisDb.HashSetAsync($"loc:{culture}", key, value);
        }

        return value;
    }
}&lt;/pre&gt;
&lt;p&gt;Yapıcı metod&lt;em&gt;(constructor)&lt;/em&gt; &amp;uuml;zerinden d&amp;ouml;rt bileşen enjekte edilmekte. Diğer &amp;ccedil;oklu dil desteği sağlayan provider bileşenleri ve Redis tarafına erişmek i&amp;ccedil;in bir referans. GetLocalizedStringAsync metodunun akışını inceleyelim. Herhangi bir culture i&amp;ccedil;in aranan key:value &amp;ccedil;ifti &amp;ouml;ncelikle in-memory provider'dan karşılanmaya &amp;ccedil;alışılır ki herhangi bir network maliyeti olmadığından ve veri RAM &amp;uuml;zerinde tutulduğundan en hızlı modeldir. Eğer veri level 1 olarak isimlendirilen bu aşamada bulunamazsa ikinci seviyeye inilir&lt;em&gt;(level 2)&lt;/em&gt; ve bu kez daha yavaş olan&lt;em&gt;(&amp;ccedil;&amp;uuml;nk&amp;uuml; arada &amp;ccedil;ıkılması gereken bir network ortamı vardır)&lt;/em&gt; Redis provider bileşeni &amp;uuml;zerinden aranır. İkinci seviyede de aranan &amp;ccedil;ift bulunamazsa son seviyeye inilir ve PostgreSQL provider kullanılır. Burada disk okuma maliyeti olduğu i&amp;ccedil;in diğerlerine g&amp;ouml;re &amp;ccedil;ok daha yavaş bir akış s&amp;ouml;z konusudur.&lt;/p&gt;
&lt;p&gt;Diğer yandan &amp;uuml;&amp;ccedil;&amp;uuml;nc&amp;uuml; seviye sonrasındaki if bloğuna dikkat etmekte yarar var. Veri bu seviyede bulunduysa bir fire and forget &amp;ccedil;ağrısı yapılarak aranan key:value bilgisinin ilgili culture adına Redis ortamına yazılması sağlanır. B&amp;ouml;ylece &amp;ccedil;ok kısa s&amp;uuml;re sonra gelen aynı key:value isteği &amp;uuml;&amp;ccedil;&amp;uuml;nc&amp;uuml; seviyeye inilmeden ikinci seviyeden yani Redis &amp;uuml;zerinden karşılanabilir. Bu strateji Cache Promotion olarak da ifade edilmektedir.&lt;/p&gt;
&lt;p&gt;IDatabase aray&amp;uuml;z&amp;uuml; &amp;uuml;zerinden &amp;ccedil;ağırılan HashSetAsync metodundan gelen Task sonucu, _ operat&amp;ouml;r&amp;uuml; ile g&amp;ouml;rmezden gelinmiştir&lt;em&gt;(discard)&lt;/em&gt; ve hatta dikkat edileceği &amp;uuml;zere await bile kullanılmamıştır. Bu tamamen bilin&amp;ccedil;li bir harekettir zira aranan ve ancak seviye &amp;uuml;&amp;ccedil;te bulunan verinin sonraki &amp;ccedil;ağrılarda yine veritabanından gelmesi yerine Redis'ten gelmesi sağlanır. Bu durum veritabanına yeni veriler eklendiğinde hen&amp;uuml;z Redis'te olmayan değerlerin eklenmesi a&amp;ccedil;ısından da &amp;ouml;nemlidir. &lt;em&gt;(&amp;Ccedil;alışmada hen&amp;uuml;z cache invalidation tarafı i&amp;ccedil;in bir şey yapmadık. Yazının ilerleyen kısmında o da var)&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Infrastructure projemizde epey bir bileşen oldu. Bu bileşenler &amp;ccedil;alışma zamanında diğer uygulamalar&lt;em&gt;(bizim senaryoda Web API olacak)&lt;/em&gt; tarafından Dependency Injection Container &amp;uuml;zerinden kullanılacaklar ve hatta bir&amp;ccedil;ok konfig&amp;uuml;rasyon ayarını da &amp;ccedil;alışma zamanına almamız gerekecek. Dolayısıyla taktik belli; IServiceCollection aray&amp;uuml;z&amp;uuml;n&amp;uuml; genişleterek bu bağımlılıkları infrastructure k&amp;uuml;t&amp;uuml;phanesi &amp;uuml;st&amp;uuml;nden y&amp;uuml;klemek. Bu ama&amp;ccedil;la projeye DependencyInjection isimli aşağıdaki sınıfı ekleyerek devam edelim.&lt;/p&gt;
&lt;pre class="brush:csharp;auto-links:false;toolbar:false" contenteditable="false"&gt;using LocalizationChallenge.Core;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Npgsql;
using StackExchange.Redis;

namespace LocalizationChallenge.Infrastructure;

public static class DependencyInjection
{
    public static IServiceCollection AddLocalizationProviders(this IServiceCollection services, IConfiguration configuration)
    {
        var pgDataSource = NpgsqlDataSource.Create(configuration.GetConnectionString("Postgres"));
        services.AddSingleton(pgDataSource);

        var redis = ConnectionMultiplexer.Connect(configuration.GetConnectionString("Redis"));
        services.AddSingleton&amp;lt;IConnectionMultiplexer&amp;gt;(redis);

        services.AddSingleton&amp;lt;MemoryCacheLocalizationProvider&amp;gt;();
        services.AddSingleton&amp;lt;RedisLocalizationProvider&amp;gt;();
        services.AddSingleton&amp;lt;PostgresLocalizationProvider&amp;gt;();

        services.AddSingleton&amp;lt;ILocalizationProvider&amp;gt;(sp =&amp;gt; sp.GetRequiredService&amp;lt;MemoryCacheLocalizationProvider&amp;gt;());
        services.AddSingleton&amp;lt;ILocalizationProvider&amp;gt;(sp =&amp;gt; sp.GetRequiredService&amp;lt;RedisLocalizationProvider&amp;gt;());
        services.AddSingleton&amp;lt;ILocalizationProvider&amp;gt;(sp =&amp;gt; sp.GetRequiredService&amp;lt;PostgresLocalizationProvider&amp;gt;());
        services.AddSingleton&amp;lt;ILocalizationProvider, HybridLocalizationProvider&amp;gt;();

        services.AddHostedService(sp =&amp;gt; sp.GetRequiredService&amp;lt;MemoryCacheLocalizationProvider&amp;gt;());

        return services;
    }
}&lt;/pre&gt;
&lt;h2&gt;Servis&lt;em&gt;(API)&lt;/em&gt; Projesi&lt;/h2&gt;
&lt;p&gt;Şimdi de API servisimizi geliştirmeye başlayalım. Servisimizin amacı provider'ların API yoluyla &amp;ccedil;alıştığını test edebilmek. Burası doğrudan &amp;uuml;retime &amp;ccedil;ıkabilecek t&amp;uuml;rden bir servis olarak d&amp;uuml;ş&amp;uuml;n&amp;uuml;lebilir. Minimal API olarak geliştirebiliriz. &amp;Ccedil;alışma zamanı i&amp;ccedil;in gerekli konfig&amp;uuml;rasyon ayarlarını da eklememiz lazım. Bu ama&amp;ccedil;la appsettings.json dosyamıza aşağıdaki i&amp;ccedil;eriğe sahip ConnectionStrings b&amp;ouml;l&amp;uuml;m&amp;uuml;n&amp;uuml; ekleyerek devam edelim.&lt;/p&gt;
&lt;pre class="brush:js;auto-links:false;toolbar:false" contenteditable="false"&gt;{
  "ConnectionStrings": {
    "Postgres": "Host=localhost;Port=5435;Database=postgres;Username=johndoe;Password=somew0rds",
    "Redis": "localhost:6379"
  }
}&lt;/pre&gt;
&lt;p&gt;Program kodlarını da aşağıdaki gibi yazabiliriz.&lt;/p&gt;
&lt;pre class="brush:csharp;auto-links:false;toolbar:false" contenteditable="false"&gt;using LocalizationChallenge.Core;
using LocalizationChallenge.Infrastructure;
using Microsoft.AspNetCore.Mvc;
using System.Diagnostics;

var builder = WebApplication.CreateBuilder(args);

// Postgres ve Redis i&amp;ccedil;in connection string bilgileri alınıyor.
var postgresConnStr = builder.Configuration.GetConnectionString("Postgres") ?? throw new InvalidOperationException("Postgres connection string is not configured.");
var redisConnStr = builder.Configuration.GetConnectionString("Redis") ?? throw new InvalidOperationException("Redis connection string is not configured.");

// Localization provider'lar DI container'a ekleniyor.
builder.Services.AddLocalizationProviders(builder.Configuration);

builder.Services.AddOpenApi();

var app = builder.Build();

if (app.Environment.IsDevelopment())
{
    app.MapOpenApi();
}

app.UseHttpsRedirection();

// Adettendir "api ayakta mı?" kontrol&amp;uuml;
app.MapGet("api/health", () =&amp;gt; Results.Ok("Healthy"));

// Belli bir provider, culture ve key i&amp;ccedil;in lokalize edilmiş string değeri d&amp;ouml;nd&amp;uuml;ren endpoint.
app.MapGet("api/localization/{provider}/{culture}/{key}", async (
    string provider,
    string culture,
    string key,
    [FromServices] IEnumerable&amp;lt;ILocalizationProvider&amp;gt; providers,
    CancellationToken cancellationToken) =&amp;gt;
{
    // [FromServices] ile t&amp;uuml;m ILocalizationProvider implementasyonlarını alıyoruz ve istenen provider adına sahip olanı buluyoruz.
    var target = providers.FirstOrDefault(p =&amp;gt; p.ProviderName.Equals(provider, StringComparison.OrdinalIgnoreCase));
    if (target is null) return Results.NotFound(new { error = $"Provider '{provider}' not found." });

    // Performans &amp;ouml;l&amp;ccedil;&amp;uuml;m&amp;uuml; i&amp;ccedil;in Stopwatch kullanarak, lokalize string alma işleminin ne kadar s&amp;uuml;rd&amp;uuml;ğ&amp;uuml;n&amp;uuml; hesaplıyoruz.
    var timer = Stopwatch.GetTimestamp();
    var value = await target.GetLocalizedStringAsync(key, culture, cancellationToken);
    var elapsed = Stopwatch.GetElapsedTime(timer);

    // &amp;Ouml;l&amp;ccedil;&amp;uuml;m sonu&amp;ccedil;larını d&amp;ouml;n&amp;uuml;yoruz
    return Results.Ok(new
    {
        Provider = target.ProviderName,
        Culture = culture,
        Key = key,
        Value = value,
        ElapsedMicroseconds = elapsed.TotalMicroseconds
    });
}).WithDescription("Get localized string by provider, culture, and key.");


// T&amp;uuml;m provider'lar i&amp;ccedil;in belli bir culture ve key'e karşılık gelen string değerleri d&amp;ouml;nd&amp;uuml;ren endpoint.
// Bunu t&amp;uuml;m provider'ların aynı key i&amp;ccedil;in aynı değeri d&amp;ouml;nd&amp;uuml;r&amp;uuml;p d&amp;ouml;nd&amp;uuml;rmediğini kontrol etmek ve performans karşılaştırması yapmak i&amp;ccedil;in kullanabiliriz.
app.MapGet("api/benchmark/{culture}/{key}", async (
    string culture,
    string key,
    [FromServices] IEnumerable&amp;lt;ILocalizationProvider&amp;gt; providers,
    CancellationToken cancellationToken) =&amp;gt;
{
    var results = new List&amp;lt;BenchmarkResult&amp;gt;();

    // DI Container'a kayıtlı t&amp;uuml;m provider'lar i&amp;ccedil;in, verilen culture ve key'e karşılık gelen lokalize string değerini alıp,
    // performans &amp;ouml;l&amp;ccedil;&amp;uuml;m&amp;uuml; yaparak sonu&amp;ccedil;ları topluyoruz.
    foreach (var provider in providers)
    {
        var timer = Stopwatch.GetTimestamp();
        var value = await provider.GetLocalizedStringAsync(key, culture, cancellationToken);
        var elapsed = Stopwatch.GetElapsedTime(timer);

        results.Add(new BenchmarkResult(
            provider.ProviderName,
            value,
            elapsed.TotalMicroseconds,
            CacheHit: value is not null));
    }

    return Results.Ok(new
    {
        Culture = culture,
        Key = key,
        MeasuredAt = DateTime.UtcNow,
        Results = results.OrderBy(r =&amp;gt; r.ElapsedMicroseconds)
    });
}).WithDescription("Benchmark all providers for a given culture and key.");

await app.RunAsync();&lt;/pre&gt;
&lt;p&gt;Bu noktaya kadar her şey doğru ilerlediyse, en azından API'nin başarılı şekilde &amp;ccedil;alıştığını ve birka&amp;ccedil; key:value değerini sorgulayabildiğimizi g&amp;ouml;rmeliyiz. Bunun i&amp;ccedil;in curl veya Postman gibi ara&amp;ccedil;lardan yararlanabiliriz. benchmark endpoint adresine yaptığım Postman &amp;ccedil;ağrısının bir &amp;ccedil;ıktısını aşağıda g&amp;ouml;rebilirsiniz.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://www.buraksenyurt.com/image.axd?picture=/2026/Nisan/LocallyBench_02.png" alt="" /&gt;&lt;/p&gt;
&lt;p&gt;Dolayısıyla tasarladığımız altyapının &amp;ccedil;oklu dil desteği sağladığını garanti etmiş olduk. Şimdi asıl amacımız olan performans &amp;ouml;l&amp;ccedil;&amp;uuml;mlerine ge&amp;ccedil;ebiliriz.&lt;/p&gt;
&lt;h2&gt;Benchmark Projesi&lt;/h2&gt;
&lt;p&gt;Benchmark projemiz bir console uygulaması ve i&amp;ccedil;erisinde BenchmarkDotNet k&amp;uuml;t&amp;uuml;phanesini kullanarak farklı provider'ların performansını &amp;ouml;l&amp;ccedil;meye yarayacak kodları barındıracak. Bu projenin Web API projemizle pek alakası yok. Servis projemiz, ilgili provider'ları API &amp;uuml;zerinden dış d&amp;uuml;nyaya a&amp;ccedil;tığımız bir hizmet sadece.&lt;/p&gt;
&lt;p&gt;Tekrar benchmark projemize d&amp;ouml;nelim. İlk olarak BenchmarkDotNet &amp;ccedil;alışma zamanı ile ilgili bazı konfig&amp;uuml;rasyon ayarlarını yapmamız gerekiyor. Bu ama&amp;ccedil;la ManualConfig tipinden t&amp;uuml;reyen aşağıdaki sınıfı ekleyerek ilerleyelim.&lt;/p&gt;
&lt;pre class="brush:csharp;auto-links:false;toolbar:false" contenteditable="false"&gt;using BenchmarkDotNet.Columns;
using BenchmarkDotNet.Configs;
using BenchmarkDotNet.Diagnosers;
using BenchmarkDotNet.Environments;
using BenchmarkDotNet.Jobs;
using BenchmarkDotNet.Exporters;
using BenchmarkDotNet.Loggers;
using BenchmarkDotNet.Order;

namespace LocalizationChallenge.Benchmarks;

// BenchmarkDotNet konfig&amp;uuml;rasyonu i&amp;ccedil;in &amp;ouml;zel bir sınıf oluşturuyoruz. 
// Bu sınıf, benchmark'larımızın nasıl &amp;ccedil;alışacağını ve hangi &amp;ouml;l&amp;ccedil;&amp;uuml;mleri yapacağını belirlemekte
public class BenchmarkConfig
    : ManualConfig
{
    public BenchmarkConfig()
    {
        AddJob(Job.Default
            .WithRuntime(CoreRuntime.Core10_0) // Testlerimiz .NET 10 &amp;ccedil;alışma zamanında koşulacak
            .WithWarmupCount(3) // Her benchmark i&amp;ccedil;in 3 kez ısınma turu yapılacak, 
            // b&amp;ouml;ylece JIT derlemesi ve diğer başlangı&amp;ccedil; maliyetleri hesaplamalara katılmaz. 
            .WithIterationCount(10) // Isınma turları bittikten sonra her benchmark i&amp;ccedil;in 10 &amp;ouml;l&amp;ccedil;&amp;uuml;m turu yapılacağını belirtmiş oluruz
            .WithInvocationCount(112)); // Her bir iterasyonda (turda) ilgili metotlar arka arkaya 112 kez &amp;ccedil;ağrılacak.(Sadece 6nın katı olması gerektiğini belirten bir hata mesajına istinaden b&amp;ouml;yle yaptım)

        AddLogger(ConsoleLogger.Default); // Terminal ekranına log basılmasını sağlar.
        AddExporter(MarkdownExporter.GitHub); // Test sonu&amp;ccedil;larını GitHub Markdown formatında dışa aktarılmasını sağlar.
        AddDiagnoser(MemoryDiagnoser.Default); // Sadece &amp;ccedil;alışmas s&amp;uuml;resinin değil, ne kadar RAM t&amp;uuml;ketildiğinin ve Garbage Collector'un ne kadar meşgul edildiğinin bilgileri de toplanır.
        AddColumnProvider(DefaultColumnProviders.Instance); // Rapor &amp;ccedil;ıktısına eklenecen kolon adlarını belirler. (&amp;Ouml;rneğin, ortalama s&amp;uuml;re, standart sapma, bellek kullanımı gibi)
        Orderer = new DefaultOrderer(SummaryOrderPolicy.FastestToSlowest); // Sonu&amp;ccedil; tablosu en hızlı metotdan en yavaşa doğru sıralanır.
        Options |= ConfigOptions.JoinSummary;
    }
}&lt;/pre&gt;
&lt;p&gt;Aslında performans &amp;ouml;l&amp;ccedil;&amp;uuml;mleri i&amp;ccedil;in gerekli ayarların belirlendiği bir sınıf. Detaylı a&amp;ccedil;ıklamaları koddaki yorum satırlarında bulabilirsiniz. Tabii birde test edilecek fonksiyonların koşturulması gerekiyor. Bu işi kodları aşağıda g&amp;ouml;r&amp;uuml;len LocalizationBenchmark sınıfı &amp;uuml;stlenecek.&lt;/p&gt;
&lt;pre class="brush:csharp;auto-links:false;toolbar:false" contenteditable="false"&gt;using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Order;
using LocalizationChallenge.Infrastructure;
using Npgsql;
using StackExchange.Redis;

namespace LocalizationChallenge.Benchmarks;

[Config(typeof(BenchmarkConfig))]
[MemoryDiagnoser]
[Orderer(SummaryOrderPolicy.FastestToSlowest)]
public class LocalizationBenchmarks
{
    private PostgresLocalizationProvider _postgres = null!;
    private RedisLocalizationProvider _redis = null!;
    private MemoryCacheLocalizationProvider _memory = null!;
    private HybridLocalizationProvider _hybrid = null!;

    /*
        Testimi sadece tek bir metin i&amp;ccedil;in değil 6 farklı senaryo i&amp;ccedil;in koşulacak.
        3 culture * 2 key olarak d&amp;uuml;ş&amp;uuml;nebiliriz.
        Yani &amp;ccedil;alışma zamanında benchmark buradaki parametrelere g&amp;ouml;re olası t&amp;uuml;m kombinasyonları işletecektir.
    */
    [Params("tr-TR", "en-US", "de-DE")]
    public string Culture { get; set; } = "tr-TR";

    [Params("welcome_message", "button_save")]
    public string Key { get; set; } = "welcome_message";

    /*
        Setup metodu sadece bir kez &amp;ccedil;alışır. Sonu&amp;ccedil;ta benchmark &amp;ouml;l&amp;ccedil;&amp;uuml;mlerinde
        sistem ayağa kaklarken veritabanı bağlantısının oluşturulması, redis'e bağlanılması 
        ve provider nesnelerinin bunları kullanarak oluşturulması gibi işlemler de dahil olmak 
        &amp;uuml;zere t&amp;uuml;m hazırlıkların yapılması gerekir.
        Bunlar &amp;ouml;l&amp;ccedil;&amp;uuml;mlerimizi etkilememli zira &amp;ouml;l&amp;ccedil;mek istediğimi konu bu hazırlık safhası değil.
    */
    [GlobalSetup]
    public async Task Setup()
    {
        const string postgresConn = "Host=localhost;Port=5435;Database=postgres;Username=johndoe;Password=somew0rds";
        const string redisConn = "localhost:6379,abortConnect=false";

        var dataSource = NpgsqlDataSource.Create(postgresConn);
        var redisDb = await ConnectionMultiplexer.ConnectAsync(redisConn);

        _postgres = new PostgresLocalizationProvider(dataSource);
        _redis = new RedisLocalizationProvider(redisDb);
        _memory = new MemoryCacheLocalizationProvider(dataSource);
        _hybrid = new HybridLocalizationProvider(_memory, _redis, _postgres, redisDb);

        await (_memory).StartAsync(CancellationToken.None);
    }

    /*
        Burası yarışmacıların tanımlandığı kısımdır.
        Buradaki metotların her biri 10 tur boyunca 112şer kez &amp;ccedil;ağrılacak. 
    */
    [Benchmark(Baseline = true, Description = "PostgreSQL (no cache)")]
    public ValueTask&amp;lt;string?&amp;gt; PostgreSQL() =&amp;gt; _postgres.GetLocalizedStringAsync(Culture, Key);

    [Benchmark(Description = "Redis (single key)")]
    public ValueTask&amp;lt;string?&amp;gt; Redis() =&amp;gt; _redis.GetLocalizedStringAsync(Culture, Key);

    [Benchmark(Description = "MemoryCache (FrozenDictionary)")]
    public ValueTask&amp;lt;string?&amp;gt; MemoryCache() =&amp;gt; _memory.GetLocalizedStringAsync(Culture, Key);
    [Benchmark(Description = "Hybrid ( Level1 -&amp;gt; Level 2 -&amp;gt; Level 3, warm)")]
    public ValueTask&amp;lt;string?&amp;gt; Hybrid_Warm() =&amp;gt; _hybrid.GetLocalizedStringAsync(Culture, Key);
}&lt;/pre&gt;
&lt;p&gt;Son olarak bu sınıfın &amp;ccedil;alıştırılması lazım :D Bunu da Program.cs dosyasına aşağıdaki kodları ekleyerek yapabiliriz.&lt;/p&gt;
&lt;pre class="brush:csharp;auto-links:false;toolbar:false" contenteditable="false"&gt;using BenchmarkDotNet.Running;

namespace LocalizationChallenge.Benchmarks;

public class Program
{
    public static void Main()
    {
        BenchmarkRunner.Run&amp;lt;LocalizationBenchmarks&amp;gt;(new BenchmarkConfig());
    }
}
&lt;/pre&gt;
&lt;h2&gt;&amp;Ccedil;alışma Zamanı ve Test &amp;Ccedil;ıktıları&lt;/h2&gt;
&lt;p&gt;Kurgumuz neredeyse hazır. &amp;Ccedil;alışma zamanı performanslarını merak ettiğimiz provider bileşenleri, bunları dış d&amp;uuml;nyaya a&amp;ccedil;an API endpoint'leri ve benchmark'ları &amp;ccedil;alıştıracak bir console uygulamamız var. Benchmark projesine dahil olan yarışmacıların maratonunu başlatmak i&amp;ccedil;in projeyi release modda &amp;ccedil;alıştırmamız gerekiyor. Bunun i&amp;ccedil;in terminalden aşağıdaki komutu verebiliriz.&lt;/p&gt;
&lt;pre class="brush:bash;auto-links:false;toolbar:false" contenteditable="false"&gt;dotnet run -c Release --project LocalizationChallenge.Benchmarks/LocalizationChallenge.Benchmarks.csproj&lt;/pre&gt;
&lt;p&gt;Yaptığımız ayarlara g&amp;ouml;re benchmark sonu&amp;ccedil;ları projenin k&amp;ouml;k dizinindeki BenchmarkDotNet.Artifacts/results klas&amp;ouml;r&amp;uuml;n&amp;uuml;n i&amp;ccedil;erisinde markdown formatında bir dosya olarak kaydedilecektir. Bu dosyayı a&amp;ccedil;arak sonu&amp;ccedil;ları inceleyebiliriz ama ben tabloyu hemen buraya da ekliyorum.&lt;/p&gt;
&lt;pre class="brush:plain;auto-links:false;toolbar:false" contenteditable="false"&gt;BenchmarkDotNet v0.15.8, Windows 11 (10.0.26200.8117/25H2/2025Update/HudsonValley2)
12th Gen Intel Core i7-1255U 1.70GHz, 1 CPU, 12 logical and 10 physical cores
.NET SDK 10.0.201
  [Host]     : .NET 10.0.5 (10.0.5, 10.0.526.15411), X64 RyuJIT x86-64-v3
  Job-RLSDCR : .NET 10.0.5 (10.0.5, 10.0.526.15411), X64 RyuJIT x86-64-v3

Runtime=.NET 10.0  InvocationCount=112  IterationCount=10  
WarmupCount=3  &lt;/pre&gt;
&lt;p&gt;ve sonu&amp;ccedil; tablosu: (Makalede okunması i&amp;ccedil;in HTML table'a d&amp;ouml;n&amp;uuml;şt&amp;uuml;r&amp;uuml;ld&amp;uuml;)&lt;/p&gt;
&lt;table border="1" width="100%"&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Method&lt;/th&gt;
&lt;th&gt;Culture&lt;/th&gt;
&lt;th&gt;Key&lt;/th&gt;
&lt;th&gt;Mean&lt;/th&gt;
&lt;th&gt;Error&lt;/th&gt;
&lt;th&gt;StdDev&lt;/th&gt;
&lt;th&gt;Ratio&lt;/th&gt;
&lt;th&gt;RatioSD&lt;/th&gt;
&lt;th&gt;Allocated&lt;/th&gt;
&lt;th&gt;Alloc Ratio&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;'MemoryCache (FrozenDictionary)'&lt;/td&gt;
&lt;td&gt;de-DE&lt;/td&gt;
&lt;td&gt;button_save&lt;/td&gt;
&lt;td&gt;42.68 ns&lt;/td&gt;
&lt;td&gt;1.394 ns&lt;/td&gt;
&lt;td&gt;0.922 ns&lt;/td&gt;
&lt;td&gt;0.000&lt;/td&gt;
&lt;td&gt;0.00&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;0.00&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;'Hybrid ( Level1 -&amp;gt; Level 2 -&amp;gt; Level 3, warm)'&lt;/td&gt;
&lt;td&gt;de-DE&lt;/td&gt;
&lt;td&gt;button_save&lt;/td&gt;
&lt;td&gt;270.24 ns&lt;/td&gt;
&lt;td&gt;6.048 ns&lt;/td&gt;
&lt;td&gt;3.599 ns&lt;/td&gt;
&lt;td&gt;0.000&lt;/td&gt;
&lt;td&gt;0.00&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;0.00&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;'Redis (single key)'&lt;/td&gt;
&lt;td&gt;de-DE&lt;/td&gt;
&lt;td&gt;button_save&lt;/td&gt;
&lt;td&gt;733,373.93 ns&lt;/td&gt;
&lt;td&gt;294,165.710 ns&lt;/td&gt;
&lt;td&gt;194,572.404 ns&lt;/td&gt;
&lt;td&gt;0.704&lt;/td&gt;
&lt;td&gt;0.19&lt;/td&gt;
&lt;td&gt;496 B&lt;/td&gt;
&lt;td&gt;0.16&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;'PostgreSQL (no cache)'&lt;/td&gt;
&lt;td&gt;de-DE&lt;/td&gt;
&lt;td&gt;button_save&lt;/td&gt;
&lt;td&gt;1,051,584.71 ns&lt;/td&gt;
&lt;td&gt;205,259.996 ns&lt;/td&gt;
&lt;td&gt;107,354.947 ns&lt;/td&gt;
&lt;td&gt;1.009&lt;/td&gt;
&lt;td&gt;0.14&lt;/td&gt;
&lt;td&gt;3022 B&lt;/td&gt;
&lt;td&gt;1.00&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;'MemoryCache (FrozenDictionary)'&lt;/td&gt;
&lt;td&gt;de-DE&lt;/td&gt;
&lt;td&gt;welcome_message&lt;/td&gt;
&lt;td&gt;52.68 ns&lt;/td&gt;
&lt;td&gt;6.997 ns&lt;/td&gt;
&lt;td&gt;4.164 ns&lt;/td&gt;
&lt;td&gt;0.000&lt;/td&gt;
&lt;td&gt;0.00&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;0.00&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;'Hybrid ( Level1 -&amp;gt; Level 2 -&amp;gt; Level 3, warm)'&lt;/td&gt;
&lt;td&gt;de-DE&lt;/td&gt;
&lt;td&gt;welcome_message&lt;/td&gt;
&lt;td&gt;314.29 ns&lt;/td&gt;
&lt;td&gt;41.261 ns&lt;/td&gt;
&lt;td&gt;24.554 ns&lt;/td&gt;
&lt;td&gt;0.000&lt;/td&gt;
&lt;td&gt;0.00&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;0.00&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;'Redis (single key)'&lt;/td&gt;
&lt;td&gt;de-DE&lt;/td&gt;
&lt;td&gt;welcome_message&lt;/td&gt;
&lt;td&gt;659,207.09 ns&lt;/td&gt;
&lt;td&gt;264,839.730 ns&lt;/td&gt;
&lt;td&gt;157,601.875 ns&lt;/td&gt;
&lt;td&gt;0.711&lt;/td&gt;
&lt;td&gt;0.18&lt;/td&gt;
&lt;td&gt;504 B&lt;/td&gt;
&lt;td&gt;0.16&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;'PostgreSQL (no cache)'&lt;/td&gt;
&lt;td&gt;de-DE&lt;/td&gt;
&lt;td&gt;welcome_message&lt;/td&gt;
&lt;td&gt;939,895.63 ns&lt;/td&gt;
&lt;td&gt;190,529.039 ns&lt;/td&gt;
&lt;td&gt;113,380.775 ns&lt;/td&gt;
&lt;td&gt;1.014&lt;/td&gt;
&lt;td&gt;0.17&lt;/td&gt;
&lt;td&gt;3170 B&lt;/td&gt;
&lt;td&gt;1.00&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;'MemoryCache (FrozenDictionary)'&lt;/td&gt;
&lt;td&gt;en-US&lt;/td&gt;
&lt;td&gt;button_save&lt;/td&gt;
&lt;td&gt;45.27 ns&lt;/td&gt;
&lt;td&gt;8.443 ns&lt;/td&gt;
&lt;td&gt;5.585 ns&lt;/td&gt;
&lt;td&gt;0.000&lt;/td&gt;
&lt;td&gt;0.00&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;0.00&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;'Hybrid ( Level1 -&amp;gt; Level 2 -&amp;gt; Level 3, warm)'&lt;/td&gt;
&lt;td&gt;en-US&lt;/td&gt;
&lt;td&gt;button_save&lt;/td&gt;
&lt;td&gt;221.43 ns&lt;/td&gt;
&lt;td&gt;6.580 ns&lt;/td&gt;
&lt;td&gt;3.442 ns&lt;/td&gt;
&lt;td&gt;0.000&lt;/td&gt;
&lt;td&gt;0.00&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;0.00&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;'Redis (single key)'&lt;/td&gt;
&lt;td&gt;en-US&lt;/td&gt;
&lt;td&gt;button_save&lt;/td&gt;
&lt;td&gt;474,298.21 ns&lt;/td&gt;
&lt;td&gt;112,236.560 ns&lt;/td&gt;
&lt;td&gt;66,790.177 ns&lt;/td&gt;
&lt;td&gt;0.669&lt;/td&gt;
&lt;td&gt;0.11&lt;/td&gt;
&lt;td&gt;496 B&lt;/td&gt;
&lt;td&gt;0.14&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;'PostgreSQL (no cache)'&lt;/td&gt;
&lt;td&gt;en-US&lt;/td&gt;
&lt;td&gt;button_save&lt;/td&gt;
&lt;td&gt;716,641.16 ns&lt;/td&gt;
&lt;td&gt;125,565.800 ns&lt;/td&gt;
&lt;td&gt;83,054.002 ns&lt;/td&gt;
&lt;td&gt;1.012&lt;/td&gt;
&lt;td&gt;0.15&lt;/td&gt;
&lt;td&gt;3523 B&lt;/td&gt;
&lt;td&gt;1.00&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;'MemoryCache (FrozenDictionary)'&lt;/td&gt;
&lt;td&gt;en-US&lt;/td&gt;
&lt;td&gt;welcome_message&lt;/td&gt;
&lt;td&gt;64.58 ns&lt;/td&gt;
&lt;td&gt;21.730 ns&lt;/td&gt;
&lt;td&gt;12.931 ns&lt;/td&gt;
&lt;td&gt;0.000&lt;/td&gt;
&lt;td&gt;0.00&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;0.00&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;'Hybrid ( Level1 -&amp;gt; Level 2 -&amp;gt; Level 3, warm)'&lt;/td&gt;
&lt;td&gt;en-US&lt;/td&gt;
&lt;td&gt;welcome_message&lt;/td&gt;
&lt;td&gt;242.96 ns&lt;/td&gt;
&lt;td&gt;19.798 ns&lt;/td&gt;
&lt;td&gt;11.781 ns&lt;/td&gt;
&lt;td&gt;0.000&lt;/td&gt;
&lt;td&gt;0.00&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;0.00&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;'Redis (single key)'&lt;/td&gt;
&lt;td&gt;en-US&lt;/td&gt;
&lt;td&gt;welcome_message&lt;/td&gt;
&lt;td&gt;611,825.98 ns&lt;/td&gt;
&lt;td&gt;158,720.398 ns&lt;/td&gt;
&lt;td&gt;104,983.716 ns&lt;/td&gt;
&lt;td&gt;0.540&lt;/td&gt;
&lt;td&gt;0.14&lt;/td&gt;
&lt;td&gt;504 B&lt;/td&gt;
&lt;td&gt;0.17&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;'PostgreSQL (no cache)'&lt;/td&gt;
&lt;td&gt;en-US&lt;/td&gt;
&lt;td&gt;welcome_message&lt;/td&gt;
&lt;td&gt;1,179,268.21 ns&lt;/td&gt;
&lt;td&gt;371,467.479 ns&lt;/td&gt;
&lt;td&gt;245,702.737 ns&lt;/td&gt;
&lt;td&gt;1.040&lt;/td&gt;
&lt;td&gt;0.30&lt;/td&gt;
&lt;td&gt;2985 B&lt;/td&gt;
&lt;td&gt;1.00&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;'MemoryCache (FrozenDictionary)'&lt;/td&gt;
&lt;td&gt;tr-TR&lt;/td&gt;
&lt;td&gt;button_save&lt;/td&gt;
&lt;td&gt;40.67 ns&lt;/td&gt;
&lt;td&gt;7.082 ns&lt;/td&gt;
&lt;td&gt;4.214 ns&lt;/td&gt;
&lt;td&gt;0.000&lt;/td&gt;
&lt;td&gt;0.00&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;0.00&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;'Hybrid ( Level1 -&amp;gt; Level 2 -&amp;gt; Level 3, warm)'&lt;/td&gt;
&lt;td&gt;tr-TR&lt;/td&gt;
&lt;td&gt;button_save&lt;/td&gt;
&lt;td&gt;290.38 ns&lt;/td&gt;
&lt;td&gt;59.079 ns&lt;/td&gt;
&lt;td&gt;35.157 ns&lt;/td&gt;
&lt;td&gt;0.000&lt;/td&gt;
&lt;td&gt;0.00&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;0.00&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;'Redis (single key)'&lt;/td&gt;
&lt;td&gt;tr-TR&lt;/td&gt;
&lt;td&gt;button_save&lt;/td&gt;
&lt;td&gt;618,747.50 ns&lt;/td&gt;
&lt;td&gt;177,637.174 ns&lt;/td&gt;
&lt;td&gt;117,495.992 ns&lt;/td&gt;
&lt;td&gt;0.733&lt;/td&gt;
&lt;td&gt;0.15&lt;/td&gt;
&lt;td&gt;496 B&lt;/td&gt;
&lt;td&gt;0.14&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;'PostgreSQL (no cache)'&lt;/td&gt;
&lt;td&gt;tr-TR&lt;/td&gt;
&lt;td&gt;button_save&lt;/td&gt;
&lt;td&gt;852,865.62 ns&lt;/td&gt;
&lt;td&gt;129,511.326 ns&lt;/td&gt;
&lt;td&gt;85,663.723 ns&lt;/td&gt;
&lt;td&gt;1.010&lt;/td&gt;
&lt;td&gt;0.14&lt;/td&gt;
&lt;td&gt;3533 B&lt;/td&gt;
&lt;td&gt;1.00&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;'MemoryCache (FrozenDictionary)'&lt;/td&gt;
&lt;td&gt;tr-TR&lt;/td&gt;
&lt;td&gt;welcome_message&lt;/td&gt;
&lt;td&gt;32.84 ns&lt;/td&gt;
&lt;td&gt;1.458 ns&lt;/td&gt;
&lt;td&gt;0.868 ns&lt;/td&gt;
&lt;td&gt;0.000&lt;/td&gt;
&lt;td&gt;0.00&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;0.00&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;'Hybrid ( Level1 -&amp;gt; Level 2 -&amp;gt; Level 3, warm)'&lt;/td&gt;
&lt;td&gt;tr-TR&lt;/td&gt;
&lt;td&gt;welcome_message&lt;/td&gt;
&lt;td&gt;278.27 ns&lt;/td&gt;
&lt;td&gt;4.919 ns&lt;/td&gt;
&lt;td&gt;2.927 ns&lt;/td&gt;
&lt;td&gt;0.000&lt;/td&gt;
&lt;td&gt;0.00&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;0.00&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;'Redis (single key)'&lt;/td&gt;
&lt;td&gt;tr-TR&lt;/td&gt;
&lt;td&gt;welcome_message&lt;/td&gt;
&lt;td&gt;517,565.18 ns&lt;/td&gt;
&lt;td&gt;164,111.238 ns&lt;/td&gt;
&lt;td&gt;108,549.423 ns&lt;/td&gt;
&lt;td&gt;0.749&lt;/td&gt;
&lt;td&gt;0.16&lt;/td&gt;
&lt;td&gt;504 B&lt;/td&gt;
&lt;td&gt;0.14&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;'PostgreSQL (no cache)'&lt;/td&gt;
&lt;td&gt;tr-TR&lt;/td&gt;
&lt;td&gt;welcome_message&lt;/td&gt;
&lt;td&gt;693,429.66 ns&lt;/td&gt;
&lt;td&gt;66,129.158 ns&lt;/td&gt;
&lt;td&gt;39,352.401 ns&lt;/td&gt;
&lt;td&gt;1.003&lt;/td&gt;
&lt;td&gt;0.08&lt;/td&gt;
&lt;td&gt;3540 B&lt;/td&gt;
&lt;td&gt;1.00&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;br /&gt;Tablo &amp;ccedil;ok g&amp;uuml;zel oluştu evet harika ama ne anlama geliyor bunca şey :D Nasıl okumak, ne şekilde değerlendirmek lazım. Bizim i&amp;ccedil;in &amp;ouml;nemli olacak terimlerle başlayalım.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Mean: Ortalama s&amp;uuml;re olarak d&amp;uuml;ş&amp;uuml;nebiliriz. İşlemin ne kadar s&amp;uuml;rd&amp;uuml;ğ&amp;uuml; konusunda bilgi verir. Tabii s&amp;uuml;reler olduk&amp;ccedil;a mikro seviyededir. Bir saniye aslında yazılım d&amp;uuml;nyası i&amp;ccedil;in &amp;ccedil;ooooooook uzundur! 1 saniye = 1.000 milisaniye (ms) = 1.000.000 mikrosaniye (&amp;mu;s) = 1.000.000.000 nanosaniye (ns) olarak ifade edilir. &amp;Ouml;rneğin, button_save i&amp;ccedil;in FrozenDictionary kullandığımızda 42.68 ns ortalamada veriye ulaştığımızı g&amp;ouml;r&amp;uuml;yoruz.&lt;/li&gt;
&lt;li&gt;Ratio: Kıyaslama i&amp;ccedil;in kullanılan oran bilgisidir. LocalizationBenchmark sınıfında PostgreSQL se&amp;ccedil;eneği i&amp;ccedil;in kullandığımız Benchmark niteliğinde&lt;em&gt;(attribute)&lt;/em&gt;, baseline değerini true olarak belirlemiştik. Boşuna değildi. Onu 1 değeri olarak sabitlediğimizi ve diğer &amp;ouml;l&amp;ccedil;&amp;uuml;mlerin y&amp;uuml;zdesel olarak ne kadar farklı olduğunu ratio değerleri ile anlarız. &amp;Ouml;rneğin, Redis senaryosunda bu değer yer yer 0,50 seviyesine yaklaşsa da 0,70 civarında. Yani Redis se&amp;ccedil;eneği, PostgreSQL se&amp;ccedil;eneğine g&amp;ouml;re neredeyse %30 daha hızlı şeklinde yorumlayabiliriz.&lt;/li&gt;
&lt;li&gt;Allocated: Bellek &amp;uuml;zerinde garbage collector'a konu olacak yer tahsislerini ifade eder. B&amp;uuml;y&amp;uuml;k değerler &amp;ccedil;ok doğal olarak GC'nin &amp;ccedil;ok daha fazla yorulması anlamına gelir zira toplaması gereken bellek miktarı fazladır. Burada g&amp;ouml;rmek isteyeceğimiz değer genelde - işaretidir :D Sıfır t&amp;uuml;ketim maliyeti. Dikkat ederseniz senaryolarımız arasında hibrit ve FrozenDictionary esaslı in-memory cache kullanımları bunu karşılar t&amp;uuml;rdendir.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Sonu&amp;ccedil;lar &amp;ccedil;ok şaşırtıcı olmasa gerek. Disk &amp;uuml;zerinden ciddi anlamda operasyon maliyeti olan Postgres modeli doğal olarak en yavaş &amp;ccedil;alışan tekniktir. Bununla birlikte kalıcılık&lt;em&gt;(persistence)&lt;/em&gt; a&amp;ccedil;ısından ve hatta SQL gibi domain odaklı bir dil ile i&amp;ccedil;erik ulaşılabilirliği a&amp;ccedil;ısından avantajlıdır. Lakin bizim senaryomuzda iddia, bu teknikler arasındaki erişim hızı farklılıkları &amp;uuml;zerine. Dolayısıyla bellek kullanımı daha ideal bir se&amp;ccedil;enek haline geliyor. Dağıtık veritabanı sistemlerinin in-memory &amp;ccedil;alışan en iyi &amp;ouml;rneklerinden birisi olan Redis, PostgreSQL kullanımına g&amp;ouml;re elbette daha hızlı&lt;em&gt;(0,6 milisaniye civarında ve %30 daha hızlı)&lt;/em&gt;. Ayrıca bellek kullanımı a&amp;ccedil;ısından da avantajlı&lt;em&gt;(3 Kilobyte'a 500 byte gibi)&lt;/em&gt;. Ancak onun da bir network maliyeti olduğunu ve dayanıklılık&lt;em&gt;(resilience)&lt;/em&gt; problemlerini de hesaba katmamızı gerektirdiğini unutmayalım. Bu iki se&amp;ccedil;enek bir yana FrozenDictionary kullanılan senaryoda işlemler nanosaniyeler mertebesinde ger&amp;ccedil;ekleşebiliyor zira burada fiziki disk operasyonları veya network ortamlarına git-gel d&amp;ouml;ng&amp;uuml;leri s&amp;ouml;z konusu değil. Her şey process'in y&amp;uuml;r&amp;uuml;t&amp;uuml;ld&amp;uuml;ğ&amp;uuml; makine belleğinde ger&amp;ccedil;ekleşmekte. Tabii &amp;ouml;l&amp;ccedil;eklenebilir bir sisteme gittiğimizde senkronizasyonu sağlamak da d&amp;uuml;ş&amp;uuml;n&amp;uuml;lmesi gerekenler arasında. D&amp;ouml;rd&amp;uuml;nc&amp;uuml; se&amp;ccedil;eneğimiz bilindiği &amp;uuml;zere hibrit model. Hibrit modelin, in-memory se&amp;ccedil;eneğine g&amp;ouml;re biraz daha yavaş olması son derece normal zira y&amp;uuml;r&amp;uuml;t&amp;uuml;c&amp;uuml; metot i&amp;ccedil;erisinde bir&amp;ccedil;ok null check kontrol&amp;uuml; var. Ancak o da veriyi level 1 mertebesinde FrozenDictionary avantajları ile karşılıyor. Hem hibrit hem de tek başına in-memory modeli, bellek yer tahsisatı&lt;em&gt;(allocation)&lt;/em&gt; konusunda da &amp;ccedil;ok verimliler.&lt;/p&gt;
&lt;p&gt;Hibrit model sahip olduğu strateji gereği tercih edilecek y&amp;ouml;ntem gibi duruyor. Sadece makul erişim s&amp;uuml;releri olarak değil, veriyi farklı seviyelerde ele alma bi&amp;ccedil;imi de dayanıklı kalma, network gecikmelerini absorbe etme gibi avantajlar sağlıyor. Ancak hen&amp;uuml;z bilmecenin &amp;ouml;nemli bir par&amp;ccedil;ası olan cache invalidation tarafını ele almadık. Şimdi bu konuya ge&amp;ccedil;elim.&lt;/p&gt;
&lt;h2&gt;Değişiklikleri Algılama Servisi &lt;em&gt;(Cache Invalidation)&lt;/em&gt;&lt;/h2&gt;
&lt;p&gt;M&amp;uuml;şteriye &amp;ouml;zel terzi dikimi iş &amp;ccedil;&amp;ouml;z&amp;uuml;mlerinden &amp;uuml;r&amp;uuml;nleşmiş olanlarına kadar neredeyse b&amp;uuml;t&amp;uuml;n uygulamalarda nadiren de olsa statik veri k&amp;uuml;melerinin değişimi ya da yenilerinin eklenmesi s&amp;ouml;z konusudur. &amp;Ouml;rneğin m&amp;uuml;şteri ihtiya&amp;ccedil;ları doğrultusunda geliştirilen yeni bir &amp;ouml;zellikteki kullanıcı mesajları, iletiler, bildirimler &amp;ccedil;oklu dil desteği i&amp;ccedil;in yeni kelimelerin girilmesini ya da değiştirilmesini gerektirebilir. Hatta bazı senaryolarda c&amp;uuml;mlelerin &amp;ccedil;oklu dil desteğine g&amp;ouml;re hazırlanması da m&amp;uuml;mk&amp;uuml;nd&amp;uuml;r. "&amp;Ouml;demeniz başarıyla ger&amp;ccedil;ekleştirildi" c&amp;uuml;mlesinin &amp;ccedil;oklu dil desteğine g&amp;ouml;re eklenmesi&lt;em&gt;(Almancacı ş&amp;ouml;yle, "Ihre Zahlung wurde erfolgreich durchgef&amp;uuml;hrt")&lt;/em&gt; veya "&amp;Ouml;demeniz başarılı bir şekilde ger&amp;ccedil;ekleştirildi." şeklinde değiştirilmesi s&amp;ouml;z konusudur. T&amp;uuml;m bu hareketlilik cache'lenen verilerin tekrardan &amp;ccedil;ekilmesini gerektirir.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Bizim &amp;ccedil;alışmamızda Cache-Invalidation &amp;ouml;nemli bir yere sahip. &amp;Ouml;rneğin hibrit metodolojimizde Level 3'te yani veritabanı seviyesinde bir g&amp;uuml;ncelleme olduğunda &amp;ouml;nceden y&amp;uuml;klenen değerler Level 1 ve Level 2 seviyesinde yaşamaya devam edecek ve sistemdeki kullanıcılar eski değere erişecek. Dolayısıyla cache &amp;uuml;zerindeki veriyi &amp;ccedil;eşitli bildirimler ile g&amp;uuml;ncellememiz gerekiyor.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Senaryomuzda &amp;ccedil;oklu dil desteğinin ana kaynağı, PostgreSQL &amp;uuml;zerinde tuttuğumuz localizations isimli veritabanı tablosu. Buradaki değişikliklere g&amp;ouml;re cache &amp;uuml;zerinde yenileme veya d&amp;uuml;ş&amp;uuml;rme gibi işlemleri nasıl ele alabileceğimize bir bakalım. İlk olarak veri tabanı tarafına gerekli enstr&amp;uuml;manları ekleyelim.&lt;/p&gt;
&lt;pre class="brush:sql;auto-links:false;toolbar:false" contenteditable="false"&gt;CREATE OR REPLACE FUNCTION notify_loc_change()
    RETURNS TRIGGER LANGUAGE plpgsql AS $$
BEGIN
    PERFORM pg_notify('loc_changed', NEW.culture || ':' || NEW.resource_key);
    RETURN NEW;
END;
$$;

CREATE OR REPLACE TRIGGER trg_loc_change
    AFTER INSERT OR UPDATE OR DELETE ON localizations
    FOR EACH ROW EXECUTE FUNCTION notify_loc_change();&lt;/pre&gt;
&lt;p&gt;localizations tablosundaki satırlarda veri ekleme, g&amp;uuml;ncelleme veya silme işlemleri ger&amp;ccedil;ekleştirildiğinde trg_loc_change isimli trigger tetiklenir. Bu trigger i&amp;ccedil;erisinden de notify_loc_change isimli fonksiyon &amp;ccedil;ağırılır. Fonksiyonumuzda PostgreSQL i&amp;ccedil;erisine g&amp;ouml;m&amp;uuml;l&amp;uuml; olan pg_notify isimli bir başka fonksiyon &amp;ccedil;ağırılmaktadır. Aslında bu basit bir pub/sub sisteminin tetikleyicisidir. Fonksiyon tetiklendiğinde değişen satırdaki culture ve resource_key bilgileri&lt;em&gt;(ki NEW ile ifade edilir)&lt;/em&gt; kanala g&amp;ouml;nderilir. Bir nevi broadcast yayını s&amp;ouml;z konusudur. Dolayısıyla burayı dinleyen aboneler bu değişikliği yakalar.&lt;/p&gt;
&lt;h2&gt;Background Service ile Değişiklikleri Algılama&lt;/h2&gt;
&lt;p&gt;PostgreSQL tarafı &amp;uuml;zerine d&amp;uuml;şen g&amp;ouml;revi yapacak ve değişiklik olduğunda bunu broadcast yayını ile ortama fırlatacak. Altyapı işlerini tuttuğumuz projemize bir BackgroundService t&amp;uuml;revi ekleyerek devam edelim.&lt;/p&gt;
&lt;pre class="brush:csharp;auto-links:false;toolbar:false" contenteditable="false"&gt;using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Npgsql;
using StackExchange.Redis;

namespace LocalizationChallenge.Infrastructure;

public class LocalizationCacheSenseService(
    NpgsqlDataSource dataSource,
    IConnectionMultiplexer redis,
    MemoryCacheLocalizationProvider memCacheProvider,
    ILogger&amp;lt;LocalizationCacheSenseService&amp;gt; logger) : BackgroundService
{
    protected override async Task ExecuteAsync(CancellationToken cancellationToken)
    {
        // Bir iptal isteği gelene kadar, PostgreSQL veritabanına bağlanarak "loc_changed" kanalını dinliyoruz.
        while (!cancellationToken.IsCancellationRequested)
        {
            try
            {
                await using var conn = await dataSource.OpenConnectionAsync(cancellationToken);

                // Notification event'ine abone olarak, veritabanında bir değişiklik olduğunda tetiklenecek kod bloğu.
                conn.Notification += async (_, args) =&amp;gt;
                {
                    // Eğer bir değişiklik varsa (update,delete, insert) event metodun i&amp;ccedil;erisine d&amp;uuml;şmemiz lazım
                    logger.LogInformation("Localization changed: {Payload}", args.Payload);
                    // postgresql tarafından g&amp;ouml;nderilen payload'u "culture:resourceKey" formatında bekliyoruz.
                    // Bu bilgiyi kullanarak, ilgili cache kaydını Redis'ten siliyoruz.
                    var parts = args.Payload.Split(':', 2);
                    if (parts.Length == 2)
                    {
                        var culture = parts[0];
                        var resourceKey = parts[1];
                        var db = redis.GetDatabase();
                        await db.HashDeleteAsync($"loc:{culture}", resourceKey);
                    }
                    // Ayrıca bellekte duran cache'i de g&amp;uuml;ncellemek adına
                    // MemoryCacheLocalizationProvider'ın StartAsync metodunu &amp;ccedil;ağırıyoruz
                    // Dolayısıyla debug ederken oraya da gidebiliyor olmamız lazım
                    await memCacheProvider.StartAsync(CancellationToken.None);
                };

                // PostgreSQL'de LISTEN komutunu kullanarak loc_changed kanalını dinlememiz gerekiyor.
                // Zira oradaki trigger i&amp;ccedil;erisinden tetiklenen fonksiyon bu isimle bir yayın yapmakta
                await using var cmd = conn.CreateCommand();
                cmd.CommandText = "LISTEN loc_changed";
                await cmd.ExecuteNonQueryAsync(cancellationToken);

                logger.LogInformation("Listening for localization changes on PostgreSQL channel loc_changed");

                while (!cancellationToken.IsCancellationRequested)
                    await conn.WaitAsync(cancellationToken);
            }
            catch (OperationCanceledException)
            {
                break;
            }
            catch (Exception ex)
            {
                logger.LogWarning(ex, "Localization invalidation connection lost. Reconnecting in 5s...");
                await Task.Delay(TimeSpan.FromSeconds(5), cancellationToken);
            }
        }
    }
}&lt;/pre&gt;
&lt;p&gt;Tabii eklediğimiz bu servisi Infrastructure projemizdeki DependencyInjection sınıfına da eklememiz gerekiyor.&lt;/p&gt;
&lt;pre class="brush:csharp;auto-links:false;toolbar:false" contenteditable="false"&gt;services.AddHostedService&amp;lt;LocalizationCacheSenseService&amp;gt;();&lt;/pre&gt;
&lt;p&gt;Artık testlerimizi yapabiliriz. API projesini ayağa kaldırdığımızda, Infrastructure projesinden DIC'ye eklenen servisler de ayağa kalkar. Dolayısıyla PostgreSQL tarafındaki değişiklikleri dinleyen LocalizationCacheSenseService de &amp;ccedil;alışmaya başlar. Ardından pgAdmin ya da DBeaver ile tablo &amp;uuml;zerinde değişiklikler yapabiliriz. Bunu debug modda denemenizi şiddetle tavsiye ederim. Zira, değişiklikleri commit edince Visual Studio ortamında ilgili event metodunun tetiklendiğini ve cache'in temizlendiğini g&amp;ouml;rebilirsiniz. Sonrasında Postman &amp;uuml;zerinden aynı key ve culture bilgileriyle API &amp;ccedil;ağrısı yapıp, değişikliklerin yansıyıp yansımadığını kontrol edebiliriz.&lt;/p&gt;
&lt;h2&gt;NBomber ile Y&amp;uuml;k Testi&lt;/h2&gt;
&lt;p&gt;Şu ana kadar yaptıklarımızı bir değerlendirelim. Hedefimiz &amp;ccedil;oklu dil desteği senaryolarında farklı yaklaşımlar arasındaki hız farkını incelemekti. Ana kaynak olarak bir veritabanı sistemi kullandık ancak &amp;ouml;zellikle hibrit sistemimiz buradaki veriyi level 2 ve level 1 olmak &amp;uuml;zere daha hızlı noktalardan da karşılar oldu. Bununla birlikte &amp;ccedil;oklu dil k&amp;uuml;melerine doğrudan API &amp;ccedil;ağrıları ile de erişebiliyoruz. Ger&amp;ccedil;ek hayat senaryolarında dil bağımlı b&amp;uuml;y&amp;uuml;k bir veri setinin veya par&amp;ccedil;alarının servisler yoluyla &amp;ccedil;ekilmesi pekala s&amp;ouml;z konusu olabilir. Hal b&amp;ouml;yle olunca bir y&amp;uuml;k testi&lt;em&gt;(load test)&lt;/em&gt; ve sonrasında ortaya &amp;ccedil;ıkacak tablonun da incelenmesi gerekiyor. Bu ama&amp;ccedil;la NBomber isimli .NET k&amp;uuml;t&amp;uuml;phanesini kullanarak bir deneme yapabiliriz. &amp;Ccedil;&amp;ouml;z&amp;uuml;m&amp;uuml;m&amp;uuml;ze yeni bir console uygulaması ekleyip gerekli nuget paketlerini y&amp;uuml;kleyelim.&lt;/p&gt;
&lt;pre class="brush:csharp;auto-links:false;toolbar:false" contenteditable="false"&gt;# Console projesi oluşturuluyor
dotnet new console -n LocalizationChallenge.LoadTest
# NBomber ile ilgili eklentiler
dotnet add LocalizationChallenge.LoadTest/LocalizationChallenge.LoadTest.csproj package NBomber
dotnet add LocalizationChallenge.LoadTest/LocalizationChallenge.LoadTest.csproj package NBomber.Http

dotnet sln add LocalizationChallenge.LoadTest&lt;/pre&gt;
&lt;p&gt;Sırada NBomber kullanımı i&amp;ccedil;in gerekli kodlarımız var.&lt;/p&gt;
&lt;pre class="brush:csharp;auto-links:false;toolbar:false" contenteditable="false"&gt;using NBomber.Contracts;
using NBomber.CSharp;
using NBomber.Http.CSharp;
using System.Text.Json;

namespace LocalizationChallenge.LoadTest;

class Program
{
    static void Main()
    {
        var postgresMetric = Metric.CreateGauge("postgres_read_us", unitOfMeasure: "us");
        var redisMetric = Metric.CreateGauge("redis_read_us", unitOfMeasure: "us");
        var memoryMetric = Metric.CreateGauge("memory_read_us", unitOfMeasure: "us");
        var hybridMetric = Metric.CreateGauge("hybrid_read_us", unitOfMeasure: "us");

        var cultures = new[] { "tr-TR", "en-US", "de-DE" };
        var keys = new[] { "welcome_message", "farewell_message", "button_save", "error_not_found" };

        var handler = new HttpClientHandler
        {
            ServerCertificateCustomValidationCallback = HttpClientHandler.DangerousAcceptAnyServerCertificateValidator
        };
        var httpClient = new HttpClient(handler);

        var scenario = Scenario.Create("ramp_up_scenario", async context =&amp;gt;
        {
            var culture = cultures[Random.Shared.Next(cultures.Length)];
            var key = keys[Random.Shared.Next(keys.Length)];

            var request = Http.CreateRequest("GET", $"https://localhost:7092/api/benchmark/{culture}/{key}");
            var response = await Http.Send(httpClient, request);

            if (response.StatusCode != "OK" || response.Payload.Value == null)
            {
                return Response.Fail(statusCode: response.StatusCode);
            }

            try
            {
                using var httpResponse = response.Payload.Value;
                await using var stream = await httpResponse.Content.ReadAsStreamAsync();
                using var jsonDoc = await JsonDocument.ParseAsync(stream);

                if (jsonDoc.RootElement.TryGetProperty("results", out var results))
                {
                    foreach (var result in results.EnumerateArray())
                    {
                        var provider = result.GetProperty("providerName").GetString();
                        var elapsed = result.GetProperty("elapsedMicroseconds").GetDouble();

                        switch (provider)
                        {
                            case "Postgres": postgresMetric.Set(elapsed); break;
                            case "Redis": redisMetric.Set(elapsed); break;
                            case "InMemoryCache": memoryMetric.Set(elapsed); break;
                            case "Hybrid": hybridMetric.Set(elapsed); break;
                        }
                    }
                }

                return Response.Ok(statusCode: response.StatusCode);
            }
            catch (Exception ex)
            {
                context.Logger.Error(ex, "JSON parse error has been occured.");
                return Response.Fail(statusCode: response.StatusCode, "Parse Error");
            }
        })
        .WithWarmUpDuration(TimeSpan.FromSeconds(5))
        .WithLoadSimulations(
            Simulation.RampingConstant(copies: 100, during: TimeSpan.FromSeconds(30)),
            Simulation.KeepConstant(copies: 100, during: TimeSpan.FromSeconds(60)),
            Simulation.RampingConstant(copies: 0, during: TimeSpan.FromSeconds(10))
        );

        NBomberRunner
            .RegisterScenarios(scenario)
            .Run();
    }
}&lt;/pre&gt;
&lt;p&gt;Tabii testler sırasında bazı beklenmedik durumlar da oldu. &amp;Ouml;rneğin bu kadar &amp;ccedil;ok isteğin PostgreSQL veritabanına gitmesi "53300: sorry, too many clients already" şeklinde bir &amp;ccedil;alışma zamanı istisnası&lt;em&gt;(runtime exception)&lt;/em&gt; oluşmasına neden oldu. Bu problemi aşmak i&amp;ccedil;in havuzda duracak maksimum ve minimum bağlantı sayısını ayarlamak gerekiyor. Dolayısıyla ilgili connection string bilgisine Maximum Pool Size ve Minimum Pool Size parametrelerini eklemeliyiz.&lt;/p&gt;
&lt;pre class="brush:js;auto-links:false;toolbar:false" contenteditable="false"&gt;"ConnectionStrings": {
    "Postgres": "Host=localhost;Port=5435;Database=postgres;Username=johndoe;Password=somew0rds;Maximum Pool Size=20;Minimum Pool Size=5"
  }&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;&amp;Ouml;nemli Not: NBomber bireysel olarak &amp;uuml;cretsiz kullanılabilen bir ara&amp;ccedil; ancak &amp;uuml;retim ortamlarında lisanslama gerektirebilir. Detaylar i&amp;ccedil;in &lt;a href="https://nbomber.com/" target="_blank"&gt;NBomber&lt;/a&gt; sayfasını incelemekte yarar var. Ya da hafifsiklet bir load test aracını kendimiz yazabiliriz de.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;LoadTest isimli konsol uygulaması &amp;ccedil;alıştırdığımızda reports isimli klas&amp;ouml;r i&amp;ccedil;erisinde &amp;ouml;l&amp;ccedil;&amp;uuml;mlere ait dosyalar oluşur. HTML, markdown, text ve csv formatlarında oluşan bu dosyalar i&amp;ccedil;erisinde tabiri caizse ne ararsak var :D&lt;/p&gt;
&lt;p&gt;&lt;img src="https://www.buraksenyurt.com/image.axd?picture=/2026/Nisan/LocallyBench_03.png" alt="" /&gt;&lt;/p&gt;
&lt;p&gt;Hatta şuraya markdown dosyasındaki &amp;ouml;l&amp;ccedil;&amp;uuml;m değerlerini i&amp;ccedil;eren tabloları da ekleyelim. Ancak benim &amp;ouml;nerim &amp;ouml;zellikle HTML sayfasını incelemeniz y&amp;ouml;n&amp;uuml;nde olacak.&lt;/p&gt;
&lt;table border="1"&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;step&lt;/th&gt;
&lt;th&gt;ok stats&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;name&lt;/td&gt;
&lt;td&gt;global information&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;request count&lt;/td&gt;
&lt;td&gt;all = 197178, ok = 197178, RPS = 1971.78&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;latency (ms)&lt;/td&gt;
&lt;td&gt;min = 1.68, mean = 40.39, max = 267.74, StdDev = 19.29&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;latency percentile (ms)&lt;/td&gt;
&lt;td&gt;p50 = 39.17, p75 = 51.42, p95 = 73.54, p99 = 94.4&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;table border="1"&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;status code&lt;/th&gt;
&lt;th&gt;count&lt;/th&gt;
&lt;th&gt;message&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;OK&lt;/td&gt;
&lt;td&gt;197178&lt;/td&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2&gt;Load Test Sonu&amp;ccedil;larını Nasıl Yorumlamalıyız?&lt;/h2&gt;
&lt;p&gt;Burada ağ gecikmelerini (Network Latency), Kestrel sunucusunun tepkisini, routing işlemlerini ve elde edilen sonu&amp;ccedil;ların JSON olarak işlenip geri d&amp;ouml;nd&amp;uuml;r&amp;uuml;lmesini kapsayan u&amp;ccedil;tan uca bir &amp;ouml;l&amp;ccedil;&amp;uuml;mleme yapılmakta. Yani benchmark testlerinden daha farklı bir durum s&amp;ouml;z konusu.&lt;/p&gt;
&lt;p&gt;Benim elde ettiğim sonu&amp;ccedil;lar 100 saniyelik zaman diliminde&lt;em&gt;(1 dakika 40 saniye)&lt;/em&gt; ger&amp;ccedil;ekleşmiş durumda. Bu aralıkta 200 bine yakın talep yollanmış ve 0 hata alınmış. Saniyede ortalama 2000 talep işlenmiş. Bir kullanıcı isteğinin API tarafına ulaşması, işlenmesi ve geri d&amp;ouml;nmesi de ortalama 40 milisaniye s&amp;uuml;rm&amp;uuml;ş. Bununla birlikte latency percentile sekmesinde p50, p75, p95 ve p99 şeklinde bazı değerler yer aldığını g&amp;ouml;rebilirsiniz. Bu değerleri de ş&amp;ouml;yle yorumlayalım; Her 100 kullanıcıdan 99'u cevabını 94.4 milisaniyenin altında almış. Yani 100 kullanıcıdan 1 tanesi cevabını 94.4 milisaniyeden daha ge&amp;ccedil; almış. Buna g&amp;ouml;re 100 kullanıcıdan 95'i cevabını 73.54 milisaniyenin altında bir s&amp;uuml;rede alırken 5 tanesi cevabını 73.54 milisaniyeden daha ge&amp;ccedil; almış. Dolayısıyla p y&amp;uuml;zdeliklerini bu mantıkta okuyabiliriz. Aslında g&amp;ouml;rsel anlatımı &amp;ccedil;ok daha etkili ne yalan s&amp;ouml;yleyeyim&lt;em&gt;(Bir resim bin kelimeye bedel mi?)&lt;/em&gt; O y&amp;uuml;zden HTML raporunu incelemenizi &amp;ouml;neririm :D&lt;/p&gt;
&lt;p&gt;&lt;img src="https://www.buraksenyurt.com/image.axd?picture=/2026/Nisan/LocallyBench_04.png" alt="" /&gt;&lt;/p&gt;
&lt;p&gt;Diğer yandan kod i&amp;ccedil;erisinde birer Gauge olarak tanımladığımız &amp;ouml;zel metriklerde &amp;ccedil;alışma s&amp;uuml;relerine dair &amp;ouml;l&amp;ccedil;&amp;uuml;mler yer alır ve HTML dosyasından ulaşılabilir.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://www.buraksenyurt.com/image.axd?picture=/2026/Nisan/LocallyBench_05.png" alt="" /&gt;&lt;/p&gt;
&lt;p&gt;B&amp;ouml;ylece geldik bir &amp;ccedil;alışmamızın daha sonuna. Benim i&amp;ccedil;in uzun ama epey eğitici oldu. En azından bir test d&amp;uuml;zeneğini &amp;ouml;rnek bir senaryo ile ele alma şansı buldum. Umarım sizler i&amp;ccedil;in de faydalı olmuştur. Tekrardan g&amp;ouml;r&amp;uuml;ş&amp;uuml;nceye dek hepinizi mutlu g&amp;uuml;nler dilerim.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://github.com/buraksenyurt/friday-night-programmer/tree/main/src/LocalizationChallenge" target="_blank"&gt;&amp;Ccedil;alışmaya ait &amp;ouml;rnek kodlara GitHub &amp;uuml;zerinden ulaşabilirsiniz.&lt;/a&gt;&lt;/p&gt;</summary>
    <published>2026-04-11T08:47:00+00:00</published>
    <link rel="related" href="https://www.buraksenyurt.com/post/hangi-localization-teknigi#comment" />
    <category term="C#" />
    <betag:tag>cSharp</betag:tag>
    <betag:tag>localization</betag:tag>
    <betag:tag>dotnet</betag:tag>
    <betag:tag>benchmark</betag:tag>
    <betag:tag>benchmarkdotnet</betag:tag>
    <betag:tag>redis</betag:tag>
    <betag:tag>frozendictionary</betag:tag>
    <betag:tag>distributed caching</betag:tag>
    <betag:tag>NBomber</betag:tag>
    <betag:tag>distributed systems</betag:tag>
    <betag:tag>postgresql</betag:tag>
    <betag:tag>cache invalidation</betag:tag>
    <dc:publisher>bsenyurt</dc:publisher>
    <dc:description>Tartışmanın konusu, çooooook uzun zamandır dünyamızda var olan çoklu dil desteği (Multi-Language Support). Kimi zaman veritabanı üzerinden, kimi zaman fiziki dosyalardan (resx gibi) yönetmeye çalıştığımız bir mevzu. Sürekli değişip genişleyebilenler bir yana, nadiren değişip genellikle statik kalanlardan oluşan veri kümeleri de cabası. Aslında temel amaç, bir program arayüzünün veya kullanıcı ile etkileşimde olan taraflarının farklı dillerde destek vermesini sağlamak. Teori basit; değişmez sabit bir kavram (key diyelim) karşılığında, kullanılan dile göre farklı değerler tutulmasını sağlamak.</dc:description>
    <pingback:server>https://www.buraksenyurt.com/pingback.axd</pingback:server>
    <pingback:target>https://www.buraksenyurt.com/post.aspx?id=afd27236-dbfd-40ac-9153-c003506fe13b</pingback:target>
    <slash:comments>0</slash:comments>
    <trackback:ping>https://www.buraksenyurt.com/trackback.axd?id=afd27236-dbfd-40ac-9153-c003506fe13b</trackback:ping>
    <wfw:comment>https://www.buraksenyurt.com/post/hangi-localization-teknigi#comment</wfw:comment>
    <wfw:commentRss>https://www.buraksenyurt.com/syndication.axd?post=afd27236-dbfd-40ac-9153-c003506fe13b</wfw:commentRss>
  </entry>
  <entry>
    <id>https://www.buraksenyurt.com/post/smart-enums</id>
    <title>Smart Enums</title>
    <updated>2026-04-01T16:53:00+00:00</updated>
    <link rel="self" href="https://www.buraksenyurt.com/post.aspx?id=4250f42a-8dfb-4888-92b5-f4d13e183f0a" />
    <link href="https://www.buraksenyurt.com/post/smart-enums" />
    <author>
      <name>bsenyurt</name>
    </author>
    <summary type="html">&lt;p&gt;Yazılım geliştirme galaksisinin en zorlu yolculuklarından birisi sanıyorum ki Domain Driven Design&lt;em&gt;(DDD)&lt;/em&gt; rotasında ilerlemek. B&amp;uuml;y&amp;uuml;k &amp;ccedil;aplı kurumsal projelerde hangi mimari ile &amp;ccedil;alışacağımıza karar vermek bir yana dursun domain sınırlarını belirlemek, model nesneleri kurgulamak, ortak jargonu &amp;ccedil;ıkarmak ve bu jargonu kod i&amp;ccedil;erisinde nasıl temsil edeceğimize karar vermek gibi pek &amp;ccedil;ok zorluğu beraberinde getiren bir yolculuk. Ger&amp;ccedil;ekten farklı yetkinlikler gerektiğine inandığım bu yaklaşımda g&amp;uuml;n ge&amp;ccedil;miyor ki yeni bir konuyu tartışalım. İşte hen&amp;uuml;z ger&amp;ccedil;ekleştirdiğimiz bir tartışma:&lt;/p&gt;
&lt;h2&gt;Problem&lt;/h2&gt;
&lt;p&gt;Diyelim ki sipariş talep formlarını&lt;em&gt;(OrderForm olarak ifade edelim)&lt;/em&gt; ele aldığımız bir &amp;ccedil;er&amp;ccedil;evede &amp;ccedil;alışıyoruz. &amp;Uuml;zerinde duracağımız konu bir sipariş formunun herhangi bir andaki durumunu nasıl temsil edeceğimiz. S&amp;ouml;z gelimi C# gibi nesne y&amp;ouml;nelimli bir dil kullanıyorsak bir enum t&amp;uuml;r&amp;uuml; ile bunu pekala sağlayabiliriz. Zira sipariş formu stat&amp;uuml;leri &amp;ccedil;oğunlukla bellidir ve değişmez&lt;em&gt;(Approved, Rejected, Canceled vb)&lt;/em&gt; Tam o sırada bir arkadaşımız ş&amp;ouml;yle seslenir; "Bu &amp;uuml;r&amp;uuml;n&amp;uuml; farklı firmalar alacak ve bu firmaların ele alacağı senaryolarda var olan stat&amp;uuml;lerin genişletilmesi gerekebilir". &amp;Ouml;rneğin sipariş formu stat&amp;uuml;leri arasında "M&amp;uuml;d&amp;uuml;r Onayı Gerekiyor" &lt;em&gt;(ManagerApprovalRequired)&lt;/em&gt; gibi farklı bir stat&amp;uuml; varsa... Bu durumda ne yapacağız? &amp;Uuml;r&amp;uuml;n&amp;uuml;m&amp;uuml;z&amp;uuml;n doğası gereği &amp;ccedil;ekirdek domain modelimizi korumamız gerekiyor ama aynı zamanda m&amp;uuml;şterinin ihtiyacına g&amp;ouml;re de genişletilmesi. Enum yapısı bu esnekliği sağlayabilir mi? Rust ile geliştiriyor olsaydık farklı bir şekilde değerlendirebilirdik durumu ancak C# a&amp;ccedil;ısından konuya bakarsak belki de bu durumu sadece enum t&amp;uuml;r&amp;uuml; ile değil bir başka Value Object tasarlayarak ele almak gerekecek.&lt;/p&gt;
&lt;p&gt;Bu problemde birka&amp;ccedil; noktaya da dikkat etmemiz gerekiyor.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&amp;Ouml;ncelikle kodumuzun yeni stat&amp;uuml;ler eklenmesine izin verecek şekilde a&amp;ccedil;ık olmasını sağlamak ama mevcut domain kurallarının da değiştirilmesini engellemek istiyoruz. Bir nevi Open/Closed Principle vakasıyla karşı karşıya olduğumuzu ifade edebiliriz.&lt;/li&gt;
&lt;li&gt;Kuvvetle muhtemel bu &amp;uuml;r&amp;uuml;n ilişkisel bir veritabanı sistemi kullanacak ve stat&amp;uuml; gibi enum benzeri yapıları saklarken metinsel bir değer yerine sayısal karşılıklarını kullanacağız. &amp;Ouml;yle bir yaklaşıma gitmeliyiz ki &amp;ouml;rneğin veri tabanında TenantId, Name, CoreStatusId gibi bir tablo kullanabilelim.&lt;/li&gt;
&lt;li&gt;Ayrıca, bu stat&amp;uuml;lerin iş mantığı ile nasıl etkileşime gireceğini de d&amp;uuml;ş&amp;uuml;nmemiz gerekiyor. &amp;Ouml;rneğin, belirli bir stat&amp;uuml;ye ge&amp;ccedil;işin hangi koşullarda m&amp;uuml;mk&amp;uuml;n olduğunu ve bu ge&amp;ccedil;işlerin nasıl y&amp;ouml;netileceğini ayarlamalı, genişletilen stat&amp;uuml;lerin de bu kurallara uymasını sağlamamız lazım.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;ve daha aklıma gelmeyen başka başka sorunlar...&lt;/p&gt;
&lt;h2&gt;&amp;Ccedil;&amp;ouml;z&amp;uuml;m Yolu&lt;/h2&gt;
&lt;p&gt;Domain Driven Design a&amp;ccedil;ısından olaya bakarsak değişmezler olarak &amp;ccedil;evirebileceğimiz invariants &amp;ouml;nemli bir role sahiptir. OrderForm gibi aslında bir Aggregate Root olarak tanımlayabileceğimiz bir nesne modeli s&amp;ouml;z konusu olduğunda, bu modelin durumunu temsil eden stat&amp;uuml;lerin de belirli değişmez değerlere sahip olması beklenir. Zira verinin her zaman tutarlı ve kurallara uygun&lt;em&gt;(ge&amp;ccedil;erli)&lt;/em&gt; bir durumda kalması sağlanmalıdır. Firmaların kendi stat&amp;uuml;lerini eklemelerine izin verdiğimizde, temel domain &amp;uuml;zerinde konuşlandırdığımız iş kurallarının ihlal edilme riski ortaya &amp;ccedil;ıkar. &amp;Ouml;rneğin, "sadece onaylandı stat&amp;uuml;s&amp;uuml;ndeki siparişlerin iptal edilebileceği" gibi bir kuralımız varsa ve &amp;uuml;r&amp;uuml;n&amp;uuml;m&amp;uuml;z&amp;uuml; kullanan firma "&amp;Uuml;st Y&amp;ouml;netici İncelemesinde&lt;em&gt;(ManagerApprovalRequired)&lt;/em&gt;" şeklinde yeni bir stat&amp;uuml; eklerse domain'imiz gelen yeni stat&amp;uuml;n&amp;uuml;n iptal edilebilir olup olmadığını bilemeyecektir. Bu sorunu ş&amp;ouml;yle &amp;ccedil;&amp;ouml;zebiliriz.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Domain tarafından kesinlikle bilinmesi gereken stat&amp;uuml;leri yine bir Enum t&amp;uuml;r&amp;uuml; ile tanımlayabiliriz. &amp;Ouml;rneğin OrderFormStatus isimli bir enum kullanılabilir. Bunlar ana stat&amp;uuml;lerdir ve domain'in temel iş kurallarına g&amp;ouml;re hareket ederler.&lt;/li&gt;
&lt;li&gt;M&amp;uuml;şteri yeni bir stat&amp;uuml; ekleyecekse bu stat&amp;uuml; mutlaka bir OrderFormStatus'a bağlanmalıdır. Yani bir nevi mapping yapısı kurgularız.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;C# Yaklaşımı&lt;/h2&gt;
&lt;div&gt;Bu kadar laf kalabalığından sonra gelin C# tarafında &amp;ccedil;ok basit bir şekilde durumu ele alalım. İlk olarak &amp;ccedil;ekirdek Enum t&amp;uuml;r&amp;uuml;m&amp;uuml;z&amp;uuml; tanımlayalım.&lt;/div&gt;
&lt;div&gt;
&lt;pre class="brush:csharp;auto-links:false;toolbar:false" contenteditable="false"&gt;public enum OrderFormStatus
{
    Draft,
    Canceled,
    Completed,
    Processing
}&lt;/pre&gt;
Şimdi standart stat&amp;uuml;leri ve m&amp;uuml;şteriye &amp;ouml;zel stat&amp;uuml;leri tutabileceğimiz değer t&amp;uuml;r&amp;uuml; nesnemizi&lt;em&gt;(Value Object)&lt;/em&gt; tasarlayalım ki bunu Smart Enum olarak isimlendirebiliriz.&lt;/div&gt;
&lt;div&gt;
&lt;pre class="brush:csharp;auto-links:false;toolbar:false" contenteditable="false"&gt;public class OrderFormTenantStatus // : ValueObject
{
    public Guid Id { get; }
    public Guid TenantId { get; }
    public string Name { get; }
    public OrderFormStatus CoreStatus { get; }

    private OrderFormTenantStatus(Guid id, Guid tenantId, string name, OrderFormStatus coreStatus)
    {
        if (string.IsNullOrEmpty(name))
            throw new ArgumentException("Name cannot be null or empty.", nameof(name));

        Id = id;
        TenantId = tenantId;
        Name = name;
        CoreStatus = coreStatus;
    }

    public static readonly OrderFormTenantStatus Draft = new(Guid.NewGuid(), Guid.Empty, "Draft", OrderFormStatus.Draft);
    public static readonly OrderFormTenantStatus Processing = new(Guid.NewGuid(), Guid.Empty, "Processing", OrderFormStatus.Processing);
    public static readonly OrderFormTenantStatus Completed = new(Guid.NewGuid(), Guid.Empty, "Completed", OrderFormStatus.Completed);
    public static readonly OrderFormTenantStatus Canceled = new(Guid.NewGuid(), Guid.Empty, "Canceled", OrderFormStatus.Canceled);

    public static OrderFormTenantStatus Create(Guid id, Guid tenantId, string name, OrderFormStatus mappedCoreStatus)
    {
        return new OrderFormTenantStatus(id, tenantId, name, mappedCoreStatus);
    }
}&lt;/pre&gt;
Ne g&amp;uuml;zel tek bir enum değerine bağlayarak devam edecektik değil mi? :D Ama işte ger&amp;ccedil;ek d&amp;uuml;nya senaryolarında durum b&amp;ouml;yle olmuyor. Nihayetinde taban sistem ve genişleyebildiği d&amp;uuml;nya a&amp;ccedil;ısından baktığımızda bir sipariş formunun durumunu belirtmek, onu sayısal, metinsel ve d&amp;ouml;n&amp;uuml;şebileceği diğer stat&amp;uuml;lerle ilişkilendirdiğimiz daha yetenekli bir nesne modeline ihtiya&amp;ccedil; duyuyor. Denklem işin i&amp;ccedil;erisine Tenant kavramının girmesiyle değişiyor. Tanımladığımız OrderFormTenantStatus sınıfı, &amp;ccedil;ekirdek stat&amp;uuml;leri temsil eden OrderFormStatus enum'ına bağlanarak m&amp;uuml;şterinin istediği kadar yeni stat&amp;uuml; ekleyebilmesine olanak tanır.&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;div&gt;Aynı zamanda domain kurallarını da korumaya devam eder. M&amp;uuml;şteri yeni bir stat&amp;uuml; eklediğinde, bu stat&amp;uuml;n&amp;uuml;n hangi &amp;ccedil;ekirdek stat&amp;uuml;ye karşılık geldiğini belirtmesi gerekir ve b&amp;ouml;ylece domain'in temel iş kurallarının ihlal edilmesini engelleriz. Varsayılan stat&amp;uuml;lerde tenant id değerleri bilerek boş bırakılır ki bunların en &amp;uuml;st noktada bağımsız stat&amp;uuml;ler olduğu anlaşılabilsin. Senaryoyu basit tutabilmek adına buradaki &amp;uuml;st t&amp;uuml;rev sınıf veya bazı domain kurallarını g&amp;ouml;z ardı ettik. Şimdi de OrderForm isimli aggregate root modelinde bu enstr&amp;uuml;manları nasıl kullanacağımıza bir bakalım.&lt;/div&gt;
&lt;div&gt;
&lt;pre class="brush:csharp;auto-links:false;toolbar:false" contenteditable="false"&gt;public class OrderForm
{
    public Guid Id { get; private set; }
    public Guid TenantId { get; private set; }
    public OrderFormTenantStatus Status { get; private set; }

    public OrderForm(Guid id, Guid tenantId, OrderFormTenantStatus initialStatus)
    {
        Id = id;
        TenantId = tenantId;
        
        if (initialStatus.CoreStatus != OrderFormStatus.Draft)
            throw new ArgumentException("Initial status must be Draft.", nameof(initialStatus));

        Status = initialStatus;
    }

    public void UpdateStatus(OrderFormTenantStatus newStatus)
    {
        if (newStatus.TenantId != Guid.Empty &amp;amp;&amp;amp; newStatus.TenantId != TenantId)
            throw new InvalidOperationException("Cannot change status to a status from a different tenant.");

        OrderFormStatus currentCoreStatus = Status.CoreStatus;
        OrderFormStatus newCoreStatus = newStatus.CoreStatus;

        if (currentCoreStatus == OrderFormStatus.Draft &amp;amp;&amp;amp; newCoreStatus != OrderFormStatus.Processing)
            throw new InvalidOperationException("Draft status can only transition to Processing.");

        if (currentCoreStatus == OrderFormStatus.Processing &amp;amp;&amp;amp; newCoreStatus == OrderFormStatus.Draft)
            throw new InvalidOperationException("Processing status cannot transition back to Draft.");

        Status = newStatus;
    }
}&lt;/pre&gt;
Evet zihinler karışmış kafamızın &amp;uuml;st kısmında dumanlar y&amp;uuml;kseliyor olabilir. OrderForm kendi i&amp;ccedil;inde stat&amp;uuml;leri OrderFormTenantStatus t&amp;uuml;r&amp;uuml;nden tutuyor. Bu t&amp;uuml;r varsayılan &amp;ccedil;ekirdek değerlerin oluşturulmasına static readonly alanlar &amp;uuml;zerinden izin verirken aynı zamanda m&amp;uuml;şterinin istediği kadar yeni stat&amp;uuml; ekleyebilmesine de olanak tanıyor. Ayrıca stat&amp;uuml; değişikliği yapmak istediğimizde &amp;ouml;zel stat&amp;uuml;ler de bağlandıkları stat&amp;uuml;ler gereğince &amp;ccedil;ekirdek iş kurallarına tabii oluyor. Yalnız burada dikkat edilmesi gereken bir şey daha var; yeni bir stat&amp;uuml; oluştururken id,name, tenantId değerleri ile m&amp;uuml;şteri nezdinde &amp;ouml;zelleşen stat&amp;uuml;y&amp;uuml; bir OrderFormStatus değeri ile i&amp;ccedil;eriye almak zorunda oluşumuz. Yani tenant'lar bir sipariş formunda kendi stat&amp;uuml;lerini kullanmak isterse mutlaka baz stat&amp;uuml;de karşılık bulmuş bir stat&amp;uuml; &amp;ouml;rneği ile eklemeliler. Dilerseniz durumu &amp;ouml;rnek bir senaryo ile pekiştirelim zira ben de ortalığı karıştırmış olabilirim. En g&amp;uuml;zel s&amp;ouml;z&amp;uuml; kod s&amp;ouml;yleyecek.&lt;br /&gt;&lt;br /&gt;M&amp;uuml;şterimizin kullanmak istediği &amp;uuml;&amp;ccedil; farklı stat&amp;uuml; olsun; Par&amp;ccedil;a bekleniyor&lt;em&gt;(WaitingForParts)&lt;/em&gt;, Montaj Hattında&lt;em&gt;(AssemblyLine)&lt;/em&gt; ve Terzide&lt;em&gt;(InTailor)&lt;/em&gt;. Yine m&amp;uuml;şteri a&amp;ccedil;ısından değerlendirdiğimiz bu stat&amp;uuml;lerin hepsi domain a&amp;ccedil;ısından Processing stat&amp;uuml;s&amp;uuml;ne karşılık geliyor olsun. Buna g&amp;ouml;re OrderForm nesnemizi &amp;ouml;rneklemeye ve stat&amp;uuml; g&amp;uuml;ncellemeleri yapmaya &amp;ccedil;alışalım.&lt;/div&gt;
&lt;div&gt;
&lt;pre class="brush:csharp;auto-links:false;toolbar:false" contenteditable="false"&gt;public class Program
{
    public static void Main()
    {
        Guid myTenantId = Guid.NewGuid();

        var waitingForPartsStatus = OrderFormTenantStatus.Create(Guid.NewGuid(), myTenantId, "Waiting for Parts", OrderFormStatus.Processing);
        var assemblyLineStatus = OrderFormTenantStatus.Create(Guid.NewGuid(), myTenantId, "Assembly Line", OrderFormStatus.Processing);
        var inTailorStatus = OrderFormTenantStatus.Create(Guid.NewGuid(), myTenantId, "In Tailor", OrderFormStatus.Processing);

        var order = new OrderForm(Guid.NewGuid(), myTenantId, OrderFormTenantStatus.Draft);
        Console.WriteLine($"Initial Order Status: {order.Status.Name}");
        
        order.UpdateStatus(waitingForPartsStatus);
        Console.WriteLine($"Updated Order Status: {order.Status.Name}");
        
        order.UpdateStatus(assemblyLineStatus);
        Console.WriteLine($"Updated Order Status: {order.Status.Name}");
        
        order.UpdateStatus(inTailorStatus);
        Console.WriteLine($"Updated Order Status: {order.Status.Name}");
        
        order.UpdateStatus(OrderFormTenantStatus.Canceled);
        Console.WriteLine($"Updated Order Status: {order.Status.Name}");
    }
}&lt;/pre&gt;
&amp;Ccedil;alışma zamanı &amp;ccedil;ıktısına bir bakalım mı?&lt;br /&gt;&lt;img src="https://www.buraksenyurt.com/image.axd?picture=/2026/Nisan/SmartEnums_00.png" alt="" /&gt;&lt;/div&gt;
&lt;div&gt;O sırada dinlediğim şarkı bir yana stat&amp;uuml;ler arasında sorunsuzca ge&amp;ccedil;iş yapabildiğimizi g&amp;ouml;rebiliriz. Yani Draft stat&amp;uuml;s&amp;uuml;nden itibaren olması gerektiği gibi sırasıyla Processing ve Canceled stat&amp;uuml;lerine ge&amp;ccedil;iş yapabildik. M&amp;uuml;şterinin eklediği stat&amp;uuml;ler de domain kurallarına uygun şekilde hareket ettik. Tabii burada iş kurallarını ihlal eden vakaları da denememiz lazım. Bu g&amp;uuml;zide g&amp;ouml;revleri de sizlere bırakıyorum :D&lt;/div&gt;
&lt;h2&gt;Rust'ın Şık Yaklaşımı&lt;/h2&gt;
&lt;div&gt;Tabii t&amp;uuml;m bu sorular &amp;uuml;zerinde ilerlerken insan ister istemez Rust dilinin zengin enum veri yapılarını d&amp;uuml;ş&amp;uuml;n&amp;uuml;yor. Evet tam anlamıyla nesne y&amp;ouml;nelimli bir dil değil ama ortak paydada soyutlamaları karşılama şekli değişse de aynı pratiği ele alabiliriz diye d&amp;uuml;ş&amp;uuml;n&amp;uuml;yorum. Rust'ın enum veri yapısı Algebraic Data Types olarak bilinir ve her bir enum varyantı kendi i&amp;ccedil;inde farklı veri taşıyabilir. Bu da bize &amp;ccedil;ok daha esnek ve g&amp;uuml;&amp;ccedil;l&amp;uuml; bir şekilde stat&amp;uuml;leri tanımlama imkanı verir. Ayrıca null diye bir kavram olmaması bu tip kontrolleri yapacağımız kısımlarda Option gibi &amp;ccedil;ok daha g&amp;uuml;venli bir t&amp;uuml;rden yararlanmamıza vesile olur. Şimdi de aynı senaryoyu Rust tarafında ele alalım. Sadece guid kullanımı i&amp;ccedil;in uuid crate'ini eklememiz gerekecek.&lt;/div&gt;
&lt;div&gt;
&lt;pre class="brush:bash;auto-links:false;toolbar:false" contenteditable="false"&gt;cargo add uuid -F v4&lt;/pre&gt;
İşe OrderFormStatus enum veri yapısını tanımlayarak başlayalım. Bu en &amp;ccedil;ekirdek stat&amp;uuml;leri tutacağı i&amp;ccedil;in d&amp;uuml;md&amp;uuml;z bir enum.&lt;br /&gt;&lt;br /&gt;&lt;/div&gt;
&lt;div&gt;
&lt;pre class="brush:cpp;auto-links:false;toolbar:false" contenteditable="false"&gt;use uuid::Uuid;

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum OrderFormStatus {
    Draft,
    Cancelled,
    Completed,
    Processing,
}&lt;/pre&gt;
Şimdi işin en azından bana g&amp;ouml;re sanata d&amp;ouml;n&amp;uuml;şt&amp;uuml;ğ&amp;uuml; bir kısım geliyor. Bir stat&amp;uuml; ya sistemin kendi stat&amp;uuml;s&amp;uuml;d&amp;uuml;r ya da i&amp;ccedil;ine veri g&amp;ouml;m&amp;uuml;lm&amp;uuml;ş bir &amp;ouml;zel stat&amp;uuml;d&amp;uuml;r. Bunu yaparken sınıf hiyerarşisine bağlı kalmadan hareket edebiliriz. Nasıl mı? İşte b&amp;ouml;yle;&lt;br /&gt;
&lt;pre class="brush:cpp;auto-links:false;toolbar:false" contenteditable="false"&gt;#[derive(Debug, Clone, PartialEq, Eq)]
pub enum OrderFormTenantStatus {
    System(OrderFormStatus),
    Custom {
        id: Uuid,
        tenant_id: Uuid,
        name: String,
        core_status: OrderFormStatus,
    },
}

impl OrderFormTenantStatus {
    pub fn core_status(&amp;amp;self) -&amp;gt; OrderFormStatus {
        match self {
            OrderFormTenantStatus::System(core) =&amp;gt; *core,
            OrderFormTenantStatus::Custom { core_status, .. } =&amp;gt; *core_status,
        }
    }

    pub fn tenant_id(&amp;amp;self) -&amp;gt; Option&amp;lt;Uuid&amp;gt; {
        match self {
            OrderFormTenantStatus::System(_) =&amp;gt; None,
            OrderFormTenantStatus::Custom { tenant_id, .. } =&amp;gt; Some(*tenant_id),
        }
    }

    pub fn name(&amp;amp;self) -&amp;gt; String {
        match self {
            OrderFormTenantStatus::System(core) =&amp;gt; format!("{:?}", core),
            OrderFormTenantStatus::Custom { name, .. } =&amp;gt; name.clone(),
        }
    }

    pub fn new(
        id: Uuid,
        tenant_id: Uuid,
        name: &amp;amp;str,
        core_status: OrderFormStatus,
    ) -&amp;gt; Result&amp;lt;Self, &amp;amp;'static str&amp;gt; {
        if name.trim().is_empty() {
            return Err("Stat&amp;uuml; adı boş olamaz.");
        }
        Ok(OrderFormTenantStatus::Custom {
            id,
            tenant_id,
            name: name.to_string(),
            core_status,
        })
    }
}&lt;/pre&gt;
G&amp;ouml;rd&amp;uuml;ğ&amp;uuml;n&amp;uuml;z gibi OrderFormTenantStatus enum'ı iki varyant i&amp;ccedil;erir. İlki System varyantıdır ki &amp;ccedil;ekirdek stat&amp;uuml;leri taşıyabilir, ikincisi ise Custom varyantıdır ve m&amp;uuml;şteriye &amp;ouml;zel stat&amp;uuml;leri temsil eder. impl bloğunda bir&amp;ccedil;ok fonksiyon bulunmakta. new fonksiyonu yeni bir stat&amp;uuml; oluşturmak i&amp;ccedil;in kullanılır ve ge&amp;ccedil;ersiz bir isimle karşılaşırsa hata d&amp;ouml;ner. Diğer fonksiyonlar ise stat&amp;uuml;n&amp;uuml;n &amp;ccedil;ekirdek stat&amp;uuml;s&amp;uuml;n&amp;uuml;, tenant id'sini ve adını almak i&amp;ccedil;in kullanılır. Bu fonksiyonlarda pattern matching tekniğini kullandığımız i&amp;ccedil;in kod olduk&amp;ccedil;a temiz ve anlaşılır kalır diyebilirim. Evet yer yer bizi d&amp;uuml;ş&amp;uuml;nd&amp;uuml;ren kısımlar da yok değil. &amp;Ouml;rneğin new fonksiyonuna eklediğimiz deneysel iş kuralı ihlal edilirse Result t&amp;uuml;r&amp;uuml; statik yaşam &amp;ouml;mr&amp;uuml;nde bir literal d&amp;ouml;ner. Burada kolay ka&amp;ccedil;tığımı itiraf edebilirim. Belki de stat&amp;uuml; oluşturulurken iş kurallarını ihlal eden bir durum varsa bunu compile time'da yakalayabileceğimiz bir yapıya gitmek daha doğru olurdu. Ancak bu &amp;ouml;rnekteki gibi runtime'da kontrol etmek de m&amp;uuml;mk&amp;uuml;n olabilir. Neyse neyse... Dağılmayalım ve OrderForm struct'ını yazarak devam edelim.&lt;br /&gt;
&lt;pre class="brush:cpp;auto-links:false;toolbar:false" contenteditable="false"&gt;pub struct OrderForm {
    pub id: Uuid,
    pub tenant_id: Uuid,
    status: OrderFormTenantStatus,
}

impl OrderForm {
    pub fn new(
        id: Uuid,
        tenant_id: Uuid,
        initial_status: OrderFormTenantStatus,
    ) -&amp;gt; Result&amp;lt;Self, &amp;amp;'static str&amp;gt; {
        if initial_status.core_status() != OrderFormStatus::Draft {
            return Err("Sipariş sadece Draft stat&amp;uuml;s&amp;uuml; ile başlayabilir.");
        }

        Ok(Self {
            id,
            tenant_id,
            status: initial_status,
        })
    }

    pub fn status(&amp;amp;self) -&amp;gt; &amp;amp;OrderFormTenantStatus {
        &amp;amp;self.status
    }

    pub fn update_status(&amp;amp;mut self, new_status: OrderFormTenantStatus) -&amp;gt; Result&amp;lt;(), &amp;amp;'static str&amp;gt; {
        if let Some(status_tenant_id) = new_status.tenant_id() {
            if status_tenant_id != self.tenant_id {
                return Err("Farklı bir firmaya ait stat&amp;uuml; bu siparişe atanamaz.");
            }
        }

        let current_core = self.status.core_status();
        let new_core = new_status.core_status();

        if current_core == OrderFormStatus::Draft &amp;amp;&amp;amp; new_core != OrderFormStatus::Processing {
            return Err("Draft stat&amp;uuml;s&amp;uuml; sadece Processing'e ge&amp;ccedil;ebilir.");
        }

        if current_core == OrderFormStatus::Processing &amp;amp;&amp;amp; new_core == OrderFormStatus::Draft {
            return Err("Processing stat&amp;uuml;s&amp;uuml; tekrar Draft'a d&amp;ouml;nemez.");
        }

        self.status = new_status;
        Ok(())
    }
}&lt;/pre&gt;
Rust tarafındaki OrderForm veri yapımızda C#'takine benzer şekilde &amp;ccedil;eşitli kuralları işletebilir ve stat&amp;uuml; g&amp;uuml;ncellemelerini y&amp;ouml;netir. new fonksiyonu sipariş formu oluştururken başlangı&amp;ccedil; stat&amp;uuml;s&amp;uuml;n&amp;uuml;n Draft olması gerektiğini kontrol eder. update_status fonksiyonu ise yeni stat&amp;uuml;n&amp;uuml;n aynı tenant'a ait olup olmadığını ve ge&amp;ccedil;iş kurallarını kontrol eder. Eğer herhangi bir kural ihlal edilirse hata d&amp;ouml;n&amp;uuml;l&amp;uuml;r. Aksi takdirde stat&amp;uuml; başarılı bir şekilde g&amp;uuml;ncellenir. Şimdi bu yapıyı nasıl kullanacağımıza bakalım.&lt;br /&gt;
&lt;pre class="brush:cpp;auto-links:false;toolbar:false" contenteditable="false"&gt;fn main() -&amp;gt; Result&amp;lt;(), &amp;amp;'static str&amp;gt; {
    let firm_tenant_id = Uuid::new_v4();

    let waiting_for_parts = OrderFormTenantStatus::new(
        Uuid::new_v4(),
        firm_tenant_id,
        "Waiting for Parts",
        OrderFormStatus::Processing,
    )?;
    let assembly_line = OrderFormTenantStatus::new(
        Uuid::new_v4(),
        firm_tenant_id,
        "Assembly Line",
        OrderFormStatus::Processing,
    )?;
    let in_tailor = OrderFormTenantStatus::new(
        Uuid::new_v4(),
        firm_tenant_id,
        "In Tailor",
        OrderFormStatus::Processing,
    )?;

    let mut order = OrderForm::new(
        Uuid::new_v4(),
        firm_tenant_id,
        OrderFormTenantStatus::System(OrderFormStatus::Draft),
    )?;

    println!("Initial Order Status: {}", order.status().name());

    order.update_status(waiting_for_parts)?;
    println!("Updated Order Status: {}", order.status().name());

    order.update_status(assembly_line)?;
    println!("Updated Order Status: {}", order.status().name());

    order.update_status(in_tailor)?;
    println!("Updated Order Status: {}", order.status().name());

    order.update_status(OrderFormTenantStatus::System(OrderFormStatus::Cancelled))?;
    println!("Updated Order Status: {}", order.status().name());

    Ok(())
}&lt;/pre&gt;
Ve &amp;ccedil;alışma zamanı &amp;ccedil;ıktısı;&lt;br /&gt;&lt;img src="https://www.buraksenyurt.com/image.axd?picture=/2026/Nisan/SmartEnums_01.png" alt="" /&gt;&lt;/div&gt;
&lt;h2&gt;Sonu&amp;ccedil;&lt;/h2&gt;
&lt;div&gt;Domain Driven Design ilkeleri ile zorlayıcı ancak bir programlama dilinin bazı yeteneklerini daha iyi benimsemek adına harika pratikler vaat ediyor. Rust bir kenara tam anlamıyla nesne y&amp;ouml;nelimli dil paradigmalarını d&amp;uuml;ş&amp;uuml;nd&amp;uuml;ğ&amp;uuml;m&amp;uuml;zde d&amp;uuml;md&amp;uuml;z veri yapıları tasarlamanın &amp;ouml;tesine ge&amp;ccedil;tiğimiz bir hatta ilerlemeye zorluyor. Bu &amp;ccedil;alışmada m&amp;uuml;şteri a&amp;ccedil;ısından kıymetli olan bir gereksinimin &amp;ccedil;ekirdek kurguyu bozmadan uyarlanabilmesi adına bazı hamleler yapmaya &amp;ccedil;alıştık. En ideal yol mudur tartışılır ama ziyadesiyle &amp;ouml;nemli dil kabiliyetlerini kullandık diyebiliriz. Adettendir bitirirken C# ve Rust a&amp;ccedil;ısından da bir kıyas yapalım.&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;Bir stat&amp;uuml;y&amp;uuml; nesne olarak oluştururken OrderFormStatus.Draft gibi kullanımlar s&amp;ouml;z konusu. Bu tahminimce bellekte bir yer tahsisine&lt;em&gt;(allocation)&lt;/em&gt; neden olabilir. Rust tarafında OrderFormTenantStatus::System(OrderFormStatus::Draft) şeklinde bir kullanım var. Bu da enum'un kendi i&amp;ccedil;inde veri taşıyabilmesi sayesinde m&amp;uuml;mk&amp;uuml;n. Buna g&amp;ouml;re gereksiz yer tahsisi yok desek doğru olur mu emin değilim :D Bunu ispatlamam en azından şimdilik zor.&lt;/li&gt;
&lt;li&gt;Boş guid kullanımında C# tarafında Guid.Empty şeklinde bir yaklaşımımız oldu. Rust tarafında Option t&amp;uuml;r&amp;uuml;ne sahip olduğumuz i&amp;ccedil;in None ile tenant bağımsız stat&amp;uuml;leri temsil edebilir durumdayız. Daha tip g&amp;uuml;venli&lt;em&gt;(type safe)&lt;/em&gt; bir yaklaşıma sahip olduğumuzu ifade edebiliriz. C# tarafında bunun i&amp;ccedil;in &amp;ccedil;aresiz miyiz, asla. Pekala kendi generic Option t&amp;uuml;r&amp;uuml;m&amp;uuml;z&amp;uuml; de yazabiliriz ama dilin doğasında bunun olması farklı bir şey.&lt;/li&gt;
&lt;li&gt;Option gibi yine Rust a&amp;ccedil;ısından g&amp;uuml;&amp;ccedil;l&amp;uuml; olan bir başka enstr&amp;uuml;man da generic Result t&amp;uuml;r&amp;uuml;d&amp;uuml;r. C# tarafında domain kural ihlallerini genellikle Exception fırlatarak cezalandırdık ama Rust tarafında Result t&amp;uuml;r&amp;uuml; kullandığımız i&amp;ccedil;in try/catch bloklarına ihtiya&amp;ccedil; duymadan hataları y&amp;ouml;netebiliriz. Yine pattern matching ile Result i&amp;ccedil;eriğini yakalayarak hataları daha temiz bir şekilde ele alabiliriz. Pek tabii C# tarafında da belki result pattern ile aynı şeyi karşılamak m&amp;uuml;mk&amp;uuml;n olabilir.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Bu anlamsız karşılaştırma bir yana dursun asıl vurgulamam gereken şey şu; Kurumsal &amp;ouml;l&amp;ccedil;ekte bir yazılımda domain driven design pratiklerini ele alacaksak &amp;uuml;r&amp;uuml;n&amp;uuml; Rust ile yazmayız. Zira, bağımlılık y&amp;ouml;netimi&lt;em&gt;(dependency injection&lt;/em&gt;) gibi kritik konularda nesne y&amp;ouml;nelimli bir dilde kalmak kodu yazmak ve bakımı a&amp;ccedil;ısından daha kolay olabilir. Lakin bu sistemin ihtiya&amp;ccedil; duyduğu y&amp;uuml;ksek performans isteyen ve g&amp;ouml;rev kritik olan bir&amp;ccedil;ok senaryoda Rust ile ilerleyebiliriz. Bu tamamen kendimce yapmış olduğum bir yorum ;)&lt;br /&gt;&lt;br /&gt;Bu &amp;ccedil;alışmada ele aldığımız &amp;ouml;rneklere &lt;a href="https://github.com/buraksenyurt/friday-night-programmer/tree/main/src/SmartEnums" target="_blank"&gt;github reposundan&lt;/a&gt; erişebilirsiniz. B&amp;ouml;ylece geldik bir &amp;ccedil;alışmamızın daha sonuna. Tekrardan g&amp;ouml;r&amp;uuml;ş&amp;uuml;nceye dek hepinize mutlu g&amp;uuml;nler dilerim.&lt;/p&gt;</summary>
    <published>2026-04-01T16:53:00+00:00</published>
    <link rel="related" href="https://www.buraksenyurt.com/post/smart-enums#comment" />
    <category term="C#" />
    <category term="Rust" />
    <betag:tag>domain driven design</betag:tag>
    <betag:tag>cSharp</betag:tag>
    <betag:tag>rust</betag:tag>
    <betag:tag>enum</betag:tag>
    <betag:tag>option</betag:tag>
    <betag:tag>result</betag:tag>
    <betag:tag>yazılım mimarileri</betag:tag>
    <betag:tag>rich entity</betag:tag>
    <dc:publisher>bsenyurt</dc:publisher>
    <dc:description>Yazılım geliştirme galaksisinin en zorlu yolculuklarından birisi sanıyorum ki Domain Driven Design(DDD) rotasında ilerlemek. Büyük çaplı kurumsal projelerde hangi mimari ile çalışacağımıza karar vermek bir yana dursun domain sınırlarını belirlemek, model nesneleri kurgulamak, ortak jargonu çıkarmak ve bu jargonu kod içerisinde nasıl temsil edeceğimize karar vermek gibi pek çok zorluğu beraberinde getiren bir yolculuk. Gerçekten farklı yetkinlikler gerektiğine inandığım bu yaklaşımda gün geçmiyor ki yeni bir konuyu tartışalım. İşte henüz gerçekleştirdiğimiz bir tartışma:</dc:description>
    <pingback:server>https://www.buraksenyurt.com/pingback.axd</pingback:server>
    <pingback:target>https://www.buraksenyurt.com/post.aspx?id=4250f42a-8dfb-4888-92b5-f4d13e183f0a</pingback:target>
    <slash:comments>0</slash:comments>
    <trackback:ping>https://www.buraksenyurt.com/trackback.axd?id=4250f42a-8dfb-4888-92b5-f4d13e183f0a</trackback:ping>
    <wfw:comment>https://www.buraksenyurt.com/post/smart-enums#comment</wfw:comment>
    <wfw:commentRss>https://www.buraksenyurt.com/syndication.axd?post=4250f42a-8dfb-4888-92b5-f4d13e183f0a</wfw:commentRss>
  </entry>
  <entry>
    <id>https://www.buraksenyurt.com/post/pi-sayisini-hesaplama-yolunda</id>
    <title>Pi(π) Sayısını Hesaplama Yolunda</title>
    <updated>2026-03-26T19:21:00+00:00</updated>
    <link rel="self" href="https://www.buraksenyurt.com/post.aspx?id=27be173a-1af1-4128-a452-21edec7506db" />
    <link href="https://www.buraksenyurt.com/post/pi-sayisini-hesaplama-yolunda" />
    <author>
      <name>bsenyurt</name>
    </author>
    <summary type="html">&lt;p&gt;Matematiksel y&amp;ouml;ntemlerden bazılarını ele alarak belli bir basamağa kadar pi(&amp;pi;) sayısını hesaplamaya &amp;ccedil;alışacağım. Doğru bir basamak değerine ulaşmak ve burada y&amp;uuml;ksek s&amp;uuml;rate &amp;ccedil;ıkmak hedeflerim arasında. &amp;Ouml;nce en amele y&amp;ouml;ntemlerden başlayarak daha sonra daha karmaşık y&amp;ouml;ntemlere ge&amp;ccedil;mek niyetindeyim ama yol beni farklı bir rotaya s&amp;uuml;r&amp;uuml;kledi diye de &amp;ouml;zet ge&amp;ccedil;eyim :D Diğer modellere ge&amp;ccedil;emeden kendimi diller arasında performans hesaplamaları, optimizasyonlar ve senkronizasyon sorunlarıyla uğraşırken buldum. O y&amp;uuml;zden bu yazıda sadece Monte Carlo y&amp;ouml;ntemini ele alacağım.&lt;/p&gt;
&lt;p&gt;Kapsam&lt;/p&gt;
&lt;p&gt;- [x] Dart oynamayı severiz. Monte Carlo ile başlayalım.&lt;br /&gt;- [x] Paralel hesaplama y&amp;ouml;ntemlerini kullanarak performansı artırmaya &amp;ccedil;alışalım.&lt;br /&gt;- [x] Race Condition'ları ve diğer senkronizasyon sorunlarını ele alarak kodumuzu optimize edelim.&lt;br /&gt;- [ ] Chudnovsky algoritması ve ardından Gauss-Legendre algoritmasını deneyelim.&lt;/p&gt;
&lt;h2&gt;Sistem&lt;/h2&gt;
&lt;p&gt;&amp;Ccedil;alışmadaki deneyleri aşağıdaki &amp;ouml;zelliklere sahip bir sistemde ger&amp;ccedil;ekleştirdim.&lt;/p&gt;
&lt;table&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;th&gt;Donanım&amp;nbsp; &amp;nbsp; &amp;nbsp;&lt;/th&gt;
&lt;th&gt;Detaylar&lt;/th&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;CPU&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;12th Gen Intel(R) Core(TM) i7-1255U, 1700 Mhz, 10 Core(s), 12 Logical Processor(s)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Bellek&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;32 Gb&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;OS&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&amp;nbsp;Windows 11 Pro&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2&gt;&amp;Ouml;nce Kısa bir Matematika ve Monte Carlo Seyahati&lt;/h2&gt;
&lt;p&gt;Pi(&amp;pi;), bir dairenin &amp;ccedil;evresinin &amp;ccedil;apına oranı olarak ifade edilebilir. Yaklaşık olarak ve genelde ezberlediğim değeri 3.14159 olarak bilinir ancak aslında sonsuz bir ondalık dizisine sahiptir ve tam değeri bilinmemektedir. Pi(&amp;pi;) sayısını kutlamak i&amp;ccedil;in &amp;ouml;zel bir g&amp;uuml;n bile vardır: 14 Mart, yani 3/14...&lt;/p&gt;
&lt;p&gt;Bir &amp;ccedil;ılgınlık yaparak kodlarımızı deterministik olmayan bir metodoloji ile yazabiliriz. Monte Carlo y&amp;ouml;ntemine g&amp;ouml;re pi sayısının hesaplanması i&amp;ccedil;in bir &amp;ccedil;embere ve rastgele iki double değere ihtiyacımız var. Rastgele değerlerin bir bileşimi &amp;ccedil;emberin i&amp;ccedil;ine d&amp;uuml;şerse, pi(&amp;pi;) sayısının yaklaşık değeri i&amp;ccedil;in bir tahmin yapılabilir. Tabii burada kullanmamız gereken bir form&amp;uuml;l ve iterasyon sayısı var. &amp;Ouml;yleyse vakit kaybeden kodlamaya başlayalım.&lt;/p&gt;
&lt;h2&gt;Temiz, Sequential Versiyon&lt;/h2&gt;
&lt;p&gt;İşte en amat&amp;ouml;r&amp;uuml;nden bir C#** kod &amp;ouml;rneği.&lt;/p&gt;
&lt;pre class="brush:csharp;auto-links:false;toolbar:false" contenteditable="false"&gt;using System.Diagnostics;

public class Program
{
    public static void Main()
    {
        long totalIterations = 100_000_000;
        Stopwatch stopwatch = Stopwatch.StartNew();
        for (int i = 0; i &amp;lt; 10; i++)
        {
            stopwatch.Restart();
            int inCircle = (int)PiEstimatorV1(totalIterations);
            Console.WriteLine($"Estimated value of &amp;pi;: {4.0 * inCircle / totalIterations} in {stopwatch.ElapsedMilliseconds} ms");
        }
    }

    public static long PiEstimatorV1(long iterations)
    {
        long inCircle = 0;
        var random = new Random();

        for (int i = 0; i &amp;lt; iterations; i++)
        {
            double x = random.NextDouble();
            double y = random.NextDouble();
            if (x * x + y * y &amp;lt;= 1.0)
            {
                inCircle++;
            }
        }
        return inCircle;
    }
}&lt;/pre&gt;
&lt;p&gt;Kendi sistemimde normal dotnet run koşusuyla elde ettiğim sonu&amp;ccedil;lar ş&amp;ouml;yle&lt;em&gt;(&amp;Ouml;zellikle dotnet run komutuna dikkat &amp;ccedil;ekmek isterim. İlerleyen b&amp;ouml;l&amp;uuml;mlerde release modun nasıl farklar yarattığını g&amp;ouml;receğiz)&lt;/em&gt;:&lt;/p&gt;
&lt;p&gt;&lt;img src="https://www.buraksenyurt.com/image.axd?picture=/2026/Mart/CalculatePi_00.png" alt="" /&gt;&lt;/p&gt;
&lt;p&gt;Yaklaşık 1000 milisaniye civarında bir s&amp;uuml;rede 3.14 etrafında dolandığımızı s&amp;ouml;yleyebilirim. Burada gayet senkron bir şekilde tek thread kullanarak hesaplama yapıyoruz. Devam etmeden &amp;ouml;nce rastgele değerlerin &amp;ccedil;ember i&amp;ccedil;erisinde kalıp kalmama form&amp;uuml;llerini daha şık yazabilirmiyim diye d&amp;uuml;ş&amp;uuml;nd&amp;uuml;m ve Math sınıfının Pow metodunu kullanarak kodu biraz daha okunabilir hale getirdim.&lt;/p&gt;
&lt;pre class="brush:csharp;auto-links:false;toolbar:false" contenteditable="false"&gt;if (Math.Pow(x, 2) + Math.Pow(y, 2) &amp;lt;= 1.0)
{
    inCircle++;
}&lt;/pre&gt;
&lt;p&gt;&lt;img src="https://www.buraksenyurt.com/image.axd?picture=/2026/Mart/CalculatePi_01.png" alt="" /&gt;&lt;/p&gt;
&lt;p&gt;Biraz g&amp;ouml;receli de olsa Math sınıfının statik Pow metodunu kullanmak kodun okunabilirliğini artırıp amacını daha şık ifade etse de s&amp;uuml;reler neredeyse &amp;uuml;&amp;ccedil; kata kadar arttı. Yol yakınken geri d&amp;ouml;nme vakti.&lt;/p&gt;
&lt;h2&gt;Iterasyon Sayısı Artıyor, Paralel &amp;Ccedil;alışma Geliyor&lt;/h2&gt;
&lt;p&gt;İlk metodolojimize g&amp;ouml;re 100 milyon iterasyon bu sistemde kabul edilebilir bir ortalama &amp;ccedil;alışma s&amp;uuml;resi yakaladı ancak daha y&amp;uuml;ksek ve tutarlı bir hesaplama i&amp;ccedil;in iterasyon sayısını artırmak yerinde olur. Bu y&amp;uuml;zden 1 milyar iterasyonu deneyebiliriz. İşte sonu&amp;ccedil;lar.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://www.buraksenyurt.com/image.axd?picture=/2026/Mart/CalculatePi_02.png" alt="" /&gt;&lt;/p&gt;
&lt;p&gt;"Ger&amp;ccedil;ekten bu kadar s&amp;uuml;re bekledin mi?" diye sorabilirsiniz. Evet, bekledim :D İterasyon sayısının b&amp;uuml;y&amp;uuml;mesi hesaplama s&amp;uuml;resini &amp;ccedil;ok daha dramatik olarak artırdı&lt;em&gt;(ama hala dotnet'i debug modda işletiyoruz onu da belirtelim&lt;/em&gt;) Dolayısıyla bir şeyleri paralel hale getirmenin ve pek tabii bunu da g&amp;uuml;venli&lt;em&gt;(thread-safe)&lt;/em&gt; ve race condition sorunlarından ka&amp;ccedil;ınarak yapmanın zamanı geldi. Aslında for d&amp;ouml;ng&amp;uuml;s&amp;uuml;n&amp;uuml; paralel hale getirebiliriz ve tahminen oluşabilecek race condition sorununu da Interlocked sınıfını kullanarak &amp;ccedil;&amp;ouml;zebiliriz. İşte paralel hale getirilmiş ve g&amp;uuml;venli bir şekilde saya&amp;ccedil; artırdığını d&amp;uuml;ş&amp;uuml;nd&amp;uuml;ğ&amp;uuml;m kod par&amp;ccedil;ası.&lt;/p&gt;
&lt;pre class="brush:csharp;auto-links:false;toolbar:false" contenteditable="false"&gt;public static long PiEstimatorV2(long iterations)
{
    long inCircle = 0;
    var random = new Random();

    Parallel.For(0, iterations, i =&amp;gt;
    {
        double x = random.NextDouble();
        double y = random.NextDouble();

        if (x * x + y * y &amp;lt;= 1.0)
        {
            Interlocked.Increment(ref inCircle);
        }
    }
    );
    return inCircle;
}&lt;/pre&gt;
&lt;p&gt;Japon bir kılı&amp;ccedil; ustasının sakince kata yaparken ki ruh haline b&amp;uuml;r&amp;uuml;n&amp;uuml;p sabırla beklesem de ilk iki &amp;ccedil;alışma s&amp;uuml;resini g&amp;ouml;r&amp;uuml;nce programın &amp;ccedil;alışmasını sonlandırdım.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://www.buraksenyurt.com/image.axd?picture=/2026/Mart/CalculatePi_03.png" alt="" /&gt;&lt;/p&gt;
&lt;p&gt;Bir şeylerin ters gittiği hatalı kodlama yaptığım g&amp;uuml;n gibi ortada. Hatta işin &amp;ccedil;ok daha enterasan yani paralel for d&amp;ouml;ng&amp;uuml;s&amp;uuml;n&amp;uuml; bir kenara bırakıp sadece Interlocked nesnesini kullanınca ortaya &amp;ccedil;ıktı.&lt;/p&gt;
&lt;pre class="brush:csharp;auto-links:false;toolbar:false" contenteditable="false"&gt;public static long PiEstimatorV3(long iterations)
{
    long inCircle = 0;
    var random = new Random();

    for (int i = 0; i &amp;lt; iterations; i++)
    {
        double x = random.NextDouble();
        double y = random.NextDouble();

        if (x * x + y * y &amp;lt;= 1.0)
        {
            Interlocked.Increment(ref inCircle);
        }
    }
    return inCircle;
}&lt;/pre&gt;
&lt;p&gt;Şaşılacak şey ama sadece Interlocked sınıfını kullanarak saya&amp;ccedil; artırmaya &amp;ccedil;alışmak daha iyi sonu&amp;ccedil;lar verdi. Fakat bu sefer de paralel &amp;ccedil;alışmanın avantajını tam olarak kullanmadık/kullanamdık.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://www.buraksenyurt.com/image.axd?picture=/2026/Mart/CalculatePi_04.png" alt="" /&gt;&lt;/p&gt;
&lt;h2&gt;Thread-Local Random ve Daha İyi Paralel &amp;Ccedil;alışma&lt;/h2&gt;
&lt;p&gt;Sanki doğru yolda ilerliyor gibiyim ama tam olarak değil. Interlocked sınıfı thread'ler arası g&amp;uuml;venli bir şekilde sayacı artırmamızı sağlasa da, paralel &amp;ccedil;alışacak her bir iterasyonda bu işlemi yapmak ciddi bir performans kaybına neden oluyor gibi. &amp;Ccedil;&amp;uuml;nk&amp;uuml; her bir thread'in saya&amp;ccedil; değerini g&amp;uuml;ncellemesi gerektiğinde diğer thread'lerin de bu değere erişmeye &amp;ccedil;alışması bir t&amp;uuml;r beklemeye sebep oluyor. Dolayısıyla her bir thread'in kendi saya&amp;ccedil; değerini tutması ve en sonunda bu değerleri birleştirmek daha doğru olacak. O zaman birde aşağıdaki kod par&amp;ccedil;asını deneyelim.&lt;/p&gt;
&lt;pre class="brush:csharp;auto-links:false;toolbar:false" contenteditable="false"&gt;public static long PiEstimatorV4(long iterations)
{
    long inCircle = 0;
    using var tlRandom = new ThreadLocal&amp;lt;Random&amp;gt;(() =&amp;gt; new Random());

    Parallel.For(
        0L,
        iterations,
        () =&amp;gt; 0L,
        (_, _, localCount) =&amp;gt;
        {
            var rng = tlRandom.Value!;
            double x = rng.NextDouble();
            double y = rng.NextDouble();
            return x * x + y * y &amp;lt;= 1.0 ? localCount + 1 : localCount;
        },
        localCount =&amp;gt; Interlocked.Add(ref inCircle, localCount)
    );

    return inCircle;
}&lt;/pre&gt;
&lt;p&gt;tlRandom isimli ThreadLocal sınıfını kullanarak her bir thread'in kendi rastgele sayı &amp;uuml;reteci &amp;ouml;rneğine sahip olmasını sağlıyoruz. Bu sayede her bir thread'in kendi saya&amp;ccedil; değerini tutması ve sonunda bu değerleri g&amp;uuml;venli bir şekilde birleştirmesi m&amp;uuml;mk&amp;uuml;n hale geliyor. Paralel for d&amp;ouml;ng&amp;uuml;s&amp;uuml; bu &amp;ouml;rnekte tam beş parametre almakta. Dile kolay tam beş, iki tane daha alsa Sonarqube'e takılır herhalde :D&lt;/p&gt;
&lt;p&gt;İlk iki parametre iterasyon aralığını tanımlarken&lt;em&gt;(0'dan maksimum iterasyon değerine kadar)&lt;/em&gt;, &amp;uuml;&amp;ccedil;&amp;uuml;nc&amp;uuml; parametre her bir thread i&amp;ccedil;in hesaplama değerinin başlangı&amp;ccedil; değerini belirliyor. D&amp;ouml;rd&amp;uuml;nc&amp;uuml; parametre her bir iterasyonda &amp;ccedil;alışacak olan metodu temsil ediyor ki burada Monte Carlo sim&amp;uuml;lasyonu yapılmakta ve bu metod her bir thread'in kendi saya&amp;ccedil; değerini g&amp;uuml;ncelliyor. Son parametre ise t&amp;uuml;m thread'lerin saya&amp;ccedil; değerlerini g&amp;uuml;venli&lt;em&gt;(thread-safe)&lt;/em&gt; bir şekilde birleştirmek i&amp;ccedil;in kullanılan metodu işaret ediyor.&lt;/p&gt;
&lt;p&gt;Peki ya &amp;ccedil;alışma zamanı &amp;ccedil;ıktısı...&lt;/p&gt;
&lt;p&gt;&lt;img src="https://www.buraksenyurt.com/image.axd?picture=/2026/Mart/CalculatePi_05.png" alt="" /&gt;&lt;/p&gt;
&lt;h2&gt;Ger&amp;ccedil;ek &amp;Ccedil;ekirdek Sayısına G&amp;ouml;re B&amp;ouml;lme&lt;/h2&gt;
&lt;p&gt;Not bad, not bad! Hesaplama s&amp;uuml;releri daha makul bir noktaya geldi. Ancak daha şık bir tasarıma veya modele gidebilir miyiz diye de d&amp;uuml;ş&amp;uuml;n&amp;uuml;yorum. Paralel for d&amp;ouml;ng&amp;uuml;s&amp;uuml;nde kalıp makinedeki &amp;ccedil;ekirdek sayısını da hesaba katarak ilerlemek mantıklı olabilir. Yani her &amp;ccedil;ekirdeğin kendi saya&amp;ccedil; değerini tutması ve sonunda bu değerlerin birleştirilmesi gibi bir yaklaşım daha verimli olabilir. Bu ama&amp;ccedil;la aşağıdaki kod par&amp;ccedil;asını ele alabiliriz.&lt;/p&gt;
&lt;pre class="brush:csharp;auto-links:false;toolbar:false" contenteditable="false"&gt;public static long PiEstimatorV5(long iterations)
{
    int coreCount = Environment.ProcessorCount;
    long chunkSize = iterations / coreCount;
    long inCircle = 0;

    var tasks = Enumerable.Range(0, coreCount).Select(id =&amp;gt; Task.Run(() =&amp;gt;
    {
        var rng = new Random();
        long localCount = 0;
        long start = id * chunkSize;
        long end = id == coreCount - 1 ? iterations : start + chunkSize;

        for (long i = start; i &amp;lt; end; i++)
        {
            double x = rng.NextDouble();
            double y = rng.NextDouble();
            if (x * x + y * y &amp;lt;= 1.0)
                localCount++;
        }

        Interlocked.Add(ref inCircle, localCount);
    })).ToArray();

    Task.WaitAll(tasks);
    return inCircle;
}&lt;/pre&gt;
&lt;p&gt;Bu sefer işlemcideki &amp;ccedil;ekirdek sayısına g&amp;ouml;re iterasyonları b&amp;ouml;l&amp;uuml;yor ve her &amp;ccedil;ekirdek i&amp;ccedil;in ayrı bir g&amp;ouml;rev nesnesi&lt;em&gt;(Task)&lt;/em&gt; oluşturuyoruz. Her g&amp;ouml;rev kendi rastgele sayı &amp;uuml;retecine sahip ve kendi saya&amp;ccedil; değerini tutuyor. G&amp;ouml;revler tamamlandığında saya&amp;ccedil; değerleri g&amp;uuml;venli bir şekilde toplanıyor. Bu, bir &amp;ouml;nceki versiyona g&amp;ouml;re performansı biraz daha artırdı diyebilirim. İşte sonu&amp;ccedil;lar,&lt;/p&gt;
&lt;p&gt;&lt;img src="https://www.buraksenyurt.com/image.axd?picture=/2026/Mart/CalculatePi_06.png" alt="" /&gt;&lt;/p&gt;
&lt;p&gt;Tabii burada "attığımız taş &amp;uuml;rk&amp;uuml;tt&amp;uuml;ğ&amp;uuml;m&amp;uuml;z kurbaya değdi mi?" &amp;ouml;zl&amp;uuml; s&amp;ouml;z&amp;uuml;n&amp;uuml; hatırlamakta fayda var. En başta senkron olarak &amp;ccedil;alışan versiyonu bu son iterasyon sayısı ile tekrar denediğimde aşağıdaki sonu&amp;ccedil;lara ulaştım. Evet paralel &amp;ccedil;alışmada sonu&amp;ccedil;lar daha iyi ama bu sanki y&amp;uuml;ksek iterasyon sayıları i&amp;ccedil;in ge&amp;ccedil;erli bir durum. D&amp;uuml;ş&amp;uuml;k aralıklarda bu maliyete girmeyebilirizde ve hatta daha kısa s&amp;uuml;rmesi gerekirken daha uzun &amp;ccedil;alışma s&amp;uuml;releride ortaya &amp;ccedil;ıkabilir.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://www.buraksenyurt.com/image.axd?picture=/2026/Mart/CalculatePi_07.png" alt="" /&gt;&lt;/p&gt;
&lt;h2&gt;Monte Carlo C# Turunun Değerlendirmesi&lt;/h2&gt;
&lt;p&gt;Buraya kadarki &amp;ouml;rnek kod versiyonlarını daha iyi karşılaştırmak i&amp;ccedil;in aşağıdaki tabloyu ele alabiliriz.&lt;/p&gt;
&lt;table style="border-collapse: collapse; width: 100%;"&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th style="border: 1px solid #ccc; padding: 8px; font-weight: bold;"&gt;Kriter&lt;/th&gt;
&lt;th style="border: 1px solid #ccc; padding: 8px; font-weight: bold;"&gt;V0&lt;/th&gt;
&lt;th style="border: 1px solid #ccc; padding: 8px; font-weight: bold;"&gt;V1&lt;/th&gt;
&lt;th style="border: 1px solid #ccc; padding: 8px; font-weight: bold;"&gt;V2&lt;/th&gt;
&lt;th style="border: 1px solid #ccc; padding: 8px; font-weight: bold;"&gt;V3&lt;/th&gt;
&lt;th style="border: 1px solid #ccc; padding: 8px; font-weight: bold;"&gt;V4&lt;/th&gt;
&lt;th style="border: 1px solid #ccc; padding: 8px; font-weight: bold;"&gt;V5&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style="border: 1px solid #ccc; padding: 8px; font-weight: bold;"&gt;Paralellik&lt;/td&gt;
&lt;td style="border: 1px solid #ccc; padding: 8px;"&gt;Yok&lt;/td&gt;
&lt;td style="border: 1px solid #ccc; padding: 8px;"&gt;Yok&lt;/td&gt;
&lt;td style="border: 1px solid #ccc; padding: 8px;"&gt;Var&lt;/td&gt;
&lt;td style="border: 1px solid #ccc; padding: 8px;"&gt;Yok&lt;/td&gt;
&lt;td style="border: 1px solid #ccc; padding: 8px;"&gt;Var&lt;/td&gt;
&lt;td style="border: 1px solid #ccc; padding: 8px;"&gt;Var&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="border: 1px solid #ccc; padding: 8px; font-weight: bold;"&gt;Thread-Safe Random&lt;/td&gt;
&lt;td style="border: 1px solid #ccc; padding: 8px;"&gt;Var&lt;/td&gt;
&lt;td style="border: 1px solid #ccc; padding: 8px;"&gt;Var&lt;/td&gt;
&lt;td style="border: 1px solid #ccc; padding: 8px;"&gt;Yok ve race-condition riski var&lt;/td&gt;
&lt;td style="border: 1px solid #ccc; padding: 8px;"&gt;Var&lt;/td&gt;
&lt;td style="border: 1px solid #ccc; padding: 8px;"&gt;ThreadLocal ile Var&lt;/td&gt;
&lt;td style="border: 1px solid #ccc; padding: 8px;"&gt;Var(kendi instance'ı &amp;uuml;zerinden)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="border: 1px solid #ccc; padding: 8px; font-weight: bold;"&gt;Math.Pow L&amp;uuml;ks&amp;uuml;&lt;/td&gt;
&lt;td style="border: 1px solid #ccc; padding: 8px;"&gt;Yok&lt;/td&gt;
&lt;td style="border: 1px solid #ccc; padding: 8px;"&gt;&amp;Ccedil;ok yavaş&lt;/td&gt;
&lt;td style="border: 1px solid #ccc; padding: 8px;"&gt;&amp;Ccedil;ok yavaş&lt;/td&gt;
&lt;td style="border: 1px solid #ccc; padding: 8px;"&gt;Yok&lt;/td&gt;
&lt;td style="border: 1px solid #ccc; padding: 8px;"&gt;Yok&lt;/td&gt;
&lt;td style="border: 1px solid #ccc; padding: 8px;"&gt;Yok&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="border: 1px solid #ccc; padding: 8px; font-weight: bold;"&gt;Iterasyon başına Interlocked&lt;/td&gt;
&lt;td style="border: 1px solid #ccc; padding: 8px;"&gt;Yok&lt;/td&gt;
&lt;td style="border: 1px solid #ccc; padding: 8px;"&gt;Yok&lt;/td&gt;
&lt;td style="border: 1px solid #ccc; padding: 8px;"&gt;Var&lt;/td&gt;
&lt;td style="border: 1px solid #ccc; padding: 8px;"&gt;Var&lt;/td&gt;
&lt;td style="border: 1px solid #ccc; padding: 8px;"&gt;Yok&lt;/td&gt;
&lt;td style="border: 1px solid #ccc; padding: 8px;"&gt;Yok&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="border: 1px solid #ccc; padding: 8px; font-weight: bold;"&gt;&amp;Ccedil;ekirdek sayısına g&amp;ouml;re b&amp;ouml;lme&lt;/td&gt;
&lt;td style="border: 1px solid #ccc; padding: 8px;"&gt;Yok&lt;/td&gt;
&lt;td style="border: 1px solid #ccc; padding: 8px;"&gt;Yok&lt;/td&gt;
&lt;td style="border: 1px solid #ccc; padding: 8px;"&gt;Yok&lt;/td&gt;
&lt;td style="border: 1px solid #ccc; padding: 8px;"&gt;Yok&lt;/td&gt;
&lt;td style="border: 1px solid #ccc; padding: 8px;"&gt;Yok&lt;/td&gt;
&lt;td style="border: 1px solid #ccc; padding: 8px;"&gt;Var&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="border: 1px solid #ccc; padding: 8px; font-weight: bold;"&gt;Lock-free d&amp;ouml;ng&amp;uuml;&lt;/td&gt;
&lt;td style="border: 1px solid #ccc; padding: 8px;"&gt;Var&lt;/td&gt;
&lt;td style="border: 1px solid #ccc; padding: 8px;"&gt;Var&lt;/td&gt;
&lt;td style="border: 1px solid #ccc; padding: 8px;"&gt;Yok&lt;/td&gt;
&lt;td style="border: 1px solid #ccc; padding: 8px;"&gt;Yok&lt;/td&gt;
&lt;td style="border: 1px solid #ccc; padding: 8px;"&gt;Var&lt;/td&gt;
&lt;td style="border: 1px solid #ccc; padding: 8px;"&gt;Var&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;ve şu ana kadar ki Monte Carlo uyarlamaları i&amp;ccedil;in şunları da s&amp;ouml;yleyebiliriz:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;V0:&lt;/strong&gt; Basit, hızlı ama tek thread ile &amp;ccedil;alışıyor. Iterasyon sayısı arttık&amp;ccedil;a s&amp;uuml;reler dramatik şekilde artıyor.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;V1:&lt;/strong&gt; Math.Pow kullanımı performansı ciddi şekilde d&amp;uuml;ş&amp;uuml;r&amp;uuml;yor. Okunabilirlik artarken s&amp;uuml;reler kabul edilemez seviyelere &amp;ccedil;ıkıyor.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;V2:&lt;/strong&gt; Paralel for d&amp;ouml;ng&amp;uuml;s&amp;uuml; i&amp;ccedil;eriyor ama iki kritik sorunu var. Random paylaşımlı olduğu i&amp;ccedil;in race condition riski var ve her iterasyonda Interlocked kullanımı ciddi performans kaybına neden oluyor.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;V3:&lt;/strong&gt; Interlocked.Increment ilgin&amp;ccedil; bir şekilde sıralı bir d&amp;ouml;ng&amp;uuml;ye ekleniyor gibi. Hesaplama V0 gibi hızlı olsa da paralel &amp;ccedil;alışmanın avantajını tam olarak kullanamıyor.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;V4:&lt;/strong&gt; ThreadLocal kullanarak her thread'in kendi Random &amp;ouml;rneğine sahip olması ve saya&amp;ccedil; değerlerini g&amp;uuml;venli bir şekilde birleştirmesi performansı &amp;ouml;nemli &amp;ouml;l&amp;ccedil;&amp;uuml;de artırıyor. Ancak paralel for d&amp;ouml;ng&amp;uuml;s&amp;uuml;n&amp;uuml;n getirdiği bir y&amp;uuml;k hala var gibi g&amp;ouml;r&amp;uuml;n&amp;uuml;yor.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;V5:&lt;/strong&gt; &amp;Ccedil;ekirdek sayısına g&amp;ouml;re iterasyonları b&amp;ouml;lerek her &amp;ccedil;ekirdeğin kendi g&amp;ouml;revini yapması ve sonunda g&amp;uuml;venli bir şekilde birleştirmesi performansı daha da artırıyor.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Peki Ya Aynı Paralel &amp;Ccedil;alışmayı Rust Dili ile Yapsaydık?&lt;/h2&gt;
&lt;p&gt;Pek tabii bu tip y&amp;uuml;ksek performans gerektiren hesaplamalar s&amp;ouml;z konusu olunca aklıma ilk gelen Garbage Collector mekanizmasını aradan &amp;ccedil;ıkarmak hatta managed environment kullanmayan bir dilde ilerlemek oluyor. Birka&amp;ccedil; yıl olsa da ilgilenme fırsatı bulduğum rust ve &amp;ccedil;ok kısa bir s&amp;uuml;re baktığım zig ile aynı senaryoyu deneyebiliriz. &amp;Ouml;nce rust tarafı ile başlayalım.&lt;/p&gt;
&lt;p&gt;İşlemci &amp;ccedil;ekirdeklerinden maksimum fayda sağlamak adına rayon k&amp;uuml;fesi bi&amp;ccedil;ilmiş kaftan. Tabii rastgele sayı &amp;uuml;retimi i&amp;ccedil;in de rand k&amp;uuml;fesini&lt;em&gt;(crate)&lt;/em&gt; kullanmayı tercih edeceğim. Bunları projeye cargo ile ekleyebiliriz.&lt;/p&gt;
&lt;pre class="brush:bash;auto-links:false;toolbar:false" contenteditable="false"&gt;cargo add rayon rand&lt;/pre&gt;
&lt;p&gt;main.rs dosyasına da aşağıdaki kod par&amp;ccedil;asını ekleyerek devam edelim.&lt;/p&gt;
&lt;pre class="brush:cpp;auto-links:false;toolbar:false" contenteditable="false"&gt;use rand::rngs::SmallRng;
use rand::RngExt;
use rayon::prelude::*;

fn main() {
    let total_iterations = 1_000_000_000;

    for _ in 0..10 {
        let start_time = std::time::Instant::now();
        let pi_estimate = calculate_pi(total_iterations);
        let elapsed = start_time.elapsed();
        println!("Estimated Pi: {} in {:?}", pi_estimate, elapsed);
    }
}

fn calculate_pi(total_iterations: u64) -&amp;gt; f64 {
    let in_circle: u64 = (0..total_iterations)
        .into_par_iter()
        .map_init(
            || rand::make_rng::&amp;lt;SmallRng&amp;gt;(),
            |rng, _| {
                let (x, y): (f64, f64) = rng.random();

                if x * x + y * y &amp;lt;= 1.0 {
                    1_u64
                } else {
                    0_u64
                }
            },
        )
        .sum();

    4.0 * (in_circle as f64) / (total_iterations as f64)
}&lt;/pre&gt;
&lt;p&gt;calculate_pi metodunu 10 kez &amp;ccedil;alıştırıp s&amp;uuml;re &amp;ouml;l&amp;ccedil;&amp;uuml;mlemesi yaptırmaktayız. into_par_iter() ile paralel bir iterasyon ve devamında kullanılan map_init ile her bir thread i&amp;ccedil;in ayrı bir rastgele sayı &amp;uuml;reteci oluşturuluyor. map_init kod bloğunda f64 t&amp;uuml;r&amp;uuml;nden rastgele x ve y değerleri &amp;uuml;retiliyor ve &amp;ccedil;ember i&amp;ccedil;inde olup olmadıkları kontrol ediliyor. En sonunda da sum yardımıyla in_circle sayısı toplanarak pi değer tahmini yapılıyor ve bulunan sonu&amp;ccedil; d&amp;ouml;nd&amp;uuml;r&amp;uuml;l&amp;uuml;yor. &amp;Ouml;ncelikle bu kodu derleme modunda &amp;ccedil;alıştırarak s&amp;uuml;releri g&amp;ouml;zlemleyelim.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://www.buraksenyurt.com/image.axd?picture=/2026/Mart/CalculatePi_08.png" alt="" /&gt;&lt;/p&gt;
&lt;p&gt;C# programımıza g&amp;ouml;re &amp;ccedil;ok daha k&amp;ouml;t&amp;uuml; bir performans sergileniyor. Aslında bu sonucu normal karşılamak gerekir zira cargo run komutu varsayılan olarak debug modunda &amp;ccedil;alışır ve bu mod cidden yavaş &amp;ccedil;alışır. Yani release modda &amp;ccedil;alıştırıp bir kıyaslama yapmak daha doğru olur&lt;em&gt;(Evet kabul ediyorum nitro modunu a&amp;ccedil;tık ve bir hile yaptık)&lt;/em&gt;&lt;/p&gt;
&lt;pre class="brush:bash;auto-links:false;toolbar:false" contenteditable="false"&gt;cargo run --release&lt;/pre&gt;
&lt;p&gt;&lt;img src="https://www.buraksenyurt.com/image.axd?picture=/2026/Mart/CalculatePi_09.png" alt="" /&gt;&lt;/p&gt;
&lt;p&gt;"Hımmm... Ama hile yapıyorsun hocam oldu mu şimdi?" :D .Net tarafında yazdığımız son paralel kodu da release modda &amp;ccedil;alıştırarak bir kıyaslama yapmak şimdi &amp;ccedil;ok daha doğru olacak. &amp;Ouml;yleyse...&lt;/p&gt;
&lt;pre class="brush:bash;auto-links:false;toolbar:false" contenteditable="false"&gt;dotnet run -c Release&lt;/pre&gt;
&lt;p&gt;&lt;img src="https://www.buraksenyurt.com/image.axd?picture=/2026/Mart/CalculatePi_10.png" alt="" /&gt;&lt;/p&gt;
&lt;p&gt;dotnet run ile doğrudan &amp;ccedil;alıştırdığımıza g&amp;ouml;re daha iyi sonu&amp;ccedil;lar aldık ama rust tarafına nazaran o kadar da iyi sayılmaz. Fakat şartlar yine eşit değil! Burada Just-in Time derleyicisinin optimizasyonları ile bir performans artışı sağlandı. Lakin .Net 8 sonrası gelen Native AOT desteği ile rust tarafına daha yakın sonu&amp;ccedil;lar elde etmek m&amp;uuml;mk&amp;uuml;n olabilir. O zaman programımızı birde Native AOT ile &amp;ccedil;alıştıralım. Bu ama&amp;ccedil;la ben aşağıdaki komutu denedim. Hem işlemci mimarisi hem de işletim sistemine uygun bir release &amp;ccedil;ıktısı hazırlanıyor. Sonrasında tabii bu exe'yi &amp;ccedil;alıştırmamız gerekiyor.&lt;/p&gt;
&lt;pre class="brush:bash;auto-links:false;toolbar:false" contenteditable="false"&gt;dotnet publish -c Release -r win-x64 --self-contained true /p:PublishAot=true
cd .\bin\release\net10.0\win-x64\publish\
.\CalculatePi.exe&lt;/pre&gt;
&lt;p&gt;&lt;img src="https://www.buraksenyurt.com/image.axd?picture=/2026/Mart/CalculatePi_11.png" alt="" /&gt;&lt;/p&gt;
&lt;p&gt;İyileşme olmadığı gibi s&amp;uuml;reler bir noktadan sonra uzayıp aynı seyirde devam etti gibi. Belki ilk &amp;ccedil;alışmaya başladığında işlemci ısındı ve donanımın bir ger&amp;ccedil;eği olarak s&amp;uuml;reler uzayıp aynı seyirde kaldı. Bunu &amp;ouml;l&amp;ccedil;&amp;uuml;mlemek i&amp;ccedil;in daha iyi bir monitoring sistemi kurgulamak gerekiyor ama bu beni şu an i&amp;ccedil;in aşacak gibi duruyor. Kod tarafında kesin atladığım bir şey var ve emin olmak adına C# uygulamasını belki de SIMD&lt;em&gt;(Single Instruction, Multiple Data)&lt;/em&gt; desteği ekleyerek denemek daha doğru olur. SIMD konusunu da kısaca izah etmek isterim, en azından anladığım kadarıyla.&lt;/p&gt;
&lt;p&gt;Monte Carlo y&amp;ouml;nteminde &amp;ccedil;embere isabet etme durumunu tespit edereken bir form&amp;uuml;l kullanıyoruz. x*x + y*y &amp;lt;= 1.0 şeklinde. İşlemcinin her bir sayı &amp;ccedil;ifti i&amp;ccedil;in tek tek bu işlemi yaptığını d&amp;uuml;ş&amp;uuml;nelim. Ancak SIMD desteği ile işlemcinin bazı register'larını kullanıp aynı anda 4 double veya 8 float işlemin tek bir saat vuruşunda&lt;em&gt;(clock cycle)&lt;/em&gt; yapılması sağlanabilir. Yani tek bir işlemle 4 veya 8 sayı &amp;ccedil;ifti i&amp;ccedil;in `x*x + y*y &amp;lt;= 1.0` kontrol&amp;uuml; yapılabilir. İşin matematiği beni şu an i&amp;ccedil;in aşıyor ancak .Net'in System.Runtime.Intrinsics k&amp;uuml;t&amp;uuml;phanesinde yer alan Vector256, Vector&amp;lt;T&amp;gt; gibi t&amp;uuml;rler ile bu t&amp;uuml;r bir optimizasyonu deneyebiliriz. T&amp;uuml;r adlarından da anlaşılacağı &amp;uuml;zere burada hesaplamaların vekt&amp;ouml;r karşılıklarının bulunduğu bir senaryo var. Ancak işimizi zorlaştıracak bir kısım var ki o da rastgele sayı &amp;uuml;retimi. NextDouble metodunun tekil&lt;em&gt;(kaynaklarda scalar olarak ifade ediliyor)&lt;/em&gt; &amp;ccedil;alıştığı ve SIMD ile doğrudan vekt&amp;ouml;r halinde &amp;ccedil;alışacak bir karşılığının olmadığı iddia ediliyor. Bu, rastgele sayıları bu şekilde kullanırsak donanımsal avantajlardan yararlanamayacağımız anlamına gelmekte. Genellikle XorShift, PCG&lt;em&gt;(Permuted Congruential Generator)&lt;/em&gt; gibi algoritmaların kullanılması ya da rastgele sayıları &amp;ouml;nce devasa bir diziye doldurup SIMD ile bu diziden vekt&amp;ouml;rler halinde &amp;ccedil;ekilmesi gibi y&amp;ouml;ntemler &amp;ouml;neriliyor. Bunun kodunu yazmak i&amp;ccedil;in biraz daha araştırma yapmam şart ki repoda denemesini yapacağım.&lt;/p&gt;
&lt;p&gt;Tekrardan rust tarafına d&amp;ouml;nelim ve uygulama kodumuzu aşağıdaki gibi değiştirelim.&lt;/p&gt;
&lt;pre class="brush:cpp;auto-links:false;toolbar:false" contenteditable="false"&gt;use rand::rngs::SmallRng;
use rand::RngExt;
use rayon::prelude::*;

fn main() {
    let total_iterations = 1_000_000_000;

    for _ in 0..10 {
        let start_time = std::time::Instant::now();
        let pi_estimate = calculate_pi(total_iterations);
        let elapsed = start_time.elapsed();
        println!("Estimated Pi: {} in {:?}", pi_estimate, elapsed);
    }
}

fn calculate_pi(total_iterations: u64) -&amp;gt; f64 {
    let chunk_size = 4; // SIMD i&amp;ccedil;in 4'l&amp;uuml; gruplar halinde işlem yapacağız
                        // &amp;ccedil;&amp;uuml;nk&amp;uuml; AVX2 256-bit genişliğinde ve 4 adet f64 (64-bit) değeri aynı anda işleyebilir.
    let loop_count = total_iterations / chunk_size;

    let in_circle: u64 = (0..loop_count)
        .into_par_iter()
        .map_init(
            || rand::make_rng::&amp;lt;SmallRng&amp;gt;(),
            |rng, _| {
                let x_values: [f64; 4] = rng.random(); // random'un g&amp;uuml;zel yanlarından birisi. Tek &amp;ccedil;ağrıda arka arkaya 4 sayı &amp;uuml;retip diziye atar.
                let y_values: [f64; 4] = rng.random();

                get_circle_value(&amp;amp;x_values, &amp;amp;y_values)
            },
        )
        .sum();

    4.0 * (in_circle as f64) / (total_iterations as f64)
}

// x_values ve y_values elemanları 4 boyutlu dizi olduğunda derleyici bunların kesinlikle sabit boyutlu olduğunu bilecek.
// Buna g&amp;ouml;re kod doğrudan AVX2 SIMD komutuna &amp;ccedil;evrilebilir.
#[inline(always)]
// Bunu eklediğimiz i&amp;ccedil;in derleyici bu fonksiyonu &amp;ccedil;ağırmak yerine doğrudan kodun i&amp;ccedil;erisine g&amp;ouml;mer.
pub fn get_circle_value(x_values: &amp;amp;[f64; 4], y_values: &amp;amp;[f64; 4]) -&amp;gt; u64 {
    let mut count = 0;

    for i in 0..4 {
        if x_values[i] * x_values[i] + y_values[i] * y_values[i] &amp;lt;= 1.0 {
            count += 1;
        }
    }

    count
}&lt;/pre&gt;
&lt;p&gt;&lt;img src="https://www.buraksenyurt.com/image.axd?picture=/2026/Mart/CalculatePi_12.png" alt="" /&gt;&lt;/p&gt;
&lt;p&gt;Haydaaa! :D SIMD dedik daha hızlı &amp;ccedil;alışır dedik ancak bir &amp;ouml;nceki rust kodumuza g&amp;ouml;re neredeyse 3.5 kat daha yavaş &amp;ccedil;alışma zamanı s&amp;uuml;releri g&amp;ouml;rd&amp;uuml;k, oldu mu şimdi! Aslında elde ettiğimiz s&amp;uuml;reler iki uygulama bi&amp;ccedil;imi i&amp;ccedil;inde olduk&amp;ccedil;a iyi. Milisaniyeler mertebesinde 1 milyarlık iterasyonları tamamladık. Hatta ilk rust kodumuz gayet sade ve anlaşılır. Yine de SIMD metodolojimiz beklediğimiz şekilde &amp;ccedil;alışmadı. Buradaki kritik nokta maliyetin nerede olduğunu tespit edebilmek. B&amp;uuml;y&amp;uuml;k ihtmalle rastgele sayı &amp;uuml;retimi işi beklenenden uzun s&amp;uuml;r&amp;uuml;yor ve doğru şekilde bir vekt&amp;ouml;rleme ger&amp;ccedil;ekleşmiyor. SIMD i&amp;ccedil;in "veriler zaten devasa bir dizide ve bellekte hazır bekliyorsa" gibi bir durum olduğu ifade ediliyor&lt;em&gt;(Yani b&amp;ouml;yle bir hazırlık sonrası daha &amp;ccedil;ok işe yarar deniyor)&lt;/em&gt; Lakin bizim &amp;ouml;rneğimizde veriyi her adımda sıfırdan &amp;uuml;retiyoruz ve &amp;uuml;retim maliyeti hesaplama maliyetinin &amp;uuml;zerine &amp;ccedil;ıkıyor. Dikkat edelim rastgele sayı &amp;uuml;retiminden 4 elemanlık bir dizi &amp;ccedil;ıkartmayı kolaylaştırdık kolaylaştırmasına ama 4erli gruplama i&amp;ccedil;in altına girdiğimiz bu maliyet hesaplama s&amp;uuml;resini ciddi şekilde artırdı. Ciddi şekilde dediğime bakmayın, .Net kodumuza nazaran burada milisaniyeler mertebesinde konuşabiliyoruz(Tabii C# program kodumuzu da SIMD ile &amp;ccedil;alışır hale getirip bir değerlendirme yapmamız lazım)&lt;/p&gt;
&lt;h2&gt;O Zaman Kontrol&amp;uuml; Biraz Daha Elimize Alalım. Zig ile Deneyelim&lt;/h2&gt;
&lt;p&gt;Zig programlama dili, gizli ya da bilin&amp;ccedil;siz kontrol akışlarına m&amp;uuml;sama g&amp;ouml;stermiyor. Yani bildiğim kadarı ile hazır bir Paralle.For elimizde yok, threar'leri ve bellek tahsislerini doğrudan bizim yapmamız lazım ki bellek tahsisi&lt;em&gt;(allocation)&lt;/em&gt; konusunda da &amp;ccedil;ok hassas bir dil. Buna g&amp;ouml;re her thread'in kendi yerel sayacını artıracağı, işi bitince bu değerleri ana sayaca ekleyeceği bir akış kurgulamak gerekiyor. Aşağıdaki kod par&amp;ccedil;ası ile başlayalım &amp;ouml;yleyse. Eğer &amp;ccedil;ok iyi s&amp;uuml;reler elde edersek belki de daha da iyileştirmeye &amp;ccedil;alışmayız :P&lt;/p&gt;
&lt;pre class="brush:cpp;auto-links:false;toolbar:false" contenteditable="false"&gt;const std = @import("std");

// Monte Carlo y&amp;ouml;ntemiyle Pi değeri hesaplanan fonksiyon
// ilk parametre iterasyon sayısını alır ki bizim &amp;ouml;rneğimizde 1 milyar / işlemci-&amp;ccedil;ekirdek sayısı kadardır.
// İkinci parametre pointer olarak gelir ve in_circle değişkenine atomik olarak ekleme yapar.
// Atomik seviyede ekleme yapmak hızlıdır &amp;ccedil;&amp;uuml;nk&amp;uuml; doğrudan işlemci instruction'larıyla yapılır ve kilitlenmeye gerek kalmaz.
fn monteCarloCalculation(iterations: usize, result: *usize) void {
    var seed: u64 = undefined;
    std.crypto.random.bytes(std.mem.asBytes(&amp;amp;seed)); // Rastgele bir seed oluşturuyoruz, b&amp;ouml;ylece her &amp;ccedil;alıştırmada farklı sonu&amp;ccedil;lar elde edebiliriz.

    // Xoshiro256 t&amp;uuml;r&amp;uuml;nden bir PRNG-Pseudo Random Number Generator- başlatıyoruz
    // Bu epey hızlı &amp;ccedil;alışır
    var rng = std.Random.DefaultPrng.init(seed);
    const random = rng.random();

    var localCount: usize = 0;
    var i: usize = 0;

    // normal iterasyon d&amp;ouml;ng&amp;uuml;m&amp;uuml;z
    while (i &amp;lt; iterations) : (i += 1) {
        // f64 yerine f32 kullanarak işlemci &amp;uuml;zerindeki y&amp;uuml;k&amp;uuml; yarı yarıya d&amp;uuml;ş&amp;uuml;rebiliriz.
        const x = random.float(f32);
        const y = random.float(f32);
        // &amp;ccedil;ember i&amp;ccedil;inde olup olmadığını kontrol ediyoruz
        if (x * x + y * y &amp;lt;= 1.0) {
            localCount += 1;
        }
    }

    // İşlem bitince tek bir atomik yazma işlemi
    // İlk parametre veri t&amp;uuml;r&amp;uuml;, ikinci parametre hedef değişken,
    // &amp;uuml;&amp;ccedil;&amp;uuml;nc&amp;uuml; parametre &amp;ccedil;ağırılacak instruction komutu,
    // d&amp;ouml;rd&amp;uuml;nc&amp;uuml; parametre eklenecek değer
    // ve en nihayetinde beşinci parametre bellek sıralama t&amp;uuml;r&amp;uuml;
    _ = @atomicRmw(usize, result, .Add, localCount, .monotonic);
}

pub fn main() !void {
    const totalIterations: usize = 1_000_000_000;
    const threadCount = try std.Thread.getCpuCount(); // işlemci &amp;ccedil;ekirdek sayısını alıyoruz

    const iterPerThread = totalIterations / threadCount; // thread başına iterasyon değeri

    // Allocator'sız olmaz :D GPA nispeten daha iyi performans g&amp;ouml;sterir
    var gpa = std.heap.GeneralPurposeAllocator(.{}){};
    defer _ = gpa.deinit(); // tedbir ama&amp;ccedil;lı deinit &amp;ccedil;ağırıyoruz, b&amp;ouml;ylece program sonunda kaynaklar d&amp;uuml;zg&amp;uuml;n şekilde serbest bırakılır
    const allocator = gpa.allocator();

    // thread'ler i&amp;ccedil;in bellek ayırıyoruz, her thread monteCarloCalculation fonksiyonunu &amp;ccedil;alıştıracak
    const threads = try allocator.alloc(std.Thread, threadCount);
    defer allocator.free(threads); // Yine tedbir ama&amp;ccedil;lı thread'ler i&amp;ccedil;in ayrılan belleği serbest bırakıyoruz

    // Standart deney d&amp;ouml;ng&amp;uuml;m&amp;uuml;z. 10 kez &amp;ccedil;alıştırılacak
    for (0..10) |_| {
        var in_circle: usize = 0;

        const start = std.time.nanoTimestamp();

        // Her test adımında thread'ler oluşturup, monteCarloCalculation fonksiyonunu &amp;ccedil;alıştırıyoruz
        for (threads) |*thread| {
            thread.* = try std.Thread.spawn(
                .{},
                monteCarloCalculation,
                .{ iterPerThread, &amp;amp;in_circle },
            );
        }

        // Burada thread'lerin bitmesini bekliyoruz,
        // her thread'in join edilmesi gerekiyor ki sonu&amp;ccedil;lar doğru şekilde toplanabilsin
        // Burası aynı zamanda en son &amp;ccedil;alışma zamanının neden y&amp;uuml;ksek &amp;ccedil;ıktığının bir sebebi olabilir
        for (threads) |thread| {
            thread.join();
        }

        const elapsed_ns = std.time.nanoTimestamp() - start;
        const elapsed_ms = @divTrunc(elapsed_ns, std.time.ns_per_ms);

        const pi = 4.0 * @as(f64, @floatFromInt(in_circle)) / @as(f64, @floatFromInt(totalIterations));
        std.debug.print("Pi = {d:.6}  ({d} ms)\n", .{ pi, elapsed_ms });
    }
}&lt;/pre&gt;
&lt;p&gt;Uygulamayı aşağıdaki gibi cpu'nun t&amp;uuml;m yeteneklerini de işin i&amp;ccedil;erisine dahil ederek release modda &amp;ccedil;alıştırabiliriz.&lt;/p&gt;
&lt;pre class="brush:bash;auto-links:false;toolbar:false" contenteditable="false"&gt;zig run .\program.zig -O ReleaseFast -mcpu=native&lt;/pre&gt;
&lt;p&gt;&lt;img src="https://www.buraksenyurt.com/image.axd?picture=/2026/Mart/CalculatePi_13.png" alt="" /&gt;&lt;/p&gt;
&lt;p&gt;Rust'ın ilk versiyonundaki release moddaki s&amp;uuml;relere ulaşamadık belki ama yine de hızlı &amp;ccedil;alıştı diyebiliriz. Dikkat &amp;ccedil;ekici noktalardan birisi ilk başlangı&amp;ccedil;taki s&amp;uuml;renin sonradan y&amp;uuml;kselmesi, belli bir ortalamada devam etmesi ve en sonda neredeyse iki katı kadar yavaş tamamlanması. Son s&amp;uuml;re &amp;ouml;l&amp;ccedil;&amp;uuml;m&amp;uuml;n&amp;uuml;n bu kadar y&amp;uuml;ksek &amp;ccedil;ıkmasının bir sebebi kuvvetle muhtemele paralel &amp;ccedil;alışan thread'lerin tamamının bitmesini bekleyen join &amp;ccedil;ağrıları olsa gerek.&lt;/p&gt;
&lt;p&gt;Diğer yandan for d&amp;ouml;ng&amp;uuml;m&amp;uuml;z her adımda işletim sisteminden yeni thread'ler talep edip sonrasında bunları yok etmekte. İşletim sistemi bu talep et-y&amp;uuml;r&amp;uuml;t-yok et s&amp;uuml;recinde yorulabilir ve bellek &amp;uuml;zerinde par&amp;ccedil;alanmalar&lt;em&gt;(fragmentation)&lt;/em&gt; oluşabilir. Bu par&amp;ccedil;alanmalar yeni thread'ler i&amp;ccedil;in alan tahsislerini geciktirebilir. Aslında burada bir thread-pool yapısı kurgulamadığımız i&amp;ccedil;in de b&amp;ouml;yle bir durum oluşuyor diye d&amp;uuml;ş&amp;uuml;n&amp;uuml;yorum.&lt;/p&gt;
&lt;p&gt;Zig kodu &amp;ccedil;ok daha iyi yazılabilir belki de ancak kaynaklardan &amp;ouml;ğrenebildiğim şimdilik bu kadar.&lt;/p&gt;
&lt;h2&gt;Sonu&amp;ccedil; Değerlendirmesi&lt;/h2&gt;
&lt;p&gt;Aslında bu &amp;ccedil;alışmada amacım Pi(&amp;pi;) sayısını hesaplarken monte carlo y&amp;ouml;ntemi ile ilerlemek ve sonrasında daha tutarlı sonu&amp;ccedil;lara ulaşmak i&amp;ccedil;in farklı matematik y&amp;ouml;ntemlere ge&amp;ccedil;mekti. Bunu yaparken en &amp;ccedil;ok aşina olduğum programlama dili C# ile işe başlamak istedim. K&amp;uuml;&amp;ccedil;&amp;uuml;k iterasyonlarda hızlı sonu&amp;ccedil;lar aldım ama y&amp;uuml;ksek iterasyonlara gelince hesaplama s&amp;uuml;releri ciddi anlamda uzamaya başladı. Dolayısıyla y&amp;ouml;ntem değişikliklerine gitmem yeni şeyler keşfetmem gerekti.&lt;/p&gt;
&lt;p&gt;&amp;Ouml;zellikle &amp;ccedil;ok y&amp;uuml;ksek iterasyonlarda paralel &amp;ccedil;alışmanın fark yarattığını g&amp;ouml;zlemedim. Derken rust ve zig dillerini işin i&amp;ccedil;erisine katmak istedim. Release modda rust'ın epey iyi sonu&amp;ccedil;lar aldığını belirtmem lazım ki fark bu kadar a&amp;ccedil;ılınca release derlemeleri ve hatta AOT&lt;em&gt;(Ahead-Of-Time)&lt;/em&gt; eklemeleri ile .net kodumuzu daha da hızlandırmaya &amp;ccedil;alıştım. Burada tek a&amp;ccedil;ık kapı .Net kodunu SIMD&lt;em&gt;(Single Instruction, Multiple Data)&lt;/em&gt; desteği ile işletip s&amp;uuml;re hesaplaması yapmamış olmam.&lt;/p&gt;
&lt;p&gt;Monte carlo doğru pi rakamlarına ulaşmak i&amp;ccedil;in iyi bir tercih değil. Bunu değiştirip farklı bir matematik model ile ilerlemek lazım ama t&amp;uuml;m kodlarımız i&amp;ccedil;in şu anda aynı y&amp;ouml;ntem s&amp;ouml;z konusu. Dolayısıyla &amp;ccedil;alışma s&amp;uuml;relerini kıyaslamak ve bir &amp;ouml;zet tablo hazırlamak iyi olabilir.&lt;/p&gt;
&lt;p&gt;Ortalık tabii &amp;ccedil;ok karıştı. Hatta &amp;ccedil;alışma sırasında C# metodunun eski versiyonunu tekrar &amp;ccedil;alıştırdığımı fark ettim. Dolayısıyla &amp;uuml;&amp;ccedil; dilinde en son karar kıldığım kod versiyonlarını aynı iterasyonlar sayıları ile ve release modda derleyerek &amp;ccedil;alıştırmaya karar verdim. S&amp;uuml;reler milisaniye cinsindendir ve toplamda 10 &amp;ccedil;alıştırma &amp;uuml;zerinden ortalama s&amp;uuml;reler hesaplanmıştır.&lt;/p&gt;
&lt;table style="border-collapse: collapse; width: 100%;"&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th style="border: 1px solid #ccc; padding: 8px; font-weight: bold;"&gt;Dil&lt;/th&gt;
&lt;th style="border: 1px solid #ccc; padding: 8px; font-weight: bold;"&gt;Y&amp;ouml;ntem&lt;/th&gt;
&lt;th style="border: 1px solid #ccc; padding: 8px; font-weight: bold;"&gt;Komut&lt;/th&gt;
&lt;th style="border: 1px solid #ccc; padding: 8px; font-weight: bold;"&gt;İlk S&amp;uuml;re&lt;/th&gt;
&lt;th style="border: 1px solid #ccc; padding: 8px; font-weight: bold;"&gt;Son S&amp;uuml;re&lt;/th&gt;
&lt;th style="border: 1px solid #ccc; padding: 8px; font-weight: bold;"&gt;En &amp;Ccedil;abuk S&amp;uuml;re&lt;/th&gt;
&lt;th style="border: 1px solid #ccc; padding: 8px; font-weight: bold;"&gt;En Yavaş S&amp;uuml;re&lt;/th&gt;
&lt;th style="border: 1px solid #ccc; padding: 8px; font-weight: bold;"&gt;Ortalama S&amp;uuml;re&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style="border: 1px solid #ccc; padding: 8px; font-weight: bold;"&gt;C#&lt;/td&gt;
&lt;td style="border: 1px solid #ccc; padding: 8px;"&gt;Paralel for, Task, &amp;Ccedil;ekirdek Sayısı kadar&lt;/td&gt;
&lt;td style="border: 1px solid #ccc; padding: 8px;"&gt;dotnet run -c Release&lt;/td&gt;
&lt;td style="border: 1px solid #ccc; padding: 8px;"&gt;1029 ms&lt;/td&gt;
&lt;td style="border: 1px solid #ccc; padding: 8px;"&gt;2252 ms&lt;/td&gt;
&lt;td style="border: 1px solid #ccc; padding: 8px;"&gt;1029 ms&lt;/td&gt;
&lt;td style="border: 1px solid #ccc; padding: 8px;"&gt;3103 ms&lt;/td&gt;
&lt;td style="border: 1px solid #ccc; padding: 8px;"&gt;2119.1 ms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="border: 1px solid #ccc; padding: 8px; font-weight: bold;"&gt;Rust&lt;/td&gt;
&lt;td style="border: 1px solid #ccc; padding: 8px;"&gt;Rayon&lt;/td&gt;
&lt;td style="border: 1px solid #ccc; padding: 8px;"&gt;cargo run --release&lt;/td&gt;
&lt;td style="border: 1px solid #ccc; padding: 8px;"&gt;255.427 ms&lt;/td&gt;
&lt;td style="border: 1px solid #ccc; padding: 8px;"&gt;254.023 ms&lt;/td&gt;
&lt;td style="border: 1px solid #ccc; padding: 8px;"&gt;247.887 ms&lt;/td&gt;
&lt;td style="border: 1px solid #ccc; padding: 8px;"&gt;257.314 ms&lt;/td&gt;
&lt;td style="border: 1px solid #ccc; padding: 8px;"&gt;251.564 ms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="border: 1px solid #ccc; padding: 8px; font-weight: bold;"&gt;Zig&lt;/td&gt;
&lt;td style="border: 1px solid #ccc; padding: 8px;"&gt;std.Thread&lt;/td&gt;
&lt;td style="border: 1px solid #ccc; padding: 8px;"&gt;zig run .\program.zig -O ReleaseFast -mcpu=native&lt;/td&gt;
&lt;td style="border: 1px solid #ccc; padding: 8px;"&gt;474 ms&lt;/td&gt;
&lt;td style="border: 1px solid #ccc; padding: 8px;"&gt;1097 ms&lt;/td&gt;
&lt;td style="border: 1px solid #ccc; padding: 8px;"&gt;474 ms&lt;/td&gt;
&lt;td style="border: 1px solid #ccc; padding: 8px;"&gt;1121 ms&lt;/td&gt;
&lt;td style="border: 1px solid #ccc; padding: 8px;"&gt;637 ms&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;br /&gt;B&amp;ouml;ylece geldik bir anlamsız maceranın daha sonunda :D Ben bir yol g&amp;ouml;sterdim ancak siz daha iyisini yapabilirsiniz. &amp;Ouml;rneğin C# kodu i&amp;ccedil;in SIMD deneyip sonu&amp;ccedil;ları kıyaslayabilirsiniz. Farklı donanımlarda, benchmark k&amp;uuml;t&amp;uuml;phaneleri ile bu testleri yapabilir, standart sapmaları ve anlık koşulları işin i&amp;ccedil;erisine katıp hangi senaryoda hangisi ile ilerlemek daha doğru olur araştırabilirsiniz. Şimdilik benden bu kadar. Tekrardan g&amp;ouml;r&amp;uuml;ş&amp;uuml;nceye dek hepinize mutlu g&amp;uuml;nler dilerim.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://github.com/buraksenyurt/friday-night-programmer/tree/main/src/CalculatePi" target="_blank"&gt;C# Kod &amp;Ouml;rneği&lt;/a&gt;, &lt;a href="https://github.com/buraksenyurt/friday-night-programmer/tree/main/src/calculate-pi-rust" target="_blank"&gt;Rust Kod &amp;Ouml;rneği&lt;/a&gt;, &lt;a href="https://github.com/buraksenyurt/friday-night-programmer/tree/main/src/calculate-pi-zig" target="_blank"&gt;Zig Kod &amp;Ouml;rneği&lt;/a&gt;&lt;/p&gt;</summary>
    <published>2026-03-26T19:21:00+00:00</published>
    <link rel="related" href="https://www.buraksenyurt.com/post/pi-sayisini-hesaplama-yolunda#comment" />
    <category term="C#" />
    <betag:tag>c#</betag:tag>
    <betag:tag>rust</betag:tag>
    <betag:tag>zig</betag:tag>
    <betag:tag>parallel programming</betag:tag>
    <betag:tag>rayon</betag:tag>
    <betag:tag>simd</betag:tag>
    <dc:publisher>bsenyurt</dc:publisher>
    <dc:description>Matematiksel yöntemlerden bazılarını ele alarak belli bir basamağa kadar pi(π) sayısını hesaplamaya çalışacağım. Doğru bir basamak değerine ulaşmak ve burada yüksek sürate çıkmak hedeflerim arasında. Önce en amele yöntemlerden başlayarak daha sonra daha karmaşık yöntemlere geçmek niyetindeyim ama yol beni farklı bir rotaya sürükledi diye de özet geçeyim :D Diğer modellere geçemeden kendimi diller arasında performans hesaplamaları, optimizasyonlar ve senkronizasyon sorunlarıyla uğraşırken buldum. O yüzden bu yazıda sadece Monte Carlo yöntemini ele alacağım.</dc:description>
    <pingback:server>https://www.buraksenyurt.com/pingback.axd</pingback:server>
    <pingback:target>https://www.buraksenyurt.com/post.aspx?id=27be173a-1af1-4128-a452-21edec7506db</pingback:target>
    <slash:comments>0</slash:comments>
    <trackback:ping>https://www.buraksenyurt.com/trackback.axd?id=27be173a-1af1-4128-a452-21edec7506db</trackback:ping>
    <wfw:comment>https://www.buraksenyurt.com/post/pi-sayisini-hesaplama-yolunda#comment</wfw:comment>
    <wfw:commentRss>https://www.buraksenyurt.com/syndication.axd?post=27be173a-1af1-4128-a452-21edec7506db</wfw:commentRss>
  </entry>
  <entry>
    <id>https://www.buraksenyurt.com/post/hexagonal-architecture-101</id>
    <title>Hexagonal Architecture 101</title>
    <updated>2026-03-13T19:31:00+00:00</updated>
    <link rel="self" href="https://www.buraksenyurt.com/post.aspx?id=6f3fa205-863e-4672-bd91-1ac64286aa38" />
    <link href="https://www.buraksenyurt.com/post/hexagonal-architecture-101" />
    <author>
      <name>bsenyurt</name>
    </author>
    <summary type="html">&lt;p&gt;Kurumsal uygulamaları g&amp;ouml;z &amp;ouml;n&amp;uuml;ne aldığımızda zaman i&amp;ccedil;erisinde bir&amp;ccedil;ok yazılım mimarisinin ortaya &amp;ccedil;ıktığını g&amp;ouml;r&amp;uuml;yoruz. Programlama dillerinin gelişimi, framework'lerin ortaya &amp;ccedil;ıkması ve değişen m&amp;uuml;şteri ihtiya&amp;ccedil;ları sonucunda bu kavram &amp;ccedil;ok daha b&amp;uuml;y&amp;uuml;k &amp;ouml;nem kazandı. Belki de her şey &lt;strong&gt;&amp;uuml;&amp;ccedil; katmanlı&lt;em&gt;(3-tier)&lt;/em&gt;&lt;/strong&gt; yaklaşımla başlamıştı. Geldiğimiz zaman diliminde ise &lt;strong&gt;monolit&lt;/strong&gt; sistemlerin &lt;strong&gt;mod&amp;uuml;ler&lt;/strong&gt; hale getirildiğiği &lt;strong&gt;Modulith&lt;/strong&gt;'lerden mikro servislere, soğan halkaları benzetmesi ile pop&amp;uuml;lerleşen &lt;strong&gt;Onion&lt;/strong&gt; mimariden &lt;strong&gt;servis odaklı&lt;em&gt;(Service-Oriented)&lt;/em&gt;&lt;/strong&gt; yaklaşıma kadar bir&amp;ccedil;ok stil var. Bazı kaynaklarda yazılım mimarileri &lt;strong&gt;katmanlı&lt;em&gt;(Layered)&lt;/em&gt; &lt;/strong&gt;ve&lt;strong&gt; dağıtık sistemler&lt;em&gt;(Distributed)&lt;/em&gt; &lt;/strong&gt;olmak &amp;uuml;zere iki ana kategoriye ayrılmakta. &amp;Uuml;zerinde uzun uzun konuşulacak olan bu kavramları elbette deneyimleyerek g&amp;ouml;rmek en g&amp;uuml;zeli. Bende bir s&amp;uuml;redir bakmak istediğim &lt;strong&gt;Hexagonal&lt;/strong&gt; mimari yaklaşımını &amp;ouml;ğrenmeye karar verdim ve işte karşınızdayım. Gelin .net platformunda bu mimariyi &amp;ccedil;ok temel seviyede de olsa uygulamalı olarak anlamaya &amp;ccedil;alışalım.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;&lt;a href="https://github.com/buraksenyurt/HexagonalArchitecture_101" target="_blank"&gt;Yazıdaki uygulama kodlarına github reposu &amp;uuml;zerinden de erişebilirsiniz.&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Bu mimari bazı kaynaklarda &lt;strong&gt;"Ports and Adapters"&lt;/strong&gt; olarak da ge&amp;ccedil;iyor. Orijini &lt;strong&gt;Alistair Cockburn&lt;/strong&gt;'ın &lt;a href="https://alistair.cockburn.us/hexagonal-architecture" target="_blank"&gt;şuradaki yazısına&lt;/a&gt; dayanıyor. Kaynaklara g&amp;ouml;re &lt;strong&gt;2005&lt;/strong&gt; yılından beri hayatımızda olan bir tasarım. Tabii işin &amp;ouml;z&amp;uuml;nde &amp;ccedil;ok temel yazılım kavramları ve ilkeleri var. Her şey uygulama domain'i i&amp;ccedil;erisindeki iş kurallarının dış d&amp;uuml;nyadan tamamen izole edilmesi fikrine dayanıyor. Bu zaten bir &amp;ccedil;ok modern mimari yaklaşımın ana noktalarından birisi ancak uygulama bi&amp;ccedil;imleri farklılık g&amp;ouml;sterebiliyor.&lt;/p&gt;
&lt;p&gt;Sonu&amp;ccedil;ta &lt;strong&gt;gevşek bağlılık&lt;em&gt;(Loose Coupling)&lt;/em&gt;&lt;/strong&gt;, sorumlulukların doğru ayrılması&lt;strong&gt;&lt;em&gt;(Separation of Concerns)&lt;/em&gt;&lt;/strong&gt;, bağımlılıkların tersine &amp;ccedil;evrilmesi&lt;strong&gt;&lt;em&gt;(Inversion of Control)&lt;/em&gt;&lt;/strong&gt;, bağımlılıkların dışarıdan sağlanması&lt;strong&gt;&lt;em&gt;(Dependency Injection)&lt;/em&gt;&lt;/strong&gt;, zengin nesneler&lt;em&gt;(Rich Entity - yazılım prensibi diyemesek de DDD'nin izlerinden birisi olarak mimaride yer bulabilir)&lt;/em&gt; kullanılması gibi temel kavramlar &amp;uuml;zerine kurulu bir mimari yaklaşım. Bu prensipler sayesinde uygulama domain'i i&amp;ccedil;erisindeki iş kuralları&lt;strong&gt;&lt;em&gt;(Business Specific Domain Rules)&lt;/em&gt;&lt;/strong&gt;, dış d&amp;uuml;nyadan gelen veri kaynaklarından, kullanıcı aray&amp;uuml;z&amp;uuml;nden, diğer sistemlerle entegrasyonlardan tamamen izole edilebilmekte. B&amp;ouml;ylece uygulama domain'i i&amp;ccedil;erisindeki kodun test edilebilirliği, s&amp;uuml;rd&amp;uuml;r&amp;uuml;lebilirliği ve esnekliği daha da artmakta.&lt;/p&gt;
&lt;p&gt;Bu mimari internette genellikle aşağıdakine benzer bir g&amp;ouml;rsel ile 50bin feet y&amp;uuml;kseklikten anlatılmaya &amp;ccedil;alışılır. &lt;em&gt;(&amp;Ccedil;izim Excalidraw.io &amp;uuml;zerinde tamamen insan eliyle oluşturulmuştur :P)&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;&lt;img src="https://www.buraksenyurt.com/image.axd?picture=/2026/Mart/HighLevelDesign.png" alt="" /&gt;&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Grafiği ş&amp;ouml;yle &amp;ouml;zetlemeye &amp;ccedil;alışalım. İş kuralları ve domain yapısı tamamen &lt;strong&gt;Application&lt;/strong&gt; katmanında yer alır. Bunu adapt&amp;ouml;rlerin oluşturduğu bir başka katman sarar. Adapt&amp;ouml;rler, uygulama domain'ini dış d&amp;uuml;nyaya bağlayan bir k&amp;ouml;pr&amp;uuml; g&amp;ouml;revi g&amp;ouml;r&amp;uuml;rler. Dış d&amp;uuml;nya ise kullanıcı aray&amp;uuml;z&amp;uuml;, veri tabanı ve diğer sistemlerle entegrasyonlar gibi unsurları i&amp;ccedil;erir. Adapt&amp;ouml;rler portlara bağlanarak uygulama domain'ine erişim sağlarlar. Portlar ise uygulama domain'inin dış d&amp;uuml;nyaya a&amp;ccedil;ılan kapılarıdır. Bu sayede uygulama domain'i tamamen izole edilmiş olur ve dış d&amp;uuml;nyadan gelen değişikliklerden etkilenmez. B&amp;ouml;yle anlatınca ne g&amp;uuml;zel değil mi? Soyut soyut :D Pek tabii uygulamayı yazıp, avantaj ve dezavantajlarını g&amp;ouml;rmeden mimariyi anlamamız pek m&amp;uuml;mk&amp;uuml;n değil.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Mimarinin ana sloganı şudur:&lt;/strong&gt; Seperating Business Logic from Infrastructure with Ports and Adapters. Yani iş kurallarını altyapıdan portlar ve adapt&amp;ouml;rler ile ayırmak.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Burada kafa karıştıcı bazı meseleler olabiliyor. &amp;Ouml;rneğin adapt&amp;ouml;rlerin &lt;strong&gt;Inbound&lt;/strong&gt; ve &lt;strong&gt;Outbound&lt;/strong&gt; olarak ikiye ayrılması, portların ne olduğu, adapt&amp;ouml;rlerin portlara nasıl bağlandığı vb. Ben bu konuları m&amp;uuml;mk&amp;uuml;n olduğunca basit senaryolar &amp;uuml;zerinden uygulamalı olarak incelemek istiyorum. Bu &amp;ccedil;alışmadaki temel amacım bu...&lt;/p&gt;
&lt;h2&gt;Senaryo&lt;/h2&gt;
&lt;p&gt;Kısır bir senaryo ile başlayalım. Stok takibi yapmak istediğimiz &amp;uuml;r&amp;uuml;nler var. Buradaki basit iş kurallarını hexagonal mimarisine g&amp;ouml;re ele almaya &amp;ccedil;alışacağız. Uygulama kodlarını .Net platformunda C# ile yazacağım. Elbette bu mimariyi uygulamaya uygun farklı bir platform veya programlama dili de se&amp;ccedil;ilebilir. Sonu&amp;ccedil;ta mimarinin prensipleri değişmeyecektir.&lt;/p&gt;
&lt;h2&gt;Geliştirme Aşamaları&lt;/h2&gt;
&lt;h3&gt;1. Solution ve Proje Yapısının inşa Edilmesi&lt;/h3&gt;
&lt;p&gt;Solution yapısını başlangı&amp;ccedil;ta aşağıdaki gibi oluşturabiliriz.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://www.buraksenyurt.com/image.axd?picture=/2026/Mart/SolutionStructure.png" alt="" /&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;HexagonalAdventure.Domain&lt;/strong&gt; bir &lt;strong&gt;class library&lt;/strong&gt; ve domain nesneleri ile iş kurallarını i&amp;ccedil;eriyor.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;HexagonalAdventure.Application&lt;/strong&gt; yine bir &lt;strong&gt;class library&lt;/strong&gt; ve&lt;strong&gt; In/Out port&lt;/strong&gt; nesnelerini i&amp;ccedil;eriyor. &lt;strong&gt;Inbound&lt;/strong&gt; &lt;strong&gt;Port&lt;/strong&gt;'lar dış d&amp;uuml;nyanın &amp;ccedil;ekirdeğe ulaşmak i&amp;ccedil;in kullanacağı s&amp;ouml;zleşmeler olarak d&amp;uuml;ş&amp;uuml;n&amp;uuml;lebilir. &lt;strong&gt;Outbound&lt;/strong&gt; &lt;strong&gt;Port&lt;/strong&gt; nesneleri ise &amp;ccedil;ekirdeğin dış d&amp;uuml;nyadan yaptırmak istediği işler i&amp;ccedil;in kullanılan s&amp;ouml;zleşmedir.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;HexagonalAdventure.Adapters&lt;/strong&gt; ise şu anda iki proje i&amp;ccedil;eriyor. Bunlardan birisi &lt;strong&gt;Class Library&lt;/strong&gt; ve &lt;strong&gt;Outbound&lt;/strong&gt; &lt;strong&gt;Adapter&lt;/strong&gt; olarak d&amp;uuml;ş&amp;uuml;n&amp;uuml;lebilir. &amp;Ouml;rneğin &lt;strong&gt;Entity Framework&lt;/strong&gt; tabanlı bir &lt;strong&gt;Repository&lt;/strong&gt;&amp;nbsp;uyarlaması burada yer alır. &lt;strong&gt;Outbound&lt;/strong&gt; &lt;strong&gt;Port&lt;/strong&gt;'ta tanımlanan s&amp;ouml;zleşmenin somut olarak uygulandığı yerdir. Diğer proje ise bir &lt;strong&gt;Web Api&lt;/strong&gt;'dir ve &lt;strong&gt;Inbound&lt;/strong&gt; &lt;strong&gt;Adapter&lt;/strong&gt; olarak d&amp;uuml;ş&amp;uuml;n&amp;uuml;lebilir. Dış d&amp;uuml;nyandan gelen isteği alır ve &lt;strong&gt;Inbound&lt;/strong&gt; &lt;strong&gt;Port&lt;/strong&gt; &amp;uuml;st&amp;uuml;nden sistemi tetikler. Hatta web api projesindeki program sınıfı &lt;strong&gt;Composition Root&lt;/strong&gt; g&amp;ouml;revini &amp;uuml;stlenir. Yani uygulama başlarken port ve adapt&amp;ouml;rlerin eşleştirilip birbirine bağlandığı yerdir. Bu sayede uygulama domain'i i&amp;ccedil;erisindeki kodun dış d&amp;uuml;nyaya olan bağımlılığı tamamen ortadan kalkar.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;2. Domain Modelinin Oluşturulması&lt;/h3&gt;
&lt;p&gt;Şimdi &lt;strong&gt;domain&lt;/strong&gt; katmanına gelip &lt;strong&gt;rich entity&lt;/strong&gt; modunda bir &lt;strong&gt;Product&lt;/strong&gt; sınıfı oluşturalım. Bu sınıf &amp;uuml;r&amp;uuml;n&amp;uuml;n temel &amp;ouml;zelliklerini ve iş kurallarını i&amp;ccedil;erecek şekilde aşağıdaki gibi tasarlanabilir.&lt;/p&gt;
&lt;pre class="brush:csharp;auto-links:false;toolbar:false" contenteditable="false"&gt;namespace HexagonalAdventure.Domain;

public class Product
{
    public Guid Id { get; private set; }
    public string Title { get; private set; }
    public decimal ListPrice { get; private set; }
    public string Category { get; private set; } // Category ayrı bir entity olabilir, şimdilik string olarak bıraktım
    public int StockQuantity { get; private set; } // Sonrasında Value Object olarak refactor edilebilir

    public Product(Guid id, string title, decimal listPrice, string category, int initialStock)
    {
        Id = id;
        Title = string.IsNullOrWhiteSpace(title) ? throw new ArgumentException("Title cannot be empty") : title;
        ListPrice = listPrice &amp;gt; 0.0M ? listPrice : throw new ArgumentException("List price must be greater than 0.0");
        Category = string.IsNullOrWhiteSpace(category) ? throw new ArgumentException("Category cannot be empty") : category;
        StockQuantity = initialStock &amp;gt;= 0 ? initialStock : throw new ArgumentException("Initial stock cannot be negative");
    }

    public void IncreaseStock(int quantity)
    {
        if (quantity &amp;lt;= 0) throw new ArgumentException("Quantity to increase must be greater than 0");

        StockQuantity += quantity;
    }

    public void DecreaseStock(int quantity)
    {
        if (quantity &amp;lt;= 0) throw new ArgumentException("Quantity to decrease must be greater than 0");
        if (StockQuantity - quantity &amp;lt; 0) throw new InvalidOperationException("Insufficient stock to decrease by the specified quantity");

        StockQuantity -= quantity;
    }
}&lt;/pre&gt;
&lt;p&gt;Şimdilik bir&amp;ccedil;ok detayı atladık. Sadece &amp;uuml;r&amp;uuml;n stok bilgisinin temel iş kurallarını ele alacağımız bir senaryo ile ilerleyeceğiz.&lt;/p&gt;
&lt;h3&gt;3. Portların Tanımlanması&lt;/h3&gt;
&lt;p&gt;&amp;Ccedil;ok doğal olarak ve b&amp;uuml;y&amp;uuml;k bir ihtimalle &amp;uuml;r&amp;uuml;nler veritabanında tutulacaktır. &lt;strong&gt;Core&lt;/strong&gt;'da yer alan domain katmanının veritabanı teknolojilerinden bihaber olması gerekir. İletişimi sadece bir s&amp;ouml;zleşme &amp;uuml;zerinden yapmalıdır, yani bir Interface&lt;em&gt;(veya mimarideki adıyla port)&lt;/em&gt; Bu amaca hizmet eden enstr&amp;uuml;man &lt;strong&gt;Outbound Port&lt;/strong&gt; olarak isimlendiriliyor. &lt;strong&gt;Solution&lt;/strong&gt; yapımızı d&amp;uuml;ş&amp;uuml;necek olursak bizim i&amp;ccedil;in gerekli s&amp;ouml;zleşme tipini &lt;strong&gt;HexagonalAdventure.Application&lt;/strong&gt; projesinde &lt;strong&gt;Ports/Outbond&lt;/strong&gt; klas&amp;ouml;r&amp;uuml;nde aşağıdaki gibi tanımlayabiliriz.&lt;/p&gt;
&lt;pre class="brush:csharp;auto-links:false;toolbar:false" contenteditable="false"&gt;using HexagonalAdventure.Domain;

namespace HexagonalAdventure.Application.Ports.Outbound;

public interface IProductRepository
{
    void AddProduct(Product product);
    Product GetById(Guid id);
}&lt;/pre&gt;
&lt;p&gt;Senaryomuz gereği sadece iki fonksiyonellik tanımladık. Birisi &amp;uuml;r&amp;uuml;n eklemek, diğeri ise &amp;uuml;r&amp;uuml;n&amp;uuml; Id bilgisine g&amp;ouml;re &amp;ccedil;ekmek i&amp;ccedil;in. Burada bir &lt;strong&gt;interface&lt;/strong&gt; tanımı s&amp;ouml;z konusu ve dikkat edileceği &amp;uuml;zere ne t&amp;uuml;r bir k&amp;uuml;t&amp;uuml;phane ile, hangi veritabanına nasıl erişileceğine dair hi&amp;ccedil;bir detay da yer almıyor. &lt;strong&gt;Domain&lt;/strong&gt; katmanı bu s&amp;ouml;zleşmeyi aslında aşağıdaki gibi kullanıyor;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;L&amp;uuml;tfen bana şu Id'ye sahip &amp;uuml;r&amp;uuml;n&amp;uuml; getir.&lt;/li&gt;
&lt;li&gt;L&amp;uuml;tfen bilgilerini verdiğim &amp;uuml;r&amp;uuml;n&amp;uuml; ekle.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;4. Uygulama Servisi ve Use Case'in Tanımlanması&lt;/h3&gt;
&lt;p&gt;Merkez domain nesnesinde temel iş kurallarımız ve dışarıya a&amp;ccedil;ılan bir s&amp;ouml;zleşmemiz hazır. Şimdi bu iki enstr&amp;uuml;manı kullanarak asıl iş akışını y&amp;ouml;netecek olan uygulama servisini&lt;em&gt;(Application Service)&lt;/em&gt; yazmamız gerekiyor. Bu servis sınıfı dışarıdan gelen isteği alacak ve ilgili domain nesnesini oluşturup g&amp;uuml;ncelleyecek. Burada bir &lt;strong&gt;port&lt;/strong&gt;'da kullanması gerekecek. Tipik olarak bir orkestrasyon yapacak diyebiliriz. Bu servis sınıfını &lt;strong&gt;HexagonalAdventure.Application&lt;/strong&gt; projesindeki &lt;strong&gt;Services&lt;/strong&gt; klas&amp;ouml;r&amp;uuml;nde aşağıdaki gibi yazabiliriz.&lt;/p&gt;
&lt;pre class="brush:csharp;auto-links:false;toolbar:false" contenteditable="false"&gt;using HexagonalAdventure.Application.Ports.Outbound;
using HexagonalAdventure.Domain;

namespace HexagonalAdventure.Application.Services;

public class ProductService(IProductRepository productRepository)
{
    private readonly IProductRepository _productRepository = productRepository;

    public Guid CreateProduct(string title, decimal price, string category, int stock)
    {
        // Domain nesnesi oluşturulur ve orada tanımlı iş kuralları da y&amp;uuml;r&amp;uuml;t&amp;uuml;l&amp;uuml;r.
        var product = new Product(Guid.NewGuid(), title, price, category, stock);
        // Outbound port olarak tanımladığımız aray&amp;uuml;z &amp;uuml;zerinden &amp;uuml;r&amp;uuml;n ekleme işlevi &amp;ccedil;ağırılır
        _productRepository.AddProduct(product);
        return product.Id;
    }
}&lt;/pre&gt;
&lt;p&gt;B&amp;ouml;ylece uygulamanın dışarıya veri g&amp;ouml;nderen kısmını da yazmış olduk. Şimdilik stok artırma ve azaltma işlemlerini eklemedik. &amp;Ouml;nce genel hatları ile inşa etmeye &amp;ccedil;alışalım. Daha yapılacak &amp;ccedil;ok iş var.&lt;/p&gt;
&lt;h3&gt;5. Inbound Adapt&amp;ouml;r&amp;uuml;n Yazılması ve Entegrasyonu&lt;/h3&gt;
&lt;p&gt;Az &amp;ouml;nce bir uygulama servisi yazdık. Dış sistemler tarafından nasıl kullanılacağına bir bakalım. Bunun i&amp;ccedil;in &lt;strong&gt;Web Api&lt;/strong&gt; projesini kobay olarak ele alacağız. Dikkat etmemiz gereken şey API projesindeki &lt;strong&gt;Controller&lt;/strong&gt; nesnesinin&lt;em&gt;(ki adapt&amp;ouml;r g&amp;ouml;revini &amp;uuml;stlenecek)&lt;/em&gt; &lt;strong&gt;ProductService&lt;/strong&gt;'e doğrudan bağımlı &lt;strong&gt;olMAmasını&lt;/strong&gt; sağlamak. Veritabanı tarafında nasıl bir &lt;strong&gt;outbound port&lt;/strong&gt; tanımladıysak burada da dış d&amp;uuml;nyanın &amp;ccedil;ekirdek ile konuşması i&amp;ccedil;in bu sefer ters y&amp;ouml;nl&amp;uuml; bir &lt;strong&gt;inbound port&lt;/strong&gt; enstr&amp;uuml;manı hazırlayacağız. Tabii eksik olan birka&amp;ccedil; şey daha var. &amp;Ouml;rneğin somut repository sınıfını yazmalıyız ve pek tabii program sınıfında gerekli &lt;strong&gt;dependency injection&lt;/strong&gt; tanımlamalarını da yapmalıyız. Ancak &amp;ouml;ncelikle &lt;strong&gt;inbound port&lt;/strong&gt; tanımını yaparak işe başlayalım.&lt;/p&gt;
&lt;p&gt;Bu y&amp;uuml;zden &amp;ouml;ncelikle &lt;strong&gt;HexagonalAdventure.Application&lt;/strong&gt; projesindeki &lt;strong&gt;Ports/Inbound&lt;/strong&gt; klas&amp;ouml;r&amp;uuml;ne aşağıdaki kod i&amp;ccedil;eriğine sahip s&amp;ouml;zleşme tipini eklememiz gerekiyor.&lt;/p&gt;
&lt;pre class="brush:csharp;auto-links:false;toolbar:false" contenteditable="false"&gt;namespace HexagonalAdventure.Application.Ports.Inbound;

public interface IProductService
{
    Guid CreateProduct(string title, decimal price, string category, int stock);
}&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Controller&lt;/strong&gt;'ın, &lt;strong&gt;ProductService&lt;/strong&gt;'e doğrudan bağımlı &lt;strong&gt;olMamasını&lt;/strong&gt; bu s&amp;ouml;zleşmeyi &lt;strong&gt;ProductService&lt;/strong&gt; sınıfına implemente ederek sağlayabiliriz. Dolayısıyla bir &amp;ouml;nceki adımda tanımladığımız &lt;strong&gt;ProductService&lt;/strong&gt; sınıfını aşağıdaki gibi g&amp;uuml;ncelleyerek ilereyelim.&lt;/p&gt;
&lt;pre class="brush:csharp;auto-links:false;toolbar:false" contenteditable="false"&gt;public class ProductService(IProductRepository productRepository)
    : IProductService
{
    // DİĞER KODLAR
}&lt;/pre&gt;
&lt;p&gt;Şimdi de asıl adapt&amp;ouml;r g&amp;ouml;revini &amp;uuml;stlenen &lt;strong&gt;controller&lt;/strong&gt; sınıfını ekleyelim. Bu sınıfı da aşağıdaki gibi geliştirebiliriz.&lt;/p&gt;
&lt;pre class="brush:csharp;auto-links:false;toolbar:false" contenteditable="false"&gt;using HexagonalAdventure.Application.Ports.Inbound;
using Microsoft.AspNetCore.Mvc;

namespace HexagonalAdventure.Adapters.In.WebApi.Controllers;

[ApiController]
[Route("api/[controller]")]
public class ProductsController(IProductService productService)
    : Controller
{
    private readonly IProductService _productService = productService;

    [HttpPost]
    public IActionResult Create([FromBody] CreateProductRequest request)
    {
        var productId = _productService.CreateProduct(request.Title, request.Price, request.Category, request.Stock);
        return Ok(new { Id = productId });
    }
}

public record CreateProductRequest(string Title, decimal Price, string Category, int Stock);&lt;/pre&gt;
&lt;p&gt;Burada dikkat etmemiz gereken nokta adapt&amp;ouml;r g&amp;ouml;revini &amp;uuml;stlenen controller sınıfının &lt;strong&gt;ProductService&lt;/strong&gt;'i bir aray&amp;uuml;z &amp;uuml;zerinden kullanmasıdır. B&amp;ouml;ylece &lt;strong&gt;controller&lt;/strong&gt; sınıfı &lt;strong&gt;ProductService&lt;/strong&gt;'in somut implementasyonundan bağımsız hale gelmiş olur. Tabii bir şeye daha ihtiyacımız olacak. O da somut repository sınıfı. İlk senaryoda verileri bellekte bir &lt;strong&gt;dictionary&lt;/strong&gt; koleksiyonu olarak tutabiliriz. Bu ama&amp;ccedil;la &lt;strong&gt;HexagonalAdventure.Adapters.Out.InMemory&lt;/strong&gt; isimli sınıf k&amp;uuml;t&amp;uuml;phanesini kullanabiliriz. Burada &lt;strong&gt;outbound adapter&lt;/strong&gt; g&amp;ouml;revini &amp;uuml;stlenecek olan &lt;strong&gt;InMemoryProductRepository&lt;/strong&gt; isimli bir sınıf pekala işimiz g&amp;ouml;r&amp;uuml;r.&lt;/p&gt;
&lt;pre class="brush:csharp;auto-links:false;toolbar:false" contenteditable="false"&gt;using HexagonalAdventure.Application.Ports.Outbound;
using HexagonalAdventure.Domain;

namespace HexagonalAdventure.Adapters.Out.InMemory;

public class InMemoryProdutRepository
    : IProductRepository
{
    private readonly Dictionary&amp;lt;Guid, Product&amp;gt; _products = [];
    public void AddProduct(Product product)
    {
        _products.Add(product.Id, product);
    }

    public Product GetById(Guid id)
    {
        _products.TryGetValue(id, out var product);
        return product;
    }
}&lt;/pre&gt;
&lt;p&gt;Artık Web Api tarafındaki son aşamayı tamamlayabiliriz. Program.cs sınıfını aşağıdaki gibi kodlayarak ilerleyelim.&lt;/p&gt;
&lt;pre class="brush:csharp;auto-links:false;toolbar:false" contenteditable="false"&gt;using HexagonalAdventure.Adapters.Out.InMemory;
using HexagonalAdventure.Application.Ports.Inbound;
using HexagonalAdventure.Application.Ports.Outbound;
using HexagonalAdventure.Application.Services;

var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllers();

// Dependency Injection tanımlamaları
builder.Services.AddSingleton&amp;lt;IProductRepository, InMemoryProdutRepository&amp;gt;(); // T&amp;uuml;m uygulama boyunca tek bir instance kullanılır
builder.Services.AddScoped&amp;lt;IProductService, ProductService&amp;gt;();

builder.Services.AddOpenApi();

var app = builder.Build();

if (app.Environment.IsDevelopment())
{
    app.MapOpenApi();
}

app.UseHttpsRedirection();
app.UseAuthorization();
app.MapControllers();

await app.RunAsync();&lt;/pre&gt;
&lt;p&gt;Şu haliyle Web api projesini ayağa kaldırıp aşağıdaki &amp;ouml;rnek http talebi ile deneyebiliriz.&lt;/p&gt;
&lt;pre class="brush:plain;auto-links:false;toolbar:false" contenteditable="false"&gt;@HexagonalAdventure.Adapters.In.WebApi_HostAddress = http://localhost:5144

POST {{HexagonalAdventure.Adapters.In.WebApi_HostAddress}}/api/products
Content-Type: application/json
Accept: application/json

{  
  "title": "Learning OCAML",
  "category": "Book",
  "price": 19.99,
  "stock": 10
}&lt;/pre&gt;
&lt;p&gt;En azından aşağıdaki ekran g&amp;ouml;r&amp;uuml;nt&amp;uuml;s&amp;uuml;nde olduğu gibi bir yanıt almamız gerekiyor.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://www.buraksenyurt.com/image.axd?picture=/2026/Mart/HttpTest_00.png" alt="" /&gt;&lt;/p&gt;
&lt;h2&gt;Yeni Deneyimler&lt;/h2&gt;
&lt;p&gt;Kaba taslak mimariyi uyguladık gibi g&amp;ouml;r&amp;uuml;n&amp;uuml;yor. Şimdi farklı senaryolar ile devam edelim.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&amp;Ouml;rneğin veritabanı tarafında &lt;strong&gt;Postgresql&lt;/strong&gt; kullanan bir &lt;strong&gt;Outbound Adapter&lt;/strong&gt; eklemeye &amp;ccedil;alışalım. &lt;strong&gt;Entity Framework&lt;/strong&gt; olur ya da &lt;strong&gt;Dapper&lt;/strong&gt; olur. &lt;em&gt;(Yeni adapt&amp;ouml;r ekleme senaryosu)&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;Farklı bir dış sistemi dahil edelim. S&amp;ouml;z gelimi bir &lt;strong&gt;Console&lt;/strong&gt; uygulaması.&lt;em&gt; (Console uygulaması Web Api'yi kullanmayacak elbette)&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;Biraz da test ekleyelim ve test edilebilirliği g&amp;ouml;rmeye &amp;ccedil;alışalım. &lt;em&gt;(Test ekleme senaryosu)&lt;/em&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;6. Entity Framework Tabanlı Yeni Adapter Eklenmesi&lt;/h3&gt;
&lt;p&gt;Adettendir her repomda olduğu gibi veritabanı s&amp;ouml;z konusu ise genellikle bir &lt;strong&gt;docker-compose&lt;/strong&gt; dosyasında &lt;strong&gt;postgresql&lt;/strong&gt; ve &lt;strong&gt;pg-admin&lt;/strong&gt; servislerini konuşlandırarak işe başlarım. Kendi sistemimdeki &lt;strong&gt;docker-compose&lt;/strong&gt; i&amp;ccedil;eriği aşağıdaki gibi.&lt;/p&gt;
&lt;pre class="brush:plain;auto-links:false;toolbar:false" contenteditable="false"&gt;services:

  postgres:
    image: postgres:latest
    container_name: hex-postgres
    environment:
      POSTGRES_USER: johndoe
      POSTGRES_PASSWORD: somew0rds
      POSTGRES_DB: postgres
    ports:
      - "5432:5432"
    volumes:
      - postgres_data:/var/lib/postgres/data
    networks:
      - hex-network

  pgadmin:
    image: dpage/pgadmin4:latest
    container_name: hex-pgadmin
    environment:
      PGADMIN_DEFAULT_EMAIL: scoth@tiger.com
      PGADMIN_DEFAULT_PASSWORD: 123456
    ports:
      - "5050:80"
    depends_on:
      - postgres
    networks:
      - hex-network

volumes:
  postgres_data:

networks:
  hex-network:
    driver: bridge&lt;/pre&gt;
&lt;p&gt;Container'ları ayağa kaldırmak i&amp;ccedil;in;&lt;/p&gt;
&lt;pre class="brush:bash;auto-links:false;toolbar:false" contenteditable="false"&gt;docker-compose up -d&lt;/pre&gt;
&lt;p&gt;Şimdi de &lt;strong&gt;HexagonalAdventure.Adapters.Out.EF&lt;/strong&gt; isimli yeni bir &lt;strong&gt;class library&lt;/strong&gt; oluşturarak devam edebiliriz. Bu projeyi de &lt;strong&gt;Adapters&lt;/strong&gt; isimli solution folder altında oluşturursak, iskelete baktığımızda g&amp;ouml;rsel olarak daha anlaşılır olacaktır.&amp;nbsp;&lt;strong&gt;Entity Framework&lt;/strong&gt; kullanacağımız i&amp;ccedil;in bir &lt;strong&gt;DbContext&lt;/strong&gt; t&amp;uuml;revine de ihtiyacımız olacak. Tabii gerekli &lt;strong&gt;nuget&lt;/strong&gt; paketlerini de eklemeyi unutmayalım. &lt;strong&gt;Microsoft.EntityFrameworkCore&lt;/strong&gt; ve &lt;strong&gt;Microsoft.EntityFrameworkCore.Design&lt;/strong&gt;. Şimdi isminden şu an i&amp;ccedil;in ş&amp;uuml;phe ettiğim &lt;strong&gt;DeppoDbContext&lt;/strong&gt; sınıfını yazarak devam edelim.&lt;/p&gt;
&lt;pre class="brush:csharp;auto-links:false;toolbar:false" contenteditable="false"&gt;using HexagonalAdventure.Domain;
using Microsoft.EntityFrameworkCore;

namespace HexagonalAdventure.Adapters.Out.EF;

public class DeppoDbContext(DbContextOptions&amp;lt;DeppoDbContext&amp;gt; options)
    : DbContext(options)
{
    public DbSet&amp;lt;Product&amp;gt; Products { get; set; }
}&lt;/pre&gt;
&lt;p&gt;Olduk&amp;ccedil;a klasik bir &lt;strong&gt;DbContext&lt;/strong&gt; sınıfı yazdık. İ&amp;ccedil;inde sadece &amp;uuml;r&amp;uuml;nler i&amp;ccedil;in bir &lt;strong&gt;DbSet&lt;/strong&gt; &amp;ouml;zelliği yer alıyor. Şimdi de &lt;strong&gt;IProductRepository&lt;/strong&gt; aray&amp;uuml;z&amp;uuml;n&amp;uuml; implemente eden &lt;strong&gt;EfProductRepository&lt;/strong&gt; sınıfını yazalım. Hatırlayacağınız &amp;uuml;zere &lt;strong&gt;IProductRepository&lt;/strong&gt;, uygulama katmanında bir &lt;strong&gt;outbound&lt;/strong&gt; &lt;strong&gt;port&lt;/strong&gt; olarak tanımlanmıştı. Şimdi bu portu somut olarak uygulayan bir adapter yazacağız. &amp;Ouml;rneğin ş&amp;ouml;yle bir sınıf olabilir.&lt;/p&gt;
&lt;pre class="brush:csharp;auto-links:false;toolbar:false" contenteditable="false"&gt;using HexagonalAdventure.Application.Ports.Outbound;
using HexagonalAdventure.Domain;

namespace HexagonalAdventure.Adapters.Out.EF;

public class EfProductRepository(DeppoDbContext deppoDbContext)
    : IProductRepository
{
    public void AddProduct(Product product)
    {
        deppoDbContext.Products.Add(product);
        deppoDbContext.SaveChanges();
    }

    public Product GetById(Guid id)
    {
        return deppoDbContext.Products.FirstOrDefault(p =&amp;gt; p.Id == id);
    }
}&lt;/pre&gt;
&lt;p&gt;Artık Web api a&amp;ccedil;ısından olaya bakabiliriz. Hatırlarsanız program.cs sınıfında &lt;strong&gt;IProductRepository&lt;/strong&gt;'nin hangi somut sınıf tarafından implemente edileceğini tanımlamamız gerekiyordu ve ilk &amp;ouml;rneğimizde &lt;strong&gt;in-memory&lt;/strong&gt; &amp;ccedil;alışan bir repository sınıfını kullanmıştık. Web api'nin yeni adapt&amp;ouml;r ile &amp;ccedil;alışması i&amp;ccedil;in tek yapmamız gereken &lt;strong&gt;Dependency&lt;/strong&gt; &lt;strong&gt;Injection&lt;/strong&gt; &lt;strong&gt;Container&lt;/strong&gt;'daki bağımlılık tanımını değiştirmekten ibaret. Aynen aşağıda g&amp;ouml;r&amp;uuml;ld&amp;uuml;ğ&amp;uuml; gibi&lt;em&gt;.&lt;/em&gt;&lt;/p&gt;
&lt;pre class="brush:csharp;auto-links:false;toolbar:false" contenteditable="false"&gt;using HexagonalAdventure.Adapters.Out.EF;
// using HexagonalAdventure.Adapters.Out.InMemory;
using HexagonalAdventure.Application.Ports.Inbound;
using HexagonalAdventure.Application.Ports.Outbound;
using HexagonalAdventure.Application.Services;
using Microsoft.EntityFrameworkCore;

var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllers();

// EF Postgresql kullanımı i&amp;ccedil;in de middleware'e bir şeyler eklememiz lazım
builder.Services.AddDbContext&amp;lt;DeppoDbContext&amp;gt;(options =&amp;gt;
    options.UseNpgsql(builder.Configuration.GetConnectionString("DeppoConnectionString")));

// Dependency Injection tanımlamaları
// builder.Services.AddSingleton&amp;lt;IProductRepository, InMemoryProdutRepository&amp;gt;(); // T&amp;uuml;m uygulama boyunca tek bir instance kullanılır

// EF Core kullanan yeni outbound port implementasyonu
builder.Services.AddScoped&amp;lt;IProductRepository, EfProductRepository&amp;gt;();
builder.Services.AddScoped&amp;lt;IProductService, ProductService&amp;gt;();

builder.Services.AddOpenApi();

var app = builder.Build();

if (app.Environment.IsDevelopment())
{
    app.MapOpenApi();
}

app.UseHttpsRedirection();
app.UseAuthorization();
app.MapControllers();

await app.RunAsync();&lt;/pre&gt;
&lt;p&gt;Tabii burada bazı konfig&amp;uuml;rasyonları da yapmamız gerekiyor. &amp;Ouml;rneğin &lt;strong&gt;appsettings.json&lt;/strong&gt; dosyasına &lt;strong&gt;Postgresql&lt;/strong&gt; bağlantı bilgisini de eklememiz gerekiyor. Aşağıdaki gibi bir i&amp;ccedil;erik olabilir.&lt;/p&gt;
&lt;pre class="brush:java;auto-links:false;toolbar:false" contenteditable="false"&gt;{
  "ConnectionStrings": {
    "DeppoConnectionString": "Host=localhost;Port=5432;Database=deppo;Username=johndoe;Password=somew0rds"
  }
}&lt;/pre&gt;
&lt;p&gt;Ayrıca yine gelenek olduğu &amp;uuml;zere bir &lt;strong&gt;migration&lt;/strong&gt; planı hazırlayıp &amp;ccedil;alıştıralım. Bunun i&amp;ccedil;in &lt;strong&gt;dotnet&lt;/strong&gt; komut satırı aracını aşağıdaki gibi kullanabiliriz.&lt;/p&gt;
&lt;pre class="brush:bash;auto-links:false;toolbar:false" contenteditable="false"&gt;# Sistemde ef tool'unun y&amp;uuml;kl&amp;uuml; olması gerekiyor. Eğer y&amp;uuml;kl&amp;uuml; değilse aşağıdaki komutla y&amp;uuml;kleyebiliriz.
dotnet tool install --global dotnet-ef

# Varsa da g&amp;uuml;ncellemek gerekebilir. O zaman da şu komut işe yarar
dotnet tool update --global dotnet-ef

# Migration planının hazırlanması
dotnet ef migrations add InitialCreate --project HexagonalAdventure.Adapters.Out.EF --startup-project HexagonalAdventure.Adapters.In.WebApi

# Migration planının işletilmesi
dotnet ef database update --project HexagonalAdventure.Adapters.Out.EF --startup-project HexagonalAdventure.Adapters.In.WebApi&lt;/pre&gt;
&lt;p&gt;Eğer her şey yolunda gittiyse aşağıdaki ekran g&amp;ouml;r&amp;uuml;nt&amp;uuml;s&amp;uuml;nde olduğu bu sefer &amp;uuml;r&amp;uuml;n bilgisinin veritabanına kaydedildiğini g&amp;ouml;rebiliriz.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://www.buraksenyurt.com/image.axd?picture=/2026/Mart/HttpTest_01.png" alt="" /&gt;&lt;/p&gt;
&lt;p&gt;Dikkat edileceği &amp;uuml;zere sisteme yeni bir adapter ekledik fakat uygulama domain'ine hi&amp;ccedil; dokunmadık. Dış sistem entegrasyonunda sadece yeni eklediğimiz adapter'ı var olan port'a bağladık. B&amp;ouml;ylece uygulama domain'inin dış d&amp;uuml;nyaya olan bağımlılığını tamamen ortadan kaldırmış olduk.&lt;/p&gt;
&lt;h3&gt;7. Farklı Bir Dış Sistem Entegrasyonu: Console Uygulaması&lt;/h3&gt;
&lt;p&gt;Senaryomuzu şimdi biraz daha genişletelim ve farklı bir dış sistem entegrasyonu yapalım. Web Api'ye ek olarak bir de &lt;strong&gt;Console&lt;/strong&gt; uygulaması geliştirelim. Console uygulaması Web Api'yi kullanmayacak elbette. Doğrudan uygulama servislerini kullanarak &amp;ccedil;alışacak. B&amp;ouml;ylece farklı bir adapt&amp;ouml;r&amp;uuml;n var olan port'a nasıl bağlandığını g&amp;ouml;receğiz. Bir başka deyişle console uygulaması farklı bir &lt;strong&gt;Inbound Adapter&lt;/strong&gt; olacak. Console uygulamasında da dikkate almamız gereken şeyler var. &amp;Ouml;rneğin burada da bir&lt;strong&gt; Composition Root&lt;/strong&gt; kullanmamız mimariye uygunluk a&amp;ccedil;ısından &amp;ouml;nemli. Yani bir &lt;strong&gt;Depdendency Injection Container&lt;/strong&gt; hazırlayıp &lt;strong&gt;port&lt;/strong&gt; ve &lt;strong&gt;adapt&amp;ouml;rleri&lt;/strong&gt; birbirine bağlayacağız.&lt;/p&gt;
&lt;p&gt;Solution'daki &lt;strong&gt;Adapters&lt;/strong&gt; klas&amp;ouml;r&amp;uuml;ne &lt;strong&gt;HexagonalAdventure.Adapters.In.ConsoleHexagonalAdventure.Adapters.In.Console&lt;/strong&gt; isimli yeni bir &lt;strong&gt;Console&lt;/strong&gt; projesi oluşturup gerekli k&amp;uuml;t&amp;uuml;phaneleri ekledikten sonra aşağıdaki program kodları ile devam edelim.&lt;/p&gt;
&lt;pre class="brush:csharp;auto-links:false;toolbar:false" contenteditable="false"&gt;using HexagonalAdventure.Adapters.Out.InMemory;
using HexagonalAdventure.Application.Ports.Inbound;
using HexagonalAdventure.Application.Ports.Outbound;
using HexagonalAdventure.Application.Services;
using Microsoft.Extensions.DependencyInjection;

var serviceProvider = new ServiceCollection()
    .AddSingleton&amp;lt;IProductRepository, InMemoryProdutRepository&amp;gt;()
    .AddScoped&amp;lt;IProductService, ProductService&amp;gt;()
    .BuildServiceProvider();

Console.WriteLine("Add a new product");

Console.Write("Title: ");
string title = Console.ReadLine();

Console.Write("Price: ");
decimal price = decimal.Parse(Console.ReadLine());

Console.Write("Category: ");
string category = Console.ReadLine();

Console.Write("Stock: ");
int stock = int.Parse(Console.ReadLine());

var productService = serviceProvider.GetRequiredService&amp;lt;IProductService&amp;gt;();
var newProductId = productService.CreateProduct(title, price, category, stock);

Console.WriteLine("Product created with ID: " + newProductId);
Console.ReadLine();&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Console&lt;/strong&gt; projesi sadece &lt;strong&gt;Application&lt;/strong&gt; ve &lt;strong&gt;InMemory&lt;/strong&gt; &lt;strong&gt;Adapter&lt;/strong&gt; projelerini referans eder. API projesini veya doğrudan &lt;strong&gt;Domain&lt;/strong&gt; projesini kullanmaz. &amp;Ouml;rneğin m&amp;uuml;mk&amp;uuml;n mertebe basit olması a&amp;ccedil;ısından console projesi&lt;strong&gt; in-memory&lt;/strong&gt; veritabanı kullanacak şekilde tasarlanmıştır. Diğer yandan web api bağımlılığı olmadığı i&amp;ccedil;in bir network servis bağımlılığı da yoktur. Doğrudan uygulama katmanındaki port'ları ve gerekli dış adapt&amp;ouml;r&amp;uuml; bir kompozisyon altında birleştirerek kullanır. Senaryonun bir &amp;ouml;zeti de şudur; &lt;strong&gt;Uygulamanın domain katmanına dokunmadan hem verinin yazıldığı yeri hemde geldiği yeri değiştirebildik.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Tam şu anda solution i&amp;ccedil;eriğine bakarsak aşağıdaki gibi bir iskelet oluştuğunu g&amp;ouml;zlemleyebiliriz.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://www.buraksenyurt.com/image.axd?picture=/2026/Mart/ConsoleRuntime.png" alt="" /&gt;&lt;/p&gt;
&lt;h2&gt;Testler&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Onion Architecture&lt;/strong&gt;, &lt;strong&gt;Clean Architecture&lt;/strong&gt; gibi diğer mimari yaklaşımlarda olduğu gibi hexagonal mimaride de test edilebilirlik &amp;ouml;nemli bir avantaj olarak &amp;ouml;ne &amp;ccedil;ıkar. Uygulama domain'i dış d&amp;uuml;nyadan tamamen izole edildiği i&amp;ccedil;in bu katmandaki kodun test edilmesi son derece kolaydır. Diğer yandan port ve adapt&amp;ouml;rler &amp;uuml;zerinden yapılan entegrasyonları da kolayca test edebiliriz. Bu noktada da devreye genellikle &lt;strong&gt;mock&lt;/strong&gt; nesneler girer. Bu sayede ger&amp;ccedil;ek veritabanı veya diğer dış sistemlere ihtiya&amp;ccedil; duymadan uygulama iş kurallarını doğrulayabiliriz.&lt;/p&gt;
&lt;p&gt;Yine basit adımlarla ilerleyelim. İlk olarak &lt;strong&gt;Domain&lt;/strong&gt; katmanı i&amp;ccedil;in birka&amp;ccedil; birim test&lt;em&gt;(unit test)&lt;/em&gt; yazalım. Şu anda kobay olarak kullandığımız &lt;strong&gt;Product&lt;/strong&gt; sınıfının &amp;ccedil;ok fonksiyonelliği olmasa da temel iş kurallarını i&amp;ccedil;eren bir sınıf olduğu i&amp;ccedil;in yine de testlerini yazmak gerekir.&lt;/p&gt;
&lt;p&gt;Test yazmayı sadece kodun doğruluğunu kontrol etmek i&amp;ccedil;in değil, aynı zamanda kodun nasıl kullanılacağını g&amp;ouml;stermek ve kodun kendisiyle ilgili bazı &amp;ouml;nemli bilgileri belgelemek i&amp;ccedil;in de kullanabiliriz. Bu y&amp;uuml;zden testler sadece doğruluk kontrol&amp;uuml; değil, aynı zamanda bir t&amp;uuml;r dok&amp;uuml;mantasyon g&amp;ouml;revi de g&amp;ouml;r&amp;uuml;rler. Ayrıca kodun kalitesini artırmak ve gelecekteki değişikliklere karşı korumak i&amp;ccedil;in de &amp;ouml;nemli bir ara&amp;ccedil;tırlar. Bir&amp;ccedil;ok statik kod analiz aracı &amp;ouml;zellikle &lt;strong&gt;Code&lt;/strong&gt; &lt;strong&gt;Coverage&lt;/strong&gt; oranını baz alarak bir skor hesaplaması yapar. &lt;strong&gt;Code Coverage&lt;/strong&gt; oranı, yazdığımız testlerin kodun ne kadarını kapsadığını g&amp;ouml;steren bir metriktir. Y&amp;uuml;ksek bir&lt;strong&gt; Code Coverage&lt;/strong&gt; oranı genellikle daha iyi test kapsamına işaret eder fakat bu sizi yanıltmasın kodun kalitesi i&amp;ccedil;in tek başına yeterli &amp;ouml;l&amp;ccedil;&amp;uuml; değildir. Testlerin kalitesi ve doğruluğu da &amp;ouml;nemlidir. Bu y&amp;uuml;zden sadece y&amp;uuml;ksek bir &lt;strong&gt;Code Coverage&lt;/strong&gt; oranına odaklanmak yerine, testlerin ger&amp;ccedil;ekten kodun doğru &amp;ccedil;alıştığını ve beklenen sonu&amp;ccedil;ları verdiğini doğrulamak &amp;ouml;nemlidir.&lt;/p&gt;
&lt;h3&gt;8. Domain Katmanı i&amp;ccedil;in Birim Testler&lt;/h3&gt;
&lt;p&gt;Bu kadar laf kalabalığını bir kenara bırakalım ve dilerseniz ilk birim testlerimizi yazalım. &lt;strong&gt;HexagonalAdventure.Domain.UnitTests&lt;/strong&gt; isimli yeni bir test projesi&lt;em&gt;(xUnit şablonundan)&lt;/em&gt; oluşturarak işe başlayabiliriz. İ&amp;ccedil;erisine &lt;strong&gt;ProductTests&lt;/strong&gt; isimli bir test sınıfı metotlarını aşağıdaki gibi d&amp;uuml;zenleyelim.&lt;/p&gt;
&lt;pre class="brush:csharp;auto-links:false;toolbar:false" contenteditable="false"&gt;namespace HexagonalAdventure.Domain.UnitTests;

public class ProductTests
{
    [Fact]
    public void DecreaseStock_When_StockIsEnough()
    {
        // Arrange (Hazırlık safhası)
        var product = new Product(Guid.NewGuid(), "Optical Mouse", 29.99m, "Electronics", 10);

        // Act (Eylem safhası)
        product.DecreaseStock(5);

        // Assert (Doğrulama safhası)
        var expectedStock = 5;
        Assert.Equal(expectedStock, product.StockQuantity);
    }

    [Fact]
    public void DecreaseStock_When_StockIsNotEnough_ShouldThrowException()
    {
        // Arrange
        var product = new Product(Guid.NewGuid(), "Mechanical Keyboard", 79.99m, "Electronics", 3);

        // Act &amp;amp; Assert
        Assert.Throws&amp;lt;InvalidOperationException&amp;gt;(() =&amp;gt; product.DecreaseStock(5));
    }

    [Fact]
    public void IncreaseStock_ShouldIncreaseStockQuantity()
    {
        // Arrange
        var product = new Product(Guid.NewGuid(), "Gaming Headset", 49.99m, "Electronics", 5);

        // Act
        product.IncreaseStock(10);

        // Assert
        var expectedStock = 15;
        Assert.Equal(expectedStock, product.StockQuantity);
    }

    [Fact]
    public void IncreaseStock_When_AmountIsNegative_ShouldThrowException()
    {
        // Arrange
        var product = new Product(Guid.NewGuid(), "USB-C Hub", 39.99m, "Electronics", 8);

        // Act &amp;amp; Assert
        Assert.Throws&amp;lt;ArgumentException&amp;gt;(() =&amp;gt; product.IncreaseStock(-5));
    }
}&lt;/pre&gt;
&lt;p&gt;Şu an i&amp;ccedil;in sadece stok miktarını artıran ve azaltan metotları test ettik. Bu test projesi &lt;strong&gt;Domain&lt;/strong&gt; katmanı dışındaki hi&amp;ccedil;bir projeyi referans etmez, hi&amp;ccedil;bir dış bağımlılık da i&amp;ccedil;ermez. Sadece bir &lt;strong&gt;Test Framework&lt;/strong&gt; kullanır. Yazdığımız testleri kod edit&amp;ouml;rleri &amp;uuml;zerinden test edebileceğimiz gibi komut satırından da koşturabiliriz.&lt;/p&gt;
&lt;pre class="brush:bash;auto-links:false;toolbar:false" contenteditable="false"&gt;# Solution i&amp;ccedil;indeki t&amp;uuml;m test projelerini &amp;ccedil;alıştırmak i&amp;ccedil;in
dotnet test

# Belli bir test projesini &amp;ccedil;alıştırmak i&amp;ccedil;inse
dotnet test HexagonalAdventure.Domain.Tests&lt;/pre&gt;
&lt;p&gt;Sonu&amp;ccedil; olarak yazdığımız testlerin başarılı olduğunu g&amp;ouml;rebiliriz.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://www.buraksenyurt.com/image.axd?picture=/2026/Mart/DomainTests.png" alt="" /&gt;&lt;/p&gt;
&lt;h3&gt;9. Uygulama Katmanı i&amp;ccedil;in Birim Testler (Mock Nesnelerle)&lt;/h3&gt;
&lt;p&gt;Şimdi de uygulama katmanını g&amp;ouml;z &amp;ouml;n&amp;uuml;ne alalım. &amp;Ouml;rneğin buradaki &lt;strong&gt;ProductService&lt;/strong&gt; sınıfı i&amp;ccedil;in birim testler ekleyelim. Tabii burada dikkat edilmesi gereken bir başla konu var. Bu sefer &lt;strong&gt;ProductService&lt;/strong&gt; sınıfının kullanmak i&amp;ccedil;in i&amp;ccedil;erisine enjekte edilen &lt;strong&gt;IProductRepository&lt;/strong&gt; s&amp;ouml;zleşmesine dayalı bir bağımlılık&lt;em&gt;(Dependency)&lt;/em&gt;&amp;nbsp;bulunuyor. Birim testlerde bu tip bağımlılıklarda somut implementasyonları kullanmak yerine genellikle mock nesneler tercih edilir. &lt;strong&gt;Mock&lt;/strong&gt; nesneler, ger&amp;ccedil;ek nesnelerin davranışlarını taklit eden sahte yapılar olarak d&amp;uuml;ş&amp;uuml;n&amp;uuml;lebilir. Bu sayede ger&amp;ccedil;ek veritabanı veya diğer dış sistemlere ihtiya&amp;ccedil; duymadan uygulama iş kurallarını doğrulayabiliriz.&lt;/p&gt;
&lt;p&gt;Bu ama&amp;ccedil;la Solution i&amp;ccedil;inde &lt;strong&gt;HexagonalAdventure.Application.UnitTests&lt;/strong&gt; isimli yeni bir test projesi oluşturarak devam edelim. Tabii bu projede &lt;a href="https://www.nuget.org/packages/moq/" target="_blank"&gt;Moq &lt;/a&gt;gibi bir mocking framework kullanarak bağımlılıkları taklit etmemiz gerekiyor. Gerekli nuget paketlerini ekledikten sonra &lt;strong&gt;ProductServiceTests&lt;/strong&gt; isimli test sınıfını aşağıdaki gibi yazarak ilerleyelim.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Pratik bilgi:&lt;/strong&gt; Mock nesneleri gibi dış bağımlılıkları taklit eden enstr&amp;uuml;manlarda testlerin ger&amp;ccedil;ekten bir veritabanına veya bir servise daha doğrusu ağ &amp;uuml;zerinde bir yerlere gitmediğinden emin olmak i&amp;ccedil;in kullanılabilecek ilkel yollardan birisi test projesindeki &lt;strong&gt;appsettings.json&lt;/strong&gt; dosyasına ge&amp;ccedil;ersiz bağlantı bilgileri eklemek olabilir. B&amp;ouml;ylece yanlışlıkla ger&amp;ccedil;ek bir veritabanına bağlanmaya &amp;ccedil;alıştığımızda testlerimiz başarısız olur ve bu durum bize bir şeylerin yanlış gittiğine dair bir sinyal verir ya da bağlantılar uzak sunucularda ise interneti testler sırasında kapatmak da benzer bir etki yaratır. Tabii bunlar ilgili testleri kendi makinemizde koşturmak istediğimiz durumlar i&amp;ccedil;in ge&amp;ccedil;erlidir. Nihayetinde ger&amp;ccedil;ekten de veritabanına gidiliyorsa bunu CI/CD s&amp;uuml;re&amp;ccedil;lerinde acı bir şekilde &amp;ouml;ğrenmek yerine local ortamda &amp;ouml;ğrenmek daha iyi olabilir.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre class="brush:csharp;auto-links:false;toolbar:false" contenteditable="false"&gt;namespace HexagonalAdventure.Application.UnitTests;

using Moq;
using HexagonalAdventure.Application.Ports.Outbound;
using HexagonalAdventure.Application.Services;
using HexagonalAdventure.Domain;

public class ProductServiceTests
{
    [Fact]
    public void CreateProduct_ShouldReturnValidGuid()
    {
        // Arange
        var mockRepo = new Mock&amp;lt;IProductRepository&amp;gt;();
        mockRepo.Setup(r =&amp;gt; r.AddProduct(It.IsAny&amp;lt;Domain.Product&amp;gt;()));

        // Act
        var service = new ProductService(mockRepo.Object);
        var actualGuid = service.CreateProduct("AyBiEm Laptop i7", 1500m, "Electronics", 10);

        // Assert
        Assert.NotEqual(Guid.Empty, actualGuid);

        // Verify (Ger&amp;ccedil;ekten de dış bağımlılıktaki AddProduct metodunun &amp;ccedil;ağrıldığını doğrulamak i&amp;ccedil;in)
        mockRepo.Verify(r =&amp;gt; r.AddProduct(It.IsAny&amp;lt;Domain.Product&amp;gt;()), Times.Once);
    }

    [Fact]
    public void CreateProduct_ShouldPassCorrectDataToRepository()
    {
        // Arange
        var mockRepo = new Mock&amp;lt;IProductRepository&amp;gt;();
        Product capturedProduct = null;
        mockRepo.Setup(r =&amp;gt; r.AddProduct(It.IsAny&amp;lt;Product&amp;gt;()))
                .Callback&amp;lt;Product&amp;gt;(p =&amp;gt; capturedProduct = p);

        // Act
        var service = new ProductService(mockRepo.Object);
        var actualGuid = service.CreateProduct("AyBiEm Laptop i7", 1500m, "Electronics", 10);

        // Assert
        Assert.NotNull(capturedProduct);
        Assert.Equal("AyBiEm Laptop i7", capturedProduct.Title);
        Assert.Equal(1500m, capturedProduct.ListPrice);
        Assert.Equal("Electronics", capturedProduct.Category);
        Assert.Equal(10, capturedProduct.StockQuantity);
    }

    [Fact]
    public void CreateProduct_WhenTitleIsEmpty_ShouldThrowException()
    {
        // Arange
        var mockRepo = new Mock&amp;lt;IProductRepository&amp;gt;();

        // Act
        var service = new ProductService(mockRepo.Object);

        // Assert
        Assert.Throws&amp;lt;ArgumentException&amp;gt;(() =&amp;gt; service.CreateProduct("", 1500m, "Electronics", 10));
    }

    [Fact]
    public void CreateProduct_WithNegativeStockQuantity_ShouldThrowException()
    {
        //Arange
        var mockRepo = new Mock&amp;lt;IProductRepository&amp;gt;();
        var service = new ProductService(mockRepo.Object);

        //Act &amp;amp; Assert
        var exception = Assert.Throws&amp;lt;ArgumentException&amp;gt;(() =&amp;gt; service.CreateProduct("AyBiEm Laptop i7", 1500m, "Electronics", -5));

        //Verify
        mockRepo.Verify(r =&amp;gt; r.AddProduct(It.IsAny&amp;lt;Product&amp;gt;()), Times.Never);
    }
}&lt;/pre&gt;
&lt;p&gt;Şimdi buradaki test metodları hakkında biraz konuşalım. &lt;strong&gt;CreateProduct_ShouldReturnValidGuid&lt;/strong&gt; testinde &lt;strong&gt;CreateProduct&lt;/strong&gt; fonksiyonunun ge&amp;ccedil;erli bir &lt;strong&gt;Guid&lt;/strong&gt; d&amp;ouml;nd&amp;uuml;r&amp;uuml;p d&amp;ouml;nd&amp;uuml;rmediğini doğruluyoruz. &lt;strong&gt;CreateProduct_ShouldPassCorrectDataToRepository&lt;/strong&gt; test metodunda ise &lt;strong&gt;CreateProduct&lt;/strong&gt; fonksiyonunun &lt;strong&gt;IProductRepository&lt;/strong&gt;'nin &lt;strong&gt;AddProduct&lt;/strong&gt; metodunu doğru verilerle &amp;ccedil;ağırıp &amp;ccedil;ağırmadığını. Zira &lt;strong&gt;CreateProduct&lt;/strong&gt; metodunun doğru &amp;ccedil;alışması sadece geriye ge&amp;ccedil;erli bir &lt;strong&gt;Guid&lt;/strong&gt; değer d&amp;ouml;nd&amp;uuml;rd&amp;uuml;ğ&amp;uuml; ile &amp;ouml;l&amp;ccedil;&amp;uuml;lemez. Ger&amp;ccedil;ekten g&amp;ouml;nderdiğimiz &amp;uuml;r&amp;uuml;n bilgilerinin &lt;strong&gt;AddProduct&lt;/strong&gt; metoduna gittiğinden de emin olmalıyız. Bunun i&amp;ccedil;in &lt;strong&gt;setup&lt;/strong&gt; metodunu kullanırken &lt;strong&gt;callback&lt;/strong&gt; fonksiyonunda bir &lt;strong&gt;Product&lt;/strong&gt; nesnesi kullandık. Sonu&amp;ccedil;ta &lt;strong&gt;CreateProduct&lt;/strong&gt; metodu i&amp;ccedil;indeki &lt;strong&gt;AddProduct&lt;/strong&gt; &amp;ccedil;ağrılmadan &amp;ouml;nce bir &lt;strong&gt;Product&lt;/strong&gt; nesnesi &amp;ouml;rnekleniyor. Dolayısıyla &lt;strong&gt;CreateProduct&lt;/strong&gt; parametreleri ile oluşan &lt;strong&gt;Product&lt;/strong&gt; nesne &amp;ouml;rneği değerlerinin, &lt;strong&gt;Callback&lt;/strong&gt; ile d&amp;ouml;nen &lt;strong&gt;Product&lt;/strong&gt; nesne &amp;ouml;rneği değerlerine eşit olması gerekir.&lt;br /&gt;&lt;br /&gt;&lt;strong&gt;CreateProduct_WhenTitleIsEmpty_ShouldThrowException&lt;/strong&gt; ve &lt;strong&gt;CreateProduct_WithNegativeStockQuantity_ShouldThrowException&lt;/strong&gt; isimli testlerde ise ge&amp;ccedil;ersiz girdilerle &lt;strong&gt;CreateProduct&lt;/strong&gt; fonksiyonunun beklenen şekilde istisna&lt;em&gt;(Exception)&lt;/em&gt; fırlatıp fırlatmadığını doğruluyoruz. &lt;strong&gt;CreateProduct&lt;/strong&gt; metodu i&amp;ccedil;inde doğrudan bir &lt;strong&gt;exception&lt;/strong&gt; fırlatımı s&amp;ouml;z konusu olmasa da, &lt;strong&gt;Product&lt;/strong&gt; sınıfı i&amp;ccedil;inde tanımlı domain kurallarımız var ve bunlar &lt;strong&gt;exception&lt;/strong&gt; d&amp;ouml;nd&amp;uuml;r&amp;uuml;yor. Buna ek olarak &lt;strong&gt;CreateProduct_WithNegativeStockQuantity_ShouldThrowException&lt;/strong&gt; testinde, ge&amp;ccedil;ersiz bir stok miktarıyla &amp;uuml;r&amp;uuml;n oluşturulmaya &amp;ccedil;alışıldığında, &lt;strong&gt;AddProduct&lt;/strong&gt; metodunun hi&amp;ccedil; &amp;ccedil;ağrılmadığını da doğruluyoruz. B&amp;ouml;ylece hem iş kurallarının hem de dış bağımlılıklara yapılan &amp;ccedil;ağrıların doğruluğunu test etmiş olduk.&lt;br /&gt;&lt;br /&gt;Eklediğimiz son testleri de &amp;ccedil;alıştıralım.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://www.buraksenyurt.com/image.axd?picture=/2026/Mart/ApplicationTests.png" alt="" /&gt;&lt;/p&gt;
&lt;h3&gt;10. Entegrasyon Testleri (Adapter Katmanı Testleri)&lt;/h3&gt;
&lt;p&gt;&amp;Ouml;zellikle veritabanı veya harici servis gibi dış bağımlılıkların yer aldığı adapt&amp;ouml;r katmanı tipik olarak entegrasyon testleri ile denetlenebilir. Burada ama&amp;ccedil; kodun dış d&amp;uuml;nya ile uyumlu bir şekilde &amp;ccedil;alışıp &amp;ccedil;alışmadığını kontrol etmektir. Mesela &lt;strong&gt;outbound adapter&lt;/strong&gt; olarak &lt;strong&gt;entity framework&lt;/strong&gt; yardımıyla &lt;strong&gt;postgresql&lt;/strong&gt; veritabanına ger&amp;ccedil;ekten kayıt atabiliyor muyuz ya da&lt;strong&gt; inbound adapter&lt;/strong&gt; olarak kullandığımız &lt;strong&gt;ProductController&lt;/strong&gt; ger&amp;ccedil;ekten &lt;strong&gt;http&lt;/strong&gt; isteğine &lt;strong&gt;200 OK&lt;/strong&gt; d&amp;ouml;nebiliyor mu gibi durumları test edebiliriz. Entegrasyon testlerinde mock nesnelerden ziyade ortamları taklit eden yapılara ihtiya&amp;ccedil; duyarız. Mesela &lt;strong&gt;docker&lt;/strong&gt; tarafı i&amp;ccedil;in &lt;strong&gt;Testcontainers&lt;/strong&gt; ya da &lt;strong&gt;entity framework&lt;/strong&gt; tarafı i&amp;ccedil;in bir&lt;strong&gt; in-memory&lt;/strong&gt; veri sağlaycısı d&amp;uuml;ş&amp;uuml;n&amp;uuml;lebilir. Şimdi &lt;strong&gt;HexagonalAdventure.Adapters.IntegrationTests&lt;/strong&gt; şeklinde yine &lt;strong&gt;xUnit&lt;/strong&gt; t&amp;uuml;r&amp;uuml;nden yeni bir proje ekleyerek devam edelim.&lt;/p&gt;
&lt;p&gt;İlk olarak entity framework aracılığıyla kayıt atıp atamadığımız bakalım. Burada mock nesne yerine iki yaklaşımı tercih ederek ilerleyebiliriz. Bunlardan birisi &lt;strong&gt;Test Container&lt;/strong&gt; diğeri ise &lt;strong&gt;In-Memory Provider&lt;/strong&gt; kullanmaktır. &lt;strong&gt;Microsoft.EntityFrameworkCore.InMemory&lt;/strong&gt; paketini kullanarak devam edelim. Buna g&amp;ouml;re veri yazma s&amp;uuml;recini bellek &amp;uuml;zerinden kontrol edeceğimizi ifade edebiliriz.&lt;/p&gt;
&lt;pre class="brush:csharp;auto-links:false;toolbar:false" contenteditable="false"&gt;using HexagonalAdventure.Domain;
using HexagonalAdventure.Adapters.Out.EF;
using Microsoft.EntityFrameworkCore;

namespace HexagonalAdventure.Apdaters.IntegrationTests;

public class EFProductRepositoryTests
{
    [Fact]
    public void Add_ShouldSaveProductToDatabase_And_GetById_ShouldReturnIt()
    {
        // Arrange
        var options = new DbContextOptionsBuilder&amp;lt;DeppoDbContext&amp;gt;()
            .UseInMemoryDatabase(databaseName: Guid.NewGuid().ToString())
            .Options;

        using var context = new DeppoDbContext(options);
        var repository = new EfProductRepository(context);
        var productId = Guid.NewGuid();
        var productToSave = new Product(productId, "Learning the Hexagonal Architecture", 29.99m, "Books", 2);

        // Act
        repository.AddProduct(productToSave);

        // Assert
        var retrievedProduct = repository.GetById(productId);
        Assert.NotNull(retrievedProduct);
        Assert.Equal(productId, retrievedProduct.Id);
        Assert.Equal("Learning the Hexagonal Architecture", retrievedProduct.Title);
        Assert.Equal(29.99m, retrievedProduct.ListPrice);
        Assert.Equal("Books", retrievedProduct.Category);
        Assert.Equal(2, retrievedProduct.StockQuantity);
    }
}&lt;/pre&gt;
&lt;p&gt;Test metodumuz, &lt;strong&gt;EfProductRepository&lt;/strong&gt; nesnemizin ihtyia&amp;ccedil; duyduğu &lt;strong&gt;DbContext&lt;/strong&gt; &amp;ouml;rneği i&amp;ccedil;in &lt;strong&gt;In-Memory&lt;/strong&gt; bir veritabanı sağlayacak şekilde yapılandırılıyor. B&amp;ouml;ylece ger&amp;ccedil;ek bir veritabanına ihtiya&amp;ccedil; duymadan repository'nin işlevselliğini test edebiliriz. Testte &amp;ouml;nce bir &amp;uuml;r&amp;uuml;n oluşturup kaydediyoruz, ardından aynı &amp;uuml;r&amp;uuml;n&amp;uuml; &lt;strong&gt;GetById&lt;/strong&gt; metodu ile &amp;ccedil;ekip kaydettiğimiz &amp;uuml;r&amp;uuml;nle eşit olup olmadığını doğruluyoruz.&lt;/p&gt;
&lt;p&gt;Diyelim ki &lt;strong&gt;DbContext&lt;/strong&gt; t&amp;uuml;revini uygularken &lt;strong&gt;SaveChanges&lt;/strong&gt; metodunu yazmayı atlamışız. Bu durumda &lt;strong&gt;GetById&lt;/strong&gt; metodunu &amp;ccedil;ağırdığımızda null değer d&amp;ouml;necektir. Dolayısıyla testimiz başarısız olur. Kısaca sadece kodun &amp;ccedil;alışıp &amp;ccedil;alışmadığını değil entity framework ayarlarını da kontrol etmiş oluruz. Bu test tam olarak &lt;strong&gt;Domain -&amp;gt; Service -&amp;gt; Interface -&amp;gt; Adapter&lt;/strong&gt; akışını takip etmekte. B&amp;ouml;ylece uygulamayı ger&amp;ccedil;ek &amp;ccedil;alışma ortamına olduk&amp;ccedil;a yakın bir seviyede test etmiş olduk diyebiliriz.&lt;/p&gt;
&lt;p&gt;Elbette yeterli değil. Diğer adapt&amp;ouml;rler i&amp;ccedil;in de benzer testler ekleyebiliriz. &amp;Ouml;rneğin &lt;strong&gt;ProductController&lt;/strong&gt; i&amp;ccedil;in de bir entegrasyon testi yazabiliriz. Bu sefer &lt;strong&gt;HTTP Post&lt;/strong&gt; isteği g&amp;ouml;nderdiğimizde her şeyin u&amp;ccedil;tan uca doğru &amp;ccedil;alıştığını g&amp;ouml;rmeyi ama&amp;ccedil;layabiliriz. Burada da &lt;strong&gt;WebApplicationFactory&lt;/strong&gt; gibi bir nesne kullanarak ger&amp;ccedil;ek bir &lt;strong&gt;HTTP&lt;/strong&gt; istemcisi &amp;uuml;zerinden API'ye istek g&amp;ouml;nderip yanıt almayı deneme şansımız var. Yine aynı test projesinde bu sefer &lt;strong&gt;ProductControllerTests&lt;/strong&gt; isimli yeni bir test sınıfı oluşturarak &amp;ccedil;alışmamıza devam edelim.&lt;em&gt;(&lt;strong&gt;WebApplicationFactory&lt;/strong&gt; nesnesini kullanabilmek i&amp;ccedil;inse test projemize &lt;strong&gt;Microsoft.AspNetCore.Mvc.Testing nuget&lt;/strong&gt; paketini eklememiz gerekiyor)&lt;/em&gt;&lt;/p&gt;
&lt;pre class="brush:csharp;auto-links:false;toolbar:false" contenteditable="false"&gt;using HexagonalAdventure.Adapters.In.WebApi.Controllers;
using Microsoft.AspNetCore.Mvc.Testing;
using System.Net;
using System.Net.Http.Json;

namespace HexagonalAdventure.Apdaters.IntegrationTests;

public class ProductControllerTests(WebApplicationFactory&amp;lt;Program&amp;gt; factory)
    : IClassFixture&amp;lt;WebApplicationFactory&amp;lt;Program&amp;gt;&amp;gt;
{
    // Program sınıfı Web API projesindeki sınıfımızdır.
    // WebApplicationFactory, bu sınıfı kullanarak testler i&amp;ccedil;in bir test sunucusu oluşturur.
    private record CreateProductResponse(Guid Id);

    [Fact]
    public async Task CreateProduct_ShouldReturn200OkWithProductId()
    {
        // Arrange
        var client = factory.CreateClient(); // Test sunucusuna istek g&amp;ouml;ndermek i&amp;ccedil;in fabrikadan bir HttpClient oluşturulur.
        var request = new CreateProductRequest("Pragmatic Programmer", 42.99m, "Books", 4);

        // Act
        var response = await client.PostAsJsonAsync("/api/products", request); // POST isteği g&amp;ouml;nderilir ve yanıt alınır.

        // Assert
        Assert.Equal(HttpStatusCode.OK, response.StatusCode);
        var responseData = await response.Content.ReadFromJsonAsync&amp;lt;CreateProductResponse&amp;gt;();
        Assert.NotNull(responseData);
        Assert.NotEqual(Guid.Empty, responseData.Id);
    }
}&lt;/pre&gt;
&lt;p&gt;&amp;Ouml;ncelike bu test sınıfında neler yaptığımıza bir bakalım. Sınıfımız, &lt;strong&gt;IClassFixture&lt;/strong&gt; aray&amp;uuml;z&amp;uuml;n&amp;uuml; implemente ediyor ve &lt;strong&gt;primary constructor&lt;/strong&gt; &amp;uuml;zerinden de generic &lt;strong&gt;WebApplicationFactory&lt;/strong&gt; t&amp;uuml;r&amp;uuml;nden bir nesne alıyor. Buradaki amacımız birim test metotları &amp;ccedil;ağırılmadan &amp;ouml;nce Web API uygulamasının ger&amp;ccedil;ek bir &amp;ouml;rneğinin bir seferliğine ayağa kaldırılmasını sağlamak. Buna g&amp;ouml;re &lt;strong&gt;CreateProduct_ShouldReturn200OkWithProductId&lt;/strong&gt; isimli test metodumuzda &amp;ouml;nce bir &lt;strong&gt;HTTP&lt;/strong&gt; istemcisi oluşturuyoruz, ardından &lt;strong&gt;CreateProductRequest&lt;/strong&gt; t&amp;uuml;r&amp;uuml;nden bir nesne &amp;ouml;rneği hazırlayıp API'ye g&amp;ouml;nderiyoruz. Son olarak da d&amp;ouml;nen yanıtın durum kodunun &lt;strong&gt;200 OK&lt;/strong&gt; olduğunu ve gelen cevapta ge&amp;ccedil;erli bir Guid&lt;em&gt;(ki product ID olarak ele alınıyor)&lt;/em&gt; olup olmadığını doğruluyoruz.&lt;/p&gt;
&lt;p&gt;Bu testi koştururken dikkat etmemiz gereken noktalardan birisi ger&amp;ccedil;ek bir web sunucusunu ger&amp;ccedil;ekten ayağa kaldırmayışımız olması. Ayrıca bu test ile dış d&amp;uuml;nyadan gelen bir isteğin&lt;em&gt;(ki burada HTTP isteği)&lt;/em&gt;,&lt;strong&gt; inbound adapter&lt;/strong&gt; vasıtasıyla sisteme girişini, iş kurallarından ge&amp;ccedil;işini ve sonunda &lt;strong&gt;outbound adapter&lt;/strong&gt; &amp;uuml;zerinden kaydedilişini doğrulamaya &amp;ccedil;alışıyoruz. Yalnız burada dikkat etmemiz gereken bir nokta daha var. Web Api tarafında program sınıfımız ger&amp;ccedil;ekten de &lt;strong&gt;Postgesql&lt;/strong&gt;'e kayıt atacak şekilde bir &lt;strong&gt;repository&lt;/strong&gt; bileşeni kullanıyor. Yani testi bu şekilde &amp;ccedil;alışıtırırsak test verisi veritabanına da yazılır. Dolayısıyla bir &amp;ouml;nceki entegrasyon testinde olduğu gibi bir &lt;strong&gt;in-memory&lt;/strong&gt; veritabanı ile &amp;ccedil;alışmak daha mantıklıdır. &lt;strong&gt;WebApplicationFactory&lt;/strong&gt; bu noktada bize &amp;ouml;nemli esneklikler sağlar. Program sınıfındaki kurguyu ezebiliriz. Buna g&amp;ouml;re az &amp;ouml;nce yazdığımız test metodunu aşağıdaki hale getirerek devam edelim.&lt;/p&gt;
&lt;pre class="brush:csharp;auto-links:false;toolbar:false" contenteditable="false"&gt;using HexagonalAdventure.Adapters.In.WebApi.Controllers;
using HexagonalAdventure.Adapters.Out.EF;
using Microsoft.AspNetCore.Mvc.Testing;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
using System.Data.Common;
using System.Net;
using System.Net.Http.Json;

namespace HexagonalAdventure.Apdaters.IntegrationTests;

public class ProductControllerTests(WebApplicationFactory&amp;lt;Program&amp;gt; factory)
    : IClassFixture&amp;lt;WebApplicationFactory&amp;lt;Program&amp;gt;&amp;gt;
{
    private record CreateProductResponse(Guid Id);

    [Fact]
    public async Task CreateProduct_ShouldReturn200OkWithProductId()
    {
        // Arrange
        // Program sınıfımızdaki DI servisi, DbContext t&amp;uuml;revini Postgresql ile &amp;ccedil;alışacak şekilde yapılandırıyor.
        // Tabbi EF kullandığımız i&amp;ccedil;in beraberinde de bir&amp;ccedil;ok servis enjekte ediliyor. Bu y&amp;uuml;zden DbContext ile ilgili
        // ne kadar kayıtlı bileşen varsa kaldırıyoruz.
        var client = factory.WithWebHostBuilder(builder =&amp;gt;
        {
            builder.ConfigureServices(services =&amp;gt;
            {
                services.RemoveAll(typeof(IDbContextOptionsConfiguration&amp;lt;DeppoDbContext&amp;gt;)); // Program sınıfında AddDbContext'in kaydettiği Npgsql yapılandırma kaynağını kaldırır.
                services.RemoveAll(typeof(DbContextOptions&amp;lt;DeppoDbContext&amp;gt;)); // DbContext ile ilgili t&amp;uuml;m servisleri kaldırır.
                services.RemoveAll(typeof(DbConnection)); // Varsa DbConnection ile ilgili t&amp;uuml;m servisleri kaldırır. &amp;Ouml;rneğin veritabanı kayıtları silinir.

                services.AddDbContext&amp;lt;DeppoDbContext&amp;gt;(options =&amp;gt;
                {
                    options.UseInMemoryDatabase("DbTest_" + Guid.NewGuid().ToString());
                });
            });
        }).CreateClient();
        var request = new CreateProductRequest("Pragmatic Programmer", 42.99m, "Books", 4);

        // Act
        var response = await client.PostAsJsonAsync("/api/products", request); // POST isteği g&amp;ouml;nderilir ve yanıt alınır.

        // Assert
        Assert.Equal(HttpStatusCode.OK, response.StatusCode);
        var responseData = await response.Content.ReadFromJsonAsync&amp;lt;CreateProductResponse&amp;gt;();
        Assert.NotNull(responseData);
        Assert.NotEqual(Guid.Empty, responseData.Id);
    }
}&lt;/pre&gt;
&lt;p&gt;Testlerimizdeki nihai durumu aşağıdaki g&amp;ouml;rselle &amp;ouml;zetleyebiliriz.&lt;em&gt;&lt;br /&gt;&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src="https://www.buraksenyurt.com/image.axd?picture=/2026/Mart/IntegrationTests.png" alt="" /&gt;&lt;/p&gt;
&lt;h3&gt;11. TestContainer ile Entegrasyon Testleri&lt;/h3&gt;
&lt;p&gt;Son entegrasyon testlerinde &lt;strong&gt;in-memory&lt;/strong&gt; veritabanı kullanarak ilerledik ancak kurumsal &amp;ccedil;aptaki &amp;ccedil;&amp;ouml;z&amp;uuml;mlerde genellikle &lt;strong&gt;Test Container&lt;/strong&gt;' lar tercih ediliyor. Bunun en b&amp;uuml;y&amp;uuml;k sebebi&lt;strong&gt; in-memory&lt;/strong&gt; veritabanı rol&amp;uuml;n&amp;uuml; &amp;uuml;stlenen enstr&amp;uuml;manın aslında ger&amp;ccedil;ekten bir veritabanı olmaması. Zira &lt;strong&gt;SQL&lt;/strong&gt;'e veye &lt;strong&gt;PostgreSQL&lt;/strong&gt;'e &amp;ouml;zg&amp;uuml; bir&amp;ccedil;ok &amp;ouml;zellik desteklenmez. Misal &lt;strong&gt;JSONB&lt;/strong&gt; veri kolonları veya stored procedure'ler. Hoş iş s&amp;uuml;re&amp;ccedil;lerindeki kuralların stored procedure'lere yazılması pek de iyi bir fikir değildir ama yine de bazı durumlarda b&amp;ouml;yle bir şeyle karşılaşmak m&amp;uuml;mk&amp;uuml;n olabilir. Bu y&amp;uuml;zden ger&amp;ccedil;ek bir veritabanı kullanmak daha sağlıklı sonu&amp;ccedil;lar verecektir.&lt;/p&gt;
&lt;p&gt;Bir &lt;strong&gt;Test Container&lt;/strong&gt; kullanarak testler sırasında ge&amp;ccedil;ici olarak ayağa kaldırılan ger&amp;ccedil;ek bir veritabanı ile entegrasyon sağlayabiliriz. &lt;strong&gt;Test Container&lt;/strong&gt; teorik olarak arka planda bir &lt;strong&gt;docker container&lt;/strong&gt; ayağa kaldırır. Dolayısıyla sisteminizde &lt;strong&gt;docker&lt;/strong&gt; kurulu olduğunu varsayıyorum. Bizim senaryomuzda ben &lt;strong&gt;Postgresql&lt;/strong&gt; kullandığım i&amp;ccedil;in &lt;strong&gt;Testcontainers.PostgreSql&lt;/strong&gt; isimli &lt;a href="https://www.nuget.org/packages/Testcontainers.PostgreSql" target="_blank"&gt;nuget paketini&lt;/a&gt; kullanarak devam edeceğim. Şimdi test projesine aşağıdaki kod i&amp;ccedil;eriğine sahip olan yeni&amp;nbsp;&lt;strong&gt;WebApplicationFactory&lt;/strong&gt; t&amp;uuml;revini ekleyelim.&lt;/p&gt;
&lt;pre class="brush:csharp;auto-links:false;toolbar:false" contenteditable="false"&gt;using HexagonalAdventure.Adapters.Out.EF;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Mvc.Testing;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
using System.Data.Common;
using Testcontainers.PostgreSql;

namespace HexagonalAdventure.Apdaters.IntegrationTests;

public class PostgresWebApplicationFactory
    : WebApplicationFactory&amp;lt;Program&amp;gt;, IAsyncLifetime
{
    // Testler başlamadan &amp;ouml;nce ve bittikten sonra yapmamız gereken kaynak y&amp;ouml;netim işlemleri olacağından
    // bu sınıfa IAsyncLifetime aray&amp;uuml;z&amp;uuml;n&amp;uuml; de uyguladık.

    private readonly PostgreSqlContainer _container = new PostgreSqlBuilder()
        .WithDatabase("DeppoTestDb")
        .WithUsername("postgres")
        .WithPassword("P@ssw0rd1234")
        .Build();
    public async Task InitializeAsync()
    {
        // Docker &amp;uuml;zerinden postgresql konteynırını başlatır. Testler bu veritabanını kullanacak.
        await _container.StartAsync();
    }

    async Task IAsyncLifetime.DisposeAsync()
    {
        await _container.DisposeAsync(); // Testler tamamlandıktan sonra konteynırı durdurur ve kaynakları temizler.
    }

    // Burada da WebApplicationFactory'den gelen ConfigureWebHost metodunu eziyoruz(override)
    // Burada klasik olarak program sınıfındaki servislerin temizlenmesi ve container db'nin context'e eklenmesi gibi işlemler yapılıyor.
    protected override void ConfigureWebHost(IWebHostBuilder builder)
    {
        builder.ConfigureServices(services =&amp;gt;
        {
            services.RemoveAll(typeof(IDbContextOptionsConfiguration&amp;lt;DeppoDbContext&amp;gt;)); // Program sınıfında AddDbContext'in kaydettiği Npgsql yapılandırma kaynağını kaldırır.
            services.RemoveAll(typeof(DbContextOptions&amp;lt;DeppoDbContext&amp;gt;)); // DbContext ile ilgili t&amp;uuml;m servisleri kaldırır.
            services.RemoveAll(typeof(DbConnection)); // Varsa DbConnection ile ilgili t&amp;uuml;m servisleri kaldırır. &amp;Ouml;rneğin veritabanı kayıtları silinir.

            services.AddDbContext&amp;lt;DeppoDbContext&amp;gt;(options =&amp;gt;
            {
                options.UseNpgsql(_container.GetConnectionString()); // Artık Npgsql, container'ın sağladığı db'ye bağlanacak
            });

            // Tabii şimdi bir konteynır kullanıyor olsa da ger&amp;ccedil;ek veritabanına ihtiyacımız var.
            // Dolayısıyla migration prosed&amp;uuml;r&amp;uuml;n&amp;uuml; y&amp;uuml;r&amp;uuml;tmemiz lazım ki gerekli tablolar da oluşsun.
            var serviceProvider = services.BuildServiceProvider();
            using var scope = serviceProvider.CreateScope();
            var dbContext = scope.ServiceProvider.GetRequiredService&amp;lt;DeppoDbContext&amp;gt;();
            dbContext.Database.EnsureCreated();
        });
    }
}&lt;/pre&gt;
&lt;p&gt;Buna g&amp;ouml;re tek yapmamız gereken test sınıfına &lt;strong&gt;WebApplicationFactory&lt;/strong&gt; yerine &lt;strong&gt;PostgresWebApplicationFactory&lt;/strong&gt; t&amp;uuml;r&amp;uuml;nden bir nesne &amp;ouml;rneğini enjekte etmek olacak. Bu &amp;ccedil;alışmadaki diğer test metodu ile karışmaması adına yeni bir test sınıfı ve metod ile devam etmeye karar verdim.&lt;/p&gt;
&lt;pre class="brush:csharp;auto-links:false;toolbar:false" contenteditable="false"&gt;using HexagonalAdventure.Adapters.In.WebApi.Controllers;
using System.Net;
using System.Net.Http.Json;

namespace HexagonalAdventure.Apdaters.IntegrationTests;

public class ProductControllerTests(PostgresWebApplicationFactory factory)
    : IClassFixture&amp;lt;PostgresWebApplicationFactory&amp;gt;
{
    private record CreateProductResponse(Guid Id);

    [Fact]
    public async Task CreateProduct_WhenUsingContainer_ShouldReturn200OkWithProductId()
    {
        // Arrange
        var client = factory.CreateClient();
        var request = new CreateProductRequest("Pragmatic Programmer", 42.99m, "Books", 4);

        // Act
        var response = await client.PostAsJsonAsync("/api/products", request);

        // Assert
        Assert.Equal(HttpStatusCode.OK, response.StatusCode);
        var responseData = await response.Content.ReadFromJsonAsync&amp;lt;CreateProductResponse&amp;gt;();
        Assert.NotNull(responseData);
        Assert.NotEqual(Guid.Empty, responseData.Id);
    }
}&lt;/pre&gt;
&lt;p&gt;Sadece bu testi &amp;ccedil;alıştırarıp ger&amp;ccedil;ekten de &lt;strong&gt;docker&lt;/strong&gt; tarafında bir &lt;strong&gt;container&lt;/strong&gt; ayağa kalkıyor mu ve testler bu container'daki veritabanına bağlanarak &amp;ccedil;alışıyor mu diye kontrol edebiliriz.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://www.buraksenyurt.com/image.axd?picture=/2026/Mart/ContainerTest.png" alt="" /&gt;&lt;/p&gt;
&lt;p&gt;Burada dikkat edilmesi gereken nokta s&amp;ouml;z konusu container'ın test tamamlanmadan &amp;ouml;nce başlatılması ve test bittikten sonra da kaldırılmasıdır. İlk ısınma sırasında&lt;em&gt;(warm-up diyelim)&lt;/em&gt; testin s&amp;uuml;resi biraz uzayabilir zira container'ın ayağa kalkması ve veritabanının hazır hale gelmesi zaman alabilir. Ancak kullanmak istediğimiz veritabanı &amp;ouml;zellikleri d&amp;uuml;ş&amp;uuml;n&amp;uuml;l&amp;uuml;rse bu maliyete değebilir.&lt;/p&gt;
&lt;h3&gt;12. Mimari Uygunluk Testleri&lt;/h3&gt;
&lt;p&gt;Pek &amp;ccedil;ok mimari yaklaşım bileşener arası bağımlılıkların ve izolasyonların doğru y&amp;ouml;netilmesi konusunda hassastır. &amp;Ouml;rneğin bu &amp;ccedil;alışmada ele aldığımız &lt;strong&gt;hexagonal&lt;/strong&gt; mimaride uygulama domain'inin dış d&amp;uuml;nyaya olan bağımlılığını tamamen ortadan kaldırmak &amp;ouml;nemli bir prensiptir. &amp;Ouml;rneğin &lt;strong&gt;domain&lt;/strong&gt; katmanında bir şekilde &lt;strong&gt;entity framework&lt;/strong&gt; ile konuşmaya başladığımız an mimarinin temel prensiplerinden biri olan bağımsızlık ilkesini ihlal etmiş oluruz.&lt;/p&gt;
&lt;p&gt;Bu t&amp;uuml;r durumları tespit etmek i&amp;ccedil;in mimari uygunluk testleri yazılabilir. &lt;strong&gt;Microsoft .Net&lt;/strong&gt; tarafından bakacak olursak bu kontrol&amp;uuml; kolayca icra etmemizi sağlayan &lt;strong&gt;NetArchTest.Rules&lt;/strong&gt; isimli bir &lt;a href="https://github.com/BenMorris/NetArchTest" target="_blank"&gt;nuget paketi&lt;/a&gt; vardır. Mimari testleri ayrı bir projede ele alalım ve bu ama&amp;ccedil;la &lt;strong&gt;HexagonalAdventure.Architecture.Tests&lt;/strong&gt; isimli yeni bir test projesi oluşturarak konunuza devam edelim. Projeye &lt;strong&gt;NetArchTest.Rules&lt;/strong&gt; paketini ekledikten sonra da aşağıdaki gibi bir test sınıfı yazalım.&lt;/p&gt;
&lt;pre class="brush:csharp;auto-links:false;toolbar:false" contenteditable="false"&gt;using HexagonalAdventure.Application.Services;
using HexagonalAdventure.Domain;
using NetArchTest.Rules;

namespace HexagonalAdventures.Architecture.Tests;

public class DomainLayerTests
{
    [Fact]
    public void DomainLayer_ShouldNotHaveDependencyOnOtherLayers()
    {
        // Arrange
        var domainAssembly = typeof(Product).Assembly;

        // Act
        var result = Types.InAssembly(domainAssembly)
            .ShouldNot()
            .HaveDependencyOnAny(
            "HexagonalAdventure.Application",
            "HexagonalAdventure.Adapters",
            "Microsoft.EntityFrameworkCore"
            )
            .GetResult();

        // Assert
        Assert.True(result.IsSuccessful, "Domain layer should not have dependencies on Application, Adapters, or EF Core.");
    }

    [Fact]
    public void ApplicationLayer_ShouldNotHaveDependencyOnAdapters()
    {
        // Arrange
        var appAssembly = typeof(ProductService).Assembly;
        
        // Act
        var result = Types.InAssembly(appAssembly)
            .ShouldNot()
            .HaveDependencyOn("HexagonalAdventure.Adapters")
            .GetResult();

        // Assert
        Assert.True(result.IsSuccessful, "Application layer should not have dependencies on Adapters.");
    }
}&lt;/pre&gt;
&lt;p&gt;Bu testlere g&amp;ouml;re &amp;ouml;rneğin &lt;strong&gt;HexagonalAdventure.Application&lt;/strong&gt; &amp;ouml;neki i&amp;ccedil;eren namespace'lerin olduğu projelerin &lt;strong&gt;Domain&lt;/strong&gt; katmanına sızmasını engellemiş oluyoruz&lt;em&gt;(Entity Framework ile birlikte tabii)&lt;/em&gt;. Ben burada sadece birka&amp;ccedil; temel kontrol ekledim ancak mimari uygunluk testlerini &amp;ccedil;ok daha detaylı hale getirmek m&amp;uuml;mk&amp;uuml;n olabilir. &amp;Ouml;rneğin domain katmanında sadece domain entity'lerin bulunması gerektiği gibi bir kural veya application katmanında sadece servislerin bulunması gerektiği gibi bir kural da ekleyebiliriz.&amp;nbsp;G&amp;uuml;ncel olarak geldiğimiz noktada projemizdeki t&amp;uuml;m testlerin başarılı bir şekilde &amp;ccedil;alıştığını g&amp;ouml;rebiliriz.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://www.buraksenyurt.com/image.axd?picture=/2026/Mart/LastTestResults.png" alt="" /&gt;&lt;/p&gt;
&lt;h2&gt;Genel G&amp;ouml;r&amp;uuml;n&amp;uuml;m&lt;/h2&gt;
&lt;p&gt;Solution i&amp;ccedil;eriğinde bir&amp;ccedil;ok proje ve harici nuget bağımlılıkları var. Gelinen noktada neler olduğunu kabaca aşağıdaki diagramda olduğu gibi &amp;ouml;zetleyebiliriz.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://www.buraksenyurt.com/image.axd?picture=/2026/Mart/GeneralOverview.png" alt="" /&gt;&lt;/p&gt;
&lt;p&gt;Yazının bu kısmına kadar geldiyseniz size canı g&amp;ouml;n&amp;uuml;lden teşekk&amp;uuml;r ederim. Umarım bilgilendirici ve denediğinize değer bir makale olmuştur. Burada ele aldığımız &lt;a href="https://github.com/buraksenyurt/HexagonalArchitecture_101" target="_blank"&gt;uygulama kodlarına elbette github reposu &amp;uuml;zerinden erişebilirsiniz&lt;/a&gt;. Hatta &lt;strong&gt;DDD&lt;/strong&gt;'ye &amp;ouml;zg&amp;uuml; bir takım yenilikler de eklemeye &amp;ccedil;alışacağım. &lt;strong&gt;Event&lt;/strong&gt;'ler gibi. Tekrardan g&amp;ouml;r&amp;uuml;ş&amp;uuml;nceye dek hepinize mutlu g&amp;uuml;nler dilerim.&lt;/p&gt;</summary>
    <published>2026-03-13T19:31:00+00:00</published>
    <link rel="related" href="https://www.buraksenyurt.com/post/hexagonal-architecture-101#comment" />
    <category term="C#" />
    <betag:tag>softwareArchitecture</betag:tag>
    <betag:tag>onionArchitecture</betag:tag>
    <betag:tag>dotnet</betag:tag>
    <betag:tag>cSharp</betag:tag>
    <betag:tag>yazılımMimarileri</betag:tag>
    <betag:tag>hexagonalArchitecture</betag:tag>
    <dc:publisher>bsenyurt</dc:publisher>
    <dc:description>Kurumsal uygulamaları göz önüne aldığımızda zaman içerisinde birçok yazılım mimarisinin ortaya çıktığını görüyoruz. Programlama dillerinin gelişim, framework'lerin ortaya çıkması ve değişen müşteri ihtiyaçları sonucunda bu kavram çok daha büyük önem kazandı. Belki de her şey üç katmanlı(3-tier) yaklaşımla başlamıştı. Geldiğimiz zaman diliminde ise monolit sistemlerin modüler hale getirildiğiği Modulith'lerden mikro servislere, soğan halkaları benzetmesi ile popülerleşen Onion mimariden, servis odaklı yaklaşıma kadar birçok stil var. Bazı kaynaklarda yazılım mimarileri katmanlı(Layered) ve dağıtık(Distributed) olmak üzere iki ana kategoriye de ayrılıyor. Üzerinde uzun uzun konuşulacak olan bu kavramları elbette deneyimleyerek görmek en güzeli. Bende bir süredir bakmak istediğim Hexagonal mimari yaklaşımını öğrenmeye karar verdim ve işte karşınızdayım. Gelin .net üzerinde bu mimariyi çok temel seviyede de olsa uygulamalı olarak anlamaya çalışalım.</dc:description>
    <pingback:server>https://www.buraksenyurt.com/pingback.axd</pingback:server>
    <pingback:target>https://www.buraksenyurt.com/post.aspx?id=6f3fa205-863e-4672-bd91-1ac64286aa38</pingback:target>
    <slash:comments>0</slash:comments>
    <trackback:ping>https://www.buraksenyurt.com/trackback.axd?id=6f3fa205-863e-4672-bd91-1ac64286aa38</trackback:ping>
    <wfw:comment>https://www.buraksenyurt.com/post/hexagonal-architecture-101#comment</wfw:comment>
    <wfw:commentRss>https://www.buraksenyurt.com/syndication.axd?post=6f3fa205-863e-4672-bd91-1ac64286aa38</wfw:commentRss>
  </entry>
</feed>