jojo亲吻替身
Recently, I have written about simplifying an event sourced application.
最近,我写了有关简化事件源应用程序的文章 。
The article starts with code from a talk by Jakub Pilimon and Kenny Bastani. And it ends with building a model for events in the code: how they are applied, and under which conditions.
本文从Jakub Pilimon和Kenny Bastani的演讲中的代码开始。 最后以为代码中的事件建立模型为基础:如何应用事件以及在何种条件下进行事件。
The sample application is about Credit Card management. You can:
该示例应用程序与信用卡管理有关。 您可以:
Assign a credit limit. But only once, otherwise the application throws an
IllegalStateException
.分配信用额度 。 但是只有一次,否则应用程序将抛出
IllegalStateException
。Withdraw money. But you can't make more than 45 withdrawals in a certain cycle. Or you'll get an exception as well.
提款 。 但是在一个特定的周期内,您最多只能提取45次。 否则您也会得到一个例外。
Repay money
还钱
I played around with the CreditCard
class. I had a feeling that something might be wrong with the withdraw
method. So I wrote a test that checks for the correct behavior.
我玩了CreditCard
类。 我感觉到withdraw
方法可能出了点问题。 所以我写了一个测试来检查正确的行为。
@Test(expected = IllegalStateException.class)
public void withdrawWithoutLimitAssignedThrowsIllegalStateException() {
CreditCard card = new CreditCard(UUID.randomUUID());
card.withdraw(BigDecimal.ZERO);
}
The test attempts to withdraw an amount of zero. But no credit limit has been assigned before. The application should reject this, and throw an IllegalStateException
. Instead, the application threw a NullPointerException
.
该测试尝试提取零。 但是之前没有分配信用额度。 应用程序应拒绝此操作,并抛出IllegalStateException
。 相反,应用程序引发了NullPointerException
。
The application assumed that the limit had been assigned before.Now: this is a sample application. If it covered all cases it probably wouldn't be as understandable as it is.
该应用程序假设已经分配了限制。现在:这是一个示例应用程序。 如果它涵盖了所有情况,那么可能就不会那么容易理解了。
Let's pretend we're dealing with a real world application. What if the required order of commands/events depends on a multitude of conditions and states?
假设我们正在处理一个实际应用程序。 如果所需的命令/事件顺序取决于多种条件和状态怎么办?
If you have ever tried to implement this with conditional statements only, you probably know it's easy to lose the overview. But there is a standard solution for managing complicated flows and changes in behavior.
如果您曾经尝试仅使用条件语句来实现这一点,那么您可能会知道很容易失去概述。 但是,存在用于管理复杂流程和行为更改的标准解决方案。
抢救状态机 (State machine to the rescue)
In computer science, state machines have been around for decades. They are well understood in theory. They are battle proven in practice. They are the de facto standard for dealing with state dependent behavior.
在计算机科学中,状态机已经存在了数十年。 它们在理论上是众所周知的。 他们在实践中得到了战斗证明。 它们是处理依赖状态行为的事实标准。
So I decided to create a UML state machine model for the sample application. I asked myself first: Do I want to deal with commands or events in the state machine?
因此,我决定为该示例应用程序创建一个UML状态机模型。 我首先问自己:我想在状态机中处理命令或事件吗?
Commands are about something the application should do in the future.Events are about something that has happened in the past.
命令是关于应用程序将来应该做的事情。事件是关于过去发生的事情。
I wanted to prevent withdrawals without a credit limit assigned.So the state machine model needed to deal with commands.
我想防止未分配信用额度的提款,因此需要状态机模型来处理命令。
The syntax of a transition in the diagram is command[condition] / commandHandler()
. It means: when a command object has been received, and the condition is fulfilled if present, handle the command and go to the next state.
图中过渡的语法为command[condition] / commandHandler()
。 这意味着:当收到命令对象并且满足条件(如果存在)时,处理命令并进入下一个状态。
The model fixes what is allowed to happen, and what not. For example: repaying is only possible after withdrawing.
该模型修复了允许发生的事情以及不允许发生的事情。 例如:只有在提款后才能还款。
But that precision has a price. If you want the state machine model to be executed and to control the behavior at runtime, you need to model every possible transition from every state. Including its condition, if there are two transitions with the same event.
但是这种精度是有代价的。 如果要执行状态机模型并在运行时控制行为,则需要对每个状态的每个可能转换进行建模。 包括其条件,如果同一事件有两个转换。
That's why there is a lot more repetition in the state machine than in the original code with the if
statements. A way to reduce the amount of repetition is to use super states and sub states:
这就是为什么状态机中的重复次数比带有if
语句的原始代码中重复次数多的原因。 减少重复次数的一种方法是使用超级状态和子状态 :
It is easy to define state dependent behavior in a state machine model. But a state independent rule like in any state (when condition X holds), do Y leads to several transitions. For example, I needed to add requestToCloseCycle
to every super state.
在状态机模型中定义状态相关的行为很容易。 但是像任何状态(当条件X成立时)一样,独立于状态的规则Y会导致多个转换。 例如,我需要将requestToCloseCycle
添加到每个超级状态。
You need people with the right skills to create the models. And it's not easy to communicate about the models with non-technical stakeholders. It's not the way they normally speak about user journeys.
您需要具有适当技能的人员来创建模型。 与非技术利益相关者就模型进行沟通并不容易。 这不是他们通常谈论用户旅程的方式。
说再见 (Saying goodbye)
It seems there are two options so far.
到目前为止,似乎有两个选择。
In the left corner: the if
statement. Easy to start with. Low overhead. Best fit for applications that have no complicated flows of behavior. But it's easy to lose the overview when the behavior gets complicated.
左上角: if
语句。 易于入门。 低开销。 最适合没有复杂行为流程的应用程序。 但是,当行为变得复杂时,很容易丢失概述。
In the right corner: the executable state machine model. Powerful. Proven. Precise. Gives you an overview of the behavior. But it's hard to define state independent rules. And state machine models are difficult to communicate about with non-technical stakeholders.
右上角:可执行状态机模型。 强大。 证明了。 精确。 给您行为的概述。 但是很难定义独立于国家的规则。 而且状态机模型很难与非技术利益相关者进行交流。
I stand in the third corner. I have found an alternative to state machines. A solution that
我站在第三个角落。 我找到了状态机的替代品。 一个解决方案
- enables you to define conditions. But you don't have to in most cases. 使您可以定义条件。 但是大多数情况下您不必这样做。
- makes state dependent and independent rules equally easy to specify. 使状态相关和独立规则同样易于指定。
- uses language that all stakeholders can relate to. 使用所有利益相关者都可以使用的语言。
Before I dig into the details, here's the sample state machine model rewritten using that solution:
在深入研究细节之前,这是使用该解决方案重写的示例状态机模型:
Model model = Model.builder()
.useCase(useCreditCard)
.basicFlow()
.step(assigningLimit).user(requestsToAssignLimit).systemPublish(assignedLimit)
.step(withdrawingCard).user(requestsWithdrawingCard).systemPublish(withdrawnCard).reactWhile(accountIsOpen)
.step(repaying).user(requestsRepay).systemPublish(repay).reactWhile(accountIsOpen)
.flow("Withdraw again").after(repaying)
.step(withdrawingCardAgain).user(requestsWithdrawingCard).systemPublish(withdrawnCard)
.step(repeating).continuesAt(withdrawingCard)
.flow("Cycle is over").anytime()
.step(closingCycle).on(requestToCloseCycle).systemPublish(closedCycle)
.flow("Limit can only be assigned once").condition(limitAlreadyAssigned)
.step(assigningLimitTwice).user(requestsToAssignLimit).system(throwsAssignLimitException)
.flow("Too many withdrawals").condition(tooManyWithdrawalsInCycle)
.step(withdrawingCardTooOften).user(requestsWithdrawingCard).system(throwsTooManyWithdrawalsException)
.build();
return model;
As you can see, the model is in the code. A model runner executes this model. The runner reacts to commands/events, similar to a state machine.
如您所见,模型在代码中 。 模型运行者执行此模型。 跑步者对命令/事件做出React,类似于状态机。
The basic flow is the "happy day scenario". The steps of a user to reach her goal. The other flows cover alternative and error scenarios.
基本流程是“欢乐时光场景”。 用户达到目标的步骤。 其他流程包括替代方案和错误方案。
A flow can define an explicit condition for its first step to run - e.g. after(...)
, anytime()
or condition()
in the sample.If a flow has an explicit condition, the flow starts when the condition is fulfilled and the runner is currently in a different flow.If a flow has no explicit condition (e.g. the basic flow in the sample), the first step runs after the runner has started, when no step has been run so far.
流可以为其第一步运行定义一个明确的条件 -例如,样本中的after(...)
, anytime()
或condition()
。如果流具有明确的条件,则在满足条件时开始该流,并且如果流没有明确的条件(例如,样品中的基本流),则第一步在流道启动后运行,此时至今尚未执行任何步骤。
Starting with the second step of a flow, each step has an implicit condition. That condition is: run the step after the previous step in the same flow, unless a different flow with an explicit condition can start. So in contrast to state machines, you don't need to specify the conditions after the first step.
从流程的第二步开始,每个步骤都有一个隐式条件 。 该条件是:在同一流程中在上一步之后执行步骤,除非可以启动具有明确条件的其他流程。 因此,与状态机相比,您无需在第一步之后指定条件。
Internally, state depending behavior is realized by checking a condition. Every step contains its complete condition that defines exactly when the step can run. That's how requirements as code can treat state dependent and independent behavior alike.
在内部,通过检查条件来实现基于状态的行为。 每个步骤都包含其完整条件,该条件精确定义了该步骤何时可以运行。 这就是需求作为代码可以如何对待依赖状态和独立行为的方式。
Have a look at further examples to dig deeper.
何时使用需求作为代码 (When to use requirements as code)
Many applications have dynamic internal behavior. This is true for distributed applications in particular. They need to deal with the fact that "the other party" is not available.
许多应用程序具有动态内部行为。 对于分布式应用程序尤其如此。 他们需要处理“另一方”不可用的事实。
But from a user's perspective, these applications look quite predictable and regular. When I want to watch a show on Netflix or Amazon Prime, I follow the exact same steps each time until I can watch it. It looks like one step just follows the other.
但是从用户的角度来看,这些应用程序看起来非常可预测且规则。 当我想在Netflix或Amazon Prime上观看节目时,每次都遵循完全相同的步骤,直到可以观看为止。 看起来一个步骤紧随另一步骤。
That's the sweet spot for requirements as code, if used as an alternative to a state machine: defining the visible behavior of an application.
如果将其用作状态机的替代方案,那么这就是需求作为代码的最佳位置:定义应用程序的可见行为 。
信用卡应用程序现在如何工作 (How the Credit Card application works now)
A client sends a command to the
CreditCardAggregateRoot
客户端将命令发送到
CreditCardAggregateRoot
The CreditCardAggregateRoot uses the event repository to replay all the events for the credit card, to restore it
CreditCardAggregateRoot使用事件存储库重播信用卡的所有事件,并将其还原
The
CreditCardAggregateRoot
uses the above model to dispatch the command to a command handling methodCreditCardAggregateRoot
使用上面的模型将命令分配给命令处理方法The command handling method produces an event and applies it to the
CreditCard
instance.命令处理方法产生一个事件并将其应用于
CreditCard
实例。The event handling model of the CreditCard instance dispatches the event to a state changing method
CreditCard实例的事件处理模型将事件分配给状态更改方法
结论 (Conclusion)
I hope you enjoyed my article. I also want to invite you to look at the library that I used throughout the article. Try it out in practice, and let me know the result.
希望您喜欢我的文章。 我还想邀请您查看我在本文中使用的库 。 在实践中尝试一下,让我知道结果。
If you want to keep up with what I'm doing or drop me a note, follow me on LinkedIn or Twitter. To learn about agile software development, visit my online course.Last edited April 27, 2020: updated event sourcing process
如果您想跟上我的工作进度或给我留言 ,请在LinkedIn或Twitter上关注我。 要了解敏捷软件开发的知识,请访问我的在线课程 。 上次编辑时间:2020年4月27日:更新了事件来源流程
翻译自: https://www.freecodecamp.org/news/kissing-the-state-machine-goodbye/
jojo亲吻替身