Blog
México
Chile
Engineering
Diseñando el SDK de tus sueños (y de los míos)

Diseñando el SDK de tus sueños (y de los míos)

Acabamos de re-escribir desde cero nuestro SDK de Python 🐍. Acá te cuento por qué lo cambiamos y las decisiones de diseño que tomamos.

Tabla de Contenidos

Un SDK es un “kit de desarrollo”, que incluye todas las piezas para desarrollar aplicaciones en una plataforma particular. Por ejemplo, si quieres desarrollar algo en Android usas el SDK de Android. En el caso de Fintoc, un SDK es algo así como un wrapper alrededor de la API, que maneja el protocolo HTTP de las llamadas, provee clases para cada recurso y levanta errores cuando las llamadas a la API fallan.

La primera vez que escribí el SDK para una API fue hace más de un año. No trabajaba en Fintoc, pero la idea de una API para conectarme a los bancos junto con la posibilidad de escribir un poco más de open source me llevaron a enfocarme en el desafío de entregarle a los desarrolladores de Node un wrapper para la API de Fintoc.

Dos días después, el SDK de Node estaba listo. Sí, suena increíble, pero en realidad hice una pequeña trampa: ¡el SDK ya estaba diseñado! Durante dos días, me dediqué a hacer lo que llamé un carbon copy del SDK de Python (que había sido recientemente diseñado e implementado), una suerte de traducción literal de un lenguaje al otro. Esta hazaña me significó recibir una polera de Fintoc en la puerta de mi casa 😍 (mi polera más usada de la pandemia).

En ese entonces, Fintoc no era tan grande como es ahora. El SDK debía soportar solamente un par de endpoints y solamente había unos pocos recursos. Como consecuencia, el diseño original SDK tenía funcionalidades que pertenecían más a una demo que a un SDK y gran parte de la interfaz hacia el usuario era rígida, difícil de extender y alejada de la “forma” de la API. Por más de un año el diseño original cumplió fielmente su rol, pero el momento de re-pensarlo se acercaba en el horizonte.

Fast-forward hasta hoy, una de mis responsabilidades como Developer Advocate de Fintoc es mantener los SDKs que ofrecemos a la comunidad. Con este objetivo en mente, emprendí la tarea de diseñar un SDK que todos los desarrolladores quieran usar (e implementarlo en Python, inicialmente). Me propuse diseñar el SDK de tus sueños (y, de pasada, de los míos también).

El SDK de tus sueños

Para diseñar un SDK digno de nuestros usuarios, tenía que asegurarme de diseñar una interfaz consistente pero no limitante, sin funcionalidades ni métodos innecesarios, y lo más similar “en forma” a la API que intenta abstraer.

Interfaz consistente, pero no limitante

Gran parte del por qué del re-diseño del SDK viene de la interfaz existente en el diseño original. Como ejemplo de lo que debía mejorar, veamos cómo conseguir una cuenta bancaria en la versión original:

from fintoc import Client

client = Client(“my_api_key”)

# Conseguir un link
link = client.get_link(“link_token”)

# Conseguir todas las cuentas
accounts = link.find_all()

# Conseguir una cuenta en particular
my_account = link.find(id=”account_id”)

# Conseguir otro recurso a partir del link
print(“F”)

Esa interfaz era razonable cuando un Link solamente podía retornar cuentas bancarias (un Link corresponde a un set de credenciales de acceso a alguna institución, acá puedes leer más sobre el tema), pero ahora un Link puede retornar otros recursos, como facturas o devoluciones de impuestos. ¿Cómo sé qué me está retornando find_all? ¿Cómo extiendo la interfaz a los nuevos recursos? Además, la interfaz para encontrar una cuenta bancaria a partir de un Link (link.find()) no era consistente con la interfaz para encontrar un Link a partir del cliente (client.get_link()).

La solución a todos estos problemas saltó de los hombros de un gigante y aterrizó directo en mi cabeza: los managers de Django. La solución resulta bastante elegante, siendo simultáneamente sencilla conceptualmente: los recursos son accedidos a través de un manager, que es simplemente un objeto que tiene métodos para interactuar con el recurso que maneja (all, get, create, update y delete). Así, todos los recursos tendrían exactamente la misma interfaz, resolviendo el problema de no saber a qué recurso de un Link corresponde el método find_all. Lo mismo que hicimos arriba, ahora se vería así:

from fintoc import Fintoc

fintoc = Fintoc(“my_api_key”)

# Conseguir un link
link = fintoc.links.get(“link_token”)  # el atributo ‘links’ es el manager

# Conseguir todas las cuentas
accounts = link.accounts.all()  # el atributo ‘accounts’ es el manager

# Conseguir una cuenta en particular
my_account = link.accounts.get(“account_id”)

# Conseguir otro recurso a partir del link
tax_returns = link.tax_returns.all()

Ahora, un Link puede retornar todas las cuentas bancarias con la misma interfaz que todas las devoluciones de impuestos, pero siendo explícito respecto a qué recurso se está usando y permitiendo extensibilidad en el futuro sin necesidad de romper la interfaz. Esto también resuelve el problema de la inconsistencia entre la interfaz para encontrar una cuenta bancaria a partir de un Link y la interfaz para encontrar un Link a partir del cliente!

KISS

En el diseño original del SDK, existían algunas funcionalidades que se alejaban de ser estrictamente un wrapper a la API y se acercaban más a ser “demos” de lo que se podría hacer. Un ejemplo de esto sería el método show_accounts de los Links. Este método imprimía en consola algo así:

 #  Name              Holder                        Currency
---  ----------------  ----------------------------  ----------
 0  Cuenta Vista      DANIEL ALEJANDRO LEAL BITRAN  CLP
 1  Cuenta Corriente  DANIEL ALEJANDRO LEAL BITRAN  CLP

Si bien puede ser algo útil para algunos usuarios, ese tipo de funcionalidades no deberían estar en esta capa de abstracción, sino que corresponden a un nivel más alto. Eso significa que hay que mantener más código y que hay que ocupar más “RAM mental” para comprender qué hace el SDK.

Mantener una “forma” similar a la API

La razón de esto es simple: el SDK no debería tener una curva de aprendizaje sobre la que tiene la API. Debería sentirse tan natural como hacer las consultas HTTP, pero sin tener que preocuparse de asuntos propios del protocolo. Y debería ser muy fácil moverse de un SDK a otro (una vez que todos implementen el nuevo diseño 🐳). Así, si la API tiene un recurso nesteado dentro de otro, el SDK debería exponer los recursos de la misma manera. Si el endpoint es el siguiente:

GET /accounts/{id}/movements

Entonces el SDK debería ser así:

account = fintoc.accounts.get(id)
account.movements.all()

Y liberamos la "RAM mental" de nuestros usuarios para que hagan lo que realmente les aportará valor.

El SDK de mis sueños

Ya hablé de cómo diseñé el SDK de tus sueños (o al menos espero que así sea), pero es importante recordar que esta herramienta debe ser mantenida por alguien y me gustaría que extenderla fuera lo más rápido y elegante posible. No basta con que el SDK sea rápido y cómodo de usar ahora, debería mantenerse así por siempre, y para eso hay que pensar.

One manager to rule them all

Lo que más me complicaba era cómo extender los managers. Inevitablemente, no todos los managers funcionan de la misma manera. Algunos requieren solamente algunos métodos, otros requieren lógica adicional luego de hacer fetch de los recursos. Parecía inevitable tener que escribir un montón de código cada vez que se agregara un recurso nuevo a la API. Pese a esto, la idea de escribir la mayor parte del código una sola vez y después re-utilizarlo me daba vueltas en la cabeza sin parar. Una vez más, la solución llegó a mí desde Django, esta vez desde el REST framework. En dicho framework, las vistas pueden ser subclaseadas prácticamente sin agregar código extra y funcionan inmediatamente. Uno solamente especifica el recurso al que hacen referencia como atributo de clase… ¡Y listo! Mi sueño se hacía realidad...

Entonces, escribí un template method. Eso que en diseño detallado de software pensé que era interesante pero que nunca usaría, había llegado a mi rescate. El principio es simple: escribo la lógica de los métodos para interactuar con la API en una superclase (los métodos all, get, create, update y delete) y en ellos “incrusto” métodos y atributos que después puedo sobreescribir en las subclases para extender el comportamiento sin reescribir los métodos mismos… ¡Y funcionó! Crear un nuevo manager ahora es tan simple como lo muestro acá:

from fintoc.mixins import ManagerMixin

class MyCustomResourceManager(ManagerMixin):
   resource = “my_custom_resource”  # Serializa objetos MyCustomResource
   methods = [“all”, “get”]

¡Así tal cual! Solamente tengo que subclasear un ManagerMixin y definir los atributos de clase resource y methods en dicha subclase. El atributo resource ayuda al manager a serializar los recursos que consigue de la API y el atributo methods define qué métodos puede o no llamar la subclase (entre los métodos all, get, create, update y delete). También se pueden re-definir los métodos post_all_handler, post_get_handler, post_create_handler, post_update_handler y post_delete_handler, que se ejecutan después de que el manager interactúe con los recursos en la API:

from fintoc.mixins import ManagerMixin

class MyCustomResourceManager(ManagerMixin):
   resource = “my_custom_resource”  # Serializa objetos MyCustomResource
   methods = [“all”, “get”]

   def post_get_handler(self, object_, identifier, **kwargs):
       print(f“Finish getting the ‘{object_.id}’ resource.”)
       return object_

En este snippet, el código de post_get_handler se ejecutará después de ejecutar el método get.

Ideas Finales

Si llegaste hasta acá, probablemente me conoces disfrutas tanto como yo pensando acerca del diseño del software que escribes. ¡Te invito a mirar el repositorio! Si encuentras errores o cosas que mejorar, ¡déjanos una issue! Ojalá que el nuevo diseño del SDK sea entretenido de leer y quizás te deje una idea o dos para el diseño de tu próximo proyecto 🐳

Como última reflexión, me quedo con que diseñar herramientas es un desafío interesante, porque hay que razonar acerca de las interfaces tanto externas como internas. Los usuarios quieren una interfaz limpia, simple y poderosa, mientras que los mantenedores quieren una interfaz con niveles de abstracción adecuados y extensibles.

Y escribir estas herramientas en modalidad open source es un desafío aún más interesante, porque queremos que cualquiera pueda leer, usar, aportar y extender nuestras librerías, lo que nos obliga a escribir código comprensible, elegante y bien documentado. ¡Todos ganamos!

Como probablemente ya notaste, para nosotros es extremadamente importante que la experiencia de uso de Fintoc sea lo más cómoda posible. Queremos que nuestros usuarios disfruten haciendo sus aplicaciones y que no tengan que gastar tiempo en cosas innecesarias. Queremos que ellos quieran usar Fintoc. Por lo mismo, te invito a probar el SDK nuevo, estoy seguro de que lo disfrutarás. Si tienes feedback sobre esta herramienta, puedes enviarlo a feedback@fintoc.com 💖

Escrito por
Daniel Leal
Software Engineer
Cada pago importa
Con Fintoc puedes recibir, mover y gestionar todos los pagos de tu negocio
Habla con nosotros
Artículos relacionados