Whether you design a native mobile app, or a "face-to-face" app that was mentioned earlier in this article (a multi-page Web App without page refreshes), you need a backend application server to provide business support. So, how to design a backend service interface is something that must be considered clearly before development.
Referring to the interface design, we need to consider from two dimensions: protocol (PROTOCOL) and prototype (Prototype), referred to as 2P dimension.
The prototype defines an abstract form of a call. Assuming that you want to do the door delivery business, each "merchant" is an object, named "Store", then a "Query the Merchant List" interface, you can design its prototype as:
Store.query() -> tbl(id, name, dscr)
In the prototype, the invocation named Store.query
, the parameter is empty, the data that returns the table type is represented by a table (described below for this type), each row in the table has a few columns of ID and name, and represents the properties of a merchant object.
Next, you need to design the protocol to implement it. This can be accomplished by the client's request to the server based on the HTTP protocol, using the HTTP GET or POST method, placing the call name at the end of the URL, placing the parameter in the URL parameter (not here), and the server-side return data is described using JSON format. Therefore, the client needs to make an HTTP request like this:
GET /api/Store.query
This is the service interface design of the loop cloud framework. In fact, the somersault cloud framework is the implementation of the DACA architecture, DACA the full name "distributed Access and Control Architecture", in which there is a definition of client-server how to communicate, that is, the design of the above example is further normalized, called the BQP Protocol (Business Query Protocol), DACA also defines how the client calls the service interface Called the client public calling interface, as described later in the callSvr
call.
The design style of the BQP protocol is between restful and RPC, so it is called REST-RPC style. Leonard Richardson and Sam Ruby introduced the term REST-RPC hybrid architecture in their book RESTful Web Services (Chinese translation), which does not use the extra envelope format to wrap the invocation name and parameters like soap or XML-RPC , the data is transmitted directly over HTTP, similar to the REST-style Web service, but it does not manipulate resources using the standard HTTP Put/delete methods, but also stores the invocation name in the URI (in the example Store.query
).
Our main principles for examining protocol design are:
- Clear and understandable
- Easy to implement
- High efficiency in transmission and processing
Compared to these principles, restful style is clear and understandable, but the compatibility of methods such as HTTP Put/delete is not good, regardless of the service side or the client will encounter obstacles in implementation, and the use of RPC-style design, not only a lot of readability, and packet unpacking less efficient. Therefore, from a practical point of view, the design of the wings of the idea that REST-RPC is a better choice at present.
In the BQP protocol, the business interface is divided into function-calling interfaces (such as login
calls) and object-invocation-type interfaces (such as Store.query
calls). The function-calling interface is free to design the prototype, and the object invocation interface is actually a special function call for manipulating the business object with a relatively fixed prototype that the designer can cut or extend. There is not much difference between the two kinds of interfaces in communication protocol and client use, the main difference is that the implementation model of backend service is different.
This article only discusses object invocation-type interfaces. The BQP protocol defines five standard operations for an object: Query list, get details (get), add, update (set), and Delete (Del). The following is a detailed example of a "merchant" (Store) object whose data model is described below:
@Store: id, name, addr, tel, dscr
This represents the Merchant table store, which has the ID, name, and so on fields. Note: The DACA specification suggests that when designing a data model, you should use an ID as the primary key.
The DACA specification requires the client to provide a callSvr
method to invoke the service interface, which is the JS function in the front of the spreading wings, and its prototype is
callSvr(ac, param?, fn?, postParam?, userOptions?) -> XMLHttpRequest或callSvr(ac, fn?, postParam?, userOptions?) -> XMLHttpRequest
ac
It represents the invocation name (action), param
and the postParam
parameters passed by URL and post content, respectively, and if not param
, it can be ignored (that is, the second prototype). fn
for the callback function, the call format fn(data)
, where the parameter data
is the returned JSON object, is described in the return value of the type reference interface prototype.
A parameter with a question mark indicates that it can be defaulted.
The XMLHttpRequest function returns the same object as the $.ajax return value in jquery.
The above call is asynchronous, that is, the function ends immediately after execution, and the data of the server is returned to the callback function fn
. A synchronous call can also be made, as long as the function name is callSvr
changed callSvrSync
to mean that it will not end until the server returns data, and that its return value is no longer a XMLHttpRequest object, but a JSON object returned by the service interface. When we test the interface in the Chrome console window, common synchronous calls are used to make it easier to see the results.
As long as you are familiar with these client interfaces, you can call any interface based on the interface prototype in the design document, without having to have a deep understanding of the BQP underlying protocol details.
adding objects
The prototype of an object addition operation defined in the BQP protocol is as follows:
{object}.add()(POST fields...) -> id
In general, the parameter section in the prototype definition uses only one parenthesis, indicating that the parameter is passed through the URL or post content. With two parentheses, the URL parameter and the post parameter are not mixed, and two brackets in turn represent the URL parameter and the post parameter.
In this way, we add a merchant that can be used:
var postParam = {name: "华莹小吃", addr: "银科路88号", tel: "13712345678"};callSvr("Store.add", api_StoreAdd, postParam);function api_StoreAdd(data) { // 根据原型定义中的返回值,data是id值。 alert("id=" + data);}
Because there is no URL parameter, callSvr
the second argument can be omitted. If you want to write complete, it will look like this:
callSvr("Store.add", null, api_StoreAdd, postParam);
If the call succeeds, the specified callback function is invoked, and if the call fails, the front-end framework takes over error handling, which the caller generally does not care about.
Updating objects
The prototypes are:
{object}.set(id)(POST fields...)
The return value is not specified, indicating that there is no specific return value when the call succeeds. The back end of the wings will return the string "OK".
If you want to update id=8
this merchant's contact number:
var param = {id: 8};var postParam = {tel: "13812345678"};callSvr("Store.set", param, api_StoreSet, postParam);function api_StoreSet(data){ alert("更新成功");}
Note: The field to be updated must be placed in the post parameter.
Place a field blank
In the BQP protocol, setting a field to an empty string is generally ignored by the server, but in a set operation, if a field is set to an empty string (or a specific string "null"
) in Postparam, the field is emptied.
To clear the address of a merchant:
var postParam = {addr: ""};// 或者 var postParam = {addr: "null"};callSvr("Store.set", {id: 8}, api_StoreSet, postParam);
The next time Store.get
you get the merchant, the addr
value of the visible attribute is null
(Note: Not a field string "null"
)
Delete Object
The prototypes are:
{object}.del(id)
The call is simple, if you want to delete id=8
the corresponding merchant:
callSvr("Store.del", {id: 8}, function (data) { alert("删除成功");});
Get Object Details
The prototypes are:
{object}.get(id, res?) -> {fields...}
Its default return object corresponds to the field in the primary table, and the design can also add sub-objects or virtual fields to the returned content (the implementation method reference the back-end document of the Loop cloud).
Assume that when you design the Get merchant interface, you add a sub-object called "Product List" items
, and the design interface prototype is:
Store.get(id, res?) -> {id, name, addr, tel, @items=[item]}item:: {id, name, price}
(Note: In the design of the interface prototype, the "Cocoon notation" layer to parse and describe the object type, not in the scope of this article, see related articles.) )
Depending on the interface, to get the details of a merchant you can call:
callSvr("Store.get", {id: 8}, api_StoreGet);function api_StoreGet(data) { ... }
Return data data
like this:
{ id: 8, name: "华莹小吃", addr: "银科路88号", tel: "13812345678", items: [ {id: 1001, name: "鲜肉小笼", price: 10.0}, {id: 1002, name: "大肉粽", price: 8.0} ... ]}
Optional parameter in URL res
it represents "result", that is, a list of fields returned, separated by commas in the middle of multiple fields. If you do not want to return to the default field, you can specify which fields you want by using this parameter.
Example: Get merchant details and return only the store name and phone number:
callSvr("Store.get", {id: 8, res: "name,tel"}, api_StoreGet);function api_StoreGet(data){ // data示例:{name: "华莹小吃", tel: "13812345678"}}
Querying the Object list
Query operations are the most flexible and complex in standard operations, with many optional parameters and two prototypes (the format of the returned content is different):
{object}.query(res?, cond?, orderby?, distinct?=0, _pagesz?=20, _pagekey?, _fmt?) -> tbl(field1,field2,...){object}.query(wantArray=1, ...) -> [{field1,field2,...}]
The first prototype returns a special table type (described below, which can be turned into an array of objects), with the benefit of data refinement and support for paging; the second prototype has more wantArray
parameter settings (the same as other parameters), the return type becomes an object array, and the child object is supported, but it does not support paging operations, generally less.
Table type
If you do not specify a parameter wantArray
(the first prototype), the returned content is the table type, which can not return child objects (such as the sub-objects in the previous get Operation item list items), such as fetch a merchant list:
callSvr("Store.query", api_StoreQuery);function api_StoreQuery(data) { ... }
api_StoreQuery
the data parameter format in the callback function is:
{ h: [ "id", "name", "addr", "tel"] d: [ [ 8, "华莹小吃", "银科路88号", "13812345678"], [ 9, ... ] ... ] nextkey: 998}
The attribute h
is the column an array group, which d
represents the data row array, and the value array for each row corresponds to element one by one in the column an array group.
If there is nextkey
a property, it means that this is only part of the data, to remove a page of data, you can use the same query, with the parameters _pagekey
set to this value, such as
callSvr("Store.query", {_pagekey: 998});
The design of table structure is advantageous to the improvement of transmission efficiency, and facilitates the design of paging mechanism.
The front end of the spreading wings provides functions rs2Array
that convert this data into an array of commonly used objects:
var arr = rs2Array(data);
Get the arr
like this:
[ {id: 8, name: "华莹小吃", addr: "银科路88号", tel: "13812345678"}, {id: 9, ...} ...]
Query parameters
Object queries Support flexible query conditions (via parameters cond
-condition), sort methods (parameters), and orderby
return fields (Parameters res
, as with get operations).
If you understand SQL statements, you'll find that these parameters are easy to use.
- Parameter
res
Specifies the return field, with multiple fields separated by commas, for example, res= "Field1,field2".
- Parameter
cond
Specifies the query condition, which is syntactically similar to the "WHERE" clause of the SQL statement, such as "field1>100 and field2= ' Hello '", noting that the string value is enclosed in single quotation marks.
- Parameters
orderby
Specify the ordering criteria, which can refer to the "ORDER BY" clause of the SQL statement, for example: orderby= "id desc", or Multiple fields: "TM Desc,status" (inverted by time, and then by state)
For example, to query all id
merchants whose names are less than 10 and begin with "Hua Ying", the returned results are sorted by first name (name):
var cond = "id<10 and name like ‘华莹%‘";var param = {res: "id,name,addr", cond: cond, orderby: "name"};callSvr("Store.query", param, api_StoreQuery);function api_StoreQuery(data){ // 先用rs2Array将table类型的数据转成对象数组 var arr = rs2Array(data); // 遍历每个商户 arr.forEach(function(store) { // 由于指定了res参数,store对象类型为:{id, name, addr} });}
Although these parameter values are similar to SQL statements, they have some security limitations:
- Res, only the list of fields (or virtual fields) cannot appear, such as functions, subqueries, and so on.
- Cond can be combined by multiple criteria with and or or, and the left side of each condition is the field name and the right is a constant. Field operations are not allowed, subqueries are not allowed (keywords such as select are not available).
cond
The following conditions are not allowed in the image parameters:
left(type, 1)=‘A‘ -- 条件左边只能是字段,不允许计算或函数type=type2 -- 字段与字段比较不允许type in (select type from table2) -- 子表不允许
Paging support
Parameters _pagesz
and is _pagekey
used to support paging. _pagesz
specifies how many data to return each time (20 is returned by default).
Here is an example of getting all the merchants. First time query:
callSvr("Store.query")
Return data like this:
{nextkey: 10800910, h: [id, ...], d: [...]}
Where the nextkey
presentation data is not finished, you need to fill in the fields to query the next page _pagekey
.
Second query (next page):
callSvr("Store.query", {_pagekey=10800910});
Return:
{nextkey: 10800931, h: [...], d: [...]}
Still return the nextkey
field description can also continue the query, and then query the next page:
callSvr("Store.query", {_pagekey=10800931});
Return:
{h: [...], d: [...]}
Returns no attributes in the data nextkey
, indicating that all data has been obtained.
If you want to return the total number of records when you first query, you can set _pagekey=0
:
callSvr("Store.query", {_pagekey: 0})
This will return
{nextkey: 10800910, total: 51, h: [id, ...], d: [...]}
More total
fields represent the total number of records. Because the default page size is 20, you can estimate a total of 51/20=3 pages.
Object List Export
Setting parameters in the Object query interface _fmt
allows you to output the specified format, which is typically used to export the list to a file. The parameters support the following values:
- CSV: comma-delimited UTF8 encoded text
- txt: tab-delimited UTF8 text
- Excel: Similar to CSV, but uses GB2312 encoding Chinese so that MS Excel that does not support UTF8 encoding can be opened directly.
Note that because there are pagination by default, to export all data, you typically specify a large page size, such as _pagesz=9999
.
For example, to export a list of merchants, you could write:
var url = makeUrl("Store.query", {_fmt: "excel", _pagesz: 9999});location.href = url; // 下载Excel文件
Note: Be sure to use the function provided by the framework to makeUrl
generate the URL, not the handwritten URL. Its usage is callSvr
similar to the incoming call name ac
with the URL parameter param
.
Spreading Wings framework: REST-RPC Style Service Interface Example analysis