Syrve POS API SDK

Data Editing

By data editing, we mean such actions as taking orders, handling order items, making table reservations, registering guests, and so on. Each individual action makes a small pinpoint change, for example, AddOrderItemProduct adds an item to the order, whereas, AddOrderItemModifier adds a modifier to the item. Many individual actions can result in data inconsistency, for example, if an item has required modifiers, then if added without modifiers, this would break a corresponding subject matter rule. Users can add an item to the order with modifiers combining actions. In which case, it is important that actions would take place according to the «All or Nothing» rule and would convert the data from one consistent state to another consistent state. To ensure transactionality when editing the data, we introduce the concept of the editing session.

Editing Session

Editing session is a kind of database transactions. All actions, even individual ones, are carried out within sessions the following way:

  1. The IEditSession is started by calling PluginContext.Operations.CreateEditSession.
  2. One or more actions are carried out within the session.
  3. Made changes are saved using the PluginContext.Operations.SubmitChanges method.

Changes made in step two cannot be seen until saved. In step three, all changes are whether stored successfully or rolled back, and an exception is generated.

Syncing

Access to data is synced using interlocks and revisions. Objects are locked during the editing and it is impossible to control such locks directly via the API as objects are locked automatically when the changes are saved (SubmitChanges). This matches the concept of optimistic locks: when in steps of creating the editing session and performing actions, objects are not locked, but when the changes are being saved, a check takes place verifying whether or not anything is changed by any other user. When saved, objects have a new revision assigned, which makes it possible to tell different versions of the object from one another. With account of low demand for the concurrent editing of the same objects, this approach makes the UI simpler (no locking management methods are required, no need to think of their proper release) and ensures the data availability (an object cannot be locked for long, locks cannot be left unreleased, in case of the emergency plugin shutdown, the object will not freeze in locked condition).

Certain programming specifics should be kept in mind:

Continuous Execution

IOperationService operations that require syncing for the data editing. They lock and then unlock objects at each call. Therefore, when several operations are invoked successively, each operation will lock data independently, make changes, and then unlock it. Other parties may wedge in between the operation calls with the intent to edit the same data; as a result, some operations are performed successfully and some return errors EntityAlreadyInUseException, EntityModifiedException, other operations may become inapplicable with reference to other changes.

For example, a delivery order with a posted external prepayment needs to be created in the plugin that accepts external delivery orders (website, aggregator). All this cannot be performed atomically within one editing session as the payment submission is irreversible and is performed separately, therefore, you need to create a delivery order first and fill up its fields, adding unposted external prepayment (CreateEditSession, CreateDeliveryOrder, AddExternalPaymentItem, etc.), save the changes (SubmitChanges), and then try to submit the prepayment (ProcessPrepay). The data remains unlocked between these operations (SubmitChanges and ProcessPrepay) and is available for editing to anybody. Those who subscribed to delivery changes can be notified of the new delivery order just in time to make changes before the prepayment is posted. Writing a code that can be interrupted and then keep running smoothly adapting to external changes is a quite time-consuming process. To make such scenarios work smoothly, we added the ability to perform several operations uninterruptedly or continuously.

ExecuteContinuousOperation — a special operation that can be used to successively and continuously execute several other operations. Operations should be grouped into one function or lambda in the plugin code and sent as the callback to the ExecuteContinuousOperation method that would invoke this callback and send a special instance of the IOperationServiceservice to the callback input, which intended for the continuous operation execution:

PluginContext.Operations.ExecuteContinuousOperation(
    operations =>
    {
        ...
        operations.SubmitChanges(...);
        ...
        operations.ProcessPrepay(...);
        ... 
    });

It should be noted that the ExecuteContinuousOperation root operation is invoked via the PluginContext.Operations general service, whereas included operations — via the service instance received by lambda at the input (which is called operations in the example above). Technically speaking, operations invoked through a special service instance work the same way but do not unlock data once they are executed, which means each operation that requires syncing locks the data if it hasn’t been locked previously by other operations, makes changes, and leaves the data locked for the next operations. This ensures that no other entity can «steal» the lock and wedge in, and our successive operations with the same objects would not get the EntityAlreadyInUseException. The data will be unlocked when the control is returned from lambda.

The following limitations should be considered:

Stubs

Since no action results are available before saving the entire session, it is required to refer to a new but not yet existing object while executing an operational sequence within one session. For example, users may want to add guests to the order, then add items to a guest, and modifiers to an item even though there is no order yet, no guests, no items. For this reason, we introduce a concept of stubs — certain fake, yet unambiguous, object references. Object creation actions, such as CreateOrder or AddOrderGuest return INew...Stub stubs that can be used instead of other objects within the same session.

Most editing methods take such stubs as arguments, which makes it possible to send both existing and new objects to these methods. For example, the SetOrderType method takes IOrderStub, therefore, a type can be set to both an existing order (IOrder : IOrderStub) and a new order (INewOrderStub : IOrderStub).

However, some actions can require strictly one of the two — new or existing object, in which case, one of the heirs (rather than the base type) will be used in the method signature.

Expected Exceptions

When saving changes various exceptions may occur. Some of them may indicate the plugin code error (for example, ArgumentNullException or ArgumentOutOfRangeException); we do not recommend suppressing such exceptions (it is preferable to fix the code error). However, some exceptions can be neither predicted nor prevented — they should rather be intercepted and processed properly:

Syntactic Sugar

It so sometimes happens that only one action is required, in which case, creating the editing session seems cumbersome. For this reason, the IOperationService has auxiliary extension methods programmed that create the editing session, perform the action required, save changes, and return the action result. Technically, the same could be written manually. We do not recommend using such wrappings if more than one action should be concurrently performed.