Domain driven design 事件源Saga实现
我已经写了一个事件源聚合,现在实现了一个事件源传奇。。。我注意到这两个是相似的,并且创建了一个事件源对象作为基类,这两个对象都是从基类派生的 我在这里看过一个演示,但觉得可能有一个问题,因为在进程崩溃的情况下,命令可能会丢失,因为命令的发送在写事务之外Domain driven design 事件源Saga实现,domain-driven-design,event-sourcing,saga,eventstoredb,Domain Driven Design,Event Sourcing,Saga,Eventstoredb,我已经写了一个事件源聚合,现在实现了一个事件源传奇。。。我注意到这两个是相似的,并且创建了一个事件源对象作为基类,这两个对象都是从基类派生的 我在这里看过一个演示,但觉得可能有一个问题,因为在进程崩溃的情况下,命令可能会丢失,因为命令的发送在写事务之外 public void Save(ISaga saga) { var events = saga.GetUncommittedEvents(); eventStore.Write(new UncommittedEventStrea
public void Save(ISaga saga)
{
var events = saga.GetUncommittedEvents();
eventStore.Write(new UncommittedEventStream
{
Id = saga.Id,
Type = saga.GetType(),
Events = events,
ExpectedVersion = saga.Version - events.Count
});
foreach (var message in saga.GetUndispatchedMessages())
bus.Send(message); // can be done in different ways
saga.ClearUncommittedEvents();
saga.ClearUndispatchedMessages();
}
相反,我使用的是Greg Young的EventStore,当我保存EventSourcedObject(聚合或saga)时,顺序如下:
CommandEmittedFromSaga
并在命令失败时重新发送的外部事件处理程序中)命令
和事件
存储在同一个流中(与基类消息混合在一起-Saga将同时使用命令和事件,聚合只使用事件)public class CommandEmittedFromSaga : Event
{
public readonly Command Command;
public readonly Identity SagaIdentity;
public readonly Type SagaType;
public CommandEmittedFromSaga(Identity sagaIdentity, Type sagaType, Command command)
{
Command = command;
SagaType = sagaType;
SagaIdentity = sagaIdentity;
}
}
Saga在将来的某个时候请求回调(AlarmRequestedBySaga事件)
报警回调请求被包装在事件旁边,并将在请求的时间或之后对事件进行回击:
public class AlarmRequestedBySaga : Event
{
public readonly Event Event;
public readonly DateTime FireOn;
public readonly Identity Identity;
public readonly Type SagaType;
public AlarmRequestedBySaga(Identity identity, Type sagaType, Event @event, DateTime fireOn)
{
Identity = identity;
SagaType = sagaType;
Event = @event;
FireOn = fireOn;
}
}
或者,我可以将命令和事件存储在相同的基本类型消息流中
public abstract class EventSourcedSaga
{
protected EventSourcedSaga() { }
protected EventSourcedSaga(Identity id, IEnumerable<Message> messages)
{
Identity = id;
if (messages == null) throw new ArgumentNullException(nameof(messages));
var count = 0;
foreach (var message in messages)
{
var ev = message as Event;
var command = message as Command;
if(ev != null) Transition(ev);
else if(command != null) _messages.Add(command);
else throw new Exception($"Unsupported message type {message.GetType()}");
count++;
}
if (count == 0)
throw new ArgumentException("No messages provided");
// All we need to know is the original number of events this
// entity has had applied at time of construction.
_unmutatedVersion = count;
_constructing = false;
}
readonly IEventDispatchStrategy _dispatcher = new EventDispatchByReflectionStrategy("When");
readonly List<Message> _messages = new List<Message>();
readonly int _unmutatedVersion;
private readonly bool _constructing = true;
public readonly Identity Identity;
public IList<Message> GetMessages()
{
return _messages.ToArray();
}
public void Transition(Event e)
{
_messages.Add(e);
_dispatcher.Dispatch(this, e);
}
protected void SendCommand(Command c)
{
// Don't add a command whilst we are in the constructor. Message
// state transition during construction must not generate new
// commands, as those command will already be in the message list.
if (_constructing) return;
_messages.Add(c);
}
public int UnmutatedVersion() => _unmutatedVersion;
}
公共抽象类EventSourcedSaga
{
受保护的EventSourcedSaga(){}
受保护的EventSourcedSaga(标识id,IEnumerable消息)
{
身份=身份;
如果(messages==null)抛出新的ArgumentNullException(nameof(messages));
var计数=0;
foreach(消息中的var消息)
{
var ev=作为事件的消息;
var命令=消息作为命令;
如果(ev!=null)转换(ev);
else if(command!=null)\ u消息。添加(command);
else抛出新异常($“不支持的消息类型{message.GetType()}”);
计数++;
}
如果(计数=0)
抛出新ArgumentException(“未提供消息”);
//我们需要知道的是今年的原始事件数量
//实体在构建时已申请。
_未变异版本=计数;
_构造=假;
}
readonly IEventDispatchStrategy\u dispatcher=新事件DispatchByReflectionStrategy(“何时”);
只读列表_messages=new List();
只读int_未经修改的版本;
私有只读bool\u=true;
公共只读标识;
公共IList GetMessages()
{
返回_messages.ToArray();
}
公共无效转换(事件e)
{
_添加(e);
_调度员。调度(本,e);
}
受保护的void SendCommand(命令c)
{
//在构造函数中时不要添加命令。消息
//施工期间的状态转换不得产生新的
//命令,因为这些命令已经在消息列表中。
如果(_)返回;
_添加(c);
}
public int UnmutatedVersion()=>\u UnmutatedVersion;
}
我认为前两个问题是对流程经理的错误理解造成的(又名Sagas,请参见底部的术语注释)
改变你的想法
你似乎正试图将它(就像我曾经做过的那样)建模为一个反向聚合。问题是:聚合的“社会契约”是它的输入(命令)可以随时间变化(因为系统必须能够随时间变化),但它的输出(事件)不能。一旦写入,事件就是历史问题,系统必须始终能够处理它们。有了这个条件,就可以从不可变的事件流可靠地加载聚合
如果您尝试将输入和输出作为process manager实现进行反转,则其输出不能成为记录问题,因为随着时间的推移,命令可能会被弃用并从系统中删除。当您尝试使用已删除的命令加载流时,它将崩溃。因此,建模为反向聚合的process manager无法重新加载liably从一个不变的消息流中重新加载。(我相信你能想出一个方法……但这明智吗?)
因此,让我们考虑通过查看流程管理器所取代的内容来实现流程管理器。例如,一名管理订单履行等流程的员工。您为该用户所做的第一件事是在UI中设置一个视图,供他们查看。第二件事是在UI中创建按钮,供用户执行响应wha的操作他们在view.Ex.上看到:“此行已付款失败
,因此我单击取消订单
。此行已付款成功
和订单项出库
,因此我单击更改订单
。此订单