Note:

If you want to create a new page for developers, you should create it on the Moodle Developer Resource site.

Talk:External services description: Difference between revisions

From MoodleDocs
Line 1: Line 1:
==== About the different alternatives describing the available WS, by Eloy ====
=== About the different alternatives describing the available WS, by Eloy ===


Hi, after reading the different alternatives, I'm inclined to implement something like the 3rd one. Having our custom structures to define the interface to the available web services seems really better than using "free-form" structures like "raw" arrays and objects or using harcoded keywords like "multiple" and so on.
Hi, after reading the different alternatives, I'm inclined to implement something like the 3rd one. Having our custom structures to define the interface to the available web services seems really better than using "free-form" structures like "raw" arrays and objects or using harcoded keywords like "multiple" and so on.
Line 118: Line 118:
</code>
</code>


Changes from original code are:
====Changes from original code are:====


* Small change in structure names. ''external_bulk_array'' renamed to ''external_multiple_structure'' and ''external_assoc_array'' to ''external_single_structure'' (note that ''structure'' can be changed to anything else. The important bits are the ''single/multiple'' ones vs. the ''assoc/bulk''.
* Small change in structure names. ''external_bulk_array'' renamed to ''external_multiple_structure'' and ''external_assoc_array'' to ''external_single_structure'' (note that ''structure'' can be changed to anything else. The important bits are the ''single/multiple'' ones vs. the ''assoc/bulk''.
Line 127: Line 127:
** Also, although there isn't an example, I think we could have some "habitual" structures predefined somewhere, see for example, the ''preference'' or the ''customfield'' single structures (or the whole ''preferences'' or ''customfields'' multiple ones if you prefer). They are basically the same (name/value pairs). It could be useful to have one predefined "name_value" structure somewhere and use it where necessary (user preferences, user custom fields, various config options...).
** Also, although there isn't an example, I think we could have some "habitual" structures predefined somewhere, see for example, the ''preference'' or the ''customfield'' single structures (or the whole ''preferences'' or ''customfields'' multiple ones if you prefer). They are basically the same (name/value pairs). It could be useful to have one predefined "name_value" structure somewhere and use it where necessary (user preferences, user custom fields, various config options...).


Concerns about return functions:
====Concerns about return functions:====


In the example above we have a good example. There are some xxxx_return() functions returning NULLs, others returning multiple structures (and also is possible to return single structures or numbers or whatever).
In the example above we have a good example. There are some xxxx_return() functions returning NULLs, others returning multiple structures (and also is possible to return single structures or numbers or whatever).

Revision as of 00:30, 18 September 2009

About the different alternatives describing the available WS, by Eloy

Hi, after reading the different alternatives, I'm inclined to implement something like the 3rd one. Having our custom structures to define the interface to the available web services seems really better than using "free-form" structures like "raw" arrays and objects or using harcoded keywords like "multiple" and so on.

Anyway, I've some (small) concerns about the example code used in the 3rd alternative, so I'm writing here how it should look like ideally IMO (rationale about changes is after the code):

class moodle_group_external extends external_api {

   public static function add_member_parameters() {
       return new external_single_structure(
           array('groupid' => new external_param(PARAM_INT, 'some group id'),
                 'userid'  => new external_param(PARAM_INT, 'some user id') 
           )
       );
   }
   public static function add_member($groupid, $userid) {
       $params = self::validate_prameters(self::add_member_parameters(), array('groupid'=>$groupid, 'userid'=>$userid));
       // actual implementation for adding one member goes here
   }
   public static function add_member_returns() { // Some concerns here. See below.
       return null; 
   }
   
   
   public static function add_members_parameters() {
       return new external_multiple_structure(
           'membership', self::add_member_parameters()
       );
   } 
   public static function add_members(array $membership) {
       foreach($membership as $one) {
           self::add_member($one->groupid, $one->userid);
       }
   }
   public static function add_members_returns() { // BIG concerns here. See below.
       return null; 
   }


   public static function get_groups_parameters() {
       return new external_multiple_structure(
           'groupids', new external_single_structure(
               array('groupid' => new external_param(PARAM_INT, 'some group id'))
           )
       );
   }
   public static function get_groups(array $groupids) {
       $params = self::validate_prameters(self::get_groups_parameters(), array('groupids'=>$groupids));
       // actual implementation for getting multiple groups goes here
   }
   public static function get_groups_returns() { // BIG concerns here. See below.
       return new external_multiple_structure(
           'groupids', new external_single_structure(
               array('id'           => new external_param(PARAM_INT, 'some group id'),
                     'name'         => new external_param(PARAM_TEXT, 'multilang compatible name, course unique'),
                     'description'  => new external_param(PARAM_RAW, 'just some text'),
                     'enrolmentkey' => new external_param(PARAM_RAW, 'group enrol secret phrase')  
               )
           )
       );
   }

}


class moodle_user_external extends external_api {

   public static function create_users_parameters() {
       return new external_multiple_structure(
           'users', new external_single_structure(
               array('username'    => new external_param(PARAM_USERNAME, 'Username policy is defined in Moodle security config'),
                     'password'    => new external_param(PARAM_RAW, 'Moodle passwords can consist of any character'),
                     'firstname'   => new external_param(PARAM_NOTAGS, 'The first name(s) of the user'),
                     'lastname'    => new external_param(PARAM_NOTAGS, 'The family name of the user'),
                     'email'       => new external_param(PARAM_EMAIL, 'A valid and unique email address'),
                     'auth'        => new external_param(PARAM_AUTH, 'Auth plugins include manual, ldap, imap, etc', false),
                     'confirmed'   => new external_param(PARAM_NUMBER, 'Active user: 1 if confirmed, 0 otherwise', false),
                     'idnumber'    => new external_param(PARAM_RAW, 'An arbitrary ID code number perhaps from the institution', false),
                     'emailstop'   => new external_param(PARAM_NUMBER, 'Email is blocked: 1 is blocked and 0 otherwise', false),
                     'lang'        => new external_param(PARAM_LANG, 'Language code such as "en_utf8", must exist on server', false),
                     'theme'       => new external_param(PARAM_THEME, 'Theme name such as "standard", must exist on server', false),
                     'timezone'    => new external_param(PARAM_ALPHANUMEXT, 'Timezone code such as Australia/Perth, or 99 for default', false),
                     'mailformat'  => new external_param(PARAM_INTEGER, 'Mail format code is 0 for plain text, 1 for HTML etc', false),
                     'description' => new external_param(PARAM_TEXT, 'User profile description, as HTML', false),
                     'city'        => new external_param(PARAM_NOTAGS, 'Home city of the user', false),
                     'country'     => new external_param(PARAM_ALPHA, 'Home country code of the user, such as AU or CZ', false),
                     'preferences' => new external_multiple_structure(
                         'preference', new external_single_structure(
                             array('type'  => new external_param(PARAM_ALPHANUMEXT, 'The name of the preference'),
                                   'value' => new external_param(PARAM_RAW, 'The value of the preference'))),
                         'User preferences', false),
                     'customfields' => new external_multiple_structure(
                         'customfield', new external_single_structure(
                             array('type'  => new external_param(PARAM_ALPHANUMEXT, 'The name of the custom field'),
                                   'value' => new external_param(PARAM_RAW, 'The value of the custom field'))),
                         'User custom fields', false)
               )
           )
       );
   }
   public static function create_users(array $users) {
       $params = self::validate_prameters(self::create_users_parameters(), array('users'=>$users));
       
       foreach ($users as $user) {
           // actual implementation for creating one user goes here
       }
   }
   public static function create_users_returns() { // BIG concerns here. See below.
       return new external_multiple_structure(
           'userids', new external_single_structure(
               array('id' => new external_param(PARAM_INT, 'id of the created user'))
           )
       );
   }

}

Changes from original code are:

  • Small change in structure names. external_bulk_array renamed to external_multiple_structure and external_assoc_array to external_single_structure (note that structure can be changed to anything else. The important bits are the single/multiple ones vs. the assoc/bulk.
  • Small change in external_multiple_structure so it has one 1st parameter giving one name to the structure.
  • The xxxx_parameters() function will return directly one single or multiple structure, saving one nesting level from original code.
  • Reuse as much as possible:
    • The moodle_group_external is a clear example. Once we have the single service implemented (add_member), it's pretty easy to build its multiple alternative. Just reuse the single parameters definition and the single executor. IMO we should always enforce the single/multiple duality.
    • Also, although there isn't an example, I think we could have some "habitual" structures predefined somewhere, see for example, the preference or the customfield single structures (or the whole preferences or customfields multiple ones if you prefer). They are basically the same (name/value pairs). It could be useful to have one predefined "name_value" structure somewhere and use it where necessary (user preferences, user custom fields, various config options...).

Concerns about return functions:

In the example above we have a good example. There are some xxxx_return() functions returning NULLs, others returning multiple structures (and also is possible to return single structures or numbers or whatever).

IMO returned structures should be, always, encapsulated into something constant, call it 'external_response'. Then, within that, we can return some constant information (bool result, string error, whatever...) and some dynamic information (single/multiple structures).

Also, while it's really simple to return results for single structures (for example the add_member() one will return one result ), it's more complex to do so for multiple structures. For example, the add_members() one must return one different result for each groupid-userid added. Imagine you create one client sending 3 pairs (within a multiple structure well formed call). And only one of them is added successfully, other fails because the userid is incorrect, and the 3 pair fails because it already exists. How will the return information be formated? How will the client know the exact status of each pair sent in the multiple request? Should we introduce one mandatory "key" parameter in all the occurrences within one multiple request, in order to get the response properly identified by those keys? Note I don't know if that has been resolved in other place of the WS documentation but I haven't been able to find it.

So, summarising, I like the 3rd alternative with the changes specified above (in order to make it more readable/simple, basically). And I think the "response" (return) part is at least as important as the "request" (parameters) part, and it still needs more work/definition/polishing. Shouldn't be difficult to achieve that.

Hope this helps, ciao --Eloy Lafuente (stronk7) 00:24, 18 September 2009 (UTC)