Tackling complexity at the heart borders of software
Deduplicate
Aggregates are transaction boundaries
var order = repo.Find(msg.OrderId);
if (!order.HasProcessed(msg.Id)) {
order.ConfirmPayment(msg.Id);
repo.Save(order);
}
Repositories are aware of messaging
var order = repo.Find(msg.OrderId);
if (!order.HasProcessed(msg.Id)) {
order.ConfirmPayment(msg.Id);
repo.Save(order);
}
var order = repo.Find(msg.OrderId);
if (!repo.HasProcessed(msg.Id)) {
order.ConfirmPayment();
repo.Save(order, msg.Id);
}
var order = repo.Find(msg.OrderId);
if (!repo.HasProcessed(msg.Id)) {
order.ConfirmPayment();
repo.Save(order, msg.Id);
//...๐งจ๐ฅ๐ฅ๐๐ญ
context.Publish(new PaymentConfirmed());
}
var order = repo.Find(msg.OrderId);
if (!repo.HasProcessed(msg.Id)) {
order.ConfirmPayment();
repo.Save(order, msg.Id);
//...๐งจ๐ฅ๐ฅ๐๐ญ
context.Publish(new PaymentConfirmed());
}
if (!repo.HasProcessed(msg.Id)) {
var order = repo.Find(msg.OrderId);
order.ConfirmPayment();
repo.Save(order, msg.Id);
}
//...๐งจ๐ฅ๐ฅ๐๐ญ
context.Publish(new PaymentConfirmed());
if (!repo.HasProcessed(msg.Id)) {
var order = repo.Find(msg.OrderId);
order.ConfirmPayment();
repo.Save(order, msg.Id);
}
//...๐งจ๐ฅ๐ฅ๐๐ญ
context.Publish(new PaymentConfirmed());
Messages based on mutable state
\(f(f(x)) = f(x)\)
var c = repo.Find(msg.CustomerId); //msg.Id == ABC
if (!repo.HasProcessed(msg.Id)) {
c.PlaceOrder(msg.OrderData);
repo.Save(c, msg.Id);
}
if (c.Orders = 1) {
context.Send(new SendGift());
}
var c = repo.Find(msg.CustomerId); //msg.Id == DEF
if (!repo.HasProcessed(msg.Id)) {
c.PlaceOrder(msg.OrderData);
repo.Save(c, msg.Id);
}
if (c.Orders = 1) {
context.Send(new SendGift());
}
var c = repo.Find(msg.CustomerId); //msg.Id == ABC
if (!repo.HasProcessed(msg.Id)) {
c.PlaceOrder(msg.OrderData);
repo.Save(c, msg.Id);
}
if (c.Orders = 1) {
context.Send(new SendGift()); //...๐งจ๐ฅ๐ฅ๐๐ญ
}
var c = repo.Find(msg.CustomerId); //msg.Id == DEF
if (!repo.HasProcessed(msg.Id)) {
c.PlaceOrder(msg.OrderData);
repo.Save(c, msg.Id);
}
if (c.Orders = 1) { //c.Orders == 2
context.Send(new SendGift());
}
var c = repo.Find(msg.CustomerId); //msg.Id == ABC
if (!repo.HasProcessed(msg.Id)) {
c.PlaceOrder(msg.OrderData);
repo.Save(c, msg.Id);
}
if (c.Orders = 1) { //c.Orders == 2
context.Send(new SendGift());
}
var c = repo.Find(msg.CustomerId); //msg.Id == DEF
if (!repo.HasProcessed(msg.Id)) {
c.PlaceOrder(msg.OrderData);
repo.Save(c, msg.Id);
}
if (c.Orders = 1) {
context.Send(new SendGift());
}
Outbox
A customer places an order
Atomic save & send
Transactional session
The network is NOT reliable
The network is NOT reliable
Double submit problem
State-based deduplication
var o = repo.Find(msg.OrderId);
if (!o.Submitted) //No message id needed
{
o.Submit(msg.OrderData);
session.Publish(
new OrderSubmitted());
}
The network is NOT reliable
State-based deduplication?
ID based deduplication
Client-generated ID
UpdateData(msg); //x = 1
var data = LoadDocumentData(); //x == 1
var doc = GenerateDocument(); //x == 1
Store(doc);
return;
UpdateData(msg);
var data = LoadDocumentData();
var doc = GenerateDocument();
Store(doc);
return;
UpdateData();
var data = LoadDocumentData();
var doc = GenerateDocument(); //x == 1
Store(doc);
return;
UpdateData(); //x = 2
var data = LoadDocumentData(); //x == 2
var doc = GenerateDocument(); //y = x
Store(doc); //y = 2
return; //x == 2, y == 2
UpdateData();
var data = LoadDocumentData();
var doc = GenerateDocument(); //x == 1
Store(doc); //y = 1
return; //x == 2, y == 1
UpdateData();
var data = LoadDocumentData();
var doc = GenerateDocument();
Store(doc);
return;