Bookstore Nedir? Sitedeki Serilerin Ortak Örnek Uygulaması: Ürün, Kullanıcılar ve İş Kuralları
Bookstore, sitedeki bütün serilerin aynı dünyada geçmesi için seçilmiş bir online kitapçıdır: bireysel satış, okul ve kütüphanelere Kurumsal Sipariş ve arka ofis. Altı rolü, katalogdan kargoya ana akışı, en az 20 adet, yüzde 10 kurumsal indirim, üst üste binmeyen kampanyalar, yarım kuruş yukarı yuvarlama gibi iş kurallarını, ürünün sayılarını ve ekibini anlatıyoruz.
Sürüm notu: Bu yazıdaki kod parçaları serinin referans uygulamasından alınmıştır ve C# 14 ile .NET 10 üzerinde derlenip denenmiştir.
Önce Tahmin Et: Üç Sepet, Üç Sonuç
Sitedeki serilerin ortak örnek uygulaması Bookstore (bir online kitapçı) aynı gün üç sipariş alıyor. Fiyatlar referans uygulamanın seed verisinden: Dune 489,00 TL, Emma 249,90 TL, Pride and Prejudice 239,90 TL, Frankenstein 219,90 TL, The Hobbit 329,90 TL.
- Ada, bireysel müşteri olarak 2 Dune ve 1 Emma alıyor.
- Nevin Hanım, çalıştığı okul adına 12 Pride and Prejudice ve 7 Frankenstein sipariş ediyor.
- Bir şehir kütüphanesi 21 adet The Hobbit istiyor; The Hobbit o hafta yüzde 25 kampanyada.
Her sipariş için iki soru: kabul edilir mi, kabul edilirse kaç TL tutar? Üçüncüsünde kütüphane hangi indirimi alır: yüzde 25 mi, kurumsal yüzde 10 mu, yoksa ikisinin toplamı mı? Cevaplar iş kuralları bölümünde. Oraya gelmeden önce kendi rakamlarını bir kenara yaz; özellikle üçüncü siparişin son kuruşuna dikkat et.
Neden Tek Bir Örnek Uygulama?
Sitede yüzlerce yazı ve onlarca seri var. Bir dönem her seri kendi örneğini seçiyordu: birinde ürün kataloğu, birinde İK (insan kaynakları) portalı, birinde genel bir mağaza. Okur her yeni seride önce yeni bir domain öğrenmek zorunda kalıyordu ve asıl konu, yani bir Entity Framework Core (EF Core) migration'ı, bir Angular signal'ı ya da bir güvenlik açığı, ikinci sıraya düşüyordu.
Bookstore bu sorunu tek bir dünya kurarak çözüyor. Book, Order, Customer, StockItem kelimelerini bir kez öğreniyorsun. Sonra Data Modeling ve Normalizasyon serisinde tablolarını, Entity Framework Core serisinde mapping'lerini, ASP.NET Core Web API serisinde endpoint'lerini, Angular serisinde ekranlarını, Web Güvenliği (OWASP Top 10) serisinde açıklarını görüyorsun. Konular birbirine bağlanıyor: bir seride konuşulan stok rezervasyonu ötekinde bir tabloya, bir sonrakinde bir endpoint'e ve bir teste dönüşüyor. Bir yazıda "bu sipariş neden iki kez kaydedildi?" sorusuyla karşılaştığında hangi tabloya ve hangi module'e bakacağını zaten biliyorsun.
Fikir yeni değil. Microsoft'un dotnet/eShop deposu kendini README'sinde "A reference .NET application implementing an eCommerce site" diye tanımlıyor. Fark şurada: eShop tek bir mimariyi baştan sona gösteriyor; Bookstore ise aynı ürünü farklı serilerin gözüyle, adım adım büyütüyor. Bir seride monolith olarak duran Bookstore, Microservices serisinde parçalara ayrılıyor; sipariş tarafı ise CQRS (Command Query Responsibility Segregation; yazma ve okuma modellerini ayırma) ile, CQRS ve Event Sourcing serisinde yeniden yazılıyor.
Neden kitapçı? Çünkü herkes kitap, sepet ve kargonun ne olduğunu biliyor; domain'i anlatmak için sayfa harcamak gerekmiyor. Ama işin içinde gerçek zorluklar da var: para hesabında yuvarlama, aynı son kitabı iki kişinin aynı anda alması, kurumların onay akışı, okul açılışında on katına çıkan trafik. Örnekler basit kalabiliyor, sorunlar gerçek kalıyor. Sitedeki "Bookstore nedir?" kutusu da bu seriye bağlanıyor; hangi seriden gelirsen gel, bu yazı aynı soruya aynı cevabı veriyor.
Ürün: Bookstore Ne Satar, Kime Satar?
Bookstore bireysel müşteriye online kitap satan bir kitapçı. Satıcı tek: Bookstore'un kendisi. Pazaryeri değil, başka satıcıların ürünlerini listelemiyor. Ürünün üç yüzü var.
Bireysel satış
Katalog ve arama, kitap detayı, sepet, sipariş, ödeme, kargo, iade, kampanya ve indirimler. Müşteri kitabı bulur, sepete ekler, siparişi verir; ödeme alınır, kitap kargoya verilir, müşteriye e-posta gider. Fiyatlar decimal ve TRY ile tutulur (gerektiğinde EUR), saatler UTC (Coordinated Universal Time; saat diliminden bağımsız ortak saat) olarak saklanır. Bir kitabı tanımlamak için ISBN (kitaplara verilen uluslararası standart numara) kullanılabilir, 13 haneli ISBN-13 biçimiyle; referans uygulamadaki URL'ler de ISBN ile çalışıyor.
Kurumsal Sipariş
Okulların, kütüphanelerin ve şirketlerin toplu siparişi, yani ürünün B2B (işletmeden işletmeye satış) tarafı. Bu özellik Requirements Analysis serisinde, Hakan'ın "Excel'den yükleme ekranı" talebinin arkasındaki problemden yola çıkılarak çıkarıldı. Oradan gelen kurallar: sipariş toplamda en az 20 adet içerir, kurumsal indirim yüzde 10'dur, stok yetmediğinde kısmi sevkiyat seçeneği ikinci sürüme (Release 2) bırakılmıştır.
Kurumsal müşterinin kendi birimleri (kampüs, şube, departman) ve kullanıcıları vardır; birimler bir hiyerarşi oluşturur ve satın almalar bir onay akışından geçer. Bu yüzden multi-tenant (bir uygulamada birden çok kurumun verisini birbirinden ayırarak tutmak) ve hiyerarşik veri senaryoları sitede hep bu kurumlar üzerinden anlatılıyor.
Arka ofis
Depoya stok girişi, tedarikçiden satın alma siparişi, iade onayı, fiyat ve kampanya yönetimi, raporlar. Müşterinin görmediği ama her siparişin arkasında çalışan taraf burası.
Kullanıcılar ve Roller
Bookstore'da altı rol var. Rol adları kodda da aynen bu şekilde geçiyor:
| Rol | Kim | Ne yapar |
|---|---|---|
Customer | Bireysel müşteri | Katalogda gezer, sipariş verir, iade ister |
InstitutionBuyer | Kurumun satın almacısı (ör. Nevin Hanım) | Toplu siparişi hazırlar |
InstitutionApprover | Kurumda onay yetkisi olan kişi | Siparişi onaylar ya da reddeder |
StoreStaff | Mağaza ekibi | Fiyat, kampanya ve iade onayı |
WarehouseStaff | Depo ekibi | Stok girişi ve sevkiyat hazırlığı |
Admin | Sistem yöneticisi | Kullanıcılar ve roller |
Roller Identity module'ünde yaşıyor ve authentication (kimlik doğrulama) ile authorization (yetkilendirme) ayrıntıları Authentication ve Authorization serisinde anlatılıyor. Referans uygulamada henüz giriş yok: sepet anonim, sipariş ad ve e-posta ile veriliyor. Bu bilinçli bir sadelik; giriş, roller ve kurum kullanıcıları ilgili serilerde ekleniyor.
Ana Akış: Katalogdan Kargoya

Mutlu yolda bir sipariş altı module'den geçer. Catalog'da kitap seçilir. Ordering sepeti siparişe çevirir ve OrderPlaced event'ini yayınlar. Inventory stoku ayırır (StockReserved); stok yetmezse OutOfStock olur. Payments ödeme sağlayıcısından parayı çeker (PaymentCaptured ya da PaymentFailed). Shipping kargo etiketini ve takip numarasını oluşturur (OrderShipped). Notifications müşteriye sipariş onayını ve kargo bilgisini e-posta ya da SMS (kısa mesaj) ile gönderir.
Akışın iki kararı Mimariye Giriş serisinden geliyor. Birincisi: ödeme sağlayıcısı 5 saniye içinde cevap vermezse sipariş PaymentPending durumuna geçer ve kullanıcıya hata dönülmez. İkincisi: sipariş sonrası e-posta senkron gönderilmez, bir queue üzerinden gider; başlangıçta in-process Channel<T> kullanılır ve mesaj kaybı riski bilerek kabul edilir, kalıcılık Messaging ve Event-Driven Architecture serisindeki outbox ile gelir.
Akışın yan kolları da aynı module'leri kullanır. Kurumsal siparişte InstitutionBuyer sepeti hazırlar, InstitutionApprover onaylar, sipariş ondan sonra normal akışa girer. İadeyi StoreStaff onaylar; Payments parayı iade eder, Inventory kitabı stoğa geri alır. Kampanyaları yine StoreStaff tanımlar ve fiyat değişince Catalog BookPriceChanged yayınlar.
İş Kuralları
Para ve yuvarlama
Para her yerde decimal. Her satırın toplamı ayrı ayrı kuruşa yuvarlanır, yarım kuruş yukarı gider. Bu karar da Requirements Analysis serisinde Bookstore muhasebesiyle verildi. Referans uygulamada kural tek bir method'da duruyor:
// Her satır iki haneye yuvarlanır, yarım kuruş sıfırdan uzağa (Requirements Analysis serisi).
public static decimal CalculateLineTotal(decimal unitPrice, int quantity) =>
Math.Round(unitPrice * quantity, 2, MidpointRounding.AwayFromZero);
MidpointRounding vermeden çağrılan Math.Round, .NET'te banker's rounding (yarım değeri en yakın çift sayıya yuvarlama, MidpointRounding.ToEven) kullanır. Fark çoğu gün görünmez, bazı faturalarda bir kuruş olarak ortaya çıkar.
İndirimler
Kurumsal indirim yüzde 10. Kampanyalar kitap bazında tanımlanır. İndirimler üst üste binmez: bir satıra kurumsal indirim ve kampanya birlikte uyuyorsa büyük olan uygulanır. Hakan bu cevabı Requirements Analysis serisindeki Example Mapping oturumunda vermişti.
Kurumsal minimum
Kurumsal sipariş toplamda en az 20 adet içerir. Tek kitaptan 20 adet de olur, iki kitaba bölünmüş 20 adet de. Kural siparişe bakar, sevkiyata değil: kısmi sevkiyatta ilk koli 12 adet çıksa bile sipariş geçerlidir.
Stok rezervasyonu
Sipariş verildiği anda stok ayrılır. Inventory her kitap için eldeki miktarı ve ayrılmış miktarı ayrı tutar; satılabilir miktar ikisinin farkıdır. Stok yetmiyorsa sipariş reddedilir; referans uygulamada bu bir 409 Conflict cevabı. Kurumun "eksik kalanı sonra gönderin" diyebilmesi Release 2'nin işi.
Sipariş durumları
public enum OrderStatus
{
Placed,
PaymentPending,
Paid,
Shipped,
Delivered,
Cancelled
}
Sipariş Placed ile doğar. Ödeme sağlayıcısı geç kalırsa PaymentPending, para çekilince Paid olur; kargoya verilince Shipped, teslim edilince Delivered. Cancelled kargodan önce iptal edilen siparişler içindir. Referans uygulama bugün yalnız Placed üretiyor; diğer geçişleri ödeme ve kargo module'lerini ekleyen seriler yazıyor.
Önce Tahmin Et'in cevapları
Ada'nın siparişi kabul edilir: 2 × 489,00 = 978,00 ve 1 × 249,90 = 249,90, toplam 1.227,90 TL. Referans uygulamanın API'si aynı sepet için şunu döndürüyor:
{"id":2,"status":"Placed","placedAtUtc":"2026-11-14T00:42:59.331223Z","items":[{"title":"Dune","unitPrice":489.00,"quantity":2,"lineTotal":978.00},{"title":"Emma","unitPrice":249.90,"quantity":1,"lineTotal":249.90}],"total":1227.90,"currency":"TRY"}
Nevin Hanım'ın siparişi reddedilir: 12 + 7 = 19 adet, kurumsal minimumun bir eksiği. Bir Frankenstein daha eklenince sipariş geçer ve iki satıra da yüzde 10 uygulanır.
Kütüphanenin siparişi kabul edilir. Liste toplamı 21 × 329,90 = 6.927,90 TL. Kurumsal yüzde 10 ve kampanya yüzde 25 üst üste binmez; büyük olan, yani yüzde 25 uygulanır. 6.927,90 × 0,75 = 5.195,925 çıkar ve yarım kuruş yukarı yuvarlanınca satır toplamı 5.195,93 TL olur. Yüzde 32,5 diye toplayan da, varsayılan Math.Round ile 5.195,92 bulan da yanlış faturayı kesmiş olur. Yalnız yüzde 10 uygulansaydı tutar 6.235,11 TL olurdu.
Sayılar: Bookstore Ne Kadar Büyük?
Mimari seriler kararlarını bu sayılara göre veriyor. Katalogda yaklaşık 40.000 kitap var. Günde yaklaşık 15.000 tekil ziyaretçi geliyor ve yaklaşık 800 sipariş veriliyor. Okul açılışı ve yıl sonu kampanyalarında trafik normalin yaklaşık 10 katına çıkıyor: zirvede dakikada yaklaşık 120 sipariş, katalog aramasında saniyede yaklaşık 200 istek.
Ölçülebilir hedefler Mimariye Giriş serisinde konuldu: katalog aramasında saniyede 200 istek altında p95 (isteklerin yüzde 95'inin altında kaldığı süre) 300 ms'nin altında, checkout'ta p95 800 ms'nin altında, checkout için aylık yüzde 99,9 availability. On sekiz aylık hedef ise sipariş hacmini üç katına çıkarmak, ikinci bir ülkeye İngilizce arayüzle açılmak ve aynı API'yi kullanan bir mobil uygulama çıkarmak.
Bugünkü ekip üç developer ve yarı zamanlı bir ops kişisinden oluşuyor, haftada bir deploy yapılıyor. On iki ila on sekiz ay içinde dört stream-aligned ekibe (Catalog ve arama, Ordering ve ödeme, Inventory ile Shipping'i kapsayan Fulfillment, Notifications ile hesap tarafını kapsayan Customer Experience) ve bir platform ekibine büyümesi bekleniyor. Bu sayılar ürünün sayıları; referans uygulamadaki 20 kitaplık seed verisi değil.
Ekip: Selin, Emre, Ayşe, Hakan, Gül ve Nevin Hanım
Süreç ve proje yönetimi serilerinde Bookstore'u bir ekip geliştiriyor. Selin Product Owner. Emre ve Ayşe developer. Hakan kurumsal satıştan sorumlu ve okullarla yıllardır çalışıyor. Gül müşteri hizmetlerinde, kurumlardan gelen listeleri bugün elle giren kişi. Nevin Hanım ise ekibin içinden değil, bir okulun satın alma sorumlusu, yani kurumsal müşterinin kendisi.
Altısı birlikte en çok Requirements Analysis serisinde görünüyor: Hakan'ın talebi, Gül'ün düzeltme listesi, Nevin Hanım'la yapılan görüşme ve Selin'in önceliklendirme kararları orada. Scrum serisinde Selin, Ayşe ve Emre'nin yanına Angular tarafında Zeynep, pipeline'da Can, test otomasyonunda Deniz ve Scrum Master olarak Kerem katılıyor. Kod örneklerinde ise kişi adları Ada, Alan, Grace, Linus gibi uluslararası adlar; kitaplar da Dune, Emma, The Hobbit, Middlemarch gibi bilinen klasikler.
Sık Düşülen Tuzaklar
Referans uygulamayı ürünün tamamı sanmak. Repo'nun main branch'inde yalnız Catalog, Ordering ve Inventory var. Payments, Shipping, Notifications, Institutional ve Identity, onları anlatan serilerin branch'lerinde ekleniyor. "Ödeme kodu nerede?" sorusunun cevabı main'de değil.
Ürünün sayılarını referans uygulamaya uygulamak. 20 kitaplık bir veritabanında yapılan arama 300 ms hedefinin çok altında kalır; bu, 40.000 kitapta ve saniyede 200 istekte de kalacağını göstermez. Performans serileri ölçümü gerçekçi veri hacmiyle yapıyor.
Kuralları genel doğru sanmak. En az 20 adet ve yüzde 10 indirim Bookstore'un örnek kararları; senin ürünündeki kurumsal kural başka olabilir. Seriler bu kuralları sabit tutuyor ki bir seride okuduğun sonuç ötekinde de tutsun.
Eski yazılarda başka bir örnek görünce kaybolmak. Bazı eski yazılar hâlâ İK Portalı ya da genel bir ürün kataloğu kullanıyor. Karşılıkları belli: şirket, departman ve çalışan Bookstore'da Institution, InstitutionUnit ve InstitutionUser; izin ya da satın alma onayı PurchaseApproval; ProductCatalog ve Product ise Catalog ve Book.
Yuvarlamayı varsayılana bırakmak. Math.Round(x, 2) ile Math.Round(x, 2, MidpointRounding.AwayFromZero) farklı sonuç verebilir; kütüphane örneğinde fark bir kuruştu.
Küçük Bir Deneme
★☆☆ Kütüphane 21 yerine 20 adet The Hobbit alsaydı satır toplamı kaç olurdu? Bu sefer varsayılan Math.Round ile AwayFromZero arasında fark çıkar mı, neden?
★★☆ Nevin Hanım siparişini 12 Pride and Prejudice ve 8 Frankenstein olarak yeniliyor; Pride and Prejudice o hafta yüzde 15 kampanyada. Her satıra hangi indirim uygulanır, satır toplamları ve sipariş toplamı ne olur? Hesabı CalculateLineTotal mantığıyla küçük bir console uygulamasında doğrula.
★★★ Figürdeki akışın her adımı için adımı hangi rolün başlattığını ya da tetiklediğini yaz. Kurumsal onay (PurchaseApproval) hangi module'ün sorumluluğunda olmalı: Ordering mi, Institutional mı? Kararını bir cümlelik gerekçeyle yaz.
Bu yazıda Bookstore'un ürün tarafını gördük: ne sattığı, kime sattığı, hangi kurallarla çalıştığı. Bir sonraki yazıda teknik yüzüne geçiyoruz: module'ler ve event'leri, BookstoreContext ile SQL Server şeması, Web API endpoint'leri ve Angular istemcisi. Repo'nun main branch'indeki referans uygulamayı Windows'ta adım adım çalıştıracağız.