banner

Blog

Apr 21, 2024

Conectando los puntos: diseño de API en un mundo distribuido

Presentaciones de la página de inicio de InfoQ Conectando los puntos: diseño de API en un mundo distribuido

Ben Gamble explora el diseño de API a través de la lente de un desarrollador y consumidor de API internas y externas.

Ben Gamble ha pasado más de 10 años liderando ingeniería en nuevas empresas y empresas de alto crecimiento. Como fundador, director de tecnología, productor y líder de producto, ha cerrado la brecha entre la investigación y el desarrollo de productos. Habiendo trabajado con lo último en realidad aumentada, escalando juegos 3D y logística en el mismo día, no es ajeno a asumir desafíos técnicos.

El software está cambiando el mundo. QCon potencia el desarrollo de software al facilitar la difusión del conocimiento y la innovación en la comunidad de desarrolladores. QCon, una conferencia dirigida por profesionales, está diseñada para líderes de equipos técnicos, arquitectos, directores de ingeniería y gerentes de proyectos que influyen en la innovación en sus equipos.

Presentado por: Tomasz Grabiec, ingeniero distinguido de ScyllaDB y Tzach Livyatan, vicepresidente de producto de ScyllaDB

Tome las decisiones correctas descubriendo cómo los desarrolladores de software senior de las empresas pioneras están adoptando las tendencias emergentes. ¡Regístrate ahora!

Gamble: Mi nombre es Ben Gamble. Actualmente trabajo en un lugar llamado Aiven. Trabajo en los equipos de relaciones con desarrolladores como una especie de sumiller de software de código abierto. Estoy aquí para acompañarte con Kafka, Postgres y tal vez un poco de queso para acompañar. Mi historia es un pasado accidentado entre todo, desde el desarrollo de juegos MMO desde las capas centrales de la red hacia arriba, hasta el desarrollo de dispositivos médicos para diagnósticos reales en el punto de atención. Caminé un poco por el lugar. De hecho, escribí la producción de Pascal en los últimos 12 meses, que era hacer transmisión MQTT en un lugar donde trabajé anteriormente. Actualmente estoy aprendiendo Rust, para poder contar mejores chistes sobre crustáceos. Porque está sucediendo algo en el mundo y ha estado sucediendo durante los últimos, creo, unos pocos millones de años, y se llama carcinización. Aquí es donde la naturaleza intenta repetidamente convertir las cosas en cangrejos. En realidad, esto es algo realmente documentado y puedes descubrirlo en la computadora si lo buscas. Es este enfoque el que la naturaleza ama a los cangrejos y quiere que todo sea uno. Por eso trabajo en Aiven, porque tiene un logo de cangrejo precioso. Con el tiempo, es posible que todos nos utilicen también. Aiven es una plataforma de alojamiento de código abierto para alojar sus necesidades de datos. Hacemos cosas como Postgres a escala. Hacemos cosas como Kafka a escala. Estamos ahí para hacer que el software de código abierto sea de fácil acceso.

El gran tema aquí es que aquellos que no pueden recordar el pasado están condenados a repetirlo. Esta es una antigua cita de George Santayana, sobre el significado de la razón, de 1905. La pintura aquí es del Smithsonian. En realidad se trata de que las grandes ideas del hombre son en realidad la misma cosa una vez más. Este es el tema que se puede ver en todo, desde el arte hasta la vida, pero también en la ingeniería de software, ya que la mayoría de las veces volvemos a hacer lo mismo, e idealmente mejor. Con eso en mente, pensemos en las API y recordemos los monolitos. Recuerda esas cosas, eran preciosas. Eran sistemas grandes que podías poner en pie y simplemente funcionaban. Eran una sola estructura, tallada en piedra, un poco como esa antigua de la película. La clave aquí es que casi siempre había algo entre usted, su sistema y sus clientes, a menudo un equilibrador de carga. Había una serie de herramientas construidas en su interior. Entonces casi siempre había una base de datos relacional debajo de todo. Dependiendo de su época, esto podría ser cualquier cosa, desde MS SQL hasta Oracle y Db2 y, en tiempos más recientes, MySQL y Postgres, e incluso algunas variantes más modernas de esto. Lo bueno aquí es que realmente solo tuvimos una gran cosa en el medio. A medida que crecías, simplemente agregabas más unidades de RAM o CPU más rápidas, particularmente en el pasado, literalmente podías actualizar tu CPU cada año, y probablemente estés bien.

Cuando hablamos de las API de estos sistemas, en esta ocasión recordamos a SOAP. A diferencia de la película, hablamos sobre SOAP. Hablemos de qué es realmente SOAP. SOAP era un protocolo. Definía cómo hablabas de las cosas. Definió el cifrado. Definió una forma de distribuir sus datos a través del cable, en este caso, XML. Como describía todos los diferentes pasos a lo largo del camino, era algo bastante completo. SOAP fue un maravilloso trampolín en la evolución de las API, especialmente en la web. Nos permitió crear aplicaciones estructuradas que entendían de dónde venían. Surgió de ese tipo CORBA original, marcos tipo RPC, pero en realidad estaba orientado a compartir datos. Era complejo y pesado. La implementación de interfaces SOAP llevó tiempo. Requirió muchos bits. En realidad, no era lo suficientemente ágil para hacer frente al creciente auge del software moderno. Con eso en mente, llegó el momento de DESCANSAR. Estamos hablando de Transferencia de Estado Representacional aquí. Es una famosa evolución como alternativa a SOAP y se considera un gran paso adelante, y hasta hace relativamente poco tiempo era la forma de facto de hacer las cosas en línea. Lo mejor de REST es que es sencillo de crear y sencillo de iniciar. Este es el auge de casi la web 1.0 y 2.0, fue la capacidad de lanzar API REST muy rápidamente con pilas, como la pila LAMP original, Linux, Apache, MySQL y, a menudo, Perl, PHP o Python. Clásicamente, PHP para la pila LAMP generó la web moderna. Con sus entonces sinceros halagos e imitaciones, se nos ocurrieron sistemas como Ruby on Rails, Django en Python y Node.js con Express. Este fue el tipo de cosas que realmente hicieron explotar la web tal como la conocemos. Como ocurre con cualquier cosa como esta, fueron fáciles de hacer, sencillos de lanzar y a gran escala. Parte de esto se debe simplemente a la naturaleza de un solo subproceso de la mayor parte del software en el que están integrados. Esa no es toda la historia. El verdadero problema surge cuando empiezas a intentar hacer esto de forma práctica.

Empiece por escalar con las mejores prácticas. Tienes tu API REST, alguna interfaz hablando con ella. Piensas, tengo disputas sobre mi base de datos aquí, simplemente voy a generar más versiones de esta API y tendré una base de datos de alta disponibilidad. Tengo muchas API, todas exactamente iguales. A medida que amplié mis bases de datos, solo puedo mover la carga de lectura a otro lugar. Lo siguiente es que todavía voy lento, porque casi siempre, la base de datos es lo primero que la procesa bajo la carga. Vaya, agreguemos algo de almacenamiento en caché, hará que todo sea mejor. Redis es como la magia oscura del mundo web, simplemente agrega y hace que las cosas desaparezcan, en realidad, son problemas en movimiento. Lo siguiente que debe hacer es que a medida que aumentan las funciones, su API se vuelve más complicada porque hay diferentes tipos de usuarios que utilizan su API. La experiencia móvil es muy diferente a la experiencia web, porque hay diferentes expectativas y diferentes cargas de red. Además, existe este problema de factor de forma concreto que estás utilizando para proporcionar información diferente. Cuando esto sucede, terminas especializándote en cómo entregas los datos, dondequiera que estén, al exponer un punto final GraphQL o al tener servicios de datos especializados detrás de escena, ahora estás hablando de API más diferentes y tienes que exponer más cosas. Piensas: "Esto se está poniendo complicado. Pongamos algo intermedio para ayudar". Separas las API. Tal vez tengas un sistema que hace un conjunto de cosas y otro sistema que hace otra. Tal vez uno sirva para su inventario y catálogo y el otro realmente maneje el inicio de sesión, y coloque una puerta de enlace API en el frente. Esta es la etapa inicial para determinar lo que quieres hacer.

Realmente no se hace nada más que decir, este sistema sirve para un conjunto de propósitos, este sistema sirve para otro conjunto de propósitos, una especie de arquitectura orientada a servicios de mediados de la década de 2010. Lo colocas todo detrás de una puerta de enlace API, que actúa como tu balanceador de carga. Esto te llevará muy lejos. Puede pasar de este enfoque de no compartir nada, en el que todo tiene que estar dentro de una máquina, a compartir casi cualquier cosa en tiempo real. De hecho, puede separar estos planos de API para que la persona que crea la API superior no necesariamente necesite saber acerca de la persona que crea la API inferior. A medida que comienzas a hacer esto, hay más cosas que hacer porque de repente ves que puedes hacer cosas como no solo buscar, sino también información procesable, porque tienes un plano API que puede hacer cualquier cosa porque simplemente puedes agregar más bits a él. Con sistemas detrás de escena, puede mostrar estos datos a las personas que los necesitan. Se trata de mejorar preventivamente las experiencias de los usuarios. Porque lo que sucede ahora es que tenemos una fachada frente a todo lo que estamos haciendo, y tenemos la capacidad de colocar motores de procesamiento detrás de escena para comenzar a ofrecer datos en tiempo real, o incluso solo apropiados a través de la web.

Aquí es donde surge la idea de los microservicios adecuados con los datos correctos. De aquí surgió el microservicio que mencioné antes, que es la idea de un servicio que hace una cosa y luego tiene los datos necesarios. Esta es la pila de datos moderna de la que sigues escuchando. Si ha visto algunas de las charlas también en este tema, habrá oído acerca de cómo elegir la herramienta de datos adecuada para satisfacer las necesidades de sus microservicios. Sin embargo, este no es el final de la historia porque lo que tenemos aquí es que tenemos todos estos diversos servicios, y algunos de ellos hablan sobre un maravilloso plano de datos como Kafka, o mallas de servicios, o casi cualquier otra cosa. Sin embargo, lo que realmente no se muestra aquí de manera importante es cuántas API internas hay. Literalmente, cada borde de este diagrama tiene una API, cada cuadro, incluso dentro de esos cuadros, habla entre ellos. Tienes numerosas cosas que hablan de SQL, algunas cosas que hablan de Redis, muchas cosas que hablan de Kafka, todo lo que habla de REST en un nivel u otro. Por otra parte, otras cosas hablan de CQL. Empiezas a llegar a este punto en el que te preguntas: ¿cuántas API tengo realmente? ¿Tengo solo el que está en la parte superior o los que están entre mi puerta de enlace API y mis servicios, o después de considerarlos todos? Luego, debe recordar que lo siguiente que haga podría tener una base de datos diferente. Es posible que tenga una política de almacenamiento en caché diferente. Puede que tenga una API completamente diferente. Podría requerir incluso más reglas. Porque lo que sucede cuando se agrega un servicio de cumplimiento, que solo puede operarse dentro de Azerbaiyán, debido a algún requisito legal, ahora esta API tiene que tener reglas adicionales.

Para poner esto en contexto, contemos una historia. Esto estará relacionado con algo que solía hacer en mi empleador anterior. Tienes una plataforma como servicio. En este caso estamos hablando de grandes sistemas de streaming. Queremos crear una API para permitir configuraciones programáticas. Esto se debe a que, anteriormente, se llegaba muy lejos al tener este maravilloso panel, poder configurar sus aplicaciones, y ellos simplemente lo hacían funcionar, y luego las aplicaciones crecían y escalaban automáticamente. El problema es que a medida que la infraestructura y el movimiento del código avanzaban, pero también el hecho de que su sistema realmente tenía una gran flexibilidad, se volvió bastante imperativo poder impulsar cambios en su configuración real de forma remota. Por supuesto, lo primero que piensas es que he oído hablar de la Iniciativa OpenAPI, que es una junta de personas de código abierto que han desarrollado una forma de especificar API REST. Lo que pasa con esto es que piensas que puedo especificarlo y piensas que es una API REST. Las API REST son geniales porque todos saben lo que hacen. Hay verbos idempotentes ahí, así que la idea de GET es que obtendrás lo mismo cada vez, o PUT, lo haces una vez, hará lo mismo cada vez. POST es el único por el que realmente debes preocuparte, pero es el más común. Más importante aún, es ampliamente adoptado. Es una especificación abierta, por lo que no existen limitaciones reales aquí. Piensas, sí, tengo esta plataforma, le voy a agregar una API. Voy a hacerlo realidad. Cada uno puede conducirlo como quiera.

Hablemos de lo que puede salir mal aquí, porque intentamos hacer esto, pero resultó ser bastante complicado. El primer y mayor problema acabó siendo el de las abstracciones con fugas. Lo que está sucediendo aquí es que toda nuestra plataforma se había convertido en un microservicio para las plataformas de todos los demás. A medida que desarrolla su sistema de microservicios, ahora incorpora sistemas externos como un servicio separado con una API. El punto al que llegamos con las abstracciones con fugas es en realidad bastante serio, porque lo que sucedió fue que, la primera vez, teníamos una API con fugas que exponía detalles de los sistemas de producción. Esto era como la agrupación lógica de dónde estaban las API, no se agrupaban semánticamente de acuerdo con lo que realmente hacían, sino en lugar de cuándo se construyeron y agregaron al sistema en el backend, porque así es como se agruparon en el backend, o qué pieza de tecnología interna colgaron. Significaba que sin mucho conocimiento interno, terminabas con un sistema problemático, que era muy difícil de entender. Además, debido a que alguien simplemente reflejó una de las API internas, tenemos una superficie de ataque muy incontrolada, porque no teníamos una forma real de conocer todas estas API que vamos a atacar. Tenía sentido, porque era sólo un espejo. No había nadie pensando, esto es algo correcto.

El otro extremo del sistema es la forma de abstraer. He visto algunos de estos, lo que fue otro intento de una empresa diferente. Lo que sucedió fue que creamos un botón mágico, una API de un solo uso, para hacer algo. Lo que hizo fue, en este caso, configurar un motor que pudiera realizar la verificación de direcciones. La configuración del motor de verificación de direcciones fue básicamente decir, dame un motor y establece su configuración en 5. Lo bueno de esto es que es muy sencillo de hacer. Lo terrible de esto es que realmente no tienes ningún control, porque tu API fue diseñada pensando en el usuario y no en la mente del sistema. Un usuario puede estar acostumbrado a ajustar algunos diales a la configuración correcta, pero eso realmente no refleja cómo funcionan los componentes internos de manera significativa. Eres demasiado abstracto. Significa algo así como, si configuro este botón en 11, ¿qué sucede realmente? ¿Es 11 en realidad un punto más que 10 o es 100 veces más? Al final necesitarás una documentación explicativa muy larga para intentar que la gente tenga la mentalidad adecuada para utilizar tu software.

Aquí es donde no desea filtrar detalles de implementación, pero sí proyectar las realidades de la implementación. Si el hecho es que la configuración 1 es solo 1 máquina, y la configuración 3 es en realidad 30 máquinas, es posible que desee cambiarles el nombre a algo más apropiado para lo que sucede en el interior. Sin embargo, si nunca tiene una configuración entre los dos, definitivamente querrá asegurarse de que esté expuesta de una manera que haga muy obvio que no hay configuraciones intermedias. Hacer esto bien es un desafío interminable. En el caso de que tuviéramos una abstracción con muchas fugas la primera vez, terminamos teniendo que rediseñarla mucho para intentar reflejar hacia dónde íbamos. Sin embargo, terminamos con personas que tenían esto en producción y, de repente, no se pueden eliminar ciertas abstracciones con fugas. Tuvimos que seguir adelante y hacerlo funcionar. Luego tapamos todos los agujeros, restringimos las superficies de ataque y agregamos más cosas detrás de escena, pero la API estaba fuera. Una vez que algo salga a la luz, será usado y dependerá de él.

El siguiente problema real fue la disponibilidad. A menudo, con una plataforma como servicio, ofrecerá un SLA muy fino, como algunos nueves. En este caso, cinco fue lo más bajo que ofrecimos, lo cual es divertido decir lo más bajo. Sin embargo, los servicios de su panel de control y los servicios en el plano de control de su sistema no siempre son los mismos; es muy diferente aprovisionar algo nuevo que operarlo realmente. La capacidad de decir, dame una nueva instancia de X, Y, Z en la región ABC es muy diferente de la capacidad de acceder a la región una vez que está allí. Tienes que planificar dependiendo de las diferentes partes del sistema; no suelen estar fijadas en el mismo lugar o al mismo nivel. Lo que es realmente preocupante aquí es que, en gran parte de este tiempo, la interfaz real de su sitio web es una sola cosa. Esto es menos cierto ahora y cada vez es menos cierto. Por un tiempo, existió. He tratado con muchos sistemas en los que surgió esto. De repente, lo que sucedió fue que conectó su superficie API a su blog y ahora, debido a que se volvió monolítico en algunos lugares y no en otros, ahora debe tener su blog tan sólido como su servicio principal. Eso no es necesariamente un problema, pero eleva el nivel de dónde provienen los problemas reales de tiempo de actividad. ¿Está bien que su servicio de disponibilidad de cinco nueves, que en realidad no recibe tantas visitas, de repente se vea inundado por un comentario de Stack Overflow que se vuelve perfectamente viral? La respuesta es no. Tienes que pensar mucho en esto.

La respuesta, por supuesto, aquí fue: hay que desacoplarlo. Tienes que desacoplarlo de tal manera que signifique que no estás rompiendo cosas, dondequiera que signifique simplemente asegurarte de que no haya nada bajo el mismo grupo de recursos, o separar tu balanceador de carga en algún lugar, o en realidad tienes un Puerta de enlace API que puede encargarse de esto por usted. Hasta cierto punto, es una elección del distribuidor. El punto clave aquí es la historia. La historia es simplemente que si realmente tienes un sistema del que dependen las cosas, se ha convertido en parte de tu producto. Por lo tanto, el SLA de su producto se aplicará a las API establecidas. Las API son productos, ya sea que considere su producto como una API en sí o no, o podría ser algún servicio. Básicamente, estás interactuando con él a través de una API. Por tanto, la API en sí es un producto. La configuración, si la ofrece como material programático, ahora también forma parte del producto. Eso nos afectó bastante. Fue rectificado y fue un gran trabajo verlo hecho. Hacer que una API de Rails, que originalmente era solo un monolito en una región y de repente se ejecutara entre los 15 y 20 días que necesitábamos, fue realmente problemático la primera vez. Es factible y está bien, pero de repente estábamos escalando cosas que no tenderíamos a escalar.

¿Qué pasa con las API internas? Acabamos de hablar de las externas. Aquí es donde llegamos a los verdaderos desafíos, porque siempre hay más. En general, solo presentas unas pocas API específicas al mundo. Sin embargo, internamente, incluso en ese maletín, viste cuántas API teníamos realmente. La opción obvia es: sabemos cómo hacer REST, usaremos REST internamente. Tenemos formas de especificarlo, podemos especificar qué cosas van y vienen, y podemos hacer que todos estos sistemas se comuniquen entre sí. Esto puede funcionar. Puede funcionar muy bien, pero estás usando API REST. Se trata nuevamente de Transferencias Estatales Representativas. Ahora estás llamando a algo cada vez. Si no estás en HTTP/2, estás haciendo un SYN-ACK completo cada vez. Esto es bastante pesado y ahora depende del bloqueo del encadenamiento de solicitudes a través de su sistema. ¿Realmente desea tener balanceadores de carga y cachés dentro de su firewall? Si lo piensa así, sus servicios externos podrían verse bastante afectados, pero a medida que los sistemas se propagan a través de sistemas internos, es posible que tenga 1000 sistemas internos y cada llamada al sistema desde los externos podría requerir de 50 a 150 de esas cosas por las que pasan. De repente, cada llamada se amplifica 100 veces o tal vez 30 veces o tal vez 1000 veces, por lo que es posible que ahora necesite tener grupos de equilibrio de carga de sus servicios internos reales. Es esto lo que realmente quieres? Tienes que unir todas estas cosas. Puedes hacerlo, pero realmente no lo recomiendo. Puedes hacerlo para algunas cosas, pero no para otras. Algunos sistemas están diseñados para hacer esto. En general, estás en este mundo en el que te preguntas: ¿estás seguro de que quieres unir las cosas tan estrechamente?

Hablemos de Pub/Sub. No estamos hablando de establecimientos de bebidas con temática náutica. Aquí es donde llegamos al paso de evolución sobre en qué se han convertido realmente las API. Debido a que tenemos este enjambre de microservicios, esto es de Netflix, y muestra cosas que pasan a través de su web hacia todos sus servicios backend, puedes ver las rutas que todos trazan. Por supuesto, querrás conectarlo todo. Conectar las API no es tan malo, excepto que el problema es que desea unificar esto, por lo que sabe que muchas cosas están hablando con la misma tecnología, por lo que no tiene 100 piezas diferentes que mantener. Puedes optar por el patrón Pub/Sub. El patrón Pub/Sub es básicamente una forma decente de desacoplar cosas. Existe la idea de un editor, un corredor y un suscriptor.

Hay varias formas de hacer esto en diversas tecnologías. El concepto central interesante aquí es que estás desvinculando el arte de hacer que algo suceda del arte de escucharlo suceder. Particularmente con cosas como microservicios, esto significa que publicas algo que dice, haz un trabajo, haz una acción, haz que algo sea así. Cualquier número de sistemas puede retomarlo posteriormente, sin que ninguna dependencia del primer sistema siga viva. Usted acopla libremente esos sistemas con un sistema interno construido específicamente para ser robusto. Los patrones comunes aquí son RabbitMQ, que es un excelente ejemplo de cómo nunca es necesario superar la versión 0.9 para tener éxito. NATS, JetStream y cosas así es un proyecto CNCF encantador y divertido que muestra que si reescribes las cosas suficientes veces, eventualmente obtendrás algo bastante interesante, ya que originalmente comenzó como un servicio de "disparar y olvidar" en Ruby, y ahora es Casi lo mismo que Pulsar. Pulsar, ser otro proyecto Apache de primera clase, es el punto final lógico de lo que son estos grandes sistemas de transmisión con más sistemas distribuidos que el resto de su red en conjunto, ya que son tres, no uno. Luego, por último, tenemos a Kafka. Es ese tipo de pensamiento de adentro hacia afuera de su base de datos. Está muy optimizado para escritura, diseñado para ser robusto desde el día cero y relativamente simple, lo que contradice lo mucho que puedes hacer con él.

Entonces la pregunta es: ¿es hora de empezar a transmitir? Porque aquí es donde llegamos, es como si ahora hubiéramos construido estos sistemas ordenados, que envían mensajes, están en registros o colas. ¿Es hora de empezar a realizar procesamiento de transmisiones? En ese contexto, te contamos una historia. Esto es hace mucho tiempo desde mi época como CTO. Digamos que construyes una plataforma SaaS, podría ser para entrega y logística, como una de las que construí anteriormente. Lo que sucede entonces es que tienes una red de microservicios poco conectada, que utiliza colas y transmisiones. En este caso, lo que se trataba era que teníamos una tarea computacional muy grande y costosa, que era calcular rutas. Esto resolvería el problema completo de rutas de vehículos. Es un problema NP-difícil, puedes adivinar cuánta contracción de la CPU realmente requirió. Podríamos tener una GPU, pero eso habría requerido mucha más experiencia en ese momento, pero también habría involucrado una gran cantidad de código que simplemente no teníamos disponible. Teníamos un montón de máquinas muy grandes con uso intensivo de CPU y la capacidad de realizar trabajos de una cola de trabajos. Originalmente intentamos hacer esto con algunos de los otros sistemas que existen. Terminamos con colas semipersonalizadas con registros adjuntos también, porque queríamos hacer realidad esta idea de equilibrio de carga justo combinado con procesamiento de flujo. Esto funcionó muy bien. Pasamos de un sistema que funcionaba con REST y teníamos ráfagas incontroladas de cantidad de trabajo desconocida, a un sistema que realmente manejaba el rendimiento. Esto creció muy bien, hasta que dejó de ser así.

Lo que sucedió fue que estábamos incorporando clientes con bastante éxito, a medida que los incorporamos, lo que no nos dimos cuenta estaba detrás de escena, estaban bastante fragmentados. Nos encontramos con este problema en el que es un archivo JSON y un archivo JSON no son necesariamente iguales. Uno tiene dimensiones en masa. Uno tiene dimensiones en centímetros. Uno tiene un error tipográfico. Se llega a este simple problema en el que todo lo que es REST ascendente llega a los sistemas de transmisión, que están diseñados para cargas de trabajo computacionalmente pesadas, y no son lo mismo. Esto terminó con un montón de mensajes tipo píldora venenosa fluyendo a través de nuestras API, que no conocíamos completamente. Más adelante, trabajamos un poco para intentar agregar más verificación en sentido ascendente. El problema terminó siendo que, una vez más, una vez que expones una API al mundo, la gente la usará. Eso significa que la gente dependerá de ello. A menudo, ni siquiera tienen control total sobre qué más hay realmente en su propio sistema. En este caso, estos objetos eran básicamente grandes BLOB binarios para ellos, porque provenían de otros sistemas de terceros. Caerá en tu red de procesos mágicos para intentar resolverlo.

Lo que se volvió realmente malo fue que, fundamentalmente, nunca antes habíamos tenido un elemento que fallara en este caso en particular, por lo que sería letra muerta en varios puntos del camino, y un sistema de cola de letras muertas era como, ¿es esto? un mensaje real? Diría, sí, este es un mensaje real, y seguirá regresando al sistema en diferentes etapas. Al final, terminamos con este problema en el que se caía del final de las colas de mensajes fallidos que nunca debieron ser colas de mensajes fallidos. Porque algo no pudo leerlo lo suficientemente bien como para volver a colocarlo en el lugar correcto o incluso registrarlo. Una vez más, estas cosas son las que aprendes cuando te das cuenta de que una superficie API sin restricciones es realmente peligrosa, pero incluso una relativamente restringida no hace las mismas suposiciones en todo momento. Como teníamos sistemas en Python, que en general podrían ser un poco así, probablemente funcionarán, y todavía había cosas en Clojure que estaban diseñadas para manejar cosas e interpretar los mensajes con los que se encontraban. Luego, cuando atacaron algunos de los sistemas C++ en el backend, fue como, sí, no, lo estamos rechazando. O lanzaremos una excepción y bloquearemos su servidor, o simplemente lo descartaremos. Se nos ocurrió un hecho simple: no podíamos igualar las impedancias en nuestra red.

Aquí es donde queríamos especificaciones. Estás pensando, ¿qué podemos hacer al respecto, porque las especificaciones API están muy bien, pero solo nos protegen hasta ahora? Porque la mayor parte de lo que se envía es JSON y tiene que serlo para mantener la flexibilidad para el mundo exterior. Quieres esquemas. Eso es lo primero, ¿qué es un mensaje? El esquema JSON en la parte superior es solo una forma de decir básicamente que al menos sabemos cuáles son estos campos. Si lo piensas bien, ¿qué es un archivo JSON? Es texto. ¿Cuánta precisión hay en un flotador? ¿Es un doble? La respuesta es que no lo sabes. También terminas con muchas cosas en las que no todos usan formalmente JSON de la misma manera. Incluso hacer una verificación podría causar cierta coerción, que simplemente no puedes saber, a menos que seas exactamente igual en todo momento, particularmente cuando comienzas a usar diferentes deserializadores. Nunca puedes estar seguro de lo que sucederá a continuación.

Luego llegamos a las siguientes opciones aquí, que es la idea de protobuf, o buffers de protocolo de Google, y Avro de la fundación Apache. Los búferes de protocolo, originalmente para uso de gRPC, están controlados por esquemas clásicos, generaremos algo de código para usted y simplemente funcionará. Ellas hacen. Se han vuelto muy populares porque gRPC es increíblemente útil. Sin embargo, no son gratuitos. Trabajé en un lugar donde teníamos mensajes de muy alta velocidad, todos codificados en protobuf, que debían decodificarse en un solo dígito de milisegundos. Deben decodificarse en lotes donde podría haber varios esquemas diferentes. Lo que se encuentra con bastante rapidez es la implementación real del sistema de búfer de protocolo detrás de la parte inferior, donde realiza las asignaciones para diseñar objetos en el nuevo formato, y ese proceso de "golpear y desechar" es bastante problemático y provoca una fragmentación masiva del montón porque simplemente No está diseñado para ir a gran velocidad. Los gRPC son geniales, pero son mucho más, hacen algo, desaparecen y listo. De vez en cuando, transmitirá exactamente lo mismo seguido y estará bien, por lo que los asignadores de arena aguantan.

A medida que avanza más rápido y trata de estresar un poco el sistema, rápidamente lo encuentra haciendo asignaciones por elemento. No me refiero simplemente a un grupo, quiero decir que funcionará en su propia arena. Ni siquiera va a llegar a hacer jemalloc, el tipo jemalloc es a donde irá. Luego hará eso donde va, ya tengo el grupo, no te preocupes, o incluso snmalloc si estás siendo inteligente. Eso simplemente no sucederá porque es una arena personalizada, por lo que simplemente arrasará tu arena. Apache Avro es un tipo similar de cosas, pero más enfocado desde antaño, en ese tipo de modelo de tipo evolución. Una vez más, muy similar en funcionamiento, mucho menor en carga de datos real a través del cable, un poco más lento en codificación y decodificación. Soy un gran admirador de este protocolo, porque realmente comprende muy bien la idea de precisión y tiene una interoperabilidad asombrosa con la mayor parte del ecosistema Kafka. Sí, trabajo mucho con Kafka. Donde hay un pequeño error es que la gente ha implementado cosas además. En realidad, hay algunos sabores diferentes de Avro volando por ahí y eso puede causar algunos problemas. Particularmente con Kafka, se agregan algunos bits mágicos a ciertas cosas dentro de los registros de esquemas, lo que le hará la vida más difícil.

Por otro lado, tenemos formatos binarios aún más amigables. FlatBuffers es una evolución lógica de los buffers de protocolo. Te recomiendo encarecidamente que los mires si eres fanático de los buffers de protocolo. Los FlatBuffers están diseñados directamente para codificar y decodificar mucho mejor en los formatos de sus usuarios finales. Se crearon a partir de la idea de hacer que los buffers de protocolo funcionen para los juegos. Los juegos tienen requisitos de latencia y ancho de banda mucho más estrictos que la mayoría del software SaaS. Luego está MessagePack, que consiste básicamente en decir: lo queremos JSON pero lo queremos binario. De hecho, es hermoso como un paquete de cables sin esquema pero aún así muy flexible y, a menudo, más pequeño que el de protobuf. Sin embargo, todavía tenemos una flexibilidad infinita, porque este binario en realidad mantiene la alineación de bits y usted mantiene la precisión de un objeto en el otro extremo. Sin embargo, una vez más, sin un esquema, todavía necesitarías algo más para proporcionar una verificación formal. Estás haciendo una gran compensación al decir que es posible que todavía necesite un esquema JSON que coincida con mi MessagePack. Caigo mucho en el enfoque de uso de tipo Avro y/o MessagePack. Recomiendo encarecidamente no protobufs la mayor parte del tiempo y digo FlatBuffers como la mejor versión. Otros sistemas como Cap'n Proto son geniales. Trabajan. No tengo opiniones firmes sobre ellos porque nunca he tenido que implementarlos.

Luego, cuando comenzamos a lidiar con estas cosas, realmente hay respuestas. La Fundación Linux ha incorporado recientemente, aproximadamente en los últimos tres años, un proyecto llamado AsyncAPI, que es como la idea de tomar una combinación de sistemas OpenAPI como RAML y otras cosas similares para especificar básicamente qué es una API asincrónica, cosas como MQTT. , cosas como WebSockets, cosas como Kafka. Estos son sistemas que no se limitan a disparar y olvidar. En cambio, a menudo, se abren puertos de transmisiones en conexiones de mayor duración. En realidad, la especificación es bastante detallada, porque especifica qué es un corredor además de solo los puntos finales, por lo que realmente sabes más sobre lo que está sucediendo. Además, tiene, por supuesto, un esquema JSON adjunto para que puedas especificar cosas. Debido a que es un esquema JSON, puedes traducirlo con bastante facilidad a otros tipos con el nivel correcto de extensión. Herramientas como SpecMesh están empezando a aparecer ahora, lo que las hace realmente útiles como consumidor de este material para básicamente especificar su sistema como un catálogo de datos y decir: "Este es mi catálogo de datos. Esta es la especificación para ello. Así es como Accedo." Otras herramientas, como el catálogo de datos de LinkedIn, no llegan tan lejos. Cosas como la propia herramienta interna de Aiven llamada Klaw, que es una forma de gobernar Kafka, con cosas como Karapace, tampoco llegan tan lejos como lo hace SpecMesh. Todos tienden a lo mismo, que es la idea de que necesitamos especificaciones que cubran todo tipo de resultados. Es muy divertido ver esto evolucionar frente a mí. Me encontré con ellos por primera vez recientemente en una reunión y pensé: esto es una locura. ¿Por qué nadie ha hecho esto antes? Lo cual es lo mejor que puedes pensar porque significa que no tienes que construirlo. Por supuesto, todos estos son de código abierto.

La verdadera pregunta que surge es: ¿deberían las API ser sus propios servicios? Debido a que estamos en este mundo distribuido, tenemos muchos dispositivos diferentes y todos están conectados. ¿Por qué las API no existen por sí solas? En realidad, esto es algo que vuelve a surgir una y otra vez, porque si lo piensas, es como, quiero hacerte la pregunta: ¿nos estamos repitiendo? Debido a que ahora tenemos estas cosas que necesitan hablar, es necesario especificar cómo hablan. Luego necesitamos definir qué hacen en esos bordes. La verdadera pregunta que tenemos aquí es mirar la palabra misma, el acrónimo API, que es una interfaz de programa de aplicación. Si nos preguntamos acerca de la repetición, miramos esto y pensamos, si una API es solo una interfaz, los microservicios son objetos, y luego pensamos, ya hemos hecho esto antes, lo cual es maravilloso, porque significa que puedes tomar prestado cosas. Piensas en los principios SÓLIDOS del diseño orientado a objetos. Todo esto depende del principio de que básicamente hemos recreado el diseño orientado a objetos, con todo, desde patrones de observador con microservicios de transmisión hasta principios de diseño SÓLIDOS cuando se trata del diseño de API real. Sin embargo, lo clave que realmente tienes que hacer es mirar SOLID y pensar: ¿cómo funciona este mapa? Realmente muy de cerca. Tenemos la idea de una responsabilidad única. Esta API hace una cosa, y podría ser que exponga mis otras API al mundo. Puede ser que administro un clúster de OpenSearch. Puede ser, yo me ocupo de la nómina.

Entonces, el principio abierto y cerrado es realmente la idea de versionar. Esto es algo enorme en las API. Debes estar abierto a agregar nuevas versiones y cosas nuevas, pero nunca puedes desaprobar cosas de las que dependen las cosas reales, de ahí el principio abierto/cerrado. Vamos a ignorar el tema de la sustitución por la importante razón de que esto no es válido aquí. Esta es una de las pocas áreas del diseño SOLID de la que no soy un gran admirador, porque, fundamentalmente, aunque se trata de objetos, el concepto de herencia no existe. Puede heredar partes de otras API, como la autenticación; sin embargo, su API es específica para su función. Esta es la única parte que no es cierta. Luego, cuando profundizamos en esto de la separación de interfaces, esta es la idea de la interfaz externa donde su API es la interfaz externa para el sistema mismo. Aquí es donde sólo se expone lo que se supone que es, en lugar de lo que hacen todos estos detalles internos. La idea de inversión de dependencia realmente se trata de ser un ciudadano de primera clase en su sistema, porque tiene una especificación y vive con ella, porque si no hace esto, lo que sucede es que tiene estos servicios estrechamente acoplados que el La API está estrechamente acoplada a su interior y ahora existe la opinión de separarla. Esto es como una interfaz. La razón por la que utiliza interfaces es para desacoplar el accesor de las cosas reales detrás de escena invirtiendo la dependencia de la interfaz, no de la implementación real.

Si reformulas un poco todas estas cosas y piensas, ¿qué más tenemos? Tenemos lo que yo llamo la filosofía del diseño de API SEGURA: responsabilidad única, control de versiones de API, especificaciones de primera clase y una interfaz externa. El orden no es del todo perfecto, pero la palabra se ve bien. Como ocurre con las mejores siglas de la historia, aquí es donde voy a aterrizar. En un sistema distribuido, esto es aún más importante, porque a medida que construyes varios bloques, no importa cuántos agregues, siempre y cuando solo hagan una cosa. En realidad, nunca deberías tener dos API diferentes para pagos, a menos que sean sustancialmente diferentes. Tienes que versionar tus API. Tienes que tener especificaciones de primera clase si realmente quieres hacer cosas a escala sin que te exploten. Realmente deberías diseñar una interfaz externa para que tu API sea utilizable. Cuando agregamos capas adicionales, como los principios del observador y todo lo demás que hemos aprendido del diseño de software, y tratamos los bloques API como si fueran solo objetos dentro de su código normal, comienza a ver que hay muchas más formas en las que podríamos extender esto. muchas más letras. Por el bien de una buena imagen, la dejaré como está. La siguiente pregunta que te harás es: ¿miraste esto anteriormente y piensas en repetirlo? Agreguemos uno más solo por el asunto. Si piensa en las API como interfaces, los microservicios como objetos, planteo que AWS Lambda y todas las funciones y servicios son mónadas.

Ver más presentaciones con transcripciones

Grabado en:

30 de agosto de 2023

por

Ben Gamble

Proteger las identidades. Servicios digitales seguros. Habilite el acceso de usuario escalable y seguro a aplicaciones web y móviles. Empiza la prueba gratuita.

Ver más presentaciones con transcripciones
COMPARTIR