﻿
{"id":25350,"date":"2022-07-25T17:18:02","date_gmt":"2022-07-25T17:18:02","guid":{"rendered":"https:\/\/www.gencayyildiz.com\/blog\/?p=25350"},"modified":"2022-07-25T17:18:02","modified_gmt":"2022-07-25T17:18:02","slug":"outbox-design-pattern-nedir-nasil-uygulanir-inbox-pattern-esliginde","status":"publish","type":"post","link":"https:\/\/www.gencayyildiz.com\/blog\/outbox-design-pattern-nedir-nasil-uygulanir-inbox-pattern-esliginde\/","title":{"rendered":"Outbox Design Pattern Nedir? Nas\u0131l Uygulan\u0131r? (Inbox Pattern E\u015fli\u011finde)"},"content":{"rendered":"<div id=\"fb-root\"><\/div>\n<p>Merhaba,<\/p>\n<p>G\u00fcnl\u00fck ya\u015famda gurbet ellerde bulunan bir yak\u0131n\u0131n\u0131za hat\u0131ra dolu bir mektup yaz\u0131p g\u00f6ndermek istedi\u011finiz olmu\u015ftur. Mesela b\u00f6yle bir istek kar\u015f\u0131l\u0131\u011f\u0131nda ne yapard\u0131n\u0131z hi\u00e7 d\u00fc\u015f\u00fcnd\u00fcn\u00fcz m\u00fc? Muhtemelen, mektubu kaleme ald\u0131ktan sonra zarflay\u0131p, g\u00f6nderilecek adres bilgisi e\u015fli\u011finde postaya b\u0131rak\u0131rd\u0131n\u0131z ve ard\u0131ndan gerisi posta memurlar\u0131n\u0131n i\u015f planlamas\u0131na kalaca\u011f\u0131ndan dolay\u0131 unutarak g\u00fcnl\u00fck rutininize d\u00f6nerdiniz. \u00d6yle de\u011fil mi? G\u00f6nderdi\u011finiz mektubun, var\u0131\u015f noktas\u0131na ne \u015fekilde hangi y\u00f6ntemlerle ne kadar zamanda varaca\u011f\u0131 sizi pek ilgilendirmezdi ve hatta mektubun bir g\u00fcn al\u0131c\u0131ya vard\u0131\u011f\u0131nda belki size geri d\u00f6n\u00fcp d\u00f6nmeyece\u011fini bile \u00f6nemsemeksizin ya\u015faman\u0131za devam ederdiniz. Gayet do\u011fal&#8230; Lakin g\u00f6ndermek istedi\u011finiz mektubu b\u0131rakaca\u011f\u0131n\u0131z posta te\u015fkilat\u0131n\u0131n \u00e7al\u0131\u015fmad\u0131\u011f\u0131 bir d\u00f6neme denk geldi\u011fini d\u00fc\u015f\u00fcn\u00fcrseniz e\u011fer vaz m\u0131 ge\u00e7erdiniz? Hay\u0131r dimi! Bu g\u00fcn \u00e7al\u0131\u015fmayan posta te\u015fkilat\u0131 elbet yar\u0131n belki yar\u0131ndan da yak\u0131n \u00e7al\u0131\u015f\u0131r diye mektubu elinizde tutar ve ne zaman te\u015fkilat faal olur o zaman gider mektubu postaya emanet ederdiniz.<\/p>\n<p>Buradaki varsay\u0131mdan \u015f\u00f6yle bir sonu\u00e7 \u00e7\u0131karabiliriz; yapt\u0131\u011f\u0131n\u0131z i\u015f her ne kadar sizden \u00e7\u0131ksa da b\u00fct\u00fcnsel olarak nihayetlenmedi\u011fi s\u00fcrece i\u015fin sorumlulu\u011fundan kurtuldunuz anlam\u0131na gelmemektedir. Siz mektubu yazm\u0131\u015f olabilirsiniz ama bazen posta te\u015fkilat\u0131na emanet etmek i\u00e7in de sabretmeniz gerekebilir. (Y\u0131lmaz komutan\u0131m\u0131n de\u011fimiyle i\u015f her ne kadar sizden \u00e7\u0131ksa da o i\u015fin \u00f6ld\u00fc\u011f\u00fcnden emin olman\u0131z ve gerekirse sizin \u00f6ld\u00fcrmeniz gerekmektedir!) Nas\u0131l ki, <em>-ben mektubu yazd\u0131m ama posta te\u015fkilat\u0131 \u00e7al\u0131\u015fmad\u0131\u011f\u0131 i\u00e7in g\u00f6nderemedim-<\/em> demek do\u011fru bir mazeret de\u011filse, da\u011f\u0131t\u0131k mimarilerdeki servisler aras\u0131 ileti\u015fim s\u00fcre\u00e7lerinde kaybolan mesajlar a\u00e7\u0131s\u0131ndan <em>-&#8216;A&#8217; servis g\u00f6ndermi\u015f &#8216;B&#8217; servis ayakta olmad\u0131\u011f\u0131 i\u00e7in veriyi alamam\u0131\u015f-<\/em> \u015feklinde bir durumda mazeret de\u011fildir. Ne de olsa, &#8216;A&#8217; servisin ilgili mesaj\u0131 \u00fcretmesi kadar &#8216;B&#8217; servise eri\u015ftirmesi de onun sorumlulu\u011fundad\u0131r.<\/p>\n<p>\u015eimdi gelin buradaki be\u015feri \u00f6rnek \u00fczerinden sadede gelelim ve sunulan davran\u0131\u015f\u0131 yaz\u0131l\u0131msal a\u00e7\u0131dan metafor ederek modelleyip, konumuz olan Outbox Design Pattern&#8217;a girizg\u00e2h eyleyelim.<\/p>\n<h3 style=\"color:#e83e8c;\">Outbox Design Pattern Nedir?<\/h3>\n<blockquote><p><em style=\"color:purple;\">Outbox pattern, <a href=\"https:\/\/www.enterpriseintegrationpatterns.com\/GuaranteedMessaging.html\" rel=\"noopener\" target=\"_blank\">Guaranteed Delivery Pattern<\/a>(Garantili Teslimat Deseni)&#8217;a dayan\u0131r!<\/em><\/p><\/blockquote>\n<p>Asenkron ileti\u015fim kuran sistemlerde, servisler aras\u0131(ya da servis ile message broker aras\u0131) ileti\u015fim s\u00fcre\u00e7lerinde g\u00f6nderilen mesajlar\u0131n yerini bulamadan ba\u011flant\u0131n\u0131n kopmas\u0131, hata al\u0131nmas\u0131 ve s\u00fcre\u00e7te beklenmeyen fiziksel yahut yaz\u0131l\u0131msal kesintilerin meydana gelmesi (vs.) durumlar\u0131na istinaden olas\u0131 veri kay\u0131plar\u0131na ve verisel a\u00e7\u0131dan servisler aras\u0131 tutars\u0131zl\u0131klara mahal vermemek i\u00e7in mesaj\u0131n kaybolmas\u0131n\u0131 engelleyerek, ya\u015fanan olumsuz durumun\/durumlar\u0131n ge\u00e7mesi \u00fczerine ba\u011flant\u0131n\u0131n tekrardan kurulmas\u0131 durumunda hedef noktaya mesaj\u0131n tekrardan g\u00f6nderilmesi i\u00e7in devreye giren basit ama kritik noktalarda muazzam etkili bir pattern&#8217;d\u0131r.<\/p>\n<h3 style=\"color:#e83e8c;\">Outbox Design Pattern&#8217;\u0131n Teorik Davran\u0131\u015f\u0131 Nas\u0131ld\u0131r?<\/h3>\n<p><div id=\"attachment_25359\" style=\"width: 1089px\" class=\"wp-caption alignleft\"><a href=\"https:\/\/www.gencayyildiz.com\/blog\/wp-content\/uploads\/2022\/07\/Outbox-Design-PatternOutbox-Tasarim-Deseni.png\"><img loading=\"lazy\" decoding=\"async\" aria-describedby=\"caption-attachment-25359\" src=\"https:\/\/www.gencayyildiz.com\/blog\/wp-content\/uploads\/2022\/07\/Outbox-Design-PatternOutbox-Tasarim-Deseni.png\" alt=\"Outbox Design Pattern(Outbox Tasar\u0131m Deseni)\" width=\"1079\" height=\"352\" class=\"size-full wp-image-25359\" srcset=\"https:\/\/www.gencayyildiz.com\/blog\/wp-content\/uploads\/2022\/07\/Outbox-Design-PatternOutbox-Tasarim-Deseni.png 1079w, https:\/\/www.gencayyildiz.com\/blog\/wp-content\/uploads\/2022\/07\/Outbox-Design-PatternOutbox-Tasarim-Deseni-300x98.png 300w, https:\/\/www.gencayyildiz.com\/blog\/wp-content\/uploads\/2022\/07\/Outbox-Design-PatternOutbox-Tasarim-Deseni-1024x334.png 1024w, https:\/\/www.gencayyildiz.com\/blog\/wp-content\/uploads\/2022\/07\/Outbox-Design-PatternOutbox-Tasarim-Deseni-768x251.png 768w\" sizes=\"auto, (max-width: 1079px) 100vw, 1079px\" \/><\/a><p id=\"caption-attachment-25359\" class=\"wp-caption-text\"><span style=\"font-size:11px;\">\u015eema Kayna\u011f\u0131 : https:\/\/github.com\/obsidiandynamics\/goharvest<\/span><\/p><\/div>Outbox pattern&#8217;\u0131 teoride anlatabilmek i\u00e7in yukar\u0131daki \u015femadan istifade edebiliriz. Bir servise gelen istek \u00fczerine yap\u0131lan i\u015flemler neticesinde farkl\u0131 bir servise yahut bir message broker&#8217;a yeni bir message\/event publish edilece\u011fini d\u00fc\u015f\u00fcnelim. Bu i\u015flem ihtiya\u00e7 an\u0131nda ger\u00e7ekle\u015ftirilirse e\u011fer g\u00f6nderilen mesaj\u0131n hedef servise yahut message broker&#8217;a vard\u0131\u011f\u0131ndan emin olmal\u0131, e\u011fer ki bir problem s\u00f6z konusu olursa bu servisin de yapt\u0131\u011f\u0131 t\u00fcm i\u015flemler b\u00fct\u00fcnsel tutarl\u0131l\u0131k i\u00e7in geri al\u0131nmal\u0131d\u0131r(<span style=\"font-size:12px;\">Bknz : <a target=\"_blank\" href=\"https:\/\/www.gencayyildiz.com\/blog\/microservice-mimarilerde-saga-pattern-ile-transaction-yonetimi\/\" rel=\"noopener\">Compensable Transaction<\/a><\/span>). Ama bu ne kadar do\u011fru olacakt\u0131r? Nihayetinde distributed bir mimaride bazen servislerin ge\u00e7ici s\u00fcreli\u011fine problem ya\u015famas\u0131 olduk\u00e7a normal bir durumdur. \u0130lgili servisin aya\u011fa kalkana kadar i\u015flenecek verilerin\/mesajlar\u0131n\/event&#8217;lerin tutulmas\u0131 ve servis aya\u011fa kalkt\u0131\u011f\u0131 zaman bunlar\u0131n i\u015flenmesi en ideal olan\u0131d\u0131r! Bizler yap\u0131lan i\u015flemleri geri almak yerine bu davran\u0131\u015f\u0131 tercih etmek istiyor olabiliriz. Aksi taktirde yap\u0131lanlar\u0131n geri al\u0131nmamas\u0131 ciddi manada veri tutars\u0131zl\u0131\u011f\u0131na sebebiyet verecektir. Yukar\u0131daki \u015femaya g\u00f6z atarsan\u0131z e\u011fer ilgili servisin gelen istek neticesinde g\u00f6revini tamamlad\u0131\u011f\u0131 ve art\u0131k i\u015fleyi\u015fin devam\u0131 i\u00e7in farkl\u0131 bir servise olay\u0131 f\u0131rlatmas\u0131 gerekmektedir. Bunu yaparken hedef servis t\u00fcrl\u00fc sebeplerden dolay\u0131 ayakta de\u011filse e\u011fer bu serviste yap\u0131lan i\u015flemler b\u00fct\u00fcnsel a\u00e7\u0131dan tutars\u0131zl\u0131k arz edecektir. \u0130\u015fte b\u00f6yle bir durumda d\u0131\u015f servis yahut message broker ile ileti\u015fim i\u015flemini direkt yapmak yerine ilgili message\/event&#8217;i giden kutusuna(Outbox) kar\u015f\u0131l\u0131k gelecek olan bir tabloya i\u015fleyip bekletmekte fayda vard\u0131r. Ard\u0131ndan d\u00fczenli aral\u0131klarla bu tabloyu tarayan ve orada kay\u0131tl\u0131 olan message\/event&#8217;leri hedef noktaya publish edecek olan &#8216;Outbox Publisher Application&#8217; arac\u0131l\u0131\u011f\u0131yla ilgili message\/event&#8217;ler al\u0131c\u0131 servislere iletilmeye \u00e7al\u0131\u015f\u0131l\u0131r. B\u00f6ylece mimari tasar\u0131m\u0131 a\u00e7\u0131s\u0131ndan, servisler aras\u0131 ileti\u015fim s\u00fcreci al\u0131c\u0131 servisin yahut message broker&#8217;\u0131n ayakta olup olmamas\u0131 durumuna g\u00f6re olas\u0131 veri kay\u0131plar\u0131 riskinden ar\u0131nd\u0131r\u0131larak daha ehemmiyetli olmas\u0131 sa\u011flanacak ve mesaj\u0131n en az bir kere hedefe varaca\u011f\u0131 garanti alt\u0131na al\u0131narak neticede servisler aras\u0131 loosely coupled(gev\u015fek ba\u011fl\u0131l\u0131k) konusunda bir ad\u0131m daha at\u0131lm\u0131\u015f olunacakt\u0131r.<\/p>\n<blockquote><p><em style=\"color:green;\">Sevisler aras\u0131 ileti\u015fim s\u00fcre\u00e7lerinde kaybolan mesajlar bir\u00e7ok geli\u015ftiricinin uykusunun ka\u00e7\u0131rmas\u0131n\u0131n temel kayna\u011f\u0131d\u0131r! O y\u00fczden bu konu ciddi kritik arz etmektedir!<\/em><\/p><\/blockquote>\n<p>Nihayetinde bakarsan\u0131z e\u011fer asenkron s\u00fcre\u00e7te servisler aras\u0131 ileti\u015fim verilerinin Outbox&#8217;da tutularak; olas\u0131 aksakl\u0131klardan, sistem kesintilerinden veya ba\u011flant\u0131 kopukluklar\u0131ndan dolay\u0131 olu\u015fabilecek ileti\u015fim ve veri transferi problemlerinden etkilenmemesi sa\u011flanarak b\u00fct\u00fcnl\u00fc\u011f\u00fcn korunmas\u0131 ama\u00e7lanmaktad\u0131r.<\/p>\n<p style=\"font-size:12px;\">\u0130\u00e7eri\u011fimize girizg\u00e2h eyledi\u011fimiz mektup misaliyle bu durumu kar\u015f\u0131la\u015ft\u0131r\u0131rsak e\u011fer; siz ve yak\u0131n\u0131n\u0131z iki servise, mektup ise aran\u0131zdaki ileti\u015fim s\u00fcrecindeki mesaj yahut event&#8217;e ve posta ise message broker&#8217;a kar\u015f\u0131l\u0131k gelmektedir. Outbox&#8217;\u0131n ise sizin yak\u0131n\u0131n\u0131za g\u00f6ndermek istedi\u011finiz mesaj\u0131n(mektubun) posta memurlar\u0131na verilip verilmedi\u011finin garantisini sa\u011flayan bir kontrol mekanizmas\u0131 oldu\u011funu d\u00fc\u015f\u00fcnebilirsiniz.<\/p>\n<p>Outbox pattern sayesinde servisler aras\u0131ndaki ileti\u015fim s\u00fcre\u00e7lerinde mesajlar\u0131n nihai olarak ba\u015far\u0131l\u0131 bir \u015fekilde hedef servis(ler)e iletildi\u011finden emin olabilir ve veri tutars\u0131zl\u0131\u011f\u0131na kar\u015f\u0131 b\u00fct\u00fcnsel a\u00e7\u0131dan \u00f6nleyici bir davran\u0131\u015f sergiledi\u011finizin bilinciyle g\u00f6n\u00fcl rahatl\u0131\u011f\u0131yla \u00e7al\u0131\u015fmalar\u0131n\u0131za devam edebilirsiniz.<\/p>\n<blockquote><p><em style=\"color:green;\">Outbox pattern distributed mimarilerde atomik bir davran\u0131\u015f sergilenmesini sa\u011flar.<\/em><\/p><\/blockquote>\n<p>Outbox pattern&#8217;\u0131 daha iyi anlayabilmek i\u00e7in <a href=\"https:\/\/www.gencayyildiz.com\/blog\/microservice-mimarilerde-saga-pattern-ile-transaction-yonetimi\/\" target=\"_blank\" rel=\"noopener\">Microservice Mimarilerde Saga Pattern \u0130le Transaction Y\u00f6netimi<\/a> ba\u015fl\u0131kl\u0131 makalemde teorik olarak de\u011findi\u011fim distributed transaction kavram\u0131n\u0131 ve bu kavram\u0131n uygulanabilir olmas\u0131 i\u00e7in ger\u00e7ekle\u015ftirilen belli ba\u015fl\u0131 teknikleri bilmeniz gerekmektedir. \u0130\u015fte t\u00fcm bunlar bir yana Outbox pattern sayesinde distributed transaction tekniklerini uygulamaya gerek kalmaks\u0131z\u0131n mesajlar\u0131 di\u011fer servislere eri\u015fim g\u00fcvenilirli\u011fini sa\u011flayarak g\u00f6nderebilir ve b\u00f6ylece uygulama b\u00fct\u00fcnl\u00fc\u011f\u00fc a\u00e7\u0131s\u0131ndan pratik olacak \u015fekilde daha emin ad\u0131mlarla veri tutarl\u0131l\u0131\u011f\u0131n\u0131 sa\u011flayabilirsiniz.<\/p>\n<blockquote><p><em>Microservice yakla\u015f\u0131mlar\u0131nda, kritik mesajlara sahip olan her bir servis kendi Outbox tablosuna sahip olmal\u0131 ve mesajlar\u0131 \u00f6nce bu tabloya at\u0131p ard\u0131ndan bir publisher arac\u0131l\u0131\u011f\u0131yla hedef servise ya da message broker&#8217;a g\u00f6ndermelidir.<\/em><\/p><\/blockquote>\n<h3 style=\"color:#e83e8c;\">Outbox Pattern Hangi Durumlarda Kullan\u0131l\u0131r?<\/h3>\n<p>Outbox pattern yukar\u0131daki sat\u0131rlarda vurgulad\u0131\u011f\u0131m\u0131z gibi bir servis taraf\u0131ndan mesajlar\/event&#8217;ler yay\u0131nland\u0131\u011f\u0131 esnada do\u011frudan hedef servise yahut message broker&#8217;a g\u00f6ndermek yerine bu mesajlar\u0131 bir veritaban\u0131 tablosuna kaydederek ard\u0131ndan bir publisher arac\u0131l\u0131\u011f\u0131yla belirli zaman aral\u0131klar\u0131nda veya CDC<a href=\"https:\/\/www.gencayyildiz.com\/blog\/debeziumcdc-ile-veritabanlarindaki-degisiklikleri-yakalama-postgresql-mssql-mongodb\/\" rel=\"noopener\" target=\"_blank\">(Change Data Capture)<\/a> arac\u0131l\u0131\u011f\u0131yla hedef servislere iletmeyi ama\u00e7layan bir pattern&#8217;d\u0131r. <\/p>\n<p>Buradan anla\u015f\u0131l\u0131yor ki, servislere ya da message broker&#8217;a g\u00f6nderilecek mesaja dair en az bir kere iletme garantisini sa\u011flamak istiyorsan\u0131z e\u011fer Outbox pattern kullan\u0131labilir.<\/p>\n<p>\u00d6zellikle bir serviste ayn\u0131 anda iki i\u015flemin yap\u0131ld\u0131\u011f\u0131 durum s\u00f6z konusuysa Outbox pattern bi\u00e7ilmi\u015f kaftan olacakt\u0131r. Buna herhangi bir e-ticaret uygulamas\u0131ndan k\u00fc\u00e7\u00fck bir kesiti misal olarak sunarsak e\u011fer; &#8216;Order Service&#8217; \u00fczerinde, kullan\u0131c\u0131dan gelen sipari\u015f \u00fczerine hem &#8216;Orders&#8217; tablosuna bir kay\u0131t at\u0131ld\u0131\u011f\u0131n\u0131 hem de sipari\u015fin olu\u015fturuldu\u011funa dair &#8216;OrderCreatedEvent&#8217; isimli bir event&#8217;in message broker&#8217;a yaz\u0131ld\u0131\u011f\u0131n\u0131 tahayy\u00fcl edelim&#8230; Bir servis yapt\u0131\u011f\u0131 i\u015flemler s\u00fcrecinde bunun gibi iki farkl\u0131 yap\u0131ya(veritaban\u0131-message broker) dair kal\u0131c\u0131 i\u015flem ger\u00e7ekle\u015ftiriyorsa e\u011fer bu durumu <strong style=\"color:red;\"><em>Dual Write<\/em><\/strong> olarak nitelendiriyoruz.<\/p>\n<blockquote><p><em>Belirli bir olay meydana geldi\u011finde hem veritaban\u0131na hem de bir message broker&#8217;a ayn\u0131 anda veri yazma i\u015flemine <u>Dual Write<\/u> denir.<\/em><\/p><\/blockquote>\n<p>\u0130\u015fte, Dual Write durumunun s\u00f6z konusu oldu\u011fu anlarda veritaban\u0131na kayd\u0131n at\u0131l\u0131p message broker&#8217;a g\u00f6nderilememesi yahut bunun tam tersi gibi aksilikler meydana gelirse e\u011fer burada hem o olay\u0131n ya\u015fand\u0131\u011f\u0131 servis hem de message broker&#8217;\u0131 dinleyen di\u011fer servis(ler) i\u00e7in ciddi verisel farklar yaratabilece\u011finden dolay\u0131 uygulama b\u00fct\u00fcnl\u00fc\u011f\u00fc a\u00e7\u0131s\u0131ndan tutars\u0131zl\u0131klar s\u00f6z konusu olabilir. Eee Dual Write&#8217;tan kaynaklanabilecek bu tutars\u0131zl\u0131klar sistem sa\u011fl\u0131kl\u0131yken ortaya \u00e7\u0131kmayabilir yahut fark edilmesi olduk\u00e7a g\u00fc\u00e7 olabilir ancak uzun vadede mutlaka kendilerini g\u00f6sterecektirler. \u0130\u015fte bu olas\u0131 tutars\u0131zl\u0131klar\u0131 s\u0131f\u0131r ihtimale indirgeyebilmek i\u00e7in Outbox pattern hayati bir rol \u00fcstlenebilir. Outbox pattern, veritaban\u0131nda yap\u0131lacak de\u011fi\u015fikli\u011fin ard\u0131ndan y\u00fcr\u00fct\u00fclecek olan i\u015flemleri, veritaban\u0131 i\u00e7erisinde bir g\u00f6rev olarak saklama temeline dayan\u0131r. B\u00f6ylece bir transaction i\u00e7erisinde hem veritaban\u0131nda yapmak istedi\u011fimiz i\u015flemi ger\u00e7ekle\u015ftiriyor hem de bu i\u015flemden sonra y\u00fcr\u00fctece\u011fimiz aksiyon i\u00e7in i\u015faret b\u0131rakm\u0131\u015f oluyoruz.<\/p>\n<p><em><strong>Unutmay\u0131n!<\/strong><\/em><br \/>\nDual Write; distributed, event based vs. gibi uygulamalarda s\u0131kl\u0131kla sorunlara neden olan bir durumdur. Ve bu sorunlar\u0131 tespit edebilmek, maliyetleri \u00f6l\u00e7ebilmek ve d\u00fczeltebilmek olduk\u00e7a zor ve zahmetli olacakt\u0131r!<\/p>\n<blockquote><p><em style=\"color:green;\">Outbox pattern <u>Dual Write<\/u> durumu varsa d\u00fc\u015f\u00fcn\u00fclmelidir!<\/em><\/p><\/blockquote>\n<h3 style=\"color:#e83e8c;\">Outbox Pattern vs Event Sourcing<\/h3>\n<p>Yukar\u0131daki sat\u0131rlar sizlere <a href=\"https:\/\/www.gencayyildiz.com\/blog\/event-sourcing-nedir-haydi-gelin-hep-beraber-inceleyelim\/\" target=\"_blank\" rel=\"noopener\">Event Sourcing<\/a>&#8216;i an\u0131msatm\u0131\u015f olabilir. Nihayetinde yap\u0131lacak i\u015flemlere dair(publish\/subscribe) bir tabloda tutulacak kay\u0131tlar s\u00f6z konusudur. Evet, Outbox pattern deyince akla event sourcing&#8217;in gelmesi normaldir lakin Outbox pattern event sourcing de\u011fildir. Belki alternatifidir diyebiliriz ama de\u011fildir! Outbox pattern ile event sourcing aras\u0131ndaki en radikal fark\u0131 ele almam\u0131z gerekirse e\u011fer; event sourcing&#8217;de bir i\u015flenen veriye dair t\u00fcm olaylar kay\u0131t alt\u0131na al\u0131n\u0131rken, Outbox&#8217;da ise i\u015flenecek olan mesajlar\u0131n\/event&#8217;lerin i\u015flendikten sonra silinmesi ya da i\u015flendi\u011fine dair g\u00fcncellenmesi s\u00f6z konusudur. Event sourcing&#8217;de kesinlikle herhangi bir olay\u0131n silinmesi s\u00f6z konusu de\u011fildir bilakis bir veriye dair yap\u0131lan bir i\u015flemin geri al\u0131nmas\u0131 bile yine ayr\u0131 bir olay olarak kabul edilmekte ve b\u00f6ylece meydana gelen olaylar\u0131n b\u00fct\u00fcn\u00fc ilgili verinin nihai halini ortaya sermekte ve s\u00fcre\u00e7te de ne gibi de\u011fi\u015fimler ya\u015fad\u0131\u011f\u0131n\u0131 bizlere s\u00f6ylemektedir.<\/p>\n<blockquote><p><em style=\"color:orange;\">Outbox pattern ile ilgili en \u00f6nemli husus yaln\u0131zca ge\u00e7ici bir mesaj\/olay deposu olmas\u0131d\u0131r. Mesajlar hedef servise ya da message broker&#8217;a ula\u015ft\u0131r\u0131ld\u0131\u011f\u0131 taktirde ya silinmeli ya da i\u015flendi\u011fine dair g\u00fcncellenmelidir.<\/em><\/p><\/blockquote>\n<h3 style=\"color:#e83e8c;\">Outbox Pattern&#8217;da Mesajlar Nas\u0131l Publish Edilmelidir?<\/h3>\n<p>Outbox pattern ile Outbox tablosuna kaydedilen mesajlar\u0131\/event&#8217;leri publish etmenin makul olan iki farkl\u0131 yolu vard\u0131r diyebiliriz;<\/p>\n<ul>\n<li><strong><em>Pulling Publisher<\/em><\/strong><br \/>\nOutbox tablosunda bulunan mesajlar\u0131 belirli bir zaman aral\u0131\u011f\u0131nda sorgulay\u0131p publish eden uygulaman\u0131n geli\u015ftirilmesi y\u00f6ntemidir. Bu uygulama basit bir console application ya da worker service olabilir. \u0130\u00e7eri\u011fimiz s\u00fcrecinde bizlerin pulling publisher&#8217;\u0131 \u00f6rneklendirirken worker service \u00fczerinden bir geli\u015ftirme yapt\u0131\u011f\u0131m\u0131z\u0131 g\u00f6receksiniz.<\/p>\n<p>Pulling publisher uygulamas\u0131n\u0131n Outbox tablosunu sorgulama zaman aral\u0131\u011f\u0131 ne kadar k\u00fc\u00e7\u00fckse veritaban\u0131na maliyeti do\u011fru orant\u0131l\u0131 bir \u015fekilde artacakt\u0131r. Bu durum ise ilgili y\u00f6ntemin tek ve \u00f6z dezavantaj\u0131d\u0131r diyebiliriz.<\/p>\n<p>Ayr\u0131ca bu y\u00f6ntemde dikkat edilmesi gereken bir di\u011fer husus ise; geli\u015ftirilen publisher uygulamas\u0131n\u0131 scale etmek istedi\u011finiz taktirde Outbox&#8217;da ki herhangi bir mesaj\u0131 farkl\u0131 instance&#8217;lar da tekrar i\u015fleme olas\u0131l\u0131\u011f\u0131n\u0131n olmas\u0131d\u0131r. Bu durumu engelleyebilmek i\u00e7in ise kullan\u0131lan veritaban\u0131na g\u00f6re baz\u0131 \u00f6nlemlerin al\u0131nmas\u0131 gerekmektedir. Bu \u00f6nlemlerin bir k\u0131sm\u0131 a\u015fa\u011f\u0131daki veritabanlar\u0131yla sa\u011flanabilir;<\/p>\n<ul>\n<li><em>MSSQL;<\/em> UPLOCK(Prevent Multiple Read)<br \/>\n<span style=\"font-size:12px;\">Kaynak i\u00e7in : <a href=\"https:\/\/dba.stackexchange.com\/questions\/48686\/uplock-to-avoid-multiple-reads\/137822#137822\" rel=\"noopener\" target=\"_blank\">https:\/\/dba.stackexchange.com\/questions\/48686\/uplock-to-avoid-multiple-reads\/137822#137822<\/a><\/span><\/li>\n<li><em>NOSQL;<\/em> findAndModify<br \/>\n<span style=\"font-size:12px;\">Kaynak i\u00e7in : <a href=\"https:\/\/www.mongodb.com\/docs\/manual\/reference\/command\/findAndModify\/\" rel=\"noopener\" target=\"_blank\">https:\/\/www.mongodb.com\/docs\/manual\/reference\/command\/findAndModify\/<\/a><\/span><\/li>\n<\/ul>\n<p>Son olarak bu y\u00f6ntemin uygulama basitli\u011fi a\u00e7\u0131s\u0131ndan avantajl\u0131 oldu\u011funu s\u00f6yleyebiliriz.\n<\/li>\n<li><strong><em>Transaction Log Tailing<\/em><\/strong><br \/>\nOutbox table&#8217;\u0131n bulundu\u011fu veritaban\u0131n\u0131n transaction loglar\u0131n\u0131 okuyarak de\u011fi\u015fiklikleri yakalay\u0131p mesajlar\u0131 publish etme y\u00f6ntemidir. Konuya dair <a href=\"https:\/\/www.gencayyildiz.com\/blog\/debeziumcdc-ile-veritabanlarindaki-degisiklikleri-yakalama-postgresql-mssql-mongodb\/\" rel=\"noopener\" target=\"_blank\">Debezium<\/a>, <a href=\"https:\/\/eventuate.io\/abouteventuatetram.html\" rel=\"noopener\" target=\"_blank\">Eventuate Tram<\/a> vs. incelenebilir.<\/p>\n<p>Genel k\u00fclt\u00fcr olmas\u0131 a\u00e7\u0131s\u0131ndan bu y\u00f6ntemin event sourcing i\u00e7in geli\u015ftirilmi\u015f olan <a href=\"https:\/\/www.gencayyildiz.com\/blog\/net-core-ortaminda-event-store-ile-event-sourcing-yapilanmasi\/\" rel=\"noopener\" target=\"_blank\">Event Store<\/a> arac\u0131nda da kullan\u0131ld\u0131\u011f\u0131n\u0131 bilmenizde fayda vard\u0131r!\n<\/li>\n<\/ul>\n<h3 style=\"color:#e83e8c;\">Outbox Pattern&#8217;\u0131n Kullan\u0131laca\u011f\u0131 \u00d6rnek Senaryo Bile\u015fenleri Neler Olabilir?<\/h3>\n<p>Outbox pattern&#8217;\u0131n hangi durumlarda kullan\u0131laca\u011f\u0131n\u0131 yukar\u0131daki sat\u0131rlarda <em>Outbox Pattern Hangi Durumlarda Kullan\u0131l\u0131r?<\/em> ba\u015fl\u0131\u011f\u0131 alt\u0131nda de\u011ferlendirmeye \u00e7al\u0131\u015fm\u0131\u015ft\u0131k. Burada ise bahsedilen durumlar\u0131 ya\u015fayabilece\u011fimiz senaryolara birka\u00e7 \u00f6rnek vermek faydal\u0131 olacakt\u0131r kanaatindeyim;<\/p>\n<ul>\n<li><em>Bir sipari\u015f verildikten sonra kullan\u0131c\u0131ya e-posta g\u00f6ndermek,<\/em><\/li>\n<li><em>Kullan\u0131c\u0131 kayd\u0131 hakk\u0131nda message broker&#8217;a bir event g\u00f6ndermek,<\/em><\/li>\n<li><em>Bir sipari\u015f verildikten sonra stoktaki \u00fcr\u00fcn say\u0131s\u0131n\u0131 g\u00fcncellemek<\/em><\/li>\n<\/ul>\n<p>vs. gibi t\u00fcm senaryolarda Outbox pattern ile yap\u0131lacak i\u015flemin devam\u0131n\u0131 kayda alabilir ve veri kayb\u0131n\u0131 \u00f6nceleyecek \u015fekilde bir publisher e\u015fli\u011finde di\u011fer i\u015fi \u00fcstlenen servis yahut message broker ile ileti\u015fim kurabilirsiniz.<\/p>\n<h3 style=\"color:#e83e8c;\">Outbox Pattern&#8217;\u0131n Idempotents Sorunsal\u0131!<\/h3>\n<p>Outbox pattern&#8217;de, publish edilen mesajlar\u0131n ya da event&#8217;lerin i\u015flendi\u011fine dair i\u015faretlenmesine yahut tablodan i\u015flenenlerin silinmesine ra\u011fmen d\u00fc\u015f\u00fck ihtimalde olsa bir handikap s\u00f6z konusu olabilir. Bu handikap, mesajlar i\u015flendikten\/publish edildikten sonra s\u0131ra Outbox table \u00fczerinde i\u015faretlenmeye yahut ilgili mesaj\u0131n silinmesi i\u015flemine gelindi\u011finde ilgili veritaban\u0131 ile olu\u015fabilecek bir olas\u0131 ileti\u015fim hatas\u0131 nedeniyle bu i\u015flemin ger\u00e7ekle\u015ftirilememesidir. Evet, mesaj yay\u0131nlanm\u0131\u015ft\u0131r ama bu yay\u0131nlama neticede Outbox table&#8217;a yans\u0131t\u0131lamam\u0131\u015ft\u0131r. B\u00f6yle bir durumda Outbox table&#8217;\u0131n bulundu\u011fu veritaban\u0131 ile tekrar ileti\u015fim kuruldu\u011funda i\u015flenmi\u015f olan mesaj tekrardan i\u015fleme tabi tutulacak ve uygulama a\u00e7\u0131s\u0131ndan b\u00fct\u00fcnsel tutarl\u0131l\u0131\u011f\u0131 sarsabilecek bir durum meydana gelebilecektir. \u0130\u015fte \u015fimdi <em><strong>Peki bu duruma kar\u015f\u0131 nas\u0131l bir davran\u0131\u015f sergilememiz gerekmektedir?<\/strong><\/em> sorusu ak\u0131llara gelmektedir&#8230;<\/p>\n<p> Bu duruma istinaden mesajlar\u0131\/event&#8217;leri t\u00fcketen consumer&#8217;lar\u0131n <strong style=\"color:red;\"><em>Idempotents<\/em><\/strong> olarak tasarlanmas\u0131 bu olas\u0131 hatay\u0131 ortadan kald\u0131racakt\u0131r.<\/p>\n<p><strong style=\"color:red;\"><em>Idempotent<\/em><\/strong>; matematik ve bilgisayar bilimlerinde kullan\u0131lan ve ilk i\u015flem haricinde sonraki t\u00fcm i\u015flemler i\u00e7in etkisi s\u00f6z konusu olmayan ve sonucu de\u011fi\u015ftirmeden uygulanabilen bir \u00f6zelli\u011fi ifade eden terminolojik bir terimdir. Matematiksel olarak <strong style=\"color:green;\"><code>f(f(x)) = f(x)<\/code><\/strong> \u015feklinde form\u00fcl\u00fcze edilebilir.<\/p>\n<p>Idempotent&#8217;e matematiksel \u00f6rnek olarak <u>mutlak de\u011feri<\/u> verebiliriz. Nihayetinde mutlak de\u011ferin amac\u0131 bir say\u0131n\u0131n s\u0131f\u0131ra olan uzakl\u0131\u011f\u0131n\u0131 \u00f6l\u00e7mek oldu\u011fu i\u00e7in |n| ile |-n| i\u015flemlerini ne kadar de\u011ferlendirirsek de\u011ferlendirelim, bu iki \u00f6l\u00e7\u00fcmde sonu\u00e7 a\u00e7\u0131s\u0131ndan herhangi bir fark hi\u00e7bir zaman s\u00f6z konusu olmayacakt\u0131r.<\/p>\n<p>Idempotent kavram\u0131na biz yaz\u0131l\u0131mc\u0131lar\u0131n anlayaca\u011f\u0131 dilden daha farkl\u0131 \u00f6rnekler verebilmemiz a\u00e7\u0131s\u0131ndan rest mimarisi \u00fczerinden de bir metafor ger\u00e7ekle\u015ftirebiliriz. Rest mimarisinde; <code>get<\/code>, <code>put<\/code> ve <code>delete<\/code> action&#8217;lar\u0131 idempotent iken, <code>post<\/code> metodu idempotent de\u011fildir! \u00c7\u00fcnk\u00fc <code>get<\/code>, <code>put<\/code> ve <code>delete<\/code> her ne kadar tekrarl\u0131 tetiklenirse tetiklensinler yapt\u0131klar\u0131 ilk i\u015flemin d\u0131\u015f\u0131nda ekstradan sonu\u00e7 \u00fcretemezler. Yani \u00f6rnek olarak, &#8216;3&#8217; id de\u011ferine sahip bir kullan\u0131c\u0131y\u0131 silmek(delete) istiyorsan\u0131z e\u011fer bunu ilk istekte ger\u00e7ekle\u015ftirir, sonraki isteklerde ise &#8216;3&#8217; id de\u011ferine kar\u015f\u0131l\u0131k bir kullan\u0131c\u0131 olmayaca\u011f\u0131ndan dolay\u0131 ekstradan bir sonu\u00e7 \u00fcretecek i\u015flem ger\u00e7ekle\u015ftiremezsiniz. Veya <code>update products set name='abc'<\/code> sorgusunu ilk \u00e7al\u0131\u015ft\u0131rd\u0131\u011f\u0131n\u0131zda t\u00fcm \u00fcr\u00fcnlerin ad\u0131 &#8216;abc&#8217; olacak sonraki tetiklemelerde ise bu sorgu bir de\u011fi\u015fikli\u011fe yol a\u00e7mayacakt\u0131r. \u0130\u015fte bundan dolay\u0131 bu \u00fc\u00e7\u00fc idempotent \u00f6zellik sergilemektedir. Amma vel\u00e2kin, ad\u0131 &#8216;Hilmi&#8217; olan bir kullan\u0131c\u0131y\u0131 eklemek(post) isterseniz her istek g\u00f6nderdi\u011finizde ad\u0131 &#8216;Hilmi&#8217; olan bir kullan\u0131c\u0131 eklenecektir. Bu da sonucu etkileyecektir. \u00c7\u00fcnk\u00fc ilk istekte ad\u0131 &#8216;Hilmi&#8217; olan kullan\u0131c\u0131 say\u0131s\u0131 n iken, be\u015finci istekte n + 5 olacakt\u0131r. Haliyle <code>post<\/code> action&#8217;\u0131 netice itibariyle veritaban\u0131 \u00fczerinde i\u015flemden \u00f6nceki son hale nazaran de\u011fi\u015fiklik\/fark meydana getirece\u011fi i\u00e7in idempotent \u00f6zellik g\u00f6stermeyen bir fonksiyondur.<\/p>\n<p>Onca \u00f6rne\u011fin yan\u0131nda bir de g\u00fcnl\u00fck hayattan bir misalde bulunal\u0131m isterim. Diyelim ki X bankas\u0131n\u0131n bankamati\u011fine gittiniz ve bakiyenizi g\u00f6rmek istediniz. \u0130\u015fte bu i\u015flem bir idempotent i\u015flemdir. Nihayetinde bankamatik \u00fczerinden hesab\u0131n\u0131zdaki bakiyeyi g\u00f6rme i\u015flemini ne kadar yaparsan\u0131z yap\u0131n sonu\u00e7 de\u011fi\u015fmeyecektir. Lakin ayn\u0131 hesap \u00fczerinden para \u00e7ekme ya da para yat\u0131rma i\u015flemi yapman\u0131z idempotent olmayan bir i\u015flemdir. \u00c7\u00fcnk\u00fc yap\u0131lan her bir i\u015flem neticesinde bakiye de\u011feri de\u011fi\u015fecektir.<\/p>\n<p>Yukar\u0131da verilen \u00f6rneklerden yola \u00e7\u0131karak konumuz olan distributed yap\u0131lanmalarda idempotent davran\u0131\u015f\u0131n\u0131 genellersek e\u011fer, bir mesaj birden \u00e7ok kez yay\u0131nlansa dahi consumer&#8217;lar a\u00e7\u0131s\u0131ndan ayn\u0131 etkiye sahip bir i\u015flevsellikte olmas\u0131 gerekmektedir. Bu durum da bir mesaj\u0131n herhangi bir soruna yol a\u00e7maks\u0131z\u0131n g\u00fcvenlice yeniden g\u00f6nderilebilece\u011fini ifade edebiliriz.<\/p>\n<blockquote><p><em>Distributed mimarilerde, servisler aras\u0131 yay\u0131nlanacak mesajlar\u0131n yahut event&#8217;lerin idempotent olarak tasarlanmas\u0131 do\u011fru ve tutarl\u0131 davran\u0131\u015f i\u00e7in gerekli oland\u0131r.<\/em><\/p><\/blockquote>\n<p><strong><em>Peki, yay\u0131nlanacak mesajlarda idempotency&#8217;yi ba\u015farman\u0131n yolu nedir?<\/em><\/strong><br \/>\nBunun i\u00e7in publish edilecek olan her bir mesaj yahut event i\u00e7in \u00f6zel bir anahtar, token veya id gibi de\u011fer \u00fcretilmeli ve b\u00f6ylece consumer&#8217;lar taraf\u0131ndan bu mesaj daha \u00f6nceden t\u00fcketilmi\u015f mi? t\u00fcketilmemi\u015f mi? ay\u0131rt edilebilir bir nitelik kazand\u0131r\u0131lmal\u0131d\u0131r. Bunu \u015f\u00f6yle \u00f6rneklendirebiliriz;<a href=\"https:\/\/www.gencayyildiz.com\/blog\/wp-content\/uploads\/2022\/07\/Outbox-Design-PatternOutbox-Tasarim-Deseni-1.png\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/www.gencayyildiz.com\/blog\/wp-content\/uploads\/2022\/07\/Outbox-Design-PatternOutbox-Tasarim-Deseni-1.png\" alt=\"Outbox Design Pattern(Outbox Tasar\u0131m Deseni)\" width=\"1379\" height=\"257\" class=\"aligncenter size-full wp-image-25378\" srcset=\"https:\/\/www.gencayyildiz.com\/blog\/wp-content\/uploads\/2022\/07\/Outbox-Design-PatternOutbox-Tasarim-Deseni-1.png 1379w, https:\/\/www.gencayyildiz.com\/blog\/wp-content\/uploads\/2022\/07\/Outbox-Design-PatternOutbox-Tasarim-Deseni-1-300x56.png 300w, https:\/\/www.gencayyildiz.com\/blog\/wp-content\/uploads\/2022\/07\/Outbox-Design-PatternOutbox-Tasarim-Deseni-1-1024x191.png 1024w, https:\/\/www.gencayyildiz.com\/blog\/wp-content\/uploads\/2022\/07\/Outbox-Design-PatternOutbox-Tasarim-Deseni-1-768x143.png 768w\" sizes=\"auto, (max-width: 1379px) 100vw, 1379px\" \/><\/a>Yukar\u0131daki g\u00f6rsel \u015femay\u0131 incelerseniz e\u011fer Outbox table&#8217;a kay\u0131t at\u0131lan verilere bir &#8216;IdempotentToken&#8217; de\u011feri e\u015flik etmektedir. Bu \u015fekilde publisher ile message broker&#8217;a g\u00f6nderilen mesajlar consumer taraf\u0131nda i\u015flenirken \u00f6ncelikle &#8216;Inbox Table&#8217; ad\u0131n\u0131 verdi\u011fimiz bir tabloya i\u015flenmeli ard\u0131ndan process edilmelidirler. Inbox table&#8217;da e\u011fer ki ayn\u0131 &#8216;IdempotentToken&#8217; de\u011ferine sahip bir kay\u0131t varsa i\u015flem ger\u00e7ekle\u015ftirilmemeli, e\u011fer yoksa process edilmelidir. Burada kullan\u0131lan y\u00f6ntem \u00f6z\u00fcnde <em><strong>Inbox Pattern<\/strong><\/em>&#8216;\u0131 bar\u0131nd\u0131rmaktad\u0131r.<\/p>\n<p><em style=\"color:green;\"><strong>Inbox Pattern<\/strong><\/em><br \/>\nOutbox pattern&#8217;\u0131n akrabas\u0131 olan bir pattern&#8217;d\u0131r diyebiliriz \ud83d\ude42 Outbox pattern&#8217;da i\u015flemler(algoritmalar) ger\u00e7ekle\u015ftirilip, yay\u0131nlanacak mesajlar\u0131n zemini olu\u015fturulduktan sonra Outbox table&#8217;a kaydedilip ard\u0131ndan bir publisher arac\u0131l\u0131\u011f\u0131yla yay\u0131nlama i\u015flemi ger\u00e7ekle\u015ftirilirken, Inbox pattern&#8217;da ise i\u015flenecek(algoritmas\u0131 ger\u00e7ekle\u015ftirilecek) mesajlar \u00f6nce Inbox table&#8217;a eklenip ard\u0131ndan process edilirler. Misal olarak; gelen sipari\u015fi veritaban\u0131na kaydedip ard\u0131ndan stok bilgisinin g\u00fcncellenmesi i\u00e7in Outbox table&#8217;a bu sipari\u015fle ilgili bir kayd\u0131n olu\u015fturulup publisher ile message broker&#8217;a g\u00f6nderilmesi Outbox pattern iken, stok bilgilerini g\u00fcncellemekten sorumlu consumer&#8217;\u0131n ilgili message broker&#8217;dan bu mesajlar\u0131 al\u0131p \u00f6nce Inbox table&#8217;a i\u015fleyip ard\u0131ndan process&#8217;e tabi tutmas\u0131 Inbox Pattern&#8217;d\u0131r. Tabi burada i\u015flenen mesajlar\u0131n Inbox table&#8217;da nihai olarak tamamland\u0131\u011f\u0131na dair g\u00fcncellenmesi gerekmektedir.<\/p>\n<blockquote><p><em>Baz\u0131 consumer&#8217;lar davran\u0131\u015flar\u0131 itibar\u0131yla varsay\u0131lan olarak idempotent&#8217;tirler.<\/em><\/p><\/blockquote>\n<h3 style=\"color:#e83e8c;\">Outbox Table&#8217;\u0131n \u015eemas\u0131 Nas\u0131l Olmal\u0131d\u0131r?<\/h3>\n<p>Outbox table&#8217;\u0131n yap\u0131s\u0131n\u0131 olu\u015fturabilmek i\u00e7in a\u015fa\u011f\u0131daki gibi bir tasar\u0131mda bulunmak yeterlidir kanaatindeyim.<\/p>\n<div style=\"font-size:12px;\">\n<pre class=\"brush: plain; title: ; notranslate\" title=\"\">\r\nCREATE TABLE &#x5B;dbo].&#x5B;OutboxTable]\r\n(\r\n\t&#x5B;OccuredOn] &#x5B;datetime2](7) NOT NULL,\r\n\t&#x5B;ProcessedDate] &#x5B;datetime2](7) NULL,\r\n\t&#x5B;Type] &#x5B;nvarchar](max) NOT NULL,\r\n\t&#x5B;Payload] &#x5B;nvarchar](max) NOT NULL,\r\n\t&#x5B;IdempotentToken] &#x5B;uniqueidentifier] NOT NULL\r\n)\r\n<\/pre>\n<\/div>\n<p><a href=\"https:\/\/www.gencayyildiz.com\/blog\/wp-content\/uploads\/2022\/07\/Outbox-Design-PatternOutbox-Tasarim-Deseni-2.png\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/www.gencayyildiz.com\/blog\/wp-content\/uploads\/2022\/07\/Outbox-Design-PatternOutbox-Tasarim-Deseni-2.png\" alt=\"Outbox Design Pattern(Outbox Tasar\u0131m Deseni)\" width=\"299\" height=\"125\" class=\"alignleft size-full wp-image-25383\" \/><\/a>Bu tablodaki kolon yap\u0131s\u0131n\u0131 izah etmemiz gerekirse e\u011fer;<\/p>\n<ul>\n<li><strong><em>OccuredOn<\/em><\/strong>, mesaj\u0131n olu\u015fturuldu\u011fu tarih bilgisini tutar.<\/li>\n<li><strong><em>ProcessedDate<\/em><\/strong>, mesaj\u0131n i\u015flendi\u011fi tarih bilgisini tutar. Mesaj yay\u0131nlanmad\u0131\u011f\u0131 s\u00fcrece null de\u011ferine sahip olacakt\u0131r. Yay\u0131nland\u0131ktan sonra o an\u0131n tarih de\u011feri atanacakt\u0131r. \u0130sterseniz bu bilgiyi i\u015flemek yerine mesaj\u0131 silebilirsiniz!<\/li>\n<li><strong><em>Type<\/em><\/strong>, publish edilecek mesaj\u0131n ya da event&#8217;in t\u00fcr\u00fcn\u00fc tutar.<\/li>\n<li><strong><em>Payload<\/em><\/strong>, mesaj\u0131n json format\u0131ndaki verisini tutar.<\/li>\n<li><strong><em>IdempotentToken<\/em><\/strong>, consumer&#8217;lar taraf\u0131ndan yinelenebilecek mesajlar\u0131n tespit edilebilmesi i\u00e7in mesaja idempotent \u00f6zelli\u011fi ekleyen ve bir yandan da mesaj\u0131n benzersiz kimli\u011fe sahip olmas\u0131n\u0131 sa\u011flayan veriyi tutar.<\/li>\n<\/ul>\n<h3 style=\"color:#e83e8c;\">Outbox Pattern&#8217;\u0131 Pratikte \u00d6rneklendirelim<\/h3>\n<p>\u015eimdi bu i\u00e7eri\u011fimizin en kritik noktas\u0131na gelmi\u015f bulunuyoruz. Outbox pattern&#8217;\u0131 pratikte ele alabilmek i\u00e7in \u00f6rneklendiriyor olaca\u011f\u0131z. Tabi bunun i\u00e7in <a href=\"https:\/\/www.gencayyildiz.com\/blog\/nedir-bu-onion-architecture-tam-teferruatli-inceleyelim\/\" rel=\"noopener\" target=\"_blank\">Onion Architecture<\/a> altyap\u0131s\u0131nda bir \u00e7al\u0131\u015fma sergileyecek ve mesajlar\u0131n publish edilmesi i\u00e7in makale s\u00fcrecinde bahsetti\u011fimiz <em>Pulling Publisher<\/em> ve <em>Transaction Log Tailing<\/em> y\u00f6ntemlerini teker teker uygulay\u0131p, \u00f6rneklendirece\u011fiz. Ayr\u0131ca \u00f6rneklendirme s\u00fcrecinde yine \u00f6nceki sat\u0131rlarda bahsetti\u011fimiz <em>Inbox Pattern<\/em>&#8216;\u0131 da uygulayarak olmas\u0131 gereken bir altyap\u0131yla somut bir \u00f6rnek ortaya koymu\u015f olaca\u011f\u0131z.<\/p>\n<p>\u00d6rneklendirme s\u00fcrecinde senaryo olarak genel e-ticaret s\u00fcre\u00e7lerindeki basit bir sipari\u015f alma ve stok g\u00fcncelleme i\u015flemlerini ayr\u0131 servisler \u015feklinde Outbox pattern e\u015fli\u011finde ele al\u0131yor olaca\u011f\u0131z.<\/p>\n<p>O halde buyrun ba\u015flayal\u0131m&#8230;<\/p>\n<ul>\n<li><em>Ad\u0131m 1 <span style=\"font-size:12px;color:green;\">(Order.API projesinin altyap\u0131s\u0131n\u0131n olu\u015fturulmas\u0131)<\/span><\/em><br \/>\n<a href=\"https:\/\/www.gencayyildiz.com\/blog\/wp-content\/uploads\/2022\/07\/Outbox-Design-PatternOutbox-Tasarim-Deseni-3.png\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/www.gencayyildiz.com\/blog\/wp-content\/uploads\/2022\/07\/Outbox-Design-PatternOutbox-Tasarim-Deseni-3.png\" alt=\"Outbox Design Pattern(Outbox Tasar\u0131m Deseni)\" width=\"256\" height=\"155\" class=\"alignleft size-full wp-image-25388\" \/><\/a>Her \u015feyden \u00f6nce ilk olarak sipari\u015f i\u015flemlerinden sorumlu olan Order.API projesini Onion Architecture altyap\u0131s\u0131nda olu\u015fturarak ba\u015flayal\u0131m. Projenin fiziksel yap\u0131s\u0131n\u0131 <a href=\"https:\/\/www.gencayyildiz.com\/blog\/nedir-bu-onion-architecture-tam-teferruatli-inceleyelim\/\" rel=\"noopener\" target=\"_blank\">Nedir Bu Onion Architecture? Tam Teferruatl\u0131 \u0130nceleyelim<\/a> ba\u015fl\u0131kl\u0131 makalemizde ele ald\u0131\u011f\u0131m\u0131z gibi in\u015fa ederek temellendirelim.\n<\/li>\n<li><em>Ad\u0131m 2 <span style=\"font-size:12px;color:green;\">(Entity&#8217;lerin olu\u015fturulmas\u0131)<\/span><\/em><br \/>\n&#8216;Domain&#8217; katman\u0131nda, uygulama s\u00fcrecinde \u00f6rnek ama\u00e7l\u0131 sipari\u015fleri temsil edecek olan &#8216;Order&#8217; ve bu sipari\u015fler olu\u015fturulduktan sonra event&#8217;inin publish edilmesi i\u00e7in Outbox table&#8217;a kar\u015f\u0131l\u0131k gelecek olan &#8216;OrderOutbox&#8217; entity&#8217;lerini olu\u015ftural\u0131m.<\/p>\n<div style=\"font-size:12px;\">\n<pre class=\"brush: jscript; title: ; notranslate\" title=\"\">\r\n    public class Order\r\n    {\r\n        public Guid Id { get; set; }\r\n        public int Quantity { get; set; }\r\n        public string Description { get; set; }\r\n        \/\/public ICollection&lt;Product&gt; Products { get; set; } ... Example\r\n    }\r\n<\/pre>\n<\/div>\n<div style=\"font-size:12px;\">\n<pre class=\"brush: jscript; title: ; notranslate\" title=\"\">\r\n    public class OrderOutbox\r\n    {\r\n        public OrderOutbox()\r\n        {\r\n        }\r\n        public DateTime OccuredOn { get; set; }\r\n        public DateTime? ProcessedDate { get; set; }\r\n        public string @Type { get; set; }\r\n        public string Payload { get; set; }\r\n        public Guid IdempotentToken { get; set; }\r\n    }\r\n<\/pre>\n<\/div>\n<\/li>\n<li><em>Ad\u0131m 3 <span style=\"font-size:12px;color:green;\">(Context s\u0131n\u0131f\u0131n\u0131n olu\u015fturulmas\u0131, veritaban\u0131n\u0131n migrate edilmesi)<\/span><\/em><br \/>\n&#8216;Persistence&#8217; katman\u0131nda a\u015fa\u011f\u0131daki context nesnesini olu\u015ftural\u0131m.<\/p>\n<div style=\"font-size:12px;\">\n<pre class=\"brush: jscript; title: ; notranslate\" title=\"\">\r\n    public class OutboxExampleDbContext : DbContext\r\n    {\r\n        public OutboxExampleDbContext(DbContextOptions options) : base(options)\r\n        {\r\n        }\r\n        public DbSet&lt;Order&gt; Orders { get; set; }\r\n        public DbSet&lt;OrderOutbox&gt; OrderOutboxes { get; set; }\r\n        protected override void OnModelCreating(ModelBuilder modelBuilder)\r\n        {\r\n            modelBuilder.ApplyConfiguration(new OrderConfiguration());\r\n            modelBuilder.ApplyConfiguration(new OrderOutboxConfiguration());\r\n        }\r\n    }\r\n<\/pre>\n<\/div>\n<p>Burada <strong><em>10<\/em><\/strong> ve <strong><em>11.<\/em><\/strong> sat\u0131rlarda kullan\u0131lan konfig\u00fcrasyon s\u0131n\u0131flar\u0131n\u0131n i\u00e7eri\u011fi a\u015fa\u011f\u0131daki gibidir.<\/p>\n<div style=\"font-size:12px;\">\n<pre class=\"brush: jscript; title: ; notranslate\" title=\"\">\r\n    public class OrderConfiguration : IEntityTypeConfiguration&lt;Order&gt;\r\n    {\r\n        public void Configure(EntityTypeBuilder&lt;Order&gt; builder)\r\n        {\r\n            builder.HasKey(p =&gt; p.Id);\r\n        }\r\n    }\r\n<\/pre>\n<\/div>\n<div style=\"font-size:12px;\">\n<pre class=\"brush: jscript; title: ; notranslate\" title=\"\">\r\n    public class OrderOutboxConfiguration : IEntityTypeConfiguration&lt;OrderOutbox&gt;\r\n    {\r\n        public void Configure(EntityTypeBuilder&lt;OrderOutbox&gt; builder)\r\n        {\r\n            builder.HasKey(p =&gt; p.IdempotentToken);\r\n        }\r\n    }\r\n<\/pre>\n<\/div>\n<p>Bu geli\u015ftirmelerden sonra veritaban\u0131n\u0131n migrate edilmesi i\u00e7in a\u015fa\u011f\u0131daki talimatlar\u0131 verelim.<br \/>\n<code>add-migration mig_1<\/code><br \/>\n<code>update-database<\/code>\n<\/li>\n<li><em>Ad\u0131m 4 <span style=\"font-size:12px;color:green;\">(Persistence katman\u0131 ServiceRegistrations s\u0131n\u0131f\u0131n\u0131n olu\u015fturulmas\u0131)<\/span><\/em><br \/>\n&#8216;Persistence&#8217; katman\u0131nda geli\u015ftirilen context nesnesini IoC Container&#8217;a ekleyebilmek i\u00e7in a\u015fa\u011f\u0131daki gibi &#8216;ServiceRegistrations&#8217; s\u0131n\u0131f\u0131n\u0131 olu\u015ftural\u0131m.<\/p>\n<div style=\"font-size:12px;\">\n<pre class=\"brush: jscript; title: ; notranslate\" title=\"\">\r\n    public static class ServiceRegistrations\r\n    {\r\n        public static void AddPersistenceServices(this IServiceCollection services)\r\n        {\r\n            services.AddDbContext&lt;OutboxExampleDbContext&gt;(options =&gt; options.UseSqlServer(&quot;Server=localhost, 1433;Database=OutboxExampleDB;User ID=SA;Password=1q2w3e4r+!;&quot;));\r\n        }\r\n    }\r\n<\/pre>\n<\/div>\n<p>Ard\u0131ndan &#8216;Order.API&#8217; projesinin &#8216;Program.cs&#8217; dosyas\u0131nda ilgili s\u0131n\u0131f i\u00e7erisinde olu\u015fturulan <code>AddPersistenceServices<\/code> isimli extension metodu a\u015fa\u011f\u0131daki gibi \u00e7a\u011f\u0131ral\u0131m.<\/p>\n<div style=\"font-size:12px;\">\n<pre class=\"brush: jscript; title: ; notranslate\" title=\"\">\r\nusing OutboxExample.Persistence;\r\n\r\nvar builder = WebApplication.CreateBuilder(args);\r\n\r\nbuilder.Services.AddPersistenceServices();\r\nbuilder.Services.AddControllers();\r\nbuilder.Services.AddEndpointsApiExplorer();\r\n.\r\n.\r\n.\r\n<\/pre>\n<\/div>\n<\/li>\n<li><em>Ad\u0131m 5 <span style=\"font-size:12px;color:green;\">(MediatR k\u00fct\u00fcphanesinin kurulmas\u0131 ve temel sipari\u015f olu\u015fturma i\u015flemlerinin CQRS altyap\u0131s\u0131n\u0131n at\u0131lmas\u0131)<\/span><\/em><br \/>\n<a href=\"https:\/\/www.gencayyildiz.com\/blog\/onion-architectureda-cqrs-mediatr-pattern-automapper-fluent-validation\/\" rel=\"noopener\" target=\"_blank\">Onion Architecture\u2019da CQRS + MediatR Pattern + AutoMapper + Fluent Validation<\/a> ba\u015fl\u0131kl\u0131 makalede oldu\u011fu gibi &#8216;Application&#8217; katman\u0131nda MediatR k\u00fct\u00fcphanesini kullanarak sipari\u015f olu\u015fturma i\u015flemlerinin CQRS altyap\u0131 in\u015fas\u0131n\u0131 ger\u00e7ekle\u015ftirelim.<\/p>\n<div style=\"font-size:12px;\">\n<pre class=\"brush: jscript; title: ; notranslate\" title=\"\">\r\n    public class CreateOrderCommandHandler : IRequestHandler&lt;CreateOrderCommandRequest, CreateOrderCommandResponse&gt;\r\n    {\r\n        public Task&lt;CreateOrderCommandResponse&gt; Handle(CreateOrderCommandRequest request, CancellationToken cancellationToken)\r\n        {\r\n            throw new NotImplementedException();\r\n        }\r\n    }\r\n<\/pre>\n<\/div>\n<div style=\"font-size:12px;\">\n<pre class=\"brush: jscript; title: ; notranslate\" title=\"\">\r\n    public class CreateOrderCommandRequest : IRequest&lt;CreateOrderCommandResponse&gt;\r\n    {\r\n        public int Quantity { get; set; }\r\n        public string Description { get; set; }\r\n    }\r\n<\/pre>\n<\/div>\n<div style=\"font-size:12px;\">\n<pre class=\"brush: jscript; title: ; notranslate\" title=\"\">\r\n    public class CreateOrderCommandResponse\r\n    {\r\n    }\r\n<\/pre>\n<\/div>\n<p>Burada olu\u015ftural\u0131m &#8216;CreateOrderCommandHandler&#8217; s\u0131n\u0131f\u0131n\u0131n i\u00e7eri\u011fini sonraki ad\u0131mlar\u0131m\u0131zda dolduruyor olaca\u011f\u0131z.\n<\/li>\n<li><em>Ad\u0131m 6 <span style=\"font-size:12px;color:green;\">(Application katman\u0131 ServiceRegistrations s\u0131n\u0131f\u0131n\u0131n olu\u015fturulmas\u0131)<\/span><\/em><br \/>\n&#8216;Application&#8217; katman\u0131nda da geli\u015ftirilen servisleri IoC Container&#8217;a ekleyebilmek i\u00e7in a\u015fa\u011f\u0131daki &#8216;ServiceRegistrations&#8217; s\u0131n\u0131f\u0131n\u0131 olu\u015fturup ard\u0131ndan yine &#8216;Order.API&#8217; projesi i\u00e7erisindeki &#8216;Program.cs&#8217;de g\u00f6sterildi\u011fi \u015fekilde \u00e7a\u011f\u0131ral\u0131m.<\/p>\n<div style=\"font-size:12px;\">\n<pre class=\"brush: jscript; title: ; notranslate\" title=\"\">\r\n    public static class ServiceRegistrations\r\n    {\r\n        public static void AddApplicationServices(this IServiceCollection services)\r\n        {\r\n            services.AddMediatR(typeof(ServiceRegistrations));\r\n        }\r\n    }\r\n<\/pre>\n<\/div>\n<div style=\"font-size:12px;\">\n<pre class=\"brush: jscript; title: ; notranslate\" title=\"\">\r\nusing OutboxExample.Persistence;\r\nusing OutboxExample.Application;\r\n\r\nvar builder = WebApplication.CreateBuilder(args);\r\n\r\nbuilder.Services.AddPersistenceServices();\r\nbuilder.Services.AddApplicationServices();\r\nbuilder.Services.AddControllers();\r\nbuilder.Services.AddEndpointsApiExplorer();\r\n.\r\n.\r\n.\r\n<\/pre>\n<\/div>\n<\/li>\n<li><em>Ad\u0131m 7 <span style=\"font-size:12px;color:green;\">(&#8216;OrdersController&#8217;\u0131n olu\u015fturulmas\u0131)<\/span><\/em><br \/>\nSipari\u015fleri alabilmek i\u00e7in &#8216;OrdersController&#8217;\u0131 a\u015fa\u011f\u0131daki gibi olu\u015ftural\u0131m.<\/p>\n<div style=\"font-size:12px;\">\n<pre class=\"brush: jscript; title: ; notranslate\" title=\"\">\r\n    &#x5B;Route(&quot;api\/&#x5B;controller]&quot;)]\r\n    &#x5B;ApiController]\r\n    public class OrdersController : ControllerBase\r\n    {\r\n        IMediator _mediator;\r\n        public OrdersController(IMediator mediator)\r\n        {\r\n            _mediator = mediator;\r\n        }\r\n\r\n        &#x5B;HttpPost]\r\n        public async Task&lt;IActionResult&gt; Post(CreateOrderCommandRequest createOrderCommandRequest)\r\n        {\r\n            return Ok(await _mediator.Send(createOrderCommandRequest));\r\n        }\r\n    }\r\n<\/pre>\n<\/div>\n<\/li>\n<li><em>Ad\u0131m 8 <span style=\"font-size:12px;color:green;\">(Repository&#8217;lerin olu\u015fturulmas\u0131)<\/span><\/em><br \/>\n&#8216;Application&#8217; katman\u0131 i\u00e7erisindeki handler s\u0131n\u0131flar\u0131nda &#8216;Persistence&#8217; katman\u0131ndaki context&#8217;e eri\u015fip i\u015flemler ger\u00e7ekle\u015ftirebilmek i\u00e7in repository aray\u00fczlerine ihtiyac\u0131m\u0131z bulunmaktad\u0131r. Bunun i\u00e7in &#8216;Application&#8217; katman\u0131nda a\u015fa\u011f\u0131daki gibi repository aray\u00fczlerini olu\u015fturup, &#8216;Persistence&#8217; katman\u0131nda ise concrete nesnelerini olu\u015fturmam\u0131z gerekmektedir.<\/p>\n<p>&#8216;Application&#8217;;<\/p>\n<div style=\"font-size:12px;\">\n<pre class=\"brush: jscript; title: ; notranslate\" title=\"\">\r\n    public interface IRepository&lt;T&gt;\r\n    {\r\n        IQueryable&lt;T&gt; GetAll();\r\n        IQueryable&lt;T&gt; GetWhere(Expression&lt;Func&lt;T, bool&gt;&gt; method);\r\n        Task AddAsync(T model);\r\n        Task SaveChangesAsync();\r\n    }\r\n<\/pre>\n<\/div>\n<div style=\"font-size:12px;\">\n<pre class=\"brush: jscript; title: ; notranslate\" title=\"\">\r\n    public interface IOrderRepository : IRepository&lt;Order&gt;\r\n    {\r\n    }\r\n<\/pre>\n<\/div>\n<div style=\"font-size:12px;\">\n<pre class=\"brush: jscript; title: ; notranslate\" title=\"\">\r\n    public interface IOrderOutboxRepository : IRepository&lt;OrderOutbox&gt;\r\n    {\r\n    }\r\n<\/pre>\n<\/div>\n<p>&#8216;Persistence&#8217;;<\/p>\n<div style=\"font-size:12px;\">\n<pre class=\"brush: jscript; title: ; notranslate\" title=\"\">\r\n    public class Repository&lt;T&gt; : IRepository&lt;T&gt; where T : class\r\n    {\r\n        readonly OutboxExampleDbContext _context;\r\n        public Repository(OutboxExampleDbContext context)\r\n        {\r\n            this._context = context;\r\n        }\r\n\r\n        public DbSet&lt;T&gt; Table { get =&gt; _context.Set&lt;T&gt;(); }\r\n\r\n        public async Task AddAsync(T model)\r\n              =&gt; await Table.AddAsync(model);\r\n\r\n        public IQueryable&lt;T&gt; GetAll()\r\n            =&gt; Table;\r\n\r\n        public IQueryable&lt;T&gt; GetWhere(Expression&lt;Func&lt;T, bool&gt;&gt; method)\r\n            =&gt; Table.Where(method);\r\n\r\n        public async Task SaveChangesAsync()\r\n            =&gt; await _context.SaveChangesAsync();\r\n    }\r\n<\/pre>\n<\/div>\n<div style=\"font-size:12px;\">\n<pre class=\"brush: jscript; title: ; notranslate\" title=\"\">\r\n    public class OrderRepository : Repository&lt;Order&gt;, IOrderRepository\r\n    {\r\n        public OrderRepository(OutboxExampleDbContext context) : base(context)\r\n        {\r\n        }\r\n    }\r\n<\/pre>\n<\/div>\n<div style=\"font-size:12px;\">\n<pre class=\"brush: jscript; title: ; notranslate\" title=\"\">\r\n    public class OrderOutboxRepository : Repository&lt;OrderOutbox&gt;, IOrderOutboxRepository\r\n    {\r\n        public OrderOutboxRepository(OutboxExampleDbContext context) : base(context)\r\n        {\r\n        }\r\n    }\r\n<\/pre>\n<\/div>\n<\/li>\n<li><em>Ad\u0131m 9 <span style=\"font-size:12px;color:green;\">(Repository s\u0131n\u0131flar\u0131n\u0131 IoC Container&#8217;a eklemek)<\/span><\/em><br \/>\nOlu\u015fturulan repository s\u0131n\u0131flar\u0131n\u0131 IoC Container&#8217;a ekleyebilmek i\u00e7in &#8216;Persistence&#8217; katman\u0131nda bulunan &#8216;ServiceRegistrations&#8217; s\u0131n\u0131f\u0131nda a\u015fa\u011f\u0131daki \u00e7al\u0131\u015fmalar\u0131n yap\u0131lmas\u0131 gerekmektedir.<\/p>\n<div style=\"font-size:12px;\">\n<pre class=\"brush: jscript; title: ; notranslate\" title=\"\">\r\n    public static class ServiceRegistrations\r\n    {\r\n        public static void AddPersistenceServices(this IServiceCollection services)\r\n        {\r\n            services.AddDbContext&lt;OutboxExampleDbContext&gt;(options =&gt; options.UseSqlServer(&quot;Server=localhost, 1433;Database=OutboxExampleDB;User ID=SA;Password=1q2w3e4r+!;&quot;));\r\n\r\n            services.AddScoped&lt;IOrderRepository, OrderRepository&gt;();\r\n            services.AddScoped&lt;IOrderOutboxRepository, OrderOutboxRepository&gt;();\r\n        }\r\n    }\r\n<\/pre>\n<\/div>\n<\/li>\n<li><em>Ad\u0131m 10 <span style=\"font-size:12px;color:green;\">(CreateOrderCommandHandler i\u015flevinin geli\u015ftirilmesi ve Outbox Table&#8217;a mesaj\u0131n\/event&#8217;in i\u015flenmesi)<\/span><\/em><br \/>\nAPI arac\u0131l\u0131\u011f\u0131yla gelen sipari\u015fleri &#8216;Orders&#8217; tablosuna kaydedelim ve ard\u0131ndan stok bilgilerinin g\u00fcncellenmesi i\u00e7in message broker&#8217;a yay\u0131nlanacak olan mesaj\u0131\/event&#8217;i Outbox pattern g\u00fcvencesiyle yay\u0131nlayabilmek i\u00e7in Outbox table&#8217;a ilgili mesaj\u0131\/event&#8217;i kaydedelim.<\/p>\n<div style=\"font-size:12px;\">\n<pre class=\"brush: jscript; title: ; notranslate\" title=\"\">\r\n    public class CreateOrderCommandHandler : IRequestHandler&lt;CreateOrderCommandRequest, CreateOrderCommandResponse&gt;\r\n    {\r\n        readonly IOrderRepository _orderRepository;\r\n        readonly IOrderOutboxRepository _orderOutboxRepository;\r\n\r\n        public CreateOrderCommandHandler(IOrderRepository orderRepository, IOrderOutboxRepository orderOutboxRepository)\r\n        {\r\n            _orderRepository = orderRepository;\r\n            _orderOutboxRepository = orderOutboxRepository;\r\n        }\r\n\r\n        public async Task&lt;CreateOrderCommandResponse&gt; Handle(CreateOrderCommandRequest request, CancellationToken cancellationToken)\r\n        {\r\n            Order order = new() { Description = request.Description };\r\n            await _orderRepository.AddAsync(order);\r\n\r\n            OrderOutbox orderOutbox = new()\r\n            {\r\n                OccuredOn = DateTime.UtcNow,\r\n                ProcessedDate = null,\r\n                Payload = JsonSerializer.Serialize(order),\r\n                Type = nameof(OrderCreatedEvent),\r\n                IdempotentToken = Guid.NewGuid()\r\n            };\r\n            await _orderOutboxRepository.AddAsync(orderOutbox);\r\n            await _orderOutboxRepository.SaveChangesAsync();\r\n            return new();\r\n        }\r\n    }\r\n<\/pre>\n<\/div>\n<p><strong><em>22.<\/em><\/strong> sat\u0131rda i\u015flenen mesaj\u0131n\/event&#8217;in type&#8217;\u0131 i\u00e7in &#8216;Shared&#8217; class library&#8217;sinde olu\u015fturulmu\u015f olup a\u015fa\u011f\u0131daki i\u00e7eri\u011fe sahip olan &#8216;OrderCreatedEvent&#8217;ini belirtti\u011fimize dikkatinizi \u00e7ekerim.<\/p>\n<div style=\"font-size:12px;\">\n<pre class=\"brush: jscript; title: ; notranslate\" title=\"\">\r\n    public class OrderCreatedEvent : IEvent\r\n    {\r\n        public Guid OrderId { get; set; }\r\n        public int Quantity { get; set; }\r\n        public string Description { get; set; }\r\n        public Guid IdempotentToken { get; set; }\r\n    }\r\n<\/pre>\n<\/div>\n<\/li>\n<li><em>Ad\u0131m 11 <span style=\"font-size:12px;color:green;\">(Outbox table&#8217;dan mesajlar\u0131n okunmas\u0131)<\/span><\/em><br \/>\nUygulaman\u0131n bu noktas\u0131nda <em>Pulling Publisher<\/em> veya <em>Transaction Log Tailing<\/em> olmak \u00fczere iki farkl\u0131 y\u00f6ntemden birini tercih edebilece\u011fimizi \u00f6nceki sat\u0131rlarda konu\u015fmu\u015ftuk. \u015eimdi gelin bu her iki y\u00f6ntemi de ayr\u0131 ayr\u0131 uygulayarak teknik de\u011ferlendirmede bulunal\u0131m.<\/p>\n<ul>\n<li>\n<strong style=\"color:orange;\"><em>Pulling Publisher<\/em><\/strong><br \/>\nBu y\u00f6ntemde belirli bir zaman aral\u0131\u011f\u0131nda Outbox table&#8217;da yay\u0131nlanmam\u0131\u015f mesajlar\u0131 \u00e7ekip publish etme sorumlulu\u011funu \u00fcstlenecek bir console app. ya da worker service altyap\u0131s\u0131nda uygulama geli\u015ftirmemiz gerekmektedir. Bizler bu i\u00e7eri\u011fimizde worker service \u00fczerinden bir geli\u015ftirmede bulunuyor olaca\u011f\u0131z.<\/p>\n<p>O halde &#8216;OutboxExample.ProcessOutboxJob.Service&#8217; isminde bir worker service olu\u015ftural\u0131m ve saniye cinsinden aral\u0131kl\u0131 tetikleme i\u015flemini ger\u00e7ekle\u015ftirebilmek i\u00e7in <a href=\"https:\/\/www.quartz-scheduler.net\/\" rel=\"noopener\" target=\"_blank\">Quartz.NET<\/a> k\u00fct\u00fcphanesini ilgili uygulamaya y\u00fckleyelim. Ayr\u0131ca Outbox table&#8217;daki mesajlar\u0131\/event&#8217;leri message broker&#8217;a yay\u0131nlayabilmek i\u00e7in <a href=\"https:\/\/www.gencayyildiz.com\/blog\/asp-net-core-masstransit-rabbitmq-ile-birlikte-messaging-uygulamasi\/\" rel=\"noopener\" target=\"_blank\">MassTransit <\/a>k\u00fct\u00fcphanesinden de faydalan\u0131yor olaca\u011f\u0131z.<\/p>\n<p><strong><code><a href=\"https:\/\/www.nuget.org\/packages\/Quartz\" rel=\"noopener\" target=\"_blank\">Install-Package Quartz<\/a><\/code><\/strong><br \/>\n<strong><code><a href=\"https:\/\/www.nuget.org\/packages\/Quartz.Extensions.DependencyInjection\" rel=\"noopener\" target=\"_blank\">Install-Package Quartz.Extensions.DependencyInjection<\/a><\/code><\/strong><br \/>\n<strong><code><a href=\"https:\/\/www.nuget.org\/packages\/Quartz.Extensions.Hosting\/\" rel=\"noopener\" target=\"_blank\">Install-Package Quartz.Extensions.Hosting<\/a><\/code><\/strong><br \/>\n<strong><code><a href=\"https:\/\/www.nuget.org\/packages\/MassTransit.Rabbitmq\/\" rel=\"noopener\" target=\"_blank\">Install-Package MassTransit.RabbitMQ<\/a><\/code><\/strong><\/p>\n<p>\u0130lgili k\u00fct\u00fcphaneleri uygulamaya y\u00fckledikten sonra worker service&#8217;in &#8216;Program.cs&#8217; dosyas\u0131n\u0131 a\u00e7al\u0131m ve a\u015fa\u011f\u0131daki konfig\u00fcrasyonel i\u015flemleri ger\u00e7ekle\u015ftirelim.<\/p>\n<div style=\"font-size:12px;\">\n<pre class=\"brush: jscript; title: ; notranslate\" title=\"\">\r\nusing OutboxExample.ProcessOutboxJob.Service;\r\nusing OutboxExample.ProcessOutboxJob.Service.Jobs;\r\nusing Quartz;\r\nusing MassTransit;\r\n\r\nIHost host = Host.CreateDefaultBuilder(args)\r\n    .ConfigureServices((hostContext, services) =&gt;\r\n    {\r\n        services.AddQuartz(configurator =&gt;\r\n        {\r\n            configurator.UseMicrosoftDependencyInjectionJobFactory();\r\n\r\n            JobKey jobKey = new(&quot;OrderOutboxPublishJob&quot;);\r\n\r\n            \/\/Bir job tan\u0131mlan\u0131p 'OrderOutboxPublishJob' isimli s\u0131n\u0131fa ba\u011flan\u0131yor.\r\n            configurator.AddJob&lt;OrderOutboxPublishJob&gt;(options =&gt; options.WithIdentity(jobKey));\r\n\r\n            TriggerKey triggerKey = new(&quot;OrderOutboxPublishTrigger&quot;);\r\n            \/\/Job 5 saniyelik aral\u0131klarla \u00e7al\u0131\u015facak \u015fekilde ayarlan\u0131yor.\r\n            configurator.AddTrigger(options =&gt; options.ForJob(jobKey)\r\n                        .WithIdentity(triggerKey)\r\n                        .StartAt(DateTime.UtcNow)\/\/Trigger'\u0131n ba\u015flang\u0131\u00e7 tarihini belirliyoruz.\r\n                        .WithSimpleSchedule\/\/Trigger'\u0131n ba\u015flad\u0131ktan sonraki program\u0131n\u0131 belirtiyoruz.\r\n                        (\r\n                            builder =&gt; builder.WithIntervalInSeconds(5) \/\/Trigger'\u0131n ka\u00e7 saniyede bir tetiklenece\u011fini belirliyoruz.\r\n                                              .RepeatForever() \/\/Trigger'\u0131n sonsuza denk \u00e7al\u0131\u015faca\u011f\u0131n\u0131 belirtiyoruz.\r\n                        ));\r\n        });\r\n\r\n        services.AddQuartzHostedService(options =&gt; options.WaitForJobsToComplete = true);\r\n\r\n        services.AddMassTransit(configurator =&gt;\r\n        {\r\n            configurator.UsingRabbitMq((context, _configurator) =&gt;\r\n            {\r\n                _configurator.Host(hostContext.Configuration&#x5B;&quot;RabbitMQ:Host&quot;], &quot;\/&quot;, hostConfigurator =&gt;\r\n                {\r\n                    hostConfigurator.Username(hostContext.Configuration&#x5B;&quot;RabbitMQ:Username&quot;]);\r\n                    hostConfigurator.Password(hostContext.Configuration&#x5B;&quot;RabbitMQ:Password&quot;]);\r\n                });\r\n            });\r\n        });\r\n    })\r\n    .Build();\r\n\r\nawait host.RunAsync();\r\n<\/pre>\n<\/div>\n<p>Burada g\u00f6r\u00fcld\u00fc\u011f\u00fc \u00fczere <strong><em>9<\/em><\/strong> ile <strong><em>30.<\/em><\/strong> sat\u0131r aral\u0131\u011f\u0131nda Quartz.NET ile ilgili konfig\u00fcrasyonlar ger\u00e7ekle\u015ftirilirken, <strong><em>32<\/em><\/strong> ile <strong><em>42.<\/em><\/strong> sat\u0131r aral\u0131\u011f\u0131nda ise MassTransit ile ilgili konfig\u00fcrasyonlar ger\u00e7ekle\u015ftirilmektedir.<\/p>\n<p>\u015eimdi ise <strong><em>16.<\/em><\/strong> sat\u0131rda job&#8217;\u0131n ba\u011fland\u0131\u011f\u0131 &#8216;OrderOutboxPublishJob&#8217; s\u0131n\u0131f\u0131n\u0131 in\u015fa edelim. Bu s\u0131n\u0131f i\u015flevsel olarak Outbox table&#8217;\u0131 sorgulayacak ve publish edilmeyen mesajlar\u0131 elde edip 5 saniyede bir message broker&#8217;a yollayacakt\u0131r. Buradaki sorgulama s\u00fcrecinde bizlere yard\u0131mc\u0131 olabilmesi i\u00e7in micro ORM arac\u0131 olan <a href=\"https:\/\/github.com\/DapperLib\/Dapper\" rel=\"noopener\" target=\"_blank\">Dapper<\/a>&#8216;dan istifade edebiliriz.<\/p>\n<p><strong><code><a href=\"https:\/\/www.nuget.org\/packages\/Dapper\/\" rel=\"noopener\" target=\"_blank\">Install-Package Dapper<\/a><\/code><\/strong><\/p>\n<div style=\"font-size:12px;\">\n<pre class=\"brush: jscript; title: ; notranslate\" title=\"\">\r\n    public class OrderOutboxPublishJob : IJob\r\n    {\r\n        readonly IPublishEndpoint _publishEndpoint;\r\n\r\n        public OrderOutboxPublishJob(IPublishEndpoint publishEndpoint)\r\n        {\r\n            _publishEndpoint = publishEndpoint;\r\n        }\r\n\r\n        public async Task Execute(IJobExecutionContext context)\r\n        {\r\n            if (OrderSingletonDatabase.DataReaderState)\r\n            {\r\n                OrderSingletonDatabase.DataReaderBusy();\r\n                List&lt;OrderOutbox&gt; orderOutboxes = (await OrderSingletonDatabase.QueryAsync&lt;OrderOutbox&gt;($@&quot;SELECT * FROM OrderOutboxes\r\n                                                                                                           WHERE ProcessedDate IS NULL\r\n                                                                                                           ORDER By OccuredOn DESC&quot;))\r\n                                                                        .ToList();\r\n                foreach (OrderOutbox orderOutbox in orderOutboxes)\r\n                {\r\n                    if (orderOutbox.Type == nameof(OrderCreatedEvent))\r\n                    {\r\n                        Order? order = JsonSerializer.Deserialize&lt;Order&gt;(orderOutbox.Payload);\r\n                        if (order != null)\r\n                        {\r\n                            OrderCreatedEvent orderCreatedEvent = new()\r\n                            {\r\n                                Description = order.Description,\r\n                                OrderId = order.Id,\r\n                                Quantity = order.Quantity,\r\n                                IdempotentToken = orderOutbox.IdempotentToken\r\n                            };\r\n\r\n                            await _publishEndpoint.Publish(orderCreatedEvent);\r\n                        }\r\n                    }\r\n\r\n                    \/*\r\n                        Outbox table'da ki publish edilmemi\u015f mesajlar\u0131\/event'leri message\r\n                        broker'a publish ettik. \u015eimdi yay\u0131nlanm\u0131\u015f bu mesajlar\u0131n yay\u0131nland\u0131\u011f\u0131na\r\n                        dair veritaban\u0131nda Outbox table'da gerekli g\u00fcncellemelerin\/i\u015faretlemelerin\r\n                        yap\u0131lmas\u0131 gerekmektedir(ya da yay\u0131nlanm\u0131\u015f mesaja dair kay\u0131t silinmelidir)\r\n                        \u0130\u015fte tam bu noktada Outbox table'\u0131n oldu\u011fu veritaban\u0131 ile ya\u015fanabilecek\r\n                        olas\u0131 ba\u011flant\u0131 kopukluklar\u0131ndan dolay\u0131 Idempotent tasar\u0131m\u0131 kullan\u0131yor olaca\u011f\u0131z.\r\n                        \u00c7\u00fcnk\u00fc bir sonraki job'\u0131n tetiklenme s\u00fcrecinde bu i\u015flendi\u011fine dair g\u00fcncellenemeyen\r\n                        ama \u00f6z\u00fcnde i\u015flenen mesajlar tekrardan\/yineli bir \u015fekilde message broker'a g\u00f6nderilecek\r\n                        ve consumer taraf\u0131ndan veri tutars\u0131zl\u0131\u011f\u0131na meydan verebilecek \u015fekilde t\u00fcketilecektir.\r\n                        Consumer gerekti\u011fi taktirde Inbox pattern'\u0131n getirisi olan Inbox\r\n                        table'\u0131 kullanarak hangi mesajlar\u0131\/event'leri i\u015fleyip i\u015flemedi\u011fini tutacak ve\r\n                        ihtimal olarak yinelenebilecek mesajlar\u0131n tutars\u0131zl\u0131\u011fa sebebiyet verebilecek durumlar\u0131\r\n                        b\u00f6ylece engellenmi\u015f olacakt\u0131r.\r\n                     *\/\r\n                    int result = await OrderSingletonDatabase.ExecuteAsync(@$&quot;UPDATE OrderOutboxes SET ProcessedDate = GETDATE()\r\n                                                                              WHERE IdempotentToken = '{orderOutbox.IdempotentToken}'&quot;);\r\n\r\n                }\r\n                OrderSingletonDatabase.DataReaderReady();\r\n                Console.WriteLine(&quot;Order outbox table checked!&quot;);\r\n            }\r\n\r\n        }\r\n    }\r\n<\/pre>\n<\/div>\n<p>Yukar\u0131daki &#8216;OrderOutboxPublishJob&#8217; i\u00e7eri\u011fine g\u00f6z atarsan\u0131z e\u011fer; <strong><em>15.<\/em><\/strong> sat\u0131rda Outbox table&#8217;dan &#8216;ProcessedDate&#8217; kolonu null olan t\u00fcm kay\u0131tlar \u00e7ekiliyor ve <strong><em>19<\/em><\/strong> ile <strong><em>56.<\/em><\/strong> sat\u0131r aral\u0131\u011f\u0131nda bu kay\u0131tlar hangi t\u00fcrde bir event ise onun instance&#8217;\u0131na d\u00f6n\u00fc\u015ft\u00fcr\u00fclerek message broker&#8217;a publish ediliyor. Burada \u00f6zellikle <strong><em>38<\/em><\/strong> ile <strong><em>52.<\/em><\/strong> sat\u0131r aral\u0131\u011f\u0131ndaki nota dikkat ederseniz idempotent \u00f6zelli\u011finin ihtiya\u00e7 duyulaca\u011f\u0131 nokta vurgulanmaktad\u0131r. Nihayetinde <strong><em>53.<\/em><\/strong> sat\u0131rda Outbox table&#8217;da publish edilen event&#8217;ler yay\u0131nland\u0131klar\u0131na dair i\u015fleme tabi tutulacakken veritaban\u0131 ile olas\u0131 bir ba\u011flant\u0131 kesintisi ile kar\u015f\u0131la\u015fma ihtimaline nazaran, publish edilmi\u015f ve consumer taraf\u0131ndan i\u015flenmi\u015f bir verinin sonraki tetiklenmede tekrar publish edilip tutars\u0131zl\u0131\u011fa mahal verebilecek \u015fekilde tekrar i\u015flenmesini engellememiz gerekmektedir! \u0130\u00e7eri\u011fimizin sonunda \u00f6rnek olarak geli\u015ftirece\u011fimiz consumer \u00fczerinden bu idempotent \u00f6zellikli mesajlar\u0131n nas\u0131l Inbox pattern e\u015fli\u011finde i\u015flendi\u011fini g\u00f6rm\u00fc\u015f olacaks\u0131n\u0131z.<\/p>\n<p>Tekrar yukar\u0131daki kod blo\u011funa odaklan\u0131rsak e\u011fer &#8216;OrderSingletonDatabase&#8217; isminde bir s\u0131n\u0131f \u00fczerinden veritaban\u0131 s\u00fcrecinin i\u015flendi\u011fini g\u00f6rmekteyiz. Hemen bu s\u0131n\u0131f\u0131nda i\u00e7eri\u011fini a\u015fa\u011f\u0131ya alarak incelersek e\u011fer;<\/p>\n<div style=\"font-size:12px;\">\n<pre class=\"brush: jscript; title: ; notranslate\" title=\"\">\r\n    public static class OrderSingletonDatabase\r\n    {\r\n        static OrderSingletonDatabase()\r\n        {\r\n            _connection = new SqlConnection(&quot;Server=localhost, 1433;Database=OutboxExampleDB;User ID=SA;Password=1q2w3e4r+!;&quot;);\r\n        }\r\n\r\n        static IDbConnection _connection;\r\n        public static IDbConnection Connection\r\n        {\r\n            get\r\n            {\r\n                if (_connection.State == ConnectionState.Closed)\r\n                    _connection.Open();\r\n                return _connection;\r\n            }\r\n        }\r\n\r\n        public static async Task&lt;IEnumerable&lt;T&gt;&gt; QueryAsync&lt;T&gt;(string sql)\r\n            =&gt; await _connection.QueryAsync&lt;T&gt;(sql);\r\n\r\n        public static async Task&lt;int&gt; ExecuteAsync(string sql)\r\n            =&gt; await _connection.ExecuteAsync(sql);\r\n\r\n        static bool _dataReaderState = true;\r\n        public static bool DataReaderState { get =&gt; _dataReaderState; }\r\n\r\n        public static void DataReaderReady() =&gt; _dataReaderState = true;\r\n        public static void DataReaderBusy() =&gt; _dataReaderState = false;\r\n    }\r\n<\/pre>\n<\/div>\n<p>\u015feklinde oldu\u011funu g\u00f6rece\u011fiz. Burada \u00f6zellikle <a href=\"https:\/\/www.gencayyildiz.com\/blog\/cta-singleton-design-patternsingleton-tasarim-deseni\/\" rel=\"noopener\" target=\"_blank\">singleton<\/a> bir \u015fekilde tasarlanm\u0131\u015f olan &#8216;SqlConnection&#8217; nesnesi y\u00f6netilmekte ve ayr\u0131ca Dapper k\u00fct\u00fcphanesinin temel sorgulama metotlar\u0131(<code>QueryAsync<\/code>, <code>ExecuteAsync<\/code>) i\u015flenmektedir. Burada \u00f6nemli olan &#8216;DataReaderState&#8217; property&#8217;sinin mahiyetini anlamakt\u0131r. &#8216;OrderOutboxPublishJob&#8217; s\u0131n\u0131f\u0131 i\u00e7erisinde her 5 saniyede bir Outbox table \u00fczerinde i\u015flem ger\u00e7ekle\u015ftirirken bazen binlerce verinin i\u015flenip publish edilmesi 5 saniyeyi ge\u00e7ebilmektedir. B\u00f6yle bir durumda i\u015flemi bitmeyen bir DataReader nesnesini tekrardan(5 saniye sonra) kullanmaya \u00e7al\u0131\u015fmak hataya sebep olacakt\u0131r. Dolay\u0131s\u0131yla bu hatan\u0131n engellenebilmesi i\u00e7in DataReader nesnesinin state&#8217;ini bu property ile tutmakta ve <code>DataReaderReady<\/code> ile <code>DataReaderBusy<\/code> fonksiyonlar\u0131yla y\u00f6netimi sa\u011flanmaktad\u0131r. Bu bilgiler e\u015fli\u011finde tekrardan &#8216;OrderOutboxPublishJob&#8217; s\u0131n\u0131f\u0131na g\u00f6z atarsan\u0131z e\u011fer <strong><em>12.<\/em><\/strong>, <strong><em>14.<\/em><\/strong> ve <strong><em>57.<\/em><\/strong> sat\u0131rlarda bu kontroller e\u015fli\u011finde DataReader g\u00fcvenli\u011fine g\u00f6re Outbox table sorgulanmaktad\u0131r.<\/p>\n<p>Pulling Publisher y\u00f6ntemiyle Outbox table&#8217;dan mesajlar\u0131n nas\u0131l okunabilece\u011fine gayet yeterli bir \u00f6rnek sundu\u011fumuz kanaatindeyim. \u015eimdi ise di\u011fer y\u00f6ntemi de de\u011ferlendirip konumuza devam edebiliriz.\n<\/li>\n<li>\n<strong style=\"color:orange;\"><em>Transaction Log Tailing<\/em><\/strong><br \/>\nBu y\u00f6ntemde ise Outbox table&#8217;dan transaction loglar\u0131 okuyarak eklenen mesajlar\u0131 an\u0131nda publish edebiliriz. Bunun i\u00e7in <a href=\"https:\/\/www.gencayyildiz.com\/blog\/debeziumcdc-ile-veritabanlarindaki-degisiklikleri-yakalama-postgresql-mssql-mongodb\/\" rel=\"noopener\" target=\"_blank\">Debezium<\/a> teknolojisini kullanan basit bir console uygulamas\u0131 geli\u015ftirebiliriz.<\/p>\n<p>Bunun i\u00e7in &#8216;OutboxExample.TransactionLog.Service&#8217; isminde bir console uygulamas\u0131 olu\u015ftural\u0131m. Ard\u0131ndan &#8216;OutboxExample\\TransactionLogService&#8217; dizininde a\u015fa\u011f\u0131daki docker compose dosyas\u0131n\u0131 olu\u015ftural\u0131m ve<br \/>\n<code style=\"color:red;\">docker-compose -f docker-compose-sqlserver.yaml up<\/code> talimat\u0131 e\u015fli\u011finde \u00e7al\u0131\u015ft\u0131ral\u0131m.<\/p>\n<div style=\"font-size:12px;\">\n<pre class=\"brush: jscript; title: ; notranslate\" title=\"\">\r\nversion: '3.1'\r\nservices:\r\n  zookeeper:\r\n    image: debezium\/zookeeper\r\n    ports:\r\n      - &quot;2181:2181&quot;\r\n      - &quot;2888:2888&quot;\r\n      - &quot;3888:3888&quot;\r\n  kafka:\r\n    image: debezium\/kafka\r\n    ports:\r\n      - &quot;9092:9092&quot;\r\n      - &quot;29092:29092&quot;\r\n    depends_on:\r\n      - zookeeper\r\n    environment:\r\n      - ZOOKEEPER_CONNECT=zookeeper:2181\r\n      - KAFKA_ADVERTISED_LISTENERS=LISTENER_EXT:\/\/localhost:29092,LISTENER_INT:\/\/kafka:9092\r\n      - KAFKA_LISTENER_SECURITY_PROTOCOL_MAP=LISTENER_INT:PLAINTEXT,LISTENER_EXT:PLAINTEXT\r\n      - KAFKA_LISTENERS=LISTENER_INT:\/\/0.0.0.0:9092,LISTENER_EXT:\/\/0.0.0.0:29092\r\n      - KAFKA_INTER_BROKER_LISTENER_NAME=LISTENER_INT\r\n  connect:\r\n    image: debezium\/connect\r\n    ports:\r\n      - 8083:8083\r\n    environment:\r\n      - BOOTSTRAP_SERVERS=kafka:9092\r\n      - GROUP_ID=1\r\n      - CONFIG_STORAGE_TOPIC=my_connect_configs\r\n      - OFFSET_STORAGE_TOPIC=my_connect_offsets\r\n      - STATUS_STORAGE_TOPIC=my_connect_statuses\r\n    depends_on:\r\n      - zookeeper\r\n      - kafka\r\n<\/pre>\n<\/div>\n<p><a href=\"https:\/\/www.gencayyildiz.com\/blog\/wp-content\/uploads\/2022\/07\/Outbox-Design-PatternOutbox-Tasarim-Deseni-4.png\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/www.gencayyildiz.com\/blog\/wp-content\/uploads\/2022\/07\/Outbox-Design-PatternOutbox-Tasarim-Deseni-4.png\" alt=\"Outbox Design Pattern(Outbox Tasar\u0131m Deseni)\" width=\"924\" height=\"284\" class=\"aligncenter size-full wp-image-25399\" srcset=\"https:\/\/www.gencayyildiz.com\/blog\/wp-content\/uploads\/2022\/07\/Outbox-Design-PatternOutbox-Tasarim-Deseni-4.png 924w, https:\/\/www.gencayyildiz.com\/blog\/wp-content\/uploads\/2022\/07\/Outbox-Design-PatternOutbox-Tasarim-Deseni-4-300x92.png 300w, https:\/\/www.gencayyildiz.com\/blog\/wp-content\/uploads\/2022\/07\/Outbox-Design-PatternOutbox-Tasarim-Deseni-4-768x236.png 768w\" sizes=\"auto, (max-width: 924px) 100vw, 924px\" \/><\/a>Ard\u0131ndan veritaban\u0131nda ve Outbox tablosunda CDC(Change Data Capture)&#8217;yi aktifle\u015ftirebilmek i\u00e7in a\u015fa\u011f\u0131daki konfig\u00fcrasyonlar\u0131 \u00e7al\u0131\u015ft\u0131ral\u0131m.<\/p>\n<p>Veritaban\u0131 seviyesinde CDC&#8217;yi aktifle\u015ftirmek i\u00e7in;<\/p>\n<div style=\"font-size:12px;\">\n<pre class=\"brush: jscript; title: ; notranslate\" title=\"\">\r\nUSE OutboxExampleDB\r\nGO\r\nEXEC sys.sp_cdc_enable_db\r\n<\/pre>\n<\/div>\n<p>Tablo seviyesinde CDC&#8217;yi aktifle\u015ftirmek i\u00e7in;<\/p>\n<div style=\"font-size:12px;\">\n<pre class=\"brush: jscript; title: ; notranslate\" title=\"\">\r\nUSE OutboxExampleDB\r\nGO\r\nEXEC sys.sp_cdc_enable_table \r\n@source_schema = N'dbo', \r\n@source_name = N'OrderOutboxes', \r\n@role_name = NULL, \r\n@filegroup_name = N'', \r\n@supports_net_changes = 0 \r\n<\/pre>\n<\/div>\n<p>S\u0131rada Debezium i\u00e7in bir connector tan\u0131mlamaya gelmi\u015ftir. Bunun i\u00e7in a\u015fa\u011f\u0131daki json format\u0131ndaki register konfig\u00fcrasyonlar\u0131 e\u015fli\u011finde curl talimat\u0131n\u0131 post edelim.<\/p>\n<div style=\"font-size:12px;\">\n<pre class=\"brush: jscript; title: ; notranslate\" title=\"\">\r\n{\r\n  &quot;name&quot;: &quot;OutboxExample-Connector&quot;,\r\n  &quot;config&quot;: {\r\n    &quot;connector.class&quot;: &quot;io.debezium.connector.sqlserver.SqlServerConnector&quot;,\r\n    &quot;tasks.max&quot;: &quot;1&quot;,\r\n    &quot;database.hostname&quot;: &quot;host.docker.internal&quot;,\r\n    &quot;database.port&quot;: &quot;1433&quot;,\r\n    &quot;database.user&quot;: &quot;SA&quot;,\r\n    &quot;database.password&quot;: &quot;1q2w3e4r+!&quot;,\r\n    &quot;database.dbname&quot;: &quot;OutboxExampleDB&quot;,\r\n    &quot;database.server.name&quot;: &quot;MSSQLServer&quot;,\r\n    &quot;schema.include.list&quot;: &quot;dbo&quot;,\r\n    &quot;table.whitelist&quot;: &quot;dbo.OrderOutboxes&quot;,\r\n    &quot;database.history.kafka.bootstrap.servers&quot;: &quot;kafka:9092&quot;,\r\n    &quot;database.history.kafka.topic&quot;: &quot;dbhistory.outboxtable&quot;\r\n  }\r\n}\r\n<\/pre>\n<\/div>\n<div style=\"text-align:center;line-height: 15px;\">\n<code style=\"color:red;font-size:12px;\">curl -i -X POST -H \"Accept:application\/json\" -H \"Content-Type:application\/json\" http:\/\/localhost:8083\/connectors\/ -d @register-sqlserver.json<br \/>\n<\/code>\n<\/div>\n<p><a href=\"https:\/\/www.gencayyildiz.com\/blog\/wp-content\/uploads\/2022\/07\/Outbox-Design-PatternOutbox-Tasarim-Deseni-5.png\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/www.gencayyildiz.com\/blog\/wp-content\/uploads\/2022\/07\/Outbox-Design-PatternOutbox-Tasarim-Deseni-5.png\" alt=\"Outbox Design Pattern(Outbox Tasar\u0131m Deseni)\" width=\"974\" height=\"235\" class=\"aligncenter size-full wp-image-25403\" srcset=\"https:\/\/www.gencayyildiz.com\/blog\/wp-content\/uploads\/2022\/07\/Outbox-Design-PatternOutbox-Tasarim-Deseni-5.png 974w, https:\/\/www.gencayyildiz.com\/blog\/wp-content\/uploads\/2022\/07\/Outbox-Design-PatternOutbox-Tasarim-Deseni-5-300x72.png 300w, https:\/\/www.gencayyildiz.com\/blog\/wp-content\/uploads\/2022\/07\/Outbox-Design-PatternOutbox-Tasarim-Deseni-5-768x185.png 768w\" sizes=\"auto, (max-width: 974px) 100vw, 974px\" \/><\/a>Evet, connector&#8217;de tan\u0131mland\u0131\u011f\u0131na g\u00f6re Outbox table&#8217;da yap\u0131lan t\u00fcm de\u011fi\u015fiklikleri takip edebilir ve yeni gelen mesajlar\u0131 rahatl\u0131kla publish edebiliriz. Bunun i\u00e7in console uygulamas\u0131na geliniz ve a\u015fa\u011f\u0131daki \u00e7al\u0131\u015fmay\u0131 ger\u00e7ekle\u015ftiriniz.<\/p>\n<div style=\"font-size:12px;\">\n<pre class=\"brush: jscript; title: ; notranslate\" title=\"\">\r\nusing Confluent.Kafka;\r\n\r\nConsumerConfig config = new()\r\n{\r\n    GroupId = &quot;MSSQLServer.dbo.OrderOutboxes&quot;,\r\n    BootstrapServers = &quot;localhost:29092&quot;,\r\n    AutoOffsetReset = AutoOffsetReset.Earliest\r\n};\r\n\r\nusing IConsumer&lt;Ignore, string&gt; consumer = new ConsumerBuilder&lt;Ignore, string&gt;(config).Build();\r\nconsumer.Subscribe(&quot;MSSQLServer.dbo.OrderOutboxes&quot;);\r\n\r\nwhile (true)\r\n{\r\n    CancellationTokenSource cancellationTokenSource = new();\r\n    ConsumeResult&lt;Ignore, string&gt; result = consumer.Consume(cancellationTokenSource.Token);\r\n\r\n    Console.WriteLine(result.Value);\r\n    \/*\r\n     \u015eu a\u015famadan sonra result.Value'da ki veriler ay\u0131klan\u0131p message broker'a g\u00f6nderilebilir.\r\n     *\/\r\n}\r\n<\/pre>\n<\/div>\n<p>Yukar\u0131daki kod blo\u011funu incelerseniz e\u011fer &#8216;Kafka&#8217;dan gelecek olan ve Outbox table&#8217;daki verileri bar\u0131nd\u0131ran mesajlar\u0131 serilize edip esas message broker&#8217;a g\u00f6nderebilecek temeller at\u0131lm\u0131\u015f bulunmaktad\u0131r. Bundan sonras\u0131 i\u00e7eri\u011fin \u015fi\u015fmesine ve yap\u0131lanlar\u0131n tekrar edilmesine sebebiyet verebilece\u011finden dolay\u0131 devam\u0131n\u0131 sizlere b\u0131rak\u0131yorum.\n<\/li>\n<\/ul>\n<\/li>\n<li><em>Ad\u0131m 12 <span style=\"font-size:12px;color:green;\">(Consumer&#8217;\u0131n geli\u015ftirilmesi)<\/span><\/em><br \/>\nArt\u0131k yolun son viraj\u0131na gelmi\u015f bulunmaktay\u0131z. Geli\u015ftirdi\u011fimiz uygulamay\u0131 t\u00fcketecek olan consumer&#8217;\u0131 geli\u015ftirecek ve Inbox pattern ile idempotent g\u00fcvencesini sa\u011fl\u0131yor olaca\u011f\u0131z. Bunun i\u00e7in &#8216;Presentation&#8217; katman\u0131nda &#8216;OutboxExample.Stock.API&#8217; uygulamas\u0131 olu\u015ftural\u0131m ve &#8216;Program.cs&#8217; dosyas\u0131nda a\u015fa\u011f\u0131daki konfig\u00fcrasyonlar\u0131 ger\u00e7ekle\u015ftirelim.<\/p>\n<div style=\"font-size:12px;\">\n<pre class=\"brush: jscript; title: ; notranslate\" title=\"\">\r\nusing MassTransit;\r\n\r\nvar builder = WebApplication.CreateBuilder(args);\r\n\r\nbuilder.Services.AddMassTransit(configurator =&gt;\r\n{\r\n    configurator.AddConsumer&lt;OrderCreatedEventConsumer&gt;();\r\n    configurator.UsingRabbitMq((context, _configurator) =&gt;\r\n    {\r\n        _configurator.Host(builder.Configuration&#x5B;&quot;RabbitMQ:Host&quot;], &quot;\/&quot;, hostConfigurator =&gt;\r\n        {\r\n            hostConfigurator.Username(builder.Configuration&#x5B;&quot;RabbitMQ:Username&quot;]);\r\n            hostConfigurator.Password(builder.Configuration&#x5B;&quot;RabbitMQ:Password&quot;]);\r\n        });\r\n        _configurator.ReceiveEndpoint(&quot;stock-order-created-event&quot;, e =&gt; e.ConfigureConsumer&lt;OrderCreatedEventConsumer&gt;(context));\r\n    });\r\n});\r\n\r\nbuilder.Services.AddControllers();\r\nbuilder.Services.AddEndpointsApiExplorer();\r\nbuilder.Services.AddSwaggerGen();\r\n<\/pre>\n<\/div>\n<p>Ard\u0131ndan &#8216;OrderCreatedEventConsumer&#8217; isimli consumer&#8217;\u0131 olu\u015fturularak i\u00e7eri\u011fini geli\u015ftirmek \u00fczere sonraki ad\u0131mlara b\u0131rakal\u0131m.<\/p>\n<div style=\"font-size:12px;\">\n<pre class=\"brush: jscript; title: ; notranslate\" title=\"\">\r\n    public class OrderCreatedEventConsumer : IConsumer&lt;OrderCreatedEvent&gt;\r\n    {\r\n        public async Task Consume(ConsumeContext&lt;OrderCreatedEvent&gt; context)\r\n        {\r\n            \/\/...\r\n            \/\/...\r\n            \/\/processing\r\n            \/\/...\r\n            \/\/...\r\n            Console.WriteLine($&quot;Stock {context.Message.Quantity} miktar kadar d\u00fc\u015f\u00fcr\u00fclm\u00fc\u015ft\u00fcr!&quot;);\r\n        }\r\n    }\r\n<\/pre>\n<\/div>\n<\/li>\n<li><em>Ad\u0131m 13 <span style=\"font-size:12px;color:green;\">(Inbox Pattern temellerinin at\u0131lmas\u0131)<\/span><\/em><br \/>\n\u015eimdi consumer&#8217;a gelen mesajlar\u0131 i\u015flemeden \u00f6nce idempotent \u00f6zelli\u011fi gere\u011fi \u00f6nceden i\u015flenip i\u015flenmedi\u011fini sorgulamak i\u00e7in Inbox pattern mant\u0131\u011f\u0131 do\u011frultusunda Inbox table&#8217;\u0131n entity&#8217;sini olu\u015ftural\u0131m. &#8216;Domain&#8217; katman\u0131na gelelim &#8216;OrderInbox&#8217; ad\u0131nda entity&#8217;i a\u015fa\u011f\u0131daki i\u00e7erikle ekleyelim.<\/p>\n<div style=\"font-size:12px;\">\n<pre class=\"brush: jscript; title: ; notranslate\" title=\"\">\r\n    public class OrderInbox\r\n    {\r\n        public Guid OrderId { get; set; }\r\n        public int Quantity { get; set; }\r\n        public string Description { get; set; }\r\n        public bool Processed { get; set; }\r\n        public Guid IdempotentToken { get; set; }\r\n    }\r\n<\/pre>\n<\/div>\n<p>Yukar\u0131daki Inbox table mahiyetinde geli\u015ftirilen &#8216;OrderInbox&#8217; entity&#8217;sine g\u00f6z atarsan\u0131z e\u011fer &#8216;Processed&#8217; property&#8217;si sayesinde ilgili mesaj\u0131n\/event&#8217;in i\u015flenip i\u015flenmedi\u011fini ifade edecek bir alan bar\u0131nd\u0131rmaktad\u0131r. Bizler publisher&#8217;dan gelecek olan mesaj\u0131 \u00f6nce bu tabloya kaydedecek sonras\u0131nda ise bu tablo \u00fczerinden al\u0131p i\u015fleme tabi tutaca\u011f\u0131z. S\u00fcre\u00e7 ba\u015far\u0131yla neticelenirse e\u011fer &#8216;Processed&#8217; kolonunu &#8216;true&#8217; olarak g\u00fcncelleyip tutarl\u0131l\u0131\u011f\u0131 hat safhaya \u00e7\u0131karaca\u011f\u0131z.<\/p>\n<p>Velhas\u0131l \u015fimdi bu entity&#8217;nin repository aray\u00fcz\u00fcn\u00fc ve concrete&#8217;ini olu\u015ftural\u0131m.<\/p>\n<div style=\"font-size:12px;\">\n<pre class=\"brush: jscript; title: ; notranslate\" title=\"\">\r\n    public interface IOrderInboxRepository : IRepository&lt;OrderInbox&gt;\r\n    {\r\n    }\r\n<\/pre>\n<\/div>\n<div style=\"font-size:12px;\">\n<pre class=\"brush: jscript; title: ; notranslate\" title=\"\">\r\n    public class OrderInboxRepository : Repository&lt;OrderInbox&gt;, IOrderInboxRepository\r\n    {\r\n        public OrderInboxRepository(OutboxExampleDbContext context) : base(context)\r\n        {\r\n        }\r\n    }\r\n<\/pre>\n<\/div>\n<p>Tabi IoC&#8217;den bu repository&#8217;nin \u00e7a\u011fr\u0131labilmesi i\u00e7in service registration&#8217;\u0131 unutmayal\u0131m.<\/p>\n<div style=\"font-size:12px;\">\n<pre class=\"brush: jscript; title: ; notranslate\" title=\"\">\r\n    public static class ServiceRegistrations\r\n    {\r\n        public static void AddPersistenceServices(this IServiceCollection services)\r\n        {\r\n            .\r\n            .\r\n            .\r\n            services.AddScoped&lt;IOrderInboxRepository, OrderInboxRepository&gt;();\r\n        }\r\n    }\r\n<\/pre>\n<\/div>\n<p>Son olarak ise context nesnesine DbSet olarak ekleyelim ve migration bas\u0131p, migrate edelim.<\/p>\n<div style=\"font-size:12px;\">\n<pre class=\"brush: jscript; title: ; notranslate\" title=\"\">\r\n    public class OutboxExampleDbContext : DbContext\r\n    {\r\n        public OutboxExampleDbContext(DbContextOptions options) : base(options)\r\n        {\r\n        }\r\n        public DbSet&lt;Order&gt; Orders { get; set; }\r\n        public DbSet&lt;OrderOutbox&gt; OrderOutboxes { get; set; }\r\n        public DbSet&lt;OrderInbox&gt; OrderInboxes{ get; set; }\r\n        protected override void OnModelCreating(ModelBuilder modelBuilder)\r\n        {\r\n            modelBuilder.ApplyConfiguration(new OrderConfiguration());\r\n            modelBuilder.ApplyConfiguration(new OrderOutboxConfiguration());\r\n            modelBuilder.ApplyConfiguration(new OrderInboxConfiguration());\r\n        }\r\n    }\r\n<\/pre>\n<\/div>\n<\/li>\n<li><em>Ad\u0131m 14 <span style=\"font-size:12px;color:green;\">(Consumer&#8217;da idempotent ilkesinin uygulanmas\u0131)<\/span><\/em><br \/>\nConsumer&#8217;\u0131 a\u015fa\u011f\u0131daki gibi geli\u015ftirelim.<\/p>\n<div style=\"font-size:12px;\">\n<pre class=\"brush: jscript; title: ; notranslate\" title=\"\">\r\n    public class OrderCreatedEventConsumer : IConsumer&lt;OrderCreatedEvent&gt;\r\n    {\r\n        readonly IOrderInboxRepository _orderInboxRepository;\r\n\r\n        public OrderCreatedEventConsumer(IOrderInboxRepository orderInboxRepository)\r\n        {\r\n            _orderInboxRepository = orderInboxRepository;\r\n        }\r\n\r\n        public async Task Consume(ConsumeContext&lt;OrderCreatedEvent&gt; context)\r\n        {\r\n            bool hasData = _orderInboxRepository.GetWhere(oi =&gt; oi.IdempotentToken == context.Message.IdempotentToken &amp;&amp; oi.Processed).Any();\r\n            \/*\r\n             hasData : true ise demek ki bu event \u00f6nceden gelmi\u015f ve ba\u015far\u0131yla i\u015flenmi\u015f lakin publisher taraf\u0131ndan g\u00f6nderildi\u011fine\r\n             dair g\u00fcncelleme i\u015flemi bilinmeyen bir sebepten dolay\u0131 ger\u00e7ekle\u015ftirilemedi\u011fi i\u00e7in tekrardan\r\n             publish edilmi\u015ftir. \u0130\u015fte idempotent \u00f6zelli\u011fi sayesinde bu event'in tekrar i\u015flenmesinin \u00f6n\u00fcne b\u00f6ylece ge\u00e7ilmektedir.\r\n             *\/\r\n\r\n            if (!hasData)\r\n            {\r\n                await _orderInboxRepository.AddAsync(new()\r\n                {\r\n                    Description = context.Message.Description,\r\n                    IdempotentToken = context.Message.IdempotentToken,\r\n                    OrderId = context.Message.OrderId,\r\n                    Quantity = context.Message.Quantity,\r\n                    Processed = false\r\n                });\r\n                await _orderInboxRepository.SaveChangesAsync();\r\n            }\r\n\r\n            \/\/Inbox table'a kaydedilen mesaj\/event \u00e7ekilip i\u015fleme tabi tutulmaktad\u0131r.\r\n\r\n            List&lt;OrderInbox&gt; orderInboxes = _orderInboxRepository.GetWhere(oi =&gt; !oi.Processed).ToList();\r\n            foreach (var orderInbox in orderInboxes)\r\n            {\r\n                Console.WriteLine(@$&quot;OrderId : {orderInbox.OrderId}\r\n                                         Id : {orderInbox.IdempotentToken}\r\n                                         Stock {orderInbox.Quantity} miktar kadar d\u00fc\u015f\u00fcr\u00fclm\u00fc\u015ft\u00fcr!&quot;);\r\n                orderInbox.Processed = true;\r\n            }\r\n            await _orderInboxRepository.SaveChangesAsync();\r\n\r\n        }\r\n    }\r\n<\/pre>\n<\/div>\n<p>G\u00f6r\u00fcld\u00fc\u011f\u00fc \u00fczere gelen mesaj \u00f6nce Inbox table&#8217;da var m\u0131 kontrol edilmekte, yoksa e\u011fer eklenmekte ve ard\u0131ndan i\u015fleme tabi tutulmaktad\u0131r.\n<\/li>\n<\/ul>\n<p>\u0130\u015fte bu kadar \ud83d\ude42<\/p>\n<h3 style=\"color:#e83e8c;\">Test Edelim<\/h3>\n<p>\u015eimdi yapt\u0131\u011f\u0131m\u0131z bu \u00e7al\u0131\u015fmay\u0131 test edebiliriz&#8230;<br \/>\nBunun i\u00e7in k\u0131ssadan hisse yaparak SQL sorgular\u0131ndan istifade edece\u011fiz.<\/p>\n<p>A\u015fa\u011f\u0131daki sorgu ile hem &#8216;Orders&#8217; hem de &#8216;OrderOutboxes&#8217; tablolar\u0131na e\u015fzamanl\u0131 olarak 10000 kay\u0131t at\u0131labilmektedir.<\/p>\n<div style=\"font-size:12px;\">\n<pre class=\"brush: plain; title: ; notranslate\" title=\"\">\r\nDECLARE @i INT = 0\r\nWHILE @i &lt; 10000\r\nBEGIN\r\n\tDECLARE @id UNIQUEIDENTIFIER = NEWID()\r\n\tINSERT Orders(Id, Description, Quantity) VALUES(@id, 'sipari\u015f - ' + CAST(@i as NVARCHAR(MAX)), @i * 2)\r\n\tINSERT OrderOutboxes(OccuredOn, Payload, Type, IdempotentToken) VALUES(GETDATE(), '{&quot;Id&quot;:&quot;' + CAST(@id as NVARCHAR(MAX)) + '&quot;,&quot;Quantity&quot;:0,&quot;Description&quot;:&quot;sipari\u015f - ' + CAST(@i as NVARCHAR(MAX))+ '&quot;}', 'OrderCreatedEvent', NEWID())\r\n\tSET @i = @i + 1\r\nEND\r\n<\/pre>\n<\/div>\n<p>Yukar\u0131daki sorgu \u00e7al\u0131\u015f\u0131rken bir yandan da &#8216;ProcessOutboxJob.Service&#8217; ve &#8216;Stock.API&#8217; uygulamalar\u0131 ayakta oldu\u011fu taktirde a\u015fa\u011f\u0131daki sorgular e\u015fli\u011finde g\u00f6rseldeki gibi tutarl\u0131 mesajla\u015fma s\u00fcrecini g\u00f6zlemleyebiliriz.<\/p>\n<div style=\"font-size:12px;\">\n<pre class=\"brush: jscript; title: ; notranslate\" title=\"\">\r\nSELECT * FROM Orders\r\n<\/pre>\n<\/div>\n<div style=\"font-size:12px;\">\n<pre class=\"brush: jscript; title: ; notranslate\" title=\"\">\r\nSELECT * FROM OrderOutboxes \r\nWHERE ProcessedDate IS NULL\r\n<\/pre>\n<\/div>\n<div style=\"font-size:12px;\">\n<pre class=\"brush: jscript; title: ; notranslate\" title=\"\">\r\nSELECT * FROM OrderInboxes\r\n<\/pre>\n<\/div>\n<p><a href=\"https:\/\/www.gencayyildiz.com\/blog\/wp-content\/uploads\/2022\/07\/Outbox-Design-PatternOutbox-Tasarim-Deseni.gif\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/www.gencayyildiz.com\/blog\/wp-content\/uploads\/2022\/07\/Outbox-Design-PatternOutbox-Tasarim-Deseni.gif\" alt=\"Outbox Design Pattern(Outbox Tasar\u0131m Deseni)\" width=\"800\" height=\"431\" class=\"aligncenter size-full wp-image-25406\" \/><\/a>Ara ara &#8216;OrderOutboxPublishJob&#8217; s\u0131n\u0131f\u0131ndaki update operasyonunu kald\u0131r\u0131p s\u00fcreci devam ettirirsek e\u011fer Outbox table&#8217;da i\u015flendi\u011fi halde hala g\u00fcncellenmeyen kay\u0131tlar\u0131n kald\u0131\u011f\u0131n\u0131 g\u00f6receksiniz. Bu kay\u0131tlar\u0131n sonraki s\u00fcre\u00e7te tutars\u0131z herhangi bir i\u015fleme maruz kalmaks\u0131z\u0131n ba\u015far\u0131l\u0131 bir \u015fekilde kald\u0131r\u0131laca\u011f\u0131n\u0131 g\u00f6zlemleyebilirsiniz. E\u011fer ki tutars\u0131zl\u0131k oldu\u011funu d\u00fc\u015f\u00fcn\u00fcyorsan\u0131z a\u015fa\u011f\u0131daki SQL sorgusu ile &#8216;Orders&#8217; ve &#8216;OrderInboxes&#8217; tablolar\u0131 aras\u0131ndaki farklar\u0131 g\u00f6rebilir ve s\u00fcre\u00e7te hangi verilerde tutars\u0131zl\u0131k olabilece\u011fini verisel a\u00e7\u0131dan monit\u00f6r edebilirsiniz.<\/p>\n<div style=\"font-size:12px;\">\n<pre class=\"brush: jscript; title: ; notranslate\" title=\"\">\r\nSELECT * FROM Orders\r\nLEFT JOIN OrderInboxes\r\nON Orders.Id = OrderInboxes.OrderID\r\nWHERE OrderInboxes.OrderID IS NULL\r\n<\/pre>\n<\/div>\n<p>Yukar\u0131daki sorgu neticesinde gelen veriler olursa e\u011fer a\u015fa\u011f\u0131daki sorgu ile de Outbox table&#8217;da ki halini taray\u0131p rahatl\u0131kla son durum hakk\u0131nda bilgi edinebilirsiniz.<\/p>\n<div style=\"font-size:12px;\">\n<pre class=\"brush: jscript; title: ; notranslate\" title=\"\">\r\nSELECT * FROM OrderOutboxes \r\nWHERE IdempotentToken = '80A716BA-6549-4A26-AD89-59D0FF63D87D'\r\n<\/pre>\n<\/div>\n<p><a href=\"https:\/\/www.gencayyildiz.com\/blog\/wp-content\/uploads\/2022\/07\/Outbox-Design-PatternOutbox-Tasarim-Deseni-1.gif\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/www.gencayyildiz.com\/blog\/wp-content\/uploads\/2022\/07\/Outbox-Design-PatternOutbox-Tasarim-Deseni-1.gif\" alt=\"Outbox Design Pattern(Outbox Tasar\u0131m Deseni)\" width=\"800\" height=\"431\" class=\"aligncenter size-full wp-image-25407\" \/><\/a><\/p>\n<p>Nihai olarak;<br \/>\nOutbox pattern ile distributed sistemlerdeki ileti\u015fim s\u00fcrecinde tahaahh\u00fct edilebilmesi zor olan i\u015flemleri daha garantili hale getiriyor ve hatta akrabas\u0131 olan Inbox pattern deste\u011fiyle de s\u00fcre\u00e7te i\u015flenen veriler \u00fczerinde garanti seviyesini bir t\u0131k daha ileri ta\u015f\u0131yoruz. Yap\u0131sal olarak Outbox pattern&#8217;\u0131n ihtiya\u00e7 noktalar\u0131nda olduk\u00e7a elveri\u015fli ve hayat kurtaran ama bir o kadar da zahmetli mimarisi oldu\u011funu g\u00f6rm\u00fc\u015f olduk. Bu i\u00e7eri\u011fimizde de\u011finmemi\u015f olsak da Outbox table&#8217;a veri eklenecek t\u00fcm eylemleri ihtiya\u00e7 noktalar\u0131nda de\u011fil de notification yap\u0131lanmalar\u0131yla koordine etmeye \u00e7al\u0131\u015fmak mimarisel a\u00e7\u0131dan daha \u015f\u0131k bir davran\u0131\u015f sergilememizi sa\u011flayacakt\u0131r kanaatindeyim. Bunuda sizlere bir fikir olmas\u0131 temennisiyle sunup i\u00e7eri\u011fimi noktal\u0131yorum.<\/p>\n<p>\u00dc\u015fenmeden okuyup, e\u015flik etti\u011finiz i\u00e7in te\u015fekk\u00fcr ederim.<\/p>\n<p>\u0130lgilenenlerin faydalanmas\u0131 dile\u011fiyle&#8230;<br \/>\nSonraki yaz\u0131lar\u0131mda g\u00f6r\u00fc\u015fmek \u00fczere&#8230;<br \/>\n\u0130yi \u00e7al\u0131\u015fmalar&#8230;<\/p>\n<p>Not : \u00d6rnek \u00e7al\u0131\u015fmay\u0131 a\u015fa\u011f\u0131daki github adresinden inceleyebilirsiniz.<br \/>\n<a href=\"https:\/\/github.com\/gncyyldz\/Outbox-Design-Pattern-Example\" rel=\"noopener\" target=\"_blank\">https:\/\/github.com\/gncyyldz\/Outbox-Design-Pattern-Example<\/a><\/p>\n<!-- AddThis Advanced Settings generic via filter on the_content --><!-- AddThis Share Buttons generic via filter on the_content -->","protected":false},"excerpt":{"rendered":"<p>Merhaba, G\u00fcnl\u00fck ya\u015famda gurbet ellerde bulunan bir yak\u0131n\u0131n\u0131za hat\u0131ra dolu bir mektup yaz\u0131p g\u00f6ndermek istedi\u011finiz olmu\u015ftur. Mesela b\u00f6yle bir istek kar\u015f\u0131l\u0131\u011f\u0131nda ne yapard\u0131n\u0131z hi\u00e7 d\u00fc\u015f\u00fcnd\u00fcn\u00fcz m\u00fc? Muhtemelen, mektubu kaleme ald\u0131ktan sonra zarflay\u0131p, g\u00f6nderilecek adres&#46;&#46;&#46;<!-- AddThis Advanced Settings generic via filter on get_the_excerpt --><!-- AddThis Share Buttons generic via filter on get_the_excerpt --><\/p>\n","protected":false},"author":1,"featured_media":25359,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[2739,12,153],"tags":[4625,4626,4217,4631,4633,4632,4635,4634,4636,4637,4623,4622,4627,4630,4624,4628,1025,4629],"class_list":["post-25350","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-net-core","category-c-sharp-c","category-design-pattern","tag-dual-write","tag-dual-write-nedir","tag-event-sourcing","tag-eventuate-tram","tag-garantili-teslimat-deseni","tag-guaranteed-delivery-pattern","tag-idempotent-nedir","tag-idempotents","tag-inbox-pattern","tag-inbox-pattern-nedir","tag-outbox-design-pattern","tag-outbox-design-pattern-nedir","tag-outbox-pattern-pulling-publisher","tag-outbox-pattern-transaction-log-tailing","tag-outbox-tasarim-deseni","tag-pulling-publisher","tag-quartz-net","tag-transaction-log-tailing"],"amp_enabled":true,"_links":{"self":[{"href":"https:\/\/www.gencayyildiz.com\/blog\/wp-json\/wp\/v2\/posts\/25350","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.gencayyildiz.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.gencayyildiz.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.gencayyildiz.com\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.gencayyildiz.com\/blog\/wp-json\/wp\/v2\/comments?post=25350"}],"version-history":[{"count":49,"href":"https:\/\/www.gencayyildiz.com\/blog\/wp-json\/wp\/v2\/posts\/25350\/revisions"}],"predecessor-version":[{"id":25412,"href":"https:\/\/www.gencayyildiz.com\/blog\/wp-json\/wp\/v2\/posts\/25350\/revisions\/25412"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/www.gencayyildiz.com\/blog\/wp-json\/wp\/v2\/media\/25359"}],"wp:attachment":[{"href":"https:\/\/www.gencayyildiz.com\/blog\/wp-json\/wp\/v2\/media?parent=25350"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.gencayyildiz.com\/blog\/wp-json\/wp\/v2\/categories?post=25350"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.gencayyildiz.com\/blog\/wp-json\/wp\/v2\/tags?post=25350"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}