github.com/voedger/voedger@v0.0.0-20240520144910-273e84102129/design/api/README.md (about)

     1  # Motivation
     2  - We need good REST API for Voedger
     3  - Old API must still be available until the new one is fully developed, so we can continue with AIR
     4  
     5  # Functional Design
     6  
     7  ## API URL
     8  API URL must support versioning ([example IBM MQ](https://www.ibm.com/docs/en/ibm-mq/9.1?topic=api-rest-versions), [example Chargebee](https://apidocs.chargebee.com/docs/api/)):
     9  
    10  - old API is available at `/api/v1/...` (for the period of AIR migration it will be available both on `/api/` and `/api/v1/`)
    11  - new API is available at `/api/v2/...`
    12      - "v1" is not allowed as an owner name, at least until API "v1" is ready
    13  
    14  TODO: add endpoint for the list of supported versions
    15  
    16  ## REST API Paths
    17  
    18  | Action                               | REST API Path                                  |
    19  |--------------------------------------|------------------------------------------------|
    20  | Create CDoc/WDoc/CRecord/WRecord     | `POST /api/v2/owner/app/wsid/pkg.table`        |
    21  | Update CDoc/WDoc/CRecord/WRecord     | `PATCH /api/v2/owner/app/wsid/pkg.table/id`    |
    22  | Deactivate CDoc/WDoc/CRecord/WRecord | `DELETE /api/v2/owner/app/wsid/pkg.table/id`   |
    23  | Execute Command                      | `POST /api/v2/owner/app/wsid/pkg.command`      |
    24  | Read CDoc/WDoc/CRecord/WRecord       | `GET /api/v2/owner/app/wsid/pkg.table/id`      |
    25  | Read from Query Function             | `GET /api/v2/owner/app/wsid/pkg.query`         |
    26  | Read from CDoc Collection            | `GET /api/v2/owner/app/wsid/pkg.table`         |
    27  | Read from View                       | `GET /api/v2/owner/app/wsid/pkg.view`          |
    28  
    29  
    30  ## Query Processor based on GET
    31  Current design of the QueryProcessor based on POST queries. 
    32  However, according to many resources, using POST for queries in RESTful API is not a good practice:
    33  - [Swagger.io: best practices in API design](https://swagger.io/resources/articles/best-practices-in-api-design/)
    34  - [MS Azure Architectural Center: Define API operations in terms of HTTP methods](https://learn.microsoft.com/en-us/azure/architecture/best-practices/api-design#define-api-operations-in-terms-of-http-methods)
    35  - [StackOverflow: REST API using POST instead of GET](https://stackoverflow.com/questions/19637459/rest-api-using-post-instead-of-get)
    36  
    37  Also, using GET and POST allows to distinguish between Query and Command processors clearly:
    38  
    39  | HTTP Method         | Processor         |
    40  |---------------------|-------------------|
    41  | GET                 | Query Processor   |
    42  | POST, PATCH, DELETE | Command Processor |
    43  
    44  >> Note: according to RESTful API design, queries should not change the state of the system. Current QueryFunction design allows it to execute commands through HTTP bus.
    45  
    46  Another thing is that according to REST best practices, it is not recommended to use verbs in the URL, the resource names should be based on nouns:
    47  
    48  [Example Microsoft](https://learn.microsoft.com/en-us/azure/architecture/best-practices/api-design#organize-the-api-design-around-resources):
    49  ```
    50  POST https://adventure-works.com/orders // Good
    51  POST https://adventure-works.com/create-order // Avoid
    52  ```
    53  
    54  Summary, the following Queries in airs-bp3:
    55  ```
    56  POST .../IssueLinkDeviceToken
    57  POST .../GetSalesMetrics
    58  ```
    59  violate Restful API design:
    60  - uses POST for query, without changing the server state
    61  - uses verb in the URL
    62  
    63  Should be:
    64  ```
    65  GET .../TokenToLinkDevice?args=...
    66  GET .../SalesMetrics?args=...
    67  ```
    68  
    69  ### Query Constraints and Query Arguments 
    70  Every query may have constraints (ex. [IQueryArguments]( https://dev.heeus.io/launchpad/#!12396)) and arguments.
    71  
    72  Constraints are:
    73  - order (string) - order by field
    74  - limit (int) - limit number of records
    75  - skip (int) skip number of records
    76  - include (string) - include referenced objects
    77  - keys (string) - select only some field(s)
    78  - where (object) - filter records
    79  
    80  Arguments are optional and are passed in `&arg=...` GET parameter.
    81  
    82  ### ACL
    83  EXECUTE -> SELECT for Queries?
    84  
    85  Currently:
    86  ```sql
    87  LIMIT AllQueriesLimit EXECUTE ON ALL QUERIES WITH TAG PosTag WITH RATE AppDefaultRate;
    88  GRANT EXECUTE ON QUERY Query1 TO LocationUser;
    89  ```
    90  
    91  Should be:
    92  ```sql
    93  LIMIT AllQueriesLimit SELECT ON ALL QUERIES WITH TAG PosTag WITH RATE AppDefaultRate;
    94  GRANT SELECT ON QUERY Query1 TO LocationUser;
    95  ```
    96  
    97  
    98  ## Paths Detailed
    99  
   100  ### Create CDoc/WDoc/CRecord/WRecord object
   101  
   102  - URL:
   103      - `POST /api/v2/owner/app/wsid/pkg.table`
   104  - Parameters:
   105      - application/json
   106      - CDoc/WDoc/CRecord/WRecord
   107  - Errors:
   108      - 400: Bad Request, e.g. Record requires sys.ParentID
   109      - 401: Unauthorized
   110      - 403: Forbidden
   111      - 404: Table Not Found
   112      - 405: Method Not Allowed, table is an ODoc/ORecord
   113  
   114  200 Result:
   115  ```json
   116  {
   117      "CurrentWLogOffset":114,
   118      "NewIDs": {
   119          "1":322685000131212
   120      }
   121  }
   122  ```
   123  
   124  ### Read CDoc/WDoc/CRecord/WRecord
   125  - URL:
   126      - `GET /api/v2/owner/app/wsid/pkg.table/id`
   127  - Parameters: none
   128  - Result:
   129      - CDoc/WDoc/CRecord/WRecord object
   130  - Errors:
   131      - 401: Unauthorized
   132      - 403: Forbidden
   133      - 404: Table Not Found
   134      - 405: Method Not Allowed, table is an ODoc/ORecord
   135  
   136  ### Update CDoc/WDoc/CRecord/WRecord
   137  - URL:
   138      - `PATCH /api/v2/owner/app/wsid/pkg.table/id`
   139  - Parameters: 
   140      - application/json
   141      - CDoc/WDoc/CRecord/WRecord (fields to be updated)
   142  - Errors:
   143      - 400: Bad Request, e.g. Record requires sys.ParentID
   144      - 401: Unauthorized
   145      - 403: Forbidden
   146      - 404: Table Not Found
   147      - 405: Method Not Allowed, table is an ODoc/ORecord
   148  
   149  200 Result:
   150  ```json
   151  {
   152      "CurrentWLogOffset":114,
   153      "NewIDs": {
   154          "1":322685000131212
   155      }
   156  }
   157  ```
   158  
   159  ### Deactivate CDoc/WDoc/CRecord/WRecord
   160  - URL:
   161      - `DELETE /api/v2/owner/app/wsid/pkg.table/id`
   162  - Parameters: none
   163  - Errors:
   164      - 401: Unauthorized
   165      - 403: Forbidden
   166      - 404: Table Not Found
   167      - 405: Method Not Allowed, table is an ODoc/ORecord
   168  
   169  200 Result:
   170  ```json
   171  {
   172      "CurrentWLogOffset":114,
   173  }
   174  ```
   175  
   176  
   177  ### Read from Query
   178  - URL:
   179      - `GET /api/v2/owner/app/wsid/pkg.query`
   180  - Parameters: 
   181      - Query [constraints](../queryprocessor/request.md)
   182      - Query function argument `&arg=...`
   183  - Result: 
   184      -  The return value is a JSON object that contains a results field with a JSON array that lists the objects [example](../queryprocessor/request.md), ref. [Parse API](https://docs.parseplatform.org/rest/guide/#basic-queries)
   185  - Errors:
   186      - 401: Unauthorized
   187      - 403: Forbidden
   188      - 404: Query Function Not Found
   189  
   190  - Examples:
   191      - Read from WLog
   192          - `GET /api/v2/owner/app/wsid/sys.wlog?limit=100&skip=13994`
   193      - Read OpenAPI app schema
   194          - `GET /api/v2/owner/app/wsid/sys.OpenApi`
   195  
   196  ### Read from CDoc collection
   197  - URL:
   198      - `GET /api/v2/owner/app/wsid/pkg.table`
   199  - Parameters: 
   200      - Query [constraints](../queryprocessor/request.md)
   201  - Result: 
   202      -  The return value is a JSON object that contains a results field with a JSON array that lists the objects [example](../queryprocessor/request.md)
   203  - Errors:
   204      - 401: Unauthorized
   205      - 403: Forbidden
   206      - 404: Table Not Found
   207  - Examples:
   208      - Read articles
   209          - `GET /api/v2/untill/airs-bp3/12313123123/untill.articles?limit=20&skip=20`
   210  
   211  ### Read from View
   212  - URL:
   213      - `GET /api/v2/owner/app/wsid/pkg.view`
   214  - Parameters: 
   215      - Query [constraints](../queryprocessor/request.md)
   216  - Constraints
   217      -  "where" must contain "eq" or "in" condition for PK fields
   218  - Result: 
   219      -  The return value is a JSON object that contains a results field with a JSON array that lists the objects [example](../queryprocessor/request.md)
   220  - Errors:
   221      - 401: Unauthorized
   222      - 403: Forbidden
   223      - 404: View Not Found
   224  - Examples:
   225      - `GET /api/v2/untill/airs-bp3/12313123123/air.SalesMetrics?where={"Year":2024, "Month":{"$in":[1,2,3]}}`
   226  ### Execute Command
   227  - URL
   228      - `POST /api/v2/owner/app/wsid/pkg.command`
   229  - Parameters: 
   230      - application/json
   231      - Parameter Type / ODoc
   232  - Result:
   233      - application/json
   234      - Return Type
   235  - Errors:
   236      - 404: Command Not Found
   237      - 403: Forbidden
   238      - 401: Unauthorized
   239  
   240  ## Errors
   241  When HTTP Result code is not OK, then [response](https://docs.parseplatform.org/rest/guide/#response-format) is an object:
   242  ```json
   243  {
   244    "code": 105,
   245    "error": "invalid field name: bl!ng"
   246  }
   247  ```
   248  
   249  
   250  # Limitations
   251  - sys.CUD function cannot be called directly
   252  
   253  # Technical Design
   254  ## Router:
   255  - redirects to api v1/v2
   256  - for v2, based on HTTP Method:
   257      - GET -> QP            
   258          - Query Function
   259          - System functions for:
   260              - Collection of CDocs
   261              - View
   262      - POST, PUT, DELETE -> CP
   263          - name is CDoc/WDoc/CRecord/WRecord: exec CUD command
   264          - POST && name_is_command: exec this command
   265  
   266  ## Updates to Query Processor
   267  [GET params](../queryprocessor/request.md) conversion:
   268  - Query constraints (`order`, `limit`, `skip`, `include`, `keys` -> `sys.QueryParams`
   269  - Query `arg` -> `sys.QueryArgs`
   270  
   271  Example:
   272  ```bash
   273  curl -X GET \
   274  -H "AccessToken: ${ACCESS_TOKEN}"
   275  --data-urlencode 'arg={"SalesMode":1,"TableNumber":100,"BillPrinter":12312312312,"SalesArea":12312312333}'
   276  
   277    https://air.untill.com/api/rest/untill/airs-bp/140737488486431/air.IssueLinkDeviceToken
   278  
   279  ```
   280  
   281  ## Migration to GET in Queries
   282  Some existing components must be updated:
   283  - Air Payouts we use Query Functions for webhooks. In this case, they should be changed to commands + projectors.
   284  
   285  ## `sys.OpenApi` query function
   286