Projects:EHub/Technical Specification
Contents |
Introduction
This document gives a technical specification and design of the Openbravo eHUB solution.
The eHUB solution will offer two distinct functionalities:
- implements the channel concept, allowing different channels to interact with the central Openbravo OMS in different ways
- offers an api to retrieve and update/create retail master data and transactions in Openbravo. The channel concept also plays a key role in this api defining access and visibility.
Questions:
- query api: discount and taxes really needed? user api?
- query for user info?
- stock update api needed?
- stock as push api, needed?
- store/pos terminal structure to be accessible through an api?
Channel Concept
The channel concept in Openbravo corresponds to a sales channel by which products are being sold to retail customers. A channel can be an e-commerce site or franchise partners, but also an Openbravo WebPOS in an owned store or a kiosk.
The retail channel is an important concept and is therefore implemented explicitly in Openbravo using a separate table.
A POS Terminal will be linked to a channel. A channel can have multiple POS terminals, a POS Terminal belongs/works for one channel.
From a functional perspective a channel defines a specific sales area, from a technical perspective a channel defines implementation logic for transaction processing and query apis. This document focuses on the technical definition of a channel.
A channel implementation can also be used to cover the same functionality as currently is provided by connectors such as Magento. So the goal of the eHUB/channel development is to also cover and provide a generic framework (and for some cases an implementation) for connecting to E-Commerce and other third party systems.
Channel Columns/Fields
A channel has properties and sub-entities.
A channel will have the following properties:
- name
- description
- type
Channel Sub Entities
For each channel it will be possible to override/set implementations and logic for specific services/hooks. These are defined in sub entities. The underlying technical concepts are discussed in more detail further down in this document.
As a technical detail, the service/hook/push definitions also support a json object as parameters. When saving a service/hook/push definition Openbravo should validate that the parameters are indeed valid json to prevent simple typos.
Channel Service
The channel api/functionality is implemented using a pre-defined set of services. A channel can override the service implementations. If a channel service is not defined explicitly then the default implementation will be used.
A channel service has the following columns:
- Type of service: predefined list of services supported by the eHUB, see further down in this document for details.
- Service Implementation Class: java class name which implements the service, must extend/implement a standard service class (alternatively a service can be modeled as a separate entity provided by modules)
- Do asynchronous execution: default is no, if yes then the response will be a standard response and the service request is executed in the background
- Log request/response: default is yes, makes most sense for transactional services
- Service Parameters: a json string which is passed to the service to configure it
Channel Hooks
A channel hook is a sub entity of a channel, for each channel one or more hooks can be defined with their implementing class. The supported/envisioned hooks are defined further down. The channel hook table will have the following columns:
- Type of hook (see below for a proposal of the hooks)
- Hook Implementation
- Hook parameters: a json object which is passed to the hook to configure it
Channel Push Definition
A channel can pull information by calling a service directly. This is a common approach. A channel can also request to get information pushed.
A push definition has the following fields/columns:
- Type of data: we will only explicitly support push mode for specific entities, see further below for an overview of the entities supported.
- Push Class Name: class which can be called by the push framework to push the data
- Push frequency in minutes
- Last Push timestamp: readonly timestamp is set when data is pushed to the channel.
- Push where clause: additional where clause to use when pushing to this channel
- Parameters: a json object which is passed to the hook to configure it
Channel Access
Channel access (who can call the underlying apis for the channel) is defined by user or role. The proposal is to have one channel access entity allowing either role or user access definition.
The channel access entity has the following properties:
- Role (non-mandatory)
- User (non-mandatory)
- Access: Read-Only/Write
Either role or user must be set in this case.
Channel Technical Aspects
Channel Requests - eHUB solution layering
All actions/updates in the eHUB are implemented as request-response actions. The following is assumed:
- a request is executed by a single thread
- the thread is executed for an authenticated user, having the OBContext available
- each thread knows for which pos terminal (or other identified end-point) it is executing, an end-point is linked exclusively to a channel. The end-point can be passed as a parameter in the request or in the json.
- request and response data format is json
- the channel is stored in a ThreadLocal during a request, so within the code it is always possible to see for which channel code is being executed
The solution should have 2 layers:
- the web layer actually receiving the http requests and returning the response. This layer also takes care of authentication.
- the execution layer, the api of this layer is more abstract and should be callable also directly without creating a http request/response. All the services provided by the eHUB should implement/extend the same default class.
public class ChannelService { public JSONObject execute(JSONObject json); }
Following a two-layered approach makes it much easier to directly call eHUB functionality from backend code and to implement test cases.
The channel framework takes care that after the execute call the database transaction is automatically committed and several standard thread based resources of Openbravo are cleaned up. Similar to the current RequestFilter logic.
Channel Hooks
A channel hook allows specific code to be executed for each channel. So the exact implementation is defined in the channel. The proposal is to integrate the current server side hooks with new channel hooks. This means that the current server side hook concept need to be extended to support different implementations depending on the channel.
The main purpose of hooks is to be able to override/change the default logic from within the main loops/logic. This allows overriding/customizing without re-implementing a service completely. Some generic hooks make sense as they can be used to implement generic behavior with
The api of the hooks will be defined in more detail later. When defining the api the following approach should be followed:
- pre-hooks: should be allowed to set/change the json which is passed in, are called before the rest of the service.
- on-hooks: receive the business object, is called just before the business object is processed. This can be specific/points depending on the type of object and process.
- post-hooks: receive the business object after it is saved, can be used for further processing, lower level post hooks can only change the returned json.
A distinction can be made between technical and functionally oriented hooks. Both are described here.
Request Hooks
Request hooks are generic hooks which are executed for each request/response. The following request hooks can be identified:
- on-request: generic hook is executed before the json is send further to be processes by the actual service, allows the channel to change the json
- on-response: is executed after the service has finished
Generic hooks should receive the service instance for which they are executed.
An example hook api:
public class RequestHook { public JSONObject onRequest(ChannelService service, JSONObject requestJson); public JSONObject onResponse(ChannelService service, JSONObject requestJson); }
Functional Transactional Hooks
Functional transactional hooks are executed before-during and after transaction processing.
The following entities are covered by the functional transaction hooks:
- Customer
- Customer Address
- Ticket
Two other transaction entities (cashup and cash management) are not covered initially because they are less relevant for eHUB type of integrations.
The current server-side hooks will be recorded/integrated into the channel eHUB integration framework.
- Customer:
- Before-Save: is called with the json before the customer entity is created.
- On-Save: is called before the customer is saved to the database
- After-Save: is called after saving the customer to the database but before the commit happend
- After-Complete: is called after the transaction is committed
public class TransactionHook <T extends BaseOBObject> { public JSONObject beforeSave(JSONObject requestJson); public void onSave(T businessObject, JSONObject requestJson); public void afterSave(T businessObject, JSONObject requestJson); public void afterComplete(T businessObject, JSONObject requestJson); }
- Customer-Address: same hooks as for customer
- Ticket: same hooks as for customer, but additionally:
public class OrderHook { public void beforeCreateShipment(Order order, JSONOrder json); public void afterCreateShipment(Order order, ShipmentInOut shipment); public void beforeCreatePayment(Order order, JSONObject json); public void afterCreatePayment(Order order, FIN_PaymentSchedule paymentSchedule); public void beforeCreateInvoice(Order order, JSONObject json); public void afterCreateInvoice(Order order, Invoice invoice); }
Query Hooks
Query hooks are executed before and after reading data from the backend system. They are directly tied to the entity which is being read and filtered. See the query services for more information.
The following generic query hooks can be identified:
- before-query: is called before the query is executed, it can be used to transform the inputted-json to a format which can be handled by the query hook.
- after-query: is called after the query result has been transformed to json, can be used by the hook to change the response or add information.
The before-query hook will have this signature:
public class BeforeQueryHook { public JSONObject transformQueryRequest(JSONObject json); } public class AfterQueryHook { public JSONObject transformQueryResponse(JSONObject json); }
Query hooks are implemented for the following entities:
- Product
- Product Price
- Product Tax
- Discounts
- Customer
- Customer Address
- Tickets (incl shipment, payments)
- Store information
- Channel information
- POS Terminals
See the query service implementation for more details on data querying.
Note: hooks are called by a service, if a channel overrides a service it is possible that specific hooks are not called by the custom implementation.
Channel Request - Response Logging
A special table will be introduced to log and track the request and response for each channel service. Next to the response the system will also use this table to log exceptions/errors. The import entry concept (which has an archiving function) can be re-used for this.
Channel Execution Context
The server side code continuously should be able to detect for which channel the logic is executed. The channel is determined by the pos terminal which needs to be passed in as a parameter. The main service/request handler/thread should determine the pos terminal and set it from the start in a thread local.
Using this thread local, there should be an api which can be called by the execution code to always obtain the current channel/posterminal:
public class ChannelManager { public Channel getCurrentChannel(); public OBPOSApplications getCurrentPOSTerminal(); }
Channel Request - Asynchronous handling
For high volume requests it makes sense to process the requests asynchronously. Asynchronous processing of channel requests (mainly makes sense for transactions) can be defined when specifying a custom implementation for a service. The asynchronous function can be implemented using the import entry framework.
Supporting push, data queries/updates
The proposal is to support both push and pull mode for channel operations. Pull mode is quite straightforward, the client does a request and receives a response. Push mode implementation is less generic, therefore the push class is defined in the channel explicitly.
For each channel a push subscription can be defined (see the push channel sub entity defined above).
The generic framework will do the following:
- run a periodic process
- for each subscription check if there are data records changed/updated since the last run
- if so create a push record in a new table, this table will keep track of records by entity/id and channel.
- a separate process will read the push records and call the appropriate push class implementation
- the record is removed from this table if it has been pushed (or alternatively we have an archive table and move the record there)
Potentially the import entry framework can be used for this.
Push mode will be supported for the following entities:
- product
- customer and customer address
- ticket
The push service is implemented for each channel. It has the following signature:
public class PushService { public boolean pushData(PushEntry pushDataEntry); }
Some notes:
- the push will only signal that something has changed it will not provide details on what data has changed.
- deletions are not pushed.
Channel Master Data Services
The proposal is to add a generic query service which can query for all kinds of master data using a simple criteria api. The main characteristic of this api is that it will allow the usage of non-db-ids to refer to searched data.
To focus the eHUB interface the proposal is to provide a generic query api for the following entities:
- Customer and its addresses
- Product and its price, stock and product categories
- Promotions
- Taxes
- Ticket with its related data such as order lines, tax lines,
The query api will expect a json like this:
{ "entity": "Order", "startRow": -1, "endRow": -1, "criteria": [ {"fieldName":"documentNo","operator":"iContains","value":"CMS1"}, {"fieldName":"orderDate","operator":"greaterOrEqual","value":"2014-01-01"}, {"fieldName":"orderDate","operator":"lessOrEqual","value":"2014-12-01"} ] }
Customers & Addresses
The customer (=Business Partner) and addresses information can be accessed through the customer, so the customer query api should return the customer business object with all its address information and other related customer information.
Products
The products api is quite extensive as all the product information is to be returned in one call. Only the products relevant for a pos terminal should be returned (assortment).
The service can be directed by providing the additional information:
- stock by warehouse: if parameter is true then also return the stock by warehouse
- price information: if this parameter is send then also return the price information for the product.
For each product also its product categories should be returned.
Product Categories
Returns the list of product categories which are valid for a pos terminal.
Discounts
The discount api returns a list of current discounts. It is probably to complex for any caller to implement the complete Openbravo discounting engine. But the discount id/names can be retrieved.
Tax
Return the list of taxes and tax rates. There is extensive logic in the backoffice to determine which tax to apply when. This api will only return the base information and will not replicate the complete Openbravo setup.
eHUB Transactional Services
The eHUB will provide transactional services for updating:
- Customer/Addresses
- Tickets
The update of product and related data is not in scope.
Currently Openbravo already offers a customer/address and orderloader. These api's can be re-used mostly, changes are needed to allow references to be send in as codes and not as database ids.
Customer
The customer loader of retail can be re-used for this. It allows to create and update a business partner. The customer loader needs to be wrapped into a ChannelService instance (so that hooks etc are called). That's the main change.
Customer Address
The customer address loader of retail can be re-used for this. It allows to create and update a business partner. The customer addressloader needs to be wrapped into a ChannelService instance (so that hooks etc are called). That's the main change.
Inventory
The inventory api allows updating the inventory of a part in the Openbravo system. This is a new api which has the following signature:
- product
- warehouse
- bin location
- inventory level or inventory change
The same structure as for the other apis can be used, so that multiple records at the same time can be send in.
Ticket
The ticket api has to be extended to distinguish the following steps:
- create a ticket and its lines: it should return the order and line ids
- ship one or more lines: the ship step should be repeatable each time shipping different lines
- pay: the pay step should allow adding payments to a line, it should be possible to call the api with additional payments
- invoice: the invoice step creates an invoice for the ticket
The above steps are present in the current order loading api but it needs to be extended, split and subdivided in 4 different services (create/update, ship, pay, invoice).