Successfully reported this slideshow.
Your SlideShare is downloading. ×

Got the time?

Ad
Ad
Ad
Ad
Ad
Ad
Ad
Ad
Ad
Ad
Ad
Upcoming SlideShare
Mongo db world 2014   billrun
Mongo db world 2014 billrun
Loading in …3
×

Check these out next

1 of 62 Ad

Got the time?

Download to read offline

“Have all my overdue invoices been paid?” Seems a simple enough question. But once you factor in the effects of time, even the simplest question can turn into a mess of edge cases and complicated batch jobs that never quite complete on time.

“Have all my overdue invoices been paid?” Seems a simple enough question. But once you factor in the effects of time, even the simplest question can turn into a mess of edge cases and complicated batch jobs that never quite complete on time.

Advertisement
Advertisement

More Related Content

Similar to Got the time? (20)

More from Particular Software (20)

Advertisement

Recently uploaded (20)

Got the time?

  1. 1. Got the Time? All we want, two, three, go! Time, got the time tick tick tickin' in my head! mauroservienti
  2. 2. mauroservienti
  3. 3. The overdue invoice dilemma At the 10th of every month… mauroservienti
  4. 4. (manual) batch jobs a first attempt mauroservienti
  5. 5. business evolution mauroservienti
  6. 6. days spent checking invoices With all sort of issues: Is the bank statements up to date? Is the payment from a different bank? Are there invoices due today? mauroservienti
  7. 7. What are we looking for? • An overdue invoices report? • Or an alert when an invoice is overdue • and a collateral report? mauroservienti
  8. 8. We want alerts! mauroservienti
  9. 9. What’s an invoice? mauroservienti
  10. 10. What’s an invoice? • An aggregate: state and behaviors • Behaviors can be modelled using state machines • Draft created • Issued • Paid • Overdue • Credit note issued (there might be more for partial payments and so on) mauroservienti
  11. 11. Behavior modelling •Trigger (in) •State manipulation •Events (out) mauroservienti
  12. 12. Trigger => State Manipulation => Event Issue Invoice Invoice Issued When due Check payment ? mauroservienti
  13. 13. messages and sagas you want mauroservienti
  14. 14. how does it work? mauroservienti
  15. 15. BUS Persister queue mauroservienti
  16. 16. BUS SAGA Persister create saga dispatch message manipulate state queue send messages persist state mauroservienti DB
  17. 17. BUS SAGA Persister dispatch message manipulate state queue retrieve state return saga if saga completed delete state get delete mauroservienti DB
  18. 18. show me the code
  19. 19. Code for your future self async Task Handle(InvoiceIssued msg, IMessageHandlerContext ctx) { Data.InvoiceNumber = msg.InvoiceNumber; var dueDate = msg.DueDate; if(msg.CustomerCountry == "Italy") { dueDate = dueDate.AddDays(20); } await RequestTimeout<CheckPayment>(ctx, dueDate); } mauroservienti
  20. 20. Code for your future self async Task Handle(InvoiceIssued msg, IMessageHandlerContext ctx) { Data.InvoiceNumber = msg.InvoiceNumber; var dueDate = msg.DueDate; if(msg.CustomerCountry == "Italy") { dueDate = dueDate.AddDays(20); } await RequestTimeout<CheckPayment>(ctx, dueDate); } mauroservienti
  21. 21. Code for your future self async Task Handle(InvoiceIssued msg, IMessageHandlerContext ctx) { Data.InvoiceNumber = msg.InvoiceNumber; var dueDate = msg.DueDate; if(msg.CustomerCountry == "Italy") { dueDate = dueDate.AddDays(20); } await RequestTimeout<CheckPayment>(ctx, dueDate); } mauroservienti
  22. 22. Code for your future self async Task Handle(InvoiceIssued msg, IMessageHandlerContext ctx) { Data.InvoiceNumber = msg.InvoiceNumber; var dueDate = msg.DueDate; if(msg.CustomerCountry == "Italy") { dueDate = dueDate.AddDays(20); } await RequestTimeout<CheckPayment>(ctx, dueDate); } mauroservienti Machine learning ;-)
  23. 23. Code for your future self async Task Handle(InvoiceIssued msg, IMessageHandlerContext ctx) { Data.InvoiceNumber = msg.InvoiceNumber; var dueDate = msg.DueDate; if(msg.CustomerCountry == "Italy") { dueDate = dueDate.AddDays(20); } await RequestTimeout<CheckPayment>(ctx, dueDate); } mauroservienti
  24. 24. Code for your future self async Task Handle(InvoiceIssued msg, IMessageHandlerContext ctx) { Data.InvoiceNumber = msg.InvoiceNumber; var dueDate = msg.DueDate; if(msg.CustomerCountry == "Italy") { dueDate = dueDate.AddDays(20); } await RequestTimeout<CheckPayment>(ctx, dueDate); } mauroservienti
  25. 25. Code for your future self async Task Timeout(CheckPayment msg, IMessageHandlerContext ctx) { var invoiceNumber = Data.InvoiceNumber; await ctx.Publish(new InvoiceOverdue() { InvoiceNumber = invoiceNumber; }); MarkAsComplete(); } mauroservienti
  26. 26. Code for your future self async Task Timeout(CheckPayment msg, IMessageHandlerContext ctx) { var invoiceNumber = Data.InvoiceNumber; await ctx.Publish(new InvoiceOverdue() { InvoiceNumber = invoiceNumber; }); MarkAsComplete(); } mauroservienti
  27. 27. Code for your future self async Task Timeout(CheckPayment msg, IMessageHandlerContext ctx) { var invoiceNumber = Data.InvoiceNumber; await ctx.Publish(new InvoiceOverdue() { InvoiceNumber = invoiceNumber; }); MarkAsComplete(); } mauroservienti
  28. 28. Code for your future self async Task Timeout(CheckPayment msg, IMessageHandlerContext ctx) { var invoiceNumber = Data.InvoiceNumber; await ctx.Publish(new InvoiceOverdue() { InvoiceNumber = invoiceNumber; }); MarkAsComplete(); } mauroservienti
  29. 29. Code for your future self async Task Timeout(CheckPayment msg, IMessageHandlerContext ctx) { var invoiceNumber = Data.InvoiceNumber; await ctx.Publish(new InvoiceOverdue() { InvoiceNumber = invoiceNumber; }); MarkAsComplete(); } mauroservienti
  30. 30. Hold on! async Task Timeout(CheckPayment msg, IMessageHandlerContext ctx) { var invoiceNumber = Data.InvoiceNumber; await ctx.Publish(new InvoiceOverdue() { InvoiceNumber = invoiceNumber; }); MarkAsComplete(); } mauroservienti
  31. 31. That’s fine! Task Handle(InvoicePaid msg, IMessageHandlerContext ctx) { MarkAsComplete(); await Task.CompletedTask; } mauroservienti
  32. 32. That’s fine! Task Handle(InvoicePaid msg, IMessageHandlerContext ctx) { MarkAsComplete(); await Task.CompletedTask; } mauroservienti
  33. 33. Trigger => State Manipulation => Event Issue Invoice Invoice Issued When due Check payment mauroservienti
  34. 34. Trigger => State Manipulation => Event Issue Invoice Invoice Issued Check payment Delayed Delivery mauroservienti
  35. 35. The premium customer •If monthly running total is > $300 •Customer get a 10% discount mauroservienti
  36. 36. A naïve approach int CalculateDiscount(int customerId) { var lastMonth = DateTimeNow.AddMonths(-1); var lastMonthTotal = (from order in db.Orders where order.CustomerId = customerId and order.Timestamp >= lastMonth select order.Amount).Sum(); return lastMonthTotal > 300 ? 10 : 0; } mauroservienti
  37. 37. business evolution mauroservienti
  38. 38. Concurrency 1. Order is placed for customer 123 • Total amount: $100 2. Discount is calculated • Running total is $250, less than 300 => no discount Concurrently 1. Order is placed for customer 123 • Total amount: $60 2. Discount is calculated • Running total is still $250, less than 300 => no discount mauroservienti
  39. 39. The problem with a naïve approach int CalculateDiscount(int customerId) { var lastMonth = DateTimeNow.AddMonths(-1); var lastMonthTotal = (from order in db.Orders where order.CustomerId = customerId and order.Timestamp >= lastMonth select order.Amount).Sum(); return lastMonthTotal > 300 ? 10 : 0; } mauroservienti
  40. 40. A naïve approach to concurrency using var tx = conn.BeginTransaction(IsolationLevel.Serializable); using var db = new OrdersContex(conn, contextOwnsConnection: false); db.Database.UseTransaction(tx); var lastMonthTotal = (from order in db.Orders where order.CustomerId = customerId and order.Timestamp >= lastMonth select order.Amount).Sum(); tx.Commit(); mauroservienti
  41. 41. pessimistic locking? Thanks, no thanks. mauroservienti
  42. 42. batch jobs shall we try again? mauroservienti
  43. 43. Batch jobs • The batch job is run on a schedule • Runs over all customers • Sums orders total amount for the last 30 days • If the policy is matched creates a discount coupon mauroservienti
  44. 44. Batch jobs • The batch job is run on a schedule • Runs over all customers • Sums orders total amount for the last 30 days • If the policy is matched creates a discount coupon However… • Good luck running the batch job during Black Friday • Discount coupons are generated only when/if the batch job runs mauroservienti
  45. 45. timeline death to the batch job mauroservienti
  46. 46. Behavior modelling •Trigger (in) •State manipulation •Events (out) mauroservienti
  47. 47. Trigger => State Manipulation => Event Place Order Order Placed Calculate Discount Deduct from Running Total Add to Running Total Process Order 30-day Delayed Delivery mauroservienti
  48. 48. Place Order Order Placed Calculate Discount Deduct from Running Total Add to Running Total Process Order Arrow of time mauroservienti t0 t1 t1 + 30 days
  49. 49. show me the code
  50. 50. Code for your future self async Task Handle(OrderPlaced msg, IMessageHandlerContext ctx) { var discount = Data.MonthlyRunningTotal > 300 ? 10 : 0; Data.MonthlyRunningTotal += msg.OrderTotalAmount; await ctx.Send(new ProcessOrder() { OrderId = msd.OrderId, Discount = discount }); var delay = DateTime.Now.AddMonths(1); await RequestTimeout(ctx, delay, new DeductFromRunningTotal() { OrderTotalAmount = msg.OrderTotalAmount }); } mauroservienti
  51. 51. Code for your future self async Task Handle(OrderPlaced msg, IMessageHandlerContext ctx) { var discount = Data.MonthlyRunningTotal > 300 ? 10 : 0; Data.MonthlyRunningTotal += msg.OrderTotalAmount; await ctx.Send(new ProcessOrder() { OrderId = msd.OrderId, Discount = discount }); var delay = DateTime.Now.AddMonths(1); await RequestTimeout(ctx, delay, new DeductFromRunningTotal() { OrderTotalAmount = msg.OrderTotalAmount }); } mauroservienti
  52. 52. Code for your future self async Task Handle(OrderPlaced msg, IMessageHandlerContext ctx) { var discount = Data.MonthlyRunningTotal > 300 ? 10 : 0; Data.MonthlyRunningTotal += msg.OrderTotalAmount; await ctx.Send(new ProcessOrder() { OrderId = msd.OrderId, Discount = discount }); var delay = DateTime.Now.AddMonths(1); await RequestTimeout(ctx, delay, new DeductFromRunningTotal() { OrderTotalAmount = msg.OrderTotalAmount }); } mauroservienti
  53. 53. Code for your future self async Task Handle(OrderPlaced msg, IMessageHandlerContext ctx) { var discount = Data.MonthlyRunningTotal > 300 ? 10 : 0; Data.MonthlyRunningTotal += msg.OrderTotalAmount; await ctx.Send(new ProcessOrder() { OrderId = msd.OrderId, Discount = discount }); var delay = DateTime.Now.AddMonths(1); await RequestTimeout(ctx, delay, new DeductFromRunningTotal() { OrderTotalAmount = msg.OrderTotalAmount }); } mauroservienti Request to process order
  54. 54. Code for your future self async Task Handle(OrderPlaced msg, IMessageHandlerContext ctx) { var discount = Data.MonthlyRunningTotal > 300 ? 10 : 0; Data.MonthlyRunningTotal += msg.OrderTotalAmount; await ctx.Send(new ProcessOrder() { OrderId = msd.OrderId, Discount = discount }); var delay = DateTime.Now.AddMonths(1); await RequestTimeout(ctx, delay, new DeductFromRunningTotal() { OrderTotalAmount = msg.OrderTotalAmount }); } mauroservienti
  55. 55. Code for your future self async Task Handle(OrderPlaced msg, IMessageHandlerContext ctx) { var discount = Data.MonthlyRunningTotal > 300 ? 10 : 0; Data.MonthlyRunningTotal += msg.OrderTotalAmount; await ctx.Send(new ProcessOrder() { OrderId = msd.OrderId, Discount = discount }); var delay = DateTime.Now.AddMonths(1); await RequestTimeout(ctx, delay, new DeductFromRunningTotal() { OrderTotalAmount = msg.OrderTotalAmount }); } mauroservienti
  56. 56. Code for your future self Task Timeout(DeductFromRunningTotal msg, IMessageHandlerContext ctx) { Data.MonthlyRunningTotal -= msg.OrderTotalAmount; return Task.CompletedTask; } mauroservienti
  57. 57. Code for your future self Task Timeout(DeductFromRunningTotal msg, IMessageHandlerContext ctx) { Data.MonthlyRunningTotal -= msg.OrderTotalAmount; return Task.CompletedTask; } mauroservienti
  58. 58. What about mauroservienti
  59. 59. DB queue BUS SAGA Persister create saga dispatch message send messages persist saga BUS SAGA Persister create saga dispatch message send messages persist saga persist saga BusinessId = 1337 persist saga BusinessId = 1337 Concurrency Control mauroservienti
  60. 60. Take away • Avoid the technical solution trap • Understand the real business needs • Model time by sending messages to your future self mauroservienti
  61. 61. Mauro Servienti Solution Architect @ Particular Software the makers of NServiceBus mauro.servienti@particular.net @mauroservienti //github.com/mauroservienti //milestone.topics.it mauroservienti
  62. 62. mauroservienti Demos: bit.ly/particular-time-demos Slides: bit.ly/particular-time-slides Tutorial: go.particular.net/particular-time

×