在多个站点上(例如,here或here, Sagas 被描述为一种机制,它监听域事件并对其做出反应,执行新命令,最后修改域等。
Saga 和简单的事件调度器之间有什么区别,你有一些订阅者对事件做出反应?
“传奇”维护进程状态。更准确的术语是流程管理器。“saga”一词是由 NServiceBus 普及的,这就是为什么现在许多人将其称为“NServiceBus saga”的原因。真正的传奇是一个数据库概念。
无论如何,由于事件调度器对流程状态不感兴趣,它不是流程管理器。正如您所提到的,服务总线也可以充当事件调度程序,通常用于其他系统,尽管服务总线处理更多的事情。
有一些方法可以在不使用 saga 的情况下处理流程状态,例如:路由单和“编排”。流程管理器更像是一种“编排”机制。
流程管理器可以让您的生活变得更简单,因此它比事件调度器做得更多。
本质上,您的订阅者将与您的流程管理器进行交互,以实现与流程相关的任何更改。
您可能会认为这有点像工作流程,您是对的。但是,工作流引擎是一件很繁重的事情,而流程管理器应该是您的 DDD 世界中的一等公民 :)
以下只是一个快速的、我想不到的、广泛的示例。最初,创建成员的数据作为状态存储在流程管理器中。只有在验证了电子邮件地址后,才会使用其有效的电子邮件地址创建和存储实际成员。
然后发送一封欢迎电子邮件,可能使用服务总线。一旦从EMailService
端点接收到邮件已成功发送的响应,该处理程序就指示流程管理器电子邮件已发送,然后完成流程管理器。
所以会有一个MemberRegistrationProcessRepository
. 完成一个过程可能会导致它被存档,如果真的不再需要它,甚至会被删除。
我怀疑事件溯源会很好地适用于流程管理器,但为了保持示例简单,我根据我之前自己实现的内容汇总了以下内容。
我之前也做的是跟踪状态变化,每个状态的 SLA 为 15 分钟。对此进行监控,所有处于一个状态超过 15 分钟的流程经理都将报告给核心运营团队进行调查。
在 C# 中,可能会有这样的事情:
public class MemberRegistrationProcess
{
public Guid ProcessId { get; private set; }
public string Name { get; private set; }
public EMailAddress EMailAddress { get; private set; }
public string Status { get; private set; }
public static MemberRegistrationProcess Create(string name, EMailAddress eMailAddress)
{
return new MemberRegistrationProcess(Guid.NewGuid(), name, eMailAddress, "Started");
}
public MemberRegistrationProcess(Guid processId, string name, EMailAddress eMailAddress, string status)
{
ProcessId = processId;
Name = name;
EMailAddress = eMailAddress;
Status = status;
}
public void EMailAddressVerified(IMemberRepository memberRepository)
{
if (!Status.Equals("Started"))
{
throw new InvalidOperationException("Can only verify e-mail address if in 'started' state.");
}
memberRepository.Add(new Member(Name, EMailAddress));
Status = "EMailAddressVerififed";
}
public void WelcomeEMailSent()
{
if (!Status.Equals("EMailAddressVerififed"))
{
throw new InvalidOperationException("Can only set welcome e-mail sent if in 'EMailAddressVerififed' state.");
}
Status = "WelcomeEMailSent";
}
public void Complete(Member member)
{
if (!Status.Equals("WelcomeEMailSent"))
{
throw new InvalidOperationException("Can only complete in 'WelcomeEMailSent' state.");
}
member.Activate();
Status = "Complete";
}
}
Saga 是一个长期运行的进程,由域外的事件触发。这些事件可能在几秒钟、几分钟或几天内发生。
与简单事件总线的不同之处在于,Saga 保留了一个状态机,可以持久处理由于外部事件而在“断开连接”的工作流中长时间运行的进程。
理解它的最简单方法是一个现实生活中的例子,经典的“我们向您发送了一封确认电子邮件以完成您在我们很棒的论坛中的注册”应该可以工作:
NServiceBus 示例:
// data to be persisted to start and resume Saga when needed
public class UserRegistrationSagaData : ISagaEntity
{
public Guid Id { get; set; }
public string Originator { get; set; }
public string OriginalMessageId { get; set; }
public string Email { get; set; }
public int Ticket { get; set; }
}
// the saga itself
public class UserRegistrationSaga :
Saga<UserRegistrationSagaData>,
// tell NServiceBus the Saga is created when RequestRegistration message arrives
ISagaStartedBy<RequestRegistration>,
// tell NServiceBus the Saga is resumed when ConfirmRegistration message arrives
// (user click in the link inside the e-mail)
IMessageHandler<ConfirmRegistration>
{
public override void ConfigureHowToFindSaga() //primary keys of this saga in persistence
{
ConfigureMapping<RequestRegistration>(saga => saga.Email, message => message.Email);
ConfigureMapping<ConfirmRegistration>(saga => saga.Ticket, message => message.Ticket);
}
// when requestRegistrarion arrives this code is executed
public void Handle(RequestRegistration message)
{
// generate new ticket if it has not been generated
if (Data.Ticket == 0)
{
Data.Ticket = NewUserService.CreateTicket();
}
Data.Email = message.Email;
MailSender.Send(message.Email,
"Your registration request",
"Please go to /registration/confirm and enter the following ticket: " + Data.Ticket);
Console.WriteLine("New registration request for email {0} - ticket is {1}", Data.Email, Data.Ticket);
}
// when ConfirmRegistration arrives this code is executed
public void Handle(ConfirmRegistration message)
{
Console.WriteLine("Confirming email {0}", Data.Email);
NewUserService.CreateNewUserAccount(Data.Email);
MailSender.Send(Data.Email,
"Your registration request",
"Your email has been confirmed, and your user account has been created");
// tell NServiceBus that this saga can be cleaned up afterwards
MarkAsComplete();
}
}
}
一个简单的
Bus.Send(new RequestRegistration(...))
通过 ie web 控制器应该可以完成这项工作。
使用简单的事件总线对这种行为进行硬编码将要求您以丑陋的方式模拟域中的状态机;即在域持久性中的用户表中添加一个布尔字段“已确认”,并且必须在系统的用户管理模块中查询和使用“已确认 = true”用户。或者在您的持久性域中有一个“待确认用户”表。我想你会明白的。
因此,Saga 就像一个简单的事件总线,它可以帮助您不使用状态机污染域和域持久性,因为“断开连接”的长时间运行过程。这只是良好 OO 设计中的职责分离。
这是一个很好的问题,因为区分这些概念很容易混淆。我同意那些说传奇是业务流程的答案。
并且因为 sagas 可以跨越多个有界上下文,因此可以跨越多个微服务或模块,那么它们可以通过两种方式实现:
事件编排是一种流程管理器或流编排器,是编排整个业务流程所需的中心组件。因此它将创建 saga,然后跨多个微服务或模块协调整个流程,然后结束 saga。
事件编排要简单得多,可以由 saga 参与者发出和订阅事件来完成。这可以由事件总线、调度程序和订阅者来完成。
所以 saga 本身可以通过事件调度器和订阅者来实现。不同之处在于 saga,发出/订阅的事件应该在 saga 本身的业务流程中有意义。
我希望我让事情变得更简单:D