Apr 22 2008
Requerimientos y limitaciones en las funciones de la API
Cuando se empieza una API de estas características es muy importante definir bien las líneas generales antes de empezar. En nuestro caso pretendemos crear una capa que nos permita acceder a Moodle de forma centralizada. Normalmente, ésto se realizaría usando directamente la funciones de Moodle, sin embargo existen algunos requerimientos adicionales para las funciones que se deben tener en cuenta:
- Las funciones deben ser suficientemente simples para que pueda ser usada por lenguajes de programación simplificados (J2ME, C# Mobile, Javascipt…) y dispositivos limitados (móviles, reproductores de MP3…).
- Las funciones deben ser genéricas para que sirvan de soporte a aplicaciones de alto nivel (gestiones académicas, interfaces de usuario alternativas…) y den acceso a todas las funcionalidades del sistema.
- Los datos se tienen que poder usar en cualquier lenguaje de programación estructurado. Pero, a su vez, tienen que ser útiles y servir para minimizar el número de llamadas a la funciones.
- El resultado de las funciones debe ser útil, completo y mínimo. Los resultados complejos deben contener aquello que se necesita, sin devolver datos superfluos o poco usados pero sin obligar a que el cliente realice llamadas extras para conseguir más información básica.
- Las estructuras de datos serán reusables y su número finito. Se debe evitar que existan más de dos estructuras para representar un mismo concepto.
- Se debe garantizar el acceso a la API mediante varios tipos de conexión externa (SOAP, JSON…) y también mediante la inclusión directa en PHP desde el propio sistema.
- Las funciones de la API tienen que basarse en las propuestas por Moodle.
Todas éstas limitaciones nos obligan a replantearnos muchas de las funciones de Moodle. Sin embargo, ésto es un aspecto positivo del proyecto, que nos brinda la posibilidad de ver Moodle desde un nuevo punto de vista y dar coherencia a todas partes del código.
Definición de los casos
Para cumplir los requerimientos y, al mismo tiempo, cubrir las necesidades de Moodle, hemos decidido diferenciar dos casos:
- La conexión directa desde PHP (en el mismo servidor)
- Cualquier método de conexión remota (SOAP, XML, Json…)
Pese a que ambos casos usarán las mismas funciones, cuando la llamada se realice desde conexión directa se permitirá más libertad en el paso de parámetros. El motivo es muy simple, PHP es un lenguaje interpretado que deja manga ancha a los programadores: parámetros optativos, tipos de datos no declarados explícitamente… en cambio, los protocolos de interconexión como SOAP están pensados para lenguajes más restrictivos como Java y, en consecuencia, no permiten muchos de los informalismos de PHP.
Limitaciones en la conexiones remotas
En principio las conexiones remotas deben permitir el acceso a la API mediante cualquier protocolo. Sin embargo, existe un par de condiciones que de deben cumplir en pos de conseguir una API eficiente:
- Orientación a objetos, o que pueda trabajar con datos estructurados como estructuras y matrices (”Structs” y “Arrays”).
- Que permita que el resultado de las funciones sean estructuras complejas; vectores, estructuras o vectores de estructuras.
Una vez cribados los protocolos, toca fijarnos en las limitaciones que se impondrán en las llamadas remotas. Para ello debemos fijarnos en aquellos protocolos y lenguajes estructurados que menos flexibilidad dejan al programador. Podemos decir que se va a mínimos, pero siempre teniendo en cuenta que las siguientes limitaciones sólo se aplican a las conexiones remotas, no a la directa en PHP.
Punto remoto 0: declaración de las funciones
En PHP las variables y funciones no se declaran. De echo, cuando se incluye un archivo de código, se puede suponer que se tiene acceso a todas la funciones y que los parámetros pueden pasarse sin necesidad de especificar el tipo.
Por desgracia, no ocurre los mismo con la mayoría de los lenguajes, donde es necesario declarar las funciones (los “.h” de c, por ejemplo) y los parámetros van asociados a un tipo (en Java, “int”, “boolean”…). De hecho, todos los protocolos de llamada remota requieren que las funciones invocables estén declaradas y los tipos especificados.
Por éste motivo, cada servicio dispondrá de una función especial llamada “mdl_nombreservicio_info” (a partir de ahora la llamaremos función “info”) donde las funciones públicas estarán declaradas mediante una estructura de datos. Ésta primera limitación es tan importante que se detallará en próximos artículos.
Punto remoto 1: un único tipo por parámetro
Cada parámetro de las funciones sólo podrá ser de un tipo de dato. Pese a que en algunos lenguajes de alto nivel es común que no sea así (por ejemplo que un parámetro pueda ser un entero para tratar un único identificador o un vector para tratar un conjunto) sería erróneo generalizar y, por tanto, es obligatorio especificar un único tipo en la función “info”.
Punto remoto 2: todos los parámetros son obligatorios
Los parámetros optativos son bastante habituales en los lenguajes interpretados (Python, Perl, PHP…) y otros usan polimorfismo para simularlos (Java, C#…), sin embargo el resto no disponen de ésta posibilidad, así que todos los parámetros que se especifiquen la función “info” deben ser obligatorios.
Punto remoto 3: limitaciones en los tipos de datos complejos
pese a que en la función “info” se puedan declaras tipos de datos complejos, sólo se permiten:
- vectores (”arrays”) de elementos simples (enteros, booleanos, cadenas…)
- estructuras (”structs”) de elementos simples
- vectores de estructuras
Por ejemplo, serían válidos:
- un vector de enteros
- una estructura llamada persona formada por una cadena de caracteres nombre (”String”) y un entero zapato con el número de zapato.
- un vector donde cada elemento es de tipo persona.
Y NO serían válidos (los errores están rallados):
- una estructura familia formada por la cadena dirección y
un vector donde los elementos son de tipos personay que se llama miembros. (No se permite que una estructura contenga un vector) - un vector donde cada elemento contiene
un entero y un booleano(La solución seria declarar una estructura M con un entero y un booleano y que los elementos del vector fuesen de tipo M). - una estructura llamada principal que contenga un entero llamado simpatía y una estructura de tipo persona llamada sujeto (No se permite que una estructura contenga otra estructura).
El motivo de ésta limitación es garantizar que la mayoría de los protocolos se podrán conectar a la API sin problemas. Si se permitieran estructuras dentro de estructuras la penalización de la serialización y deserialización recursiva de las variables podría llegar a ser excesiva.
Punto remoto 4: evitar los parámetros complejos
Podemos suponer que todos los clientes potenciales de los webservices podrán usar parámetros complejos en las llamadas pero, como norma general, a costa de complicar el código. En consecuencia, se intentarán evitar en la medida de los posible.
En caso que no sea posible, es recomendable que los parámetros complejos sólo sean vectores de tipos simples (enteros, booleanos…). Por parte del cliente, montar un vector simple no supone un gran coste, sin embargo, montar una estructura con una especificación remota (por ejemplo si están definidas en el WSDL de SOAP, y no en el cliente) puede ser complicado y puede generar más acoplamiento del necesario.
Ésta recomendación sólo se aplicará a los parámetros, pero no a los resultados (o retornos). Si se intenta que todos los resultados sean simples, lo más probable es que se acaben codificando funciones incompletas que requieran de más llamadas por parte del cliente. Así que no hay ningún problema en devolver resultados complejos siempre que cumplan el punto remoto 3 y que se declaren en la función “info”.
Punto remoto 5: un único resultado y siempre del mismo tipo
Son muy pocos los lenguajes donde el tipo de dato del retorno puede variar arbitrariamente (por ejemplo devolver un objeto complejo o un identificador entero según se necesite) y muchos menos los que permiten que las funciones devuelvan más de un resultado.
Sin duda, sería un error que la API fuese permisiva en éste aspecto, así que todas las funciones tendrán un máximo de un retorno y su tipo siempre será el mismo (que será especificado en la función “info”). Además, pese a que no es obligatorio que las funciones devuelvan algo, seria recomendable que así fuese.
Limitaciones en la conexión directa PHP
Técnicamente, no existe ninguna limitación para que las funciones de la API no sean parecidas a las internas de Moodle. Sin embargo, todas las funciones deben ser invocables también desde las conexiones remotas, así que es conveniente tomar ciertas precauciones:
Punto directo 1: se permiten los parámetros optativos
Pese a que en las conexiones remotas no se permiten los parámetros optativos por limitaciones técnicas (punto remoto 2), prohibirlo en la conexión directa PHP sería limitar mucho el trabajo de los programadores y, en consecuencia, está permitido usar valores por defecto en los parámetros de las funciones.
En cualquier caso se debe asegurar que, como mínimo, aquellos parámetros que no tengan un valor por defecto estarán especificados en la función “info”. Es decir, en caso que en conexión directa se permita llamar a la función con más parámetros (algunos de ellos optativos), los obligatorios deben aparecer en la función “info”. Igualmente, cuando en conexión directa se permitan menos parámetros (por ejemplo porqué algunos valores se recogen de la sesión en caso que no se especifiquen), los optativos restantes también aparecerán “info”.
Punto directo 2: el retorno será siempre del mismo tipo
Como se ha explicado en las conexiones remotas, los retornos de una función serán siempre del mismo tipo (punto remoto 5) y tendrán limitaciones (punto remoto 3). Ésto también afecta la conexión PHP, donde será obligatorio.
Pese a que pueda parecer un planteamiento extremo, es importante que se cumpla ésta limitación. Si se permitiera devolver resultados distintos cuando se ejecuta en conexión directa o remota, estaríamos creando acomplamiento entre los conectores y la API porqué algunas partes de código serían específicas dependiendo de la conexión. Con ésta limitación se evita.
A primera vista se podría pensar que el parágrafo anterior se contradice con el punto directo 1. Sin embargo, si analizamos más a fondo, podemos ver que el punto directo 1 sólo explota los parámetros optativos de PHP y no crea acoplamiento alguno, dado que no funciona distinto según el tipo de conexión. De éste modo, si en un futuro se implementa un nuevo tipo de conector que permita parámetros optativos, el código no se modificaría en absoluto.
Punto directo 3: comprobaciones de tipo de dato en los parámetros
Para mantener las especificaciones impuestas por Moodle, es necesario permitir que los tipos de dato de los parámetros puedan ser distintos según interese (por ejemplo, pasar un entero para tratar un único elemento o vector para realizar un tratamiento masivo). Éste uso de las variables es común en PHP, sin embargo la mayoría de los lenguajes no lo soportan.
Sin duda, permitir ésto provoca un cierto grado de acoplamiento entre la API i los conectores. Por éste motivo es IMPRESCINDIBLE que la comprobación de tipos no provoque replicación de código, dado que ésto sólo limitaría la escalabilidad de la arquitectura.
Por ejemplo, supongamos que tenemos la función “mdl_user_get_users($userids)” donde el parámetro puede ser un simple entero para conseguir un usuario, o un vector de enteros para conseguir varios:
En éste caso el código se puede plantear de dos formas: suponiendo inicialmente que $userids es un vector o, por el contrario, un único entero. Pese a que las dos funciones serían parecidas y ambas cumplirían las especificaciones, la segunda opción requiere replicación y, por tanto, NO sería válida para la API.
Opción A: suponiendo que es un vector, correcta para la API:
function mdl_user_gest_users ($userids) {
if (!is_array($userid)) $userids = array($userids);
$res = array();
foreach ($userids as $userid) {
(... conseguir el usuario y ponerlo en res ...)
}
return $res;
}
Opción B: suponiendo que es un entero, inválida para la API (la parte replicada está rallada):
function mdl_user_gest_users ($userids) {
$res = array();
if (!is_numeric($userid)) {
(… buscar el usuario y ponerlo en res …)
} else {
foreach ($userids as $userid) {
(… conseguir el usuario y ponerlo en res …)
}
}
return $res;
}
Se permitimos que se replique código en las funciones, a la larga sería imposible mantener los dos códigos actualizados y la posibilidad de cometer errores de copia y pegar (”cut and paste”) aumentaría dramáticamente. Por éste motivo es prioritario que se cumpla esté punto y, en caso que no sea posible, se debe evitar el uso de parámetros con tipos variables.
