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:

// blocking method - current thread is blocked while waiting
// for the server's response. IMPORTANT: blocking methods
// cannot be invoked on the main UI thread in Android
UnitOfWorkResult result = unitOfWorkInstance.execute();

// non-blocking method - current thread does not wait.
// Result is delivered to the callback object
unitOfWork.execute( new AsyncCallback<UnitOfWorkResult>()
{
  @Override
  public void handleResponse( UnitOfWorkResult response )
  {
    // Transaction completed. Check the response object if it is a success.
  }

  @Override
  public void handleFault( BackendlessFault fault )
  {
    // 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.
UnitOfWork uow = new UnitOfWork();

// Create a person object
HashMap<String, Object> person = new HashMap<>();
person.put( "name", "Joe" );
person.put( "age", 20 );

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

// 
// 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
public boolean isSuccess();

// 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
public TransactionOperationError getError();

// returns a map between operation IDs and the result object containing
// the intermediary results of the corresp?nding operation
public Map<String, OperationResult> getResults()

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
public OperationType getType();

// returns the result of the operation. The object type is determined
// by what the operation returned.
public Object getResult();

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

public enum OperationType {
  CREATE("create"),
  CREATE_BULK("createBulk"),
  UPDATE("update"),
  UPDATE_BULK("updateBulk"),
  DELETE("delete"),
  DELETE_BULK("deleteBulk"),
  FIND("find"),
  ADD_RELATION("addToRelation"),
  SET_RELATION("setRelation"),
  DELETE_RELATION("deleteRelation");

  public String getOperationName();
}

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.