Tips esenciales para el diseño e implementación de APIs

Mary Andrea Doria Gomez
Pragma
Published in
9 min readApr 23, 2024
Una API exitosa garantiza la seguridad, optimiza el tráfico y ofrece una experiencia fluida al desarrollador. Esto se logra definiendo de forma clara los recursos, manteniendo operaciones eficientes y controlando excepciones

La implementación de un API funcional, eficiente y segura son elementos cruciales en el desarrollo de software moderno. Diseñar una API que cumpla estas 3 características no solo facilita la integración, escalabilidad y mantenimiento, sino que también mejora la experiencia del desarrollador y fomenta la adopción de terceros. Te presentaré a continuación 5 tips esenciales para diseñar e implementar APIs que sean funcionales, eficientes y seguras.

Iniciemos con algunos conceptos fundamentales:

  1. Recursos: Las APIs se estructuran alrededor de recursos, que representan objetos, datos o servicios accesibles para el cliente. Cada recurso tiene un identificador único (URI) y se manipula mediante operaciones estándar.
  2. Métodos HTTP: Las operaciones en los recursos se realizan utilizando métodos HTTP estándar, como GET, POST, PUT, PATCH y DELETE, cada uno con su significado semántico específico.
  3. Convención de URIs: Es importante establecer una convención coherente para los URIs de los recursos, utilizando nombres plurales que reflejen colecciones y organizándolos en una estructura lógica.
  4. Formatos de datos: Los datos intercambiados entre el cliente y el servidor suelen representarse en formatos estándar como JSON o XML, especificados mediante el tipo de medio (MIME type) en los encabezados HTTP.
  5. Excepciones: Las excepciones en un API son eventos inesperados que interrumpen el funcionamiento normal del programa. Pueden surgir por errores internos del API o cuando el uso del mismo no se ajusta a las condiciones esperadas.
  6. Códigos de estado HTTP: son mensajes que el servidor web devuelve al navegador del cliente para indicar el resultado de una solicitud HTTP
  7. Seguridad: La seguridad es un aspecto crítico en el diseño de APIs, que requiere la implementación de medidas de autenticación y autorización para proteger los recursos y las operaciones de accesos no autorizados.

Para que sea más fácil de entender, vamos con algunos ejemplos dentro del sector bancario, de cada concepto mencionado:

Recursos:

Los recursos en una API de banco podrían incluir entidades como cuentas, transacciones y clientes. Por ejemplo, el recurso “cuenta” podría tener un URI como /cuentas/456, donde “456” es el identificador único de la cuenta de un cliente.

Métodos HTTP:

  • Para ver el saldo actual de una cuenta, se podría utilizar el método GET con una solicitud a /cuentas/456.
  • Para depositar dinero en una cuenta, se podría usar el método POST para enviar una solicitud a /cuentas/456/depositos con la cantidad a depositar.
  • Para actualizar la información de contacto de un cliente, se usaría PUT o PATCH enviando la solicitud a /clientes/789, donde “789” es el ID del cliente.

Convención de URIs:

Siguiendo una convención lógica, /cuentas representaría todas las cuentas bancarias, /cuentas/456 sería una cuenta específica, y /clientes listará todos los clientes del banco. Además, /cuentas/456/transacciones podría usarse para acceder a todas las transacciones relacionadas con la cuenta 456. El objetivo de esta convención es proporcionar una estructura lógica y coherente para acceder a los diferentes recursos en la API

Formatos de datos:

Cuando un cliente solicita el saldo de su cuenta, el banco podría responder con datos en formato JSON:

{
“id”: 456,
“tipo”: “Cuenta Corriente”,
“saldo”: 3500.75,
“moneda”: “USD”
}

o un formato XML:

<Cuenta>
<id>456</id>
<tipo>Cuenta Corriente</tipo>
<saldo>3500.75</saldo>
<moneda>USD</moneda>
</Cuenta>

Excepciones y Códigos de Estado HTTP:

En el siguiente código, estamos intentando procesar una solicitud de depósito en una cuenta específica. Si la solicitud es incorrecta debido a datos faltantes o incorrectos, devolvemos un mensaje de error junto con un código de estado HTTP 400 (Bad Request). Si hay un problema interno en el servidor durante el proceso, devolvemos un mensaje de error con un código de estado HTTP 500 (Internal Server Error).

try:
# Procesar solicitud de depósito
depositar_en_cuenta(456, cantidad)

except SolicitudIncorrectaError:
# Manejar la excepción de solicitud incorrecta
return {“error”: “La solicitud de depósito es incorrecta”}, 400

except ErrorInternoServidor:
# Manejar la excepción de error interno del servidor
return {“error”: “Error interno del servidor”}, 500

Seguridad:

La seguridad es crucial en aplicaciones. La API requiere autenticación mediante JWT (JSON Web Tokens) para cada solicitud. Por ejemplo, las solicitudes a /cuentas/456 necesitan un encabezado de autorización con un JWT válido. Además, la autorización basada en roles varía según sea cliente o empleado, lo que otorgará diferentes permisos de consulta, modificación o eliminación.

Ahora que entendemos estos conceptos podremos comenzar con los 5 tips para el diseño e implementación de nuestras APIS:

  1. Definir los recursos y operaciones de forma clara: Antes de desarrollar una API, es fundamental tener una comprensión clara de los recursos que representará y las operaciones que se podrán realizar sobre esos recursos. Un recurso en el contexto de una API puede ser cualquier cosa, desde un objeto de datos como un usuario o una publicación hasta un servicio como un proceso de pago. Definir estos recursos de manera clara implica identificar qué datos o funcionalidades se expondrán a través de la API y cómo serán accesibles y manipulables por los clientes.
  2. Mantener las operaciones idempotentes: Una operación es idempotente si al aplicarla múltiples veces tiene el mismo efecto que aplicarla una sola vez. En el contexto de una API, esto significa que si un cliente realiza la misma solicitud varias veces, el estado del sistema no cambia más allá del primer intento. Esto ayuda a prevenir problemas como la duplicación de recursos o la ejecución de acciones no deseadas debido a solicitudes repetidas.
  3. Control de las Excepciones: En el desarrollo de APIs, es de suma importancia manejar las excepciones de manera significativa para proporcionar una experiencia consistente al cliente. Esto implica capturar y manejar adecuadamente cualquier error que pueda surgir durante el procesamiento de las solicitudes. Proporcionar respuestas claras y descriptivas en caso de errores ayuda a los desarrolladores a comprender y solucionar problemas de manera más efectiva.
  4. Minimización del tráfico de red:La minimización del tráfico de red es importante para optimizar el rendimiento de la API y reducir el consumo de ancho de banda. Esto se puede lograr implementando técnicas como la caché de datos del lado del cliente para reducir la necesidad de realizar múltiples solicitudes al servidor y el uso eficiente de la compresión de datos para reducir el tamaño de las respuestas enviadas a los clientes.
  5. Autenticación y Autorización: Garantizar la seguridad de la API es esencial para proteger los datos y prevenir accesos no autorizados. La autenticación verifica la identidad del cliente que realiza la solicitud, mientras que la autorización determina si el cliente tiene permiso para acceder al recurso solicitado y realizar la operación deseada. Implementar mecanismos robustos de autenticación y autorización, como JWT (JSON Web Tokens) u OAuth, ayuda a garantizar la integridad y la seguridad de la API.

Vamos con un ejemplo donde podamos agrupar estos 5 Tips:

Imagina que estás trabajando en el desarrollo de un sistema bancario en línea. Este sistema permite a los clientes acceder a sus cuentas bancarias, realizar transferencias de fondos, ver su historial de transacciones y realizar otras operaciones financieras. Para implementar este sistema, decides crear una API bancaria llamada BankApi que proporcione endpoints para interactuar con las cuentas bancarias de los clientes. Esta API permitirá a los clientes autenticarse, ver el saldo de su cuenta, realizar transferencias de fondos a otras cuentas y obtener un historial de transacciones.

  • Se define una clase BankAccount que representa una cuenta bancaria. Esta clase tiene un constructor que inicializa la cuenta con un identificador único y un saldo inicial, y métodos para depositar y retirar fondos. También registra todas las transacciones en un historial.
  • Se define una excepción personalizada llamada InsufficientFundsException, que se utiliza para manejar situaciones en las que un cliente intenta realizar una transacción bancaria sin tener fondos suficientes en su cuenta. Además, introducen la clase BankApi, que es responsable de gestionar las operaciones bancarias del sistema. Esta clase contiene dos variables para almacenar las cuentas bancarias y los tokens de autenticación, inicializadas mediante un constructor que crea nuevas instancias de HashMap para estos fines.
  • Este método authenticate se encarga de autenticar a un usuario verificando si la cuenta bancaria asociada al accountId existe en el sistema y si el pin proporcionado coincide con el valor esperado “1234” . Si la autenticación es exitosa se genera un nuevo token de sesión utilizando el método generateToken(), y se asocia este token con el accountId en el mapa authTokens. Finalmente, el método devuelve el token generado. Si la autenticación falla, devuelve null.

Con los siguiente métodos podemos gestionar los tokens de sesión dentro de la clase BankAPI

  • isValidToken(String token): Este método privado verifica si un token dado es válido. Comprueba si el token está presente en el mapa authTokens. Devuelve true si el token es válido y false si no lo es.
  • generateToken(): Este método privado genera un nuevo token único. Combina la cadena “TOKEN-” con el tiempo actual en milisegundos utilizando System.currentTimeMillis(). Esto garantiza que cada token generado sea único y difícil de predecir. El token generado se devuelve como una cadena.
  • Este método getBalance devuelve el saldo de una cuenta bancaria si el token de sesión proporcionado es válido y está asociado con el identificador de cuenta proporcionado. Si la verificación falla, se lanza una excepción UnauthorizedAccessException.
  • El método transfer permite mover fondos de una cuenta a otra. Verifica la autenticidad del token y la autorización para acceder a la cuenta de origen. Luego, realiza la transferencia retirando fondos de la cuenta de origen y depositandolos en la cuenta de destino. Si la autenticación falla o si no hay fondos suficientes, se lanzan excepciones correspondientes.
  • El método registerAccount añade una nueva cuenta bancaria al sistema con un saldo inicial especificado. Utiliza el accountId como clave para almacenar la cuenta en un mapa de cuentas llamado accounts.
  • La clase UnauthorizedAccessException es una excepción personalizada para manejar casos en los que se intenta acceder a una operación sin autorización. Esta excepción se lanza cuando se detecta un acceso no autorizado y puede contener un mensaje explicativo.
  • El método getTransactionHistory retorna el historial de transacciones de una cuenta bancaria identificada por accountId. Verifica la autenticidad del token proporcionado y la autorización para acceder a la cuenta. Si la verificación es exitosa, devuelve el historial de transacciones; de lo contrario, lanza una excepción UnauthorizedAccessException.

Se realizó una API bancaria a modo de ejemplo que muestra cómo podemos llevar a cabo cada uno de los Tips , esta proporciona funcionalidades básicas como implementar la autenticación de usuarios, consulta de saldos, transferencias entre cuentas y obtención de historiales de transacciones incluyendo la definición clara de recursos y operaciones, el mantenimiento de operaciones idempotentes y el control de excepciones.

Es esencial seguir prácticas sólidas de diseño de API y utilizar patrones de programación seguros para proteger la integridad y la privacidad de los datos financieros de los usuarios. Esto incluye definir claramente las funciones y datos, mantener operaciones predecibles y sin efectos secundarios no deseados, minimizar el tiempo de espera y asegurar que solo los usuarios autorizados accedan a la información.

--

--