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 only in the non-blocking manner. The method 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:

UnitOfWork *unitOfWork = [UnitOfWork new];

// non-blocking method - current thread does not wait.
// Result is delivered to the callback object
[unitOfWork executeWithResponseHandler:^(UnitOfWorkResult *response) {
    // Transaction completed. Check the response object if it is a success.
} errorHandler:^(Fault *fault) {
    // Server reported an error.
}];
let unitOfWork = UnitOfWork()

// non-blocking method - current thread does not wait.
// Result is delivered to the callback object
unitOfWork.execute(responseHandler: { (response: UnitOfWorkResult) in
    // Transaction completed. Check the response object if it is a success.
}, errorHandler: { fault in
    // 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 retrieving the opResultId property of 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 = [UnitOfWork new];

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

// add a "create" operation to the transaction
// notice the result object will be reused later on
OpResult *createPersonResult = [uow createWithTableName:@"Person" objectToSave:person];
NSString *opResultId = createPersonResult.opResultId;

//
// skipped for brevity
//
// create a UnitOfWork instance - it represents a transaction and
// will accumulate all operations in it.
let uow = UnitOfWork()

// Create a person object
let person = ["name": "Joe", "age": 20] as [String : Any]

// add a "create" operation to the transaction
// notice the result object will be reused later on
let createPersonResult = uow.create(tableName: "Person", objectToSave: person)
let 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 properties 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
@property (nonatomic) BOOL 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
@property (nonatomic, strong) TransactionOperationError * _Nullable error;

// returns a map between operation IDs and the result object containing
// the intermediary results of the corresponding operation
@property (nonatomic, copy) NSDictionary<NSString *, OperationResult *> * _Nullable results;
// returns true if the transaction has completed successfully
public var isSuccess: Bool

// 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 var error: TransactionOperationError?

// returns a map between operation IDs and the result object containing
// the intermediary results of the corresponding operation
public var results: [String : OperationResult]?

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 retrieving the isSuccess property. 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 retrieving the results property. The property is 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
@property (nonatomic) enum OperationType operationType;

// returns the result of the operation. The object type is determined
// by what the operation returned.
@property (nonatomic) id _Nullable result;
// return the type of the of the operation using the OperationType
// enumeration
public var operationType: OperationType?

// returns the result of the operation. The object type is determined
// by what the operation returned.
public var result: Any?

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

typedef SWIFT_ENUM(NSInteger, OperationType, closed) {
  OperationTypeCREATE = 0,
  OperationTypeCREATE_BULK = 1,
  OperationTypeUPDATE = 2,
  OperationTypeUPDATE_BULK = 3,
  OperationTypeDELETE = 4,
  OperationTypeDELETE_BULK = 5,
  OperationTypeFIND = 6,
  OperationTypeADD_RELATION = 7,
  OperationTypeSET_RELATION = 8,
  OperationTypeDELETE_RELATION = 9,
};
@objc public enum OperationType: Int {
    case CREATE = 0
    case CREATE_BULK
    case UPDATE
    case UPDATE_BULK
    case DELETE
    case DELETE_BULK
    case FIND
    case ADD_RELATION
    case SET_RELATION
    case DELETE_RELATION

    public func getOperationName() -> String?
}

The result property 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 result property for that operation will return the created object.