diff --git a/documentation/src/main/paradox/2pc.md b/documentation/src/main/paradox/2pc.md index 6cf192f..2e1d3dd 100644 --- a/documentation/src/main/paradox/2pc.md +++ b/documentation/src/main/paradox/2pc.md @@ -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 @@ -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). diff --git a/documentation/src/main/paradox/diagrams/TransactionEntity.png b/documentation/src/main/paradox/diagrams/TransactionEntity.png index 35efc67..458f645 100644 Binary files a/documentation/src/main/paradox/diagrams/TransactionEntity.png and b/documentation/src/main/paradox/diagrams/TransactionEntity.png differ diff --git a/documentation/src/main/paradox/diagrams/TransactionEntity.puml b/documentation/src/main/paradox/diagrams/TransactionEntity.puml index 1eaa5fc..88570c6 100644 --- a/documentation/src/main/paradox/diagrams/TransactionEntity.puml +++ b/documentation/src/main/paradox/diagrams/TransactionEntity.puml @@ -1,5 +1,4 @@ @startuml -skinparam handwritten true skinparam defaultFontName Chalkboard title Transaction entity state diagram @@ -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 \ No newline at end of file diff --git a/documentation/src/main/paradox/nutshell.md b/documentation/src/main/paradox/nutshell.md index 6ebeefd..3ed26df 100644 --- a/documentation/src/main/paradox/nutshell.md +++ b/documentation/src/main/paradox/nutshell.md @@ -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. \ No newline at end of file +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) +@@@ \ No newline at end of file