<?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[Devlog of Uygar Öztürk Ceylan]]></title><description><![CDATA[Laravel, .NET, Docker ve self-hosting üzerine notlarımı ve çözümlerimi paylaştığım kişisel yazılım blogu.]]></description><link>https://blog.uygarceylan.net</link><image><url>https://cdn.hashnode.com/res/hashnode/image/upload/v1750848775832/39bc6ea4-51ac-4099-af84-b9aad30b5305.png</url><title>Devlog of Uygar Öztürk Ceylan</title><link>https://blog.uygarceylan.net</link></image><generator>RSS for Node</generator><lastBuildDate>Sat, 18 Apr 2026 09:10:02 GMT</lastBuildDate><atom:link href="https://blog.uygarceylan.net/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[.NET Result Pattern: Mikroservis ve Monolit Mimarilere Yönelik Modern Bir Yaklaşım]]></title><description><![CDATA[Giriş: Neden Result Pattern'e İhtiyacımız Var?
Yazılım geliştirmede hata yönetimi kritik bir konudur. Geleneksel olarak, özellikle C# gibi dillerde, hata durumlarını belirtmek için istisnalar (exceptions) kullanılır. Ancak istisnalar, beklenmedik ve ...]]></description><link>https://blog.uygarceylan.net/net-result-pattern-mikroservis-ve-monolit-mimarilere-yonelik-modern-bir-yaklasim</link><guid isPermaLink="true">https://blog.uygarceylan.net/net-result-pattern-mikroservis-ve-monolit-mimarilere-yonelik-modern-bir-yaklasim</guid><category><![CDATA[patterns]]></category><category><![CDATA[C#]]></category><category><![CDATA[dotnet]]></category><category><![CDATA[#Domain-Driven-Design]]></category><category><![CDATA[Microservices]]></category><category><![CDATA[Clean Architecture]]></category><category><![CDATA[Functional Programming]]></category><category><![CDATA[asp.net core]]></category><dc:creator><![CDATA[Uygar Öztürk Ceylan]]></dc:creator><pubDate>Sat, 11 Oct 2025 14:05:52 GMT</pubDate><content:encoded><![CDATA[<h3 id="heading-giris-neden-result-patterne-ihtiyacimiz-var">Giriş: Neden Result Pattern'e İhtiyacımız Var?</h3>
<p>Yazılım geliştirmede hata yönetimi kritik bir konudur. Geleneksel olarak, özellikle C# gibi dillerde, hata durumlarını belirtmek için istisnalar (<code>exceptions</code>) kullanılır. Ancak istisnalar, <strong>beklenmedik ve programın akışını bozan istisnai durumlar</strong> için tasarlanmıştır. "Kullanıcı bulunamadı", "E-posta zaten kayıtlı" veya "Stok yetersiz" gibi durumlar, uygulamanın normal işleyişi içinde öngörülebilen ve beklenen başarısızlık senaryolarıdır.</p>
<p>Bu tür beklenen hataları istisnalarla yönetmek birkaç soruna yol açar:</p>
<ol>
<li><p><strong>Performans Maliyeti:</strong> İstisna fırlatmak ve yakalamak (try-catch), normal kod akışına göre oldukça maliyetli bir operasyondur.</p>
</li>
<li><p><strong>Anlamsal Kargaşa:</strong> Kodun okunabilirliğini düşürür ve "hangi metodun ne tür bir beklenen hata fırlatabileceği" bilgisini metodun imzasından gizler.</p>
</li>
<li><p><strong>Kontrol Akışı Anti-Pattern'i:</strong> İstisnaları bir <code>goto</code> gibi kontrol akışı için kullanmak, kodun takibini zorlaştırır.</p>
</li>
</ol>
<p><strong>Result Pattern</strong>, bu sorunlara zarif bir çözüm sunar. Bir metodun sonucunu, başarı durumunda bir değer (<code>value</code>) ya da başarısızlık durumunda bir hata (<code>error</code>) içerebilen tek bir nesne içinde döndürmemizi sağlar.</p>
<p>Bu rehberde, <code>ProblemDetails</code> (RFC 7807) standardını kullanarak hem API'ler için tutarlı hem de servis katmanında esnek bir desen oluşturacağız.</p>
<h3 id="heading-bolum-1-mimarinin-temel-taslari">Bölüm 1: Mimarinin Temel Taşları</h3>
<p>Desenimizi oluşturacak temel sınıfları ve yardımcı yapıları adım adım inşa edelim.</p>
<h4 id="heading-11-problemdetails-ve-standart-hata-fabrikasi-errors-sinifi">1.1. <code>ProblemDetails</code> ve Standart Hata Fabrikası (<code>Errors</code> Sınıfı)</h4>
<p>Hata nesnelerimizi standart hale getirmek için <a target="_blank" href="http://ASP.NET">ASP.NET</a> Core'un yerleşik <code>ProblemDetails</code> sınıfını kullanacağız. Bu, API'lerimizin RFC 7807 standardına uygun, tutarlı ve makine tarafından okunabilir hatalar üretmesini sağlar.</p>
<pre><code class="lang-csharp"><span class="hljs-keyword">using</span> Microsoft.AspNetCore.Http;
<span class="hljs-keyword">using</span> Microsoft.AspNetCore.Mvc;

<span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">class</span> <span class="hljs-title">Errors</span>
{
    <span class="hljs-comment">// 404 Not Found</span>
    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> ProblemDetails <span class="hljs-title">NotFound</span>(<span class="hljs-params"><span class="hljs-keyword">string</span> title, <span class="hljs-keyword">string</span> detail</span>)</span> =&gt;
        <span class="hljs-keyword">new</span>()
        {
            Status = StatusCodes.Status404NotFound,
            Type = <span class="hljs-string">"https://tools.ietf.org/html/rfc7231#section-6.5.4"</span>,
            Title = title,
            Detail = detail
        };

    <span class="hljs-comment">// 400 Bad Request (özellikle validasyon hataları için)</span>
    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> ProblemDetails <span class="hljs-title">BadRequest</span>(<span class="hljs-params"><span class="hljs-keyword">string</span> title, <span class="hljs-keyword">string</span> detail, IReadOnlyDictionary&lt;<span class="hljs-keyword">string</span>, <span class="hljs-keyword">string</span>[]&gt;? validationErrors = <span class="hljs-literal">null</span></span>)</span>
    {
        <span class="hljs-keyword">var</span> problemDetails = <span class="hljs-keyword">new</span> ProblemDetails
        {
            Status = StatusCodes.Status400BadRequest,
            Type = <span class="hljs-string">"https://tools.ietf.org/html/rfc7231#section-6.5.1"</span>,
            Title = title,
            Detail = detail,
        };

        <span class="hljs-keyword">if</span> (validationErrors <span class="hljs-keyword">is</span> not <span class="hljs-literal">null</span>)
        {
            problemDetails.Extensions.Add(<span class="hljs-string">"validationErrors"</span>, validationErrors);
        }

        <span class="hljs-keyword">return</span> problemDetails;
    }

    <span class="hljs-comment">// 409 Conflict</span>
    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> ProblemDetails <span class="hljs-title">Conflict</span>(<span class="hljs-params"><span class="hljs-keyword">string</span> title, <span class="hljs-keyword">string</span> detail</span>)</span> =&gt;
        <span class="hljs-keyword">new</span>()
        {
            Status = StatusCodes.Status409Conflict,
            Type = <span class="hljs-string">"https://tools.ietf.org/html/rfc7231#section-6.5.8"</span>,
            Title = title,
            Detail = detail
        };
}
</code></pre>
<h4 id="heading-12-deger-dondurmeyen-sonuc-result-sinifi">1.2. Değer Döndürmeyen Sonuç: <code>Result</code> Sınıfı</h4>
<p><code>void</code> metotlar (örn: <code>Update</code>, <code>Delete</code>) için operasyonun sadece başarılı veya başarısız olduğunu bildiren temel sınıftır.</p>
<pre><code class="lang-csharp"><span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title">Result</span>
{
    <span class="hljs-keyword">public</span> <span class="hljs-keyword">bool</span> IsSuccess { <span class="hljs-keyword">get</span>; }
    <span class="hljs-keyword">public</span> <span class="hljs-keyword">bool</span> IsFailure =&gt; !IsSuccess;
    <span class="hljs-keyword">public</span> ProblemDetails? Error { <span class="hljs-keyword">get</span>; }

    <span class="hljs-function"><span class="hljs-keyword">protected</span> <span class="hljs-title">Result</span>(<span class="hljs-params"><span class="hljs-keyword">bool</span> isSuccess, ProblemDetails? error</span>)</span>
    {
        <span class="hljs-keyword">if</span> (isSuccess &amp;&amp; error <span class="hljs-keyword">is</span> not <span class="hljs-literal">null</span> || !isSuccess &amp;&amp; error <span class="hljs-keyword">is</span> <span class="hljs-literal">null</span>)
        {
            <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> InvalidOperationException(<span class="hljs-string">"Invalid result state."</span>);
        }
        IsSuccess = isSuccess;
        Error = error;
    }

    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> Result <span class="hljs-title">Success</span>(<span class="hljs-params"></span>)</span> =&gt; <span class="hljs-keyword">new</span>(<span class="hljs-literal">true</span>, <span class="hljs-literal">null</span>);
    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> Result <span class="hljs-title">Failure</span>(<span class="hljs-params">ProblemDetails error</span>)</span> =&gt; <span class="hljs-keyword">new</span>(<span class="hljs-literal">false</span>, error);
    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">implicit</span> <span class="hljs-keyword">operator</span> <span class="hljs-title">Result</span>(<span class="hljs-params">ProblemDetails error</span>)</span> =&gt; Failure(error);
}
</code></pre>
<h4 id="heading-13-deger-donduren-sonuc-result-sinifi">1.3. Değer Döndüren Sonuç: <code>Result&lt;TValue&gt;</code> Sınıfı</h4>
<p>Başarılı olduğunda bir değer taşıyan generic versiyondur.</p>
<pre><code class="lang-csharp"><span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title">Result</span>&lt;<span class="hljs-title">TValue</span>&gt; : <span class="hljs-title">Result</span>
{
    <span class="hljs-keyword">private</span> <span class="hljs-keyword">readonly</span> TValue? _value;

    <span class="hljs-keyword">public</span> TValue Value =&gt; IsSuccess
        ? _value!
        : <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> InvalidOperationException(<span class="hljs-string">"Cannot access the value of a failed result."</span>);

    <span class="hljs-function"><span class="hljs-keyword">private</span> <span class="hljs-title">Result</span>(<span class="hljs-params">TValue <span class="hljs-keyword">value</span></span>) : <span class="hljs-title">base</span>(<span class="hljs-params"><span class="hljs-literal">true</span>, <span class="hljs-literal">null</span></span>)</span> =&gt; _value = <span class="hljs-keyword">value</span>;
    <span class="hljs-function"><span class="hljs-keyword">private</span> <span class="hljs-title">Result</span>(<span class="hljs-params">ProblemDetails error</span>) : <span class="hljs-title">base</span>(<span class="hljs-params"><span class="hljs-literal">false</span>, error</span>)</span> =&gt; _value = <span class="hljs-keyword">default</span>;

    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> Result&lt;TValue&gt; <span class="hljs-title">Success</span>(<span class="hljs-params">TValue <span class="hljs-keyword">value</span></span>)</span> =&gt; <span class="hljs-keyword">new</span>(<span class="hljs-keyword">value</span>);
    <span class="hljs-function"><span class="hljs-keyword">public</span> new <span class="hljs-keyword">static</span> Result&lt;TValue&gt; <span class="hljs-title">Failure</span>(<span class="hljs-params">ProblemDetails error</span>)</span> =&gt; <span class="hljs-keyword">new</span>(error);

    <span class="hljs-comment">// Implicit operator'ler sayesinde kod daha akıcı hale gelir.</span>
    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">implicit</span> <span class="hljs-keyword">operator</span> <span class="hljs-title">Result</span>&lt;<span class="hljs-title">TValue</span>&gt;(<span class="hljs-params">TValue <span class="hljs-keyword">value</span></span>)</span> =&gt; Success(<span class="hljs-keyword">value</span>);
    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">implicit</span> <span class="hljs-keyword">operator</span> <span class="hljs-title">Result</span>&lt;<span class="hljs-title">TValue</span>&gt;(<span class="hljs-params">ProblemDetails error</span>)</span> =&gt; Failure(error);

    <span class="hljs-comment">// Sonucu fonksiyonel bir şekilde işlemek için Match metodu</span>
    <span class="hljs-function"><span class="hljs-keyword">public</span> TResponse <span class="hljs-title">Match</span>&lt;<span class="hljs-title">TResponse</span>&gt;(<span class="hljs-params">
        Func&lt;TValue, TResponse&gt; onSuccess,
        Func&lt;ProblemDetails, TResponse&gt; onFailure</span>)</span> =&gt;
        IsSuccess ? onSuccess(Value) : onFailure(Error!);
}
</code></pre>
<h3 id="heading-bolum-2-kullanim-senaryolari">Bölüm 2: Kullanım Senaryoları</h3>
<p>Şimdi bu yapıları gerçekçi bir servis katmanı senaryosunda kullanalım.</p>
<h4 id="heading-21-servis-arayuzu-ve-modeller">2.1. Servis Arayüzü ve Modeller</h4>
<pre><code class="lang-csharp"><span class="hljs-function"><span class="hljs-keyword">public</span> record <span class="hljs-title">Product</span>(<span class="hljs-params">Guid Id, <span class="hljs-keyword">string</span> Name, <span class="hljs-keyword">decimal</span> Price, <span class="hljs-keyword">int</span> Stock</span>)</span>;
<span class="hljs-function"><span class="hljs-keyword">public</span> record <span class="hljs-title">CreateProductRequest</span>(<span class="hljs-params"><span class="hljs-keyword">string</span> Name, <span class="hljs-keyword">decimal</span> Price, <span class="hljs-keyword">int</span> Stock</span>)</span>;
<span class="hljs-function"><span class="hljs-keyword">public</span> record <span class="hljs-title">UpdatePriceRequest</span>(<span class="hljs-params">Guid Id, <span class="hljs-keyword">decimal</span> NewPrice</span>)</span>;

<span class="hljs-keyword">public</span> <span class="hljs-keyword">interface</span> <span class="hljs-title">IProductService</span>
{
    <span class="hljs-function">Result&lt;Product&gt; <span class="hljs-title">GetById</span>(<span class="hljs-params">Guid id</span>)</span>;
    <span class="hljs-function">Result&lt;Product&gt; <span class="hljs-title">Create</span>(<span class="hljs-params">CreateProductRequest request</span>)</span>;
    <span class="hljs-function">Result <span class="hljs-title">UpdatePrice</span>(<span class="hljs-params">UpdatePriceRequest request</span>)</span>;
    <span class="hljs-function">Result <span class="hljs-title">Delete</span>(<span class="hljs-params">Guid id</span>)</span>;
}
</code></pre>
<p>2.2. <code>ProductService</code> Implementasyonu</p>
<pre><code class="lang-csharp"><span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title">ProductService</span> : <span class="hljs-title">IProductService</span>
{
    <span class="hljs-keyword">private</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">readonly</span> List&lt;Product&gt; _products = [];

    <span class="hljs-function"><span class="hljs-keyword">public</span> Result&lt;Product&gt; <span class="hljs-title">GetById</span>(<span class="hljs-params">Guid id</span>)</span>
    {
        <span class="hljs-keyword">var</span> product = _products.FirstOrDefault(p =&gt; p.Id == id);
        <span class="hljs-keyword">return</span> product <span class="hljs-keyword">is</span> not <span class="hljs-literal">null</span>
            ? product <span class="hljs-comment">// Implicit conversion to Result&lt;Product&gt;.Success</span>
            : Errors.NotFound(<span class="hljs-string">"Product Not Found"</span>, <span class="hljs-string">$"Product with ID '<span class="hljs-subst">{id}</span>' was not found."</span>);
    }

    <span class="hljs-function"><span class="hljs-keyword">public</span> Result&lt;Product&gt; <span class="hljs-title">Create</span>(<span class="hljs-params">CreateProductRequest request</span>)</span>
    {
        <span class="hljs-comment">// 1. Validasyon</span>
        <span class="hljs-keyword">var</span> validationErrors = <span class="hljs-keyword">new</span> Dictionary&lt;<span class="hljs-keyword">string</span>, <span class="hljs-keyword">string</span>[]&gt;();
        <span class="hljs-keyword">if</span> (<span class="hljs-keyword">string</span>.IsNullOrWhiteSpace(request.Name))
        {
            validationErrors.Add(<span class="hljs-keyword">nameof</span>(request.Name), [<span class="hljs-string">"Product name cannot be empty."</span>]);
        }
        <span class="hljs-keyword">if</span> (request.Price &lt;= <span class="hljs-number">0</span>)
        {
            validationErrors.Add(<span class="hljs-keyword">nameof</span>(request.Price), [<span class="hljs-string">"Price must be positive."</span>]);
        }
        <span class="hljs-keyword">if</span> (validationErrors.Count &gt; <span class="hljs-number">0</span>)
        {
            <span class="hljs-keyword">return</span> Errors.BadRequest(
                <span class="hljs-string">"Validation Failed"</span>, 
                <span class="hljs-string">"One or more validation errors occurred."</span>, 
                validationErrors);
        }

        <span class="hljs-comment">// 2. İş Kuralı Kontrolü</span>
        <span class="hljs-keyword">if</span> (_products.Any(p =&gt; p.Name.Equals(request.Name, StringComparison.OrdinalIgnoreCase)))
        {
            <span class="hljs-keyword">return</span> Errors.Conflict(<span class="hljs-string">"Duplicate Product"</span>, <span class="hljs-string">$"A product with the name '<span class="hljs-subst">{request.Name}</span>' already exists."</span>);
        }

        <span class="hljs-comment">// 3. Başarılı Durum</span>
        <span class="hljs-keyword">var</span> newProduct = <span class="hljs-keyword">new</span> Product(Guid.NewGuid(), request.Name, request.Price, request.Stock);
        _products.Add(newProduct);

        <span class="hljs-keyword">return</span> newProduct;
    }

    <span class="hljs-function"><span class="hljs-keyword">public</span> Result <span class="hljs-title">UpdatePrice</span>(<span class="hljs-params">UpdatePriceRequest request</span>)</span>
    {
        <span class="hljs-comment">// 1. Varlık kontrolü</span>
        Result&lt;Product&gt; getResult = GetById(request.Id);
        <span class="hljs-keyword">if</span> (getResult.IsFailure)
        {
            <span class="hljs-keyword">return</span> getResult.Error!; <span class="hljs-comment">// Varlık bulunamadı hatasını aynen geri döndür.</span>
        }

        <span class="hljs-comment">// 2. Validasyon</span>
        <span class="hljs-keyword">if</span> (request.NewPrice &lt;= <span class="hljs-number">0</span>)
        {
            <span class="hljs-keyword">return</span> Errors.BadRequest(<span class="hljs-string">"Invalid Price"</span>, <span class="hljs-string">"New price must be a positive value."</span>);
        }

        <span class="hljs-comment">// 3. Başarılı Durum</span>
        <span class="hljs-keyword">var</span> existingProduct = getResult.Value;
        <span class="hljs-keyword">var</span> updatedProduct = existingProduct with { Price = request.NewPrice };

        _products.Remove(existingProduct);
        _products.Add(updatedProduct);

        <span class="hljs-keyword">return</span> Result.Success();
    }

    <span class="hljs-function"><span class="hljs-keyword">public</span> Result <span class="hljs-title">Delete</span>(<span class="hljs-params">Guid id</span>)</span>
    {
        <span class="hljs-keyword">var</span> product = _products.FirstOrDefault(p =&gt; p.Id == id);
        <span class="hljs-keyword">if</span> (product <span class="hljs-keyword">is</span> <span class="hljs-literal">null</span>)
        {
            <span class="hljs-keyword">return</span> Errors.NotFound(<span class="hljs-string">"Product Not Found"</span>, <span class="hljs-string">$"Cannot delete. Product with ID '<span class="hljs-subst">{id}</span>' was not found."</span>);
        }

        _products.Remove(product);
        <span class="hljs-keyword">return</span> Result.Success();
    }
}
</code></pre>
<h3 id="heading-bolum-3-api-katmani-entegrasyonu">Bölüm 3: API Katmanı Entegrasyonu</h3>
<p>Servis katmanından dönen <code>Result</code> nesnelerini standart HTTP yanıtlarına çevirelim.</p>
<h4 id="heading-31-yaklasim-1-minimal-apiler-modern-ve-hafif">3.1. Yaklaşım 1: Minimal API'ler (Modern ve Hafif)</h4>
<p><code>Result</code>'ı <code>IResult</code>'a dönüştüren bir yardımcı metot ve endpoint tanımları:</p>
<pre><code class="lang-csharp"><span class="hljs-comment">// Helper Class</span>
<span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">class</span> <span class="hljs-title">ApiExtensions</span>
{
    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> IResult <span class="hljs-title">ToHttpResult</span>&lt;<span class="hljs-title">TValue</span>&gt;(<span class="hljs-params"><span class="hljs-keyword">this</span> Result&lt;TValue&gt; result</span>)</span> =&gt;
        result.Match(
            onSuccess: <span class="hljs-keyword">value</span> =&gt; Results.Ok(<span class="hljs-keyword">value</span>),
            onFailure: problem =&gt; Results.Problem(problem)
        );

    <span class="hljs-comment">// Yeni oluşturulan kaynaklar için 201 Created yanıtı</span>
    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> IResult <span class="hljs-title">ToCreatedHttpResult</span>&lt;<span class="hljs-title">TValue</span>&gt;(<span class="hljs-params"><span class="hljs-keyword">this</span> Result&lt;TValue&gt; result, <span class="hljs-keyword">string</span> location</span>) <span class="hljs-keyword">where</span> TValue : Product</span> =&gt;
        result.Match(
            onSuccess: <span class="hljs-keyword">value</span> =&gt; Results.Created(<span class="hljs-string">$"<span class="hljs-subst">{location}</span>/<span class="hljs-subst">{<span class="hljs-keyword">value</span>.Id}</span>"</span>, <span class="hljs-keyword">value</span>),
            onFailure: problem =&gt; Results.Problem(problem)
        );

    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> IResult <span class="hljs-title">ToHttpResult</span>(<span class="hljs-params"><span class="hljs-keyword">this</span> Result result</span>)</span>
    {
        <span class="hljs-keyword">if</span> (result.IsFailure) <span class="hljs-keyword">return</span> Results.Problem(result.Error!);
        <span class="hljs-comment">// Başarılı Update/Delete işlemleri için genellikle 204 No Content döndürülür.</span>
        <span class="hljs-keyword">return</span> Results.NoContent();
    }
}

<span class="hljs-comment">// Program.cs</span>
<span class="hljs-comment">// ... (Service registration)</span>
builder.Services.AddScoped&lt;IProductService, ProductService&gt;();
<span class="hljs-comment">// Route registration</span>
app.MapGet(<span class="hljs-string">"/products/{id:guid}"</span>, ([FromRoute] Guid id,[FromService] IProductService service) =&gt; service.GetById(id).ToHttpResult());
app.MapPost(<span class="hljs-string">"/products"</span>, ([FromBody] CreateProductRequest req,[FromService] IProductService service) =&gt; service.Create(req).ToCreatedHttpResult(<span class="hljs-string">"/products"</span>));
app.MapPut(<span class="hljs-string">"/products/price"</span>, ([FromBody] UpdatePriceRequest req,[FromService] IProductService service) =&gt; service.UpdatePrice(req).ToHttpResult());
app.MapDelete(<span class="hljs-string">"/products/{id:guid}"</span>, ([FromRoute] Guid id,[FromService] IProductService service) =&gt; service.Delete(id).ToHttpResult());

app.Run();
</code></pre>
<h4 id="heading-32-yaklasim-2-mvc-controllerlar-geleneksel-ve-guclu">3.2. Yaklaşım 2: MVC Controller'lar (Geleneksel ve Güçlü)</h4>
<p>Tüm controller'ların miras alacağı bir <code>ApiControllerBase</code> oluşturarak tekrarı önleyebiliriz.</p>
<pre><code class="lang-csharp">[<span class="hljs-meta">ApiController</span>]
<span class="hljs-keyword">public</span> <span class="hljs-keyword">abstract</span> <span class="hljs-keyword">class</span> <span class="hljs-title">ApiControllerBase</span> : <span class="hljs-title">ControllerBase</span>
{
    <span class="hljs-function"><span class="hljs-keyword">protected</span> IActionResult <span class="hljs-title">HandleResult</span>&lt;<span class="hljs-title">TValue</span>&gt;(<span class="hljs-params">Result&lt;TValue&gt; result</span>)</span> =&gt;
        result.IsSuccess
            ? Ok(result.Value)
            : Problem(result.Error!);

    <span class="hljs-function"><span class="hljs-keyword">protected</span> IActionResult <span class="hljs-title">HandleCreatedResult</span>&lt;<span class="hljs-title">TValue</span>&gt;(<span class="hljs-params">Result&lt;TValue&gt; result</span>) <span class="hljs-keyword">where</span> TValue : Product</span> =&gt;
        result.IsSuccess
            ? CreatedAtAction(<span class="hljs-keyword">nameof</span>(GetProduct), <span class="hljs-keyword">new</span> { id = result.Value.Id }, result.Value) <span class="hljs-comment">// GetProduct metoduna referans verir</span>
            : Problem(result.Error!);

    <span class="hljs-function"><span class="hljs-keyword">protected</span> IActionResult <span class="hljs-title">HandleResult</span>(<span class="hljs-params">Result result</span>)</span> =&gt;
        result.IsSuccess
            ? NoContent()
            : Problem(result.Error!);

    <span class="hljs-comment">// Bu, HandleCreatedResult'ın çalışması için gerekli.</span>
    <span class="hljs-comment">// Gerçek bir controller'da olması gerekir. Örnek olarak burada.</span>
    [<span class="hljs-meta">HttpGet(<span class="hljs-meta-string">"{id:guid}"</span>, Name = <span class="hljs-meta-string">"GetProduct"</span>)</span>]
    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">virtual</span> IActionResult <span class="hljs-title">GetProduct</span>(<span class="hljs-params">Guid id</span>)</span> =&gt; <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> NotImplementedException();
}

[<span class="hljs-meta">Route(<span class="hljs-meta-string">"api/[controller]"</span>)</span>]
<span class="hljs-function"><span class="hljs-keyword">public</span> class <span class="hljs-title">ProductsController</span>(<span class="hljs-params">IProductService productService</span>) : ApiControllerBase</span>
{
    [<span class="hljs-meta">HttpGet(<span class="hljs-meta-string">"{id:guid}"</span>)</span>]
    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">override</span> IActionResult <span class="hljs-title">GetProduct</span>(<span class="hljs-params">Guid id</span>)</span> =&gt;
        HandleResult(productService.GetById(id));

    [<span class="hljs-meta">HttpPost</span>]
    <span class="hljs-function"><span class="hljs-keyword">public</span> IActionResult <span class="hljs-title">CreateProduct</span>(<span class="hljs-params">CreateProductRequest request</span>)</span> =&gt;
        HandleCreatedResult(productService.Create(request));

    [<span class="hljs-meta">HttpPut(<span class="hljs-meta-string">"price"</span>)</span>]
    <span class="hljs-function"><span class="hljs-keyword">public</span> IActionResult <span class="hljs-title">UpdatePrice</span>(<span class="hljs-params">UpdatePriceRequest request</span>)</span> =&gt;
        HandleResult(productService.UpdatePrice(request));

    [<span class="hljs-meta">HttpDelete(<span class="hljs-meta-string">"{id:guid}"</span>)</span>]
    <span class="hljs-function"><span class="hljs-keyword">public</span> IActionResult <span class="hljs-title">DeleteProduct</span>(<span class="hljs-params">Guid id</span>)</span> =&gt;
        HandleResult(productService.Delete(id));
}
</code></pre>
<h3 id="heading-sonuc-ve-tasarimin-avantajlari">Sonuç ve Tasarımın Avantajları</h3>
<p>Bu desen, uygulamanızın hata yönetimi stratejisini kökten iyileştirir:</p>
<ol>
<li><p><strong>Öngörülebilir Kod:</strong> Metot imzaları (<code>Result&lt;T&gt;</code>), bir metodun hem başarılı bir değer hem de bir hata döndürebileceğini açıkça belirtir. Sürpriz istisnalar ortadan kalkar.</p>
</li>
<li><p><strong>Temiz Mimariler:</strong> Servis katmanı (iş mantığı) sunum katmanından (API) tamamen habersizdir. Sadece operasyonun sonucunu bildirir. API katmanı ise bu sonucu alıp HTTP dünyasına çevirmekle sorumludur. Bu, <code>Clean Architecture</code> ve <code>DDD</code> prensipleriyle mükemmel uyum sağlar.</p>
</li>
<li><p><strong>Standart ve Tutarlı API'ler:</strong> <code>ProblemDetails</code> kullanımı sayesinde tüm hatalarınız standart bir formatta olur, bu da istemci (frontend, mobil, diğer servisler) geliştirmeyi çok kolaylaştırır.</p>
</li>
<li><p><strong>Yüksek Performans:</strong> Beklenen hatalar için istisna fırlatma maliyetinden kaçınılır.</p>
</li>
<li><p><strong>Geliştirici Dostu:</strong> Implicit operator'ler ve yardımcı metotlar, deseni kullanmayı son derece kolay ve akıcı hale getirir. <code>return product;</code> veya <code>return Errors.NotFound(...);</code> gibi ifadelerle kodunuz temiz kalır.</p>
</li>
</ol>
<h3 id="heading-ek-bolum-ornek-proje-mimarisi-ve-klasor-yapisi">Ek Bölüm: Örnek Proje Mimarisi ve Klasör Yapısı</h3>
<p>Bu bölümde, anlattığımız Result Pattern'in ve ilgili sınıfların, modern, test edilebilir ve sürdürülebilir bir .NET projesinde nasıl organize edilebileceğini göreceğiz. Önerilen yapı, katmanların sorumluluklarını net bir şekilde ayıran <strong>Clean Architecture (Temiz Mimari)</strong> prensiplerini temel almaktadır.</p>
<h4 id="heading-projenin-genel-klasor-yapisi-ascii-tree">Projenin Genel Klasör Yapısı (ASCII Tree)</h4>
<pre><code class="lang-plaintext">/ResultPattern.sln
│
├── 📂 src/
│   │
│   ├── 📁 ResultPattern.Domain/
│   │   └── 📦 Entities/
│   │       └── 📜 Product.cs
│   │
│   ├── 📁 ResultPattern.Application/
│   │   ├── 📦 Abstractions/
│   │   │   └── 📜 IProductService.cs
│   │   ├── 📦 Core/
│   │   │   └── 📜 Result.cs             // Result ve Result&lt;TValue&gt; sınıfları
│   │   ├── 📦 DTOs/
│   │   │   ├── 📜 CreateProductRequest.cs
│   │   │   └── 📜 UpdatePriceRequest.cs
│   │   └── 📦 Services/
│   │       └── 📜 ProductService.cs
│   │
│   ├── 📁 ResultPattern.Infrastructure/
│   │   └── 📦 Persistence/
│   │       └── 📜 AppDbContext.cs       // Örnek: Entity Framework Core context'i
│   │       └── 📦 Repositories/
│   │           └── 📜 ProductRepository.cs  // IProductRepository implementasyonu
│   │
│   └── 📁 ResultPattern.Api/ (Presentation)
│       ├── 📦 Common/
│       │   ├── 📜 ApiExtensions.cs
│       │   └── 📜 Errors.cs             // ProblemDetails üreten fabrika sınıfı
│       ├── 📦 Controllers/
│       │   └── 📜 ProductsController.cs   // MVC yaklaşımı için
│       └── 📜 Program.cs                // Minimal API endpoint'leri ve servis kayıtları
│
└── 📂 tests/
    │
    ├── 📁 ResultPattern.Application.Tests/
    │   └── 📜 ProductServiceTests.cs
    │
    └── 📁 ResultPattern.Domain.Tests/
        └── 📜 ProductTests.cs
</code></pre>
<h4 id="heading-katmanlarin-aciklamalari-ve-sorumluluklari">Katmanların Açıklamaları ve Sorumlulukları</h4>
<p><strong>1. 📖</strong> <code>ResultPattern.Domain</code></p>
<ul>
<li><p><strong>Amacı:</strong> Uygulamanızın kalbidir. İş kurallarını ve varlıkları (entities) içerir. Diğer hiçbir projeye bağımlılığı yoktur; tamamen saf ve bağımsızdır.</p>
</li>
<li><p><strong>İçerik:</strong></p>
<ul>
<li><code>Entities/Product.cs</code>: Uygulamanın temel iş nesnesi olan <code>Product</code> kaydı burada yer alır.</li>
</ul>
</li>
</ul>
<p><strong>2. ⚙️</strong> <code>ResultPattern.Application</code></p>
<ul>
<li><p><strong>Amacı:</strong> Uygulamanın iş mantığını (use case'leri) yönetir. Domain katmanındaki varlıkları kullanarak operasyonları gerçekleştirir. Altyapı (Infrastructure) ve sunum (Api) katmanlarından habersizdir.</p>
</li>
<li><p><strong>İçerik:</strong></p>
<ul>
<li><p><code>Core/Result.cs</code>: <code>Result</code> ve <code>Result&lt;TValue&gt;</code> sınıfları burada bulunur. Çünkü bu yapılar, uygulama katmanındaki servislerin dönüş tipini belirleyen temel bir araçtır.</p>
</li>
<li><p><code>Abstractions/IProductService.cs</code>: Servislerin kontratları (arayüzler) burada tanımlanır.</p>
</li>
<li><p><code>Services/ProductService.cs</code>: <code>IProductService</code> arayüzünün somut implementasyonu. Örnekte statik bir liste kullansak da, gerçek bir projede bu sınıf veritabanı işlemleri için <code>IProductRepository</code> gibi bir arayüze bağımlı olurdu.</p>
</li>
<li><p><code>DTOs/</code>: <code>CreateProductRequest</code> gibi, sunum katmanından uygulama katmanına veri taşımak için kullanılan Veri Transfer Nesneleri (Data Transfer Objects) burada yer alır.</p>
</li>
</ul>
</li>
</ul>
<p><strong>3. 🔌</strong> <code>ResultPattern.Infrastructure</code></p>
<ul>
<li><p><strong>Amacı:</strong> Veritabanları, harici servisler, dosya sistemleri gibi dış dünyayla ilgili tüm detayları içerir. Application katmanında tanımlanan arayüzleri uygular.</p>
</li>
<li><p><strong>İçerik:</strong></p>
<ul>
<li><code>Persistence/</code>: Entity Framework Core, Dapper veya başka bir ORM ile ilgili tüm kodlar burada bulunur. <code>AppDbContext</code> ve repository implementasyonları bu katmanın parçasıdır.</li>
</ul>
</li>
</ul>
<p><strong>4. 💻</strong> <code>ResultPattern.Api</code> (Sunum Katmanı)</p>
<ul>
<li><p><strong>Amacı:</strong> Dış dünyadan gelen istekleri (örn: HTTP istekleri) kabul edip uygulama katmanına yönlendiren ve uygulama katmanından dönen sonuçları dış dünyaya uygun bir formatta (örn: JSON yanıtı) sunan katmandır.</p>
</li>
<li><p><strong>İçerik:</strong></p>
<ul>
<li><p><code>Program.cs</code>: Minimal API endpoint'lerinin tanımlandığı, servislerin (DI) kaydedildiği ve uygulamanın konfigürasyonunun yapıldığı yerdir.</p>
</li>
<li><p><code>Common/ApiExtensions.cs</code>: <code>Result&lt;T&gt;</code> nesnesini <code>IResult</code>'a (HTTP yanıtına) dönüştüren <code>ToHttpResult</code> gibi extension metotları içerir. Bu, tamamen sunum katmanına ait bir sorumluluktur.</p>
</li>
<li><p><code>Common/Errors.cs</code>: <code>ProblemDetails</code> nesneleri üreten ve HTTP durum kodlarına sıkıca bağlı olan <code>Errors</code> sınıfı için en uygun yer burasıdır.</p>
</li>
<li><p><code>Controllers/ProductsController.cs</code>: Eğer Minimal API yerine MVC yaklaşımını tercih ediyorsanız, controller'larınız bu klasörde yer alır.</p>
</li>
</ul>
</li>
</ul>
<h4 id="heading-proje-referanslari-dependency-flow">Proje Referansları (Dependency Flow)</h4>
<p>Bu mimarideki en önemli kural, bağımlılıkların her zaman merkeze doğru olmasıdır.</p>
<p><code>Api</code> ➡️ <code>Application</code> ➡️ <code>Domain</code></p>
<ul>
<li><p><code>Api</code> projesi, <code>Application</code> projesine referans verir.</p>
</li>
<li><p><code>Infrastructure</code> projesi, <code>Application</code> projesine referans verir.</p>
</li>
<li><p><code>Application</code> projesi, <code>Domain</code> projesine referans verir.</p>
</li>
<li><p><code>Domain</code> projesi hiçbir projeye referans vermez.</p>
</li>
</ul>
<p>Bu yapı, Result Pattern'in "sorumlulukları ayırma" felsefesini proje geneline yayarak esnek, test edilebilir ve bakımı kolay bir uygulama geliştirmenizi sağlar.</p>
]]></content:encoded></item><item><title><![CDATA[.NET Ayarlarınızı Yönetmenin Modern ve Güvenli Yolu: Options Pattern]]></title><description><![CDATA[Uygulamalarımızın can damarı olan yapılandırma (configuration) ayarlarını nasıl yönetiyorsunuz? Veritabanı bağlantıları, API anahtarları, servis adresleri... Bu değerli bilgileri kodun içine gömmek (hard-coding) kabul edilemez. Peki bu ayarları yönet...]]></description><link>https://blog.uygarceylan.net/net-ayarlarinizi-yonetmenin-modern-ve-guvenli-yolu-options-pattern</link><guid isPermaLink="true">https://blog.uygarceylan.net/net-ayarlarinizi-yonetmenin-modern-ve-guvenli-yolu-options-pattern</guid><category><![CDATA[.NET]]></category><category><![CDATA[asp.net core]]></category><category><![CDATA[C#]]></category><category><![CDATA[options pattern]]></category><category><![CDATA[configuration]]></category><dc:creator><![CDATA[Uygar Öztürk Ceylan]]></dc:creator><pubDate>Thu, 09 Oct 2025 19:52:27 GMT</pubDate><content:encoded><![CDATA[<p>Uygulamalarımızın can damarı olan yapılandırma (configuration) ayarlarını nasıl yönetiyorsunuz? Veritabanı bağlantıları, API anahtarları, servis adresleri... Bu değerli bilgileri kodun içine gömmek (hard-coding) kabul edilemez. Peki bu ayarları yönetmenin en doğru, en modern ve en <strong>güvenli</strong> yolu nedir?</p>
<p>Cevap, .NET'in bize sunduğu zarif bir desende gizli: <strong>Options Pattern</strong>.</p>
<p>Bu yazıda, .NET 9'da Options Pattern'i sıfırdan ele alacağız. Sadece nasıl çalıştığını değil, aynı zamanda ayarlarımızı daha en başından nasıl doğrulanabilir ve kurşun geçirmez hale getirebileceğimizi bolca örnekle öğreneceğiz. Arkanıza yaslanın, çünkü <code>appsettings.json</code> karmaşasına son veriyoruz!</p>
<h3 id="heading-neden-options-patterne-ihtiyacimiz-var">Neden Options Pattern'e İhtiyacımız Var?</h3>
<p>Eski yöntemleri hatırlayalım: Kod içinde sihirli string'ler (<code>configuration["Smtp:Server"]</code>), tip dönüşümüyle uğraşma, test yazmanın zorluğu... Bunların hepsi hem hata yapmaya çok açık hem de kod kalitesini düşüren alışkanlıklardı.</p>
<p>Options Pattern bu sorunları kökünden çözer:</p>
<ul>
<li><p><strong>Güçlü Tip Desteği (Strongly-Typed):</strong> String'lerle değil, bildiğiniz C# nesneleriyle (POCO) çalışırsınız.</p>
</li>
<li><p><strong>Merkezi Yönetim:</strong> Ayarlarınız tek bir yerden yönetilir ve uygulamanın her yerine kolayca dağıtılır.</p>
</li>
<li><p><strong>Test Edilebilirlik:</strong> Ayarlarınızı taklit etmek (mock) ve testler yazmak çocuk oyuncağı haline gelir.</p>
</li>
<li><p><strong>Güvenilirlik:</strong> Ayarlarınızı uygulama başlarken doğrulayarak (validate) hataları anında yakalarsınız.</p>
</li>
</ul>
<p>Hadi modern bir .NET projesinde bu deseni nasıl uygulayacağımıza bakalım.</p>
<h3 id="heading-adim-adim-modern-options-pattern-kullanimi">Adım Adım Modern Options Pattern Kullanımı</h3>
<p>Senaryomuz basit: Uygulamamızın bir e-posta gönderme özelliği var ve SMTP ayarlarını <code>appsettings.json</code> dosyasından güvenli bir şekilde okumak istiyoruz.</p>
<h4 id="heading-adim-1-appsettingsjson-dosyasini-hazirlama">Adım 1: <code>appsettings.json</code> Dosyasını Hazırlama</h4>
<p>Her şeyin başladığı yer! Projenizin <code>appsettings.json</code> dosyasına ayarlarımızı ekleyelim.</p>
<pre><code class="lang-bash">{
  <span class="hljs-string">"Logging"</span>: {
    // ...
  },
  <span class="hljs-string">"AllowedHosts"</span>: <span class="hljs-string">"*"</span>,
  <span class="hljs-string">"SmtpSettings"</span>: {
    <span class="hljs-string">"Server"</span>: <span class="hljs-string">"smtp.example.com"</span>,
    <span class="hljs-string">"Port"</span>: 587,
    <span class="hljs-string">"Username"</span>: <span class="hljs-string">"user@example.com"</span>,
    <span class="hljs-string">"Password"</span>: <span class="hljs-string">"gizli-bir-sifre"</span>,
    <span class="hljs-string">"EnableSsl"</span>: <span class="hljs-literal">true</span>
  }
}
</code></pre>
<p>Burada <code>SmtpSettings</code> adında, sınıfımızın (class) adıyla aynı olacak şekilde bir bölüm oluşturduk. Bu isimlendirme standardı birazdan çok işimize yarayacak.</p>
<h4 id="heading-adim-2-ayar-sinifini-poco-ve-kurallarini-tanimlama">Adım 2: Ayar Sınıfını (POCO) ve Kurallarını Tanımlama</h4>
<p>Şimdi JSON yapısıyla eşleşen ve <strong>doğrulama kurallarını içeren</strong> C# sınıfımızı oluşturalım. <code>System.ComponentModel.DataAnnotations</code> kütüphanesinden gelen attribute'ları burada kullanacağız.</p>
<pre><code class="lang-csharp"><span class="hljs-comment">// Models/SmtpSettings.cs</span>

<span class="hljs-keyword">using</span> System.ComponentModel.DataAnnotations;

<span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title">SmtpSettings</span>
{
    [<span class="hljs-meta">Required(ErrorMessage = <span class="hljs-meta-string">"SMTP Sunucusu boş olamaz."</span>)</span>]
    <span class="hljs-keyword">public</span> <span class="hljs-keyword">string</span> Server { <span class="hljs-keyword">get</span>; <span class="hljs-keyword">set</span>; }

    [<span class="hljs-meta">Range(1, 65535, ErrorMessage = <span class="hljs-meta-string">"Port numarası 1 ile 65535 arasında olmalıdır."</span>)</span>]
    <span class="hljs-keyword">public</span> <span class="hljs-keyword">int</span> Port { <span class="hljs-keyword">get</span>; <span class="hljs-keyword">set</span>; }

    [<span class="hljs-meta">Required</span>]
    [<span class="hljs-meta">EmailAddress</span>]
    <span class="hljs-keyword">public</span> <span class="hljs-keyword">string</span> Username { <span class="hljs-keyword">get</span>; <span class="hljs-keyword">set</span>; }

    [<span class="hljs-meta">Required</span>]
    [<span class="hljs-meta">MinLength(6)</span>]
    <span class="hljs-keyword">public</span> <span class="hljs-keyword">string</span> Password { <span class="hljs-keyword">get</span>; <span class="hljs-keyword">set</span>; }

    <span class="hljs-keyword">public</span> <span class="hljs-keyword">bool</span> EnableSsl { <span class="hljs-keyword">get</span>; <span class="hljs-keyword">set</span>; }
}
</code></pre>
<p>Gördüğünüz gibi, sadece property'leri tanımlamakla kalmadık, aynı zamanda hangi alanın zorunlu olduğunu, hangisinin bir e-posta formatında olması gerektiğini de belirttik. Kuralları verinin kendisine en yakın yere koymuş olduk.</p>
<h4 id="heading-adim-3-servisleri-modern-yontemle-konfigure-etme-programcs">Adım 3: Servisleri Modern Yöntemle Konfigüre Etme (<code>Program.cs</code>)</h4>
<p>İşte sihrin gerçekleştiği yer! .NET'e ayarlarımızı nasıl okuyacağını, bağlayacağını ve en önemlisi nasıl doğrulayacağını tek bir akıcı (fluent) kod bloğuyla söyleyeceğiz.</p>
<p>Projenizin <code>Program.cs</code> dosyasını açın ve aşağıdaki satırları ekleyin:</p>
<pre><code class="lang-csharp"><span class="hljs-comment">// Program.cs</span>

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

<span class="hljs-comment">// --- Options Pattern Yapılandırması ---</span>
builder.Services.AddOptions&lt;SmtpSettings&gt;()
    .BindConfiguration(<span class="hljs-keyword">nameof</span>(SmtpSettings))
    .ValidateDataAnnotations()
    .ValidateOnStart();
<span class="hljs-comment">// -------------------------------------------</span>

<span class="hljs-comment">// Örnek bir servis ekleyelim</span>
builder.Services.AddScoped&lt;IEmailService, EmailService&gt;();

<span class="hljs-comment">// Kalan servisleriniz...</span>

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

<span class="hljs-comment">// ... (kalan kod)</span>
</code></pre>
<p>Bu zincirleme yapı ne anlama geliyor? Tek tek inceleyelim:</p>
<ul>
<li><p><code>.AddOptions&lt;SmtpSettings&gt;()</code>: Options Pattern sürecini başlatır ve <code>SmtpSettings</code> sınıfı için bir yapılandırma oluşturucu (OptionsBuilder) döndürür.</p>
</li>
<li><p><code>.BindConfiguration(nameof(SmtpSettings))</code>: En temiz kısım burası! <code>appsettings.json</code> dosyasında, sınıfın adıyla (<code>"SmtpSettings"</code>) eşleşen bölümü bulur ve tüm değerleri otomatik olarak <code>SmtpSettings</code> nesnesine bağlar. <code>nameof()</code> kullandığımız için "string" derdinden kurtuluruz.</p>
</li>
<li><p><code>.ValidateDataAnnotations()</code>: .NET'e "Az önce <code>SmtpSettings</code> sınıfında tanımladığım <code>[Required]</code>, <code>[Range]</code> gibi kuralları dikkate al!" der.</p>
</li>
<li><p><code>.ValidateOnStart()</code>: İşte bu bizim sigortamız! Uygulama başlar başlamaz, <code>ValidateDataAnnotations</code> ile belirtilen kuralları çalıştırır. Eğer ayarlarda herhangi bir kural ihlali varsa (örneğin <code>Port</code> değeri eksikse), uygulama <strong>başlamadan çöker</strong> ve size sorunun ne olduğunu açıkça söyleyen bir hata fırlatır.</p>
</li>
</ul>
<h4 id="heading-adim-4-ayarlari-kullanma-dependency-injection">Adım 4: Ayarları Kullanma (Dependency Injection)</h4>
<p>Yapılandırmamız artık hazır ve güvenli. Şimdi onu bir serviste kullanalım.</p>
<pre><code class="lang-csharp"><span class="hljs-comment">// Services/EmailService.cs</span>

<span class="hljs-keyword">using</span> Microsoft.Extensions.Options;

<span class="hljs-keyword">public</span> <span class="hljs-keyword">interface</span> <span class="hljs-title">IEmailService</span>
{
    <span class="hljs-function"><span class="hljs-keyword">void</span> <span class="hljs-title">SendEmail</span>(<span class="hljs-params"><span class="hljs-keyword">string</span> to, <span class="hljs-keyword">string</span> subject, <span class="hljs-keyword">string</span> body</span>)</span>;
}

<span class="hljs-function"><span class="hljs-keyword">public</span> class <span class="hljs-title">EmailService</span>(<span class="hljs-params">IOptions&lt;SmtpSettings&gt; smtpSettings</span>) : IEmailService</span>
{
    <span class="hljs-comment">// Primary constructor'dan gelen parametreyi kullanarak</span>
    <span class="hljs-comment">// sınıf içinde kullanacağımız private field'ı hemen burada başlatıyoruz.</span>
    <span class="hljs-keyword">private</span> <span class="hljs-keyword">readonly</span> SmtpSettings _settings = smtpSettings.Value;

    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">SendEmail</span>(<span class="hljs-params"><span class="hljs-keyword">string</span> to, <span class="hljs-keyword">string</span> subject, <span class="hljs-keyword">string</span> body</span>)</span>
    {
        Console.WriteLine(<span class="hljs-string">"E-posta gönderiliyor..."</span>);
        Console.WriteLine(<span class="hljs-string">$"Server: <span class="hljs-subst">{_smtpSettings.Server}</span>:<span class="hljs-subst">{_smtpSettings.Port}</span>"</span>);
        Console.WriteLine(<span class="hljs-string">$"Kullanıcı: <span class="hljs-subst">{_smtpSettings.Username}</span>"</span>);
        Console.WriteLine(<span class="hljs-string">$"SSL Aktif: <span class="hljs-subst">{_smtpSettings.EnableSsl}</span>"</span>);
        <span class="hljs-comment">// Burada e-posta gönderme kodu olacak.</span>
    }
}
</code></pre>
<p>Gördüğünüz gibi, ayarları kullanma şeklimiz değişmedi. <code>EmailService</code>, ayarların nasıl okunduğundan veya doğrulandığından tamamen habersiz. O sadece <code>IOptions&lt;SmtpSettings&gt;</code> arayüzü üzerinden ihtiyacı olan geçerli ayarlara ulaşıyor.</p>
<h3 id="heading-peki-ayarlar-hataliysa-ne-olur-validateonstartin-gucu">Peki Ayarlar Hatalıysa Ne Olur? <code>ValidateOnStart</code>'ın Gücü</h3>
<p>Diyelim ki bir geliştirici <code>appsettings.json</code> dosyasından <code>Port</code> satırını sildi. Eğer <code>.ValidateOnStart()</code> kullanmasaydık, uygulamanız başarıyla başlar, ancak <code>SendEmail</code> metodu çağrıldığında <code>_smtpSettings.Port</code> değeri <code>0</code> olacağı için çalışma anında (runtime) bir hata alırdınız.</p>
<p>Ancak bizim modern yapılandırmamız sayesinde, uygulamayı başlattığınız anda konsolda şöyle bir hata görürsünüz:</p>
<pre><code class="lang-bash">Microsoft.Extensions.Options.OptionsValidationException: DataAnnotation validation failed <span class="hljs-keyword">for</span> <span class="hljs-string">'SmtpSettings'</span> members: <span class="hljs-string">'Port'</span> with the error: <span class="hljs-string">'Port numarası 1 ile 65535 arasında olmalıdır.'</span>.
</code></pre>
<p>Bu "fail-fast" (hata anında dur) yaklaşımı, yapılandırma hatalarını anında yakalamanızı sağlayarak sizi production'da yaşanacak büyük sorunlardan kurtarır.</p>
<h3 id="heading-ioptions-ioptionssnapshot-ve-ioptionsmonitor"><code>IOptions</code>, <code>IOptionsSnapshot</code> ve <code>IOptionsMonitor</code></h3>
<ul>
<li><p><code>IOptions&lt;T&gt;</code> (Singleton): Ayarları uygulama başlarken bir kez okur.</p>
</li>
<li><p><code>IOptionsSnapshot&lt;T&gt;</code> (Scoped): Her HTTP isteği için ayarları yeniden okur. Web uygulamaları için harikadır.</p>
</li>
<li><p><code>IOptionsMonitor&lt;T&gt;</code> (Singleton): <code>appsettings.json</code> dosyasındaki değişiklikleri uygulama çalışırken bile anlık olarak algılar.</p>
</li>
</ul>
<p>Kullanım senaryonuza göre constructor'ınıza bu arayüzlerden uygun olanı enjekte edebilirsiniz.</p>
<h3 id="heading-ozet">Özet</h3>
<p>.NET ile uygulama geliştirirken, ayarlarınızı yapılandırmak için <code>AddOptions()</code> ile başlayan bu akıcı ve güvenli yöntemi kullanmak en iyi pratiktir. Bu yöntem size şunları sağlar:</p>
<ul>
<li><p><strong>Okunabilirlik:</strong> Zincirleme yapı, ayarların nasıl yüklendiğini ve doğrulandığını net bir şekilde anlatır.</p>
</li>
<li><p><strong>Güvenlik:</strong> <code>ValidateOnStart</code> ile hatalı yapılandırmaların uygulamanızı başlatmasını engellersiniz.</p>
</li>
<li><p><strong>Bakım Kolaylığı:</strong> <code>nameof()</code> kullanımı, "sihirli string" kaynaklı hataların önüne geçer.</p>
</li>
</ul>
<p>Artık ayarlarınızı yönetirken endişelenmenize gerek yok. Bu modern yaklaşımı benimseyin ve daha sağlam, güvenilir ve bakımı kolay uygulamalar geliştirin.</p>
]]></content:encoded></item><item><title><![CDATA[Go HTML Template ile Temiz UI: Base, Partial, FuncMap]]></title><description><![CDATA[Go’da html/template ile sunucu tarafı HTML üretimini daha düzenli ve güvenli hale getiriyoruz. Layout sistemiyle tekrar eden yapıları azaltıyor, FuncMap ile şablonlara küçük ama etkili yardımcılar ekliyoruz. Arayüzü de TailwindCSS ile sade ve şık tut...]]></description><link>https://blog.uygarceylan.net/go-html-template-ile-temiz-ui-base-partial-funcmap</link><guid isPermaLink="true">https://blog.uygarceylan.net/go-html-template-ile-temiz-ui-base-partial-funcmap</guid><category><![CDATA[golang]]></category><category><![CDATA[Go Language]]></category><category><![CDATA[htmx]]></category><category><![CDATA[Golang web development]]></category><dc:creator><![CDATA[Uygar Öztürk Ceylan]]></dc:creator><pubDate>Mon, 11 Aug 2025 12:58:29 GMT</pubDate><content:encoded><![CDATA[<p>Go’da <code>html/template</code> ile sunucu tarafı HTML üretimini daha düzenli ve güvenli hale getiriyoruz. Layout sistemiyle tekrar eden yapıları azaltıyor, FuncMap ile şablonlara küçük ama etkili yardımcılar ekliyoruz. Arayüzü de TailwindCSS ile sade ve şık tutuyoruz.</p>
<h2 id="heading-neyi-cozuyoruz">🔧 Neyi Çözüyoruz?</h2>
<ul>
<li><p><strong>Sunucu-taraflı render</strong>: SEO dostu, hızlı ilk yükleme, sade mimari.</p>
</li>
<li><p><strong>XSS koruması</strong>: <code>html/template</code> otomatik escape ile “yanlışlıkla XSS” şovunu bitiriyor.</p>
</li>
<li><p><strong>Parça/kalıp</strong>: Layout + partial düzeniyle kopyala-yapıştır çilesi yok.</p>
</li>
<li><p><strong>Tailwind ile hız</strong>: Component kütüphanesine boğulmadan temiz UI.</p>
</li>
</ul>
<h2 id="heading-temel-kavramlar">🧠 Temel Kavramlar</h2>
<ul>
<li><p><strong>text/template vs html/template</strong></p>
<p>  Go'da iki farklı template paketi var. <code>text/template</code> metin çıktıları için, <code>html/template</code> ise HTML çıktıları için kullanılır. HTML'de XSS koruması sağladığı için web projelerinde <strong>her zaman</strong> <code>html/template</code> <strong>tercih edilir</strong>.</p>
</li>
<li><p><strong>define / block</strong></p>
<p>  Template miras yapısını kurmak için kullanılır. <code>define</code> ile bir şablon bloğu tanımlanır, <code>block</code> ile bu blok başka bir dosyada override edilebilir. Örneğin <code>base.html</code> içinde <code>{{block "content" .}}...{{end}}</code> varsa, <code>home.html</code> bu bloğu doldurur. Böylece <strong>base → sayfa</strong> ilişkisi kurulur.</p>
</li>
<li><p><strong>template</strong></p>
<p>  Başka bir template dosyasını çağırmak için kullanılır. Genellikle partial (parça) dosyaları çağırmak için kullanılır: <code>{{template "partials/nav" .}}</code>. Bu sayede tekrar eden yapılar (örn. navigasyon) tek bir yerde tanımlanır.</p>
</li>
<li><p><strong>FuncMap</strong></p>
<p>  Template içinde özel fonksiyonlar tanımlamak için kullanılır. Örneğin <code>upper</code>, <code>truncate</code>, <code>fmtDate</code> gibi fonksiyonlarla veriyi biçimlendirebilirsin. Go tarafında <code>template.FuncMap</code> ile tanımlanır ve <code>Parse*</code> öncesinde şablona eklenir.</p>
</li>
<li><p><strong>Otomatik Escape (auto-escape)</strong></p>
<p>  <code>html/template</code> kullanıcıdan gelen verileri otomatik olarak encode eder. Örneğin <code>&lt;script&gt;</code> gibi zararlı içerikler HTML olarak değil, düz metin olarak basılır. Bu sayede <strong>XSS saldırılarına karşı koruma sağlanır</strong>. Ancak <code>template.HTML</code> ile escape’i devre dışı bırakmak mümkündür — dikkatli kullanılmalıdır.</p>
</li>
</ul>
<p>Basit örnek:</p>
<pre><code class="lang-go"><span class="hljs-keyword">type</span> PageData <span class="hljs-keyword">struct</span>{ Title, User <span class="hljs-keyword">string</span> }

http.HandleFunc(<span class="hljs-string">"/"</span>, <span class="hljs-function"><span class="hljs-keyword">func</span><span class="hljs-params">(w http.ResponseWriter, r *http.Request)</span></span> {
  tpl := template.Must(template.ParseFiles(<span class="hljs-string">"views/home.html"</span>))
  _ = tpl.Execute(w, PageData{Title: <span class="hljs-string">"Anasayfa"</span>, User: <span class="hljs-string">"Uygar"</span>})
})
</code></pre>
<p><code>views/home.</code>html:</p>
<pre><code class="lang-xml"><span class="hljs-meta">&lt;!doctype <span class="hljs-meta-keyword">html</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">html</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">head</span>&gt;</span><span class="hljs-tag">&lt;<span class="hljs-name">title</span>&gt;</span>{{.Title}}<span class="hljs-tag">&lt;/<span class="hljs-name">title</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">head</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">body</span>&gt;</span><span class="hljs-tag">&lt;<span class="hljs-name">h1</span>&gt;</span>Merhaba {{.User}}<span class="hljs-tag">&lt;/<span class="hljs-name">h1</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">body</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">html</span>&gt;</span>
</code></pre>
<h2 id="heading-proje-yapisi-ve-baslangic">🗂 Proje Yapısı ve Başlangıç</h2>
<p>Hızlı bir iskelet:</p>
<pre><code class="lang-bash">/cmd/server/main.go
/internal/http/router.go
/views
  /layouts/base.html
  /partials/nav.html
  /pages/home.html
/assets/tailwind.css
/public/app.css (build çıkışı)
</code></pre>
<h2 id="heading-layout-amp-partial-sistemi">🏗 Layout &amp; Partial Sistemi</h2>
<p><code>views/layouts/base.html</code></p>
<pre><code class="lang-xml">{{define "base"}}
<span class="hljs-meta">&lt;!DOCTYPE <span class="hljs-meta-keyword">html</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">html</span> <span class="hljs-attr">lang</span>=<span class="hljs-string">"tr"</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">head</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">meta</span> <span class="hljs-attr">charset</span>=<span class="hljs-string">"utf-8"</span> /&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">meta</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"viewport"</span> <span class="hljs-attr">content</span>=<span class="hljs-string">"width=device-width,initial-scale=1"</span> /&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">title</span>&gt;</span>{{block "title" .}}Varsayılan Başlık{{end}}<span class="hljs-tag">&lt;/<span class="hljs-name">title</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">link</span> <span class="hljs-attr">rel</span>=<span class="hljs-string">"stylesheet"</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"/app.css"</span> /&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">head</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">body</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"bg-neutral-50 text-neutral-900"</span>&gt;</span>
    {{template "partials/nav" .}}
    <span class="hljs-tag">&lt;<span class="hljs-name">main</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"container mx-auto max-w-4xl px-4 py-8"</span>&gt;</span>
      {{block "content" .}}{{end}}
    <span class="hljs-tag">&lt;/<span class="hljs-name">main</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">footer</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"py-8 text-center text-sm text-neutral-500"</span>&gt;</span>
      © {{.Year}} MyBlog
    <span class="hljs-tag">&lt;/<span class="hljs-name">footer</span>&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">body</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">html</span>&gt;</span>
{{end}}
</code></pre>
<p><code>views/partials/nav.html</code></p>
<pre><code class="lang-xml">{{define "partials/nav"}}
<span class="hljs-tag">&lt;<span class="hljs-name">header</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"border-b bg-white/80 backdrop-blur"</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">div</span>
    <span class="hljs-attr">class</span>=<span class="hljs-string">"container mx-auto max-w-4xl px-4 h-14 flex items-center justify-between"</span>
  &gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">a</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"/"</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"font-semibold"</span>&gt;</span>uygarceylan.net<span class="hljs-tag">&lt;/<span class="hljs-name">a</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">nav</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"flex gap-4 text-sm"</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">a</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"hover:underline"</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"/"</span>&gt;</span>Ana Sayfa<span class="hljs-tag">&lt;/<span class="hljs-name">a</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">a</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"hover:underline"</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"/posts"</span>&gt;</span>Bloglar<span class="hljs-tag">&lt;/<span class="hljs-name">a</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">nav</span>&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">header</span>&gt;</span>
{{end}}
</code></pre>
<p><code>views/pages/home.html</code></p>
<pre><code class="lang-xml">{{define "content"}}
<span class="hljs-tag">&lt;<span class="hljs-name">section</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"space-y-6"</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">h1</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"text-3xl font-bold"</span>&gt;</span>Merhaba {{.User}}<span class="hljs-tag">&lt;/<span class="hljs-name">h1</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">p</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"text-neutral-600"</span>&gt;</span>
    Bu blog Go <span class="hljs-tag">&lt;<span class="hljs-name">code</span>&gt;</span>html/template<span class="hljs-tag">&lt;/<span class="hljs-name">code</span>&gt;</span> ile render ediliyor.
  <span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"rounded-lg border border-amber-200 bg-amber-50 p-4"</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">strong</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"block mb-1"</span>&gt;</span>İpucu:<span class="hljs-tag">&lt;/<span class="hljs-name">strong</span>&gt;</span>
    Layout + partial ile tekrar eden HTML’i çöpe at.
  <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">section</span>&gt;</span>
{{end}}
</code></pre>
<blockquote>
<p>Küçük ama kritik: Sayfa dosyası <strong>sadece</strong> <code>content</code> block’larını tanımlar; base her şeyi çerçeveler.</p>
</blockquote>
<h2 id="heading-funcmap-ile-helperlar">🧰 FuncMap ile Helper’lar</h2>
<p>Go tarafında:</p>
<pre><code class="lang-go"><span class="hljs-keyword">import</span> (
  <span class="hljs-string">"html/template"</span>
  <span class="hljs-string">"strings"</span>
  <span class="hljs-string">"time"</span>
)

<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">newTemplates</span><span class="hljs-params">()</span> *<span class="hljs-title">template</span>.<span class="hljs-title">Template</span></span> {
  <span class="hljs-keyword">return</span> template.Must(template.New(<span class="hljs-string">""</span>).Funcs(template.FuncMap{
    <span class="hljs-string">"upper"</span>: strings.ToUpper,
    <span class="hljs-string">"truncate"</span>: <span class="hljs-function"><span class="hljs-keyword">func</span><span class="hljs-params">(s <span class="hljs-keyword">string</span>, n <span class="hljs-keyword">int</span>)</span> <span class="hljs-title">string</span></span> {
      <span class="hljs-keyword">if</span> <span class="hljs-built_in">len</span>(s) &lt;= n { <span class="hljs-keyword">return</span> s }
      <span class="hljs-keyword">return</span> s[:n] + <span class="hljs-string">"…"</span>
    },
    <span class="hljs-string">"fmtDate"</span>: <span class="hljs-function"><span class="hljs-keyword">func</span><span class="hljs-params">(t time.Time)</span> <span class="hljs-title">string</span></span> {
      <span class="hljs-keyword">return</span> t.Format(<span class="hljs-string">"02 Jan 2006"</span>)
    },
  }).ParseGlob(<span class="hljs-string">"views/**/*.html"</span>))
}
</code></pre>
<p>Kullanımı (örnek):</p>
<pre><code class="lang-xml"><span class="hljs-tag">&lt;<span class="hljs-name">h2</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"text-xl"</span>&gt;</span>{{upper .Title}}<span class="hljs-tag">&lt;/<span class="hljs-name">h2</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">p</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"text-sm text-neutral-500"</span>&gt;</span>{{fmtDate .PublishedAt}}<span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">p</span>&gt;</span>{{truncate .Excerpt 120}}<span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>
</code></pre>
<p>Diğer bölümlerde burayı kodumuza entegre edeceğiz</p>
<h2 id="heading-tailwindcss-entegrasyonu">🎨 TailwindCSS Entegrasyonu</h2>
<p><strong>Kurulum (Node gerektirir):</strong></p>
<pre><code class="lang-bash">yarn add -D tailwindcss@3 postcss autoprefixer
npx tailwindcss init -p
</code></pre>
<p><code>tailwind.config.js</code></p>
<pre><code class="lang-javascript"><span class="hljs-built_in">module</span>.exports = {
  <span class="hljs-attr">content</span>: [<span class="hljs-string">"./views/**/*.html"</span>, <span class="hljs-string">"./assets/**/*.js"</span>],
  <span class="hljs-attr">theme</span>: { <span class="hljs-attr">extend</span>: {} },
  <span class="hljs-attr">plugins</span>: []
}
</code></pre>
<p><code>/assets/tailwind.css</code></p>
<pre><code class="lang-css"><span class="hljs-keyword">@tailwind</span> base;
<span class="hljs-keyword">@tailwind</span> components;
<span class="hljs-keyword">@tailwind</span> utilities;

<span class="hljs-selector-class">.container</span> { @apply mx-auto px-4; }
<span class="hljs-selector-class">.btn</span> { @apply inline-flex items-center gap-2 rounded-md px-3 py-2 text-sm font-medium border bg-white <span class="hljs-attribute">hover</span>:bg-neutral-<span class="hljs-number">50</span>; }
<span class="hljs-selector-class">.input</span> { @apply w-full rounded-md border px-3 py-2 text-sm <span class="hljs-attribute">focus</span>:outline-none focus:ring-<span class="hljs-number">2</span> focus:ring-neutral-<span class="hljs-number">900</span>/<span class="hljs-number">10</span>; }
<span class="hljs-selector-class">.card</span> { @apply rounded-lg border bg-white p-4 shadow-sm; }
</code></pre>
<p><strong>Geliştirme build:</strong></p>
<pre><code class="lang-bash">npx tailwindcss -i ./assets/tailwind.css -o ./public/app.css --watch
</code></pre>
<p><strong>Prod build (minify):</strong></p>
<pre><code class="lang-bash">npx tailwindcss -i ./assets/tailwind.css -o ./public/app.css --minify
</code></pre>
<h2 id="heading-ornek-sayfa-blog-listesi">🧪 Örnek Sayfa: “Blog Listesi”</h2>
<h3 id="heading-go-veri-render">Go (veri + render)</h3>
<h3 id="heading-internalhttproutergo"><code>internal/http/router.go</code></h3>
<pre><code class="lang-go"><span class="hljs-keyword">type</span> Post <span class="hljs-keyword">struct</span> {
    ID             <span class="hljs-keyword">int</span>
    Title          <span class="hljs-keyword">string</span>
    Excerpt        <span class="hljs-keyword">string</span>
    ExcerptShort   <span class="hljs-keyword">string</span>
    PublishedAt    time.Time
    PublishedAtStr <span class="hljs-keyword">string</span>
}

<span class="hljs-keyword">type</span> Router <span class="hljs-keyword">struct</span> {
    <span class="hljs-comment">// render, HTTP yanıtını yöneten ve HTML şablonunu işleyen fonksiyondur.</span>
    <span class="hljs-comment">// Bu fonksiyon, bir HTTP yanıt yazıcısı (ResponseWriter), bir şablon adı ve veriyi bekler.</span>
    render <span class="hljs-function"><span class="hljs-keyword">func</span><span class="hljs-params">(http.ResponseWriter, <span class="hljs-keyword">string</span>, any)</span></span>
}

<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">New</span><span class="hljs-params">(render <span class="hljs-keyword">func</span>(http.ResponseWriter, <span class="hljs-keyword">string</span>, any)</span>) <span class="hljs-title">http</span>.<span class="hljs-title">Handler</span></span> {
    r := &amp;Router{render: render}
    mux := chi.NewRouter()

    <span class="hljs-comment">// Ana sayfa ve blog listesi için GET isteklerini dinler.</span>
    mux.Get(<span class="hljs-string">"/home"</span>, r.Home)
    mux.Get(<span class="hljs-string">"/posts"</span>, r.PostList)

    <span class="hljs-keyword">return</span> mux
}

<span class="hljs-comment">// Home fonksiyonu, ana sayfa için gerekli veriyi hazırlar ve render fonksiyonuna gönderir.</span>
<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-params">(r *Router)</span> <span class="hljs-title">Home</span><span class="hljs-params">(w http.ResponseWriter, _ *http.Request)</span></span> {
    <span class="hljs-comment">// Şablona gönderilecek veriyi bir map içinde topluyoruz.</span>
    data := <span class="hljs-keyword">map</span>[<span class="hljs-keyword">string</span>]any{
        <span class="hljs-string">"Title"</span>: <span class="hljs-string">"Anasayfa"</span>,
        <span class="hljs-string">"User"</span>:  <span class="hljs-string">"Onur"</span>,
        <span class="hljs-string">"Year"</span>:  time.Now().Year(),
    }
    <span class="hljs-comment">// "home" adlı şablonu (views/pages/home.html) bu veriyle işliyor ve yanıt olarak gönderiyoruz.</span>
    r.render(w, <span class="hljs-string">"home"</span>, data)
}

<span class="hljs-comment">// PostList fonksiyonu, blog yazıları listesi için veriyi hazırlar.</span>
<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-params">(r *Router)</span> <span class="hljs-title">PostList</span><span class="hljs-params">(w http.ResponseWriter, _ *http.Request)</span></span> {
    <span class="hljs-comment">// Örnek blog yazıları oluşturup bir slice içine ekliyoruz.</span>
    posts := []Post{
        {
            ID:             <span class="hljs-number">1</span>,
            Title:          <span class="hljs-string">"Go Templates ile Başlarken"</span>,
            Excerpt:        <span class="hljs-string">"Template mimarisi, base ve partial pratikleri…"</span>,
            ExcerptShort:   <span class="hljs-string">"Template mimarisi, base ve partial pratikleri…"</span>,
            PublishedAt:    time.Now().AddDate(<span class="hljs-number">0</span>, <span class="hljs-number">0</span>, <span class="hljs-number">-2</span>),
            PublishedAtStr: time.Now().AddDate(<span class="hljs-number">0</span>, <span class="hljs-number">0</span>, <span class="hljs-number">-2</span>).Format(<span class="hljs-string">"02 Jan 2006"</span>),
        },
        {
            ID:             <span class="hljs-number">2</span>,
            Title:          <span class="hljs-string">"HTMX’a Giriş"</span>,
            Excerpt:        <span class="hljs-string">"Partial render ve progressive enhancement…"</span>,
            ExcerptShort:   <span class="hljs-string">"Partial render ve progressive enhancement…"</span>,
            PublishedAt:    time.Now().AddDate(<span class="hljs-number">0</span>, <span class="hljs-number">0</span>, <span class="hljs-number">-1</span>),
            PublishedAtStr: time.Now().AddDate(<span class="hljs-number">0</span>, <span class="hljs-number">0</span>, <span class="hljs-number">-1</span>).Format(<span class="hljs-string">"02 Jan 2006"</span>),
        },
    }

    <span class="hljs-comment">// Şablona gönderilecek veriyi hazırlıyoruz.</span>
    data := <span class="hljs-keyword">map</span>[<span class="hljs-keyword">string</span>]any{
        <span class="hljs-string">"Title"</span>: <span class="hljs-string">"Yazılar"</span>,
        <span class="hljs-string">"Year"</span>:  time.Now().Year(),
        <span class="hljs-string">"Posts"</span>: posts,
    }

    <span class="hljs-comment">// "posts" adlı şablonu (views/pages/posts.html) bu veriyle işliyoruz.</span>
    r.render(w, <span class="hljs-string">"posts"</span>, data)
}
</code></pre>
<h3 id="heading-viewspagespostshtml"><code>views/pages/posts.html</code></h3>
<pre><code class="lang-xml">{{define "content"}}
<span class="hljs-tag">&lt;<span class="hljs-name">section</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"space-y-6"</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">header</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"flex items-end justify-between"</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">h1</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"text-3xl font-bold"</span>&gt;</span>{{.Title}}<span class="hljs-tag">&lt;/<span class="hljs-name">h1</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"w-64"</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">input</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"input"</span> <span class="hljs-attr">placeholder</span>=<span class="hljs-string">"Ara (dummy — HTMX sonraki yazıda)"</span> /&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">header</span>&gt;</span>

  <span class="hljs-tag">&lt;<span class="hljs-name">ul</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"grid gap-4 md:grid-cols-2"</span>&gt;</span>
    {{range .Posts}}
    <span class="hljs-tag">&lt;<span class="hljs-name">li</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"card"</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">a</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"block group"</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"/posts/{{.ID}}"</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"flex items-center justify-between"</span>&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">h2</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"text-lg font-semibold group-hover:underline"</span>&gt;</span>
            {{.Title}}
          <span class="hljs-tag">&lt;/<span class="hljs-name">h2</span>&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">span</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"text-xs text-neutral-500"</span>&gt;</span>{{.PublishedAtStr}}<span class="hljs-tag">&lt;/<span class="hljs-name">span</span>&gt;</span>
        <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">p</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"mt-2 text-sm text-neutral-600"</span>&gt;</span>{{.ExcerptShort}}<span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"mt-4"</span>&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">span</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"btn text-neutral-700"</span>&gt;</span>Oku<span class="hljs-tag">&lt;/<span class="hljs-name">span</span>&gt;</span>
        <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
      <span class="hljs-tag">&lt;/<span class="hljs-name">a</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">li</span>&gt;</span>
    {{else}}
    <span class="hljs-tag">&lt;<span class="hljs-name">li</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"card"</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">p</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"italic text-neutral-600"</span>&gt;</span>Henüz yazı yok.<span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">li</span>&gt;</span>
    {{end}}
  <span class="hljs-tag">&lt;/<span class="hljs-name">ul</span>&gt;</span>

  <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"rounded-lg border border-blue-200 bg-blue-50 p-4"</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">strong</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"block mb-1"</span>&gt;</span>Sonraki adım:<span class="hljs-tag">&lt;/<span class="hljs-name">strong</span>&gt;</span>
    Bu listeyi HTMX ile <span class="hljs-tag">&lt;<span class="hljs-name">em</span>&gt;</span>partial<span class="hljs-tag">&lt;/<span class="hljs-name">em</span>&gt;</span> olarak yeniler hale getireceğiz.
  <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">section</span>&gt;</span>
{{end}}
</code></pre>
<h3 id="heading-cmdservermaingo"><code>cmd/server/main.go</code></h3>
<pre><code class="lang-go"><span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">render</span><span class="hljs-params">(w http.ResponseWriter, name <span class="hljs-keyword">string</span>, data any)</span></span> {

    <span class="hljs-comment">// template.ParseFiles fonksiyonu, tüm şablon dosyalarını tek tek okuyup ayrıştırır.</span>
    <span class="hljs-comment">// Bu örnekte, her istekte şablonlar yeniden okunup ayrıştırılıyor.</span>
    <span class="hljs-comment">// Production ortamında bu işlemi sadece uygulama başlangıcında yapmak daha performanslıdır.</span>
    tpl := template.Must(template.ParseFiles(
        <span class="hljs-string">"views/layouts/base.html"</span>,
        <span class="hljs-string">"views/partials/nav.html"</span>,
        <span class="hljs-string">"views/pages/"</span>+name+<span class="hljs-string">".html"</span>,
    ))

    <span class="hljs-comment">// ExecuteTemplate, ana (base) şablonu çağırır ve diğer şablonları (partial, content) onun içine yerleştirir.</span>
    <span class="hljs-keyword">if</span> err := tpl.ExecuteTemplate(w, <span class="hljs-string">"base"</span>, data); err != <span class="hljs-literal">nil</span> {
        <span class="hljs-comment">// Herhangi bir hata oluşursa, sunucu hatası (500) döndürürüz.</span>
        http.Error(w, err.Error(), <span class="hljs-number">500</span>)
    }
}

<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">main</span><span class="hljs-params">()</span></span> {

    mux := http.NewServeMux()
    <span class="hljs-comment">// /app.css isteğini, public klasöründeki dosyayı sunacak şekilde yönlendiriyoruz.</span>
    mux.Handle(<span class="hljs-string">"/app.css"</span>, http.FileServer(http.Dir(<span class="hljs-string">"public"</span>)))

    <span class="hljs-comment">// Router'ı oluştururken, yukarıdaki render fonksiyonunu parametre olarak veriyoruz.</span>
    app := routerpkg.New(render)

    <span class="hljs-comment">// Gelen tüm HTTP isteklerini (varsayılan "/" yolu üzerinden) oluşturduğumuz router'a yönlendiriyoruz.</span>
    mux.Handle(<span class="hljs-string">"/"</span>, app)
    log.Println(<span class="hljs-string">"http://localhost:8081"</span>)
    _ = http.ListenAndServe(<span class="hljs-string">"localhost:8081"</span>, mux)
}
</code></pre>
<h2 id="heading-guvenlik-amp-anti-patternler">🔒 Güvenlik &amp; Anti-Pattern’ler</h2>
<ul>
<li><p><code>html/template</code> <strong>otomatik escape</strong> yapar; kullanıcı verisini güvenle basarsın.</p>
</li>
<li><p><strong>Asla</strong> doğrulanmamış HTML’i <code>template.HTML</code> ile “güvenli” yapma.</p>
</li>
<li><p>Unutma: escape, <code>{{...}}</code> çıktısında çalışır; JS eventlerinde inline string kaçışını da düzgün yap.</p>
</li>
</ul>
<p><strong>“HTML basmam lazım”</strong> diyorsan:</p>
<ul>
<li><p>İçeriği back-office’te sanitize et (ör. allowlist).</p>
</li>
<li><p>Sonra <code>template.HTML</code> ver — ama gerçekten güvenli olduğundan emin ol.</p>
</li>
</ul>
<h2 id="heading-zorluklar-vs-kolayliklar">🥊 Zorluklar vs Kolaylıklar</h2>
<p>✅ Kolaylıklar</p>
<ul>
<li><p><strong>Basit yığın:</strong> Go + Template + (ileride) HTMX/Alpine. Build karmaşası yok.</p>
</li>
<li><p><strong>SEO ve hız:</strong> SSR ile hızlı FCP, botlar mutlu.</p>
</li>
<li><p><strong>Güvenlik:</strong> Oto-escape default güvenli.</p>
</li>
</ul>
<p>⚠️ Zorluklar</p>
<ul>
<li><p><strong>Büyük front-end interaktivite</strong>: SPA konforunu birebir bekleme. (Çözüm: HTMX/Alpine parça parça.)</p>
</li>
<li><p><strong>Template mirası</strong>: <code>define/block</code> isim çakışmaları, hangi dosyada hangi block override ediliyor — dikkat ister.</p>
</li>
<li><p><strong>Önbellekleme &amp; hot-reload</strong>: Dev’de her istekte parse rahat; prod’da parse-cache veya build-time embed istersin.</p>
</li>
</ul>
<h2 id="heading-bonus-gelistirme-deneyimini-iyilestirme-vite-air">🚀 Bonus: Geliştirme Deneyimini İyileştirme (Vite + Air)</h2>
<p>Hot-reload eksikliğini <strong>tek komutla</strong> çözmek mümkün.<br />İşin püf noktası:</p>
<ul>
<li><p><strong>Vite</strong> → TailwindCSS derlemesi + <code>views</code> klasöründeki <code>.html</code> dosyalarını izler</p>
</li>
<li><p><strong>Air</strong> → Go kod değişikliklerini izler, otomatik yeniden başlatır</p>
</li>
</ul>
<h3 id="heading-1-air-kurulumu">1️⃣ Air Kurulumu</h3>
<pre><code class="lang-bash">go install github.com/cosmtrek/air@latest
</code></pre>
<p><code>.air.toml</code> (örnek):</p>
<pre><code class="lang-ini"><span class="hljs-attr">root</span> = <span class="hljs-string">"."</span>
<span class="hljs-attr">tmp_dir</span> = <span class="hljs-string">"tmp"</span>

<span class="hljs-section">[build]</span>
<span class="hljs-attr">cmd</span> = <span class="hljs-string">"go build -o ./tmp/main ./cmd/server"</span>
<span class="hljs-attr">bin</span> = <span class="hljs-string">"tmp/main"</span>
<span class="hljs-attr">include_ext</span> = [<span class="hljs-string">"go"</span>, <span class="hljs-string">"html"</span>]
<span class="hljs-attr">exclude_dir</span> = [<span class="hljs-string">"node_modules"</span>, <span class="hljs-string">"public"</span>]

<span class="hljs-section">[log]</span>
<span class="hljs-attr">time</span> = <span class="hljs-literal">true</span>
</code></pre>
<h3 id="heading-2-vite-konfigurasyonu">2️⃣ Vite Konfigürasyonu</h3>
<p><code>vite.config.js</code></p>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> { defineConfig } <span class="hljs-keyword">from</span> <span class="hljs-string">'vite'</span>
<span class="hljs-keyword">import</span> tailwindcss <span class="hljs-keyword">from</span> <span class="hljs-string">'tailwindcss'</span>
<span class="hljs-keyword">import</span> autoprefixer <span class="hljs-keyword">from</span> <span class="hljs-string">'autoprefixer'</span>

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> defineConfig({
  <span class="hljs-attr">server</span>: {
    <span class="hljs-attr">watch</span>: {
      <span class="hljs-comment">// .html dosyalarını izleyelim</span>
      <span class="hljs-attr">paths</span>: [<span class="hljs-string">'views/**/*.html'</span>]
    }
  },
  <span class="hljs-attr">css</span>: {
    <span class="hljs-attr">postcss</span>: {
      <span class="hljs-attr">plugins</span>: [tailwindcss, autoprefixer]
    }
  }
})
</code></pre>
<h3 id="heading-3-makefile-ile-tek-komut">3️⃣ Makefile ile Tek Komut</h3>
<pre><code class="lang-makefile"><span class="hljs-section">dev:</span>
\t<span class="hljs-comment"># Vite + Air aynı anda çalışsın</span>
\tconcurrently <span class="hljs-string">"yarn dev"</span> <span class="hljs-string">"air"</span>
</code></pre>
<p><code>package.json</code></p>
<pre><code class="lang-json">{
  <span class="hljs-attr">"scripts"</span>: {
    <span class="hljs-attr">"dev"</span>: <span class="hljs-string">"vite"</span>,
    <span class="hljs-attr">"dev:css"</span>: <span class="hljs-string">"tailwindcss -i ./assets/tailwind.css -o ./public/app.css --watch"</span>
  },
  <span class="hljs-attr">"devDependencies"</span>: {
    <span class="hljs-attr">"vite"</span>: <span class="hljs-string">"^5.0.0"</span>,
    <span class="hljs-attr">"tailwindcss"</span>: <span class="hljs-string">"^3.4.0"</span>,
    <span class="hljs-attr">"autoprefixer"</span>: <span class="hljs-string">"^10.4.0"</span>,
    <span class="hljs-attr">"concurrently"</span>: <span class="hljs-string">"^8.0.0"</span>
  }
}
</code></pre>
<h2 id="heading-htmltemplatei-tercih-etmek-icin-uygun-senaryolar">🧭 <code>html/template'i</code> Tercih Etmek İçin Uygun Senaryolar</h2>
<p>✅ <strong>Statik ağırlıklı içerikler</strong></p>
<ul>
<li><p>Blog, haber, dokümantasyon gibi sayfa odaklı projelerde mükemmel çalışır.</p>
</li>
<li><p>Hızlı ilk yükleme, SEO dostu yapı.</p>
</li>
</ul>
<p>✅ <strong>Yönetim panelleri ve dashboard’lar</strong></p>
<ul>
<li><p>SSR ile hızlı tepki veren sayfalar üretilebilir.</p>
</li>
<li><p>Görsel tutarlılığı layout/partial yapısı sağlar. ✅ <strong>Basit formlar, CRUD işlemleri</strong></p>
</li>
<li><p>Backend’de üretilen form yapıları için uygundur.</p>
</li>
<li><p>Validasyon sonrası sayfanın tamamını ya da bir kısmını kolayca güncelleyebilirsiniz.</p>
</li>
</ul>
<h2 id="heading-ne-zaman-alternatif-dusunmeli">⚠️ Ne Zaman Alternatif Düşünmeli?</h2>
<p>❌ <strong>Ağır client-side etkileşimler</strong></p>
<ul>
<li><p>Offline modlar</p>
</li>
<li><p>Karmaşık form sihirbazları</p>
</li>
<li><p>Çok adımlı state’li UI’ler</p>
</li>
</ul>
<blockquote>
<p>Bu tür senaryolarda <strong>SSR tabanlı başla</strong>, gerektiğinde <strong>HTMX</strong> veya <strong>Alpine.js</strong> gibi çözümlerle client tarafını güçlendir.</p>
</blockquote>
<h2 id="heading-sik-yapilan-hatalar">🚨 Sık Yapılan Hatalar</h2>
<ul>
<li><p><code>text/template</code> ile HTML basmak → XSS riski.</p>
</li>
<li><p><code>Execute</code> yerine <code>ExecuteTemplate("base", ...)</code> çağırmamak → layout çalışmaz.</p>
</li>
<li><p><code>Funcs()</code>’ı <code>Parse*</code>’dan sonra çağırmak → helper’lar tanınmaz.</p>
</li>
<li><p>Aynı <code>define</code> adını iki farklı dosyada hatalı kullanmak → override karmaşası.</p>
</li>
<li><p>Template’te <code>.Title</code> beklerken Go tarafında <code>Title</code> vermemek → <code>zero</code> string ve sessiz hatalar.</p>
</li>
</ul>
<h2 id="heading-sss">❓ SSS</h2>
<details><summary>template.Must prod’da kalmalı mı?</summary><div data-type="detailsContent">Güvenli ama panik attırır. Prod’da template’leri uygulama açılışında parse etmek zaten mantıklı — parse hatası varsa servis ayağa kalkmasın.</div></details><details><summary>Template’leri embed edebilir miyim?</summary><div data-type="detailsContent">Evet, Go <code>embed</code> ile binary içine gömebilirsin. Deploy sadeleşir.</div></details><details><summary>Tailwind JIT prod’da ağır mı?</summary><div data-type="detailsContent">Build aşamasında minify eder ve kullanılmayan class’ları budar. Üretimde tek <code>app.css</code> ile gayet hafif.</div></details><details><summary>HTML’i nasıl güvenle göstereceğim?</summary><div data-type="detailsContent">Admin onaylı/sanitize edilmiş içerikleri <code>template.HTML</code> olarak ver. Kullanıcı input’unu ASLA doğrudan bastırma.</div></details>

<h2 id="heading-kapanis">🏁 Kapanış</h2>
<p>Bu bölümde:</p>
<ul>
<li><p><code>html/template</code> ile <strong>base/partial</strong> mimarisini kurduk,</p>
</li>
<li><p><code>FuncMap</code> ile helper yazdık,</p>
</li>
<li><p>“Blog Listesi” sayfası yaptık,</p>
</li>
<li><p>Güvenlik ve pratik tuzakları konuştuk.</p>
</li>
</ul>
<h2 id="heading-bir-sonraki-bolumde-ne-var">🔜 Bir Sonraki Bölümde Ne Var?</h2>
<blockquote>
<p><strong>Bölüm 2: HTMX ile Dinamik Parçalar</strong></p>
</blockquote>
<p>Sıradaki yazımızda, <code>html/template</code> sistemimizi <strong>HTMX</strong> ile zenginleştireceğiz.<br />Arama kutusu gibi bir input ile, sadece sayfanın bir bölümünü (örneğin liste alanını) güncelleyen interaktif yapılar kuracağız:</p>
<ul>
<li><p>Sayfa yenilemeden içerik güncelleme</p>
</li>
<li><p><code>hx-get</code>, <code>hx-target</code>, <code>hx-swap</code> gibi temel HTMX kavramları</p>
</li>
<li><p>Liste filtreleme örneği</p>
</li>
<li><p>Kodla açıklanan canlı demo parçaları</p>
</li>
</ul>
<p>👉 Hiçbir framework ya da build sistemi kullanmadan, sadece HTML ve Go ile <strong>SPA benzeri deneyimler</strong> oluşturmanın yolunu göreceğiz.</p>
<h2 id="heading-kaynak-kodu">📬 Kaynak Kodu</h2>
<ul>
<li>GitHub repo: <strong>https://github.com/uodev/go-htmx-blog</strong></li>
</ul>
]]></content:encoded></item><item><title><![CDATA[🧠 Mikroservislerde Rich Domain Modelleri: DDD ve CQRS ile Karmaşayı Ehlileştirin]]></title><description><![CDATA[Mikroservis mimarileri, günümüz yazılım dünyasının parlayan yıldızı. Esneklik, ölçeklenebilirlik ve bağımsız geliştirme imkanı sunarken, karmaşık iş mantığını yönetmek ve sürdürülebilir bir kod tabanı oluşturmak çoğu zaman büyük bir meydan okuma hali...]]></description><link>https://blog.uygarceylan.net/mikroservislerde-rich-domain-modelleri-ddd-ve-cqrs-ile-karmasayi-ehlilestirin</link><guid isPermaLink="true">https://blog.uygarceylan.net/mikroservislerde-rich-domain-modelleri-ddd-ve-cqrs-ile-karmasayi-ehlilestirin</guid><category><![CDATA[.NET]]></category><category><![CDATA[dotnet]]></category><category><![CDATA[CORS]]></category><category><![CDATA[DDD]]></category><category><![CDATA[#Domain-Driven-Design]]></category><category><![CDATA[domain]]></category><category><![CDATA[Microservices]]></category><dc:creator><![CDATA[Uygar Öztürk Ceylan]]></dc:creator><pubDate>Thu, 03 Jul 2025 14:40:14 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1751552947971/15346d41-0f45-4e53-a157-6f12df1caec6.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Mikroservis mimarileri, günümüz yazılım dünyasının parlayan yıldızı. Esneklik, ölçeklenebilirlik ve bağımsız geliştirme imkanı sunarken, karmaşık iş mantığını yönetmek ve sürdürülebilir bir kod tabanı oluşturmak çoğu zaman büyük bir meydan okuma haline gelebilir. Her şeyin sadece veri tabanına yazıp okumaktan ibaret olduğu "<strong>Anemic Model</strong>" yaklaşımlarıyla baş etmek, uzun vadede sürdürülemez bir kod tabanına ve teknik borca yol açabilir.</p>
<p>Peki, bu karmaşayı nasıl dizginleyebiliriz? Cevap: <strong>Domain-Driven Design (DDD)</strong> prensiplerini uygulayarak ve mikroservislerinizi <strong>Rich Domain Model</strong> ile donatarak. Gelin, bu yolculuğa birlikte çıkalım!</p>
<hr />
<h2 id="heading-rich-domain-model-nedir-ve-neden-gereklidir">🧱 <strong>Rich Domain Model</strong> Nedir ve Neden Gereklidir?</h2>
<p>Basitçe ifade etmek gerekirse, <strong>Rich Domain Model</strong>, sadece veri tutan nesnelerden ibaret değildir; aynı zamanda bu veriler üzerindeki <strong>iş kurallarını ve davranışları</strong> da içinde barındırır. Her <strong>Bounded Context (Sınırlı Bağlam)</strong> veya mikroservis için tek ve bütüncül bir <strong>Domain Model</strong> tanımlamak esastır.</p>
<p>Diyelim ki bir e-ticaret sistemindeki <code>Order</code> (Sipariş) nesnesinden bahsediyoruz. Anemik bir modelde <code>Order</code>, yalnızca <code>OrderId</code>, <code>CustomerId</code>, <code>TotalAmount</code> gibi alanlara sahip basit bir veri yapısı olurdu. Siparişle ilgili tüm iş kuralları (ürün ekleme, indirim hesaplama, stok kontrolü vb.) ise genellikle ayrı <strong>Service Layer</strong> (Servis Katmanı) içinde (örneğin bir <code>OrderService</code> içinde) bulunurdu.</p>
<p>Rich bir modelde ise <code>Order</code> sınıfı, <code>AddItem(Product product, int quantity)</code> veya <code>ApplyDiscount(DiscountCode code)</code> gibi metotlara sahip olurdu. Bu metotlar, siparişle ilgili tüm iş mantığını ve kurallarını (örneğin, <code>quantity</code> sıfırdan küçük olamaz gibi) kendi içinde barındırırdı.</p>
<h3 id="heading-rich-domain-model-vs-anemic-model-temel-farklar"><strong>Rich Domain Model</strong> vs. <strong>Anemic Model</strong>: Temel Farklar</h3>
<p>Bu ayrım, yazılımın karmaşıklığı arttıkça hayati hale gelir:</p>
<div class="hn-table">
<table>
<thead>
<tr>
<td>Özellik</td><td><strong>Rich Domain Model</strong></td><td><strong>Anemic Model</strong></td></tr>
</thead>
<tbody>
<tr>
<td><strong>Data + Behavior</strong> (Veri + Davranış)</td><td>Bir arada, <strong>Entity</strong> içinde</td><td>Ayrı; davranışlar <strong>Service Layer</strong>'da</td></tr>
<tr>
<td><strong>OOP Principles</strong> (OOP Prensipleri)</td><td>✅ Kapsülleme ve sorumluluklar net</td><td>❌ Daha çok prosedürel yaklaşım</td></tr>
<tr>
<td><strong>Application Complexity</strong> (Uygulama Karmaşıklığı)</td><td>Yüksek, ancak uzun vadede sürdürülebilir</td><td>Düşük, basit CRUD için yeterli olabilir</td></tr>
<tr>
<td><strong>Recommended Scenario</strong> (Tavsiye Edilen Senaryo)</td><td>Karmaşık <strong>Domain</strong>'lerde uzun vadeli bakım için</td><td>Basit servislerde (örneğin sadece CRUD işlemleri)</td></tr>
</tbody>
</table>
</div><p>Rich <strong>Domain Model</strong>'ler, iş kurallarının kod içinde açıkça ifade edilmesini, test edilebilirliğin artmasını ve sistemin daha kolay sürdürülebilir olmasını sağlar.</p>
<hr />
<h2 id="heading-dddnin-temel-yapi-taslari-value-object-entity-ve-aggregate">🧩 DDD'nin Temel Yapı Taşları: <strong>Value Object</strong>, <strong>Entity</strong> ve <strong>Aggregate</strong></h2>
<p>Rich <strong>Domain Model</strong>'i inşa ederken, <strong>Domain-Driven Design (DDD)</strong>'ın temel yapı taşlarını doğru anlamak çok önemlidir.</p>
<h3 id="heading-value-object-deger-nesnesi-amp-entity-varlik-ayrimi"><strong>Value Object</strong> (Değer Nesnesi) &amp; <strong>Entity</strong> (Varlık) Ayrımı</h3>
<ul>
<li><p><strong>Value Object:</strong> Kimliği olmayan, sadece <strong>anlam taşıyan</strong> nesnelerdir. İki <strong>Value Object</strong>, içerikleri aynıysa birbirine eşittir. Örneğin, bir <code>Address</code> (Adres) (sokak, şehir, posta kodu), bir <code>Money</code> (Para) (miktar, para birimi) veya <code>EmailAddress</code> (E-posta Adresi) bir <strong>Value Object</strong> olabilir. "Kim" olduğundan ziyade "ne olduğu" önemlidir.</p>
<pre><code class="lang-csharp">  <span class="hljs-function"><span class="hljs-keyword">public</span> record <span class="hljs-title">Address</span>(<span class="hljs-params"><span class="hljs-keyword">string</span> Street, <span class="hljs-keyword">string</span> City, <span class="hljs-keyword">string</span> ZipCode</span>)</span>;
</code></pre>
<p>  Burada iki <code>Address</code> nesnesi aynı <code>Street</code>, <code>City</code> ve <code>ZipCode</code> değerlerine sahipse, onlar eşittir.</p>
</li>
<li><p><strong>Entity:</strong> Kimliği olan, zamanla değişebilen ve yaşam döngüsü boyunca bir <strong>benzersizliğe</strong> sahip olan nesnelerdir. Bir <code>User</code> (Kullanıcı), <code>Order</code> (Sipariş) veya <code>Product</code> (Ürün) bir <strong>Entity</strong>'dir. <strong>Entity</strong>'nin kimliği (genellikle bir ID alanı), değerleri değişse bile sabit kalır. "Ne olduğu"ndan ziyade "kim olduğu" önemlidir.</p>
<pre><code class="lang-csharp">  <span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title">User</span> {
      <span class="hljs-keyword">public</span> Guid Id { <span class="hljs-keyword">get</span>; <span class="hljs-keyword">set</span>; }
      <span class="hljs-keyword">public</span> <span class="hljs-keyword">string</span> FullName { <span class="hljs-keyword">get</span>; <span class="hljs-keyword">set</span>; }
      <span class="hljs-keyword">public</span> <span class="hljs-keyword">string</span> Email { <span class="hljs-keyword">get</span>; <span class="hljs-keyword">set</span>; }
  }
</code></pre>
</li>
</ul>
<h3 id="heading-aggregate-topluluk-ve-aggregate-root-topluluk-koku-kavrami">🏘️ <strong>Aggregate</strong> (Topluluk) ve <strong>Aggregate Root</strong> (Topluluk Kökü) Kavramı</h3>
<p>İşte kafa karıştırıcı olabilecek ama en kritik DDD kavramlarından biri!</p>
<ul>
<li><p><strong>Aggregate:</strong> Birbirine sıkı sıkıya bağlı <strong>Entity</strong> ve <strong>Value Object</strong>'lerin mantıksal bir kümesidir. Bu küme, <strong>Domain</strong> içindeki bir <strong>Consistency Boundary</strong> (Tutarlılık Sınırı)'nı temsil eder. Örneğin, bir <code>Order</code> (Sipariş), içindeki <code>OrderItem</code>'lar (Sipariş Kalemleri) ile birlikte bir <strong>Aggregate</strong> oluşturur.</p>
</li>
<li><p><strong>Aggregate Root:</strong> Bu <strong>Aggregate</strong> kümesinin dış dünyayla tek erişim noktası, yani "<strong>kapı bekçisidir</strong>." <strong>Aggregate</strong> içindeki diğer nesneler (<strong>Child Entity</strong>'ler veya <strong>Value Object</strong>'ler) doğrudan dışarıdan manipüle edilemez; tüm işlemler <strong>Aggregate Root</strong> üzerinden yapılır.</p>
<p>  <strong>Amaç:</strong> Tutarlılığı korumak ve <strong>Domain Boundary</strong> (Alan Sınırı)'nı belirgin kılmaktır. Bir işlem sırasında, <strong>Aggregate</strong> içindeki tüm nesnelerin birlikte tutarlı kalması sağlanır. Bu, aynı zamanda <strong>Transaction Boundary</strong> (İşlem Sınırı)'nı da tanımlar.</p>
</li>
</ul>
<p><strong>Örnek: Order Aggregate ve Aggregate Root (.NET)</strong></p>
<p>Bir siparişi yöneten <strong>Aggregate</strong>'i düşünelim:</p>
<pre><code class="lang-csharp"><span class="hljs-keyword">public</span> <span class="hljs-keyword">interface</span> <span class="hljs-title">IAggregateRoot</span> {}

<span class="hljs-comment">// Aggregate Root</span>
<span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title">Order</span> : <span class="hljs-title">Entity</span>, <span class="hljs-title">IAggregateRoot</span> <span class="hljs-comment">// IAggregateRoot bir işaretleyici arayüz olabilir</span>
{
    <span class="hljs-keyword">private</span> List&lt;OrderItem&gt; _orderItems = <span class="hljs-keyword">new</span>();
    <span class="hljs-keyword">public</span> IReadOnlyCollection&lt;OrderItem&gt; OrderItems =&gt; _orderItems.AsReadOnly(); <span class="hljs-comment">// Dışarıdan sadece okunabilir</span>

    <span class="hljs-keyword">public</span> DateTime OrderDate { <span class="hljs-keyword">get</span>; <span class="hljs-keyword">private</span> <span class="hljs-keyword">set</span>; }
    <span class="hljs-keyword">public</span> Address ShippingAddress { <span class="hljs-keyword">get</span>; <span class="hljs-keyword">private</span> <span class="hljs-keyword">set</span>; } <span class="hljs-comment">// Value Object</span>

    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-title">Order</span>(<span class="hljs-params">Address shippingAddress</span>)</span>
    {
        ShippingAddress = shippingAddress;
        OrderDate = DateTime.UtcNow;
    }

    <span class="hljs-comment">// OrderItem'lar sadece Order üzerinden yönetilir</span>
    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">AddItem</span>(<span class="hljs-params"><span class="hljs-keyword">int</span> productId, <span class="hljs-keyword">string</span> name, <span class="hljs-keyword">decimal</span> price, <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 positive."</span>);

        <span class="hljs-keyword">var</span> existingItem = _orderItems.FirstOrDefault(i =&gt; i.ProductId == productId);
        <span class="hljs-keyword">if</span> (existingItem <span class="hljs-keyword">is</span> not <span class="hljs-literal">null</span>)
            existingItem.IncreaseQuantity(quantity); <span class="hljs-comment">// İş kuralı OrderItem içinde</span>
        <span class="hljs-keyword">else</span>
            _orderItems.Add(<span class="hljs-keyword">new</span> OrderItem(productId, name, price, quantity));
    }
    <span class="hljs-comment">// Diğer iş metotları (örneğin CompleteOrder, CancelOrder vb.)</span>
}

<span class="hljs-comment">// Child Entity (Aggregate içindeki)</span>
<span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title">OrderItem</span> : <span class="hljs-title">Entity</span>
{
    <span class="hljs-keyword">public</span> <span class="hljs-keyword">int</span> ProductId { <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> ProductName { <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">decimal</span> UnitPrice { <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">public</span> <span class="hljs-title">OrderItem</span>(<span class="hljs-params"><span class="hljs-keyword">int</span> productId, <span class="hljs-keyword">string</span> productName, <span class="hljs-keyword">decimal</span> price, <span class="hljs-keyword">int</span> quantity</span>)</span>
    {
        ProductId = productId;
        ProductName = productName;
        UnitPrice = price;
        Quantity = quantity;
    }

    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">IncreaseQuantity</span>(<span class="hljs-params"><span class="hljs-keyword">int</span> amount</span>)</span>
    {
        <span class="hljs-comment">// Kendi iş kuralı</span>
        <span class="hljs-keyword">if</span> (amount &lt;= <span class="hljs-number">0</span>) <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> ArgumentException(<span class="hljs-string">"Amount must be positive."</span>);
        Quantity += amount;
    }
}
</code></pre>
<p>Yukarıdaki örnekte:</p>
<ul>
<li><p><code>Order</code> sınıfı <strong>Aggregate Root</strong>'tur.</p>
</li>
<li><p><code>OrderItem</code>'lar, <code>Order</code> <strong>Aggregate</strong>'i içindeki <strong>Child Entity</strong>'lerdir.</p>
</li>
<li><p><code>OrderItem</code>'lara doğrudan dışarıdan ulaşıp ekleme/silme yapılmaz; <code>Order</code>'ın <code>AddItem</code> metodu aracılığıyla yönetilirler. Bu, kuralların (örneğin <code>Quantity</code> artışı) tek bir yerde kontrol edilmesini ve <strong>consistency (tutarlılığın) sağlanmasını</strong> garantiler.</p>
</li>
</ul>
<hr />
<h2 id="heading-bounded-context-sinirli-baglam-mahallelerin-ayrimi">🌍 <strong>Bounded Context</strong> (Sınırlı Bağlam): Mahallelerin Ayrımı</h2>
<p><strong>Bounded Context</strong>, DDD'nin belki de en önemli kavramlarından biridir ve mikroservis mimarisinde doğrudan karşılığı vardır. Bir kavramın veya terimin sadece <strong>o bağlamda (context) belirli bir anlamı olduğu</strong> anlamına gelir. Farklı bağlamlarda aynı terim, farklı anlamlara veya farklı <strong>Model</strong>'lere sahip olabilir.</p>
<p><strong>Örnek: "User" (Kullanıcı) Kavramı</strong></p>
<p>Bir e-ticaret sistemini düşünelim:</p>
<ul>
<li><p><strong>Identity (Kimlik) Microservice (Mikroservis):</strong> Burada bir <code>User</code> <strong>Entity</strong>'si, kullanıcının <code>Email</code> (E-posta), <code>PasswordHash</code>, <code>FullName</code> (Tam Adı), <code>NationalId</code> (Kimlik Numarası) gibi tüm kimlik doğrulama ve profil bilgilerini içerebilir.</p>
<pre><code class="lang-csharp">  <span class="hljs-comment">// Identity Context - Kullanıcı detayları odaklı</span>
  <span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title">User</span>
  {
      <span class="hljs-keyword">public</span> <span class="hljs-keyword">int</span> Id { <span class="hljs-keyword">get</span>; <span class="hljs-keyword">set</span>; }
      <span class="hljs-keyword">public</span> <span class="hljs-keyword">string</span> FullName { <span class="hljs-keyword">get</span>; <span class="hljs-keyword">set</span>; }
      <span class="hljs-keyword">public</span> <span class="hljs-keyword">string</span> Email { <span class="hljs-keyword">get</span>; <span class="hljs-keyword">set</span>; }
      <span class="hljs-keyword">public</span> <span class="hljs-keyword">string</span> NationalId { <span class="hljs-keyword">get</span>; <span class="hljs-keyword">set</span>; } <span class="hljs-comment">// Kimlik bilgisi</span>
      <span class="hljs-keyword">public</span> <span class="hljs-keyword">string</span> PasswordHash { <span class="hljs-keyword">get</span>; <span class="hljs-keyword">set</span>; }
      <span class="hljs-comment">// ... Diğer kimlik bilgileri</span>
  }
</code></pre>
</li>
<li><p><strong>Ordering (Sipariş) Microservice:</strong> Sipariş mikroservisinin bir siparişi kimin verdiğini bilmesi gerekir, ancak kullanıcının tüm kimlik bilgilerine (şifresi, kimlik numarası vb.) ihtiyacı yoktur. Burada <code>User</code> yerine <code>Buyer</code> (Alıcı) adında bir <strong>Entity</strong> tanımlarız ve sadece siparişle ilgili bilgileri (örneğin <code>UserId</code>, <code>FullName</code>, <code>ShippingAddress</code>) içerir.</p>
<pre><code class="lang-csharp">  <span class="hljs-comment">// Ordering Context - Sipariş için sadece gerekli bilgiler</span>
  <span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title">Buyer</span>
  {
      <span class="hljs-keyword">public</span> <span class="hljs-keyword">int</span> Id { <span class="hljs-keyword">get</span>; <span class="hljs-keyword">set</span>; } <span class="hljs-comment">// Bu, Identity Context'teki User.Id ile eşleşir</span>
      <span class="hljs-keyword">public</span> <span class="hljs-keyword">string</span> FullName { <span class="hljs-keyword">get</span>; <span class="hljs-keyword">set</span>; }
      <span class="hljs-comment">// Siparişle ilgili diğer bilgiler, örn: PaymentMethod, PreferredShippingOption</span>
  }
</code></pre>
<p>  Aynı kişi, iki farklı "<strong>Bounded Context</strong>"te iki farklı <strong>Entity</strong> olarak modellenmiştir. Her bağlam (<strong>Context</strong>) sadece kendi ihtiyaçlarını bilir ve kendi <strong>Domain Model</strong>'ini oluşturur. Bu, mikroservislerin <strong>independence (bağımsızlığını)</strong> ve <strong>loose coupling (gevşek bağlılığını)</strong> destekler.</p>
</li>
</ul>
<h2 id="heading-service-hizmet-orkestrasyon-ve-capraz-alan-is-mantigi">🧠 <strong>Service</strong> (Hizmet): Orkestrasyon ve Çapraz Alan İş Mantığı</h2>
<p><strong>Service</strong>'ler, doğrudan bir varlığa ait olmayan veya birden fazla <strong>Aggregate</strong>'i ilgilendiren iş mantığını barındırmak için kullanılır. DDD'de iki ana <strong>Service</strong> türü bulunur: <strong>Domain Service</strong> (Alan Hizmeti) ve <strong>Application Service</strong> (Uygulama Hizmeti).</p>
<h4 id="heading-domain-service-domain-layerdaki-alan-katmani-is-akislari">🔸 <strong>Domain Service</strong>: <strong>Domain Layer</strong>'daki (Alan Katmanı) İş Akışları</h4>
<p><strong>Domain Service</strong>'ler, belirli bir <strong>Aggregate</strong>'e ait olmayan ancak <strong>Domain Layer</strong> için önemli olan iş süreçlerini veya hesaplamaları gerçekleştirir. Genellikle <strong>stateless</strong> (durumsuz) olurlar ve <strong>Aggregate</strong>'ler arasında köprü görevi görebilirler.</p>
<ul>
<li><p><strong>Cross-Aggregate Interaction</strong> (Çoklu Aggregate Etkileşimi): Bir işlem birden fazla <strong>Aggregate</strong>'i ilgilendiriyorsa (örneğin, bir kullanıcının bakiyesinden düşüp bir siparişin ödemesini onaylama), bu tür bir mantık <strong>Domain Service</strong>'te yer alabilir.</p>
</li>
<li><p><strong>Calculations and Policies</strong> (Hesaplamalar ve Politikalar): Karmaşık hesaplamalar veya birden fazla <strong>Aggregate</strong>'i etkileyen iş politikaları burada konumlandırılabilir.</p>
</li>
</ul>
<p><strong>Örnek (.NET):</strong></p>
<pre><code class="lang-csharp"><span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title">PricingService</span>
{
    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">decimal</span> <span class="hljs-title">CalculateDiscount</span>(<span class="hljs-params">Order order</span>)</span>
    {
        <span class="hljs-comment">// İndirim hesaplama gibi domain kuralı, doğrudan Order'ın içinde olmak zorunda değildir.</span>
        <span class="hljs-keyword">if</span> (order.TotalAmount &gt; <span class="hljs-number">1000</span>)
            <span class="hljs-keyword">return</span> order.TotalAmount * <span class="hljs-number">0.1</span>m;
        <span class="hljs-keyword">return</span> <span class="hljs-number">0</span>;
    }
}
</code></pre>
<p><code>PricingService</code>, belirli bir <code>Order</code> nesnesine bağlı olmayan, genel bir indirim hesaplama mantığını barındırır. Bu mantık, farklı siparişler veya senaryolar için tekrar kullanılabilir.</p>
<h4 id="heading-application-service-uygulama-hizmeti-application-layerdaki-uygulama-katmani-orkestrasyon">🔸 <strong>Application Service</strong> (Uygulama Hizmeti): <strong>Application Layer</strong>'daki (Uygulama Katmanı) Orkestrasyon</h4>
<p><strong>Application Service</strong>'ler, <strong>Domain Service</strong>'lerden farklı olarak doğrudan <strong>Application Layer</strong>'da yer alır. Kullanıcı isteklerini (<strong>Command</strong>'ları) alır, uygun <strong>Aggregate</strong>'leri veya <strong>Domain Service</strong>'leri çağırır, <strong>Repository</strong>'ler (Depo) aracılığıyla veritabanı işlemlerini koordine eder ve sonucu geri döndürür. Genellikle <strong>CQRS (Command Query Responsibility Segregation)</strong> deseninde <strong>Command Handler</strong> veya <strong>Query Handler</strong> olarak görev yaparlar.</p>
<ul>
<li><p><strong>Orchestration Role</strong> (Orkestrasyon Rolü): Uygulamanın dış dünyayla etkileşimini ve iç <strong>Domain Model</strong>'ini bir araya getiren bir orkestratör görevi görür. Doğrudan iş mantığı içermekten ziyade, mevcut <strong>Domain Object</strong>'lerini ve <strong>Service</strong>'leri kullanarak bir akışı yönetir.</p>
</li>
<li><p><strong>Repository Interaction</strong> (Depo Etkileşimi): Veri erişim katmanıyla (<strong>Repository</strong>'ler) iletişim kurarak <strong>Aggregate</strong>'leri yükler ve kaydeder.</p>
</li>
</ul>
<p><strong>Örnek (.NET):</strong></p>
<pre><code class="lang-csharp"><span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title">CreateOrderHandler</span> : <span class="hljs-title">IRequestHandler</span>&lt;<span class="hljs-title">CreateOrderCommand</span>&gt;
{
    <span class="hljs-keyword">private</span> <span class="hljs-keyword">readonly</span> IOrderRepository _orderRepository; <span class="hljs-comment">// Repository bağımlılığı</span>

    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-title">CreateOrderHandler</span>(<span class="hljs-params">IOrderRepository orderRepository</span>)</span>
    {
        _orderRepository = orderRepository;
    }

    <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">CreateOrderCommand command, CancellationToken cancellationToken</span>)</span>
    {
        <span class="hljs-comment">// Application Service, Aggregate'i oluşturur ve onun metodlarını çağırır.</span>
        <span class="hljs-keyword">var</span> shippingAddress = <span class="hljs-keyword">new</span> Address(command.Street, command.City, command.ZipCode);
        <span class="hljs-keyword">var</span> order = <span class="hljs-keyword">new</span> Order(shippingAddress); 

        <span class="hljs-keyword">foreach</span> (<span class="hljs-keyword">var</span> item <span class="hljs-keyword">in</span> command.Items)
        {
            order.AddItem(item.ProductId, item.ProductName, item.Price, item.Quantity);
        }

        <span class="hljs-comment">// Aggregate'i repository aracılığıyla kalıcı hale getirir.</span>
        <span class="hljs-keyword">await</span> _orderRepository.AddAsync(order); 
    }
}
</code></pre>
<p><code>CreateOrderHandler</code> bir <strong>Application Service</strong>'tir. Kullanıcının <code>CreateOrderCommand</code>'ını alır, <code>Order</code> <strong>Aggregate</strong>'ini oluşturur ve içindeki <code>AddItem</code> metodunu çağırır. Son olarak, <code>Order</code> <strong>Aggregate</strong>'ini bir <strong>Repository</strong> aracılığıyla veritabanına kaydeder. Burada işin <strong>nasıl yapılacağı</strong> (orkestrasyon) yönetilir.</p>
<hr />
<h3 id="heading-aggregate-ve-service-arasindaki-temel-farklar">🎯 <strong>Aggregate</strong> ve <strong>Service</strong> Arasındaki Temel Farklar</h3>
<div class="hn-table">
<table>
<thead>
<tr>
<td>Özellik</td><td><strong>Aggregate</strong> (Varlık Topluluğu)</td><td><strong>Service</strong> (Hizmet)</td></tr>
</thead>
<tbody>
<tr>
<td><strong>What Is It?</strong> (Nedir?)</td><td><strong>Domain Model</strong> içindeki <strong>Data + Behavior</strong> birleşimi</td><td><strong>Business Logic Carrier</strong> (İş Mantığı Taşıyıcısı) (Orkestrasyon veya çapraz kural)</td></tr>
<tr>
<td><strong>State?</strong> (Durum?)</td><td>Evet, kendi durumunu (verilerini) yönetir.</td><td>Genellikle <strong>hayır</strong>, <strong>stateless</strong>'tir (durumsuz).</td></tr>
<tr>
<td><strong>Where Does It Operate?</strong> (Nerede Çalışır?)</td><td><strong>Domain</strong> içinde, belirli bir varlığa odaklıdır.</td><td><strong>Domain Layer</strong>'da (<strong>Domain Service</strong>) veya <strong>Application Layer</strong>'da (<strong>Application Service</strong>)</td></tr>
<tr>
<td><strong>When Is It Used?</strong> (Ne Zaman Kullanılır?)</td><td>Nesnenin kendisiyle ilgili <strong>Business Rules</strong> (İş Kuralları) ve <strong>Consistency</strong> (Tutarlılık) sağlanacaksa.</td><td>Birden fazla <strong>Aggregate</strong>'i etkileyen işlemler, sistem etkileşimleri, orkestrasyon veya karmaşık hesaplamalar varsa.</td></tr>
</tbody>
</table>
</div><p>Bu ayrımı doğru yapmak, hem kodunuzun daha temiz ve anlaşılır olmasını sağlar hem de karmaşık <strong>Domain</strong>'lerde bakım ve genişletilebilirlik açısından büyük avantajlar sunar. Mikroservis mimarisinde, her mikroservisin kendi <strong>Aggregate</strong>'lerini ve bu <strong>Aggregate</strong>'leri yöneten <strong>Service</strong>'lerini barındırması, <strong>independence (bağımsızlık)</strong> ve <strong>encapsulation (kapsülleme)</strong> için kritik öneme sahiptir.</p>
<h2 id="heading-cqrs-command-query-responsibility-segregation-ile-entegrasyon">⚡️ <strong>CQRS (Command Query Responsibility Segregation)</strong> ile Entegrasyon</h2>
<p><strong>Rich Domain Model</strong>'ler ve DDD prensipleri, özellikle <strong>CQRS (Command Query Responsibility Segregation)</strong> mimarisiyle birlikte kullanıldığında tam potansiyelini ortaya koyar.</p>
<ul>
<li><p><strong>Command (Write) Side</strong>: <strong>Rich Domain Model</strong>'ler ve <strong>Aggregate</strong>'ler, genellikle CQRS'in <strong>Command (Write) Side</strong>'ında kullanılır. Kullanıcıdan gelen bir <strong>Command</strong> (Komut) (örneğin, "Sipariş Oluştur"), ilgili <strong>Aggregate Root</strong>'u yükler, iş mantığını <strong>Aggregate</strong> üzerinde çalıştırır ve <strong>Aggregate</strong>'in durumunu değiştirerek veritabanına kaydeder. Tüm iş kuralları ve tutarlılık <strong>Aggregate</strong> içinde sağlanır.</p>
</li>
<li><p><strong>Query (Read) Side</strong>: Okuma işlemleri (veri sorgulama) için ise daha basit, optimize edilmiş ve genellikle <strong>Anemic Data Model</strong>'ler (DTO'lar - Data Transfer Objects) kullanılır. Bu taraf, doğrudan veritabanından veri okuyabilir ve karmaşık <strong>Domain Logic</strong> (Alan Mantığı)'na ihtiyaç duymaz. Okuma modelleri, belirli ekran veya rapor ihtiyaçlarına göre <strong>denormalize</strong> edilebilir. Bu sayede, okuma performansından ödün vermeden, yazma tarafındaki karmaşık <strong>Domain Model</strong>'i koruyabiliriz.</p>
</li>
</ul>
<p>Bu ayrım, her iki tarafın bağımsız olarak ölçeklenmesine ve optimize edilmesine olanak tanır.</p>
<hr />
<h2 id="heading-sikca-sorulan-sorular-sss">💬 Sıkça Sorulan Sorular (SSS)</h2>
<h3 id="heading-1-her-mikroserviste-rich-domain-model-kullanmak-zorunda-miyim">1. Her mikroserviste <strong>Rich Domain Model</strong> kullanmak zorunda mıyım?</h3>
<p>Hayır. Eğer mikroservisiniz sadece basit <strong>CRUD (Create, Read, Update, Delete)</strong> işlemleri yapıyorsa ve karmaşık bir iş mantığı içermiyorsa, <strong>Anemic Model</strong> yeterli olabilir. Ancak <strong>Business Domain</strong>'inizde (İş Alanı) karmaşık kurallar ve davranışlar varsa, <strong>Rich Domain Model</strong> uzun vadede size büyük faydalar sağlayacaktır.</p>
<h3 id="heading-2-aggregateler-mikroservis-sinirlari-icinde-mi-kalmali">2. <strong>Aggregate</strong>'ler mikroservis sınırları içinde mi kalmalı?</h3>
<p>Evet, genellikle bir <strong>Aggregate</strong>, tek bir mikroservisin (veya <strong>Bounded Context</strong>'in) içindeki <strong>Consistency Boundary</strong>'yi (Tutarlılık Sınırı) temsil eder. Bir <strong>Aggregate</strong>'i birden fazla mikroservise yaymak, dağıtık işlem karmaşasına ve tutarlılık sorunlarına yol açabilir.</p>
<h3 id="heading-3-application-service-ile-domain-service-arasindaki-farki-nasil-daha-iyi-anlarim">3. <strong>Application Service</strong> ile <strong>Domain Service</strong> arasındaki farkı nasıl daha iyi anlarım?</h3>
<ul>
<li><p><strong>Application Service:</strong> "<strong>What is being done?</strong>" (Ne yapılıyor?) sorusuna cevap verir. Dış dünya ile <strong>Domain</strong> arasındaki orkestrasyonu sağlar (örneğin "Yeni Sipariş Oluştur").</p>
</li>
<li><p><strong>Domain Service:</strong> "<strong>How is it done according to business rules?</strong>" (İş kurallarına göre nasıl yapılıyor?) sorusuna cevap verir. Birden fazla <strong>Aggregate</strong>'i içeren veya tek bir <strong>Aggregate</strong>'e ait olmayan <strong>Domain Logic</strong>'i barındırır (örneğin "İndirim Hesapla").</p>
</li>
</ul>
<h3 id="heading-4-dddyi-ogrenmek-zor-mu">4. DDD'yi öğrenmek zor mu?</h3>
<p>DDD, başlangıçta karmaşık görünebilir çünkü sadece teknik bir desen değil, aynı zamanda bir düşünce yapısıdır. Ancak temel prensipleri (<strong>Bounded Context</strong>, <strong>Aggregate</strong>, <strong>Value Object</strong> vb.) anladıkça, karmaşık iş alanlarını modelleme yeteneğiniz önemli ölçüde gelişecektir. Pratik yaparak ve küçük adımlarla başlayarak ustalaşabilirsiniz.</p>
<hr />
<h2 id="heading-sonuc">🔚 Sonuç</h2>
<p>Mikroservis mimarileri, doğru yaklaşımla uygulandığında yazılım geliştirme sürecine büyük avantajlar sağlar. <strong>Domain-Driven Design</strong> ve <strong>Rich Domain Model</strong>'ler ise bu avantajları maksimuma çıkarmak için kritik bir araç setidir. İş kurallarınızı ve davranışlarınızı doğrudan <strong>Domain Object</strong>'lerinizin içine yerleştirmek, kodunuzu daha okunabilir, test edilebilir ve uzun vadede bakımı kolay hale getirir.</p>
<p>Unutmayın, "<strong>Anemic Model</strong>"ler basitlik vaat etse de, karmaşık <strong>Domain</strong>'lerde sizi hızla teknik borca ve yönetilemez bir kod tabanına sürükleyebilir. <strong>Rich Domain Model</strong>'ler ile, karmaşıklığı ehlileştirebilir, işinizi kodunuzda açıkça ifade edebilir ve daha sağlam, tutarlı mikroservisler inşa edebilirsiniz.</p>
<p>Peki, siz projelerinizde DDD prensiplerini uygularken hangi zorluklarla karşılaşıyor veya hangi faydaları görüyorsunuz? Yorumlarda düşüncelerinizi paylaşmaktan çekinmeyin!</p>
]]></content:encoded></item><item><title><![CDATA[🚀 .NET ile CQRS + MediatR Yapısını Kafaya Takmadan Öğren]]></title><description><![CDATA[CQRS ve MediatR kulağa karmaşık geliyor olabilir. Ama aslında doğru yerden bakarsan, gayet okunabilir ve sade bir yapı sunuyor.
Bu yazıda hiç veritabanına bulaşmadan, sadece bellekte (in-memory) bir ToDo listesi örneğiyle CQRS + MediatR nasıl kullanı...]]></description><link>https://blog.uygarceylan.net/net-ile-cqrs-mediatr-yapisini-kafaya-takmadan-ogren</link><guid isPermaLink="true">https://blog.uygarceylan.net/net-ile-cqrs-mediatr-yapisini-kafaya-takmadan-ogren</guid><category><![CDATA[C#]]></category><category><![CDATA[dotnet]]></category><category><![CDATA[.NET]]></category><category><![CDATA[#CQRS]]></category><category><![CDATA[MediatR]]></category><dc:creator><![CDATA[Uygar Öztürk Ceylan]]></dc:creator><pubDate>Fri, 27 Jun 2025 15:40:26 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1751037777391/e159f438-ae51-4b6d-9d00-501ff3df55b0.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>CQRS ve MediatR kulağa karmaşık geliyor olabilir. Ama aslında doğru yerden bakarsan, gayet okunabilir ve sade bir yapı sunuyor.</p>
<p>Bu yazıda hiç veritabanına bulaşmadan, <strong>sadece bellekte (in-memory)</strong> bir <code>ToDo</code> listesi örneğiyle <strong>CQRS + MediatR</strong> nasıl kullanılır onu göstereceğim.</p>
<hr />
<h2 id="heading-cqrs-nedir">🧠 CQRS Nedir?</h2>
<p>CQRS (Command Query Responsibility Segregation):<br /><strong>Yazma (Command)</strong> işlemleri ile <strong>okuma (Query)</strong> işlemlerini birbirinden ayırır. Bu sayede:</p>
<ul>
<li><p>Kodun sorumlulukları ayrılır</p>
</li>
<li><p>Handler’lar sade olur</p>
</li>
<li><p>Okunabilirlik ve test kolaylığı artar</p>
</li>
</ul>
<h2 id="heading-mediatr-nedir">🧩 MediatR Nedir?</h2>
<p>MediatR, .NET dünyasında kullanılan bir kütüphane. Amacı:</p>
<ul>
<li><p>Controller’ın doğrudan servis çağırmaması</p>
</li>
<li><p>Onun yerine bir Request objesiyle işlem yapılması</p>
</li>
</ul>
<p>Yani controller → request → MediatR → handler → işlem</p>
<h2 id="heading-proje-yapisi">🏗️ Proje Yapısı</h2>
<p>Projeyi oluştur:</p>
<pre><code class="lang-bash">dotnet new webapi -n CqrsMediatRDemo
<span class="hljs-built_in">cd</span> CqrsMediatRDemo
dotnet add package MediatR
</code></pre>
<h2 id="heading-klasor-yapisi">📁 Klasör Yapısı</h2>
<pre><code class="lang-plaintext">/CqrsMediatRDemo
│
├── Controllers/
│   └── ToDoController.cs
│
├── Domain/
│   ├── ToDo.cs
│   └── ToDoStore.cs
│
├── Application/
│   └── Features/
│       └── ToDo/
│           ├── CreateToDoCommand.cs
│           ├── CreateToDoHandler.cs
│           ├── GetAllToDosQuery.cs
│           └── GetAllToDosHandler.cs
</code></pre>
<p>⚙️ Program.cs ayarları</p>
<pre><code class="lang-csharp">builder.Services.AddMediatR(x =&gt; x.RegisterServicesFromAssembly(Assembly.GetExecutingAssembly()));
</code></pre>
<h2 id="heading-domain-katmani">🧩 Domain Katmanı</h2>
<p>🔹 ToDo.cs</p>
<pre><code class="lang-csharp"><span class="hljs-keyword">namespace</span> <span class="hljs-title">CqrsMediatRDemo.Domain</span>;

<span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title">ToDo</span>
{
    <span class="hljs-keyword">public</span> <span class="hljs-keyword">int</span> Id { <span class="hljs-keyword">get</span>; <span class="hljs-keyword">set</span>; }
    <span class="hljs-keyword">public</span> <span class="hljs-keyword">string</span> Title { <span class="hljs-keyword">get</span>; <span class="hljs-keyword">set</span>; } = <span class="hljs-keyword">string</span>.Empty;
    <span class="hljs-keyword">public</span> <span class="hljs-keyword">string</span>? Description { <span class="hljs-keyword">get</span>; <span class="hljs-keyword">set</span>; }
    <span class="hljs-keyword">public</span> DateTime CreatedAt { <span class="hljs-keyword">get</span>; <span class="hljs-keyword">set</span>; }
}
</code></pre>
<p>🔹 ToDoStore.cs</p>
<pre><code class="lang-csharp"><span class="hljs-keyword">namespace</span> <span class="hljs-title">CqrsMediatRDemo.Domain</span>;

<span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">class</span> <span class="hljs-title">ToDoStore</span>
{
    <span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> List&lt;ToDo&gt; ToDos { <span class="hljs-keyword">get</span>; <span class="hljs-keyword">set</span>; } = <span class="hljs-keyword">new</span>();
    <span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">int</span> CurrentId = <span class="hljs-number">1</span>;
}
</code></pre>
<h2 id="heading-application-katmani">🛠️ Application Katmanı</h2>
<p>🔹 CreateToDoCommand.cs</p>
<pre><code class="lang-csharp"><span class="hljs-keyword">using</span> MediatR;

<span class="hljs-keyword">namespace</span> <span class="hljs-title">CqrsMediatRDemo.Application.Features.ToDo</span>;

<span class="hljs-function"><span class="hljs-keyword">public</span> record <span class="hljs-title">CreateToDoCommand</span>(<span class="hljs-params"><span class="hljs-keyword">string</span> Title, <span class="hljs-keyword">string</span>? Description</span>) : IRequest&lt;Unit&gt;</span>;
</code></pre>
<p>🔹 CreateToDoHandler.cs</p>
<pre><code class="lang-csharp"><span class="hljs-keyword">using</span> MediatR;
<span class="hljs-keyword">using</span> CqrsMediatRDemo.Domain;

<span class="hljs-keyword">namespace</span> <span class="hljs-title">CqrsMediatRDemo.Application.Features.ToDo</span>;

<span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title">CreateToDoHandler</span> : <span class="hljs-title">IRequestHandler</span>&lt;<span class="hljs-title">CreateToDoCommand</span>, <span class="hljs-title">Unit</span>&gt;
{
    <span class="hljs-function"><span class="hljs-keyword">public</span> Task&lt;Unit&gt; <span class="hljs-title">Handle</span>(<span class="hljs-params">CreateToDoCommand request, CancellationToken cancellationToken</span>)</span>
    {
        <span class="hljs-keyword">var</span> todo = <span class="hljs-keyword">new</span> Domain.ToDo
        {
            Id = ToDoStore.CurrentId++,
            Title = request.Title,
            Description = request.Description,
            CreatedAt = DateTime.UtcNow
        };

        ToDoStore.ToDos.Add(todo);

        <span class="hljs-keyword">return</span> Task.FromResult(Unit.Value);
    }
}
</code></pre>
<p>🔹 GetAllToDosQuery.cs</p>
<pre><code class="lang-csharp"><span class="hljs-keyword">using</span> MediatR;
<span class="hljs-keyword">using</span> CqrsMediatRDemo.Domain;

<span class="hljs-keyword">namespace</span> <span class="hljs-title">CqrsMediatRDemo.Application.Features.ToDo</span>;

<span class="hljs-keyword">public</span> <span class="hljs-keyword">record</span> <span class="hljs-title">GetAllToDosQuery</span> : <span class="hljs-title">IRequest</span>&lt;<span class="hljs-title">List</span>&lt;<span class="hljs-title">Domain.ToDo</span>&gt;&gt;;
</code></pre>
<p>🔹 GetAllToDosHandler.cs</p>
<pre><code class="lang-csharp"><span class="hljs-keyword">using</span> MediatR;
<span class="hljs-keyword">using</span> CqrsMediatRDemo.Domain;

<span class="hljs-keyword">namespace</span> <span class="hljs-title">CqrsMediatRDemo.Application.Features.ToDo</span>;

<span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title">GetAllToDosHandler</span> : <span class="hljs-title">IRequestHandler</span>&lt;<span class="hljs-title">GetAllToDosQuery</span>, <span class="hljs-title">List</span>&lt;<span class="hljs-title">Domain.ToDo</span>&gt;&gt;
{
    <span class="hljs-keyword">public</span> Task&lt;List&lt;ToDo&gt;&gt; Handle(GetAllToDosQuery request, CancellationToken cancellationToken)
    {
        <span class="hljs-keyword">return</span> Task.FromResult(ToDoStore.ToDos);
    }
}
</code></pre>
<h2 id="heading-controller">🌐 Controller</h2>
<p>🔹 ToDoController.cs</p>
<pre><code class="lang-csharp"><span class="hljs-keyword">using</span> MediatR;
<span class="hljs-keyword">using</span> Microsoft.AspNetCore.Mvc;
<span class="hljs-keyword">using</span> CqrsMediatRDemo.Application.Features.ToDo;

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

[<span class="hljs-meta">ApiController</span>]
[<span class="hljs-meta">Route(<span class="hljs-meta-string">"api/todos"</span>)</span>]
<span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title">ToDoController</span> : <span class="hljs-title">ControllerBase</span>
{
    <span class="hljs-keyword">private</span> <span class="hljs-keyword">readonly</span> IMediator _mediator;

    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-title">ToDoController</span>(<span class="hljs-params">IMediator mediator</span>)</span>
    {
        _mediator = mediator;
    }

    [<span class="hljs-meta">HttpPost</span>]
    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">async</span> Task&lt;IActionResult&gt; <span class="hljs-title">Create</span>(<span class="hljs-params">CreateToDoCommand command</span>)</span>
    {
        <span class="hljs-keyword">await</span> _mediator.Send(command);
        <span class="hljs-keyword">return</span> Created();
    }

    [<span class="hljs-meta">HttpGet</span>]
    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">async</span> Task&lt;IActionResult&gt; <span class="hljs-title">GetAll</span>(<span class="hljs-params"></span>)</span>
    {
        <span class="hljs-keyword">var</span> result = <span class="hljs-keyword">await</span> _mediator.Send(<span class="hljs-keyword">new</span> GetAllToDosQuery());
        <span class="hljs-keyword">return</span> Ok(result);
    }
}
</code></pre>
<p>❓ Unit Nedir? CreateToDoCommand gibi işlemlerden veri dönmeyeceksen, MediatR Unit tipini kullanır. Bu void’un async versiyonu gibi düşünebilirsin.</p>
<pre><code class="lang-csharp"><span class="hljs-keyword">public</span> <span class="hljs-keyword">record</span> <span class="hljs-title">CreateToDoCommand</span> : <span class="hljs-title">IRequest</span>&lt;<span class="hljs-title">Unit</span>&gt;;
</code></pre>
<p>Handler içinde:</p>
<pre><code class="lang-csharp"><span class="hljs-keyword">return</span> Unit.Value;
</code></pre>
<h2 id="heading-sik-sorulan-sorular">❓ Sık Sorulan Sorular</h2>
<h3 id="heading-her-projede-cqrs-mediatr-kullanilmali-mi">💬 Her projede CQRS + MediatR kullanılmalı mı?</h3>
<p>Hayır. Küçük, tek modüllü projelerde fazla gelir. Asıl gücünü karmaşık yapılarda gösterir.</p>
<h3 id="heading-service-katmani-yoksa-ne-oluyor">💬 Service katmanı yoksa ne oluyor?</h3>
<p>MediatR zaten bir "aracı" görevi gördüğü için çoğu zaman Service katmanına gerek kalmaz. Handler’lar doğrudan işi yapar.</p>
<h3 id="heading-test-yazmasi-kolay-mi">💬 Test yazması kolay mı?</h3>
<p>Evet. Command ve Query handler’ları ayrı ayrı test edilebilir, mock dependency yapısı sayesinde unit test'e çok uygundur.</p>
<h3 id="heading-performans-farki-var-mi">💬 Performans farkı var mı?</h3>
<p>Genellikle ihmal edilebilir düzeydedir. Ancak MediatR gibi katmanlar mikroservislerde değil de, yüksek frekanslı tekil işlemlerde kullanılıyorsa dikkatli olunmalı.</p>
<hr />
<h2 id="heading-kapanis">🔚 Kapanış</h2>
<p>CQRS + MediatR yapısı, ilk bakışta "fazla katmanlı" gibi görünse de büyüyen projelerde <strong>net sorumluluk ayrımı</strong>, <strong>test kolaylığı</strong> ve <strong>daha az kafa karışıklığı</strong> sağlar.</p>
<p>Ancak:</p>
<blockquote>
<p>⚠️ Küçük projelerde <strong>gereksiz soyutlama</strong>, ileride seni yavaşlatabilir.<br />Bu yapıyı seçmeden önce <strong>gerçekten ihtiyacın olup olmadığını</strong> sorgula.</p>
</blockquote>
<p>Bu örnek, CQRS + MediatR yapısının temellerini öğrenmen için fazlasıyla yeterli.<br />Gerisi senin ihtiyacına ve projenin büyüklüğüne göre şekillenir.</p>
<blockquote>
<p>🎯 Kafaya takmadan, adım adım öğrendik.<br />Şimdi sıra sende: Kodla, kurcala, dene.</p>
</blockquote>
]]></content:encoded></item><item><title><![CDATA[Laravel Nightwatch Nedir? Nasıl Kurulur? Supervisor ile Nasıl Çalıştırılır?]]></title><description><![CDATA[Laravel uygulamanızda performans takibi, log analizi ve hata gözlemi için kullanabileceğiniz yeni bir oyuncu var: Laravel Nightwatch. Laravel ekosistemine entegre şekilde tasarlanmış bu monitoring aracı, saniyeler içinde kurulabiliyor ve arka planda ...]]></description><link>https://blog.uygarceylan.net/laravel-nightwatch-nedir-nasil-kurulur-supervisor-ile-nasil-calistirilir</link><guid isPermaLink="true">https://blog.uygarceylan.net/laravel-nightwatch-nedir-nasil-kurulur-supervisor-ile-nasil-calistirilir</guid><category><![CDATA[Laravel]]></category><category><![CDATA[laravel12]]></category><category><![CDATA[nightwatch]]></category><category><![CDATA[supervisor]]></category><category><![CDATA[ssh]]></category><category><![CDATA[monitoring]]></category><dc:creator><![CDATA[Uygar Öztürk Ceylan]]></dc:creator><pubDate>Wed, 25 Jun 2025 10:00:50 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1750849008324/53fe56ff-0522-426e-8607-b64d8b2ea2b6.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Laravel uygulamanızda performans takibi, log analizi ve hata gözlemi için kullanabileceğiniz yeni bir oyuncu var: <strong>Laravel Nightwatch</strong>. Laravel ekosistemine entegre şekilde tasarlanmış bu monitoring aracı, saniyeler içinde kurulabiliyor ve arka planda Laravel agentı ile uygulamanızı dinliyor. Bu yazıda, Nightwatch nedir, ne işe yarar, nasıl kurulur ve server ortamında nasıl sürekli çalışacak şekilde ayarlanır, adım adım anlatıyoruz.</p>
<h2 id="heading-nightwatch-nedir">📈 Nightwatch Nedir?</h2>
<p><strong>Laravel Nightwatch</strong>, Laravel uygulamaları için geliştirilmiş bir izleme (monitoring) aracıdır. Aşağıdaki olayları gerçek zamanlı olarak izleyebilir:</p>
<ul>
<li><p>HTTP request ve response performansı</p>
</li>
<li><p>Dış API çağrıları</p>
</li>
<li><p>Mail gönderimleri</p>
</li>
<li><p>Job kuyruğu ve performans takibi</p>
</li>
<li><p>Cache hit/miss durumları</p>
</li>
<li><p>Artisan komut çalıştırımları</p>
</li>
<li><p>Scheduled task durumu</p>
</li>
</ul>
<p>Ayrıca, hataları e-posta yoluyla da bildirebiliyor ve projenin tamamını tek bir panelden izlemenizi sağlıyor.</p>
<blockquote>
<p>Nightwatch, Laravel'e özel geliştirilmiş bir SaaS hizmetidir. Trilyonlarca olaydaki veriyi optimize şekilde sunabilecek altyapıya sahiptir.</p>
</blockquote>
<h2 id="heading-nightwatch-kurulumu">✨ Nightwatch Kurulumu</h2>
<h3 id="heading-1-hesap-olusturma">1. Hesap Oluşturma</h3>
<p><a target="_blank" href="https://nightwatch.laravel.com/sign-up">https://nightwatch.laravel.com/sign-up</a> adresinden bir hesap oluşturmanız gerekiyor.</p>
<h3 id="heading-2-organizasyon-olusturma">2. Organizasyon Oluşturma</h3>
<p>Hesabı açtıktan sonra sizden bir organizasyon bilgisi isteniyor:</p>
<ul>
<li><p>Organizasyon adı</p>
</li>
<li><p>Logo (opsiyonel)</p>
</li>
<li><p>Vergi numarası / fatura adresi (opsiyonel)</p>
</li>
</ul>
<h3 id="heading-3-uygulama-kurulumu">3. Uygulama Kurulumu</h3>
<p>Uygulama adı, varsa URL, logo, ve ilk ortam bilgilerini girin. Ortam adı genelde <code>Production</code> olur.</p>
<h3 id="heading-4-laravel-paket-kurulumu">4. Laravel Paket Kurulumu</h3>
<p>Terminalden şu komutla Nightwatch paketini kurun:</p>
<pre><code class="lang-markdown">composer require laravel/nightwatch
</code></pre>
<p><code>.env</code> dosyasına size verilen <code>NIGHTWATCH_TOKEN</code>'u ekleyin:</p>
<pre><code class="lang-markdown">NIGHTWATCH<span class="hljs-emphasis">_TOKEN=... (size özel token)</span>
</code></pre>
<p>Log kanalını da Nightwatch olarak değiştirin:</p>
<pre><code class="lang-markdown">LOG<span class="hljs-emphasis">_CHANNEL=nightwatch</span>
</code></pre>
<h3 id="heading-5-request-sampling-ayari">5. Request Sampling Ayarı</h3>
<p>Nightwatch'a gönderilecek istek oranını belirlemek için:</p>
<pre><code class="lang-markdown">NIGHTWATCH<span class="hljs-emphasis">_REQUEST_</span>SAMPLE<span class="hljs-emphasis">_RATE=0.1</span>
</code></pre>
<p>Bu değeri yükseltip/düşürebilirsiniz.</p>
<h3 id="heading-6-agent-calistirma">6. Agent Çalıştırma</h3>
<p>Aşağıdaki komut ile Nightwatch agentını başlatın:</p>
<pre><code class="lang-markdown">php artisan nightwatch:agent
</code></pre>
<p>Console üzerinde <code>LISTENING</code> yazıyorsa bağlantı başarılı demektir.</p>
<h2 id="heading-supervisor-ile-nightwatch-agenti-arka-planda-calistirmak">🪖 Supervisor ile Nightwatch Agentı Arka Planda Çalıştırmak</h2>
<p>Sunucunuzda agentı sürekli çalışması için Supervisor kullanabilirsiniz.</p>
<h3 id="heading-1-config-dosyasi">1. Config Dosyası</h3>
<p>Dosyayı oluşturun:</p>
<pre><code class="lang-markdown">sudo nano /etc/supervisor/conf.d/nightwatch-agent.conf
</code></pre>
<p>Içine şunu yazın:</p>
<pre><code class="lang-markdown">[program:nightwatch-agent]
process<span class="hljs-emphasis">_name=%(program_</span>name)s
command=php /var/www/projeniz/artisan nightwatch:agent
autostart=true
autorestart=true
user=www-data
numprocs=1
redirect<span class="hljs-emphasis">_stderr=true
stdout_</span>logfile=/var/www/projeniz/storage/logs/nightwatch-agent.log
stopwaitsecs=3600
</code></pre>
<h3 id="heading-2-supervisori-yenile">2. Supervisor’ı Yenile</h3>
<pre><code class="lang-markdown">sudo supervisorctl reread
sudo supervisorctl update
sudo supervisorctl start nightwatch-agent
</code></pre>
<p>Durumu kontrol etmek için:</p>
<pre><code class="lang-markdown">sudo supervisorctl status nightwatch-agent
</code></pre>
<blockquote>
<p>Agentın sürekli çalışması için bu şart. Yoksa sunucu yeniden başladığında tekrar elle başlatman gerekebilir.</p>
</blockquote>
<h2 id="heading-nightwatch-paneline-genel-bakis">📊 Nightwatch Paneline Genel Bakış</h2>
<p>Agent aktif olduktan sonra <a target="_blank" href="https://nightwatch.laravel.com">panelde</a> şunları takip edebilirsiniz:</p>
<ul>
<li><p>Hatalar</p>
</li>
<li><p>Query performansı</p>
</li>
<li><p>Job durumu (processed, released, failed)</p>
</li>
<li><p>Şema gecikmeler</p>
</li>
<li><p>Mail gönderim durumu</p>
</li>
<li><p>Scheduled task'ların zamanlaması</p>
</li>
</ul>
<p>Ayrıca şu gibi PRO+ özellikler de mevcut:</p>
<ul>
<li><p>Job threshold tanımlama</p>
</li>
<li><p>Route bazlı performans alarmı</p>
</li>
<li><p>Scheduled task timeout kontrolü</p>
</li>
</ul>
<hr />
<h2 id="heading-sonuc">🌟 Sonuç</h2>
<p>Laravel Nightwatch, modern uygulamaların izlenmesi için doğrudan Laravel takımı tarafından geliştirilen çok hafif ama bir o kadar da güçlü bir monitoring çözümü. Kurulumu kolay, kullanımı keyifli ve geliştirici deneyimini bir üst seviyeye çıkaran bir sistem.</p>
<p>Denemek isteyenler için şöyle diyebiliriz:</p>
<blockquote>
<p>1 dakikada kuruluyor, ama projeye kattığı şey aylarca uğraşmayla bile zor elde edilir.</p>
</blockquote>
<hr />
<p>Laravel Nightwatch'a kayıt olmak için: 👉 <a target="_blank" href="https://nightwatch.laravel.com/sign-up">https://nightwatch.laravel.com/sign-up</a></p>
]]></content:encoded></item><item><title><![CDATA[Dokploy ile Laravel 12 Self-Hosting]]></title><description><![CDATA[Giriş: Neden Self-Hosting, Neden Dokploy?
Yazılım geliştirme dünyasında her geçen gün daha fazla kişi, uygulamalarını kendi kontrolünde barındırmak istiyor. "Self-hosting" dediğimiz bu yaklaşım, verilerini büyük platformlara emanet etmek istemeyenler...]]></description><link>https://blog.uygarceylan.net/dokploy-ile-laravel-12-self-hosting</link><guid isPermaLink="true">https://blog.uygarceylan.net/dokploy-ile-laravel-12-self-hosting</guid><category><![CDATA[laravel12]]></category><category><![CDATA[Laravel]]></category><category><![CDATA[Livewire]]></category><category><![CDATA[self-hosted]]></category><category><![CDATA[ssh]]></category><category><![CDATA[dokploy]]></category><dc:creator><![CDATA[Uygar Öztürk Ceylan]]></dc:creator><pubDate>Tue, 24 Jun 2025 18:40:04 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1750845716434/3acab5d3-d411-4036-9e89-4fc547e93c49.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2 id="heading-giris-neden-self-hosting-neden-dokploy">Giriş: Neden Self-Hosting, Neden Dokploy?</h2>
<p>Yazılım geliştirme dünyasında her geçen gün daha fazla kişi, uygulamalarını kendi kontrolünde barındırmak istiyor. "Self-hosting" dediğimiz bu yaklaşım, verilerini büyük platformlara emanet etmek istemeyenler için hem özgürlük hem de tam kontrol anlamına geliyor. Ancak self-hosting'in klasik yolu genellikle karmaşık konfigürasyonlar, sunucu yönetimi ve zaman alıcı devops süreçleriyle dolu.</p>
<p>Tam da bu noktada, <strong>Dokploy</strong> gibi modern araçlar devreye giriyor. Dokploy, hem açık kaynak hem de son derece kullanıcı dostu bir self-hosting platformu. GitHub hesabını bağlayarak projeni birkaç tıklamayla kendi sunucuna kurabiliyorsun. Kendi sunucunda, ama zahmetsizce.</p>
<p>Ben de bu yazıda, <strong>Laravel 12</strong> ile geliştirdiğim bir web uygulamasını, Dokploy kullanarak nasıl kendi sunucuma self-host ettiğimi adım adım paylaşacağım. Bu rehberde şu sorulara yanıt bulacaksın:</p>
<ul>
<li><p>Dokploy nedir ve nasıl çalışır?</p>
</li>
<li><p>Laravel projesi nasıl deploy edilir?</p>
</li>
<li><p>SSL, veritabanı ve .env ayarları nasıl yapılır?</p>
</li>
<li><p>Deployment sonrası neler kontrol edilmeli?</p>
</li>
</ul>
<h2 id="heading-gereksinimler-proje-hazirligi">⚙️ Gereksinimler + Proje Hazırlığı</h2>
<p>Laravel 12 uygulamamızı Dokploy üzerinden deploy edebilmek için Dockerfile ve .dockerignore gibi dosyalarla projeyi hazırlamamız gerekiyor.</p>
<h3 id="heading-gerekli-araclar">✅ Gerekli Araçlar</h3>
<ul>
<li><p>Laravel 12 projesi (GitHub repo)</p>
</li>
<li><p><code>Dockerfile</code></p>
</li>
<li><p>Ubuntu sunucu (IP/domain hazır)</p>
</li>
</ul>
<h3 id="heading-dockerfile-octane-swole">🐋 Dockerfile (Octane + Swole)</h3>
<p>composer.json dosyasına eklenmesi için projenizde terminal üzerinden</p>
<pre><code class="lang-markdown">composer require laravel/octane
php artisan octane:install --server=swoole

yarn
yarn build
</code></pre>
<p>bir kerelik bu komutları çalıştırmalısınız. Ardından aşağıdaki dockerfile ve .dockerignore dosyalarını proje kök dizininde oluşturun.</p>
<pre><code class="lang-dockerfile"><span class="hljs-keyword">FROM</span> php:<span class="hljs-number">8.3</span>-cli AS base

<span class="hljs-comment"># Sistem paketleri ve PHP extension'ları</span>
<span class="hljs-keyword">RUN</span><span class="bash"> apt-get update &amp;&amp; apt-get install -y \
    git unzip curl libpng-dev libonig-dev libxml2-dev \
    libzip-dev libpq-dev libcurl4-openssl-dev libssl-dev \
    zlib1g-dev libicu-dev g++ libevent-dev procps \
    &amp;&amp; docker-php-ext-install pdo pdo_mysql pdo_pgsql mbstring zip exif pcntl bcmath sockets intl</span>

<span class="hljs-comment"># Swoole GitHub üzerinden kuruluyor</span>
<span class="hljs-keyword">RUN</span><span class="bash"> curl -L -o swoole.tar.gz https://github.com/swoole/swoole-src/archive/refs/tags/v5.1.0.tar.gz \
    &amp;&amp; tar -xf swoole.tar.gz \
    &amp;&amp; <span class="hljs-built_in">cd</span> swoole-src-5.1.0 \
    &amp;&amp; phpize \
    &amp;&amp; ./configure \
    &amp;&amp; make -j$(nproc) \
    &amp;&amp; make install \
    &amp;&amp; docker-php-ext-enable swoole</span>

<span class="hljs-comment"># Node.js 18 (vite uyumlu) ve yarn kurulumu</span>
<span class="hljs-keyword">RUN</span><span class="bash"> curl -fsSL https://deb.nodesource.com/setup_18.x | bash - \
    &amp;&amp; apt-get install -y nodejs \
    &amp;&amp; npm install -g yarn</span>

<span class="hljs-comment"># Composer kurulumu</span>
<span class="hljs-keyword">COPY</span><span class="bash"> --from=composer:latest /usr/bin/composer /usr/bin/composer</span>

<span class="hljs-keyword">WORKDIR</span><span class="bash"> /var/www</span>

<span class="hljs-comment"># Composer dosyaları ve artisan dosyasını kopyala</span>
<span class="hljs-keyword">COPY</span><span class="bash"> composer.json composer.lock artisan ./</span>

<span class="hljs-comment"># Laravel'in temel dizin yapısını oluştur</span>
<span class="hljs-keyword">RUN</span><span class="bash"> mkdir -p bootstrap/cache storage/app storage/framework/cache/data \
    storage/framework/sessions storage/framework/views storage/logs</span>

<span class="hljs-comment"># Composer bağımlılıklarını yükle (post-scripts olmadan)</span>
<span class="hljs-keyword">RUN</span><span class="bash"> composer install --no-dev --optimize-autoloader --no-interaction --prefer-dist --no-scripts</span>

<span class="hljs-comment"># Node dosyaları (vite build için cache)</span>
<span class="hljs-keyword">COPY</span><span class="bash"> package.json yarn.lock ./</span>
<span class="hljs-keyword">RUN</span><span class="bash"> yarn install --frozen-lockfile</span>

<span class="hljs-comment"># Proje dosyalarının geri kalanını kopyala</span>
<span class="hljs-keyword">COPY</span><span class="bash"> . .</span>

<span class="hljs-comment"># Composer post-scripts'leri çalıştır</span>
<span class="hljs-keyword">RUN</span><span class="bash"> composer dump-autoload --optimize</span>

<span class="hljs-comment"># Vite build</span>
<span class="hljs-keyword">RUN</span><span class="bash"> yarn build</span>

<span class="hljs-comment"># Laravel config cache (runtime'da yapılacak, build sırasında değil)</span>
<span class="hljs-keyword">RUN</span><span class="bash"> php artisan config:clear \
 &amp;&amp; php artisan route:clear \
 &amp;&amp; php artisan view:clear</span>

<span class="hljs-comment"># Dosya izinleri</span>
<span class="hljs-keyword">RUN</span><span class="bash"> chown -R www-data:www-data /var/www \
 &amp;&amp; chmod -R 775 /var/www/storage /var/www/bootstrap/cache</span>

<span class="hljs-keyword">EXPOSE</span> <span class="hljs-number">9000</span>

<span class="hljs-comment"># Startup script</span>
<span class="hljs-keyword">RUN</span><span class="bash"> <span class="hljs-built_in">echo</span> <span class="hljs-string">'#!/bin/bash\n\
# Cache configurations after environment variables are loaded\n\
php artisan config:cache\n\
php artisan route:cache\n\
php artisan view:cache\n\
# Start the server\n\
exec php artisan octane:start --server=swoole --host=0.0.0.0 --port=9000\n\
'</span> &gt; /start.sh &amp;&amp; chmod +x /start.sh</span>

<span class="hljs-keyword">CMD</span><span class="bash"> [<span class="hljs-string">"sh"</span>, <span class="hljs-string">"-c"</span>, <span class="hljs-string">"echo 'APP_KEY:' <span class="hljs-variable">$APP_KEY</span> &amp;&amp; php artisan config:cache &amp;&amp; php artisan route:cache &amp;&amp; php artisan view:cache &amp;&amp; php artisan octane:start --server=swoole --host=0.0.0.0 --port=9000"</span>]</span>
</code></pre>
<h3 id="heading-dockerignore">⚠️ <code>.dockerignore</code></h3>
<pre><code class="lang-dockerfile">node_modules
vendor
storage/logs
storage/framework/sessions
storage/framework/views
storage/framework/cache
.<span class="hljs-keyword">env</span>
.git
</code></pre>
<h2 id="heading-dokploy-sunucusu-kurulumu">🚀 Dokploy Sunucusu Kurulumu</h2>
<h3 id="heading-1-dokploy-kurulumu">1. Dokploy Kurulumu</h3>
<p>Sunucuna SSH ile bağlandıktan sonra aşağıdaki tek satırlık komutla Dokploy'u kurabilirsin:</p>
<pre><code class="lang-markdown">curl -sSL https://dokploy.com/install.sh | sh
</code></pre>
<p>Bu komut Docker dahil tüm bağımlılıkları otomatik olarak kurar. Kurulum tamamlandıktan sonra tarayıcında <code>http://sunucu-ip:3000</code> adresine giderek bir "root" hesabı oluşturabilirsin.</p>
<h3 id="heading-2-ssh-anahtari-olusturma">2. SSH Anahtarı Oluşturma</h3>
<ul>
<li><p>Sol menüden <strong>SSH Keys</strong> bölümüne gel.</p>
</li>
<li><p><strong>Add SSH Key</strong> diyerek yeni bir anahtar ekle.</p>
</li>
</ul>
<h3 id="heading-3-sunucu-tanimlama">3. Sunucu Tanımlama</h3>
<ul>
<li><p><strong>Remote Servers</strong> sekmesine gel.</p>
</li>
<li><p>"Setup Server" seçeneğini kullanarak sunucunu oluşturduğun SSH anahtarıyla tanımla.</p>
</li>
</ul>
<h3 id="heading-4-github-entegrasyonu">4. GitHub Entegrasyonu</h3>
<ul>
<li><p><strong>Git</strong> sekmesine gelerek bir GitHub uygulaması oluştur ve bağlantıyı kur.</p>
</li>
<li><p>Bu bağlantı ile GitHub’daki repolarına erişim sağlar.</p>
</li>
</ul>
<h2 id="heading-dokploy-panelinden-projeyi-deploy-etmek">🔗 Dokploy Panelinden Projeyi Deploy Etmek</h2>
<h3 id="heading-1-yeni-proje-olusturma">1. Yeni Proje Oluşturma</h3>
<ul>
<li><p>Sol menüden <strong>Projects</strong> sekmesine git.</p>
</li>
<li><p><strong>Create Project</strong> butonuna tıkla.</p>
</li>
<li><p>Proje adını ve environment’ı belirle.</p>
</li>
</ul>
<h3 id="heading-2-yeni-servis-tanimlama">2. Yeni Servis Tanımlama</h3>
<ul>
<li><p>Oluşturduğun projeye gir.</p>
</li>
<li><p>Sağ üstten <strong>Create Service</strong> → <strong>Application</strong> seç.</p>
</li>
<li><p>Sunucunu ve uygulama adını girip <strong>Create</strong> butonuna tıkla.</p>
</li>
</ul>
<h3 id="heading-3-github-repo-baglantisi">3. GitHub Repo Bağlantısı</h3>
<ul>
<li>Oluşturulan serviste kaynak kısmına gelip <strong>GitHub repo</strong> ve branch bilgisini tanımla.</li>
</ul>
<h3 id="heading-4-docker-build-ayarlari">4. Docker Build Ayarları</h3>
<ul>
<li><p>Build Type olarak <strong>Dockerfile</strong> seç.</p>
</li>
<li><p>Dockerfile adı: <code>Dockerfile</code></p>
</li>
<li><p>Docker context path: <code>.</code></p>
</li>
<li><p>Build stage (opsiyonel): -boş bırakıyoruz-</p>
</li>
</ul>
<p>İki tarafı da kaydettikten sonra <strong>Deploy</strong> butonuna tıklayarak kurulumu başlatabilirsin. Dokploy geri kalan tüm işlemleri (build, deploy, container başlatma) senin yerine halleder.</p>
<h3 id="heading-5-veritabani-olusturma">5. Veritabanı Oluşturma</h3>
<ul>
<li><p>Aynı projeye bir servis daha ekleyerek <strong>Database</strong> tipi seçilebilir.</p>
</li>
<li><p>Gerekli bağlantı bilgileri oluşturulduktan sonra <code>.env</code> dosyasında bu bilgiler kullanılır.</p>
</li>
</ul>
<p>şu anki Dockerfile dosyasından dolayı, Dokploy üzerinden proje kısmı açıkken ‘open terminal‘ ile terminali açın. Sonrasında güncellenmesi için</p>
<pre><code class="lang-markdown">php artisan optimize:clear
php artisan optimize
</code></pre>
<h2 id="heading-domain-ve-ssl-ayarlari">🌐 Domain ve SSL Ayarları</h2>
<ul>
<li><p>Domain panelinden A kaydı ile IP'ye yönlendir</p>
</li>
<li><p>Dokploy panelinden domain ekle</p>
</li>
<li><p>SSL otomatik yüklenir (Let's Encrypt)</p>
</li>
</ul>
<h2 id="heading-uygulama-yayinda">📅 Uygulama Yayında</h2>
<p>Proje build edildikten sonra uygulama yayınlanacak:</p>
<pre><code class="lang-markdown">https://senin-uygulaman.dokploy.app
</code></pre>
<p>veya bağladığın domain ile.</p>
<h2 id="heading-sonuc">🫠 Sonuç</h2>
<p>Self-hosting artık çok daha erişilebilir. Dokploy, Laravel projeni kolayca kendi sunucunda barındırmanı sağlar.</p>
<h3 id="heading-artilar">Artılar:</h3>
<ul>
<li><p>Kolay kurulum</p>
</li>
<li><p>Git push ile deploy</p>
</li>
<li><p>Otomatik SSL</p>
</li>
</ul>
<h3 id="heading-eksiler">Eksiler:</h3>
<ul>
<li><p>Docker bilmeyen için eğitici olabilir</p>
</li>
<li><p>Özel durumlar için el ile ayar gerekebilir</p>
</li>
</ul>
]]></content:encoded></item><item><title><![CDATA[Vue3 ile 404 Sayfası Oluşturma]]></title><description><![CDATA[404 Nedir?
Öncelikle "404" hatasının neden kaynaklığını bilmeyenler için açıklamak isterim. Genelde "4xx" hatalar kullanıcı taraflı olup kullanıcının yaptığı bir işlem sonucu açığa çıkarlar. "404" hatası da yine bu sebepten ortaya çıkmaktadır. Bu hat...]]></description><link>https://blog.uygarceylan.net/vue3-ile-404-sayfasi-olusturma</link><guid isPermaLink="true">https://blog.uygarceylan.net/vue3-ile-404-sayfasi-olusturma</guid><category><![CDATA[Vue.js]]></category><category><![CDATA[#vue-router]]></category><category><![CDATA[JavaScript]]></category><dc:creator><![CDATA[Uygar Öztürk Ceylan]]></dc:creator><pubDate>Sun, 07 May 2023 04:26:40 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1683433428111/18d011bd-1f34-4e6f-b20a-0db12c181837.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h3 id="heading-404-nedir">404 Nedir?</h3>
<p>Öncelikle "404" hatasının neden kaynaklığını bilmeyenler için açıklamak isterim. Genelde "4xx" hatalar kullanıcı taraflı olup kullanıcının yaptığı bir işlem sonucu açığa çıkarlar. "404" hatası da yine bu sebepten ortaya çıkmaktadır. Bu hatanın adı aynı zamanda "Not Found - Bulunamadı" olarak da isimlendirilir. Genelde bu hatanın sebebi, kullanıcı sitede olmayan bir linke giriş yapmaya çalıştığında "404" hatası ile karşılaşır. Bazı sitelerde olmayan bir url'e istek geldiğinde direkt olarak anasayfaya yönlendirilirken bazı sitelerde "404.html" adlı bir dosya ile kullanıcıya bilgi döner. Bugün bende bu sayfanın Vue'da "vue-router" paketini kullanarak bunu nasıl yapacağız bunu göstereceğim.</p>
<h3 id="heading-proje-kurulumu">Proje Kurulumu</h3>
<p>Vite ile basitçe projemi kuracağım. Ben "yarn" ile kuruluma ve indirme işlemlerine devam edeceğim siz isterseniz npm, pnpm ya da türevlerini kullanabilirsiniz.</p>
<pre><code class="lang-plaintext">yarn create vite
</code></pre>
<p>Bu komut satırını çalıştırdıktan sonra karşınıza üç soru gelecektir. Öncelikle proje isminizi belirtmenizi isteyecek buraya herhangi bir isim yazabilirsiniz ki ben "errorpage" olarak bir isim verdim. İkinci soruda size hangi framework ile projenin kurulmasını istediğinizi soracak burada yön tuşları ile "Vue" yazısını seçip "enter" tuşuna basın. Son soruda ise "variant" soracak burada yine yön tuşları ile "Javascript" seçip "enter" tuşuna basın. Sonrasında proje klasörüne girip paketleri yüklememiz gerekli bunun için aşağıdaki kodları çalıştırabilirsiniz.</p>
<p>Not: "cd errorpage" kısmında "errorpage" yazan yere kendi proje adınızı girmeniz gerekmekte.</p>
<pre><code class="lang-plaintext">cd errorpage
yarn install
yarn add vue-router@4
</code></pre>
<p>Tüm kurulumları yaptık. Şimdi "src" dosyası içerisinde "router" adında bir klasör oluşturalım ve içinde de "index.js" adlı dosyamızı oluşturalım.</p>
<p>Vue'da router oluşturmak oldukça basit. "index.js" dosyasını açalım ve içerisini aşağıdaki gibi dolduralım.</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> { createRouter, createWebHistory } <span class="hljs-keyword">from</span> <span class="hljs-string">'vue-router'</span>
<span class="hljs-keyword">import</span> Home <span class="hljs-keyword">from</span> <span class="hljs-string">'../views/Home.vue'</span>
<span class="hljs-keyword">import</span> About <span class="hljs-keyword">from</span> <span class="hljs-string">'../views/About.vue'</span>

<span class="hljs-comment">//Routes dizini projemizde olacak url'leri belirtmemize yarar</span>
<span class="hljs-keyword">const</span> routes = [
  {
    <span class="hljs-attr">name</span>: <span class="hljs-string">'home'</span>,
    <span class="hljs-attr">path</span>: <span class="hljs-string">'/'</span>,
    <span class="hljs-attr">component</span>: Home
  },
  {
    <span class="hljs-attr">name</span>: <span class="hljs-string">'about'</span>,
    <span class="hljs-attr">path</span>: <span class="hljs-string">'/about'</span>,
    <span class="hljs-attr">component</span>: About
  },
  <span class="hljs-comment">// "/:catchAll(.*)*" =&gt; 404 sayfamızı oluşturmamız için "vue-router" ile birlikte gelen özel url yoludur.</span>
  {
    <span class="hljs-attr">path</span>: <span class="hljs-string">'/:catchAll(.*)*'</span>,
    <span class="hljs-comment">//Burada ise eğer kullanıcı, bulunmayan bir url istek attığı zaman otomatik olarak "home" url'ine yani "/" url'ine yönlendirilir.</span>
    <span class="hljs-attr">redirect</span>: { <span class="hljs-attr">name</span>: <span class="hljs-string">'home'</span> }
    <span class="hljs-comment">//Yönlendirme yerine bir hata sayfası göstermek istiyorsanız ise diğer route'larımızda olduğu gibi burada da component kullanabilirsiniz.</span>
    <span class="hljs-attr">component</span>: <span class="hljs-function">() =&gt;</span> <span class="hljs-keyword">import</span>(<span class="hljs-string">'../views/NotFound.vue'</span>)
  }
]

<span class="hljs-keyword">const</span> router = createRouter({
  <span class="hljs-attr">history</span>: createWebHistory(<span class="hljs-keyword">import</span>.meta.env.BASE_URL),
  <span class="hljs-comment">//Buradaki "routes" aslında "routes:routes" anlamına gelmektedir.</span>
  <span class="hljs-comment">//Fakat sadece "routes" yazsak bile bunu kendisi halledecektir.</span>
  routes,
})
<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> router
</code></pre>
<p>Şimdi ise "router"ımızı app genelinde tanımlayalım bunun için "main.js" dosyasını açın ve aşağıdaki gibi düzenleyin.</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> { createApp } <span class="hljs-keyword">from</span> <span class="hljs-string">"vue"</span>;
<span class="hljs-keyword">import</span> <span class="hljs-string">"./style.css"</span>;
<span class="hljs-keyword">import</span> App <span class="hljs-keyword">from</span> <span class="hljs-string">"./App.vue"</span>;

<span class="hljs-keyword">import</span> router <span class="hljs-keyword">from</span> <span class="hljs-string">"./router/index.js"</span>;

createApp(App).use(router).mount(<span class="hljs-string">"#app"</span>);

<span class="hljs-comment">//Alternatif olarak bunu da kullanabilirsiniz</span>
<span class="hljs-keyword">const</span> app = createApp(App)
app.use(router)
app.mount(<span class="hljs-string">"#app"</span>)
</code></pre>
<p>Daha sonra "App.vue" dosyamızı açıp içini aşağıdaki gibi dolduralım.</p>
<pre><code class="lang-xml"><span class="hljs-tag">&lt;<span class="hljs-name">template</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">ul</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">li</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">router-link</span> <span class="hljs-attr">:to</span>=<span class="hljs-string">"{name: 'home'}"</span>&gt;</span>Ana Sayfa<span class="hljs-tag">&lt;/<span class="hljs-name">router-link</span>&gt;</span>
        <span class="hljs-tag">&lt;/<span class="hljs-name">li</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">li</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">router-link</span> <span class="hljs-attr">:to</span>=<span class="hljs-string">"{name: 'about'}"</span>&gt;</span>Hakkımızda<span class="hljs-tag">&lt;/<span class="hljs-name">router-link</span>&gt;</span>
        <span class="hljs-tag">&lt;/<span class="hljs-name">li</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">li</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">router-link</span> <span class="hljs-attr">to</span>=<span class="hljs-string">"/testsayfa"</span>&gt;</span>Hata Verdirecek Sayfa<span class="hljs-tag">&lt;/<span class="hljs-name">router-link</span>&gt;</span>
        <span class="hljs-tag">&lt;/<span class="hljs-name">li</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">ul</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">router-view</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">router-view</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">template</span>&gt;</span>
</code></pre>
<p>"src/views/Home.vue" dosyasına girelim ve aşağıdaki kodları ekleyelim.</p>
<pre><code class="lang-xml"><span class="hljs-tag">&lt;<span class="hljs-name">template</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">h1</span>&gt;</span>Ana Sayfa<span class="hljs-tag">&lt;/<span class="hljs-name">h1</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">template</span>&gt;</span>
</code></pre>
<p>"src/views/About.vue" dosyasına girin ve aşağıdaki kodları ekleyin.</p>
<pre><code class="lang-xml"><span class="hljs-tag">&lt;<span class="hljs-name">template</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">h1</span>&gt;</span>Hakkımızda<span class="hljs-tag">&lt;/<span class="hljs-name">h1</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">template</span>&gt;</span>
</code></pre>
<p>Daha sonrasında aşağıdaki komut ile projeyi çalıştıralım ve uygulamayı test edelim.</p>
<pre><code class="lang-xml">yarn dev
</code></pre>
<p>"Ana Sayfa" ve "Hakkımızda" sayfasına girdiğiniz zaman sayfa içeriği doğru bir şekilde yüklenilirken test amaçlı oluşturduğumuz "Hata Verdirecek Sayfa" linkine tıkladığımızda eğer "redirect" yaptıysanız "Ana Sayfa"ya "component" kullandıysanız ise kullanmış olduğunuz "component" görüntülenecektir.</p>
<p>"src/routers/index.js" dosyasında "routes" dizisinde kullanmış olduğumuz "catchAll" tüm "route" yolları denendikten sonra en son kullanılacak olan "route"dur.</p>
<p>Ekstra olarak "Vue Router"ın kendi dökümanında "404" sayfası için "'/:pathMatch(.*)*" kullanılıyor. Yazının sonunda link olarak bırakacağım detaylı olarak inceleyebilirsiniz.</p>
<p>Zaten yazımın da sonuna gelmiş bulunuyoruz. İlk yazım olduğu için eksiklerim veya hatalarım var ise mazur görmenizi ve bunları yorum olarak bırakmanızı isterim. Bir sonraki yazılarımda görüşmek üzere kodlamayla kalın.</p>
<p><a target="_blank" href="https://router.vuejs.org/guide/essentials/dynamic-matching.html#catch-all-404-not-found-route">Vue Router Dökümanı</a></p>
]]></content:encoded></item></channel></rss>