Distributed DDD

a no-nonsense implementation guide

exactly-once.github.io

linkedin.com/in/SzymonPobiega

twitter.com/SzymonPobiega

Domain-Driven Design

Event Sourcing

Tackling complexity at the heart of software

The network is reliable

Latency is zero

Bandwidth is infinite

The network is secure

Topology doesn't change

There is one administrator

Transport cost is zero

The network is homogeneous

The network is NOT reliable

Latency is NOT zero

Bandwidth is NOT infinite

The network is NOT secure

Topology DEOES change

There is NOT one administrator

Transport cost is NOT zero

The network is NOT homogeneous

The network is NOT reliable

The system is as good as its weakest component

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;
					

Comms is a separate domain

Network is NOT not reliable

Repository is key to reliability

Deduplication is not optional

Network is NOT not reliable

Generic? Ideally

Supporting? Likely

Core? Rarely

What if message contained processing information?

Messages are documents


Documents are state

Consistent document creation


Documents are state

Consistent document creation


State-based deduplication

Thank you!

github.com/exactly-once

exactly-once.github.io

linkedin.com/in/SzymonPobiega

twitter.com/SzymonPobiega