微信小程序在url简化
Every time you make a change to the application state, you record the change as an event. You can replay the events since the beginning of the recording, up to a certain time. Then you've recreated the state of the application at that time.
每次更改应用程序状态时,都将更改记录为事件。 您可以从录制开始到指定时间重播事件。 然后,您当时已重新创建了应用程序的状态。
That's what Event Sourcing is about. It's like you can time travel to the past. I find it fascinating.
这就是事件源 。 就像您可以穿越时空走过去。 我觉得很有趣。
Event sourcing provides an audit trail when you need to meet regulatory requirements. It can help with debugging. And you can even explore alternate realities: what would have happened if...
当您需要满足法规要求时,事件源可提供审计跟踪。 它可以帮助调试。 您甚至可以探索其他现实:如果...
I recently saw a great talk by Jakub Pilimon and Kenny Bastani about event sourcing.
最近,我看到Jakub Pilimon和Kenny Bastani进行了有关活动采购的精彩演讲 。
The talk is a 1 hour session of life coding. The two speakers start with a simple application that is not event sourced. Then they refactor it to use events.
这次演讲是一小时的生活编码课程。 两位发言人以一个非事件源的简单应用程序开始。 然后他们将其重构为使用事件。
They end up wiring the application up with Apache Kafka. I will skip that part in this article and focus on the conceptual part of event sourcing instead.
他们最终将应用程序与Apache Kafka连接起来。 我将在本文中跳过该部分,而将重点放在事件源的概念部分。
谈话回顾 (A recap of the talk)
As a user of a Credit Card management application, you can:
作为信用卡管理应用程序的用户,您可以:
- Assign a limit to the credit card 为信用卡分配限额
- Withdraw money 提款
- Repay money 还钱
For each of these commands, there is a method in the CreditCard
class.Here's the original code of the assignLimit
method:
对于这些命令中的每一个, CreditCard
都有一个方法。这是assignLimit
方法的原始代码:
public void assignLimit(BigDecimal amount) {
if(limitAlreadyAssigned()) {
throw new IllegalStateException();
}
this.initialLimit = amount;
}
Here's the withdraw
method:
这是withdraw
方法:
public void withdraw(BigDecimal amount) {
if(notEnoughMoneyToWithdraw(amount)) {
throw new IllegalStateException();
}
if(tooManyWithdrawalsInCycle()) {
throw new IllegalStateException();
}
this.usedLimit = usedLimit.add(amount);
withdrawals++;
}
The repay
method is similiar.
repay
方法类似。
Remember that for event sourcing, you need to record an event any time the application changes its state?So the speakers extract each state change to its own method in the CreditCard class.
请记住,对于事件源,您需要在应用程序更改状态时记录一个事件?因此,发言人将每个状态更改提取到CreditCard类中自己的方法。
Here's the refactored withdraw
method:
这是重构的withdraw
方法:
public void withdraw(BigDecimal amount) {
if(notEnoughMoneyToWithdraw(amount)) {
throw new IllegalStateException();
}
if(tooManyWithdrawalsInCycle()) {
throw new IllegalStateException();
}
cardWithdrawn(new CardWithdrawn(uuid, amount, Instant.now()));
}
private CreditCard cardWithdrawn(CardWithdrawn event) {
this.usedLimit = usedLimit.add(event.getAmount());
withdrawals++;
pendingEvents.add(event);
return this;
}
An instance of CardWithdrawn
represents the event that a user has successfully withdrawn money. After the state has changed, the event is added to the list of pending events.
CardWithdrawn
实例代表用户成功取款的事件。 状态发生变化后 ,事件被添加到待处理事件的清单。
You call the save
method of the CreditCardRepository class to flush the pending events to the event stream. Event listeners may handle the events then.
您调用CreditCardRepository类的save
方法将未决事件刷新到事件流。 然后,事件侦听器可以处理事件。
Apart from the payload, each event has its own unique identifier and timestamp. So you can sequence and replay the events later.To replay the events for a specific credit card, the repository calls the recreateFrom
method of the CreditCard class, passing in the id of the card and the events stored for it:
除有效负载外,每个事件都有其自己的唯一标识符和时间戳。 因此,您可以稍后对事件进行排序和重播。要重播特定信用卡的事件,存储库将调用CreditCard类的recreateFrom
方法,并传入卡的ID和为其存储的事件:
public static CreditCard recreateFrom(UUID uuid, List<DomainEvent> events) {
return ofAll(events).foldLeft(new CreditCard(uuid), CreditCard::handle);
}
private CreditCard handle(DomainEvent event) {
return Match(event).of(
Case($(Predicates.instanceOf(LimitAssigned.class)), this::limitAssigned),
Case($(Predicates.instanceOf(CardWithdrawn.class)), this::cardWithdrawn),
Case($(Predicates.instanceOf(CardRepaid.class)), this::cardRepaid),
Case($(Predicates.instanceOf(CycleClosed.class)), this::cycleWasClosed)
);
}
This code uses the vavr.io library to call the handle
method for each event. The handle
method dispatches the event object to the appropriate method. For example: for each LimitAssigned
event, the handle
method calls the limitAssigned
method with the event as parameter.
这段代码使用vavr.io库为每个事件调用handle
方法。 handle
方法将事件对象分派到适当的方法。 例如:对于每个LimitAssigned
事件, handle
方法都以事件为参数调用limitAssigned
方法。
简化申请 (Simplifying the application)
I used the requirements as code library for simplifying the code. First, I put all of the event classes and the handling methods in a model. Like this:
我使用需求作为代码库来简化代码。 首先,我将所有事件类和处理方法放入模型中。 像这样:
this.eventHandlingModel =
Model.builder()
.on(LimitAssigned.class).system(this::limitAssigned)
.on(CardWithdrawn.class).system(this::cardWithdrawn)
.on(CardRepaid.class).system(this::cardRepaid)
.on(CycleClosed.class).system(this::cycleWasClosed)
.build();
I had to change the return type of the handling methods (e.g. limitAssigned
) to void
. Apart from that, the conversion from vavr.io was straight forward.
我不得不将处理方法的返回类型(例如limitAssigned
)更改为void
。 除此之外,来自vavr.io的转换非常简单。
Then, I created a runner and started it for the model:
然后,我创建了一个跑步者并为模型启动了它:
this.modelRunner = new ModelRunner();
modelRunner.run(eventHandlingModel);
After that, I changed the recreateFrom
and handle
methods to this:
之后,我将recreateFrom
和handle
方法更改为:
public static CreditCard recreateFrom(UUID uuid, List<DomainEvent> events) {
CreditCard creditCard = new CreditCard(uuid);
events.forEach(ev -> creditCard.handle(ev));
return creditCard;
}
private void handle(DomainEvent event) {
modelRunner.reactTo(event);
}
At that point, I could get rid of the dependency to vavr.io. Transition complete. Now I could get some more simplifying done.
到那时,我可以摆脱对vavr.io的依赖。 过渡完成。 现在,我可以做更多的简化了。
I revisited the withdraw
method:
我重新回顾了withdraw
方法:
public void withdraw(BigDecimal amount) {
if(notEnoughMoneyToWithdraw(amount)) {
throw new IllegalStateException();
}
if(tooManyWithdrawalsInCycle()) {
throw new IllegalStateException();
}
cardWithdrawn(new CardWithdrawn(uuid, amount, Instant.now()));
}
The check tooManyWithdrawalsInCycle()
didn't depend on the data of the event. It only depended on the state of the CreditCard
.State checks like this can be represented in the model as conditions
.
检查tooManyWithdrawalsInCycle()
不依赖于事件的数据。 它仅取决于CreditCard
的状态。像这样的状态检查可以在模型中表示为conditions
。
After I moved all state checks for all methods to the model, it looked like this:
将所有方法的所有状态检查移至模型后,它看起来像这样:
this.eventHandlingModel =
Model.builder()
.condition(this::limitNotAssigned)
.on(LimitAssigned.class).system(this::limitAssigned)
.condition(this::limitAlreadyAssigned)
.on(LimitAssigned.class).system(this::throwsException)
.condition(this::notTooManyWithdrawalsInCycle)
.on(CardWithdrawn.class).system(this::cardWithdrawn)
.condition(this::tooManyWithdrawalsInCycle)
.on(CardWithdrawn.class).system(this::throwsException)
.on(CardRepaid.class).system(this::cardRepaid)
.on(CycleClosed.class).system(this::cycleWasClosed)
.build();
For this to work, I needed to replace the direct calls to methods that change the state with the handle
method. After that, the assignLimit
and withdraw
methods looked like this:
为此,我需要用handle
方法替换对更改状态的方法的直接调用。 之后, assignLimit
和withdraw
方法如下所示:
public void assignLimit(BigDecimal amount) {
handle(new LimitAssigned(uuid, amount, Instant.now()));
}
private void limitAssigned(LimitAssigned event) {
this.initialLimit = event.getAmount();
pendingEvents.add(event);
}
public void withdraw(BigDecimal amount) {
if(notEnoughMoneyToWithdraw(amount)) {
throw new IllegalStateException();
}
handle(new CardWithdrawn(uuid, amount, Instant.now()));
}
private void cardWithdrawn(CardWithdrawn event) {
this.usedLimit = usedLimit.add(event.getAmount());
withdrawals++;
pendingEvents.add(event);
}
As you can see, most of the conditional logic has moved out of the methods into the model. This makes the methods easier to understand.
如您所见,大多数条件逻辑已从方法中移出到模型中。 这使方法更易于理解。
One thing that bothered me is that you must not forget to add the event to the pending events. Every time. Or your code won't work.
让我困扰的一件事是,您一定不要忘记将事件添加到未决事件中。 每次。 否则您的代码将无法正常工作。
Requirements as code allows you to control how the system handles the events. So I extracted pendingEvents.add(event)
from the methods as well:
作为代码的要求使您可以控制系统如何处理事件。 所以我也从方法中提取了pendingEvents.add(event)
:
modelRunner.handleWith(this::addingPendingEvents);
...
public void addingPendingEvents(StepToBeRun stepToBeRun) {
stepToBeRun.run();
DomainEvent domainEvent = (DomainEvent) stepToBeRun.getEvent().get();
pendingEvents.add(domainEvent);
}
I could have gone further and extract the validation logic as well. But I leave that as a thought exercise to you, dear reader.
我可以走得更远,并提取验证逻辑。 但是,亲爱的读者,我将其作为思想练习留给您。
重点是什么? (What's the point?)
What I tried to achieve is a clear separation of concerns:
我试图实现的目标是明确分离关注点:
- The state dependent execution of methods is defined in the model 在模型中定义了方法的状态相关执行
- The data validation and state changes are in the implementations of the methods 数据验证和状态更改在方法的实现中
- The events are automatically added to the pending events. In general: the infrastructure code is clearly separated from the business logic. 这些事件将自动添加到未决事件中。 通常,基础结构代码与业务逻辑明显分开。
Simplifying an example that is already very simple is good for explaining.But that's not the point I want to make.
简化一个已经很简单的示例可以很好地说明问题,但这不是我想讲的重点。
The point is: having such a clear separation of concerns pays out in practice. Especially, if you work with multiple teams. On complicated problems.
关键是:在实践中将关注点如此清晰地分开是值得的。 特别是,如果您与多个团队一起工作。 关于复杂的问题。
Separation of concerns helps with changing different parts of code at a different pace. You have simple rules where to find something. The code is easier to understand. And it's easier to isolate units for testing purposes.
关注点分离有助于以不同的速度更改代码的不同部分。 您在哪里可以找到简单的规则。 该代码更容易理解。 而且,出于测试目的隔离单元更容易。
结论 (Conclusion)
I hope you enjoyed my article. Please give me feedback.
希望您喜欢我的文章。 请给我反馈。
Have you been working on event sourcing applications? What were your experiences like? Can you relate to what I wrote in this article?
您是否一直在从事事件源应用程序的工作? 您的经历如何? 您能谈谈我在本文中写的内容吗?
I also want to invite you to look at my library that I used throughout the article. I would be thrilled if you try it out in practice, and tell me what you think.
我还想邀请您查看我在本文中使用的库 。 如果您在实践中尝试一下,并告诉我您的想法,我会很兴奋。
This article was first published on dev.to
本文最初发表于dev.to
翻译自: https://www.freecodecamp.org/news/simplifying-an-event-sourced-application/
微信小程序在url简化