mead-steve / tale
Requires
- php: >=7.1, <8.0
- psr/log: ^1.0
Requires (Dev)
- gamez/psr-testlogger: ^3.0
- monolog/monolog: ^1.23
- phpstan/phpstan: ^0.11.5
- phpunit/phpunit: ^7.2
- squizlabs/php_codesniffer: ^3.3
- vimeo/psalm: ^3.2
This package is auto-updated.
Last update: 2024-09-06 02:12:14 UTC
README
是什么?
Tale 是一个小型库,帮助跨多个服务编写“分布式事务类似”的对象。它基于 saga 模式。Couchbase 博客上有一篇很好的介绍:[https://blog.couchbase.com/saga-pattern-implement-business-transactions-using-microservices-part/](https://blog.couchbase.com/saga-pattern-implement-business-transactions-using-microservices-part/)
安装
composer require mead-steve/tale
示例用法
该示例的一个用例是一些假日预订软件,被分解为几个服务。
假设我们有以下服务:航班预订 API、酒店预订 API 和客户 API。
我们将编写以下步骤
class DebitCustomerBalanceStep implements Step { //.. Some constructor logic for initialising the api etc... public function execute(CustomerPurchase $state) { $paymentId = $this->customerApi->debit($state->Amount); return $state->markAsPaid($paymentId); } public function compensate($state): void { $this->customerApi->refundAccountForPayment($state->paymentId) }
class BookFlightStep implements Step { //.. Some constructor logic for initialising the api etc... public function execute(FlightPurchase $state) { $flightsBookingRef = $this->flightApi->buildBooking( $state->Destination, $state->Origin, self::RETURN, $this->airline ); if ($flightsBookingRef=== null) { raise \Exception("Unable to book flights"); } return $state->flightsBooked($flightsBookingRef); } public function compensate($state): void { $this->customerApi->cancelFlights($state->flightsBookingRef) }
对于任何需要的步骤,以此类推。然后在处理用户请求的任何东西中,可以构建一个分布式事务
$transaction = (new Transaction()) ->add(new DebitCustomerBalance($user)) ->add(new BookFlightStep($airlineOfChoice)) ->add(new BookHotelStep()) ->add(new EmailCustomerDetailsOfBookingStep()) $result = $transaction ->run($startingData) ->throwFailures() ->finalState();
如果任何步骤失败,则会以相反的顺序调用每个步骤的补偿方法,直到一切都撤销。
状态不可变性
当前状态从一步传递到下一步。相同的州还用于补偿事务中更远处的失败。由于这种情况,实现者考虑使状态不可变是很重要的。
Tale 提供了一个 CloneableState
接口来帮助实现这一点。任何实现此接口的状态都将在其 cloneState
方法被调用之前传递给步骤,以确保步骤不会共享同一状态。
class FakeState implements CloneableState { public function cloneState() { return clone $this; } } $stepOne = new LambdaStep( function (MyStateExample $state) { $state->mutateTheState = "step one" return $state; } ); $stepTwo = new LambdaStep( function (MyStateExample $state) { $state->mutateTheState = "step two" return $state; } ); $transaction = (new Transaction()) ->add($stepOne) ->add($stepTwo); $startingState = new MyStateExample(); $finalState = $transaction->run($startingState)->finalState();
在上面的示例中,$startingState
、$finalState
和 $state
给出的所有函数调用都是彼此的克隆,所以改变一个不会影响任何早期状态。
测试 / 开发
欢迎贡献。如果更改较大或会破坏向后兼容性,请首先打开一个问题。
所有构建必须在合并之前通过 travis 测试。运行 ./run_tests.sh
将运行与 travis.yml 中相同的测试,但本地运行。
dockerfile 提供了一个可以执行所有测试和静态分析的执行环境。