Skip to content

Unit of Work API

The heart of the Backendless Transactions API is a class called UnitOfWork. It used by the client applications to compose a transaction. Composing a transaction means adding various database operations to a unit of work. These operations may be "linked" between each other by using the output/result on one operation as the input for another. When a transaction is composed, the client application uses the execute() method on the UnitOfWork instance to dispatch the transaction so that the server can run all the operations. The execute() method can be invoked either in the blocking or the non-blocking manner. The blocking execute() will block the execution of the client  program until the entire transaction is complete or errors out and then returns the result of the transaction. The non-blocking execute() does not block the client program and the transaction result or its error is delivered to a callback.

When the client application calls the execute() method, the information about all operations you added to UnitOfWork is sent to the Backendless server. The server executes all operations one-by-one in the context of a single database transaction. If any of the operations fails, Backendless rolls back all the changes and returns the information about the result of each operation to the client. However, if all operations succeeded, the entire database transaction is committed. This means the data will be finalized in the database.

Running a Transaction

Use the following methods to initiate the transaction on the server:

// sync call - current thread is blocked while waiting
// for the server's response.
UnitOfWorkResult result = await unitOfWorkInstance.execute();

// async call - current thread does not wait.
// Result is delivered to the callback object
unitOfWorkInstance.execute().then((result) {
 // Transaction completed. Check the response object if it is a success.
}).catchError((error) {
 // Server reported an error.
});

Codeless Reference

You can run transactions using the following Codeless block:

data_service_is_transaction_successful_4

where:

Argument                Description
transaction Expects an object containing transaction details, this includes: operation type and data.
return result When this box is checked, the operation is set to return an object containing the operation result.
throw error Check this box to return an error in case if the operation cannot be executed.

Find the example in the OpResult Id section below to learn more about how this block is used in the Codeless logic.

Operations

UnitOfWork supports the following database operations. Each operation is described in detail in the sections of this guide that follow.

OpResult Id

Unit of Work assigns a unique Id to every operation added to a transaction. These Id's are referred to as OpResult Id's and are important for several reasons: when a transaction is complete and you get the result, you can see the intermediate results for each operation. This is particularly helpful when a transaction fails, in this case OpResult Id's then can be used to see which operation resulted in the overall failure. Additionally, OpResult Id's are used internally when you pass the output of one operation as the input for another one. You can obtain the OpResult Id value assigned to an operation by using the getOpResultId() method on the OpResult object returned from every operation. By default Backendless uses the following scheme to generate operation Id values:

operation name + table where operation is performed + sequential number

For example, suppose your code adds the following operation to a transaction:

// create a UnitOfWork instance - it represents a transaction and
// will accumulate all operations in it.
final uow = UnitOfWork();

// Create a person object
final person = {
 "name": "Joe",
 "age": 20
};

// add a "create" operation to the transaction
// notice the result object will be reused later on
final createPersonResult = uow.create(person, "Person");
final opResultID = createPersonResult.opResultId;

//
// skipped for brevity
//

the opResultId variable in the code above will have a value of createPerson1. For additional details about OpResult and how to assign a custom OpResult Id, see the Operation Result chapter of this guide.

Codeless Reference

The example below saves a new object to the Person data table using the "Create" Operation Codeless block. After the Codeless logic runs, the operation assigns the operation reference to the opResultId variable. Refer to the Saving a single object topic to learn more about the "Create" Operation Codeless block.

data_service_is_transaction_successful_3

Transaction Result

As you can see from the API shown above, the execute method returns an instance of the UnitOfWorkResult object. It can be used to determine if the transaction has completed successfully, or resulted in an error. It also provides access to the information about the status of each operation the transaction consists of. See the API below. All listed methods are available in the UnitOfWorkResult class - your code receives that object as a result of your transaction:

// returns true if the transaction has completed successfully
bool success;


// returns an object describing the error. Information available 
// through the TransactionOperationError object includes the
// error message as well as a reference to the operation which 
// caused the error
TransactionOperationError error;


// returns a map between operation IDs and the result object containing
// the intermediary results of the corresponding operation
Map<String, OperationResult> results;

When your code receives UnitOfWorkResult from the transaction execution, it is important to check if the transaction has completed successfully. You can do this by checking the return value of the isSuccess() method. If the value is true, the transaction has succeeded - all operations completed without any errors.

You can inspect the results of each individual operation by using the getResults() method. The method returns a map between OpResult ID (see the section above) and the result from the corresponding operation using the OperationResult class. The OperationResult class has the following API:

// return the type of the of the operation using the OperationType
// enumeration
OperationType operationType;

// returns the result of the operation. The object type is determined
// by what the operation returned.
dynamic result;

The OperationType is an enumeration defined as shown below. Every value in the enumeration corresponds to an operation.

enum OperationType {
 CREATE,
 CREATE_BULK,
 UPDATE,
 UPDATE_BULK,
 DELETE,
 DELETE_BULK,
 FIND,
 ADD_RELATION,
 SET_RELATION,
 DELETE_RELATION
}

The getResult() method in the OperationResult class returns the actual object which the corresponding database operation returned. For example, if the database operation creates an object in the database, the getResult() method for that operation will return the created object.

Codeless Reference

data_service_is_transaction_successful_1

Returns a boolean value indicating the transaction result.

Consider the structure of the data table called Person:

data_service_transactions_bulk_save_4

The following example creates and runs a transaction containing a "Find" Operation and returns true, indicating that the transaction is successful. Note that this block must be used after the Run Transaction Codeless block as shown below:

data_service_is_transaction_successful_2