Historia sobre cómo diseñar una API

Una vez ayudé a un amigo que necesitaba integrar datos sobre viviendas libres y ocupadas del sistema de administración de propiedades con el sitio de su cliente. Para mi deleite, este sistema tenía una API. Pero, desafortunadamente, se arregló muy mal.

imagen

Decidí escribir este artículo no para criticar el sistema que se discutirá, sino para hablar sobre los errores que se encuentran durante el desarrollo de la API y sugerir formas de corregir estos errores.

Resumen de la situación


La organización en cuestión utilizó el sistema Beds24 para administrar el espacio vital. La información sobre lo que está libre y lo que está ocupado se sincronizó con varios sistemas de reserva de alojamiento (como Booking, AirBnB y otros). La organización participó en el desarrollo del sitio y quería que la búsqueda mostrara solo información sobre las habitaciones disponibles durante un período de tiempo específico y con capacidad adecuada. Esta tarea parecía muy simple, ya que Beds24 proporciona una API para la integración con otros sistemas. De hecho, resultó que los desarrolladores de esta API cometieron muchos errores en su diseño. Propongo analizar estos errores, identificar problemas específicos y hablar sobre cómo abordar el desarrollo de la API en las situaciones bajo consideración.

Problema # 1: formato del cuerpo de la solicitud


Dado que el cliente solo está interesado en información sobre si, por ejemplo, una habitación de hotel está libre u ocupada, solo estamos interesados ​​en referirnos al /getAvailabilities API /getAvailabilities . Y, aunque una llamada a dicha API debería conducir a datos sobre la disponibilidad de habitaciones, esta llamada, de hecho, parece una solicitud POST, ya que el autor de la API decidió equiparla con la capacidad de aceptar filtros como un cuerpo JSON de la solicitud. Aquí hay una lista de posibles parámetros de consulta y ejemplos de los valores que aceptan:

 {   "checkIn": "20151001",   "lastNight": "20151002",   "checkOut": "20151003",   "roomId": "12345",   "propId": "1234",   "ownerId": "123",   "numAdult": "2",   "numChild": "0",   "offerId": "1",   "voucherCode": "",   "referer": "",   "agent": "",   "ignoreAvail": false,   "propIds": [       1235,       1236   ],   "roomIds": [       12347,       12348,       12349   ] } 

Veamos este objeto JSON y hablemos sobre lo que está mal aquí.

  1. Las fechas ( checkIn , lastNight y checkOut ) están en formato YYYYMMDD . No hay absolutamente ninguna razón para no usar el formato estándar ISO 8601 ( YYYY-MM-DD ) al convertir fechas en cadenas, ya que este es un estándar ampliamente utilizado para presentar fechas. Es familiar para muchos desarrolladores, es lo que muchos analizadores JSON esperan recibir en la entrada. Además, existe la sensación de que el campo lastNight es redundante, ya que hay un campo checkOut , que siempre está representado por una fecha un día antes de la fecha especificada en lastNight . En relación con las deficiencias mencionadas anteriormente, sugiero que, al diseñar tales API, se esfuerce por utilizar siempre métodos estándar de presentación de fechas y trate de no agobiar a los usuarios de API con la necesidad de trabajar con datos redundantes.
  2. Todos los campos de identificación, así como los numChild numAdult y numChild , son numéricos, pero se representan como cadenas. En este caso, no hay razón aparente para representarlos como cadenas.
  3. Aquí puede observar los siguientes pares de campos: roomId y roomIds , así como propId y propIds . La presencia de los propId roomId y propId es redundante, ya que ambos pueden usarse para transmitir identificadores. Además, hay un problema con los tipos. Tenga en cuenta que el campo roomId es un campo de cadena, y los valores numéricos de los identificadores deben usarse en la matriz roomIds . Esto puede generar confusión, problemas con el análisis y, además, sugiere que en el servidor algunas operaciones se realizan con cadenas y otras con números, a pesar de que estas cadenas y números se utilizan para representar lo mismo. datos

Me gustaría invitar a los desarrolladores de API a intentar no complicar la vida de aquellos que usarán estas API, mientras cometen errores como las API al diseñar las API. Es decir, vale la pena luchar por el formato de datos estándar, para garantizar que no sean redundantes, para garantizar que no se utilicen diferentes tipos de datos para representar entidades homogéneas. Y no represente indiscriminadamente todo como cadenas.

Problema # 2: formato del cuerpo de respuesta


Como ya se mencionó, solo estamos interesados ​​en el /getAvailabilities API /getAvailabilities . Veamos cómo se ve la respuesta de este punto final y hablemos sobre las deficiencias que se hicieron durante su formación. Recuerde que al acceder a la API, estamos interesados ​​en una lista de identificadores de objetos que son gratuitos durante un período de tiempo determinado y pueden acomodar a un número determinado de personas. A continuación se muestra un ejemplo del cuerpo de la solicitud a la API y un ejemplo de lo que devuelve en respuesta a esta solicitud.

Aquí está la solicitud:

 {   "checkIn": "20190501",   "checkOut": "20190503",   "ownerId": "25748",   "numAdult": "2",   "numChild": "0" } 

Aquí está la respuesta:

 {   "10328": {       "roomId": "10328",       "propId": "4478",       "roomsavail": "0"   },   "13219": {       "roomId": "13219",       "propId": "5729",       "roomsavail": "0"   },   "14900": {       "roomId": "14900",       "propId": "6779",       "roomsavail": 1   },   "checkIn": "20190501",   "lastNight": "20190502",   "checkOut": "20190503",   "ownerId": 25748,   "numAdult": 2 } 

Hable sobre problemas de respuesta.

  1. En el cuerpo de la respuesta, las numAdult ownerId y numAdult repente se convirtieron en números. Y en la solicitud fue necesario indicarlos en forma de cadenas.
  2. La lista de objetos inmobiliarios se presenta en forma de propiedades de objeto, cuyas claves son identificadores de habitación ( roomId ). Sería lógico esperar que dichos datos se generen como una matriz. Para nosotros, esto significa que para obtener una lista de habitaciones disponibles, necesitamos roomsavail sobre todo el objeto, mientras verificamos la presencia de ciertas propiedades de los objetos encerrados en él, como roomsavail , y no prestamos atención a algo como checkIn y lastNight . Entonces sería necesario verificar el valor de la propiedad roomsavail , y si es mayor que 0, podríamos concluir que la propiedad correspondiente está disponible para la reserva. Ahora echemos un vistazo a la propiedad roomsavail . Estas son las opciones para presentarlo en el cuerpo de respuesta: "roomsavail": "0" y "roomsavail": 1 . ¿Ves el patrón? Si las habitaciones están ocupadas, el valor de la propiedad se representa con una cadena. Si es gratis, se convierte en un número. Esto puede generar muchos problemas en los lenguajes que están estrictamente relacionados con los tipos de datos, ya que en ellos la misma propiedad no debe tomar valores de diferentes tipos. En relación con lo anterior, me gustaría sugerir a los desarrolladores que usen matrices de objetos JSON para representar ciertos conjuntos de datos, y que no usen construcciones inconvenientes en forma de pares clave-valor similares a los que estamos considerando aquí. Además, es necesario asegurarse de que los campos de los objetos homogéneos no contengan datos de diferentes tipos. Una respuesta del servidor formateada correctamente podría parecerse a la siguiente. Tenga en cuenta que al presentar datos en este formulario, la información de la sala no contiene datos duplicados.

 {   "properties": [       {           "id": 4478,           "rooms": [               {                   "id": 12328,                   "available": false               }           ]       },       {           "id": 5729,           "rooms": [               {                   "id": 13219,                   "available": false               }           ]       },       {           "id": 6779,           "rooms": [               {                   "id": 14900,                   "available": true               }           ]       }   ],   "checkIn": "2019-05-01",   "lastNight": "2019-05-02",   "checkOut": "2019-05-03",   "ownerId": 25748,   "numAdult": 2 } 

Problema 3: manejo de errores


Así es como se organiza el manejo de errores en la API considerada aquí: el sistema envía respuestas con un código de 200 a todas las solicitudes, incluso si se produce un error. Esto significa que la única forma de distinguir una respuesta normal de una respuesta con un mensaje de error es analizar el cuerpo de la respuesta y verificar la presencia de campos de error o errorCode de error en ella. Solo se proporcionan los siguientes 6 códigos de error en la API.


Camas24 códigos de error API

Sugiero que todos los que lean esto intenten no devolver una respuesta con el código 200 (procesamiento exitoso de la solicitud) si algo salió mal al procesar la solicitud. Puede realizar este paso solo si lo proporciona el marco en función del cual está desarrollando la API. La devolución de códigos de respuesta adecuados permite a los clientes API saber de antemano si necesitan analizar el cuerpo de la respuesta o no, y cómo hacerlo (es decir, analizar una respuesta normal del servidor o un objeto de error).

En nuestro caso, hay dos formas de mejorar la API en esta dirección: puede proporcionar un código HTTP especial en el rango de 400-499 para cada uno de los 6 posibles errores (es mejor hacerlo), o devolver, si ocurre un error, el código 500, que permitirá al menos, el cliente debe saber antes de analizar el cuerpo de la respuesta que contiene información sobre el error.

Problema número 4: "instrucciones"


A continuación se encuentran las "instrucciones" para usar la API de la documentación del proyecto:

Lea las siguientes instrucciones cuando use la API.

  1. Las llamadas a la API deben diseñarse de modo que durante su ejecución tengan que enviar y recibir una cantidad mínima de datos.
  2. Las llamadas a la API se realizan de una en una. Debe esperar hasta la próxima llamada a la API antes de hacer la próxima llamada.
  3. Si necesita hacer varias llamadas a la API, se debe proporcionar una pausa de unos segundos entre ellas.
  4. No es necesario realizar llamadas API con demasiada frecuencia, manteniendo el nivel de llamadas en el nivel mínimo necesario para resolver las tareas del cliente.
  5. El uso excesivo de la API dentro de un período de 5 minutos conducirá a la suspensión de su cuenta sin notificaciones adicionales.
  6. Nos reservamos el derecho de bloquear el acceso al sistema a los clientes que, en nuestra opinión, usan la API en exceso. Esto se hace a nuestra discreción y sin previo aviso.

Si bien los puntos 1 y 4 parecen bastante justificados, no puedo estar de acuerdo con los otros puntos de esta instrucción. Considéralos.

  1. Artículo número 2. Si está desarrollando una API REST, se supone que será una API independiente del estado. La independencia de las llamadas API de las llamadas anteriores es una de las razones por las que la tecnología REST ha encontrado una amplia aplicación en aplicaciones en la nube. Si un determinado módulo del sistema no es compatible con el estado, se puede volver a implementar fácilmente en caso de error. Los sistemas basados ​​en dichos módulos se escalan fácilmente cuando cambia la carga sobre ellos. Al diseñar una API RESTful, debe asegurarse de que sea una API independiente del estado y de que quienes la utilizan no tengan que preocuparse por algo como ejecutar solo una solicitud a la vez.
  2. Artículo número 3. Este artículo se ve bastante extraño y ambiguo. No puedo entender la razón por la cual se escribió este párrafo de la instrucción, pero tengo la sensación de que nos dice que en el proceso de procesamiento de la solicitud, el sistema realiza ciertas acciones, y si está "distraído" por otra solicitud, no enviado a tiempo, esto puede interferir con su trabajo. Además, el hecho de que el autor del manual diga "unos segundos" no nos permite averiguar la duración exacta de la pausa que debe sostenerse entre solicitudes sucesivas.
  3. Artículos No. 5 y No. 6. Se refiere al "uso excesivo de la API", pero no se dan criterios para el "uso excesivo". ¿Quizás son 10 solicitudes por segundo? O tal vez 1? Además, algunos proyectos web pueden tener grandes cantidades de tráfico. Si, sin ninguna razón adecuada y sin notificación, cierran el acceso a la API que necesitan, sus administradores probablemente se negarán a usar tales API. Si escribe tales instrucciones, use un lenguaje claro en ellas y ubíquese en el lugar de los usuarios que tienen que trabajar con su sistema, guiado por sus instrucciones.

Problema número 5: documentación


Así es como se ve la documentación de la API.


Camas24 Documentación API

El único problema con esta documentación es su apariencia. Se vería mucho mejor si estuviera bien formateado. Especialmente para mostrar la posible aparición de dicha documentación, yo, usando Dillinger y gastando menos de dos minutos en ello, hice la siguiente versión. En mi opinión, se ve mucho mejor que lo anterior.


Documentación mejorada

Para crear tales materiales, se recomienda utilizar herramientas especiales. Si estamos hablando de documentos simples similares al descrito anteriormente, entonces algo como un archivo de rebajas regular es suficiente para su diseño. Si la documentación es más complicada, para su diseño es mejor usar herramientas como Swagger o Apiary .

Por cierto, si quieres echar un vistazo a la documentación de la API de Beds24, échale un vistazo aquí .

Problema 6: Seguridad


La documentación para todos los puntos finales API dice lo siguiente:

Para usar estas funciones, se debe permitir el acceso a la API. Esto se hace en el menú AJUSTES → CUENTA → ACCESO A LA CUENTA.

Sin embargo, en realidad, cualquiera puede acceder a esta API y, mediante algunas llamadas, obtener información de ella sin proporcionar ninguna credencial. Por ejemplo, esto también se aplica a las consultas sobre la disponibilidad de ciertas viviendas. Esto se discute en otra parte de la documentación.

La mayoría de los métodos JSON requieren una clave API para acceder a una cuenta. La clave de acceso a la API se puede configurar usando el menú AJUSTES → CUENTA → ACCESO A LA CUENTA.

Además de la explicación incomprensible de los problemas de autenticación, resulta que el usuario debe crear la clave para acceder a la API por su cuenta (esto se hace, por cierto, rellenando manualmente el campo correspondiente, no se proporcionan algunos medios para crear claves automáticamente). La longitud de la clave debe tener entre 16 y 64 caracteres. Si permite que los usuarios creen sus propias claves para acceder a la API, esto puede dar lugar a la aparición de claves muy inseguras que se pueden recoger fácilmente. En una situación similar, son posibles los problemas asociados con el contenido de las claves, ya que puede ingresar cualquier cosa en el campo clave. En el peor de los casos, esto podría conducir a un ataque al servicio mediante inyección SQL o algo así. Al diseñar una API, no permita que los usuarios creen claves para acceder a la API ellos mismos. En su lugar, genere claves para ellos automáticamente. El usuario no debería poder cambiar el contenido de dicha clave, pero, si es necesario, debería poder generar una nueva clave, reconociendo la antigua como inválida.

En el caso de solicitudes que requieren autenticación, vemos otro problema. Consiste en el hecho de que se debe enviar un token de autenticación como parte del cuerpo de la solicitud. Así es como se describe en la documentación.


Ejemplo de autenticación API de Beds24

Si el token de autenticación se transmite en el cuerpo de la solicitud, esto significa que el servidor deberá analizar el cuerpo de la solicitud antes de que llegue a la clave. Después de eso, extrae la clave, realiza la autenticación y luego decide, qué debe hacer con la solicitud, cumplirla o no. Si la autenticación es exitosa, el servidor no estará sujeto a una carga adicional, ya que en este caso el cuerpo de la solicitud aún debería analizarse. Pero si la solicitud no se pudo autenticar, se utilizará un tiempo valioso del procesador para analizar el cuerpo de la solicitud para nada. Sería mejor enviar un token de autenticación en el encabezado de la solicitud, utilizando algo como un esquema de autenticación de portador . Con este enfoque, el servidor necesitará analizar el cuerpo de la solicitud solo si la autenticación es exitosa. Otra razón por la que recomendamos utilizar un esquema estándar como Bearer para la autenticación es el hecho de que la mayoría de los desarrolladores están familiarizados con dichos esquemas.

Problema número 7: rendimiento


Este es el último problema en mi lista, pero no le resta importancia. El hecho es que lleva un poco más de un segundo completar la solicitud a la API en cuestión. En aplicaciones modernas, tales demoras pueden ser inaceptables. De hecho, aquí puede aconsejar a todos los involucrados en el desarrollo de la API que no se olviden del rendimiento.

Resumen


A pesar de todos los problemas de los que estábamos hablando aquí, la API en cuestión nos permitió resolver los problemas que enfrenta el proyecto. Pero los desarrolladores se tomaron mucho tiempo para comprender la API e implementar todo lo que necesitan. Además, para resolver problemas simples, tuvieron que escribir código bastante complicado. Si esta API se diseñara como debería, el trabajo se realizaría más rápido y la solución llave en mano sería más simple.

Por lo tanto, me gustaría pedirles a todos los que diseñan la API que piensen cómo los usuarios de sus servicios trabajarán con ella. Asegúrese de que la documentación de la API describe completamente sus capacidades, de modo que sea comprensible y esté bien diseñado. Controle el nombre de las entidades, asegúrese de que los datos que emite o recibe su API están claramente estructurados para que sea fácil y conveniente trabajar con ellos. Además, no se olvide de la seguridad y el manejo correcto de los errores. Si tomamos en cuenta todo lo que hablamos al diseñar la API, entonces no necesita escribir algo como las extrañas "instrucciones" que discutimos anteriormente para trabajar con ella.

Como ya se mencionó, este material no pretende disuadir a los lectores de usar Beds24 o cualquier otro sistema con una API mal diseñada. Mi objetivo era mostrar ejemplos de errores y enfoques para su solución, dar recomendaciones, después de lo cual todos podrían mejorar la calidad de sus desarrollos. Espero que este material atraiga la atención de los programadores que lo leen a la calidad de las soluciones que desarrollan. Esto significa que habrá más API buenas en el mundo.

Estimados lectores! ¿Te has encontrado con API mal diseñadas?

Source: https://habr.com/ru/post/es436888/


All Articles