Consejos para diseñar API's en PHP

Consejos para diseñar API’s en PHP

Publicado Por | 18 septiembre , 2013 | Blog Adrenalina | One Comment
api1

El lenguaje PHP ha sufrido muchos cambios en los últimos años. Desde el gran paso de la orientación a objetos hasta el uso de namespaces. Cada vez más desarrolladores de PHP usan un IDE y estructuran sus proyectos orientados a objetos. Esto nos lleva a que la comunidad de desarrolladores va produciendo un código cada vez mejor y más entendible. Parece que ha quedado atrás eso de que una aplicación web en PHP sean una serie de archivos con una mezcla de html, css, js, sql, con código php mezclado, y un par de clases para ‘estructurar’. Aunque, por supuesto, sin escarbar mucho puedes encontrar todavía este tipo de código.

En general, todos estos cambios son para bien. La cantidad de desarrolladores PHP senior o expertos va aumentando y se están produciendo cada vez más frameworks y librerías de nivel. Todavía quedan puntos flacos en la reutilización de código de frameworks o librerías, sobre todo por la falta de convención de nombres en el uso de namespaces. En este artículo intentaré explicar qué es una API, cuáles son sus objetivos y algunos consejos para tener en cuenta en su diseño.

Qué es una API?

Antes que nada debemos tener claro qué es una API y cuál es su objetivo. Una API es un conjunto de reglas y especificaciones concretas que un desarrollador puede seguir para acceder y usar servicios y recursos proporcionados por otro programa, componente o librería. Para decirlo de otro modo, es una interfície entre diferentes piezas de software, y facilita su interacción.

En PHP 4 (o librerías en programación estructurada), la API se define por las funciones que se declaran para el uso de esa librería. Se describe por los nombres y estados globales que usa la librería para trabajar. En general, las API’s basadas en librerías de funciones son bastante simples de entender.

Una API orientada a objetos es un poco más compleja. Cuando diseñas un componente o librería orientada a objetos, generalmente estás construyendo 2 API’s a la vez (o tu API se podrá usar de 2 maneras). Esto es gracias a la naturaleza de los lenguajes orientados a objetos, si se usan clases abstractas e interfícies.

El primer tipo de API, el más común, es la API de acceso. Está pensada para cumplir el objetivo: “¿Qué necesita la gente que usa esta API (en la mayoría de casos)?”. Un ejemplo de uso de este tipo sería el siguiente:

$a = new UnaLibreria\UnComponente\UnComponente($opciones);
$a->setAdapter(new UnaLibreria\UnComponente\Adapter\UnAdapter($opcionesAdapter));
$elResultado = $a->hacerAlgo();

Como se puede comprobar, es una manera simple y llana de acceder a lo que la API ofrece públicamente. Una API bien diseñada debería permitir, solo examinando sus elementos, deducir su funcionamiento sin tener que consultar su documentación. Cuando eso es posible, la API se convierte como una historia que sigue su curso.

Como decíamos, el objetivo de la API es cumplir con la mayor parte de casos de uso posibles y dar solución a la mayoría de necesidades. A pesar de eso, existen una minoría de casos que no se pueden contemplar, ya que al intentar generalizar los procesos, hay casos específicos que se pueden quedar sin contemplar. Para ello existe el segundo tipo de API: la API de extensión.

El paradigma de orientación a objetos permite cubrir estos casos minoritarios, gracias al uso del polimorfismo y la sobreescritura. La herramienta principal es la sobreescritura de métodos. Para que esto sea posible, los casos base o las funcionalidades de la API serán sobreescritos para cubrir la funcionalidad específica que se necesita. Por ejemplo, podríamos tener el siguiente código:

namespace MiLibreria\UnComponente\Adapter; // Mi componente
use UnaLibreria\UnComponente\Adapter\UnAdapter; // El componente de la API
// Extender el componente de la API con un uso especial
class MiAdapter extends UnAdapter
{
protected function _unaFuncionalidad()
{
// Hacer algo especial para mi caso de uso
return parent::_unaFuncionalidad(); // El método protected de la clase padre
}
}

Como se puede ver, hemos extendido la funcionalidad del Adapter base de la librería original con nuestra funcionalidad. Esto es posible porqué la lógica que necesitamos se encuentra dentro de un método protected. Esto es lo que nos permite usar la sobreescritura para extender el código para ajustarnos a nuestras necesidades concretas. Este tipo de API de extensión es posible al definir todos los métodos y propiedades como protected para que se puedan usar en las clases hijas.

Filosofía de la API

Es difícil cuantificar un aspecto o otro del diseño de una API sin hablar primero de su filosofía. Teniendo en cuenta la gran cantidad de frameworks y librerías que existen actualmente, una división fácil es entre las que están bien escritas y las que no. Incluso las que no están bien escritas, se las puede clasificar por la filosofía que siguen.

Existen dos objetivos principales en las filosofía de la mayoría de librerías o componentes. Dependiendo de la perspectiva, podrían llegar a ser contradictorios. En general, debemos considerar que los dos son igual de importantes.

El primero es la facilidad de uso. El éxito que puede tener una API entre desarrolladores, en general se determina por su facilidad de uso, si es intuitiva y si cumple con la mayoría de las necesidades. El otro es la facilidad de extender. En la mayoría de casos, un componente se escribe para cubrir unos casos de uso conocidos. Generalmente, eso cubrirá la mayoría de necesidades de cualquier programador, pero siempre hay casos de uso desconocidos. La habilidad de un componente para dar solución a los casos conocidos así como permitir al desarrollador extenderlo para llegar a los casos no conocidos marcará su facilidad de extenderlo.

A menudo, la facilidad de uso y la de extensión acaban siendo contrarios. Las cosas fáciles de usar son difíciles de extender, y las fáciles de extender son difíciles de usar. Normalmente la causa de esto es porqué al intentar mejorar uno de los dos aspectos es en detrimento del otro.

Volviendo a la filosofía de las API, la clave está en encontrar el equilibrio entre estos dos objetivos.

Un par de trucos para mejorar las API’s

Existen muchas recomendaciones de como diseñar y mejorar una API. A continuación remarcamos una muestra.

  • Usar una nomenclatura común para los namespace y clases

Aunque PHP no cuenta con un sistema predefinido de packaging o importación basada en ficheros, el autoloader de PHP, con la ayuda de algunas convenciones reconocidas, puede ofrecernos la solución. Grandes proyectos como Zend Framework, Symfony, PHPUnit o FuelPHP han ayudado a reafirmar las convenciones marcadas por PEAR sobre los estándares de nomenclatura. Usando este esquema de nombres, tu código será familiar a desarrolladores que ya hayan usado este tipo de esquema en otros proyectos. La ventaja es que esos desarrolladores ya sabrán donde encontrar exactamente las clases dentro de la estructura de directorios.

namespace MiLibreria\MiComponente;
class UnaClase 
{
// se encontrará relativa al include_path 
// o algun otro path cargado usando el autoloader, 
// en el archivo MiLibreria/MiComponente/UnaClase.php
}
  • Evitar cargar mucho el constructor

El objetivo es permitir a quien use la API dar la información que sea necesaria en el momento de instanciar el objeto y no limitarlo en este aspecto. Por ejemplo, una estructura que permitiría esto sería:

class UnaClase
{
public function __construct($opciones = null)
{
if (is_array($opciones)) {
$this->setOpciones($opciones);
} elseif (is_string($opciones)) {
$this->setElValorConocido($opciones);
}
}
}

Generalmente, la llamada a setOpciones acabará llamando a un conjunto de setters o guardará la información pasada en las opciones. Lo importante es que en el momento de la instanciación quien usa la API no tiene que llenar todo lo necesario en la clase. La importancia de esto es poder invertir el orden en el cual se cargan los datos u objetos necesarios para la clase principal. Veamos un ejemplo:

// Ejemplo 1
// asumiendo: class Foo { __construct(A $a, B $b, C $c) {} }
$a = new A($aOpcion1, $aOpcion2);
$b = new B();
$c = new C($cOpcion, $a);
$foo = new Foo($a, $b, $c); // y finalmente
$foo->haceAlgo();
/** ALTERNATIVAMENTE **/
// Ejemplo 2
// asumiendo: class Foo { __construct($opciones = null) {} }
$foo = new Foo(array(
'a' => ($a = new A($aOpcion1, $aOpcion2)),
'b' => new B(),
'c' => new C($cOpcion, $a)
));
$foo->haceAlgo();
// Ejemplo 3
// o mejor:
$foo = new Foo();
$a = new A($aOpcion1, $aOpcion2);
$foo->setA($a)
->setB(new B())
->setC(new C($cOpcion, $a));
$foo->haceAlgo();

La diferencia es que en el ejemplo 1, aunque el objetivo de nuestro caso de uso se ejecute en la clase Foo, el desarrollador está forzado a interactuar primero con las dependencias. Al contrario, los ejemplos 2 y 3 muestran que nuestro objetivo Foo se crea al inicio, y las dependencias se tratan después de la instanciación. Si el objetivo es la claridad del código, leerlo en los ejemplos 2 y 3 tiene más sentido que en ejemplo 1, ya que la API permite al desarrollador escribir su caso de uso de arriba a abajo como una historia.

  • Evitar final y private

Esto es para mejorar la extensión del código. A menos que se pretenda restringir a que el usuario use algo en concreto en su caso de uso, no es recomendable marcar elementos como final o private. Tarde o temprano, alguien necesitará sobreescribir un método que has implementado para lo que sea. Lo mejor que podemos hacer es proporcionar un conjunto que cumpla con la mayor parte de las necesidades, y que se pueda extender para cubrir el resto. De esta manera, quien usa la API no se ve forzado a parchear el código original.

 Resumen

Esto es solo una aproximación general al diseño de una API, son los primeros pasos, pero es importante conocerlo al iniciar una API de manera que tengamos los objetivos claros desde el inicio. Si se cumplen los objetivos, probablemente nos ahorraremos muchos dolores de cabeza en el futuro (y se los ahorraremos a otros).

 

Autor: Marçal Panareda

Estudiamos y analizamos su negocio en profundidad, definimos objetivos y planteamos la estrategia de marketing más adecuada centrándonos en conseguir cada uno de los objetivos propuestos. Solicita Presupuesto Ahora

Uso de cookies

En este sitio web utilizamos cookies propias y de terceros para mejorar nuestros servicios, para que usted tenga la mejor experiencia de usuario y analizar su visita. Si continúa navegando está dando su consentimiento para la aceptación de las mencionadas cookies y la aceptación de nuestra política de cookies, pinche el enlace para mayor información.