Conéctate con el mundo exterior: Las mejores prácticas para usar APIs

connecting-with-the-outside-world-api-best-practices

Llega un momento como desarrollador en el que debes integrar una aplicación con un servicio externo (JIRA, Toggl, Slack, Github, WordPress, entre otros). Puedes utilizar REST API, GraphQL, SOAP, o el protocolo que el servicio en cuestión utilice para este fin. Este artículo no es sobre cómo utilizar un protocolo en específico sino que contiene recomendaciones generales que he descubierto que facilitan el desarrollo cuando debes conectar tu aplicación a uno, o más, servicios externos. Para facilitar la lectura, usaré el término «API» para referirme a estos protocolos; pero las recomendaciones son generales por lo que puedes utilizarlas con cualquier protocolo sobre el que te debas conectar. Así mismo, todos los ejemplos están escritos en python, pero los conceptos deberían poderse implementar en cualquier lenguaje de programación.

Así que ¡Empecemos!

Lee la documentación

Empieza por el principio. Generalmente, el mejor lugar para empezar a desarrollar es la documentación del servicio en cuestión. Si el servicio con el que te necesitas integrar tiene una manera de consumir sus datos, tendrá documentación relacionada. Algunas documentaciones serán suficientes, otras no lo serán; pero todas las documentaciones te mostrarán, por lo menos, cómo usar la API, cómo funciona su autenticación y qué puedes y no puedes hacer con esta.

Sé que la recomendación es muy obvia, pero es increible la cantidad de desarrolladores que nunca leen la documentación sino que van directamente a foros como Stack Overflow o al primero resultado de Google. Este tipo de foros son muy útiles y casi siempre tienen información relevante; pero la documentación es el único lugar donde encontrarás cómo debe ser usada la API desde la perspectiva de los desarrolladores que la implementaron. Ellos son quienes la desarrollaron, así que, en primer lugar, es mejor seguir sus recomendaciones que las recomendaciones de MasterHamster98 en Stack Overflow.

A su vez, si el servicio es pago, seguramente tienen un canal de soporte. Envíales un mensaje pidiendo asesoría, puedes recibir información valiosa por este medio.

Buscar paquetes o librerías existentes

Es muy probable que anteriormente otros desarrolladores hayan necesitado integrarse a la API del servicio en cuestión o que hayan creado una librería o un SDK que les facilitara el proceso. Así mismo, si el servicio es lo suficientemente grande, puede que hayan creado SDKs para diferentes lenguajes. Busca dichos paquetes antes de empezar a desarrollar. Usualmente reducen la cantidad de código necesario, brindan ejemplos, tienen pruebas automatizadas para asegurar que el código funciona y tienen algún tipo de soporte. También tienen manejo de errores, por lo que no tendrás que esforzarte mucho a la hora de devolver un mensaje de error en caso de que algo falle con la integración. Al usar estos paquetes, no necesitas empezar tu integración de cero.

Por ejemplo, puedes encontrar SDKs para python para Slack, JIRA, Toggl, GitHub, y muchos más servicios.

Si no encuentras un paquete para integrar el servicio en cuestión, puede ser una buena oportunidad para crear un proyecto open source y contribuir a la comunidad de desarrolladores ¡Depende de ti!

Crea una clase para encapsular tu código

Probablemente necesitarás obtener una gran cantidad de información desde la API del servicio. Por esta razón, utilizarás más de uno de sus métodos, o endpoints. Así, tu código puede salirse de control si no lo manejas de la manera adecuada. La solución que más me ha servido para este dilema es crear una clase que se encargue de mantener el código limpio y organizado.

Utiliza el método init de la clase para inicializar todos los requerimientos de la  API, como el Token de autorización, la url del servicio y otros requerimientos que pueda tener.

class ServiceConnector(object):
    """
    Class in charge of retrieving information
    from Service
    """
    def __init__(self):
        self.headers = {'Authorization': settings.SERVICE_API_KEY}
        self.url = "{}/api/v1/".format(settings.SERVICE_API_URL)

Usar una clase te permitirá saber qué hace tu código, qué no hace y qué no se está usando. La recomendación es crear métodos con nombres dicientes y docstrings que te permitan saber qué función cumple cada método.  Así mismo, mantén los métodos tan sencillos como sea posible. Es mejor tener muchos métodos que hagan una o dos cosas que tener un solo método qué esté a cargo de todo. Veámoslo con un ejemplo.

from service_idk import Service
from .skeletons import ValueSkeleton
class ServiceConnector(object):
    """
    Class in charge of retrieving information
    from Service
    """
    def __init__(self):
        self.service = Service('MySecretToken')

    def calculate(self):
        returned_data = self.service.get_data()
        total = 0
        spent = 0
        for data in returned_data:
            value = ValueSkeleton()
            value.total = data['total_days']
            value.price = data['money']['amount']
            total += value.total
            spent += value.price
        average = spent / total
        return average
from service_idk import Service
from .skeletons import ValueSkeleton
class ServiceConnector(object):
    """
    Class in charge of retrieving information
    from Service
    """
    def __init__(self):
        """
        starts service opbject with the API token
        """
        self.service = Service('MySecretToken')

    def get_information_from_service(self):
        """
        uses service.get_data to retrieve
        the important data from service.
        Returns the date normalized
        """
        returned_data = self.service.get_data()
        return [self.normalize_data(data) for data in returned_data]

    def normalize_data(self, data):
        """
        Gets data from service and normalizes it
        to match ValueSkeleton format
        """
        value = ValueSkeleton()
        value.total = data['total_days']
        value.price = data['money']['amount']
        return value

    def calculate_spent_average(self):
        """
        gets the spent average for the total days
        from the data retrieved from service
        """
        returned_data = self.get_information_from_service()
        total = 0
        spent = 0
        for data in returned_data:
            total += data.total
            spent += data.price

        average = spent / total
        return average

Veamos el primer ejemplo. Puede que obtengas lo que necesitas, el valor promedio, pero solo podrás obtener esa información. Si necesitas usar los datos obtenidos directamente u obtener otro tipo de información, como el total, no podrás hacerlo. En cambio, en el segundo ejemplo, puedes obtener el valor promedio pero también puedes obte otro tipo de información, como los datos obtenidos, sin mayor esfuerzo.

¡No te asustes con los esqueletos!

Tal vez notaste en el ejemplo anterior que utilicé un ValueSkeleton así que te explicaré qué es. El concepto de esqueleto, Skeleton, me lo dio Andrés González hace tres años cuando apenas estaba empezando a trabajar en Swapps. Mi primer proyecto fue un sistema de reportes que integraba información de muchos de los servicios externos que la empresa utiliza. Este proyecto sigue activo y yo sigo a cargo de él.

Entonces, Andrés me dijo «Jose, crea un Skeleton para saber qué información necesitas de cada objeto que recibirás de la API». Yo estaba dando mis primeros pasos como desarrollador así que seguí la instrucción ciegamente. Lo que no sabía en ese entonces y sé ahora es que ese consejo me ayudaría muchísimo en el futuro. Incluso me permitió integrar otros servicios con información similar sin mayor esfuerzo.

Básicamente, un Skeleton es una clase que contiene las propiedades que vas a usar del objeto que retorna la API. A su vez, es una manera sencilla de proveer valores por defecto, métodos relacionados al objeto, etc. Este concepto también se conoce como InterfacesModels (No confundirlos con los modelos de una base de datos, son conceptos diferentes)

Veamos un ejemplo.

Sé que es un ejemplo sencillo, pero una vez utilices este concepto en una implementación mayor, entenderás su poder.

class ValueSkeleton(object):
    """
    Normalized version of value
    """
    total = 0
    price = 0
    def __str__(self):
        return self.total

Este concepto es especialmente útil para evitar ruido proveniente de la API. Generalmente, las API devuelven mucha información, pero solo necesitamos utilizar algunos de los campos retornados. Usando el Skeleton, sabes exactamente qué información vas a usar y qué tipo de variable es. También, evitas cargar objetos gigantes en la memoria cada vez que se utiliza la integración. Finalmente, es una herramenta muy poderosa si vas a consumir información similar de servicios diferentes y quieres procesarla de igual manera siempre.

¡Lo desconocido está ahí para conquistarlo, no para temerle!

Sé que integrar servicios externos en tu aplicación puede ser aburrido, complicado o incluso casi imposible cuando la API del servicio no es tan buena como uno quisiera. Sin embargo, he encontrado que estas recomendaciones facilitan el desarrollo de este tipo de integraciones. A su vez, estas recomendaciones ayudarán a otros desarrolladores de tu equipo a entender la integración de una mejor manera. Considerando que en unos cinco o seis meses tú mismo podrías ser considerado otro desarrollador frente a la integración, te agradecerás bastante en caso de que tengas que  retomar el desarrollo de la integración luego de este tiempo.

!Espero que mis recomendaciones te ayuden la próxima vez que necesites integrar un sistema externo a tu aplicación!