Yasin Ateş

AI Agent’larla Sıfırdan Uçtan Uca E-Ticaret Projesi Geliştirmek: BRAID Mimarisi ve Mermeid…

AI Agent’larla Sıfırdan Uçtan Uca E-Ticaret Projesi Geliştirmek: BRAID Mimarisi ve Mermeid…

AI Agent’larla Sıfırdan Uçtan Uca E-Ticaret Projesi Geliştirmek: BRAID Mimarisi ve Mermeid Deneyimlerim 💫

Bu yazıda uçtan uca tüm modülleri ile bir e-ticaret projesi yaparken, AI agent’lar ve skill’leri nasıl kullandığımı anlatacağım

Projeyi planlarken ki hedefim, kodları AI agent’larla yönetilebilir kılmak. Sadece “ben de yapay zeka kullanıyorum” demek için değil, gerçekten işime yarayan ve geliştirme sürecini hızlandıran ve kod kalitesini artıran bir sistem kurmak istedim. Bu yazının asıl amacı, kodu üreten ve denetleyen agent’ları tasarlamak

📝 Proje Gereksinimlerini Belirleyelim

Bir projeyi AI agent’larla geliştirmeye başlamadan önce yapılması gereken ilk şey, gereksinimleri mümkün olduğunca net tanımlamak.

Çünkü agent’lar varsayım üreterek çalışırlar. Varsayımlar çoğaldıkça da proje farklı yönlere sapmaya başlar.

Bu nedenle geliştirmeye tek satır kod yazmadan önce kapsamlı bir PRD (Product Requirements Document) hazırladım.

Dokuman çok uzun olduğu için buraya direkt eklemek yerine, linkini paylaşıyorum:
👉 https://github.com/yasinatesim/sklent/blob/master/examples/e-commerce/docs/PRD.md

Tech Stack’i inceleyelim:

Tech Stack

🧠 AI Agent Ekosistemini Kurgulayalım

Agent’larla çalışırken en sık yaşadığımız sorun şu: model bir adımda hata yapar, sonraki adıma yanlış girdiyle devam eder ve hata büyüyerek ilerler. Buna error compounding deniyor. Düz bir yapılacaklar listesi bunu çözüyor, çünkü listede “başarısız olursa ne olacak” sorusunun yanıtı yok.

Biz bunun için BRAID mental modelini kullandım. Açılımı Bounded Reasoning for Autonomous Inference and Decisions, (kaynak: arXiv 2512.15959) BRAID, karmaşık bir işi dört tür düğüme böler:

Constraint (kısıtlama) → Fact (bilinen gerçek) → Step (eylem) → Check (kontrol)

Constraint (bağlamdan çıkan kural), Fact (bilinen gerçek), Step (atomik aksiyon) ve Check (doğrulama). Kritik nokta şu: her Check adımının tam olarak iki çıkışı var, Pass ve Fail.

Basit bir örnekle açıklayayım. Diyelim ki bir ürün kategorisi sayfası yazacağız:

flowchart TD
 C1[Constraint: sayfa 1 saniyeden hızlı yüklenmeli]
 F1[Fact: 500+ ürün var, pagination lazım]
 S1[Step: limit=20 ile SQL sorgusu yaz]
 D1[Check: sorgu < 100ms çalışıyor mu?]
 S2[Step: index ekle veya query optimize et]
 End([End: kategori sayfası hazır])
 C1 --> F1 --> S1 --> D1
 D1 -- "Pass" --> End
 D1 -- "Fail" --> S2 --> S1

Eğer bunu grafil oalrak görmek istersek https://mermaid.live/ sitesine yukarıdaki kodu yapıştırarak kontrol edebiliriz, çıktı şu şekilde:

Mermeid çıktısı

İşin sırrı Fail kenarında. Bir Check başarısız olduğunda model “aynı girdiyle tekrar dene” demez. Bunun yerine daha önceki bir Step’e geri döner, farklı bir girdi üretir ve tekrar kontrol eder. Yani döngünün kendisi retry mekanizmasının ta kendisi olur

💡 Ralph Loop mantığındaki gibi max_retry: 3 gibi sayısal bir sınır koymayız, çünkü BRAID mermeid kullanır ve döngü yapısı zaten bu isteri karşılar

Geliştirmeyle alakalı grafiği bir kez ürettikten sonra proje dizininde .local-artifacts/braid/<task-slug>.mmd altında önbelleğe alırız. Aynı iş tekrar geldiğinde yeniden üretmeyiz, çünkü grafik bir iskelet görevi görür. Bu iki fazlı ayrım (önce mimar üretir, sonra executor adım adım gezer) BRAID'in temel fikri budur. Tek dosyalık bir değişiklik için grafik çizmeyiz tabii. Ama üç dosyadan fazla refactor veya bir mimari karar varsa grafik kullanmamızda fayda var

🤖 Skill Sistemi: Agent’ların Uzmanlaşması

Claude Code’da iki temel yapıtaşı var: agent’lar (kendi context’inde çalışan, belirli araçlara erişimi olan uzman) ve skill’ler (belirli bir görevi anlatan, model çağırınca yüklenen yönerge dosyaları). Bu ikisini bir fabrika hattı gibi kurguladım.

Projenin kök dizininde .claude/ klasörü her şeyin merkezi. İçerisinde proje için tanımladığımız agent’lar, skilller, referance’ler yer alır.

Agent’lara tek bir “her şeyi yap” talimatı vermek yerine, 15 farklı skill (beceri) tanımladım. Her skill belirli bir uzmanlık alanını kapsar:

Skiller

Kod Kalitesi Skill’leri

Code Review Metrikleri

Bunun için benim en çok kullandığım site skills.sh. Skill’leri ve agent’ları yönlendirirken de genelde “awesome”, “wtf”, “clean code” repository’leri bulup onları kullanıyorum

Örneğin:

Süreç Skill’leri

★ Proje planlama için bildiğim skil’ler;

Gerçek kullanıcı gibi test yapan skill veya muadili olan repolar;

Her skill kendi SKILL.md dosyası içinde şu bilgileri barındırır:

---
name: braid-plan
description: Generate a BRAID reasoning graph for complex multi-step tasks.
---

Skill’lerin içinde adım adım talimatlar, template’ler ve referanslar bulunur. Örneğin wtf-go skill'i Go kodunda hangi pattern'leri arayacağını, hangi hataları bildireceğini ve hangi kuralların ihlal edildiğini tek tek listeler.

Agent’lar ve İş Akışı

Skill’lerin üzerinde 8 farklı agent tanımladım:

  1. braid-solver — BRAID grafiğini adım adım yürütür
  2. wtf-code-reviewer — Dispatcher; dosya türüne göre doğru reviewer’a yönlendirir
  3. wtf-go — Go kodu inceler
  4. wtf-js-react — TS/TSX kodu inceler
  5. wtf-security — Güvenlik denetimi yapar
  6. wtf-ux-playwright — UI testlerini çalıştırır
  7. constants-guard — Sabit tanımlarında tekrar denetimi yapar
  8. issue-auditor — Issue’ları yönetir

Bir PR açmadan önceki iş akışımız şöyle:
1. TDD: Önce test yaz, sonra implement et
2. Local doğrulama: go vet, lint, type-check, test
3. wtf-code-reviewer dispatch (tüm reviewer’lar paralel)
4. Eğer UI değişikliği varsa: Playwright snapshot
5. Coverage raporu
6. PR aç (ship-pr skill’i ile)

wtf-code-reviewer agent'ı, bir dosyanın değiştirilip değiştirilmediğine bakar ve buna göre doğru reviewer'ları paralel olarak çalıştırır:

★ api/internal/payment/ değişmiş → wtf-go + wtf-security

★ web/src/**/*.{ts,tsx} değişmiş-> wtf-js-react

★ rendered UI etkileyen her web dosyasi -> wtf-ux-playwright

★ docker/*, *.yml, Dockerfile*, .env* değişmiş -> wtf-security

★ web/src/app/odeme/ değişmiş → wtf-js-react + wtf-ux-playwright + wtf-security

Aggregation kuralı net: herhangi bir uzman reddederse sonuç REJECTED, biri Major bulursa NEEDS_FIXES, hepsi onaylarsa VERIFIED.

Döngü VERIFIED olana kadar sürüyor, en fazla 3 tur. 3 turdan sonra kalan bulgular bize çıkıyor.

Bu yapının güzelliği şu: biz tek bir komut veririz, dispatcher diff’e bakıp doğru uzmanları çağırır ve biz sadece sonucu görürüz. Her uzman kendi context’inde, kendi standart dosyasını okuyarak çalışır, yani biri diğerinin yaptığı şeylerden etkilenmez.

🧩 Hook’lar: Agent’a Sınır Koymak

Bir agent’a kural söylemek yetmiyor, çünkü unutabiliyor veya yorumlayabiliyor. Asıl güç, kuralı mekanik olarak imkansız hale getiren hook’larda. Agent’ların her eylemden sonra ne yapacağını .claude/hooks/ altında tanımladım. Bunlar;

  • ★ pre-commit-verify.sh: git commit veya gh pr create sırasında CI'ı birebir aynalayan verify lane'i çalıştırır. Ayrıca master ve development'a doğrudan commit'i ve bilinmeyen branch prefix'lerini bloklar.
  • ★ enforce-branch-base.sh: gh pr create sırasında branch prefix ile --base çelişiyorsa bloklar. feature/* mutlaka development'a, hotfix/* mutlaka master'a gitmek zorunda.
  • ★ block-pr-merge.sh: gh pr merge'i sert şekilde bloklar. Claude asla PR merge etmez, PR'ı ben merge ederim.
  • ★ post-edit-go.sh ve post-edit-ts.sh: her dosya düzenlemesinde lint, format ve named import kontrolü.
  • ★ no-long-comments.sh: iki satırdan uzun yorum bloklarını bloklar; çünkü bir WHY iki satırı aşıyorsa kodun yapay zeka tarafından ayzıldığı çok bellidir ve basitleştirilmesi gerekir. (Humanize etmek diyebilriz kısaca 😁)
  • ★ constants-guard-trigger.sh: yeni bir UPPER_SNAKE_CASE sabit eklenince constants-guard ‘ı tetikler.

Bu hook’ların felsefesi şu: agent’ın iyimserliğine güvenmek yerine yanlış yapmayı imkansız kılmak. Agent “bu PR’ı merge edeyim” dese bile hook araya girip durdurur. Bu, kuralları “olası” olmaktan çıkarıp “imkansız” yapar.

Proje gereksinimleri veya modeli kullanırken yaşadığımız sorunlara göre hook’ları belirlemeliyiz

CLAUDE.md

Tüm bu sistemin temelinde CLAUDE.md dosyası yatıyor. Bu dosya, projeye başlayan her AI agent'ına "bu projede kurallar bunlar, beklentiler bunlar" diyen bir görevler bütününü içerir:

  • Stack ve teknoloji seçimleri
  • Non-negotiable kurallar (git-flow, issue zorunluluğu, test politikası)
  • Kod standartları ve referance’lar
  • Projenin route yapısı
  • Pipeline tanımları (bug fix, feature)

En önemli kural: “AI agent never feels time pressure. No shortcuts. Do the full job.” (Yapay zeka ajanı asla zaman baskısı hissetmez. Kısayollara başvurmaz. İşin tamamını yapar.)

Bugünkü modeller, bazen insani duyguları fazla abartıp, “yoruldum”, “bu iş 4 saate biter, uzun süreceği için kısayolu tercih ediyorum” diye çıktılar vererek kestirme yoldan gitmeyi seviyorlar 😅. Onlara sadece basit bir asistan olduklarını hatırlatmak için zaman sınırı olmadığını rahat rahat herşeyi yapmaları gerektiğini hatırlatmakta yarar var 🥲

⚙️ Hangi Agent Hangi Modelle ve Ne Kadar Effort ile Çalışır

Burası çoğu yazıda atlanan ama pratikte en çok para ve kalite farkı yaratan kısım. Her agent’a en güçlü modeli vermek hem pahalı hem gereksiz. Biz işi üç sınıfa ayırdık ve her sınıfa farklı bir model ve effort seviyesi atadık.

İlk sınıf, akıl yürütmenin yoğun olduğu işler. Burada bir hata sonradan çok pahalıya patlar, o yüzden en güçlü modeli ve en yüksek effort’u veririz.

braid-plan ve spec-driven-development → yüksek model (Opus sınıfı), effort yüksek. Grafiği veya spec’i bir kez üretiyoruz ve sonraki her şey buna dayanıyor; kalitesi her şeyi belirliyor.

wtf-security → yüksek model, effort yüksek. Payment bypass, IDOR, mass assignment gibi sınıfları kaçırmak felaket. Güvenlikte false negative en pahalı hatadır.

Ana implementasyon agent’ı (yeni, karmaşık feature) → yüksek model, effort yüksek. Sıradan bir refactor’da ise orta modele düşeriz.

İkinci sınıf, kuralı belli olan, daha çok desen eşleştiren işler. Burada orta model ve orta effort yeterli.

wtf-code-reviewer (dispatcher) → orta model (Sonnet sınıfı), effort orta. Sadece diff’i okuyup yönlendirme yapıyor.

wtf-go ve wtf-js-react → orta model, effort orta. Standart dosyasındaki ret kriterlerine göre eşleştirme yapıyorlar.

wtf-ux-playwright → orta model, effort orta. Tarayıcıyı sürüp ekran görüntüsü ve console hatası topluyor; daha çok mekanik.

braid-solver → orta model, effort orta. Önbellekteki grafiği gezip adımları uyguluyor; düşünme işini plan fazı zaten yaptı.

Üçüncü sınıf, neredeyse grep’e benzeyen denetimler. Burada hızlı ve ucuz model, düşük effort.

constants-guard → hızlı model (Haiku sınıfı), effort düşük. Yeni sabit var mı diye mevcut sabitlerle karşılaştırıyor.

issue-auditor → hızlı model, effort düşük-orta. Issue’ları tarayıp duplicate/stale raporu çıkarıyor.

Bunu neden böyle ayırdığımızı açıklayalım: bir reviewer’a Opus vermek, bir grep işine Ferrari koşturmaktır. Tersine, güvenlik denetimini ucuz modele bırakmak ise kapıyı açık unutmaktır. Effort seviyesi de aynı mantıkla: tasarım ve güvenlik yüksek, inceleme ve yürütme orta, denetim düşük.

Agent eforları

🧷 Memory: Agent’ların Oturumlar Arası Kalıcı Hafızası

Bir agent her oturuma sıfırdan başlar. Geçen hafta verdiğiniz kararı, çözdüğünüz tuzağı, “şunu bir daha yapma” dediğiniz şeyi hatırlamaz. Bunu çözmek için dosya tabanlı bir memory sistemi kurduk.

.claude/memory/ altında her dosya tek bir gerçeği tutar ve başında bir frontmatter olur. Aşağıda bir memory dosyasının iskeletini görüyorsunuz. Üstünde mantığını, altında alanlarını anlatacağız.

---
name: project-csrf-cookie-domain
description: Prod admin login CSRF_MISMATCH = COOKIE_DOMAIN set edilmemesi
metadata:
 type: project
---

Prod'da admin login CSRF_MISMATCH veriyordu çünkü cookie host-only kalıyordu.
Çözüm: COOKIE_DOMAIN=.alanadi.com. İlgili not: [[project-ci-selfhosted]]
  • name: kısa, kebab-case bir kimlik; başka notlardan [[name]] ile buna link verilir.
  • description: recall sırasında bu notun konuyla ilgili olup olmadığına karar veren tek satır.
  • metadata.type: dört tür var.

★user (kullanıcı kim),

★feedback (nasıl çalışmam gerektiği),

★project (devam eden iş, koddan çıkmayan kararlar),

★reference (harici kaynak işaretçileri).

Bütün bunların indeksi MEMORY.md dosyasında. Bu dosya her oturum başında context'e yükleniyor; her notun bir satırlık özeti burada. Notların içeriğini buraya koymuyoruz, sadece işaretçi.

Sistemi iki hook ile otomatikleştirdik. SessionStart hook’u oturum açılır açılmaz “önce MEMORY.md'yi oku ve linkleri takip et" diyor. Stop hook'u ise oturum bitmeden "bu konuşmada öğrenilen kalıcı bir şey var mı, varsa kaydet" diye hatırlatıyor.

Bu arada eğer istersek, Claude ve agent ekosisteminde hafıza problemi için geliştirilmiş çeşitli pluginler de bulunuyor. Bunlardan en bilinenleri:

Her iki plugin de agent’ların geçmiş bilgileri vektör veritabanları, embedding tabanlı retrieval mekanizmaları veya otomatik hafıza yönetim katmanları ile hatırlamasını amaçlıyor.

İlk bakışta oldukça güçlü görünseler de pratik kullanımda birkaç problemle karşılaştım:

  • Hafızanın neden geri çağrıldığını anlamak zorlaşıyor.
  • Embedding ve retrieval katmanları nedeniyle davranışlar bazen tahmin edilemez hale geliyor.
  • Yanlış veya alakasız session’lar zaman zaman context’e geri yüklenebiliyor.
  • Sistemin nasıl çalıştığını anlamak için ek bağımlılıklar ve karmaşıklık oluşuyor.

Bu yüzden son dönemde daha basit ve yönetilebilir olması açısından .claude/memory klasörü ve iyi tasarlanmış memory notlarının daha verimli olacağını düşündüğümden makalede de bunu paylaştım. Doğrusu bu demiyorum 😃 Ben kendi doğrularımı sizinle paylaştım, herkesin geliştirme stratejisi farklı olabilir.

💰 Token Optimizasyonu

Caveman: Agent’ın daha az token kullanması için cevapları sıkıştırıyor. Teknik doğruluğu koruyup gereksiz açıklamaları azaltarak maliyet ve context tüketimini düşürmeyi hedefleyen bir plugin

Stop Slop: Yapay zekâ tarafından üretilen metinlerde sık görülen kalıpları, klişeleri ve tekrar eden ifadeleri temizlemeyi amaçlayan bir skill. Daha doğal, daha okunabilir ve daha insan benzeri yazılar üretmeye yardımcı oluyor. README veya dokuman hazırlarken çok işe yarıyor

Ponytail: Son dönemde denediğim en ilginç agent skill’lerinden biri. Temel fikri, AI’ın gereğinden fazla kod üretme eğilimini engellemek. “Odadaki en tembel ama en tecrübeli senior geliştirici” gibi davranarak her kod üretiminden önce şu soruları soruyor: Buna gerçekten ihtiyaç var mı? Standart kütüphane zaten çözmüyor mu? Tarayıcı veya platformun yerleşik özelliği yok mu? Mevcut bir bağımlılık kullanılabilir mi? Tek satırla çözülebilir mi? Ancak bunların hiçbiri mümkün değilse yeni kod yazıyor. Sonuç olarak daha az abstraction, daha az bağımlılık, daha küçük PR’lar ve daha okunabilir kod ortaya çıkıyor. Projenin iddiasına göre birçok senaryoda %80–90 daha az kod, daha düşük token maliyeti ve daha hızlı agent çıktıları elde edilebiliyor.

📝 Makalede anlattığım agentic yapı, Ponytail plugini ile çakışabileceği için eğer denemek isterseniz, bu agentic yapıyı kurmadan önce deneyebilirsiniz
Plugin kurmanın kötü yanlarından birisi bu. Eğer çok fazla plugin kurarsak, yeni model çıktığında veya halihazırda bir agentic yapımız varsa, projeyi yönetmeyi zorlaştırıyor. Son dönemde ben olabildiği kadar pluginlerden kaçmaya çalışıyorum 😁
Github zaten artık bence büyük bir plugin cehennemine dönüştü 😁 Yapay zeka modeli üreten şirketler, bizi sürekli bir şeyleri optimize etmeye zorluyorlar 😬

Şimdi projenin detaylarına bakalım, Product Requirements Document (PRD)’ye göre tasarım, Frontend, Backend ve Security geliştirmelerine göz atalım.

🎨 OpenDesign ile UI Tasarımı ve Prototipleme

Kapsamlı bir e-ticaret projesinde sadece backend ve frontend kodu üretmek yeterli değil.

Admin paneli nasıl görünecek?

Ürün kartları nasıl tasarlanacak?

Checkout ekranı nasıl akacak?

Dashboard hangi metrikleri gösterecek?

Normalde bu süreçte Figma açılır, ekranlar çizilir, revizyonlar yapılır ve daha sonra geliştiriciye aktarılır.

Bu projede farklı bir yaklaşım denedim.

UI ve UX tarafında açık kaynak bir tasarım çalışma alanı olan Open Design kullanmaya başladım. Open Design kendisini “Claude Design alternatifi” olarak konumlandırıyor ve agent tabanlı tasarım üretimi üzerine odaklanıyor. Tasarım sistemleri, skill’ler, plugin’ler ve farklı coding agent’larla entegre çalışabiliyor.

Benim kullanım şeklim oldukça basitti:

Yukarıda bahsettiğim PRD’yi Open Design’a verdim ve ilgili ekranları üretmesini istedim

Örneğin:

  • Ana sayfa
  • Kategori listeleme ekranı
  • Ürün detay sayfası
  • Sepet ekranı
  • Checkout akışı
  • Admin Dashboard
  • Sipariş yönetim ekranları
  • Kampanya yönetimi
  • Kupon yönetimi

gibi tüm ekranları PRD üzerinden üretmesini sağladım. Ancak bunu yaparken tek bir index.html de yapmasını istedim herşeyi. Yani sepeet tıklayınca sepet ekranı açılsın, ürün kartına tıklayınca ürün detay açılsın gibi gibi. Bunun sebebi tek index’i Frontend ile ilgilenen Agentlara sağlayıp projenin ona göre oluşmasını sağlamak. Ayrı HTML’lerde parçalı da olabilir tabii ki ama o zaman yönetmek bana daha zor geldiği için tek HTML ile ilerledim tasarım tarafında

🏗️ Backend Mimarisi: Go Paket Düzenini Agent’a Sağlayalım

Go tarafında en çok zorlandığım şey tutarlılıktı. Model bazen named import kullanıyor, bazen tipleri yanlış yere koyuyordu. Bunu çözmek için .claude/references/backend-standards.md içinde kuralları yazdım ve agent'a "her Go işinde önce bunu oku" dedim.

API Folder Structure

Paket düzeni internal/ altında domain bazlı: auth, cart, category, order, payment, marketplace, llm, product, promotion, reservation ve diğerleri. Tip tanımları her zaman ilgili modülün models/ alt paketinde duruyor.

Test düzeninde Go’nun kendi tree kısıtından doğan iki katmanlı bir kural var.

Black-box (disa acik API) -> <modul>/tests/<isim>_test.go // Ör: product/tests/sorting_test.go
White-box (ic helper'lar) -> <modul>/export_test.go // Ör: product/export_test.go
  • Black-box testler dışa açık davranışı sorting_test paketinden test eder, gerçek kullanıcı gibi davranır.
  • White-box testler export_test.go ile aynı dizinde kalmak zorunda. Taşırsanız build sırasında undefined hatası alırsınız. Go'nun kendi fmt, bufio, bytes paketleri de tam olarak böyle çalışır.

Import kuralı

Agent’ın Go’da named import kullanmasını istemiyorum. alias "pkg/path" yerine çakışma varsa paket bildiriminde yeniden adlandırmayı sağlıyor ama ben bunu istemiyorum. Örneğin üç ayrı modülün models paketi çakışınca, her birinin paket adını package categorymodels, package productmodels diye import edip, alias'sız import ediyoruz. Bunu sadece kuralla bırakmadım, bir hook'la zorunlu kılsım. Her .go düzenlemesinde post-edit-go.sh çalışıp go vet, gofmt -l ve named import kontrolü yapıyor. Başarısız olursa düzenleme bloklanır.

💾 Veritabanı: PostgreSQL 16 ve Migration Stratejisi

golang-migrate/migrate kullandım, migration'lar Go binary içine embed edilmiş.

// embed ile migration dosyaları
//go:embed migrations/*.sql
var migrationsFS embed.FS

Migration’lar container ayağa kalkarken otomatik çalışır (docker-compose içinde migrate servisi). Rollback planlı değil, her migration ileri yönlü.

Redis’i rate limiting ve optional cache için kullandım. Redis yoksa in-memory rate limiter devreye giriyor.

🛒 Kullanıcı Akışları: Sepet, Rezervasyon ve Sipariş

Kullanıcı tarafının kalbi sepet ve rezervasyon mekanizması. İki tür kullanıcımız var: misafir (session cookie ile) ve üye. Sepet state’ini frontend’de Zustand ile yönetiyorum, backend ise gerçek stok rezervasyonunu tutuyor.

En kritik kural: kullanıcı ödemeye geçtiğinde stok 15 dakikalığına rezerve edilir. Ödeme başarılı olursa rezervasyon gerçekleşmiş olur, başarısız olur veya süre dolarsa serbest bırakılır. Bu sayede iki kişi aynı son ürünü aynı anda satın alamaz

🏷️ Ürün, Kategori ve LLM ile Ürün Ekleme

Admin tarafının en güçlü özelliği, bir ürünü ham bir başlık ve birkaç görselle ekleyip gerisini LLM’in doldurması. SEO başlığı, HTML açıklama, slug, baskın renk, materyal, anahtar kelimeler ve pazaryeri kategori eşleşmeleri otomatik gelir. Bunu nasıl yaptığımı Kısaca anlatayım:

🧬 RAG ile Sıfıra Yakın Maliyetle İçerik Üretimi

LLM ile içerik üretmenin bir maliyeti var. Bunu düşürmek için daha önce tuvix.js içindeki chatbot projesinde RAG ile bir LLM'e içerik ürettirdiğim deneyimi bu projeye taşıdım. Fikir basit: modele boş bir sayfadan başlatmak yerine, benzer ürünleri bağlam olarak verip daha kısa ve isabetli üretim yaptırmak.

ChromaDB’nin HTTP API’si olduğu için retrieval ve generation’ı doğrudan Go API’nin içinde bir api/internal/rag paketinde yaptım. Benzer ürünleri ChromaDB'den vektörle çekip, OpenRouter üzerinden bir modele (MiniMax sınıfı) başlık ve açıklama ürettirdim. docker compose up dediğimde Postgres, API, web ve ChromaDB birlikte ayağa kalkıyor.

🔗 Marketplace Entegrasyonları

Pazaryeri geliştirmeleri api/nternal/marketplace altında. Her birinde kategori ağacı, attribute'lar, listing ve push akışları var. Kategori verisi sık değişmediği için bir cache katmanı koydum; ilk kullanımda çekip, sonra önbellekten okunur.

intended-vs-implemented skill’i, dokümante edilen davranış ile kodun gerçekte yaptığı arasındaki farkı denetler. Bir agent doğası gereği iyimserdir, "evet bu çalışır" demeye meyillidir. Biz hem reference'lara hem de bu skill'e "varlık tamlık değildir, iddia etmeden önce koda bak" prensibini koyarak bu iyimserliği frenledik.

💳 Ödeme Entegrasyonu

Ödeme tarafı en hassas yer, çünkü burada hem para hem stok var. Iyzico 3D Secure akışını api/nternal/payment altında kurguladım. Kullanıcı 3DS doğrulamasını tamamlayınca, bize bir callback gönderiyor.

Callback handler’ının mantığı şu sırayla ilerler. Önce zorunlu alanlar var mı, sonra 3DS doğrulaması başarılı mı, sonra sipariş gerçekten var mı, sonra ödenen tutar sipariş tutarıyla birebir tutuyor mu. Her adımda bir hata varsa rezervasyonu serbest bırakıp kullanıcıyı hata sayfasına yönlendiririz

Bu akışı yazarken wtf-security reviewer'ı sürekli devredeydi. En çok yakaladığımız bulgu "payment bypass" olduğu için. Callback'i doğrulamadan siparişi ödendi işaretlemek gibi. Reviewer bu deseni görür görmez CRITICAL olarak işaretler ve PR'ı bloklar. Sandbox tarafında test ortamı ile çalıştım; gerçek kart bilgisi olmadan tüm 3DS akışını uçtan uca denemiş oldum

📦 Kargo ve Sipariş Takibi

Kargo tarafını sade tuttuk: sabit ücret (flat rate) ve admin panelinden manuel takip numarası girişi. Admin bir siparişi kargoladığında takip numarasını girer, sistem siparişi “kargolandı” durumuna alır ve müşteriye e-posta gider.

📧 Email Sistemi: React Email + Stalwart

Email template’leri için React Email kullandım. Component bazlı email tasarımı, hot-reload destekli bir render server ile çalışıyor.

SMTP olarak self-hosted Stalwart mail server kullandım. Template’ler container içinde render edilir ve Stalwart üzerinden gönderilir.

Emailler:

  • Sipariş onayı
  • Şifre sıfırlama
  • Email doğrulama
  • Düşük stok uyarısı (admin’e)
  • İade durum güncellemesi

🧾 Fatura: GIB e-Arşiv Entegrasyonu

Fatura tarafı için GIB e-Arşiv portalına bağlanan ayrı bir entegrasyon var. Burada ilginç bir teknik problem çıkıyor: e-Arşiv portalı CORS izni vermiyor, yani tarayıcıdan doğrudan istek atamıyorsunuz. Çözüm olarak istekleri kendi sunucumuzdaki bir proxy üzerinden geçiriyoruz.

👑 Admin Paneli: Tam Yetkili Yönetim

Admin paneli, frontend’de /admin rotası altında, backend'de api/internal/admin/ paketinde yer alıyor

Dashboard: Günlük siparişler, gelir, ziyaretçi istatistikleri

Ürün Yönetimi: CRUD, bulk update, AI enhancement, image manager

Kategori Yönetimi: Site için kullanılan ve marketplace’lerin kendi platformlarındaki kategoriler.

Sipariş Yönetimi: Listeleme, filtreleme, kargo takibi, ödeme onayı

Promosyonlar: Yüzde/para indirimi, sepet/ürün/kategori kapsamı

Kuponlar: CRUD + misafir kullanıcılara atama

Müşteriler: Listeleme, sipariş geçmişi

İncelemeler: Onay/red moderasyonu

İadeler: Durum yönetimi

İçerik: Banner ve home rail yönetimi

Analytics: Event bazlı istatistikler

Denetim Günlüğü: Tüm admin aksiyonları loglanır

Admin’e erişim sadece admin rolüne sahip kullanıcılara açık. Tüm admin isteklerinde CSRF koruması var.

🌐 Frontend: Next.js 16

Frontend’de Next.js 16 ve React 19 kullandım. CSS Modules + SCSS ile stil yönetimimizi yapıyoruz. Tailwind kullanmadım. Kendi Design sistemim üzerinden ilerlemek istedim

i18n: Çoklu Dil Desteği

next-intl v4 ile iki dil desteği mevcut. Dil dosyaları JSON formatında ve feature-based organize edilmiş:

web/src/shared/i18n/messages/
├── tr.json # Türkçe
└── en.json # English

Her dil dosyası şu şekilde yapılandırılmış:

{
 "home": { "hero": { "title": "...", "subtitle": "..." } },
 "product": { "addToCart": "...", "stock": "...", "reviews": "..." },
 "admin": { "products": { "list": "...", "create": "..." } },
 "checkout": { "payment": "...", "shipping": "..." }
}

Middleware’de dil tespiti yapılıyor ve yönlendirme otomatik olarak gerçekleşiyor

State Yönetimi: Zustand

React Context API yerine Zustand kullanıyoruz. Sepet, modal, toast bildirimleri ve çerez onayı Zustand store’ları ile yönetiliyor:

// Cart store örneği
interface CartState {
 items: CartItem[]
 total: number
 addItem: (product: Product, qty: number) => void
 removeItem: (id: string) => void
 clearCart: () => void
}

Component Mimarisi

Frontend kodumuzda sıkı kurallarımız var:

  • Her dosyada tek component
  • Arrow function + default export
  • Status-based state management (isLoading/isError boolean paraleli yok)
  • Dispatch object map (JSX’te ternary tree yok)
  • Inline style yasak, sadece CSS Modules
  • SVG icon’lar merkezi components/icons/ altında

Admin paneli ayrı bir modüler yapıda. 12 alt modülü var: products, orders, promotions, coupons, categories, customers, analytics, banners, home-rails, reviews, returns, dashboard. Her modül kendi view’ları, store’u ve component’leri ile bağımsız

🛡️ Güvenlik: İki Katmanlı Yaklaşım

Güvenliği iki ayrı katmana böldüm:

Birincisi sürekli katman:

wtf-securityskill’i dispatcher tarafından auth, payment, middleware, input handler, docker compose veya env şablonlarına dokunduğunda otomatik tetiklenir. Saniyeler içinde çalışır ve her PR'da çalışır.

İkincisi periyodik katman: security-pentest. Bu, dinamik bir pentest ve üç alt skill'i var:

  • web app pentest (Next.js akışları),
  • API pentest (JWT, IDOR, rate limiting, iş mantığı)
  • Network pentest (TLS, header'lar, açık portlar, DNS).

Bunları büyük sürümler öncesi ve üç ayda bir paralel çalıştırırız.

Auth tarafında JWT 15 dakikalık access ve 7 günlük dönen (rotating) refresh token, httpOnly cookie, CSRF double-submit koruması ve rate limit var. Rate limit kurallarını rota bazında tanımladım.

Bu skill’i geliştirirken, yine skills.sh’da bulduğum security skillerinden ve şu repo’dan faydalandım:

Ancak güvenliği yalnızca AI agent’ların yaptığı incelemelere bırakmak istemedim.Bu nedenle GitHub Actions tarafında da ikinci bir güvenlik hattı kurdum.Her Pull Request açıldığında aşağıdaki güvenlik kontrolleri otomatik çalışıyor:

Secrets Scan (Gitleaks)

İlk kontrol katmanı Gitleaks.

Amaç; yanlışlıkla commit edilmiş API key, JWT secret, access token, SMTP şifresi veya diğer hassas bilgilerin repoya girmesini engellemek.

Agent veya geliştirici fark etmeksizin bir secret repoya sızmaya çalışırsa pipeline başarısız oluyor.

SAST — Go (Semgrep)

Backend tarafında Semgrep ile statik analiz çalıştırıyorum.

Bu aşamada:

  • SQL Injection
  • Command Injection
  • Hardcoded Secret
  • SSRF
  • Path Traversal
  • Güvensiz Kriptografi Kullanımı

gibi yaygın güvenlik problemleri taranıyor.

SAST — TypeScript (Semgrep)

Frontend tarafında da ayrı bir Semgrep pipeline’ı bulunuyor.

Burada özellikle:

  • XSS riskleri
  • Güvensiz HTML render işlemleri
  • LocalStorage üzerinde hassas veri kullanımı
  • Güvensiz fetch çağrıları
  • Authorization akışındaki problemler

tespit edilmeye çalışılıyor.

🐳 Docker ve CI/CD: Production’a Giden Yol

Üç katmanlı Docker compose yapımız var:

Base (docker-compose.yml): postgres, migrate, api, web, redis

Dev (docker-compose.dev.yml): + mailhog (local mail için), + email-renderer, + seed data

Prod (docker-compose.prod.yml): + stalwart SMTP (prod mail için)

🧨 Dockploy ile Preview Deploy

Her PR şu şekilde otomatik preview deployment çalışır:

http://preview-siteadi-web-pwrqoq-heiknc.siteadi.com/

Bu sayede değişiklikleri production'a almadan önce gerçek ortamda görebiliyoruz.

E2E testleri preview URL hazır olduğunda çalışıyor. Playwright snapshot’ları alınıyor ve PR comment’i olarak ekleniyor.

☁️ CDN: Cloudflare R2

Ürün görselleri Cloudflare R2'de saklanıyor. R2'yi tercih etme nedenim: S3 uyumlu API, egress ücreti yok, Cloudflare paneli üzerinden hızlı erişim.

⚙️ CI/CD: 2000 Dakika Limiti ve Self-Hosted Runner’a Geçiş

Tam “her şey yolunda action’lar güzel güzel çalışıyor” derken, Github’dan şöyle bir mail geldi 😅

Github Action Mail

Her PR’da lint, type-check, test, coverage ve UI tarafında Playwright çalışır. Backend ve frontend ayrı job’lar. Bu Github Action, dakikalarını hızla yiyor, özellikle benim gibi Playwright da geliştirme yaparken, belirli Best Practice’lere uymayı unuttuysanız.

Playwright Best Practice’leri ile alakalı Kader’in güzel bir yazısı vardı. Geçenlerde denk gelmiştim, linkini bırakayım:
👉 https://medium.com/@kaderkaaya/building-a-playwright-based-crawler-test-system-without-overloading-production-035207504ff6

Bu Best Practice’leri uyguladıktan sonra çözüm self-hosted runner’a geçmek oldu. Kendi makinemde bir runner ayağa kaldırdım ve workflow’ları runs-on: self-hosted olarak işaretledim. Aşağıda CI dosyasından bir kesit var.

jobs:
 filter:
 name: Detect changed paths
 runs-on: self-hosted
 timeout-minutes: 5
 steps:
 - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
 - uses: dorny/paths-filter@d1c1ffe0248fe513906c8e24db8ea791d46f8590 # v3
 id: changes
 with:
 filters: |
 backend:
 - 'examples/e-commerce/api/**'
 frontend:
 - 'examples/e-commerce/web/**'

Self-hosted runner’ın bir bedeli var tabii: runner’ı temiz tutmak. Her iş bittiğinde container’lar, dangling image’lar ve cache birikir. Bunun için ayrı bir runner-cleanup.yml workflow'u yazdım; periyodik olarak makineyi temizler. Self-hosted'a geçerken bu temizlik adımını atlamayın, yoksa diskiniz birkaç gün içinde doluyor.

🚀 Tüm Bunları Açık Kaynak Yaptım: Sklent

Bu süreçte öğrendiğim her şeyi açık kaynak bir repoya taşıdım. Ama repoyu sadece bir e-ticaret demosu olarak değil, asıl mesele olan agent ekosistemi öne çıkacak şekilde kurguladık. Repo aslında teknik olarak bir agent bootstrap: herhangi bir projeye eklenebilen, agent + skill + reference + hook setinden oluşan, BRAID etrafında kurulu bir iskelet. Adını Sklent koydum.

Proje dizininde en baştaki README de bir gist linki paylaştım.

👉 https://gist.github.com/yasinatesim/bd5230ca0cc9b033c16280813c3ce6ff

Bu Gist benim bu tüm süreci tasarlarken oluşturduğum Agent ve SKILL yapısını tek prompt ile oluşturmayı sağlıyor.

E-ticaret uygulaması ise examples/e-commerce/ altında, markasız "Vela Commerce" olarak yaşıyor. Yani okuyan kişi repoyu açar açmaz amacın "bir e-ticaret scripti satmak" değil, "agent ekosistemini göstermek" olduğunu anlıyor.

📬 Geri Bildirim

Makaleyi yazarken, kaynakları belirleme ve araştırma için kendi notlarımı, yazım denetimi ve ek araştırma için Claude Fable 5 modelini kullandım. Resimleri üretmek için ise Gemini 3 Pro Preview 2k (Nano Banana Pro) modelini kullandım.

Yazı ile ilgili tavsiye, öneri, eleştirileri dikkate alıyorum. İletişime geçmek isterseniz bana websitemdeki sosyal medya adreslerimden veya Linkedin üzerinden ulaşabilirsiniz.

Sevgiyle kalın, Yasin 🤗

📚 Makaleyi Yazarken Kullandığım Kaynaklar