<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[Serhat Levent Yavaş]]></title><description><![CDATA[Software Developer]]></description><link>https://serhatleventyavas.dev</link><generator>RSS for Node</generator><lastBuildDate>Sat, 06 Jun 2026 10:00:19 GMT</lastBuildDate><atom:link href="https://serhatleventyavas.dev/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[AWS Regions ve Availability Zones: Global Altyapının Temel Taşları]]></title><description><![CDATA[Bulut bilişim, işletmelerin dijital çağda rekabet avantajını sürdürebilmesi için kritik bir rol oynamaktadır. Amazon Web Services (AWS), sunduğu geniş hizmet yelpazesi ve küresel altyapısıyla bu alanda lider konumdadır. AWS'nin yüksek erişilebilirlik...]]></description><link>https://serhatleventyavas.dev/aws-regions-ve-availability-zones-global-altyapinin-temel-taslari</link><guid isPermaLink="true">https://serhatleventyavas.dev/aws-regions-ve-availability-zones-global-altyapinin-temel-taslari</guid><category><![CDATA[AWS]]></category><category><![CDATA[aws regions]]></category><category><![CDATA[AWS Availability zones]]></category><category><![CDATA[AWS Cloud Practitioner]]></category><category><![CDATA[#aws#region#availabilityzone]]></category><dc:creator><![CDATA[Serhat Levent Yavaş]]></dc:creator><pubDate>Tue, 15 Apr 2025 09:43:04 GMT</pubDate><content:encoded><![CDATA[<p>Bulut bilişim, işletmelerin dijital çağda rekabet avantajını sürdürebilmesi için kritik bir rol oynamaktadır. Amazon Web Services (AWS), sunduğu geniş hizmet yelpazesi ve küresel altyapısıyla bu alanda lider konumdadır. AWS'nin yüksek erişilebilirlik, dayanıklılık ve ölçeklenebilirlik sunmasını mümkün kılan temel yapı taşlarından ikisi ise "Regions" (Bölgeler) ve "Availability Zones" (Erişilebilirlik Alanları) kavramlarıdır. Bu yazıda, bu iki kritik bileşeni detaylıca inceleyecek ve bulut mimarinizi inşa ederken neden dikkate almanız gerektiğini ele alacağız.</p>
<h3 id="heading-aws-global-altyapisina-genel-bakis">AWS Global Altyapısına Genel Bakış</h3>
<p>AWS, dünya çapındaki veri merkezlerini <strong>yüksek kullanılabilirlik ve düşük gecikme süreleriyle sunmak</strong> için bölgesel olarak organize eder. Bu altyapı aşağıdaki ana bileşenlerden oluşur:</p>
<ul>
<li><p><strong>AWS Bölgeleri (Regions):</strong> Birbirinden bağımsız coğrafi alanlardır.</p>
</li>
<li><p><strong>Erişilebilirlik Alanları (Availability Zones):</strong> Her bir bölge içinde yer alan, birbirinden izole edilmiş fiziksel konumlardır.</p>
</li>
<li><p><strong>Yerel Alanlar (Local Zones):</strong> Hesaplama ve depolama kaynaklarını son kullanıcılara daha yakın konumlara yerleştirme imkanı sunar.</p>
</li>
<li><p><strong>AWS Outposts:</strong> AWS'nin yerel hizmetlerini, altyapısını ve işletim modellerini neredeyse her veri merkezine, ortak barındırma alanına veya şirket içi tesise getirir.</p>
</li>
<li><p><strong>Wavelength Alanları (Wavelength Zones):</strong> 5G cihazlara ve son kullanıcılara ultra düşük gecikmeli uygulamalar sunmayı sağlar. Wavelength, standart AWS hesaplama ve depolama hizmetlerini telekom operatörlerinin 5G ağlarının uç noktalarına yerleştirir.</p>
</li>
</ul>
<p>Bu yazıda, özellikle Bölgeler ve Erişilebilirlik Alanları kavramlarına odaklanacağız.</p>
<h3 id="heading-1-aws-regions-cografi-dagilim-ve-izolasyon">1. AWS Regions: Coğrafi Dağılım ve İzolasyon</h3>
<p><img src="https://docs.aws.amazon.com/images/global-infrastructure/latest/regions/images/regions.png" alt="Multiple Regions in the AWS cloud." class="image--center mx-auto" /></p>
<p>Bir AWS Region, diğerlerinden izole şekilde konumlandırılmış bir coğrafi alandır. Her Region kendi veri merkezi kümelerine sahiptir ve bu izolasyon, yüksek hata toleransı ile operasyonel süreklilik sağlar.</p>
<p><strong>1.1 Neden Bölge Seçimi Önemlidir?</strong></p>
<p>AWS'de kaynak dağıtırken doğru bölgeyi seçmek kritik bir karardır. Aşağıdaki faktörler dikkate alınmalıdır:</p>
<ul>
<li><p><strong>Gecikme Süresi:</strong> Kullanıcılara en yakın bölge seçilerek performans artırılır.</p>
</li>
<li><p><strong>Yasal Düzenlemeler ve Uyumluluk:</strong> Yasal düzenlemeler gereği verilerin belirli ülkelerde tutulması gerekebilir.</p>
</li>
<li><p><strong>Hizmet Erişilebilirliği:</strong> Bazı AWS hizmetleri her bölgede mevcut olmayabilir.</p>
</li>
<li><p><strong>Maliyet:</strong> Farklı bölgelerdeki servis fiyatları değişiklik gösterebilir.</p>
</li>
</ul>
<p><strong>1.2 Kaynak İzolasyonu</strong></p>
<p>Çoğu AWS kaynağı bölgeseldir. Örneğin, Frankfurt (eu-central-1) bölgesindeki bir EC2 instance'ı, Ohio (us-east-2) bölgesinden doğrudan erişilemez. Bu yapı, veri güvenliği ve izolasyon avantajı sağlar.</p>
<p><strong>1.3 Bölgelerin Listesi ve Özellikleri</strong></p>
<p>AWS, dünya çapında sürekli olarak genişleyen bir bölge ağına sahiptir. Her bir bölge, farklı sayıda Erişilebilirlik Alanı'na (Availability Zones) sahip olabilir. Aşağıdaki tabloda, AWS bölgeleri ve temel özellikleri verilmiştir:</p>
<div class="hn-table">
<table>
<thead>
<tr>
<td><strong>Code</strong></td><td><strong>Name</strong></td><td><strong>AZs</strong></td><td><strong>Geography</strong></td><td><strong>Opt-in status</strong></td></tr>
</thead>
<tbody>
<tr>
<td>us-east-1</td><td>US East (N. Virginia)</td><td>6</td><td>United States of America</td><td>Not required</td></tr>
<tr>
<td>us-east-2</td><td>US East (Ohio)</td><td>3</td><td>United States of America</td><td>Not required</td></tr>
<tr>
<td>us-west-1</td><td>US West (N. California)</td><td>3  †</td><td>United States of America</td><td>Not required</td></tr>
<tr>
<td>us-west-2</td><td>US West (Oregon)</td><td>4</td><td>United States of America</td><td>Not required</td></tr>
<tr>
<td>af-south-1</td><td>Africa (Cape Town)</td><td>3</td><td>South Africa</td><td>Required</td></tr>
<tr>
<td>ap-east-1</td><td>Asia Pacific (Hong Kong)</td><td>3</td><td>Hong Kong</td><td>Required</td></tr>
<tr>
<td>ap-south-2</td><td>Asia Pacific (Hyderabad)</td><td>3</td><td>India</td><td>Required</td></tr>
<tr>
<td>ap-southeast-3</td><td>Asia Pacific (Jakarta)</td><td>3</td><td>Indonesia</td><td>Required</td></tr>
<tr>
<td>ap-southeast-5</td><td>Asia Pacific (Malaysia)</td><td>3</td><td>Malaysia</td><td>Required</td></tr>
<tr>
<td>ap-southeast-4</td><td>Asia Pacific (Melbourne)</td><td>3</td><td>Australia</td><td>Required</td></tr>
<tr>
<td>ap-south-1</td><td>Asia Pacific (Mumbai)</td><td>3</td><td>India</td><td>Not required</td></tr>
<tr>
<td>ap-northeast-3</td><td>Asia Pacific (Osaka)</td><td>3</td><td>Japan</td><td>Not required</td></tr>
<tr>
<td>ap-northeast-2</td><td>Asia Pacific (Seoul)</td><td>4</td><td>South Korea</td><td>Not required</td></tr>
<tr>
<td>ap-southeast-1</td><td>Asia Pacific (Singapore)</td><td>3</td><td>Singapore</td><td>Not required</td></tr>
<tr>
<td>ap-southeast-2</td><td>Asia Pacific (Sydney)</td><td>3</td><td>Australia</td><td>Not required</td></tr>
<tr>
<td>ap-southeast-7</td><td>Asia Pacific (Thailand)</td><td>3</td><td>Thailand</td><td>Required</td></tr>
<tr>
<td>ap-northeast-1</td><td>Asia Pacific (Tokyo)</td><td>4</td><td>Japan</td><td>Not required</td></tr>
<tr>
<td>ca-central-1</td><td>Canada (Central)</td><td>3</td><td>Canada</td><td>Not required</td></tr>
<tr>
<td>ca-west-1</td><td>Canada West (Calgary)</td><td>3</td><td>Canada</td><td>Required</td></tr>
<tr>
<td>eu-central-1</td><td>Europe (Frankfurt)</td><td>3</td><td>Germany</td><td>Not required</td></tr>
<tr>
<td>eu-west-1</td><td>Europe (Ireland)</td><td>3</td><td>Ireland</td><td>Not required</td></tr>
<tr>
<td>eu-west-2</td><td>Europe (London)</td><td>3</td><td>United Kingdom</td><td>Not required</td></tr>
<tr>
<td>eu-south-1</td><td>Europe (Milan)</td><td>3</td><td>Italy</td><td>Required</td></tr>
<tr>
<td>eu-west-3</td><td>Europe (Paris)</td><td>3</td><td>France</td><td>Not required</td></tr>
<tr>
<td>eu-south-2</td><td>Europe (Spain)</td><td>3</td><td>Spain</td><td>Required</td></tr>
<tr>
<td>eu-north-1</td><td>Europe (Stockholm)</td><td>3</td><td>Sweden</td><td>Not required</td></tr>
<tr>
<td>eu-central-2</td><td>Europe (Zurich)</td><td>3</td><td>Switzerland</td><td>Required</td></tr>
<tr>
<td>il-central-1</td><td>Israel (Tel Aviv)</td><td>3</td><td>Israel</td><td>Required</td></tr>
<tr>
<td>mx-central-1</td><td>Mexico (Central)</td><td>3</td><td>Mexico</td><td>Required</td></tr>
<tr>
<td>me-south-1</td><td>Middle East (Bahrain)</td><td>3</td><td>Bahrain</td><td>Required</td></tr>
<tr>
<td>me-central-1</td><td>Middle East (UAE)</td><td>3</td><td>United Arab Emirates</td><td>Required</td></tr>
<tr>
<td>sa-east-1</td><td>South America (São Paulo)</td><td>3</td><td>Brazil</td><td>Not required</td></tr>
</tbody>
</table>
</div><blockquote>
<p>Daha fazla bölge için <a target="_blank" href="https://aws.amazon.com/about-aws/global-infrastructure/">AWS Global Infrastructure sayfasına</a> başvurabilirsiniz.</p>
</blockquote>
<p><strong>1.4 Yeni Bölgelerin Etkinleştirilmesi</strong></p>
<p>20 Mart 2019'dan sonra eklenen yeni AWS Bölgeleri, varsayılan olarak devre dışıdır. Bu bölgeleri kullanmaya başlamadan önce AWS Management Console, API veya CLI aracılığıyla manuel olarak etkinleştirmeniz gerekmektedir. 20 Mart 2019'dan önceki bölgeler ise otomatik olarak etkindir ve hemen kullanılabilir.</p>
<p>Aşağıda varsayılan olarak etkin olan AWS Bölgeleri verilmiştir:</p>
<ul>
<li><p>US East (N. Virginia)</p>
</li>
<li><p>US East (Ohio)</p>
</li>
<li><p>US West (N. California)</p>
</li>
<li><p>US West (Oregon)</p>
</li>
<li><p>Asia Pacific (Mumbai)</p>
</li>
<li><p>Asia Pacific (Osaka)</p>
</li>
<li><p>Asia Pacific (Seoul)</p>
</li>
<li><p>Asia Pacific (Singapore)</p>
</li>
<li><p>Asia Pacific (Sydney)</p>
</li>
<li><p>Asia Pacific (Tokyo)</p>
</li>
<li><p>Canada (Central)</p>
</li>
<li><p>Europe (Frankfurt)</p>
</li>
<li><p>Europe (Ireland)</p>
</li>
<li><p>Europe (London)</p>
</li>
<li><p>Europe (Paris)</p>
</li>
<li><p>Europe (Stockholm)</p>
</li>
<li><p>South America (São Paulo)</p>
</li>
</ul>
<h3 id="heading-2-availability-zones-az-bolge-ici-yuksek-erisilebilirlik">2. Availability Zones (AZ): Bölge İçi Yüksek Erişilebilirlik</h3>
<p><img src="https://docs.aws.amazon.com/images/global-infrastructure/latest/regions/images/availability-zones.png" alt="A Region three Availability Zones." class="image--center mx-auto" /></p>
<p>Her bir AWS Bölgesi, "Erişilebilirlik Alanları" (Availability Zones - AZs) olarak adlandırılan, birbirinden izole edilmiş birden fazla fiziksel konumdan oluşur. <strong>Her bölge en az 3 Erişilebilirlik Alanı içerir.</strong> Erişilebilirlik Alanları, bir bölge içinde <strong>yüksek erişilebilirlik ve hata toleransı</strong> sağlamak amacıyla tasarlanmıştır.</p>
<p><strong>2.1 Erişilebilirlik Alanlarının Özellikleri</strong></p>
<ul>
<li><p>Bu alanlar, <strong>düşük gecikmeli, yüksek bant genişliğine sahip özel metro fiber bağlantılarıyla</strong> birbirine bağlıdır. Bu bağlantılar, Erişilebilirlik Alanları arasında hızlı ve güvenilir veri transferi sağlar.</p>
</li>
<li><p>Her bir Erişilebilirlik Alanı (Availability Zone), <strong>bir veya daha fazla bağımsız veri merkezinden oluşur.</strong> Bu veri merkezlerinin her biri, yedekli güç kaynakları, ağ bağlantıları gibi çeşitli yedekli bileşenlere sahiptir ve ayrı fiziksel tesislerde barındırılır. Bu fiziksel ayrılık, yangın, fırtına veya sel gibi tek bir konumu etkileyebilecek olaylardan kaynaklanan kesintilere karşı koruma sağlar.</p>
</li>
</ul>
<h4 id="heading-22-kaynak-tipleri-global-bolgesel-vs-zonal"><strong>2.2 Kaynak Tipleri: Global, Bölgesel vs Zonal</strong></h4>
<p>AWS kaynakları, erişilebilirlik kapsamlarına göre global, bölgesel veya zonal olabilir. Bu ayrımı anlamak, AWS kaynaklarınızı etkili bir şekilde yönetmek, yüksek erişilebilirlik sağlamak ve maliyetleri optimize etmek için kritik önem taşır.</p>
<div class="hn-table">
<table>
<thead>
<tr>
<td>Kaynak Türü</td><td>Açıklama</td><td>Örnekler</td><td>Ek Notlar</td></tr>
</thead>
<tbody>
<tr>
<td>Global</td><td>Hizmetin bölgelerden bağımsız olarak AWS genelinde sunulmasıdır. Kaynaklar, tüm bölgelerden erişilebilir ve merkezi olarak yönetilir.</td><td>IAM kullanıcıları, rolleri, politikaları; Route 53 (çoğunlukla); CloudFront</td><td>Global hizmetler, genellikle yönetim ve kontrol düzlemi için geçerlidir. Veri düzlemi bazı durumlarda bölgesel olabilir (örneğin, CloudFront önbellekleri).</td></tr>
<tr>
<td>Bölgesel</td><td>Hizmetin belirli bir AWS Bölgesi'ne özgü olmasıdır. Kaynaklar, o bölgedeki tüm Erişilebilirlik Alanları (AZ) tarafından erişilebilir.</td><td>S3 bucket'ları; DynamoDB tabloları; EC2 snapshot'ları; VPC'ler</td><td>Bölgesel hizmetler, veri yedekliliği ve bölgesel bağımsızlık sağlar.</td></tr>
<tr>
<td>Zonal</td><td>Hizmetin belirli bir Erişilebilirlik Alanı (AZ) içinde sunulmasıdır. Kaynaklar, yalnızca o AZ içindeki diğer kaynaklar tarafından doğrudan ve düşük gecikmeyle erişilebilir.</td><td>EC2 instance'ları; EBS volume'leri; RDS instance'ları; Subnet'ler</td><td>Zonal hizmetler, en yüksek performansı ve en düşük gecikmeyi sunar, ancak yüksek erişilebilirlik için birden fazla AZ'ye dağıtılmalıdır.</td></tr>
</tbody>
</table>
</div><p>Zonal kaynaklar arasında yüksek performanslı iletişim mümkünken, farklı AZ’ler arası veri trafiği az da olsa gecikmeye neden olabilir. Örneğin, bir EC2 instance'ı belirli bir Erişilebilirlik Alanı içinde başlatılır ve o alandaki diğer kaynaklarla (örneğin, aynı alandaki EBS volume'leri) düşük gecikmeyle iletişim kurabilir. Ancak, farklı bir Erişilebilirlik Alanındaki bir EC2 instance'ı ile iletişim kurması, bölge içindeki alanlar arası ağ trafiği üzerinden gerçekleşir ve bu da biraz daha yüksek gecikmeye neden olabilir. Bu ayrım, yüksek erişilebilirlik stratejileri tasarlarken kritik rol oynar. Uygulamalarınızı birden fazla Erişilebilirlik Alanına dağıtarak, bir alanda meydana gelebilecek bir sorunun uygulamanızın tamamını etkilemesini önleyebilirsiniz.</p>
<h4 id="heading-23-yuksek-erisilebilirlik-stratejisi"><strong>2.3 Yüksek Erişilebilirlik Stratejisi</strong></h4>
<p>Uygulamalarınızın yüksek erişilebilirliğini sağlamak için, AWS, kaynakların birden fazla Erişilebilirlik Alanına dağıtılmasını önerir. Bu mimari sayesinde, bir alandaki arıza durumunda bile uygulamanız diğer alanlarda çalışmayı sürdürebilir. Örneğin; bir load balancer ile iki farklı AZ'de yer alan EC2 instance'lar arasında trafik yönlendirme yapılabilir.</p>
<h4 id="heading-24-az-adlari-ve-az-kimlikleri-aciklama-farklar-ve-onemi"><strong>2.4 AZ Adları ve AZ Kimlikleri: Açıklama, Farklar ve Önemi</strong></h4>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1744709211597/8210d1ca-66e3-4fbb-8aef-7f26b7c80b26.png" alt class="image--center mx-auto" /></p>
<p>AWS'de her bir Erişilebilirlik Alanı (Availability Zone - AZ), ait olduğu bölge içinde benzersiz bir konuma sahiptir. Örneğin, "eu-central-1" (Frankfurt) bölgesindeki AZ'ler "eu-central-1a", "eu-central-1b" ve "eu-central-1c" olarak adlandırılır. Ancak, AWS altyapısında Erişilebilirlik Alanlarını yönetirken, "AZ Adları" ve "AZ Kimlikleri" olmak üzere iki farklı tanımlayıcı türüyle karşılaşırız ve bu ikisi arasındaki ayrımı anlamak kritik önem taşır.</p>
<p><strong>AZ Adları,</strong> AWS hesabına özgüdür. Bu, "us-east-1a" gibi bir AZ Adının, farklı AWS hesaplarında farklı fiziksel konumları işaret edebileceği anlamına gelir.</p>
<ul>
<li>Örneğin, bir veri merkezinin adı Türkiye olsun. sizin AWS hesabınızda "us-east-1a" Erişilebilirlik Alanı, Türkiye veri merkezine karşılık gelirken, başka bir AWS hesabında "us-east-1b" Türkiye veri merkezini gösterebilir.</li>
</ul>
<p>Bu durum, farklı AWS hesapları arasında Erişilebilirlik Alanlarının fiziksel konumlarını karşılaştırmayı zorlaştırır. Aynı AZ Adına sahip kaynakların, farklı hesaplarda fiziksel olarak farklı yerlerde olabileceğini unutmamak önemlidir.</p>
<p><strong>AZ Kimlikleri (AZ IDs)</strong> ise hesaptan bağımsızdır ve AWS tarafından global olarak tanımlanır. Bir AZ Kimliği, belirli bir <strong>Erişilebilirlik Alanının evrensel kimliğidir</strong> ve tüm AWS hesaplarında aynı fiziksel konumu temsil eder.</p>
<ul>
<li>Örneğin, "use1-az1" AZ Kimliği, "us-east-1" bölgesindeki belirli bir veri merkezini ifade eder ve bu kimlik, tüm AWS hesaplarında aynı fiziksel konumu gösterir.</li>
</ul>
<p>Bu sayede, farklı AWS hesapları arasında Erişilebilirlik Alanlarının fiziksel konumları hakkında net ve tutarlı bir iletişim sağlanır.</p>
<p>Aşağıdaki tablo, AZ Adları ve AZ Kimlikleri arasındaki temel farkları özetlemektedir:</p>
<div class="hn-table">
<table>
<thead>
<tr>
<td>Özellik</td><td>AZ Adı (Örn: us-east-1a)</td><td>AZ Kimliği (Örn: use1-az1)</td></tr>
</thead>
<tbody>
<tr>
<td>Hesaba özel mi?</td><td>Evet</td><td>Hayır (Global)</td></tr>
<tr>
<td>Fiziksel yeri sabit mi?</td><td>Değişebilir</td><td>Evet</td></tr>
<tr>
<td>Hesaplar arası kıyas için uygun mu?</td><td>Değil</td><td>Evet</td></tr>
</tbody>
</table>
</div><p>AZ Kimlikleri, özellikle büyük dağıtımlarda veya birden fazla AWS hesabıyla çalışırken, Erişilebilirlik Alanlarının fiziksel konumlarını doğru bir şekilde belirlemek için kritik öneme sahiptir.  Örneğin, Sizin hesabınızda "us-east-1a" AZ Adı "use1-az1" AZ Kimliğine karşılık gelebilir.  Başka birinin hesabında "us-east-1c" AZ Adı da "use1-az1" AZ Kimliğine karşılık gelebilir.</p>
<p>Bu durumda, her iki hesap da aslında aynı fiziksel veri merkezinde çalışıyor olabilir, ancak AZ Adları farklıdır. Bu durum, fiziksel yakınlık veya bağlantı gereksinimleri gibi faktörlerin yanlış yorumlanmasına yol açabilir.</p>
<p>AZ Kimliklerini görüntülemek için AWS CLI'ı kullanabilirsiniz. İşte bir örnek komut:</p>
<p>Bash</p>
<pre><code class="lang-bash">aws ec2 describe-availability-zones \
    --region us-east-1 \
    --query <span class="hljs-string">'AvailabilityZones[*].{Name:ZoneName, AZ_ID:ZoneId}'</span>
</code></pre>
<p>Bu komut, belirtilen bölgedeki Erişilebilirlik Alanlarının AZ Adlarını ve AZ Kimliklerini listeleyecektir</p>
<pre><code class="lang-bash">----------------------------
| DescribeAvailabilityZones|
+-----------+--------------+
|    ID     |    Name      |
+-----------+--------------+
|  use2-az1 |  us-east-2a  |
|  use2-az2 |  us-east-2b  |
|  use2-az3 |  us-east-2c  |
+-----------+--------------+
</code></pre>
<h3 id="heading-sonuc">Sonuç</h3>
<p>AWS’nin global altyapısında temel rol oynayan Bölgeler (Regions) ve Erişilebilirlik Alanları (Availability Zones), güvenilir, yüksek erişilebilirlik sağlayan ve ölçeklenebilir uygulamalar tasarlamanın olmazsa olmaz yapı taşlarıdır. Bölgeler, coğrafi izolasyon sayesinde felaket senaryolarına karşı dayanıklılık sağlarken, Erişilebilirlik Alanları bölge içindeki donanım hatalarına karşı sistem sürekliliğini garanti eder. Uygulama mimarinizi tasarlarken bu kavramları doğru anlamak, AWS'nin önerdiği en iyi uygulamaları (best practices) takip etmek ve kaynaklarınızı uygun şekilde dağıtmak; operasyonel verimlilik, performans ve güvenlik açısından büyük fark yaratacaktır. Bulut yolculuğunuzda sağlam bir temel atmak için bu mimari prensipleri göz ardı etmemeniz gerekir.</p>
<h3 id="heading-kaynaklar">Kaynaklar</h3>
<p><a target="_blank" href="https://docs.aws.amazon.com/global-infrastructure/latest/regions/aws-regions-availability-zones.html">https://docs.aws.amazon.com/global-infrastructure/latest/regions/aws-regions-availability-zones.html</a></p>
<p><a target="_blank" href="https://docs.aws.amazon.com/global-infrastructure/latest/regions/aws-regions.html">https://docs.aws.amazon.com/global-infrastructure/latest/regions/aws-regions.html</a></p>
<p><a target="_blank" href="https://docs.aws.amazon.com/global-infrastructure/latest/regions/aws-availability-zones.html">https://docs.aws.amazon.com/global-infrastructure/latest/regions/aws-availability-zones.html</a></p>
<p><a target="_blank" href="https://docs.aws.amazon.com/global-infrastructure/latest/regions/az-ids.html">https://docs.aws.amazon.com/global-infrastructure/latest/regions/az-ids.html</a></p>
]]></content:encoded></item><item><title><![CDATA[AWS Well-Architected Framework: Bulutta Başarılı Sistemler İnşa Etmenin Anahtarı]]></title><description><![CDATA[Günümüzde bulut bilişim, işletmelerin çevikliklerini artırmaları, maliyetleri optimize etmeleri ve inovasyonu hızlandırmaları için vazgeçilmez bir araç haline gelmiştir. Amazon Web Services (AWS), sunduğu geniş hizmet yelpazesiyle bu dönüşümün öncüle...]]></description><link>https://serhatleventyavas.dev/aws-well-architected-framework-bulutta-basarili-sistemler-insa-etmenin-anahtari</link><guid isPermaLink="true">https://serhatleventyavas.dev/aws-well-architected-framework-bulutta-basarili-sistemler-insa-etmenin-anahtari</guid><category><![CDATA[AWS]]></category><category><![CDATA[AWS Cloud Practitioner]]></category><category><![CDATA[AWS Well-Architected Framework]]></category><category><![CDATA[AWS Well-Architected]]></category><dc:creator><![CDATA[Serhat Levent Yavaş]]></dc:creator><pubDate>Mon, 07 Apr 2025 10:13:18 GMT</pubDate><content:encoded><![CDATA[<p>Günümüzde bulut bilişim, işletmelerin çevikliklerini artırmaları, maliyetleri optimize etmeleri ve inovasyonu hızlandırmaları için vazgeçilmez bir araç haline gelmiştir. Amazon Web Services (AWS), sunduğu geniş hizmet yelpazesiyle bu dönüşümün öncülerinden biridir. Ancak, AWS üzerinde başarılı ve sürdürülebilir sistemler inşa etmek, doğru mimari kararlar almayı gerektirir. Tam da bu noktada, <a target="_blank" href="https://aws.amazon.com/architecture/well-architected/?wa-lens-whitepapers.sort-by=item.additionalFields.sortDate&amp;wa-lens-whitepapers.sort-order=desc&amp;wa-guidance-whitepapers.sort-by=item.additionalFields.sortDate&amp;wa-guidance-whitepapers.sort-order=desc">AWS Well-Architected Framework</a> devreye girmektedir.</p>
<p>Bu blog yazısında, AWS Well-Architected Framework’ün ne olduğunu, temel prensiplerini ve bulut yolculuğunuzda size nasıl rehberlik edebileceğini keşfedeceğiz.</p>
<h2 id="heading-aws-well-architected-framework-nedir">AWS Well-Architected Framework Nedir?</h2>
<p><a target="_blank" href="https://aws.amazon.com/architecture/well-architected/?wa-lens-whitepapers.sort-by=item.additionalFields.sortDate&amp;wa-lens-whitepapers.sort-order=desc&amp;wa-guidance-whitepapers.sort-by=item.additionalFields.sortDate&amp;wa-guidance-whitepapers.sort-order=desc">AWS Well-Architected Framework</a>, AWS üzerinde sistemler inşa ederken aldığınız kararların avantajlarını ve dezavantajlarını anlamanıza yardımcı olan bir kılavuzdur. Amazon'un yıllarca süren deneyimi ve binlerce müşteri mimarisinin incelenmesiyle ortaya çıkan mimari en iyi uygulamaları içerir.</p>
<p>Bu çerçeve, <strong>güvenilir</strong>, <strong>güvenli</strong>, <strong>verimli</strong>, <strong>maliyet-etkin</strong> ve <strong>sürdürülebilir sistemler</strong> tasarlamak ve işletmek için size yol gösterir. Amacı, mimarilerinizi bulut en iyi uygulamalarına göre tutarlı bir şekilde değerlendirmenizi ve iyileştirme alanlarını belirlemenizi sağlamaktır.</p>
<blockquote>
<p>Bu sürecin bir denetim mekanizması değil, yapıcı bir mimari karar tartışması olduğunu unutmayın.</p>
<p>AWS Well-Architected Framework, başta CTO'lar, mimarlar, geliştiriciler ve operasyon ekipleri olmak üzere teknoloji rollerindeki kişiler için tasarlanmıştır.</p>
</blockquote>
<h2 id="heading-aws-well-architected-frameworkun-alti-temel-ilkesi-pillars">AWS Well-Architected Framework'ün Altı Temel İlkesi (Pillars)</h2>
<h3 id="heading-1-operasyonel-mukemmellik-operational-excellence">1. Operasyonel Mükemmellik (Operational Excellence)</h3>
<p>İş yüklerini etkili bir şekilde geliştirme ve çalıştırma, operasyonlar hakkında içgörü kazanma ve iş değeri sunmak için destekleyici süreçleri sürekli olarak iyileştirme yeteneğidir. Bu ilke, ekiplerin organizasyonu, iş yüklerinin tasarımı, ölçekte işletilmesi ve zaman içinde geliştirilmesi için en iyi uygulamaları kapsar.</p>
<blockquote>
<p><strong>Örnek:</strong> CI/CD süreçlerini otomatize eden bir ekip, geri bildirim süresini %30 oranında azaltarak operasyonel verimliliği artırmıştır.</p>
</blockquote>
<h3 id="heading-2-guvenlik-security">2. Güvenlik (Security)</h3>
<p>Veri, sistem ve varlıkların korunmasına odaklanır. Güvenlik temelleri, kimlik ve erişim yönetimi, tehdit tespiti, altyapı koruması, veri güvenliği ve olay müdahalesi gibi alanları kapsar. Bu ilke, güvenli bir mimari kurmak için gerekli yapı taşlarını sunar.</p>
<h3 id="heading-3-guvenilirlik-reliability">3. Güvenilirlik (Reliability)</h3>
<p>Bir iş yükünün beklendiği gibi tutarlı ve doğru şekilde çalışabilmesini ifade eder. İş yükünün tüm yaşam döngüsü boyunca çalıştırılabilmesi ve test edilebilmesi bu ilkenin kapsamına girer. Mimari sağlamlığı, değişiklik yönetimi ve hata toleransı ana başlıklardır.</p>
<h3 id="heading-4-performans-verimliligi-performance-efficiency">4. Performans Verimliliği (Performance Efficiency)</h3>
<p>Bulut kaynaklarının, performans gereksinimlerini karşılayacak şekilde verimli kullanılmasını ve talep ile birlikte bu verimliliğin sürdürülebilmesini kapsar. Doğru hizmet seçimi, veri işleme, ağ yapısı ve içerik dağıtımı bu başlık altında değerlendirilir.</p>
<h3 id="heading-5-maliyet-optimizasyonu-cost-optimization">5. Maliyet Optimizasyonu (Cost Optimization)</h3>
<p>İş değerini korurken en düşük maliyetle çalışabilecek sistemler inşa etmeyi hedefler. Kaynak seçimi, kullanım ile maliyeti eşleştirme, gereksiz kaynaklardan kaçınma ve zaman içinde optimizasyon bu ilkenin odak noktalarıdır.</p>
<h3 id="heading-6-surdurulebilirlik-sustainability">6. Sürdürülebilirlik (Sustainability)</h3>
<p>Kullanılan hizmetlerin çevresel etkisini anlamak ve bu etkileri iş yükü yaşam döngüsü boyunca azaltmak için tasarım kararları almaktır. Yazılım optimizasyonu, donanım kullanımı, veri yönetimi ve gereksiz kaynak tüketiminin önlenmesi gibi alanlara odaklanır.</p>
<h2 id="heading-aws-well-architected-framework-nasil-kullanilir">AWS Well-Architected Framework Nasıl Kullanılır?</h2>
<p>AWS Well-Architected Framework’ü uygulamak için aşağıdaki adımlar izlenir:</p>
<h3 id="heading-1-temel-sorulari-yanitlama">1. Temel Soruları Yanıtlama</h3>
<p>Her ilke için belirlenmiş sorular, mimarinizin güçlü ve zayıf yönlerini anlamanızı sağlar.</p>
<h3 id="heading-2-en-iyi-uygulamalari-degerlendirme">2. En İyi Uygulamaları Değerlendirme</h3>
<p>AWS'nin sunduğu en iyi uygulamaları referans alarak mevcut yapınızla karşılaştırma yapabilir, iyileştirme noktalarını tespit edebilirsiniz.</p>
<h3 id="heading-3-aws-well-architected-tool-kullanimi">3. AWS Well-Architected Tool Kullanımı</h3>
<p><a target="_blank" href="https://aws.amazon.com/well-architected-tool/">AWS Well-Architected Tool</a>, sistemlerinizi bu çerçeveye göre değerlendirmenize ve raporlamanıza olanak sağlar. Ayrıca özel lensler oluşturarak kurumsal standartlarınıza uygun analizler gerçekleştirebilirsiniz.</p>
<h3 id="heading-4-aws-well-architected-labs">4. AWS Well-Architected Labs</h3>
<p><a target="_blank" href="https://wellarchitectedlabs.com/">AWS Well-Architected Labs</a>, uygulamalı öğrenme için senaryolar, kod örnekleri ve belgeler içerir. En iyi uygulamaları deneyimleyerek öğrenmenizi sağlar.</p>
<h3 id="heading-5-aws-well-architected-partner-programi">5. AWS Well-Architected Partner Programı</h3>
<p>Derin AWS bilgisine sahip iş ortakları ile birlikte çalışarak mimarilerinizi detaylı şekilde gözden geçirebilir ve dış gözle öneriler alabilirsiniz.</p>
<h3 id="heading-6-surekli-iyilestirme">6. Sürekli İyileştirme</h3>
<p>İncelemeler tek seferlik değil, düzenli olarak tekrarlanmalıdır. Bu sayede teknolojideki ve iş ihtiyaçlarındaki değişimlere hızlı adapte olunabilir.</p>
<hr />
<h2 id="heading-aws-iyi-tasarlanmis-inceleme-sureci">AWS İyi Tasarlanmış İnceleme Süreci</h2>
<p>İnceleme süreci; tutarlılığı, derinliği ve verimliliği esas alır. Hafif, saatler içinde tamamlanabilen bir süreç olmalı ve bir denetimden ziyade yapıcı bir değerlendirme görüşmesi şeklinde yürütülmelidir. Bu sürecin sonunda ise müşteri deneyimini iyileştirmeye yönelik bir dizi eylem belirlenir.</p>
<p>İncelemeler, ürün yaşam döngüsünün kilit noktalarında — özellikle tasarımın ilk aşamalarında ve canlıya geçişten önce — yapılmalıdır.</p>
<hr />
<h2 id="heading-sik-yapilan-hatalar">Sık Yapılan Hatalar</h2>
<ul>
<li><p>Güvenliği mimari sürecin sonuna bırakmak</p>
</li>
<li><p>Maliyetleri sadece operasyon aşamasında değerlendirmek</p>
</li>
<li><p>Performans sorunlarını canlıya çıktıktan sonra fark etmek</p>
</li>
<li><p>Sürdürülebilirlik ilkesini göz ardı etmek</p>
</li>
</ul>
<hr />
<h2 id="heading-aws-well-architected-frameworkun-faydalari">AWS Well-Architected Framework'ün Faydaları</h2>
<ul>
<li><p><strong>Daha İyi Mimari Kararları:</strong> Bulut en iyi uygulamalarına göre sistemlerinizi tasarlamanıza yardımcı olur.</p>
</li>
<li><p><strong>Risk Azaltma:</strong> Potansiyel tehditleri ve zayıf noktaları erken aşamada tespit etmenizi sağlar.</p>
</li>
<li><p><strong>Maliyet Optimizasyonu:</strong> Kaynak kullanımını izleyerek gereksiz maliyetleri azaltır.</p>
</li>
<li><p><strong>Performans Artışı:</strong> İş yüklerine en uygun hizmetleri ve yapılandırmaları seçmenize olanak tanır.</p>
</li>
<li><p><strong>Güvenlik Güçlendirme:</strong> En iyi güvenlik uygulamalarıyla sistemlerinizi korumanızı sağlar.</p>
</li>
<li><p><strong>Yüksek Güvenilirlik:</strong> Hatalara dayanıklı sistemler kurmanızı destekler.</p>
</li>
<li><p><strong>Sürdürülebilirlik:</strong> Çevresel etkileri azaltacak bilinçli tasarımlar yapmanızı sağlar.</p>
</li>
<li><p><strong>Takımlar Arası İletişimi Artırma:</strong> Mimariler üzerine sağlıklı tartışmalar yapılmasını teşvik eder.</p>
</li>
</ul>
<hr />
<h2 id="heading-sonuc">Sonuç</h2>
<p>AWS Well-Architected Framework, AWS üzerinde başarılı, güvenli, verimli ve sürdürülebilir bulut sistemleri inşa etmek için güçlü bir araçtır. Sunulan temel ilkeler ve en iyi uygulamalar sayesinde, mimari kararlarınızı daha bilinçli bir şekilde alabilir, riskleri azaltabilir ve iş hedeflerinize ulaşabilirsiniz. Bulut yolculuğunuzda bu değerli rehberi kullanarak sistemlerinizin potansiyelini en üst düzeye çıkarın.</p>
]]></content:encoded></item><item><title><![CDATA[Monolitik Mimarinin Gücü ve Sınırları: Mikroservislere Geçişin İlk Adımı]]></title><description><![CDATA[Yazılım geliştirme dünyasında mimari seçimleri, projelerin başarısı için kritik bir öneme sahiptir. Günümüzde birçok ekip mikroservis mimarisine yönelse de, monolitik mimari hala güçlü bir seçenek olarak karşımıza çıkmaktadır. Özellikle hızlı başlang...]]></description><link>https://serhatleventyavas.dev/monolitik-mimarinin-gucu-ve-sinirlari-mikroservislere-gecisin-ilk-adimi</link><guid isPermaLink="true">https://serhatleventyavas.dev/monolitik-mimarinin-gucu-ve-sinirlari-mikroservislere-gecisin-ilk-adimi</guid><category><![CDATA[Microservices]]></category><category><![CDATA[monolithic architecture]]></category><category><![CDATA[Modular Monolith]]></category><category><![CDATA[KISS Principle]]></category><category><![CDATA[YAGNI]]></category><category><![CDATA[DRY Principle (Don't Repeat Yourself)]]></category><dc:creator><![CDATA[Serhat Levent Yavaş]]></dc:creator><pubDate>Sat, 04 Jan 2025 09:40:45 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1735812219066/0c42e870-e2c4-4d2f-b1ea-02e4683adec4.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Yazılım geliştirme dünyasında mimari seçimleri, projelerin başarısı için kritik bir öneme sahiptir. Günümüzde birçok ekip mikroservis mimarisine yönelse de, monolitik mimari hala güçlü bir seçenek olarak karşımıza çıkmaktadır. Özellikle hızlı başlangıçlar, basitlik ve düşük karmaşıklık arayan projeler için monolitik mimari vazgeçilmez bir çözüm sunar. Bu yazıda, monolitik mimarinin avantajlarını, dezavantajlarını ve doğru kullanıldığı durumları ele alırken, katmanlı mimarinin (N-Layer Architecture) monolitik yapılardaki önemini de inceleyeceğiz. Ayrıca, KISS, DRY ve YAGNI gibi tasarım prensipleri ile daha etkili bir yapı oluşturmanın yollarını keşfedeceğiz. Monolitik mimarinin günümüzde neden hâlâ önemli bir seçenek olduğunu ve hangi durumlarda tercih edilmesi gerektiğini bu blog yazısında detaylı bir şekilde ele alacağız.</p>
<h3 id="heading-monolitik-mimari-nedir">Monolitik Mimari Nedir?</h3>
<p>Monolitik mimari, geleneksel yazılım geliştirme süreçlerinde en sık tercih edilen mimari yaklaşımlardan biridir. Bu mimaride, bir uygulamanın tüm bileşenleri tek bir kod tabanında toplanır ve tek bir bütün olarak çalışır. Kullanıcı arayüzünden iş mantığına, veri tabanı erişiminden üçüncü taraf entegrasyonlara kadar tüm bileşenler aynı proje içinde yer alır.</p>
<p>Bu yaklaşım, özellikle projelerin başlangıç aşamasında <strong>hızlı geliştirme</strong> ve <strong>yönetim kolaylığı</strong> sağlar. Günümüzde birçok legacy proje, monolitik mimari ile geliştirilmiş ve uzun yıllardır bu yapı üzerinde kullanıcılarına hizmet vermektedir. Monolitik mimari, ilk bakışta <strong>basitliği</strong> ve <strong>sadeliği</strong> ile öne çıkarken, büyük ölçekli projelerde <strong>yönetim</strong> ve <strong>ölçeklenebilirlik</strong> açısından bazı zorluklar yaratabilir.</p>
<p>Monolitik mimarinin önemli bir özelliği, <strong>tüm iş kurallarının, veri işlemlerinin ve kullanıcı arayüzlerinin</strong> tek bir yerde toplanmasıdır. Bu, başlangıçta hızlı bir geliştirme süreci sağlasa da, <strong>proje büyüdükçe karmaşıklığın artmasına</strong> neden olabilir. Ancak doğru tasarım prensipleri uygulandığında, monolitik mimari hala <strong>etkili</strong> ve <strong>sürdürülebilir</strong> bir çözüm sunabilir.</p>
<h3 id="heading-monolitik-mimarinin-avantajlari">Monolitik Mimarinin Avantajları</h3>
<p>Monolitik mimari, yazılım projelerinde <strong>basitlik</strong> ve <strong>hızlı ilerleme</strong> sağlayan birçok avantaja sahiptir. Bu avantajlar, özellikle <strong>küçük ekipler veya sınırlı kaynaklarla</strong> çalışan projelerde oldukça cazip hale gelir. Monolitik mimarinin sunduğu temel avantajlar bahsedecek olursak:</p>
<ol>
<li><p><strong>Hızlı Başlangıç ve Geliştirme</strong><br /> Monolitik mimaride tüm bileşenler tek bir projede toplandığı için yeni bir projeye başlamak son derece kolaydır. Geliştiriciler, farklı servisler veya sistemler arasında iletişim kurma gereksinimi olmadan çalışabilir, bu da <strong>geliştirme sürecini hızlandırır.</strong></p>
</li>
<li><p><strong>Kolay Debug ve Test Süreçleri</strong><br /> Monolitik mimaride, uygulamanın tüm bileşenlerinin tek bir kod tabanında bulunması sayesinde <strong>hata ayıklama ve test süreçlerini kolaylaştırır</strong>. Tek bir ortamda çalışan sistemde, <strong>hataların kaynağını bulmak ve çözmek daha az zaman alır.</strong></p>
</li>
<li><p><strong>Basit ve Etkili Deployment Süreçleri</strong><br /> Monolitik mimaride uygulama, <strong>tek bir birim olarak deploy edilir.</strong> Mikroservislerdeki gibi bağımsız servislerin uyumunu sağlama veya farklı sürümleri yönetme zorunluluğu yoktur. Bu, deployment süreçlerini daha sade ve yönetilebilir hale getirir.</p>
</li>
<li><p><strong>Dikey Ölçeklenebilirlik</strong><br /> Monolitik mimaride, <strong>donanım gücünü artırarak dikey olarak (scale up, vertical scaling) ölçeklenebilir.</strong> Daha güçlü bir sunucuya geçiş yapmak, genellikle performansı artırmak için yeterli olur. Bu, özellikle düşük ve orta trafik seviyelerine sahip projeler için ideal bir çözümdür.</p>
</li>
<li><p><strong>Birlikte Çalışabilirlik</strong><br /> Tüm bileşenler aynı kod tabanında olduğu için, bileşenler arası entegrasyon son derece kolaydır. Geliştiriciler, herhangi bir servis arası iletişim protokolü veya API tasarımı ile uğraşmak zorunda kalmaz.</p>
</li>
<li><p><strong>Eğitim ve Yeni Katılanlar İçin Kolaylık</strong><br /> Yeni bir ekip üyesinin projeye adapte olması, monolitik mimarilerde genellikle daha kolaydır. Tüm kod tabanının bir yerde bulunması, yeni katılan birinin projeyi anlamasını ve katkıda bulunmasını hızlandırır.</p>
</li>
</ol>
<h3 id="heading-monolitik-mimarinin-dezavantajlari">Monolitik Mimarinin Dezavantajları</h3>
<p>Her ne kadar monolitik mimari basitliği ve hızlı başlangıç avantajları sunsa da, <strong>büyük ve karmaşık projelerde bazı sınırlamalar ve zorluklar ortaya çıkabilir</strong>. İşte monolitik mimarinin dezavantajları:</p>
<ol>
<li><p><strong>Yönetim Zorlukları</strong><br /> Proje büyüdükçe, kod tabanının <strong>karmaşıklığı artar</strong>. Bu durum, özellikle büyük ekiplerde çalışırken <strong>kodun yönetimini zorlaştırabilir.</strong> Herhangi bir değişiklik, tüm sistem üzerinde geniş çaplı etkiler yaratabilir.</p>
</li>
<li><p><strong>Yeni Özellikler Eklemek Zorlaşır</strong><br /> Kod tabanı büyüdükçe ve bağımlılıklar arttıkça, yeni bir özellik eklemek zaman alıcı ve karmaşık bir hal alabilir. Kodun bir bölgesinde yapılan değişiklikler, beklenmedik bir şekilde diğer bölgelere etki edebilir.</p>
</li>
<li><p><strong>Teknoloji Bağımlılığı</strong><br /> Monolitik mimariler genellikle belirli bir programlama dili veya framework ile geliştirilir. Bu durum, yeni teknolojileri entegre etmeyi zorlaştırır. Örneğin, bir projeyi tamamen farklı bir dil veya framework'e geçirmek neredeyse imkansız olabilir.</p>
</li>
<li><p><strong>Ölçeklenebilirlik Sorunları</strong><br /> Monolitik mimariler <strong>dikey olarak ölçeklenebilir</strong> olsa da, bu yöntem <strong>yüksek trafik seviyelerinde</strong> yetersiz kalabilir. Yatay ölçekleme (Scale out, Horizontal Scaling) ise monolitik yapılarda oldukça karmaşıktır ve verimsizdir. Eğer yapı stateful bir yapıya sahipse, yatay ölçekleme neredeyse imkansız hale gelebilir.</p>
</li>
<li><p><strong>Tek Nokta Arızası (Single Point of Failure)</strong><br /> Monolitik mimariye sahip bir uygulamada herhangi bir bileşende meydana gelen bir hata, <strong>tüm sistemin çökmesine neden olabilir</strong>. Mikroservis mimarisinde ise bu tür sorunlar genellikle sadece ilgili servisi etkiler.</p>
</li>
<li><p><strong>Deployment Süreçlerindeki Riskler</strong><br /> Monolitik mimariye sahip bir uygulamada, küçük bir değişiklik bile tüm sistemin yeniden deploy edilmesini gerektirir. Bu, büyük projelerde zaman kaybına yol açabilir ve hata yapma riskini artırabilir.</p>
</li>
<li><p><strong>Ekiplerin Çalışma Şekline Uyumsuzluk</strong><br /> Büyük ekiplerde, farklı özelliklerin aynı kod tabanında geliştirilmesi çakışmalara neden olabilir. Kod üzerinde aynı anda çalışan çok sayıda kişi, kod yönetimi sorunlarını ve entegrasyon zorluklarını beraberinde getirir.</p>
</li>
</ol>
<h3 id="heading-hangi-durumlarda-monolitik-mimari-kullanilabilir">Hangi Durumlarda Monolitik Mimari Kullanılabilir?</h3>
<p>Monolitik mimari, her ne kadar büyük ve karmaşık projelerde bazı zorluklar ortaya çıkarsa da, belirli durumlarda hâlâ oldukça etkili bir mimari seçeneği olarak karşımıza çıkar. İşte monolitik mimarinin kullanılmasının uygun olduğu bazı senaryolar:</p>
<ol>
<li><p><strong>Küçük Ekipler ve Projeler</strong><br /> Küçük ölçekli projeler ve sınırlı sayıda geliştiriciden oluşan ekipler için monolitik mimari, geliştirme sürecini basitleştirir ve hızlandırır. Bu tür projelerde karmaşık mikroservis altyapılarına ihtiyaç duyulmaz.</p>
</li>
<li><p><strong>MVP (Minimum Viable Product) Geliştirme</strong><br /> Yeni bir iş modeli veya ürün fikri test ediliyorsa, monolitik mimari hızlı bir başlangıç yapmak için mükemmel bir tercihtir. Ürün piyasaya çıktıktan ve doğrulandıktan sonra <strong>ihtiyaç duyulursa</strong> mikroservis mimarisine geçiş yapılabilir.</p>
</li>
<li><p><strong>Hızlı Pazara Çıkış (Time-to-Market) İhtiyacı</strong><br /> Rekabetin yüksek olduğu sektörlerde, bir ürünün pazara hızlı bir şekilde çıkması gerekebilir. Monolitik mimari, geliştirme ve deployment süreçlerini hızlandırarak zaman kazandırır.</p>
</li>
<li><p><strong>Düşük Trafik ve Basit İşlevsellik</strong><br /> Eğer bir uygulamanın kullanıcı kitlesi ve trafiği sınırlıysa, monolitik mimari teknik açıdan yeterli bir çözüm sunar. Bu tür projelerde, yatay ölçekleme gereksinimi olmadığı için monolitik yapı herhangi bir kısıtlama oluşturmaz.</p>
</li>
<li><p><strong>Ekipte Mikroservis Deneyiminin Eksikliği</strong><br /> Mikroservis mimarisi, ekipte derin bir bilgi birikimi ve deneyim gerektirir. Eğer ekipte bu deneyim yoksa, monolitik mimariyle başlamak daha doğru bir yaklaşım olacaktır. Ekip, proje ilerledikçe mimari bilgi birikimini artırabilir.</p>
</li>
<li><p><strong>Yüksek Entegrasyon Gereksinimi Olmayan Projeler</strong><br /> Bazı projeler, genellikle birbiriyle sıkı entegre çalışan modüllerden oluşur. Bu tür projelerde, mikroservislerin sağladığı esnekliğe ihtiyaç duyulmaz. Tüm sistemin tek bir kod tabanında yer aldığı monolitik mimari, bu tür projeler için uygundur.</p>
</li>
<li><p><strong>Kısa Süreli ve Deneysel Projeler</strong><br /> Deneysel veya geçici olarak geliştirilen projelerde, mikroservis altyapısı kurmak zaman ve kaynak israfına neden olabilir. Monolitik mimari, bu tür projelerde hızlı bir şekilde sonuç almak için idealdir.</p>
</li>
</ol>
<h3 id="heading-monolitik-mimari-ile-tasarim-prensipleri">Monolitik Mimari ile Tasarım Prensipleri</h3>
<p>Daha öncede bahsettiğimiz gibi monolitik mimari, tüm bileşenlerin tek bir yapı içinde toplandığı bir yazılım geliştirme yaklaşımıdır. Bu yapıda, tasarım prensiplerine uyum, büyüyen projelerde <strong>yönetilebilirliği korumak</strong>, <strong>kodun bakımını kolaylaştırmak</strong> ve <strong>uzun vadede sürdürülebilir bir sistem yaratmak</strong> için kritik bir öneme sahiptir.</p>
<p><strong>Tasarım prensipleri</strong>, bir yazılım projesinde <strong>düzen</strong>, <strong>sadelik</strong> ve <strong>esneklik</strong> sağlamak için kullanılan rehberlerdir. Monolitik mimari gibi büyük ve sıkı bağlı yapılarda bu prensiplerin uygulanması, projenin karmaşıklığını azaltır ve uzun vadeli faydalar sağlar.</p>
<p>Aşağıda, monolitik mimariyle ilişkili olarak dört önemli tasarım prensibini ele alacağız:</p>
<ul>
<li><p><strong>KISS (Keep It Simple, Stupid)</strong></p>
</li>
<li><p><strong>DRY (Don't Repeat Yourself)</strong></p>
</li>
<li><p><strong>YAGNI (You Aren't Gonna Need It)</strong></p>
</li>
<li><p><strong>Separation of Concerns (SoC)</strong></p>
</li>
</ul>
<h4 id="heading-1-kiss-keep-it-simple-stupid"><strong>1. KISS (Keep It Simple, Stupid)</strong></h4>
<p><strong>KISS</strong> (Keep It Simple, Stupid) prensibi, tasarım veya yazılım geliştirme süreçlerinde <strong>gereksiz karmaşıklıktan kaçınılması gerektiğini</strong> vurgulayan bir prensiptir. Amacı, sistemlerin veya yazılımların <strong>daha kolay anlaşılabilir, daha az hata barındıran ve daha kolay bakım yapılabilir olmasını sağlamaktır.</strong></p>
<p>Monolitik mimarilerde, tüm bileşenler tek bir yerde toplandığı için karmaşıklık hızlı bir şekilde artabilir. KISS prensibi, kodu ve sistemi olabildiğince basit tutarak bu karmaşıklığı kontrol altında tutar.</p>
<h4 id="heading-temel-ilkeler"><strong>Temel İlkeler:</strong></h4>
<ol>
<li><p><strong>Basitlik Önemlidir:</strong> Karmaşık çözümler, daha fazla hata ve bakım zorluğu yaratır.</p>
</li>
<li><p><strong>Doğal Çözümler Tercih Edilmelidir:</strong> Sorunu çözmek için en az bileşene sahip en etkili çözüm seçilmelidir.</p>
</li>
<li><p><strong>Kodun ve Tasarımın Kolay Anlaşılabilir Olması:</strong> Ekip arkadaşları ve hatta gelecekteki siz için kolay okunabilir kod yazılmalıdır.</p>
</li>
<li><p><strong>Karmaşıklıktan Kaçın:</strong> Gereksiz özellik eklemeleri, gereksiz soyutlamalar ve aşırı mühendislikten uzak durulmalıdır.</p>
</li>
</ol>
<h4 id="heading-neden-onemlidir"><strong>Neden Önemlidir?</strong></h4>
<ul>
<li><p>Daha <strong>hızlı geliştirme</strong> süreçleri sağlar.</p>
</li>
<li><p><strong>Bakımı kolay</strong> bir sistem ortaya çıkarır.</p>
</li>
<li><p>Yeni ekip üyelerinin sisteme daha <strong>hızlı adapte olmasına</strong> yardımcı olur.</p>
</li>
<li><p>Karmaşık sistemlerdeki hataları çözmek için <strong>harcanan süreyi azaltır.</strong></p>
</li>
</ul>
<p><strong>Örnek</strong></p>
<p>Bu kod örneği, yalnızca dikdörtgen ve daire alanını hesaplamak gibi basit bir problem için <strong>gereksiz soyutlama</strong> ve <strong>aşırı mühendislik</strong> barındırmaktadır. <strong>Bu durum kodun hem anlaşılabilirliğini hem de bakımını zorlaştırmaktadır.</strong></p>
<pre><code class="lang-csharp"><span class="hljs-keyword">using</span> System;

<span class="hljs-keyword">public</span> <span class="hljs-keyword">abstract</span> <span class="hljs-keyword">class</span> <span class="hljs-title">Shape</span>
{
    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">abstract</span> <span class="hljs-keyword">double</span> <span class="hljs-title">Area</span>(<span class="hljs-params"></span>)</span>;
}

<span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title">Rectangle</span> : <span class="hljs-title">Shape</span>
{
    <span class="hljs-keyword">private</span> <span class="hljs-keyword">double</span> width;
    <span class="hljs-keyword">private</span> <span class="hljs-keyword">double</span> height;

    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-title">Rectangle</span>(<span class="hljs-params"><span class="hljs-keyword">double</span> width, <span class="hljs-keyword">double</span> height</span>)</span>
    {
        <span class="hljs-keyword">this</span>.width = width;
        <span class="hljs-keyword">this</span>.height = height;
    }

    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">override</span> <span class="hljs-keyword">double</span> <span class="hljs-title">Area</span>(<span class="hljs-params"></span>)</span>
    {
        <span class="hljs-keyword">return</span> width * height;
    }
}

<span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title">Circle</span> : <span class="hljs-title">Shape</span>
{
    <span class="hljs-keyword">private</span> <span class="hljs-keyword">double</span> radius;

    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-title">Circle</span>(<span class="hljs-params"><span class="hljs-keyword">double</span> radius</span>)</span>
    {
        <span class="hljs-keyword">this</span>.radius = radius;
    }

    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">override</span> <span class="hljs-keyword">double</span> <span class="hljs-title">Area</span>(<span class="hljs-params"></span>)</span>
    {
        <span class="hljs-keyword">return</span> Math.PI * Math.Pow(radius, <span class="hljs-number">2</span>);
    }
}

<span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title">ShapePrinter</span>
{
    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">PrintArea</span>(<span class="hljs-params">Shape shape</span>)</span>
    {
        <span class="hljs-keyword">if</span> (shape <span class="hljs-keyword">is</span> Rectangle)
        {
            Console.WriteLine(<span class="hljs-string">$"Rectangle area: <span class="hljs-subst">{shape.Area()}</span>"</span>);
        }
        <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (shape <span class="hljs-keyword">is</span> Circle)
        {
            Console.WriteLine(<span class="hljs-string">$"Circle area: <span class="hljs-subst">{shape.Area()}</span>"</span>);
        }
        <span class="hljs-keyword">else</span>
        {
            Console.WriteLine(<span class="hljs-string">"Unknown shape"</span>);
        }
    }
}

<span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title">Program</span>
{
    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title">Main</span>(<span class="hljs-params"></span>)</span>
    {
        Shape rectangle = <span class="hljs-keyword">new</span> Rectangle(<span class="hljs-number">5</span>, <span class="hljs-number">10</span>);
        Shape circle = <span class="hljs-keyword">new</span> Circle(<span class="hljs-number">7</span>);

        ShapePrinter printer = <span class="hljs-keyword">new</span> ShapePrinter();
        printer.PrintArea(rectangle);
        printer.PrintArea(circle);
    }
}
</code></pre>
<p>Aşağıdaki kod ise aynı problemi daha sade ve doğrudan bir şekilde çözer. Bu yaklaşım, hem daha <strong>hızlı bir şekilde anlaşılır</strong> hem de <strong>daha az kodla aynı sonucu</strong> üretir.</p>
<pre><code class="lang-csharp"><span class="hljs-keyword">using</span> System;

<span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title">Program</span>
{
    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">double</span> <span class="hljs-title">RectangleArea</span>(<span class="hljs-params"><span class="hljs-keyword">double</span> width, <span class="hljs-keyword">double</span> height</span>)</span>
    {
        <span class="hljs-keyword">return</span> width * height;
    }

    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">double</span> <span class="hljs-title">CircleArea</span>(<span class="hljs-params"><span class="hljs-keyword">double</span> radius</span>)</span>
    {
        <span class="hljs-keyword">return</span> Math.PI * Math.Pow(radius, <span class="hljs-number">2</span>);
    }

    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title">Main</span>(<span class="hljs-params"></span>)</span>
    {
        <span class="hljs-keyword">double</span> rectangleArea = RectangleArea(<span class="hljs-number">5</span>, <span class="hljs-number">10</span>);
        <span class="hljs-keyword">double</span> circleArea = CircleArea(<span class="hljs-number">7</span>);

        Console.WriteLine(<span class="hljs-string">$"Rectangle area: <span class="hljs-subst">{rectangleArea}</span>"</span>);
        Console.WriteLine(<span class="hljs-string">$"Circle area: <span class="hljs-subst">{circleArea}</span>"</span>);
    }
}
</code></pre>
<p>Bu sade çözüm ile KISS prensibinin gereksiz soyutlamalardan ve karmaşıklıktan kaçınma ilkesini tam anlamıyla uygularız. Bu sayede kodun hem <strong>anlaşılabilirliği</strong> hem de <strong>bakımı kolaylaşır</strong>.</p>
<p><strong>2. DRY (Don't Repeat Yourself)</strong></p>
<p>DRY (Don't Repeat Yourself) prensibi, yazılım geliştirme süreçlerinde <strong>tekrar eden kodları önlemek</strong> ve <strong>kodun tekrar kullanılabilirliğini</strong> artırmayı amaçlayan bir tasarım ilkesidir. Bu prensip, bir bilgiyi veya işlevi kodda yalnızca bir kez tanımlamayı hedefler. DRY, yazılım projelerinde bakım ve genişletme sırasında karşılaşılabilecek hataları azaltarak, daha verimli bir geliştirme süreci sağlar.</p>
<h4 id="heading-temel-ilkeler-1"><strong>Temel İlkeler:</strong></h4>
<ol>
<li><p><strong>Tek Sorumluluk Alanı:</strong> Her bilgi veya işlev kodda yalnızca bir kez tanımlanmalıdır.</p>
</li>
<li><p><strong>Kodun Merkezi Olması:</strong> Aynı işlevi gerçekleştiren kod parçaları tek bir yerde toplanmalı ve gerektiğinde yeniden kullanılmalıdır.</p>
</li>
<li><p><strong>Kolay Bakım:</strong> Kodda değişiklik gerektiğinde, yalnızca bir yeri değiştirmek yeterli olmalıdır.</p>
</li>
<li><p><strong>Doğru soyutlama seviyesi bulun:</strong> Gereksiz yere her şeyi bir fonksiyona taşımak yerine, yalnızca tekrar eden işlevleri soyutlanmalıdır.</p>
</li>
<li><p><strong>Kodun anlaşılabilirliğini koruyun:</strong> DRY prensibini uygularken kodun okunabilirliğini azaltmaktan kaçınılmalıdır.</p>
</li>
<li><p><strong>İhtiyaç duydukça uygula (YAGNI):</strong> Henüz ihtiyaç olmayan soyutlamalar yapmaktan kaçınılmalıdır.</p>
</li>
</ol>
<h4 id="heading-neden-onemlidir-1"><strong>Neden Önemlidir?</strong></h4>
<ul>
<li><p><strong>Kodun Kolay Yönetimi:</strong> Aynı kodu farklı yerlerde değiştirmek yerine yalnızca bir yeri düzenlersiniz.</p>
</li>
<li><p><strong>Daha Az Hata:</strong> Bir hatayı düzeltmek için yalnızca tek bir yere odaklanmak yeterlidir.</p>
</li>
<li><p><strong>Yeniden Kullanılabilirlik:</strong> İşlevsellik tek bir yerde tanımlandığında, farklı projelerde veya kod bölümlerinde kolayca tekrar kullanılabilir.</p>
</li>
<li><p><strong>Daha Az Karmaşıklık:</strong> Kodda tekrar eden bölümler olmadığında, kod daha temiz, anlaşılır ve yönetilebilir hale gelir.</p>
</li>
</ul>
<p><strong>Monolitik mimarilerde, kodun tekrar eden bölümleri projeler büyüdükçe karmaşıklığı artırır ve bakımı zorlaştırır.</strong> DRY prensibi, bu tekrarları azaltarak kodun <strong>sürdürülebilirliğini</strong> ve <strong>genişletilebilirliğini</strong> sağlar. Özellikle büyük ekiplerin çalıştığı projelerde, tekrar eden kod parçalarını ortadan kaldırarak ekipler arası uyumu artırır ve sürüm yönetimini kolaylaştırır.</p>
<p><strong>Örnek</strong></p>
<p>Bu kod örneği, üç farklı dikdörtgenin alanını hesaplamak için her bir dikdörtgenin boyutlarına özel ayrı hesaplama işlemleri yapılmış. <strong>Bu tekrar eden kod parçaları, hem okunabilirliği azaltmış hem de kodun bakımını zorlaştırmıştır.</strong> DRY prensibini ihlal eden bu tür yapılar, değişiklik gerektiğinde hatalara yol açabilir ve geliştirme sürecini karmaşık hale getirebilir.</p>
<pre><code class="lang-csharp"><span class="hljs-keyword">using</span> System;

<span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title">Program</span>
{
    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title">Main</span>(<span class="hljs-params"></span>)</span>
    {
        <span class="hljs-keyword">double</span> length = <span class="hljs-number">5</span>;
        <span class="hljs-keyword">double</span> width = <span class="hljs-number">10</span>;
        <span class="hljs-keyword">double</span> area1 = length * width;
        Console.WriteLine(<span class="hljs-string">$"Rectangle 1 area: <span class="hljs-subst">{area1}</span>"</span>);

        <span class="hljs-keyword">double</span> length2 = <span class="hljs-number">7</span>;
        <span class="hljs-keyword">double</span> width2 = <span class="hljs-number">3</span>;
        <span class="hljs-keyword">double</span> area2 = length2 * width2;
        Console.WriteLine(<span class="hljs-string">$"Rectangle 2 area: <span class="hljs-subst">{area2}</span>"</span>);

        <span class="hljs-keyword">double</span> length3 = <span class="hljs-number">6</span>;
        <span class="hljs-keyword">double</span> width3 = <span class="hljs-number">8</span>;
        <span class="hljs-keyword">double</span> area3 = length3 * width3;
        Console.WriteLine(<span class="hljs-string">$"Rectangle 3 area: <span class="hljs-subst">{area3}</span>"</span>);
    }
}
</code></pre>
<p>Çözüm olarak, tekrar eden kod parçalarını tek bir fonksiyona taşıyarak, DRY prensibine uygun bir yapı elde edebiliriz. Aşağıdaki örnekte <code>CalculateRectangleArea</code> adında bir metod tanımlanmış ve tüm alan hesaplamaları bu metod üzerinden gerçekleştirilmiştir. <strong>Bu yaklaşım sayesinde, kod daha temiz, modüler ve sürdürülebilir hale gelmiştir. Artık alan hesaplama mantığında bir değişiklik gerektiğinde, yalnızca bu metod üzerinde çalışmak yeterli olacaktır.</strong> Ayrıca, metodu tekrar kullanarak kodun farklı bölümlerinde de aynı işlemleri kolaylıkla gerçekleştirebiliriz.</p>
<pre><code class="lang-csharp"><span class="hljs-keyword">using</span> System;

<span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title">Program</span>
{
    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">double</span> <span class="hljs-title">CalculateRectangleArea</span>(<span class="hljs-params"><span class="hljs-keyword">double</span> length, <span class="hljs-keyword">double</span> width</span>)</span>
    {
        <span class="hljs-keyword">return</span> length * width;
    }

    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title">Main</span>(<span class="hljs-params"></span>)</span>
    {
        <span class="hljs-keyword">double</span> area1 = CalculateRectangleArea(<span class="hljs-number">5</span>, <span class="hljs-number">10</span>);
        Console.WriteLine(<span class="hljs-string">$"Rectangle 1 area: <span class="hljs-subst">{area1}</span>"</span>);

        <span class="hljs-keyword">double</span> area2 = CalculateRectangleArea(<span class="hljs-number">7</span>, <span class="hljs-number">3</span>);
        Console.WriteLine(<span class="hljs-string">$"Rectangle 2 area: <span class="hljs-subst">{area2}</span>"</span>);

        <span class="hljs-keyword">double</span> area3 = CalculateRectangleArea(<span class="hljs-number">6</span>, <span class="hljs-number">8</span>);
        Console.WriteLine(<span class="hljs-string">$"Rectangle 3 area: <span class="hljs-subst">{area3}</span>"</span>);
    }
}
</code></pre>
<p><strong>DRY prensibinin uygulanmasıyla elde edilen bu sadeleştirilmiş yapı, hem hata yapma olasılığını azaltır hem de uzun vadede bakım ve genişletme maliyetlerini düşürür.</strong></p>
<h4 id="heading-3-yagni-you-arent-gonna-need-it"><strong>3. YAGNI (You Aren't Gonna Need It)</strong></h4>
<p>YAGNI (You Aren’t Gonna Need It), yazılım geliştirme süreçlerinde <strong>gereksiz özelliklerin veya işlevlerin önceden eklenmemesi</strong> gerektiğini savunan bir prensiptir. Genellikle Agile yöntemlerinde kullanılan bu prensip, şunu vurgular:</p>
<blockquote>
<p>"Şu anda ihtiyaç duyulmayan bir özellik için kod yazmayın. İhtiyaç doğduğunda ekleyin."</p>
</blockquote>
<p>Bu yaklaşım, <strong>aşırı mühendislik ve gereksiz karmaşıklığın</strong> önüne geçmeyi hedefler. YAGNI, <strong>KISS (Keep It Simple, Stupid)</strong> prensibiyle uyumlu olarak çalışır ve <strong>MVP (Minimum Viable Product)</strong> yaklaşımını destekler.</p>
<h4 id="heading-temel-ilkeler-2"><strong>Temel İlkeler:</strong></h4>
<ol>
<li><p><strong>Mevcut İhtiyaca Odaklanın:</strong> Yalnızca şu anda gerekli olan özellik veya işlevleri geliştirilmelidir. Gelecekte gerekebilecek özellikler için zaman harcanmamalıdır.</p>
</li>
<li><p><strong>Agile Yöntemleri Benimseyin:</strong> Yazılım iteratif bir şekilde geliştirilmeli ve yalnızca gerçekten ihtiyaç duyulduğunda yeni özellikler eklenmelidir.</p>
</li>
<li><p><strong>Aşırı Mühendislikten Kaçının:</strong> Henüz kullanılmayacak soyutlamalar veya işlevler eklemekten kaçınılmalıdır.</p>
</li>
<li><p><strong>Kodun Basitliğini Koruyun:</strong> Geliştirme sürecinde kodun anlaşılabilirliğini artırmaya öncelik verilmelidir.</p>
</li>
<li><p><strong>İhtiyaca Göre Geliştirme:</strong> Gelecekteki belirsiz ihtiyaçları tahmin etmek yerine, mevcut gereksinimlere odaklanarak kaynakları verimli kullanılmalıdır.</p>
</li>
</ol>
<p><strong>Neden Önemlidir?</strong></p>
<ul>
<li><p><strong>Gereksiz Çaba ve Kaynak İsrafını Önler:</strong> Şu anda kullanılmayacak bir özellik için zaman ve enerji harcanmaz.</p>
</li>
<li><p><strong>Kodun Karmaşıklığını Azaltır:</strong> Gereksiz işlevler ve soyutlamalar kodun anlaşılabilirliğini zorlaştırır. YAGNI, yalnızca gerekli olanı geliştirerek bu karmaşıklığı ortadan kaldırır.</p>
</li>
<li><p><strong>Hataları Azaltır:</strong> Kullanılmayan veya nadiren kullanılan özellikler genellikle hatalara sebep olur. Bu özelliklerin eklenmemesi, yazılımın daha kararlı olmasını sağlar.</p>
</li>
<li><p><strong>Zaman ve Kaynak Tasarrufu Sağlar:</strong> Geliştirme süresi kısalır ve ekip kaynakları daha verimli bir şekilde kullanılır.</p>
</li>
<li><p><strong>MVP ve Agile Yaklaşımlarını Destekler:</strong> Ürünü hızlı bir şekilde piyasaya sürmek ve kullanıcıdan geri bildirim almak için yalnızca temel özelliklerin geliştirilmesi gerektiğini vurgular.</p>
</li>
</ul>
<p>Monolitik mimarilerde, gelecekteki potansiyel ihtiyaçları karşılamak adına eklenen gereksiz işlevler ve özellikler, <strong>kodun karmaşıklığını artırır ve geliştirme sürecini uzatır.</strong> Bu durum, <strong>bakım süreçlerini de zorlaştırır.</strong> YAGNI prensibi, monolitik yapılarda yalnızca mevcut ihtiyaçlara odaklanarak bu sorunların önüne geçer. Özellikle MVP geliştirme aşamasında, YAGNI sayesinde <strong>kodun basitliği korunur ve uygulamanın daha hızlı bir şekilde kullanıma sunulması sağlanır.</strong></p>
<p><strong>Örnek</strong></p>
<p>Bu örnekte, yalnızca ABD kullanıcılarına satış yapılan bir alışveriş uygulamasında, gelecekte Avrupa ülkelerine genişleme planlanarak şimdiden döviz dönüşümü gibi özellikler kodlanmıştır. Bu, YAGNI prensibini ihlal eder çünkü şu anda bu özellikler ihtiyaç değildir. Bu yaklaşım:</p>
<ul>
<li><p>Kodun karmaşıklığını gereksiz yere artırır.</p>
</li>
<li><p>Kullanılmayan özelliklerin bakımını zorlaştırır.</p>
</li>
<li><p>Gelecekte yapılacak değişikliklerde hata riskini artırır.</p>
</li>
</ul>
<pre><code class="lang-csharp"><span class="hljs-keyword">using</span> System;
<span class="hljs-keyword">using</span> System.Collections.Generic;

<span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title">CurrencyConverter</span>
{
    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">double</span> <span class="hljs-title">ConvertToCurrency</span>(<span class="hljs-params"><span class="hljs-keyword">double</span> amount, <span class="hljs-keyword">string</span> targetCurrency</span>)</span>
    {
        <span class="hljs-keyword">var</span> conversionRates = <span class="hljs-keyword">new</span> Dictionary&lt;<span class="hljs-keyword">string</span>, <span class="hljs-keyword">double</span>&gt;
        {
            { <span class="hljs-string">"USD"</span>, <span class="hljs-number">1.0</span> },
            { <span class="hljs-string">"EUR"</span>, <span class="hljs-number">0.85</span> },
            { <span class="hljs-string">"GBP"</span>, <span class="hljs-number">0.75</span> }
        };

        <span class="hljs-keyword">if</span> (conversionRates.ContainsKey(targetCurrency))
        {
            <span class="hljs-keyword">return</span> amount * conversionRates[targetCurrency];
        }
        <span class="hljs-keyword">else</span>
        {
            <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> ArgumentException(<span class="hljs-string">"Unsupported currency"</span>);
        }
    }
}

<span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title">Program</span>
{
    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title">Main</span>(<span class="hljs-params"></span>)</span>
    {
        CurrencyConverter converter = <span class="hljs-keyword">new</span> CurrencyConverter();

        <span class="hljs-keyword">double</span> productPriceInUSD = <span class="hljs-number">100.0</span>;
        Console.WriteLine(<span class="hljs-string">$"Price in USD: <span class="hljs-subst">{productPriceInUSD}</span>"</span>);

        <span class="hljs-comment">// Henüz ihtiyaç yokken EUR ve GBP dönüşümü yapılmış</span>
        <span class="hljs-keyword">double</span> priceInEUR = converter.ConvertToCurrency(productPriceInUSD, <span class="hljs-string">"EUR"</span>);
        Console.WriteLine(<span class="hljs-string">$"Price in EUR: <span class="hljs-subst">{priceInEUR}</span>"</span>);

        <span class="hljs-keyword">double</span> priceInGBP = converter.ConvertToCurrency(productPriceInUSD, <span class="hljs-string">"GBP"</span>);
        Console.WriteLine(<span class="hljs-string">$"Price in GBP: <span class="hljs-subst">{priceInGBP}</span>"</span>);
    }
}
</code></pre>
<p>Çözüm olarak, gereksiz özellikleri sağlayan kod parçaları kaldırılmış ve yalnızca mevcut ihtiyaca odaklanılmıştır. USD fiyatlarının gösterilmesi, uygulamanın mevcut gereksinimlerini tamamen karşılamakta ve kodun sadeliğini korumaktadır. Bu yaklaşım:</p>
<ul>
<li><p>Kodun anlaşılabilirliğini artırır.</p>
</li>
<li><p>Geliştirme süresini ve bakım maliyetlerini azaltır.</p>
</li>
<li><p>Gelecekte eklenmesi gereken özellikler için esneklik sağlar.</p>
</li>
</ul>
<pre><code class="lang-csharp"><span class="hljs-keyword">using</span> System;

<span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title">Program</span>
{
    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title">Main</span>(<span class="hljs-params"></span>)</span>
    {
        <span class="hljs-keyword">double</span> productPriceInUSD = <span class="hljs-number">100.0</span>;
        Console.WriteLine(<span class="hljs-string">$"Price in USD: <span class="hljs-subst">{productPriceInUSD}</span>"</span>);

        <span class="hljs-comment">// Şu anda yalnızca USD fiyatlarını göstermek yeterlidir</span>
    }
}
</code></pre>
<p><strong>4. Separation of Concerns (SoC)</strong></p>
<p>Separation of Concerns (SoC), yazılım geliştirmede her bir yazılım bileşeninin (modülün, sınıfın, katmanın, vb.) yalnızca <strong>tek bir sorumluluğa odaklanmasını sağlayan bir prensiptir</strong>. Amaç, yazılımı <strong>modüler</strong>, <strong>bakımı kolay</strong> ve <strong>yeniden kullanılabilir</strong> hale getirmektir. Bu prensip, <strong>düşük bağlılık (low coupling) ve yüksek bağlılık (high cohesion)</strong> ilkelerine dayanır.</p>
<p><strong>Temel İlkeler:</strong></p>
<ol>
<li><p><strong>Tek Sorumluluk İlkesi (Single Responsibility Principle - SRP):</strong> Her modül veya sınıf yalnızca bir işlevi yerine getirmelidir. Örneğin: Bir sınıf hem kullanıcı arayüzüyle hem de veritabanıyla ilgileniyorsa, bu SoC'yi ihlal eder. Bu durum kod karmaşıklığını artırır ve bakımı zorlaştırır.</p>
</li>
<li><p><strong>Soyutlama (Abstraction):</strong> Katmanlar arasında net soyutlamalar yapılmalıdır. Bu, bir katmanda yapılan değişikliklerin diğer katmanları etkilemesini engeller. Örneğin, veritabanı katmanını değiştirdiğinizde kullanıcı arayüzünün etkilenmemesi gerekir.</p>
</li>
<li><p><strong>Bağımsızlık (Independence):</strong> Bileşenler birbiriyle mümkün olduğunca az iletişim kurmalı ve bağımsız çalışmalıdır. Örneğin, iş mantığı (business logic) katmanı, veri erişim (data access) katmanına doğrudan bağımlı olmamalıdır.</p>
</li>
</ol>
<p><strong>Neden Önemlidir?</strong></p>
<ul>
<li><p><strong>Bakımı Kolaylaştırır:</strong> Kodun bir bölümü üzerinde yapılan değişiklikler diğer bölümleri etkilemez. Bu, değişikliklerin daha güvenli ve hızlı bir şekilde uygulanmasını sağlar.</p>
</li>
<li><p><strong>Test Edilebilirliği Artırır:</strong> Her bir bileşen bağımsız olarak test edilebilir. Örneğin, veri erişim katmanındaki bir hatayı düzeltmek için kullanıcı arayüzünü test etmek gerekmez.</p>
</li>
<li><p><strong>Yeniden Kullanılabilirlik:</strong> Farklı projelerde veya uygulama bölümlerinde belirli modüller kolayca yeniden kullanılabilir.</p>
</li>
<li><p><strong>Kodun Anlaşılabilirliğini Artırır:</strong> Kod daha düzenli ve modüler hale gelir, bu da yeni ekip üyelerinin projeyi anlamasını kolaylaştırır.</p>
</li>
</ul>
<p>Monolitik mimarilerde, tüm bileşenlerin (kullanıcı arayüzü, iş mantığı, veri erişimi) tek bir yerde toplanması, <strong>kodun bakımını ve yönetimini zorlaştırır.</strong> Özellikle ekip büyüdükçe, farklı modüller üzerinde çalışan ekiplerin çakışma ihtimali artar. SoC prensibi, bileşenleri işlevlerine göre ayırarak bu sorunları ortadan kaldırır.<br />Örneğin, bir e-ticaret uygulamasında sipariş oluşturma süreci şu şekilde organize edilebilir:</p>
<ul>
<li><p><strong>Sunum Katmanı (Presentation Layer):</strong> Kullanıcıdan sipariş verilerini alır.</p>
</li>
<li><p><strong>İş Mantığı Katmanı (Business Logic Layer):</strong> Sipariş doğrulamalarını ve işlemlerini yönetir.</p>
</li>
<li><p><strong>Veri Erişim Katmanı (Data Access Layer):</strong> Sipariş verilerini veritabanına kaydeder.</p>
</li>
</ul>
<p><strong>Örnek</strong></p>
<p>Bu örnekte, kullanıcıların sipariş oluşturma işlemini gerçekleştiren bir sınıf, hem kullanıcı girişlerini doğruluyor, hem iş mantığını yönetiyor, hem de veri tabanına veri kaydediyor. Bu, SoC prensibini ihlal eder çünkü tüm sorumluluklar tek bir sınıfta toplanmıştır.</p>
<pre><code class="lang-csharp"><span class="hljs-keyword">using</span> System;

<span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title">OrderManager</span>
{
    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">CreateOrder</span>(<span class="hljs-params"><span class="hljs-keyword">string</span> username, <span class="hljs-keyword">string</span> password, <span class="hljs-keyword">int</span> productId, <span class="hljs-keyword">int</span> quantity</span>)</span>
    {
        <span class="hljs-comment">// Kullanıcı doğrulama (UI sorumluluğu)</span>
        <span class="hljs-keyword">if</span> (username != <span class="hljs-string">"admin"</span> || password != <span class="hljs-string">"password"</span>)
        {
            <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> UnauthorizedAccessException(<span class="hljs-string">"Invalid credentials"</span>);
        }

        <span class="hljs-comment">// İş mantığı (Business Logic sorumluluğu)</span>
        <span class="hljs-keyword">if</span> (quantity &lt;= <span class="hljs-number">0</span>)
        {
            <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> ArgumentException(<span class="hljs-string">"Quantity must be greater than zero"</span>);
        }

        <span class="hljs-comment">// Veritabanına kayıt (Data Access sorumluluğu)</span>
        Console.WriteLine(<span class="hljs-string">$"Order created: ProductId=<span class="hljs-subst">{productId}</span>, Quantity=<span class="hljs-subst">{quantity}</span>"</span>);
    }
}

<span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title">Program</span>
{
    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title">Main</span>(<span class="hljs-params"></span>)</span>
    {
        OrderManager orderManager = <span class="hljs-keyword">new</span> OrderManager();
        orderManager.CreateOrder(<span class="hljs-string">"admin"</span>, <span class="hljs-string">"password"</span>, <span class="hljs-number">1</span>, <span class="hljs-number">5</span>);
    }
}
</code></pre>
<h3 id="heading-neden-socyi-ihlal-ediyor"><strong>Neden SoC'yi İhlal Ediyor?</strong></h3>
<ol>
<li><p><strong>UI Sorumluluğu:</strong> Kullanıcı doğrulama işlemi, <code>OrderManager</code> sınıfında yapılmış.</p>
</li>
<li><p><strong>Business Logic Sorumluluğu:</strong> İş mantığı (ör. quantity kontrolü) aynı sınıfta bulunuyor.</p>
</li>
<li><p><strong>Data Access Sorumluluğu:</strong> Veritabanına kayıt işlemi de aynı sınıfın içinde tanımlanmış.</p>
</li>
<li><p><strong>Bakım Zorluğu:</strong> Farklı sorumlulukların tek bir sınıfta toplanması, kodun bakımını ve genişletilmesini zorlaştırır.</p>
</li>
</ol>
<p>Aşağıdaki çözüm, her sorumluluğu ayrı bir sınıfa ayırarak SoC prensibine uygun hale getirilmiştir.</p>
<pre><code class="lang-csharp"><span class="hljs-keyword">using</span> System;

<span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title">AuthenticationService</span>
{
    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">bool</span> <span class="hljs-title">Authenticate</span>(<span class="hljs-params"><span class="hljs-keyword">string</span> username, <span class="hljs-keyword">string</span> password</span>)</span>
    {
        <span class="hljs-keyword">return</span> username == <span class="hljs-string">"admin"</span> &amp;&amp; password == <span class="hljs-string">"password"</span>;
    }
}

<span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title">OrderService</span>
{
    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">ValidateOrder</span>(<span class="hljs-params"><span class="hljs-keyword">int</span> quantity</span>)</span>
    {
        <span class="hljs-keyword">if</span> (quantity &lt;= <span class="hljs-number">0</span>)
        {
            <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> ArgumentException(<span class="hljs-string">"Quantity must be greater than zero"</span>);
        }
    }

    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">CreateOrder</span>(<span class="hljs-params"><span class="hljs-keyword">int</span> productId, <span class="hljs-keyword">int</span> quantity</span>)</span>
    {
        Console.WriteLine(<span class="hljs-string">$"Order created: ProductId=<span class="hljs-subst">{productId}</span>, Quantity=<span class="hljs-subst">{quantity}</span>"</span>);
    }
}

<span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title">Program</span>
{
    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title">Main</span>(<span class="hljs-params"></span>)</span>
    {
        <span class="hljs-comment">// Kullanıcı doğrulama</span>
        AuthenticationService authService = <span class="hljs-keyword">new</span> AuthenticationService();
        <span class="hljs-keyword">if</span> (!authService.Authenticate(<span class="hljs-string">"admin"</span>, <span class="hljs-string">"password"</span>))
        {
            <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> UnauthorizedAccessException(<span class="hljs-string">"Invalid credentials"</span>);
        }

        <span class="hljs-comment">// Sipariş oluşturma</span>
        OrderService orderService = <span class="hljs-keyword">new</span> OrderService();
        orderService.ValidateOrder(<span class="hljs-number">5</span>);
        orderService.CreateOrder(<span class="hljs-number">1</span>, <span class="hljs-number">5</span>);
    }
}
</code></pre>
<h3 id="heading-monolitik-ve-katmanli-mimari-n-layer-architecture"><strong>Monolitik ve Katmanlı Mimari (N-Layer Architecture)</strong></h3>
<p>Monolitik mimaride projeler büyüdükçe, kodun tek bir yapıda toplanması yönetimi zorlaştırır ve karmaşıklığı artırır. Her bir bileşenin aynı kod tabanında bulunması, yeni özellikler eklenirken veya bakım yapılırken hatalara ve uzun geliştirme sürelerine yol açabilir. İşte bu noktada, katmanlı mimari (N-Layer Architecture) devreye girer. Katmanlı mimari, kodun belirli sorumluluklara göre ayrılmasını sağlayarak hem yönetimi kolaylaştırır hem de <strong>Separation of Concerns (SoC)</strong> prensibine uygun bir yapı oluşturur. Peki, katmanlı mimarinin temel yapısı ve faydaları nelerdir?</p>
<h4 id="heading-katmanli-mimari-nedir"><strong>Katmanlı Mimari Nedir?</strong></h4>
<p>Katmanlı mimari, bir yazılım uygulamasının belirli sorumluluklara göre ayrılmış katmanlar üzerinde çalıştığı bir tasarım modelidir. Bu modelde her bir katman, belirli bir işlevi yerine getirir ve diğer katmanlarla belirli bir bağımsızlık içinde çalışır. Genellikle kullanılan katmanlar şunlardır:</p>
<ol>
<li><p><strong>Presentation Layer (Sunum Katmanı):</strong><br /> Kullanıcı arayüzünü ve kullanıcı etkileşimlerini yönetir. Örneğin: Web veya mobil arayüzler.<br /> <strong>Headless Architecture Senaryosunda:</strong><br /> Eğer uygulama bir <strong>headless architecture</strong> ile tasarlanmışsa, bu katman bir kullanıcı arayüzü sunmaz. Bunun yerine, bir API noktası (örneğin, REST API, GraphQL, gRPC) aracılığıyla istemci (client) uygulamalarına hizmet eder. Bu durumda, istemci ile Sunum Katmanı arasındaki iletişim bu API'ler üzerinden gerçekleştirilir ve bu katman, kullanıcı etkileşimlerinin başladığı merkez noktası haline gelir.</p>
</li>
<li><p><strong>Application Layer (Uygulama Katmanı):</strong><br /> Uygulama Katmanı, iş mantığını ve kurallarını yöneten merkez katmandır. Kullanıcıdan gelen istekleri işler, doğrular ve diğer katmanlarla koordinasyonu sağlar.</p>
</li>
<li><p><strong>Domain Layer (Alan Katmanı):</strong><br /> İş mantığının kurallarını ve domain modellerini içerir. Bu katman, uygulamanın merkezi iş mantığını ve kurallarını temsil eder. İş mantığının geri kalan katmanlardan izole edilmesi bu katmanda sağlanır.</p>
</li>
<li><p><strong>Infrastructure/Data Access Layer (Veri Erişim Katmanı):</strong><br /> Bu katman, uygulamanın dış dünyayla etkileşimini sağlar. Örneğin:</p>
<ul>
<li><p>Veritabanı işlemleri (SQL veya NoSQL veritabanları),</p>
</li>
<li><p>Üçüncü taraf API çağrıları,</p>
</li>
<li><p>Dosya sistemi erişimi.</p>
</li>
</ul>
</li>
</ol>
<h4 id="heading-katmanli-mimarinin-avantajlari"><strong>Katmanlı Mimarinin Avantajları</strong></h4>
<ul>
<li><p><strong>Kod Organizasyonu:</strong><br />  Kodun farklı sorumluluklara göre ayrılması, büyüyen projelerde okunabilirliği ve yönetimi kolaylaştırır.</p>
</li>
<li><p><strong>Bağımsızlık:</strong><br />  Katmanlar arasındaki düşük bağımlılık, ekiplere paralel çalışma imkanı tanır.</p>
</li>
<li><p><strong>Test Edilebilirlik:</strong><br />  Her katmanın bağımsız olarak test edilebilmesi, geliştirme sürecini hızlandırır.</p>
</li>
<li><p><strong>Yeniden Kullanılabilirlik:</strong><br />  İş mantığı veya veri erişim kodları, diğer projelerde veya modüllerde tekrar kullanılabilir.</p>
</li>
<li><p><strong>Sürdürülebilirlik:</strong><br />  Proje büyüdükçe kodun düzenli kalmasını sağlar.</p>
</li>
</ul>
<h4 id="heading-katmanli-mimarinin-dezavantajlari"><strong>Katmanlı Mimarinin Dezavantajları</strong></h4>
<ul>
<li><p><strong>Performans Sorunları:</strong><br />  Katmanlar arasında sürekli veri geçişi, büyük projelerde performans kaybına yol açabilir.</p>
</li>
<li><p><strong>Yüksek Karmaşıklık:</strong><br />  Küçük projelerde fazla katman kullanımı gereksiz bir karmaşıklık oluşturabilir.</p>
</li>
<li><p><strong>Çift Yönlü Bağımlılık Riski:</strong><br />  Yanlış tasarımda, katmanlar arasında çift yönlü bağımlılıklar oluşabilir, bu da yönetimi zorlaştırır.</p>
</li>
</ul>
<p>Monolitik mimarilerde, tüm kodun tek bir yapı içinde yer alması, projelerin büyüdükçe yönetimini zorlaştırır. Bu durumda, katmanlı mimari kullanılarak kod daha düzenli ve sürdürülebilir bir hale getirilebilir. Özellikle büyük ve karmaşık projelerde, katmanlı mimari olmadan tüm kodun tek bir yerde bulunması yönetimi imkânsız hale getirebilir.</p>
<p>Katmanlı mimari, sorumlulukların ayrılması ile şu faydaları sağlar:</p>
<ul>
<li><p>Kod daha düzenli bir şekilde organize edilir.</p>
</li>
<li><p>Her katman kendi sorumluluğunu üstlenir ve diğer katmanlardan bağımsız olarak çalışabilir.</p>
</li>
<li><p>Test edilebilir bir yapı ortaya çıkar, bu da geliştirme ve bakım süreçlerini kolaylaştırır.</p>
</li>
<li><p>Ekipler arasında görev dağılımı daha verimli bir şekilde yapılabilir.</p>
</li>
</ul>
<p>Örneğin, bir e-ticaret uygulamasında:</p>
<ul>
<li><p><strong>Sunum Katmanı:</strong> Kullanıcı sipariş verir.</p>
</li>
<li><p><strong>Uygulama Katmanı:</strong> Sipariş doğrulaması yapılır.</p>
</li>
<li><p><strong>Domain Katmanı:</strong> İş mantıkları çalıştırılır.</p>
</li>
<li><p><strong>Veri Erişim Katmanı:</strong> Sipariş bilgileri veri tabanına kaydedilir.</p>
</li>
</ul>
<h3 id="heading-moduler-monolitik-mimari-monolitik-yapilarin-evrimi"><strong>Modüler Monolitik Mimari: Monolitik Yapıların Evrimi</strong></h3>
<h4 id="heading-moduler-monolitik-mimari-nedir"><strong>Modüler Monolitik Mimari Nedir?</strong></h4>
<p>Modüler monolitik mimari, klasik monolitik mimarinin bir uzantısı olarak, büyük kod tabanlarının daha düzenli, sürdürülebilir ve yönetilebilir bir yapıya dönüştürülmesini sağlayan bir tasarım yaklaşımıdır. Geleneksel monolitik mimaride tüm bileşenler tek bir kod tabanında sıkıca bağlıyken, modüler monolitik mimaride kod tabanı belirli modüllere ayrılarak organize edilir. Her bir modül kendi sorumluluk alanına sahiptir ve diğer modüllerden bağımsız bir şekilde çalışabilir.</p>
<p>Modüler yapı sayesinde, monolitik mimarinin sağladığı basitlik ve hız gibi avantajlardan yararlanırken, kod tabanının karmaşıklığı daha iyi yönetilir ve sürdürülebilirlik artırılır.</p>
<h4 id="heading-monolitik-mimari-ile-iliskisi"><strong>Monolitik Mimari ile İlişkisi</strong></h4>
<p>Klasik monolitik mimari, tüm uygulamanın tek bir kod tabanında birleştirildiği bir yapıdır. Modüler monolitik mimari ise bu yapıyı daha düzenli hale getirerek, uygulamanın farklı parçalarını işlevlerine göre modüller halinde organize eder. Ancak bu modüller yine tek bir kod tabanı ve deploy birimi içinde yer alır.</p>
<p>Bu yapı, klasik monolitik mimarinin sadeliğini korurken, büyük ve karmaşık projelerde karşılaşılan yönetim ve ölçeklenebilirlik sorunlarına çözüm getirir.</p>
<h4 id="heading-neden-moduler-monolitik-mimari"><strong>Neden Modüler Monolitik Mimari?</strong></h4>
<ol>
<li><p><strong>Karmaşıklığı Yönetir:</strong><br /> Kod tabanı büyüdükçe karmaşıklık artar. Modüler yapı, bu karmaşıklığı işlevlere göre ayrıştırarak yönetimi kolaylaştırır.</p>
</li>
<li><p><strong>Bağımsız Geliştirme ve Test:</strong><br /> Modüller bağımsız olarak geliştirilebilir ve test edilebilir. Bu, ekiplerin paralel çalışmasını sağlar.</p>
</li>
<li><p><strong>Mikroservislere Geçiş İçin Temel:</strong><br /> Modüller, bağımsız mikroservislere dönüştürülebilecek şekilde tasarlanabilir. Bu sayede, mikroservislere geçiş daha az maliyetli ve daha hızlı bir şekilde gerçekleştirilebilir.</p>
</li>
<li><p><strong>Kod Tekrarını Azaltır:</strong><br /> Ortak işlevler modüller içinde merkezi olarak yönetilir ve kod tekrarı önlenir.</p>
</li>
<li><p><strong>Sürdürülebilirlik:</strong><br /> Modüler yapı, uzun vadeli projelerde kod tabanının sürdürülebilirliğini artırır ve bakımı kolaylaştırır.</p>
</li>
</ol>
<h4 id="heading-nasil-bir-fayda-saglar"><strong>Nasıl Bir Fayda Sağlar?</strong></h4>
<p>Monolitik mimarilerde büyüyen projeler, genellikle kod tabanının karmaşıklığını artırır ve geliştirme sürecini yavaşlatır. Modüler monolitik mimari, bu sorunları çözmek için etkili bir araçtır. Kodun daha düzenli olması, ekiplerin farklı modüller üzerinde bağımsız çalışmasını sağlar ve büyük projelerin daha kolay yönetilmesine olanak tanır.</p>
<h3 id="heading-monolitikten-mikroservise-gecis-surec-ve-prensipler"><strong>Monolitikten Mikroservise Geçiş: Süreç ve Prensipler</strong></h3>
<h4 id="heading-neden-mikroservislere-gecis"><strong>Neden Mikroservislere Geçiş?</strong></h4>
<p>Modüler monolitik mimari, belirli bir ölçekte etkili olsa da, çok büyük ölçekli projelerde bazı sınırlamalarla karşılaşabilir. Özellikle aşağıdaki durumlarda mikroservislere geçiş bir gereklilik haline gelebilir:</p>
<ul>
<li><p><strong>Bağımsız Ölçeklenebilirlik:</strong> Her modülün farklı trafik ve yük gereksinimleri olabilir. Mikroservisler, her modülü ayrı ayrı ölçeklendirme imkanı sunar.</p>
</li>
<li><p><strong>Teknoloji Çeşitliliği:</strong> Farklı modüllerin farklı teknolojilerle geliştirilmesi gerekiyorsa, mikroservis mimarisi bu çeşitliliği destekler.</p>
</li>
<li><p><strong>Bağımsız Geliştirme ve Dağıtım:</strong> Mikroservisler, farklı ekiplerin bağımsız olarak geliştirme yapabilmesine ve sadece ilgili servislerin dağıtımına olanak tanır.</p>
</li>
<li><p><strong>Yüksek Karmaşıklık:</strong> Çok büyük ve karmaşık monolitik yapılarda, değişiklik yapmak ve hataları çözmek zorlaşabilir. Mikroservisler, bu karmaşıklığı izole eder.</p>
</li>
</ul>
<h4 id="heading-gecis-surecindeki-zorluklar"><strong>Geçiş Sürecindeki Zorluklar</strong></h4>
<p>Monolitikten mikroservislere geçiş karmaşık bir süreçtir ve dikkatli bir planlama gerektirir. Geçiş sürecindeki başlıca zorluklar şunlardır:</p>
<ol>
<li><p><strong>Servislerin Tanımlanması:</strong> Mevcut monolitik yapının hangi modüllerinin bağımsız birer servis haline getirileceği belirlenmelidir.</p>
</li>
<li><p><strong>Veri Yönetimi:</strong> Merkezi bir veritabanından, her servisin kendi veri deposuna sahip olduğu bir yapıya geçiş yapılır. Bu, <strong>veritabanı bölme (database partitioning)</strong> ve <strong>veri tutarlılığı (data consistency)</strong> sorunlarını gündeme getirir.</p>
</li>
<li><p><strong>Servisler Arası İletişim:</strong> Monolitik yapılarda genellikle aynı kod tabanı içinde olan bileşenler arasındaki iletişim, mikroservislerde API veya mesajlaşma sistemleri ile sağlanır.</p>
</li>
<li><p><strong>Karmaşık Dağıtım Süreçleri:</strong> Mikroservisler, CI/CD süreçlerinde ek karmaşıklık yaratır. Dağıtım stratejileri dikkatlice planlanmalıdır.</p>
</li>
</ol>
<h4 id="heading-gecis-sureci-adim-adim"><strong>Geçiş Süreci: Adım Adım</strong></h4>
<ol>
<li><p><strong>Hazırlık ve Analiz:</strong></p>
<ul>
<li><p>Monolitik yapıyı analiz ederek bağımsız servisler için uygun olan modülleri belirleyin.</p>
</li>
<li><p>Veri bağımlılıklarını ve modüller arası ilişkileri inceleyin.</p>
</li>
<li><p>İhtiyaçlara uygun bir mikroservis stratejisi belirleyin.</p>
</li>
</ul>
</li>
<li><p><strong>Modüler Monolitik Yapıyı Güçlendirin:</strong><br /> Geçiş sürecinden önce, monolitik yapının modüler hale getirilmesi, geçişin daha sorunsuz gerçekleşmesini sağlar.</p>
</li>
<li><p><strong>Pilot Mikroservis Oluşturun:</strong><br /> Geçişe düşük riskli bir modül ile başlayarak, bir pilot mikroservis oluşturun ve öğrenim süreci başlatın.</p>
</li>
<li><p><strong>Kademeli Geçiş Yapın:</strong><br /> Tüm sistemi aynı anda mikroservise dönüştürmek yerine, kademeli bir geçiş süreci izleyin. Parça parça ayrıştırma, riskleri minimize eder.</p>
</li>
<li><p><strong>Servisler Arası İletişim Altyapısını Kurun:</strong></p>
<ul>
<li><p>REST API, gRPC veya mesajlaşma sistemleri (ör. <a target="_blank" href="https://www.rabbitmq.com/">RabbitMQ</a>, <a target="_blank" href="https://kafka.apache.org/">Kafka</a>, <a target="_blank" href="https://aws.amazon.com/tr/sqs/">AWS SQS</a>) kullanarak servisler arası iletişimi sağlayın.</p>
</li>
<li><p>Servislerin birbirine bağımlılığını azaltmak için asenkron iletişim yöntemlerini tercih edin.</p>
</li>
</ul>
</li>
<li><p><strong>Monitoring ve Observability Altyapısını Güçlendirin:</strong><br /> Mikroservislerin izlenebilirliğini artırmak için loglama, dağıtılmış tracing (ör. OpenTelemetry), ve metrik toplama araçları (ör. Prometheus, Grafana) kullanın.</p>
</li>
</ol>
<h4 id="heading-mikroservise-geciste-kullanilan-patternler"><strong>Mikroservise Geçişte Kullanılan Patternler</strong></h4>
<ol>
<li><p><strong>Strangler Fig Pattern:</strong><br /> <a target="_blank" href="https://learn.microsoft.com/en-us/azure/architecture/patterns/strangler-fig">Strangler Pattern</a>, mevcut bir monolitik sistemi parça parça bir mikroservice mimarisine taşımak için kullanılan bir yazılım geliştirme desenidir. Bu desen, eski sistemin tamamını bir anda yeniden yazmak yerine, sistemin farklı modüllerini ya da işlevlerini aşamalı olarak yenilerken, eski ve yeni sistemin aynı anda çalışmasını sağlar.</p>
</li>
<li><p><a target="_blank" href="https://microservices.io/patterns/data/database-per-service.html"><strong>Database Per Service</strong></a><strong>:</strong><br /> Her mikroservisin kendi veri tabanına sahip olduğu bu yaklaşım, veri bağımsızlığı sağlar.</p>
</li>
<li><p><a target="_blank" href="https://microservices.io/patterns/apigateway.html"><strong>API Gateway:</strong></a><br /> Servisler arası iletişimi kolaylaştırmak ve servisleri dış dünyadan izole etmek için bir API Gateway kullanılır.</p>
</li>
<li><p><a target="_blank" href="https://microservices.io/patterns/data/event-driven-architecture.html"><strong>Event-Driven Architecture</strong></a><strong>:</strong><br /> Servisler arasındaki iletişim, olay tabanlı bir yapıya dayanır. Örneğin, bir sipariş oluşturulduğunda, bu olay mesajlaşma sistemi aracılığıyla diğer servislere iletilir.</p>
</li>
<li><p><a target="_blank" href="https://learn.microsoft.com/en-us/azure/architecture/patterns/bulkhead"><strong>Bulkhead Pattern</strong></a><strong>:</strong><br /> Bulkhead Pattern, sistem tasarımında kullanılan bir <strong>dayanıklılık (resilience)</strong> tasarım desenidir. Bu desen, sistemdeki farklı bileşenlerin veya işlevlerin birbirinden izole edilmesini sağlar. Böylece bir bileşende meydana gelen bir sorun, diğer bileşenlerin çalışmasını etkilemez. Bu desenin adı, gemilerdeki su geçirmez bölmelere benzetilir. Gemilerde bulkhead bölmeleri, geminin bir kısmında su sızıntısı olsa bile, diğer bölümlerin zarar görmeden çalışmasını sağlar.</p>
</li>
</ol>
<p>Yazılım geliştirme dünyasında mimari seçimler, projelerin başarısını doğrudan etkileyen kritik unsurlardır. Monolitik mimari, hızlı başlangıçlar, basitlik ve düşük karmaşıklık gerektiren projeler için hâlâ güçlü bir seçenek olmaya devam etmektedir. Ancak, projeler büyüdükçe ve gereksinimler karmaşıklaştıkça, mikroservislere geçiş ihtiyacı doğabilir.</p>
<p>Bu yazıda, monolitik mimarinin avantajlarını ve dezavantajlarını tartışırken, katmanlı mimari ve modüler monolitik yapının önemine dikkat çektik. Ayrıca, mikroservislere geçiş sürecinin aşamalarını, dikkat edilmesi gereken zorlukları ve bu süreçte kullanılabilecek patternlerden kısaca bahsettik. Mikroservislere geçiş, uzun vadeli bir strateji ile planlanmalı ve kademeli bir şekilde gerçekleştirilmelidir. Doğru tasarım prensipleri ve güçlü bir altyapı ile, hem monolitik yapılardan hem de mikroservis mimarisinden en iyi şekilde faydalanmak mümkündür.</p>
<p>Yazılım geliştirme süreçlerinde doğru mimari seçimi, yalnızca teknolojik değil, aynı zamanda iş hedeflerine uygun bir yapı oluşturarak, projenin başarısını garanti altına alır. Gelecekteki blog yazılarımızda, moduler monolith mimarisi, mikroservis mimarisi ve bu yapılardaki best practice’ler üzerine daha derinlemesine odaklanacağız.</p>
<p>Bu bilgilerin üzerine daha fazla araştırma yaparak, denemeler yaparak bu konularda kendinizi geliştirebilir ve ihtiyaçlara göre projelerinizde doğru mimari kararlar verebilirsiniz. Umarım sana dokunmuş olabilirim. Merak ettiğin bir şey olursa benimle iletişime geçebilirsin.</p>
<h3 id="heading-daha-derine-dalmak-isteyenler-icin-kaynaklar"><strong>Daha Derine Dalmak İsteyenler İçin Kaynaklar</strong></h3>
<ol>
<li><p><a target="_blank" href="https://learning.oreilly.com/library/view/building-microservices-2nd/9781492034018/"><strong>Building Microservices – Sam Newman</strong></a><br /> Mikroservis mimarisiyle ilgili kapsamlı bilgiler sunan bu kitap, yazılım geliştiriciler için bir rehber niteliğindedir.</p>
</li>
<li><p><a target="_blank" href="https://learning.oreilly.com/library/view/monolith-to-microservices/9781492047834/"><strong>Monolith to Microservices - Sam Newman</strong></a></p>
<p> Bu kitap, monolitik bir yapıdan mikroservis mimarisine geçiş sürecini detaylı bir şekilde ele alır. İş sürekliliğini koruyarak geçiş yapmanın yöntemlerini, örnekler ve desenlerle açıklar. Uygulama ve veri tabanı ayrıştırması, entegrasyon stratejileri ve iletişim süreçleri gibi konulara değinir. Mikroservise geçişin ne zaman ve nasıl yapılması gerektiği üzerine rehberlik sağlar. Sistemlerini yeniden inşa etmek yerine parça parça dönüştürmek isteyen organizasyonlar için idealdir.</p>
</li>
<li><p><a target="_blank" href="https://martinfowler.com/bliki/MonolithFirst.html"><strong>Martin Fowler - Monolith First</strong></a></p>
<p> Bu metinde Martin Fowler, <strong>monolith-first stratejisi</strong> (önce monolitik mimari ile başlama) ve <strong>mikroservis mimarisi</strong> arasında bir karşılaştırma yapıyor. Mikroservislerin birçok avantajı olsa da, bu avantajların belirli bir karmaşıklık seviyesinde işe yaradığını ve küçük/orta ölçekli ya da başlangıç aşamasındaki projelerde genellikle monolitik bir mimariyle başlamanın daha mantıklı olduğunu savunuyor.</p>
</li>
<li><p><a target="_blank" href="https://learn.microsoft.com/en-us/azure/architecture/patterns/strangler-fig"><strong>Strangler Fig Pattern</strong></a><br /> Microsoft kendi yayınladığı bu blog yazısında monolitikten mikroservislere geçiş sürecinde kullanılan bu desen hakkında detaylı bilgi veriyor.</p>
</li>
<li><p><a target="_blank" href="https://learning.oreilly.com/library/view/clean-code-a/9780136083238/"><strong>Clean Code – Robert C. Martin (Uncle Bob)</strong></a><br /> Bu kitap, kaliteli kod yazmanın prensiplerini ve iyi bir yazılım geliştiricinin sahip olması gereken disiplinleri ele alır. Kodun nasıl daha okunabilir, sürdürülebilir ve kolay bakımı yapılabilir hale getirileceği üzerine pratik öneriler sunar. Yazılım geliştiriciler için bir başucu kitabıdır.</p>
</li>
<li><p><a target="_blank" href="https://learning.oreilly.com/library/view/clean-architecture-a/9780134494272/"><strong>Clean Architecture – Robert C. Martin (Uncle Bob)</strong></a><br /> Bu kitap, yazılım mimarisi tasarımı üzerine kapsamlı bilgiler sağlar. Sürdürülebilir, bağımsız ve modüler sistemler inşa etmek için temel prensipleri ve desenleri açıklar. Uygulama mimarilerinde yüksek bağlılığı azaltmak ve uzun vadeli başarıyı garantilemek isteyen yazılımcılar için ideal bir kaynaktır.</p>
</li>
<li><p><a target="_blank" href="https://learning.oreilly.com/library/view/designing-data-intensive-applications/9781491903063/"><strong>Designing Data-Intensive Applications – Martin Kleppmann</strong></a><br /> Bu kitap, güvenilir, ölçeklenebilir ve sürdürülebilir sistemler tasarlamak için veri yönetimi ve sistem mimarisi üzerine kapsamlı bir rehberdir. Dağıtık sistemler, veri tabanı mimarileri, veri işleme, tutarlılık ve performans konularında derinlemesine bilgiler sunar. Özellikle büyük veri ve mikroservis mimarisi üzerine çalışanlar için vazgeçilmez bir kaynaktır.</p>
</li>
</ol>
]]></content:encoded></item><item><title><![CDATA[Kubernetes Objelerini Nasıl Oluşturur ve Yönetirsiniz?]]></title><description><![CDATA[Kubernetes dünyasında yapılandırma dosyalarının doğru ve etkili bir şekilde yönetilmesi, başarılı bir operasyonun temel taşlarından biridir. Bu yazıda, Kubernetes objelerini tanımlarken kullandığımız araçları inceleyecek ve aralarındaki farkları açık...]]></description><link>https://serhatleventyavas.dev/kubernetes-objelerini-nasil-olusturur-ve-yonetirsiniz</link><guid isPermaLink="true">https://serhatleventyavas.dev/kubernetes-objelerini-nasil-olusturur-ve-yonetirsiniz</guid><category><![CDATA[Kubernetes]]></category><category><![CDATA[YAML]]></category><dc:creator><![CDATA[Serhat Levent Yavaş]]></dc:creator><pubDate>Sun, 11 Aug 2024 17:23:41 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1722755000176/381235f3-a6f6-422e-8605-dea0c33c0da6.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Kubernetes dünyasında yapılandırma dosyalarının doğru ve etkili bir şekilde yönetilmesi, başarılı bir operasyonun temel taşlarından biridir. Bu yazıda, Kubernetes objelerini tanımlarken kullandığımız araçları inceleyecek ve aralarındaki farkları açıklayacağız. Önceki makalelere göz atmak isterseniz, aşağıdaki bağlantılardan başlayabilirsiniz:</p>
<ol>
<li><p><a target="_blank" href="https://serhatleventyavas.dev/kubernetesin-gecmisi-ve-evrimi">Kubernetes'in Geçmişi ve Evrimi</a></p>
</li>
<li><p><a target="_blank" href="https://serhatleventyavas.dev/kubernetes-cluster-nedir-yeni-baslayanlar-icin-kilavuz">Kubernetes Cluster Nedir? Yeni Başlayanlar İçin Kılavuz</a></p>
</li>
<li><p><a target="_blank" href="https://serhatleventyavas.dev/kubernetes-komut-satiri-araci-kubectl">Kubernetes Komut Satırı Aracı: kubectl</a></p>
</li>
</ol>
<p>Kubernetes'de bir obje oluşturmanın üç temel yolu vardır:</p>
<ol>
<li><p>Yaml yapılandırma dosyaları</p>
</li>
<li><p>JSON yapılandırma dosyaları</p>
</li>
<li><p>kubectl komut satırı</p>
</li>
</ol>
<h3 id="heading-yaml-yapilandirma-dosyalari">YAML Yapılandırma Dosyaları</h3>
<p><a target="_blank" href="https://yaml.org/"><strong>YAML (YAML Ain't Markup Language)</strong></a>, insan tarafından okunabilir yapılandırma dosyaları oluşturmak için kullanılan sade ve anlaşılır bir veri serileştirme dilidir. Kubernetes'de, YAML dosyaları, sistemin ihtiyaç duyduğu yapılandırmaları tanımlamak ve yönetmek için standart bir araç haline gelmiştir.</p>
<p><strong>Artıları:</strong></p>
<ol>
<li><p><strong>Okunabilirlik:</strong> YAML, girintileme ve boşluklarla yapıyı ifade eder, bu da onu insanlar tarafından daha kolay okunabilir hale getirir. Karmaşık yapılandırma dosyaları için daha anlaşılırdır.</p>
</li>
<li><p><strong>Yalınlık:</strong> Daha az sembol ve işaretleme kullanır (<code>{}</code>, <code>[]</code>, <code>,</code>, <code>:</code> gibi). Bu da özellikle büyük dosyalarda temiz ve anlaşılır bir yapı sağlar.</p>
</li>
<li><p><strong>Desteklenen Veri Türleri:</strong> YAML, string, integer, float, boolean gibi temel veri türlerini destekler. Ayrıca çok satırlı string'ler, liste ve sözlük (dictionary) gibi daha karmaşık yapıları da rahatlıkla ifade edebilir.</p>
</li>
<li><p><strong>Esneklik:</strong> YAML esnek bir yapı sunar. Veri türleri genellikle otomatik olarak algılanır, bu da özellikle hızlıca yapılandırma dosyaları yazarken kullanışlı olabilir. Örneğin, string değerler tırnak işareti olmadan da yazılabilir, bu da yazmayı ve okumayı kolaylaştırır.</p>
</li>
</ol>
<p><strong>Eksileri:</strong></p>
<ol>
<li><p><strong>Hata Yapma Riski:</strong> Girintileme hataları YAML dosyalarında sıkça karşılaşılan bir sorundur. Girintileme hataları doğru çalışmayan yapılandırmalara yol açabilir.</p>
</li>
<li><p><strong>Performans:</strong> JSON'a göre daha karmaşık ve soyut bir yapısı olduğundan, işlenmesi JSON'dan daha yavaş olabilir.</p>
</li>
<li><p><strong>Standart Dışı Kullanımlar:</strong> YAML, isteğe bağlı olarak yorum satırları ve özel karakterleri destekler, bu da bazen taşınabilirlik ve standart uyumu açısından sorun yaratabilir.</p>
</li>
</ol>
<p>YAML ile bir pod oluşturmak istediğimizde aşağıdaki gibi bir tanımlama yapabiliriz.</p>
<pre><code class="lang-yaml"><span class="hljs-attr">apiVersion:</span> <span class="hljs-string">v1</span>
<span class="hljs-attr">kind:</span> <span class="hljs-string">Pod</span>
<span class="hljs-attr">metadata:</span>
  <span class="hljs-attr">name:</span> <span class="hljs-string">my-pod</span>
<span class="hljs-attr">spec:</span>
  <span class="hljs-attr">containers:</span>
    <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">my-container</span>
      <span class="hljs-attr">image:</span> <span class="hljs-string">nginx:latest</span>
      <span class="hljs-attr">ports:</span>
        <span class="hljs-bullet">-</span> <span class="hljs-attr">containerPort:</span> <span class="hljs-number">80</span>
</code></pre>
<h3 id="heading-json-yapilandirma-dosyalari">JSON Yapılandırma Dosyaları</h3>
<p><a target="_blank" href="https://www.json.org/json-en.html"><strong>JSON (JavaScript Object Notation)</strong></a>, veri depolama ve veri aktarımı için yaygın olarak kullanılan hafif bir veri değişim formatıdır. JSON, insan tarafından okunabilir bir metin formatı olup, JavaScript nesne gösterimi üzerine kurulmuştur, ancak dil bağımsızdır. JSON, yapılandırılmış verileri bir dizi anahtar-değer çifti ve sıralı liste olarak ifade eder.</p>
<p><strong>Artıları:</strong></p>
<ol>
<li><p><strong>Basitlik:</strong> JSON, basit bir yapıdadır ve çok net bir sözdizimi kullanır. Bu da onu hızlı ve kolay bir şekilde yazılabilir ve işlenebilir hale getirir.</p>
</li>
<li><p><strong>Performans:</strong> JSON, daha basit yapısı sayesinde daha hızlı işlenebilir. Genellikle web API'larında veri aktarımı için kullanılır.</p>
</li>
<li><p><strong>Standart Uyumluluk:</strong> JSON, web servisleri ve API'lar için yaygın olarak kullanılan bir format olduğu için geniş bir destek ve uyumluluk sağlar.</p>
</li>
<li><p><strong>Tip Güvenliği ve Veri Doğrulama:</strong> JSON, tip güvenliği ve veri doğrulama konusunda daha katıdır. JSON dosyasında her veri türü (string, number, boolean, array, object) açıkça belirtilir ve bu sayede veri yapısının doğruluğu daha net bir şekilde kontrol edilebilir. Örneğin, bir string değeri her zaman tırnak içinde belirtilir, bu da JSON'un veri doğrulama araçları tarafından kolayca işlenmesini sağlar.</p>
</li>
</ol>
<p><strong>Eksileri:</strong></p>
<ol>
<li><p><strong>Okunabilirlik:</strong> Özellikle karmaşık yapılandırma dosyaları söz konusu olduğunda, JSON'un okunması ve yönetilmesi zor olabilir.</p>
</li>
<li><p><strong>Yazım Hataları:</strong> JSON'un sıkı bir sözdizimi vardır (örneğin, tırnak işaretleri, virgüller). Küçük bir hata, JSON dosyasının tamamen geçersiz olmasına neden olabilir.</p>
</li>
<li><p><strong>Esneklik Eksikliği:</strong> JSON, çok satırlı stringler ve yorumlar gibi özellikleri desteklemez. Bu da yapılandırma dosyalarında esnekliği sınırlayabilir.</p>
</li>
</ol>
<p>JSON ile bir pod oluşturmak istediğimizde aşağıdaki gibi bir tanımlama yapabiliriz.</p>
<pre><code class="lang-json">{
  <span class="hljs-attr">"apiVersion"</span>: <span class="hljs-string">"v1"</span>,
  <span class="hljs-attr">"kind"</span>: <span class="hljs-string">"Pod"</span>,
  <span class="hljs-attr">"metadata"</span>: {
    <span class="hljs-attr">"name"</span>: <span class="hljs-string">"my-pod"</span>
  },
  <span class="hljs-attr">"spec"</span>: {
    <span class="hljs-attr">"containers"</span>: [
      {
        <span class="hljs-attr">"name"</span>: <span class="hljs-string">"my-container"</span>,
        <span class="hljs-attr">"image"</span>: <span class="hljs-string">"nginx:latest"</span>,
        <span class="hljs-attr">"ports"</span>: [
          {
            <span class="hljs-attr">"containerPort"</span>: <span class="hljs-number">80</span>
          }
        ]
      }
    ]
  }
}
</code></pre>
<h3 id="heading-kubectl-komut-satiri">Kubectl Komut Satırı</h3>
<p>Yaml ya da json dosyaları kullanmadan da <a target="_blank" href="https://kubernetes.io/docs/reference/kubectl/">kubectl</a> komutlarını kullanarak çeşitli objeler oluşturmak ve güncellemek mümkündür.</p>
<p><strong>Artıları:</strong></p>
<ol>
<li><strong>Hızlı ve Anlık Yönetim:</strong> Kubernetes objeleri doğrudan komut satırı aracılığıyla da oluşturulabilir ve yönetilebilir.</li>
</ol>
<p><strong>Eksileri:</strong></p>
<ol>
<li><p><strong>Sürdürülebilirlik:</strong> Yapılandırmaların belgelenmesi ve versiyon kontrolü zor olur.</p>
</li>
<li><p><strong>Karmaşıklık:</strong> Daha karmaşık yapılandırmalar için komut satırı yeterli olmaz.</p>
</li>
</ol>
<p>Kubectl ile bir pod oluşturmak istediğimizde aşağıdaki gibi komut yazabiliriz.</p>
<pre><code class="lang-bash">kubectl run my-pod --image=nginx:latest --port=80
</code></pre>
<p>Her ne kadar kubectl komut satırı, hızlı ve doğrudan müdahale gerektiren durumlar için kullanışlı olsa da, uzun vadeli yapılandırmalar için tercih edilmez. Kubectl, komut satırı doğrudan terminal üzerinden çalıştığı için yapılan işlemlerin <strong>tekrarlanabilirliği</strong> ve <strong>izlenebilirliği</strong> sınırlıdır. Bu yöntem, özellikle karmaşık ve uzun yapılandırmalar için uygun değildir, çünkü komutlar terminalde çalıştırıldıktan sonra kaybolur ve bu da uzun vadeli yapılandırmaların yönetimini zorlaştırır. Bunun yerine, JSON ve YAML dosyaları, yapılandırma dosyalarının daha etkili bir şekilde yönetilmesine olanak tanır. <strong>CI/CD (Continuous Integration/Continuous Deployment) süreçlerinde</strong>, yapılandırma dosyalarının <strong>versiyon kontrolü altında tutulması, izlenebilir olması ve otomatik olarak işlenebilirliği</strong> son derece önemlidir. Hem JSON hem de YAML dosyaları, bu süreçlerde başarıyla kullanılabilir. YAML dosyaları, özellikle insan tarafından okunabilirliği ve yazılabilirliği nedeniyle CI/CD süreçlerinde daha fazla tercih edilirken, JSON da belirli otomasyon araçları ve API entegrasyonları ile uyumluluğu nedeniyle tercih edilebilir.</p>
<h3 id="heading-yaml-ve-json-arasindaki-farklar">YAML ve JSON Arasındaki Farklar</h3>
<ol>
<li><p><strong>Sözdizimi:</strong> YAML, girintileme kullanırken JSON köşeli parantezler (<code>[]</code>) ve süslü parantezler (<code>{}</code>) kullanır. Bu, YAML'ı daha okunabilir, JSON'u ise daha katı bir format haline getirir.</p>
</li>
<li><p><strong>Yorumlar:</strong> YAML yorumları desteklerken JSON desteklemez. Bu, YAML'ı yapılandırma dosyaları için daha uygun hale getirir.</p>
</li>
<li><p><strong>Veri Yapıları:</strong> Her iki format da aynı veri yapılarının çoğunu destekler, ancak YAML daha zengin ve esnek bir şekilde çok satırlı stringler ve karmaşık yapıların ifade edilmesine izin verir.</p>
</li>
</ol>
<h3 id="heading-kubernetes-ve-cloud-dunyasinda-neden-yaml-tercih-ediliyor">Kubernetes ve Cloud Dünyasında Neden YAML Tercih Ediliyor?</h3>
<ol>
<li><p><strong>Okunabilirlik:</strong> Kubernetes gibi karmaşık sistemlerde yapılandırma dosyalarının anlaşılması ve yönetilmesi önemlidir. YAML, okunabilirliği sayesinde kullanıcılar için daha erişilebilir hale gelir.</p>
</li>
<li><p><strong>Modülerlik ve Esneklik:</strong> YAML, girintileme ve blok tabanlı yapısıyla, karmaşık nesne yapılarını ve hiyerarşileri düzenlemek için idealdir. Kubernetes gibi büyük ölçekli sistemlerde, farklı konfigürasyon parçalarının bir araya getirilmesi ve düzenlenmesi gerektiğinde, YAML'ın modüler yapısı büyük bir avantaj sağlar.</p>
</li>
<li><p><strong>Yorumlama Yeteneği:</strong> YAML, yapılandırma dosyalarında yorum satırlarına izin verir. Bu, yapılandırma dosyalarının amacı ve kullanımı hakkında daha fazla bilgi vermek için yararlıdır.</p>
</li>
</ol>
<p>YAML ve JSON'un her biri farklı senaryolar için güçlü araçlar sunar. Kubernetes başta olmak üzere birçok modern bulut sistemi, yapılandırma dosyaları için YAML'ın okunabilirliği, esnekliği ve yorumlama yeteneği gibi avantajları onu tercih edilen bir format haline getirir.</p>
<h3 id="heading-yaml-ile-kubernetes-objelerinin-yonetimi">YAML ile Kubernetes Objelerinin Yönetimi</h3>
<p>YAML, JSON ve kubectl komut satırından bahsettiğimize göre şimdi bir kubernetes yaml dosyasının nasıl yapılandırıldığını, bu dosyalarda sıkça kullanılan alanları ve objeye göre değişebilen alanları inceleyeceğiz.</p>
<p>Bir Kubernetes YAML dosyası, genellikle dört ana bölüm içerir:</p>
<ol>
<li><p><code>apiVersion</code><strong>:</strong> Bu alan, Kubernetes API'sinin hangi versiyonunun kullanıldığını belirtir. Her objenin, belirli bir API versiyonuna bağlı olarak tanımlanması gerekir.</p>
</li>
<li><p><code>kind</code><strong>:</strong> Bu alan, oluşturulacak Kubernetes objesinin türünü tanımlar (örneğin, <a target="_blank" href="https://kubernetes.io/docs/concepts/workloads/pods/">Pod</a>, <a target="_blank" href="https://kubernetes.io/docs/concepts/services-networking/service/">Service</a>, <a target="_blank" href="https://kubernetes.io/docs/concepts/workloads/controllers/deployment/">Deployment</a> vb.).</p>
</li>
<li><p><code>metadata</code><strong>:</strong> Objenin adı, etiketler, ve açıklamalar gibi meta bilgileri içerir.</p>
</li>
<li><p><code>spec</code><strong>:</strong> Bu alan, objenin özel yapılandırmalarını içerir. Bu bölüm, objenin türüne göre büyük ölçüde değişir.</p>
</li>
</ol>
<blockquote>
<p>Bu dört ana bölümden <code>apiVersion</code>, <code>kind</code> ve <code>metada</code> tüm objelerin tanımında bulunur. <code>spec</code> bölümü obje türüne göre değişir.</p>
</blockquote>
<pre><code class="lang-yaml"><span class="hljs-attr">apiVersion:</span> <span class="hljs-string">v1</span>
<span class="hljs-attr">kind:</span> <span class="hljs-string">Pod</span>
<span class="hljs-attr">metadata:</span>
  <span class="hljs-attr">name:</span> <span class="hljs-string">my-pod</span>
  <span class="hljs-attr">labels:</span>
    <span class="hljs-attr">app:</span> <span class="hljs-string">my-app</span>
<span class="hljs-attr">spec:</span>
  <span class="hljs-attr">containers:</span>
    <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">my-container</span>
      <span class="hljs-attr">image:</span> <span class="hljs-string">nginx:latest</span>
      <span class="hljs-attr">ports:</span>
        <span class="hljs-bullet">-</span> <span class="hljs-attr">containerPort:</span> <span class="hljs-number">80</span>
</code></pre>
<p>Örneğin yukarıdaki yaml tanımlamasında, <code>kind</code> değeri <code>Pod</code>. Yani biz bir pod tanımlaması yapıyoruz. <code>apiVersion</code> kısmında <code>v1</code> değerini görüyoruz. Bu tanımlar rastgele yazdığımız tanımlar değildir. Bu tanımlar kubernetes api içerisinde bulunur. Örneğin biz bir pod oluşturmak istiyoruz ancak <code>apiVersion</code>, <code>kind</code> alanlarına ne yazacağımızı bilmiyoruz diyelim. Bu durumda kubectl komut satırını kullanarak ya da kubernetes dökümantasyonunu okuyarak öğrenebiliriz.</p>
<ul>
<li><strong>Dökümantasyon</strong>: <a target="_blank" href="https://kubernetes.io/docs/reference/kubernetes-api/workload-resources/pod-v1/">https://kubernetes.io/docs/reference/kubernetes-api/workload-resources/pod-v1/</a></li>
</ul>
<p>Yukarıdaki adresi ziyaret ettiğimizde tarayıcımızda aşağıdakine benzer sayfa yüklenecektir. Burada <code>kind</code> ve <code>apiVersion</code> bilgilerini bulabileceğiz.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1723385213468/f2c608cb-6f98-443b-be81-1bdbcd69bd7f.png" alt class="image--center mx-auto" /></p>
<ul>
<li><p>Bu bilgileri kubectl komut satırı üzerinden de öğrenebiliriz.</p>
<pre><code class="lang-yaml">  <span class="hljs-string">kubectl</span> <span class="hljs-string">explain</span> <span class="hljs-string">pod</span>
</code></pre>
<p>  Yukarıdaki komutu çalıştırdığımızda, terminal bize cevap olarak aşağıdaki gibi bir çıktı üretir. Burada <code>kind</code> ve <code>apiVersion</code> bilgilerini görebilirsiniz.</p>
<p>  <img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1723385062711/09119873-74f1-48d0-b4e3-359e4c3285bf.png" alt class="image--center mx-auto" /></p>
<p>  Her kubernetes objesine ek bilgiler eklemek için <code>metada</code> bölümünü kullanırız. <code>metada</code> içerisine <code>name</code>, <code>labels</code> <strong>(etiketler)</strong>, <code>annotations</code> <strong>(ek açıklamalar)</strong> gibi tanımlamalar yapabiliriz. Aşağıdaki yaml tanımlamasında gördüğünüz gibi <code>metadata</code> içerisinde pod'a isim, etiketler ve ekstra bilgiler ekledik.</p>
</li>
</ul>
<blockquote>
<p><strong>Labels (etiketler),</strong> Kubernetes ekosisteminde çok önemlidir. Kubernetes objelerini ayırmada, gruplamada ve seçici sorgulamalar yapmada kullanılır. Etiketler sayesinde belirli bir etiketle işaretlenmiş objeleri seçebilir, bu objeler üzerinde toplu işlemler gerçekleştirebilir ve monitoring, scaling gibi operasyonlarda etiketleri referans alarak otomatik süreçler oluşturabilirsiniz. Ayrıca, etiketler kullanarak belirli bir uygulamanın farklı versiyonlarını veya ortamlarını ayırt etmek mümkündür.</p>
</blockquote>
<pre><code class="lang-yaml"><span class="hljs-attr">apiVersion:</span> <span class="hljs-string">v1</span>
<span class="hljs-attr">kind:</span> <span class="hljs-string">Pod</span>
<span class="hljs-attr">metadata:</span>
  <span class="hljs-attr">name:</span> <span class="hljs-string">frontend</span>
  <span class="hljs-attr">labels:</span>
    <span class="hljs-attr">app:</span> <span class="hljs-string">cmspanel</span>
    <span class="hljs-attr">environment:</span> <span class="hljs-string">production</span>
    <span class="hljs-attr">tier:</span> <span class="hljs-string">frontend</span>
  <span class="hljs-attr">annotations:</span>
    <span class="hljs-attr">description:</span> <span class="hljs-string">"Bu pod frontend uygulamamızı, production ortamında çalıştırır."</span>
    <span class="hljs-attr">maintainer:</span> <span class="hljs-string">"serhatleventyavas@gmail.com"</span>
<span class="hljs-attr">spec:</span>
  <span class="hljs-attr">containers:</span>
    <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">cmspanel</span>
      <span class="hljs-attr">image:</span> <span class="hljs-string">nginx:latest</span>
      <span class="hljs-attr">ports:</span>
        <span class="hljs-bullet">-</span> <span class="hljs-attr">containerPort:</span> <span class="hljs-number">80</span>
</code></pre>
<p>Her kubernetes objesinin kendi özgü ayarları, tanımlamaları vardır. Bu tanımları yapmak için <code>spec</code> bölümünü kullanırız. Bu bölüm, objenin türüne göre büyük ölçüde değişir.</p>
<p>Örneğin, aşağıdaki YAML dosyasında bir Pod oluşturulurken kullanılan <code>spec</code> tanımını görebilirsiniz.</p>
<pre><code class="lang-yaml"><span class="hljs-attr">apiVersion:</span> <span class="hljs-string">v1</span>
<span class="hljs-attr">kind:</span> <span class="hljs-string">Pod</span>
<span class="hljs-attr">metadata:</span>
  <span class="hljs-attr">name:</span> <span class="hljs-string">my-pod</span>
<span class="hljs-attr">spec:</span>
  <span class="hljs-attr">containers:</span>
    <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">my-container</span>
      <span class="hljs-attr">image:</span> <span class="hljs-string">nginx:latest</span>
      <span class="hljs-attr">ports:</span>
        <span class="hljs-bullet">-</span> <span class="hljs-attr">containerPort:</span> <span class="hljs-number">80</span>
</code></pre>
<p><strong>Spec bölümünde kullandığımız tanımları açıklamak gerekirse:</strong></p>
<ul>
<li><p><code>containers</code>: Pod içinde çalışacak container'ları tanımlar.</p>
</li>
<li><p><code>name</code>: Container'ın adını belirler.</p>
</li>
<li><p><code>image</code>: Kullanılacak Docker imajını belirtir.</p>
</li>
<li><p><code>ports</code>: Container'da açılacak portları tanımlar. Bu örnekte, <code>containerPort</code> 80 olarak belirlenmiştir.</p>
</li>
</ul>
<p>Pod'un <code>spec</code> bölümü hakkında daha detaylı bilgi edinmek için Kubernetes <a target="_blank" href="https://kubernetes.io/docs/reference/kubernetes-api/workload-resources/pod-v1/#PodSpec">dokümantasyonunu</a> ziyaret edebilirsiniz.</p>
<h3 id="heading-kubectl-komutu-ve-yaml-ile-pod-yonetimi">Kubectl Komutu ve YAML ile Pod Yönetimi</h3>
<p>Yazımızın bu son bölümünde, bir YAML dosyası kullanarak nasıl bir Pod oluşturabileceğimizi, güncelleyebileceğimizi ve silebileceğimizi ele alacağız. Bu konuları ileride yazmayı planladığım Pod makalesinde daha detaylı olarak inceleyeceğim. Ancak, bu noktada bir YAML dosyasının nasıl çalıştırıldığını göstermek ve temel Pod yönetimi işlemlerini anlamak adına bu örnek oldukça faydalı olacaktır.</p>
<h4 id="heading-pod-olusturma">Pod Oluşturma</h4>
<p>Öncelikle, bir YAML dosyası oluşturarak Pod tanımlamamızı yapalım. Aşağıdaki içerikle <code>mypod.yaml</code> adlı bir dosya oluşturalım:</p>
<pre><code class="lang-yaml"><span class="hljs-attr">apiVersion:</span> <span class="hljs-string">v1</span>
<span class="hljs-attr">kind:</span> <span class="hljs-string">Pod</span>
<span class="hljs-attr">metadata:</span>
  <span class="hljs-attr">name:</span> <span class="hljs-string">mypod</span>
<span class="hljs-attr">spec:</span>
  <span class="hljs-attr">containers:</span>
    <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">mycontainer</span>
      <span class="hljs-attr">image:</span> <span class="hljs-string">nginx:latest</span>
      <span class="hljs-attr">ports:</span>
        <span class="hljs-bullet">-</span> <span class="hljs-attr">containerPort:</span> <span class="hljs-number">80</span>
</code></pre>
<p>Bu YAML dosyasını oluşturduktan sonra, aşağıdaki <code>kubectl apply</code> komutunu kullanarak Pod'u çalıştırabiliriz:</p>
<pre><code class="lang-bash">kubectl apply -f mypod.yaml
</code></pre>
<p>Bu komut, Kubernetes API'sine dosyada tanımlı olan Pod'u oluşturmasını söyler. Pod'un başarıyla oluşturulup oluşturulmadığını doğrulamak için şu komutu kullanabilirsiniz:</p>
<pre><code class="lang-bash">kubectl get pods
</code></pre>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1723395269542/43383c49-b32d-4602-ac7a-37280d583270.png" alt class="image--center mx-auto" /></p>
<p>Ayrıca aşağıdaki komut ile pod hakkında daha fazla bilgi öğrenebiliriz.</p>
<pre><code class="lang-bash">kubectl describe pods mypod
</code></pre>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1723395502908/5fe8bf00-c8e4-448f-81c5-d5480b43338f.png" alt class="image--center mx-auto" /></p>
<h4 id="heading-pod-guncelleme">Pod Güncelleme</h4>
<p>Oluşturduğumuz Pod'u güncellemek için YAML dosyamızda değişiklikler yapabiliriz. Örneğin, container'ın kullandığı imajı değiştirelim ve bir adet etiket ekleyelim:</p>
<pre><code class="lang-yaml"><span class="hljs-attr">apiVersion:</span> <span class="hljs-string">v1</span>
<span class="hljs-attr">kind:</span> <span class="hljs-string">Pod</span>
<span class="hljs-attr">metadata:</span>
  <span class="hljs-attr">name:</span> <span class="hljs-string">mypod</span>
  <span class="hljs-attr">labels:</span>
    <span class="hljs-attr">app:</span> <span class="hljs-string">backend</span>
<span class="hljs-attr">spec:</span>
  <span class="hljs-attr">containers:</span>
    <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">mycontainer</span>
      <span class="hljs-attr">image:</span> <span class="hljs-string">nginx:alpine</span>
      <span class="hljs-attr">ports:</span>
        <span class="hljs-bullet">-</span> <span class="hljs-attr">containerPort:</span> <span class="hljs-number">80</span>
</code></pre>
<p>Değişiklikleri kaydettikten sonra, yine <code>kubectl apply</code> komutunu çalıştırarak Pod'u güncelleyebilirsiniz:</p>
<pre><code class="lang-bash">kubectl apply -f mypod.yaml
</code></pre>
<p>Bu komut, mevcut Pod'u günceller ve yeni yapılandırmaya göre yeniden oluşturur.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1723395632924/c40be2fb-5275-4960-8da2-127aeaf9576b.png" alt class="image--center mx-auto" /></p>
<p>Tekrardan aşağıdaki komutu çalıştıralım ve yaptığımız değişiklikleri görelim.</p>
<pre><code class="lang-bash">kubectl describe pods mypod
</code></pre>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1723395662814/7898209d-4b87-422c-896b-4027071eb11a.png" alt class="image--center mx-auto" /></p>
<h4 id="heading-pod-silme">Pod Silme</h4>
<p>Son olarak, oluşturduğumuz Pod'u silmek istersek, <code>kubectl delete</code> komutunu kullanabiliriz:</p>
<pre><code class="lang-bash">kubectl delete -f mypod.yaml
</code></pre>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1723395905696/eddceef3-cf28-4c9a-a420-7e07fb393e2b.png" alt class="image--center mx-auto" /></p>
<p>Bu komut, YAML dosyasında tanımlı olan Pod'u siler ve kümede bu isimde bir Pod kalmaz. Bunu görebilmek için tekrardan podları listeleyelim.</p>
<pre><code class="lang-bash">kubectl get pods
</code></pre>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1723395899485/bdaa34d9-d6c1-4906-8478-0318f340c92f.png" alt class="image--center mx-auto" /></p>
<p>Bu makalede, Kubernetes yapılandırma dosyalarının oluşturulması ve yönetilmesi konusunda JSON, YAML formatlarını ve kubectl komut satırı ele aldık. YAML'ın neden sektör standardı haline geldiğini ve Kubernetes dünyasında yapılandırma dosyalarının vazgeçilmez bir parçası olduğunu inceledik. Kubernetes YAML dosyalarının ana bölümlerini tanıtarak, Kubernetes objelerinin nasıl tanımlandığını ve yönetildiğini açıkladık. Son olarak, öğrendiklerimizi pekiştiren bir örnekle, YAML kullanarak Kubernetes'te Pod oluşturma sürecini adım adım uyguladık. Bu bilgilerin üzerine daha fazla araştırma yaparak, denemeler yaparak kubernetes konusunda kendinizi geliştirebilir ve uygulamalarınızı kubernetes ortamında çalıştırabilirsiniz. Umarım sana dokunmuş olabilirim. Merak ettiğin bir şey olursa benimle iletişime geçebilirsin.</p>
]]></content:encoded></item><item><title><![CDATA[Kubernetes Komut Satırı Aracı: kubectl]]></title><description><![CDATA[Önceki makalelerimizde Kubernetes'in geçmişi ve evriminden, ardından Kubernetes kümesinin ne olduğundan ve nasıl çalıştığından bahsetmiştik. Bu yazımızda, Kubernetes'in yönetimi ve etkileşimi için kullanılan temel araçlardan biri olan kubectl'i ele a...]]></description><link>https://serhatleventyavas.dev/kubernetes-komut-satiri-araci-kubectl</link><guid isPermaLink="true">https://serhatleventyavas.dev/kubernetes-komut-satiri-araci-kubectl</guid><category><![CDATA[kubectl-config]]></category><category><![CDATA[Kubernetes]]></category><category><![CDATA[kubectl]]></category><dc:creator><![CDATA[Serhat Levent Yavaş]]></dc:creator><pubDate>Sun, 04 Aug 2024 09:56:24 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1722759712134/c9b5fd9d-7b66-43ea-87b7-59d5f7359f92.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Önceki makalelerimizde <a target="_blank" href="https://serhatleventyavas.dev/kubernetesin-gecmisi-ve-evrimi">Kubernetes'in geçmişi ve evriminden</a>, ardından <a target="_blank" href="https://serhatleventyavas.dev/kubernetes-cluster-nedir-yeni-baslayanlar-icin-kilavuz">Kubernetes kümesinin ne olduğundan ve nasıl çalıştığından</a> bahsetmiştik. Bu yazımızda, Kubernetes'in yönetimi ve etkileşimi için kullanılan temel araçlardan biri olan <strong>kubectl</strong>'i ele alacağız. Kubernetes dünyasına adım atan herkesin öğrenmesi gereken bu güçlü komut satırı aracını daha yakından tanıyacağız.</p>
<h3 id="heading-kubectl-nedir">kubectl Nedir?</h3>
<p>kubectl, Kubernetes kümesi ile etkileşime geçmenizi sağlayan bir komut satırı aracıdır. Kubernetes'in <strong>api-server</strong> objesine komutlar göndererek kubernetes kümesindeki kaynakları yönetmenize olanak tanır. kubectl, Kubernetes'in çeşitli bileşenlerini ve kaynaklarını (örneğin, podlar, hizmetler, dağıtımlar) yaratmak, güncellemek, silmek ve görüntülemek için kullanılır.</p>
<blockquote>
<p>Eğer örnekleri sizde uygulamak isterseniz bir kubernetes kümesine ihtiyacınız olacak. Kendi bilgisayarınıza <a target="_blank" href="https://minikube.sigs.k8s.io/docs/start/"><strong>minikube</strong></a> kurarak hızlı bir şekilde bir kubernetes kümesine sahip olabilirsiniz.</p>
</blockquote>
<h3 id="heading-kubectl-kubernetes-cluster-ile-nasil-iletisim-kurar">kubectl Kubernetes Cluster İle Nasıl İletişim Kurar?</h3>
<p>kubectl, kubernetes kümesi ile iletişime geçebilmesi için bir takım bilgilere ihtiyaç duyar. Bunlar;</p>
<ol>
<li><p>Kullanıcı kimlik bilgileri (users)</p>
</li>
<li><p>Küme bilgileri (clusters)</p>
</li>
<li><p>Bağlam bilgileri (context)</p>
</li>
</ol>
<p>Bu bilgileri doğru bir şekilde kubectl tarafına verdiğimiz zaman kubernetes kümesine başarılı bir şekilde bağlanabilir ve kubernetes api-server objesi ile sağlıklı bir şekilde iletişim kurabiliriz. Kubectl bu verileri okumak için varsayılan olarak bilgisayarımızdaki ana dizin altındaki <strong>.kube klasörü</strong> içerisindeki <strong>config</strong> dosyasına bakar.</p>
<p>Örneğin;</p>
<ul>
<li><p>macos: <code>~/.kube/config</code></p>
</li>
<li><p>linux: <code>~/.kube/config</code></p>
</li>
<li><p>windows: <code>"C:\Users\&lt;KullanıcıAdı&gt;\.kube\config"</code></p>
</li>
</ul>
<p>Örneğin ben kendi makinemdeki config dosyasını vscode editörü ile açtığımda aşağıdaki yaml tanımını görüyorum. docker-desktop ve minikube adında iki farklı kümeye, docker-desktop ve minikube adında iki farklı kullanıcıya sahibim. Son olarak bu kullanıcıları ve küme bilgilerini birleştiren context bilgilerimi görüyorum. Contextler sayesinde hangi kümeye hangi kullanıcı ile bağlanacağımızı belirleriz.</p>
<pre><code class="lang-yaml"><span class="hljs-attr">apiVersion:</span> <span class="hljs-string">v1</span>
<span class="hljs-attr">clusters:</span>
<span class="hljs-bullet">-</span> <span class="hljs-attr">cluster:</span>
    <span class="hljs-attr">certificate-authority-data:</span> <span class="hljs-string">LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSURCVENDQWUyZ0F3SUJBZ0lJSXFzd1Q3eUN0bFl3RFFZSktvWklodmNOQVFFTEJRQXdGVEVUTUJFR0ExVUUKQXhNS2EzVmlaWEp1WlhSbGN6QWVGdzB5TkRBM01USXdOVFV3TlRCYUZ3MHpOREEzTVRBd05UVTFOVEJhTUJVeApFekFSQmdOVkJBTVRDbXQxWW1WeWJtVjBaWE13Z2dFaU1BMEdDU3FHU0liM0RRRUJBUVVBQTRJQkR3QXdnZ0VLCkFvSUJBUUM2SkdXeFFONHNLZWpkNWNmcnhsOEJhNytnUGxiZkpZRmRVbTFjZmE5cmlRd1UxazJyaFpMaDJuN0MKOS9zcXpzckx5dkhwaktSby85R3VoZmJFVEVObkJYTU1GT3BmOG96Vnd3SzlyMzRWa1p1QTVEY213d3VxOW1lWApRZkhwajJicE12SS9iYno3V1lzVkJ5TzNOL2xGWGMxN3ppQTJzeWRNT0NRZjRjZHlycWc1QnRGejlQb2FaRVZtCk84Q2tnK0NZMVVBTm9jWHNEVE5LTjFDbU5GejFIa3NjTElvR0hRQmJVbCtqT1FNNndoQ0Ewd2FmU0w0ZDJPSHEKK1QzL3BIWEJRUmhuNHV5VmZBcDBxZ1hhVE5LMWtLTGZQOCs1aDVPVDh0OFF4T1NsVWozb1VLOEZBTGFUL05mUApzejZlL08rendLWVU2UjJHK2dSM2ViZG5uWC8xQWdNQkFBR2pXVEJYTUE0R0ExVWREd0VCL3dRRUF3SUNwREFQCkJnTlZIUk1CQWY4RUJUQURBUUgvTUIwR0ExVWREZ1FXQkJURDhkMFJTRkRuMEJNWmpBOFJsZlFvcmthV2h6QVYKQmdOVkhSRUVEakFNZ2dwcmRXSmxjbTVsZEdWek1BMEdDU3FHU0liM0RRRUJDd1VBQTRJQkFRQ1RoS1V2SUlQZgpITU9RNi9iQ2pjUk40RlF4M0p6My9CQXFwRkhiaTBTMHcvcEllNW9FMDdLUnJWdDZnSVp0UGtMNUhrNmxrUnovCjNtQ3Z6TysxMDBKaW1RWjNqVzVtUElUS0czcWhjNDdhMHl5c1k2TXNabG1OR2lBbEtiSUNCc0xpbFpCaXFGMFAKQkYzRUlrVnY1dVZzV05VTjJYWWo4QkwvczhCYjRWUXJFeCtFYTZwc1UxdkROa2ZDQjFkakRnb0hVaXBra0JVZQo2REV2M1dINHNsS0x0QTRGSEZ2OXNwckR4MUx6cmJvRzUraExvUUphZS8zTE1mMlRTL25tVFlZMFVScEZXbmxaCjhlSlh2NDZQQ2NRT0pYYTRtS08rZkxIM2piSkZpSHk2MkgrNnp3Zko2eW1NRVhoQmgrUjgySjJaQ0xUL2FRTXkKZEwzeFJ4d1NGZFh4Ci0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K</span>
    <span class="hljs-attr">server:</span> <span class="hljs-string">https://kubernetes.docker.internal:6443</span>
  <span class="hljs-attr">name:</span> <span class="hljs-string">docker-desktop</span>
<span class="hljs-bullet">-</span> <span class="hljs-attr">cluster:</span>
    <span class="hljs-attr">certificate-authority:</span> <span class="hljs-string">C:\Users\serha\.minikube\ca.crt</span>
    <span class="hljs-attr">extensions:</span>
    <span class="hljs-bullet">-</span> <span class="hljs-attr">extension:</span>
        <span class="hljs-attr">last-update:</span> <span class="hljs-string">Sun,</span> <span class="hljs-number">02</span> <span class="hljs-string">Aug</span> <span class="hljs-number">2024 09:08:25 +03</span>
        <span class="hljs-attr">provider:</span> <span class="hljs-string">minikube.sigs.k8s.io</span>
        <span class="hljs-attr">version:</span> <span class="hljs-string">v1.33.1</span>
      <span class="hljs-attr">name:</span> <span class="hljs-string">cluster_info</span>
    <span class="hljs-attr">server:</span> <span class="hljs-string">https://127.0.0.1:57415</span>
  <span class="hljs-attr">name:</span> <span class="hljs-string">minikube</span>
<span class="hljs-attr">contexts:</span>
<span class="hljs-bullet">-</span> <span class="hljs-attr">context:</span>
    <span class="hljs-attr">cluster:</span> <span class="hljs-string">docker-desktop</span>
    <span class="hljs-attr">user:</span> <span class="hljs-string">docker-desktop</span>
  <span class="hljs-attr">name:</span> <span class="hljs-string">docker-desktop</span>
<span class="hljs-bullet">-</span> <span class="hljs-attr">context:</span>
    <span class="hljs-attr">cluster:</span> <span class="hljs-string">minikube</span>
    <span class="hljs-attr">extensions:</span>
    <span class="hljs-bullet">-</span> <span class="hljs-attr">extension:</span>
        <span class="hljs-attr">last-update:</span> <span class="hljs-string">Sun,</span> <span class="hljs-number">02</span> <span class="hljs-string">Aug</span> <span class="hljs-number">2024 09:08:25 +03</span>
        <span class="hljs-attr">provider:</span> <span class="hljs-string">minikube.sigs.k8s.io</span>
        <span class="hljs-attr">version:</span> <span class="hljs-string">v1.33.1</span>
      <span class="hljs-attr">name:</span> <span class="hljs-string">context_info</span>
    <span class="hljs-attr">namespace:</span> <span class="hljs-string">default</span>
    <span class="hljs-attr">user:</span> <span class="hljs-string">minikube</span>
  <span class="hljs-attr">name:</span> <span class="hljs-string">minikube</span>
<span class="hljs-attr">current-context:</span> <span class="hljs-string">minikube</span>
<span class="hljs-attr">kind:</span> <span class="hljs-string">Config</span>
<span class="hljs-attr">preferences:</span> {}
<span class="hljs-attr">users:</span>
<span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">docker-desktop</span>
  <span class="hljs-attr">user:</span>
    <span class="hljs-attr">client-certificate-data:</span> <span class="hljs-string">LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSURRakNDQWlxZ0F3SUJBZ0lJT2Z4WHdqOWNtUjh3RFFZSktvWklodmNOQVFFTEJRQXdGVEVUTUJFR0ExVUUKQXhNS2EzVmlaWEp1WlhSbGN6QWVGdzB5TkRBM01USXdOVFV3TlRCYUZ3MHlOVEEzTVRJd05UVTFOVEZhTURZeApGekFWQmdOVkJBb1REbk41YzNSbGJUcHRZWE4wWlhKek1Sc3dHUVlEVlFRREV4SmtiMk5yWlhJdFptOXlMV1JsCmMydDBiM0F3Z2dFaU1BMEdDU3FHU0liM0RRRUJBUVVBQTRJQkR3QXdnZ0VLQW9JQkFRQytvVitqazFhWklZTloKTVNDVUsrckx1UEZYVzZOWnNZWG12aG1yc3NhOXUyNFpLelhVcWxxOEdLZHlHNnhJZ2c5WW41L1cwdkN6eVAzcwpsWXpnR2tNYlZiYm0wZGh4UytUVUFiVWZCcjgybG1DbS80Y0MyVHJBODVIUHNsSUFhdWJsTlM1L05GVzMwMEZkCjhWaFN3dmhVdjZTU281dm15Z2RxMzZpdVk3S01ZeGlHUHVEQUZpSlhybjR0bFh0MEs4QW85ZEV5bGJhS0h0MFoKVmFJOWcyUjBCcXZXK21pQTlyeTVNbjhsNlA2S0hLRTAyVmxucGVzeDlpWjB6VjhuZ0pKOC9aTlh5TXVJdW9ZUwo3NDgyVUNucVpRVktteDNPZEdGYkVKZDNjWG5WM0Y1K21ZTFhMNXhMRk1odUw2TFhkaDdMVExRa1lYNk5LQmtGCmlFVTM2ZFdiQWdNQkFBR2pkVEJ6TUE0R0ExVWREd0VCL3dRRUF3SUZvREFUQmdOVkhTVUVEREFLQmdnckJnRUYKQlFjREFqQU1CZ05WSFJNQkFmOEVBakFBTUI4R0ExVWRJd1FZTUJhQUZNUHgzUkZJVU9mUUV4bU1EeEdWOUNpdQpScGFITUIwR0ExVWRFUVFXTUJTQ0VtUnZZMnRsY2kxbWIzSXRaR1Z6YTNSdmNEQU5CZ2txaGtpRzl3MEJBUXNGCkFBT0NBUUVBUXk1Wmd2dHhYNDBaSGNaNDR0L3ZvbEhGQWk2eG8vdVVhUmFSN1IzeUpMVjNTaXp3Sk1ZRmxrNFcKdDAwRkMzL3VsWS9NdTkrTlE1MnpXU0s0R0NwaFllOHpMdFNnUGM4WU41QnFrTDlWYnYwNTJtMjBYWHBjaHJ2RAo1cDBjMklIWFhMSkJ6SG0zMDNoQkN1N2w3MVVsSUFTVExkcTBzK3ZrMkFhVmVTVjkwbG56bFdXbi92OUtjaFZ6CmJPWFo2T3JvZDB0MXNmczNCZlRoMmZsd09vNkdadCtwN0J0TTJIT0Jta0ZOYUNYTFFuYkNRYXpkQU5rbWMyWEwKUkxxSVVibVdGeWwrcjFZaG1Ibk50NHRqRlBldzF1L1piWUFnd1J1am5hQUdKM08ya1Q0VkFrNUtNSHNyZFZHNApyL0lINC8rN1ZGNk8rSnFoQlBXVXVIcTlwVkpHNFE9PQotLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0tCg==</span>
    <span class="hljs-attr">client-key-data:</span> <span class="hljs-string">LS0tLS1CRUdJTiBSU0EgUFJJVkFURSBLRVktLS0tLQpNSUlFcEFJQkFBS0NBUUVBdnFGZm81TldtU0dEV1RFZ2xDdnF5N2p4VjF1aldiR0Y1cjRacTdMR3ZidHVHU3MxCjFLcGF2QmluY2h1c1NJSVBXSitmMXRMd3M4ajk3SldNNEJwREcxVzI1dEhZY1V2azFBRzFId2EvTnBaZ3B2K0gKQXRrNndQT1J6N0pTQUdybTVUVXVmelJWdDlOQlhmRllVc0w0Vkwra2txT2I1c29IYXQrb3JtT3lqR01ZaGo3Zwp3QllpVjY1K0xaVjdkQ3ZBS1BYUk1wVzJpaDdkR1ZXaVBZTmtkQWFyMXZwb2dQYTh1VEovSmVqK2loeWhOTmxaClo2WHJNZlltZE0xZko0Q1NmUDJUVjhqTGlMcUdFdStQTmxBcDZtVUZTcHNkem5SaFd4Q1hkM0Y1MWR4ZWZwbUMKMXkrY1N4VEliaStpMTNZZXkweTBKR0YralNnWkJZaEZOK25WbXdJREFRQUJBb0lCQVFDa1EvaGV3d0szVjVxUQppL1hQMkh3dDZvTUV6UEZZdzlGbmdONHNCeFNjdjlyaWswcUNvLzBsNG5TL3JqcnFERERmSkVXZTN3d05VQ0FHCjh1Tis1UUo0bG9iU0pYZEdRYWpBUzJ4Z2ZQYmVPZnkxU3JGemNlN2YvOExnMzM4cjN2SnlCajYzM0VnVTdGU0MKZUFxczNsY1E2RWNQR1M2cFUrUEtZMG8rWTZpaXNPN0V3MEt2QmlFVkd0Y3NwbzAxSGhERXlIYUIvRWNMaVR4RQprY29ndmUvbHFMRldZN0w0MU9IY0srUDRXclN5M0pRVXpNUjVYV0VwRkU4Ymk2UHZiRlVZTEpVTVNueitOVmxoCnU5bVVZMks1Zzg1c003dkxKYnppVUp5eStRZHFBS09MSEhSNmwzRmxBWnJpckQrVkx4elVHRzZWNG1FUFdtN28KekttUzNtZUJBb0dCQU1iTGhoQVhkZVNlZ0JRVi83YkJYRm1BYTdPOHFBN0trNkNqSWh0NkhRekJvY0pncWc2eApvQTZidmdmd3hmSGJpeEMyT1Jib0VBdy8rdlNlVlJUZTRwb2dCeDU4TlkzeS9PU1ZWUXdwTlZZbmJtSlBuU2lPCmdXUk9rbitNZ1IyeTJtaURvZnRyc2V2a3lRa3E4OURWbytieGgranB6MmljbTFqdGh1ZXpkUGxiQW9HQkFQVjgKWkNFa3Iycm1TbnBTdFIyYnd5OUpaKzhUVm9yOGZaMXZmeHF6Szd3NGx6QmtUdDNwaW5JSmdFNDZGRnBVL3o1MwpPNit3QVNyOGtHRWtTWEp5YmZZOVJMcXNWVGVFTkh1SG5jQnU4RzJnT0JLaTI2YTJ0TG5FaC8rTyt4b0l5L3QzCmNVRTB0Q3hGSjNObERnalRETE04dkN4YjQ2NjJVdXlvYzRSaS9RakJBb0dBUDU3M0FzTmZXWkZZVUJWU1J6ek8KdjE0WUdlZXdxVHN2eitNbGtVR2RkbTJweFRtR2N6bHBqZ05ONStDb21PUzROdHI2bmxnYWVyRW5NWTVTa0dGYwppQkxqOUYrd0RBUE41NkhiSEE4OElKeHgrVWlkZFZOV0diSUR0SXBVOEJwRFI5dUl4WndMendEalRlblBLZkNWCmlkMldyM1hVaVJoRnAwb3RPSTM0UzYwQ2dZQUdvRkh2bHhicEVzaEYzdittaWZMTnp2UndQcHhpYWdoVi9KRjQKdmdkYk1FZmNkWWl2Y3NOYTZxaTg4OUppMGRLRjlCLzNVUS9uQWlRL2l3UTBnNlEyTmxjcGxzZENGVjU1U3lMVgo4K2luZk9DbW1DREhzanpVbXRwMDZuNGFxTXdnd0l1ZEQvZ2hEY2pQMDVWNlpYLzlRcEZ0dlJrN09RNnA5cTRQCmo4QjJ3UUtCZ1FDNWlWbGdrdWZFUGowazhiMkVZU01VcXlMR1RlaXRrZmtJYkQ3Q1BZejZva0p4eUxLWVY1UncKYi9JZDd0RndiREZPemc3Vk1zMnBhajd6UmF2dnd6bHc5NWViUTUzT094aG9NbWxPTmtUVTRhMk9vS1ZTdW9CYQo5S2VyanRjZnlNMVRWelJJZVlrWWFST1QxbW1KUFNtclkwUnovWWVUUlRQVG9GcVVscjdqamc9PQotLS0tLUVORCBSU0EgUFJJVkFURSBLRVktLS0tLQo=</span>
<span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">minikube</span>
  <span class="hljs-attr">user:</span>
    <span class="hljs-attr">client-certificate:</span> <span class="hljs-string">C:\Users\serha\.minikube\profiles\minikube\client.crt</span>
    <span class="hljs-attr">client-key:</span> <span class="hljs-string">C:\Users\serha\.minikube\profiles\minikube\client.key</span>
</code></pre>
<p>Ayrıca gördüğünüz üzere bir config dosyası üzerinde birden fazla küme, kullanıcı ve context bilgisi bulunabilir ve biz kubectl üzerinden contextler arasında geçişler yapabiliriz.</p>
<h3 id="heading-kubectl-config-komutlari">kubectl config Komutları</h3>
<ol>
<li>Contextleri listeleme için aşağıdaki komutu yazarız.</li>
</ol>
<pre><code class="lang-bash">kubectl config get-contexts
</code></pre>
<p>Bu komut bize aşağıdaki gibi bir sonuç üretir.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1722763800296/b0fdee1a-b1fb-4933-925e-f826e18261c0.png" alt /></p>
<p>Bu tablodaki kolonlardan kısaca bahsedelim.</p>
<ul>
<li><p><strong>CURRENT:</strong> Config içerisine birden fazla context tanımlanıyor olsada biz anlık olarak sadece bir context üzerinde çalışabiliriz. kubectl'e verdiğimiz tüm komutlar current olarak işaretlenmiş context üzerinde çalıştırılıyor. Yukarıdaki tabloda benim current context'im minikube isimle context. Yazdığım tüm komutlarda minikube context'in bağlandığı kubernetes kümesi üzerinde çalıştırılacak. Tablodaki current kolonu yıldız ile işaretlenen context, current context'dir.</p>
</li>
<li><p><strong>NAME:</strong> Context adı</p>
</li>
<li><p><strong>CLUSTER:</strong> Context'in bağlandığı kubernetes cluster adı</p>
</li>
<li><p><strong>AUTHINFO:</strong> Context'in kullandığı kullanıcı adı</p>
</li>
<li><p><strong>NAMESPACE:</strong> Context'in kullanıdığı namespace bilgisi</p>
</li>
</ul>
<ol start="2">
<li>Aktif context bilgisini alabilmek için aşağıdaki komutu yazarız.</li>
</ol>
<pre><code class="lang-bash">kubectl config current-context
</code></pre>
<p>Bu sonuç olarak sadece aktif context'in adını yazar.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1722764768315/0d415502-4e77-463e-b209-168b9c2d5ad4.png" alt /></p>
<ol start="3">
<li>Eğer aktif context'den başka bir context geçiş yapmak istiyorsak aşağıdaki komutu yazarız.</li>
</ol>
<pre><code class="lang-bash">kubectl config use-context docker-desktop
</code></pre>
<p>Bu komut çalıştıktan sonra aktif context değerimiz docker-desktop isimli context olur.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1722764886830/c075e8e4-ca81-42ec-b96a-dff7e36ce738.png" alt /></p>
<h3 id="heading-kubectl-yapisi">kubectl Yapısı</h3>
<p>kubectl komutları belirli bir yapıya sahiptir. Temel bir kubectl komutu şu şekilde yapılandırılmıştır:</p>
<pre><code class="lang-bash">kubectl [komut] [kaynak tipi] [kaynak adı] [flags]
</code></pre>
<h4 id="heading-1-komut">1. Komut</h4>
<p>kubectl'de kullanılabilen temel komutlar şunlardır:</p>
<ul>
<li><p><code>create</code>: Yeni bir kaynak oluşturur.</p>
</li>
<li><p><code>get</code>: Mevcut kaynakları listeler.</p>
</li>
<li><p><code>describe</code>: Bir kaynağın detaylarını gösterir.</p>
</li>
<li><p><code>apply</code>: Bir YAML dosyasındaki kaynak tanımlarını uygular veya günceller.</p>
</li>
<li><p><code>delete</code>: Bir kaynağı siler.</p>
</li>
</ul>
<p>Örnek:</p>
<pre><code class="lang-bash">kubectl get pods
</code></pre>
<p>Bu komut, mevcut tüm podları listeler.</p>
<h4 id="heading-2-kaynak-tipi">2. Kaynak Tipi</h4>
<p>kubectl komutlarında kullanılan kaynak tipleri, Kubernetes'in temel yapı taşlarını oluşturur. Aşağıda yaygın olarak kullanılan bazı kaynak tiplerini ve kısa açıklamalarını bulabilirsiniz:</p>
<h4 id="heading-21-pod">2.1. Pod</h4>
<p><a target="_blank" href="https://kubernetes.io/docs/concepts/workloads/pods/">Podlar</a>, Kubernetes'in en küçük dağıtım birimidir. Bir veya daha fazla konteyneri barındırır.</p>
<p>Örnek:</p>
<pre><code class="lang-bash">kubectl get pods
</code></pre>
<h4 id="heading-22-deployments">2.2. Deployments</h4>
<p><a target="_blank" href="https://kubernetes.io/docs/concepts/workloads/controllers/deployment/">Deployments</a>, belirli bir pod setinin ve bu podların sürümlerinin yönetimini sağlar.</p>
<p>Örnek:</p>
<pre><code class="lang-bash">kubectl get deployments
</code></pre>
<h4 id="heading-23-services">2.3. Services</h4>
<p><a target="_blank" href="https://kubernetes.io/docs/concepts/services-networking/service/">Services</a>, Kubernetes içindeki podların ağ üzerinde erişilebilir olmasını sağlar. Yük dengeleme (load balancer) ve hizmet keşfi (service discovery) sunar.</p>
<p>Örnek:</p>
<pre><code class="lang-bash">kubectl get services
</code></pre>
<h4 id="heading-24-configmaps">2.4. ConfigMaps</h4>
<p><a target="_blank" href="https://kubernetes.io/docs/concepts/configuration/configmap/">ConfigMap</a>, yapılandırma verilerini key-value (anahtar-değer) çiftleri olarak saklar ve podlar tarafından kullanılabilir.</p>
<p>Örnek:</p>
<pre><code class="lang-bash">kubectl get configmaps
</code></pre>
<h4 id="heading-25-secrets">2.5. Secrets</h4>
<p><a target="_blank" href="https://kubernetes.io/docs/concepts/configuration/secret/">Secret</a>, duyarlı verileri (örneğin, parolalar, tokenlar) güvenli bir şekilde saklar ve podlar tarafından kullanılabilir.</p>
<p>Örnek:</p>
<pre><code class="lang-bash">kubectl get secrets
</code></pre>
<h4 id="heading-26-replicaset">2.6. ReplicaSet</h4>
<p><a target="_blank" href="https://kubernetes.io/docs/concepts/workloads/controllers/replicaset/">ReplicaSet</a>, belirli bir pod sayısının her zaman çalışır durumda olmasını sağlar.</p>
<p>Örnek:</p>
<pre><code class="lang-bash">kubectl get replicasets
</code></pre>
<h4 id="heading-27-namespace-namespaces">2.7. Namespace (namespaces)</h4>
<p><a target="_blank" href="https://kubernetes.io/docs/concepts/overview/working-with-objects/namespaces/">Namespace</a>, kaynakları mantıksal olarak izole etmek için kullanılır. Büyük kümelerde kaynakları organize etmek için yararlıdır.</p>
<p>Örnek:</p>
<pre><code class="lang-bash">kubectl get namespaces
</code></pre>
<h4 id="heading-28-ingress-ingresses">2.8. Ingress (ingresses)</h4>
<p><a target="_blank" href="https://kubernetes.io/docs/concepts/services-networking/ingress/">Ingress</a>, HTTP ve HTTPS yönlendirmeleri için kuralları tanımlar. Dış trafiğin cluster içindeki hizmetlere nasıl yönlendirileceğini belirler.</p>
<p>Örnek:</p>
<pre><code class="lang-bash">kubectl get ingresses
</code></pre>
<h4 id="heading-29-node-nodes">2.9. Node (nodes)</h4>
<p><a target="_blank" href="https://kubernetes.io/docs/concepts/architecture/nodes/">Node</a>, Kubernetes kümesindeki fiziksel veya sanal makineleri temsil eder.</p>
<p>Örnek:</p>
<pre><code class="lang-bash">kubectl get nodes
</code></pre>
<h4 id="heading-210-persistentvolume">2.10. PersistentVolume</h4>
<p><a target="_blank" href="https://kubernetes.io/docs/concepts/storage/persistent-volumes/">PersistentVolume</a> (PV), kümede depolama kaynaklarını temsil eder. Podlar için kalıcı depolama sağlar.</p>
<p>Örnek:</p>
<pre><code class="lang-bash">kubectl get persistentvolumes
</code></pre>
<h4 id="heading-211-persistentvolumeclaim-persistentvolumeclaims">2.11. PersistentVolumeClaim (persistentvolumeclaims)</h4>
<p><a target="_blank" href="https://kubernetes.io/docs/concepts/storage/persistent-volumes/">PersistentVolumeClaim</a> (PVC), podlar tarafından talep edilen kalıcı depolama kaynaklarını tanımlar.</p>
<p>Örnek:</p>
<pre><code class="lang-bash">kubectl get persistentvolumeclaims
</code></pre>
<h4 id="heading-212-statefulset">2.12. StatefulSet</h4>
<p><a target="_blank" href="https://kubernetes.io/docs/concepts/workloads/controllers/statefulset/">StatefulSet</a>, durum bilgisi olan uygulamaları yönetmek için kullanılır. Her bir podun kimliğini korur.</p>
<p>Örnek:</p>
<pre><code class="lang-bash">kubectl get statefulsets
</code></pre>
<h4 id="heading-213-job">2.13. Job</h4>
<p><a target="_blank" href="https://kubernetes.io/docs/concepts/workloads/controllers/job/">Job</a>, belirli sayıda podu çalıştıran ve tamamlandıklarında sona eren geçici görevler için kullanılır.</p>
<p>Örnek:</p>
<pre><code class="lang-bash">kubectl get <span class="hljs-built_in">jobs</span>
</code></pre>
<h4 id="heading-214-cronjob">2.14. CronJob</h4>
<p><a target="_blank" href="https://kubernetes.io/docs/concepts/workloads/controllers/cron-jobs/">CronJob</a>, belirli zaman aralıklarında yinelenen görevleri (job'ları) çalıştırmak için kullanılır.</p>
<p>Örnek:</p>
<pre><code class="lang-bash">kubectl get cronjobs
</code></pre>
<h4 id="heading-3-kaynak-adi">3. Kaynak Adı</h4>
<p>Komutun hedef aldığı spesifik kaynağın adıdır. Bu, belirli bir pod, deployment, service veya başka bir kaynağın adını belirtir.</p>
<p>Örnek:</p>
<pre><code class="lang-bash">kubectl delete pod mypod
</code></pre>
<p>Bu komut, "mypod" adlı podu siler.</p>
<h4 id="heading-4-bayraklar-flag">4. Bayraklar (Flag)</h4>
<p>Bayraklar, komutun davranışını değiştirmek veya ek seçenekler eklemek için kullanılır. Örneğin, komuta <code>-o</code> eklediğimiz çıktıyı belirli bir formatta görüntülemek için kullanılır (json, yaml, wide vb.).</p>
<p>Örnek:</p>
<pre><code class="lang-bash">kubectl get pods -o wide
</code></pre>
<p>Bu komut, podları daha geniş bir formatta listeler, daha fazla bilgi içerir.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1722765219248/dd08a336-52dd-4ec9-ba6a-6a2daac7627b.png" alt /></p>
<h3 id="heading-kubectl-kullanim-ornekleri">kubectl Kullanım Örnekleri</h3>
<p>kubectl'in gücünü ve esnekliğini göstermek için bazı kullanım örneklerine bakalım.</p>
<h4 id="heading-bir-pod-olusturma">Bir Pod Oluşturma</h4>
<p>Bir pod oluşturmak için <code>run</code> komutunu kullanabilirsiniz:</p>
<pre><code class="lang-bash">kubectl run nginxpod --image=nginx
</code></pre>
<p>Bu komut, nginx imajını kullanan bir pod oluşturur.</p>
<h4 id="heading-podlari-listeleme">Podları Listeleme</h4>
<p>Kümedeki tüm podları listelemek için <code>get</code> komutunu kullanabilirsiniz:</p>
<pre><code class="lang-bash">kubectl get pods
</code></pre>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1722765265767/fd0a5524-0d2a-4988-8c88-d9784458b764.png" alt class="image--center mx-auto" /></p>
<h4 id="heading-pod-detaylarini-goruntuleme">Pod Detaylarını Görüntüleme</h4>
<p>Bir podun detaylarını görüntülemek için <code>describe</code> komutunu kullanabilirsiniz:</p>
<pre><code class="lang-bash">kubectl describe pod nginxpod
</code></pre>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1722765296185/023dff87-6487-4999-a938-110928070b78.png" alt class="image--center mx-auto" /></p>
<h4 id="heading-bir-yaml-dosyasini-uygulama">Bir YAML Dosyasını Uygulama</h4>
<p>Bir YAML dosyasındaki tanımları uygulamak için <code>apply</code> komutunu kullanabilirsiniz:</p>
<pre><code class="lang-bash">kubectl apply -f deployment.yaml
</code></pre>
<h4 id="heading-bir-kaynagi-silme">Bir Kaynağı Silme</h4>
<p>Bir kaynağı silmek için <code>delete</code> komutunu kullanabilirsiniz:</p>
<pre><code class="lang-bash">kubectl delete pod nginx-pod
</code></pre>
<p>Özetlemek gerekirse kubectl, Kubernetes kümesi ile etkileşime geçmek için temel bir araçtır. Komut yapısı ve esnekliği sayesinde Kubernetes kaynaklarını yönetmek oldukça kolay ve etkilidir. Bu makalede kubectl'in temel yapısını ve bazı yaygın kullanım örneklerini ele aldık. Bu bilgilerin üzerine daha fazla araştırma yaparak, denemeler yaparak kubernetes konusunda kendinizi geliştirebilir ve uygulamalarınızı kubernetes ortamında çalıştırabilirsiniz. Umarım sana dokunmuş olabilirim. Merak ettiğin bir şey olursa benimle iletişime geçebilirsin.</p>
]]></content:encoded></item><item><title><![CDATA[Kubernetes Cluster Nedir? Yeni Başlayanlar İçin Kılavuz]]></title><description><![CDATA[Bir önceki yazımda sizlere Kubernetes'in geçmişi ve evriminden bahsetmiştim. Bu yazıda ise Kubernetes dünyasına bir adım atacağız ve bir Kubernetes cluster'ının temel bileşenlerini keşfetmeye başlayacağız. Yeni başlayanlar için hazırladığım bu kılavu...]]></description><link>https://serhatleventyavas.dev/kubernetes-cluster-nedir-yeni-baslayanlar-icin-kilavuz</link><guid isPermaLink="true">https://serhatleventyavas.dev/kubernetes-cluster-nedir-yeni-baslayanlar-icin-kilavuz</guid><category><![CDATA[Kubernetes]]></category><category><![CDATA[#kubernetes components]]></category><category><![CDATA[kubernetes architecture]]></category><category><![CDATA[#kubernetes #container ]]></category><dc:creator><![CDATA[Serhat Levent Yavaş]]></dc:creator><pubDate>Sun, 21 Jul 2024 17:28:41 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1721572130476/d9cac737-3916-460b-8ba9-4ecaf83fedeb.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Bir önceki yazımda sizlere <a target="_blank" href="https://serhatleventyavas.dev/kubernetesin-gecmisi-ve-evrimi">Kubernetes'in geçmişi ve evriminden</a> bahsetmiştim. Bu yazıda ise Kubernetes dünyasına bir adım atacağız ve bir Kubernetes cluster'ının temel bileşenlerini keşfetmeye başlayacağız. Yeni başlayanlar için hazırladığım bu kılavuzda, Kubernetes cluster'ının ne olduğunu ve nasıl çalıştığını basit bir dille anlatacağım.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1721569472068/db2f0348-d00d-4409-aaef-7b1fc4dcb2c2.png" alt class="image--center mx-auto" /></p>
<p>Yukarıda gördüğünüz diagram bir Kubernetes cluster diagramıdır. Basit bir şekilde anlatmak gerekirse, Kubernetes kurduğumuzda bir cluster'a sahip oluruz. Bir cluster, bir grup makineden oluşur. Cluster içerisinde bir veya daha fazla worker makineler bulunur ve biz bu worker makinelerine <strong>Node</strong> adını veririz. Node'lar, uygulama iş yüklerimizi çalıştırır ve yönetir.</p>
<p>Cluster ayrıca bir veya daha fazla <strong>Master Node</strong> içerir. <strong>Master Node</strong>'lar, Kubernetes'in <strong>Control Plane</strong> bileşenlerini barındırır. Control Plane, cluster'ın yönetim ve kontrol işlevlerini yerine getiren bileşenlerin tümüdür ve şunları içerir:</p>
<ul>
<li><p><strong>API Server</strong>: Tüm API isteklerini karşılar ve yönetir. Bu bileşen sayesinde biz istediğimiz işlemleri api üzerinden istek atarak kubernetes'e iletiriz. Kubernetes api server ana uygulaması <strong>kube-apiserver</strong>’dır. Yatay ölçeklenebilir.</p>
</li>
<li><p><strong>etcd</strong>: Cluster'ın tüm yapılandırma ve durum bilgilerini saklayan dağıtılmış anahtar-değer deposudur. Basitleştirmek gerekirse Kubernetes'in veritabanıdır.</p>
</li>
<li><p><strong>Controller Manager</strong>: Cluster'ın durumunu kontrol eden ve yönetim işlemlerini gerçekleştiren <strong>Node Controller</strong>, <strong>Job Controller</strong>, <strong>EndpointSlice Controller</strong> gibi bir çok controller çalıştırır. Her controller'ın ayrı bir görevi vardır.</p>
</li>
<li><p><strong>Scheduler</strong>: Yeni oluşturulan Pod'ları uygun Node'lara yerleştirir. Uygun node seçimini yaparken dikkate aldığı bir kaç faktör vardır.</p>
<ol>
<li><p><strong>Kaynak Gereksinimleri:</strong> Pod'un CPU ve bellek gibi bireysel ve toplu kaynak ihtiyaçları, Node'un mevcut kaynaklarıyla karşılaştırılır.</p>
</li>
<li><p><strong>Donanım/Yazılım/Politika Kısıtlamaları:</strong> Pod'un belirli donanım veya yazılım gereksinimlerine ve organizasyon politikalarına uygun olup olmadığı kontrol edilir.</p>
</li>
<li><p><strong>Affinity ve Anti-Affinity Şartları:</strong> Pod'un belirli Node'lara yakın veya uzak olmasını sağlayan kurallar dikkate alınır.</p>
</li>
<li><p><strong>Veri Yerelleştirme:</strong> Pod'un kullanacağı veri kaynaklarının bulunduğu yer de dikkate alınır. Veri kaynaklarına yakın olmak, veri erişim hızını artırabilir.</p>
</li>
<li><p><strong>İş Yükleri Arası Etkileşim:</strong> Pod'ların diğer iş yükleriyle olan etkileşimleri göz önünde bulundurulur, bu etkileşimler performans ve güvenilirlik açısından önemlidir.</p>
</li>
<li><p><strong>Son Teslim Tarihleri:</strong> Pod'un belirli bir zaman diliminde çalışması veya tamamlanması gerekiyorsa, bu da yerleşim kararlarını etkileyebilir.</p>
</li>
</ol>
</li>
<li><p><strong>Cloud Controller Manager</strong>: Cloud Provider ile etkileşimi sağlar ve cloud kaynaklarının yönetimini gerçekleştirir. Örneğin, load balancer oluşturma, Node'ların sağlıklı olup olmadığını kontrol etme gibi işlemleri yapar. Bu bileşen, AWS EKS (Elastic Kubernetes Service), Google Kubernetes Engine (GKE), Microsoft Azure Kubernetes Service (AKS) gibi popüler cloud sağlayıcılar ile entegrasyonu kolaylaştırır. Cloud Controller Manager, bu hizmetler aracılığıyla otomatik ölçeklendirme, yük dengeleme ve cloud ortamında kaynak yönetimi gibi kritik görevleri yerine getirir.</p>
</li>
</ul>
<p>Worker Node'lar, uygulamalarınızı çalıştıran birimlerdir ve her biri şu bileşenleri içerir:</p>
<ul>
<li><p><strong>kubelet</strong>: Her Node'da çalışan ve Pod'ların durumunu API Server'a raporlayan ajan.</p>
</li>
<li><p><strong>kube-proxy</strong>: Ağ kurallarını uygulayan ve ağ trafiğini yönlendiren ağ bileşeni.</p>
</li>
<li><p><strong>Container Runtime</strong>: Konteynerlerin çalıştırıldığı ortam (örn. Docker, containerd).</p>
</li>
</ul>
<p>Worker Node'lar üzerinde çalışan en küçük birim ise <strong>Pod</strong>'lardır. <strong>Pod</strong>'lar, aynı Node üzerinde çalışan bir veya daha fazla konteyneri barındıran yapılardır. Her <strong>Pod</strong>, kendi IP adresine ve depolama alanına sahip olabilir ve aynı Node üzerindeki diğer <strong>Pod</strong>'larla ağ paylaşımında bulunabilir. Kısacası, Kubernetes cluster, uygulamalarınızı dağıtmak ve yönetmek için birlikte çalışan bir Node ve Pod grubudur.</p>
<p>Kubernetes cluster'ının temel bileşenlerini incelediğimiz bu yazıda, cluster'ın yapı taşları olan Node'lar, Pod'lar ve Control Plane bileşenlerini tanıdık. Bu bileşenler, Kubernetes'in iş yüklerinizi etkili bir şekilde yönetmesini sağlar.</p>
<p>Özetle, Kubernetes cluster:</p>
<ul>
<li><p>Uygulama iş yüklerinizi çalıştıran ve yöneten <strong>worker Node'lar</strong>,</p>
</li>
<li><p>Cluster'ın yönetim ve kontrol işlevlerini yerine getiren <strong>Master Node'lar</strong>,</p>
</li>
<li><p>Yeni oluşturulan Pod'ları uygun Node'lara yerleştiren <strong>Scheduler</strong>,</p>
</li>
<li><p>Cluster'ın tüm yapılandırma ve durum bilgilerini saklayan <strong>etcd</strong>,</p>
</li>
<li><p>Tüm API isteklerini karşılayan ve yöneten <strong>API Server</strong>,</p>
</li>
<li><p>Cluster'ın durumunu kontrol eden ve yönetim işlemlerini gerçekleştiren <strong>Controller Manager</strong>,</p>
</li>
<li><p>Cloud provider ile etkileşimi sağlayan ve cloud kaynaklarının yönetimini gerçekleştiren <strong>Cloud Controller Manager</strong>,</p>
</li>
<li><p>Her Node'da çalışan ve Pod'ların durumunu API Server'a raporlayan <strong>kubelet</strong>,</p>
</li>
<li><p>Ağ kurallarını uygulayan ve ağ trafiğini yönlendiren <strong>kube-proxy</strong>,</p>
</li>
<li><p>Konteynerlerin çalıştırıldığı ortam olan <strong>container runtime</strong> gibi bileşenlerden oluşur.</p>
</li>
</ul>
<p>Bu temel bileşenlerin yanı sıra, Kubernetes'in iş yüklerinizi ve cluster'ınızı yönetmek için daha birçok bileşeni bulunmaktadır. Bu bileşenler, Kubernetes'in esnek ve ölçeklenebilir yapısını daha da güçlendirir.</p>
<p>Kubernetes'in temel bileşenlerini anlamak, Kubernetes'i verimli bir şekilde kullanmanın ilk adımıdır. Bu bilgilerin üzerine daha fazla araştırma yaparak, denemeler yaparak kubernetes konusunda kendinizi geliştirebilir ve uygulamalarınızı kubernetes ortamında çalıştırabilirsiniz. Bir sonraki yazımda Podlardan bahsedeceğim. Umarım sana dokunmuş olabilirim. Merak ettiğin bir şey olursa benimle iletişime geçebilirsin.</p>
<p>Kaynakçalar</p>
<ol>
<li><a target="_blank" href="https://kubernetes.io/docs/concepts/overview/components/">https://kubernetes.io/docs/concepts/overview/components/</a></li>
</ol>
]]></content:encoded></item><item><title><![CDATA[Kubernetes'in Geçmişi ve Evrimi]]></title><description><![CDATA[Kubernetes'ten Öncesi
Kubernetes ve faydalarını daha iyi anlamak için, kubernetesin tarihçesinden bahsetmemizin iyi olacağını düşünüyorum. Günümüzde web trafiğinin büyük bir kısmı Google'ın hizmetleri, reklam ağları ve analiz araçları üzerinden geçme...]]></description><link>https://serhatleventyavas.dev/kubernetesin-gecmisi-ve-evrimi</link><guid isPermaLink="true">https://serhatleventyavas.dev/kubernetesin-gecmisi-ve-evrimi</guid><category><![CDATA[Kubernetes]]></category><category><![CDATA[#kubernetes #container ]]></category><dc:creator><![CDATA[Serhat Levent Yavaş]]></dc:creator><pubDate>Sat, 13 Jul 2024 07:52:03 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1720857103471/237ec7d0-266a-4e92-b303-ad324d445b17.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h3 id="heading-kubernetesten-oncesi">Kubernetes'ten Öncesi</h3>
<p>Kubernetes ve faydalarını daha iyi anlamak için, kubernetesin tarihçesinden bahsetmemizin iyi olacağını düşünüyorum. Günümüzde web trafiğinin büyük bir kısmı Google'ın hizmetleri, reklam ağları ve analiz araçları üzerinden geçmektedir. Google, insanoğlunun hayatına girdiği günden beri her zaman yoğun istekleri işleme ile karşı karşıya kalmıştır. Bu yoğun istekleri yönetmek için devamlı veri merkezlerine yeni sunucular, o sunucuların üstlerine yeni sistemler inşa etmek ve bunları uyumlu şekilde çalıştırmak için mücadele vermiştir. Tahmin edersiniz ki binlerce makineye sahip veri merkezlerini manuel yönetmek imkansıza yakın bir çalışma olur. Bunun sonucunda google tarafında bu süreçleri otonomize etme ihtiyacı doğdu.</p>
<h3 id="heading-borg-ve-omeganin-gelisimi-ve-birlesmesi">Borg ve Omega'nın Gelişimi ve Birleşmesi</h3>
<p>Google mühendisleri bu sunucuları yönetebilmek için bir cluster manager sistem geliştirmeye karar verdiler ve <strong>2003</strong> yılında <strong>Borg</strong> adında bir cluster manager sistem yazılımı geliştirdiler. Bu yazılım binlerce işi, binlerce makine üzerinde çalışan bir çok cluster arasında çalıştırmaya ve onları yönetmeye başladı. Borg sayesinde artık süreçleri otonomize etmeye başladılar. Ancak bir sorun vardı. Borg aslında kendi içerisinde karmaşık bir sistemdi ve onu yönetmek zaman ilerledikçe zorlamaya başlamıştı ve artan iş yükü ve çeşitlilik nedeniyle taleplere cevap veremez hale gelmeye başlamıştı. Onlara daha modüler ve esnek bir yapı sunabilecek bir sisteme ihtiyaçları vardı. Bu yüzden google mühendisleri kolları baştan sıvadılar ve modüler, esnek bir yapıya sahip olacak ve <strong>borg'un</strong> iyi yaptığı işleri daha iyi yapabilecek, zaman geçtikçe ihtiyaç duyulan özellikleri de içerecek bir sistem geliştirmeye başladılar. Bu sistemin adınada Omega adını verdiler. <strong>Omega</strong>, <strong>Borg'un</strong> bazı sınırlamalarını aşmak için geliştirilmiş bir sistemdi. Ancak, geliştirme sürecinde bazı teknik zorluklarla karşılaşıldı ve bu nedenle <strong>Omega</strong>, <strong>Borg</strong> ile entegre edildi. <strong>Omega</strong>, <strong>Borg'un</strong> üzerine eklenen modüler ve esnek özelliklerle Borg'un daha güçlü ve verimli hale gelmesini sağladı. Ayrıca <strong>omega</strong> tarafındaki bu geliştirmeler Google'da çalışan bazı mühendislere ilham oldu ve yeni bir açık kaynak sistem geliştirmelerine motivasyon sağladı.</p>
<h3 id="heading-kubernetesin-dogusu">Kubernetes'in Doğuşu</h3>
<p><strong>Craig Mcluckie</strong>, <strong>Brendan Burns</strong>, <strong>Joe Beda</strong> adındaki bu üç google mühendisi Borg ve Omega'nın sağladığı verimlilik ve esneklikten ilham alarak, bu özellikleri daha geniş bir geliştirici topluluğu ile paylaşmak amacıyla yeni bir sistem geliştirmeye karar verdiler. <strong>Project Seven</strong> adıyla geliştirilen proje, <strong>2014</strong> yılında <strong>Kubernetes</strong> adını alarak bu sistemi ekosistem ile paylaştılar. Bu yeni sistemi açık kaynak yaparak, tüm geliştiricilerin kullanmasına ve katkıda bulunmasına olanak sağladı. Bu şekilde ihtiyaçlar daha hızlı giderildi. Hatalar daha hızlı farkedildi ve düzeltildi. Ayrıca 2010'ların başında bulut bilişiminin hızlıca popülerleşmesi, dağıtık sistemlere duyulan ihtiyacın artması gibi durumlar sebebiyle devops süreçleri daha önemli hale gelmeye başladı. Kubernetes bulut ortamlarında kaynakları verimli kullanma ve devops süreçlerinin içerisinde olarak yazılım geliştirme ve dağıtım süreçlerinin otomasyonunu sağlaması ve süreçlerin hızını artırması ile birlikte zamanla ekosistemde popüler olmaya başladı.</p>
<p>Google, Kubernetes'i <strong>2015</strong> yılında <strong>Cloud Native Computing Foundation'a</strong> devretti. Bu adım, Kubernetes'in daha geniş bir açık kaynak topluluğu tarafından benimsenmesini ve geliştirilmesini sağladı. CNCF, Kubernetes'in geliştirilmesini destekleyerek, konteyner orkestrasyonu için endüstri standardı haline gelmesine yardımcı oldu.</p>
<p>Bu yazımda Kubernetes'in geçmişini, evrimini ve nasıl bir standart hale gelmesini anlatmaya çalıştım. Bir sonraki yazımda kubernetes'in kurulumuna ve temellerine gireceğiz. Umarım sana dokunmuş olabilirim. Merak ettiğin bir şey olursa benimle iletişime geçebilirsin.</p>
<p>Kaynakçalar:</p>
<ol>
<li><p><a target="_blank" href="https://research.google/pubs/large-scale-cluster-management-at-google-with-borg/">https://research.google/pubs/large-scale-cluster-management-at-google-with-borg/</a></p>
</li>
<li><p><a target="_blank" href="https://research.google/pubs/omega-flexible-scalable-schedulers-for-large-compute-clusters/">https://research.google/pubs/omega-flexible-scalable-schedulers-for-large-compute-clusters/</a></p>
</li>
<li><p><a target="_blank" href="https://www.cncf.io/">https://www.cncf.io/</a></p>
</li>
<li><p><a target="_blank" href="https://opensource.googleblog.com/2024/05/kubernetes-130-is-now-available-in-gke.html">https://opensource.googleblog.com/2024/05/kubernetes-130-is-now-available-in-gke.html</a></p>
</li>
</ol>
]]></content:encoded></item><item><title><![CDATA[Entegrasyon Testini Keşfetmek: Temel Kavramlar ve Uygulamalar]]></title><description><![CDATA[Entegrasyon testi, yazılım projelerimizi test etmek için kullandığımız yöntemlerinden biridir. Entegrasyon testi ile projemizdeki modüllerin ve bileşenlerin arasındaki etkileşimleri aynı zamanda veri akışını test edebiliriz. Testlerimizde dosyaya ya ...]]></description><link>https://serhatleventyavas.dev/entegrasyon-testini-kesfetmek-temel-kavramlar-ve-uygulamalar</link><guid isPermaLink="true">https://serhatleventyavas.dev/entegrasyon-testini-kesfetmek-temel-kavramlar-ve-uygulamalar</guid><category><![CDATA[entegrasyon test nedir]]></category><category><![CDATA[entegrasyon]]></category><category><![CDATA[integration test]]></category><category><![CDATA[Testcontainers]]></category><category><![CDATA[asp.net core]]></category><category><![CDATA[entity framework]]></category><category><![CDATA[PostgreSQL]]></category><dc:creator><![CDATA[Serhat Levent Yavaş]]></dc:creator><pubDate>Mon, 06 May 2024 10:03:05 GMT</pubDate><content:encoded><![CDATA[<p>Entegrasyon testi, yazılım projelerimizi test etmek için kullandığımız yöntemlerinden biridir. Entegrasyon testi ile projemizdeki modüllerin ve bileşenlerin arasındaki etkileşimleri aynı zamanda veri akışını test edebiliriz. Testlerimizde dosyaya ya da veritabanına bir veri yazabilir, okuyabilir ve 3.parti servislere istek atabiliriz. Yani, birim testlerimiz infrastructure katmanından izole durumundayken entegrasyon testlerimiz infrastructure katmanı ile birlikte çalışır. Entegrasyon testlerini yazmak maliyetlidir. Birim testlere göre zahmetli ve çalışma süreleri daha uzundur. Entegrasyon testlerin sayısı artıkça testlerin tamamlanma süresi artacaktır. Her entegrasyon testi için infrastructure katmanını ayağa kaldırmak gerekecektir. Her test senaryosu için verilerin ilgili test senaryosuna göre hazırlanması gerekecektir. Entegrasyon testlerin bu tip dezavantajları sebebiyle sadece <strong>happy path testlerimizi</strong> yazmak daha doğru olacaktır. Diğer test senaryolarımız için birim testleri kullanarak test süresini olabilecek en kısa süreye çekebilir ve sadece iş akışına odaklanan testler yazabiliriz.</p>
<blockquote>
<p><strong>Happy Path Test:</strong> Hata ve istisna içermeyen, girdilerin ve çıktıların beklendiği gibi olan test senaryosudur.</p>
</blockquote>
<p>Entegrasyon testinden kısaca bahsettiğimize göre basit bir örnek yaparak <a target="_blank" href="https://dotnet.microsoft.com/en-us/apps/aspnet">aspnetcore</a> ile geliştirdiğimiz projelerde nasıl entegrasyon testi yazabiliriz bakalım. Entegrasyon testlerinde ihtiyacımız olan infrastructure katmanlarını her test senaryosunda sıfırdan oluşturmak için <a target="_blank" href="https://testcontainers.com/"><strong>testcontainers</strong></a> kullanabiliriz. <a target="_blank" href="https://testcontainers.com/"><strong>Testcontainers</strong></a>, <a target="_blank" href="https://www.docker.com/">Docker</a> kullanarak test senaryosunda ihtiyacımız olan <a target="_blank" href="https://www.postgresql.org/">postgres</a>, <a target="_blank" href="https://rabbitmq.com/">rabbitmq</a>, <a target="_blank" href="https://redis.io/">redis</a> gibi sistemleri ya da teknolojileri çalıştıran ve test senaryosu tamamlandığında kaynağı ortadan kaldıran bir açık kaynak bir yazılımdır.</p>
<blockquote>
<p>Testcontainer için daha fazla bilgiye erişmek istiyorsanız, <a target="_blank" href="https://testcontainers.com/">https://testcontainers.com/</a> adresini ziyaret edebilirsiniz.</p>
</blockquote>
<p>Bizim örneğimizde bir webapi projemiz olacak ve bu projemiz ürün modülünü yönetecek. Basit bir şekilde sisteme ürün ekleyebilecek, ürün silebilecek, ürün güncelleyebilecek ve sistemdeki ürünleri geri dönebilecek. Her servis için <strong>happy path test senaryosunu</strong> uygulayacağız.</p>
<ul>
<li><p>POST /api/products</p>
</li>
<li><p>PUT /api/products</p>
</li>
<li><p>DELETE /api/products</p>
</li>
<li><p>GET /api/products</p>
</li>
<li><p>GET /api/products/{uuid}</p>
</li>
</ul>
<h2 id="heading-projemizi-olusturalim">Projemizi Oluşturalım.</h2>
<p>İlk önce aspnetcore 8.0 projemizi oluşturalım. Boş bir solution oluşturmak için aşağıya terminal kodunu yazıyorum.</p>
<pre><code class="lang-powershell">dotnet new sln <span class="hljs-literal">-o</span> IntegrationTestAspNetCoreExample <span class="hljs-literal">-n</span> IntegrationTestAspNetCoreExample
</code></pre>
<p>Ardından domain, application, infrastructure ve http api katmanlarını oluşturuyorum ve bunları solution'a ekliyorum.</p>
<pre><code class="lang-powershell">dotnet new classlib <span class="hljs-literal">-o</span> Domain <span class="hljs-literal">-n</span> Domain
dotnet new classlib <span class="hljs-literal">-o</span> Application <span class="hljs-literal">-n</span> Application
dotnet new classlib <span class="hljs-literal">-o</span> Infrastructure <span class="hljs-literal">-n</span> Infrastructure
dotnet new webapi <span class="hljs-literal">-o</span> HttpApi <span class="hljs-literal">-n</span> HttpApi

dotnet sln add .\Domain\Domain.csproj
dotnet sln add .\Application\Application.csproj
dotnet sln add .\Infrastructure\Infrastructure.csproj
dotnet sln add .\HttpApi\HttpApi.csproj
</code></pre>
<p>Projenin kurulmuş temiz halini aşağıdaki <a target="_blank" href="https://github.com/serhatleventyavas/IntegrationTestAspNetCoreExample/tree/starterProject">linkten</a> erişebilirsiniz.</p>
<h2 id="heading-domain-katmanini-yazalim">Domain Katmanını Yazalım.</h2>
<p>Projemize ilk Product domain entity kodlayarak başlayalım. Ürün sınıfı içerisinde id, isim, açıklama, ürünün kapak resim linki, fiyat ve stok adedini tutuyoruz. Ürün sınıfımız aşağıdaki gibi olacaktır.</p>
<pre><code class="lang-csharp"><span class="hljs-keyword">using</span> Domain.Exceptions;
<span class="hljs-keyword">using</span> System.Text.RegularExpressions;

<span class="hljs-keyword">namespace</span> <span class="hljs-title">Domain</span>;

<span class="hljs-keyword">public</span> <span class="hljs-keyword">sealed</span> <span class="hljs-keyword">class</span> <span class="hljs-title">Product</span>
{
    <span class="hljs-keyword">public</span> Guid Id { <span class="hljs-keyword">get</span>; <span class="hljs-keyword">private</span> <span class="hljs-keyword">set</span>; }
    <span class="hljs-keyword">public</span> <span class="hljs-keyword">string</span> Name { <span class="hljs-keyword">get</span>; <span class="hljs-keyword">private</span> <span class="hljs-keyword">set</span>; } = <span class="hljs-literal">null</span>!;
    <span class="hljs-keyword">public</span> <span class="hljs-keyword">string</span> Description { <span class="hljs-keyword">get</span>; <span class="hljs-keyword">private</span> <span class="hljs-keyword">set</span>; } = <span class="hljs-literal">null</span>!;
    <span class="hljs-keyword">public</span> <span class="hljs-keyword">string</span> ImageLink { <span class="hljs-keyword">get</span>; <span class="hljs-keyword">private</span> <span class="hljs-keyword">set</span>; } = <span class="hljs-literal">null</span>!;
    <span class="hljs-keyword">public</span> <span class="hljs-keyword">decimal</span> Price { <span class="hljs-keyword">get</span>; <span class="hljs-keyword">private</span> <span class="hljs-keyword">set</span>; }
    <span class="hljs-keyword">public</span> <span class="hljs-keyword">int</span> Quantity { <span class="hljs-keyword">get</span>; <span class="hljs-keyword">private</span> <span class="hljs-keyword">set</span>; }

    <span class="hljs-function"><span class="hljs-keyword">private</span> <span class="hljs-title">Product</span>(<span class="hljs-params"></span>)</span> {}

    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> Product <span class="hljs-title">Create</span>(<span class="hljs-params"><span class="hljs-keyword">string</span> name, <span class="hljs-keyword">string</span> description, <span class="hljs-keyword">string</span> imageLink, <span class="hljs-keyword">decimal</span> price, <span class="hljs-keyword">int</span> quantity</span>)</span>
    {
        <span class="hljs-keyword">var</span> product = <span class="hljs-keyword">new</span> Product
        {
            Id = Guid.NewGuid()
        };
        product.SetName(name);
        product.SetDescription(description);
        product.SetImageLink(imageLink);
        product.SetPrice(price);
        product.SetQuantity(quantity);
        <span class="hljs-keyword">return</span> product;
    }

    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">SetName</span>(<span class="hljs-params"><span class="hljs-keyword">string</span> name</span>)</span>
    {
        <span class="hljs-keyword">if</span> (<span class="hljs-keyword">string</span>.IsNullOrEmpty(name))
        {
            <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> NameMinLengthException();
        }

        <span class="hljs-keyword">if</span> (name.Length &gt; <span class="hljs-number">120</span>)
        {
            <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> NameMaxLengthException();
        }

        Name = name;
    }

    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">SetDescription</span>(<span class="hljs-params"><span class="hljs-keyword">string</span> description</span>)</span>
    {
        <span class="hljs-keyword">if</span> (<span class="hljs-keyword">string</span>.IsNullOrEmpty(description))
        {
            <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> DescriptionMinLengthException();
        }

        <span class="hljs-keyword">if</span> (description.Length &gt; <span class="hljs-number">1000</span>)
        {
            <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> DescriptionMaxLengthException();
        }

        Description = description;
    }

    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">SetImageLink</span>(<span class="hljs-params"><span class="hljs-keyword">string</span> imageLink</span>)</span>
    {
        <span class="hljs-keyword">var</span> isCreatedUri = Uri.TryCreate(imageLink, UriKind.Absolute, <span class="hljs-keyword">out</span> <span class="hljs-keyword">var</span> imageLinkUri);
        <span class="hljs-keyword">if</span> (!isCreatedUri || imageLinkUri == <span class="hljs-literal">null</span>)
        {
            <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> InvalidImageLinkException();
        }

        <span class="hljs-keyword">var</span> availableSchemes = <span class="hljs-keyword">new</span> List&lt;<span class="hljs-keyword">string</span>&gt;
        {
                        Uri.UriSchemeHttp, Uri.UriSchemeHttps
        };

        <span class="hljs-keyword">if</span> (availableSchemes.Contains(imageLinkUri.Scheme) == <span class="hljs-literal">false</span>)
        {
            <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> InvalidImageLinkException();
        }

        <span class="hljs-keyword">var</span> isMatchExtension = Regex.IsMatch(imageLink, <span class="hljs-string">@"\.(jpeg|jpg|gif|png)$"</span>, RegexOptions.IgnoreCase);
        <span class="hljs-keyword">if</span> (isMatchExtension == <span class="hljs-literal">false</span>)
        {
            <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> InvalidImageLinkException();
        }

        ImageLink = imageLink;
    }

    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">SetPrice</span>(<span class="hljs-params"><span class="hljs-keyword">decimal</span> price</span>)</span>
    {
        <span class="hljs-keyword">if</span> (price &lt;= <span class="hljs-number">0</span>)
        {
            <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> InvalidPriceException();
        }

        Price = price;
    }

    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">SetQuantity</span>(<span class="hljs-params"><span class="hljs-keyword">int</span> quantity</span>)</span>
    {
        <span class="hljs-keyword">if</span> (quantity &lt; <span class="hljs-number">0</span>)
        {
            <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> InvalidQuantityException();
        }

        Quantity = quantity;
    }
}
</code></pre>
<p>Ürünler için veritabanı sorgulamalarını yapmak Repository design patterni kullanacağız. Bunun için IProductRepository interfacesini yazıyoruz.</p>
<pre><code class="lang-csharp"><span class="hljs-keyword">public</span> <span class="hljs-keyword">interface</span> <span class="hljs-title">IProductRepository</span>
{
    <span class="hljs-function">Task <span class="hljs-title">AddAsync</span>(<span class="hljs-params">Product product</span>)</span>;
    <span class="hljs-function"><span class="hljs-keyword">void</span> <span class="hljs-title">Update</span>(<span class="hljs-params">Product product</span>)</span>;
    <span class="hljs-function"><span class="hljs-keyword">void</span> <span class="hljs-title">Delete</span>(<span class="hljs-params">Product product</span>)</span>;
    <span class="hljs-function">Task&lt;Product&gt; <span class="hljs-title">GetByIdAsync</span>(<span class="hljs-params">Guid id</span>)</span>;
    Task&lt;List&lt;Product&gt;&gt; GetListAsync();
}
</code></pre>
<p>Yapılan işlemler sonucunda, etkilenen nesnelerin veritabanına tek bir bağlantı altında toplu şekilde işlenmesi için Unit of Work design patterni kullanacağız. Bunun için IUnitOfWork interfacesini yazıyoruz.</p>
<blockquote>
<p>Unit of Work patterni sayesinde veritabanı tarafında bir transaction oluşturup, yapılan değişiklikler veritabanına yazılmaya çalışılacak. Eğer bu işlem sırasında bir hata oluşursa veritabanında yapılan tüm değişiklikleri geri alınacak. Bu sayede veritabanında <strong>tutarlılığı (consistency)</strong> sağlamış olacağız.</p>
</blockquote>
<pre><code class="lang-csharp"><span class="hljs-keyword">public</span> <span class="hljs-keyword">interface</span> <span class="hljs-title">IUnitOfWork</span>
{
    <span class="hljs-function">Task&lt;<span class="hljs-keyword">int</span>&gt; <span class="hljs-title">SaveChangesAsync</span>(<span class="hljs-params">CancellationToken cancellationToken = <span class="hljs-keyword">default</span></span>)</span>;
}
</code></pre>
<blockquote>
<p>Tutarlılık (Consistency): Bir dizi veritabanı işleminin tek bir birim olarak ele alınmasını ve tüm değişikliklerin bir araya getirilmesini veya geri alınmasını sağlamaya yardımcı olur. Bu, özellikle veri tutarlılığının kritik olduğu uygulamalarda önemlidir.</p>
</blockquote>
<p>Yapılan geliştirmeleri bu <a target="_blank" href="https://github.com/serhatleventyavas/IntegrationTestAspNetCoreExample/tree/domain/Product">linkten</a> erişebilirsiniz.</p>
<h2 id="heading-infrastructure-katmanini-kodlayalim">Infrastructure Katmanını Kodlayalım.</h2>
<p>Projemizin domain katmanını yazdıktan sonra infrastructure katmanını geliştirmeye başlayabiliriz. Infrastructure katmanı, Domain katmanına erişebilmesi için Domain katmanın referansını eklemeliyiz.</p>
<pre><code class="lang-powershell">dotnet add .\Application\Application.csproj reference .\Domain\Domain.csproj
dotnet add .\Infrastructure\Infrastructure.csproj reference .\Application\Application.csproj
</code></pre>
<p>Referansları ekledikten sonra geliştirme sürecimize devam edebiliriz. Ürünlerimizi <strong>postgres</strong> veritabanı üzerinde tutacağız. Verileri okumak, yazmak için <a target="_blank" href="https://learn.microsoft.com/en-us/ef/core/"><strong>Entity Framework Core</strong></a> <strong>ORM</strong> kütüphanesini kullanacağız. Bunun için bazı nuget paketlerine ihtiyacımız var. <strong>Postgres</strong> veritabanı üzerinde entity framework core orm kütüphanesi ile çalışabilmek için <a target="_blank" href="https://www.npgsql.org/efcore/index.html"><strong>Npsql.EntityFrameworkCore.PostgreSQL</strong></a> nuget paketini indirelim. Ardından veritabanı tablolarımızda, kolonlarımızda Snake-Case isimlendirme yapabilmemiz için <strong>EFCore.NamingConventions</strong> nuget paketini indirelim.</p>
<pre><code class="lang-powershell">dotnet add .\Infrastructure\Infrastructure.csproj package Npgsql.EntityFrameworkCore.PostgreSQL
dotnet add .\Infrastructure\Infrastructure.csproj package EFCore.NamingConventions
</code></pre>
<p>Bu nuget paketler dışında <strong>Microsoft.Extensions.Configuration.Abstractions</strong> ve <strong>Microsoft.Extensions.DependencyInjection.Abstractions</strong> paketlerine ihtiyacımız olacak. Bu paketler sayesinde infrastructure katmanımızın konfigurasyonlarını yapabileceğiz.</p>
<pre><code class="lang-powershell">dotnet add .\Infrastructure\Infrastructure.csproj package Microsoft.Extensions.Configuration.Abstractions
dotnet add .\Infrastructure\Infrastructure.csproj package Microsoft.Extensions.DependencyInjection.Abstractions
</code></pre>
<p>Gerekli nuget paketlerini indirdikten sonra ApplicationDbContext sınıfımızı yazabiliriz. Bu sınıfımızı DbContext sınıfından türeteceğiz. Ayrıca domain katmanında yazdığımız <strong>IUnitOfWork</strong> interfacesinide ApplicationDbContext sınıfına implement ediyoruz.</p>
<blockquote>
<p>DbContext sınıfı, projemiz ile veritabanı arasında bir bağlantı sağlar, veritabanını yönetmemizi sağlar. Veritabanı sorgularını gerçekleştirir, okuma ve yazma işlemlerini yapar. DbContext hakkında daha fazla bilgi edinmek istiyorsan, <a target="_blank" href="https://learn.microsoft.com/en-us/ef/core/dbcontext-configuration/">https://learn.microsoft.com/en-us/ef/core/dbcontext-configuration/</a> adresini ziyaret edebilirsin.</p>
</blockquote>
<pre><code class="lang-csharp"><span class="hljs-keyword">using</span> Domain;
<span class="hljs-keyword">using</span> Microsoft.EntityFrameworkCore;

<span class="hljs-keyword">namespace</span> <span class="hljs-title">Infrastructure</span>;

<span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">sealed</span> class <span class="hljs-title">ApplicationDbContext</span>(<span class="hljs-params">DbContextOptions options</span>) : <span class="hljs-title">DbContext</span>(<span class="hljs-params">options</span>), IUnitOfWork</span>
{
    <span class="hljs-function"><span class="hljs-keyword">protected</span> <span class="hljs-keyword">override</span> <span class="hljs-keyword">void</span> <span class="hljs-title">OnModelCreating</span>(<span class="hljs-params">ModelBuilder modelBuilder</span>)</span>
    {
        modelBuilder.ApplyConfigurationsFromAssembly(<span class="hljs-keyword">typeof</span>(ApplicationDbContext).Assembly);
        <span class="hljs-keyword">base</span>.OnModelCreating(modelBuilder);
    }
}
</code></pre>
<p>ProductRepository sınıfımı yazıyoruz ve IProductRepository interfacesini implement ediyoruz. ApplicationDbContext sınıfımızı kullanarak ürün için gerekli veritabanı işlemlerini gerçekleştiriyoruz.</p>
<pre><code class="lang-csharp"><span class="hljs-keyword">using</span> Domain;
<span class="hljs-keyword">using</span> Microsoft.EntityFrameworkCore;

<span class="hljs-keyword">namespace</span> <span class="hljs-title">Infrastructure.Repositories</span>;

<span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">sealed</span> class <span class="hljs-title">ProductRepository</span>(<span class="hljs-params">ApplicationDbContext dbContext</span>): IProductRepository</span>
{
    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">async</span> Task <span class="hljs-title">AddAsync</span>(<span class="hljs-params">Product product</span>)</span>
    {
        <span class="hljs-keyword">await</span> dbContext.AddAsync(product);
    }

    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">Update</span>(<span class="hljs-params">Product product</span>)</span>
    {
        dbContext.Update(product);
    }

    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">Delete</span>(<span class="hljs-params">Product product</span>)</span>
    {
        dbContext.Remove(product);
    }

    <span class="hljs-keyword">public</span> <span class="hljs-keyword">async</span> Task&lt;Product?&gt; GetByIdAsync(Guid id)
    {
        <span class="hljs-keyword">return</span> <span class="hljs-keyword">await</span> dbContext.Set&lt;Product&gt;().FirstOrDefaultAsync(p =&gt; p.Id == id);
    }

    <span class="hljs-keyword">public</span> <span class="hljs-keyword">async</span> Task&lt;List&lt;Product&gt;&gt; GetListAsync()
    {
        <span class="hljs-keyword">return</span> <span class="hljs-keyword">await</span> dbContext.Set&lt;Product&gt;().ToListAsync();
    }
}
</code></pre>
<p>Bu demo projemizde code first yaklaşımıyla çalışıyoruz. Product entity için veritabanında bir tablo oluşturmak için migration oluşturacağız. Bu yüzden ProductTypeConfiguration sınıfını yazalım.</p>
<pre><code class="lang-csharp"><span class="hljs-keyword">using</span> Domain;
<span class="hljs-keyword">using</span> Microsoft.EntityFrameworkCore;
<span class="hljs-keyword">using</span> Microsoft.EntityFrameworkCore.Metadata.Builders;

<span class="hljs-keyword">namespace</span> <span class="hljs-title">Infrastructure.TypeConfigurations</span>;

<span class="hljs-keyword">public</span> <span class="hljs-keyword">sealed</span> <span class="hljs-keyword">class</span> <span class="hljs-title">ProductTypeConfiguration</span>: <span class="hljs-title">IEntityTypeConfiguration</span>&lt;<span class="hljs-title">Product</span>&gt;
{
    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">Configure</span>(<span class="hljs-params">EntityTypeBuilder&lt;Product&gt; builder</span>)</span>
    {
        builder.ToTable(<span class="hljs-string">"products"</span>);

        builder.HasIndex(p =&gt; p.Id).IsUnique();
        builder.Property(p =&gt; p.Id)
                        .IsRequired();

        builder.Property(p =&gt; p.Name)
                        .HasMaxLength(<span class="hljs-number">120</span>)
                        .IsRequired();

        builder.Property(p =&gt; p.Description)
                        .HasMaxLength(<span class="hljs-number">1000</span>)
                        .IsRequired();

        builder.Property(p =&gt; p.ImageLink)
                        .HasMaxLength(<span class="hljs-number">255</span>)
                        .IsRequired();

        builder.Property(p =&gt; p.Price)
                        .IsRequired();

        builder.Property(p =&gt; p.Quantity)
                        .IsRequired();

    }
}
</code></pre>
<p>Infrastructure katmanında yapacağımız son işlem ise bağımlıkları tanımlamak olacak. Bu yüzden DependencyInjection sınıfımızı oluşturalım ve aşağıdaki gibi dolduralım.</p>
<pre><code class="lang-csharp"><span class="hljs-keyword">using</span> Domain;
<span class="hljs-keyword">using</span> Infrastructure.Repositories;
<span class="hljs-keyword">using</span> Microsoft.EntityFrameworkCore;
<span class="hljs-keyword">using</span> Microsoft.Extensions.Configuration;
<span class="hljs-keyword">using</span> Microsoft.Extensions.DependencyInjection;

<span class="hljs-keyword">namespace</span> <span class="hljs-title">Infrastructure</span>;

<span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">class</span> <span class="hljs-title">DependencyInjection</span>
{
    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> IServiceCollection <span class="hljs-title">AddInfrastructure</span>(<span class="hljs-params"><span class="hljs-keyword">this</span> IServiceCollection services, IConfiguration configuration</span>)</span>
    {
        <span class="hljs-keyword">var</span> dbConnectionString = configuration.GetConnectionString(<span class="hljs-string">"Database"</span>) ??
                                 <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> ArgumentNullException(<span class="hljs-keyword">nameof</span>(configuration));
        services.AddDbContext&lt;ApplicationDbContext&gt;(builder =&gt;
        {
            builder.UseNpgsql(dbConnectionString).UseSnakeCaseNamingConvention();
        });

        services.AddScoped&lt;IProductRepository, ProductRepository&gt;();
        services.AddScoped&lt;IUnitOfWork&gt;(sp =&gt; sp.GetRequiredService&lt;ApplicationDbContext&gt;());

        <span class="hljs-keyword">return</span> services;
    }
}
</code></pre>
<p>Yapılan geliştirmeleri bu <a target="_blank" href="https://github.com/serhatleventyavas/IntegrationTestAspNetCoreExample/tree/infrastructure">linkten</a> erişebilirsiniz.</p>
<h2 id="heading-application-katmanini-kodlayalim">Application Katmanını Kodlayalım.</h2>
<p>Infrastructure katmanını da hazırladık. Şimdi application katmanını yazalım. Ürün ekleme, güncelleme, silme, ürünleri getirme servisleri bu katmanda bulunacak. Servislerimizi yazmadan önce ihtiyacımız olan nuget paketi olan <strong>Microsoft.Extensions.DependencyInjection.Abstractions</strong> paketini indirelim. Bu paket ile servislerimizi dependency injection altyapısına kaydedeceğiz.</p>
<pre><code class="lang-powershell">dotnet add .\Application\Application.csproj package Microsoft.Extensions.DependencyInjection.Abstractions
</code></pre>
<blockquote>
<p>Dependency Injection hakkında daha fazla bilgiye <a target="_blank" href="https://learn.microsoft.com/en-us/aspnet/core/fundamentals/dependency-injection?view=aspnetcore-8.0">bu adresten</a> ulaşabilirsiniz.</p>
</blockquote>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1711881502270/1042a38a-fe49-4a02-a27e-60278ec1bf0b.png" alt="Application Layer Project Structure" class="image--center mx-auto" /></p>
<p>Benim uygulama katmanındaki proje yapım yukarıdaki görseldeki gibidir. Dilerseniz bu şekilde sizde klasörleme yapabilirsiniz. Sizin tüm kod parçalarını kolaylıkla alabilmeniz için kod örneklerinde hepsini beraber koyacağım.</p>
<p>Servislere ürün ya da ürünleri getiren servisler ile başlıyoruz. İlk servisimiz <strong>GetProductItemService</strong> servisidir. Bu servis verilen id değerine göre ürünü getirmeye çalışıyor. Eğer ürün bulunmazsa bir hata fırlatıyoruz.</p>
<pre><code class="lang-csharp"><span class="hljs-keyword">using</span> Domain;

<span class="hljs-keyword">namespace</span> <span class="hljs-title">Application.Products.GetItem</span>;

<span class="hljs-keyword">public</span> <span class="hljs-keyword">sealed</span> <span class="hljs-keyword">record</span> <span class="hljs-title">GetProductItemInput</span>
{
    <span class="hljs-keyword">public</span> required Guid Id { <span class="hljs-keyword">get</span>; <span class="hljs-keyword">set</span>; }
}

<span class="hljs-keyword">public</span> <span class="hljs-keyword">interface</span>  <span class="hljs-title">IGetProductItemService</span>
{
    <span class="hljs-function">Task&lt;Product&gt; <span class="hljs-title">Handle</span>(<span class="hljs-params">GetProductItemInput input</span>)</span>;
}

<span class="hljs-function"><span class="hljs-keyword">internal</span> <span class="hljs-keyword">sealed</span> class <span class="hljs-title">GetProductItemService</span>(<span class="hljs-params">
    IProductRepository productRepository
    </span>): IGetProductItemService</span>
{
    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">async</span> Task&lt;Product&gt; <span class="hljs-title">Handle</span>(<span class="hljs-params">GetProductItemInput input</span>)</span>
    {
        <span class="hljs-keyword">var</span> id = input.Id;
        <span class="hljs-keyword">return</span> <span class="hljs-keyword">await</span> productRepository.GetByIdAsync(id) 
               ?? <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> NullReferenceException(<span class="hljs-keyword">nameof</span>(Product));
    }
}
</code></pre>
<p>İkinci servisimiz <strong>GetProductListService</strong> servisidir. Bu servis veritabanındaki tüm ürünleri getirmeye çalışıyor.</p>
<pre><code class="lang-csharp"><span class="hljs-keyword">using</span> Domain;

<span class="hljs-keyword">namespace</span> <span class="hljs-title">Application.Products.GetList</span>;

<span class="hljs-keyword">public</span> <span class="hljs-keyword">interface</span>  <span class="hljs-title">IGetProductListService</span>
{
    Task&lt;List&lt;Product&gt;&gt; Handle();
}

<span class="hljs-function"><span class="hljs-keyword">internal</span> <span class="hljs-keyword">sealed</span> class <span class="hljs-title">GetProductListService</span>(<span class="hljs-params">
    IProductRepository productRepository
    </span>): IGetProductListService</span>
{
    <span class="hljs-keyword">public</span> <span class="hljs-keyword">async</span> Task&lt;List&lt;Product&gt;&gt; Handle()
    { 
        <span class="hljs-keyword">return</span> <span class="hljs-keyword">await</span> productRepository.GetListAsync();
    }
}
</code></pre>
<p>Üçüncü servisimiz <strong>CreateProductService</strong> servisidir. Bu servis ile veritabanımıza bir ürün ekleyeceğiz.</p>
<pre><code class="lang-csharp"><span class="hljs-keyword">using</span> Domain;

<span class="hljs-keyword">namespace</span> <span class="hljs-title">Application.Products.Create</span>;

<span class="hljs-keyword">public</span> <span class="hljs-keyword">sealed</span> <span class="hljs-keyword">record</span> <span class="hljs-title">CreateProductInput</span>
{
    <span class="hljs-keyword">public</span> required <span class="hljs-keyword">string</span> Name { <span class="hljs-keyword">get</span>; <span class="hljs-keyword">set</span>; } = <span class="hljs-literal">null</span>!;
    <span class="hljs-keyword">public</span> required <span class="hljs-keyword">string</span> Description { <span class="hljs-keyword">get</span>; <span class="hljs-keyword">set</span>; } = <span class="hljs-literal">null</span>!;
    <span class="hljs-keyword">public</span> required <span class="hljs-keyword">string</span> ImageLink { <span class="hljs-keyword">get</span>; <span class="hljs-keyword">set</span>; } = <span class="hljs-literal">null</span>!;
    <span class="hljs-keyword">public</span> required <span class="hljs-keyword">decimal</span> Price { <span class="hljs-keyword">get</span>; <span class="hljs-keyword">set</span>; }
    <span class="hljs-keyword">public</span> required <span class="hljs-keyword">int</span> Quantity { <span class="hljs-keyword">get</span>; <span class="hljs-keyword">set</span>; }
}

<span class="hljs-keyword">public</span> <span class="hljs-keyword">interface</span> <span class="hljs-title">ICreateProductService</span>
{
    <span class="hljs-function">Task&lt;Guid&gt; <span class="hljs-title">Handle</span>(<span class="hljs-params">CreateProductInput input</span>)</span>;
}

<span class="hljs-function"><span class="hljs-keyword">internal</span> <span class="hljs-keyword">sealed</span> class <span class="hljs-title">CreateProductService</span>(<span class="hljs-params">
    IUnitOfWork unitOfWork, 
    IProductRepository productRepository
    </span>): ICreateProductService</span>
{
    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">async</span> Task&lt;Guid&gt; <span class="hljs-title">Handle</span>(<span class="hljs-params">CreateProductInput input</span>)</span>
    {
        <span class="hljs-keyword">var</span> name = input.Name;
        <span class="hljs-keyword">var</span> description = input.Description;
        <span class="hljs-keyword">var</span> imageLink = input.ImageLink;
        <span class="hljs-keyword">var</span> price = input.Price;
        <span class="hljs-keyword">var</span> quantity = input.Quantity;

        <span class="hljs-keyword">var</span> product = Product.Create(name, description, imageLink, price, quantity);
        <span class="hljs-keyword">await</span> productRepository.AddAsync(product);
        <span class="hljs-keyword">await</span> unitOfWork.SaveChangesAsync();

        <span class="hljs-keyword">return</span> product.Id;
    }
}
</code></pre>
<p>Dördüncü servisimiz <strong>UpdateProductService</strong> servisidir. Bu servis ile verilen id değerindeki ürünü buluyoruz ve onun değerlerini güncelliyoruz. Eğer verilen id'ye göre bir ürün bulunamazsa hata veriyoruz.</p>
<pre><code class="lang-csharp"><span class="hljs-keyword">using</span> Domain;

<span class="hljs-keyword">namespace</span> <span class="hljs-title">Application.Products.Update</span>;

<span class="hljs-keyword">public</span> <span class="hljs-keyword">sealed</span> <span class="hljs-keyword">record</span> <span class="hljs-title">UpdateProductInput</span>
{
    <span class="hljs-keyword">public</span> required Guid Id { <span class="hljs-keyword">get</span>; <span class="hljs-keyword">set</span>; }
    <span class="hljs-keyword">public</span> required <span class="hljs-keyword">string</span> Name { <span class="hljs-keyword">get</span>; <span class="hljs-keyword">set</span>; } = <span class="hljs-literal">null</span>!;
    <span class="hljs-keyword">public</span> required <span class="hljs-keyword">string</span> Description { <span class="hljs-keyword">get</span>; <span class="hljs-keyword">set</span>; } = <span class="hljs-literal">null</span>!;
    <span class="hljs-keyword">public</span> required <span class="hljs-keyword">string</span> ImageLink { <span class="hljs-keyword">get</span>; <span class="hljs-keyword">set</span>; } = <span class="hljs-literal">null</span>!;
    <span class="hljs-keyword">public</span> required <span class="hljs-keyword">decimal</span> Price { <span class="hljs-keyword">get</span>; <span class="hljs-keyword">set</span>; }
    <span class="hljs-keyword">public</span> required <span class="hljs-keyword">int</span> Quantity { <span class="hljs-keyword">get</span>; <span class="hljs-keyword">set</span>; }
}

<span class="hljs-keyword">public</span> <span class="hljs-keyword">interface</span>  <span class="hljs-title">IUpdateProductService</span>
{
    <span class="hljs-function">Task&lt;Product&gt; <span class="hljs-title">Handle</span>(<span class="hljs-params">UpdateProductInput input</span>)</span>;
}

<span class="hljs-function"><span class="hljs-keyword">internal</span> <span class="hljs-keyword">sealed</span> class <span class="hljs-title">UpdateProductService</span>(<span class="hljs-params">
    IUnitOfWork unitOfWork, 
    IProductRepository productRepository
    </span>): IUpdateProductService</span>
{
    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">async</span> Task&lt;Product&gt; <span class="hljs-title">Handle</span>(<span class="hljs-params">UpdateProductInput input</span>)</span>
    {
        <span class="hljs-keyword">var</span> id = input.Id;
        <span class="hljs-keyword">var</span> name = input.Name;
        <span class="hljs-keyword">var</span> description = input.Description;
        <span class="hljs-keyword">var</span> imageLink = input.ImageLink;
        <span class="hljs-keyword">var</span> price = input.Price;
        <span class="hljs-keyword">var</span> quantity = input.Quantity;

        <span class="hljs-keyword">var</span> product = <span class="hljs-keyword">await</span> productRepository.GetByIdAsync(id) 
                      ?? <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> NullReferenceException(<span class="hljs-keyword">nameof</span>(Product));

        product.SetName(name);
        product.SetDescription(description);
        product.SetImageLink(imageLink);
        product.SetPrice(price);
        product.SetQuantity(quantity);

        productRepository.Update(product);
        <span class="hljs-keyword">await</span> unitOfWork.SaveChangesAsync();

        <span class="hljs-keyword">return</span> product;
    }
}
</code></pre>
<p>Beşinci ve son servisimiz <strong>DeleteProductService</strong> servisidir. Bu servis ile verilen id değerindeki ürünü buluyoruz ve onu siliyoruz. Eğer verilen id'ye göre bir ürün bulunamazsa hata veriyoruz.</p>
<pre><code class="lang-csharp"><span class="hljs-keyword">using</span> Domain;

<span class="hljs-keyword">namespace</span> <span class="hljs-title">Application.Products.Delete</span>;

<span class="hljs-keyword">public</span> <span class="hljs-keyword">sealed</span> <span class="hljs-keyword">record</span> <span class="hljs-title">DeleteProductInput</span>
{
    <span class="hljs-keyword">public</span> required Guid Id { <span class="hljs-keyword">get</span>; <span class="hljs-keyword">set</span>; }
}

<span class="hljs-keyword">public</span> <span class="hljs-keyword">interface</span> <span class="hljs-title">IDeleteProductService</span>
{
    <span class="hljs-function">Task <span class="hljs-title">Handle</span>(<span class="hljs-params">DeleteProductInput input</span>)</span>;
}

<span class="hljs-function"><span class="hljs-keyword">internal</span> <span class="hljs-keyword">sealed</span> class <span class="hljs-title">DeleteProductService</span>(<span class="hljs-params">
    IUnitOfWork unitOfWork, 
    IProductRepository productRepository
    </span>): IDeleteProductService</span>
{
    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">async</span> Task <span class="hljs-title">Handle</span>(<span class="hljs-params">DeleteProductInput input</span>)</span>
    {
        <span class="hljs-keyword">var</span> id = input.Id;

        <span class="hljs-keyword">var</span> product = <span class="hljs-keyword">await</span> productRepository.GetByIdAsync(id) 
               ?? <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> NullReferenceException(<span class="hljs-keyword">nameof</span>(Product));

        productRepository.Delete(product);
        <span class="hljs-keyword">await</span> unitOfWork.SaveChangesAsync();
    }
}
</code></pre>
<p>Servislerimizi yazdık. Bu servisleri di altyapısına kaydetmek için gerekli konfigürasyonu <strong>DependencyInjection</strong> sınıfında yapıyoruz.</p>
<pre><code class="lang-csharp"><span class="hljs-keyword">using</span> Application.Products.Create;
<span class="hljs-keyword">using</span> Application.Products.Delete;
<span class="hljs-keyword">using</span> Application.Products.GetItem;
<span class="hljs-keyword">using</span> Application.Products.GetList;
<span class="hljs-keyword">using</span> Application.Products.Update;
<span class="hljs-keyword">using</span> Microsoft.Extensions.DependencyInjection;

<span class="hljs-keyword">namespace</span> <span class="hljs-title">Application</span>;

<span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">class</span> <span class="hljs-title">DependencyInjection</span>
{
    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> IServiceCollection <span class="hljs-title">AddApplication</span>(<span class="hljs-params"><span class="hljs-keyword">this</span> IServiceCollection services</span>)</span>
    {
        services.AddScoped&lt;ICreateProductService, CreateProductService&gt;();
        services.AddScoped&lt;IUpdateProductService, UpdateProductService&gt;();
        services.AddScoped&lt;IDeleteProductService, DeleteProductService&gt;();
        services.AddScoped&lt;IGetProductListService, GetProductListService&gt;();
        services.AddScoped&lt;IGetProductItemService, GetProductItemService&gt;();

        <span class="hljs-keyword">return</span> services;
    }
}
</code></pre>
<p>Application katmanını geliştirme sürecini tamamladık. Son olarak HttpApi katmanını kodlayalım ve entegrasyon testini yazma sürecine girelim.</p>
<p>Yapılan geliştirmeleri bu <a target="_blank" href="https://github.com/serhatleventyavas/IntegrationTestAspNetCoreExample/tree/application">linkten</a> erişebilirsiniz.</p>
<h2 id="heading-httpapi-katmanini-kodlayalim">HttpApi Katmanını Kodlayalım.</h2>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1711884674384/f1c81c64-ff20-4541-9312-118e166af915.png" alt class="image--center mx-auto" /></p>
<p>HttpApi katmanı bir sunum (presentation) katmanıdır. Bu katman ile gelen istekleri karşılayıp ilgili uygulama katmanındaki servislere yönlendireceğiz ve gelen cevapları istemciye geri döneceğiz. Ayrıca bu katman sayesinde merkezi hata yakalama altyapısı kurarak, sistemde oluşan hataları yakalayıp, işleyebileceğiz. Son olarak bu katmanda veritabanımızı, tablolarımızı oluşturmak için bir migration versiyonu oluşturacağız ve onu veritabanına aktaracağız.</p>
<p>İlk önce <strong>HttpApi</strong> katmanı, <strong>Infrastructure</strong> katmanına erişebilmesi için ilgili referansı ekliyoruz.</p>
<pre><code class="lang-powershell">dotnet add .\HttpApi\HttpApi.csproj reference .\Infrastructure\Infrastructure.csproj
</code></pre>
<p>Daha sonra veritabanı bağlantısı bilgisini <strong>appsettings.Development.json</strong> dosyası içerisine ekliyoruz.</p>
<pre><code class="lang-json">{
  <span class="hljs-attr">"Logging"</span>: {
    <span class="hljs-attr">"LogLevel"</span>: {
      <span class="hljs-attr">"Default"</span>: <span class="hljs-string">"Information"</span>,
      <span class="hljs-attr">"Microsoft.AspNetCore"</span>: <span class="hljs-string">"Warning"</span>
    }
  },
  <span class="hljs-attr">"ConnectionStrings"</span>: {
    <span class="hljs-attr">"Database"</span>: <span class="hljs-string">"Host=localhost;Port=5432;Database=Demo;Username=postgres;Password=password"</span>
  },
  <span class="hljs-attr">"AllowedHosts"</span>: <span class="hljs-string">"*"</span>
}
</code></pre>
<p><strong>Program.cs</strong> her aspnetcore projesinde gördüğümüz c# dosyasıdır. Bu dosyada bir <strong>WebApplication</strong> ayağa kaldırmak için gerekli kurulumları, konfigürasyonları yaparız. Aşağıdaki kod parçasında <strong>Application</strong> ve <strong>Infrastructure</strong> katmanlarının <strong>AddInfrastructure</strong> ve <strong>AddApplication extension methodlarını</strong> çağırdık. Bu şekilde uygulamamız çalışmaya başladığı zaman yazmış olduğumuz kurulumlar ve konfigürasyonlar işlenmiş olacak. Merkezi olarak web uygulamamız çalışırken oluşan hataları yakalamak için GlobalExceptionHandler sınıfını geliştirdik. Ayrıca oluşturmuş olduğumuz migration dosyasının postgres veritabanından işlenmesi için migration işlemini de yapıyoruz.</p>
<pre><code class="lang-csharp"><span class="hljs-keyword">using</span> Application;
<span class="hljs-keyword">using</span> HttpApi;
<span class="hljs-keyword">using</span> Infrastructure;
<span class="hljs-keyword">using</span> Microsoft.EntityFrameworkCore;

<span class="hljs-keyword">var</span> builder = WebApplication.CreateBuilder(args);

builder.Services.AddControllers(options =&gt; { options.SuppressAsyncSuffixInActionNames = <span class="hljs-literal">false</span>; });
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
builder.Services.AddInfrastructure(builder.Configuration);
builder.Services.AddApplication();
builder.Services.AddExceptionHandler&lt;GlobalExceptionHandler&gt;();
builder.Services.AddProblemDetails();

<span class="hljs-keyword">var</span> app = builder.Build();

<span class="hljs-keyword">if</span> (app.Environment.IsDevelopment())
{
    app.UseSwagger();
    app.UseSwaggerUI();
}

app.UseExceptionHandler();
app.UseHttpsRedirection();
app.MapControllers();


<span class="hljs-keyword">using</span> (<span class="hljs-keyword">var</span> scope = app.Services.CreateScope())
{
    <span class="hljs-keyword">var</span> services = scope.ServiceProvider;
    <span class="hljs-keyword">var</span> applicationDbContext = services.GetRequiredService&lt;ApplicationDbContext&gt;();
    applicationDbContext.Database.Migrate();
}

app.Run();

<span class="hljs-keyword">public</span> <span class="hljs-keyword">abstract</span> <span class="hljs-keyword">partial</span> <span class="hljs-keyword">class</span> <span class="hljs-title">Program</span>;
</code></pre>
<pre><code class="lang-csharp"><span class="hljs-keyword">using</span> System.Text;
<span class="hljs-keyword">using</span> System.Text.Json;
<span class="hljs-keyword">using</span> Microsoft.AspNetCore.Diagnostics;

<span class="hljs-keyword">namespace</span> <span class="hljs-title">HttpApi</span>;

<span class="hljs-function"><span class="hljs-keyword">internal</span> <span class="hljs-keyword">sealed</span> class <span class="hljs-title">GlobalExceptionHandler</span>(<span class="hljs-params">ILogger&lt;GlobalExceptionHandler&gt; logger</span>) : IExceptionHandler</span>
{
    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">async</span> ValueTask&lt;<span class="hljs-keyword">bool</span>&gt; <span class="hljs-title">TryHandleAsync</span>(<span class="hljs-params">
        HttpContext httpContext,
        Exception exception,
        CancellationToken cancellationToken</span>)</span>
    {
        logger.LogError(
            exception, <span class="hljs-string">"Exception occurred: {Message}"</span>, exception.Message);

        httpContext.Response.ContentType = <span class="hljs-string">"application/json"</span>;

        httpContext.Response.StatusCode = StatusCodes.Status400BadRequest;
        <span class="hljs-keyword">var</span> jsonString = JsonSerializer.Serialize(<span class="hljs-keyword">new</span>
        {
            StatusCode = StatusCodes.Status400BadRequest,
            Message = <span class="hljs-string">"Bir hata oluştu"</span>
        });
        <span class="hljs-keyword">var</span> jsonBytes = Encoding.UTF8.GetBytes(jsonString);
        <span class="hljs-keyword">await</span> httpContext.Response.Body.WriteAsync(jsonBytes, cancellationToken);

        <span class="hljs-keyword">return</span> <span class="hljs-literal">true</span>;
    }
}
</code></pre>
<p><strong>Program.cs</strong> dosyası içerisinde gerekli kurulumları ve konfigürasyonları yazdıktan sonra <strong>ProductController</strong> sınıfımızı kodlayabiliriz. Bu kontroller sınıfı ile istemcilerden istekleri alacağız ve ilgili servisleri çağırarak istekleri işleyeceğiz ve servisten cevap geldiğinde istemciye geri döneceğiz.</p>
<pre><code class="lang-csharp"><span class="hljs-keyword">using</span> Application.Products.Create;
<span class="hljs-keyword">using</span> Application.Products.Delete;
<span class="hljs-keyword">using</span> Application.Products.GetItem;
<span class="hljs-keyword">using</span> Application.Products.GetList;
<span class="hljs-keyword">using</span> Application.Products.Update;
<span class="hljs-keyword">using</span> Domain;
<span class="hljs-keyword">using</span> HttpApi.Controllers.Products.RequestBodies;
<span class="hljs-keyword">using</span> Microsoft.AspNetCore.Mvc;

<span class="hljs-keyword">namespace</span> <span class="hljs-title">HttpApi.Controllers.Products</span>;

[<span class="hljs-meta">ApiController</span>]
[<span class="hljs-meta">Route(<span class="hljs-meta-string">"api/products"</span>)</span>]
<span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">sealed</span> class <span class="hljs-title">ProductController</span>
    (<span class="hljs-params">
        ICreateProductService createProductService,
        IUpdateProductService updateProductService,
        IDeleteProductService deleteProductService,
        IGetProductItemService getProductItemService,
        IGetProductListService getProductListService</span>): ControllerBase</span>
{
    [<span class="hljs-meta">HttpPost</span>]
    <span class="hljs-keyword">public</span> <span class="hljs-keyword">async</span> Task&lt;ActionResult&lt;Guid&gt;&gt; CreateAsync([FromBody] CreateProductRequestBody requestBody)
    {
        <span class="hljs-keyword">var</span> result = <span class="hljs-keyword">await</span> createProductService.Handle(<span class="hljs-keyword">new</span> CreateProductInput
        {
            Description = requestBody.Description,
            Name = requestBody.Name,
            Price = requestBody.Price,
            Quantity = requestBody.Quantity,
            ImageLink = requestBody.ImageLink
        });
        <span class="hljs-keyword">return</span> CreatedAtAction(<span class="hljs-keyword">nameof</span>(GetItemAsync), <span class="hljs-keyword">new</span> { id = result }, result);
    }

    [<span class="hljs-meta">HttpPut(template: <span class="hljs-meta-string">"{id:guid}"</span>)</span>]
    <span class="hljs-keyword">public</span> <span class="hljs-keyword">async</span> Task&lt;ActionResult&lt;Product&gt;&gt; UpdateAsync(Guid id, [FromBody] UpdateProductRequestBody requestBody)
    {
        <span class="hljs-keyword">var</span> result = <span class="hljs-keyword">await</span> updateProductService.Handle(<span class="hljs-keyword">new</span> UpdateProductInput
        {
            Id = id,
            Description = requestBody.Description,
            Name = requestBody.Name,
            Price = requestBody.Price,
            Quantity = requestBody.Quantity,
            ImageLink = requestBody.ImageLink
        });
        <span class="hljs-keyword">return</span> Ok(result);
    }

    [<span class="hljs-meta">HttpDelete(template: <span class="hljs-meta-string">"{id:guid}"</span>)</span>]
    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">async</span> Task&lt;ActionResult&gt; <span class="hljs-title">DeleteAsync</span>(<span class="hljs-params">Guid id</span>)</span>
    {
        <span class="hljs-keyword">await</span> deleteProductService.Handle(<span class="hljs-keyword">new</span> DeleteProductInput
        {
            Id = id
        });
        <span class="hljs-keyword">return</span> NoContent();
    }

    [<span class="hljs-meta">HttpGet(template: <span class="hljs-meta-string">"{id:guid}"</span>)</span>]
    <span class="hljs-keyword">public</span> <span class="hljs-keyword">async</span> Task&lt;ActionResult&lt;Product&gt;&gt; GetItemAsync(Guid id)
    {
        <span class="hljs-keyword">var</span> result = <span class="hljs-keyword">await</span> getProductItemService.Handle(<span class="hljs-keyword">new</span> GetProductItemInput
        {
            Id = id
        });

        <span class="hljs-keyword">return</span> Ok(result);
    }

    [<span class="hljs-meta">HttpGet</span>]
    <span class="hljs-keyword">public</span> <span class="hljs-keyword">async</span> Task&lt;ActionResult&lt;List&lt;Product&gt;&gt;&gt; GetListAsync()
    {
        <span class="hljs-keyword">var</span> result = <span class="hljs-keyword">await</span> getProductListService.Handle();
        <span class="hljs-keyword">return</span> Ok(result);
    }
}
</code></pre>
<pre><code class="lang-csharp">
<span class="hljs-keyword">namespace</span> <span class="hljs-title">HttpApi.Controllers.Products.RequestBodies</span>;

<span class="hljs-keyword">public</span> <span class="hljs-keyword">sealed</span> <span class="hljs-keyword">record</span> <span class="hljs-title">CreateProductRequestBody</span>
{
    <span class="hljs-keyword">public</span> required <span class="hljs-keyword">string</span> Name { <span class="hljs-keyword">get</span>; <span class="hljs-keyword">set</span>; } = <span class="hljs-literal">null</span>!;
    <span class="hljs-keyword">public</span> required <span class="hljs-keyword">string</span> Description { <span class="hljs-keyword">get</span>; <span class="hljs-keyword">set</span>; } = <span class="hljs-literal">null</span>!;
    <span class="hljs-keyword">public</span> required <span class="hljs-keyword">string</span> ImageLink { <span class="hljs-keyword">get</span>; <span class="hljs-keyword">set</span>; } = <span class="hljs-literal">null</span>!;
    <span class="hljs-keyword">public</span> required <span class="hljs-keyword">decimal</span> Price { <span class="hljs-keyword">get</span>; <span class="hljs-keyword">set</span>; }
    <span class="hljs-keyword">public</span> required <span class="hljs-keyword">int</span> Quantity { <span class="hljs-keyword">get</span>; <span class="hljs-keyword">set</span>; }
}
</code></pre>
<pre><code class="lang-csharp"><span class="hljs-keyword">namespace</span> <span class="hljs-title">HttpApi.Controllers.Products.RequestBodies</span>;

<span class="hljs-keyword">public</span> <span class="hljs-keyword">sealed</span> <span class="hljs-keyword">record</span> <span class="hljs-title">UpdateProductRequestBody</span>
{
    <span class="hljs-keyword">public</span> required <span class="hljs-keyword">string</span> Name { <span class="hljs-keyword">get</span>; <span class="hljs-keyword">set</span>; } = <span class="hljs-literal">null</span>!;
    <span class="hljs-keyword">public</span> required <span class="hljs-keyword">string</span> Description { <span class="hljs-keyword">get</span>; <span class="hljs-keyword">set</span>; } = <span class="hljs-literal">null</span>!;
    <span class="hljs-keyword">public</span> required <span class="hljs-keyword">string</span> ImageLink { <span class="hljs-keyword">get</span>; <span class="hljs-keyword">set</span>; } = <span class="hljs-literal">null</span>!;
    <span class="hljs-keyword">public</span> required <span class="hljs-keyword">decimal</span> Price { <span class="hljs-keyword">get</span>; <span class="hljs-keyword">set</span>; }
    <span class="hljs-keyword">public</span> required <span class="hljs-keyword">int</span> Quantity { <span class="hljs-keyword">get</span>; <span class="hljs-keyword">set</span>; }
}
</code></pre>
<p><strong>HttpApi</strong> katmanını geliştirme sürecini tamamladık ve beklenen an geldi. Şimdi entegrasyon testini yazma sürecine girelim.</p>
<p>Yapılan geliştirmeleri bu <a target="_blank" href="https://github.com/serhatleventyavas/IntegrationTestAspNetCoreExample/tree/httpApi">linkten</a> erişebilirsiniz.</p>
<h2 id="heading-entegrasyon-testlerimizi-kodlayalim">Entegrasyon Testlerimizi Kodlayalım.</h2>
<p>Demo projemizi yazdıktan sonra geldik projemizi test etmeye. Bu örnekte application katmanındaki servislerimizi için testler yazacağız. Toplamda beş adet servisimiz var. Tüm bu servisler için <strong>happy path test senaryolarını</strong> yazacağız. Lafı çok uzatmadan hemen işe koyulalım. İlk önce yeni bir xunit test projesi oluşturuyoruz.</p>
<pre><code class="lang-powershell">dotnet new xunit <span class="hljs-literal">-o</span> tests/Application.IntegrationTests <span class="hljs-literal">-n</span> Application.IntegrationTests
</code></pre>
<p>Ardından bu projemizi solution içerisine ekliyoruz.</p>
<pre><code class="lang-powershell">dotnet sln add .\tests\Application.IntegrationTests\Application.IntegrationTests.csproj
</code></pre>
<p>Integration testlerimizi yapabilmemiz için test projesine iki adet paket yüklememiz gerekiyor. Birincisi her test senaryosu için temiz postgres veritabanını kaldırması için testcontainers ve ikincisi projemizi her test senaryosunda ayağa kaldırılması için AspNetCore Mvc Testing paketi.</p>
<pre><code class="lang-powershell"> dotnet add .\tests\application.IntegrationTests\Application.IntegrationTests.csproj package Testcontainers.PostgreSql
</code></pre>
<pre><code class="lang-powershell">dotnet add .\tests\application.IntegrationTests\Application.IntegrationTests.csproj package Microsoft.AspNetCore.Mvc.Testing
</code></pre>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1714909764305/c2b49163-2edb-42df-bddc-229765f8bd97.png" alt="Installed Packages in Integration Tests" class="image--center mx-auto" /></p>
<p>Tüm herşeyi doğru yaptıysak, paketlerimiz yukarıdaki görseldeki gibi olacaktır. Son olarakda HttpApi projemizi, test projemize referans veriyoruz.</p>
<pre><code class="lang-powershell">dotnet add .\tests\Application.IntegrationTests\Application.IntegrationTests.csproj reference .\HttpApi\HttpApi.csproj
</code></pre>
<p>Her test senaryomuzda, yeni bir test sunucusunun ayağa kalkması için <strong>WebApplicationFactory</strong> sınıfından türeyen <strong>IntegrationTestWebAppFactory</strong> isminde bir sınıf yazıyoruz. Bu sınıf içerisinde postgres veritabanıda ayağa kaldırıyoruz. Ayrıca <strong>ConfigureWebHost</strong> methodunu override ederek, entity framework konfigürasyonunu test ortamına göre düzenliyoruz.</p>
<pre><code class="lang-csharp"><span class="hljs-keyword">using</span> Infrastructure;
<span class="hljs-keyword">using</span> Microsoft.AspNetCore.Hosting;
<span class="hljs-keyword">using</span> Microsoft.AspNetCore.Mvc.Testing;
<span class="hljs-keyword">using</span> Microsoft.AspNetCore.TestHost;
<span class="hljs-keyword">using</span> Microsoft.EntityFrameworkCore;
<span class="hljs-keyword">using</span> Microsoft.Extensions.DependencyInjection;
<span class="hljs-keyword">using</span> Microsoft.Extensions.DependencyInjection.Extensions;
<span class="hljs-keyword">using</span> Testcontainers.PostgreSql;

<span class="hljs-keyword">namespace</span> <span class="hljs-title">Application.IntegrationTests</span>;

<span class="hljs-keyword">public</span> <span class="hljs-keyword">sealed</span> <span class="hljs-keyword">class</span> <span class="hljs-title">IntegrationTestWebAppFactory</span>: <span class="hljs-title">WebApplicationFactory</span>&lt;<span class="hljs-title">Program</span>&gt;, <span class="hljs-title">IAsyncLifetime</span>
{
    <span class="hljs-keyword">private</span> <span class="hljs-keyword">readonly</span> PostgreSqlContainer _postgresContainer = <span class="hljs-keyword">new</span> PostgreSqlBuilder()
        .WithImage(<span class="hljs-string">"postgres:latest"</span>)
        .WithUsername(<span class="hljs-string">"postgres"</span>)
        .WithPassword(<span class="hljs-string">"postgres"</span>)
        .WithDatabase(<span class="hljs-string">"demo"</span>)
        .Build();

    <span class="hljs-function"><span class="hljs-keyword">protected</span> <span class="hljs-keyword">override</span> <span class="hljs-keyword">void</span> <span class="hljs-title">ConfigureWebHost</span>(<span class="hljs-params">IWebHostBuilder builder</span>)</span>
    {
        builder.ConfigureTestServices(services =&gt;
        {
            <span class="hljs-keyword">var</span> connectionString = <span class="hljs-string">$"<span class="hljs-subst">{_postgresContainer.GetConnectionString()}</span>;Pooling=False"</span>;
            services.RemoveAll(<span class="hljs-keyword">typeof</span>(DbContextOptions&lt;ApplicationDbContext&gt;));
            services.AddDbContext&lt;ApplicationDbContext&gt;(options =&gt;
            {
                options.UseNpgsql(connectionString,
                        op =&gt; op.UseQuerySplittingBehavior(QuerySplittingBehavior.SplitQuery))
                    .EnableSensitiveDataLogging()
                    .UseSnakeCaseNamingConvention();
            });
        });
    }

    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">async</span> Task <span class="hljs-title">InitializeAsync</span>(<span class="hljs-params"></span>)</span>
    {
        <span class="hljs-keyword">await</span> _postgresContainer.StartAsync().ConfigureAwait(<span class="hljs-literal">false</span>);
    }

    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">async</span> Task <span class="hljs-title">DisposeAsync</span>(<span class="hljs-params"></span>)</span>
    {
        <span class="hljs-keyword">await</span> _postgresContainer.DisposeAsync().ConfigureAwait(<span class="hljs-literal">false</span>);
    }
}
</code></pre>
<p>Ardından <strong>BaseIntegrationTest</strong> adında yeni sınıf oluşturuyoruz. Bu sınıfa IClassFixture interfacesini implement ediyoruz. Bu sınıf içerisinde testlerimizi gerçekleştirirken ihtiyacımız olan servisleri hazırlıyoruz.</p>
<blockquote>
<p><strong>IClassFixture</strong>,Xunit test sınıfında kullanılan bir interface'dir. Belirli bir test sınıfı için ortak kurulum ve temizleme işlemlerini yapmak için kullanılır. Testler arasında paylaşılmak üzere sınıf düzeyinde sabit bir nesnenin yaratılmasını ve yok edilmesini sağlar. Bu, testlerin daha düzenli ve yönetilmesi daha kolay hale getirirken aynı zamanda test sürelerini azaltabilir çünkü ortak nesneler her test metodu için yeniden yaratılmaz.</p>
</blockquote>
<pre><code class="lang-csharp"><span class="hljs-keyword">using</span> Application.Products.Create;
<span class="hljs-keyword">using</span> Application.Products.Delete;
<span class="hljs-keyword">using</span> Application.Products.GetItem;
<span class="hljs-keyword">using</span> Application.Products.GetList;
<span class="hljs-keyword">using</span> Application.Products.Update;
<span class="hljs-keyword">using</span> Infrastructure;
<span class="hljs-keyword">using</span> Microsoft.Extensions.DependencyInjection;

<span class="hljs-keyword">namespace</span> <span class="hljs-title">Application.IntegrationTests</span>;

<span class="hljs-keyword">public</span> <span class="hljs-keyword">abstract</span> <span class="hljs-keyword">class</span> <span class="hljs-title">BaseIntegrationTest</span>: <span class="hljs-title">IClassFixture</span>&lt;<span class="hljs-title">IntegrationTestWebAppFactory</span>&gt;
{
    <span class="hljs-keyword">protected</span> <span class="hljs-keyword">readonly</span> ApplicationDbContext DbContext;
    <span class="hljs-keyword">protected</span> <span class="hljs-keyword">readonly</span> ICreateProductService createProductService;
    <span class="hljs-keyword">protected</span> <span class="hljs-keyword">readonly</span> IUpdateProductService updateProductService;
    <span class="hljs-keyword">protected</span> <span class="hljs-keyword">readonly</span> IDeleteProductService deleteProductService;
    <span class="hljs-keyword">protected</span> <span class="hljs-keyword">readonly</span> IGetProductItemService getProductItemService;
    <span class="hljs-keyword">protected</span> <span class="hljs-keyword">readonly</span> IGetProductListService getProductListService;

    <span class="hljs-function"><span class="hljs-keyword">protected</span> <span class="hljs-title">BaseIntegrationTest</span>(<span class="hljs-params">IntegrationTestWebAppFactory factory</span>)</span>
    {
        <span class="hljs-keyword">var</span> scope = factory.Services.CreateScope();
        DbContext = scope.ServiceProvider.GetRequiredService&lt;ApplicationDbContext&gt;();
        createProductService = scope.ServiceProvider.GetRequiredService&lt;ICreateProductService&gt;();
        updateProductService = scope.ServiceProvider.GetRequiredService&lt;IUpdateProductService&gt;();
        deleteProductService = scope.ServiceProvider.GetRequiredService&lt;IDeleteProductService&gt;();
        getProductItemService = scope.ServiceProvider.GetRequiredService&lt;IGetProductItemService&gt;();
        getProductListService = scope.ServiceProvider.GetRequiredService&lt;IGetProductListService&gt;();
    }
}
</code></pre>
<p><strong>IntegrationTestWebAppFactory</strong> ve <strong>BaseIntegrationTest</strong> sınıflarımızı yazdık. Artık testlerimizi yazma vakti geldi. İlk önce ürün oluşturma servisimizin testini yazalım. <strong>BaseIntegrationTest</strong> sınıfından türeyen <strong>CreateProductTests</strong> adında bir sınıf oluşturuyoruz. Gerekli input objemizi oluşturup istek atıyoruz. İstek sonucunda gelen id değerinin Guid türünde olduğunu ve Empty bir guid olmadığını kontrol ediyoruz.</p>
<pre><code class="lang-csharp"><span class="hljs-keyword">using</span> Application.Products.Create;

<span class="hljs-keyword">namespace</span> <span class="hljs-title">Application.IntegrationTests.Products</span>;

<span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">sealed</span> class <span class="hljs-title">CreateProductTests</span>(<span class="hljs-params">IntegrationTestWebAppFactory factory</span>): <span class="hljs-title">BaseIntegrationTest</span>(<span class="hljs-params">factory</span>)</span>
{
    [<span class="hljs-meta">Fact</span>]
    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">async</span> Task <span class="hljs-title">Should_Create_Product</span>(<span class="hljs-params"></span>)</span>
    {
        <span class="hljs-keyword">var</span> input = <span class="hljs-keyword">new</span> CreateProductInput
        {
            Name = <span class="hljs-string">"IPhone 15 Pro"</span>,
            Description = <span class="hljs-string">"IPhone telefon"</span>,
            Price = <span class="hljs-number">100</span>_000,
            Quantity = <span class="hljs-number">100</span>,
            ImageLink = <span class="hljs-string">"https://s3.superproducts.com/iphone-15-pro.png"</span>
        };

        <span class="hljs-keyword">var</span> result = <span class="hljs-keyword">await</span> createProductService.Handle(input);
        Assert.IsType&lt;Guid&gt;(result);
        Assert.NotEqual(Guid.Empty, result);
    }
}
</code></pre>
<p>İkinci yazacağımız test ürün güncelleme testi olacak. <strong>BaseIntegrationTest</strong> sınıfından türeyen <strong>UpdateProductTests</strong> adında bir sınıf oluşturuyoruz. İlk önce veritabanımıza bir ürün ekliyoruz. Daha sonra gerekli input objemizi oluşturup istek servise atıyoruz. İstek sonucunda dönen ürünün null olmadığını ve input içerisinde verilen değerler ile ürünün değerlerini karşılaştırıyoruz.</p>
<pre><code class="lang-csharp"><span class="hljs-keyword">using</span> Application.Products.Update;
<span class="hljs-keyword">using</span> Domain;

<span class="hljs-keyword">namespace</span> <span class="hljs-title">Application.IntegrationTests.Products</span>;

<span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">sealed</span> class <span class="hljs-title">UpdateProductTests</span>(<span class="hljs-params">IntegrationTestWebAppFactory factory</span>): <span class="hljs-title">BaseIntegrationTest</span>(<span class="hljs-params">factory</span>)</span>
{
    [<span class="hljs-meta">Fact</span>]
    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">async</span> Task <span class="hljs-title">Should_Update_Product</span>(<span class="hljs-params"></span>)</span>
    {
        <span class="hljs-keyword">var</span> product = Product.Create(
            name: <span class="hljs-string">"IPhone 15 Pro"</span>, 
            description: <span class="hljs-string">"IPhone telefon"</span>, 
            imageLink: <span class="hljs-string">"https://s3.superproducts.com/iphone-15-pro.png"</span>, 
            price: <span class="hljs-number">100</span>_000,
            quantity: <span class="hljs-number">100</span>);

        <span class="hljs-keyword">await</span> DbContext.Set&lt;Product&gt;().AddAsync(product);
        <span class="hljs-keyword">await</span> DbContext.SaveChangesAsync();

        <span class="hljs-keyword">var</span> input = <span class="hljs-keyword">new</span> UpdateProductInput
        {
            Id = product.Id,
            Name = <span class="hljs-string">"Updated IPhone 15 Pro"</span>,
            Description = <span class="hljs-string">"Updated IPhone telefon"</span>,
            Price = <span class="hljs-number">125</span>_000,
            Quantity = <span class="hljs-number">80</span>,
            ImageLink = <span class="hljs-string">"https://s3.superproducts.com/updated-iphone-15-pro.png"</span>
        };

        <span class="hljs-keyword">var</span> result = <span class="hljs-keyword">await</span> updateProductService.Handle(input);
        Assert.NotNull(result);
        Assert.IsType&lt;Product&gt;(result);
        Assert.Equal(input.Id, result.Id);
        Assert.Equal(input.Name, result.Name);
        Assert.Equal(input.Description, result.Description);
        Assert.Equal(input.Price, result.Price);
        Assert.Equal(input.Quantity, result.Quantity);
        Assert.Equal(input.ImageLink, result.ImageLink);
    }
}
</code></pre>
<p>Üçüncü yazacağımız test ürün silme testi olacak. <strong>BaseIntegrationTest</strong> sınıfından türeyen <strong>DeleteProductTests</strong> adında bir sınıf oluşturuyoruz. İlk önce veritabanımıza bir ürün ekliyoruz. Daha sonra gerekli input objemizi oluşturup istek servise atıyoruz. İstek tamamlandıktan sonra tekrardan <strong>DbContext</strong> üzerinden veritabanından ürünü sorguluyoruz ve sonucun null gelmesini bekliyoruz.</p>
<pre><code class="lang-csharp"><span class="hljs-keyword">using</span> Application.Products.Delete;
<span class="hljs-keyword">using</span> Domain;
<span class="hljs-keyword">using</span> Microsoft.EntityFrameworkCore;

<span class="hljs-keyword">namespace</span> <span class="hljs-title">Application.IntegrationTests.Products</span>;

<span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">sealed</span> class <span class="hljs-title">DeleteProductTests</span>(<span class="hljs-params">IntegrationTestWebAppFactory factory</span>): <span class="hljs-title">BaseIntegrationTest</span>(<span class="hljs-params">factory</span>)</span>
{
    [<span class="hljs-meta">Fact</span>]
    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">async</span> Task <span class="hljs-title">Should_Delete_Product</span>(<span class="hljs-params"></span>)</span>
    {
        <span class="hljs-keyword">var</span> product = Product.Create(
            name: <span class="hljs-string">"IPhone 15 Pro"</span>, 
            description: <span class="hljs-string">"IPhone telefon"</span>, 
            imageLink: <span class="hljs-string">"https://s3.superproducts.com/iphone-15-pro.png"</span>, 
            price: <span class="hljs-number">100</span>_000,
            quantity: <span class="hljs-number">100</span>);

        <span class="hljs-keyword">await</span> DbContext.Set&lt;Product&gt;().AddAsync(product);
        <span class="hljs-keyword">await</span> DbContext.SaveChangesAsync();

        <span class="hljs-keyword">var</span> input = <span class="hljs-keyword">new</span> DeleteProductInput
        {
            Id = product.Id
        };

        <span class="hljs-keyword">await</span> deleteProductService.Handle(input);

        <span class="hljs-keyword">var</span> deletedProduct = <span class="hljs-keyword">await</span> DbContext.Set&lt;Product&gt;().FirstOrDefaultAsync(p =&gt; p.Id == input.Id);
        Assert.Null(deletedProduct);
    }
}
</code></pre>
<p>Dördüncü yazacağımız test id ye göre ürün getirme testi olacak. <strong>BaseIntegrationTest</strong> sınıfından türeyen <strong>GetProductByIdTests</strong> adında bir sınıf oluşturuyoruz. İlk önce veritabanımıza bir ürün ekliyoruz. Daha sonra gerekli input objemizi oluşturup servise istek atıyoruz. İsteğin sonucunda dönen ürünün null olmadığını ve ürünün id değerinin istek atarken gönderdiğimiz id ile karşılatırıyoruz.</p>
<pre><code class="lang-csharp"><span class="hljs-keyword">using</span> Application.Products.GetItem;
<span class="hljs-keyword">using</span> Domain;

<span class="hljs-keyword">namespace</span> <span class="hljs-title">Application.IntegrationTests.Products</span>;

<span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">sealed</span> class <span class="hljs-title">GetProductByIdTests</span>(<span class="hljs-params">IntegrationTestWebAppFactory factory</span>): <span class="hljs-title">BaseIntegrationTest</span>(<span class="hljs-params">factory</span>)</span>
{
    [<span class="hljs-meta">Fact</span>]
    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">async</span> Task <span class="hljs-title">Should_Get_Product</span>(<span class="hljs-params"></span>)</span>
    {
        <span class="hljs-keyword">var</span> product = Product.Create(
            name: <span class="hljs-string">"IPhone 15 Pro"</span>,
            description: <span class="hljs-string">"IPhone telefon"</span>,
            imageLink: <span class="hljs-string">"https://s3.superproducts.com/iphone-15-pro.png"</span>,
            price: <span class="hljs-number">100</span>_000,
            quantity: <span class="hljs-number">100</span>);

        <span class="hljs-keyword">await</span> DbContext.Set&lt;Product&gt;().AddAsync(product);
        <span class="hljs-keyword">await</span> DbContext.SaveChangesAsync();

        <span class="hljs-keyword">var</span> result = <span class="hljs-keyword">await</span> getProductItemService.Handle(<span class="hljs-keyword">new</span> GetProductItemInput
        {
            Id = product.Id
        });

        Assert.NotNull(product);
        Assert.IsType&lt;Product&gt;(product);
        Assert.Equal(product.Id, result.Id);
    }
}
</code></pre>
<p>Beşinci ve son yazacağımız test ise ürün listesini getirme olacaktır. <strong>BaseIntegrationTest</strong> sınıfından türeyen <strong>GetProductListTests</strong> adında bir sınıf oluşturuyoruz. İlk önce veritabanımıza üç adet ürün ekliyoruz. Daha sonra gerekli input objemizi oluşturup servise istek atıyoruz. İsteğin sonucunda dönen ürün listesinin null olmaması gerektiğini ve liste içerisinde üç adet ürünün olması gerektiğini kontrol ediyoruz.</p>
<pre><code class="lang-csharp"><span class="hljs-keyword">using</span> Domain;

<span class="hljs-keyword">namespace</span> <span class="hljs-title">Application.IntegrationTests.Products</span>;

<span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">sealed</span> class <span class="hljs-title">GetProductListTests</span>(<span class="hljs-params">IntegrationTestWebAppFactory factory</span>): <span class="hljs-title">BaseIntegrationTest</span>(<span class="hljs-params">factory</span>)</span>
{
    [<span class="hljs-meta">Fact</span>]
    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">async</span> Task <span class="hljs-title">Should_Get_Product_List</span>(<span class="hljs-params"></span>)</span>
    {
        <span class="hljs-keyword">await</span> DbContext.Set&lt;Product&gt;().AddRangeAsync(
            [<span class="hljs-meta">
                Product.Create(
                    name: <span class="hljs-meta-string">"IPhone 15"</span>, 
                    description: <span class="hljs-meta-string">"IPhone telefon"</span>, 
                    imageLink: <span class="hljs-meta-string">"https://s3.superproducts.com/iphone-15.png"</span>, 
                    price: 80_000,
                    quantity: 100),
                Product.Create(
                    name: <span class="hljs-meta-string">"IPhone 15 Plus"</span>, 
                    description: <span class="hljs-meta-string">"IPhone telefon"</span>, 
                    imageLink: <span class="hljs-meta-string">"https://s3.superproducts.com/iphone-15-plus.png"</span>, 
                    price: 90_000,
                    quantity: 100),
                Product.Create(
                    name: <span class="hljs-meta-string">"IPhone 15 Pro"</span>, 
                    description: <span class="hljs-meta-string">"IPhone telefon"</span>, 
                    imageLink: <span class="hljs-meta-string">"https://s3.superproducts.com/iphone-15-pro.png"</span>, 
                    price: 100_000,
                    quantity: 100)
            </span>]
        );
        <span class="hljs-keyword">await</span> DbContext.SaveChangesAsync();

        <span class="hljs-keyword">var</span> products = <span class="hljs-keyword">await</span> getProductListService.Handle();
        Assert.NotNull(products);
        Assert.IsType&lt;List&lt;Product&gt;&gt;(products);
        Assert.Equal(<span class="hljs-number">3</span>, products.Count);
    }
}
</code></pre>
<p>Testlerimizi yazdıktan sonra çalıştıralım. Testlerimizin hepsi aşağıdaki görseldeki gibi başarılı bir şekilde tamamlanacaktır.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1714917917511/5d7209b5-eb36-489a-9b53-f411ad070fb2.png" alt="test results" class="image--center mx-auto" /></p>
<p>Peki şimdi biz ne yaptık?</p>
<p>Biz bu test altyapısı ile her test senaryosu için bir postgres veritabanı ayağa kaldırdık. Bunu yapabilmek için docker ve testcontainers kullandık. Testcontainers tüm süreci kendisi yönetti. Aşağıdaki görselde de bunu görebilirsiniz. Bir adet testcontainer kendisi için container ve her test senaryosu için bir postgres docker container'ı ayağa kaldırıldı. Testler tamamlandıkça containerlar öldürüldü ve ortadan kaldırıldı. Tüm testler tamamlandıktan sonra testcontainer için oluşturulan container da öldürüldü ve ortadan kaldırıldı. Biz bu şekilde tüm test senaryolarının birbirinden izole bir şekilde test ortamları kurulabiliriz ve test edilebiliriz.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1714917884600/865ac6ce-585a-477e-84de-5254aa120849.png" alt="docker postgres test containers" class="image--center mx-auto" /></p>
<p>Bu yazımda testcontainers ile nasıl integration test yazılır konusunu anlatmaya çalıştım. Sizde kendi integration testlerinizi testcontainers kullanarak kolaylıkla gerçekleştirebilirsiniz. Umarım sana dokunmuş olabilirim. Bu örnek projenin kaynak kodunu aşağıya bırakıyorum. Merak ettiğin bir şey olursa benimle iletişime geçebilirsin.</p>
<p>Projenin Son Hali : <a target="_blank" href="https://github.com/serhatleventyavas/IntegrationTestAspNetCoreExample">https://github.com/serhatleventyavas/IntegrationTestAspNetCoreExample</a></p>
]]></content:encoded></item><item><title><![CDATA[Kötü Yazılım Tasarımın Belirtileri]]></title><description><![CDATA[Biz yazılım geliştiricileri olarak amacımız bir ya da birden fazla yazılım dilini, kütüphaneleri, teknolojileri kullanarak ve belirli bir miktar kod yazarak ortaya çalışan bir yazılım ürünü çıkarmaktır. Ancak bu her zaman bizim yaptığımız işi kalitel...]]></description><link>https://serhatleventyavas.dev/kotu-yazilim-tasarimin-belirtileri</link><guid isPermaLink="true">https://serhatleventyavas.dev/kotu-yazilim-tasarimin-belirtileri</guid><category><![CDATA[Rigidity]]></category><category><![CDATA[Fragility]]></category><category><![CDATA[Immobility]]></category><category><![CDATA[Good Software]]></category><category><![CDATA[Bad Software]]></category><dc:creator><![CDATA[Serhat Levent Yavaş]]></dc:creator><pubDate>Wed, 20 Mar 2024 20:06:26 GMT</pubDate><content:encoded><![CDATA[<p>Biz yazılım geliştiricileri olarak amacımız bir ya da birden fazla yazılım dilini, kütüphaneleri, teknolojileri kullanarak ve belirli bir miktar kod yazarak ortaya çalışan bir yazılım ürünü çıkarmaktır. Ancak bu her zaman bizim yaptığımız işi kaliteli olduğu anlamına gelmez. Ortaya çıkan yazılımın kaliteli olduğunu anlayabilmemiz için <strong>esneklik</strong>, <strong>güvenlik</strong>, <strong>güvenilirlik</strong>, <strong>ölçeklenebilirlik</strong>, <strong>modülarite</strong>, <strong>adaptasyon</strong> gibi bazı durumlara bakmamız gerekir. Bu durumlar bize yazılımın kalitesi hakkında bilgi verir. Tüm bu durumlara uyacak bir kod yazabilmekte belirli tasarım kalıplarına, prensiblere uyarak geliştirmekten geçer. Kısacası bu kalıplara ve prensiblere uyarak yazılımda olumsuz sayılacak etkileri azaltabiliriz ve kaliteli bir yazılım ortaya çıkarabilirz.</p>
<p><strong>Rigidity — Esnemezlik</strong></p>
<p>Esnemezlik, bir yazılım sisteminin değişime ya da gelişime karşı gösterdiği dirençtir. Bir yazılım zaman içerisinde gerekli değişimleri ve istekleri karşılayabilecek şekilde gelişebilmeli ya da değişebilmelidir. Eğer bir sistem zaman içerisinde gelen talepleri karşılaması için gelişim ya da değişim yapılması isteniyorsa ve sistem buna aşırı direnç gösteriyorsa ya da hiç izin vermiyorsa biz bu sistemlere esnemez (rigidity) sistem diyoruz. Esnemez sistemlerde en ufak bir değişim ya da gelişim kod yapısında dalgalanma etkisi yaratabilir ve buda istenmeyen hatalara, kötü sonuçlara yol açabilir ya da kapsamlı yeniden bir çalışma yapmak zorunda bırakabilir. İyi bir tasarımda değişim ya da gelişime karşı direncin düşük olması beklenir. Bu şekilde olası değişimler ve gelişimler büyük sorunlar yaşanmadan rahatlıkla yapılabilir.</p>
<p><strong>Fragility — Kırılganlık</strong></p>
<p>Kırılganlık aslında esnemezlik ile yakından ilişkilidir. Biri olmazsa diğeride olmaz. Kırılganlık, bir yazılım sisteminin değişim ya da gelişim sonrası oluşan hata eğilimini ifade eder. Kırılgan sistemler, değişime ya da gelişime karşı oldukça hassastır ve kolayca bozulabilir. Belki bu hatalar bir domino etkisi yaratabilir ve birden çok yapının değişmesine neden olabilir. Bu değişim kontrol edilemez bir noktaya gelebilir ve çözülemeyen, devamlı oluşan kronik hatalara yol açabilir. Kırılganlığı yüksek ve esnekliği düşük sistemlerde bakım zor olduğundan dolayı bakım maliyetleri yüksektir. Bu tip sistemlerin tasarımı kötüdür.</p>
<p><strong>Immobility — Hareketsizlik</strong></p>
<p>Hareketsizlik, yazılım sistemindeki bir kod parçasının değişime, yeniden düzenlemeye ya da bir yerden başka bir yere taşımaya karşı oluşan zorluk ile ilgilidir. Modüler olmayan sistemlerde, kodların birbirlerine karşı olan yüksek bağımlılıkları sebebiyle bu problem ortaya çıkar. Hareketsizlik sistemin gelişimini ve değişimini engeller. Bakım ve geliştirme maliyetlerini artırır.</p>
<p>Yazılım sistemlerimizin iyi bir tasarıma sahip, değişimlere ve gelişimlere uyarlanabilir olmasını istiyorsak, modüler yapılar kurmaya, zayıf bağımlılıklar yaratmaya, tekrar kullanılabilen kodlar üretmeye, kohezyonu yüksek bileşenler ortaya çıkarmamız gerekiyor. Sistemlerimizde soyutlamayı mümkün oldukça yapmamız gerekiyor. KISS (Keep It Simple, Stupid), DRY (Don’t Repeat Yourself), YAGNI (You aren’t gonna need it), SOLID gibi prensipleri uygulamalıyız. Tasarım kalıpları üzerine çalışmalıyız ve onları aktif bir şekilde kullanmalıyız. İyi bir tasarıma sahip sistemlerin zamanla kötü bir tasarıma dönüşebileceğini unutmamalıyız. Mevcut kodumuzu incelemeli, gerektiğinde mevcut kodumuzu düzenlemeli ve kodumuzun kalitesini devamlı yüksek tutmalıyız. Yazılım sistemimizi tasarlarken testi göz önünde bulundurmalıyız. Test edilmesi kolay yapılar oluşturmayı ve yazılım güvenilirliğini sağlamak için birim testi, entegrasyon testi ve test odaklı geliştirme (TDD) gibi teknikleri kullanmalıyız.</p>
]]></content:encoded></item><item><title><![CDATA[Liskov Substitution Prensibi Nedir?]]></title><description><![CDATA[Liskov Substitution Prensibi
Liskov Substitution Prensibi, nesne yönelimli tasarımda ilgili sınıflar arasında ikame edilebilirliğin (substitutability) ve davranışsal alt tiplendirmenin (behavioral subtyping) önemini vurgulayan temel bir prensiptir. İ...]]></description><link>https://serhatleventyavas.dev/liskov-substitution-prensibi-nedir-6218eba21204</link><guid isPermaLink="true">https://serhatleventyavas.dev/liskov-substitution-prensibi-nedir-6218eba21204</guid><category><![CDATA[Liskov Substitution Principle]]></category><category><![CDATA[lsp]]></category><category><![CDATA[SOLID principles]]></category><category><![CDATA[solid]]></category><dc:creator><![CDATA[Serhat Levent Yavaş]]></dc:creator><pubDate>Mon, 27 Nov 2023 09:11:54 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1710966617725/e9032483-b485-46a9-b524-15ad18397eb1.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p><strong>Liskov Substitution Prensibi</strong></p>
<p><strong>Liskov Substitution Prensibi</strong>, nesne yönelimli tasarımda ilgili sınıflar arasında ikame edilebilirliğin <strong>(substitutability)</strong> ve davranışsal alt tiplendirmenin <strong>(behavioral subtyping)</strong> önemini vurgulayan temel bir prensiptir. İyi bir yazılım tasarımında olması gereken sürdürülebilirliği ve esnekliği teşvik eden beş <strong>SOLID</strong> ilkesinden biridir. <strong>LSP</strong> olarak kısaltılır. Bu prensip ilk kez 1987 yılında Barbara Liskov tarafından yayınlanan bir makale ile ortaya çıkmıştır. Barbara Liskov bu prensibin matematiksel formülünü aşağıdaki gibi tanımlamıştır.</p>
<p><strong>Tanım</strong></p>
<blockquote>
<p>If for each object o1 of type S there is an object o2 of type T such that for all programs P defined in terms of T, the behavior of P is unchanged when o1 is substituted for o2 then S is a subtype of T.</p>
</blockquote>
<p>Tanım biraz karışık geldiyse, beraber bu tanımı açıklamaya çalışalım. Elimizde T adında bir sınıf olsun ve T sınıfından o2 adında bir nesne üretelim ve P adındaki uygulamamızda kullanalım. Ardından bir S sınıfı oluşturalım ve S sınıfından o1 adında başka bir nesne oluşturalım. Daha sonra biz o2 nesnesi yerine o1 nesnesini koyabilirsek ve bunu yaparken uygulamanın davranışları değişmezse S sınıfı, T sınıfının bir alt tipidir diyebiliriz. Bu durumda T sınıfı <strong>üst sınıf (parent class)</strong>, S sınıfı ise <strong>alt sınıf (child class)</strong> diyebiliriz. Yani S sınıfı, T sınıfını miras almıştır ve T sınıfının tüm özelliklere sahiptir. Aslında bir çoğumuzda bu tanım nesne tabanlı programlamadaki <strong>inheritance</strong> konseptini aklımıza getiriyor olabilir. Böyle düşünmemiz doğrudur. Çünkü <strong>LSP</strong>, <strong>inheritance</strong> ile yakından ilişkilidir. <strong>LSP</strong>, <strong>inheritance</strong> nasıl uygulamamız konusunda bize bazı kurallar koyarak yol gösterir. Bu kurallar şunlardır:</p>
<ol>
<li><strong>Davranışı Koruma (Behavior Preservation)</strong>: Alt sınıflar, üst sınıflarının yerine geçebilmeli ve bu durumda programın doğruluğunu bozmamalıdır. Yani, bir alt sınıfın nesnesi, üst sınıfın nesnesi olarak kullanıldığında, beklenen davranışın sağlanması gerekmektedir.</li>
<li><strong>Değişmezler Korunmalıdır(Invariants must be preserved):</strong> Alt sınıflar, üst sınıflarının metot imzalarını (isim, parametreler, dönüş tipleri) korumalıdır. Bu, alt sınıf nesnelerinin üst sınıf nesneleri ile değiştirilebilir olmasını sağlar. Yani <strong>çok biçimlilik (Polymorphism)</strong> sağlar.</li>
<li><strong>Ön Koşullar Güçlendirilemez (Preconditions cannot be strengthened):</strong> Alt sınıflar, üst sınıflarının metotlarındaki ön koşulları güçlendirmemelidir. Üst sınıfın ön koşullarını ya da daha gevşek koşulları kabul etmelidir.</li>
<li><strong>Son Koşullar Zayıflatılamaz (Postconditions cannot be weakened):</strong> Alt sınıflar, üst sınıflarının metotlarındaki son koşulları zayıflatmamalıdır. Üst sınıfın son koşullarını ya da daha spesifik koşulları kabul etmelidir.</li>
<li><strong>İstisna Kuralı (Exception Rule):</strong> Eğer üst sınıfın bir metodu <strong>istisna (exception)</strong> fırlatıyorsa, alt sınıfın o metodu, üst sınıfın fırlattığı istisnaların aynısını ya da alt türlerini fırlatabilmelidir. Bu, istisna türlerinin uyumlu bir şekilde işlenmesini sağlar.</li>
</ol>
<p>Bu kurallara uyum sağlayarak geliştirlen sistemlerin tasarımları <strong>esnek, genişletilebilir</strong> ve <strong>sürdürebilir</strong> olur. Şimdi hep beraber bu prensibi ve kuralları örneklerle anlamaya çalışalım.</p>
<p>İlk örneğimizde Licence adında bir üst sınıf, PersonalLicence ve BusinessLicence adında iki alt sınıfımız var. Licence sınıfı içerisinde CalcFee adında bir metot var ve bu metot lisans ücretini hesaplıyor. PersonalLicence ve BusinessLicence sınıflarıda Licence sınıfını miras alıyor ve CalcFee metodunu ezerek<strong>(overriding)</strong> farklı hesaplamalar yaparak lisans bedelini dönüyor.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1710966611933/41b25fc0-1cae-433e-b599-a99bb1bcf8c5.png" alt /></p>
<p>Figure 1</p>
<p>public class Licence<br />{<br />    public virtual decimal CalcFee()<br />    {<br />        Console.WriteLine("Licence is calculating");<br />        // Some calculations<br />        return 120;<br />    }<br />}  </p>
<p>public sealed class PersonalLicence : Licence<br />{<br />    public override decimal CalcFee()<br />    {<br />        Console.WriteLine("Personal licence is calculating");<br />        // Some calculations<br />        return 85;<br />    }<br />}  </p>
<p>public sealed class BusinessLicence : Licence<br />{<br />    public override decimal CalcFee()<br />    {<br />        Console.WriteLine("Business licence is calculating");<br />        // Some calculations<br />        return 100;<br />    }<br />}</p>
<p>Ayrıca Billing adında bir sınıfımız var. Bu sınıf oluşturulurken Licence tipinde bir parametre alıyor.</p>
<p>public sealed class Billing<br />{<br />    private readonly Licence _licence;  </p>
<p>    public Billing(Licence licence)<br />    {<br />        _licence = licence;<br />    }  </p>
<p>    public void CalcFee()<br />    {<br />        _licence.CalcFee();<br />    }<br />}</p>
<p>Aşağıdaki kod parçasında ise üç farklı billing nesnesi oluşturdum. Nesneleri oluştururken Licence, PersonalLicence ve BusinessLicence nesnelerini sırasıyla parametre olarak verdim. Daha sonra her birinde CalcFee metodunu çağırdım.</p>
<p>var billing1 = new Billing(new Licence());<br />var billing2 = new Billing(new PersonalLicence());<br />var billing3 = new Billing(new BusinessLicence());<br />billing1.CalcFee();<br />billing2.CalcFee();<br />billing3.CalcFee();</p>
<p>Uygulamayı çalıştırdığımda aşağıdaki çıktıyı aldım.</p>
<p>Licence is calculating<br />Personal licence is calculating<br />Business licence is calculating</p>
<p>Bu örnekte üst sınıf olan Licence, Billing ile birlikte çalışabiliyor. Licence yerine Personal Licence ve Business Licence alt sınıflarının nesnelerini parametre olarak Billing servisine verdiğimizde uygulamamızın akışı bozulmadan çalışmaya devam edebiliyor. Böylece bu örnekte LSP’ye uygun bir şekilde geliştirme yapmış olduk.</p>
<p>İkinci örneğimizde LSP’yi ihlel eden bir senaryo oluşturmaya çalışalım. Calculator adında bir üst sınıf ve PositiveCalculator adında bir alt sınıfımız olsun. Calculator sınıfındaki Sum metodu 2 sayıyı alıyor ve toplayıp geri dönüyor. PositiveCalculator sınıfı ise Sum metodunu eziyor (overriding). Number1 ve number2 parametrelerine sıfır ya da sıfırdan büyük olma koşulu uyguluyor. Eğer bu koşul sağlanmaz ise hata fırlatıyor.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1710966613340/ef3370e7-39f6-4b2b-b195-31cb7e8567dc.png" alt /></p>
<p>Figure 2</p>
<p>public class Calculator<br />{<br />    public virtual int Sum(int number1, int number2)<br />    {<br />        return number1 + number2;<br />    }<br />}  </p>
<p>public sealed class PositiveCalculator : Calculator<br />{<br />    public override int Sum(int number1, int number2)<br />    {<br />        if (number1 &lt; 0)<br />        {<br />            throw new InvalidOperationException("number1 must be positive");<br />        }  </p>
<p>        if (number2 &lt; 0)<br />        {<br />            throw new InvalidOperationException("number1 must be positive");<br />        }  </p>
<p>        return number1 + number2;<br />    }<br />}</p>
<p>Calculator sınıfından bir nesne ürettiğimizde ve Sum metodunu çağırdığımızda uygulamamız beklediğimiz gibi çalışıyor.</p>
<p>Calculator calculator = new Calculator();<br />int result = calculator.Sum(-2, 8);  </p>
<p>if (result != 6)<br />{<br />    Console.WriteLine($"expected result 6, but result is {result}");<br />}<br />else<br />{<br />    Console.WriteLine("The result is as expected");<br />}</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1710966614558/9816deb6-e48a-4d4e-ad9e-2429cef3dda9.png" alt /></p>
<p>Figure 3</p>
<p>Şimdi aynı kod parçasını PositiveCalculator ile deneyelim.</p>
<p>Calculator calculator = new PositiveCalculator();<br />int result = calculator.Sum(-2, 8);  </p>
<p>if (result != 6)<br />{<br />    Console.WriteLine($"expected result 6, but result is {result}");<br />}<br />else<br />{<br />    Console.WriteLine("The result is as expected");<br />}</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1710966616015/ea3dfbd8-631f-4e18-abd8-9ba01cb6efd3.png" alt /></p>
<p>Figure 4</p>
<p>Calculator sınıfı yerine PositiveCalculator sınıfını kullandığımız zaman uygulamamızda beklenmedik bir şekilde bir hata aldık ve bu uygulamamızın akışını bozdu. Bu akışı düzeltmek için kodumuzu try catch bloğu içerisine alabiliriz. Ancak böyle bir değişim uygulamamızın akışını değiştirmek oluyor. Bizim amacımız üst sınıfın rahatlıkla çalışabildiği ortamlarda alt sınıflarında bir değişim olmadan rahatlıkla çalışabilmesi olmalıdır. PositiveCalculator sınıfı içerisindeki sum metodu içerisine eklediğimiz ön koşul yüzünden <strong>Ön koşullar güçlendirilemez</strong> kuralını ihlal ettik. <strong>Üst sınıfın bilmediği bir hatayı alt sınıfta fırlattığımız</strong> için ve istemci böyle bir hatadan haberdar olmadığı için uygulamamızın beklenmedik bir şekilde kapanmasına neden oldu. Son olarak uygulamanın akışı bozulduğu için <strong>Davranışı Koruma kuralını</strong> da ihlal ettik. Yani LSP’yi ihlal etmiş olduk.</p>
<p>LSP, geliştiricileri “<strong>is-a</strong>” ilişkisine tüm yönleriyle uymaya çalışan sınıflar ve hiyerarşiler tasarlamaya teşvik ederek <strong>sürdürülebilir</strong>, <strong>genişletilebilir</strong> ve <strong>öngörülebilir</strong> yazılım sistemleri oluşturmak için bir temel sağlar. LSP’yi uygulayarak daha <strong>modüler,</strong> uyumlu çalışabilen kodlar yazılabilir. Yeni sınıfların ve özelliklerin kolay entegrasyonu sağlanabilir. Kod tabanını zaman içerisinde oluşabilecek değişikliklere karşı daha <strong>dayanıklı</strong> hale getirebilir. LSP uyumluluğu, yüksek kaliteli nesne yönelimli kod yazmanın önemli bir yönüdür. Yazılım projelerinin genel sağlığına ve uzun ömürlülüğüne katkıda bulunur.</p>
<p>Bu yazımda <strong>Liskov Substitution Prensibini</strong> anlatmaya çalıştım. Umarım sana dokunmuş olabilirim. Merak ettiğin bir şey olursa benimle iletişime geçebilirsin.</p>
<p>— Bana ulaşmak için: <a target="_blank" href="https://twitter.com/serhatleventyvs">Twitter</a>, <a target="_blank" href="https://www.linkedin.com/in/serhatleventyavas/">Linkedin</a></p>
]]></content:encoded></item><item><title><![CDATA[Open-Closed Prensibi Nedir?]]></title><description><![CDATA[Yazılım geliştirmenin bir yaşam döngüsü vardır. İlk önce talebi ya da sorunu analiz eder ve gereksinimleri ortaya çıkarırız. Ardından analize göre sistemi tasarlarız. Tasarlama süreci bitirdikten sonra kodlama aşamasına geçeriz. Kodlama sürecinin ard...]]></description><link>https://serhatleventyavas.dev/open-closed-prensibi-nedir</link><guid isPermaLink="true">https://serhatleventyavas.dev/open-closed-prensibi-nedir</guid><category><![CDATA[OCP]]></category><category><![CDATA[Open Closed Principle]]></category><category><![CDATA[SOLID principles]]></category><dc:creator><![CDATA[Serhat Levent Yavaş]]></dc:creator><pubDate>Sun, 12 Nov 2023 19:21:10 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1710966621061/ba0a2ec1-d205-4cb8-bd37-6b769c1dde4e.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Yazılım geliştirmenin bir yaşam döngüsü vardır. İlk önce talebi ya da sorunu <strong>analiz</strong> eder ve gereksinimleri ortaya çıkarırız. Ardından analize göre sistemi <strong>tasarlarız</strong>. Tasarlama süreci bitirdikten sonra <strong>kodlama</strong> aşamasına geçeriz. Kodlama sürecinin ardından <strong>testlerimizi</strong> yapar ve en sonunda yazılımı <strong>canlıya alırız</strong>. Bu süreç kendini sürekli tekrar eder. Zaman ilerledikçe ihtiyaçları karşılamak için mevcut kod üzerinde eklemeler yapmak isteriz. Bu eklemeler basit olsa dahi mevcut kod üzerinde büyük değişikliklere neden oluyorsa, tasarım aşamasında büyük bir hata yapmışız demektir. Elimizde büyük bir legacy, birbirine sıkı bağımlı kod yapıları oluşmuş olabilir. Belki gerekli soyutlamaları ya doğru yapamadık ya da yanlış bir şekilde yaptık. Modüllerin, sınıfların, bileşenlerin birbirleri arasında sıkı bağımlı yapılar ortaya çıkardık. Aslında amacımız yazılım sistemlerini tasarlarken <strong>gevşek bağımlı(loose coupling)</strong>, <strong>yüksek kohezyonlu(high cohesion)</strong> ve <strong>doğru soyutlamaların(abstractions)</strong> olduğu yapılar ortaya çıkarmaktır. Tüm bunları prensiplerden ve patternlerden yardım alarak yapabiliriz. <strong>DRY (Don’t Repeat Yourself)</strong>, <strong>KISS (Keep It Simple, Stupid)</strong>, <strong>YAGNI (You Ain’t Gonna Need It)</strong>, <strong>Single Responsibility Principle</strong>, <strong>Open/Closed Principle</strong>, <strong>Liskov Substitution Principle</strong>, <strong>Interface Segregation Principle</strong>, <strong>Dependency Inversion Principle</strong> gibi bir çok prensip bulunuyor. Ayrıca <strong>Singleton Pattern</strong>, <strong>Factory Method Pattern</strong>, <strong>Observer Pattern</strong>, <strong>Decorator Pattern</strong>, <strong>Strategy Pattern</strong>, <strong>Adapter Pattern</strong>, <strong>Command Pattern</strong>, <strong>Composite Pattern</strong>, <strong>Template Method Pattern</strong>, <strong>State Pattern</strong> gibi bir çok pattern bulunuyor. Ben bu yazımda <strong>Open/Closed Prensibinden</strong> bahsetmeye çalışacağım.</p>
<p>Open-Closed prensibi, ben dahil bir çoğumuz için SOLID prensiplerini araştırırken karşısına çıkan 5 prensipten birisidir. “O” harfine karşılık gelir. <strong>OCP</strong> olarak kısaltırılır. Aslında SOLID arka arkaya bir kişi tarafından ortaya atılmış prensipler değildir. <strong>OCP</strong> 1988 yılında <a target="_blank" href="https://en.wikipedia.org/wiki/Bertrand_Meyer">Bertrand Meyer</a> tarafından ortaya atılmış bir prensiptir. Daha sonra <a target="_blank" href="https://en.wikipedia.org/wiki/Robert_C._Martin">Robert C. Martin</a> <strong>OCP</strong> ile 4 diğer prensibi bir bütün halinde insanlara aktarmaya çalışmıştır. Open-Closed prensibini ilk ortaya atan Bertrand Meyer prensibi aşağıdaki gibi açıklamıştır.</p>
<blockquote>
<p><strong>A software artifact (classes, modules, functions, etc.) should be open for extension but closed for modification.</strong></p>
</blockquote>
<p>Türkçe’ye çevirmek istersek; Bir yazılım genişlemeye açık, fakat değişime kapalı olmalıdır. Başka bir söylemle, bir yazılımının yapısını ve davranışını değiştirmek zorunda kalmadan, genişletilebilir olmasıdır. Bertrand Meyer çözüm olarak <strong>kalıtımı (inheritance)</strong> önermiştir. Kalıtım ile sınıfları genişletmeyi amaçlamıştır. Eğer bir sınıfın genişlemeye ihtiyacı varsa ondan türeyen yeni bir alt sınıf oluşturmayı önermiştir. Bu oluşturulan yeni alt sınıfa, ihtiyaç duyulan yeni değişkenler, methodlar eklenerek genişlemenin sağlanabileceğini belirtmiştir.</p>
<p>Günümüzde bu yöntem <strong>OCP</strong> için çok uygulanmasada tarihteki ilk tanımı ve çözümü bu şekilde ortaya atılmıştır. Ancak bu çözümün bazı sıkıntıları vardır. Öncelikle <strong>kalıtım</strong> (<strong>inheritance)</strong> yoluyla oluşturulan alt sınıflar, üst sınıfın methodlarına, değişkenlerine bağlı ise iki sınıf arasında <strong>sıkı bir bağımlılık (tight coupling)</strong> oluşturur. Birbirine sıkı bağımlı yapılar esnemez ve kırılgan sistemler oluşturur. Bizim amacımız <strong>gevşek bağımlı (loose coupling)</strong> yapılar oluşturarak esnek sistemler geliştirmektir. Bir diğer problem ise c#, java, kotlin, swift gibi dillerde bir alt sınıfın sadece bir üst sınıftan türetilebilir olmasıdır. Bir sınıfa birden fazla yeni özellik eklemek istediğimizde, bunu tek bir kalıtım ile yapamayacağımız için devamlı birbirinden türeyen sınıflar oluşturmak zorunda kalacağız. Bu da yönetmemiz ve bakımını yapmamız gereken çok fazla sınıflar ortaya çıkaracaktır. Bir başka problem ise zaman içerisinde mevcut sınıflardan türetilen yeni sınıfların <strong>Liskov Substitution Prinsibini</strong> ihlal edebilir olmasıdır. Bu ihlal de bize başka teknik borçlara ve problemlere yol açabilir.</p>
<p>Robert C. Martin OCP’iyi tekrardan ele aldı ve yeni bir çözüm ortaya koydu. Bu çözümü oluştururken <strong>soyutlama (abstraction)</strong> ve <strong>çok biçimliliğin (polymorphism)</strong> güçlerinden faydalandı. Robert C. Martin üst sınıflar yerine <strong>arayüz(interface)</strong> kullanmayı tercih etti. Yeni gelen bir geliştirme talebi olduğunda, yeni bir sınıf oluşturdu ve ilgili <strong>arayüzü (interface)</strong> yeni sınıfa uyguladı**(implementation)** ve detayları oluşturduğu sınıf içerisinde tanımladı. Arayüzün kullanıldığı diğer sınıflarda herhangi bir değişiklik yapmadan yeni özellikleri ekledi. Yeni oluşturduğu sınıfları <strong>çok biçimlilik (polymorphism)</strong> sayesinde arayüzün kullandığı sınıflara parametre olarak verebilmiş oldu. Bu çözüm sayesinde gereksiz <strong>kalıtım (inheritance)</strong> işlemlerinden kurtuldu, <strong>soyutlama(abstraction)</strong> yaparak <strong>gevşek bağımlı(loose coupling)</strong> yapılar ortaya çıkardı ve esnek bir sistem kurmayı başardı.</p>
<p>Bu prensibi bir örnek ile anlatmaya çalışalım. Öncelikle Open-closed prensibini ihlal edebilecek bir kod yapısı üzerine konuşalım. Ardından bunu nasıl düzeltiriz ve nasıl Open-Closed prensibine uygun hale getirebiliriz bakalım. Örneğimizde müşterilerin türüne göre taşıma bedelini hesaplayalım. İş modelimize göre 2 farklı müşteri türümüz var. Bu müşteri türleri <strong>Default</strong> ve <strong>Silver</strong> olsun. Sistem içerisinde son 6 ayda yapılan taşıma sayısının 10 adedi geçmeyen müşterilere Default müşteri ve son 6 ayda yapılan taşıma sayısının 10 ve üzeri olduğu müşterilere de Silver müşteri diyelim. Default müşterilerimizden kilogram başına 50,00 TRY ve Silver müşterilerimizden kilogram başına 30,00 TRY ücret alınacak olsun. Örneğin gelen ağırlığımız 1.000 kg olursa, taşıma bedeli Default müşterilerimiz için 1.000 * 50,00 = 50.000 TRY ve Silver müşterilerimiz için 1.000 * 30,00 = 30.000 TRY olacaktır. Bu koşullara göre aşağıdaki kodu geliştirdiğimizi düşünelim.</p>
<pre><code class="lang-csharp"><span class="hljs-keyword">public</span> <span class="hljs-keyword">enum</span> CustomerType
{
    Default,
    Silver
}
</code></pre>
<pre><code class="lang-csharp"><span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title">ShippingService</span>
{
    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">double</span> <span class="hljs-title">CalculateShippingCost</span>(<span class="hljs-params">CustomerType customerType, <span class="hljs-keyword">double</span> totalWeightAsKg</span>)</span>
    {
        <span class="hljs-keyword">return</span> customerType <span class="hljs-keyword">switch</span>
        {
            CustomerType.Default =&gt; totalWeightAsKg * <span class="hljs-number">50.0</span>,
            CustomerType.Silver =&gt; totalWeightAsKg * <span class="hljs-number">30.0</span>,
            _ =&gt; <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> NotSupportedException(<span class="hljs-string">"Invalid customer type"</span>)
        };
    }
}
</code></pre>
<p>Elimizde <strong>CustomerType</strong> adında bir enum ve <strong>ShippingService</strong> adında bir sınıf var. Bu sınıf <strong>CalculateShippingCost</strong> adında bir method barındırıyor. Bu method parametre olarak CustomerType ve toplam taşınacak malın ağırlığını alıyor. CustomerType göre müşterinin kilogram başına ücreti belirleniyor ve toplam ağırlık ile çarpılarak taşıma bedeli ortaya çıkıyor. Bu istekleri karşılayan bir kod yazdık ve test ettik. Herşey doğru bir şekilde çalışıyor. Bir süre sonra iş modelimizin değiştiğini ve <strong>Gold</strong> türünde yeni bir müşteri türü daha eklendiğini öğrendik. Gold türündeki bir müşterinin fiyat hesaplaması yapılırken kilogram başına 15,00 TRY ücret belirlendiğini öğrendik. Bizim bu koşuluda karşılayacak geliştirmeyi yapmamız gerekiyor.</p>
<pre><code class="lang-csharp"><span class="hljs-keyword">public</span> <span class="hljs-keyword">enum</span> CustomerType
{
    Default,
    Silver,
    Gold
}
</code></pre>
<pre><code class="lang-csharp"><span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title">ShippingService</span>
{
    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">double</span> <span class="hljs-title">CalculateShippingCost</span>(<span class="hljs-params">CustomerType customerType, <span class="hljs-keyword">double</span> totalWeightAsKg</span>)</span> {
        <span class="hljs-keyword">return</span> customerType <span class="hljs-keyword">switch</span>
        {
            CustomerType.Default =&gt; totalWeightAsKg * <span class="hljs-number">50.0</span>,
            CustomerType.Silver =&gt; totalWeightAsKg * <span class="hljs-number">30.0</span>,
            CustomerType.Gold =&gt; totalWeightAsKg * <span class="hljs-number">15.0</span>,
            _ =&gt; <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> NotSupportedException(<span class="hljs-string">"Invalid customer type"</span>)
        };
    }
}
</code></pre>
<p>Yukarıdaki kod örneğinde gerekli geliştirmeyi yaptık. Peki, bu yaptığımız geliştirme yöntemi doğru bir yöntem mi? Bir sonraki talepte Platinum, Diamond gibi farklı müşteri türleri daha eklenmek istenirse ne yapacağız? Burayı gelip tekrardan gerekli yerlere eklemeler yapmak mı isteyeceğiz? Tüm bunlar Open-Closed prensibini ihlal eden davranışlardır. Her yeni bir talepte var olan kodu değiştirmek, çalıştığına emin olduğumuz kod parçaların tutarlığının tekrardan sorgulanmasına sebep olabilir. Bir değişim gördüğü için test edilmesi gerekir. Yapılan değişimler bize başka noktalarda başka hatalara sebep olabilir. Peki bunu Open-Closed prensibine göre nasıl geliştirebiliriz?</p>
<pre><code class="lang-csharp"><span class="hljs-keyword">public</span> <span class="hljs-keyword">interface</span> <span class="hljs-title">ICalculateShippingCostService</span>
{
    <span class="hljs-function"><span class="hljs-keyword">double</span> <span class="hljs-title">Calculate</span>(<span class="hljs-params"><span class="hljs-keyword">double</span> totalWeightAsKg</span>)</span>;
}
</code></pre>
<pre><code class="lang-csharp"><span class="hljs-keyword">public</span> <span class="hljs-keyword">sealed</span> <span class="hljs-keyword">class</span> <span class="hljs-title">DefaultCalculateShippingCostService</span> : <span class="hljs-title">ICalculateShippingCostService</span>
{
    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">double</span> <span class="hljs-title">Calculate</span>(<span class="hljs-params"><span class="hljs-keyword">double</span> totalWeightAsKg</span>)</span>
    {
        <span class="hljs-keyword">const</span> <span class="hljs-keyword">double</span> pricePerWeightAsKg = <span class="hljs-number">50.0</span>;
        <span class="hljs-keyword">var</span> totalPrice = pricePerWeightAsKg * totalWeightAsKg;
        <span class="hljs-keyword">return</span> totalPrice;
    }
}
</code></pre>
<pre><code class="lang-csharp"><span class="hljs-keyword">public</span> <span class="hljs-keyword">sealed</span> <span class="hljs-keyword">class</span> <span class="hljs-title">SilverCalculateShippingCostService</span> : <span class="hljs-title">ICalculateShippingCostService</span>
{
    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">double</span> <span class="hljs-title">Calculate</span>(<span class="hljs-params"><span class="hljs-keyword">double</span> totalWeightAsKg</span>)</span>
    {
        <span class="hljs-keyword">const</span> <span class="hljs-keyword">double</span> pricePerWeightAsKg = <span class="hljs-number">30.0</span>;
        <span class="hljs-keyword">var</span> totalPrice = pricePerWeightAsKg * totalWeightAsKg;
        <span class="hljs-keyword">return</span> totalPrice;
    }
}
</code></pre>
<pre><code class="lang-csharp"><span class="hljs-keyword">public</span> <span class="hljs-keyword">sealed</span> <span class="hljs-keyword">class</span> <span class="hljs-title">GoldCalculateShippingCostService</span> : <span class="hljs-title">ICalculateShippingCostService</span>
{
    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">double</span> <span class="hljs-title">Calculate</span>(<span class="hljs-params"><span class="hljs-keyword">double</span> totalWeightAsKg</span>)</span>
    {
        <span class="hljs-keyword">const</span> <span class="hljs-keyword">double</span> pricePerWeightAsKg = <span class="hljs-number">15.0</span>;
        <span class="hljs-keyword">var</span> totalPrice = pricePerWeightAsKg * totalWeightAsKg;
        <span class="hljs-keyword">return</span> totalPrice;
    }
}
</code></pre>
<pre><code class="lang-csharp"><span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title">ShippingService</span>
{
    <span class="hljs-keyword">private</span> <span class="hljs-keyword">readonly</span> ICalculateShippingCostService _calculateShippingCostService;

    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-title">ShippingService</span>(<span class="hljs-params">ICalculateShippingCostService calculateShippingCostService</span>)</span>
    {
        _calculateShippingCostService = calculateShippingCostService;
    }

    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">double</span> <span class="hljs-title">CalculateShippingCost</span>(<span class="hljs-params"><span class="hljs-keyword">double</span> totalWeightAsKg</span>)</span>
    {
        <span class="hljs-keyword">return</span> _calculateShippingCostService.Calculate(totalWeightAsKg);
    }
}
</code></pre>
<p>Yukarıdaki geliştirdiğimiz koda baktığımızda <strong>ICalculateShippingCostService</strong> adında bir arayüz(interface) oluşturduk. Arayüz içerisinde Calculate adında bir method tanımladık. Daha sonra <strong>DefaultCalculateShippingCostService</strong>, <strong>SilverCalculateShippingCostService</strong>, <strong>GoldCalculateShippingCostService</strong> adında 3 adet sınıf oluşturduk. Hepsi <strong>ICalculateShippingCostService</strong> arayüzünü uyguladı ve her biri Calculate methodu içerisinde gerekli geliştirmeler yapıldı. <strong>ShippingService</strong> refactor edip yeniden düzenledik. Artık <strong>constructor</strong> içerisinde <strong>ICalculateShippingCostService</strong> arayüzünü parametre olarak alıyoruz ve <strong>CalculateShippingCost</strong> methodu içerisinde arayüzün Calculate methodunu çağırıyoruz. Bu şekilde Open-Closed prensibi uygulayan bir kod yapısına sahip olduk. İleride Platinum, Diamond gibi farklı müşteri türleri gelsede mevcut kodu değiştirmeden yeni sınıflar oluşturarak mevcut sistemimizi genişletebileceğiz.</p>
<p>Özetlemek gerekirse, Open-Closed prensibi sistemimize esneklik katar. Sistemi genişlemeye açık olacak şekilde tasarladığımız için mevcut kodu değiştirmeden yeni özellikler, işlevler ekleyebiliriz. Değişime karşı direnci düşük seviyede tutarız. Ayrıca bakım maliyetleri daha az olur. Open-Closed prensibi ile mevcut kodu değiştirmediğimiz için mevcut kodumuzda hata oluşma riskini azaltmış oluruz. Ölçeklenebilir sistemler kurabiliriz. Yeni gereksinimler ortaya çıktıkça mevcut sistemimizi değiştirmek zorunda kalmadan yeni sınıflar ekleyebiliriz. Böylece modüler bir sistem kurmuş oluruz. Yeniden kullanılabilen kod bloklarını geliştirmiş oluruz. OCP’ye uygun yaptığımız örnekte de gördüğünüz gibi <strong>GoldCalculateShippingCostService</strong> adında bir sınıfımız var. Bu sınıfımız ihtiyaç halinde başka noktalarda da kullanabiliriz. Örneğimizde <strong>ShippingService</strong> içerisindeki <strong>CalculateShippingCost</strong> methodu sadece <strong>Calculate</strong> adında bir methodu çağırıyor**.** Bunun dışında hesaplama ile ilgili hiçbir bilgiye sahip değil. OCP sayesinde <strong>kapsülleme (encapsulation)</strong> yaparak uygulama ayrıntılarını gizleyebiliriz.</p>
<p>Bu yazımda Open-Closed prensibini anlatmaya çalıştım. Umarım sana dokunmuş olabilirim. Merak ettiğin bir şey olursa benimle iletişime geçebilirsin.</p>
]]></content:encoded></item><item><title><![CDATA[AWS Cdk kullanarak Serverless uygulamamızı nasıl oluştururuz?]]></title><description><![CDATA[AWS CDK (AWS Cloud Development Kit), uygulamalarımızı modellemek için ortak programlama dillerini (typescript, javascript, python, java, go, c#) kullanarak bulut ortamını geliştirme sürecini hızlandırır. AWS CDK ile aws paneli üzerinden yaptğımız her...]]></description><link>https://serhatleventyavas.dev/aws-cdk-kullanarak-serverless-uygulamamc4b1zc4b1-nasc4b1l-oluc59ftururuz-13fd3a9fd42d</link><guid isPermaLink="true">https://serhatleventyavas.dev/aws-cdk-kullanarak-serverless-uygulamamc4b1zc4b1-nasc4b1l-oluc59ftururuz-13fd3a9fd42d</guid><category><![CDATA[AWS]]></category><category><![CDATA[aws-cdk]]></category><category><![CDATA[CDK]]></category><dc:creator><![CDATA[Serhat Levent Yavaş]]></dc:creator><pubDate>Sat, 21 Oct 2023 19:00:52 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1710966677131/65749713-aa75-410e-9086-5fb2e6be38f0.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p><strong>AWS CDK (AWS Cloud Development Kit)</strong>, uygulamalarımızı modellemek için ortak programlama dillerini (typescript, javascript, python, java, go, c#) kullanarak bulut ortamını geliştirme sürecini hızlandırır. AWS CDK ile aws paneli üzerinden yaptğımız her süreci kodlayarak yapabiliriz. EC2 oluşturmak, RDS oluşturmak, VPC oluşturmak, S3 Bucket oluşturmak, Certificate Manager ile bir SSL oluşturmak, Route53 üzerinde bir subdomain eklemek ve tüm aws kaynaklarını birbirleri ile ilişkilendirmek gibi bir çok süreci kodlayarak yapabiliriz.</p>
<p>Bugünkü yazımızda <strong>AWS CDK</strong> ile serverless uygulama geliştireceğiz. <strong>Serverless compute</strong>, bir sunucunun sorumluluğunu almadan uygulamalarımızı ya da servislerimizi derlemeyi ve çalıştırmayı sağlayan sistemlerdir. <strong>Serverless</strong> ile <strong>uygun maliyetli</strong>, <strong>esnek, ölçeklenebilir</strong>, <strong>her zaman kullanılabilir</strong> sistemler tasarlayabiliriz. Tüm bunları yaparken sunucu yönetimi (kurulum, bakım, güvenlik) gibi önemli bir sorumluluğuda AWS bırakırız.</p>
<p>Bu örnek projemizde bir <strong>apigateaway</strong> servisi oluşturacağız ve bu oluşturduğumuz <strong>apigateaway</strong>, kullanıcıların isteklerini karşılayacak. Gelen istekleri <strong>lambda</strong> servislerine yönlendirecek. Lambda servisleri <strong>dynamodb</strong> üzerinde okuma ya da yazma işlemlerini yapacak ve cevabı geri döndürecek. Tüm bu süreçleri, typescript dili ve aws cdk kütüphanesi ile kodlayarak oluşturacağız. Modellediğimiz bulut altyapısınıda <strong>cloudformation</strong> ile deploy edeceğiz.</p>
<p>AWS CDK, Serverless ve yapacağımız uygulama hakkında kısaca bahsettik. Şimdi yapacağımız uygulamaya geçelim. Bir AWS Cdk uygulaması oluşturabilmemiz için bilgisayarımızda <strong>aws-cdk</strong> npm paketi global olarak yüklü olması gerekiyor.</p>
<p>npm install -g aws-cdk</p>
<p>Yukarıdaki kod ile <strong>aws-cdk</strong> paketini bilgisayarımıza indiriyoruz. Daha sonra <strong>mkdir</strong> ile yeni bir klasör oluşturuyoruz ve <strong>cd</strong> ile o klasörün içerisine geçiyoruz. <strong>cdk init</strong> ile bir aws cdk uygulaması oluşturuyoruz ve projenin dilinin <strong>typescript</strong> olacağını belirtiyoruz.</p>
<p>mkdir ProductServerlessApplication<br />cd ProductServerlessApplication<br />cdk init --language typescript</p>
<p>Bu işlem sonunda cdk uygulamamız oluşmuş oluyor. Uygulamayı vscode ile açtığımızda uygulamanın yapısı aşağıda görebilirsiniz.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1710966630147/29706170-1a9d-414a-a2a6-f76276efa8ed.png" alt /></p>
<p>cdk application structure</p>
<p><strong>bin</strong> klasörü genişlettiğimizde <strong>product_serverless_application.ts</strong> dosyasını görüyoruz. Bu dosyayı açtığımızda bir <strong>cdk.App</strong> nesnesi ve bir alt satırda ise <strong>ProductServerlessApplicationStack</strong> nesnesinin oluşturulduğunu görüyoruz.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1710966631973/38fdb442-f273-4bc7-888e-8511d664126d.png" alt /></p>
<p><strong>lib</strong> klasörünü genişleştiğimizde <strong>product_serverless_application-stack.ts</strong> adında bir dosya bulacağız. Bu dosyayı açtığımızda <strong>cdk.Stack</strong> miras alan bir <strong>ProductServerlessApplicationStack</strong> sınıfı görüyoruz.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1710966633953/2ec30457-8c49-40f5-81ec-045c8bbb15cd.png" alt /></p>
<p>Bir cdk uygulamasının yapısı aşağıdaki görseldeki gibidir. Her <strong>AWS CDK</strong> uygulamasında bir <strong>App</strong> ve bir ya da birden fazla <strong>Stack</strong> olabilir. <strong>cdk.Stack</strong> AWS <strong>CloudFormationdaki</strong> Stacklere karşılık gelir. Her bir <strong>stack</strong> deployment birimidir. <strong>Stackleri</strong> birbirinden bağımsız bir şekilde deploy edebiliriz ancak bir stack kapsamında tanımlanan tüm AWS kaynakların tümü birlikte deploy edilecektir. <strong>Construct,</strong> aws cdk uygulamasındaki temel yapı taşıdır. Her bir construct, aws’deki bulut bileşinini temsil eder. Bir <strong>construct</strong> içerisinde bir ya da birden fazla aws kaynakları bulunabilir. Bir <strong>stack</strong> içerisinde bir ya da birden fazla <strong>Construct</strong> olabilir.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1710966636139/0ceafd9d-c9a4-49cf-9bed-c219ee6d2993.png" alt /></p>
<p>AWS CDK uygulamasındaki temel konseptlerden kısaca bahsettikten sonra örnek uygulamamızda bir app, bir stack ve bir adet construct bulunacak.</p>
<p><strong>lib</strong> klasörü içerisine <strong>modules</strong> adında bir klasör oluşturdum. <strong>modules</strong> içerisine <strong>products</strong> adında başka bir klasör oluşturdum. Ayrıca hataları yönetebilmek için <strong>modules</strong> içerisine <strong>api-error.ts</strong> dosyasını oluşturdum.</p>
<p>export class ApiError extends Error {<br />  statusCode: number = 0;<br />  message: string = "";  </p>
<p>  constructor(statusCode: number, message: string) {<br />    super();<br />    this.statusCode = statusCode;<br />    this.message = message;<br />  }<br />}</p>
<p><strong>products</strong> klasörü altına <strong>services</strong> adında bir klasör oluşturdum. services klasörüne <strong>lambda servislerimizi</strong> koyacağız. <strong>products</strong> klasörü içerisine ayrıca <strong>product-construct.ts</strong> adında dosya oluşturdum. <strong>product-construct.ts</strong> dosyasını açıp product construct tanımlaması yaptım.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1710966637801/53a9af98-28cd-4448-ba8e-ada6c99e40a3.png" alt /></p>
<p>modules directory</p>
<p>import { Construct } from "constructs";  </p>
<p>export class ProductConstruct extends Construct {<br />  constructor(scope: Construct, id: string) {<br />    super(scope, id);<br />  }<br />}</p>
<p>ProductConstruct oluşturduktan sonra ProductServerlessApplicationStack gelip ProductConstruct nesnesini oluşturdum.</p>
<p>import * as cdk from "aws-cdk-lib";<br />import { Construct } from "constructs";<br />import { ProductConstruct } from "./modules";  </p>
<p>export class ProductServerlessApplicationStack extends cdk.Stack {<br />  constructor(scope: Construct, id: string, props?: cdk.StackProps) {<br />    super(scope, id, props);  </p>
<p>    new ProductConstruct(this, "Products");<br />  }<br />}</p>
<p>Bu şekilde bir construct’ı stack ile bağlamış olduk. Şimdi lambda servislerimizi yazalım. İlk yazacağımız servis ürün oluşturmamızı sağlayacak. Her ürünün bir id değeri olacak ve id değerini uuid şeklinde tutacağız. Bu yüzden <strong>uuid</strong> npm paketini indirmemiz gerekiyor.</p>
<p>npm install uuid<br />npm install --include\=dev @types/uuid</p>
<p>Yukarıdaki kodlar ile uuid ve uuid types paketlerini indirmiş olduk. Bu örnek uygulamada ürünlerimizi <strong>dynamodb</strong> üzerinde saklayacağız. Bu yüzden dynamodb client nesnesi oluşturabilmek, put command, update command, scan command sınıflarına erişebilmek için <strong>dynamodb</strong> npm paketini indirmeliyiz.</p>
<p>npm install @aws-sdk/client-dynamodb</p>
<p>Son olarak lambda sınıflarına, type tanımlamalarına erişebilmek için <strong>aws-lambda types</strong> npm paketini indirmeliyiz.</p>
<p>npm install --include\=dev @types/aws-lambda</p>
<p><strong>services</strong> klasörü içerisine <strong>create.ts</strong> adında bir dosya oluşturdum. Bir ürün oluşturabilmek için ürünün başlık, açıklama ve ücret parametreleri alıyoruz.</p>
<p>// create.ts<br />import { APIGatewayEvent, APIGatewayProxyResult, Context } from "aws-lambda";<br />import {<br />  AttributeValue,<br />  DynamoDBClient,<br />  PutItemCommand,<br />} from "@aws-sdk/client-dynamodb";<br />import { v4 as uuidv4 } from "uuid";<br />import { ApiError } from "../../api-error";  </p>
<p>export const handler = async (<br />  event: APIGatewayEvent,<br />  context: Context<br />): Promise =&gt; {<br />  try {<br />    const requestBodyJSON = JSON.parse(event.body ?? "{}");<br />    const title = (requestBodyJSON["title"] ?? "") as string;<br />    const description = (requestBodyJSON["description"] ?? "") as string;<br />    const price = (requestBodyJSON["price"] ?? "") as string;  </p>
<p>    if (title.length === 0) {<br />      throw new ApiError(400, "Title is required");<br />    }  </p>
<p>    if (description.length === 0) {<br />      throw new ApiError(400, "Description is required");<br />    }  </p>
<p>    if (price.length === 0) {<br />      throw new ApiError(400, "Price is required");<br />    }  </p>
<p>    if (isNaN(Number(price))) {<br />      throw new ApiError(400, "Price is not valid");<br />    }  </p>
<p>    const productId = uuidv4();  </p>
<p>    const client = new DynamoDBClient({});  </p>
<p>    const newRecord: Record = {<br />      id: {<br />        S: productId,<br />      },<br />      title: {<br />        S: title,<br />      },<br />      description: {<br />        S: description,<br />      },<br />      price: {<br />        N: parseFloat(price).toFixed(2),<br />      },<br />      is_deleted: {<br />        BOOL: false,<br />      },<br />      created_date: {<br />        S: new Date().toUTCString(),<br />      },<br />    };  </p>
<p>    const command = new PutItemCommand({<br />      TableName: "products",<br />      Item: newRecord,<br />    });  </p>
<p>    await client.send(command);  </p>
<p>    return {<br />      statusCode: 201,<br />      body: JSON.stringify({<br />        data: {<br />          title: title,<br />          description: description,<br />          price: price,<br />          id: productId,<br />        },<br />      }),<br />    };<br />  } catch (error) {<br />    if (error instanceof ApiError) {<br />      return {<br />        statusCode: error.statusCode,<br />        body: JSON.stringify({<br />          message: error.message,<br />        }),<br />      };<br />    }<br />    return {<br />      statusCode: 500,<br />      body: JSON.stringify({<br />        message: "Unknown Error",<br />      }),<br />    };<br />  }<br />};</p>
<p>Burada kullanıcıdan gelen başlık, açıklama ve ücret parametrelerini basit bir kaç kontrolden geçirdikten sonra dynamodb’ye yeni bir kayıt eklemek için <strong>PutItemCommand</strong> sınıfını kullanıyoruz ve oluşturulan nesneyi <strong>send</strong> methoduna parametre olarak vererek yeni bir ürün oluşturuyoruz.</p>
<p>Sırada bir ürünü güncellemek için kullanacağımız fonksiyonu yazalım. Bunun için <strong>services</strong> klasörü altında <strong>update.ts</strong> adında bir dosya oluşturuyoruz.</p>
<p>//update.ts<br />import { APIGatewayEvent, APIGatewayProxyResult, Context } from "aws-lambda";<br />import {<br />  AttributeValue,<br />  DynamoDBClient,<br />  UpdateItemCommand,<br />} from "@aws-sdk/client-dynamodb";<br />import { validate as uuidValidate } from "uuid";<br />import { ApiError } from "../../api-error";  </p>
<p>export const handler = async (<br />  event: APIGatewayEvent,<br />  context: Context<br />): Promise =&gt; {<br />  try {<br />    const pathParameters = event.pathParameters ?? {};<br />    const productId = pathParameters["id"] ?? "";  </p>
<p>    const requestBodyJSON = JSON.parse(event.body ?? "{}");<br />    const title = (requestBodyJSON["title"] ?? "") as string;<br />    const description = (requestBodyJSON["description"] ?? "") as string;<br />    const price = (requestBodyJSON["price"] ?? "") as string;  </p>
<p>    if (productId.length === 0) {<br />      throw new ApiError(400, "Product Id is required");<br />    }  </p>
<p>    if (!uuidValidate(productId)) {<br />      throw new ApiError(400, "Product Id is not valid");<br />    }  </p>
<p>    if (title.length === 0) {<br />      throw new ApiError(400, "Title is required");<br />    }  </p>
<p>    if (description.length === 0) {<br />      throw new ApiError(400, "Description is required");<br />    }  </p>
<p>    if (price.length === 0) {<br />      throw new ApiError(400, "Price is required");<br />    }  </p>
<p>    if (isNaN(Number(price))) {<br />      throw new ApiError(400, "Price is not valid");<br />    }  </p>
<p>    const client = new DynamoDBClient({});  </p>
<p>    const command = new UpdateItemCommand({<br />      TableName: "products",<br />      Key: {<br />        id: {<br />          S: productId,<br />        },<br />      },<br />      UpdateExpression:<br />        "set title = :title, description = :description, price = :price",<br />      ConditionExpression: "id = :id and is_deleted = :is_deleted",<br />      ExpressionAttributeValues: {<br />        ":title": { S: title },<br />        ":description": { S: description },<br />        ":price": { N: parseFloat(price).toFixed(2) },<br />        ":id": { S: productId },<br />        ":is_deleted": { BOOL: false },<br />      },<br />      ReturnValues: "ALL_NEW",<br />    });  </p>
<p>    await client.send(command);  </p>
<p>    return {<br />      statusCode: 200,<br />      body: JSON.stringify({<br />        data: {<br />          title: title,<br />          description: description,<br />          price: price,<br />          id: productId,<br />        },<br />      }),<br />    };<br />  } catch (error) {<br />    if (error instanceof ApiError) {<br />      return {<br />        statusCode: error.statusCode,<br />        body: JSON.stringify({<br />          message: error.message,<br />        }),<br />      };<br />    }<br />    return {<br />      statusCode: 500,<br />      body: JSON.stringify({<br />        message: "Unknown Error",<br />      }),<br />    };<br />  }<br />};</p>
<p>Ürünü güncellemek için ürün id, başlık, açıklama, ücret parametrelerini kullanıcıdan alıyoruz. Bunları kontrollerden geçirdikten sonra ürünü güncellemek için <strong>UpdateItemCommand</strong> sınıfını kullanarak bir nesne oluşturuyoruz. Daha sonra <strong>send</strong> methodu ile ürün verisini güncelliyoruz.</p>
<p>Sıradaki servisimiz ürün silme fonksiyonu. <strong>services</strong> klasörü içerisine <strong>delete.ts</strong> dosyasını oluşturuyoruz.</p>
<p>//delete.ts<br />import { APIGatewayEvent, APIGatewayProxyResult, Context } from "aws-lambda";<br />import { validate as uuidValidate } from "uuid";<br />import { ApiError } from "../../api-error";<br />import { DynamoDBClient, UpdateItemCommand } from "@aws-sdk/client-dynamodb";  </p>
<p>export const handler = async (<br />  event: APIGatewayEvent,<br />  context: Context<br />): Promise =&gt; {<br />  try {<br />    const pathParameters = event.pathParameters ?? {};<br />    const productId = pathParameters["id"] ?? "";  </p>
<p>    if (productId.length === 0) {<br />      throw new ApiError(400, "Product Id is required");<br />    }  </p>
<p>    if (!uuidValidate(productId)) {<br />      throw new ApiError(400, "Product Id is not valid");<br />    }<br />    const client = new DynamoDBClient({});  </p>
<p>    const command = new UpdateItemCommand({<br />      TableName: "products",<br />      Key: {<br />        id: {<br />          S: productId,<br />        },<br />      },<br />      UpdateExpression: "set is_deleted = :is_deleted",<br />      ConditionExpression: "id = :id and is_deleted = :current_is_deleted",<br />      ExpressionAttributeValues: {<br />        ":id": { S: productId },<br />        ":current_is_deleted": { BOOL: false },<br />        ":is_deleted": { BOOL: true },<br />      },<br />      ReturnValues: "ALL_NEW",<br />    });  </p>
<p>    await client.send(command);  </p>
<p>    return {<br />      statusCode: 200,<br />      body: JSON.stringify({<br />        message: "Product is deleted",<br />      }),<br />    };<br />  } catch (error) {<br />    if (error instanceof ApiError) {<br />      return {<br />        statusCode: error.statusCode,<br />        body: JSON.stringify({<br />          message: error.message,<br />        }),<br />      };<br />    }<br />    return {<br />      statusCode: 500,<br />      body: JSON.stringify({<br />        message: "Unknown Error",<br />      }),<br />    };<br />  }<br />};</p>
<p>Ürün silme fonksiyonu, ürün güncelleme fonksiyonu ile mantık olarak aynı. Bu örnekte ürünü veritabanından tamamen silmek yerine <strong>is_deleted</strong> değerini <strong>true</strong> yaparak silinmiş olarak işaretliyoruz.</p>
<p>Son olarak ürünleri getirecek olan fonksiyonumuzu yazalım. Bunun için <strong>services</strong> klasörü altına <strong>get-list.ts</strong> dosyasını açalım.</p>
<p>// get-list.ts<br />import { DynamoDBClient, ScanCommand } from "@aws-sdk/client-dynamodb";<br />import { APIGatewayEvent, APIGatewayProxyResult, Context } from "aws-lambda";<br />import { ApiError } from "../../api-error";  </p>
<p>export interface ProductDto {<br />  id: string;<br />  title: string;<br />  description: string;<br />  price: number;<br />}  </p>
<p>export const handler = async (<br />  event: APIGatewayEvent,<br />  context: Context<br />): Promise =&gt; {<br />  try {<br />    const client = new DynamoDBClient({});  </p>
<p>    const command = new ScanCommand({<br />      FilterExpression: "is_deleted = :is_deleted",<br />      ExpressionAttributeValues: {<br />        ":is_deleted": { BOOL: false },<br />      },<br />      TableName: "products",<br />    });  </p>
<p>    const response = await client.send(command);  </p>
<p>    const productList: ProductDto[] = (response.Items ?? []).map((p) =&gt; {<br />      return {<br />        id: p["id"].S ?? "",<br />        title: p["title"].S ?? "",<br />        description: p["description"].S ?? "",<br />        price: parseFloat(p["price"].N ?? "0.0"),<br />      };<br />    });  </p>
<p>    return {<br />      statusCode: 200,<br />      body: JSON.stringify({<br />        data: productList,<br />      }),<br />    };<br />  } catch (error) {<br />    if (error instanceof ApiError) {<br />      return {<br />        statusCode: error.statusCode,<br />        body: JSON.stringify({<br />          message: error.message,<br />        }),<br />      };<br />    }<br />    return {<br />      statusCode: 500,<br />      body: JSON.stringify({<br />        message: "Unknown Error",<br />      }),<br />    };<br />  }<br />};</p>
<p><strong>ScanCommand</strong> sınıfını dynamodb’den verileri çekmek için kullanırız. Burada <strong>is_deleted=false</strong> olan ürünleri istediğimiz için filtreme yaptık ve <strong>send</strong> methodu ile ürünleri dynamodb’den aldık. Temel crud işlemlerini yapan fonksiyonları yazdığımıza göre artık bunları lambda olarak tanımla işlemini geçebiliriz. Bunun için <strong>ProductConstruct</strong> içerisine geri dönüyoruz.</p>
<p>import { Construct } from "constructs";<br />import { NodejsFunction } from "aws-cdk-lib/aws-lambda-nodejs";<br />import { Runtime } from "aws-cdk-lib/aws-lambda";<br />import { join } from "path";<br />import { Duration } from "aws-cdk-lib";<br />import * as apigateway from "aws-cdk-lib/aws-apigateway";<br />import { AttributeType, Table } from "aws-cdk-lib/aws-dynamodb";<br />import * as cdk from "aws-cdk-lib";  </p>
<p>export class ProductConstruct extends Construct {<br />  constructor(scope: Construct, id: string) {<br />    super(scope, id);  </p>
<p>    const restApiId = "ProductApi";<br />    const dynamoProductTableName = "products";  </p>
<p>    const dynamoProductTable = new Table(this, dynamoProductTableName, {<br />      partitionKey: {<br />        name: "id",<br />        type: AttributeType.STRING,<br />      },<br />      tableName: dynamoProductTableName,<br />      removalPolicy: cdk.RemovalPolicy.RETAIN,<br />    });  </p>
<p>    const productApiGateaway = new apigateway.RestApi(this, restApiId, {<br />      restApiName: "Product Api",<br />      description: "Product Rest Api",<br />      deploy: true,<br />      deployOptions: {<br />        stageName: "prod",<br />        tracingEnabled: true,<br />      },<br />      endpointTypes: [apigateway.EndpointType.EDGE],<br />    });  </p>
<p>    const createProductFunction = new NodejsFunction(this, "CreateProduct", {<br />      runtime: Runtime.NODEJS_18_X,<br />      entry: join(__dirname, "services", "create.ts"),<br />      timeout: Duration.minutes(1),<br />      memorySize: 512,<br />    });  </p>
<p>    const putProductFunction = new NodejsFunction(this, "PutProduct", {<br />      runtime: Runtime.NODEJS_18_X,<br />      entry: join(__dirname, "services", "update.ts"),<br />      timeout: Duration.minutes(1),<br />      memorySize: 512,<br />    });  </p>
<p>    const deleteProductFunction = new NodejsFunction(this, "DeleteProduct", {<br />      runtime: Runtime.NODEJS_18_X,<br />      entry: join(__dirname, "services", "delete.ts"),<br />      timeout: Duration.minutes(1),<br />      memorySize: 512,<br />    });  </p>
<p>    const getProductListFunction = new NodejsFunction(this, "getProductList", {<br />      runtime: Runtime.NODEJS_18_X,<br />      entry: join(__dirname, "services", "get-list.ts"),<br />      timeout: Duration.minutes(1),<br />      memorySize: 512,<br />    });  </p>
<p>    dynamoProductTable.grantReadWriteData(createProductFunction);<br />    dynamoProductTable.grantReadData(getProductListFunction);<br />    dynamoProductTable.grantReadWriteData(putProductFunction);<br />    dynamoProductTable.grantReadWriteData(deleteProductFunction);  </p>
<p>    const getProductListFunctionIntegration = new apigateway.LambdaIntegration(<br />      getProductListFunction<br />    );  </p>
<p>    const createProductFunctionIntegration = new apigateway.LambdaIntegration(<br />      createProductFunction<br />    );  </p>
<p>    const putProductFunctionIntegration = new apigateway.LambdaIntegration(<br />      putProductFunction<br />    );  </p>
<p>    const deleteProductFunctionIntegration = new apigateway.LambdaIntegration(<br />      deleteProductFunction<br />    );  </p>
<p>    const productResource = productApiGateaway.root.addResource("products", {});<br />    const productResourceWithId = productResource.addResource("{id}");  </p>
<p>    productResource.addMethod("GET", getProductListFunctionIntegration);  </p>
<p>    productResource.addMethod("POST", createProductFunctionIntegration);  </p>
<p>    productResourceWithId.addMethod("PUT", putProductFunctionIntegration);  </p>
<p>    productResourceWithId.addMethod("DELETE", deleteProductFunctionIntegration);<br />  }<br />}</p>
<p>İlk önce bir <strong>dynamodb tablosuna</strong> ihtiyacımız olduğu için construct içerisinde tablo oluşturduk. <strong>partitionKey</strong> basit bir <strong>primary key’dir.</strong> Bu gerekli bir parametredir. ismini ve tipini belirttim. <strong>tableName</strong> tablonun adıdır. <strong>RemovalPolicy</strong> dynamodb tablosunun CloudFormation tarafından yönetilmesinin durdurulması durumunda tablonun durumunun ne olacağını kontrol eder. 3 farklı durum vardır:</p>
<ol>
<li>Destroy: Tabloyu yok et.</li>
<li>Retain: Tabloyu tutmaya devam et.</li>
<li>Snapshot: Tabloyu yok et, ancak yok etmeden önce bir kopyasını al ve onu sakla.</li>
</ol>
<p>Bir çok kaynakta <strong>Retain</strong> default seçenektir.</p>
<p>const dynamoProductTableName = "products";  </p>
<p>const dynamoProductTable = new Table(this, dynamoProductTableName, {<br />  partitionKey: {<br />    name: "id",<br />    type: AttributeType.STRING,<br />  },<br />  tableName: dynamoProductTableName,<br />  removalPolicy: cdk.RemovalPolicy.RETAIN,<br />});</p>
<p>Daha sonra bir <strong>apigateaway</strong> kaynağına ihtiyacımız var. Kullanıcıyı bir ürün oluşturmak, güncellemek, silmek ya da ürünleri getirmek istediğinde <strong>apigateaway</strong> ile karşılayacağız. <strong>apigateaway</strong> ile bir <strong>RestApi</strong> oluşturduk ve deploy değerini <strong>true</strong> yaptık. Bu şekilde rest api direk dış dünyaya açmış olduk. <strong>deployOptions</strong> ile <strong>stageName</strong> ve <strong>tracingEnabled</strong> değerlerimizi verdik. <strong>tracingEnabled</strong> değerine <strong>true</strong> vererek Amazon X-Ray izlemeyi aktifleştirdik. <strong>endpointTypes</strong> değerine ise <strong>apigateway.EndpointType.EDGE</strong> değerini verdik. Bu restApi’lar için varsayılan bir değerdir. <strong>EDGE</strong> dışında <strong>REGIONAL</strong> ve <strong>PRIVATE</strong> değerler vardır.</p>
<p>const restApiId = "ProductApi";<br />const productApiGateaway = new apigateway.RestApi(this, restApiId, {<br />  restApiName: "Product Api",<br />  description: "Product Rest Api",<br />  deploy: true,<br />  deployOptions: {<br />    stageName: "prod",<br />    tracingEnabled: true,<br />  },<br />  endpointTypes: [apigateway.EndpointType.EDGE],<br />});</p>
<p>Daha sonra <strong>NodeJs lambda fonksiyonlarını</strong> tanımladık. lambda fonksiyonlarımın çalışacağı <strong>runtime</strong> versiyonunu <strong>nodejs18</strong> seçtik. <strong>timeout</strong> olarak <strong>1</strong> <strong>dakika</strong> verdik. lambda fonksiyonun kullanacağı <strong>memorySize</strong> değerini <strong>512mb</strong> olarak belirledik. Son olarak <strong>entrye</strong> değer olarak lambda fonksiyonun çalıştıracağı handler fonksiyonlarını verdik.</p>
<p>const createProductFunction = new NodejsFunction(this, "CreateProduct", {<br />    runtime: Runtime.NODEJS_18_X,<br />    entry: join(__dirname, "services", "create.ts"),<br />    timeout: Duration.minutes(1),<br />    memorySize: 512,<br />  });  </p>
<p>  const putProductFunction = new NodejsFunction(this, "PutProduct", {<br />    runtime: Runtime.NODEJS_18_X,<br />    entry: join(__dirname, "services", "update.ts"),<br />    timeout: Duration.minutes(1),<br />    memorySize: 512,<br />  });  </p>
<p>  const deleteProductFunction = new NodejsFunction(this, "DeleteProduct", {<br />    runtime: Runtime.NODEJS_18_X,<br />    entry: join(__dirname, "services", "delete.ts"),<br />    timeout: Duration.minutes(1),<br />    memorySize: 512,<br />  });  </p>
<p>  const getProductListFunction = new NodejsFunction(this, "getProductList", {<br />    runtime: Runtime.NODEJS_18_X,<br />    entry: join(__dirname, "services", "get-list.ts"),<br />    timeout: Duration.minutes(1),<br />    memorySize: 512,<br />  });</p>
<p>Bu lambda fonksiyonların dynamodb ile etkileşime geçebilmesi için gerekli izinleri lambda servislerine vermemiz gerekiyor. Create, update, delete servislerine <strong>okuma(read)</strong> ve <strong>yazma(write)</strong> <strong>izinlerini,</strong> get list servisine ise sadece <strong>okuma (read) iznini</strong> verdik.</p>
<p>dynamoProductTable.grantReadWriteData(createProductFunction);<br />dynamoProductTable.grantReadData(getProductListFunction);<br />dynamoProductTable.grantReadWriteData(putProductFunction);<br />dynamoProductTable.grantReadWriteData(deleteProductFunction);</p>
<p>Oluşturduğumuz apigateaway ile lambda fonksiyonları entegre etmemiz gerekiyor.</p>
<p>const getProductListFunctionIntegration = new apigateway.LambdaIntegration(<br />  getProductListFunction<br />);  </p>
<p>const createProductFunctionIntegration = new apigateway.LambdaIntegration(<br />  createProductFunction<br />);  </p>
<p>const putProductFunctionIntegration = new apigateway.LambdaIntegration(<br />  putProductFunction<br />);  </p>
<p>const deleteProductFunctionIntegration = new apigateway.LambdaIntegration(<br />  deleteProductFunction<br />);</p>
<p>Lambda fonksiyonlarını apigateaway ile entegre ettikten sonra ise api kaynakları, methodları tanımlamamız gerekiyor. Bu şekilde apigateaway ile müşterimizinden istekleri alıp, gerekli lambda fonksiyonlarını çalıştırabileceğiz. Lambda fonksiyonlarımız dynamodb ile beraber ürün ekleyebilecek, güncelleyebilecek, silebilecek ve tüm ürünleri listeleyebilecek.</p>
<p>const productResource = productApiGateaway.root.addResource("products", {});<br />const productResourceWithId = productResource.addResource("{id}");  </p>
<p>productResource.addMethod("GET", getProductListFunctionIntegration);  </p>
<p>productResource.addMethod("POST", createProductFunctionIntegration);  </p>
<p>productResourceWithId.addMethod("PUT", putProductFunctionIntegration);  </p>
<p>productResourceWithId.addMethod("DELETE", deleteProductFunctionIntegration);</p>
<p>Sıradaki adım uygulamamızı <strong>cloudformation</strong> ile birlikte tüm kaynakları oluşturmak ve çalışabilir hale getirmek olacaktır. Bunu yapmak için önce uygulamamızın <strong>cloudformation</strong> tarafından anlaşılıp, çalıştırabilmesi için bir şablon oluşturmamız gerekiyor. Aşağıda görmekte olduğunuz kod parçası uygulamamızı çalıştırır ve uygulamamızdaki bulunan tüm kaynakları içeren bir <strong>cloudformation şablonuna</strong> çevirir. Oluşturulan şablon dosyası <strong>cdk.out</strong> klasörü içerisinde bulunur. Yine yazmış olduğumuz crud fonksiyonlarını da burada esbuild tarafından javascripte dönüşmüş hallerini bulabilirsiniz.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1710966639705/e48ccf78-c3c7-484d-bf30-6c040650bda5.png" alt /></p>
<p>cdk synth</p>
<p>Dosya format türü yaml’dır. <strong>cdk synth</strong> komutu çalıştığında <strong>esbuild</strong> ile nodejs lambda fonksiyonlarımızı javascript koduna dönüştürmek için <strong>dockera</strong> ihtiyaç duyacaktır. Eğer docker makinenizde yoksa ya da çalışmıyorsa aşağıdaki gibi bir hata alabilirsiniz.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1710966642166/083ef357-9c89-4969-9098-b228b59a46d7.png" alt /></p>
<p>docker error</p>
<p>Cloudformation şablonunu oluşturduktan sonra bunu deploy edebiliriz. Deploy edebilmemiz için <strong>cdk deploy</strong> komutunu kullanabiliriz. Bu işlemi yapabilmemiz için bir aws hesabına ihtiyacımız olacaktır. aws hesabınıza giriş yaptıktan sonra <strong>IAM Identity Center</strong> panelinden kullanıcılar ekranına gelip bir kullanıcı oluşturacağız.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1710966644331/ecdba60f-acad-41ea-a869-89f955c76d59.png" alt /></p>
<p>Add user butonuna tıklayalım.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1710966646384/6bbcfb18-1f9c-4992-a1c7-729e5212c299.png" alt /></p>
<p>Gerekli bilgileri girdikten sonra oluşturduğumuzun kullanıcının email adresine kurulum bilgilerini içeren bir email alacaktır. Bu email üzerinden şifresini oluşturabilecek ve aws paneline erişim sağlayabilecek.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1710966648578/2cd2629c-3714-4313-8c2a-929868b455dc.png" alt /></p>
<p>Bu işlemleri yaptıktan sonra kullacımızın paneli yukarıdaki ekran alıntısındaki gibi gözükecektir. AWS kaynaklarına erişim sağlayabilmesi için AWS account’u ile kullanıcıyı eşleştirmemiz gerekiyor.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1710966650490/d9da09e6-1461-4f6a-b59e-d96ad8a40fa1.png" alt /></p>
<p>AWS Accounts</p>
<p>Aws accounts sekmesine geliyoruz ve root altındaki AWS tıklıyoruz.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1710966652654/023b0e7b-a7a5-47b1-aa3d-182f904f73dc.png" alt /></p>
<p>AWS Account Detail</p>
<p><strong>Assign users or groups</strong> butonuna tıkladıktan sonra açılan ekranda oluşturduğumuz kullanıcıyı ve izin kümesini seçip süreci tamamlıyoruz. Eğer sistemde bir izin kümesi yoksa onu da <strong>permission sets</strong> sekmesinden oluşturabilirsiniz. Bu işlemi yaptıktan sonra kullanıcınız verilen izinler doğrultusunda kaynaklara erişim sağlayabilecektir.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1710966654632/3b449fbe-1cb1-4b1e-a721-f0c6f75e05fb.png" alt /></p>
<p>Bu işlemlerden sonra konsol üzerinden <strong>aws configure sso</strong> komutunu çağırarak kullanıcımızın profil bilgisini bilgisayarımız üzerinde oluşturmalıyız. Bizden birkaç bilgi istedikten sonra profilimiz başarılı bir şekilde oluşturmuş olduk.</p>
<p>aws configure sso</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1710966656109/83012cf7-e4b2-40c3-b312-046c3f3238cc.png" alt /></p>
<p>Son olarak profilimiz ile deployment sürecini başlatmalıyız. Bunun için <strong>aws deploy —profile [profile name]</strong> komutunu çalıştırmalıyız.</p>
<p>aws deploy --profile [profile name]</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1710966658409/2c54eda5-6e8a-44f7-8d7f-b2601869b35f.png" alt /></p>
<p>deployment process</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1710966660200/d3f8a829-02aa-4eac-a15f-0bd0f629c1e8.png" alt /></p>
<p>deploymen result</p>
<p>Deployment işlemi bittikten sonra bize api endpoint url bilgisini döndürdüğünü görebiliriz. Panelden cloudformation ekranına geldiğimizde yeni bir stack oluştuğunu görebiliriz.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1710966661716/218d43b0-24f4-4cc4-b583-2785ff66fe58.png" alt /></p>
<p>cloudformation</p>
<p>dynamodb tables ekranına geldiğimizde <strong>products</strong> isminde yeni bir dynamodb tablosu oluşturulduğunu görebiliriz.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1710966663309/9dfbc121-7c29-47dc-add5-1b6f04fcdbf7.png" alt /></p>
<p>dynamodb tables</p>
<p>apigateaway ekranına geldiğimizde yeni bir rest api oluşturulduğunu görebiliriz.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1710966665405/92473213-54a3-4b87-8c0b-976f646c521f.png" alt /></p>
<p>api gateaway</p>
<p>lambda ekranına geldiğimizde de lambda fonskiyonlarını görebiliriz.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1710966667180/75cbb00d-a9c6-4923-93f0-339d216f7f09.png" alt /></p>
<p>lambda functions</p>
<p>Herşey doğru bir şekilde oluşturulduğunu gördüğümüze göre beraber doğru bir şekilde çalıştıklarını test edelim.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1710966669353/1fc35d3a-8b86-4326-b79d-a80a6aeaf434.png" alt /></p>
<p>create product</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1710966671328/4dce3892-6668-481a-a094-a799a2e3e33a.png" alt /></p>
<p>update product</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1710966673481/02a9f66b-99ce-48a7-8b96-8fc4db67acbf.png" alt /></p>
<p>get product list</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1710966675622/1368b462-eff1-4465-a0c2-38b0368653f4.png" alt /></p>
<p>delete product</p>
<p>postman üzerinden yaptığımız testlerde rest api servislerimiz doğru şekilde çalıştığını görüyoruz. Eğer oluşturduğunuz stackleri yok etmek istiyorsanız cdk destroy — profile [profile name] komutunu çalıştırarak yok edebilirsiniz.</p>
<p>cdk destroy --profile [profile name]</p>
<p>Bu yazımızda aws cdk ile lambda fonksiyonları, dynamodb ve apigateaway birbirleri ile entegre şekilde çalıştırabildik. Tüm bu süreci tamamen infrastructure kod ile yaptık. Sizde istediğiniz aws kaynaklarını aws cdk ile oluşturabilirsiniz. Umarım sana dokunmuş olabilirim. Bu örnek projenin kaynak kodunu aşağıya bırakıyorum. Merak ettiğin bir şey olursa benimle iletişime geçebilirsin.</p>
<p>[<strong>GitHub - serhatleventyavas/ProductServerlessApplication</strong><br /><em>Contribute to serhatleventyavas/ProductServerlessApplication development by creating an account on GitHub.</em>github.com](https://github.com/serhatleventyavas/ProductServerlessApplication "https://github.com/serhatleventyavas/ProductServerlessApplication")<a target="_blank" href="https://github.com/serhatleventyavas/ProductServerlessApplication"></a></p>
]]></content:encoded></item><item><title><![CDATA[xUnit ile kodumuzu test edelim.]]></title><description><![CDATA[Geçenlerde çok sevdiğim bir arkadaşım geldi ve beraber bir wallet uygulaması geliştirelim dedi. Tamam olur yapalım dedim ve uygulamayı geliştirebilmek için mobil tarafta flutter ve backend tarafta .net core kullanmayı tercih ettik. İş planını yaptık ...]]></description><link>https://serhatleventyavas.dev/xunit-ile-kodumuzu-test-edelim-4c21f1b369b7</link><guid isPermaLink="true">https://serhatleventyavas.dev/xunit-ile-kodumuzu-test-edelim-4c21f1b369b7</guid><category><![CDATA[unit testing]]></category><category><![CDATA[xunit]]></category><category><![CDATA[C#]]></category><category><![CDATA[TDD (Test-driven development)]]></category><dc:creator><![CDATA[Serhat Levent Yavaş]]></dc:creator><pubDate>Mon, 16 Oct 2023 21:50:25 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1710966626538/7115586f-2da4-4f07-a57b-798263d234d0.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Geçenlerde çok sevdiğim bir arkadaşım geldi ve beraber bir wallet uygulaması geliştirelim dedi. Tamam olur yapalım dedim ve uygulamayı geliştirebilmek için mobil tarafta flutter ve backend tarafta .net core kullanmayı tercih ettik. İş planını yaptık ve arkadaşım flutter ile uygulamayı geliştirirken ben de .net core ile rest api servislerini geliştirecektim. Uygulamamızın amacı bir kullanıcı borçlarını takip edebileceği bir ortam oluşturmaktı. Kredi borçlarını, kredi kartı taksit ödemelerini ya da düzenli ödediği ne kadar ödeme varsa hepsini bir yerde görebildiği, grafikler ile renklendirilmiş raporları takip edebildiği bir uygulama olacaktı.</p>
<p>Uygulamayı konuştukça bir yandan da kafamda ilgili entity’leri düşünüyordum. Kullanıcılar için bir User entity, borçlar için bir Loan entity olacaktı. İlk amacımız kullanıcıları email ve şifre ile giriş yapmalarını sağlamaktı. Bunun için kayıt ol, giriş yap, şifremi unuttum gibi temel servisleri yazdık. İkinci aşamamız ise kullanıcı bir borç (loan) oluşturabilmesiydi. Arkadaşımla bir borç oluşturulurken hangi parametreleri almalı ve kayıt gerçekleşirken iş kurallarımız ne olacak onları belirledik.</p>
<ul>
<li>Loan Title: minimum 3 karakter ve maksimum 120 karakter olabilir.</li>
<li>Loan Description: boş olabilir ve maksimum 255 karakter olabilir.</li>
<li>Loan Type : boş olamaz, bizim sistem içerisinde belirlediğimiz loan type listesinin içerisinde olmalı.</li>
<li>Total Amount: boş olamaz ve negatif bir değer olamaz.</li>
<li>Total Currency: boş olamaz ve bizim sistem içerisinde belirlediğimiz currency type listesinin içerisinde olmalı.</li>
<li>Bir kullanıcı ücretsiz pakette maksimum 5 tane borç kaydı açabilir.</li>
</ul>
<p>Bu kurallara göre bir loan entity yazmam gerekiyordu. Proje küçük bir proje olmasına rağmen ben bu projede birim testlerini yazmak istedim. Birim testler yani unit test, kodumuzu test etmek için kullandığımız bir yöntemdir. Unit testler kodumuzdaki en küçük birimi test eder olarak öğreniriz ve bu şekilde uygulamaya çalışırız. Birim testler aslında kodumuzdaki bir davranışı (use case) bağımlılıklar olmadan test etmelidir. Projemizdeki her en küçük birim için testler yazarsak, kodumuzun hareket kabiliyetini kısıtlarız, kodu kendi ellerimiz ile kitleriz. Bir yerden sonra refactor ederken bile karşımızda direnç gösteren testler ile karşı karşıya kalırız. Bizim amacımız iş kurallarımızın doğru şekilde çalıştığına emin olmak ve bağımlılıklardan sıyrılmış birim testler yazmak olmalıdır.</p>
<p>Bu uygulamada yeni borç oluşturmak bir davranıştır ve bunu bağımlılıklar olmadan birim testler ile test etmeliyiz. Borcu kapamak ya da bir taksidini ödemek de uygulamadaki diğer davranışlardır. Hepsi uygulamadaki kullanıcı ile etkileşime geçen davranışlardır. Bizim amacımız bu davranışları birim testler ile bağımlılıklar olmadan test edebilmektir.</p>
<p>Daha fazla uzatmadan kolları sıvayıp servislerimizi geliştirmeye başlayalım. Projemizin solution oluşturmak için terminalden aşağıdaki kodu yazdım.</p>
<p>dotnet new sln -o Wallet</p>
<p>Bu kod Wallet klasörü içerisine boş bir solution açar. Wallet klasörü içerisine girelim. Şimdi projemizin domain katmanını oluşturmak ve solution ile bağlamak için terminale aşağıdaki kodu yazılım.</p>
<p>dotnet new classlib -o Wallet.Domain<br />dotnet sln add .\Wallet.Domain\</p>
<p>Projemizin application katmanını oluşturmak ve solution ile bağlamak için için aşağıdaki kodu yazalım.</p>
<p>dotnet new classlib -o Wallet.Application<br />dotnet sln add .\Wallet.Application\</p>
<p>Application katmanındaki servislerimizi test etmek için application test katmanını oluşturalım ve solution ile bağlayalım.</p>
<p>dotnet new xunit -o Wallet.Application.Tests<br />dotnet sln add .\Wallet.Application.Tests\</p>
<p>Son olarak application katmanının domain katmanına erişebilmesi ve application test katmanının application katmanına erişebilmesi için referansları ekleyelim.</p>
<p>dotnet add .\Wallet.Application.Tests\ reference .\Wallet.Application\<br />dotnet add .\Wallet.Application\ reference .\Wallet.Domain\</p>
<p>Projemizin gerekli kurulumları yaptıktan sonra ilk servisimiz olan borç oluşturma servisini yazabiliriz.</p>
<p>Servisimizi yazmadan önce servisimizin bir borç oluşturabilmek için ihtiyaç duyduğu parametreleri saklayan bir veri yapısına ihtiyacımız var. Bunun için aşağıdaki <strong>CreateLoanInput</strong> recordunu oluşturdum.</p>
<p>using Wallet.Domain.Users;  </p>
<p>namespace Wallet.Application.Loans.CreateLoan;  </p>
<p>public sealed record CreateLoanInput( string Title,<br />    string Description,<br />    string LoadTypeCode,<br />    decimal TotalAmount,<br />    string CurrencyCode,<br />    User User);</p>
<p>Daha sonra <strong>ICreateLoanService</strong> interfacesini oluşturdum.</p>
<p>using Wallet.Domain.Loans;  </p>
<p>namespace Wallet.Application.Loans.CreateLoan;  </p>
<p>public interface ICreateLoanService<br />{<br />    Task Handle(CreateLoanInput input, CancellationToken cancellationToken);<br />}</p>
<p>Neden bir interface ihtiyacımız var sorusu akıllarda oluşmuş olabilir. Projelerde amacımız bağımlılıkları mümkün oldukça soyutlamaktır. Somut yapılar projenin parçalarını birbirleri ile sıkı sıkıya bağlar ve birbirinden ayrılmasını zorlaştırır. Soyut yapılar ile bu bağımlılıkları gevşek tutmaya çalışırız, böylelikle projenin değişime olan direncini azaltmış oluruz. Bir örnek vermek gerekirse, herkes sıklıkla e-ticaret platformlarından ya da bankalardan ürünler, kampanyalar hakkında sms ve email yoluyla bilgilendirme alıyordur. Zaman zaman bu platformlardan email, sms ile bilgilendirme ayarlarınızı değiştiriyor olabilirsiniz. Bu durumda sistem sizin talebinize göre sizi bilgilendirme yöntemlerini değiştiriyor. Peki bu değişimi nasıl yönetiyor olabilirler? Ben olsam şöyle bir çözüm üretiyor olabilirdim;</p>
<p>public sealed record NotifyUserInput(User User, string Title, string Description);  </p>
<p>public interface INotifyUserService {<br />    Task Notify(NotifyUserInput input);<br />}  </p>
<p>public sealed class NotifySmsUserService : INotifyUserService<br />{<br />    public Task Notify(NotifyUserInput input)<br />    {<br />        //notify with sms api<br />    }<br />}  </p>
<p>public sealed class NotifyEmailUserService : INotifyUserService<br />{<br />    public Task Notify(NotifyUserInput input)<br />    {<br />        //notify with email api<br />    }<br />}  </p>
<p>public sealed class NotifyNothingUserService: INotifyUserService<br />{<br />    public Task Notify(NotifyUserInput input)<br />    {<br />        //do not notify user<br />    }<br />}</p>
<p>Burada bir <strong>INotifyUserService</strong> interface oluşturdum. Bu interface 3 farklı sınıfa uyguladım. Kullanıcı duruma göre sms ile bilgilendirilmek isterse <strong>NotifySmsUserService,</strong> email ile bilgilendirilmek isterse <strong>NotifyEmailUserService,</strong> herhangi bir şekilde bilgilendirmek istemiyorsa <strong>NotifyNothingUserServic</strong>e servisini çağırabilirim.</p>
<p>public sealed class SendNewProductToCustomerService<br />{<br />    private readonly INotifyUserService _notifyUserService;<br />    public User User { get; private set; }<br />    public Product Product { get; private set; }    </p>
<p>    public SendNewProductToCustomerService( User user,<br />        Product product,<br />        INotifyUserService notifyUserService )<br />    {<br />        User = user;<br />        Product = product;<br />        _notifyUserService = notifyUserService;<br />    }  </p>
<p>    public Task Handle()<br />    {<br />        // handle it<br />    }<br />}  </p>
<p>// notify with email<br />var sendNewProductToCustomerService = new SendNewProductToCustomerService(<br />    user: User,<br />    product: Product,<br />    notifyUserService: new NotifyEmailUserService()<br />);  </p>
<p>// notify with sms<br />var sendNewProductToCustomerService = new SendNewProductToCustomerService(<br />    user: User,<br />    product: Product,<br />    notifyUserService: new NotifySmsUserService()<br />);  </p>
<p>// do not notify<br />var sendNewProductToCustomerService = new SendNewProductToCustomerService(<br />    user: User,<br />    product: Product,<br />    notifyUserService: new NotifyNothingUserService()<br />);</p>
<p>Yukarıdaki örnekte gördüğünüz gibi <strong>SendNewProductToCustomerService</strong> servisi <strong>INotifyUserService</strong> interfacesine bağlı olduğu için <strong>INotifyUserService</strong> interfacesini uygulayan bir çok farklı sınıf ile birlikte çalışabilir hale geldi. Neyse konumuza geri dönelim. En son <strong>ICreateLoanService</strong> interfacemizi oluşturmuştuk. Sıra geldi <strong>CreateLoanService</strong> sınıfını oluşturalım ve <strong>ICreateLoanService</strong> interfacesini uygulayalım.</p>
<p>using Wallet.Domain.Loans;  </p>
<p>namespace Wallet.Application.Loans.CreateLoan;  </p>
<p>public sealed class CreateLoanService : ICreateLoanService<br />{<br />    public Task Handle(CreateLoanInput input)<br />    {<br />        throw new NotImplementedException();<br />    }<br />}</p>
<p>Şimdi iş kurallarımızı kontrol eden kodumuzu yazalım.</p>
<p>using Wallet.Domain.Abstractions;<br />using Wallet.Domain.Loans;  </p>
<p>namespace Wallet.Application.Loans.CreateLoan;  </p>
<p>public sealed class CreateLoanService : ICreateLoanService<br />{<br />    private readonly IUnitOfWork _unitOfWork;<br />    private readonly ILoanRepository _loanRepository;  </p>
<p>    public CreateLoanService(IUnitOfWork unitOfWork, ILoanRepository loanRepository)<br />    {<br />        _unitOfWork = unitOfWork;<br />        _loanRepository = loanRepository;<br />    }  </p>
<p>    public async Task Handle(CreateLoanInput input, CancellationToken cancellationToken)<br />    {<br />        var user = input.User;<br />        var title = input.Title;<br />        var description = input.Description;<br />        var loadTypeCode = input.LoadTypeCode;<br />        var currencyCode = input.CurrencyCode;<br />        var totalAmount = input.TotalAmount;  </p>
<p>        var currency = Currency.GetByCode(currencyCode);<br />        var amount = Money.Create(totalAmount, currency);<br />        var loanType = LoanType.GetByCode(loadTypeCode);  </p>
<p>        if (!user.IsPremium)<br />        {<br />            var totalLoanCount = await _loanRepository.GetTotalLoanCountByUserId(user.Id);<br />            if (totalLoanCount &gt;= 5)<br />            {<br />                throw new MaxLoanCountException();<br />            }<br />        }  </p>
<p>        var loan = Loan.Create(user, title, description, loanType, amount);  </p>
<p>        await _loanRepository.Add(loan);<br />        await _unitOfWork.SaveChangesAsync(cancellationToken);  </p>
<p>        return loan;<br />    }<br />}</p>
<p>Burada <strong>CreateLoanService</strong> oluşturulurken 2 adet parametreye ihtiyaç duyuyor. Burada da gördüğünüz gibi <strong>CreateLoanService</strong> interfacelere bağlı. <strong>IUnitOfWork</strong> ve <strong>ILoanRepository</strong> implement eden sınıfların nesneleri bir kullanıcının toplam kayıtlı loan sayısını döndürmek ve yeni bir loan kaydını veritabanına kaydetmek için kullanacağız. Borç türü, borç tutarı ve borcun para birimini yönetebilmek için LoanType, Currency ve Money value objelerini oluşturdum. Ayrıca kullanıcıyı temsil edecek User entity sınıfını oluşturdum.</p>
<p>using Wallet.Domain.Abstractions;  </p>
<p>namespace Wallet.Domain.Users;  </p>
<p>public sealed class User : Entity<br />{<br />    public string FirstName { get; private set; }<br />    public string LastName { get; private set; }<br />    public string Email { get; private set; }<br />    public string Password { get; private set; }<br />    public bool IsPremium { get; private set; }   </p>
<p>    private User(Guid id, string firstName, string lastName, string email, string password, bool isPremium) : base(id, DateTime.UtcNow)<br />    {<br />        FirstName = firstName;<br />        LastName = lastName;<br />        Email = email;<br />        Password = password;<br />        IsPremium = isPremium;<br />    }  </p>
<p>    public static User Create(string firstName, string lastName, string email, string password, bool isPremium)<br />    {<br />        var user = new User(Guid.NewGuid(), firstName, lastName, email, password, isPremium);  </p>
<p>        return user;<br />    }<br />}  </p>
<p>namespace Wallet.Domain.Abstractions;  </p>
<p>public interface IUnitOfWork<br />{<br />    Task SaveChangesAsync(CancellationToken cancellationToken = default);<br />}</p>
<p>namespace Wallet.Domain.Loans;  </p>
<p>public interface ILoanRepository<br />{<br />    public Task GetTotalLoanCountByUserId(Guid userId);<br />    public Task Add(Loan loan);<br />}</p>
<p>namespace Wallet.Domain.Loans;  </p>
<p>public sealed record Currency<br />{<br />    public static readonly Currency Try = new("TRY");<br />    public static readonly Currency Eur = new("EUR");<br />    public static readonly Currency Usd = new("USD");<br />    public static readonly Currency Gbp = new("GBP");<br />    public static readonly IReadOnlyCollection All = new[] { Try, Eur, Usd, Gbp };  </p>
<p>    public string Code { get; init; }  </p>
<p>    private Currency(string code) =&gt; Code = code;  </p>
<p>    public static Currency GetByCode(string code)<br />    {<br />        return All.FirstOrDefault(p =&gt; p.Code == code) ?? throw new InvalidCurrencyException();<br />    }<br />}</p>
<p>namespace Wallet.Domain.Loans;  </p>
<p>public sealed record LoanType<br />{<br />    public static readonly LoanType PersonalFinanceCredit = new("PersonalFinanceCredit");<br />    public static readonly LoanType Mortgage= new ("Mortgage");<br />    public static readonly LoanType CreditCardInstallment = new("CreditCardInstallment");<br />    public static readonly IReadOnlyCollection All = new[] { PersonalFinanceCredit, Mortgage, CreditCardInstallment };  </p>
<p>    public string Code { get; init; }  </p>
<p>    private LoanType(string code)<br />    {<br />        Code = code;<br />    }  </p>
<p>    public static LoanType GetByCode(string code)<br />    {<br />        return All.FirstOrDefault(p =&gt; p.Code == code) ?? throw new InvalidLoanTypeException();<br />    }<br />}</p>
<p>namespace Wallet.Domain.Loans;  </p>
<p>public sealed record Money<br />{<br />    public decimal Amount { get; init; }<br />    public Currency Currency { get; init; }  </p>
<p>    private Money(decimal amount, Currency currency)<br />    {<br />        Amount = amount;<br />        Currency = currency;<br />    }  </p>
<p>    public static Money Create(decimal amount, Currency currency)<br />    {<br />        if (amount &lt; 0)<br />        {<br />            throw new AmountMustBePositiveException();<br />        }  </p>
<p>        return new Money(amount, currency);<br />    }  </p>
<p>    private static void Validate(Money first, Money second)<br />    {<br />        if (first.Currency != second.Currency)<br />        {<br />            throw new CurrenciesMustBeSameException();<br />        }  </p>
<p>        if (first.Amount &lt; 0)<br />        {<br />            throw new AmountMustBePositiveException();<br />        }  </p>
<p>        if (second.Amount &lt; 0)<br />        {<br />            throw new AmountMustBePositiveException();<br />        }  </p>
<p>        if (first.Amount &gt;= int.MaxValue)<br />        {<br />            throw new InvalidAmountException();<br />        }  </p>
<p>        if (second.Amount &gt;= int.MaxValue)<br />        {<br />            throw new InvalidAmountException();<br />        }<br />    }  </p>
<p>    public static Money operator +(Money first, Money second)<br />    {<br />        Validate(first, second);  </p>
<p>        var tempSum = first.Amount + second.Amount;  </p>
<p>        if (tempSum &gt;= int.MaxValue)<br />        {<br />            throw new InvalidAmountException();<br />        }  </p>
<p>        var tempDiff = tempSum - first.Amount;  </p>
<p>        if (tempDiff != second.Amount)<br />        {<br />            throw new InvalidAmountException();<br />        }  </p>
<p>        return new Money(first.Amount + second.Amount, first.Currency);<br />    }  </p>
<p>    public static Money operator -(Money first, Money second)<br />    {<br />        Validate(first, second);  </p>
<p>        var diff = first.Amount - second.Amount;  </p>
<p>        if (diff &lt; 0)<br />        {<br />            throw new AmountMustBePositiveException();<br />        }  </p>
<p>        return new Money(diff, first.Currency);<br />    }<br />}</p>
<p>Aşağıda da Loan entity sınıfını görebilirsin. Yeni bir borç nesnesi oluşturmak için constructor kullanmıyorum. Bunun yerine <strong>Create</strong> methodunu çağırıyorum.</p>
<p>using Wallet.Domain.Abstractions;<br />using Wallet.Domain.Users;  </p>
<p>namespace Wallet.Domain.Loans;  </p>
<p>public sealed class Loan: Entity<br />{<br />    public Guid UserId { get; private set; }<br />    public string Title { get; private set; }<br />    public string Description { get; private set; }<br />    public LoanType LoanType { get; private set; }<br />    public Money TotalAmount { get; private set; }  </p>
<p>    private Loan(Guid id, Guid userId, string title, string description, LoanType loanType, Money totalAmount) : base(id, DateTime.UtcNow)<br />    {<br />        UserId = userId;<br />        SetTitle(title);<br />        SetDescription(description);<br />        LoanType = loanType;<br />        TotalAmount = totalAmount;<br />    }  </p>
<p>    public static Loan Create(User user,<br />        string title, string description, LoanType loanType, Money totalAmount)<br />    {<br />        var userId = user.Id;  </p>
<p>        var loan = new Loan(Guid.NewGuid(), userId, title, description, loanType, totalAmount);<br />        return loan;<br />    }  </p>
<p>    internal void SetTitle(string title) {<br />        var trimTitle = title.Trim();<br />        if (string.IsNullOrEmpty(trimTitle))<br />        {<br />            throw new RequiredLoanTitleException();<br />        }  </p>
<p>        if (trimTitle.Length &lt; 3)<br />        {<br />            throw new MinLengthLoanTitleException();<br />        }  </p>
<p>        if (trimTitle.Length &gt; 120)<br />        {<br />            throw new MaxLengthLoanTitleException();<br />        }<br />        Title = title;<br />    }  </p>
<p>    internal void SetDescription(string description) {<br />        var trimDescription = description.Trim();<br />        if (trimDescription.Length &gt; 255)<br />        {<br />            throw new MaxLengthLoanDescriptionException();<br />        }<br />        Description = description;<br />    }<br />}</p>
<p>Bir loan nesnesi oluşturmak için gerekli olan tüm geliştirmeyi yaptık. Şimdi testlerini yazalım.</p>
<p>using Wallet.Application.Loans.CreateLoan;<br />using Wallet.Domain.Loans;<br />using Wallet.Domain.Users;  </p>
<p>namespace Wallet.Application.Tests.Loans.CreateLoan;  </p>
<p>public class CreateLoanServiceTests<br />{<br />    [Fact]<br />    public async Task Given_Empty_Title_Throw_RequiredLoanTitleException()<br />    {<br />        var user \= User.Create("Serhat", "Yavaş", "serhatleventyavas@gmail.com", "test123", true);<br />        var input \= new CreateLoanInput(<br />            User: user,<br />            Title: "",<br />            Description: "",<br />            LoadTypeCode: LoanType.Mortgage.Code,<br />            TotalAmount: 10_000,<br />            CurrencyCode: Currency.Try.Code<br />        );<br />        var service \= new CreateLoanService(new FakeUnitOfWork(), new FakeLoanRepository());  </p>
<p>        await Assert.ThrowsAsync(() =&gt; service.Handle(input, new CancellationToken()));  </p>
<p>    }  </p>
<p>    [Fact]<br />    public async Task Given_2_Chars_Title_Throw_MinLengthLoanTitleException()<br />    {<br />        var user \= User.Create("Serhat", "Yavaş", "serhatleventyavas@gmail.com", "test123", true);<br />        var input \= new CreateLoanInput(<br />            User: user,<br />            Title: new string('*',2),<br />            Description: "",<br />            LoadTypeCode: LoanType.Mortgage.Code,<br />            TotalAmount: 10_000,<br />            CurrencyCode: Currency.Try.Code<br />        );<br />        var service \= new CreateLoanService(new FakeUnitOfWork(), new FakeLoanRepository());  </p>
<p>        await Assert.ThrowsAsync(() =&gt; service.Handle(input, new CancellationToken()));  </p>
<p>    }  </p>
<p>    [Fact]<br />    public async Task Given_121_Chars_Title_Throw_MaxLengthLoanTitleException()<br />    {<br />        var user \= User.Create("Serhat", "Yavaş", "serhatleventyavas@gmail.com", "test123", true);<br />        var input \= new CreateLoanInput(<br />            User: user,<br />            Title: new string('*', 121),<br />            Description: "",<br />            LoadTypeCode: LoanType.Mortgage.Code,<br />            TotalAmount: 10_000,<br />            CurrencyCode: Currency.Try.Code<br />        );<br />        var service \= new CreateLoanService(new FakeUnitOfWork(), new FakeLoanRepository());  </p>
<p>        await Assert.ThrowsAsync(() =&gt; service.Handle(input, new CancellationToken()));  </p>
<p>    }  </p>
<p>    [Fact]<br />    public async Task Given_256_Chars_Description_Throw_MaxLengthLoanDescriptionException()<br />    {<br />        var user \= User.Create("Serhat", "Yavaş", "serhatleventyavas@gmail.com", "test123", true);<br />        var input \= new CreateLoanInput(<br />            User: user,<br />            Title: "Title",<br />            Description: new string('*', 256),<br />            LoadTypeCode: LoanType.Mortgage.Code,<br />            TotalAmount: 10_000,<br />            CurrencyCode: Currency.Try.Code<br />        );<br />        var service \= new CreateLoanService(new FakeUnitOfWork(), new FakeLoanRepository());  </p>
<p>        await Assert.ThrowsAsync(() =&gt; service.Handle(input, new CancellationToken()));<br />    }  </p>
<p>    [Fact]<br />    public async Task Given_Empty_Loan_Type_Code_Throw_InvalidLoanTypeException()<br />    {<br />        var user \= User.Create("Serhat", "Yavaş", "serhatleventyavas@gmail.com", "test123", true);<br />        var input \= new CreateLoanInput(<br />            User: user,<br />            Title: "Title",<br />            Description: "Description",<br />            LoadTypeCode: "",<br />            TotalAmount: 10_000,<br />            CurrencyCode: Currency.Try.Code<br />        );<br />        var service \= new CreateLoanService(new FakeUnitOfWork(), new FakeLoanRepository());  </p>
<p>        await Assert.ThrowsAsync(() =&gt; service.Handle(input, new CancellationToken()));<br />    }  </p>
<p>    [Fact]<br />    public async Task Given_Not_Exists_Code_Loan_Type_Code_Throw_InvalidLoanTypeException()<br />    {<br />        var user \= User.Create("Serhat", "Yavaş", "serhatleventyavas@gmail.com", "test123", true);<br />        var input \= new CreateLoanInput(<br />            User: user,<br />            Title: "Title",<br />            Description: "Description",<br />            LoadTypeCode: "invalidCode",<br />            TotalAmount: 10_000,<br />            CurrencyCode: Currency.Try.Code<br />        );<br />        var service \= new CreateLoanService(new FakeUnitOfWork(), new FakeLoanRepository());  </p>
<p>        await Assert.ThrowsAsync(() =&gt; service.Handle(input, new CancellationToken()));<br />    }  </p>
<p>    [Fact]<br />    public async Task Given_Empty_Currency_Code_Throw_InvalidCurrencyException()<br />    {<br />        var user \= User.Create("Serhat", "Yavaş", "serhatleventyavas@gmail.com", "test123", true);<br />        var input \= new CreateLoanInput(<br />            User: user,<br />            Title: "Title",<br />            Description: "Description",<br />            LoadTypeCode: LoanType.Mortgage.Code,<br />            TotalAmount: 10_000,<br />            CurrencyCode: ""<br />        );<br />        var service \= new CreateLoanService(new FakeUnitOfWork(), new FakeLoanRepository());  </p>
<p>        await Assert.ThrowsAsync(() =&gt; service.Handle(input, new CancellationToken()));<br />    }  </p>
<p>    [Fact]<br />    public async Task Given_Not_Exists_Currency_Code_Throw_InvalidCurrencyException()<br />    {<br />        var user \= User.Create("Serhat", "Yavaş", "serhatleventyavas@gmail.com", "test123", true);<br />        var input \= new CreateLoanInput(<br />            User: user,<br />            Title: "Title",<br />            Description: "Description",<br />            LoadTypeCode: LoanType.Mortgage.Code,<br />            TotalAmount: 10_000,<br />            CurrencyCode: "invalid_code"<br />        );<br />        var service \= new CreateLoanService(new FakeUnitOfWork(), new FakeLoanRepository());  </p>
<p>        await Assert.ThrowsAsync(() =&gt; service.Handle(input, new CancellationToken()));<br />    }  </p>
<p>    [Fact]<br />    public async Task Given_Negative_Amount_Throw_AmountMustBePositiveException()<br />    {<br />        var user \= User.Create("Serhat", "Yavaş", "serhatleventyavas@gmail.com", "test123", true);<br />        var input \= new CreateLoanInput(<br />            User: user,<br />            Title: "Title",<br />            Description: "Description",<br />            LoadTypeCode: LoanType.Mortgage.Code,<br />            TotalAmount: -10_000,<br />            CurrencyCode: Currency.Try.Code<br />        );<br />        var service \= new CreateLoanService(new FakeUnitOfWork(), new FakeLoanRepository());  </p>
<p>        await Assert.ThrowsAsync(() =&gt; service.Handle(input, new CancellationToken()));<br />    }  </p>
<p>    [Fact]<br />    public async Task Given_Freemium_User_With_5_Loans_Total_Throw_MaxLoanCountException()<br />    {<br />        var user \= User.Create("Serhat", "Yavaş", "serhatleventyavas@gmail.com", "test123", false);<br />        var input \= new CreateLoanInput(<br />            User: user,<br />            Title: "Title",<br />            Description: "Description",<br />            LoadTypeCode: LoanType.Mortgage.Code,<br />            TotalAmount: 10_000,<br />            CurrencyCode: Currency.Try.Code<br />        );<br />        var service \= new CreateLoanService(new FakeUnitOfWork(), new FakeLoanRepository(5));  </p>
<p>        await Assert.ThrowsAsync(() =&gt; service.Handle(input, new CancellationToken()));<br />    }  </p>
<p>    [Fact]<br />    public async Task Given_Correct_Input_And_Freemium_User_Create_Loan_Successfully()<br />    {<br />        var user \= User.Create("Serhat", "Yavaş", "serhatleventyavas@gmail.com", "test123", false);<br />        var input \= new CreateLoanInput(<br />            User: user,<br />            Title: "Title",<br />            Description: "Description",<br />            LoadTypeCode: LoanType.Mortgage.Code,<br />            TotalAmount: 10_000,<br />            CurrencyCode: Currency.Try.Code<br />        );  </p>
<p>        var fakeUnitOfWork \= new FakeUnitOfWork();<br />        var fakeLoanRepository \= new FakeLoanRepository(4);<br />        var service \= new CreateLoanService(fakeUnitOfWork, fakeLoanRepository);  </p>
<p>        var loan \= await service.Handle(input, new CancellationToken());  </p>
<p>        Assert.Equal(1, fakeLoanRepository.TotalCalledGetTotalLoanCountByUserId);<br />        Assert.Equal(1, fakeUnitOfWork.Count);<br />        Assert.Contains(loan, fakeLoanRepository.AddedLoanList);<br />        Assert.NotNull(loan);<br />        Assert.Equal(loan.UserId, user.Id);<br />        Assert.Equal(loan.LoanType.Code, input.LoadTypeCode);<br />        Assert.Equal(loan.TotalAmount.Currency.Code, input.CurrencyCode);<br />        Assert.Equal(loan.TotalAmount.Amount, input.TotalAmount);<br />        Assert.Equal(loan.Title, input.Title);<br />        Assert.Equal(loan.Description, input.Description);<br />    }  </p>
<p>    [Fact]<br />    public async Task Given_Correct_Input_And_Premium_User_Create_Loan_Successfully()<br />    {<br />        var user \= User.Create("Serhat", "Yavaş", "serhatleventyavas@gmail.com", "test123", true);<br />        var input \= new CreateLoanInput(<br />            User: user,<br />            Title: "Title",<br />            Description: "Description",<br />            LoadTypeCode: LoanType.Mortgage.Code,<br />            TotalAmount: 10_000,<br />            CurrencyCode: Currency.Try.Code<br />        );  </p>
<p>        var fakeUnitOfWork \= new FakeUnitOfWork();<br />        var fakeLoanRepository \= new FakeLoanRepository(5);<br />        var service \= new CreateLoanService(fakeUnitOfWork, fakeLoanRepository);  </p>
<p>        var loan \= await service.Handle(input, new CancellationToken());  </p>
<p>        Assert.Equal(0, fakeLoanRepository.TotalCalledGetTotalLoanCountByUserId);<br />        Assert.Equal(1, fakeUnitOfWork.Count);<br />        Assert.Contains(loan, fakeLoanRepository.AddedLoanList);<br />        Assert.NotNull(loan);<br />        Assert.Equal(loan.UserId, user.Id);<br />        Assert.Equal(loan.LoanType.Code, input.LoadTypeCode);<br />        Assert.Equal(loan.TotalAmount.Currency.Code, input.CurrencyCode);<br />        Assert.Equal(loan.TotalAmount.Amount, input.TotalAmount);<br />        Assert.Equal(loan.Title, input.Title);<br />        Assert.Equal(loan.Description, input.Description);<br />    }<br />}</p>
<p>Yukarıdaki testlerimiz bir borç oluşturmak için tanımladığımız tüm iş kurallarını test ediyor. Ayrıca testlerimizde gördüğünüz gibi iş kurallarına uymayan durumlarda özel hata fırlatıyoruz. Bu hatalarıda aşağıda bulabilirsin.</p>
<p>namespace Wallet.Domain.Loans;  </p>
<p>public sealed class RequiredLoanTitleException: Exception;<br />public sealed class MinLengthLoanTitleException : Exception;<br />public sealed class MaxLengthLoanTitleException : Exception;<br />public sealed class MaxLengthLoanDescriptionException : Exception;<br />public sealed class MaxLoanCountException: Exception;<br />public sealed class InvalidLoanTypeException: Exception;<br />public sealed class InvalidCurrencyException: Exception;<br />public sealed class AmountMustBePositiveException: Exception;<br />public sealed class CurrenciesMustBeSameException: Exception;<br />public sealed class InvalidAmountException: Exception;</p>
<p>Test Results</p>
<p>Yukarıdaki ekran alıntısında yazdığımız testlerin hepsinin doğru şekilde çalıştığını görebiliyoruz.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1710966624520/737257c2-8144-4e53-86fb-448b50d652fa.png" alt /></p>
<p>Tests Coverage</p>
<p>Yukarıdaki ekran alıntısında ise <strong>Unit Tests Coverage</strong> sonucunu görebiliriz. <strong>Unit Test Coverage,</strong> testler kodumuzun ne kadar ile etkileşime geçti onu yüzdesel olarak bize sunar. <strong>CreateLoanService ve Loan sınıflarına %100</strong> olarak dokunmuşuz, her satırı ile etkileşime geçmişiz. Yani elimizdeki iş kurallarının her maddesini test etmişiz. Buradaki amacımız %100 oranına ulaşmak değil. Amacımız bir borç oluşturulurken elimizdeki iş kuralları doğru şekilde çalıştığından emin olmak. Bu şekilde oluşabilecek hataların önüne geçebiliriz. Tabi farkedemediğimiz iş kuralları yüzünden hatalar oluşabilir, ancak elimizdeki iş kurallarının doğru çalıştığına eminiz. Bu sayede kodun hata ve bakım maliyetlerini azaltabiliriz.</p>
<p>Uzun bir yazı oldu. Bu yazımızda xUnit ile nasıl test yazabiliriz, unit test nedir, soyutlama ve bağımlılıklardan küçük küçük örnekler ile bahsetmeye çalıştım. Umarım sana dokunmuş olabilirim. Bu örnek projenin kaynak kodunu aşağıya bırakıyorum. Merak ettiğin bir şey olursa benimle iletişime geçebilirsin.</p>
<p>[<strong>GitHub - serhatleventyavas/Wallet</strong><br /><em>Contribute to serhatleventyavas/Wallet development by creating an account on GitHub.</em>github.com](https://github.com/serhatleventyavas/Wallet "https://github.com/serhatleventyavas/Wallet")<a target="_blank" href="https://github.com/serhatleventyavas/Wallet"></a></p>
]]></content:encoded></item><item><title><![CDATA[Static Typing Nedir?]]></title><description><![CDATA[Static typing destekleyen dillerde, bir değişkenin tipi derleme zamanında (compile-time) bilinir ve çalışma zamanında (runtime) değişkenin tipi değiştirilemez. Eğer kodu yazarken oluşturduğumuz değişkenin tipine uygun olmayan bir değer ataması yapılı...]]></description><link>https://serhatleventyavas.dev/static-typing-nedir-dbb8653cabf</link><guid isPermaLink="true">https://serhatleventyavas.dev/static-typing-nedir-dbb8653cabf</guid><category><![CDATA[static typic]]></category><dc:creator><![CDATA[Serhat Levent Yavaş]]></dc:creator><pubDate>Sat, 16 Oct 2021 16:59:58 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1710966608618/21925d7e-c82a-4892-9335-7af4f919668e.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Static typing destekleyen dillerde, bir değişkenin tipi derleme zamanında <strong>(compile-time)</strong> bilinir ve çalışma zamanında <strong>(runtime)</strong> değişkenin tipi değiştirilemez. Eğer kodu yazarken oluşturduğumuz değişkenin tipine uygun olmayan bir değer ataması yapılırsa derleme zamanında hata alırız. Günümüzde kodu yazarken kullandığımız akıllı IDE’ler sayesinde bu tip hataları derleme yapmaya gerek kalmadan farkedebiliriz.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1710966603670/5960c9bf-96f0-45bb-89ff-bfda8907a324.png" alt /></p>
<p>Örneğin yukarıda <a target="_blank" href="https://www.jetbrains.com/rider/">Rider IDE</a>’ sini kullanarak yazmış olduğum basit bir c# kodu örneği bulunuyor. Örnekte int tipinde ve <strong>age</strong> isminde bir değişken oluşturdum ve 25 değerini bu değişkene atadım. Daha sonra bir alt satırda aynı değişkene <strong>Serhat</strong> ataması yaptım. Bu atama sonrasında IDE burada Serhat değerinin altını çizerek hata döndürdü ve bu atamanın yapılamayacağı hakkında bilgi verdi.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1710966605297/a6ee3eef-c913-4c76-b3cc-681f0484ca46.png" alt /></p>
<p>Rider build failed</p>
<p>Ayrıca Rider üzerinden build işlemi yapmak istediğim zaman derleme aşamasında hata oluştu ve yine aynı hatayı bana bildirdi.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1710966607057/a8dad00d-c3a6-4719-8b7a-85645800e1ec.png" alt /></p>
<p>Terminal build failed</p>
<p>Aynı hatayı terminal üzerinden <strong>dotnet build</strong> komutunu çalıştırdığımızda da görebiliriz.</p>
<p>C# dışında static typing destekleyen bazı diller; C, C++, Java, Rust, Go, Scala, Kotlin, Swift</p>
<p>Kısaca özetleyecek olursak; static typing dillerde derleme zamanında tip kontrolü yapılır. Böylelikle çalışma zamanında oluşabilecek hataların önüne geçilir.</p>
]]></content:encoded></item></channel></rss>