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
==============

All URLs in the document are assumed to be relative to some base URL. E.g.
assuming LedgerSMB to be hosted under the following URL:
https://example.com/path/to/ledgersmb/ , the URL /api in this document in
fact means https://example.com/path/to/ledgersmb/api .

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.

Alternatively, API calls can be invoked from sessions originally
authenticated against /login.pl?action=authenticate (with the same further
requirements as above).


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:

 /api.pl?path=/api/v1/exchangerate/EUR/1/2015-12-12&perform=approve

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!



[1] http://semver.org/
[2] http://doc.gitlab.com/ce/api/projects.html#get-project-team-member

-- 
Bye,

Erik.

http://efficito.com -- Hosted accounting and ERP.
Robust and Flexible. No vendor lock-in.
------------------------------------------------------------------------------
_______________________________________________
Ledger-smb-devel mailing list
Ledger-smb-devel@lists.sourceforge.net
https://lists.sourceforge.net/lists/listinfo/ledger-smb-devel

Reply via email to