{"id":4304,"date":"2023-03-09T21:36:28","date_gmt":"2023-03-09T20:36:28","guid":{"rendered":"https:\/\/gokhan-gokalp.com\/?p=4304"},"modified":"2023-03-11T19:27:19","modified_gmt":"2023-03-11T18:27:19","slug":"providing-atomicity-for-eventual-consistency-with-outbox-pattern-in-net-microservices","status":"publish","type":"post","link":"https:\/\/gokhan-gokalp.com\/tr\/providing-atomicity-for-eventual-consistency-with-outbox-pattern-in-net-microservices\/","title":{"rendered":".NET Microservice\u2019lerinde Outbox Pattern\u2019\u0131 ile Eventual Consistency i\u00e7in Atomicity Sa\u011flama"},"content":{"rendered":"<p>Bildi\u011fimiz gibi microservice architecture&#8217;\u0131na adapte olman\u0131n bir \u00e7ok art\u0131 noktas\u0131 oldu\u011fu gibi, maalesef getirdi\u011fi baz\u0131 zorlay\u0131c\u0131 noktalar\u0131 da bulunmakta. \u00d6zellikle <strong>data <\/strong>consistency taraf\u0131nda.\u00a0\u00d6rne\u011fin, monolithic d\u00fcnyada birden fazla i\u015flemi consistent bir \u015fekilde kolayca ger\u00e7ekle\u015ftirebilmek i\u00e7in, <strong><em>ACID<\/em> <\/strong>database transaction&#8217;lar\u0131ndan yararlanabilmekteyiz. Fakat microservice d\u00fcnyas\u0131nda i\u015flemlerin farkl\u0131 service&#8217;ler taraf\u0131ndan distributed olarak ele al\u0131nmas\u0131 gerekti\u011fi i\u00e7in, maalesef i\u015fler monolithic d\u00fcnyas\u0131 kadar kolay olmamaktad\u0131r ve data consistency&#8217;i genellikle event&#8217;ler arac\u0131l\u0131\u011f\u0131yla <strong>eventual<\/strong> olarak sa\u011flamam\u0131z gerekmektedir.<\/p>\n<p>Ayr\u0131ca long-running business i\u015flemlerini de consistent bir \u015fekilde ger\u00e7eke\u015ftirebilmek i\u00e7in, <em><strong>Saga<\/strong> <\/em>gibi pattern&#8217;lerden yararlanabilmekteyiz. Yani her bir microservice&#8217;in bireysel olarak sorumluluklar\u0131n\u0131 kendi database&#8217;inde yerine getirdikten sonra, di\u011fer microservice&#8217;lerin de ilgili sorumluluklar\u0131n\u0131 yerine getirebilmeleri i\u00e7in bir event publish etmesi veya bir orchestrator taraf\u0131ndan ilgili business s\u00fcrecinin s\u0131ras\u0131yla koordine edilmesi \u015feklinde.<\/p>\n<p>Bu transaction management konusunu daha \u00f6nce farkl\u0131 makaleler alt\u0131nda, farkl\u0131 senaryolar ile ele alm\u0131\u015ft\u0131m. \u00d6rne\u011fin &#8220;<a href=\"https:\/\/gokhan-gokalp.com\/implementation-of-choreography-based-saga-in-dotnet-microservices\/\" target=\"_blank\" rel=\"noopener\"><em>.NET Microservice\u2019lerinde Choreography-based Saga<\/em><\/a>&#8221; makalesinde, saga pattern&#8217;ini choreography-based olarak kolayca nas\u0131l implemente edebilece\u011fimizden bahsetmi\u015ftim. Ek olarak event&#8217;ler de resiliency&#8217;nin b\u00fcy\u00fck \u00f6nem ta\u015f\u0131d\u0131\u011f\u0131n\u0131 ve bu sebeple outbox gibi pattern&#8217;ler den yararlanabilece\u011fimizden de bahsetmi\u015ftim. Dolay\u0131s\u0131yla bu makaleyi de saga makalesinin devam\u0131 niteli\u011finde d\u00fc\u015f\u00fcnebiliriz. Ayr\u0131ca saga makalesindeki ayn\u0131 \u00f6rnek senaryo \u00fczerinden ilerleyece\u011fim. Bu nedenle konuyu daha iyi anlayabilmek ad\u0131na, \u00f6ncelikle <em><a href=\"https:\/\/gokhan-gokalp.com\/implementation-of-choreography-based-saga-in-dotnet-microservices\/\" target=\"_blank\" rel=\"noopener\">bu<\/a> <\/em>makaleye h\u0131zl\u0131 bir g\u00f6z atmak, faydal\u0131 olacakt\u0131r.<\/p>\n<p>Distributed d\u00fcnyada s\u00f6z konusu business process&#8217;lerinin <strong>b\u00fct\u00fcnl\u00fc\u011f\u00fc<\/strong> \/ data&#8217;n\u0131n farkl\u0131 noktalarda <strong>consistent<\/strong> ve <strong>reliable<\/strong> olmas\u0131 oldu\u011funda, event&#8217;lerin <strong>resilient<\/strong> bir \u015fekilde publish edilebilmeleri olduk\u00e7a b\u00fcy\u00fck bir \u00f6bnem ta\u015f\u0131maktad\u0131r.<a href=\"\/wp-content\/uploads\/2023\/02\/saga-happy-path.jpg\"><img decoding=\"async\" class=\"aligncenter size-full wp-image-4309 lazyload\" data-src=\"\/wp-content\/uploads\/2023\/02\/saga-happy-path.jpg\" alt=\"\" width=\"822\" height=\"322\" data-srcset=\"https:\/\/gokhan-gokalp.com\/wp-content\/uploads\/2023\/02\/saga-happy-path.jpg 822w, https:\/\/gokhan-gokalp.com\/wp-content\/uploads\/2023\/02\/saga-happy-path-300x118.jpg 300w, https:\/\/gokhan-gokalp.com\/wp-content\/uploads\/2023\/02\/saga-happy-path-768x301.jpg 768w\" data-sizes=\"(max-width: 822px) 100vw, 822px\" src=\"data:image\/svg+xml;base64,PHN2ZyB3aWR0aD0iMSIgaGVpZ2h0PSIxIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPjwvc3ZnPg==\" style=\"--smush-placeholder-width: 822px; --smush-placeholder-aspect-ratio: 822\/322;\" \/><\/a><\/p>\n<p>\u00d6rne\u011fin yukar\u0131daki senaryoda asynchronous bir sat\u0131n alma s\u00fcreci bulunmaktad\u0131r (saga makaleside kulland\u0131\u011f\u0131m senaryo). Her bir service ilgili i\u015flemini ger\u00e7ekle\u015ftirdikten sonra, message queue&#8217;ya bir event publish etmektedir ve bir sonraki s\u00fcreci tetiklemektedir.<\/p>\n<p>Peki, ilgili service kendi i\u015flemini kendi database&#8217;inde ger\u00e7ekle\u015ftirdikten sonra, ilgili event&#8217;ini herhangi bir nedenden dolay\u0131 publish edemezse? \u00d6rne\u011fin &#8220;<em>StockReservedEvent<\/em>&#8221; inin publish edildi\u011fini ve \u00f6deme i\u015flemlerinin &#8220;<em>PaymentService<\/em>&#8221; i taraf\u0131ndan ger\u00e7ekle\u015ftirdi\u011fini fakat &#8220;<em>PaymentCompletedEvent<\/em>&#8221; ini publish edemedi\u011fini d\u00fc\u015f\u00fcnelim. Elbette ge\u00e7ici network kaynakl\u0131 hatalar\u0131 \u00e7e\u015fitli retry mekanizmalar\u0131 ile ele alabiliriz. Fakat message broker&#8217;\u0131n down oldu\u011fu gibi farkl\u0131 senaryolarda ise ilgili event&#8217;leri kaybediyor olaca\u011f\u0131z. \u00d6zellikle \u00f6deme i\u015flemi gibi \u00f6nemli noktalarda inconsistent bir data&#8217;ya sahip olmak, san\u0131r\u0131m hi\u00e7 birimizin istemeyece\u011fi bir durum.<\/p>\n<p>K\u0131sacas\u0131 kritik business process&#8217;leri \u00fczerinde \u00e7al\u0131\u015f\u0131yorsak, tahmin edebilece\u011fimiz gibi data&#8217;n\u0131n consistent ve accurate durumda olmas\u0131 olduk\u00e7a \u00f6nem arz etmektedir ve dolay\u0131s\u0131yla ilgili event&#8217;lerin <strong>atomic<\/strong> tarzda publish edilmeleri faydam\u0131za olacakt\u0131r.<\/p>\n<h2>Outbox Pattern<\/h2>\n<p>Outbox pattern&#8217;\u0131 k\u0131saca bizlere event&#8217;lerimizi reliable bir \u015fekilde publish edebilece\u011fimiz bir yakla\u015f\u0131m sunmaktad\u0131r. Bu yakla\u015f\u0131mda event&#8217;leri do\u011frudan publish etmek yerine, onlar\u0131 ilgili microservice&#8217;in database&#8217;i i\u00e7erisinde ilgili business process&#8217;inin bir par\u00e7as\u0131 olarak atomic bir \u015fekilde outbox table&#8217;\u0131na kaydetmemiz ve daha sonra bir ba\u015fka service arac\u0131l\u0131\u011f\u0131 ile kaydetmi\u015f oldu\u011fumuz event&#8217;leri publish etmemiz gerekmektedir. B\u00f6ylelikle message broker down olsa bile, ilgili data&#8217;n\u0131n state&#8217;ini ve event&#8217;leri kaybetmemi\u015f ve eventual consistency&#8217;nin <strong>reliable<\/strong> ve <strong>durable<\/strong> bir \u015fekilde \u00e7al\u0131\u015fabilmesini sa\u011flam\u0131\u015f olaca\u011f\u0131z.<\/p>\n<p><a href=\"\/wp-content\/uploads\/2023\/02\/outbox-flow.jpg\"><img decoding=\"async\" class=\"aligncenter size-full wp-image-4316 lazyload\" data-src=\"\/wp-content\/uploads\/2023\/02\/outbox-flow.jpg\" alt=\"\" width=\"541\" height=\"341\" data-srcset=\"https:\/\/gokhan-gokalp.com\/wp-content\/uploads\/2023\/02\/outbox-flow.jpg 541w, https:\/\/gokhan-gokalp.com\/wp-content\/uploads\/2023\/02\/outbox-flow-300x189.jpg 300w\" data-sizes=\"(max-width: 541px) 100vw, 541px\" src=\"data:image\/svg+xml;base64,PHN2ZyB3aWR0aD0iMSIgaGVpZ2h0PSIxIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPjwvc3ZnPg==\" style=\"--smush-placeholder-width: 541px; --smush-placeholder-aspect-ratio: 541\/341;\" \/><\/a><\/p>\n<blockquote><p><em><strong>NOT<\/strong>: Outbox pattern&#8217;\u0131 &#8220;at-least-once&#8221; delivery yakla\u015f\u0131m\u0131yla \u00e7al\u0131\u015ft\u0131\u011f\u0131 i\u00e7in, event&#8217;leri outbox table&#8217;\u0131ndan publish edece\u011fimiz zaman da duplication&#8217;lar ger\u00e7ekle\u015febilir. Dolay\u0131s\u0131yla consumer&#8217;lar\u0131n duplication&#8217;\u0131 handle edebilmeleri i\u00e7in, event&#8217;lerin i\u00e7erisine bir identifier dahil etmemiz ve olabildi\u011fince <strong>idempotent <\/strong>olabilmelerine \u00f6zen g\u00f6stermemiz gerekmektedir.<\/em><\/p><\/blockquote>\n<p><a href=\"\/wp-content\/uploads\/2023\/02\/outbox-worker.jpeg\"><img decoding=\"async\" class=\"aligncenter size-full wp-image-4318 lazyload\" data-src=\"\/wp-content\/uploads\/2023\/02\/outbox-worker.jpeg\" alt=\"\" width=\"541\" height=\"341\" data-srcset=\"https:\/\/gokhan-gokalp.com\/wp-content\/uploads\/2023\/02\/outbox-worker.jpeg 541w, https:\/\/gokhan-gokalp.com\/wp-content\/uploads\/2023\/02\/outbox-worker-300x189.jpeg 300w\" data-sizes=\"(max-width: 541px) 100vw, 541px\" src=\"data:image\/svg+xml;base64,PHN2ZyB3aWR0aD0iMSIgaGVpZ2h0PSIxIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPjwvc3ZnPg==\" style=\"--smush-placeholder-width: 541px; --smush-placeholder-aspect-ratio: 541\/341;\" \/><\/a><\/p>\n<p>Tech stack&#8217;e ba\u011fl\u0131 olarak bu pattern&#8217;\u0131 uygulaman\u0131n farkl\u0131 yollar\u0131 bulunmakta. \u00d6rne\u011fin message broker olarak <em><strong>Kafka<\/strong> <\/em>kullan\u0131yorsak, <em><strong>Debezium <\/strong><\/em>ile kolay bir \u015fekilde log-based change data capturing i\u015flemi yapabilir ve ilgili de\u011fi\u015fiklikleri kafka taraf\u0131na stream edebiliriz. Farkl\u0131 bir broker ile ise service bus olarak <em><strong>NServiceBus<\/strong><\/em>, <em><strong>MassTransit<\/strong> veya <strong>Cap<\/strong> <\/em>kullan\u0131yorsak, onlar\u0131n da sunmu\u015f oldu\u011fu outbox \u00f6zelliklerinden faydalanabilir veya manuel olarak kendimiz de implemente edebiliriz.<\/p>\n<p>Bu makale kapsam\u0131nda ise implementation i\u015flemini, <em><a href=\"https:\/\/gokhan-gokalp.com\/implementation-of-choreography-based-saga-in-dotnet-microservices\/\" target=\"_blank\" rel=\"noopener\">saga<\/a> <\/em>makalesinde kullanm\u0131\u015f oldu\u011fum asynchronous sat\u0131n alma s\u00fcreci \u00f6rnek senaryosu \u00fczerinde manuel olarak ger\u00e7ekle\u015ftirece\u011fiz.<\/p>\n<h3>Implemente Edelim<\/h3>\n<p>Outbox pattern&#8217;\u0131n\u0131n implementation i\u015flemini manuel olarak ger\u00e7ekle\u015ftirece\u011fiz \u00e7\u00fcnk\u00fc ger\u00e7ek d\u00fcnyada herhangi bir ihtiya\u00e7 kar\u015f\u0131s\u0131nda bu \u00f6zelli\u011fi destekleyen service&#8217;bus&#8217;lar\u0131 kullanm\u0131yor olabiliriz veya farkl\u0131 bir message broker kullan\u0131yor olabiliriz.<\/p>\n<p>Ge\u00e7ti\u011fimiz sene \u00fczerinde \u00e7al\u0131\u015ft\u0131\u011f\u0131m \u00f6zel bir projede benzer bir ihtiya\u00e7la kar\u015f\u0131la\u015fm\u0131\u015ft\u0131m. Bizim i\u00e7in kritik say\u0131lacak bir noktada nadirende olsa publish olamayan event&#8217;lerden dolay\u0131, m\u00fc\u015fterilerimiz sat\u0131n ald\u0131klar\u0131 online hizmetlere maalesef eri\u015fememi\u015flerdi. Message broker olarak <em>RabbitMQ<\/em> ve service bus olarak da <em>EasyNetQ<\/em> kulland\u0131\u011f\u0131m\u0131z i\u00e7in \u00f6deme i\u015flemleri taraf\u0131nda outbox pattern&#8217;\u0131n\u0131 manuel olarak implement etme ihtiyac\u0131m olu\u015fmu\u015ftu.<\/p>\n<p>\u015eimdi ilk olarak \u00f6rnek projeye <em><a href=\"https:\/\/github.com\/GokGokalp\/choreography-saga-dotnet\" target=\"_blank\" rel=\"noopener\">buradan<\/a> <\/em>eri\u015fim sa\u011flayal\u0131m. Payment Service microservice&#8217;i i\u00e7erisinde \u00f6deme i\u015flemlerini ger\u00e7ekle\u015ftikten sonra, <em>PaymentCompletedEvent<\/em> &#8216;ini atomic bir \u015fekilde publish etmek istedi\u011fimizi varsayal\u0131m. \u00c7\u00fcnk\u00fc daha \u00f6nce a\u015fa\u011f\u0131daki gibi \u00f6deme i\u015flemleri ba\u015far\u0131yla ger\u00e7ekle\u015ftikten sonra, direkt olarak ilgili event&#8217;i publish ediyorduk.<\/p>\n<pre>public class StocksReservedEventConsumer : IConsumeAsync&lt;StocksReservedEvent&gt;\r\n{\r\n    private readonly IPaymentService _paymentService;\r\n    private readonly IBus _bus;\r\n\r\n    public StocksReservedEventConsumer(IPaymentService paymentService, IBus bus)\r\n    {\r\n        _paymentService = paymentService;\r\n        _bus = bus;\r\n    }\r\n\r\n    public async Task ConsumeAsync(StocksReservedEvent message, CancellationToken cancellationToken = default)\r\n    {   \r\n        Tuple&lt;bool, string&gt; isPaymentCompleted = await _paymentService.DoPaymentAsync(message.WalletId, message.UserId, message.TotalAmount);\r\n\r\n        if (isPaymentCompleted.Item1)\r\n        {\r\n            await _bus.PubSub.PublishAsync(new PaymentCompletedEvent\r\n            {\r\n                OrderId = message.OrderId\r\n            });\r\n        }\r\n        else\r\n        {\r\n            await _bus.PubSub.PublishAsync(new PaymentRejectedEvent\r\n            {\r\n                OrderId = message.OrderId,\r\n                Reason = isPaymentCompleted.Item2\r\n            });\r\n        }\r\n    }\r\n}<\/pre>\n<p>\u0130lk olarak &#8220;<em>PaymentService.Infra<\/em>&#8221; (.NET 5.0) ad\u0131nda bir class library olu\u015ftural\u0131m ve ard\u0131ndan a\u015fa\u011f\u0131daki paketleri <em>NuGet<\/em> \u00fczerinden dahil edelim.<\/p>\n<pre>Microsoft.EntityFrameworkCore - 5.0.0\r\nMicrosoft.EntityFrameworkCore.SqlServer - 5.0.0<\/pre>\n<p>\u015eimdi event&#8217;lerimizi store edece\u011fimiz outbox table&#8217;\u0131n\u0131, a\u015fa\u011f\u0131daki gibi &#8220;<em>Models<\/em>&#8221; ad\u0131nda bir klas\u00f6r olu\u015fturarak i\u00e7erisinde tan\u0131mlayal\u0131m.<\/p>\n<pre>using System;\r\n\r\nnamespace PaymentService.Infra.Models\r\n{\r\n\tpublic class OutboxMessage\r\n\t{\r\n\t\tpublic OutboxMessage()\r\n\t\t{\r\n\t\t\tEventDate = DateTime.UtcNow;\r\n\t\t}\r\n\t\tpublic int Id { get; set; }\r\n\t\tpublic string EventType { get; set; }\r\n\t\tpublic string EventPayload { get; set; }\r\n\t\tpublic DateTime EventDate { get; set; }\r\n\t\tpublic bool IsSent { get; set; }\r\n\t\tpublic DateTime? SentDate { get; set; }\r\n\t}\r\n}<\/pre>\n<pre>CREATE TABLE OutboxMessages\r\n(\r\n\tId int IDENTITY PRIMARY KEY,\r\n\tEventType nvarchar(255) NOT NULL,\r\n\tEventPayload nvarchar(Max) NOT NULL,\r\n\tEventDate datetime NOT NULL,\r\n\tIsSent bit NOT NULL,\r\n\tSentDate datetime\r\n);<\/pre>\n<p>Daha \u00f6nce \u00f6rnek projeyi geli\u015ftirirken ana konuya odaklanabilmek i\u00e7in baz\u0131 noktalar\u0131 es ge\u00e7mi\u015ftik ve \u00f6deme i\u015flemlerini ger\u00e7ekle\u015ftiren &#8220;<em>DoPaymentAsync<\/em>&#8221; method&#8217;unu implemente etmemi\u015ftik. Bu y\u00fczden \u015fimdi basit bir &#8220;<em>Payment<\/em>&#8221; modelini de a\u015fa\u011f\u0131daki gibi projeye dahil edelim.<\/p>\n<pre>using System;\r\n\r\nnamespace PaymentService.Infra.Models\r\n{\r\n\tpublic class Payment\r\n\t{\r\n\t\tpublic Payment()\r\n\t\t{\r\n\t\t\tPaymentDate = DateTime.UtcNow;\r\n\t\t}\r\n\r\n\t\tpublic int Id { get; set; }\r\n\t\tpublic int UserId { get; set; }\r\n\t\tpublic int OrderId { get; set; }\r\n\t\tpublic int WalletId { get; set; }\r\n\t\tpublic decimal TotalAmount { get; set; }\r\n\t\tpublic bool IsPaid { get; set; }\r\n\t\tpublic DateTime PaymentDate { get; set; }\r\n\t}\r\n}<\/pre>\n<pre>CREATE TABLE Payments\r\n(\r\n\tId int IDENTITY PRIMARY KEY,\r\n\tUserId int NOT NULL,\r\n\tOrderId int NOT NULL,\r\n\tWalletId int NOT NULL,\r\n\tTotalAmount decimal(18,4) NOT NULL,\r\n\tIsPaid bit NOT NULL,\r\n\tPaymentDate datetime NOT NULL\r\n);<\/pre>\n<p>Ard\u0131ndan &#8220;<em>AppDbContext<\/em>&#8221; ad\u0131nda bir class olu\u015fturarak i\u00e7erisinde dbcontext&#8217;i de tan\u0131mlayal\u0131m.<\/p>\n<pre>using Microsoft.EntityFrameworkCore;\r\nusing PaymentService.Models;\r\n\r\nnamespace PaymentService.Infra\r\n{\r\n\tpublic class AppDbContext : DbContext\r\n\t{\r\n\t\tpublic AppDbContext(DbContextOptions&lt;AppDbContext&gt; options) : base(options)\r\n\t\t{ }\r\n\r\n\t\tpublic DbSet&lt;Payment&gt; Payments { get; set; }\r\n\t\tpublic DbSet&lt;OutboxMessage&gt; OutboxEvents { get; set; }\r\n\r\n\t\tprotected override void OnModelCreating(ModelBuilder builder)\r\n\t\t{\r\n\t\t\tbuilder.Entity&lt;Payment&gt;(entity =&gt;\r\n\t\t\t{\r\n\t\t\t\tentity.HasKey(k =&gt; k.Id);\r\n\t\t\t\tentity.Property(p =&gt; p.TotalAmount).HasPrecision(18, 4);\r\n\t\t\t\tentity.ToTable(\"Payments\");\r\n\t\t\t});\r\n\r\n\t\t\tbuilder.Entity&lt;OutboxMessage&gt;(entity =&gt;\r\n\t\t\t{\r\n\t\t\t\tentity.HasKey(k =&gt; k.Id);\r\n\t\t\t\tentity.ToTable(\"OutboxMessages\");\r\n\t\t\t});\r\n\r\n\t\t\tbase.OnModelCreating(builder);\r\n\t\t}\r\n\t}\r\n}<\/pre>\n<p>\u015eimdi &#8220;<em>PaymentService.Infra<\/em>&#8221; class library&#8217;sini Payment Service\u00a0projesine referans olarak ekleyelim ve\u00a0 &#8220;<em>IPaymentService<\/em>&#8221; interface&#8217;i i\u00e7erisindeki &#8220;<em>DoPaymentAsync<\/em>&#8221; method&#8217;unun signature&#8217;\u0131n\u0131 da a\u015fa\u011f\u0131daki gibi g\u00fcncelleyelim.<\/p>\n<pre>using System.Threading.Tasks;\r\n\r\nnamespace PaymentService.Services\r\n{\r\n    public interface IPaymentService\r\n    {\r\n        Task DoPaymentAsync(int orderId, int walletId, int userId, decimal totalAmount);\r\n    }\r\n}<\/pre>\n<p>Ard\u0131ndan outbox implementasyonunu &#8220;<em>PaymentService<\/em>&#8221; class&#8217;\u0131 i\u00e7erisinde a\u015fa\u011f\u0131daki gibi ger\u00e7ekle\u015ftirelim.<\/p>\n<pre>using System.Threading.Tasks;\r\nusing Newtonsoft.Json;\r\nusing PaymentService.Infra;\r\nusing PaymentService.Infra.Models;\r\nusing Shared.Contracts;\r\n\r\nnamespace PaymentService.Services\r\n{\r\n\tpublic class PaymentService : IPaymentService\r\n\t{\r\n\t\tprivate readonly AppDbContext _appDbContext;\r\n\r\n\t\tpublic PaymentService(AppDbContext appDbContext)\r\n\t\t{\r\n\t\t\t_appDbContext = appDbContext;\r\n\t\t}\r\n\r\n\t\tpublic async Task DoPaymentAsync(int orderId, int walletId, int userId, decimal totalAmount)\r\n\t\t{\r\n\t\t\t\/\/ after payment operation is done...\r\n\r\n\t\t\tvar isPaid = true;\r\n\r\n\t\t\tvar payment = new Payment\r\n\t\t\t{\r\n\t\t\t\tOrderId = orderId,\r\n\t\t\t\tWalletId = walletId,\r\n\t\t\t\tUserId = userId,\r\n\t\t\t\tTotalAmount = totalAmount,\r\n\t\t\t\tIsPaid = isPaid\r\n\t\t\t};\r\n\r\n\t\t\tawait _appDbContext.Payments.AddAsync(payment);\r\n\r\n\t\t\tobject paymentResultEvent;\r\n\t\t\tif (isPaid)\r\n\t\t\t{\r\n\t\t\t\tpaymentResultEvent = new PaymentCompletedEvent\r\n\t\t\t\t{\r\n\t\t\t\t\tOrderId = orderId\r\n\t\t\t\t};\r\n\t\t\t}\r\n\t\t\telse\r\n\t\t\t{\r\n\t\t\t\tpaymentResultEvent = new PaymentRejectedEvent\r\n\t\t\t\t{\r\n\t\t\t\t\tOrderId = orderId,\r\n\t\t\t\t\tReason = \"\"\r\n\t\t\t\t};\r\n\t\t\t}\r\n\r\n\t\t\tvar outboxMessage = new OutboxMessage\r\n\t\t\t{\r\n\t\t\t\tEventPayload = JsonConvert.SerializeObject(paymentResultEvent),\r\n\t\t\t\tEventType = paymentResultEvent.GetType().AssemblyQualifiedName\r\n\t\t\t};\r\n\r\n\t\t\tawait _appDbContext.OutboxEvents.AddAsync(outboxMessage);\r\n\r\n\t\t\tawait _appDbContext.SaveChangesAsync();\r\n\t\t}\r\n\t}\r\n}<\/pre>\n<p>G\u00f6rd\u00fc\u011f\u00fcm\u00fcz gibi \u00f6deme i\u015flemlerini ger\u00e7ekle\u015ftikten sonra &#8220;<em>Payment<\/em>&#8221; kay\u0131t\u0131 ile birlikte ilgili &#8220;<em>PaymentCompletedEvent<\/em>&#8221; ini de outbox table&#8217;\u0131na ayn\u0131 transaction alt\u0131nda ilgili business&#8217;\u0131n bir par\u00e7as\u0131 olarak kaydediyoruz. B\u00f6ylelikle ilgili event&#8217;in herhangi bir hata kar\u015f\u0131s\u0131nda dayan\u0131kl\u0131 oldu\u011fundan emin olaca\u011f\u0131z ve di\u011fer ilgili domain&#8217;lerin de consistent bir state&#8217;e sahip olabilmelerini sa\u011flayaca\u011f\u0131z.<\/p>\n<p>\u015eimdi &#8220;<em>StocksReservedEvent<\/em>&#8221; ini consume etti\u011fimiz &#8220;<em>StocksReservedEventConsumer<\/em>&#8221; class&#8217;\u0131na gidelim ve a\u015fa\u011f\u0131daki gibi refacor edelim.<\/p>\n<pre>using System.Threading;\r\nusing System.Threading.Tasks;\r\nusing EasyNetQ.AutoSubscribe;\r\nusing PaymentService.Services;\r\nusing Shared.Contracts;\r\n\r\nnamespace PaymentService.Consumers\r\n{\r\n\tpublic class StocksReservedEventConsumer : IConsumeAsync&lt;StockReservedEvent&gt;\r\n\t{\r\n\t\tprivate readonly IPaymentService _paymentService;\r\n\r\n\t\tpublic StocksReservedEventConsumer(IPaymentService paymentService)\r\n\t\t{\r\n\t\t\t_paymentService = paymentService;\r\n\t\t}\r\n\r\n\t\tpublic async Task ConsumeAsync(StocksReservedEvent message, CancellationToken cancellationToken = default)\r\n\t\t{\r\n\t\t\tawait _paymentService.DoPaymentAsync(message.OrderId, message.WalletId, message.UserId, message.TotalAmount);\r\n\t\t}\r\n\t}\r\n}<\/pre>\n<p>Ard\u0131ndan &#8220;<em>appsettings.json<\/em>&#8221; file&#8217;\u0131na olu\u015fturmu\u015f oldu\u011fumuz \u00f6rnek &#8220;<em>PaymentDB<\/em>&#8221; nin connection string bilgilerini ekleyelim ve &#8220;<em>Program.cs<\/em>&#8221; class&#8217;\u0131nda gerekli injection i\u015flemlerini de ger\u00e7ekle\u015ftirelim.<\/p>\n<pre>\"ConnectionStrings\": {\r\n  \"PaymentDB\": \"Server=127.0.0.1;Database=PaymentDB;Trusted_Connection=true;MultipleActiveResultSets=true\"\r\n}<\/pre>\n<pre>services.AddDbContext&lt;AppDbContext&gt;(x =&gt; x.UseSqlServer(hostContext.Configuration.GetConnectionString(\"PaymentDB\")));<\/pre>\n<p>B\u00f6ylelikle outbox pattern&#8217;\u0131n\u0131n ilk k\u0131sm\u0131n\u0131 tamamlam\u0131\u015f olduk.<\/p>\n<p>H\u0131zl\u0131 bir test yapabilmek i\u00e7in Order Service, Stock Service ve Payment Service&#8217;i \u00e7al\u0131\u015ft\u0131ral\u0131m, ard\u0131ndan Order Service&#8217;in <em>Swagger UI<\/em>&#8216;\u0131 \u00fczerinden bir test sipari\u015fi olu\u015ftural\u0131m.<\/p>\n<p><a href=\"\/wp-content\/uploads\/2023\/02\/outbox-message.jpg\"><img decoding=\"async\" class=\"aligncenter size-full wp-image-4330 lazyload\" data-src=\"\/wp-content\/uploads\/2023\/02\/outbox-message.jpg\" alt=\"\" width=\"930\" height=\"406\" data-srcset=\"https:\/\/gokhan-gokalp.com\/wp-content\/uploads\/2023\/02\/outbox-message.jpg 930w, https:\/\/gokhan-gokalp.com\/wp-content\/uploads\/2023\/02\/outbox-message-300x131.jpg 300w, https:\/\/gokhan-gokalp.com\/wp-content\/uploads\/2023\/02\/outbox-message-768x335.jpg 768w\" data-sizes=\"(max-width: 930px) 100vw, 930px\" src=\"data:image\/svg+xml;base64,PHN2ZyB3aWR0aD0iMSIgaGVpZ2h0PSIxIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPjwvc3ZnPg==\" style=\"--smush-placeholder-width: 930px; --smush-placeholder-aspect-ratio: 930\/406;\" \/><\/a><\/p>\n<p>G\u00f6rd\u00fc\u011f\u00fcm\u00fcz gibi &#8220;<em>Payment<\/em>&#8221; kay\u0131t\u0131 ile birlikte, ilgili event kay\u0131t&#8217;\u0131 da &#8220;<em>OutboxMessages<\/em>&#8221; table&#8217;\u0131na kay\u0131t edilmi\u015f durumda. \u015eimdi ise bu table&#8217;daki kay\u0131tlar\u0131 dinleyecek ve publish edecek bir background service&#8217;ine ihtiyac\u0131m\u0131z var.<\/p>\n<p>Bunun i\u00e7in solution i\u00e7erisine &#8220;<em>PaymentServiceOutboxWorker<\/em>&#8221; ad\u0131nda ayr\u0131 bir console application&#8217;\u0131 olu\u015ftural\u0131m ve ard\u0131ndan &#8220;<em>PaymentService.Infra<\/em>&#8221; ve &#8220;<em>Shared.Contracts<\/em>&#8221; class library&#8217;lerini referans ekleyelim. Ayr\u0131ca <em>NuGet<\/em> \u00fczerinden a\u015fa\u011f\u0131daki paket&#8217;leri de projeye dahil edelim.<\/p>\n<pre>Microsoft.Extensions.Configuration.Json - 5.0.0\r\nMicrosoft.Extensions.DependencyInjection - 5.0.2\r\nMicrosoft.Extensions.Hosting - 5.0.0\r\nEasyNetQ - 6.3.1<\/pre>\n<p>\u015eimdi proje i\u00e7erisinde &#8220;<em>Worker<\/em>&#8221; ad\u0131nda bir class tan\u0131mlayal\u0131m ve a\u015fa\u011f\u0131daki gibi code&#8217;layal\u0131m.<\/p>\n<pre>using Microsoft.Extensions.Hosting;\r\nusing System;\r\nusing System.Collections.Generic;\r\nusing System.Linq;\r\nusing System.Threading;\r\nusing System.Threading.Tasks;\r\nusing EasyNetQ;\r\nusing Microsoft.Extensions.DependencyInjection;\r\nusing Newtonsoft.Json;\r\nusing PaymentService.Infra;\r\nusing PaymentService.Infra.Models;\r\n\r\nnamespace PaymentServiceOutboxWorker\r\n{\r\n\tpublic class Worker : BackgroundService\r\n\t{\r\n\t\tprivate readonly IServiceScopeFactory _scopeFactory;\r\n\r\n\t\tpublic Worker(IServiceScopeFactory scopeFactory)\r\n\t\t{\r\n\t\t\t_scopeFactory = scopeFactory;\r\n\t\t}\r\n\r\n\t\tprotected override async Task ExecuteAsync(CancellationToken stoppingToken)\r\n\t\t{\r\n\t\t\twhile (!stoppingToken.IsCancellationRequested)\r\n\t\t\t{\r\n\t\t\t\tawait PublishOutboxMessages(stoppingToken);\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\tprivate async Task PublishOutboxMessages(CancellationToken stoppingToken)\r\n\t\t{\r\n\t\t\ttry\r\n\t\t\t{\r\n\t\t\t\tusing var scope = _scopeFactory.CreateScope();\r\n\t\t\t\tawait using var appDbContext = scope.ServiceProvider.GetRequiredService&lt;AppDbContext&gt;();\r\n\r\n\t\t\t\tIBus bus = scope.ServiceProvider.GetRequiredService&lt;IBus&gt;();\r\n\r\n\t\t\t\tList&lt;OutboxMessage&gt; messages = appDbContext.OutboxMessages.Where(om =&gt; !om.IsSent).ToList();\r\n\r\n\t\t\t\tforeach (OutboxMessage outboxMessage in messages)\r\n\t\t\t\t{\r\n\t\t\t\t\ttry\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tvar messageType = Type.GetType(outboxMessage.EventType);\r\n\t\t\t\t\t\tvar message = JsonConvert.DeserializeObject(outboxMessage.EventPayload, messageType!);\r\n\r\n\t\t\t\t\t\tawait bus.PubSub.PublishAsync(message, messageType);\r\n\r\n\t\t\t\t\t\toutboxMessage.IsSent = true;\r\n\t\t\t\t\t\toutboxMessage.SentDate = DateTime.UtcNow;\r\n\r\n\t\t\t\t\t\tappDbContext.OutboxMessages.Update(outboxMessage);\r\n\t\t\t\t\t\tawait appDbContext.SaveChangesAsync();\r\n\t\t\t\t\t}\r\n\t\t\t\t\tcatch (Exception e)\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tConsole.WriteLine(e);\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t\tcatch (Exception e)\r\n\t\t\t{\r\n\t\t\t\tConsole.WriteLine(e);\r\n\t\t\t}\r\n\r\n\t\t\tawait Task.Delay(TimeSpan.FromSeconds(5), stoppingToken);\r\n\t\t}\r\n\t}\r\n}<\/pre>\n<p>Burada basit bir \u015fekilde &#8220;<em>BackgroundService<\/em>&#8221; base class&#8217;\u0131n\u0131 kullanarak bir long-running background service&#8217;i tan\u0131ml\u0131yoruz. Her 5 saniye de bir \u00e7al\u0131\u015facak olan bu service, outbox table&#8217;\u0131n\u0131 poll ederek publish edilmemi\u015f olan event&#8217;lerin publish i\u015flemlerini kesin bir \u015fekilde ger\u00e7ekle\u015ftirecektir.<\/p>\n<p>B\u00f6ylece bizim i\u00e7in kritik olan event&#8217;lerin ilgili business process&#8217;inin devam edebilmesi i\u00e7in kaybolmadan istenilen hedefe ula\u015ft\u0131\u011f\u0131ndan emin olabilece\u011fiz.<\/p>\n<p>\u015eimdi &#8220;<em>Program.cs<\/em>&#8221; class&#8217;\u0131n\u0131 a\u015fa\u011f\u0131daki gibi g\u00fcncelleyerek gerekli configuration ve injection i\u015flemlerini ger\u00e7ekle\u015ftirebiliriz.<\/p>\n<pre>using Microsoft.Extensions.Hosting;\r\nusing EasyNetQ;\r\nusing Microsoft.EntityFrameworkCore;\r\nusing Microsoft.Extensions.Configuration;\r\nusing Microsoft.Extensions.DependencyInjection;\r\nusing PaymentService.Infra;\r\n\r\nnamespace PaymentServiceOutboxWorker\r\n{\r\n\tclass Program\r\n\t{\r\n\t\tstatic void Main(string[] args)\r\n\t\t{\r\n\t\t\tCreateHostBuilder(args).Build().Run();\r\n\t\t}\r\n\r\n\t\tstatic IHostBuilder CreateHostBuilder(string[] args) =&gt;\r\n\t\t\tHost.CreateDefaultBuilder(args)\r\n\t\t\t\t.ConfigureServices((hostContext, services) =&gt;\r\n\t\t\t\t{\r\n\t\t\t\t\tservices.AddDbContext&lt;AppDbContext&gt;(x =&gt; x.UseSqlServer(hostContext.Configuration.GetConnectionString(\"PaymentDB\")));\r\n\r\n\t\t\t\t\tvar bus = RabbitHutch.CreateBus(hostContext.Configuration[\"RabbitMQ:ConnectionString\"]);\r\n\r\n\t\t\t\t\tservices.AddSingleton&lt;IBus&gt;(bus);\r\n\r\n\t\t\t\t\tservices.AddHostedService&lt;Worker&gt;();\r\n\t\t\t\t});\r\n\t}\r\n}<\/pre>\n<p>Ard\u0131ndan &#8220;<em>appsettings.json<\/em>&#8221; dosyas\u0131n\u0131 a\u015fa\u011f\u0131daki gibi tan\u0131mlayabiliriz.<\/p>\n<pre>{\r\n  \"Logging\": {\r\n    \"LogLevel\": {\r\n      \"Default\": \"Information\",\r\n      \"Microsoft\": \"Warning\",\r\n      \"Microsoft.Hosting.Lifetime\": \"Information\"\r\n    }\r\n  },\r\n  \"RabbitMQ\": {\r\n    \"ConnectionString\": \"host=localhost;username=guest;password=guest\"\r\n  },\r\n  \"ConnectionStrings\": {\r\n    \"PaymentDB\": \"Server=127.0.0.1;Database=PaymentDB;Trusted_Connection=true;MultipleActiveResultSets=true\"\r\n  }\r\n}<\/pre>\n<p>Hepsi bu kadar. &#8220;<em>PaymentServiceOutboxWorker<\/em>&#8221; service&#8217;ini \u00e7al\u0131\u015ft\u0131rd\u0131\u011f\u0131m\u0131zda event&#8217;lerin ba\u015far\u0131l\u0131 bir \u015fekilde ilgili queue&#8217;ya publish edildi\u011fini g\u00f6rebiliriz.<\/p>\n<h3>Baz\u0131 Noktalar<\/h3>\n<p>Bu pattern&#8217;\u0131n &#8220;<em>at-least-once<\/em>&#8221; delivery yakla\u015f\u0131m\u0131yla \u00e7al\u0131\u015ft\u0131\u011f\u0131ndan bahsetmi\u015ftik (\u00f6rnek kod&#8217;dan da g\u00f6rebilece\u011fimiz \u00fczere). Yani ilgili event&#8217;lerin birden fazla publish edildi\u011fi senaryolar ile kar\u015f\u0131 kar\u015f\u0131ya kalabiliriz.<\/p>\n<p>Outbox pattern&#8217;\u0131 ile data b\u00fct\u00fcnl\u00fc\u011f\u00fcn\u00fc sa\u011flamaya \u00e7al\u0131\u015f\u0131rken, di\u011fer taraftan duplication&#8217;lar yaratarak sistemin ve ilgili business process&#8217;inin yine inconsistent data&#8217;ya sahip olmas\u0131na sebep olabiliriz. Bu sebeple olabildi\u011fince event&#8217;lerimizin ve ilgili event-consumer&#8217;lar\u0131n\u0131n idempotent olabilmelerine \u00f6zen g\u00f6stermemiz gerekmektedir.<\/p>\n<p>\u00d6rne\u011fin event&#8217;lerin i\u00e7erisine bir identifier dahil edebilir ve consumer taraf\u0131nda her bir event&#8217;in kayd\u0131n\u0131 database&#8217;de tutabiliriz. B\u00f6ylece herhangi bir event&#8217;i i\u015flemeden \u00f6nce, daha \u00f6nce i\u015flenip i\u015flenmedi\u011finden emin olabiliriz. <strong>Inbox pattern<\/strong> olarak isimlendirilen bu yakla\u015f\u0131mla da k\u0131saca exactly-once processing&#8217;i garanti alt\u0131na alabilmekteyiz.<\/p>\n<p>Ayr\u0131ca event ordering&#8217;e ihtiya\u00e7 duyulan noktalarda da, ilgili event&#8217;leri belirleyecek oldu\u011fumuz identifier&#8217;lara g\u00f6re s\u0131ralay\u0131p, i\u015fleye de biliriz.<\/p>\n<h2>K\u0131saca<\/h2>\n<p>Maalesef microservice d\u00fcnyas\u0131nda communication ve data b\u00fct\u00fcnl\u00fc\u011f\u00fc sorunlar\u0131n\u0131 \u00e7\u00f6zmek, bazen olduk\u00e7a zahmetli ve dikkat gereken bir konu. Bazen bir event&#8217;in fail olmas\u0131, sistemdeki tutars\u0131zl\u0131k nedeniyle di\u011fer business process&#8217;lerinin de fail olmas\u0131na neden olabilir. Bu sebeple kritik event&#8217;lerin atomic bir \u015fekilde g\u00f6nderilebilmeleri ve idempotent consumer&#8217;lar taraf\u0131ndan i\u015flenebilmeleri olduk\u00e7a \u00f6nemli bir konu. Her ne kadar basit bir \u00e7\u00f6z\u00fcm gibi g\u00f6r\u00fcnse de outbox&amp;inbox pattern&#8217;lar\u0131, bazen gereksiz bir karardan dolay\u0131 sistemimiz i\u00e7in bir overkill haline de gelebilirler. Bu y\u00fczden ilgili business use-case&#8217;lerimiz i\u00e7in neyin do\u011fru olaca\u011f\u0131na karar verip, onu uygulamam\u0131z en do\u011frusu olacakt\u0131r.<\/p>\n<h2>Referanslar<\/h2>\n<blockquote><p>https:\/\/microservices.io\/patterns\/data\/transactional-outbox.html<\/p><\/blockquote>\n","protected":false},"excerpt":{"rendered":"<p>Bildi\u011fimiz gibi microservice architecture&#8217;\u0131na adapte olman\u0131n bir \u00e7ok art\u0131 noktas\u0131 oldu\u011fu gibi, maalesef getirdi\u011fi baz\u0131 zorlay\u0131c\u0131 noktalar\u0131 da bulunmakta. \u00d6zellikle data consistency taraf\u0131nda.\u00a0\u00d6rne\u011fin, monolithic d\u00fcnyada birden fazla i\u015flemi consistent bir \u015fekilde kolayca ger\u00e7ekle\u015ftirebilmek i\u00e7in, ACID database transaction&#8217;lar\u0131ndan yararlanabilmekteyiz. Fakat microservice d\u00fcnyas\u0131nda i\u015flemlerin farkl\u0131 service&#8217;ler taraf\u0131ndan&#8230;<\/p>\n<div class=\"more-link-wrapper\"><a class=\"more-link\" href=\"https:\/\/gokhan-gokalp.com\/tr\/providing-atomicity-for-eventual-consistency-with-outbox-pattern-in-net-microservices\/\">Devam\u0131n\u0131 okuyun<span class=\"screen-reader-text\">.NET Microservice\u2019lerinde Outbox Pattern\u2019\u0131 ile Eventual Consistency i\u00e7in Atomicity Sa\u011flama<\/span><\/a><\/div>\n","protected":false},"author":1,"featured_media":4352,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[5,417,285,368,152],"tags":[111,668,666,667,650,669,663,665,664,520,349,670],"class_list":["post-4304","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-net","category-asp-net-core","category-messaging","category-microservices","category-rabbitmq","tag-net","tag-at-least-once","tag-c","tag-consistency","tag-event-driven","tag-idempotent","tag-inbox-pattern","tag-microservices","tag-outbox-pattern","tag-resiliency","tag-saga","tag-transaction-management","entry"],"translation":{"provider":"WPGlobus","version":"3.0.2","language":"tr","enabled_languages":["en","tr"],"languages":{"en":{"title":true,"content":true,"excerpt":false},"tr":{"title":true,"content":true,"excerpt":false}}},"yoast_head":"<!-- This site is optimized with the Yoast SEO plugin v27.3 - https:\/\/yoast.com\/product\/yoast-seo-wordpress\/ -->\n<title>.NET Microservice\u2019lerinde Outbox Pattern\u2019\u0131 ile Eventual Consistency i\u00e7in Atomicity Sa\u011flama - G\u00f6khan G\u00f6kalp<\/title>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/gokhan-gokalp.com\/providing-atomicity-for-eventual-consistency-with-outbox-pattern-in-net-microservices\/\" \/>\n<meta property=\"og:locale\" content=\"tr_TR\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\".NET Microservice\u2019lerinde Outbox Pattern\u2019\u0131 ile Eventual Consistency i\u00e7in Atomicity Sa\u011flama - G\u00f6khan G\u00f6kalp\" \/>\n<meta property=\"og:url\" content=\"https:\/\/gokhan-gokalp.com\/providing-atomicity-for-eventual-consistency-with-outbox-pattern-in-net-microservices\/\" \/>\n<meta property=\"og:site_name\" content=\"G\u00f6khan G\u00f6kalp\" \/>\n<meta property=\"article:published_time\" content=\"2023-03-09T20:36:28+00:00\" \/>\n<meta property=\"article:modified_time\" content=\"2023-03-11T18:27:19+00:00\" \/>\n<meta property=\"og:image\" content=\"https:\/\/gokhan-gokalp.com\/wp-content\/uploads\/2023\/03\/outbox-pattern-gokhan-gokalp.jpg\" \/>\n\t<meta property=\"og:image:width\" content=\"1200\" \/>\n\t<meta property=\"og:image:height\" content=\"675\" \/>\n\t<meta property=\"og:image:type\" content=\"image\/jpeg\" \/>\n<meta name=\"author\" content=\"G\u00f6khan G\u00f6kalp\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<meta name=\"twitter:label1\" content=\"Yazan:\" \/>\n\t<meta name=\"twitter:data1\" content=\"G\u00f6khan G\u00f6kalp\" \/>\n\t<meta name=\"twitter:label2\" content=\"Tahmini okuma s\u00fcresi\" \/>\n\t<meta name=\"twitter:data2\" content=\"25 dakika\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\\\/\\\/schema.org\",\"@graph\":[{\"@type\":\"Article\",\"@id\":\"https:\\\/\\\/gokhan-gokalp.com\\\/providing-atomicity-for-eventual-consistency-with-outbox-pattern-in-net-microservices\\\/#article\",\"isPartOf\":{\"@id\":\"https:\\\/\\\/gokhan-gokalp.com\\\/providing-atomicity-for-eventual-consistency-with-outbox-pattern-in-net-microservices\\\/\"},\"author\":{\"name\":\"G\u00f6khan G\u00f6kalp\",\"@id\":\"https:\\\/\\\/gokhan-gokalp.com\\\/#\\\/schema\\\/person\\\/7e2a7fa98babd22a5fdae563c4b8cdbe\"},\"headline\":\".NET Microservice\u2019lerinde Outbox Pattern\u2019\u0131 ile Eventual Consistency i\u00e7in Atomicity Sa\u011flama\",\"datePublished\":\"2023-03-09T20:36:28+00:00\",\"dateModified\":\"2023-03-11T18:27:19+00:00\",\"mainEntityOfPage\":{\"@id\":\"https:\\\/\\\/gokhan-gokalp.com\\\/providing-atomicity-for-eventual-consistency-with-outbox-pattern-in-net-microservices\\\/\"},\"wordCount\":3489,\"commentCount\":6,\"publisher\":{\"@id\":\"https:\\\/\\\/gokhan-gokalp.com\\\/#\\\/schema\\\/person\\\/7e2a7fa98babd22a5fdae563c4b8cdbe\"},\"image\":{\"@id\":\"https:\\\/\\\/gokhan-gokalp.com\\\/providing-atomicity-for-eventual-consistency-with-outbox-pattern-in-net-microservices\\\/#primaryimage\"},\"thumbnailUrl\":\"https:\\\/\\\/gokhan-gokalp.com\\\/wp-content\\\/uploads\\\/2023\\\/03\\\/outbox-pattern-gokhan-gokalp.jpg\",\"keywords\":[\".NET\",\"at least once\",\"C#\",\"consistency\",\"event driven\",\"idempotent\",\"inbox pattern\",\"microservices\",\"outbox pattern\",\"resiliency\",\"Saga\",\"transaction management\"],\"articleSection\":[\".NET\",\"ASP.NET Core\",\"Messaging\",\"Microservices\",\"RabbitMQ\"],\"inLanguage\":\"tr\",\"potentialAction\":[{\"@type\":\"CommentAction\",\"name\":\"Comment\",\"target\":[\"https:\\\/\\\/gokhan-gokalp.com\\\/providing-atomicity-for-eventual-consistency-with-outbox-pattern-in-net-microservices\\\/#respond\"]}]},{\"@type\":\"WebPage\",\"@id\":\"https:\\\/\\\/gokhan-gokalp.com\\\/providing-atomicity-for-eventual-consistency-with-outbox-pattern-in-net-microservices\\\/\",\"url\":\"https:\\\/\\\/gokhan-gokalp.com\\\/providing-atomicity-for-eventual-consistency-with-outbox-pattern-in-net-microservices\\\/\",\"name\":\".NET Microservice\u2019lerinde Outbox Pattern\u2019\u0131 ile Eventual Consistency i\u00e7in Atomicity Sa\u011flama - G\u00f6khan G\u00f6kalp\",\"isPartOf\":{\"@id\":\"https:\\\/\\\/gokhan-gokalp.com\\\/#website\"},\"primaryImageOfPage\":{\"@id\":\"https:\\\/\\\/gokhan-gokalp.com\\\/providing-atomicity-for-eventual-consistency-with-outbox-pattern-in-net-microservices\\\/#primaryimage\"},\"image\":{\"@id\":\"https:\\\/\\\/gokhan-gokalp.com\\\/providing-atomicity-for-eventual-consistency-with-outbox-pattern-in-net-microservices\\\/#primaryimage\"},\"thumbnailUrl\":\"https:\\\/\\\/gokhan-gokalp.com\\\/wp-content\\\/uploads\\\/2023\\\/03\\\/outbox-pattern-gokhan-gokalp.jpg\",\"datePublished\":\"2023-03-09T20:36:28+00:00\",\"dateModified\":\"2023-03-11T18:27:19+00:00\",\"breadcrumb\":{\"@id\":\"https:\\\/\\\/gokhan-gokalp.com\\\/providing-atomicity-for-eventual-consistency-with-outbox-pattern-in-net-microservices\\\/#breadcrumb\"},\"inLanguage\":\"tr\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\\\/\\\/gokhan-gokalp.com\\\/providing-atomicity-for-eventual-consistency-with-outbox-pattern-in-net-microservices\\\/\"]}]},{\"@type\":\"ImageObject\",\"inLanguage\":\"tr\",\"@id\":\"https:\\\/\\\/gokhan-gokalp.com\\\/providing-atomicity-for-eventual-consistency-with-outbox-pattern-in-net-microservices\\\/#primaryimage\",\"url\":\"https:\\\/\\\/gokhan-gokalp.com\\\/wp-content\\\/uploads\\\/2023\\\/03\\\/outbox-pattern-gokhan-gokalp.jpg\",\"contentUrl\":\"https:\\\/\\\/gokhan-gokalp.com\\\/wp-content\\\/uploads\\\/2023\\\/03\\\/outbox-pattern-gokhan-gokalp.jpg\",\"width\":1200,\"height\":675},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\\\/\\\/gokhan-gokalp.com\\\/providing-atomicity-for-eventual-consistency-with-outbox-pattern-in-net-microservices\\\/#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\\\/\\\/gokhan-gokalp.com\\\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"Providing Atomicity for Eventual Consistency with Outbox Pattern in .NET Microservices\"}]},{\"@type\":\"WebSite\",\"@id\":\"https:\\\/\\\/gokhan-gokalp.com\\\/#website\",\"url\":\"https:\\\/\\\/gokhan-gokalp.com\\\/\",\"name\":\"G\u00f6khan G\u00f6kalp\",\"description\":\"C# &amp; Python lover\",\"publisher\":{\"@id\":\"https:\\\/\\\/gokhan-gokalp.com\\\/#\\\/schema\\\/person\\\/7e2a7fa98babd22a5fdae563c4b8cdbe\"},\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https:\\\/\\\/gokhan-gokalp.com\\\/?s={search_term_string}\"},\"query-input\":{\"@type\":\"PropertyValueSpecification\",\"valueRequired\":true,\"valueName\":\"search_term_string\"}}],\"inLanguage\":\"tr\"},{\"@type\":[\"Person\",\"Organization\"],\"@id\":\"https:\\\/\\\/gokhan-gokalp.com\\\/#\\\/schema\\\/person\\\/7e2a7fa98babd22a5fdae563c4b8cdbe\",\"name\":\"G\u00f6khan G\u00f6kalp\",\"pronouns\":\"he\",\"image\":{\"@type\":\"ImageObject\",\"inLanguage\":\"tr\",\"@id\":\"https:\\\/\\\/gokhan-gokalp.com\\\/wp-content\\\/litespeed\\\/avatar\\\/e645f66b6264ced10d7b6d8b1f85509b.jpg?ver=1776170659\",\"url\":\"https:\\\/\\\/gokhan-gokalp.com\\\/wp-content\\\/litespeed\\\/avatar\\\/e645f66b6264ced10d7b6d8b1f85509b.jpg?ver=1776170659\",\"contentUrl\":\"https:\\\/\\\/gokhan-gokalp.com\\\/wp-content\\\/litespeed\\\/avatar\\\/e645f66b6264ced10d7b6d8b1f85509b.jpg?ver=1776170659\",\"caption\":\"G\u00f6khan G\u00f6kalp\"},\"logo\":{\"@id\":\"https:\\\/\\\/gokhan-gokalp.com\\\/wp-content\\\/litespeed\\\/avatar\\\/e645f66b6264ced10d7b6d8b1f85509b.jpg?ver=1776170659\"},\"sameAs\":[\"https:\\\/\\\/gokhan-gokalp.com\"],\"url\":\"https:\\\/\\\/gokhan-gokalp.com\\\/tr\\\/author\\\/gok-gokalp\\\/\"}]}<\/script>\n<!-- \/ Yoast SEO plugin. -->","yoast_head_json":{"title":".NET Microservice\u2019lerinde Outbox Pattern\u2019\u0131 ile Eventual Consistency i\u00e7in Atomicity Sa\u011flama - G\u00f6khan G\u00f6kalp","robots":{"index":"index","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"canonical":"https:\/\/gokhan-gokalp.com\/providing-atomicity-for-eventual-consistency-with-outbox-pattern-in-net-microservices\/","og_locale":"tr_TR","og_type":"article","og_title":".NET Microservice\u2019lerinde Outbox Pattern\u2019\u0131 ile Eventual Consistency i\u00e7in Atomicity Sa\u011flama - G\u00f6khan G\u00f6kalp","og_url":"https:\/\/gokhan-gokalp.com\/providing-atomicity-for-eventual-consistency-with-outbox-pattern-in-net-microservices\/","og_site_name":"G\u00f6khan G\u00f6kalp","article_published_time":"2023-03-09T20:36:28+00:00","article_modified_time":"2023-03-11T18:27:19+00:00","og_image":[{"width":1200,"height":675,"url":"https:\/\/gokhan-gokalp.com\/wp-content\/uploads\/2023\/03\/outbox-pattern-gokhan-gokalp.jpg","type":"image\/jpeg"}],"author":"G\u00f6khan G\u00f6kalp","twitter_card":"summary_large_image","twitter_misc":{"Yazan:":"G\u00f6khan G\u00f6kalp","Tahmini okuma s\u00fcresi":"25 dakika"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"Article","@id":"https:\/\/gokhan-gokalp.com\/providing-atomicity-for-eventual-consistency-with-outbox-pattern-in-net-microservices\/#article","isPartOf":{"@id":"https:\/\/gokhan-gokalp.com\/providing-atomicity-for-eventual-consistency-with-outbox-pattern-in-net-microservices\/"},"author":{"name":"G\u00f6khan G\u00f6kalp","@id":"https:\/\/gokhan-gokalp.com\/#\/schema\/person\/7e2a7fa98babd22a5fdae563c4b8cdbe"},"headline":".NET Microservice\u2019lerinde Outbox Pattern\u2019\u0131 ile Eventual Consistency i\u00e7in Atomicity Sa\u011flama","datePublished":"2023-03-09T20:36:28+00:00","dateModified":"2023-03-11T18:27:19+00:00","mainEntityOfPage":{"@id":"https:\/\/gokhan-gokalp.com\/providing-atomicity-for-eventual-consistency-with-outbox-pattern-in-net-microservices\/"},"wordCount":3489,"commentCount":6,"publisher":{"@id":"https:\/\/gokhan-gokalp.com\/#\/schema\/person\/7e2a7fa98babd22a5fdae563c4b8cdbe"},"image":{"@id":"https:\/\/gokhan-gokalp.com\/providing-atomicity-for-eventual-consistency-with-outbox-pattern-in-net-microservices\/#primaryimage"},"thumbnailUrl":"https:\/\/gokhan-gokalp.com\/wp-content\/uploads\/2023\/03\/outbox-pattern-gokhan-gokalp.jpg","keywords":[".NET","at least once","C#","consistency","event driven","idempotent","inbox pattern","microservices","outbox pattern","resiliency","Saga","transaction management"],"articleSection":[".NET","ASP.NET Core","Messaging","Microservices","RabbitMQ"],"inLanguage":"tr","potentialAction":[{"@type":"CommentAction","name":"Comment","target":["https:\/\/gokhan-gokalp.com\/providing-atomicity-for-eventual-consistency-with-outbox-pattern-in-net-microservices\/#respond"]}]},{"@type":"WebPage","@id":"https:\/\/gokhan-gokalp.com\/providing-atomicity-for-eventual-consistency-with-outbox-pattern-in-net-microservices\/","url":"https:\/\/gokhan-gokalp.com\/providing-atomicity-for-eventual-consistency-with-outbox-pattern-in-net-microservices\/","name":".NET Microservice\u2019lerinde Outbox Pattern\u2019\u0131 ile Eventual Consistency i\u00e7in Atomicity Sa\u011flama - G\u00f6khan G\u00f6kalp","isPartOf":{"@id":"https:\/\/gokhan-gokalp.com\/#website"},"primaryImageOfPage":{"@id":"https:\/\/gokhan-gokalp.com\/providing-atomicity-for-eventual-consistency-with-outbox-pattern-in-net-microservices\/#primaryimage"},"image":{"@id":"https:\/\/gokhan-gokalp.com\/providing-atomicity-for-eventual-consistency-with-outbox-pattern-in-net-microservices\/#primaryimage"},"thumbnailUrl":"https:\/\/gokhan-gokalp.com\/wp-content\/uploads\/2023\/03\/outbox-pattern-gokhan-gokalp.jpg","datePublished":"2023-03-09T20:36:28+00:00","dateModified":"2023-03-11T18:27:19+00:00","breadcrumb":{"@id":"https:\/\/gokhan-gokalp.com\/providing-atomicity-for-eventual-consistency-with-outbox-pattern-in-net-microservices\/#breadcrumb"},"inLanguage":"tr","potentialAction":[{"@type":"ReadAction","target":["https:\/\/gokhan-gokalp.com\/providing-atomicity-for-eventual-consistency-with-outbox-pattern-in-net-microservices\/"]}]},{"@type":"ImageObject","inLanguage":"tr","@id":"https:\/\/gokhan-gokalp.com\/providing-atomicity-for-eventual-consistency-with-outbox-pattern-in-net-microservices\/#primaryimage","url":"https:\/\/gokhan-gokalp.com\/wp-content\/uploads\/2023\/03\/outbox-pattern-gokhan-gokalp.jpg","contentUrl":"https:\/\/gokhan-gokalp.com\/wp-content\/uploads\/2023\/03\/outbox-pattern-gokhan-gokalp.jpg","width":1200,"height":675},{"@type":"BreadcrumbList","@id":"https:\/\/gokhan-gokalp.com\/providing-atomicity-for-eventual-consistency-with-outbox-pattern-in-net-microservices\/#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https:\/\/gokhan-gokalp.com\/"},{"@type":"ListItem","position":2,"name":"Providing Atomicity for Eventual Consistency with Outbox Pattern in .NET Microservices"}]},{"@type":"WebSite","@id":"https:\/\/gokhan-gokalp.com\/#website","url":"https:\/\/gokhan-gokalp.com\/","name":"G\u00f6khan G\u00f6kalp","description":"C# &amp; Python lover","publisher":{"@id":"https:\/\/gokhan-gokalp.com\/#\/schema\/person\/7e2a7fa98babd22a5fdae563c4b8cdbe"},"potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/gokhan-gokalp.com\/?s={search_term_string}"},"query-input":{"@type":"PropertyValueSpecification","valueRequired":true,"valueName":"search_term_string"}}],"inLanguage":"tr"},{"@type":["Person","Organization"],"@id":"https:\/\/gokhan-gokalp.com\/#\/schema\/person\/7e2a7fa98babd22a5fdae563c4b8cdbe","name":"G\u00f6khan G\u00f6kalp","pronouns":"he","image":{"@type":"ImageObject","inLanguage":"tr","@id":"https:\/\/gokhan-gokalp.com\/wp-content\/litespeed\/avatar\/e645f66b6264ced10d7b6d8b1f85509b.jpg?ver=1776170659","url":"https:\/\/gokhan-gokalp.com\/wp-content\/litespeed\/avatar\/e645f66b6264ced10d7b6d8b1f85509b.jpg?ver=1776170659","contentUrl":"https:\/\/gokhan-gokalp.com\/wp-content\/litespeed\/avatar\/e645f66b6264ced10d7b6d8b1f85509b.jpg?ver=1776170659","caption":"G\u00f6khan G\u00f6kalp"},"logo":{"@id":"https:\/\/gokhan-gokalp.com\/wp-content\/litespeed\/avatar\/e645f66b6264ced10d7b6d8b1f85509b.jpg?ver=1776170659"},"sameAs":["https:\/\/gokhan-gokalp.com"],"url":"https:\/\/gokhan-gokalp.com\/tr\/author\/gok-gokalp\/"}]}},"_links":{"self":[{"href":"https:\/\/gokhan-gokalp.com\/tr\/wp-json\/wp\/v2\/posts\/4304","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/gokhan-gokalp.com\/tr\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/gokhan-gokalp.com\/tr\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/gokhan-gokalp.com\/tr\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/gokhan-gokalp.com\/tr\/wp-json\/wp\/v2\/comments?post=4304"}],"version-history":[{"count":36,"href":"https:\/\/gokhan-gokalp.com\/tr\/wp-json\/wp\/v2\/posts\/4304\/revisions"}],"predecessor-version":[{"id":4354,"href":"https:\/\/gokhan-gokalp.com\/tr\/wp-json\/wp\/v2\/posts\/4304\/revisions\/4354"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/gokhan-gokalp.com\/tr\/wp-json\/wp\/v2\/media\/4352"}],"wp:attachment":[{"href":"https:\/\/gokhan-gokalp.com\/tr\/wp-json\/wp\/v2\/media?parent=4304"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/gokhan-gokalp.com\/tr\/wp-json\/wp\/v2\/categories?post=4304"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/gokhan-gokalp.com\/tr\/wp-json\/wp\/v2\/tags?post=4304"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}