particular.net
Open in
urlscan Pro
104.40.215.219
Public Scan
Submitted URL: https://go.particular.net/e/27442/-announcement-utm-medium-email/8xgrx7/1723779645?h=C1SsSM4BUceNLnCYsPJ-ny8zis0r-WpuWFE_l...
Effective URL: https://particular.net/blog/transactional-session?utm_source=announcement&utm_medium=email
Submission: On November 03 via api from US — Scanned from DE
Effective URL: https://particular.net/blog/transactional-session?utm_source=announcement&utm_medium=email
Submission: On November 03 via api from US — Scanned from DE
Form analysis
2 forms found in the DOMPOST
<form id="blog-subscription-form" method="post">
<div class="message"><strong>Don't miss a thing. </strong>Sign up today and we'll send you an email when new posts come out.</div>
<div class="success">Thank you for subscribing. We'll be in touch soon.</div>
<div class="input-group"> <input class="email-address" name="request-email" placeholder="Email address"> <button class="submit">Get notified</button> </div>
<div class="registration-disclaimer">We collect and use this information in accordance with our <a href="/gdpr-privacy" target="_blank">privacy policy</a>.</div>
</form>
<form class="needs-validation" novalidate="">
<div class="hide alert alert-danger" role="alert"></div>
<p>Required fields are marked with <abbr class="required" title="required">*</abbr></p>
<div class="form-row">
<div class="form-group col-12 col-sm-6"> <input class="form-control" name="FirstName" id="wtu-first-name" required=""> <label for="wtu-first-name"> <span>First name <abbr class="required" title="required">*</abbr></span>
<div class="invalid-feedback">Missing</div>
</label> </div>
<div class="form-group col-12 col-sm-6"> <input class="form-control" name="LastName" id="wtu-last-name" required=""> <label for="wtu-last-name"> <span>Last name <abbr class="required" title="required">*</abbr></span>
<div class="invalid-feedback">Missing</div>
</label> </div>
</div>
<div class="form-group"> <input class="form-control" name="Company" id="wtu-company"> <label for="wtu-company"> <span>Company name</span> </label> </div>
<div class="form-group"> <input class="form-control" name="Email" id="wtu-email-address" required="" pattern="^[a-z0-9._%+-]+@[a-z0-9.-]+\.[a-z]{2,}$" type="email"> <label for="wtu-email-address"> <span>Email address <abbr class="required"
title="required">*</abbr></span>
<div class="invalid-feedback">Invalid</div>
</label> </div>
<div class="form-group"> <input class="form-control" name="Phone" id="wtu-phone-number"> <label for="wtu-phone-number"> <span>Phone number</span> </label> </div>
<div class="form-group message-input"> <textarea class="form-control" id="wtu-message" name="Message" required="" rows="3"></textarea> <label for="wtu-message"> <span>Message <abbr class="required" title="required">*</abbr></span>
<div class="invalid-feedback">Missing</div>
</label> </div> <button class="btn btn-primary" type="submit">Send Message</button>
<div class="overall-invalid-feedback" role="alert">Unable to submit. Please see errors above.</div>
<p class="registration-terms">We collect and use this information in accordance with our <a href="/gdpr-privacy" target="_blank">privacy policy</a></p>
</form>
Text Content
We use cookies and similar technologies to ensure you get the best experience on our websites. By actively using the website you're consenting to use these cookies. Change cookie preferences OK Skip to main content * Documentation * Blog * Distributed systems design fundamentals training Home page * Platform * Platform overview Everything in Particular * NServiceBus Messaging & workflow * ServiceInsight Advanced debugging * ServicePulse Production monitoring * Downloads * Solutions * Finance * Healthcare * Retail * Pricing * Pricing overview * Licensing definitions * Frequently asked questions * Support * Support options * Consulting * On-site training * Resources * Documentation * Learning path * Training courses * Webinars * Videos & presentations * Ask a question * Source code * Proof-of-concept help * Third-party licenses * Community * Events * Blog * NServiceBus Champs * Discussion group * Company * About us * Customers * Case studies & testimonials * Partners * Careers * Contact us * Get started BANISH GHOST MESSAGES AND ZOMBIE RECORDS FROM YOUR WEB TIER Written by Laila Bougria, Tomek Masternak, Daniel Marbach, Tim Bussmann, and Szymon Pobiega on October 25, 2022 Because it’s hard to write idempotent code effectively, NServiceBus provides the outbox feature to make your business data transaction and any sent or received messages atomic. That way, you don’t get any ghost messages or zombie records polluting your system. 1 But the outbox can only be used inside a message handler. What about web applications and APIs? With the new NServiceBus.TransactionalSession package, you can use the outbox pattern outside of a message handler too. 🔗THE PROBLEM Let’s say you have a web application where you need to create an entity and perform some background processing. Before the transactional session, the best guidance was to not do any database work inside the ApiController, but to only take the input data and send a message to the back end, responding only with an HTTP 202 Accepted message. Then, a few milliseconds later, a message handler would pick up the message and process it with the complete protection of the outbox feature. But this isn’t always very realistic. What if the database is in charge of ID generation, and you must return that ID to the client? Or do you need to update the UI to show the request, even if the processing isn’t complete yet? 🔗GHOST PROTOCOL This example code compromises by inserting a single record using Entity Framework and then sends a message to the backend. As a result of the compromise, this code is still vulnerable to ghost messages if the database transaction has to roll back after the message has been sent. [ApiController] public class SendMessageController : Controller { readonly MyDataContext dataContext; readonly IMessageSession messageSession; public SendMessageController(IMessageSession messageSession, MyDataContext dataContext) { this.messageSession = messageSession; this.dataContext = dataContext; } [HttpPost] public async Task<string> Post(Guid id) { await dataContext.MyEntities.AddAsync(new MyEntity { Id = id, Processed = false }); var message = new MyMessage { EntityId = id }; await messageSession.SendLocal(message); return $"Message with entity ID '{id}' sent to endpoint"; } } Those familiar with Entity Framework might wonder where the call to SaveChangesAsync is happening. Because Entity Framework already supports the Unit of Work pattern, the calls to SaveChangesAsync can be done using ASP.NET Core middleware, which means the database is only updated when the HTTP request successfully completes: public class UnitOfWorkMiddleware { readonly RequestDelegate next; public UnitOfWorkMiddleware(RequestDelegate next, MyDataContext dataContext) { this.next = next; } public async Task InvokeAsync(HttpContext httpContext, ITransactionalSession session) { await next(httpContext); await dataContext.SaveChangesAsync(); } } Regardless of the outcome of the database operations, the moment our controller logic calls SendLocal, the message is handed over to the transport. This makes the message MyMessage almost immediately available to be processed in the background. Things will often seem fine until a transient exception forces your Entity Framework operations to roll back. The database entity was never committed, but the ghost message starts processing in the background anyway. The message handler tries to load the missing entity from the database…and disaster unfolds. Imagine looking into this error and trying to figure out why that entity doesn’t exist. Obviously, the message was sent, so the entity should be in the database, but it’s not. Where is it? Unfortunately, ghost messages don’t know they’re ghosts, 2 which makes them hard to diagnose. 🔗GHOSTBUSTERS So how do we banish the ghost message? A proton pack won’t help you, but our new TransactionalSession packages can. In this case, since we’re using Entity Framework, we’ll use NServiceBus.Persistence.Sql.TransactionalSession, but we’ve got a bunch of different packages available depending on what database you’re using. In our code, we replace IMessageSession with ITransactionalSession like this: [ApiController] public class SendMessageController : Controller { readonly MyDataContext dataContext; - readonly IMessageSession messageSession; + readonly ITransactionalSession messageSession; - public SendMessageController(IMessageSession messageSession, MyDataContext dataContext) + public SendMessageController(ITransactionalSession messageSession, MyDataContext dataContext) { this.messageSession = messageSession; this.dataContext = dataContext; } // Rest omitted. Believe us, the code looks the same ;) } All the business logic stays the same. With that small change, the newly stored entity and the sends are wrapped in an all-or-nothing transaction, meaning both succeed or fail. The TransactionalSession package works with all the persistence technologies supported by NServiceBus, like Microsoft SQL Server, CosmosDB, and many more. Check out this video demo of the TransactionalSession in action: 🔗ONE FINAL DETAIL Much like the code that ensures Entity Framework operations are executed against the database by calling SaveChangesAsync, there needs to be a tiny bit of middleware that ensures the transactional session is committed when the HTTP pipeline has to be executed. public class UnitOfWorkMiddleware { readonly RequestDelegate next; + readonly ITransactionalSession messageSession; - public UnitOfWorkMiddleware(RequestDelegate next, MyDataContext dataContext) + public UnitOfWorkMiddleware(RequestDelegate next, ITransactionalSession messageSession) { this.next = next; + this.messageSession = messageSession; } public async Task InvokeAsync(HttpContext httpContext, ITransactionalSession session) { + await session.Open(new SqlPersistenceOpenSessionOptions()); await next(httpContext); - await dataContext.SaveChangesAsync(); + await session.Commit(); } } Notice how the middleware no longer calls SaveChangesAsync anymore–instead, the middleware commits the transaction on the data context. The transaction created by the transactional session will take care of saving all database changes and triggering the outbox so that everything remains consistent and atomic. 🔗BULLETPROOF The algorithm behind the Transactional Session feature was based on the proven NServiceBus Outbox implementation. But we’ve also modeled and verified the algorithm using TLA+, 3 a formal specification language to verify and test programs. Plus, we’ve covered it with a rich set of automated test suites covering every supported database engine, so you know you can trust it. 🔗SUMMARY With the new TransactionalSession, there are no more compromises. You don’t have to painfully redesign a web application to move all the data transactions to the backend. Instead, you can update the database and send a message, and be confident that the outbox implementation will prevent ghost messages or zombie records. To get started, check out the TransactionalSession documentation, including a detailed description of how it works. Or, check out our Using TransactionalSession with Entity Framework and ASP.NET Core sample to see how to use it in your own projects. Share on Twitter ABOUT THE AUTHORS Laila is a software engineer who's terrified of ghost messages. * Twitter * LinkedIn Tomek is a software engineer who prefers things to occur exactly once. * Twitter * LinkedIn Daniel is a software engineer who strives to be eventually consistent. * Twitter * Personal blog * LinkedIn Tim is a software engineer who highly values idempotency. Tim is a software engineer who highly values idempotency. Szymon is a software engineer who loathes zombie records. * Twitter * Personal blog * LinkedIn RELATED CONTENT * RPC vs. Messaging – which is faster? * Cross-platform integration with NServiceBus native message processing * Autosave for your business * What does idempotent mean? * What Starbucks can teach us about software scalability -------------------------------------------------------------------------------- 1. For details on ghost messages, zombie records, and why they pose a problem for distributed systems, check out our blog post What does idempotent mean? 2. Which is sad for the messages, I guess? 3. …which you can learn about in our webinar Implementing an Outbox – model-checking first. Don't miss a thing. Sign up today and we'll send you an email when new posts come out. Thank you for subscribing. We'll be in touch soon. Get notified We collect and use this information in accordance with our privacy policy. Blog home RSS feed Webinar: An exception occurred... Please try again. with Laila Bougria If there is one certainty in software, it’s that things fail. See how to embrace those failures. Learn more → NServiceBus Quick Start Learn why software systems built on asynchronous messaging using NServiceBus are superior to traditional synchronous HTTP-based web services. Check it out → Contact us TELEPHONE NUMBERS: US toll-free:(888) 495-6156 New York:(646) 741-8577 London:(020) 33189097 Sydney:(02) 80466544 Tel Aviv:(03) 721-9577 Write to us * Platform * Platform overview * NServiceBus * ServiceInsight * ServicePulse * Downloads * Solutions * Finance * Healthcare * Retail * Pricing * Pricing overview * Licensing definitions * Frequently asked questions * Support * Support options * Consulting * On-site training * Resources * Documentation * Learning path * Training courses * Webinars * Videos & presentations * Ask a question * Source code * Proof-of-concept help * Third-party licenses * Community * Events * Blog * NServiceBus Champs * Discussion group * Company * About us * Customers * Case studies & testimonials * Partners * Careers * Contact us © 2010-2022 NServiceBus Ltd. doing business as Particular Software. All rights reserved. * Privacy policy * GDPR privacy policy * Cookie Settings * Terms of Use Need help getting started? WRITE TO US × Required fields are marked with * First name * Missing Last name * Missing Company name Email address * Invalid Phone number Message * Missing Send Message Unable to submit. Please see errors above. We collect and use this information in accordance with our privacy policy Thank you for your message. We will get back to you soon.