<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: TAHA TÜZEL</title>
    <description>The latest articles on DEV Community by TAHA TÜZEL (@tahatuzel).</description>
    <link>https://dev.to/tahatuzel</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4105742%2F7f19128b-3348-431f-93ca-c247d1cdcc61.jpg</url>
      <title>DEV Community: TAHA TÜZEL</title>
      <link>https://dev.to/tahatuzel</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/tahatuzel"/>
    <language>en</language>
    <item>
      <title>Bir Siparişten Satış Raporuna: OLTP, OLAP ve Data Warehouse</title>
      <dc:creator>TAHA TÜZEL</dc:creator>
      <pubDate>Wed, 14 Oct 2026 07:12:26 +0000</pubDate>
      <link>https://dev.to/tahatuzel/bir-siparisten-satis-raporuna-oltp-olap-ve-data-warehouse-4oig</link>
      <guid>https://dev.to/tahatuzel/bir-siparisten-satis-raporuna-oltp-olap-ve-data-warehouse-4oig</guid>
      <description>&lt;p&gt;Backend tarafında bir kullanıcı kaydı oluştururken veya bir siparişi kaydederken database kullanıyorum. Hangi alanları tutacağım, tabloları nasıl ilişkilendireceğim ve sorguyu nasıl yazacağım genellikle uygulamanın o anki ihtiyacına göre şekilleniyor.&lt;/p&gt;

&lt;p&gt;Aynı veriden bir yıllık satış raporu çıkarmak istediğimde ise ihtiyaç değişiyor. Belirli bir müşterinin siparişini bulmakla bütün satışları aylara göre gruplamak aynı veriyi kullansa da database'den farklı işler istiyor.&lt;/p&gt;

&lt;p&gt;Bu konuları çalışırken sık karşılaştığım açıklama şuydu:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;OLTP günlük işlemler, OLAP analiz için kullanılır.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Bu ayrım başlangıç için anlamlı. Ama uygulamanın database'inde zaten rapor sorgusu yazabiliyorsam neden ayrıca data warehouse kullandığımızı tek başına açıklamıyor. Veriyi başka bir yere taşımak ne kazandırıyor, tablo yapısı neden değişiyor, satır ve sütun odaklı depolama bu resimde nereye oturuyor?&lt;/p&gt;

&lt;p&gt;Bu yazıda önce bu kavramların neyi tarif ettiğini açıklayıp ardından aynı e-ticaret örneği üzerinde ilerleyeceğim. Ahmet'in 300 TL'lik muz siparişini, uygulamada oluştuğu andan aylık satış raporuna girdiği noktaya kadar takip edeceğiz. Tablolar ve sorgular anlatım için hazırladığım basitleştirilmiş örnekler.&lt;/p&gt;




&lt;h3&gt;
  
  
  Büyük Resim: Veriyi Kaydetmek ve Veriden Sonuç Çıkarmak
&lt;/h3&gt;

&lt;p&gt;Bir veri sistemi, uygulamanın günlük işlemlerini yürütmek için çalışabilir. Kullanıcının hesabına erişmesi, bir kaydın durumunu görmesi veya yeni bir kayıt oluşturması bu kullanımın parçasıdır. Burada işlemlerin kısa sürede tamamlanması ve eşzamanlı kullanıcıların yaptığı değişikliklerin doğru yönetilmesi önemlidir.&lt;/p&gt;

&lt;p&gt;Aynı veriyi analiz için kullandığımızda ise çok sayıda kaydı birlikte inceleriz. Toplamları hesaplar, dönemleri karşılaştırır veya veriyi farklı kırılımlarda gruplarız. Tek bir kaydın güncel durumundan, kayıtların birlikte ne anlattığına geçeriz.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;OLTP (Online Transaction Processing)&lt;/strong&gt; ilk kullanım biçimine, &lt;strong&gt;OLAP (Online Analytical Processing)&lt;/strong&gt; ise analitik kullanıma odaklanır. Bunlar veriyi hangi iş için işlediğimizi anlatır. &lt;strong&gt;Data warehouse&lt;/strong&gt;, analiz ihtiyacını desteklemek için kurduğumuz veri deposudur; farklı kaynaklardan gelen veriyi ortak bir düzen içinde saklamamızı sağlar.&lt;/p&gt;

&lt;p&gt;E-ticaret örneğinde genel yol şöyle olabilir:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Müşteri sipariş verir
        ↓
Operasyonel Database
Siparişi kaydet, stok bilgisini güncelle → OLTP
        ↓
Veriyi analiz için hazırla ve aktar
        ↓
Data Warehouse
        ↓
Aylara ve kategorilere göre satışları hesapla → OLAP
        ↓
Satış raporu / Dashboard
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Ahmet sipariş verdiğinde uygulama ona işlemin sonucunu gösteriyor. Ay sonunda hazırlanan raporda ise aynı sipariş, meyve kategorisinin satış toplamına katkı sağlıyor.&lt;/p&gt;

&lt;p&gt;Her uygulama için bu aşamaları ayrı sistemlerle kurmamız gerekmiyor. Ancak bu yolu takip etmek, iki kullanımın nerede farklılaştığını anlamayı kolaylaştırıyor.&lt;/p&gt;




&lt;h3&gt;
  
  
  Adım 1: Database ve OLTP ile Günlük İşlemleri Yürütmek
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Database&lt;/strong&gt;, veriyi belirli bir düzen içinde sakladığımız ve sorguladığımız yapıdır. &lt;strong&gt;DBMS (Database Management System)&lt;/strong&gt; ise bu verinin saklanmasını, okunmasını ve değiştirilmesini yöneten yazılımı ifade eder. PostgreSQL ve MySQL birer DBMS örneğidir.&lt;/p&gt;

&lt;p&gt;İlişkisel bir database'de veriyi tablolarda tutarız. Satırlar kayıtları, sütunlar ise bu kayıtların alanlarını temsil eder. &lt;strong&gt;Primary key&lt;/strong&gt;, bir kaydı benzersiz biçimde tanımlar; &lt;strong&gt;foreign key&lt;/strong&gt; de tablolar arasındaki ilişkinin bütünlüğünü korumaya yardımcı olur.&lt;/p&gt;

&lt;p&gt;Bir kaydı bulmak için kullanılan &lt;strong&gt;index&lt;/strong&gt;, sorgunun ilgili verilere daha az kayıt okuyarak ulaşmasını sağlayan ek bir yapıdır. Bunun da maliyeti vardır: index alanı kaplar ve veri değiştiğinde database index'i de güncellemek zorunda kalabilir. Bu nedenle her sütuna index eklemek yerine uygulamanın sorgularını dikkate almak gerekir.&lt;/p&gt;

&lt;p&gt;OLTP sistemleri bu araçları, çok sayıda kullanıcının kısa süren işlemlerini karşılamak için kullanır. İşlem çoğunlukla belirli bir müşteri, sipariş veya ürün gibi sınırlı bir kayıt kümesiyle ilgilidir. Hem okuma hem yazma vardır; yanıt süresinin kısa olması uygulamanın kullanımını doğrudan etkiler.&lt;/p&gt;

&lt;p&gt;Örneğin Ahmet siparişinin durumunu görmek istediğinde bütün satış geçmişini incelemiyoruz. Belirli bir sipariş kaydına gidiyoruz:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;order_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;customer_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;created_at&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;orders&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;order_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1042&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;order_id&lt;/code&gt; primary key ise bu alanı destekleyen index, database'in ilgili siparişi bulmasına yardımcı olur. Sonuç, sipariş ekranında göstereceğim durum bilgisi olabilir.&lt;/p&gt;

&lt;p&gt;Aynı sırada başka bir kullanıcı yeni sipariş oluşturabilir, bir çalışan da başka bir siparişin durumunu güncelleyebilir. OLTP tarafındaki ihtiyaç bu işlemleri birlikte yürütürken veriyi doğru durumda tutmak. Sipariş kaydetmek kadar sipariş okumak da bu iş yüküne ait.&lt;/p&gt;

&lt;h4&gt;
  
  
  Normalization: Veriyi Neden Ayrı Tablolarda Tutuyoruz?
&lt;/h4&gt;

&lt;p&gt;&lt;strong&gt;Normalization&lt;/strong&gt;, veriyi ilişkilerine göre düzenleyerek gereksiz tekrarları ve bu tekrarlardan doğan değişiklik sorunlarını azaltma yaklaşımıdır. Aynı bilginin birçok kayıtta tutulması, bir değişiklik sırasında kopyaların bir bölümünün güncel, diğer bölümünün eski kalmasına yol açabilir.&lt;/p&gt;

&lt;p&gt;Bu nedenle bir varlığa ait bilgiyi uygun tabloda saklar, diğer kayıtları ona anahtar üzerinden bağlarız. Bir ilişkiyi okumak için join yapmak gerekebilir; bunun karşılığında ortak bilgiyi her kopyasında ayrı ayrı güncellemekten kurtuluruz.&lt;/p&gt;

&lt;p&gt;E-ticaret uygulamasında şöyle bir model kurabiliriz:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;customers
  customer_id, name, email

orders
  order_id, customer_id, status, created_at

order_items
  order_item_id, order_id, product_id, quantity, unit_price

products
  product_id, name, category_id, current_price, stock_quantity

categories
  category_id, name
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Ahmet'in adı ve e-posta adresi &lt;code&gt;customers&lt;/code&gt; tablosunda duruyor. Sipariş kaydı, aynı alanları tekrar taşımak yerine &lt;code&gt;customer_id&lt;/code&gt; ile Ahmet'e bağlanıyor. E-posta adresi değiştiğinde bütün eski siparişleri dolaşmam gerekmiyor.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;orders&lt;/code&gt;, siparişin genel durumunu; &lt;code&gt;order_items&lt;/code&gt; ise hangi üründen kaç adet alındığını saklıyor. Bir siparişte farklı ürünler bulunabileceği için ürün kalemlerini ayrı tutuyoruz.&lt;/p&gt;

&lt;p&gt;Burada &lt;code&gt;unit_price&lt;/code&gt; alanını özellikle sipariş kaleminde saklıyorum. Bu örnekte alan, indirim varsa onu da yansıtan satış anındaki birim fiyatı temsil etsin. Ahmet iki paket muzu paket başına 150 TL'den aldıysa sipariş tutarı 300 TL olur. Ürünün &lt;code&gt;current_price&lt;/code&gt; değeri yarın değiştiğinde geçmiş siparişin fiyatını değiştirmek istemiyorum.&lt;/p&gt;

&lt;p&gt;Bu iki fiyat alanı gereksiz bir kopya değil; biri güncel fiyatı, diğeri geçmiş satışın fiyatını anlatıyor. Veri modelini kurarken alanların yalnızca isimlerine değil, hangi bilgiyi temsil ettiklerine de bakmam gerekiyor.&lt;/p&gt;

&lt;h4&gt;
  
  
  Transaction: Birbiriyle İlgili Değişiklikleri Birlikte Tamamlamak
&lt;/h4&gt;

&lt;p&gt;&lt;strong&gt;Transaction&lt;/strong&gt;, database üzerindeki bir veya daha fazla işlemi bir bütün olarak ele aldığımız çalışma birimidir. Değişiklikleri &lt;code&gt;COMMIT&lt;/code&gt; ile tamamlar, işlemden vazgeçtiğimizde veya hata durumunda &lt;code&gt;ROLLBACK&lt;/code&gt; ile geri alırız.&lt;/p&gt;

&lt;p&gt;Transaction kelimesi burada ödeme anlamına gelmiyor. Bir adres değişikliği veya kayıt silme de transaction içinde gerçekleşebilir. Önemli olan, database işlemlerinin hangi sınır içinde birlikte değerlendirildiğidir.&lt;/p&gt;

&lt;p&gt;Transaction davranışını açıklamak için kullanılan &lt;strong&gt;ACID&lt;/strong&gt; özellikleri şunlardır:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Atomicity:&lt;/strong&gt; Transaction'ın değişiklikleri birlikte gerçekleşir; işlem tamamlanmazsa yarım değişiklikler kalıcı hale gelmez.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Consistency:&lt;/strong&gt; Doğru tasarlanmış işlemler database'in tanımlı bütünlük kurallarını korur. Foreign key gibi kurallar bu sınırın parçasıdır.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Isolation:&lt;/strong&gt; Database, eşzamanlı transaction'ların birbirlerinin değişikliklerini nasıl göreceğini yönetir. Davranış seçilen isolation level'a bağlıdır.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Durability:&lt;/strong&gt; Database başarıyla commit ettiği değişiklikleri kalıcı tutma garantisi verir.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Ahmet'in siparişinde sipariş kaydı oluşturmak, ürün kalemini eklemek ve stoktan iki paket düşmek birlikte ele alınabilir:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;BEGIN
  ↓
Sipariş kaydını oluştur
  ↓
Sipariş kalemini ekle
  ↓
Stok miktarını güncelle
  ↓
COMMIT
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Bu değişiklikler aynı transaction içindeyse hata durumunda geri alabilirim. Böylece ürün kalemi oluşmadığı halde tamamlanmış görünen bir sipariş bırakmam.&lt;/p&gt;

&lt;p&gt;Eşzamanlı stok kontrolünü de doğru tasarlamak gerekir. İki müşteri son ürünü aynı anda almaya çalışıyorsa kontrol ve güncellemeyi uygun koşullar veya kilitlemeyle yönetmeliyim. Sadece transaction başlatmak, uygulamadaki bütün iş kurallarını kendiliğinden doğru hale getirmiyor.&lt;/p&gt;

&lt;p&gt;Sipariş kaydolduktan sonra uygulama günlük işlerini yapmaya devam edebilir. Şimdi aynı kayıtlara başka bir amaçla bakacağız.&lt;/p&gt;




&lt;h3&gt;
  
  
  Adım 2: OLAP ile Veriden Analiz Çıkarmak
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;OLAP&lt;/strong&gt;, büyük veri kümeleri üzerinde analiz yapmaya odaklanan işlem yaklaşımıdır. Çok sayıda kaydı filtrelemek, gruplamak ve farklı dönemleri karşılaştırmak bu iş yükünde sık görülür.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Aggregation&lt;/strong&gt;, birçok kayıttan toplam, ortalama veya adet gibi bir sonuç hesaplamaktır. SQL'deki &lt;code&gt;SUM&lt;/code&gt;, &lt;code&gt;AVG&lt;/code&gt; ve &lt;code&gt;COUNT&lt;/code&gt; bu hesaplamalara örnek. Analizde kullandığımız satış tutarı veya ürün adedi gibi sayısal değerlere de &lt;strong&gt;measure&lt;/strong&gt; diyebiliriz.&lt;/p&gt;

&lt;p&gt;Bir measure'ı hangi açıdan inceleyeceğimizi ise &lt;strong&gt;dimension&lt;/strong&gt; belirler. Tarih, ürün kategorisi veya mağaza gibi bilgiler, sonucu gruplamak ve filtrelemek için kullanabileceğimiz kırılımlardır. Böylece tek bir toplam yerine, o toplamın farklı bölümlerini inceleyebiliriz.&lt;/p&gt;

&lt;p&gt;E-ticaret uygulamasında artık şu soruyu soralım:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;2025 yılında aylara göre hangi ürün kategorisinden ne kadar satış yaptık?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Raporun kullanacağı kayıtların küçük bir bölümü şöyle olsun:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Tarih&lt;/th&gt;
&lt;th&gt;Ürün&lt;/th&gt;
&lt;th&gt;Kategori&lt;/th&gt;
&lt;th&gt;Adet&lt;/th&gt;
&lt;th&gt;Satış tutarı&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;12 Ocak 2025&lt;/td&gt;
&lt;td&gt;Muz&lt;/td&gt;
&lt;td&gt;Meyve&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;300 TL&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;13 Ocak 2025&lt;/td&gt;
&lt;td&gt;Kahve&lt;/td&gt;
&lt;td&gt;İçecek&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;120 TL&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;20 Ocak 2025&lt;/td&gt;
&lt;td&gt;Muz&lt;/td&gt;
&lt;td&gt;Meyve&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;150 TL&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3 Şubat 2025&lt;/td&gt;
&lt;td&gt;Muz&lt;/td&gt;
&lt;td&gt;Meyve&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;150 TL&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4 Şubat 2025&lt;/td&gt;
&lt;td&gt;Kahve&lt;/td&gt;
&lt;td&gt;İçecek&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;200 TL&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Ahmet'in siparişi ilk satırda duruyor. Ocak ayının meyve satışlarını hesapladığımda onun 300 TL'lik siparişini, aynı ayın diğer 150 TL'lik muz satışıyla topluyorum. Bu örnekte sonuç 450 TL.&lt;/p&gt;

&lt;p&gt;Satış tutarı measure, ay ve kategori ise analizin kırılımları. Başta tek bir siparişin durumuna bakıyorduk; şimdi birden fazla sipariş kaleminden sonuç çıkarıyoruz.&lt;/p&gt;

&lt;h4&gt;
  
  
  Analizin Ayrıntı Düzeyini Değiştirmek
&lt;/h4&gt;

&lt;p&gt;OLAP analizinde aynı measure'ı farklı ayrıntı seviyelerinde inceleyebiliriz. &lt;strong&gt;Drill-down&lt;/strong&gt;, yıllık toplamdan aylara veya aylardan günlere inmek gibi daha ayrıntılı bir seviyeye geçmeyi ifade eder. &lt;strong&gt;Roll-up&lt;/strong&gt; ise ayrıntılı kayıtları daha üst bir seviyede toplar; günlük satışlardan aylık toplam çıkarmak buna örnek.&lt;/p&gt;

&lt;p&gt;Bu geçişlerin mümkün olması, elimizdeki verinin ayrıntı düzeyine bağlıdır. Yalnızca aylık toplamı sakladıysak, günlük sonuçları o toplamdan geri çıkaramayız.&lt;/p&gt;

&lt;p&gt;Örnekte Ocak ayındaki 450 TL'yi gördükten sonra günlere inebilirim. 12 Ocak'ta 300 TL, 20 Ocak'ta 150 TL meyve satışı görürüm. İki kaydı koruduğum için hem aylık rapor çıkarabiliyorum hem de o toplamın nasıl oluştuğunu inceleyebiliyorum.&lt;/p&gt;

&lt;h4&gt;
  
  
  OLAP İçin Mutlaka Ayrı Bir Sistem Gerekir mi?
&lt;/h4&gt;

&lt;p&gt;OLAP bir kullanım biçimini tarif eder. İlişkisel bir database de &lt;code&gt;JOIN&lt;/code&gt; ve &lt;code&gt;GROUP BY&lt;/code&gt; gibi işlemlerle analitik sorgular çalıştırabilir. Küçük bir veri kümesinde mevcut database üzerinde rapor üretmek yeterli olabilir.&lt;/p&gt;

&lt;p&gt;Ayrı bir analiz sistemi ihtiyacı, veri büyüklüğü ve sorguların yaptığı işle birlikte belirginleşir. Tek bir kaydı index üzerinden bulmakla geniş bir tarih aralığındaki tutarları okumak aynı maliyete sahip değildir. Analitik sorgu, uygulamanın günlük işlemleriyle aynı CPU, bellek ve disk kaynaklarını paylaşabilir.&lt;/p&gt;

&lt;p&gt;Örnekte yıllık raporu PostgreSQL üzerinde çıkarabilirim. Ama rapor sorguları büyüdüğünde yeni siparişlerin yanıt süresini etkileyip etkilemediğine de bakmam gerekir. Fiziksel mağazaların satışlarını rapora eklemek istersem farklı kaynakları birleştirme ihtiyacı da doğar.&lt;/p&gt;

&lt;p&gt;Bu noktada farkın bir bölümü sorgunun kapsamından, bir bölümü de verinin nasıl saklandığından geliyor.&lt;/p&gt;




&lt;h3&gt;
  
  
  Adım 3: Row-Based ve Column-Based Depolama
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Row-based&lt;/strong&gt; ve &lt;strong&gt;column-based storage&lt;/strong&gt;, verinin fiziksel depolama düzenini anlatır. Tablo ekranda yine satır ve sütunlardan oluşabilir; değişen şey, database'in bu alanları depolama katmanında nasıl yerleştirdiğidir.&lt;/p&gt;

&lt;p&gt;Bu ayrım, bir sorgunun ihtiyaç duyduğu verilere ulaşırken ne kadar veri okuduğunu etkileyebilir. Sorgu birkaç kaydın birçok alanını mı istiyor, yoksa çok sayıda kaydın yalnızca birkaç sütununu mu kullanıyor?&lt;/p&gt;

&lt;h4&gt;
  
  
  Row-Based Storage: Aynı Kaydın Alanları Birlikte
&lt;/h4&gt;

&lt;p&gt;Satır odaklı depolamada aynı kayda ait alanlar birlikte yer alır. Belirli bir kaydın farklı alanlarını okumak veya o kayıt üzerinde değişiklik yapmak için uygun bir erişim düzenidir. Geleneksel OLTP sistemlerinde bu yaklaşım yaygındır.&lt;/p&gt;

&lt;p&gt;Satış kayıtlarının ürün, adet ve tutar alanlarını kavramsal olarak şöyle gösterebiliriz:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[Muz,   2, 300]
[Kahve, 1, 120]
[Muz,   1, 150]
[Muz,   1, 150]
[Kahve, 2, 200]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Ahmet'in sipariş kalemini okuduğumda ürün, adet ve fiyat gibi alanları birlikte istiyorum. Satır odaklı yerleşim bu ihtiyaca uyuyor.&lt;/p&gt;

&lt;p&gt;Ancak sadece bütün satış tutarlarını toplamak istersem satırları içeren depolama bloklarından, sorguda kullanmadığım alanları da okumam gerekebilir. Tabloda daha fazla sütun bulundukça bu fark büyüyebilir; gerçek maliyet sorgu planına ve depolama düzenine bağlıdır.&lt;/p&gt;

&lt;h4&gt;
  
  
  Column-Based Storage: Aynı Alanın Değerleri Birlikte
&lt;/h4&gt;

&lt;p&gt;Sütun odaklı depolamada aynı sütuna ait değerler birlikte saklanır. Çok sayıda satırın az sayıda alanını kullanan sorgular, ihtiyaç duyduğu sütunların bloklarını okuyabilir. Benzer değerlerin bir arada bulunması sıkıştırmayı da kolaylaştırabilir.&lt;/p&gt;

&lt;p&gt;Aynı örneğin sütun odaklı gösterimi şöyle olur:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Ürün:         [Muz, Kahve, Muz, Muz, Kahve]
Adet:         [2, 1, 1, 1, 2]
Satış tutarı: [300, 120, 150, 150, 200]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;SUM(sales_amount)&lt;/code&gt; hesaplamak istediğimde yalnızca satış tutarı alanına ihtiyaç duyuyorum. Kategori bazında analiz yapıyorsam ilgili kategori bilgilerini de okurum; müşterinin adresi gibi raporda kullanmadığım alanları taşımam gerekmeyebilir.&lt;/p&gt;

&lt;p&gt;Gerçek sistemlerde sütunlar bloklara ve farklı makinelere dağılabilir. Filtreler veya bölümleme sayesinde sütunun tamamı yerine ilgili bölümünü okumak yeterli olabilir. Çizim, bütün değerlerin tek ve kesintisiz bir yerde durduğunu söylemiyor.&lt;/p&gt;

&lt;p&gt;Sütun odaklı sistemlere yeni veri yazmak da mümkündür. Bu sistemler toplu yüklemeler için optimizasyonlar kullanabilir; sık ve tekil satır güncellemelerinin maliyeti ise farklı olabilir. Aynı şekilde satır odaklı bir sisteme kayıt eklemek, index'ler ve bütünlük kontrolleri nedeniyle yalnızca yeni satırı diske koymaktan ibaret değildir.&lt;/p&gt;

&lt;p&gt;Dolayısıyla “OLTP her zaman satır, OLAP her zaman sütun” kesin bir kural değil. Bu yerleşimler belirli erişim biçimlerini destekleyen yaygın tasarım tercihleridir. Verinin tablolar arasında nasıl modellendiği ise bundan ayrı bir karar; ona warehouse içinde döneceğiz.&lt;/p&gt;




&lt;h3&gt;
  
  
  Adım 4: Data Warehouse Nedir?
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Data warehouse&lt;/strong&gt;, analiz ve raporlama için tasarladığımız veri deposudur. Farklı kaynakların verilerini bir araya getirir, ortak tanımlarla düzenler ve tarihsel karşılaştırmalar için saklar. Kullanıcılar bu hazırlanmış veri üzerinden sorgular çalıştırabilir.&lt;/p&gt;

&lt;p&gt;Buradaki düzen sadece dosyaların veya tabloların aynı yerde durmasıyla oluşmaz. Kaynakların alanlarını ortak anlamlara taşımak, ürün kodlarını eşleştirmek ve rapordaki hesapları tanımlamak gerekir. Bir kaynakta “satış” anlamına gelen kayıt, diğerinde farklı bir aşamayı temsil ediyorsa bunları doğrudan toplamak yanlış sonuç verebilir.&lt;/p&gt;

&lt;p&gt;Warehouse genellikle satış veya finans gibi iş konuları etrafında düzenlenir. Veriyi farklı dönemlerde karşılaştırmak için tarihler ve gerektiğinde değişen bilgilerin geçmiş sürümleri de modelin parçası olur. Günlük uygulamada güncel duruma odaklanırken burada geçmişin hangi haliyle analiz edileceğini ayrıca düşünürüz.&lt;/p&gt;

&lt;p&gt;Bu, warehouse'daki verinin hiçbir zaman değişmediği anlamına gelmez. Yeni kayıtlar yüklenebilir, hatalı veriler düzeltilebilir veya geç gelen bilgiler işlenebilir. Tarihsel analiz için önemli olan, bu değişiklikleri tanımlı kurallarla yönetmektir.&lt;/p&gt;

&lt;h4&gt;
  
  
  Database ile Data Warehouse Arasındaki İlişki
&lt;/h4&gt;

&lt;p&gt;Database, veriyi saklama ve sorgulama için kullandığımız genel kavramdır. Warehouse da veritabanı teknolojileri kullanabilir. “Database mi, warehouse mı?” karşılaştırmasında çoğunlukla uygulamanın operasyonel database'i ile analitik veri deposunu karşılaştırıyoruz.&lt;/p&gt;

&lt;p&gt;Operasyonel database, uygulamanın çalışma akışını destekler. Warehouse'ın modeli ve kaynakları ise raporlama ihtiyacına göre şekillenir. OLTP/OLAP işlem yaklaşımını, operasyonel database/warehouse ayrımı da bu yaklaşımı destekleyen veri düzenini anlatır.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Karşılaştırma&lt;/th&gt;
&lt;th&gt;Operasyonel Database&lt;/th&gt;
&lt;th&gt;Data Warehouse&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Ana ihtiyaç&lt;/td&gt;
&lt;td&gt;Günlük uygulama işlemlerini yürütmek&lt;/td&gt;
&lt;td&gt;Analiz ve raporlama yapmak&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Tipik erişim&lt;/td&gt;
&lt;td&gt;Belirli kayıtlar üzerinde kısa okumalar ve değişiklikler&lt;/td&gt;
&lt;td&gt;Geniş kayıt kümeleri üzerinde gruplama ve hesaplama&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Veri düzeni&lt;/td&gt;
&lt;td&gt;Uygulamanın iş kuralları ve ilişkileri&lt;/td&gt;
&lt;td&gt;Ortak iş tanımları ve analitik model&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Geçmiş&lt;/td&gt;
&lt;td&gt;Uygulamanın ihtiyacına göre saklanır&lt;/td&gt;
&lt;td&gt;Dönem karşılaştırmaları ve geçmişin yorumu için planlanır&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Ahmet'in sipariş durumunu değiştirirken operasyonel database'e gidiyorum. Aylık satış raporunda ise warehouse'daki hazırlanmış satış verisini kullanabilirim. Uygulamanın database'i yıllarca sipariş saklayabilir; warehouse da birkaç dakika önce oluşan satışları içerebilir. Ayrımı yalnızca verinin yaşı belirlemiyor.&lt;/p&gt;

&lt;h4&gt;
  
  
  Satış Analizinde Warehouse Ne Kazandırır?
&lt;/h4&gt;

&lt;p&gt;Warehouse kurarken veri kaynaklarını birleştirmenin yanında analiz yükünü ayrı yönetebiliriz. Bu, günlük uygulama işlemleri ile raporların aynı çalışma kaynakları için yarışmasını azaltmaya yardımcı olur. Karşılığında bir aktarım süreci kurmak, veri güncelliğini izlemek ve iki sistemin maliyetini yönetmek gerekir.&lt;/p&gt;

&lt;p&gt;E-ticaret örneğinde internet siparişleriyle mağaza satışlarını birleştirelim. İnternet sisteminde muzun kodu &lt;code&gt;10&lt;/code&gt;, mağaza dosyasında &lt;code&gt;MUZ-01&lt;/code&gt; olabilir. Ortak ürün eşleştirmesi olmadan rapor bunları iki farklı ürün gibi sayabilir.&lt;/p&gt;

&lt;p&gt;Benzer şekilde satış tutarının anlamını da belirlemem gerekiyor. Bu yazıdaki rapor tamamlanmış sipariş kalemlerinin indirim sonrası ürün tutarını hesaplasın; vergi, kargo ve iadeleri basit örneğin dışında tutalım. Gerçek kullanımda bu alanların her biri için iş kuralı gerekir.&lt;/p&gt;

&lt;p&gt;Bu kararları hazırlık sırasında verdiğimde her rapor aynı satış tanımından hareket edebilir. Şimdi bu ortak veriyi warehouse içinde nasıl düzenleyeceğimize bakalım.&lt;/p&gt;




&lt;h3&gt;
  
  
  Adım 5: Fact ve Dimension ile Analitik Model Kurmak
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Dimensional modeling&lt;/strong&gt;, analiz için veriyi olaylar ve bu olayları açıklayan bilgiler etrafında düzenleme yaklaşımıdır. Amaç, hangi değerleri ölçtüğümüzü ve bunları hangi kırılımlarda inceleyebileceğimizi modelde görünür hale getirmek.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Fact table&lt;/strong&gt;, satış gibi gerçekleşen olayları ve bu olaylara ait ölçümleri saklar. &lt;strong&gt;Dimension table&lt;/strong&gt; ise olayları tarih, ürün veya müşteri gibi açılardan açıklayan bilgileri tutar. Fact içindeki anahtarlar, ilgili dimension kayıtlarına bağlanır.&lt;/p&gt;

&lt;p&gt;Bu ayrım, raporun her defasında operasyonel sistemdeki bütün ilişkileri çözmesi yerine analize uygun bağlantılarla çalışmasını sağlar. Modelin doğru kurulması performans kadar hesapların anlamını da etkiler.&lt;/p&gt;

&lt;p&gt;Satış örneğinde adet ve tutarı &lt;code&gt;fact_sales&lt;/code&gt; içinde tutabiliriz. Ürünün adı ve kategorisi &lt;code&gt;dim_product&lt;/code&gt;, tarih bilgileri ise &lt;code&gt;dim_date&lt;/code&gt; içinde yer alabilir. Böylece satış miktarını ürün veya zaman kırılımıyla birlikte sorgulayabiliriz.&lt;/p&gt;

&lt;h4&gt;
  
  
  Grain: Bir Satır Tam Olarak Neyi Temsil Ediyor?
&lt;/h4&gt;

&lt;p&gt;&lt;strong&gt;Grain&lt;/strong&gt;, fact table'daki bir satırın temsil ettiği ayrıntı düzeyidir. Satır bir siparişin tamamını, tek bir sipariş kalemini veya günlük kategori toplamını temsil edebilir. Bu kararı baştan vermek, hangi measure'ları hangi düzeyde tutabileceğimizi belirler.&lt;/p&gt;

&lt;p&gt;Farklı ayrıntı seviyelerini aynı hesapta karıştırırsak veriyi yanlış sayabiliriz. Bir siparişin toplamını her ürün kalemine tekrar yazmak, kalemleri topladığımızda aynı sipariş tutarını birkaç kez hesaba katmamıza neden olur.&lt;/p&gt;

&lt;p&gt;Bizim &lt;code&gt;fact_sales&lt;/code&gt; tablomuzda her satır tek bir sipariş kalemini temsil etsin. Ahmet'in iki paket muz aldığı kalem için adet &lt;code&gt;2&lt;/code&gt;, tutar &lt;code&gt;300&lt;/code&gt; olur. Aynı siparişte kahve de olsaydı kahve ayrı bir satırda dururdu; iki satırın tutarlarını topladığımda siparişin ürün toplamına ulaşabilirdim.&lt;/p&gt;

&lt;p&gt;Warehouse'da yalnızca günlük veya aylık toplam tutmamız gerekmez. Bu ayrıntılı kayıtları koruyarak farklı seviyelerde rapor çıkarabiliriz. Sık kullanılan sonuçlar için ayrıca özet tablolar hazırlamak mümkün; hangi ayrıntıyı saklayacağımızla hangi hesabı önceden yapacağımız ayrı kararlar.&lt;/p&gt;

&lt;h4&gt;
  
  
  Star Schema: Fact ve Dimension Tablolarını Bağlamak
&lt;/h4&gt;

&lt;p&gt;&lt;strong&gt;Star schema&lt;/strong&gt;, dimension tablolarının merkezdeki fact table'a doğrudan bağlandığı modeldir. Fact, olayın ölçümlerini ve dimension anahtarlarını tutar; dimension tablolarındaki alanlar filtreleme ve gruplama için kullanılır.&lt;/p&gt;

&lt;p&gt;Dimension anahtarı, kaynak uygulamadaki kimlikle aynı olmak zorunda değildir. Warehouse içinde ayrı bir anahtar üretebiliriz. Bu, farklı kaynakların kayıtlarını eşleştirmeyi ve gerektiğinde bir varlığın geçmiş sürümlerini ayrı tutmayı kolaylaştırır.&lt;/p&gt;

&lt;p&gt;Örnekteki model şöyle görünebilir:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;dim_date                  fact_sales                  dim_product
  date_key          ←       date_key
  full_date                 product_key         →       product_key
  year                      order_item_id               product_id
  month                     quantity                    product_name
                            sales_amount                category_name
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;date_key&lt;/code&gt;, satışın ait olduğu tarih kaydına; &lt;code&gt;product_key&lt;/code&gt; ise ürün dimension'ındaki kayda bağlanıyor. Bu örnekte satış tarihi için siparişin oluşturulma tarihini kullanıyoruz. Başka bir rapor ödeme veya teslim tarihini esas alabilir; tarih seçimi de raporun tanımına ait.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;dim_date&lt;/code&gt; içinde yıl ve ayın yanında hafta veya hafta sonu bilgisi de saklayabilirim. Böylece aynı satış verisini farklı zaman kırılımlarında inceleyebilirim. &lt;code&gt;dim_product&lt;/code&gt; ise ürün adıyla birlikte kategoriyi veriyor.&lt;/p&gt;

&lt;h4&gt;
  
  
  Denormalization: Analizde Gereken Bilgileri Bir Araya Getirmek
&lt;/h4&gt;

&lt;p&gt;&lt;strong&gt;Denormalization&lt;/strong&gt;, belirli sorguların ihtiyaç duyduğu bilgileri daha az ilişki üzerinden okuyabilmek için bazı verileri bir arada tutma veya kontrollü biçimde tekrar etme yaklaşımıdır. Normalization ile farklı bir kullanım ihtiyacına cevap verir.&lt;/p&gt;

&lt;p&gt;Analitik modelde dimension'ların açıklayıcı alanlarını bir arada tutmak yaygındır. Bunun karşılığında veri hazırlama sırasında tekrarların doğru güncellenmesini yönetmemiz gerekir. Join sayısının azalması bazı sorguları kolaylaştırabilir, ancak daha az normalization her koşulda daha yüksek hız garantisi vermez.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Snowflake schema&lt;/strong&gt;, bazı dimension bilgilerini ayrı ilişkili tablolara bölerek daha normalize biçimde düzenleyen modeldir. Veri tekrarını azaltabilir ama sorguda ek bağlantılar gerektirir. Star schema ile arasındaki fark, dimension bilgilerinin ne kadar ayrıştırıldığıyla ilgilidir.&lt;/p&gt;

&lt;p&gt;Operasyonel modelde ürün ile kategoriyi ayrı tablolarda tutmuştuk. Warehouse'da &lt;code&gt;category_name&lt;/code&gt; alanını &lt;code&gt;dim_product&lt;/code&gt; içine koyabiliriz. Böylece rapor, ürün dimension'ına bağlandığında kategori bilgisine de ulaşır. Kategoriyi ayrı bir dimension olarak tutup ürün dimension'ını ona bağlarsam snowflake yaklaşımına geçmiş olurum.&lt;/p&gt;

&lt;p&gt;Bizim basit satış raporumuzda star schema ile ilerleyebiliriz. Fact ile dimension tablolarını yine join edeceğiz. OLAP sistemlerinin join yapmadığı gibi bir kural yok; modelde analizin ihtiyaç duyduğu ilişkileri kullanıyoruz.&lt;/p&gt;

&lt;h4&gt;
  
  
  Değişen Dimension Bilgilerinin Geçmişini Korumak
&lt;/h4&gt;

&lt;p&gt;&lt;strong&gt;Slowly Changing Dimensions (SCD)&lt;/strong&gt;, dimension alanları değiştiğinde geçmişi nasıl yöneteceğimizi ele alan yöntemlerdir. Yeni değerin eski kaydın üzerine yazılmasıyla ayrı bir sürüm olarak saklanması, raporun geçmişi yorumlama biçimini değiştirir.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Type 1&lt;/strong&gt; yaklaşımı eski değeri günceller ve o alanın geçmiş sürümünü korumaz. &lt;strong&gt;Type 2&lt;/strong&gt; ise yeni bir dimension satırı açarak sürümleri saklar; geçerlilik tarihleriyle hangi sürümün hangi döneme ait olduğunu belirleyebiliriz.&lt;/p&gt;

&lt;p&gt;Kahveyi bugün “İçecek” kategorisinden “Kahve ve Çay” kategorisine taşıdığımızı düşünelim. Type 1 kullandığımda geçmiş satışlar da güncel kategori altında görünebilir. Type 2 kullandığımda ise yeni kategori için ayrı bir ürün sürümü oluşturur, eski satışları eski sürüme bağlı tutarım.&lt;/p&gt;

&lt;p&gt;Hangi davranışın doğru olduğu raporun sorusuna bağlı. Geçmişi o dönemdeki sınıflandırmayla mı görmek istiyorum, yoksa bütün yılları bugünkü kategorilere göre mi karşılaştıracağım? Warehouse kurarken bunu da belirlemem gerekiyor.&lt;/p&gt;

&lt;p&gt;Artık satışın ne anlama geldiğini ve onu hangi modelde saklayacağımızı biliyoruz. Sıradaki iş, kaynak veriyi bu modele ulaştırmak.&lt;/p&gt;




&lt;h3&gt;
  
  
  Adım 6: ETL ve ELT ile Veriyi Taşımak ve Dönüştürmek
&lt;/h3&gt;

&lt;p&gt;Veriyi kaynak sistemlerden alıp başka bir veri deposuna hazırlayan akışlara &lt;strong&gt;data pipeline&lt;/strong&gt; diyebiliriz. Bu akış veri çekme, kontrol etme ve hedefe yazma gibi işleri birbirine bağlar. Kaynak bir database olabileceği gibi bir API veya dosya da olabilir.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Extract&lt;/strong&gt;, veriyi kaynaktan alma adımıdır. &lt;strong&gt;Transform&lt;/strong&gt;, veriyi hedef kullanım için dönüştürmeyi; &lt;strong&gt;Load&lt;/strong&gt; ise hedefe yüklemeyi ifade eder. Tarih formatlarını eşitlemek, kaynak ürün kodlarını eşleştirmek veya satış tutarını hesaplamak dönüşüme örnek.&lt;/p&gt;

&lt;p&gt;ETL ve ELT, bu adımların hangi sırayla ve nerede gerçekleştiğini ayırır. İkisinde de analize uygun veri üretmek istiyoruz; dönüşümün hedefe yüklemeden önce veya sonra yapılması çalışma biçimini değiştiriyor.&lt;/p&gt;

&lt;h4&gt;
  
  
  ETL: Önce Dönüştürmek, Sonra Yüklemek
&lt;/h4&gt;

&lt;p&gt;&lt;strong&gt;ETL (Extract, Transform, Load)&lt;/strong&gt; akışında veriyi alır, hedefe yüklemeden önce bir işlem ortamında dönüştürür ve hazırladığımız sonucu hedefe yazarız. Dönüşüm, hedef warehouse'ın dışında çalışan bir işlem tarafından yürütülebilir.&lt;/p&gt;

&lt;p&gt;Bu düzen, hedefe hangi verinin hangi biçimde gireceğini yükleme öncesinde kontrol etmeyi sağlar. Dönüşüm ortamının işlem kapasitesi ve hata durumları da akışın parçasıdır; veriyi almakla başarıyla yüklemek arasında izlememiz gereken işler vardır.&lt;/p&gt;

&lt;p&gt;Sipariş örneğinde &lt;code&gt;orders&lt;/code&gt; ile &lt;code&gt;order_items&lt;/code&gt; kayıtlarını alıp ilgili alanları birleştirebilirim. Tamamlanmış siparişleri seçer, &lt;code&gt;quantity × unit_price&lt;/code&gt; ile ürün tutarını hesaplar ve uygun dimension anahtarlarını bulurum. Ardından hazırladığım satış satırlarını &lt;code&gt;fact_sales&lt;/code&gt; tablosuna yazarım.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Operasyonel Database
        ↓ Extract
Siparişleri ve sipariş kalemlerini al
        ↓ Transform
Kontrol et, birleştir, tutarı hesapla, anahtarları eşleştir
        ↓ Load
Hazırlanan kayıtları fact_sales tablosuna yükle
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Ahmet'in iki paketlik sipariş kalemi bu akışın sonunda &lt;code&gt;quantity = 2&lt;/code&gt; ve &lt;code&gt;sales_amount = 300&lt;/code&gt; değerleriyle warehouse'a ulaşır.&lt;/p&gt;

&lt;h4&gt;
  
  
  ELT: Önce Yüklemek, Sonra Hedefte Dönüştürmek
&lt;/h4&gt;

&lt;p&gt;&lt;strong&gt;ELT (Extract, Load, Transform)&lt;/strong&gt; akışında veriyi önce hedef sistemin ham veri alanına yükler, dönüşümleri daha sonra o sistemin işlem gücüyle yaparız. Warehouse içinde çalışan SQL sorguları, ham tablolardan analitik tablolar üretebilir.&lt;/p&gt;

&lt;p&gt;Ham veriyi almakla analize hazır veri sunmak farklı aşamalardır. Kaynak tabloların hedefe yüklenmesi, raporun bunları doğrudan kullanması gerektiği anlamına gelmez. Kontroller ve iş kuralları, dönüşüm sırasında yine uygulanır.&lt;/p&gt;

&lt;p&gt;Örnekte sipariş ve sipariş kalemi tablolarını warehouse'ın ham alanına alabilirim. Ardından orada çalışan sorgularla tamamlanmış siparişleri seçer, fiyat hesaplarını yapar ve &lt;code&gt;fact_sales&lt;/code&gt; tablosunu oluştururum:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Operasyonel Database
        ↓ Extract
Kaynak kayıtları al
        ↓ Load
Warehouse'ın ham veri alanına yükle
        ↓ Transform
Hedefteki sorgularla dimension ve fact tablolarını hazırla
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;ETL ile aynı satış sonucunu elde edebilirim. Fark, dönüşümün warehouse'a girmeden önce mi yoksa girdikten sonra mı çalıştığı.&lt;/p&gt;

&lt;p&gt;Bu yöntemler sadece OLTP'den OLAP'e geçişte kullanılmaz. Mağazanın gönderdiği bir CSV dosyasını temizleyip yüklemek veya bir API'den gelen kayıtları hedef tabloya hazırlamak da ETL ya da ELT akışı olabilir.&lt;/p&gt;

&lt;h4&gt;
  
  
  Staging ve Data Lake Hangi İhtiyacı Karşılıyor?
&lt;/h4&gt;

&lt;p&gt;&lt;strong&gt;Staging&lt;/strong&gt;, gelen veriyi karşılamak ve sonraki işlem için hazırlamak amacıyla kullandığımız ara alanı ifade eder. Kayıtları kontrol etmek veya bir yükleme grubunu diğerinden ayırmak için kullanılabilir. ETL ve ELT akışlarının ikisinde de staging bulunabilir.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Data lake&lt;/strong&gt; ise farklı türlerdeki veriyi, çoğu zaman kaynakta gelen ham biçimini de koruyarak saklayabildiğimiz alandır. Tablo verilerinin yanında JSON event'leri, görseller veya ses dosyaları da burada yer alabilir. Veriyi ham saklamak, ne içerdiğini belgelemek ve erişimini yönetmek ihtiyacını ortadan kaldırmaz.&lt;/p&gt;

&lt;p&gt;Satış örneğinde mağazanın gönderdiği CSV dosyasını önce staging alanına alıp satırlarını kontrol edebilirim. Dosyanın orijinal halini ileride tekrar işlemek için data lake içinde saklamayı da tercih edebilirim. Böylece dönüşüm kuralını değiştirdiğimde kaynak dosyaya dönebilirim.&lt;/p&gt;

&lt;p&gt;Her aktarımda ayrı bir data lake kurmak zorunda değilim. Veriyi doğrudan warehouse'a yükleyebilirim; bu bileşenlerin yerini, saklama ve yeniden işleme ihtiyacı belirler.&lt;/p&gt;




&lt;h3&gt;
  
  
  Adım 7: Verinin Güncelliğini ve Aktarımın Doğruluğunu Yönetmek
&lt;/h3&gt;

&lt;p&gt;Bir pipeline'ın dönüşüm sırası kadar ne zaman çalıştığı da önemlidir. Raporun veri güncelliği, kaynaktaki değişikliklerin analitik sisteme ne kadar gecikmeyle ulaştığına bağlıdır.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Batch processing&lt;/strong&gt;, belirli bir veri grubunu toplu işlemektir. İş saatlik veya günlük çalışabilir; bir dosyanın gelmesi de işlemi başlatabilir. &lt;strong&gt;Stream processing&lt;/strong&gt; ise sürekli gelen veri akışını işleme yaklaşımıdır. Her kaydın sıfır gecikmeyle sonuçlanacağını garanti etmez; hedef daha kısa gecikmelerle akışı işlemektir.&lt;/p&gt;

&lt;p&gt;Günlük satış raporunda siparişleri gece toplu aktarmak yeterli olabilir. Ahmet'in bugün verdiği sipariş, aktarım tamamlandıktan sonra yarınki raporda görünür. Satış ekranının gün içinde güncellenmesi gerekiyorsa daha sık yükleme veya değişiklikleri sürekli işleyen bir akış seçebilirim.&lt;/p&gt;

&lt;p&gt;ETL/ELT ile batch/stream farklı kararları anlatır. İlki dönüşümün yüklemeye göre sırasıyla, ikincisi veriyi işleme biçimiyle ilgilidir.&lt;/p&gt;

&lt;h4&gt;
  
  
  Aynı Veriyi Tekrar Aktardığımızda Ne Olur?
&lt;/h4&gt;

&lt;p&gt;&lt;strong&gt;Artımlı yükleme&lt;/strong&gt;, her seferinde bütün kaynağı almak yerine yeni veya değişen kayıtları aktarma yaklaşımıdır. Bunun için hangi değişikliklerin gerçekleştiğini takip etmemiz gerekir. Güncellemeler kadar silmelerin hedefe nasıl yansıtılacağını da belirlemeliyiz.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;CDC (Change Data Capture)&lt;/strong&gt;, kaynaktaki ekleme, güncelleme ve silmeleri yakalamaya yarayan yaklaşımı ifade eder. Bu değişiklikleri veri aktarımına girdi olarak kullanabiliriz. Hangi yöntemi seçeceğimiz, kaynak sistemin sunduğu imkanlara ve gerekli güncelliğe bağlıdır.&lt;/p&gt;

&lt;p&gt;Örnekte her gece bütün sipariş geçmişini kopyalamak yerine yeni ve değişen siparişleri alabilirim. Ancak aktarım yarıda kalırsa aynı kayıtları yeniden işlemek gerekebilir. Ahmet'in siparişini ikinci kez eklersem 300 TL'lik satış raporda 600 TL görünür.&lt;/p&gt;

&lt;p&gt;Bu yüzden kaydı hangi anahtarla tanıyacağımı ve tekrar geldiğinde nasıl davranacağımı belirlemem gerekiyor. Tek kaynaklı örnekte &lt;code&gt;order_item_id&lt;/code&gt; satış kalemini tanıyabilir; farklı kaynakları birleştirdiğimde kaynak kimliğini de dikkate almam gerekir. Bir mağazanın &lt;code&gt;1042&lt;/code&gt; numaralı kaydı, internet sistemindeki aynı numarayla aynı olay olmayabilir.&lt;/p&gt;

&lt;p&gt;Pipeline'ın başarılı çalışması da yalnızca hata vermeden bitmesi değildir. Beklenen veriyi alıp almadığını kontrol etmeli, eksik veya başarısız aktarımda raporu güncelmiş gibi göstermemeliyim. Verinin doğruluğunu korumak, rapor sorgusundan önce başlayan bir iş.&lt;/p&gt;




&lt;h3&gt;
  
  
  Adım 8: BI ve Data Mart ile Hazırlanan Veriyi Kullanmak
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;BI (Business Intelligence)&lt;/strong&gt;, veriyi raporlar ve analizlerle iş kararlarında kullanılabilir hale getiren çalışmaların ve araçların genel adıdır. Bir BI aracı veri kaynağına bağlanabilir, ölçümleri hesaplayabilir ve sonuçları grafik veya dashboard olarak sunabilir.&lt;/p&gt;

&lt;p&gt;Warehouse bu kullanım için düzenlenmiş veriyi sağlar. Rapor katmanı da bu veri üzerinde hangi measure'ların nasıl gösterileceğini belirler. Veri doğru gelse bile raporda farklı filtre veya hesap kullanmak sonucu değiştirebilir; modelin anlamını raporda da korumak gerekir.&lt;/p&gt;

&lt;p&gt;Örnekte artık başta sorduğumuz soruya dönebiliriz:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;2025 yılında aylara göre hangi ürün kategorisinden ne kadar satış yaptık?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Hazırladığımız modelde sorgu şöyle olabilir:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt;
    &lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;year&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;month&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;category_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;SUM&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sales_amount&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;total_sales&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;fact_sales&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;
&lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="n"&gt;dim_date&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;date_key&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;date_key&lt;/span&gt;
&lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="n"&gt;dim_product&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;product_key&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;product_key&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;year&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;2025&lt;/span&gt;
&lt;span class="k"&gt;GROUP&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;year&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;month&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;category_name&lt;/span&gt;
&lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;year&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;month&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;total_sales&lt;/span&gt; &lt;span class="k"&gt;DESC&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;fact_sales&lt;/code&gt;, sipariş kalemlerinin tutarını veriyor. &lt;code&gt;dim_date&lt;/code&gt; ile yıl ve ayı, &lt;code&gt;dim_product&lt;/code&gt; ile kategoriyi öğreniyoruz. &lt;code&gt;WHERE&lt;/code&gt; yalnızca 2025'i seçiyor; &lt;code&gt;GROUP BY&lt;/code&gt; ise aynı ay ve kategoriye ait satışları birlikte hesaplamamızı sağlıyor.&lt;/p&gt;

&lt;p&gt;İkinci bölümdeki beş örnek kayıtla beklenen sonuç şu olur:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Yıl&lt;/th&gt;
&lt;th&gt;Ay&lt;/th&gt;
&lt;th&gt;Kategori&lt;/th&gt;
&lt;th&gt;Toplam satış&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;2025&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;Meyve&lt;/td&gt;
&lt;td&gt;450 TL&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2025&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;İçecek&lt;/td&gt;
&lt;td&gt;120 TL&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2025&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;İçecek&lt;/td&gt;
&lt;td&gt;200 TL&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2025&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;Meyve&lt;/td&gt;
&lt;td&gt;150 TL&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Ocak ayındaki 450 TL'nin 300 TL'si Ahmet'in siparişinden, kalan 150 TL'si aynı ayın diğer muz satışından geliyor. Ahmet'in kaydı warehouse'da ayrı bir satış kalemi olarak duruyor; raporda ise başka kayıtlarla aynı grupta yer alıyor.&lt;/p&gt;

&lt;p&gt;Bir BI aracı bu sonucu aylık kategori grafiği olarak gösterebilir. Günlük ayrıntıya indiğimde yıl ve ayın yanında gün bilgisini de kullanırım. Bunun mümkün olması, satışları sipariş kalemi düzeyinde saklamamıza bağlı.&lt;/p&gt;

&lt;h4&gt;
  
  
  Data Mart: Belirli Bir Konuya Odaklanmak
&lt;/h4&gt;

&lt;p&gt;&lt;strong&gt;Data mart&lt;/strong&gt;, satış veya finans gibi belirli bir konuya odaklanan analitik veri kümesidir. Bir warehouse'ın ilgili bölümünden oluşturulabilir; ayrı bir depolama yapısı veya mantıksal veri modeli olarak sunulabilir. Her mart'ın mutlaka merkezi bir warehouse'dan gelmesi gerekmez.&lt;/p&gt;

&lt;p&gt;Amaç, belirli kullanıcıların ilgilendiği verilere ve tanımlara daha kolay ulaşmasını sağlamaktır. Konuya odaklanmak karmaşıklığı azaltabilir; erişim güvenliğini ise ayrıca yetkilendirme kurallarıyla uygulamamız gerekir.&lt;/p&gt;

&lt;p&gt;Bizim örnekte warehouse ileride satış dışındaki konuları da içerebilir. Satış ekibine &lt;code&gt;fact_sales&lt;/code&gt; ve ilgili dimension'ları içeren bir satış mart'ı sunarak raporlarında kullanacakları modeli daraltabilirim. İlgisiz tabloları incelemek zorunda kalmazlar.&lt;/p&gt;

&lt;p&gt;Diğer verilere erişmelerini engellemek istiyorsam gerekli izinleri de tanımlarım. Bir veri kümesine data mart adını vermek, tek başına güvenlik sınırı oluşturmuyor.&lt;/p&gt;

&lt;p&gt;Ahmet'in siparişini uygulamada kaydetmekle başlayan yol, böylece satış raporuna ulaşıyor. İşlem sırasında gereken ayrıntıları OLTP tarafında yönettik; analize uygun modeli hazırlayıp OLAP sorgusuyla sonucu çıkardık.&lt;/p&gt;




&lt;h2&gt;
  
  
  Kendime Notlar
&lt;/h2&gt;

&lt;p&gt;Bu konuları çalışırken OLTP, OLAP ve warehouse başta ayrı araç isimleri gibi görünüyordu. Aynı siparişi takip ettiğimde, bunların hangi ihtiyaca cevap verdiği daha anlaşılır hale geldi.&lt;/p&gt;

&lt;p&gt;OLTP tarafında kısa süren okumalar ve değişiklikler var. OLAP tarafında ise birçok kaydı birlikte hesaplayıp sonucu farklı kırılımlarda inceliyoruz. Bu yüzden yalnızca “write için biri, read için diğeri” demek, iki taraftaki işlemleri açıklamaya yetmiyor.&lt;/p&gt;

&lt;p&gt;Row-based ve column-based depolama, hangi alanlara nasıl eriştiğimi anlamama yardımcı oluyor. Normalization ve denormalization ise veriyi hangi ilişkilerle düzenlediğimle ilgili. Fiziksel depolamayla veri modelini ayrı düşündüğümde, tercihlerinin nedenlerini daha rahat takip edebiliyorum.&lt;/p&gt;

&lt;p&gt;Warehouse kurarken veriyi taşımak kadar raporun anlamını belirlemek de gerekiyor. Ahmet'in satış anındaki fiyatını saklamasaydım yanlış tutar hesaplayabilirdim; aynı kalemi aktarımda iki kez ekleseydim toplamı şişirirdim. Raporun doğru olması, sorgudan önceki adımların da doğru olmasına bağlı.&lt;/p&gt;

&lt;p&gt;Küçük bir uygulamada mevcut database üzerinde rapor çıkarmak yeterli olabilir. Analiz yükü, veri kaynakları ve tarihsel karşılaştırma ihtiyacı büyüdükçe ayrı bir warehouse'ın sağladığı düzen daha anlamlı hale geliyor.&lt;/p&gt;

&lt;p&gt;Bir veri sistemiyle çalışırken önce hangi işlemi tamamlamak veya hangi soruyu cevaplamak istediğime bakmak istiyorum. Depolama biçimi, model ve aktarım kararları da bu sorunun ardından geliyor.&lt;/p&gt;

</description>
      <category>data</category>
      <category>database</category>
      <category>dataengineering</category>
      <category>architecture</category>
    </item>
    <item>
      <title>Koddan Deployment'a</title>
      <dc:creator>TAHA TÜZEL</dc:creator>
      <pubDate>Tue, 06 Oct 2026 15:46:23 +0000</pubDate>
      <link>https://dev.to/tahatuzel/koddan-deploymenta-18kj</link>
      <guid>https://dev.to/tahatuzel/koddan-deploymenta-18kj</guid>
      <description>&lt;p&gt;Lokal ortamda frontend'i, backend'i ve veritabanını ayrı ayrı çalıştırıyorum. Hepsi benim bilgisayarımda sorunsuz açılıyor; peki aynı uygulamayı başka bir makineye taşıdığımda ne değişiyor? Runtime sürümü, bağlantı adresleri ve servislerin birbirini bulması da artık benim sorumluluğumda.&lt;/p&gt;

&lt;p&gt;Docker, test, CI/CD ve deployment başlıklarını çalışırken bunları kodun izlediği tek bir yol olarak anlamak istedim. Image'ı sunucuya nasıl taşıyorum, yeni sürüm eskisinin yerini nasıl alıyor, bir şey ters giderse bunu hangi kontrol gösteriyor?&lt;/p&gt;

&lt;p&gt;Bu sorular için Nuxt, .NET API ve PostgreSQL kullanan küçük bir To-do uygulaması hazırladım. &lt;strong&gt;Git to Prod&lt;/strong&gt; projesinde uygulamanın kendisini basit tuttum; asıl odağım kodu paketleyip test etmek ve K3s'e kadar götürmek oldu. Yazıda önce çalışma notlarımdaki teoriyi anlatıp her adımda projeden kod veya deployment denemesiyle örnekleyeceğim.&lt;/p&gt;

&lt;p&gt;Bu nedenle anlatı container ve image ile başlayıp testlere, CI/CD'ye ve K3s deployment'ına ilerliyor. Her bölümde aynı soruyu takip edeceğim: Bu aşama zincire ne katıyor ve projedeki kodda bunu nerede görebiliyorum?&lt;/p&gt;




&lt;h3&gt;
  
  
  Büyük Resim: Kod Nasıl Çalışan Uygulamaya Dönüşüyor?
&lt;/h3&gt;

&lt;p&gt;Yazdığımız kodun kullanıcıya ulaşmasını kabaca şöyle düşünebiliriz:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Kod değişikliği
    ↓
Otomatik kontroller ve testler
    ↓
Uygulamayı container image olarak paketleme
    ↓
Image'ı saklanacağı depoya (registry) gönderme
    ↓
Hedef ortamda yeni sürümü çalıştırma (deployment)
    ↓
Yeni sürümün çalıştığını kontrol etme
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Bu, &lt;strong&gt;Git to Prod&lt;/strong&gt; projesinde kurduğum akışın da özeti. Gerçek pipeline biraz daha somut ilerliyor:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Pull request
    ↓
Backend ve frontend kontrolleri
    ↓
main'e push veya elle Release başlatma
    ↓
Kontrolleri tekrar çalıştırma
    ↓
EF Core migration → PostgreSQL VM
    ↓
Commit SHA etiketli image'lar → GHCR
    ↓
Manifestleri K3s'e uygulama ve rollout'u bekleme
    ↓
ALB üzerinden ana sayfa ve API kontrolü
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Yazının ana hattı bu: Her adım bir sonraki adıma bir çıktı bırakıyor. Testler kodun seçilmiş davranışlarını kontrol ediyor; Docker bu kodu image'a paketliyor; registry belirli image sürümünü saklıyor; K3s de manifestteki sürümü çalıştırmaya çalışıyor. Son HTTP kontrolü, zincirin dışarıdan erişilen ucunun da yanıt verdiğini gösteriyor.&lt;/p&gt;

&lt;p&gt;Bir de veritabanı şeması değişebilir. Bu durumda uygulama koduyla birlikte şema güncellemesinin sırasını da düşünmem gerekiyor; deployment bölümünde buna döneceğim.&lt;/p&gt;




&lt;h3&gt;
  
  
  Adım 1: Docker ve Container Mantığı
&lt;/h3&gt;

&lt;p&gt;Kaynak kodunu başka bir makineye kopyalamak, uygulamanın orada çalışacağı anlamına gelmiyor. O makinede farklı bir runtime sürümü olabilir; ihtiyaç duyduğum kütüphaneler de kurulu olmayabilir.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Benim bilgisayarımda çalışıyordu.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;Container (konteyner)&lt;/strong&gt;, uygulamayı izole bir process olarak çalıştırır. Uygulama dosyalarını ve bağımlılıklarını bir &lt;strong&gt;image&lt;/strong&gt; içinde paketlediğimde hedef makinede aynı dosyaları yeniden kurmak yerine bu image'dan container başlatabilirim.&lt;/p&gt;

&lt;p&gt;İzolasyon, farklı uygulamaların kendi bağımlılıklarıyla aynı makinede çalışmasını sağlar. Container'lar host'un CPU ve RAM'ini paylaşır; process ve dosya alanlarını ayırır.&lt;/p&gt;

&lt;p&gt;VM kendi işletim sistemini çalıştırır. Linux container'ları ise aynı Linux kernel'ini paylaşır; her container için baştan ayrı bir işletim sistemi açılmaz. Windows ve macOS üzerinde Docker Desktop, Linux container'ları sanallaştırılmış Linux ortamında çalıştırır.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Docker&lt;/strong&gt;, image hazırlamak ve container çalıştırmak için kullandığım araçları sağlar. Paket aynı kalabilir, ama uygulamanın bağlanacağı veritabanı ve ağ erişimini yine ayarlamam gerekir.&lt;/p&gt;

&lt;h4&gt;
  
  
  Dockerfile, Image ve Container Arasındaki İlişki
&lt;/h4&gt;

&lt;p&gt;Bu kavramların birbiriyle ilişkisi şöyle:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Kavram&lt;/th&gt;
&lt;th&gt;Görevi&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Dockerfile&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Image'ı hazırlamak için gereken komutları tarif eder.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Image&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Uygulama dosyalarını ve çalışma ortamını içeren pakettir.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Container&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Image'dan başlattığımız uygulamanın izole çalışma ortamıdır.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Dockerfile → docker build → Image → docker run → Container
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;docker build&lt;/code&gt;, Dockerfile'daki talimatlarla image hazırlar; &lt;code&gt;docker run&lt;/code&gt; bu image'dan container oluşturup başlatır.&lt;/p&gt;

&lt;p&gt;Aynı image'dan birden fazla container başlatabilirim. Her biri kendi process'lerini ve yazılabilir katmanını kullanır. Bu ayrım, uygulamanın değişmeyen paketiyle çalışırken ürettiği veriyi ayrı düşünmemi sağlıyor.&lt;/p&gt;

&lt;h4&gt;
  
  
  Image Katmanları ve Build Cache
&lt;/h4&gt;

&lt;p&gt;Dockerfile'daki komutların sırası build süresini etkiler. Docker her adımın sonucunu saklayabilir; bir adım değiştiğinde sonraki adımların da yeniden çalışması gerekebilir.&lt;/p&gt;

&lt;p&gt;Örneğin önce paket listesini kopyalayıp bağımlılıkları yükler, sonra kaynak kodu kopyalarsam yalnızca kod değiştiğinde bağımlılık kurulumunu tekrar etmeyebilirim. Projedeki frontend Dockerfile'ında bu sıralama var:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; package.json package-lock.json ./&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;--global&lt;/span&gt; npm@11.17.0 &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; npm ci

&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; . ./&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;npm run build
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Bu sıralama, kaynak dosyaları her değiştiğinde bağımlılıkları baştan kurmamamı sağlıyor. Docker önce değişmeyen paket listesi adımını önbellekten kullanabilir.&lt;/p&gt;

&lt;p&gt;Build sırasında gereksiz dosyaları &lt;code&gt;.dockerignore&lt;/code&gt; ile dışarıda bırakıyorum. Özellikle parola içeren yerel ayar dosyalarını image'a kopyalamıyorum.&lt;/p&gt;

&lt;h4&gt;
  
  
  Derleme Ortamı ile Çalışma Ortamını Ayırmak
&lt;/h4&gt;

&lt;p&gt;Derleme araçlarını son image'a taşımam gerekmiyor. &lt;strong&gt;Multi-stage build&lt;/strong&gt;, derleme ve çalıştırma aşamalarını ayırıyor; backend Dockerfile'ında önce .NET SDK, ardından yalnızca uygulama runtime'ı kullanılıyor:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;mcr.microsoft.com/dotnet/sdk:10.0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;build&lt;/span&gt;
&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /source&lt;/span&gt;

&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; src/ ./src/&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;dotnet restore ./src/Api/Api.csproj
&lt;span class="k"&gt;RUN &lt;/span&gt;dotnet publish ./src/Api/Api.csproj &lt;span class="se"&gt;\
&lt;/span&gt;    &lt;span class="nt"&gt;--configuration&lt;/span&gt; Release &lt;span class="se"&gt;\
&lt;/span&gt;    &lt;span class="nt"&gt;--no-restore&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;    &lt;span class="nt"&gt;--output&lt;/span&gt; /app/publish
&lt;span class="k"&gt;RUN &lt;/span&gt;&lt;span class="nb"&gt;rm&lt;/span&gt; /app/publish/appsettings.Development.json

&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;mcr.microsoft.com/dotnet/aspnet:10.0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;runtime&lt;/span&gt;
&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /app&lt;/span&gt;

&lt;span class="k"&gt;ENV&lt;/span&gt;&lt;span class="s"&gt; ASPNETCORE_HTTP_PORTS=8080&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --from=build /app/publish ./&lt;/span&gt;

&lt;span class="k"&gt;USER&lt;/span&gt;&lt;span class="s"&gt; $APP_UID&lt;/span&gt;
&lt;span class="k"&gt;EXPOSE&lt;/span&gt;&lt;span class="s"&gt; 8080&lt;/span&gt;
&lt;span class="k"&gt;ENTRYPOINT&lt;/span&gt;&lt;span class="s"&gt; ["dotnet", "Api.dll"]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;İlk aşama kaynak kodunu yayıma hazırlıyor, ikinci aşama bu çıktıyı alıyor. SDK son image'a girmiyor. Container uygulamayı root yetkisi olmayan kullanıcıyla başlatıyor; &lt;code&gt;ENTRYPOINT&lt;/code&gt; ise çalıştırılacak komutu belirliyor.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;EXPOSE 8080&lt;/code&gt;, uygulamanın dinleyeceği port'u belirtir; tek başına dış erişim açmaz.&lt;/p&gt;

&lt;p&gt;Frontend'in nasıl paketleneceği uygulamanın yapısına bağlı. Projedeki Nuxt, sunucu tarafında da kod çalıştırıp API çağrılarını ilettiği için runtime image'ında Node.js bulunuyor.&lt;/p&gt;

&lt;h4&gt;
  
  
  Docker Compose ile Servisleri Birlikte Çalıştırmak
&lt;/h4&gt;

&lt;p&gt;Bir uygulama birden fazla servisten oluştuğunda her container için ayrı komut yazmak zorlaşır. &lt;strong&gt;Docker Compose&lt;/strong&gt;, bir uygulamanın servislerini tek dosyada tanımlayıp birlikte başlatmayı, durdurmayı ve incelemeyi sağlayan araçtır. Hangi image'ın kullanılacağını, çalışma ayarlarını ve servislerin iletişimini burada tarif ederim.&lt;/p&gt;

&lt;p&gt;Compose'ta &lt;strong&gt;servis&lt;/strong&gt;, &lt;code&gt;frontend&lt;/code&gt; veya &lt;code&gt;backend&lt;/code&gt; gibi adı olan bir uygulama bileşeninin tanımıdır. Bu tanımdan container oluşturulur. Compose varsayılan olarak proje için ortak bir ağ oluşturur ve servisler bu ağda isimleriyle birbirine ulaşır.&lt;/p&gt;

&lt;p&gt;Projedeki Compose dosyasının frontend bölümü şöyle:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;frontend&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;context&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;./frontend&lt;/span&gt;
    &lt;span class="na"&gt;dockerfile&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Dockerfile&lt;/span&gt;
  &lt;span class="na"&gt;depends_on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;backend&lt;/span&gt;
  &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;NUXT_API_BASE_URL&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;http://backend:8080&lt;/span&gt;
  &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;3000:3000"&lt;/span&gt;
  &lt;span class="na"&gt;restart&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;unless-stopped&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Bu bölüm &lt;code&gt;services&lt;/code&gt; altında bulunuyor. &lt;code&gt;build.context&lt;/code&gt;, build sırasında erişilebilecek dizini; &lt;code&gt;dockerfile&lt;/code&gt;, talimatların bulunduğu dosyayı seçiyor. &lt;code&gt;environment&lt;/code&gt; çalışma ayarlarını, &lt;code&gt;ports&lt;/code&gt; host üzerinden erişim eşlemesini belirliyor. &lt;code&gt;restart: unless-stopped&lt;/code&gt;, elle durdurulmadığı sürece container için yeniden başlatma politikası tanımlıyor.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;depends_on&lt;/code&gt;, frontend'den önce backend container'ının başlatılmasını sağlar.&lt;/p&gt;

&lt;p&gt;Docker Compose ile tanımlanan bir proje, gerekli bağlantı ayarları ve dış bağımlılıkları hazır olduğunda şu komutla ayağa kaldırılabilir:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker compose up &lt;span class="nt"&gt;--build&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Compose dosyasının bulunduğu dizinde &lt;code&gt;--build&lt;/code&gt; image'ları hazırlıyor, &lt;code&gt;up&lt;/code&gt; container'ları başlatıyor. Çalışan servisleri &lt;code&gt;docker compose ps&lt;/code&gt;, log'ları ise &lt;code&gt;docker compose logs&lt;/code&gt; ile inceleyebilirim.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;docker compose down&lt;/code&gt; ise Compose'un oluşturduğu container'ları ve proje ağını kaldırır; named volume'ları varsayılan olarak silmez.&lt;/p&gt;

&lt;h4&gt;
  
  
  Container Ağı: &lt;code&gt;localhost&lt;/code&gt; Nereye Bakıyor?
&lt;/h4&gt;

&lt;p&gt;Birden fazla container başlattığımda her birinin kendi ağ ortamı ve &lt;code&gt;localhost&lt;/code&gt; adresi bulunur. &lt;code&gt;localhost&lt;/code&gt;, isteği gönderen process'in çalıştığı makineyi ya da container'ı gösterir. Bu yüzden frontend container'ındaki &lt;code&gt;localhost&lt;/code&gt;, backend container'ı değil, frontend'in kendisidir.&lt;/p&gt;

&lt;p&gt;Compose dosyasında servisleri tanımladığımda Compose varsayılan bir network oluşturur ve bu servisleri o ağa bağlar. Aynı network'teki bir servis, diğerine değişebilen IP adresiyle değil servis adıyla ulaşabilir. Docker bu adı ilgili container'ın güncel IP'sine çözer. Projedeki ayarlar bu farkı gösteriyor:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;services&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;backend&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;5157:8080"&lt;/span&gt;
  &lt;span class="na"&gt;frontend&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;NUXT_API_BASE_URL&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;http://backend:8080&lt;/span&gt;
    &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;3000:3000"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Tarayıcı benim bilgisayarımda &lt;code&gt;http://localhost:3000&lt;/code&gt; adresinden Nuxt uygulamasını açar. Görev listesi istenirken tarayıcı aynı adreste &lt;code&gt;/api/todo-items&lt;/code&gt; yoluna request gönderir. Nuxt'un server tarafındaki API route'u bu isteği alır ve backend'e iletir:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Browser
  │ http://localhost:3000/api/todo-items
  ▼
Host'taki 3000 portu → frontend container'ındaki 3000 portu
  │
  ▼
Nuxt server
  │ http://backend:8080/api/todo-items
  ▼
backend servis adı → backend container'ının 8080 portu
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Nuxt burada &lt;strong&gt;reverse proxy&lt;/strong&gt; gibi davranıyor; tarayıcıdan gelen request'i alıp iç network'teki backend'e aktarıyor. Bu nedenle &lt;code&gt;backend:8080&lt;/code&gt; adresini tarayıcının bilmesi gerekmiyor. Bu adresi Nuxt server kullanıyor. Tarayıcı yalnızca kendi açtığı &lt;code&gt;localhost:3000&lt;/code&gt; adresine request gönderiyor.&lt;/p&gt;

&lt;p&gt;Buradaki iki port eşlemesi farklı yerlerden erişim sağlıyor. &lt;code&gt;3000:3000&lt;/code&gt;, benim bilgisayarımdaki 3000 portunu frontend container'ındaki 3000'e; &lt;code&gt;5157:8080&lt;/code&gt; ise host'taki 5157'yi backend container'ındaki 8080'e bağlıyor. Yazım sırası &lt;code&gt;host:container&lt;/code&gt; şeklinde:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;5157:8080"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Dolayısıyla host'taki bir araçla backend'e giderken &lt;code&gt;localhost:5157&lt;/code&gt;, Compose network'ündeki frontend'den giderken &lt;code&gt;backend:8080&lt;/code&gt; kullanırım. Frontend ile backend'in konuşması için backend port'unu host'a yayınlamak şart değil; bu projede &lt;code&gt;ports&lt;/code&gt; eşlemesi ayrıca host'tan test edebilmeyi de sağlıyor. Bir bağlantı hatasında hangi process'in hangi ağda bulunduğunu ve adresi nereden kullandığını kontrol etmek bu yüzden önemli.&lt;/p&gt;

&lt;h4&gt;
  
  
  Container Silinirse Veri Ne Oluyor?
&lt;/h4&gt;

&lt;p&gt;Image, uygulamanın ve çalışma ortamının paketidir; container ise bu image'dan başlatılan çalışan kopyadır. Container çalışırken dosya yazarsa bu değişiklikler container'ın kendi yazılabilir katmanına gider. Container'ı durdurup yeniden başlatmak bu katmanı korur, ancak container'ı silip yenisini oluşturduğumda eski container'a ait katman da silinir. Veritabanı kayıtlarını burada tutarsam yeni sürüme geçerken veriyi kaybedebilirim.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Volume&lt;/strong&gt;, container'ın ömründen ayrı tuttuğum depolama alanıdır. Volume'u container içindeki bir dizine bağlarım; uygulama o dizine yazdığında veri container'ın geçici katmanına değil volume'a gider. Yeni container'ı aynı volume ile başlatınca veriye erişmeye devam ederim.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Named volume&lt;/strong&gt;'u Docker bir isimle yönetir. Container silinse bile volume ayrıca silinmedikçe kalır; yeni container'a aynı named volume'u bağlayabilirim. Bu, veriyi container'dan ayırır ama yedek anlamına gelmez. Volume host'un diskinde duruyorsa host'u kaybetmek volume'u da kaybettirebilir.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Bind mount&lt;/strong&gt; ise host'ta seçtiğim dosya veya dizini container içindeki bir yola bağlar. Kaynağı Docker'ın yönettiği bir alan olarak değil, benim bilgisayarımdaki belirli bir yol olarak belirtirim. Git to Prod README'sinde geliştirme sırasında backend ayar dosyasını container'a bağlamak için şu örnek veriliyor:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;services&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;backend&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;./backend/src/Api/appsettings.Development.json:/app/appsettings.Development.json:ro&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;İki yolun arasındaki &lt;code&gt;:&lt;/code&gt; ayıracının solunda host'taki dosya, sağında container içindeki hedef yol bulunur. Sondaki &lt;code&gt;:ro&lt;/code&gt; mount'u salt okunur yapar. Backend dosyayı container içindeki &lt;code&gt;/app/appsettings.Development.json&lt;/code&gt; yolundan okur; ayar dosyasını image'a kopyalamam veya her değişiklikte image'ı yeniden üretmem gerekmez.&lt;/p&gt;

&lt;p&gt;Bu mount, ana Compose tanımına eklenen yerel geliştirme ayarıdır. README, proje kökünde &lt;code&gt;docker-compose.local.yml&lt;/code&gt; adında ikinci bir dosya oluşturup şu komutla iki tanımı birleştirmeyi öneriyor:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker compose &lt;span class="nt"&gt;-f&lt;/span&gt; docker-compose.yml &lt;span class="nt"&gt;-f&lt;/span&gt; docker-compose.local.yml up &lt;span class="nt"&gt;--build&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;-f&lt;/code&gt; seçenekleri kullanılacak Compose dosyalarını belirtir. Compose bunları birleştirir; ikinci dosyadaki &lt;code&gt;volumes&lt;/code&gt; ayarı backend servisinin ana tanımına eklenir. &lt;code&gt;up&lt;/code&gt; servisleri başlatır, &lt;code&gt;--build&lt;/code&gt; de image'ları oluşturur veya değişiklik varsa yeniden build eder.&lt;/p&gt;

&lt;p&gt;Bu yerel senaryoda PostgreSQL container içinde değil, host makinede çalışıyor. Backend'in bağlantı ayarında README &lt;code&gt;host.docker.internal&lt;/code&gt; adını kullanıyor; Compose dosyasındaki &lt;code&gt;extra_hosts&lt;/code&gt; bu adı host gateway'e yönlendiriyor. Veritabanı ve tablo yapısının önceden hazır olması da gerekiyor. Canlı ortamda ise PostgreSQL'i ayrı bir VM'de tuttum; frontend ve backend container'ları veritabanı dosyalarını saklamıyor.&lt;/p&gt;

&lt;p&gt;Bind mount kaynak kodu geliştirme sırasında container'a göstermek için de işe yarar. Dosya değişikliğinin container içinde görünmesi tek başına uygulamayı yeniden yüklemez; &lt;strong&gt;hot reload&lt;/strong&gt; davranışını uygulamanın geliştirme sunucusu sağlar. Buradaki ayar dosyası mount'u ise yalnızca dosyayı bağlar ve &lt;code&gt;:ro&lt;/code&gt; nedeniyle container'ın bu dosyaya yazmasını engeller.&lt;/p&gt;




&lt;h3&gt;
  
  
  Adım 2: Testing ile Uygulamanın Davranışını Kontrol Etmek
&lt;/h3&gt;

&lt;p&gt;Image'ın build olması, uygulamanın doğru davrandığını göstermiyor. Kod derlenebilir; yine de endpoint yanlış cevap döndürebilir veya veriyi kaydedemeyebilir. Bu nedenle testin kapsamını, doğrulamak istediğim davranışa göre seçiyorum.&lt;/p&gt;

&lt;p&gt;Bir testte önce başlangıç koşullarını hazırlar, ardından davranışı çalıştırıp sonucu beklediğim değerle karşılaştırırım. Bu yapı &lt;strong&gt;Arrange, Act, Assert&lt;/strong&gt; olarak geçiyor: hazırla, çalıştır, doğrula. &lt;strong&gt;Assertion&lt;/strong&gt;, sonuç beklediğimden farklıysa testi başarısız yapan kontroldür.&lt;/p&gt;

&lt;p&gt;Kontrol edeceğim şey yalnızca başarılı sonuç olmayabilir. &lt;strong&gt;Validation&lt;/strong&gt;, gelen verinin kurallara uygunluğunu denetler; zorunlu başlık eksikse görev oluşturmamak da beklenen bir davranıştır. HTTP cevabındaki &lt;strong&gt;status code&lt;/strong&gt; işlemin sonucunu belirtir: &lt;code&gt;201&lt;/code&gt; kaynağın oluştuğunu, &lt;code&gt;404&lt;/code&gt; bulunamadığını gösterir.&lt;/p&gt;

&lt;p&gt;Bu kontrolleri sonraki değişikliklerde yeniden çalıştırınca, daha önce çalışan bir davranışın bozulup bozulmadığını görebilirim. Buna &lt;strong&gt;regression&lt;/strong&gt; kontrolü deniyor; yine de test yalnızca kapsadığı senaryolar hakkında bilgi verir.&lt;/p&gt;

&lt;h4&gt;
  
  
  Unit Test: Bir Parçanın Davranışı
&lt;/h4&gt;

&lt;p&gt;&lt;strong&gt;Unit test&lt;/strong&gt;, küçük bir parçayı bağımlılıklarından ayırarak sınar. Projedeki &lt;strong&gt;handler&lt;/strong&gt;, görev oluşturma işlemini yürüten kod; &lt;strong&gt;repository&lt;/strong&gt; ise kayıt ekleme ve okuma gibi veri erişim işlemlerini sunan parçadır. Handler'ı test ederken gerçek veritabanı yerine fake repository kullanabilirim; böylece kontrol ettiğim davranış handler'ın kendisi olur.&lt;/p&gt;

&lt;p&gt;Projeden bir örnek:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;repository&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;FakeTodoItemRepository&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;TodoItemCreateCommandHandler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;repository&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;TodoItemTestMapper&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Instance&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Handle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;TodoItemCreateCommand&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Read"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"Book"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;CancellationToken&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;None&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="n"&gt;Assert&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;True&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;IsSuccess&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;Assert&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Equal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;TodoItemCreateCommandResponse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"Read"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"Book"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;Assert&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Empty&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Errors&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;Assert&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Single&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;repository&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Items&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Önce fake repository'yi hazırlıyorum, ardından &lt;code&gt;Handle&lt;/code&gt; ile görev oluşturmayı çalıştırıyorum. Assertion'lar hem dönen cevabı hem de repository'ye tek kayıt eklendiğini kontrol ediyor. Gerçek PostgreSQL bağlantısı bu testin kapsamına girmiyor; bu sınır entegrasyon testinde değişiyor.&lt;/p&gt;

&lt;h4&gt;
  
  
  Integration Test: Parçalar Birlikte Çalışıyor mu?
&lt;/h4&gt;

&lt;p&gt;&lt;strong&gt;Entegrasyon testi&lt;/strong&gt;, birden fazla parçanın birlikte çalışıp çalışmadığını sınar. API'ye HTTP request gönderip gerçek veritabanına kayıt yapıldığını kontrol etmek, endpoint ile veri erişimini aynı akışta görmemi sağlar.&lt;/p&gt;

&lt;p&gt;Bu amaçla projede &lt;code&gt;WebApplicationFactory&lt;/code&gt; kullanıyorum; bu araç ASP.NET uygulamasını test ortamında başlatıp ona HTTP request göndermemi sağlar. &lt;strong&gt;Testcontainers&lt;/strong&gt;, test sırasında gerçek bağımlılıkları container olarak başlatıp test sonunda temizlemeyi sağlayan kütüphanedir. Burada geçici PostgreSQL'i açıp tablo yapısını hazırlıyorum; testler canlı veritabanını kullanmıyor.&lt;/p&gt;

&lt;p&gt;Görev oluşturma request'inin ilgili kısmı:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;var&lt;/span&gt; &lt;span class="n"&gt;createResponse&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;PostAsJsonAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s"&gt;"/api/todo-items"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;CreateTodoItemRequest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"First task"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"Created by integration test"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;false&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="n"&gt;cancellationToken&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="n"&gt;Assert&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Equal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;HttpStatusCode&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Created&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;createResponse&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;StatusCode&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;Assert&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;NotNull&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;createResponse&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Headers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Location&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;PostAsJsonAsync&lt;/code&gt;, veriyi JSON biçiminde POST request'iyle gönderiyor. Test ardından &lt;code&gt;201 Created&lt;/code&gt; cevabını ve yeni kaynağın adresini taşıyan &lt;code&gt;Location&lt;/code&gt; başlığını doğruluyor.&lt;/p&gt;

&lt;p&gt;Test devamında kaydı okuyup güncelliyor ve siliyor. Fake repository ile geçen bir testin yakalayamayacağı veri erişimi sorunları bu akışta ortaya çıkabilir.&lt;/p&gt;

&lt;h4&gt;
  
  
  Kullanıcı Akışı ve Deployment Kontrolü
&lt;/h4&gt;

&lt;p&gt;&lt;strong&gt;End-to-end test&lt;/strong&gt;, kullanıcının başlattığı akışı arayüzden depolamaya kadar sınar; Browser'da görev ekleyip sonucu görmek buna örnek. Projedeki frontend proxy testi bu kadar geniş değil: production Nuxt server ile sahte backend arasındaki HTTP aktarımını kontrol ediyor, Browser arayüzünü kapsamıyor.&lt;/p&gt;

&lt;p&gt;Test kapsamı büyüdükçe daha fazla parçayı doğrularım, ancak kurulum ve hata ayıklama da zorlaşır. Unit testte sorun handler'a kadar daralırken geniş akışta Browser, API veya veritabanından kaynaklanabilir. Bu yüzden her seviyeden testin neyi kapsadığını açık tutuyorum.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Smoke test&lt;/strong&gt; daha dar bir kontrol yapar: deployment sonrasında ana sayfa ve temel API erişilebilir mi? Projede &lt;code&gt;/&lt;/code&gt; ve &lt;code&gt;/api/todo-items&lt;/code&gt; adreslerine request gönderiyorum. Bu kontrol, uygulamanın temel erişim yolunu sınarken bütün CRUD senaryolarını tekrar etmiyor.&lt;/p&gt;




&lt;h3&gt;
  
  
  Adım 3: CI/CD ile Değişiklikleri Dağıtıma Hazırlamak
&lt;/h3&gt;

&lt;p&gt;Testleri yalnızca kendi bilgisayarımda çalıştırırsam bir değişikliği kolayca atlayabilirim. &lt;strong&gt;CI (Continuous Integration)&lt;/strong&gt;, her değişiklikte aynı kontrolleri otomatik çalıştırarak bu riski azaltıyor. GitHub Actions'ta YAML dosyasına yazdığım &lt;strong&gt;workflow&lt;/strong&gt;, bir olayla başlıyor; örneğin &lt;code&gt;pull_request&lt;/code&gt;, &lt;code&gt;push&lt;/code&gt; veya elle başlatılan &lt;code&gt;workflow_dispatch&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Workflow içindeki &lt;strong&gt;job&lt;/strong&gt; ayrı bir iş grubudur ve bir &lt;strong&gt;runner&lt;/strong&gt; üzerinde çalışır. Job'un &lt;strong&gt;step&lt;/strong&gt;'leri sırayla ilerler; &lt;code&gt;run&lt;/code&gt; terminal komutu çalıştırır, &lt;code&gt;uses&lt;/code&gt; hazır bir Action çağırır. Bağımsız job'lar paralel çalışabilir. Projede backend ve frontend CI kontrolleri kendi workflow dosyalarında tanımlı.&lt;/p&gt;

&lt;p&gt;Kontroller farklı şeylere bakıyor: lint kurallara uymayan kodu bulmaya, typecheck tip uyuşmazlıklarını yakalamaya, testler davranışı doğrulamaya çalışıyor. Docker image build ise paketlemenin tamamlanabildiğini gösteriyor. Runner bunları başarıyla bitirse de uygulamayı sunucuda çalıştırmış olmuyor; o ayrı deployment adımı.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;CD&lt;/strong&gt; de iki farklı hedefi anlatabilir. &lt;strong&gt;Continuous Delivery&lt;/strong&gt;, başarılı değişikliği dağıtıma hazırlar; canlıya geçiş için onay bekleyebilir. &lt;strong&gt;Continuous Deployment&lt;/strong&gt; ise kontrollerden geçen değişikliği otomatik olarak canlıya taşır.&lt;/p&gt;

&lt;h4&gt;
  
  
  Bir Adım Diğerini Beklediğinde
&lt;/h4&gt;

&lt;p&gt;Testler geçmeden migration'ın veya deployment'ın başlamasını istemem. Job bağımlılıkları bu sırayı kuruyor; release workflow'undaki kısaltılmış bölüm şöyle:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;migrate&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;needs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;backend-ci&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;frontend-ci&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;

&lt;span class="na"&gt;publish&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;needs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;migrate&lt;/span&gt;

&lt;span class="na"&gt;deploy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;needs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;publish&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;needs&lt;/code&gt;, bir job'un ilerlemeden önce bekleyeceği işleri belirtir. Önce backend ve frontend kontrolleri tamamlanıyor; ardından &lt;code&gt;migrate&lt;/code&gt; veritabanı şemasını güncelliyor, &lt;code&gt;publish&lt;/code&gt; image'ları registry'ye gönderiyor ve &lt;code&gt;deploy&lt;/code&gt; yeni sürümü K3s'te çalıştırıyor. Migration başarısız olursa image yayımı ve deployment başlamıyor. Şema değişikliğinin neden image dağıtımından önce geldiğine deployment bölümünde döneceğim.&lt;/p&gt;

&lt;h4&gt;
  
  
  Registry ve Sürüm Takibi
&lt;/h4&gt;

&lt;p&gt;Image hazırlandıktan sonra sunucunun da ona ulaşması gerekiyor. &lt;strong&gt;Container registry&lt;/strong&gt;, image'ları sakladığım ve çalışma ortamına indirdiğim depo; Docker Hub ve GitHub Container Registry buna örnek.&lt;/p&gt;

&lt;p&gt;Git her commit'i bir hash ile tanımlar; &lt;strong&gt;commit SHA&lt;/strong&gt;, bu kimlik değeridir. &lt;code&gt;latest&lt;/code&gt; etiketi zamanla başka image'ı gösterebilir. SHA etiketi ise hangi kaynak kodundan image üretildiğini izlemeyi kolaylaştırır.&lt;/p&gt;

&lt;p&gt;Projede kullandığım GitHub Container Registry'nin kısaltması &lt;strong&gt;GHCR&lt;/strong&gt;. Gönderdiğim image adlarından biri:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ghcr.io/tahatuzel/git-to-prod-backend:&amp;lt;commit-sha&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Registry'ye yeni image göndermek, çalışan container'ı kendiliğinden güncellemez. Deployment sırasında hedef ortama hangi image sürümünü çalıştıracağını ayrıca bildirmek gerekir.&lt;/p&gt;




&lt;h3&gt;
  
  
  Adım 4: Deployment, Kubernetes ve K3s
&lt;/h3&gt;

&lt;p&gt;Image registry'ye ulaştı; peki sunucu onu nasıl çalıştıracak? &lt;strong&gt;Deployment&lt;/strong&gt;, uygulama sürümünü hedef ortamda başlatıp mevcut sürümden yenisine geçme süreci. Bunun için veritabanı erişimi, bağlantı ayarları ve dış trafiğin uygulamaya ulaşacağı yol da hazır olmalı.&lt;/p&gt;

&lt;p&gt;Aynı image'ı farklı ortamlarda kullanıp bağlantı ayarlarını çalışma anında verebilirim; her ortam için yeniden paketlemem gerekmez. Hassas bilgileri de image içine koymak yerine ortamın Secret mekanizmasıyla sağlıyorum.&lt;/p&gt;

&lt;p&gt;Bu çalışmada Terraform ile AWS ağını ve makineleri hazırladım. PostgreSQL'i ayrı bir sanal makineye, K3s'i uygulama makinelerine kurdum; pipeline da uygulamanın yeni sürümlerini buraya taşıdı.&lt;/p&gt;

&lt;h4&gt;
  
  
  Veritabanı Şeması da Değişebiliyor
&lt;/h4&gt;

&lt;p&gt;Uygulamanın kodu değişirken veritabanındaki tablolar da değişebilir. Örneğin yeni bir alan eklediğimde bu değişikliği &lt;strong&gt;migration&lt;/strong&gt; olarak tanımlarım. Eski sürüm hâlâ çalışırken yeni sürümün ihtiyaç duyduğu alanı erkenden silmek sorun çıkarabilir; bu yüzden kod ve şema değişikliklerinin sırasını birlikte düşünmek gerekir.&lt;/p&gt;

&lt;p&gt;Projede EF Core'un &lt;strong&gt;migration bundle&lt;/strong&gt;'ı release akışında önce çalışıyor. Migration tamamlanırsa image'lar yayımlanıyor ve K3s'e dağıtılıyor. Böylece her uygulama kopyasının açılışta aynı şemayı değiştirmeye çalışmasını önlüyorum. Image'ı önceki sürüme döndürmek de veritabanı değişikliğini kendiliğinden geri almaz.&lt;/p&gt;

&lt;h4&gt;
  
  
  Kubernetes ve K3s Ne Yapıyor?
&lt;/h4&gt;

&lt;p&gt;Compose ile servisleri tek makinede başlatabiliyorum. Birden fazla makinede ise hangi makinenin uygulamayı çalıştıracağını, kapanan bir kopyanın yerine ne geleceğini ve yeni sürüme nasıl geçileceğini de yönetmem gerekiyor. &lt;strong&gt;Kubernetes&lt;/strong&gt;, container'ların bu yaşam döngüsünü yönetiyor.&lt;/p&gt;

&lt;p&gt;Cluster içindeki makineler &lt;strong&gt;node&lt;/strong&gt; olarak adlandırılır. Kubernetes uygulamayı &lt;strong&gt;Pod&lt;/strong&gt; içinde çalıştırır; çoğu Pod'da bir uygulama container'ı bulunur. Kaç kopya istediğimi bir &lt;strong&gt;Deployment&lt;/strong&gt; tanımında belirtirim:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;replicas&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;2&lt;/span&gt;
  &lt;span class="na"&gt;template&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;containers&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;backend&lt;/span&gt;
          &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ghcr.io/tahatuzel/git-to-prod-backend:__IMAGE_TAG__&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Burada iki backend kopyası istiyorum. Kubernetes mevcut durumu bu istekle karşılaştırır; bir Pod kapanırsa yenisini oluşturmaya çalışır. &lt;strong&gt;Scheduler&lt;/strong&gt; Pod için uygun node'u seçer, node üzerindeki &lt;strong&gt;kubelet&lt;/strong&gt; de container runtime'a uygulamayı başlatmasını söyler. K3s'in varsayılan runtime'ı &lt;strong&gt;containerd&lt;/strong&gt;; bu nedenle worker makinelerine Docker Engine kurmam gerekmedi.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;K3s&lt;/strong&gt;, Kubernetes'i daha kolay kurup çalıştırmak için bazı bileşenleriyle birlikte paketleyen bir dağıtımdır. Yani K3s'te de Deployment, Pod ve Service aynı Kubernetes kaynaklarıdır. Projemde uygulama Pod'larını worker node'larda çalıştırmak için node label ve &lt;strong&gt;nodeSelector&lt;/strong&gt; kullandım. İki replica istemek tek başına bunların farklı node'lara yerleşmesini garanti etmez.&lt;/p&gt;

&lt;p&gt;Pod yeniden oluşturulunca IP'si değişebilir. Uygulamanın bu IP'yi takip etmemesi için &lt;strong&gt;Service&lt;/strong&gt; sabit bir ad sağlar; Nuxt server projede backend'e &lt;code&gt;http://todo-backend:8080&lt;/code&gt; üzerinden ulaşıyor. Dışarıdan gelen HTTP trafiğinin hangi servise gideceğini &lt;strong&gt;Ingress&lt;/strong&gt; tanımlar, K3s içindeki Traefik ise bu kuralı uygular.&lt;/p&gt;

&lt;p&gt;Kurulumdaki istek yolu özetle şöyle:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Browser → AWS ALB → Traefik → frontend Service → Nuxt Pod
                                                   ↓
                                            backend Service
                                                   ↓
                                              .NET Pod
                                                   ↓
                                             PostgreSQL
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;PostgreSQL'i cluster dışında ayrı bir VM'de tuttum. Kubernetes bu projede frontend ve backend Pod'larını yönetiyor; veritabanının diskini veya yedeklerini yönetmiyor.&lt;/p&gt;

&lt;h4&gt;
  
  
  K3s ile Yeni Sürümü Çalıştırmak
&lt;/h4&gt;

&lt;p&gt;Kubernetes'e istediğim durumu &lt;strong&gt;manifest&lt;/strong&gt; adı verilen YAML dosyalarıyla bildiririm. Projedeki backend manifestinde image etiketi release sırasında commit SHA ile değiştirilir. Bağlantı bilgisi de dosyaya açık metin olarak yazılmaz; Pod bunu Kubernetes Secret'ından alır:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;replicas&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;2&lt;/span&gt;
  &lt;span class="na"&gt;template&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;containers&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;backend&lt;/span&gt;
          &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ghcr.io/tahatuzel/git-to-prod-backend:__IMAGE_TAG__&lt;/span&gt;
          &lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ConnectionStrings__DefaultConnection&lt;/span&gt;
              &lt;span class="na"&gt;valueFrom&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
                &lt;span class="na"&gt;secretKeyRef&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
                  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;todo-db&lt;/span&gt;
                  &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;connectionString&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Bu, projedeki Deployment tanımının ilgili alanlarının sadeleştirilmiş hali. Release script'i image etiketini commit SHA ile doldurup manifesti cluster'a uygular:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sed&lt;/span&gt; &lt;span class="s2"&gt;"s/__IMAGE_TAG__/&lt;/span&gt;&lt;span class="nv"&gt;$release_sha&lt;/span&gt;&lt;span class="s2"&gt;/g"&lt;/span&gt; app.yaml | &lt;span class="nb"&gt;sudo &lt;/span&gt;k3s kubectl apply &lt;span class="nt"&gt;-f&lt;/span&gt; -
&lt;span class="nb"&gt;sudo &lt;/span&gt;k3s kubectl &lt;span class="nt"&gt;-n&lt;/span&gt; todo rollout status deployment/todo-backend &lt;span class="nt"&gt;--timeout&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;300s
&lt;span class="nb"&gt;sudo &lt;/span&gt;k3s kubectl &lt;span class="nt"&gt;-n&lt;/span&gt; todo rollout status deployment/todo-frontend &lt;span class="nt"&gt;--timeout&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;300s
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;kubectl apply&lt;/code&gt;, manifestteki istenen durumu cluster'a iletir. &lt;strong&gt;Rolling update&lt;/strong&gt; sırasında Kubernetes yeni Pod'ları hazır etmeye çalışırken eski kopyaları kademeli kapatır. &lt;code&gt;rollout status&lt;/code&gt; geçiş tamamlanana kadar bekler; ancak bunun düzgün çalışması için yeni Pod'un trafiğe hazır olup olmadığını da kontrol etmesi gerekir.&lt;/p&gt;

&lt;p&gt;Projede bu iki kontrol backend manifestinde şöyle tanımlı:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;readinessProbe&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;httpGet&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/api/todo-items&lt;/span&gt;
    &lt;span class="na"&gt;port&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;8080&lt;/span&gt;
  &lt;span class="na"&gt;periodSeconds&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;10&lt;/span&gt;
&lt;span class="na"&gt;livenessProbe&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;tcpSocket&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;port&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;8080&lt;/span&gt;
  &lt;span class="na"&gt;periodSeconds&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;20&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Readiness probe&lt;/strong&gt;, her 10 saniyede &lt;code&gt;/api/todo-items&lt;/code&gt; endpoint'ine istek gönderiyor. Başarısızsa Pod Service üzerinden trafik almıyor. &lt;strong&gt;Liveness probe&lt;/strong&gt; ise her 20 saniyede container port'una bağlantı kurmayı deniyor; eşik aşılarak başarısız olursa kubelet container'ı yeniden başlatabiliyor. Bu iki kontrol farklı sorulara cevap veriyor: uygulama trafik alabilir mi, çalışmaya devam edebiliyor mu?&lt;/p&gt;

&lt;p&gt;Rollout tamamlandıktan sonra GitHub Actions, ALB üzerinden hem ana sayfaya hem &lt;code&gt;/api/todo-items&lt;/code&gt; yoluna HTTP isteği gönderiyor. Bu son smoke test, dış trafik yolunun da çalıştığını kontrol ediyor. Projenin akışı böylece testlerden migration'a, image yayımına, K3s rollout'una ve dışarıdan kontrole kadar ilerliyor.&lt;/p&gt;

&lt;h4&gt;
  
  
  Hands-on Denemesi: Host Bağlanıyor, Pod Neden Bağlanamıyor?
&lt;/h4&gt;

&lt;p&gt;&lt;strong&gt;Sorun&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;İlk cluster kurulumunda worker makinesinden PostgreSQL'e bağlanabiliyordum; backend Pod'undan yaptığım aynı test ise zaman aşımına uğruyordu.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Worker host → PostgreSQL    Bağlantı kuruluyor
Backend Pod → PostgreSQL   Timeout
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Önce iki arayüzü tanıtayım: &lt;code&gt;ens5&lt;/code&gt;, worker makinesinin AWS VPC ağına bağlanan ağ arayüzü. &lt;code&gt;cni0&lt;/code&gt; ise K3s'in aynı worker üzerindeki Pod'ları bağladığı yerel ağ köprüsü. Devamında bu yolları kısaca &lt;strong&gt;VPC ağı&lt;/strong&gt; ve &lt;strong&gt;K3s'in yerel Pod ağı&lt;/strong&gt; olarak anacağım.&lt;/p&gt;

&lt;p&gt;VPC'm &lt;code&gt;10.42.0.0/16&lt;/code&gt; aralığını kullanıyordu. K3s'in varsayılan Pod ağı da &lt;code&gt;10.42.0.0/16&lt;/code&gt; idi. Yani VPC'deki makinelerle Pod'lar aynı IP aralığını kullanıyordu. Aynı IP bloğu hem VPC'deki DB'yi hem de K3s'in yerel Pod ağını gösterebildiği için worker gelen paketi hangi yola vereceğine karar vermek zorundaydı.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Böyle tespit ettim&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;İlk timeout'ta paketin nerede kaybolduğunu bilmiyordum. Bunu görmek için daha küçük bir AWS ortamında sorunu kontrollü olarak tekrar oluşturdum. Bu ortamda bir K3s server, bir worker ve bir DB vardı. Server ve worker makinelerini farklı subnet'lere koydum; DB adresini ise worker'ın Pod'ları için kullandığı aralığa denk getirdim:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;VPC:               10.42.0.0/16
Server EC2 subnet: 10.42.254.0/24
Worker EC2 subnet: 10.42.253.0/24
DB EC2 subnet:     10.42.1.0/24
Worker'ın Pod aralığı: 10.42.1.0/24

Pod IP:            10.42.1.2
DB IP:             10.42.1.63
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Pod &lt;code&gt;10.42.1.63&lt;/code&gt; adresindeki DB'ye bağlanmaya çalışırken worker'daki rota kontrolü K3s'in yerel Pod ağına giden yolu gösterdi:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Pod'dan gelen hedef:  10.42.1.63
Worker'ın seçtiği yol: K3s'in yerel Pod ağı (cni0)
DB tarafındaki sonuç:  Hiç paket görülmedi
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;DB IP'si &lt;code&gt;10.42.1.63&lt;/code&gt;, worker'ın Pod'ları için kullandığı &lt;code&gt;10.42.1.0/24&lt;/code&gt; aralığıyla aynı blok içindeydi. Worker'ın rota tablosunda bu blok için K3s'in yerel ağına giden bir yol vardı. Hedef bu aralığa uyduğu için worker bu paket için tek bir yol seçti: VPC ağına göndermek yerine K3s'in yerel ağına verdi. DB IP'sinde yerel bir Pod bulunmadığından istek orada sonuçsuz kaldı. Paket VPC'ye hiç çıkmadı; DB tarafındaki tcpdump'ın hiç paket görmemesi de bunu doğruladı.&lt;/p&gt;

&lt;p&gt;Dolayısıyla bu denemede sorun PostgreSQL'in isteği reddetmesi ya da yanıtın geri dönememesi değildi: istek DB'ye hiç ulaşmadı. Worker host'undan yapılan test farklıydı; bağlantı host'un kendi IP'si ve VPC yolu üzerinden çıkıyordu. Pod testi ise Pod IP'siyle başlayıp worker'da farklı bir yol seçiyordu.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Çözüm&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Çözüm, K3s'in Pod adresleri için VPC'den farklı bir CIDR kullanmasıydı:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;VPC ağı:  10.42.0.0/16
Pod ağı:  10.244.0.0/16

K3s server ayarı:
--cluster-cidr=10.244.0.0/16
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;--cluster-cidr&lt;/code&gt;, K3s'in Pod'lara vereceği IP aralığını belirliyor. Cluster'ı bu ayarla yeniden kurduğumda Pod ağı &lt;code&gt;10.244.0.0/16&lt;/code&gt; oldu; DB ise aynı &lt;code&gt;10.42.1.63&lt;/code&gt; adresinde kaldı. Artık DB adresi K3s'in Pod aralığında değildi. Worker hedefi K3s'in yerel ağıyla eşleştirmedi; paketi VPC yoluna, &lt;code&gt;ens5&lt;/code&gt; üzerinden gönderdi:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Pod IP'si:       10.244.1.3
DB IP'si:        10.42.1.63
Worker rotası:   VPC ağı (ens5)
DB tcpdump:      SYN paketi görüldü
Sonuç:           Bağlantı ve SELECT 1 / SELECT 2 başarılı
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;DB tcpdump'ında SYN paketi görüldü; &lt;code&gt;SELECT 1&lt;/code&gt; ve &lt;code&gt;SELECT 2&lt;/code&gt; başarılı oldu. Bu sonuç, isteğin DB'ye ulaştığını ve yanıtın Pod'a döndüğünü doğruladı. CIDR'lar ayrılınca worker'ın seçebileceği ağ yolu netleşti: DB isteği VPC üzerinden gitti. K3s'in Pod ağı ve Flannel yapılandırması &lt;a href="https://docs.k3s.io/networking/basic-network-options" rel="noopener noreferrer"&gt;K3s ağ seçeneklerinde&lt;/a&gt; açıklanıyor.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Hatanın hangi katmanda olduğunu aramak&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Sorun olduğunda önce Pod'un durumunu ve log'larını inceleyebilirim:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;k3s kubectl &lt;span class="nt"&gt;-n&lt;/span&gt; todo get pods &lt;span class="nt"&gt;-o&lt;/span&gt; wide
&lt;span class="nb"&gt;sudo &lt;/span&gt;k3s kubectl &lt;span class="nt"&gt;-n&lt;/span&gt; todo describe pod POD_ADI
&lt;span class="nb"&gt;sudo &lt;/span&gt;k3s kubectl &lt;span class="nt"&gt;-n&lt;/span&gt; todo logs deployment/todo-backend
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;get pods -o wide&lt;/code&gt;, Pod'ları node ve IP bilgileriyle listeler. &lt;code&gt;describe pod&lt;/code&gt;, seçilen Pod'un ayrıntılarını ve olaylarını; &lt;code&gt;logs&lt;/code&gt;, uygulamanın mesajlarını gösterir. &lt;code&gt;POD_ADI&lt;/code&gt; yerine listeden aldığım gerçek adı yazmam gerekir.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;ImagePullBackOff&lt;/code&gt; image'ın indirilmesinde sorun olduğunu gösterir; image adı, tag ve registry erişimine bakarım. &lt;code&gt;CrashLoopBackOff&lt;/code&gt; durumunda uygulama log'larını ve başlangıç ayarlarını incelerim. Pod çalışıyor ama hazır değilse readiness kontrolü ve onun bağımlılıkları öne çıkar.&lt;/p&gt;

&lt;p&gt;Bağlantı hatalarında da request'i hangi ortamdan gönderdiğimi, kullanılan adresi ve aradaki ağları ayrı ayrı kontrol etmem gerekiyor.&lt;/p&gt;




&lt;h2&gt;
  
  
  Kendime Notlar
&lt;/h2&gt;

&lt;p&gt;Bu çalışmada birden fazla kez aynı dersi gördüm: Bir adımın başarılı olması sonraki adımı garanti etmiyor. Testler kodun belirli davranışlarını kontrol etti; image build paketlemenin tamamlandığını gösterdi. Pod'dan veritabanına giden ağ yolunu ise ancak gerçek cluster'da fark ettim.&lt;/p&gt;

&lt;p&gt;Kubernetes'te iki replica yazmak da yeterli olmadı; hangi node'da çalıştıklarını ve veritabanı gibi bağımlılıkların nerede durduğunu ayrıca düşünmem gerekti. Bir hata çıktığında artık yalnızca “uygulama çalışıyor mu?” diye bakmıyorum. Request hangi ortamdan çıkıyor, hangi adresi kullanıyor ve sıradaki kontrol neyi kanıtlıyor, bunları takip ediyorum.&lt;/p&gt;

&lt;p&gt;Kod, test ve deployment tanımları GitHub'da:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/tahatuzel/git-to-prod" rel="noopener noreferrer"&gt;Git to Prod: GitHub projesi&lt;/a&gt;&lt;/p&gt;

</description>
      <category>cicd</category>
      <category>docker</category>
      <category>kubernetes</category>
      <category>software</category>
    </item>
    <item>
      <title>URL'den Response'a: Tarayıcıda Ne Oluyor?</title>
      <dc:creator>TAHA TÜZEL</dc:creator>
      <pubDate>Sun, 27 Sep 2026 13:03:40 +0000</pubDate>
      <link>https://dev.to/tahatuzel/urlden-responsea-tarayicida-ne-oluyor-34l</link>
      <guid>https://dev.to/tahatuzel/urlden-responsea-tarayicida-ne-oluyor-34l</guid>
      <description>&lt;p&gt;Her gün interneti kullanıyorum; bir siteye erişmek için adresi yazıp Enter'a basıyorum. Projelerde frontend tarafında &lt;code&gt;fetch&lt;/code&gt; çağırıyorum, backend tarafında route tanımlıyorum. Bunlar sürekli kullandığım şeyler olsa da Browser ile Server arasında o kısa sürede tam olarak neler yaşandığı uzun süre benim için biraz ezberlenmiş bilgilerden ibaretti.&lt;/p&gt;

&lt;p&gt;İzlediğim eğitim videolarının ve okuduğum blog yazılarının büyük bölümü benzer bir şablon üzerinden ilerliyordu:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;DNS telefon rehberidir, IP bulunur, sayfa gelir.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Bu anlatım genel resmi anlamak için yeterli olsa da arka planda gerçekleşen süreci daha detaylı görmek istedim.&lt;/p&gt;

&lt;p&gt;Bu nedenle teorik konuları çalışırken birkaç küçük lab yaptım; &lt;code&gt;nslookup&lt;/code&gt; ve &lt;code&gt;curl&lt;/code&gt; gibi araçlarla Request'in geçtiği adımları incelemeye çalıştım. Bu yazıda hem çalışma sırasında çıkardığım notları hem de kullandığım terminal komutlarını ve aldığım çıktıları tek bir akış altında topladım.&lt;/p&gt;




&lt;h3&gt;
  
  
  Büyük Resim: Request Nereden Geçiyor?
&lt;/h3&gt;

&lt;p&gt;İlk olarak genel akışı oturtmak gerekiyor.&lt;/p&gt;

&lt;p&gt;Browser'a bir adres yazıp Enter'a bastığımızda süreç kabaca şu sırayla ilerliyor:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;URL girilir&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;DNS&lt;/strong&gt;, domain adını IP adresine çevirir.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;TCP connection&lt;/strong&gt; kurulur.&lt;/li&gt;
&lt;li&gt;HTTPS kullanılıyorsa &lt;strong&gt;TLS handshake&lt;/strong&gt; gerçekleştirilir.&lt;/li&gt;
&lt;li&gt;Client, &lt;strong&gt;HTTP request&lt;/strong&gt; gönderir.&lt;/li&gt;
&lt;li&gt;Server, &lt;strong&gt;HTTP response&lt;/strong&gt; döndürür.&lt;/li&gt;
&lt;li&gt;Browser gelen içeriği işleyerek &lt;strong&gt;sayfayı render eder&lt;/strong&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Kısaca:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;URL → DNS → TCP → TLS → HTTP Request → HTTP Response → Render&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Bu iletişimde temelde iki taraf bulunuyor.&lt;/p&gt;

&lt;p&gt;Request'i başlatan taraf &lt;strong&gt;Client&lt;/strong&gt;. Bu bir Browser, mobil uygulama veya terminalde kullandığımız &lt;code&gt;curl&lt;/code&gt; olabilir.&lt;/p&gt;

&lt;p&gt;Karşı tarafta ise Request'e Response üreten &lt;strong&gt;Server&lt;/strong&gt; bulunuyor. Bu Server tarafında Nginx, Apache, Go veya Node.js ile çalışan bir servis olabilir.&lt;/p&gt;

&lt;p&gt;Akış ilk bakışta oldukça basit görünüyor. Ancak her adımın içine girdiğimizde arka planda çok daha fazla mekanizmanın çalıştığını görüyoruz.&lt;/p&gt;




&lt;h3&gt;
  
  
  Adım 1: Yazdığımız URL Nelerden Oluşuyor?
&lt;/h3&gt;

&lt;p&gt;Süreci anlamak için önce Browser'a yazdığımız URL'nin yapısına bakalım.&lt;/p&gt;

&lt;p&gt;Örnek olarak biraz uzun bir URL'yi parçalarına ayıralım:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;https://api.example.com:443/products/5?category=tech&amp;amp;sort=asc#reviews&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;https://   api.   example   .com   :443   /products/5   ?category=tech&amp;amp;sort=asc   #reviews
│          │      │         │      │      │             │                         │
Scheme     Sub-   Domain    TLD    Port   Path          Query Parameters          Fragment
           domain
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Buradaki her parçanın farklı bir görevi var:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Scheme (&lt;code&gt;https&lt;/code&gt;):&lt;/strong&gt; İletişimde hangi protocol'ün kullanılacağını belirtir.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Subdomain (&lt;code&gt;api&lt;/code&gt;):&lt;/strong&gt; Ana domain altında bulunan özelleşmiş servisi ifade eder. &lt;code&gt;app&lt;/code&gt;, &lt;code&gt;cdn&lt;/code&gt; veya &lt;code&gt;api&lt;/code&gt; gibi yapılar burada karşımıza çıkar.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Domain (&lt;code&gt;example&lt;/code&gt;):&lt;/strong&gt; Ana domain adıdır.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;TLD (&lt;code&gt;.com&lt;/code&gt;):&lt;/strong&gt; Top-Level Domain bölümüdür. &lt;code&gt;.com&lt;/code&gt;, &lt;code&gt;.org&lt;/code&gt;, &lt;code&gt;.net&lt;/code&gt;, &lt;code&gt;.dev&lt;/code&gt; gibi uzantılar bu gruba girer.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Port (&lt;code&gt;:443&lt;/code&gt;):&lt;/strong&gt; Server üzerinde hangi port'a bağlanılacağını belirtir. HTTP için varsayılan port &lt;code&gt;80&lt;/code&gt;, HTTPS için ise &lt;code&gt;443&lt;/code&gt;'tür. URL içerisinde açıkça yazılmasa bile Browser bunu bilir. Lokal geliştirmede kullandığımız &lt;code&gt;localhost:3000&lt;/code&gt; adresindeki &lt;code&gt;3000&lt;/code&gt; de aynı kavramdır.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Path (&lt;code&gt;/products/5&lt;/code&gt;):&lt;/strong&gt; Server'dan istenen resource'un yolunu belirtir.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Query Parameters (&lt;code&gt;?category=tech&amp;amp;sort=asc&lt;/code&gt;):&lt;/strong&gt; Filtreleme veya sıralama gibi işlemler için kullanılan key-value çiftleridir.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fragment (&lt;code&gt;#reviews&lt;/code&gt;):&lt;/strong&gt; Sayfa içerisindeki belirli bir bölümü ifade eder.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Burada diğer URL bileşenlerinden ayrılan kısım &lt;strong&gt;Fragment&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;#reviews&lt;/code&gt; Server'a gönderilmez. Browser, sayfayı aldıktan sonra Fragment değerini Client tarafında kullanarak ilgili bölüme gider.&lt;/p&gt;

&lt;p&gt;Yani URL'de gördüğümüz her bölüm HTTP Request'in bir parçası değildir.&lt;/p&gt;




&lt;h3&gt;
  
  
  Adım 2: DNS Sorgusu
&lt;/h3&gt;

&lt;p&gt;URL'nin yapısından sonra sıradaki soru şu:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;wikipedia.org&lt;/code&gt; yazdığımızda bilgisayar karşıdaki Server'ı nasıl buluyor?&lt;/p&gt;

&lt;p&gt;Bilgisayarlar ve Router'lar bizim kullandığımız domain isimleri üzerinden iletişim kurmaz. Ağ üzerindeki cihazlara ulaşabilmek için IP adresleri kullanılır.&lt;/p&gt;

&lt;p&gt;Bu nedenle Browser'ın &lt;code&gt;wikipedia.org&lt;/code&gt; Server'ına ulaşabilmesi için önce bu domain'in arkasındaki IP adresini öğrenmesi gerekir.&lt;/p&gt;

&lt;p&gt;Bu işlemi &lt;strong&gt;DNS — Domain Name System&lt;/strong&gt; gerçekleştirir.&lt;/p&gt;

&lt;p&gt;Browser bir IP adresini bulmaya çalışırken süreç kabaca şu şekilde ilerler:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Önce kendi Cache'ini kontrol eder.&lt;/li&gt;
&lt;li&gt;Ardından işletim sisteminin Cache'ine ve yerel &lt;code&gt;hosts&lt;/code&gt; dosyasına bakılır.&lt;/li&gt;
&lt;li&gt;Burada sonuç bulunamazsa bilgisayarın ağ ayarlarında tanımlanmış &lt;strong&gt;DNS Resolver&lt;/strong&gt; kullanılır. Bu ISP'nin Resolver'ı olabileceği gibi &lt;code&gt;1.1.1.1&lt;/code&gt; veya &lt;code&gt;8.8.8.8&lt;/code&gt; gibi public DNS servisleri de olabilir.&lt;/li&gt;
&lt;li&gt;Resolver da cevabı bilmiyorsa Root DNS Server'lardan başlayarak ilgili TLD Server'a ve sonrasında domain'in Authoritative DNS Server'ına kadar uzanan sorgu süreci çalışır.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Burada önemli kavramlardan biri de &lt;strong&gt;TTL — Time To Live&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;DNS record'larında bulunan TTL değeri saniye cinsinden tutulur ve DNS Response'unun Cache içerisinde ne kadar süre saklanabileceğini belirler.&lt;/p&gt;

&lt;p&gt;Bunun pratikte önemli olduğu senaryolardan biri Server migration işlemidir.&lt;/p&gt;

&lt;p&gt;Bir proje başka bir Server'a taşınıyor ve IP adresi değişiyorsa geçişten önce TTL değerinin düşürülmesi faydalı olabilir. Aksi durumda eski IP adresi bazı DNS Cache'lerinde kalmaya devam edebilir ve kullanıcıların bir bölümü eski Server'a yönlenebilir.&lt;/p&gt;

&lt;h4&gt;
  
  
  Terminal Denemesi: &lt;code&gt;wikipedia.org&lt;/code&gt; IP Adresini Bulmak
&lt;/h4&gt;

&lt;p&gt;DNS çözümlemesinin terminalde nasıl göründüğünü incelemek için PowerShell üzerinde &lt;code&gt;nslookup&lt;/code&gt; kullanılabilir:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;PS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;C:\&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;nslookup&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;wikipedia.org&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;Server:&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;one.one.one.one&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;Address:&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;1.1.1.1&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="n"&gt;Non-authoritative&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;answer:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;Name:&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;wikipedia.org&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;Addresses:&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;2a02:ec80:600:ed1a::1&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="mf"&gt;185.15&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;58&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;224&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Çıktıda birkaç önemli bilgi bulunuyor.&lt;/p&gt;

&lt;p&gt;İlk olarak kullanılan DNS Resolver:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Server:  one.one.one.one
Address: 1.1.1.1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Burada sorgu Cloudflare'ın &lt;code&gt;1.1.1.1&lt;/code&gt; DNS Resolver'ına gönderiliyor.&lt;/p&gt;

&lt;p&gt;Bir diğer önemli satır:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Non-authoritative answer
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;1.1.1.1&lt;/code&gt;, Wikipedia'nın Authoritative DNS Server'ı değil. İlgili cevabı öğrenip Client'a ileten Resolver rolünde.&lt;/p&gt;

&lt;p&gt;Response içerisinde iki farklı IP adresi de bulunuyor:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;2a02:ec80:600:ed1a::1
185.15.58.224
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;İlk adres IPv6 tarafındaki &lt;strong&gt;AAAA record&lt;/strong&gt;, ikinci adres ise IPv4 tarafındaki &lt;strong&gt;A record&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Bu kayıt türlerini örnek bir site üzerinden inceleyelim:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;A record:&lt;/strong&gt; &lt;code&gt;benimsitem.com&lt;/code&gt; adını doğrudan sunucunun IPv4 adresine bağlar.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;AAAA record:&lt;/strong&gt; A record'un IPv6 adresleri için kullanılan karşılığıdır.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;CNAME record:&lt;/strong&gt; &lt;code&gt;www.benimsitem.com&lt;/code&gt; adını bir IP adresine değil, &lt;code&gt;benimsitem.com&lt;/code&gt; adına bağlar.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Böylece biri &lt;code&gt;www.benimsitem.com&lt;/code&gt; adresini yazdığında DNS önce &lt;code&gt;benimsitem.com&lt;/code&gt; adını, ardından onun A kaydındaki IP adresini bulur. &lt;code&gt;www&lt;/code&gt; için aynı IP adresini ikinci kez yazmam gerekmez. CNAME bir HTTP yönlendirmesi yapmaz; tarayıcıdaki URL değişmez.&lt;/p&gt;

&lt;h4&gt;
  
  
  DNS'e URL Verirsek Ne Oluyor?
&lt;/h4&gt;

&lt;p&gt;&lt;code&gt;nslookup&lt;/code&gt; ile şu sorguyu çalıştırdığımızı düşünelim:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;PS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;C:\&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;nslookup&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;http://http.badssl.com/&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;Server:&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;one.one.one.one&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;Address:&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;1.1.1.1&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="o"&gt;***&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;one.one.one.one&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;can&lt;/span&gt;&lt;span class="s1"&gt;'t find http://http.badssl.com/: Non-existent domain
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Buradaki problem DNS'e yalnızca domain vermek yerine bütün URL'nin gönderilmesi.&lt;/p&gt;

&lt;p&gt;DNS'in görevi HTTP ile ilgilenmek değil; domain name resolution yapmaktır.&lt;/p&gt;

&lt;p&gt;Bu nedenle &lt;code&gt;http://&lt;/code&gt; ve sondaki &lt;code&gt;/&lt;/code&gt; domain'in bir parçası değildir.&lt;/p&gt;

&lt;p&gt;Sadece hostname sorgulandığında sonuç alınabilir:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;PS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;C:\&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;nslookup&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;http.badssl.com&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;Non-authoritative&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;answer:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;Name:&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;http.badssl.com&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;Address:&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;104.154.89.105&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Bu küçük deney URL ile domain arasındaki farkı oldukça net gösteriyor.&lt;/p&gt;




&lt;h3&gt;
  
  
  Adım 3: TCP ve UDP
&lt;/h3&gt;

&lt;p&gt;DNS üzerinden IP adresini bulduk.&lt;/p&gt;

&lt;p&gt;Artık hangi Server'a ulaşmak istediğimiz belli. Sıradaki konu ise verinin iki cihaz arasında nasıl taşındığı.&lt;/p&gt;

&lt;p&gt;Transport Layer'da en sık karşılaşılan iki protocol:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;TCP&lt;/li&gt;
&lt;li&gt;UDP&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  TCP: Reliable Connection
&lt;/h4&gt;

&lt;p&gt;Web tarafında sık karşılaştığımız protocol'lerden biri TCP.&lt;/p&gt;

&lt;p&gt;Bir HTML dosyasının, API Response'unun veya başka bir verinin yalnızca bir bölümünün ulaşması yeterli değildir. Gönderilen verinin eksiksiz ve doğru sırada karşı tarafa ulaşması gerekir.&lt;/p&gt;

&lt;p&gt;TCP bunu sağlayabilmek için veri transferinden önce Client ve Server arasında bir &lt;strong&gt;Connection&lt;/strong&gt; oluşturur.&lt;/p&gt;

&lt;p&gt;Bu süreç &lt;strong&gt;3-Way Handshake&lt;/strong&gt; olarak adlandırılır:&lt;br&gt;
&lt;/p&gt;

&lt;pre data-lang="mermaid"&gt;&lt;code&gt;sequenceDiagram
    autonumber
    participant C as Client (Sender)
    participant S as Server (Receiver)
    C-&amp;gt;&amp;gt;S: SYN
    S-&amp;gt;&amp;gt;C: SYN-ACK
    C-&amp;gt;&amp;gt;S: ACK&lt;/code&gt;&lt;/pre&gt;



&lt;p&gt;Akış şu şekilde ilerler:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Client, Server'a &lt;code&gt;SYN&lt;/code&gt; göndererek Connection başlatmak istediğini bildirir.&lt;/li&gt;
&lt;li&gt;Server, &lt;code&gt;SYN-ACK&lt;/code&gt; ile cevap verir.&lt;/li&gt;
&lt;li&gt;Client, &lt;code&gt;ACK&lt;/code&gt; gönderir ve Connection kurulmuş olur.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Connection kurulduktan sonra veri tek parça halinde gönderilmez. Büyük veri, ağ üzerinde taşınabilecek daha küçük bölümler halinde iletilir.&lt;/p&gt;

&lt;p&gt;TCP burada &lt;strong&gt;sequence number&lt;/strong&gt; kullanır.&lt;/p&gt;

&lt;p&gt;Sequence number'lar Receiver'ın gelen verinin hangi sıraya ait olduğunu anlamasını sağlar. Böylece veri farklı zamanlarda ulaşsa bile doğru sıraya yerleştirilebilir.&lt;/p&gt;

&lt;p&gt;Basitleştirilmiş olarak şöyle düşünebiliriz:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Sender

Data
 ↓
[1] [2] [3] [4] [5]
 ↓   ↓   ↓   ↓   ↓
Network
 ↓
Receiver
 ↓
[1] [2] [3] [4] [5]
 ↓
Original Data
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Network üzerinde her bölümün aynı anda veya aynı sırada ulaşması garanti değildir.&lt;/p&gt;

&lt;p&gt;Örneğin veri şu şekilde ulaşabilir:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;1 → 2 → 4 → 5
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Burada &lt;code&gt;3&lt;/code&gt; numaralı bölüm eksik.&lt;/p&gt;

&lt;p&gt;TCP'nin reliability mekanizmaları sayesinde eksik veri tespit edilir ve gerekli bölüm yeniden gönderilebilir.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;1 → 2 → 4 → 5
        ↑
        3 missing

Sender
  ↓
retransmission
  ↓
3
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Bu sayede Receiver yalnızca gelen veriyi almakla kalmaz; eksik veya sırası bozulmuş veriyi de doğru şekilde işleyebilir.&lt;/p&gt;

&lt;p&gt;REST API çağrıları, dosya indirme işlemleri veya database connection'ları gibi senaryolarda TCP kullanılmasının temel nedenlerinden biri bu &lt;strong&gt;reliability&lt;/strong&gt; mekanizmasıdır.&lt;/p&gt;

&lt;h4&gt;
  
  
  UDP: Daha Hafif Bir Yaklaşım
&lt;/h4&gt;

&lt;p&gt;UDP farklı bir çalışma modeline sahiptir.&lt;/p&gt;

&lt;p&gt;TCP'deki gibi Connection kurulmaz ve 3-Way Handshake yapılmaz.&lt;/p&gt;

&lt;p&gt;Packet gönderilir ancak karşı tarafa ulaşıp ulaşmadığına dair TCP seviyesinde bir garanti bulunmaz.&lt;/p&gt;

&lt;p&gt;Packet kaybolduğunda otomatik retransmission garantisi de yoktur.&lt;/p&gt;

&lt;p&gt;İlk bakışta bu bir dezavantaj gibi görünse de bazı kullanım alanlarında istenen davranış tam olarak budur.&lt;/p&gt;

&lt;p&gt;Örneğin canlı yayın, online oyun veya görüntülü görüşme sırasında yarım saniye önce gönderilmiş bir Packet'ın tekrar ulaşması her zaman anlamlı olmayabilir. Güncel verinin düşük latency ile ulaşması daha önemli olabilir.&lt;/p&gt;

&lt;p&gt;Bu nedenle bu tür senaryolarda UDP tercih edilebilir.&lt;/p&gt;




&lt;h3&gt;
  
  
  Adım 4: HTTP, HTTPS ve TLS
&lt;/h3&gt;

&lt;p&gt;TCP Connection kurulduktan sonra HTTP Request gönderilebilir.&lt;/p&gt;

&lt;p&gt;Burada HTTP ve HTTPS arasındaki farkı ayrı ele almak daha anlaşılır.&lt;/p&gt;

&lt;h3&gt;
  
  
  HTTP Request Gönderirsek Ne Oluyor?
&lt;/h3&gt;

&lt;p&gt;HTTP tarafında Client ile Server arasındaki trafik TLS ile şifrelenmez.&lt;/p&gt;

&lt;p&gt;Bu nedenle Request ve Response içerisinde taşınan bilgiler ağ üzerinde plaintext olarak görülebilir.&lt;/p&gt;

&lt;p&gt;Örneğin:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;GET /login HTTP/1.1
Host: example.com
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;gibi bir Request HTTP üzerinden gönderildiğinde aradaki trafik şifreli değildir.&lt;/p&gt;

&lt;p&gt;Bu yalnızca verinin okunabilmesi anlamına gelmez. HTTP trafiği aynı zamanda iletişim sırasında değiştirilmesine karşı da TLS seviyesinde bir koruma sağlamaz.&lt;/p&gt;

&lt;p&gt;HTTPS kullanılmasının temel amacı burada ortaya çıkıyor.&lt;/p&gt;

&lt;p&gt;HTTPS aslında tamamen farklı bir HTTP protocol'ü değil. Temelde:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;HTTP
  +
TLS
  =
HTTPS
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;şeklinde düşünülebilir.&lt;/p&gt;

&lt;p&gt;TLS, HTTP iletişimine üç önemli özellik kazandırır:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Encryption:&lt;/strong&gt; Client ile Server arasındaki trafiğin üçüncü kişiler tarafından okunmasını zorlaştırır.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Integrity:&lt;/strong&gt; Trafiğin iletişim sırasında değiştirilip değiştirilmediğinin kontrol edilmesini sağlar.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Authentication:&lt;/strong&gt; Client'ın gerçekten ulaşmak istediği Server ile iletişim kurduğunu doğrulamasına yardımcı olur.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Wikipedia'ya özellikle HTTP üzerinden bir Request gönderelim:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;-I&lt;/code&gt; seçeneği &lt;code&gt;GET&lt;/code&gt; yerine &lt;code&gt;HEAD&lt;/code&gt; isteği gönderir; böylece yanıtın yalnızca durum satırını ve Header'larını görürüz.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;PS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;C:\&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;curl.exe&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-I&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;http://wikipedia.org&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;HTTP/1.1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;301&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Moved&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Permanently&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;content-length:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;0&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;location:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;https://wikipedia.org/&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;server:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;HAProxy&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;x-cache:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;cp6014&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;int&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;x-cache-status:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;int-tls&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;connection:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;close&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Burada Wikipedia HTTP Request'i kabul edip sayfanın içeriğini göndermek yerine Client'ı HTTPS adresine yönlendiriyor.&lt;/p&gt;

&lt;p&gt;Bunu Response'un ilk satırında görebiliyoruz:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;HTTP/1.1 301 Moved Permanently
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;301 Moved Permanently&lt;/code&gt;, istenen resource'un başka bir adrese taşındığını belirtiyor.&lt;/p&gt;

&lt;p&gt;Nereye gidilmesi gerektiği ise &lt;code&gt;Location&lt;/code&gt; Header içerisinde bulunuyor:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;location: https://wikipedia.org/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Akış kabaca şu hale geliyor:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Client
  │
  │ HTTP Request
  │ http://wikipedia.org
  ▼
Server
  │
  │ 301 Moved Permanently
  │ Location: https://wikipedia.org/
  ▼
Client
  │
  │ HTTPS Request
  ▼
Server
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Yani Browser'a &lt;code&gt;http://wikipedia.org&lt;/code&gt; yazılmış olsa bile Server, Client'ı HTTPS endpoint'ine yönlendiriyor.&lt;/p&gt;




&lt;h4&gt;
  
  
  HTTPS Tarafında TLS Devreye Giriyor
&lt;/h4&gt;

&lt;p&gt;HTTPS kullanıldığında HTTP iletişiminden önce &lt;strong&gt;TLS Handshake&lt;/strong&gt; gerçekleşir.&lt;/p&gt;

&lt;p&gt;HTTP Request henüz gönderilmeden önce Client ve Server güvenli iletişim için gerekli adımları tamamlar.&lt;/p&gt;

&lt;p&gt;TLS'in ilk görevlerinden biri Client'ın bağlandığı Server'ın identity'sini doğrulamaktır.&lt;/p&gt;

&lt;p&gt;Burada &lt;strong&gt;TLS Certificate&lt;/strong&gt; devreye girer.&lt;/p&gt;

&lt;p&gt;Server, TLS Handshake sırasında Client'a Certificate'ını gönderir.&lt;/p&gt;

&lt;p&gt;Certificate içerisinde diğer bilgilerin yanında hangi domain veya domain'ler için geçerli olduğu bilgisi bulunur.&lt;/p&gt;

&lt;p&gt;Client daha sonra kabaca şu kontrolleri yapar:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Server Certificate
        ↓
Certificate güvenilir bir CA tarafından imzalanmış mı?
        ↓
Certificate geçerlilik süresi içinde mi?
        ↓
Certificate bağlanılan domain için geçerli mi?
        ↓
Validation başarılı
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Buradaki &lt;strong&gt;CA — Certificate Authority&lt;/strong&gt;, Certificate'ların güvenilirliğini doğrulayan yapılardır.&lt;/p&gt;

&lt;p&gt;Browser ve işletim sistemleri önceden güvendikleri Root CA'ların listesini tutar. Server'ın gönderdiği Certificate'ın güven zinciri bu trusted root'lardan birine kadar doğrulanabiliyorsa Certificate güvenilir kabul edilebilir.&lt;/p&gt;

&lt;p&gt;Domain kontrolü de bu sürecin önemli bir parçasıdır.&lt;/p&gt;

&lt;p&gt;Örneğin Client:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;https://www.wikipedia.org
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;adresine bağlanıyorsa gelen Certificate'ın &lt;code&gt;www.wikipedia.org&lt;/code&gt; için geçerli olup olmadığını kontrol eder.&lt;/p&gt;

&lt;p&gt;Certificate, Server'ın &lt;strong&gt;public key&lt;/strong&gt;'ini de içerir; eşleşen &lt;strong&gt;private key&lt;/strong&gt; ise Client'a gönderilmez. Server, TLS Handshake sırasında private key'e sahip olduğunu dijital imzayla kanıtlar; Client bu imzayı public key ile doğrular. Böylece anahtar çifti Server'ın kimliğini doğrulamada kullanılır.&lt;/p&gt;

&lt;p&gt;Client ve Server, Handshake sırasında ortak bir sır üzerinde anlaşarak &lt;strong&gt;simetrik session key&lt;/strong&gt;'ler türetir. Sonraki HTTP iletişimi bu key'lerle şifrelenir; her Request public key ile ayrı ayrı şifrelenmez.&lt;/p&gt;

&lt;p&gt;Basitleştirilmiş akış şu şekilde düşünülebilir:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Client
  │
  │ TCP Connection
  ▼
Server
  │
  │ TLS Handshake
  │ Certificate
  ▼
Client
  │
  │ Certificate Validation
  ▼
TLS Session Keys
  │
  ▼
Encrypted HTTP Request / Response
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Bu noktadan sonra network üzerinde taşınan HTTP içeriği doğrudan plaintext olarak görülmez.&lt;/p&gt;

&lt;p&gt;Yani HTTPS yalnızca "HTTP'nin şifreli hali" demekten biraz daha fazlasını sağlar.&lt;/p&gt;

&lt;p&gt;Client açısından iki önemli soru cevaplanmış olur:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Doğru Server ile mi konuşuyorum?
        ↓
Certificate Authentication

Aradaki trafik okunabilir veya değiştirilebilir mi?
        ↓
Encryption + Integrity
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Bu yapıdan sonra IP adresine doğrudan HTTPS Request gönderdiğimizde neden Certificate hatası aldığımızı inceleyebiliriz.&lt;/p&gt;

&lt;h4&gt;
  
  
  IP Adresine Doğrudan HTTPS Request Gönderirsek Ne Oluyor?
&lt;/h4&gt;

&lt;p&gt;DNS sorgusundan Wikipedia'nın IP adreslerinden birini elde etmiştik:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;185.15.58.224
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;IP adresini bildiğimize göre doğrudan HTTPS Request göndermeyi deneyebiliriz:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;PS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;C:\&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;curl.exe&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;https://185.15.58.224&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Ancak Request başarılı olmuyor:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;curl: (60) schannel: SNI or certificate check failed: SEC_E_WRONG_PRINCIPAL (0x80090322) - Hedef asıl adı yanlış.

curl failed to verify the legitimacy of the server and therefore could not establish a secure connection to it. 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Burada problem TCP Connection'dan farklı bir katmanda oluşuyor.&lt;/p&gt;

&lt;p&gt;IP adresine ulaşabiliyoruz ancak TLS Certificate doğrulaması başarısız oluyor.&lt;/p&gt;

&lt;p&gt;Server tarafından kullanılan Certificate, &lt;code&gt;wikipedia.org&lt;/code&gt; gibi domain isimleri için düzenlenmiş durumda.&lt;/p&gt;

&lt;p&gt;Client ise şu adrese bağlanmaya çalışıyor:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;https://185.15.58.224
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;curl&lt;/code&gt;, Server'ın gönderdiği Certificate ile ulaşmaya çalıştığımız adresi karşılaştırıyor.&lt;/p&gt;

&lt;p&gt;Certificate içerisindeki geçerli isimlerle IP adresi eşleşmediği için TLS doğrulaması başarısız oluyor ve HTTPS Connection devam etmiyor.&lt;/p&gt;




&lt;h4&gt;
  
  
  Aynı Request'i Domain ile Gönderirsek
&lt;/h4&gt;

&lt;p&gt;Bu kez Request'i IP yerine domain üzerinden gönderelim:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;PS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;C:\&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;curl.exe&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;https://www.wikipedia.org&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="err"&gt;&amp;lt;&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;DOCTYPE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;html&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="err"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;html&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;lang&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"en"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kr"&gt;class&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"no-js"&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="o"&gt;...&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Bu durumda TLS doğrulaması başarılı oluyor ve Response alınabiliyor.&lt;/p&gt;

&lt;p&gt;Burada &lt;strong&gt;SNI — Server Name Indication&lt;/strong&gt; da devreye giriyor.&lt;/p&gt;

&lt;p&gt;TLS Handshake sırasında Client, hangi domain'e ulaşmak istediğini Server'a bildiriyor.&lt;/p&gt;

&lt;p&gt;Basitleştirilmiş olarak:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Client
  │
  │ TLS Handshake
  │ SNI: www.wikipedia.org
  ▼
Server
  │
  │ Certificate for wikipedia.org
  ▼
Client
  │
  │ Certificate validation
  ▼
HTTPS Connection
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Böylece Server doğru Certificate'ı sunabiliyor ve Client Certificate doğrulamasını gerçekleştirebiliyor.&lt;/p&gt;

&lt;p&gt;Burada DNS, TCP ve TLS'in farklı görevleri olduğu daha net görülüyor:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;DNS
→ Hangi IP adresine gideceğiz?

TCP
→ Server ile reliable Connection nasıl kuracağız?

TLS
→ Bağlandığımız Server'ı nasıl doğrulayacağız ve trafiği nasıl şifreleyeceğiz?

HTTP
→ Hangi resource'u isteyeceğiz?
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h3&gt;
  
  
  Adım 5: Tek IP Adresinde Birden Fazla Site Nasıl Çalışıyor?
&lt;/h3&gt;

&lt;p&gt;Tek bir IP adresi birden fazla web sitesine hizmet verebilir. HTTP/1.1 Request'indeki &lt;code&gt;Host&lt;/code&gt; Header'ı, Server'ın hangi site için yanıt üreteceğini belirlemesine yardımcı olur. Bunu &lt;code&gt;http.badssl.com&lt;/code&gt; için bulduğumuz &lt;code&gt;104.154.89.105&lt;/code&gt; adresiyle üç şekilde inceleyelim.&lt;/p&gt;

&lt;h4&gt;
  
  
  Yalnızca IP Adresine Request
&lt;/h4&gt;

&lt;p&gt;Önce &lt;code&gt;Host&lt;/code&gt; değerini ayrıca belirtmeden IP adresine gidelim:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;PS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;C:\&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;curl.exe&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;104.154.89.105&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="err"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;&lt;/span&gt;&lt;span class="nx"&gt;Welcome&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;to&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;nginx&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="err"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;/title&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="o"&gt;...&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="err"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;h1&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;&lt;/span&gt;&lt;span class="nx"&gt;Welcome&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;to&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;nginx&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="err"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;/h1&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Burada &lt;code&gt;curl&lt;/code&gt;, URL'deki IP adresini &lt;code&gt;Host&lt;/code&gt; değeri olarak kullanır. &lt;code&gt;http.badssl.com&lt;/code&gt; istenmediği için BadSSL sayfası yerine Nginx'in karşılama sayfası döner.&lt;/p&gt;

&lt;h4&gt;
  
  
  IP Adresine Manuel &lt;code&gt;Host&lt;/code&gt; Header ile Request
&lt;/h4&gt;

&lt;p&gt;Şimdi aynı IP adresine bağlanıp hangi siteyi istediğimizi ayrıca belirtelim:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;PS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;C:\&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;curl.exe&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;104.154.89.105&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-H&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Host: http.badssl.com"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="err"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;&lt;/span&gt;&lt;span class="nx"&gt;http.badssl.com&lt;/span&gt;&lt;span class="err"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;/title&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="o"&gt;...&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="err"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;h1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;style&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"font-size: 8vw;"&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;http.badssl.com&lt;/span&gt;&lt;span class="err"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;/h1&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Ağdaki hedef hâlâ &lt;code&gt;104.154.89.105&lt;/code&gt;. Değişen şey HTTP Request'indeki &lt;code&gt;Host: http.badssl.com&lt;/code&gt; başlığı. Server bu bilgiyi kullanarak BadSSL sayfasını döndürüyor. Bu, &lt;strong&gt;Virtual Host&lt;/strong&gt; mantığını somutlaştırıyor.&lt;/p&gt;

&lt;h3&gt;
  
  
  URL ile Request ve &lt;code&gt;curl -v&lt;/code&gt; Çıktısı
&lt;/h3&gt;

&lt;p&gt;Son olarak IP yerine URL'yi kullanalım. &lt;code&gt;-v&lt;/code&gt; seçeneği bağlantı bilgisini, giden Request'i ve gelen Response'u görmemizi sağlıyor. Ham çıktının ilgili satırları şöyle:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;PS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;C:\&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;curl.exe&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-v&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;http://http.badssl.com&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Host&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;http.badssl.com:80&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;was&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;resolved.&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;IPv4:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;104.154.89.105&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="n"&gt;Trying&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;104.154.89.105:80...&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Established&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;connection&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;to&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;http.badssl.com&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;104.154&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;89&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;105&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;port&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;80&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;192.168.18.51&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;port&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;57300&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;GET&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;/&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;HTTP/1.1&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Host:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;http.badssl.com&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;User-Agent:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;curl/8.21.0&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Accept:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;/&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="err"&gt;&amp;lt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;HTTP/1.1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;200&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;OK&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="err"&gt;&amp;lt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Server:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;nginx/1.10.3&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Ubuntu&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="err"&gt;&amp;lt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Content-Type:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;text/html&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="err"&gt;&amp;lt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Content-Length:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;483&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="err"&gt;&amp;lt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Cache-Control:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;no-store&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="err"&gt;&amp;lt;&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;DOCTYPE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;html&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="o"&gt;...&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="err"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;&lt;/span&gt;&lt;span class="nx"&gt;http.badssl.com&lt;/span&gt;&lt;span class="err"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;/title&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;*&lt;/code&gt; ile başlayan satırlar adın IP adresine çözülmesini ve bağlantıyı gösteriyor. &lt;code&gt;&amp;gt;&lt;/code&gt; işaretli satırlar giden HTTP Request'i: &lt;code&gt;curl&lt;/code&gt;, URL'deki &lt;code&gt;http.badssl.com&lt;/code&gt; adını alıp &lt;code&gt;Host&lt;/code&gt; Header'ına otomatik eklemiş. &lt;code&gt;&amp;lt;&lt;/code&gt; işaretli satırlar ise gelen Response'un status ve header bilgileri. &lt;code&gt;200 OK&lt;/code&gt; yanıtından sonra HTML içeriği geliyor.&lt;/p&gt;

&lt;p&gt;Üç isteğin ulaştığı IP aynı; dönen sayfayı değiştiren, HTTP seviyesinde hangi host'un istendiği. DNS IP adresini bulurken &lt;code&gt;Host&lt;/code&gt; Header'ı Server'ın doğru siteyi seçmesine yardımcı oluyor.&lt;/p&gt;




&lt;h2&gt;
  
  
  Adım 6: Gelen Yanıt Tarayıcıda Nasıl Görünüyor?
&lt;/h2&gt;

&lt;p&gt;Şimdiye kadar Request'in Server'a ulaşmasını ve Response'un geri dönmesini inceledik. Peki Response Body içerisinde HTML geldiğinde Browser bunu nasıl gördüğümüz sayfaya dönüştürüyor?&lt;/p&gt;

&lt;p&gt;Browser HTML'i okuyarak sayfanın yapısını temsil eden &lt;strong&gt;DOM&lt;/strong&gt;'u oluşturur. HTML içinde bağlantısı verilen CSS, JavaScript ve görseller gibi dosyalar için de gerektiğinde yeni HTTP Request'ler gönderir. CSS kurallarını işleyip hangi öğelerin nasıl görüneceğini belirler; ardından öğelerin ekrandaki yerini hesaplar ve sayfayı çizer. JavaScript çalıştığında DOM'u değiştirebilir, yeni veriler isteyebilir ve sayfanın yeniden güncellenmesine yol açabilir.&lt;/p&gt;

&lt;p&gt;Bu adımlar her zaman birbirini bekleyen tek bir sıra halinde gerçekleşmez. Browser, HTML'in tamamı gelmeden bazı kaynakları indirmeye ve sayfanın bir bölümünü göstermeye başlayabilir.&lt;/p&gt;

&lt;p&gt;Terminalde kullandığımız &lt;code&gt;curl&lt;/code&gt; ile Browser arasındaki fark da burada belirginleşir: &lt;code&gt;curl&lt;/code&gt; bize Response'un içeriğini gösterir; Browser ise bu içeriği ve diğer kaynakları işleyerek ekranda gördüğümüz sayfayı oluşturur.&lt;/p&gt;




&lt;h2&gt;
  
  
  Kendime Notlar
&lt;/h2&gt;

&lt;p&gt;Bu konuları çalışmadan önce networking tarafındaki birçok kavram birbirinden bağımsız başlıklar gibi görünüyordu.&lt;/p&gt;

&lt;p&gt;DNS ayrı bir konu, TCP ayrı bir konu, TLS ayrı bir konu, HTTP ayrı bir konu.&lt;/p&gt;

&lt;p&gt;Terminal üzerinde Request'in geçtiği adımları incelemeye başladığımda bunların aslında aynı iletişim sürecinin farklı aşamaları olduğu daha görünür hale geldi.&lt;/p&gt;

&lt;p&gt;Frontend tarafında &lt;code&gt;fetch()&lt;/code&gt; çağrısı yaparken veya backend tarafında bir route tanımlarken bütün bu katmanları sürekli düşünmek gerekmiyor.&lt;/p&gt;

&lt;p&gt;Ancak Request beklediğimiz gibi çalışmadığında arka plandaki akışı bilmek problemin hangi noktada oluştuğunu anlamayı kolaylaştırıyor.&lt;/p&gt;

&lt;p&gt;Bir HTTP Request artık yalnızca:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Client
  ↓
Server
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;şeklinde düşünülmek zorunda değil.&lt;/p&gt;

&lt;p&gt;Arka plandaki akış daha çok şu şekilde ilerliyor:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;URL
 ↓
DNS
 ↓
IP
 ↓
TCP
 ↓
TLS
 ↓
HTTP
 ↓
Response
 ↓
Browser'da işleme ve render
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Bu çalışmadan çıkan en faydalı alışkanlıklardan biri de şu oldu:&lt;/p&gt;

&lt;p&gt;Bir Request beklediğim gibi çalışmadığında yalnızca Browser Console'a veya backend log'larına bakmak yerine terminal üzerinden Request'in kendisini de incelemek.&lt;/p&gt;

</description>
      <category>software</category>
      <category>webdev</category>
      <category>beginners</category>
      <category>learning</category>
    </item>
  </channel>
</rss>
