Hi all,
Working on the multi currency branch, I need to define a new webservice. While we have a REST class somewhere and we have an ad-hoc service to query accounts for the account drop-downs, I haven't found any documentation so far about how we want services to be set up and interact with the outside world.
So, before adding to the chaos, I'm going to go out on a limb and try to document how I hope the world will work ideally, if we ever achieve paradise :-). My proposal describes a future situation where we have a different organization of our URL space. However, at the end of the proposal I add a section on how to work with the current URL structure in the intermediate time.
So, lets go and have the actual proposal:
JUSTIFICATION
============
In order to continue on our path of development with the browser pages, we need to decide on the services structure rather sooner than later (rather, since I'm building new pages: now).
GOAL
====
The goal of having a webservice API is to allow development of a broader ecosystem of service-using applications. In addition, we're more and more moving to a rich browser-based webapplication ourselves; rich-client web application frameworks expect to integrate with a backend service through web-services.
VERSIONING
==========
More even than the Perl API and database stored procedures, will the webservice API be public in the sense that services and applications will bind to it that fall out of the scope of management of the LedgerSMB project. To that extent, API versioning (and version detection) is of highest importance. I'd like to put the API under the rules of [semantic versioning][1]
Chris has argued that our software is in flux and as such the API can't really be stable. My counter argument is that the Subversion project built a new working copy library, using a completely new paradigm, all within the rules of the v1 api. On the other hand, numbers are cheap and Chrome is at version 43 now. Who cares about which number it is? It's only a sequence specifier, I'd say.
VERSION DETECTION
=================
The user of the api should run an OPTIONS request on the base URL (/api) to discover version, options and features of the API.
URL STRUCTURE
==============
The proposed URL structure is (as can be found in many existing web service schemas):
(a) /api/<version>/<resource>/[?query parameters]
(b) /api/<version>/<resource>/id
(c) /api/<version>/<resource>/id?perform=<action>
The above is mostly inspired on the PayPal API which - I think - drives a system much like ours in the sense that their system manages workflow producing transactions.
In our case, I think the "id" specifier in the resource may be multiple path segments long; e.g. for currency rates: /api/v1/exchangerate/EUR/1/2015-12-12 where "EUR/1/2015-12-12" is the identifier for the: currency identifier, rate type and (start)date of the rate.
Form (a) will be used for creating (POST) and listing (GET) resources instances. Dojo proposes to use the 'Range:' HTTP header to limit results in the request. I think that makes more sense than to use query parameters for it.
Form (b) will be used for retrieving (GET) an individual resource instances.
Form (c) will be used for (POST) modifying state of individual resource instances by executing <action> on the specified resource.
MEANING OF REQUEST TYPES
=========================
(Note that the API doesn't attach meaning to the HTTP request types PUT and PATCH (which the PayPal API *does* do)) -- I could see value in supporting a PATCH request for resources which require secondary approval and have not yet been approved (this is where PayPal uses it too).
GET
------
Retrieves an object or collection of objects, potentially restricted by query parameters or HTTP headers.
POST
--------
Creates an object or collectino of objects when executed on a resource URL; when executed on a resource-instance URL, a required ?perform=<action> query string is to be added to the URL to specify which state transition is to be executed.
Each POST request in the API carries a payload where the consuming service should support at least one of the following formats (as indicated by the OPTIONS response)
(i) application/json
(ii) application/xml
(iii) application/form-data
(iv) application/x-www-form-urlencoded
OPTIONS
--------------
In general requests metadata about the endpoint the URL points to. Minimal endpoints that provide metadata are:
/api
/api/<version>
/api/<version>/<resource>
Whether or not metadata can be requested for individual resource instances is to be specified in the return value of the resource collection URL OPTIONS return.
Requirements for return values of the OPTIONS request should be separately documented to make them meaningful and machine processable. Typical items to be included in the OPTIONS response are the DTD for the POST XML payload and response and JSON field specification.
DELETE
-----------
Most objects can't be removed from the system (although e.g. GL accounts can be marked 'obsolete'), but some (notably sessions) *should* be removed from the system after they have served their purpose. Running DELETE on anything other than an individual resource instance isn't supported. In case the resource supports deletion, the resource instance is deleted.
ATOMICITY
=========
When an api call affects multiple resources and the API call returns an error *none* of the affected resources are to be affected.
SESSION MANAGEMENT
====================
The API user logs in by creating a new session through the /api/<version>/session/ API. Each application login (including API logins) is attached to an application user. the webservice caller thereby identifies itself as an application user/employee. Currently, credentials will be provided through basic auth on the first *and* all following requests. Session replay attacks are prevented by sending cookies back and forth; just as they are now. Each request should provide the cookies created during the session; possibly updated by the response of the last request -- basic cookie management.
At the end of a session, the session is to be removed by issueing a DELETE request on the session resource instance.
Regardless of whether the response generated by the server is a failure or a success, the session cookies should be updated on each request. The client must respect cookie updates regardless of the type of response.
ENCODING OF VALUES
==================
Each of the supported formats need to have their own design documents which specify how to encode specific values. While this has been mostly handled for JSON, there's a missing data point with respect to encoding dates. Dojo handles encoding dates from the client to the server, but I've been unable to find if/how Dojo's JSON can deserialize dates coming from the server.
VALUE OF METADATA SPECIFICATION
===============================
The purpose of having the server be required to specify metadata and include in that metadata a description of the response objects, is among others, meant to serve a generic response parser on the client which can parse responses into the correct objects on the client (e.g. parse dates into dates, even if dates are transferred as JSON strings) -- without the need to implement knowledge in advance into the client.
NESTING OF RESOURCES
=====================
When obtaining a resource from the server, the serving webservice may include embedded in its response objects that it refers to; e.g. the server may decide to include address data included in a response to a query for a customer. The server isn't required to include more than just the key by which the resource can be queried out of the resource collection.
Nested resources in the URL space (such as the GitLab example with team members in a project [2]).
*** Nested resources like the GitLab example pollute the namespace, because there's a two way correspondence: users-in-project and projects-in-user. *** How to handle this in the way that creates the least complexity??? *** Presumably, we want things to be layered, building complex resources on simple ones; so it's problematic in the gitlab example to make the user aware of the projects... ***
TRANSITIONING TO THE TARGET URL SPACE
====================================
Since we have no infrastructure in place (yet) to create all of the above, I'm thinking to start out with a new script in the toplevel: /
api.pl.
api.pl accepts all the query parameters it accepts in the proposal above, but in addition, it accepts a 'path' query parameter which is the API path in the future URL space, like this:
Which maps to:
/api/v1/exchangerate/EUR/1/2015-12-12?perform=approve
in the target namespace.
INITIAL IMPLEMENTATION
=====================
The initial implementation should implement at least 2 resources:
* sessions
* exchangerates
So, this being the initial draft, there's probably a lot wrong with it :-) Lets hear your feedback!
--
Bye,
Erik.
Robust and Flexible. No vendor lock-in.