Add abstract transactional BulkOperationProcessor
I've edited this issue's description to include my analysis -- @joelalejandro
In order to implement /bulk operations, a new key { operations: Operation[] } must be defined in JsonApiDocument.
⚠️ operations cannot co-exist with data or errors.
An error should be triggered if this condition isn't met.
A bulk operation must be treated as a transaction:
T = { O1, O2, ..., On } => R
If any given Ox fails, all of T must be reverted:
T = O1 ==> O2 ==> O3 =/=> O4 = R |___________________________|
Rollbacks are relatively trivial for processors derived of a KnexProcessor, where the database engine takes care of the transaction with little concern from JSONAPI-TS.
Implementing non-DB transactions (for later iterations)
However, for more generic processors, since we do not know what an operation could entail, we can't automatically figure out how to revert it.
We define RB(O) as a function around an operation O that can revert whatever impact (if any) ocurred from executing O.
RB(O) should only be necessary for add, update and remove, since get-ops should not have any side-effects.
At a semantic level, rollbacks would act as follows:
- RB(add) = remove
- RB(update) = update(Sn - 1), where
Sis a list of states for a given resource - RB(remove) = add
This could be auto-mapped and executed, but if an opertion has more complex side effects, the user should be able to provide definitions of how a rollback should proceed on such operations:
- RB(add) = unadd = remove
- RB(update) = unupdate = update
- RB(remove) = unremove = add
A BulkOperationProcessor class should imply the following responsabilities:
- Maintain a list of every Operation (request) and every result (response). This is called a Transaction.
- Redirect execution of every Operation to the appropriate ResourceProcessor.
- Define a rollback operation for every write-like operation as described before.
- Extend from OperationProcessor with a generic resource class, since a bulk request can involve multiple operation types.
(1) is currently handled by the Application object. Maybe it's something that the BulkOperationProcessor has access to?
// Access current transaction
app.transaction.push(...);
This would require the existence of a Transaction class.
Currently, a Transaction is created by the app.createTransaction() function, which simply wraps the execution of operations in a Promise.all call.
A public API for a transaction must include:
- A method to set which operation should be included in the transaction
- An "execution" method that will attempt to run the operations; if it fails, it should delegate to a "rollback execution" method.
It should also hold a state array, defined as:
type State = {
operation: Operation;
success: boolean;
error?: JsonApiError;
result?: OperationResponse
}