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.
});

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.

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. Th ze 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.