CiviCRM Community Forums (archive)

*

News:

Have a question about CiviCRM?
Get it answered quickly at the new
CiviCRM Stack Exchange Q+A site

This forum was archived on 25 November 2017. Learn more.
How to get involved.
What to do if you think you've found a bug.



  • CiviCRM Community Forums (archive) »
  • Old sections (read-only, deprecated) »
  • Developer Discussion »
  • 5.0 Saloon »
  • Towards HATEOAS with API
Pages: [1]

Author Topic: Towards HATEOAS with API  (Read 1803 times)

JoeMurray

  • Administrator
  • Ask me questions
  • *****
  • Posts: 578
  • Karma: 24
    • JMA Consulting
  • CiviCRM version: 4.4 and 4.5 (as of Nov 2014)
  • CMS version: Drupal, WordPress, Joomla
  • MySQL version: MySQL 5.5, 5.6, MariaDB 10.0 (as of Nov 2014)
Towards HATEOAS with API
May 18, 2014, 07:40:36 am
Might be nice to aim towards supporting at least level 3 in http://timelessrepo.com/haters-gonna-hateoas.

Quote
Content negotiation is the answer to the question, “How do I version my API?” The proper way to do this is with the Accepts header, and use a MIME type like application/yourcompany.v1+json. There’s a great article about this by Peter Williams here: http://barelyenough.org/blog/2008/05/versioning-rest-web-services/.
Co-author of Using CiviCRM https://www.packtpub.com/using-civicrm/book

totten

  • Administrator
  • Ask me questions
  • *****
  • Posts: 695
  • Karma: 64
Re: Towards HATEOAS with API
May 19, 2014, 10:56:08 am
FWIW, when working on the current Doctrine CRUD API, I initially took a stab with http://hateoas-php.org/ . The library didn't seem to be a great match for our situation (eg it doesn't handle of any the actual REST, and it assumes that you always send the exact same data embedded/linked in every request, and it requires work to integrate with our other metadata, and we'd have to reinvent several features to reach parity with our current APIs), so we shifted to refactoring civicrm_api ( http://forum.civicrm.org/index.php/topic,32126.0.html ).

The upshot is that -- while working with hateoas-php -- I had to write a new REST entry point:

https://github.com/civicrm/civicrm-core/blob/doctrine/Civi/API/Page/REST.php

You might say that Civi\API\Page\REST is to Doctrine/hateoas-php as CRM_Utils_REST is to civicrm_api, but Civi\API\Page\REST aims to follow more common RESTful conventions, and it has a series of unit-tests. The Doctrine/hateoas-php stuff in Civi\API\Page\REST is a relatively small part of the code -- at some point, I'd like to rip out the Doctrine-specific parts and switch it back to use civicrm_api(). This would enable it to work as a RESTful entry point for APIv3 or APIv4.

xavier

  • Forum Godess / God
  • I’m (like) Lobo ;)
  • *****
  • Posts: 4453
  • Karma: 161
    • Tech To The People
  • CiviCRM version: yes probably
  • CMS version: drupal
Re: Towards HATEOAS with API
May 19, 2014, 02:12:38 pm
Few comments on the code:

- It's much easier to read than the php4.x/5 style we have now, great improvements
- Instead of having civicrm/rest as "static" prefix, I'd rather have the _format in the path, eg. civicrm/json and civicrm/xml (and skip the content type, mostly because I find it quite hidden as a way to set a param type)
- We tried to introduce "computer readable" error codes, I'm not sure using the http code is good enough (eg. we might end up with several 400 or 500 reasons, adding an extra error_code="INVALID_PATH" or "INVALID_ENTITY" will make it easier to test/debug. I think it's worthwhile having these codes, event when using better the return code 4xx or 5xx
- how do you modify an entity? POST+ id isn't handled?
- should we have PUT instead of/as well as POST without id to create?
- what do we do with ajax requests?
ie. I don't think we can move too far away from GET or POST when the client is a browser, is it? so the REST interface is going to be quite different than the ajax one?

X+
-Hackathon and data journalism about the European parliament 24-26 jan. Watch out the result

totten

  • Administrator
  • Ask me questions
  • *****
  • Posts: 695
  • Karma: 64
Re: Towards HATEOAS with API
May 19, 2014, 03:57:31 pm
General note -- I did do a round of comparisons of how different REST implementations handle some of the questions. The spreadsheet is a bit dense but there may be some interesting things anyway: https://docs.google.com/spreadsheet/ccc?key=0AujctkQuBowkdDBNOTFqTmpUbVFJV0lGRW5kdGVpVXc&usp=sharing As a general thought, researching common REST practices is a bit frustrating -- it's like an "anti-standard" standard which combines all the ambiguities and variations of a non-standard with all the dogma and bloviation of a standard. ;)

Quote from: xavier on May 19, 2014, 02:12:38 pm
- Instead of having civicrm/rest as "static" prefix, I'd rather have the _format in the path, eg. civicrm/json and civicrm/xml (and skip the content type, mostly because I find it quite hidden as a way to set a param type)

One idea I took away from the comparison is that each HTTP request includes a number of common options that may be sent across (such as the media-type or the offset in a paginated result-list). And there are ~4 techniques for conveying these options (eg putting them in the path, the query-parameters, the headers, or the message-body [$params['options']['limit']]). You'll find many different permutations in the wild (depending on which implementation is used, what fad was current at the time of writing, the phase of the moon, and the price of tea in China).

FWIW, the most common techniques for signalling the media-type in the systems I surveyed were (a) headers ["Accept:"/"Content-type:"] and (b) file-extensions ["/contact/123.json"]. I haven't seen anyone put the media-type in the base URL.

We could simply pick one format and require its use for all options. Or we could negotiate it on a case-by-case basis (eg "use headers for media-types and use parameters for page-offsets"). My own preference would be to support all options as headers *or* parameters *or* message bodies. I don't think it would be too hard (although the current code is a bad example of the concept).

Quote from: xavier on May 19, 2014, 02:12:38 pm
- We tried to introduce "computer readable" error codes, I'm not sure using the http code is good enough (eg. we might end up with several 400 or 500 reasons, adding an extra error_code="INVALID_PATH" or "INVALID_ENTITY" will make it easier to test/debug. I think it's worthwhile having these codes, event when using better the return code 4xx or 5xx

We should return both. The HTTP status codes allow better out-of-the-box behavior with most HTTP clients, and our error-codes provide more clarity

Quote from: xavier on May 19, 2014, 02:12:38 pm
- how do you modify an entity? POST+ id isn't handled?
- should we have PUT instead of/as well as POST without id to create?

Oops, I implemented better verb support in a branch but never merged it (because we switched to the API kernel approach). You can see the code at:

https://github.com/totten/civicrm-core/compare/civicrm:doctrine...doctrine-rest-updates

In any event, I think POST+id should be treated the same as PATCH -- ie a set of changes to the existing record.  (This differs from PUT which is supposed to replace a record.) This is a slight deviation from current dogma (which only uses POST for creation), but people just aren't aware of PATCH yet, and I think it's been the de-facto standard even there are no "architects" publishing new advocacy blogs for it.

For creation, most of the writers I've seen recommend only using POST.

Quote from: xavier on May 19, 2014, 02:12:38 pm
- what do we do with ajax requests?
ie. I don't think we can move too far away from GET or POST when the client is a browser, is it? so the REST interface is going to be quite different than the ajax one?

IMHO, the only difference for AJAX should be authentication. An AJAX request uses an existing CMS session-cookie to authenticate. PUT and DELETE should work with the browser's XMLHttpRequest ( http://annevankesteren.nl/2007/10/http-method-support ).

Never-the-less, GET/POST is the lowest-common-denominator supported well by the broadest list of libraries (eg jQuery has a $.get helper but no $.delete helper). This is a concern with libraries both inside and outside the browser, but alternative verbs and headers can be used in many libraries (eg with jQuery's $.ajax, curl's curl_setopt, php's stream contexts).

xavier

  • Forum Godess / God
  • I’m (like) Lobo ;)
  • *****
  • Posts: 4453
  • Karma: 161
    • Tech To The People
  • CiviCRM version: yes probably
  • CMS version: drupal
Re: Towards HATEOAS with API
May 20, 2014, 12:28:22 am
Agree with the state of the REST "standard" ;)

I'm a bit unsure about how much change it will imply to extend to more http actions outside of the get+post (I've seen some nginx config that block them), but hopefully we'll have various clients (at least for jquery, we'll have a CRM.api wrapper) so it doesn't matter to much.

You are right, using the extension to define the type feels more natural than part of the part. For the media-type, I don't care too much, as long as it defaults to json ;)

Last open question: what about using the same url for both the ui and the rest, eg
civicrm/contact/123 -> contact summary UI
civicrm/contact/123.json -> json of the contact.

probably a bit of a mess on the routing+permission to handle.

A python project I'm working with has that convention, and I like the discoverability of whatever you see+".json" and you get the raw data. I think that's something we should try to improve... just not sure how ;)

X+



X+
-Hackathon and data journalism about the European parliament 24-26 jan. Watch out the result

JoeMurray

  • Administrator
  • Ask me questions
  • *****
  • Posts: 578
  • Karma: 24
    • JMA Consulting
  • CiviCRM version: 4.4 and 4.5 (as of Nov 2014)
  • CMS version: Drupal, WordPress, Joomla
  • MySQL version: MySQL 5.5, 5.6, MariaDB 10.0 (as of Nov 2014)
Re: Towards HATEOAS with API
May 21, 2014, 01:22:33 pm
Main feedback: this is looking great!

A somewhat random minor note:

I notice in https://docs.google.com/a/jmaconsulting.biz/spreadsheet/ccc?key=0AujctkQuBowkdDBNOTFqTmpUbVFJV0lGRW5kdGVpVXc&usp=sharing#gid=0 that Tim thinks the coolest Salesforce API feature is field level permissions, and that CiviCRM is marked as not having them. It's not proper field level permission, but ACLs on profiles and sets of custom fields does go partway towards that.
Co-author of Using CiviCRM https://www.packtpub.com/using-civicrm/book

JoeMurray

  • Administrator
  • Ask me questions
  • *****
  • Posts: 578
  • Karma: 24
    • JMA Consulting
  • CiviCRM version: 4.4 and 4.5 (as of Nov 2014)
  • CMS version: Drupal, WordPress, Joomla
  • MySQL version: MySQL 5.5, 5.6, MariaDB 10.0 (as of Nov 2014)
Re: Towards HATEOAS with API
May 21, 2014, 01:36:42 pm
I like the idea of a separate verb PATCH for updates that is different than PUT which is a full replacement. And POST+id being equivalent to PATCH makes sense. This would be particularly useful on imports that update only certain fields. Ideally this could provide finer granularity to the logging of changes.

But given there is no functionality behind this idea, I take it that was the reason that https://github.com/civicrm/civicrm-core/blob/doctrine/Civi/API/Page/REST.php doesn't support PATCH or POST+id.

As a performance note, it seems the code often saves records to multiple tables where there are no changes to the data. As writing to disk is much slower than comparing fields in RAM, it might be useful as GSoC project or something to find code that is executed frequently that does this and to optimize it to only write when necessary, and to only write fields that are necessary, which on multi-table writes will likely reduce the number of saves of the current data.
Co-author of Using CiviCRM https://www.packtpub.com/using-civicrm/book

totten

  • Administrator
  • Ask me questions
  • *****
  • Posts: 695
  • Karma: 64
Re: Towards HATEOAS with API
May 21, 2014, 02:59:54 pm
I updated the spreadsheet to mention profiles as a partial solution for field-level permissions. And now that you mention it... APIv3's profile approach goes a bit further than D8's views approach -- because the profile API is read/write while D8's is read-only ( http://drupalize.me/blog/201402/your-first-restful-view-drupal-8 ).

I should clarify something - Civi\API\Page\REST was written with a particular architecture in mind (where we basically cut out our existing API layer and instead glue Doctrine, JMS Serializer, and hateoas-php directly). It turned out that that architecture wasn't giving us the value we hoped for, so we pivoted to a different architecture -- making our API layer more modular/extensible (ie the "API kernel"). During the pivot, I copied over most of the useful bits (from Civi\API\Page\REST to Civi\API\Provider\DoctrineCrudProvider). In this architecture, Civi\API\Page\REST is no longer necessary -- we can continue using CRM_Utils_REST. In fact, we could delete Civi\API\Page\REST now (because it no longer works and because some features -- like PATCH/POST+id -- were never merged in). However, I think it's an improvement over CRM_Utils_REST, and I like to keep it as a proof-of-concept/starting-point for rewriting CRM_Utils_REST. It would probably take me 6-12hr to adapt it to the new architecture and merge in PATCH/POST+id support.

Pages: [1]
  • CiviCRM Community Forums (archive) »
  • Old sections (read-only, deprecated) »
  • Developer Discussion »
  • 5.0 Saloon »
  • Towards HATEOAS with API

This forum was archived on 2017-11-26.