[{"data":1,"prerenderedAt":3777},["ShallowReactive",2],{"search-index":3,"about-projects":2846},[4,394,1104,1591,2341],{"id":5,"title":6,"body":7,"category":378,"cover":379,"date":380,"demo":381,"description":382,"draft":381,"extension":383,"featured":384,"meta":385,"navigation":384,"path":386,"readingTime":387,"seo":388,"stem":389,"tags":390,"updated":380,"__hash__":393},"blog\u002Fyazilar\u002Fteknoloji-ve-ai-kararlari.md","Teknoloji ve AI Kararlarını Projeye Bağlamak",{"type":8,"value":9,"toc":364},"minimark",[10,19,22,27,30,46,49,53,56,82,85,100,104,107,167,170,174,181,184,191,195,198,230,234,260,274,278,284,287,290,317,320,324,327,344,347,351,354,357],[11,12,13,14,18],"p",{},"Yeni bir teknolojiyi denemek heyecan verici bir karar gibi görünüyor. Pratikte bir maliyet kararı: seçtiğin araç, o projeyi bundan sonra kimin nasıl sürdüreceğini de belirliyor. Yapay zekâ için de aynı şey geçerli — asıl soru \"kullanayım mı\" değil, ",[15,16,17],"strong",{},"hangi adımda kullanayım",".",[11,20,21],{},"Bu yazı, bu iki soruyu ayrı ayrı değil, aynı karar hattının parçaları olarak ele alıyor.",[23,24,26],"h2",{"id":25},"önce-problem-sonra-araç","Önce problem, sonra araç",[11,28,29],{},"Bir teknoloji tartışması genellikle araçtan başlıyor: \"şunu mu kullansak, bunu mu?\" Bu soru neredeyse her zaman erken. Cevaplanabilmesi için önce şunların netleşmesi gerekiyor:",[31,32,33,37,40,43],"ul",{},[34,35,36],"li",{},"Bu sistem hangi işi yapacak ve kim kullanacak?",[34,38,39],{},"Veri nereden geliyor, nereye gidiyor?",[34,41,42],{},"Hangi kısım gerçekten değişken, hangi kısım yıllarca aynı kalacak?",[34,44,45],{},"Yanlış giderse ne olur — bir rapor mu bozulur, bir ödeme mi kaybolur?",[11,47,48],{},"Son madde belirleyici. Bir dashboard'un yanlış sayı göstermesiyle bir ödemenin iki kez işlenmesi aynı ağırlıkta değil. Aracın ne kadar \"yeni\" olabileceğini de büyük ölçüde bu belirliyor.",[23,50,52],{"id":51},"yeni-bir-aracın-gerçek-maliyeti-öğrenmek-değil","Yeni bir aracın gerçek maliyeti öğrenmek değil",[11,54,55],{},"Yeni bir framework ya da kütüphane seçerken en çok konuşulan şey öğrenme eğrisi oluyor. Halbuki öğrenmek bir kerelik bir maliyet. Asıl maliyet sonrasında geliyor:",[31,57,58,64,70,76],{},[34,59,60,63],{},[15,61,62],{},"Bakım:"," Bu aracın kırıcı sürüm geçişlerini kim takip edecek?",[34,65,66,69],{},[15,67,68],{},"Devralınabilirlik:"," Yarın başka biri bu projeye baktığında ne kadar sürede anlar?",[34,71,72,75],{},[15,73,74],{},"Kaynak derinliği:"," Sıra dışı bir hatayla karşılaştığında arayıp bulabileceğin bir cevap var mı, yoksa kaynak koda mı bakacaksın?",[34,77,78,81],{},[15,79,80],{},"Sağlayıcı bağımlılığı:"," Aracın arkasındaki servis sözleşmesini değiştirirse elinde ne kalıyor?",[11,83,84],{},"Bu sorular \"yeni olan kötüdür\" demiyor. Sadece yeniliğin bedava olmadığını söylüyor. Bedeli ödemeye değecek bir karşılık varsa yeni araç doğru seçim.",[86,87,90,97],"callout",{"title":88,"type":89},"Bir yenilik bütçesi tut","tip",[11,91,92,93,96],{},"Her projede sınırlı sayıda \"yeni ve kanıtlanmamış\" tercih taşıyabilirsin. Bunu bir bütçe gibi düşünmek işe yarıyor: bütçeyi projenin ",[15,94,95],{},"ayırt edici"," kısmına harca, sıradan kısmına değil.",[11,98,99],{},"Kimlik doğrulama, dosya yükleme ve tablo listeleme gibi çözülmüş problemlerde bilinen aracı seç. Bütçeyi, projenin gerçekten farklı olduğu yere sakla.",[23,101,103],{"id":102},"karar-tablosu","Karar tablosu",[11,105,106],{},"Pratikte kendime sorduğum ayrım kabaca şu:",[108,109,110,123],"table",{},[111,112,113],"thead",{},[114,115,116,120],"tr",{},[117,118,119],"th",{},"Durum",[117,121,122],{},"Yönelim",[124,125,126,135,143,151,159],"tbody",{},[114,127,128,132],{},[129,130,131],"td",{},"Problem çözülmüş ve sıradan",[129,133,134],{},"Bilinen, olgun araç",[114,136,137,140],{},[129,138,139],{},"Problem projenin ayırt edici kısmı",[129,141,142],{},"Yeni araç değerlendirilebilir",[114,144,145,148],{},[129,146,147],{},"Hata maliyeti finansal veya yasal",[129,149,150],{},"Olgun araç, geniş kaynak",[114,152,153,156],{},[129,154,155],{},"Kısa ömürlü, atılabilir iş",[129,157,158],{},"Deneme için uygun",[114,160,161,164],{},[129,162,163],{},"Uzun ömürlü, devredilecek sistem",[129,165,166],{},"Devralınabilirlik öncelikli",[11,168,169],{},"Tablo bir kural değil, bir başlangıç noktası. Ama \"bu aracı neden seçtim\" sorusuna sonradan verilecek cevabı hazırlıyor — ve bir kararı savunabilmek, çoğu zaman doğru olmasının ön koşulu.",[23,171,173],{"id":172},"yapay-zekâ-bu-hattın-neresinde","Yapay zekâ bu hattın neresinde",[11,175,176,177,180],{},"Çalışma biçimimi dört adımda düşünüyorum: ",[15,178,179],{},"problemi anla → mimariyi kur → hızlandır → kontrol et ve yayınla."," Yapay zekâ bu hattın üçüncü adımında duruyor. Birinci ve ikinciyi devralmıyor.",[11,182,183],{},"Bunun sebebi AI'ın mimari öneremeyeceği değil. Önerebiliyor, üstelik makul öneriler de çıkıyor. Sebep şu: mimari kararın doğruluğu, çoğunlukla kodda görünmeyen bilgilere bağlı. Hangi ekip bakacak, hangi kısım gelecek çeyrekte değişecek, hangi kural mevzuattan geliyor, geçen sene hangi yaklaşım denenip bırakıldı. Bu bilgiler istemde yoksa, cevap teknik olarak doğru ama bağlam olarak yanlış oluyor.",[11,185,186,187,190],{},"Bir örnek: analiz ekranları olan bir sistemde \"iki ekran aynı metrik için farklı sayı gösteriyor\" problemine hızlıca bir düzeltme yazdırmak mümkün. Ama sorun genellikle hesaplamada değil, ",[15,188,189],{},"kapsamın tanımında"," oluyor — iki ekran farklı kümeyi değerlendiriyordur. Bunu görebilmek için ekranların ortak bir filtre sözleşmesine bağlanması gerektiğini bilmek gerekiyor. O karar verildikten sonra AI'ın hızlandıracağı çok iş var; ama kararın kendisi verilmeden yazılan kod, yanlış yerde iyileştirme yapıyor.",[23,192,194],{"id":193},"aiı-hangi-işlerde-kullanıyorum","AI'ı hangi işlerde kullanıyorum",[11,196,197],{},"Somut olarak üretkenlik kazandığı yerler:",[31,199,200,206,212,218,224],{},[34,201,202,205],{},[15,203,204],{},"Araştırma ve alternatif çıkarma."," Bir problemin kaç farklı yaklaşımı olduğunu hızlıca görmek, her birinin takasını konuşmak.",[34,207,208,211],{},[15,209,210],{},"Bilinen bir kalıbı yazmak."," Şekli belli olan doğrulama, dönüştürme, tekrar eden CRUD katmanları.",[34,213,214,217],{},[15,215,216],{},"Kendi koduma ikinci bir göz."," \"Bu fonksiyonda gözden kaçırdığım sınır durumu var mı?\" sorusu, tek başına çalışırken en çok işe yarayan kullanım.",[34,219,220,223],{},[15,221,222],{},"Test ve dokümantasyon taslağı."," Taslak; son hâli değil.",[34,225,226,229],{},[15,227,228],{},"Yabancı bir kod tabanını okumak."," Devraldığın bir projede hangi dosyanın ne yaptığını çıkarmak.",[23,231,233],{"id":232},"aia-bırakmadığım-şeyler","AI'a bırakmadığım şeyler",[31,235,236,242,248,254],{},[34,237,238,241],{},[15,239,240],{},"Mimari kararlar."," Yukarıdaki sebeple.",[34,243,244,247],{},[15,245,246],{},"Güvenlik sınırları."," Gizli değerlerin nerede duracağı, kimin neye erişeceği, bir endpoint'in neyi kabul edeceği. Bunlar gözden geçirilecek değil, baştan tasarlanacak konular.",[34,249,250,253],{},[15,251,252],{},"Doğrulanmamış teknik iddialar."," Bir kütüphanenin belirli bir sürümde nasıl davrandığı, bir sağlayıcının API sözleşmesi, bir platformun varsayılanı. Bunları kendi sürümünle ve kendi hesabınla sınamadan yazıya da koda da geçirmiyorum.",[34,255,256,259],{},[15,257,258],{},"Üretim verisine dokunan son adım."," Migrasyon, toplu güncelleme, gerçek para veya gerçek kişisel veriyle çalışan akışlar.",[86,261,264,271],{"title":262,"type":263},"En pahalı hata türü: makul görünen yanlış","warning",[11,265,266,267,270],{},"Yapay zekânın ürettiği kodda asıl risk, açıkça bozuk çıktı değil — o zaten hemen fark ediliyor. Risk, ",[15,268,269],{},"çalışan ama yanlış varsayıma dayanan"," kod. Doğru görünür, testten geçer, aylar sonra bir kenar durumda ortaya çıkar.",[11,272,273],{},"Bu yüzden kabul kriterim \"çalışıyor mu\" değil, \"neden böyle yazıldığını açıklayabiliyor muyum\". Açıklayamıyorsam henüz benim kodum değildir.",[23,275,277],{"id":276},"kararı-projeye-bağlamak","Kararı projeye bağlamak",[11,279,280,281],{},"Teknoloji seçimiyle AI kullanımı aslında aynı soruyu soruyor: ",[15,282,283],{},"bu kararın sahibi kim?",[11,285,286],{},"Bir aracı \"popüler olduğu için\" seçmekle, bir kodu \"AI öyle yazdığı için\" bırakmak arasında yapısal bir fark yok. İkisinde de karar dışarıya devredilmiş oluyor. Sorun çıktığında ise açıklama sende aranıyor.",[11,288,289],{},"Pratikte bunu şöyle işletiyorum:",[291,292,293,299,305,311],"ol",{},[34,294,295,298],{},[15,296,297],{},"Problemi ve sınırlarını yazıyorum."," Ne yapacak, ne yapmayacak, yanlış giderse ne olur.",[34,300,301,304],{},[15,302,303],{},"Mimariyi kendim kuruyorum."," Veri modeli, sınırlar, hangi kısım nereye ait.",[34,306,307,310],{},[15,308,309],{},"Hızlandırma adımında AI'dan yararlanıyorum."," Alternatifler, taslaklar, ikinci göz.",[34,312,313,316],{},[15,314,315],{},"Çıktıyı kendi ölçütlerimle denetliyorum."," Testler geçiyor mu, sınır durumları karşılanıyor mu, altı ay sonra ben bunu okuyabilir miyim.",[11,318,319],{},"Dördüncü adım geçilmeden hiçbir şey yayına çıkmıyor.",[23,321,323],{"id":322},"yayına-almadan-önce-sorduklarım","Yayına almadan önce sorduklarım",[11,325,326],{},"Yeni bir araç ya da hızlandırılmış bir çıktı üretime girmeden önce:",[31,328,329,332,335,338,341],{},[34,330,331],{},"Bu kararı bir başkasına gerekçesiyle anlatabiliyor muyum?",[34,333,334],{},"Araç yarın bakımsız kalırsa elimde çalışan bir plan var mı?",[34,336,337],{},"Kodun yaptığı işi satır satır takip edebiliyor muyum?",[34,339,340],{},"Hata durumunda ne olacağı tanımlı mı — sessizce mi geçiyor, yoksa görünür bir yerde mi duruyor?",[34,342,343],{},"Gizli bir değer yanlış tarafa mı düştü?",[11,345,346],{},"Bu soruların hiçbiri araç karşıtı değil. Hepsi aynı şeyi soruyor: karar bende mi kaldı?",[23,348,350],{"id":349},"kapanış","Kapanış",[11,352,353],{},"Güncel teknolojiyi takip etmek, her yeniyi kullanmak demek değil; hangi problemin artık daha iyi bir çözümü olduğunu bilmek demek. Yapay zekâyı kullanmak da her şeyi ona yazdırmak değil; hangi adımda gerçekten hızlandırdığını bilmek demek.",[11,355,356],{},"İkisinde de belirleyici olan araç değil, aracı seçerken sorulan soru. Daha iyi çözümler, doğru soruları sormakla başlıyor.",[86,358,361],{"title":359,"type":360},"Kapsam notu","info",[11,362,363],{},"Buradaki yaklaşım kendi çalışma biçimimi anlatır; ölçülmüş bir verimlilik iddiası ya da genel geçer bir kural değildir. Belirli araç ve sağlayıcı davranışları zamanla değişiyor — kendi projende uygularken güncel dokümantasyonla ve kendi sürümünle doğrula.",{"title":365,"searchDepth":366,"depth":367,"links":368},"",2,3,[369,370,371,372,373,374,375,376,377],{"id":25,"depth":366,"text":26},{"id":51,"depth":366,"text":52},{"id":102,"depth":366,"text":103},{"id":172,"depth":366,"text":173},{"id":193,"depth":366,"text":194},{"id":232,"depth":366,"text":233},{"id":276,"depth":366,"text":277},{"id":322,"depth":366,"text":323},{"id":349,"depth":366,"text":350},"Mimari","\u002Fimages\u002Fblog\u002Fkarar.svg","2026-09-09",false,"Yeni bir aracı ne zaman seçmeli, yapay zekâyı hangi adımda kullanmalı ve bu kararları projenin gerçek ihtiyacına nasıl bağlamalı.","md",true,{},"\u002Fyazilar\u002Fteknoloji-ve-ai-kararlari",10,{"title":6,"description":382},"yazilar\u002Fteknoloji-ve-ai-kararlari",[391,378,392],"AI","Teknoloji Seçimi","7kSI0_9qdl659dQ__tlhEG-5KdkoJnp8Av1XrY5-7Qw",{"id":395,"title":396,"body":397,"category":1092,"cover":1093,"date":1094,"demo":381,"description":1095,"draft":381,"extension":383,"featured":384,"meta":1096,"navigation":384,"path":1097,"readingTime":565,"seo":1098,"stem":1099,"tags":1100,"updated":380,"__hash__":1103},"blog\u002Fyazilar\u002Fnuxt-cloudflare-workers-yayinlama.md","Nuxt Uygulamasını Cloudflare Workers'da Yayınlamak",{"type":8,"value":398,"toc":1082},[399,402,405,409,412,471,489,493,496,600,618,622,625,711,718,724,731,735,738,826,832,836,843,923,941,950,954,960,963,986,997,1001,1004,1021,1032,1036,1039,1069,1072,1078],[11,400,401],{},"Nuxt uygulamasını edge ortamına taşımak yalnızca deploy hedefinin adını değiştirmek değil. Sunucu çıktısının Web API'leriyle uyumu, statik dosyaların nasıl sunulduğu ve secret'ların build ile runtime arasındaki yeri birlikte düşünülmeli.",[11,403,404],{},"Bu not, okuduğun sitenin Cloudflare Workers kurulumundan çıktı. Yapı küçük tutuldu: ayrı bir adaptör katmanı yerine Nitro'nun kendi Cloudflare preset'i ve resmi Wrangler aracı kullanılıyor.",[23,406,408],{"id":407},"sürümleri-sabitle","Sürümleri sabitle",[11,410,411],{},"Build ortamında sürpriz yaşamamak için Node ve paket yöneticisi sürümleri depoda tanımlı:",[413,414,418],"pre",{"className":415,"code":416,"language":417,"meta":365,"style":365},"language-json shiki shiki-themes github-dark","{\n  \"packageManager\": \"pnpm@11.19.0\",\n  \"engines\": { \"node\": \"24.x\" }\n}\n","json",[419,420,421,430,446,465],"code",{"__ignoreMap":365},[422,423,426],"span",{"class":424,"line":425},"line",1,[422,427,429],{"class":428},"s95oV","{\n",[422,431,432,436,439,443],{"class":424,"line":366},[422,433,435],{"class":434},"sDLfK","  \"packageManager\"",[422,437,438],{"class":428},": ",[422,440,442],{"class":441},"sU2Wk","\"pnpm@11.19.0\"",[422,444,445],{"class":428},",\n",[422,447,448,451,454,457,459,462],{"class":424,"line":367},[422,449,450],{"class":434},"  \"engines\"",[422,452,453],{"class":428},": { ",[422,455,456],{"class":434},"\"node\"",[422,458,438],{"class":428},[422,460,461],{"class":441},"\"24.x\"",[422,463,464],{"class":428}," }\n",[422,466,468],{"class":424,"line":467},4,[422,469,470],{"class":428},"}\n",[11,472,473,476,477,480,481,484,485,488],{},[419,474,475],{},".node-version"," dosyası da ",[419,478,479],{},"24"," değerini taşır. Cloudflare Workers Builds bu dosyayı okuyabilir; ",[419,482,483],{},"packageManager"," ise Corepack'in aynı pnpm sürümünü seçmesini sağlar. Kurulumda ",[419,486,487],{},"pnpm install --frozen-lockfile"," kullanmak, manifest ile kilit dosyası uyuşmadığında sessizce farklı bağımlılık çözülmesini engeller.",[23,490,492],{"id":491},"modern-worker-presetini-açıkça-seç","Modern Worker preset'ini açıkça seç",[11,494,495],{},"Nuxt'un sunucu katmanı Nitro tarafından hazırlanır. Bu projede hedef ES module tabanlı Cloudflare Worker'dır:",[413,497,501],{"className":498,"code":499,"language":500,"meta":365,"style":365},"language-ts shiki shiki-themes github-dark","export default defineNuxtConfig({\n  content: {\n    experimental: { sqliteConnector: \"native\" },\n    database: { type: \"d1\", bindingName: \"DB\" },\n  },\n  nitro: {\n    preset: \"cloudflare_module\",\n    prerender: { crawlLinks: true, failOnError: true },\n  },\n});\n","ts",[419,502,503,519,524,535,551,557,563,574,590,595],{"__ignoreMap":365},[422,504,505,509,512,516],{"class":424,"line":425},[422,506,508],{"class":507},"snl16","export",[422,510,511],{"class":507}," default",[422,513,515],{"class":514},"svObZ"," defineNuxtConfig",[422,517,518],{"class":428},"({\n",[422,520,521],{"class":424,"line":366},[422,522,523],{"class":428},"  content: {\n",[422,525,526,529,532],{"class":424,"line":367},[422,527,528],{"class":428},"    experimental: { sqliteConnector: ",[422,530,531],{"class":441},"\"native\"",[422,533,534],{"class":428}," },\n",[422,536,537,540,543,546,549],{"class":424,"line":467},[422,538,539],{"class":428},"    database: { type: ",[422,541,542],{"class":441},"\"d1\"",[422,544,545],{"class":428},", bindingName: ",[422,547,548],{"class":441},"\"DB\"",[422,550,534],{"class":428},[422,552,554],{"class":424,"line":553},5,[422,555,556],{"class":428},"  },\n",[422,558,560],{"class":424,"line":559},6,[422,561,562],{"class":428},"  nitro: {\n",[422,564,566,569,572],{"class":424,"line":565},7,[422,567,568],{"class":428},"    preset: ",[422,570,571],{"class":441},"\"cloudflare_module\"",[422,573,445],{"class":428},[422,575,577,580,583,586,588],{"class":424,"line":576},8,[422,578,579],{"class":428},"    prerender: { crawlLinks: ",[422,581,582],{"class":434},"true",[422,584,585],{"class":428},", failOnError: ",[422,587,582],{"class":434},[422,589,534],{"class":428},[422,591,593],{"class":424,"line":592},9,[422,594,556],{"class":428},[422,596,597],{"class":424,"line":387},[422,598,599],{"class":428},"});\n",[11,601,602,605,606,609,610,613,614,617],{},[419,603,604],{},"cloudflare_module",", Worker'ın beklediği ",[419,607,608],{},"fetch"," girişini üretir. Çıktının sunucu parçası ",[419,611,612],{},".output\u002Fserver\u002Findex.mjs",", tarayıcıya açık dosyaları ise ",[419,615,616],{},".output\u002Fpublic"," altında bulunur.",[23,619,621],{"id":620},"worker-kodu-ve-assetleri-birlikte-yayınla","Worker kodu ve asset'leri birlikte yayınla",[11,623,624],{},"Wrangler yapılandırmasının esas bölümü iki çıktı dizinini birbirine bağlar:",[413,626,628],{"className":415,"code":627,"language":417,"meta":365,"style":365},"{\n  \"name\": \"mehmet-macar\",\n  \"main\": \".output\u002Fserver\u002Findex.mjs\",\n  \"compatibility_flags\": [\"nodejs_compat\"],\n  \"assets\": {\n    \"binding\": \"ASSETS\",\n    \"directory\": \".output\u002Fpublic\"\n  }\n}\n",[419,629,630,634,646,658,672,680,692,702,707],{"__ignoreMap":365},[422,631,632],{"class":424,"line":425},[422,633,429],{"class":428},[422,635,636,639,641,644],{"class":424,"line":366},[422,637,638],{"class":434},"  \"name\"",[422,640,438],{"class":428},[422,642,643],{"class":441},"\"mehmet-macar\"",[422,645,445],{"class":428},[422,647,648,651,653,656],{"class":424,"line":367},[422,649,650],{"class":434},"  \"main\"",[422,652,438],{"class":428},[422,654,655],{"class":441},"\".output\u002Fserver\u002Findex.mjs\"",[422,657,445],{"class":428},[422,659,660,663,666,669],{"class":424,"line":467},[422,661,662],{"class":434},"  \"compatibility_flags\"",[422,664,665],{"class":428},": [",[422,667,668],{"class":441},"\"nodejs_compat\"",[422,670,671],{"class":428},"],\n",[422,673,674,677],{"class":424,"line":553},[422,675,676],{"class":434},"  \"assets\"",[422,678,679],{"class":428},": {\n",[422,681,682,685,687,690],{"class":424,"line":559},[422,683,684],{"class":434},"    \"binding\"",[422,686,438],{"class":428},[422,688,689],{"class":441},"\"ASSETS\"",[422,691,445],{"class":428},[422,693,694,697,699],{"class":424,"line":565},[422,695,696],{"class":434},"    \"directory\"",[422,698,438],{"class":428},[422,700,701],{"class":441},"\".output\u002Fpublic\"\n",[422,703,704],{"class":424,"line":576},[422,705,706],{"class":428},"  }\n",[422,708,709],{"class":424,"line":592},[422,710,470],{"class":428},[11,712,713,714,717],{},"Statik bir dosya eşleştiğinde Workers Assets bunu doğrudan sunar. ",[419,715,716],{},"\u002Fapi\u002Fcontact"," gibi bir asset'e karşılık gelmeyen istek Worker'a ulaşır. Böylece prerender edilmiş sayfalar ile server endpoint'i aynı deployment içinde kalır.",[11,719,720,723],{},[419,721,722],{},"nodejs_compat"," bayrağı, bağımlılıkların ihtiyaç duyduğu Node uyumluluk API'lerini Workers runtime'ında açar. Bu yine de klasik bir Node sunucusunda çalışıldığı anlamına gelmez; dosya sistemi veya uzun yaşayan process varsayımı yapılmamalıdır.",[11,725,726,727,730],{},"Nuxt Content'in Workers runtime'ında sorgu çalıştırabilmesi için ",[419,728,729],{},"DB"," adlı Cloudflare D1 binding'i de gerekir. Wrangler'ın kimliksiz resource provisioning özelliğiyle binding config'e yazılıp veritabanı ilk deploy sırasında oluşturulabilir; böylece hesap özelindeki database ID'yi repoya önceden koymak gerekmez.",[23,732,734],{"id":733},"prerenderı-yayın-öncesi-kontrol-olarak-kullan","Prerender'ı yayın öncesi kontrol olarak kullan",[11,736,737],{},"İçerik sayfaları build sırasında hazırlanır ve bağlantılar taranır:",[413,739,741],{"className":498,"code":740,"language":500,"meta":365,"style":365},"routeRules: {\n  \"\u002F\": { prerender: true },\n  \"\u002Fyazilar\u002F**\": { prerender: true },\n  \"\u002Fprojeler\u002F**\": { prerender: true },\n},\nnitro: {\n  prerender: { crawlLinks: true, failOnError: true },\n}\n",[419,742,743,750,762,773,784,789,796,822],{"__ignoreMap":365},[422,744,745,748],{"class":424,"line":425},[422,746,747],{"class":514},"routeRules",[422,749,679],{"class":428},[422,751,752,755,758,760],{"class":424,"line":366},[422,753,754],{"class":441},"  \"\u002F\"",[422,756,757],{"class":428},": { prerender: ",[422,759,582],{"class":434},[422,761,534],{"class":428},[422,763,764,767,769,771],{"class":424,"line":367},[422,765,766],{"class":441},"  \"\u002Fyazilar\u002F**\"",[422,768,757],{"class":428},[422,770,582],{"class":434},[422,772,534],{"class":428},[422,774,775,778,780,782],{"class":424,"line":467},[422,776,777],{"class":441},"  \"\u002Fprojeler\u002F**\"",[422,779,757],{"class":428},[422,781,582],{"class":434},[422,783,534],{"class":428},[422,785,786],{"class":424,"line":553},[422,787,788],{"class":428},"},\n",[422,790,791,794],{"class":424,"line":559},[422,792,793],{"class":514},"nitro",[422,795,679],{"class":428},[422,797,798,801,803,806,808,810,813,816,818,820],{"class":424,"line":565},[422,799,800],{"class":514},"  prerender",[422,802,453],{"class":428},[422,804,805],{"class":514},"crawlLinks",[422,807,438],{"class":428},[422,809,582],{"class":434},[422,811,812],{"class":428},", ",[422,814,815],{"class":514},"failOnError",[422,817,438],{"class":428},[422,819,582],{"class":434},[422,821,534],{"class":428},[422,823,824],{"class":424,"line":576},[422,825,470],{"class":428},[11,827,828,831],{},[419,829,830],{},"failOnError: true",", bozuk bir içerik route'unu ziyaretçiden önce CI build'inde görünür kılar. İletişim API'si ise prerender edilmez; her POST isteği Worker içinde çalışır.",[23,833,835],{"id":834},"build-değişkeni-ile-runtime-secretını-ayır","Build değişkeni ile runtime secret'ını ayır",[11,837,838,839,842],{},"Nuxt runtime config'te ",[419,840,841],{},"public"," altındaki değerler istemciye gidebilir, diğerleri yalnız sunucuda kalır:",[413,844,846],{"className":498,"code":845,"language":500,"meta":365,"style":365},"runtimeConfig: {\n  resendApiKey: \"\",\n  contactEmail: \"\",\n  contactFrom: \"\",\n  public: {\n    siteUrl: process.env.NUXT_PUBLIC_SITE_URL || \"http:\u002F\u002Flocalhost:3000\",\n  },\n}\n",[419,847,848,855,867,878,889,896,915,919],{"__ignoreMap":365},[422,849,850,853],{"class":424,"line":425},[422,851,852],{"class":514},"runtimeConfig",[422,854,679],{"class":428},[422,856,857,860,862,865],{"class":424,"line":366},[422,858,859],{"class":514},"  resendApiKey",[422,861,438],{"class":428},[422,863,864],{"class":441},"\"\"",[422,866,445],{"class":428},[422,868,869,872,874,876],{"class":424,"line":367},[422,870,871],{"class":514},"  contactEmail",[422,873,438],{"class":428},[422,875,864],{"class":441},[422,877,445],{"class":428},[422,879,880,883,885,887],{"class":424,"line":467},[422,881,882],{"class":514},"  contactFrom",[422,884,438],{"class":428},[422,886,864],{"class":441},[422,888,445],{"class":428},[422,890,891,894],{"class":424,"line":553},[422,892,893],{"class":514},"  public",[422,895,679],{"class":428},[422,897,898,901,904,907,910,913],{"class":424,"line":559},[422,899,900],{"class":514},"    siteUrl",[422,902,903],{"class":428},": process.env.",[422,905,906],{"class":434},"NUXT_PUBLIC_SITE_URL",[422,908,909],{"class":507}," ||",[422,911,912],{"class":441}," \"http:\u002F\u002Flocalhost:3000\"",[422,914,445],{"class":428},[422,916,917],{"class":424,"line":565},[422,918,556],{"class":428},[422,920,921],{"class":424,"line":576},[422,922,470],{"class":428},[11,924,925,926,812,929,932,933,936,937,940],{},"Runtime override isimleri sırasıyla ",[419,927,928],{},"NUXT_RESEND_API_KEY",[419,930,931],{},"NUXT_CONTACT_EMAIL"," ve ",[419,934,935],{},"NUXT_CONTACT_FROM"," olur. Resend anahtarının ",[419,938,939],{},"NUXT_PUBLIC_"," ile başlamaması kritik; aksi hâlde gizli değer istemci yapılandırmasına taşınabilir.",[11,942,943,945,946,949],{},[419,944,906],{}," ise gerçekten public'tir. Canonical, sitemap ve RSS prerender sırasında üretildiği için bu değer Cloudflare'ın build ortamında ",[419,947,948],{},"https:\u002F\u002Fmehmetmacar.com"," olarak tanımlanır. Aynı değer origin kontrolü için Worker runtime variable olarak da bulunur.",[23,951,953],{"id":952},"i̇letişim-formunu-edgede-güvenli-tut","İletişim formunu edge'de güvenli tut",[11,955,956,957,959],{},"Form tarayıcıdan doğrudan Resend'e gitmez. Ziyaretçi ",[419,958,716],{}," endpoint'ine JSON gönderir; Worker girdiyi doğruladıktan sonra Resend API'yi server-side çağırır.",[11,961,962],{},"Bu projedeki temel kontroller:",[31,964,965,968,971,974,977,983],{},[34,966,967],{},"Alan tipi, e-posta biçimi, boşluk ve uzunluk doğrulaması",[34,969,970],{},"JSON ve toplam istek boyutu sınırı",[34,972,973],{},"Honeypot alanı",[34,975,976],{},"Site origin kontrolü",[34,978,979,982],{},[419,980,981],{},"CF-Connecting-IP"," üzerinden temel, isolate içi rate limit",[34,984,985],{},"Sağlayıcı hatasını ve form içeriğini loglara taşımayan hata cevabı",[11,987,988,989,992,993,996],{},"Mailin ",[419,990,991],{},"Reply-To"," alanına ziyaretçinin adresi yazılır. Böylece gerçek alıcı kutusunda ",[15,994,995],{},"Yanıtla"," seçildiğinde yanıt formu dolduran kişiye gider.",[23,998,1000],{"id":999},"github-pushunu-deploymenta-dönüştür","GitHub push'unu deployment'a dönüştür",[11,1002,1003],{},"Workers Builds için işlem iki adımlıdır:",[413,1005,1009],{"className":1006,"code":1007,"language":1008,"meta":365,"style":365},"language-sh shiki shiki-themes github-dark","corepack pnpm build\ncorepack pnpm exec wrangler deploy\n","sh",[419,1010,1011,1016],{"__ignoreMap":365},[422,1012,1013],{"class":424,"line":425},[422,1014,1015],{},"corepack pnpm build\n",[422,1017,1018],{"class":424,"line":366},[422,1019,1020],{},"corepack pnpm exec wrangler deploy\n",[11,1022,1023,1024,1027,1028,1031],{},"İlk komut Nuxt\u002FNitro çıktısını üretir, ikincisi Worker ile asset'leri yükler. Production branch ",[419,1025,1026],{},"main"," olduğunda bu branch'e gelen push aktif deployment'ı günceller. Diğer branch'lerde ",[419,1029,1030],{},"wrangler versions upload"," kullanılırsa production'ı değiştirmeden preview version alınabilir.",[23,1033,1035],{"id":1034},"deploy-etmeden-bundleı-doğrula","Deploy etmeden bundle'ı doğrula",[11,1037,1038],{},"Gerçek hesapta değişiklik yapmadan önce üç kontrol yeterli bir taban sağlar:",[413,1040,1042],{"className":1006,"code":1041,"language":1008,"meta":365,"style":365},"pnpm lint\npnpm typecheck\npnpm test\npnpm build\npnpm exec wrangler deploy --dry-run\n",[419,1043,1044,1049,1054,1059,1064],{"__ignoreMap":365},[422,1045,1046],{"class":424,"line":425},[422,1047,1048],{},"pnpm lint\n",[422,1050,1051],{"class":424,"line":366},[422,1052,1053],{},"pnpm typecheck\n",[422,1055,1056],{"class":424,"line":367},[422,1057,1058],{},"pnpm test\n",[422,1060,1061],{"class":424,"line":467},[422,1062,1063],{},"pnpm build\n",[422,1065,1066],{"class":424,"line":553},[422,1067,1068],{},"pnpm exec wrangler deploy --dry-run\n",[11,1070,1071],{},"Dry-run, Wrangler'ın Worker girişini ve statik asset yapılandırmasını okuyabildiğini gösterir. Canlı dağıtımdan sonra doğrudan detay URL'leri, sitemap\u002FRSS\u002Frobots, 404 ve iletişim formu ayrıca sınanmalıdır. Build'in başarılı olması DNS, domain doğrulaması veya gerçek e-posta teslimini tek başına kanıtlamaz.",[86,1073,1075],{"title":1074,"type":360},"Platform ayarları zamanla değişir",[11,1076,1077],{},"Bu yapı Nuxt 4.5, Nitro 2.13 ve Wrangler 4 ile hazırlanmıştır. Compatibility date ve Wrangler sürümü güncellenirken Cloudflare'ın güncel framework rehberiyle yeniden karşılaştırılmalıdır.",[1079,1080,1081],"style",{},"html pre.shiki code .s95oV, html code.shiki .s95oV{--shiki-default:#E1E4E8}html pre.shiki code .sDLfK, html code.shiki .sDLfK{--shiki-default:#79B8FF}html pre.shiki code .sU2Wk, html code.shiki .sU2Wk{--shiki-default:#9ECBFF}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html pre.shiki code .snl16, html code.shiki .snl16{--shiki-default:#F97583}html pre.shiki code .svObZ, html code.shiki .svObZ{--shiki-default:#B392F0}",{"title":365,"searchDepth":366,"depth":367,"links":1083},[1084,1085,1086,1087,1088,1089,1090,1091],{"id":407,"depth":366,"text":408},{"id":491,"depth":366,"text":492},{"id":620,"depth":366,"text":621},{"id":733,"depth":366,"text":734},{"id":834,"depth":366,"text":835},{"id":952,"depth":366,"text":953},{"id":999,"depth":366,"text":1000},{"id":1034,"depth":366,"text":1035},"Nuxt","\u002Fimages\u002Fblog\u002Fdeploy.svg","2026-09-05","Nitro preset, Workers Assets, Wrangler ve runtime secret'larıyla Nuxt 4 uygulamasını Cloudflare Workers'a hazırlarken önemli kararlar.",{},"\u002Fyazilar\u002Fnuxt-cloudflare-workers-yayinlama",{"title":396,"description":1095},"yazilar\u002Fnuxt-cloudflare-workers-yayinlama",[1092,1101,1102],"DevOps","Cloudflare","-5UWtZ5sVsNkp_2BonyAXDc_YGPfbgC0x2E4S37id10",{"id":1105,"title":1106,"body":1107,"category":1580,"cover":1581,"date":1582,"demo":381,"description":1583,"draft":381,"extension":383,"featured":384,"meta":1584,"navigation":384,"path":1585,"readingTime":592,"seo":1586,"stem":1587,"tags":1588,"updated":1582,"__hash__":1590},"blog\u002Fyazilar\u002Fayni-metrik-farkli-sayi.md","Aynı Metrik İki Ekranda Neden Farklı Çıkıyor?",{"type":8,"value":1108,"toc":1570},[1109,1116,1119,1123,1126,1146,1149,1155,1159,1165,1171,1177,1183,1187,1190,1196,1202,1209,1212,1454,1461,1465,1472,1475,1482,1485,1489,1492,1495,1499,1502,1510,1513,1520,1523,1527,1530,1547,1550,1552,1559,1562,1567],[11,1110,1111,1112,1115],{},"Bir dashboard'da en can sıkıcı hata türü budur: iki ekran aynı metriği gösteriyor, isimleri aynı, ama sayılar tutmuyor. Toplantıda birisi fark ediyor ve o andan itibaren ",[15,1113,1114],{},"ekranların hiçbirine güvenilmiyor"," — doğru olan da dahil.",[11,1117,1118],{},"İlk refleks hesaplamayı kontrol etmek oluyor. Neredeyse her seferinde yanlış yer.",[23,1120,1122],{"id":1121},"sorun-kapsamın-tanımında","Sorun kapsamın tanımında",[11,1124,1125],{},"Bir metrik üç şeyden oluşuyor ve isim bunların hiçbirini içermiyor:",[31,1127,1128,1134,1140],{},[34,1129,1130,1133],{},[15,1131,1132],{},"Küme:"," Hangi kayıtlar sayılıyor? Test kayıtları dahil mi? Silinmiş olanlar? Belirli bir alt kırılıma ait olanlar?",[34,1135,1136,1139],{},[15,1137,1138],{},"Zaman:"," Hangi tarih alanına göre filtreleniyor? Kaydın oluşturulma tarihi mi, güncellenme tarihi mi, ilgili olayın gerçekleştiği tarih mi?",[34,1141,1142,1145],{},[15,1143,1144],{},"Durum:"," Hangi aşamalar \"başarılı\" sayılıyor? Bu liste iki ekranda aynı mı?",[11,1147,1148],{},"İki ekran farklı sayı gösteriyorsa, bu üçünden en az biri farklıdır. Hesaplama formülü genellikle ikisinde de doğrudur; sadece farklı kümeler üzerinde doğrudur.",[86,1150,1152],{"title":1151,"type":89},"Önce farkı ölç, sonra açıkla",[11,1153,1154],{},"Hangi ekranın doğru olduğunu tartışmadan önce, aradaki kayıtları listele. Bir ekranda olup diğerinde olmayan kayıtları görmek, sorunun hangi eksende olduğunu genellikle tek bakışta gösteriyor: hepsi aynı tarih aralığının kenarındaysa zaman, hepsi aynı aşamadaysa durum, hepsi aynı kırılıma aitse küme.",[23,1156,1158],{"id":1157},"en-sık-karşılaşılan-üç-ayrışma","En sık karşılaşılan üç ayrışma",[11,1160,1161,1164],{},[15,1162,1163],{},"Farklı tarih alanı."," Bir ekran kaydın sisteme düştüğü tarihe, diğeri son güncellenme tarihine bakıyor. Ay sonunda güncellenen mayıs kaydı, birinde mayısta diğerinde haziranda görünüyor.",[11,1166,1167,1170],{},[15,1168,1169],{},"Farklı durum listesi."," \"Başarılı\" sayılan aşamalar bir ekranda üç, diğerinde iki tane. Bir aşama sonradan eklenmiş, ama sadece bir sorguya.",[11,1172,1173,1176],{},[15,1174,1175],{},"Sessiz filtre."," Bir ekranda kullanıcının önceden seçtiği bir daraltma açık kalmış. Sayı yanlış değil, sadece dar bir kümenin doğru sayısı.",[11,1178,1179,1180,18],{},"Üçünün de ortak yanı şu: hiçbiri hatalı kod değil. Hepsi ",[15,1181,1182],{},"eksik anlaşma",[23,1184,1186],{"id":1185},"anlık-durum-mu-o-tarihteki-durum-mu","Anlık durum mu, o tarihteki durum mu?",[11,1188,1189],{},"Bu, dönemsel raporlarda en çok yanılgı üreten ayrım ve genellikle hiç konuşulmuyor.",[11,1191,1192,1195],{},[15,1193,1194],{},"Anlık analiz"," kayıtların şu andaki durumuna bakıyor: \"Mayısta gelen kayıtların bugün kaçı tamamlanmış?\"",[11,1197,1198,1201],{},[15,1199,1200],{},"Seçili dönem analizi"," kayıtların geçmiş bir tarihteki durumunu yeniden kuruyor: \"Mayısta gelen kayıtlar, 15 Mayıs'ta hangi aşamadaydı?\"",[11,1203,1204,1205,1208],{},"İkisi de doğru sorular ama aynı soru değiller. Karıştırıldığında ortaya sistematik bir yanlış çıkıyor: geçmiş bir dönemi bugünün aşama dağılımıyla okumak, o dönemde ",[15,1206,1207],{},"henüz olgunlaşmamış"," kayıtları başarısız gibi gösteriyor. Yeni gelmiş bir kayıt doğal olarak ilk aşamada; ona bakıp \"bu ayın performansı düşük\" demek, sürecin kendisini değil geçen süreyi ölçmek oluyor.",[11,1210,1211],{},"Teknik olarak fark, tek bir tabloya bakmakla bir geçmiş tablosuna bakmak arasında:",[413,1213,1217],{"className":1214,"code":1215,"language":1216,"meta":365,"style":365},"language-sql shiki shiki-themes github-dark","-- Anlık: kaydın bugünkü aşaması\nSELECT stage, COUNT(*) FROM records\nWHERE created_at >= '2026-05-01' AND created_at \u003C '2026-06-01'\nGROUP BY stage;\n\n-- Seçili dönem: kaydın o tarihteki aşaması\nSELECT h.stage, COUNT(*)\nFROM records r\nJOIN stage_history h ON h.record_id = r.id\nWHERE r.created_at >= '2026-05-01' AND r.created_at \u003C '2026-06-01'\n  AND h.valid_from \u003C= '2026-05-15'\n  AND (h.valid_to IS NULL OR h.valid_to > '2026-05-15')\nGROUP BY h.stage;\n","sql",[419,1218,1219,1225,1251,1276,1284,1289,1294,1317,1324,1353,1382,1401,1440],{"__ignoreMap":365},[422,1220,1221],{"class":424,"line":425},[422,1222,1224],{"class":1223},"sAwPA","-- Anlık: kaydın bugünkü aşaması\n",[422,1226,1227,1230,1233,1236,1239,1242,1245,1248],{"class":424,"line":366},[422,1228,1229],{"class":507},"SELECT",[422,1231,1232],{"class":428}," stage, ",[422,1234,1235],{"class":434},"COUNT",[422,1237,1238],{"class":428},"(",[422,1240,1241],{"class":507},"*",[422,1243,1244],{"class":428},") ",[422,1246,1247],{"class":507},"FROM",[422,1249,1250],{"class":428}," records\n",[422,1252,1253,1256,1259,1262,1265,1268,1270,1273],{"class":424,"line":367},[422,1254,1255],{"class":507},"WHERE",[422,1257,1258],{"class":428}," created_at ",[422,1260,1261],{"class":507},">=",[422,1263,1264],{"class":441}," '2026-05-01'",[422,1266,1267],{"class":507}," AND",[422,1269,1258],{"class":428},[422,1271,1272],{"class":507},"\u003C",[422,1274,1275],{"class":441}," '2026-06-01'\n",[422,1277,1278,1281],{"class":424,"line":467},[422,1279,1280],{"class":507},"GROUP BY",[422,1282,1283],{"class":428}," stage;\n",[422,1285,1286],{"class":424,"line":553},[422,1287,1288],{"emptyLinePlaceholder":384},"\n",[422,1290,1291],{"class":424,"line":559},[422,1292,1293],{"class":1223},"-- Seçili dönem: kaydın o tarihteki aşaması\n",[422,1295,1296,1298,1301,1303,1306,1308,1310,1312,1314],{"class":424,"line":565},[422,1297,1229],{"class":507},[422,1299,1300],{"class":434}," h",[422,1302,18],{"class":428},[422,1304,1305],{"class":434},"stage",[422,1307,812],{"class":428},[422,1309,1235],{"class":434},[422,1311,1238],{"class":428},[422,1313,1241],{"class":507},[422,1315,1316],{"class":428},")\n",[422,1318,1319,1321],{"class":424,"line":576},[422,1320,1247],{"class":507},[422,1322,1323],{"class":428}," records r\n",[422,1325,1326,1329,1332,1335,1337,1339,1342,1345,1348,1350],{"class":424,"line":592},[422,1327,1328],{"class":507},"JOIN",[422,1330,1331],{"class":428}," stage_history h ",[422,1333,1334],{"class":507},"ON",[422,1336,1300],{"class":434},[422,1338,18],{"class":428},[422,1340,1341],{"class":434},"record_id",[422,1343,1344],{"class":507}," =",[422,1346,1347],{"class":434}," r",[422,1349,18],{"class":428},[422,1351,1352],{"class":434},"id\n",[422,1354,1355,1357,1359,1361,1364,1367,1369,1371,1373,1375,1377,1380],{"class":424,"line":387},[422,1356,1255],{"class":507},[422,1358,1347],{"class":434},[422,1360,18],{"class":428},[422,1362,1363],{"class":434},"created_at",[422,1365,1366],{"class":507}," >=",[422,1368,1264],{"class":441},[422,1370,1267],{"class":507},[422,1372,1347],{"class":434},[422,1374,18],{"class":428},[422,1376,1363],{"class":434},[422,1378,1379],{"class":507}," \u003C",[422,1381,1275],{"class":441},[422,1383,1385,1388,1390,1392,1395,1398],{"class":424,"line":1384},11,[422,1386,1387],{"class":507},"  AND",[422,1389,1300],{"class":434},[422,1391,18],{"class":428},[422,1393,1394],{"class":434},"valid_from",[422,1396,1397],{"class":507}," \u003C=",[422,1399,1400],{"class":441}," '2026-05-15'\n",[422,1402,1404,1406,1409,1412,1414,1417,1420,1423,1426,1428,1430,1432,1435,1438],{"class":424,"line":1403},12,[422,1405,1387],{"class":507},[422,1407,1408],{"class":428}," (",[422,1410,1411],{"class":434},"h",[422,1413,18],{"class":428},[422,1415,1416],{"class":434},"valid_to",[422,1418,1419],{"class":507}," IS",[422,1421,1422],{"class":507}," NULL",[422,1424,1425],{"class":507}," OR",[422,1427,1300],{"class":434},[422,1429,18],{"class":428},[422,1431,1416],{"class":434},[422,1433,1434],{"class":507}," >",[422,1436,1437],{"class":441}," '2026-05-15'",[422,1439,1316],{"class":428},[422,1441,1443,1445,1447,1449,1451],{"class":424,"line":1442},13,[422,1444,1280],{"class":507},[422,1446,1300],{"class":434},[422,1448,18],{"class":428},[422,1450,1305],{"class":434},[422,1452,1453],{"class":428},";\n",[11,1455,1456,1457,1460],{},"İkincisi ancak aşama değişikliklerinin geçmişini tutuyorsan mümkün. Bu yüzden karar mimariye ait: ",[15,1458,1459],{},"geçmişi tutmuyorsan, dönemsel analiz yapamıyorsun"," — sadece bugünün fotoğrafını geçmişe uygulayabiliyorsun ki bu da yanlış cevap veriyor.",[23,1462,1464],{"id":1463},"ortak-filtre-bir-sözleşmedir","Ortak filtre bir sözleşmedir",[11,1466,1467,1468,1471],{},"Çok kanallı lead yönlendirme ve analiz üzerine çalıştığım sistemde bu problem tekrar tekrar karşıma çıktı. Çözüm tek bir formülü düzeltmek değildi; ",[15,1469,1470],{},"filtreyi ekranlar arasında bir sözleşmeye dönüştürmek"," oldu.",[11,1473,1474],{},"Pratikte şu anlama geliyor: tarih aralığı, kırılım, sorumlu kişi, kaynak gibi seçimler her sekmede aynı anlamı taşıyor ve aynı kümeyi üretiyor. Bir ekran kendi başına ek bir daraltma yapmıyor. Aynı filtreyi kullanan her ekran, aynı kayıt kümesini değerlendiriyor.",[11,1476,1477,1478,1481],{},"Bunun bir sonucu var: filtre artık bir arayüz detayı değil, ",[15,1479,1480],{},"paylaşılan bir tanım",". Yeni bir ekran eklerken de aynı yapıya bağlanması gerekiyor; kendi sorgusunu yazması değil.",[11,1483,1484],{},"Aynı mantık metriğin kendisi için de geçerli. \"Başarılı sayılan aşamalar\" listesi tek bir yerde tanımlanıyor ve bütün sorgular oradan besleniyor. Yeni bir aşama eklendiğinde tek bir yer değişiyor — sekiz ayrı sorgu değil.",[23,1486,1488],{"id":1487},"detay-ekranı-filtreyi-devralmamalı","Detay ekranı filtreyi devralmamalı",[11,1490,1491],{},"Küçük ama önemli bir davranış: bir karttaki sayıya tıklayıp detay açtığında, o detayın kapsamı kartın kapsamıyla birebir aynı olmalı. Başka bir sekmede yapılmış daraltıcı bir seçimin sessizce taşınması, kullanıcının gördüğü sayıyla açtığı listenin uyuşmamasına yol açıyor.",[11,1493,1494],{},"Bu yüzden detay açılışında filtreyi bilinçli olarak varsayılana döndürmek, \"kullanıcının son seçimini hatırlamak\"tan daha doğru bir davranış oluyor. Kullanıcı tıkladığı sayının dökümünü görmek istiyor; başka bir yerde bıraktığı ayarın etkisini değil.",[23,1496,1498],{"id":1497},"paydayı-da-tanımla","Paydayı da tanımla",[11,1500,1501],{},"Oranlarda ikinci bir tuzak var: payı tanımlayıp paydayı unutmak.",[413,1503,1508],{"className":1504,"code":1506,"language":1507,"meta":365},[1505],"language-text","Dönüşüm (%) = (başarılı kayıt \u002F toplam kayıt) × 100\n","text",[419,1509,1506],{"__ignoreMap":365},[11,1511,1512],{},"Buradaki \"toplam\" ne? Gelen tüm kayıtlar mı, yoksa yalnızca ulaşılabilmiş olanlar mı? İkisi de anlamlı metrik üretiyor ama çok farklı sayılar veriyor ve ikisi de \"dönüşüm oranı\" diye adlandırılabiliyor.",[11,1514,1515,1516,1519],{},"Çözüm ikisini birden göstermek ve ",[15,1517,1518],{},"adlarını ayırmak",": \"gelen → başarılı\" ve \"ulaşılan → başarılı\". İki ayrı sütun, iki ayrı isim. Aynı isimle iki farklı payda kullanmak, en baştaki güven sorununu geri getiriyor.",[11,1521,1522],{},"Ayrıca payda sıfırken ne olacağı da tanımlanmalı. Sıfıra bölme hatası vermek yerine oranı sıfır kabul etmek, raporun bozulmadan çalışmasını sağlıyor.",[23,1524,1526],{"id":1525},"kontrol-listesi","Kontrol listesi",[11,1528,1529],{},"Yeni bir metrik ya da ekran eklerken sorduklarım:",[31,1531,1532,1535,1538,1541,1544],{},[34,1533,1534],{},"Bu metriğin kümesi, zaman alanı ve durum listesi nerede tanımlı?",[34,1536,1537],{},"Aynı tanımı kullanan başka ekranlar var mı, onlar da aynı yerden mi besleniyor?",[34,1539,1540],{},"Bu anlık bir sayı mı, dönemsel bir sayı mı? Ekranda bu ayrım görünüyor mu?",[34,1542,1543],{},"Oransa paydası ne ve adı bunu yansıtıyor mu?",[34,1545,1546],{},"Filtre değişince bu ekran diğerleriyle aynı yönde mi hareket ediyor?",[11,1548,1549],{},"Son madde en pratik testi veriyor: bir filtreyi değiştirdiğinde bütün ekranlar tutarlı biçimde hareket ediyorsa, kapsam ortak demektir. Biri sabit kalıyorsa, kendi sorgusunu yazıyor demektir.",[23,1551,350],{"id":349},[11,1553,1554,1555,1558],{},"Bir rakamın doğru olması yetmiyor; ",[15,1556,1557],{},"hangi kapsam üzerinden hesaplandığının anlaşılabilir olması"," gerekiyor. Sorgulanabilir olmayan bir sayıyı savunmak da mümkün olmuyor.",[11,1560,1561],{},"İki ekran aynı metrik için farklı sayı gösteriyorsa, önce hesaplamaya değil tanıma bak. Sorun büyük ihtimalle orada.",[86,1563,1564],{"title":359,"type":360},[11,1565,1566],{},"Buradaki SQL parçaları kavramı anlatmak için yazılmış örneklerdir; belirli bir projenin üretim kodu ya da gerçek bir veri modeli değildir. Sayfada geçen oran ve aşama adları da örnektir; herhangi bir kuruma ait gerçek performans verisi paylaşılmıyor.",[1079,1568,1569],{},"html pre.shiki code .sAwPA, html code.shiki .sAwPA{--shiki-default:#6A737D}html pre.shiki code .snl16, html code.shiki .snl16{--shiki-default:#F97583}html pre.shiki code .s95oV, html code.shiki .s95oV{--shiki-default:#E1E4E8}html pre.shiki code .sDLfK, html code.shiki .sDLfK{--shiki-default:#79B8FF}html pre.shiki code .sU2Wk, html code.shiki .sU2Wk{--shiki-default:#9ECBFF}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}",{"title":365,"searchDepth":366,"depth":367,"links":1571},[1572,1573,1574,1575,1576,1577,1578,1579],{"id":1121,"depth":366,"text":1122},{"id":1157,"depth":366,"text":1158},{"id":1185,"depth":366,"text":1186},{"id":1463,"depth":366,"text":1464},{"id":1487,"depth":366,"text":1488},{"id":1497,"depth":366,"text":1498},{"id":1525,"depth":366,"text":1526},{"id":349,"depth":366,"text":350},"Analitik","\u002Fimages\u002Fblog\u002Fmetrik-kapsam.svg","2026-09-02","Sorun genellikle hesaplamada değil, kapsamın tanımında. Küme, zaman ve durum üçlüsünü ekranlar arasında bir sözleşmeye bağlamak.",{},"\u002Fyazilar\u002Fayni-metrik-farkli-sayi",{"title":1106,"description":1583},"yazilar\u002Fayni-metrik-farkli-sayi",[1580,1589,378],"Dashboard","1Jwm09Jf5fJiw1mV_W5RIqgQnsf58dV_w5M3fnW4Bns",{"id":1592,"title":1593,"body":1594,"category":2329,"cover":2330,"date":2331,"demo":381,"description":2332,"draft":381,"extension":383,"featured":384,"meta":2333,"navigation":384,"path":2334,"readingTime":592,"seo":2335,"stem":2336,"tags":2337,"updated":2331,"__hash__":2340},"blog\u002Fyazilar\u002Fdis-servis-bildirim-kuyrugu.md","Dış Servise Bağlı Bildirimlerde Kesinti Bir Hata Değildir",{"type":8,"value":1595,"toc":2320},[1596,1602,1608,1612,1615,1694,1697,1711,1718,1722,1729,1732,1944,1947,1951,1958,2090,2097,2100,2109,2113,2116,2122,2173,2183,2189,2225,2228,2232,2235,2246,2249,2252,2256,2259,2275,2278,2285,2289,2292,2306,2312,2317],[11,1597,1598,1599],{},"Bir kaydı hem kendi sisteminde tutup hem de dışarıdaki bir servise bildirmen gerekiyorsa — bir kamu web servisi, bir CRM, bir muhasebe sistemi — er ya da geç şu soruyla karşılaşıyorsun: ",[15,1600,1601],{},"servis yanıt vermediğinde ne oluyor?",[11,1603,1604,1605,18],{},"Bu sorunun cevabı çoğu entegrasyonun kalitesini belirliyor. Çünkü dış servisin geçici olarak erişilemez olması bir istisna değil; yeterince uzun çalışan her sistemde ",[15,1606,1607],{},"beklenen bir durum",[23,1609,1611],{"id":1610},"gönder-ve-sonucu-göster-neden-yetmiyor","\"Gönder ve sonucu göster\" neden yetmiyor",[11,1613,1614],{},"En doğal kurgu şu: kullanıcı kaydı oluşturur, aynı istek içinde dış servise gönderilir, sonuç ekranda gösterilir.",[413,1616,1620],{"className":1617,"code":1618,"language":1619,"meta":365,"style":365},"language-php shiki shiki-themes github-dark","$record = Record::create($data);\n$response = $client->notify($record);   \u002F\u002F dış servis burada\nreturn $response->successful() ? 'Bildirildi' : 'Hata';\n","php",[419,1621,1622,1642,1664],{"__ignoreMap":365},[422,1623,1624,1627,1630,1633,1636,1639],{"class":424,"line":425},[422,1625,1626],{"class":428},"$record ",[422,1628,1629],{"class":507},"=",[422,1631,1632],{"class":434}," Record",[422,1634,1635],{"class":507},"::",[422,1637,1638],{"class":514},"create",[422,1640,1641],{"class":428},"($data);\n",[422,1643,1644,1647,1649,1652,1655,1658,1661],{"class":424,"line":366},[422,1645,1646],{"class":428},"$response ",[422,1648,1629],{"class":507},[422,1650,1651],{"class":428}," $client",[422,1653,1654],{"class":507},"->",[422,1656,1657],{"class":514},"notify",[422,1659,1660],{"class":428},"($record);   ",[422,1662,1663],{"class":1223},"\u002F\u002F dış servis burada\n",[422,1665,1666,1669,1672,1674,1677,1680,1683,1686,1689,1692],{"class":424,"line":367},[422,1667,1668],{"class":507},"return",[422,1670,1671],{"class":428}," $response",[422,1673,1654],{"class":507},[422,1675,1676],{"class":514},"successful",[422,1678,1679],{"class":428},"() ",[422,1681,1682],{"class":507},"?",[422,1684,1685],{"class":441}," 'Bildirildi'",[422,1687,1688],{"class":507}," :",[422,1690,1691],{"class":441}," 'Hata'",[422,1693,1453],{"class":428},[11,1695,1696],{},"Bu akış mutlu günde çalışıyor. Sorun mutsuz günde:",[31,1698,1699,1702,1705,1708],{},[34,1700,1701],{},"Servis yanıt vermiyor ve kullanıcı bir hata ekranı görüyor. Kayıt oluştu mu, olmadı mı belli değil.",[34,1703,1704],{},"Kullanıcı tekrar deniyor. Şimdi iki kayıt var.",[34,1706,1707],{},"Ya da kullanıcı vazgeçiyor. Kayıt sistemde duruyor ama hiç bildirilmemiş — ve bunu kimse bilmiyor.",[34,1709,1710],{},"İstek zaman aşımına uğruyor, ama dış servis aslında işlemi almış oluyor. En kötüsü bu.",[11,1712,1713,1714,1717],{},"Ortak kusur şu: ",[15,1715,1716],{},"kaydın kendisiyle bildiriminin durumu tek bir şeymiş gibi ele alınıyor."," Halbuki bunlar iki ayrı olay ve ikisi aynı anda başarılı olmak zorunda değil.",[23,1719,1721],{"id":1720},"bildirim-durumu-ayrı-bir-varlıktır","Bildirim durumu ayrı bir varlıktır",[11,1723,1724,1725,1728],{},"Kayıtta ",[419,1726,1727],{},"bildirildi"," diye bir boolean alan tutmak yeterli görünüyor ama değil. Bir bildirimin kendi geçmişi var: ne zaman denendi, kaç kez denendi, en son ne cevap geldi, hâlâ denenmeli mi?",[11,1730,1731],{},"Bunu ayrı bir tabloya taşımak işi tümüyle değiştiriyor:",[413,1733,1735],{"className":1214,"code":1734,"language":1216,"meta":365,"style":365},"CREATE TABLE outbox (\n  id            BIGINT PRIMARY KEY AUTO_INCREMENT,\n  record_id     BIGINT NOT NULL,\n  event_type    VARCHAR(64) NOT NULL,\n  payload       JSON NOT NULL,\n  idempotency_key VARCHAR(80) NOT NULL,\n  status        ENUM('pending','sent','failed') NOT NULL DEFAULT 'pending',\n  attempts      INT NOT NULL DEFAULT 0,\n  next_retry_at DATETIME NULL,\n  last_error    TEXT NULL,\n  created_at    DATETIME NOT NULL,\n  UNIQUE KEY uniq_idem (idempotency_key),\n  KEY idx_due (status, next_retry_at)\n);\n",[419,1736,1737,1751,1765,1777,1797,1809,1827,1861,1878,1890,1902,1913,1924,1938],{"__ignoreMap":365},[422,1738,1739,1742,1745,1748],{"class":424,"line":425},[422,1740,1741],{"class":507},"CREATE",[422,1743,1744],{"class":507}," TABLE",[422,1746,1747],{"class":514}," outbox",[422,1749,1750],{"class":428}," (\n",[422,1752,1753,1756,1759,1762],{"class":424,"line":366},[422,1754,1755],{"class":428},"  id            ",[422,1757,1758],{"class":507},"BIGINT",[422,1760,1761],{"class":507}," PRIMARY KEY",[422,1763,1764],{"class":428}," AUTO_INCREMENT,\n",[422,1766,1767,1770,1772,1775],{"class":424,"line":367},[422,1768,1769],{"class":428},"  record_id     ",[422,1771,1758],{"class":507},[422,1773,1774],{"class":507}," NOT NULL",[422,1776,445],{"class":428},[422,1778,1779,1782,1785,1787,1790,1792,1795],{"class":424,"line":467},[422,1780,1781],{"class":428},"  event_type    ",[422,1783,1784],{"class":507},"VARCHAR",[422,1786,1238],{"class":428},[422,1788,1789],{"class":434},"64",[422,1791,1244],{"class":428},[422,1793,1794],{"class":507},"NOT NULL",[422,1796,445],{"class":428},[422,1798,1799,1802,1805,1807],{"class":424,"line":553},[422,1800,1801],{"class":428},"  payload       ",[422,1803,1804],{"class":507},"JSON",[422,1806,1774],{"class":507},[422,1808,445],{"class":428},[422,1810,1811,1814,1816,1818,1821,1823,1825],{"class":424,"line":559},[422,1812,1813],{"class":428},"  idempotency_key ",[422,1815,1784],{"class":507},[422,1817,1238],{"class":428},[422,1819,1820],{"class":434},"80",[422,1822,1244],{"class":428},[422,1824,1794],{"class":507},[422,1826,445],{"class":428},[422,1828,1829,1832,1835,1838,1841,1844,1846,1849,1851,1853,1856,1859],{"class":424,"line":565},[422,1830,1831],{"class":507},"  status",[422,1833,1834],{"class":428},"        ENUM(",[422,1836,1837],{"class":441},"'pending'",[422,1839,1840],{"class":428},",",[422,1842,1843],{"class":441},"'sent'",[422,1845,1840],{"class":428},[422,1847,1848],{"class":441},"'failed'",[422,1850,1244],{"class":428},[422,1852,1794],{"class":507},[422,1854,1855],{"class":507}," DEFAULT",[422,1857,1858],{"class":441}," 'pending'",[422,1860,445],{"class":428},[422,1862,1863,1866,1869,1871,1873,1876],{"class":424,"line":576},[422,1864,1865],{"class":428},"  attempts      ",[422,1867,1868],{"class":507},"INT",[422,1870,1774],{"class":507},[422,1872,1855],{"class":507},[422,1874,1875],{"class":434}," 0",[422,1877,445],{"class":428},[422,1879,1880,1883,1886,1888],{"class":424,"line":592},[422,1881,1882],{"class":428},"  next_retry_at ",[422,1884,1885],{"class":507},"DATETIME",[422,1887,1422],{"class":507},[422,1889,445],{"class":428},[422,1891,1892,1895,1898,1900],{"class":424,"line":387},[422,1893,1894],{"class":428},"  last_error    ",[422,1896,1897],{"class":507},"TEXT",[422,1899,1422],{"class":507},[422,1901,445],{"class":428},[422,1903,1904,1907,1909,1911],{"class":424,"line":1384},[422,1905,1906],{"class":428},"  created_at    ",[422,1908,1885],{"class":507},[422,1910,1774],{"class":507},[422,1912,445],{"class":428},[422,1914,1915,1918,1921],{"class":424,"line":1403},[422,1916,1917],{"class":507},"  UNIQUE",[422,1919,1920],{"class":507}," KEY",[422,1922,1923],{"class":428}," uniq_idem (idempotency_key),\n",[422,1925,1926,1929,1932,1935],{"class":424,"line":1442},[422,1927,1928],{"class":507},"  KEY",[422,1930,1931],{"class":428}," idx_due (",[422,1933,1934],{"class":507},"status",[422,1936,1937],{"class":428},", next_retry_at)\n",[422,1939,1941],{"class":424,"line":1940},14,[422,1942,1943],{"class":428},");\n",[11,1945,1946],{},"Artık \"bu kayıt bildirildi mi\" sorusunun cevabı bir tahmin değil. Ne zaman denendiği, neyle sonuçlandığı ve sırada olup olmadığı görülebiliyor.",[23,1948,1950],{"id":1949},"kaydı-ve-bildirim-niyetini-aynı-anda-yaz","Kaydı ve bildirim niyetini aynı anda yaz",[11,1952,1953,1954,1957],{},"Kritik nokta: kaydı oluşturan işlemle bildirim satırını oluşturan işlem ",[15,1955,1956],{},"aynı transaction içinde"," olmalı.",[413,1959,1961],{"className":1617,"code":1960,"language":1619,"meta":365,"style":365},"DB::transaction(function () use ($data) {\n    $record = Record::create($data);\n    Outbox::create([\n        'record_id' => $record->id,\n        'event_type' => 'record.created',\n        'payload' => $record->toNotificationPayload(),\n        'idempotency_key' => \"record.created:{$record->id}\",\n    ]);\n});\n",[419,1962,1963,1986,2001,2013,2029,2041,2058,2081,2086],{"__ignoreMap":365},[422,1964,1965,1967,1969,1972,1974,1977,1980,1983],{"class":424,"line":425},[422,1966,729],{"class":434},[422,1968,1635],{"class":507},[422,1970,1971],{"class":514},"transaction",[422,1973,1238],{"class":428},[422,1975,1976],{"class":507},"function",[422,1978,1979],{"class":428}," () ",[422,1981,1982],{"class":507},"use",[422,1984,1985],{"class":428}," ($data) {\n",[422,1987,1988,1991,1993,1995,1997,1999],{"class":424,"line":366},[422,1989,1990],{"class":428},"    $record ",[422,1992,1629],{"class":507},[422,1994,1632],{"class":434},[422,1996,1635],{"class":507},[422,1998,1638],{"class":514},[422,2000,1641],{"class":428},[422,2002,2003,2006,2008,2010],{"class":424,"line":367},[422,2004,2005],{"class":434},"    Outbox",[422,2007,1635],{"class":507},[422,2009,1638],{"class":514},[422,2011,2012],{"class":428},"([\n",[422,2014,2015,2018,2021,2024,2026],{"class":424,"line":467},[422,2016,2017],{"class":441},"        'record_id'",[422,2019,2020],{"class":507}," =>",[422,2022,2023],{"class":428}," $record",[422,2025,1654],{"class":507},[422,2027,2028],{"class":428},"id,\n",[422,2030,2031,2034,2036,2039],{"class":424,"line":553},[422,2032,2033],{"class":441},"        'event_type'",[422,2035,2020],{"class":507},[422,2037,2038],{"class":441}," 'record.created'",[422,2040,445],{"class":428},[422,2042,2043,2046,2048,2050,2052,2055],{"class":424,"line":559},[422,2044,2045],{"class":441},"        'payload'",[422,2047,2020],{"class":507},[422,2049,2023],{"class":428},[422,2051,1654],{"class":507},[422,2053,2054],{"class":514},"toNotificationPayload",[422,2056,2057],{"class":428},"(),\n",[422,2059,2060,2063,2065,2068,2071,2073,2076,2079],{"class":424,"line":565},[422,2061,2062],{"class":441},"        'idempotency_key'",[422,2064,2020],{"class":507},[422,2066,2067],{"class":441}," \"record.created:{",[422,2069,2070],{"class":428},"$record",[422,2072,1654],{"class":507},[422,2074,2075],{"class":428},"id",[422,2077,2078],{"class":441},"}\"",[422,2080,445],{"class":428},[422,2082,2083],{"class":424,"line":576},[422,2084,2085],{"class":428},"    ]);\n",[422,2087,2088],{"class":424,"line":592},[422,2089,599],{"class":428},[11,2091,2092,2093,2096],{},"Bunun adı outbox kalıbı ve tek bir şeyi garanti ediyor: ",[15,2094,2095],{},"kayıt varsa bildirim niyeti de vardır."," Kaydın oluşup bildiriminin hiç kuyruğa girmemesi mümkün olmuyor.",[11,2098,2099],{},"Gönderim ise ayrı bir işçi tarafından, isteğin dışında yapılıyor. Kullanıcı dış servisin hızını beklemiyor; kullanıcıya \"işlem alındı\" demek için dış servisin cevabına ihtiyaç kalmıyor.",[86,2101,2103,2106],{"title":2102,"type":89},"Kuyruğa atmak yetmiyor, önce yazmak gerekiyor",[11,2104,2105],{},"Kaydı oluşturup bildirimi doğrudan kuyruk sistemine göndermek de yaygın bir çözüm. Ama transaction commit olmadan kuyruğa mesaj bırakırsan, işçi henüz var olmayan bir kaydı okumaya çalışabiliyor. Commit sonrasında bırakırsan da, tam o aralıkta çöken bir süreçte mesaj hiç bırakılmamış oluyor.",[11,2107,2108],{},"Outbox bu aralığı kapatıyor: mesaj da veri de aynı veritabanı işleminde. Kuyruk, outbox'ı okuyan bir tetikleyiciye dönüşüyor.",[23,2110,2112],{"id":2111},"yeniden-denemenin-sınırları-olmalı","Yeniden denemenin sınırları olmalı",[11,2114,2115],{},"Sonsuz yeniden deneme, çalışmayan bir entegrasyonu sonsuza kadar gizler. Üç şeyi belirlemek gerekiyor:",[11,2117,2118,2121],{},[15,2119,2120],{},"Aralık."," Sabit aralıkla denemek, servis toparlandığı anda ona toplu bir yük bindiriyor. Üstel artan bir bekleme ve üzerine rastgele bir sapma (jitter) daha iyi çalışıyor — bütün işçiler aynı saniyede uyanmıyor.",[413,2123,2125],{"className":1617,"code":2124,"language":1619,"meta":365,"style":365},"\u002F\u002F 1., 2., 3. deneme: ~1dk, ~4dk, ~9dk (üzerine rastgele sapma)\n$delay = ($attempt ** 2) * 60 + random_int(0, 30);\n",[419,2126,2127,2132],{"__ignoreMap":365},[422,2128,2129],{"class":424,"line":425},[422,2130,2131],{"class":1223},"\u002F\u002F 1., 2., 3. deneme: ~1dk, ~4dk, ~9dk (üzerine rastgele sapma)\n",[422,2133,2134,2137,2139,2142,2145,2148,2150,2152,2155,2158,2161,2163,2166,2168,2171],{"class":424,"line":366},[422,2135,2136],{"class":428},"$delay ",[422,2138,1629],{"class":507},[422,2140,2141],{"class":428}," ($attempt ",[422,2143,2144],{"class":507},"**",[422,2146,2147],{"class":434}," 2",[422,2149,1244],{"class":428},[422,2151,1241],{"class":507},[422,2153,2154],{"class":434}," 60",[422,2156,2157],{"class":507}," +",[422,2159,2160],{"class":514}," random_int",[422,2162,1238],{"class":428},[422,2164,2165],{"class":434},"0",[422,2167,812],{"class":428},[422,2169,2170],{"class":434},"30",[422,2172,1943],{"class":428},[11,2174,2175,2178,2179,2182],{},[15,2176,2177],{},"Üst sınır."," Belirli bir denemeden sonra kayıt ",[419,2180,2181],{},"failed"," olmalı. Bu bir kayıp değil, bir görünürlük kazancı: artık birinin bakması gereken bir liste var.",[11,2184,2185,2188],{},[15,2186,2187],{},"Kalıcı hata ayrımı."," Her hata yeniden denemeyi hak etmiyor:",[31,2190,2191,2203],{},[34,2192,2193,2196,2197,812,2200,18],{},[15,2194,2195],{},"Yeniden dene:"," zaman aşımı, bağlantı hatası, ",[419,2198,2199],{},"5xx",[419,2201,2202],{},"429",[34,2204,2205,2208,2209,2212,2213,2216,2217,2220,2221,2224],{},[15,2206,2207],{},"Deneme, hemen işaretle:"," ",[419,2210,2211],{},"400"," (geçersiz veri), ",[419,2214,2215],{},"401","\u002F",[419,2218,2219],{},"403"," (yetki), ",[419,2222,2223],{},"422"," (doğrulama).",[11,2226,2227],{},"Geçersiz veriyi kırk kez göndermek onu geçerli yapmıyor. Bu ayrımı yapmayan bir kuyruk, çözülemeyecek işlerle doluyor ve gerçekten geçici olan hatalar arada kayboluyor.",[23,2229,2231],{"id":2230},"mükerrer-gönderimi-engellemek","Mükerrer gönderimi engellemek",[11,2233,2234],{},"Zaman aşımı, en zor durumu üretiyor: isteği gönderdin, cevap gelmedi, ama karşı taraf işlemi almış olabilir. Yeniden denersen aynı işlem iki kez oluşabilir.",[11,2236,2237,2238,2241,2242,2245],{},"Çözüm iki taraflı. Kendi tarafında her bildirime kararlı bir anahtar veriyorsun — ",[419,2239,2240],{},"record.created:1042"," gibi, rastgele değil, aynı olay için hep aynı üretilen bir değer. Karşı taraf bu anahtarı destekliyorsa (çoğu modern API ",[419,2243,2244],{},"Idempotency-Key"," başlığıyla destekliyor) tekrar gönderim güvenli hâle geliyor.",[11,2247,2248],{},"Desteklemiyorsa, göndermeden önce sorgulamak gerekiyor: \"bu referansla bir kayıt var mı?\" Bu ekstra bir istek maliyeti getiriyor ama mükerrer kayıt üretmekten ucuz.",[11,2250,2251],{},"Bunun neden önemli olduğunu ödeme tarafında çalışırken daha net görüyorsun: aynı ödemenin iki kez işlenmesi sadece bir fazla satır değil, dönüşüm oranını da bozan bir hata. Rapor yanlış çıkıyor ve yanlış olduğunu fark etmek günler alıyor.",[23,2253,2255],{"id":2254},"takılan-kayıt-görünür-olmalı","Takılan kayıt görünür olmalı",[11,2257,2258],{},"Bir kuyruk, kimse bakmadığında sessizce dolan bir yere dönüşürse hiçbir şey çözmemiş olur. En az iki şey gerekiyor:",[31,2260,2261,2269],{},[34,2262,2263,2208,2266,2268],{},[15,2264,2265],{},"Operasyon ekranı.",[419,2267,2181],{}," durumundaki kayıtlar, hatasıyla birlikte listeleniyor ve elle yeniden denenebiliyor.",[34,2270,2271,2274],{},[15,2272,2273],{},"Eşik uyarısı."," Bekleyen kayıt sayısı ya da en eski bekleyen kaydın yaşı bir eşiği aştığında haber veren bir kontrol.",[11,2276,2277],{},"İkincisi olmadan, dış servisin üç gündür veri almadığını ancak karşı taraf arayınca öğreniyorsun.",[11,2279,2280,2281,2284],{},"İş sağlığı ve güvenliği süreçlerini kamu web servisleriyle entegre eden bir sistemde çalışırken şunu öğrendim: resmi bildirim yapan bir sistemde ",[15,2282,2283],{},"\"bildirildi\" bilgisi, kaydın bir alanı değil, kendi geçmişi olan ayrı bir durumdur."," Ne zaman denendiği ve neyle sonuçlandığı görülemiyorsa, bildirimin yapıldığını söylemek de mümkün olmuyor.",[23,2286,2288],{"id":2287},"özet","Özet",[11,2290,2291],{},"Dayanıklı bir entegrasyon şu dört şeyden oluşuyor:",[291,2293,2294,2297,2300,2303],{},[34,2295,2296],{},"Kayıt ve bildirim niyeti aynı transaction'da yazılıyor.",[34,2298,2299],{},"Gönderim isteğin dışında, ayrı bir işçide yapılıyor.",[34,2301,2302],{},"Yeniden deneme sınırlı, artan aralıklı ve kalıcı hatayı ayırt ediyor.",[34,2304,2305],{},"Başarısız kayıtlar sessizce birikmiyor, görünür oluyor.",[11,2307,2308,2309,2311],{},"Hiçbiri karmaşık değil. Zor olan, kesintiyi baştan ",[15,2310,1607],{}," olarak kabul etmek — sonradan eklenen bir yeniden deneme mantığı, en başından bu varsayımla kurulmuş bir akış kadar iyi çalışmıyor.",[86,2313,2314],{"title":359,"type":360},[11,2315,2316],{},"Buradaki şema ve kod parçaları kavramı anlatmak için yazılmış örneklerdir; belirli bir projenin üretim kodu değildir. Yeniden deneme aralıkları ve hata sınıflandırması, entegre olduğun servisin sözleşmesine göre değişir — kendi sağlayıcının dokümantasyonuyla doğrula.",[1079,2318,2319],{},"html pre.shiki code .snl16, html code.shiki .snl16{--shiki-default:#F97583}html pre.shiki code .svObZ, html code.shiki .svObZ{--shiki-default:#B392F0}html pre.shiki code .s95oV, html code.shiki .s95oV{--shiki-default:#E1E4E8}html pre.shiki code .sDLfK, html code.shiki .sDLfK{--shiki-default:#79B8FF}html pre.shiki code .sU2Wk, html code.shiki .sU2Wk{--shiki-default:#9ECBFF}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html pre.shiki code .sAwPA, html code.shiki .sAwPA{--shiki-default:#6A737D}",{"title":365,"searchDepth":366,"depth":367,"links":2321},[2322,2323,2324,2325,2326,2327,2328],{"id":1610,"depth":366,"text":1611},{"id":1720,"depth":366,"text":1721},{"id":1949,"depth":366,"text":1950},{"id":2111,"depth":366,"text":2112},{"id":2230,"depth":366,"text":2231},{"id":2254,"depth":366,"text":2255},{"id":2287,"depth":366,"text":2288},"Entegrasyon","\u002Fimages\u002Fblog\u002Fbildirim-kuyrugu.svg","2026-08-26","Kayıt bizde, bildirim dışarıda. Servis yanıt vermediğinde kaydın kaybolmaması için bildirim durumunu ayrı bir varlık olarak ele almak.",{},"\u002Fyazilar\u002Fdis-servis-bildirim-kuyrugu",{"title":1593,"description":2332},"yazilar\u002Fdis-servis-bildirim-kuyrugu",[2329,2338,2339],"API","Kuyruk","segofW3JQnh32q6phe1vl5JiQ_EHILsbskfUfftC548",{"id":2342,"title":2343,"body":2344,"category":378,"cover":2835,"date":2836,"demo":381,"description":2837,"draft":381,"extension":383,"featured":384,"meta":2838,"navigation":384,"path":2839,"readingTime":592,"seo":2840,"stem":2841,"tags":2842,"updated":2836,"__hash__":2845},"blog\u002Fyazilar\u002Fcoklu-veritabani-veri-izolasyonu.md","Çok Kiracılı Sistemlerde Veri İzolasyonu",{"type":8,"value":2345,"toc":2825},[2346,2352,2355,2359,2362,2372,2378,2384,2476,2482,2488,2491,2533,2540,2547,2551,2554,2574,2577,2581,2584,2591,2598,2607,2611,2614,2620,2681,2688,2694,2700,2706,2709,2713,2716,2772,2775,2779,2782,2814,2817,2822],[11,2347,2348,2349],{},"Aynı ürünü birden fazla kuruma verdiğin anda cevaplaman gereken bir soru çıkıyor: ",[15,2350,2351],{},"bu kurumların verisi nerede duracak?",[11,2353,2354],{},"Soru ilk bakışta bir veritabanı tercihi gibi görünüyor. Değil. Verdiğin cevap; performansı, yetkilendirmeyi, yedeklemeyi, silme taleplerini ve en çok da yeni sürüm çıkarma biçimini belirliyor. Yanlış tarafı seçmenin bedeli ilk gün değil, altıncı ayda ödeniyor.",[23,2356,2358],{"id":2357},"üç-yaklaşım","Üç yaklaşım",[11,2360,2361],{},"Pratikte üç seçenek var:",[11,2363,2364,2367,2368,2371],{},[15,2365,2366],{},"Ortak tablo."," Bütün kurumlar aynı tabloları paylaşıyor, her satırda bir ",[419,2369,2370],{},"tenant_id"," duruyor. Kurulumu en basit olan, işletmesi en ucuz olan yaklaşım.",[11,2373,2374,2377],{},[15,2375,2376],{},"Ayrı şema."," Aynı veritabanı sunucusu, kurum başına ayrı şema. Tablolar çoğalıyor ama veri sınırı netleşiyor.",[11,2379,2380,2383],{},[15,2381,2382],{},"Ayrı veritabanı."," Her kurumun kendi veritabanı var. Uygulama, isteğin hangi kuruma ait olduğunu belirleyip bağlantıyı ona göre seçiyor.",[108,2385,2386,2401],{},[111,2387,2388],{},[114,2389,2390,2392,2395,2398],{},[117,2391],{},[117,2393,2394],{},"Ortak tablo",[117,2396,2397],{},"Ayrı şema",[117,2399,2400],{},"Ayrı veritabanı",[124,2402,2403,2417,2428,2439,2452,2465],{},[114,2404,2405,2408,2411,2414],{},[129,2406,2407],{},"Kurulum maliyeti",[129,2409,2410],{},"Düşük",[129,2412,2413],{},"Orta",[129,2415,2416],{},"Yüksek",[114,2418,2419,2422,2424,2426],{},[129,2420,2421],{},"Sızma riski",[129,2423,2416],{},[129,2425,2413],{},[129,2427,2410],{},[114,2429,2430,2433,2435,2437],{},[129,2431,2432],{},"Gürültülü komşu etkisi",[129,2434,2416],{},[129,2436,2413],{},[129,2438,2410],{},[114,2440,2441,2444,2447,2449],{},[129,2442,2443],{},"Kiracı bazlı yedek\u002Fgeri yükleme",[129,2445,2446],{},"Zor",[129,2448,2413],{},[129,2450,2451],{},"Kolay",[114,2453,2454,2457,2460,2463],{},[129,2455,2456],{},"Şema değişikliği",[129,2458,2459],{},"Tek işlem",[129,2461,2462],{},"N işlem",[129,2464,2462],{},[114,2466,2467,2470,2472,2474],{},[129,2468,2469],{},"Kiracılar arası rapor",[129,2471,2451],{},[129,2473,2413],{},[129,2475,2446],{},[11,2477,2478,2479],{},"Tablodaki son iki satır kararın gerçek eksenini gösteriyor: ",[15,2480,2481],{},"izolasyon kazandıkça operasyon zorlaşıyor.",[23,2483,2485,2486],{"id":2484},"ortak-tablonun-asıl-riski-bir-where","Ortak tablonun asıl riski bir ",[419,2487,1255],{},[11,2489,2490],{},"Ortak tablo yaklaşımında veri sınırı fiziksel değil, mantıksal. Yani her sorgunun doğru filtreyi taşımasına bağlı:",[413,2492,2494],{"className":1214,"code":2493,"language":1216,"meta":365,"style":365},"SELECT * FROM appointments\nWHERE tenant_id = :tenant AND status = 'pending';\n",[419,2495,2496,2509],{"__ignoreMap":365},[422,2497,2498,2500,2503,2506],{"class":424,"line":425},[422,2499,1229],{"class":507},[422,2501,2502],{"class":507}," *",[422,2504,2505],{"class":507}," FROM",[422,2507,2508],{"class":428}," appointments\n",[422,2510,2511,2513,2516,2518,2521,2524,2527,2529,2531],{"class":424,"line":366},[422,2512,1255],{"class":507},[422,2514,2515],{"class":428}," tenant_id ",[422,2517,1629],{"class":507},[422,2519,2520],{"class":428}," :tenant ",[422,2522,2523],{"class":507},"AND",[422,2525,2526],{"class":507}," status",[422,2528,1344],{"class":507},[422,2530,1858],{"class":441},[422,2532,1453],{"class":428},[11,2534,2535,2536,2539],{},"Bu satır yüzlerce yerde tekrar ediyor ve ",[15,2537,2538],{},"bir kez unutulması yeterli",". Unutulduğunda hata da vermiyor: sorgu çalışıyor, sonuç dönüyor, sadece başka bir kurumun verisi de içinde geliyor.",[11,2541,2542,2543,2546],{},"Bu riski azaltmanın yolları var — global scope'lar, repository katmanı, satır düzeyi güvenlik. Hepsi işe yarıyor. Ama hepsinin ortak özelliği şu: ",[15,2544,2545],{},"doğru davranış, geliştiricinin bir şeyi hatırlamasına bağlı kalıyor."," Sağlık ya da finans verisi taşıyan bir üründe bu, tek başına ayrı veritabanını düşünmek için yeterli bir sebep.",[23,2548,2550],{"id":2549},"neden-ayrı-veritabanını-seçtim","Neden ayrı veritabanını seçtim",[11,2552,2553],{},"Muayenehaneler ve küçük tıp merkezleri için geliştirdiğim bilgi yönetim sisteminde kurum bazlı ayrı veritabanı kullandım. Üç gerekçesi vardı:",[31,2555,2556,2562,2568],{},[34,2557,2558,2561],{},[15,2559,2560],{},"Performans izolasyonu."," Büyük bir kurumun veri hacmi, küçük bir kurumun sorgularını yavaşlatmıyor. Ortak tabloda bir kiracının büyümesi herkesin sorun yaşaması demek.",[34,2563,2564,2567],{},[15,2565,2566],{},"Veri sınırı."," Kurum verisi bir filtreye değil, ayrı bir veritabanı sınırına dayanıyor. Yanlış yazılmış tek bir sorgu diğer kurumu göremiyor.",[34,2569,2570,2573],{},[15,2571,2572],{},"Yetki ayrımı."," Erişim veritabanı düzeyinde de kısıtlanabiliyor; uygulama katmanının doğru davranmasına ek bir kat daha ekleniyor.",[11,2575,2576],{},"Buna bir de şu eklendi: aynı altyapı farklı ülkelerdeki kuruluşlara hizmet verecekti. Verinin nerede durduğunu kurum kurum söyleyebilmek, sonradan eklenmesi çok zor bir özellik.",[23,2578,2580],{"id":2579},"bedeli-şema-değişikliği-artık-bir-dağıtım-problemi","Bedeli: şema değişikliği artık bir dağıtım problemi",[11,2582,2583],{},"Ayrı veritabanının maliyeti sorgularda değil. Sorgular neredeyse aynı kalıyor; sadece hangi bağlantıya gittikleri değişiyor.",[11,2585,2586,2587,2590],{},"Asıl mesele şu: ",[15,2588,2589],{},"tek bir migrasyon, N kez çalışan bir operasyona dönüşüyor."," Ve bu operasyon kısmen başarısız olabiliyor. Otuz kurumdan yirmi sekizinde çalışıp ikisinde hata veren bir migrasyon, sistemi tutarsız bir duruma bırakıyor.",[11,2592,2593,2594,2597],{},"Buradan çıkan sorun, bu mimarinin en sinsi tarafı: ",[15,2595,2596],{},"şema kayması."," Tek bir kurumda elle yapılmış bir düzeltme, orada kalıyor. Bir sonraki migrasyon o kurumda farklı bir başlangıç noktası buluyor ve zamanla birbirinden ayrışan kopyalar oluşuyor. Altı ay sonra \"bu kurumda neden çalışmıyor\" sorusunun cevabı, kodda değil o veritabanının geçmişinde saklı oluyor.",[86,2599,2601,2604],{"title":2600,"type":263},"Elle yapılan tek düzeltme, kalıcı bir ayrışmadır",[11,2602,2603],{},"Bir kurumda acil bir sorunu doğrudan veritabanında çözmek çok cazip. Sorun o an gerçekten çözülüyor. Ama o kurum artık diğerleriyle aynı şemaya sahip değil ve bunu kimse bilmiyor.",[11,2605,2606],{},"Kural basit: düzeltme migrasyon olarak yazılır, sonra hepsine uygulanır. Acil durumda elle müdahale ettiysen, aynı gün içinde onu migrasyona çevir.",[23,2608,2610],{"id":2609},"migrasyonu-izlenebilir-hâle-getirmek","Migrasyonu izlenebilir hâle getirmek",[11,2612,2613],{},"Ayrı veritabanı mimarisinde migrasyon çalıştırmak bir komut değil, bir süreç. Ele alınması gereken dört şey var:",[11,2615,2616,2619],{},[15,2617,2618],{},"Her kiracının kendi sürümünü bilmek."," Hangi migrasyonun nerede uygulandığı, tahmin edilecek değil sorgulanacak bir bilgi olmalı:",[413,2621,2623],{"className":1214,"code":2622,"language":1216,"meta":365,"style":365},"SELECT tenant_id, MAX(migration) AS son_surum, MAX(applied_at) AS son_tarih\nFROM tenant_migrations\nGROUP BY tenant_id\nORDER BY son_surum ASC;\n",[419,2624,2625,2654,2661,2668],{"__ignoreMap":365},[422,2626,2627,2629,2632,2635,2638,2641,2644,2646,2649,2651],{"class":424,"line":425},[422,2628,1229],{"class":507},[422,2630,2631],{"class":428}," tenant_id, ",[422,2633,2634],{"class":434},"MAX",[422,2636,2637],{"class":428},"(migration) ",[422,2639,2640],{"class":507},"AS",[422,2642,2643],{"class":428}," son_surum, ",[422,2645,2634],{"class":434},[422,2647,2648],{"class":428},"(applied_at) ",[422,2650,2640],{"class":507},[422,2652,2653],{"class":428}," son_tarih\n",[422,2655,2656,2658],{"class":424,"line":366},[422,2657,1247],{"class":507},[422,2659,2660],{"class":428}," tenant_migrations\n",[422,2662,2663,2665],{"class":424,"line":367},[422,2664,1280],{"class":507},[422,2666,2667],{"class":428}," tenant_id\n",[422,2669,2670,2673,2676,2679],{"class":424,"line":467},[422,2671,2672],{"class":507},"ORDER BY",[422,2674,2675],{"class":428}," son_surum ",[422,2677,2678],{"class":507},"ASC",[422,2680,1453],{"class":428},[11,2682,2683,2684,2687],{},"Bu sorgunun sonucunda birden fazla farklı ",[419,2685,2686],{},"son_surum"," görüyorsan, kayma başlamış demektir.",[11,2689,2690,2693],{},[15,2691,2692],{},"Sıralı ve tekrarlanabilir uygulama."," Migrasyonlar her kiracıda aynı sırayla çalışmalı ve ikinci kez çalıştırıldığında bozmamalı.",[11,2695,2696,2699],{},[15,2697,2698],{},"Başarısızlıkta durmak, devam etmemek."," Bir kiracıda hata alındığında kalanlara devam etmek, tutarsızlığı büyütüyor. Durup raporlamak daha iyi.",[11,2701,2702,2705],{},[15,2703,2704],{},"Sonuç raporu."," Kaç kiracıda çalıştı, hangilerinde çalışmadı, hata neydi. Bu rapor olmadan \"migrasyon tamam\" cümlesi bir varsayım.",[11,2707,2708],{},"Yeni kiracı açmak da aynı sürecin parçası. Yeni veritabanı, en güncel şemayla ve tüm migrasyon kayıtları işaretlenmiş hâlde oluşturulmalı — yoksa ilk günden farklı bir noktada başlıyor.",[23,2710,2712],{"id":2711},"bağlantı-seçimi-tek-bir-yerde-kalmalı","Bağlantı seçimi tek bir yerde kalmalı",[11,2714,2715],{},"Uygulama tarafında dikkat edilecek şey, bağlantı seçiminin dağılmaması. Hangi kurumun veritabanına bağlanılacağı isteğin başında bir kez belirlenmeli; sonrasındaki kod bunu bilmemeli.",[413,2717,2719],{"className":1617,"code":2718,"language":1619,"meta":365,"style":365},"\u002F\u002F İsteğin başında bir kez: bundan sonrası hangi kiracıda olduğunu bilmez.\n$tenant = $this->tenants->resolve($request);\n$this->connection->useDatabase($tenant->database);\n",[419,2720,2721,2726,2749],{"__ignoreMap":365},[422,2722,2723],{"class":424,"line":425},[422,2724,2725],{"class":1223},"\u002F\u002F İsteğin başında bir kez: bundan sonrası hangi kiracıda olduğunu bilmez.\n",[422,2727,2728,2731,2733,2736,2738,2741,2743,2746],{"class":424,"line":366},[422,2729,2730],{"class":428},"$tenant ",[422,2732,1629],{"class":507},[422,2734,2735],{"class":434}," $this",[422,2737,1654],{"class":507},[422,2739,2740],{"class":428},"tenants",[422,2742,1654],{"class":507},[422,2744,2745],{"class":514},"resolve",[422,2747,2748],{"class":428},"($request);\n",[422,2750,2751,2754,2756,2759,2761,2764,2767,2769],{"class":424,"line":367},[422,2752,2753],{"class":434},"$this",[422,2755,1654],{"class":507},[422,2757,2758],{"class":428},"connection",[422,2760,1654],{"class":507},[422,2762,2763],{"class":514},"useDatabase",[422,2765,2766],{"class":428},"($tenant",[422,2768,1654],{"class":507},[422,2770,2771],{"class":428},"database);\n",[11,2773,2774],{},"Bu ayrım korunmadığında, bağlantı seçimi servislere sızıyor ve test edilmesi zor bir yapı ortaya çıkıyor. Daha kötüsü, arka planda çalışan işlerde kiracı bağlamının hiç kurulmaması gibi hatalar mümkün hâle geliyor — kuyruk işçileri bir HTTP isteğinden gelmediği için bu bağlamı ayrıca taşımaları gerekiyor.",[23,2776,2778],{"id":2777},"ne-zaman-hangisi","Ne zaman hangisi",[11,2780,2781],{},"Karar verirken baktığım şey:",[31,2783,2784,2790,2796,2802,2808],{},[34,2785,2786,2789],{},[15,2787,2788],{},"Veri hassasiyeti yüksekse"," (sağlık, finans, kişisel veri) izolasyon tarafına yaslan.",[34,2791,2792,2795],{},[15,2793,2794],{},"Kiracı sayısı çok yüksek ve her biri küçükse"," ortak tablo daha yönetilebilir; binlerce veritabanına migrasyon çalıştırmak kendi başına bir problem.",[34,2797,2798,2801],{},[15,2799,2800],{},"Kiracı sayısı sınırlı ve her biri büyükse"," ayrı veritabanı hem performans hem sınır olarak kazandırıyor.",[34,2803,2804,2807],{},[15,2805,2806],{},"Kiracılar arası raporlama ürünün merkezindeyse"," ayrı veritabanı bunu pahalı hâle getiriyor; ayrı bir raporlama katmanı gerekiyor.",[34,2809,2810,2813],{},[15,2811,2812],{},"Kiracı bazlı yedek alma veya veri silme talebi bekliyorsan"," ayrı veritabanı bunu önemsiz bir işe indiriyor.",[11,2815,2816],{},"Son madde giderek daha belirleyici oluyor. \"Bu kurumun tüm verisini silin\" talebi, ortak tabloda dikkatli bir operasyon; ayrı veritabanında tek bir işlem.",[86,2818,2819],{"title":359,"type":360},[11,2820,2821],{},"Buradaki kod parçaları kavramı anlatmak için yazılmış örneklerdir; belirli bir projenin üretim kodu değildir. Kendi kurulumunda uygularken veritabanı sürümünün ve ORM'inin davranışını kendi ortamında doğrula.",[1079,2823,2824],{},"html pre.shiki code .snl16, html code.shiki .snl16{--shiki-default:#F97583}html pre.shiki code .s95oV, html code.shiki .s95oV{--shiki-default:#E1E4E8}html pre.shiki code .sU2Wk, html code.shiki .sU2Wk{--shiki-default:#9ECBFF}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html pre.shiki code .sDLfK, html code.shiki .sDLfK{--shiki-default:#79B8FF}html pre.shiki code .sAwPA, html code.shiki .sAwPA{--shiki-default:#6A737D}html pre.shiki code .svObZ, html code.shiki .svObZ{--shiki-default:#B392F0}",{"title":365,"searchDepth":366,"depth":367,"links":2826},[2827,2828,2830,2831,2832,2833,2834],{"id":2357,"depth":366,"text":2358},{"id":2484,"depth":366,"text":2829},"Ortak tablonun asıl riski bir WHERE",{"id":2549,"depth":366,"text":2550},{"id":2579,"depth":366,"text":2580},{"id":2609,"depth":366,"text":2610},{"id":2711,"depth":366,"text":2712},{"id":2777,"depth":366,"text":2778},"\u002Fimages\u002Fblog\u002Fveri-izolasyonu.svg","2026-08-19","Ortak tablo mu, ayrı veritabanı mı? Kararın kolay kısmı sorgular; zor kısmı şema değişikliğinin her kiracıya tutarlı ulaşması.",{},"\u002Fyazilar\u002Fcoklu-veritabani-veri-izolasyonu",{"title":2343,"description":2837},"yazilar\u002Fcoklu-veritabani-veri-izolasyonu",[378,2843,2844],"Veritabanı","Çok Kiracılılık","ed9_AsUkRT_s2T7xgy2DRBBuNXvTRWDLS5Quan_vYmg",[2847,3176,3395,3693],{"id":2848,"title":2849,"body":2850,"category":3155,"cover":365,"demo":381,"description":3156,"draft":381,"extension":383,"featured":384,"meta":3157,"monogram":3158,"navigation":384,"order":425,"path":3159,"problem":3160,"screenshotLayout":3161,"screenshots":3162,"seo":3163,"solution":3164,"stem":3165,"tags":3166,"technologies":3169,"__hash__":3175},"projects\u002Fprojeler\u002Flead-intelligence.md","Lead Intelligence",{"type":8,"value":2851,"toc":3136},[2852,2856,2859,2862,2866,2873,2877,2880,2903,2906,2912,2916,2919,2922,2926,2929,2932,2936,2939,2942,2956,2959,2963,2966,2969,2973,2976,2980,2983,2986,2998,3001,3004,3010,3013,3017,3020,3023,3027,3030,3033,3037,3040,3044,3047,3051,3077,3081,3088,3108,3112,3115,3118,3121,3125,3128,3131],[23,2853,2855],{"id":2854},"problem","Problem",[11,2857,2858],{},"Farklı kanallardan gelen potansiyel müşterilerin doğru satış temsilcisine aktarılması, müşteri geçmişinin korunması ve sürecin CRM ile tutarlı ilerlemesi gerekiyor. Lead elle dağıtıldığında iki şey birden bozuluyor: aynı kişiyle daha önce görüşen temsilci kaybediliyor ve mesai dışında gelen kayıtlar sahipsiz kalıyor.",[11,2860,2861],{},"Operasyon tarafında ise lead, satış ve kampanya verilerini ayrı ekranlardan birleştirmek yerine birlikte değerlendirebilmek önemli. Reklamın getirdiği kayıt ile o kaydın satışa ne kadar yaklaştığı farklı sistemlerde durduğunda, hangi kampanyanın gerçekten işe yaradığı da söylenemiyor.",[23,2863,2865],{"id":2864},"yaklaşım","Yaklaşım",[11,2867,2868,2869,2872],{},"Lead Intelligence'ı bir dağıtım kuyruğu olarak değil, ",[15,2870,2871],{},"lead gelir → kapsam belirlenir → temsilci seçilir → CRM'e yazılır → sonuç reklama geri bildirilir"," zincirinin tamamı olarak ele aldım. Hangi kanaldan gelirse gelsin her kayıt aynı karar hattından geçiyor; ekranlar da bu hattın farklı noktalarına bakan görünümler oluyor.",[23,2874,2876],{"id":2875},"lead-dağıtım-akışı","Lead Dağıtım Akışı",[11,2878,2879],{},"Dağıtım rastgele bir seçim değil, sıralı bir eleme:",[291,2881,2882,2885,2888,2891,2894,2897,2900],{},[34,2883,2884],{},"Lead sisteme düşüyor ve daha önce kayıt olup olmadığı kontrol ediliyor.",[34,2886,2887],{},"Aynı kişiyle daha önce ilgilenen bir temsilci varsa süreklilik korunuyor.",[34,2889,2890],{},"Kaydın hangi pipeline altında değerlendirileceği belirleniyor; bu bilgi formdan doğrudan gelebiliyor, telefon akışında seçilebiliyor ya da chatbot akışına bağlı olabiliyor.",[34,2892,2893],{},"Kaynak bazlı özel yönlendirme tanımlıysa uygulanıyor: belirli kanallardan gelen kayıtlar belirli temsilcilere veya temsilci gruplarına gidiyor.",[34,2895,2896],{},"Uygunluk filtreleri çalışıyor — aktiflik, vardiya, izin durumu, pipeline yetkisi, günlük lead limiti ve mesai dışı on\u002Foff durumu.",[34,2898,2899],{},"Kalan temsilciler arasında ağırlıklı ve dengeli dağıtım yapılıyor.",[34,2901,2902],{},"Kayıt CRM'de oluşturuluyor ve süreç analiz ekranlarına yansıyor.",[11,2904,2905],{},"Karar verilirken kaydın kaynağı, pipeline\u002Foperasyon türü, telefon ülke kodu ve sistemdeki geçmişi birlikte değerlendiriliyor.",[86,2907,2909],{"title":2908,"type":89},"Neden süreklilik kontrolü en başta?",[11,2910,2911],{},"Ağırlıklı dağıtım tek başına çalıştırıldığında, daha önce görüşülmüş bir müşteri yeni bir temsilciye düşebiliyor. Bu hem müşteri için baştan anlatma anlamına geliyor hem de iki temsilci arasında sahiplik tartışması yaratıyor. Bu yüzden süreklilik kontrolü dengeleme mantığından önce çalışıyor.",[23,2913,2915],{"id":2914},"onoff-ve-mesai-dışı-kurgu","On\u002FOff ve Mesai Dışı Kurgu",[11,2917,2918],{},"Mesai saatleri dışında ve resmi tatillerde ayrı bir kurgu devreye giriyor. Temsilciler kendilerini bu dönemlerde lead almaya açık hale getirebiliyor; sistem de yalnızca açık ve uygun olan temsilcilere yönlendirme yapıyor.",[11,2920,2921],{},"Kurgunun iki sınırı var: temsilci başına maksimum lead limiti uygulanabiliyor ve izinli ya da uygun olmayan temsilciler otomatik olarak devre dışı kalıyor. Amaç hafta sonu ve gece gelen kayıtların ertesi güne sarkmasını engellemek.",[23,2923,2925],{"id":2924},"crm-senkronizasyonu","CRM Senkronizasyonu",[11,2927,2928],{},"CRM tarafında kişi, satış fırsatı, pipeline, aşama ve aktivite kayıtları takip ediliyor. Entegrasyon çift yönlü çalışıyor: sistem CRM'e kayıt yazıyor, CRM'de yapılan değişiklikleri de geri alıyor.",[11,2930,2931],{},"Bunun pratik karşılığı şu: satış ekibi kendi alışkın olduğu ekranda çalışmaya devam ediyor, aşama değişikliği yaptığında analiz ekranları da güncelleniyor. Raporlama için ikinci bir veri girişi istenmiyor.",[23,2933,2935],{"id":2934},"kampanya-takibi-ve-offline-conversion","Kampanya Takibi ve Offline Conversion",[11,2937,2938],{},"Reklam tarafında kampanya adı ve kimlik bilgileri, lokasyon hedeflemeleri, kampanyadan gelen kayıtlar ve kampanya bazlı maliyet\u002Fperformans verileri izleniyor. Kampanya ekranından conversion value tanımlanabiliyor.",[11,2940,2941],{},"Offline conversion, zincirin kapandığı yer: reklamdan gelen bir kayıt CRM içinde değerli bir aşamaya ulaştığında sonuç Google Ads'e geri bildiriliyor.",[291,2943,2944,2947,2950,2953],{},[34,2945,2946],{},"Kullanıcı reklama tıklıyor ve kayıt sisteme düşüyor.",[34,2948,2949],{},"Satış ekibi kayıtla ilgileniyor.",[34,2951,2952],{},"Kayıt kapora gibi satışa yakın bir aşamaya geliyor.",[34,2954,2955],{},"Bu dönüşüm, kampanya eşleşmesiyle birlikte reklam platformuna gönderiliyor.",[11,2957,2958],{},"Böylece optimizasyon yalnızca form doldurma gibi ilk aksiyona değil, gerçek satış değerine daha yakın bir sinyale göre yapılabiliyor.",[23,2960,2962],{"id":2961},"kapora-bildirimi","Kapora Bildirimi",[11,2964,2965],{},"Ödeme bildirimi geldiğinde akış şöyle ilerliyor: bildirimin gerçekten yeni bir ödeme olup olmadığı kontrol ediliyor, ilgili müşteri ve satış kaydı bulunuyor, CRM'de aşama ve gerekli alanlar güncelleniyor, ödeme aktivite olarak işleniyor ve bildirim\u002Flog kayıtları oluşturuluyor.",[11,2967,2968],{},"Mükerrer kontrolü burada kritik: aynı ödemenin iki kez işlenmesi sadece bir kayıt fazlası değil, dönüşüm oranını da bozan bir hata oluyor.",[23,2970,2972],{"id":2971},"chatbot-ai-ve-çağrı-kaynakları","Chatbot, AI ve Çağrı Kaynakları",[11,2974,2975],{},"Sistem yalnızca form ve CRM kayıtlarıyla çalışmıyor. Chatbot akışlarından gelen kayıtların hangi aşamada olduğu, ulaşılabilirlik ve cevap verme durumu izleniyor; sesli AI ve çağrı kaynaklı kayıtlar da aynı hatta giriyor. Çağrı sonrası CRM'e aktivite ekleniyor, gerekiyorsa yeni kişi veya fırsat oluşturuluyor.",[23,2977,2979],{"id":2978},"dashboard-ve-analiz-modları","Dashboard ve Analiz Modları",[11,2981,2982],{},"Dashboard; lead analizi, yönetici özeti, performans analizi, chatbot analizi, kapora analizi, aylık analiz ve kampanya analizi sekmelerinden oluşuyor. Her sekme kendi filtresini kullanıp ilgili analizi arka uçtan istiyor; hesaplama arayüzde değil, sunucu tarafında yapılıyor.",[11,2984,2985],{},"En belirleyici ayrım iki analiz modu arasında:",[31,2987,2988,2993],{},[34,2989,2990,2992],{},[15,2991,1194],{}," kayıtların şu andaki durumuna bakıyor. Bugünün operasyon fotoğrafını veriyor.",[34,2994,2995,2997],{},[15,2996,1200],{}," kayıtların belirli bir tarihte hangi aşamada olduğunu yeniden kuruyor. \"Mayıs ayında gelen kayıtlar ayın 15'inde neredeydi?\" sorusunun cevabı bu.",[11,2999,3000],{},"Bu ayrım olmadan aylık değerlendirme yanıltıcı oluyor: geçmiş bir dönemi bugünün aşama dağılımıyla okumak, o dönemde henüz olgunlaşmamış kayıtları başarısız gibi gösteriyor.",[11,3002,3003],{},"Dönüşüm oranı da tek bir tanım üzerinden hesaplanıyor:",[413,3005,3008],{"className":3006,"code":3007,"language":1507,"meta":365},[1505],"Lead → Kapora (%) = (Kapora aşamasındaki kayıt \u002F Toplam kayıt) × 100\n",[419,3009,3007],{"__ignoreMap":365},[11,3011,3012],{},"Kapora sayılan aşamalar tek bir tanımda toplanıyor; toplam sıfırsa oran sıfır kabul ediliyor. Bazı tablolarda aynı mantıkla, ulaşılan kayıt üzerinden ikinci bir oran gösteriliyor.",[23,3014,3016],{"id":3015},"ortak-filtre-yapısı","Ortak Filtre Yapısı",[11,3018,3019],{},"Filtreler ekranlara özel değil, ortak. Tarih aralığı, pipeline, temsilci, kaynak ve kampanya seçimleri sekmeler arasında aynı anlamı taşıyor; ülke ve kaynak gibi alanlar çoklu seçim destekliyor ve sorgu tarafında küme filtresine dönüşüyor.",[11,3021,3022],{},"Kart üzerinden detay açıldığında filtrenin varsayılana dönmesi de bilinçli: başka bir sekmede yapılmış daraltıcı seçimin, tıklanan kartın kapsamını sessizce değiştirmesini engelliyor.",[23,3024,3026],{"id":3025},"arka-plan-i̇şleri","Arka Plan İşleri",[11,3028,3029],{},"Panelde kimse işlem yapmasa da çalışan süreçler var: CRM verilerinin düzenli kontrolü, kuyrukta bekleyen satış kayıtlarının işlenmesi, kampanya bilgilerinin güncellenmesi, offline conversion gönderimleri, günlük\u002Fhaftalık\u002Faylık mail raporları, on\u002Foff listelerinin belirli saatlerde temizlenmesi ve planlı sesli çağrıların işlenmesi.",[11,3031,3032],{},"Bu işler sayesinde sistem açılıp bakılan bir ekran değil, sürekli çalışan bir operasyon motoru gibi davranıyor.",[23,3034,3036],{"id":3035},"yetki-yönetimi","Yetki Yönetimi",[11,3038,3039],{},"Kullanıcının göreceği ekranlar ve yapabileceği işlemler yetki gruplarına bağlı. Yönetici kullanıcılar geniş rapor ve ayar ekranlarına erişirken, satış temsilcileri kendi performanslarına yönelik daha dar bir alan görüyor; kritik ayar işlemleri üst yetki gerektiriyor.",[23,3041,3043],{"id":3042},"kullandığım-teknolojiler","Kullandığım Teknolojiler",[11,3045,3046],{},"Laravel, PHP, MySQL, JavaScript, jQuery, Bootstrap, Vite ve ApexCharts. CRM (Pipedrive) ve reklam platformu entegrasyonları API ve webhook akışlarıyla, tekrarlayan işler zamanlanmış görevlerle yürütülüyor.",[23,3048,3050],{"id":3049},"teknik-odak","Teknik Odak",[31,3052,3053,3056,3059,3062,3065,3068,3071,3074],{},[34,3054,3055],{},"Çok kanallı lead kayıtları ve kural tabanlı, sıralı dağıtım.",[34,3057,3058],{},"Müşteri geçmişiyle temsilci sürekliliği.",[34,3060,3061],{},"Mesai dışı on\u002Foff kurgusu ve günlük limitler.",[34,3063,3064],{},"CRM kayıtlarının çift yönlü senkronizasyonu.",[34,3066,3067],{},"Kampanya eşleştirme ve offline conversion geri bildirimi.",[34,3069,3070],{},"Anlık ve dönemsel analiz modlarının ayrılması.",[34,3072,3073],{},"Ekranlar arasında ortak filtre sözleşmesi.",[34,3075,3076],{},"Yönetici özetleri, karşılaştırmalar ve rapor dışa aktarımı.",[23,3078,3080],{"id":3079},"karşılaştığım-problemler","Karşılaştığım Problemler",[11,3082,3083,3084,3087],{},"Zorluk lead'i bir temsilciye yazmak değil, ",[15,3085,3086],{},"kapsamın belirsiz kaldığı"," durumları tanımlamaktı:",[31,3089,3090,3093,3096,3099,3102,3105],{},[34,3091,3092],{},"Aynı kişinin farklı kanallardan tekrar gelmesi ve sahipliğin tartışmalı hale gelmesi.",[34,3094,3095],{},"Uygunluk kurallarının birbirini iptal etmesi; filtreler arka arkaya uygulandığında geriye hiç temsilci kalmaması.",[34,3097,3098],{},"Aynı metriğin iki ekranda farklı çıkması.",[34,3100,3101],{},"Geçmiş bir dönemin bugünün aşama dağılımıyla okunması.",[34,3103,3104],{},"Mükerrer ödeme bildiriminin dönüşüm oranını şişirmesi.",[34,3106,3107],{},"Reklamdan gelen kaydın kampanyayla eşleşmemesi ve geri bildirim zincirinin kopması.",[23,3109,3111],{"id":3110},"çözümler","Çözümler",[11,3113,3114],{},"Dağıtım kurallarının sırasını sabitlemek belirleyici oldu: süreklilik, kaynak bazlı yönlendirme ve uygunluk filtreleri hep aynı sırayla çalışıyor, böylece bir kaydın neden o temsilciye gittiği sonradan açıklanabiliyor.",[11,3116,3117],{},"Ortak filtre yapısı, farklı ekranların aynı kapsamı değerlendirmesini sağlıyor. Hesaplama servislerinin ayrılması, analiz mantığının tek bir büyük controller içinde birikmesini önlüyor. Anlık ve dönemsel analizin ayrı tutulması ise geçmiş dönem yorumlarını düzeltti.",[11,3119,3120],{},"Reklam ve CRM verilerinin birlikte ele alınması, lead akışını sonuçlarıyla beraber incelemeyi mümkün kılıyor; mükerrer kontrolü de bu sonuçların şişmesini engelliyor.",[23,3122,3124],{"id":3123},"tasarım-notu","Tasarım Notu",[11,3126,3127],{},"Bu projede yönlendirme kuralları kadar, bir rakamın hangi kapsam ve dönem üzerinden hesaplandığının anlaşılması da önemli. İki ekran aynı metrik için farklı sayı gösteriyorsa sorun çoğu zaman hesaplamada değil, kapsamın tanımında oluyor. Bu yüzden ortak filtre yapısını ekranlar arasındaki bir sözleşme gibi ele aldım: aynı filtreyi kullanan her ekran aynı kümeyi değerlendiriyor.",[11,3129,3130],{},"Aynı yaklaşım dağıtım tarafında da geçerli. Kuralların sırası belliyse \"bu lead neden bana gelmedi?\" sorusu tahmin değil, kontrol listesiyle cevaplanabilir bir soruya dönüşüyor.",[86,3132,3133],{"title":359,"type":360},[11,3134,3135],{},"Bu sayfa projenin kapsamını ve teknik yaklaşımını anlatır. Gerçek müşteri kayıtları, lead ve dönüşüm rakamları, dağıtım ağırlıkları, kaynak\u002Fiş ortağı adları ve kurum içi performans verileri paylaşılmıyor.",{"title":365,"searchDepth":366,"depth":367,"links":3137},[3138,3139,3140,3141,3142,3143,3144,3145,3146,3147,3148,3149,3150,3151,3152,3153,3154],{"id":2854,"depth":366,"text":2855},{"id":2864,"depth":366,"text":2865},{"id":2875,"depth":366,"text":2876},{"id":2914,"depth":366,"text":2915},{"id":2924,"depth":366,"text":2925},{"id":2934,"depth":366,"text":2935},{"id":2961,"depth":366,"text":2962},{"id":2971,"depth":366,"text":2972},{"id":2978,"depth":366,"text":2979},{"id":3015,"depth":366,"text":3016},{"id":3025,"depth":366,"text":3026},{"id":3035,"depth":366,"text":3036},{"id":3042,"depth":366,"text":3043},{"id":3049,"depth":366,"text":3050},{"id":3079,"depth":366,"text":3080},{"id":3110,"depth":366,"text":3111},{"id":3123,"depth":366,"text":3124},"CRM","Çok kanallı lead yönlendirme, CRM entegrasyonu ve pazarlama performansını bir araya getiren analiz platformu.",{},"LI","\u002Fprojeler\u002Flead-intelligence","Farklı kanallardan gelen potansiyel müşterileri uygun satış temsilcisine yönlendirmek ve satış sürecini reklam performansıyla birlikte izlemek.","stacked",[],{"title":2849,"description":3156},"Kural tabanlı lead dağıtımı, CRM senkronizasyonu ve ortak filtrelerle çalışan analiz ekranları.","projeler\u002Flead-intelligence",[3155,3167,3168],"Automation","Analytics",[3170,3171,3172,3173,3174],"Laravel","PHP","MySQL","JavaScript","ApexCharts","EU0RT0MQaaFVtH8HCBPakiQZgOZhcr1DeAqiZI6HIo0",{"id":3177,"title":3178,"body":3179,"category":3380,"cover":365,"demo":381,"description":3381,"draft":381,"extension":383,"featured":384,"meta":3382,"monogram":3383,"navigation":384,"order":366,"path":3384,"problem":3385,"screenshotLayout":3161,"screenshots":3386,"seo":3387,"solution":3388,"stem":3389,"tags":3390,"technologies":3392,"__hash__":3394},"projects\u002Fprojeler\u002Fvpm-finance.md","VPM — Vanity Payment Manager",{"type":8,"value":3180,"toc":3365},[3181,3183,3186,3189,3191,3198,3202,3205,3222,3228,3232,3235,3238,3242,3245,3248,3252,3255,3258,3262,3282,3286,3289,3292,3294,3297,3299,3322,3324,3330,3347,3349,3352,3355,3357,3360],[23,3182,2855],{"id":2854},[11,3184,3185],{},"Hastane operasyonlarında ödeme bilgisi tek bir yerde oluşmuyor. Bir kısmı hastayla görüşen ekipler tarafından form üzerinden giriliyor, bir kısmı hastaya gönderilen online ödeme sayfasından geliyor. Hasta, randevu ve fatura bilgileri ise ayrı bir hastane bilgi sisteminde tutuluyor.",[11,3187,3188],{},"Bu yapı elle yönetildiğinde iki şey birden zorlaşıyor: bir hastanın ödeme geçmişine hızlı erişmek ve bir dönemin cirosunu güvenle söyleyebilmek. Kayıtların kimin tarafından girildiği, kimin onayladığı ve hangi kanaldan geldiği izlenemediğinde, finansal bir rakamı savunmak da mümkün olmuyor.",[23,3190,2865],{"id":2864},[11,3192,3193,3194,3197],{},"VPM'i bir ödeme kayıt ekranı olarak değil, ",[15,3195,3196],{},"ödeme alma → doğrulama → hasta ile ilişkilendirme → analiz → satış sürecine yansıtma"," zincirinin tamamı olarak ele aldım. Her ödeme kaydı, hangi kanaldan geldiğinden bağımsız olarak aynı doğrulama ve onay hattından geçiyor.",[23,3199,3201],{"id":3200},"ödeme-akışı","Ödeme Akışı",[11,3203,3204],{},"Manuel giriş ile online tahsilat, sistemin içinde aynı noktada buluşuyor:",[291,3206,3207,3210,3213,3216,3219],{},[34,3208,3209],{},"Ödeme bilgisi iki kanaldan biriyle oluşuyor: operasyon ekibinin doldurduğu standart form veya hastaya gönderilen online tahsilat sayfası.",[34,3211,3212],{},"Online ödemeler doğrudan kesin kayda yazılmıyor; önce kontrol edilebilir bir ara aşamaya alınıyor.",[34,3214,3215],{},"Hasta eşleştirmesi yapılıyor; eşleşmeyen veya eksik bilgiyle gelen kayıtlar ara aşamada bekletiliyor.",[34,3217,3218],{},"Yetkili kullanıcı kaydı onaylıyor veya açıklama yazarak reddediyor.",[34,3220,3221],{},"Onaylanan kayıt finansal analize ve satış sürecine aktarılıyor.",[86,3223,3225],{"title":3224,"type":89},"Neden ara aşama var?",[11,3226,3227],{},"Online ödemeyi doğrudan kesin kayda yazmak, hasta kaydıyla eşleşmeyen veya eksik bilgi içeren bir tahsilatı da anında finansal tabloya sokar. Ara aşama, \"para geldi\" bilgisi ile \"bu para şu hastanın şu hizmetine ait\" bilgisini birbirinden ayırıyor.",[23,3229,3231],{"id":3230},"online-tahsilat-sayfası","Online Tahsilat Sayfası",[11,3233,3234],{},"Hastanın veya hasta adına ödeme yapan kişinin kullandığı tahsilat sayfası, kredi kartı ve banka havalesini aynı deneyimde sunuyor. Kart ödemelerinde sanal POS ve 3D Secure akışı işletiliyor; havale seçeneğinde hastaya referans kodu ve hesap bilgisi gösterilerek ödemenin takip edilebilmesi sağlanıyor.",[11,3236,3237],{},"Sayfa, ödeme yapan kişinin hastadan farklı olabileceği varsayımıyla tasarlandı — pratikte sık karşılaşılan bir durum. Referans kodu, ödeme ile hasta kaydı arasındaki bağı kuran alan olarak kullanılıyor.",[23,3239,3241],{"id":3240},"çoklu-dil-ve-para-birimi","Çoklu Dil ve Para Birimi",[11,3243,3244],{},"Uluslararası hasta operasyonu nedeniyle tahsilat sayfası Türkçe, İngilizce, Almanca, Fransızca, İtalyanca ve İspanyolca olarak sunuluyor. Finansal tarafta TRY, EUR, USD ve GBP birlikte ele alınıyor.",[11,3246,3247],{},"Çoklu para birimi, raporlamada ayrı bir tasarım kararı gerektirdi: farklı para birimlerindeki tutarları tek bir toplama indirgemek yerine, her para birimini kendi ekseninde izlenebilir tutmak.",[23,3249,3251],{"id":3250},"onay-ve-i̇zlenebilirlik","Onay ve İzlenebilirlik",[11,3253,3254],{},"Girilen ödeme kayıtları doğrudan kesinleşmiyor. Finans veya yönetici yetkisine sahip kullanıcılar kaydı inceleyip onaylıyor; eksik veya hatalı bulduklarında red açıklaması yazarak geri çeviriyor. Bekleyen, onaylanan ve reddedilen kayıtlar ayrı ayrı izleniyor.",[11,3256,3257],{},"Red açıklaması bilinçli bir tercih: kaydı giren kişi neyin yanlış olduğunu görmeden düzeltemez. Her kaydın kim tarafından, ne zaman, hangi işlemle değiştirildiği de tutuluyor.",[23,3259,3261],{"id":3260},"entegrasyonlar","Entegrasyonlar",[31,3263,3264,3270,3276],{},[34,3265,3266,3269],{},[15,3267,3268],{},"Hastane bilgi sistemi:"," Hasta, randevu ve fatura\u002Fciro verileri ödeme kayıtlarıyla birlikte değerlendiriliyor. Böylece bir ödemenin hangi hasta, hangi hizmet ve hangi faturayla ilişkili olduğu görülebiliyor.",[34,3271,3272,3275],{},[15,3273,3274],{},"Satış süreci (Pipedrive):"," Onaylanan ödemeler ve online tahsilat sonuçları ilgili satış kaydına aktivite\u002Fnot olarak yansıtılıyor. Başarısız ödemeler de işaretleniyor; satış ekibi takip edebilsin diye.",[34,3277,3278,3281],{},[15,3279,3280],{},"Ödeme sağlayıcıları:"," Kart tahsilatı sanal POS üzerinden, sonuç doğrulaması sağlayıcı dönüşünün kontrol edilmesiyle yapılıyor.",[23,3283,3285],{"id":3284},"dashboard-ve-raporlama","Dashboard ve Raporlama",[11,3287,3288],{},"Dashboard, ödeme kayıtlarının listelendiği bir ekran değil; finans ve yönetim ekiplerinin günlük takibi ile dönemsel analizini aynı yerde yapabildiği bir karar destek alanı olarak tasarlandı.",[11,3290,3291],{},"Panelde dönemsel ciro hareketi, tahsil edilen ve bekleyen tutarlar, onay bekleyen kayıt sayısı, ödeme yöntemi ve ödeme aracı bazlı dağılımlar, ürün grubu kırılımı ve iade toplamları birlikte sunuluyor. Tarih aralığı, para birimi, ödeme tipi, ödeme yöntemi ve fatura durumu üzerinden filtreleme yapılabiliyor; sonuçlar dışa aktarılabiliyor.",[23,3293,3043],{"id":3042},[11,3295,3296],{},"Laravel, PHP, MySQL, Redis, Blade, jQuery, Vite ve ApexCharts. Hastane bilgi sistemi, satış süreci ve ödeme sağlayıcısı entegrasyonları API ve webhook akışlarıyla yürütülüyor.",[23,3298,3050],{"id":3049},[31,3300,3301,3304,3307,3310,3313,3316,3319],{},[34,3302,3303],{},"Manuel giriş ile online tahsilatın ortak veri modelinde birleştirilmesi.",[34,3305,3306],{},"Kontrol edilebilir aktarım kuyruğu ve hasta eşleştirme.",[34,3308,3309],{},"Kademeli, yetki seviyesine göre onay ve red akışı.",[34,3311,3312],{},"Çoklu para birimi ve çoklu dil desteği.",[34,3314,3315],{},"Referans kodu üzerinden havale takibi.",[34,3317,3318],{},"Fatura ve ciro verisiyle birlikte dönemsel analiz.",[34,3320,3321],{},"İşlem geçmişi ve denetlenebilirlik.",[23,3323,3080],{"id":3079},[11,3325,3326,3327,3087],{},"Geliştirme sırasında asıl zorluk ödeme almak değil, ödemenin ",[15,3328,3329],{},"belirsiz kaldığı",[31,3331,3332,3335,3338,3341,3344],{},[34,3333,3334],{},"Hasta kaydıyla eşleşmeyen tahsilatlar.",[34,3336,3337],{},"Eksik veya tutarsız bilgiyle gelen online ödemeler.",[34,3339,3340],{},"Aynı ödemenin farklı kanallardan iki kez kayda girmesi riski.",[34,3342,3343],{},"Fatura veya randevu bilgisinin ödeme sonrasında değişmesi.",[34,3345,3346],{},"Farklı para birimlerinin aynı raporda karşılaştırılması ihtiyacı.",[23,3348,3111],{"id":3110},[11,3350,3351],{},"Ara aşama ve hasta eşleştirme kuralları, belirsiz kayıtların finansal tabloya sızmasını engelliyor. Mükerrer kayıt riski, referans kodu ve kayıt durumu üzerinden kontrol ediliyor. Onay akışı, verinin analiz ekranlarına girmeden önce bir insan tarafından doğrulanmasını sağlıyor.",[11,3353,3354],{},"Raporlama tarafında ise alınan ödeme ile beklenen ödemeyi ayrı tutmak belirleyici oldu: bu ikisi tek bir sayıya karıştığında, dönem sonunda hangi tutarın gerçekten kasaya girdiği tartışmalı hale geliyor.",[23,3356,3124],{"id":3123},[11,3358,3359],{},"Finans uygulamasında sadece toplam tutarı göstermek yeterli değil; o tutarı oluşturan kaydın nereden geldiği ve hangi süreçten geçtiği de anlaşılmalı. Bir rakamın sorgulanabilir olması, doğru olmasının ön koşulu: kaydın kaynağı ve onay geçmişi izlenemiyorsa, toplamın doğruluğunu tartışmak da mümkün olmuyor.",[86,3361,3362],{"title":359,"type":360},[11,3363,3364],{},"Bu sayfa projenin kapsamını ve teknik yaklaşımını anlatır. Gerçek işlem tutarları, hasta bilgileri, hesap bilgileri, sağlayıcı sözleşmeleri veya kurum içi yetki ayrıntıları paylaşılmıyor.",{"title":365,"searchDepth":366,"depth":367,"links":3366},[3367,3368,3369,3370,3371,3372,3373,3374,3375,3376,3377,3378,3379],{"id":2854,"depth":366,"text":2855},{"id":2864,"depth":366,"text":2865},{"id":3200,"depth":366,"text":3201},{"id":3230,"depth":366,"text":3231},{"id":3240,"depth":366,"text":3241},{"id":3250,"depth":366,"text":3251},{"id":3260,"depth":366,"text":3261},{"id":3284,"depth":366,"text":3285},{"id":3042,"depth":366,"text":3043},{"id":3049,"depth":366,"text":3050},{"id":3079,"depth":366,"text":3080},{"id":3110,"depth":366,"text":3111},{"id":3123,"depth":366,"text":3124},"FinTech","Hastane ödemelerini online tahsilat, iç operasyon formları ve onay süreçleriyle tek merkezde toplayan finans yönetim platformu.",{},"VPM","\u002Fprojeler\u002Fvpm-finance","Farklı kanallardan gelen hasta ödemelerini doğrulamak, yetkili onayından geçirmek ve hasta\u002Ffatura verisiyle birlikte analiz edilebilir hale getirmek.",[],{"title":3178,"description":3381},"Ödeme kaydı, çok dilli online tahsilat, kademeli onay akışı, kontrollü aktarım kuyruğu ve finans dashboard'unu ortak bir platformda birleştirmek.","projeler\u002Fvpm-finance",[3380,3391,3168,2329],"Backend",[3170,3171,3172,3393,3174],"Redis","3YIo44m7Ype-JJpLGrbVfl3ZBEnhrIOTdWDqYqemATY",{"id":3396,"title":3397,"body":3398,"category":3679,"cover":365,"demo":381,"description":3680,"draft":381,"extension":383,"featured":384,"meta":3681,"monogram":3682,"navigation":384,"order":367,"path":3683,"problem":3684,"screenshotLayout":3161,"screenshots":3685,"seo":3686,"solution":3687,"stem":3688,"tags":3689,"technologies":3690,"__hash__":3692},"projects\u002Fprojeler\u002Fvanity-appointment.md","Vanity Appointment",{"type":8,"value":3399,"toc":3663},[3400,3402,3405,3408,3410,3417,3424,3428,3431,3451,3457,3461,3464,3467,3471,3474,3488,3491,3495,3498,3501,3505,3508,3511,3514,3516,3550,3553,3557,3560,3563,3577,3579,3582,3584,3610,3612,3619,3639,3641,3644,3647,3650,3652,3655,3658],[23,3401,2855],{"id":2854},[11,3403,3404],{},"Estetik operasyon randevusu, takvimden bir saat seçmekten ibaret değil. Hasta hangi operasyonlarla ilgilendiğini söylüyor, o operasyonları yapan doktorlar arasından biri seçiliyor, doktorun o gün gerçekten müsait olup olmadığı kontrol ediliyor, ön görüşme için ücret tahsil ediliyor ve görüşme bağlantısı ile bilgilendirme her iki tarafa ulaştırılıyor.",[11,3406,3407],{},"Bu adımlar birbirinden ayrı yönetildiğinde iki taraf da aynı anda kör kalıyor: hasta randevusunun gerçekten oluşup oluşmadığını bilemiyor, klinik tarafı ise elindeki listenin hangi kayıtlarının ödenmiş randevu, hangilerinin yarım kalmış talep olduğunu ayırt edemiyor. Uluslararası hasta trafiği eklendiğinde dil, saat dilimi ve ödeme sağlayıcısı farkları da aynı akışın üzerine biniyor.",[23,3409,2865],{"id":2864},[11,3411,3412,3413,3416],{},"Vanity Appointment'ı iki ayrı ürün — bir randevu formu ve bir yönetim paneli — olarak değil, ",[15,3414,3415],{},"talep → doktor eşleştirme → müsaitlik doğrulama → ödeme → randevu kaydı → bilgilendirme"," zincirinin tamamı olarak ele aldım. Ziyaretçiye açık akış ile admin paneli tek Laravel uygulamasında, ortak veri modeli üzerinde çalışıyor.",[11,3418,3419,3420,3423],{},"Belirleyici tasarım kararı şu oldu: randevu, form gönderildiğinde değil, ",[15,3421,3422],{},"ödeme doğrulandığında"," kesinleşiyor. Aradaki her şey geçici bir booking session kaydı olarak tutuluyor.",[23,3425,3427],{"id":3426},"randevu-akışı","Randevu Akışı",[11,3429,3430],{},"Ziyaretçi tarafı, hangi adımda olduğunu her ekranda gösteren üç aşamalı bir akış olarak kurgulandı:",[291,3432,3433,3439,3445],{},[34,3434,3435,3438],{},[15,3436,3437],{},"Hasta bilgileri."," Ad, soyad, uluslararası formatta telefon ve e-posta alınıyor; ardından ilgilenilen operasyonlar seçiliyor. Tek operasyon zorunluluğu yok — hasta birden fazla işlem için aynı ön görüşmeyi talep edebiliyor.",[34,3440,3441,3444],{},[15,3442,3443],{},"Doktor ve randevu."," Seçilen operasyonları yapan doktorlar listeleniyor; doktorun uygunluk takvimi, tanımlı izinleri ve mevcut randevuları birlikte değerlendirilerek yalnızca gerçekten seçilebilir saatler gösteriliyor.",[34,3446,3447,3450],{},[15,3448,3449],{},"Önizleme ve ödeme."," Hasta seçimlerini son kez görüyor ve ön görüşme ödemesini yapıyor. Sağlayıcıdan dönen sonuç doğrulandıktan sonra randevu kesinleşiyor, görüşme bağlantısı üretiliyor ve bilgilendirme e-postaları gönderiliyor.",[86,3452,3454],{"title":3453,"type":89},"Neden ara bir booking session var?",[11,3455,3456],{},"Ödeme sayfasına giden her ziyaretçi geri dönmüyor. Yarım kalan akışı doğrudan randevu tablosuna yazmak, doktorun takvimini hiç gerçekleşmeyecek kayıtlarla doldurur. Bu yüzden akış boyunca toplanan veri, ödeme doğrulanana kadar ayrı bir booking session kaydında tutuluyor; ödenmemiş ve başarısız oturumlar zamanlanmış bir komutla düzenli olarak temizleniyor.",[23,3458,3460],{"id":3459},"çok-dilli-randevu-deneyimi","Çok Dilli Randevu Deneyimi",[11,3462,3463],{},"Randevu akışı locale'e göre ayrı route üzerinden sunuluyor; dil seçimi akışın başında değiştirilebiliyor ve ödeme dönüşü dâhil bütün adımlarda korunuyor. Operasyon adları ve açıklamaları panelden dile göre yönetiliyor, koda gömülü metin olarak taşınmıyor.",[11,3465,3466],{},"Uluslararası hasta trafiğinin görünmeyen tarafı ise saat dilimi. Hastanın gördüğü saat ile doktorun takviminde açılan saat aynı anı işaret etmek zorunda; bu yüzden doktor tarafı için referans saat dilimi uygulama ayarı olarak tutuluyor ve müsaitlik hesabı bu referans üzerinden yapılıyor.",[23,3468,3470],{"id":3469},"doktor-planlaması-ve-müsaitlik","Doktor Planlaması ve Müsaitlik",[11,3472,3473],{},"Panelde doktor kaydı, doktorun hangi operasyonları yaptığı, uygunluk takvimi, izin dönemleri ve konsültasyon bilgileri ayrı ayrı yönetiliyor. Ziyaretçiye gösterilen saat listesi bu kaynakların kesişiminden üretiliyor:",[31,3475,3476,3479,3482,3485],{},[34,3477,3478],{},"Doktor ↔ operasyon eşleşmesi, seçilen işlemi yapmayan doktorun listede görünmesini engelliyor.",[34,3480,3481],{},"Uygunluk takvimi, doktorun çalışma düzenini tanımlıyor.",[34,3483,3484],{},"İzin kayıtları, tanımlı çalışma düzenini geçici olarak kapatıyor.",[34,3486,3487],{},"Mevcut randevular, dolmuş saatleri listeden düşüyor.",[11,3489,3490],{},"İzni ayrı bir kavram olarak tutmak bilinçli bir tercihti: doktorun çalışma düzenini bir haftalığına silip sonra geri yazmak, hem düzenin kendisini bozuyor hem de o aralığın neden kapatıldığı bilgisini kaybettiriyor.",[23,3492,3494],{"id":3493},"ödeme-ve-randevunun-kesinleşmesi","Ödeme ve Randevunun Kesinleşmesi",[11,3496,3497],{},"Ön görüşme tahsilatı, birden fazla ödeme sağlayıcısıyla çalışacak şekilde kurgulandı; hangi sağlayıcının kullanılacağı ve test\u002Fcanlı ayrımı panelden yönetiliyor. Sağlayıcıdan dönüş için her sağlayıcının kendi callback route'u var; bu route'lar sağlayıcı doğrulaması ve istek sınırlandırmasıyla korunuyor.",[11,3499,3500],{},"Kullanıcının tarayıcısına dönen yönlendirme, ödemenin gerçekleştiğinin kanıtı olarak kabul edilmiyor. Randevu, dönen sonucun doğrulanıp booking session'ın kesin kayda dönüştürüldüğü anda oluşuyor. Ekranda \"randevunuz oluşturuldu\" yazması ile veritabanında randevunun bulunması aynı anlama gelmeli.",[23,3502,3504],{"id":3503},"yönetim-paneli","Yönetim Paneli",[11,3506,3507],{},"Panel, randevu taleplerinin listelendiği bir tablodan ibaret değil; operasyon ekibinin günlük takibi ile klinik tarafının planlama işini aynı yerde yapabildiği bir çalışma alanı olarak tasarlandı.",[11,3509,3510],{},"Randevu talebi sayısı, aktif doktor ve operasyon sayısı ile bekleyen\u002Födenmemiş kayıtlar bir arada gösteriliyor; randevular hem durumlarına (bekleyen, onaylanan, tamamlanan, iptal edilen) hem de günlere göre dağılımlarıyla izleniyor. Tarih aralığı filtresi bu görünümlerin tamamına aynı anda uygulanıyor, yaklaşan randevular ayrı bir listede aranabiliyor.",[11,3512,3513],{},"Erişim rol bazlı: randevu operasyonunu yürüten kullanıcı ile ödeme ayarlarını veya entegrasyon anahtarlarını görebilen kullanıcı aynı yetki seviyesinde değil.",[23,3515,3261],{"id":3260},[31,3517,3518,3523,3533,3538,3544],{},[34,3519,3520,3522],{},[15,3521,3274],{}," Randevu talebi oluşturan hasta, ilgili CRM kaydına kişi ve fırsat olarak yansıtılıyor; satış ekibi randevuyu kendi akışı içinde takip edebiliyor.",[34,3524,3525,3528,3529,3532],{},[15,3526,3527],{},"Finans platformu:"," Tahsil edilen ön görüşme ödemeleri, ödeme aracı bilgisiyle birlikte ",[3530,3531,3383],"a",{"href":3384}," tarafına aktarılıyor. Böylece randevu tahsilatı, kliniğin diğer ödeme kanallarıyla aynı finansal tabloda değerlendirilebiliyor.",[34,3534,3535,3537],{},[15,3536,3268],{}," Hasta ve randevu bilgisi, kurumun kendi bilgi sistemiyle birlikte ele alınıyor.",[34,3539,3540,3543],{},[15,3541,3542],{},"Görüşme bağlantısı:"," Onaylanan randevu için çevrimiçi görüşme bağlantısı üretiliyor ve bilgilendirme e-postalarıyla hastaya ve doktora ulaştırılıyor.",[34,3545,3546,3549],{},[15,3547,3548],{},"Senkronizasyon callback'i:"," Dış sistemden gelen bildirimler imza doğrulamasından geçtikten sonra işleniyor; gönderilen bilgilendirme mailleri panelde ayrı bir log ekranından izlenebiliyor.",[11,3551,3552],{},"Entegrasyonların hepsi aynı prensiple çalışıyor: dış sistemin cevabı gecikebilir veya hiç gelmeyebilir; bu durum randevunun oluşmasını engellememeli, ama sessizce de kaybolmamalı.",[23,3554,3556],{"id":3555},"ayar-yönetimi-ve-güvenlik","Ayar Yönetimi ve Güvenlik",[11,3558,3559],{},"Sağlayıcı anahtarları, entegrasyon token'ları ve akışa ait eşik değerleri koda ya da deploy dosyalarına gömülmek yerine veritabanı tabanlı bir ayar katmanında tutuluyor. Secret niteliğindeki değerler şifreli saklanıyor ve panelde açık metin olarak geri gösterilmiyor.",[11,3561,3562],{},"Birkaç güvenlik kararı doğrudan bu yapının parçası:",[31,3564,3565,3568,3571,3574],{},[34,3566,3567],{},"Randevu bağlantıları imzalı üretiliyor; bağlantıdaki parametrelerin sonradan değiştirilmesi kaydı ele geçirmeye yetmiyor.",[34,3569,3570],{},"Ödeme callback route'ları sağlayıcı doğrulaması ve throttle ile korunuyor.",[34,3572,3573],{},"Dış sistem callback'i HMAC imza doğrulaması gerektiriyor.",[34,3575,3576],{},"İlk yönetici hesabı repoda default parolayla gelmiyor; kurulumda bilinçli olarak, yalnızca hiç yönetici yokken çalışan bir seeder ile oluşturuluyor.",[23,3578,3043],{"id":3042},[11,3580,3581],{},"Laravel, PHP, MySQL, Blade, JavaScript, Bootstrap, SCSS ve Vite. Randevu akışında tarih\u002Fsaat seçimi ve uluslararası telefon girişi için arayüz bileşenleri kullanılıyor. Bilgilendirme e-postaları ve entegrasyon bildirimleri kuyruk üzerinden işleniyor; oturum temizliği zamanlanmış komutla yürütülüyor.",[23,3583,3050],{"id":3049},[31,3585,3586,3589,3592,3595,3598,3601,3604,3607],{},[34,3587,3588],{},"Çok dilli, adım adım ilerleyen ve durumunu koruyan randevu akışı.",[34,3590,3591],{},"Ödeme doğrulanana kadar geçici tutulan booking session modeli.",[34,3593,3594],{},"Operasyon, uygunluk takvimi, izin ve mevcut randevunun kesişiminden müsaitlik hesabı.",[34,3596,3597],{},"Saat dilimi farkının hasta ve doktor tarafında tutarlı ele alınması.",[34,3599,3600],{},"Sağlayıcı doğrulamalı ve sınırlandırılmış ödeme callback akışları.",[34,3602,3603],{},"İmzalı randevu bağlantıları ve HMAC doğrulamalı dış sistem callback'i.",[34,3605,3606],{},"Şifreli saklanan, panelden yönetilen çalışma zamanı ayarları.",[34,3608,3609],{},"Kuyruk tabanlı bildirim ve zamanlanmış oturum temizliği.",[23,3611,3080],{"id":3079},[11,3613,3614,3615,3618],{},"Zorluk randevu kaydetmekte değil, akışın ",[15,3616,3617],{},"yarım kaldığı"," durumları tanımlamaktaydı:",[31,3620,3621,3624,3627,3630,3633,3636],{},[34,3622,3623],{},"Ödeme sayfasına gidip geri dönmeyen ziyaretçilerin bıraktığı yarım oturumlar.",[34,3625,3626],{},"Sağlayıcı dönüşü ile gerçek ödeme sonucunun aynı anlama gelmediği durumlar.",[34,3628,3629],{},"Aynı saat için iki ziyaretçinin neredeyse eş zamanlı ilerlemesi.",[34,3631,3632],{},"Doktorun izne çıkması veya takviminin akış sürerken değişmesi.",[34,3634,3635],{},"Hastanın gördüğü saat ile doktorun takvimindeki saatin farklı dilim üzerinden hesaplanma riski.",[34,3637,3638],{},"Dış sistem bildiriminin gecikmesi ya da aynı bildirimin tekrar gelmesi.",[23,3640,3111],{"id":3110},[11,3642,3643],{},"Booking session, yarım kalan akışın randevu tablosuna sızmasını engelliyor; ödenmemiş ve başarısız oturumlar zamanlanmış temizlikle düşürülüyor. Ödeme sonucunun doğrulanması randevunun oluşma koşulu olduğu için, sağlayıcıdan dönen yönlendirme tek başına bir sonuç kabul edilmiyor.",[11,3645,3646],{},"Müsaitlik, akışın başında hesaplanıp saklanan bir liste değil; seçim anında yeniden değerlendirilen bir sonuç. Doktor tarafı için referans saat dilimini ayar olarak tutmak da hasta ve doktor takvimindeki saatin ortak bir eksende hesaplanmasını sağlıyor.",[11,3648,3649],{},"Entegrasyon tarafında ise bildirimin randevu akışından ayrı ele alınması belirleyici oldu: dış sistem cevap vermediğinde randevu yine oluşuyor, bildirim ise tekrar denenebilir bir iş olarak kalıyor. Aynı bildirimin ikinci kez gelmesi de sonucu değiştirmiyor.",[23,3651,3124],{"id":3123},[11,3653,3654],{},"Randevu ekranının basit görünmesi, arkadaki akışın tek adımdan oluştuğu anlamına gelmiyor. Ziyaretçi için \"randevu al\" tek bir düğme; arkasında operasyon eşleştirme, müsaitlik kontrolü, ödeme doğrulaması, kayıt oluşturma ve iki tarafın bilgilendirilmesi var.",[11,3656,3657],{},"Bu zincirin herhangi bir halkasında kalan kayıt, iki taraf için de belirsizlik üretiyor: hasta randevusunun var olduğunu sanıyor, klinik ise olmayan bir randevu için saat ayırıyor. Bu yüzden projede en çok uğraştığım şey mutlu senaryo değil, akışın ortasında kesildiği anlar oldu.",[86,3659,3660],{"title":359,"type":360},[11,3661,3662],{},"Bu sayfa projenin kapsamını ve teknik yaklaşımını anlatır. Gerçek hasta bilgileri, doktor kayıtları, ödeme sağlayıcısı sözleşmeleri, entegrasyon anahtarları ve kurum içi yetki ayrıntıları paylaşılmıyor.",{"title":365,"searchDepth":366,"depth":367,"links":3664},[3665,3666,3667,3668,3669,3670,3671,3672,3673,3674,3675,3676,3677,3678],{"id":2854,"depth":366,"text":2855},{"id":2864,"depth":366,"text":2865},{"id":3426,"depth":366,"text":3427},{"id":3459,"depth":366,"text":3460},{"id":3469,"depth":366,"text":3470},{"id":3493,"depth":366,"text":3494},{"id":3503,"depth":366,"text":3504},{"id":3260,"depth":366,"text":3261},{"id":3555,"depth":366,"text":3556},{"id":3042,"depth":366,"text":3043},{"id":3049,"depth":366,"text":3050},{"id":3079,"depth":366,"text":3080},{"id":3110,"depth":366,"text":3111},{"id":3123,"depth":366,"text":3124},"Healthcare","Çok dilli hasta randevu akışını doktor planlaması, online ödeme ve operasyon yönetimiyle tek uygulamada birleştiren randevu platformu.",{},"VA","\u002Fprojeler\u002Fvanity-appointment","Randevu talebi, doktor uygunluğu, ödeme sonucu ve operasyon bilgisinin birbirinden kopmadan tek bir kayıt üzerinde ilerlemesini sağlamak.",[],{"title":3397,"description":3680},"Ziyaretçiye açık çok dilli randevu akışı ile doktor, müsaitlik, operasyon ve ödeme yönetimini aynı uygulamada toplamak; ödeme doğrulamasını randevunun kesinleşme koşulu olarak kurgulamak.","projeler\u002Fvanity-appointment",[3679,3391,3167,2329],[3170,3171,3172,3173,3691],"Bootstrap","w2AqxAj0XOg8BAL4EIW99Mi8laAMSmvUsQGda26MJ60",{"id":3694,"title":3695,"body":3696,"category":3764,"cover":365,"demo":381,"description":3765,"draft":381,"extension":383,"featured":384,"meta":3766,"monogram":3767,"navigation":384,"order":467,"path":3768,"problem":3769,"screenshotLayout":3161,"screenshots":3770,"seo":3771,"solution":3772,"stem":3773,"tags":3774,"technologies":3775,"__hash__":3776},"projects\u002Fprojeler\u002Fvanity-payment.md","Vanity Ödeme Sayfası",{"type":8,"value":3697,"toc":3755},[3698,3700,3703,3705,3708,3712,3715,3717,3720,3722,3739,3741,3744,3746,3749],[23,3699,2855],{"id":2854},[11,3701,3702],{},"Bir ödeme ekranının sorumluluğu, formu gösterip kullanıcıyı yönlendirmekle bitmiyor. Ödeme dönüşünün doğrulanması, kaydın doğru duruma geçirilmesi ve CRM\u002Ffinans sistemlerinin sonuçtan haberdar edilmesi gerekiyor.",[23,3704,2865],{"id":2864},[11,3706,3707],{},"Vanity ödeme uygulaması, ziyaretçinin bilgilerini ve ödeme tercihini çok dilli bir arayüzde topluyor. Kredi kartı seçeneğinde ödeme sağlayıcısının ortak ödeme sayfasına yönlendiriyor; banka havalesinde gerekli referans ve hesap bilgisini sunuyor.",[23,3709,3711],{"id":3710},"teknik-mimari","Teknik Mimari",[11,3713,3714],{},"Laravel backend üzerinde ödeme kaydı, sağlayıcı yönlendirmesi ve sonuç doğrulaması bulunuyor. Doğrulanan sonuçlar ilgili kayda işleniyor; entegrasyon bildirimleri outbox yapısıyla ele alınıyor. Böylece ödeme sonucu ile dış sisteme bildirim ayrı adımlar olarak izlenebiliyor.",[23,3716,3043],{"id":3042},[11,3718,3719],{},"Laravel, PHP, MySQL, Blade, JavaScript, Bootstrap, SCSS ve Vite. Kart ödeme akışında ortak ödeme sayfası ve 3D Secure entegrasyonları bulunuyor.",[23,3721,3050],{"id":3049},[31,3723,3724,3727,3730,3733,3736],{},[34,3725,3726],{},"Çok dilli kart ve banka havalesi akışları.",[34,3728,3729],{},"Sağlayıcı callback ve işlem sonucu doğrulaması.",[34,3731,3732],{},"CRM kişi\u002Fişlem doğrulaması.",[34,3734,3735],{},"Idempotent outbox ve otomatik yeniden deneme.",[34,3737,3738],{},"Teknik servis hatalarında kontrollü sağlayıcı değişimi.",[23,3740,3111],{"id":3110},[11,3742,3743],{},"Outbox yaklaşımı, ödeme işleme ile entegrasyon bildiriminin birbirine sıkı şekilde bağlanmasını azaltıyor. Yeniden deneme ve idempotency, aynı sonucun tekrar ele alınabildiği durumlar için kullanılıyor. Sağlayıcı değişimi, ödeme sonucunun belirsiz olabileceği durumları gözeten kontrollü bir akış olarak ele alınıyor.",[23,3745,3124],{"id":3123},[11,3747,3748],{},"Kullanıcının ekranda gördüğü başarı mesajı ile backend’in doğruladığı sonuç aynı anlama gelmeli. Bu yüzden akıştaki belirleyici an, sağlayıcıdan dönüşün kendisi değil, o dönüşün doğrulanıp kayda işlendiği an oldu. Kullanıcıyı geri getiren yönlendirme bir bildirimdir; ödemenin gerçekleştiğinin kanıtı değildir.",[86,3750,3752],{"title":3751,"type":263},"Paylaşılmayanlar",[11,3753,3754],{},"Bu portfolyo metni gerçek kart, hasta, hesap veya işlem bilgisi içermez.",{"title":365,"searchDepth":366,"depth":367,"links":3756},[3757,3758,3759,3760,3761,3762,3763],{"id":2854,"depth":366,"text":2855},{"id":2864,"depth":366,"text":2865},{"id":3710,"depth":366,"text":3711},{"id":3042,"depth":366,"text":3043},{"id":3049,"depth":366,"text":3050},{"id":3110,"depth":366,"text":3111},{"id":3123,"depth":366,"text":3124},"Payments","Kart ve havale seçeneklerini sunan, ödeme sonuçlarını CRM ve finans sistemlerine aktaran çok dilli tahsilat uygulaması.",{},"VP","\u002Fprojeler\u002Fvanity-payment","Ödeme alırken ziyaretçiye anlaşılır bir akış sunmak ve doğrulanan sonucu ilgili sistemlere güvenilir biçimde aktarmak.",[],{"title":3695,"description":3765},"Ortak ödeme sayfası entegrasyonu, callback doğrulaması ve yeniden denenebilir bildirim akışı.","projeler\u002Fvanity-payment",[3764,3391],[3170,3171,3172,3173,3691],"3x6ZUSpnPTpFVx0Zonlzeft2o17TEuBn-JSAXGFcoUE",1788979557264]