Rational
At B2CK we maintain a Flask web shop based on flask-tryton. We have achieved good web vitals score (~95%) for desktop but we are still low on mobile (for LCP, FCP and TTFB). This is mainly due to slow time to first bit because we need to render the full page before flask send the first bit. The reason for that is that we use Tryton model instances in the template so stream rendering is not possible as the database connection will be closed. (An option would be to collect all the data before the rendering but it is complex and not very flexible).
There are also others inconveniences with flask-tryton for example:
- The Tryton models can not be cached with their value (in memcached for example).
- It is difficult to add cache on template blocks because Tryton models have hidden keys in the context like the language or the company.
- To scale we need to add more flask instances which all keep a database pool which increase the number of connection to the database (even worse with cache managed by bus).
So having a stream rendering could improve the web vitals because the header could be sent quickly which can contain preload and other optimizations.
So to improve the situation, we think that we need to have a stateless communication between the web server (Flask or other) (which can optionally be cached) and Tryton.
We think that a REST API would provide the most flexibility and performance with a simple implementation that can follow the modularity of Tryton (compared to GraphQL).
Proposal
The proposal is to implement a REST API using user application authentication.
Using user application provides access control and log. So a dedicated user can be used per consumer.
API
GET https://tryton.example.com/api/rest/database/party.party
To get all the parties as a json list of party values.
Parameters:d
: a json domain liked=[['name', '=', 'John']]
s
: the limitp
: the offseto
: a json order likeo=[['name', 'ASC']]
POST https://tryton.example.com/api/rest/database/party.party
To create a party and return the URL of the created party values.GET https://tryton.example.com/api/rest/database/party.party/id
Return the party values for given id.GET https://tryton.example.com/api/rest/database/party.party/id/addresses
Return the relations field for the given party id.
Such sub-collections are advised in the header of the main collection.PUT https://tryton.example.com/api/rest/database/party.party/id
Update the party for given id with the json value. And return the new party values.DELETE https://tryton.example.com/api/rest/database/party.party/id
Delete the party for given id.POST https://tryton.example.com/api/rest/database/party.party/id/button
Call the button for the given id.
The context can be set using HTTP header X-Tryton-Context
as a json dictionary.
The language is set using the HTTP header Accept-Language
.
The JSON values of a record are build using a method ModelStorage.__json__(self, usages=None)
. The usages is a list of string which represent for which purpose the API is used by the consumer (ex: sale
and webshop
). These usage are set in the HTTP header X-Tryton-Usage
as a comma separated list.
By default the returned values are {'id': self.id, 'rec_name': self.rec_name}
.
Library
A Python library is started as a consumer of this API. It uses the ActiveRecord pattern to access and browse the resources (using CRUD methods).
It is based on python-requests using a connection pooling.
It keeps a global context that is set as header of the requests.
The library must be independent of the Tryton series.