Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Minor documentation improvements #24

Merged
merged 5 commits into from
Mar 7, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 18 additions & 15 deletions documentation/src/main/paradox/2pc.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,19 +21,20 @@ Like for *prepare* above, this expression can involve any kind of asynchronous p

Some examples of commit operations in the same imaginary touristic journey booking process:

| Example: orchestration of the booking process for a journey | Data store | Transaction branch operation |
|---------------------------------------------------------------|--------------------|--------------------------------------------------------|
| Do nothing: reservations were already made | External API | Send a message or call and endpoint of another service |
| Do nothing: the guarantee charge was already made | Internal service | Send a message to another service |
| Update customer details in the internal database | Database | Perform the row update and unlock |
| Update the "recent bookings" cache and release the semaphore | In-memory resource | Edit and unlock an in-memory resource |
| Do nothing: the reminder notifications were already scheduled | Actor cluster | Send a command to an actor |
| Do nothing: the bookings log was already updated | File | Persist a change in a file |

@@@ note { title="Consistency" }
It's up to the implementer to decide the level of consistency in the execution of the commit. Transaction failure is also a valid state and can be signaled by raising an exception in the target effect. This will lead to some degree of inconsistency in the overall system state, but that can be an acceptable compromise in some use cases.

For instance, in our imaginary example the "in-memory cache" could be locked only for a short time to preserve throughput. Because it's not essential to the journey process, updating it could be skipped in case of delays. On the other hand, if the "internal database" update would still fail despite the lock, this could be signaled by raising an exception. This would conclude the transaction in a failed state, which could be surfaced in the UI to allow for manual remediation.
| Example: orchestration of the booking process for a journey | Data store | Transaction branch operation |
|---------------------------------------------------------------|--------------------|---------------------------------------|
| Do nothing: reservations were already made | External API | - |
| Do nothing: the guarantee charge was already made | Internal service | - |
| Update customer details in the internal database | Database | Perform the row update and unlock |
| Update the "recent bookings" cache and release the semaphore | In-memory resource | Edit and unlock an in-memory resource |
| Do nothing: the reminder notifications were already scheduled | Actor cluster | - |
| Do nothing: the bookings log was already updated | File | - |

@@@ note { title="Commit consistency" }
It's up to the implementer to decide the level of consistency in the execution of the commit. Transaction failure is also valid and can be signaled by raising an exception in the target effect. Failure will lead to inconsistency in the overall system state, which can be an acceptable
compromise in some use cases.

For instance, in our imaginary example, the "in-memory cache" could be locked only briefly to preserve throughput. Because it's optional to the journey process, updating it could be skipped in case of delays. On the other hand, if the "internal database" update still fails despite the lock, the commit expression could signal this by raising an exception. The exception would conclude the transaction in a failed state, allowing surfacing in the UI for manual remediation.
@@@

## Abort
Expand All @@ -51,11 +52,13 @@ Some examples of abort operations in the same imaginary process:
| Cancel the reminder notifications | Actor cluster | Send a command to an actor |
| Roll back the booking log entry | File | Roll back the change in a file |

The same flexibility applies here as for the commit operation: it's up to the implementer to decide the level of consistency in the execution of the abort. In this dummy example, let's suppose traveler reminders have already been sent, a compensation action could be to schedule a new notification inviting the customer to ignore previous messages. On the other hand, failing to cancel the hotel and flight reservations would be a more serious issue and should be surfaced as a failed transaction, to elicit manual remediation.
@@@ note { title="Abort consistency" }
The same flexibility applies here as for the commit operation: it's up to the implementer to decide the level of consistency in the execution of the abort. In this dummy example, let's suppose traveler reminders have already been sent: a compensation action could be to schedule a new notification inviting the customer to ignore previous messages. On the other hand, failing to cancel the hotel and flight reservations would be a more serious issue and should be considered a failed transaction to elicit manual remediation.
@@@

## State diagram

Protocol state throughout the aforementioned phases is tracked by an event-sourced entity, with events representing transitions of the state. The transaction state machine diagram is depicted below. As usual, side-effects are invoked after successful event persistence, and repeated in case of recovery (at least once delivery characteristics).
Protocol state throughout the phases mentioned above is tracked by an event-sourced entity, with events representing state transitions. The transaction state machine diagram is depicted below. As usual, side-effects are invoked after successful event persistence and repeated in case of recovery (at least once delivery characteristics).

<img src="diagrams/TransactionEntity.png"/>

Expand Down
Binary file modified documentation/src/main/paradox/diagrams/TransactionEntity.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
@startuml
skinparam handwritten true
skinparam defaultFontName Chalkboard

title Transaction entity state diagram
Expand All @@ -12,6 +11,12 @@ Committing --> Committed : all branches confirm commit
Aborting --> Aborted : all branches confirm abort

note right of Preparing: side-effect: call prepare() on branches
note left of Committing: side-effect: call commit() on branches
note bottom of Committing: side-effect: call commit() on branches
note right of Aborting: side-effect: call abort() on branches

state Preparing #LightBlue
state Committing #LightGreen
state Aborting #LightCoral
state Committed #Green
state Aborted #Coral
@enduml
6 changes: 5 additions & 1 deletion documentation/src/main/paradox/nutshell.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,8 @@ The three tenants of the 2PC protocol are *prepare*, *commit*, and *abort*: in e

Diverse forms of two-phase consensus appear frequently in service-oriented systems, especially when involving complex business workflows and heterogeneous data stores. Its usefulness goes beyond the specific area of strongly consistent, atomic transactions such as [XA](https://en.wikipedia.org/wiki/X/Open_XA). [Long-running transactions](https://en.wikipedia.org/wiki/Long-running_transaction), also known as sagas, typically make use of rollback or undo operations in the abort phase, also called [compensating transactions](https://en.wikipedia.org/wiki/Compensating_transaction).

Abstractions in the library are implemented by one of the two available runtimes, Pekko or Akka. Internally, transactions are materialized with a persistent sharded entity implementing the two-phase protocol asynchronously with at least once delivery guarantee.
Abstractions in the library are implemented by one of the two available runtimes, Pekko or Akka. Internally, transactions are materialized with a persistent sharded entity implementing the two-phase protocol asynchronously with at least once delivery guarantee.

@@@ note { .tip title="For more info" }
Check out the blog article [Two-phase consensus with functional Scala](https://jonas-chapuis.medium.com/two-phase-consensus-with-functional-scala-5dc29388ac5a)
@@@
Loading