Laravel uygulamanızdan bir dış sistemi yönetmeye çalıştığınızda, genellikle aynı rahatsız edici gerçeğe takılırsınız: veritabanınız bir şey düşünüyor, dış sistem başka bir şey düşünüyor ve hiçbiri birbirlerinin farklılaştığını bilmiyor. Bugün, Laravel üzerinden bir API geçidini çalıştırmanın her zaman gösterişli olmayan ama hayati önem taşıyan yönüne çoğu zaman harcadım; uygulamanın dünyasının görünümü ile geçidin gerçek durumu arasında anlaşmayı sağlamak ve bunlar uyumlu olmadığında düzeltmek için güvenli araçlar edinmek.
<p>Yazmamın değerli olduğu iki tema çıktı: <strong>yetim temizliği</strong> (geçitte var olan ancak uygulamamda izlenmeyen nesneler) ve <strong>anahtar senkronizasyonu</strong> (JWKS'nin uygulamanın verdiği kimlik bilgileriyle uyumlu olmasını sağlamak). İkisi de aynı sorunun farklı yüzleri; tam kontrol edemediğiniz bir sınırda uzlaşma.</p>
<h2>
<a name="why-drift-happens-at-all" href="#why-drift-happens-at-all">
</a>
Drift Neden Oluyor?
</h2>
<p>Bir geçitte bir hizmet, yön veya tüketici sağladığınızda, iki sistemde de yazma işlemi yapıyorsunuz: kendi veritabanı satırınız ve geçidin yönetim API'si aracılığıyla yapılandırması. Bu iki yazım atomik olmadığında — ve bir ağ üzerinde asla atomik olmuyor — aralarında anlaşmazlık olabileceği bir pencere açılıyor.</p>
<p>Bunun olabileceği birkaç normal, suçsuz yol:</p>
<ul>
<li>Geçitte bir oluşturma işlemi başarılı olurken, izleyen veritabanı yazımı başarısız olabilir (veya tersine).</li>
<li>Biri bir olay sırasında geçitte doğrudan bir şeyi düzeltirse, uygulama asla haberdar olmaz.</li>
<li>Bir silme işlemi yerel satırı kaldırır, ancak geçit çağrısı zaman aşımına uğrar ve nesneyi sessizce bırakır.</li>
<li>Eski bir geçitten yarım kalan bir geçiş, kimsenin izlenmediği nesneler bırakır.</li>
</ul>
<p>Bunlar tam olarak hatalar değil — iki sistemi koordine etmenin maliyetidir. Hatanızın bu olmayacağını varsaymak. Bu nedenle geçidi, varsaydığım şey değil, <em>karşılaştırdığım</em> bir doğruluk kaynağı olarak düşünüyorum.</p>
<h2>
<a name="orphans-things-on-the-gateway-my-app-forgot" href="#orphans-things-on-the-gateway-my-app-forgot">
</a>
Yetimler: Uygulamamın Unuttuğu Geçitteki Şeyler
</h2>
<p>Yetim, geçitte mevcut olup uygulama tarafında karşılık gelen bir kaydı olmayan her nesnedir. Zararsız değiller — bir yetim yön hâlâ trafik servis edebilir, bir yetim tüketici hâlâ geçerli kimlik bilgilerine sahip olabilir. Bu, yalnızca dağınıklık değil, bir güvenlik ve faturalandırma sorunudur.</p>
<p>Keşfi kavramsal olarak basit: geçidin sahip olduğu şeyi listeleyin, sahip olduğum şeyi listeleyin ve farkları alın. Disiplin, <em>farklılık</em> üzerinde nasıl <em>harekete geçeceğinizdir</em>. Bunu bir araç olarak sunarım ve kritik tasarım kararı, listeleme ve silme işleminin, arada bir insan (veya açık bir onay) ile iki ayrı adım olmasıdır.<br/></p>
<div class="highlight js-code-highlight">
<pre class="highlight php"><code><span class="k">final</span> <span class="kd">class</span> <span class="nc">GatewayOrphanCleanup</span>{
public function __construct(
private GatewayClient $gateway,
private ServiceRepository $services,
) {}
<span class="cd">/ @return array<int orphan=""> objects on the gateway we don't track. */</int></span>
<span class="k">public</span> <span class="k">function</span> <span class="n">detect</span><span class="p">():</span> <span class="kt">array</span>
<span class="p">{</span>
<span class="nv">$tracked</span> <span class="o">=</span> <span class="nv">$this</span><span class="o">-></span><span class="n">services</span><span class="o">-></span><span class="nf">allGatewayIds</span><span class="p">();</span> <span class="c1">// bilmediğimiz id'ler</span>
<span class="nv">$remote</span> <span class="o">=</span> <span class="nv">$this</span><span class="o">-></span><span class="n">gateway</span><span class="o">-></span><span class="nf">listServices</span><span class="p">();</span> <span class="c1">// aslında var olanlar</span>
<span class="k">return</span> <span class="nf">collect</span><span class="p">(</span><span class="nv">$remote</span><span class="p">)</span>
<span class="o">-></span><span class="nf">reject</span><span class="p">(</span><span class="k">fn</span> <span class="p">(</span><span class="kt">array</span> <span class="nv">$obj</span><span class="p">)</span> <span class="o">=></span> <span class="nb">in_array</span><span class="p">(</span><span class="nv">$obj</span><span class="p">[</span><span class="s1">'id'</span><span class="p">],</span> <span class="nv">$tracked</span><span class="p">,</span> <span class="kc">true</span><span class="p">))</span>
<span class="o">-></span><span class="nf">map</span><span class="p">(</span><span class="k">fn</span> <span class="p">(</span><span class="kt">array</span> <span class="nv">$obj</span><span class="p">)</span> <span class="o">=></span> <span class="k">new</span> <span class="nc">Orphan</span><span class="p">(</span>
<span class="n">id</span><span class="o">:</span> <span class="nv">$obj</span><span class="p">[</span><span class="s1">'id'</span><span class="p">],</span>
<span class="n">name</span><span class="o">:</span> <span class="nv">$obj</span><span class="p">[</span><span class="s1">'name'</span><span class="p">]</span> <span class="o">??</span> <span class="kc">null</span><span class="p">,</span>
<span class="n">createdAt</span><span class="o">:</span> <span class="nv">$obj</span><span class="p">[</span><span class="s1">'created_at'</span><span class="p">]</span> <span class="o">??</span> <span class="kc">null</span><span class="p">,</span>
<span class="p">))</span>
<span class="o">-></span><span class="nf">values</span><span class="p">()</span>
<span class="o">-></span><span class="nf">all</span><span class="p">();</span>
<span class="p">}</span>}
<svg xmlns="http://www.w3.org/2000/svg" width="20px" height="20px" viewbox="0 0 24 24" class="highlight-action crayons-icon highlight-action--fullscreen-off"><title>Fullscreen modundan çık</title>
<path d="M18 7h4v2h-6V3h2v4zM8 9H2V7h4V3h2v6zm10 8v4h-2v-6h6v2h-4zM8 15v6H6v-4H2v-2h6z"/>
</svg>
</div>
</div>
<p>Burada <code>detect()</code>’in ne yapmadığını fark edin: hiçbir şeyi silmez. Farkın bir tanımını döndürür. Silme işlemi, keşif adımından aldığınız belirli bir kimliği alan, belirli bir ikinci çağrıdır:<br/></p>
<div class="highlight js-code-highlight">
<pre class="highlight php"><code><span class="k">public</span> <span class="k">function</span> <span class="n">delete</span><span class="p">(</span><span class="kt">string</span> <span class="nv">$gatewayId</span><span class="p">):</span> <span class="kt">Result</span>{
// Gerçekten izlediğimiz herhangi bir şeyi silmeyi reddediyoruz — bu yol,
// normal işlem + onay akışını geçer, asla yetim temizliği ile değil.
if ($this->services->existsByGatewayId($gatewayId)) {
return Result::refused(‘Yetim değil — yönetilen silme akışını kullanın.’);
}
<span class="nv">$this</span><span class="o">-></span><span class="n">gateway</span><span class="o">-></span><span class="nf">deleteService</span><span class="p">(</span><span class="nv">$gatewayId</span><span class="p">);</span>
<span class="k">return</span> <span class="nc">Result</span><span class="o">::</span><span class="nf">ok</span><span class="p">(</span><span class="s2">"Yetim nesne kaldırıldı </span><span class="si">{</span><span class="nv">$gatewayId</span><span class="si">}</span><span class="s2">."</span><span class="p">);</span>}
<svg xmlns="http://www.w3.org/2000/svg" width="20px" height="20px" viewbox="0 0 24 24" class="highlight-action crayons-icon highlight-action--fullscreen-off"><title>Fullscreen modundan çık</title>
<path d="M18 7h4v2h-6V3h2v4zM8 9H2V7h4V3h2v6zm10 8v4h-2v-6h6v2h-4zM8 15v6H6v-4H2v-2h6z"/>
</svg>
</div>
</div>
<p>Bu koruma şartı, bir temizleme aracındaki en tehlikeli hatadır; "temizliyor" olan bir nesneyi aslında yetim olmayan bir şey olarak silmektir. Bu nedenle silme yolu, hedefin gerçekten izlenmediğini <em>silme anında yeniden kontrol eder</em>, sadece listelendiğinde değil. Keşfet-sonra-onayla yaklaşımı, geri alınamayacak bir "her şeyi senkronize et" düğmesine karşıdır — özellikle, çağrıyı yapan şeyin giderek bir AI ajanı olduğu bir dünyada.</p>
<h2>
<a name="keeping-jwks-honest" href="#keeping-jwks-honest">
</a>
JWKS'yi Dürüst Tutmak
</h2>
<p>İkinci konu, kimlik bilgisi görünürlüğü ve anahtar senkronizasyonuydu. Bir geçit JWT'leri doğrularken, kamu anahtarlarına ihtiyacı vardır — genellikle bir JWKS (JSON Web Key Set) olarak sergilenir. Uygulamanız anahtarları döndürdüğünde veya verirken, geçidin kopyasının bunu takip etmesi gerekir. Aksi takdirde en kötü türden bir kesinti ile karşı karşıyasınız demektir: <em>doğru bir şekilde imzalanmış</em> ancak doğrulayıcıların eski anahtarlara baktığı için reddedilen token'lar.</p>
<p>Bu nedenle kendime iki tamamlayıcı araç verdim: birisi, bir tüketici tarafından şu anda sahip olunan kimlik bilgilerini <strong>görmek</strong>, diğeri geçidin görünümünün uygulamanın verdiği ile eşleşmesini sağlamak için JWKS'yi <strong>senkronize</strong> etmek. Önce görünürlük, çünkü neyi denetleyemediğinizi güvenli bir şekilde senkronize edemezsiniz.<br/></p>
<div class="highlight js-code-highlight">
<pre class="highlight php"><code><span class="k">final</span> <span class="kd">class</span> <span class="nc">SyncConsumerJwks</span>{
public function __construct(private GatewayClient $gateway) {}
<span class="k">public</span> <span class="k">function</span> <span class="n">handle</span><span class="p">(</span><span class="kt">string</span> <span class="nv">$consumerId</span><span class="p">,</span> <span class="kt">array</span> <span class="nv">$jwks</span><span class="p">):</span> <span class="kt">Result</span>
<span class="p">{</span>
<span class="nv">$existing</span> <span class="o">=</span> <span class="nv">$this</span><span class="o">-></span><span class="n">gateway</span><span class="o">-></span><span class="nf">listJwtCredentials</span><span class="p">(</span><span class="nv">$consumerId</span><span class="p">);</span>
<span class="nv">$desired</span> <span class="o">=</span> <span class="nv">$this</span><span class="o">-></span><span class="nf">normalise</span><span class="p">(</span><span class="nv">$jwks</span><span class="p">);</span> <span class="c1">// anahtar-id => kamu anahtarı</span>
<span class="c1">// Geçidin eksik olan her şeyi ekleyin.</span>
<span class="k">foreach</span> <span class="p">(</span><span class="nb">array_diff_key</span><span class="p">(</span><span class="nv">$desired</span><span class="p">,</span> <span class="nv">$existing</span><span class="p">)</span> <span class="k">as</span> <span class="nv">$kid</span> <span class="o">=></span> <span class="nv">$key</span><span class="p">)</span> <span class="p">{</span>
<span class="nv">$this</span><span class="o">-></span><span class="n">gateway</span><span class="o">-></span><span class="nf">addJwtCredential</span><span class="p">(</span><span class="nv">$consumerId</span><span class="p">,</span> <span class="nv">$kid</span><span class="p">,</span> <span class="nv">$key</span><span class="p">);</span>
<span class="p">}</span>
<span class="c1">// Uygulamanın artık geçerliliğini onaylamadığı anahtarları kaldırın (iptal edilmiş / döndürülen).</span>
<span class="k">foreach</span> <span class="p">(</span><span class="nb">array_diff_key</span><span class="p">(</span><span class="nv">$existing</span><span class="p">,</span> <span class="nv">$desired</span><span class="p">)</span> <span class="k">as</span> <span class="nv">$kid</span> <span class="o">=></span> <span class="nv">$_</span><span class="p">)</span> <span class="p">{</span>
<span class="nv">$this</span><span class="o">-></span><span class="n">gateway</span><span class="o">-></span><span class="nf">deleteJwtCredential</span><span class="p">(</span><span class="nv">$consumerId</span><span class="p">,</span> <span class="nv">$kid</span><span class="p">);</span>
<span class="p">}</span>
<span class="k">return</span> <span class="nc">Result</span><span class="o">::</span><span class="nf">ok</span><span class="p">(</span><span class="s1">'JWKS uzlaştırıldı.'</span><span class="p">);</span>
<span class="p">}</span>}
<svg xmlns="http://www.w3.org/2000/svg" width="20px" height="20px" viewbox="0 0 24 24" class="highlight-action crayons-icon highlight-action--fullscreen-off"><title>Fullscreen modundan çık</title>
<path d="M18 7h4v2h-6V3h2v4zM8 9H2V7h4V3h2v6zm10 8v4h-2v-6h6v2h-4zM8 15v6H6v-4H2v-2h6z"/>
</svg>
</div>
</div>
<p>Yetim mantığıyla aynı şekil, değil mi? Uzak olan listeyi, istenen seti hesaplayın, her iki yönde farkı uygulayın. Senkronizasyon "benim durumumu üzerlerine aktar" değildir — "onların benimle eşleşmesini sağla, <em>ekleyerek ve</em> kaldırarak" şeklindedir. Kaldırma kısmını unutmak, iptal edilmiş anahtarların çok geçmeden hâlâ çalışmasını sağlar.</p>
<p>Önemli ama ince bir çağrı: Bir anahtarı yalnızca uygulamanın <em>kesin olarak</em> döndürdüğünde kaldırırım, sadece mevcut istekten dolayı değil. Burada yanlış bir şekilde fazla kudretli ağaç, canlı istemcileri dışarıda bırakabilir. Tereddüt durumunda, senkronizasyon ekleyici olur ve fazladan olanları silmek yerine incelemeye alır.</p>
<h2>
<a name="why-this-is-worth-a-pest-test" href="#why-this-is-worth-a-pest-test">
</a>
Neden Bu Bir Pest Testi Yapmaya Değer
</h2>
<p>Uzlaştırma mantığı, görünüşte doğru görünen ve altı ay sonra sessizce yanlış şeyler yapan kod olduğu için çok önemli kabullenmeler yazmak gerekir. Yazılması gereken iddialar, sınırlarıdır: bir nesneyi izlemem gerektiği sürece <em>asla</em> yetim olarak sınıflandırılmamalıdır ve senkronizasyon iptal edilmiş anahtarları kaldırmalıdır, yalnızca yenilerini eklememelidir.<br/></p>
<div class="highlight js-code-highlight">
<pre class="highlight php"><code><span class="nf">it</span><span class="p">(</span><span class="s1">'asla izlediğim bir nesneyi yetim olarak tanımlamaz'</span><span class="p">,</span> <span class="k">function</span> <span class="p">()</span> <span class="p">{</span>
<span class="nv">$service</span> <span class="o">=</span> <span class="nc">Service</span><span class="o">::</span><span class="nf">factory</span><span class="p">()</span><span class="o">-></span><span class="nf">create</span><span class="p">([</span><span class="s1">'gateway_id'</span> <span class="o">=></span> <span class="s1>'svc_123'</span><span class="p">]);</span>
<span class="nv">$gateway</span> <span class="o">=</span> <span class="nf">fakeGatewayWith</span><span class="p">([</span><span class="s1>'svc_123'</span><span class="p">,</span> <span class="s1>'svc_999'</span><span class="p">]);</span> <span class="c1">// 999 gerçek yetimdir</span>
<span class="nv">$orphans</span> <span class="o">=</span> <span class="nf">app</span><span class="p">(</span><span class="nc">GatewayOrphanCleanup</span><span class="o">::</span><span class="n">class</span><span class="p">)</span><span class="o">-></span><span class="nf">detect</span><span class="p">();</span>
<span class="nf">expect</span><span class="p">(</span><span class="nv">$orphans</span><span class="p">)</span><span class="o">-></span><span class="nf">toHaveCount</span><span class="p">(</span><span class="mi">1</span><span class="p">)</span>
<span class="o">-></span><span class="k">and</span><span class="p">(</span><span class="nv">$orphans</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span><span class="o">-></span><span class="n">id</span><span class="p">)</span><span class="o">-></span><span class="nf">toBe</span><span class="p">(</span><span class="s1>'svc_999'</span><span class="p">)</span>
<span class="o">-></span><span class="k">and</span><span class="p">(</span><span class="nf">collect</span><span class="p">(</span><span class="nv">$orphans</span><span class="p">)</span><span class="o">-></span><span class="nf">pluck</span><span class="p">(</span><span class="s1>'id'</span><span class="p">))</span><span class="o">-></span><span class="n">not</span><span class="o">-></span><span class="nf">toContain</span><span class="p">(</span><span class="s1>'svc_123'</span><span class="p">);</span>});
it(<span class=”s1>’izlenmekte olan bir nesneyi yetim yolu ile silmeyi reddeder’, function () {
Service::factory()->create([<span class=”s1>’gateway_id’ => <span class=”s1>’svc_123′]);
<span class="nv">$result</span> <span class="o">=</span> <span class="nf">app</span><span class="p">(</span><span class="nc">GatewayOrphanCleanup</span><span class="o">::</span><span class="n">class</span><span class="p">)</span><span class="o">-></span><span class="nb">delete</span><span class="p">(</span><span class="s1>'svc_123'</span><span class="p">);</span>
<span class="nf">expect</span><span class="p">(</span><span class="nv">$result</span><span class="o">-></span><span class="nf">refused</span><span class="p">())</span><span class="o">-></span><span class="nf">toBeTrue</span><span class="p">();</span>});
<svg xmlns="http://www.w3.org/2000/svg" width="20px" height="20px" viewbox="0 0 24 24" class="highlight-action crayons-icon highlight-action--fullscreen-off"><title>Fullscreen modundan çık</title>
<path d="M18 7h4v2h-6V3h2v4zM8 9H2V7h4V3h2v6zm10 8v4h-2v-6h6v2h-4zM8 15v6H6v-4H2v-2h6z"/>
</svg>
</div>
</div>
<p>İkinci test, en çok önemsediğim testtir. Bu, mutlu yolla ilgili olmayan ve yanlış bir şeyi silmemekle ilgili olan bir kuralı kodlar: yetim temizleme kapısı, uygulamanın gerçekten yönettiği herhangi bir şey için kapalı kalmalıdır. Eğer gelecekte bir yeniden yapılandırma, bu korumayı zayıflatırsa, bu test, hiç gönderilmeden önce kırmızıya döner.</p>
<h2>
<a name="the-pattern-underneath" href="#the-pattern-underneath">
</a>
Alttaki Kalıp
</h2>
<p>Geçit özel detaylarını kaldırdığınızda burada yeniden kullanılabilir bir fikir var: bir durumu tamamen sahip olmadığınız bir sisteme yansıttığınızda, <strong>ihtiyacınız olmadan önce uzlaştırıcıyı oluşturun ve her yıkıcı adımının kendi ön koşulunu yeniden doğrulamasını sağlayın.</strong> Tespit ve hareket ayrıdır. Senkronizasyon her iki yönde gerçekleşir. Silme işlemi, hedefin gerçekten silme anında <em>silinmeye güvenli</em> olması gerektiğini kanıtlar, sadece listeleme anında değil.</p>
<p>Bu, bir veritabanı kısıtlaması gibi iyi bir sistem tasarlama veya yapma içgüdüsüdür — çağrıcıların her zaman dikkatli olmasını ummazsınız, tehlikeli şeyi yapmadan zor hale getirirsiniz. Özellikle "çağıran" şeyin, aracınızı tanımlayan bir AI ajanı olduğu bir dünyada çok daha fazla dikkat edilmesi gereken bir konudur.</p>
<p>Sonraki listemde: DNS, token'lar ve tüketici kimlik bilgileri gibi her şeyin canlı trafiği düşürmeden geçişte bir geliştirici dostu rehber yazmak var. Bu da bir uzlaştırma sorunudur, yalnızca çok daha korkutucu bir saat üzerinde.</p>Kaynak: Orijinal Makale


