Video Summary and Transcription
Los proxies de objetos son middleware para objetos que permiten controlar la entrada y salida. Tienen varios casos de uso, como controlar el acceso a datos, agregar registros y manejar respuestas. Implementar proxies de objetos implica transpilar llamadas en solicitudes de red y manejar el acceso a propiedades y llamadas a métodos. Manejar trampas y errores de proxies de objetos implica falsificar la estructura del objeto, registrar objetos y propiedades objetivo y resolver errores. Realizar llamadas a API con proxies de objetos implica definir el tipo correcto, realizar llamadas al backend y envolver los métodos para devolver promesas. Los proxies de objetos se utilizan ampliamente en bibliotecas ORM y RPC y se deben explorar y experimentar.
1. Introducción a los Proxies de Objetos
Voy a hablar sobre cómo desatar los proxies de objetos, construyendo envoltorios seguros por tipo para cualquier cosa. Los proxies de objetos son middleware para tus objetos que te permiten controlar la entrada y salida. Tienen varios casos de uso como controlar el acceso a los datos, agregar registros y manejar respuestas. Prisma y PRPC son ejemplos de frameworks que utilizan proxies de objetos para acceder a bases de datos y habilitar llamadas de métodos transparentes.
Hola chicos, mi nombre es Akash Joshi. Soy un ingeniero de software en SIGTECH y hoy voy a hablar sobre cómo desatar los proxies de objetos, construyendo envoltorios seguros por tipo para cualquier cosa. Los proxies de objetos son una herramienta increíblemente útil pero poco explorada que se utiliza ampliamente en todas las bibliotecas que usamos en nuestro día a día. Así que voy a explicar cómo funcionan, qué son y cómo puedes usarlos también.
Entonces, ¿qué son los proxies de objetos? Los proxies de objetos son básicamente middleware para tus objetos. Así que piensa en ellos como un envoltorio alrededor de tu objeto con el que puedes controlar qué entrada llega al objeto y qué devuelves del objeto cuando un usuario intenta acceder a un método o una propiedad. Entonces, ¿cuáles son algunos de los casos de uso que vemos con los proxies de objetos en la vida real? Uno de los primeros casos de uso que se nos ocurre es controlar el acceso a los datos. Por ejemplo, realizar validación de entrada o implementar una capa de control de acceso en cualquier tipo de objeto que pueda contener propiedades privilegiadas. Por ejemplo, en un banco, si quieres crear un objeto que contenga los datos del usuario, en función de si un usuario o uno de tus desarrolladores tiene acceso a esa propiedad o no, puedes realizar validación de entrada en ella. Entonces, si el usuario o el desarrollador tiene acceso a esa propiedad, entonces la devuelves, de lo contrario puedes devolver un error o lo que quieras hacer en su lugar.
De manera similar, puedes agregar registros a tus objetos a través de los proxies de objetos. Por ejemplo, si un usuario intenta acceder repetidamente a datos no autorizados, o si simplemente quieres tener un registro de todas las actividades que tus desarrolladores están realizando en cualquier objeto privilegiado, entonces puedes agregar registros a través de los proxies de objetos. Todos los accesos se registran en uno de tus registros de acceso. Pero lo más importante, para lo que usamos los proxies de objetos es el manejo de respuestas. Esto puede implicar devolver algo diferente en función de lo que el usuario proporcione o cargar en memoria de forma diferida ciertos objetos. Por ejemplo, siguiendo con el caso de uso del usuario. Digamos que tenemos un objeto que contiene los detalles bancarios de un usuario, como su historial de direcciones y su historial de transacciones, entre otras cosas. Pero no quieres obtener todo esto en tiempo de ejecución. Entonces, cada vez que alguien intenta acceder a su historial de direcciones, hacemos una llamada a la API de direcciones para obtener todos esos datos y luego los devolvemos como resultado. Así que desde el lado del desarrollador, parece que hiciste una llamada a una propiedad, como user.address. Pero en el backend, en realidad se realiza una llamada a la API. Y también puedes implementar almacenamiento en caché y otras cosas encima de esto para hacer que tus llamadas a objetos sean más rápidas.
En la práctica, los proxies de objetos son utilizados por Prisma. ¿Alguna vez te has preguntado cómo Prisma sabe cuáles son todas tus tablas y cómo puede acceder a todas ellas en tiempo de ejecución, aunque en realidad no estén presentes en su código fuente? Lo que hacen es generar los tipos para todas tus tablas y luego los aplican a un proxy de objeto. Así que cada vez que accedes a una propiedad utilizando el método de punto, como prisma.analytics, prisma.user, prisma.posts, lo que realmente hace es realizar una llamada a la red a través de los proxies de objeto para acceder a la base de datos real y luego obtener esos resultados y devolverlos a tu cliente. Por supuesto, no está todo codificado en el código base de Prisma, sino que se genera en tiempo de ejecución a través de TypeScript y luego los proxies de objetos se encargan del resto. De manera similar, PRPC funciona con un concepto similar donde te permite realizar llamadas a objetos y llamadas a métodos desde tu frontend a tu backend de manera bastante transparente. También utilizan proxies de objetos. Exponen un objeto enrutador en el backend y exponen sus tipos y luego utilizan esos tipos en el frontend.
2. Implementando Proxies de Objetos
La proxyización real ocurre en el frontend donde cualquier llamada a una de sus consultas es transpilada por el proxy de objeto en una llamada de red. Vamos a implementar una versión simple de la biblioteca TRPC y explicar cómo funcionan los proxies de objetos. En el servidor, tenemos un servidor Fastify con una ruta que acepta un nombre de procedimiento y parámetros, los aplica al procedimiento y devuelve la respuesta. El objeto API proporcionado por el desarrollador contiene las APIs expuestas. En el lado del cliente, definimos un proxy de objeto para acceder a propiedades y realizar llamadas a métodos. Utilizamos las propiedades get, has y apply del proxy para manejar el acceso a propiedades y las llamadas a métodos.
Entonces, la proxyización real ocurre en el frontend donde cualquier llamada a una de sus consultas es realmente transpilada por el proxy de objeto en una llamada de red y luego la llamada de red se ejecuta en el backend para devolverte el resultado en el frontend. Así que lo que vamos a hacer ahora es implementar una versión muy simple de la biblioteca TRPC o intentar implementar un poco de RPC al backend nosotros mismos utilizando proxies de objetos. También voy a explicar cómo funcionan los proxies de objetos. Esto solo implica un poco de código. En primer lugar, veamos cómo se ve nuestro servidor. Este es un servidor Fastify muy simple. Si vas a la página de documentación de Fastify, esto es similar a lo que verás en la página principal. De hecho, esto es lo que he copiado de allí. La única ruta que he agregado aquí es /RPC. Acepta un nombre de procedimiento y un conjunto de parámetros para ese procedimiento. Luego, analiza esto a esta cadena de datos y luego aplica esos parámetros a ese procedimiento y luego devuelve la respuesta al frontend.
¿De dónde obtenemos el procedimiento real? Digamos que tenemos un objeto API que el desarrollador proporciona al servidor y este objeto API contiene todas las APIs que el desarrollador realmente quiere exponer al cliente. En este caso, estamos exponiendo hello, que simplemente devuelve world, y luego otro método llamado sum, que devuelve la suma de dos números. Y observa cómo hemos definido correctamente los tipos aquí. Esto se debe a que vamos a usar las propiedades de TypeScript para pasar ese tipo también al cliente.
En el lado del cliente, lo que vamos a hacer ahora es definir un proxy de objeto muy simple. Echa un vistazo al método principal aquí donde voy a definir algunas líneas de código que intentan acceder a algunas propiedades del proxy de objeto y tratan de obtener los resultados de ello. El primero es que intentamos destruir una propiedad llamada hello en un objeto que aún no existe. Luego verificamos dos propiedades para ver si existen en el objeto y por último intentamos hacer llamadas a métodos en el proxy de objeto y luego mostramos el resultado de ello en la consola. Así que definamos rápidamente nuestro proxy de objeto aquí. La sintaxis para definir proxies de objetos es usar un nuevo proxy para definirlos. El primer parámetro que toma es el objeto objetivo en el que quieres construirlo. La forma estándar sería usar un envoltorio sobre un objeto existente. En este caso, vamos a usar solo las propiedades del envoltorio, por lo que paso un objeto vacío.
A continuación, vamos a ver todas las propiedades que acepta un proxy. Principalmente son get, has y apply. Estas son las propiedades en las que te centrarás principalmente al construir proxies de objetos. La propiedad get es el método de punto, por lo que cuando estás destruyendo hello o estás haciendo proxy de cliente.punto o proxy de cliente.cualquierotracosa, entonces se llama al método get y puedes devolver lo que quieras desde él. El método has es como hasOwnProperty, que verifica si una propiedad existe en el proxy de objeto o no, y en este caso simplemente devolvemos true, por lo que estos dos métodos que verifican si estos dos parámetros existen en la propiedad del objeto deben devolver true aunque en realidad no existan allí, y por último tenemos apply. No creo que tengamos suficiente tiempo para profundizar en ello, pero es una trampa para cualquier llamada a métodos que hagas en tu proxy de objeto. En este caso, get debería ser suficiente, profundizaremos en ello a medida que lo definamos.
3. Manejo de Trampas y Errores en los Proxies de Objetos
Tenemos un error de tipo porque la propiedad hello no existe en el objeto vacío. Necesitamos simular la estructura del objeto. Al registrar el objeto objetivo y la propiedad que se llama, podemos ver la salida real. El error hello is not a function es esperado ya que no hemos definido el código para ello. Nos interesan las primeras tres líneas, que muestran el acceso a la propiedad y los valores resultantes. Al cambiar el valor definido a false, podemos imprimir lo que queramos. Para resolver el error, debemos registrar los args en lugar de hacer una llamada a la función.
Permítanme, sí. Ahora tenemos un error de tipo aquí que dice que la propiedad hello no existe en el objeto vacío, eso se debe a que en realidad está devolviendo un tipo de objeto vacío, ya que es el objeto predeterminado que pasamos aquí. Pero dado que vamos a crear trampas en este proxy de objeto, ahora tenemos que simular cómo se ve realmente. Intentemos hacer un console log en get aquí y el primer parámetro que toma es el objeto objetivo. En este caso, nuestro objetivo es un objeto vacío, así que solo obtenemos un guión bajo, no necesitamos realmente usar eso. Lo que realmente estamos buscando es la propiedad que se llama aquí. Luego haremos un console log en property. Y cuando ejecutemos este código aquí, esto ejecuta main que ejecuta todas las líneas aquí. Veremos la salida real. El error que estamos obteniendo aquí es hello is not a function, que es esperado porque está llamando al método hello aquí. Vamos a definir el código para eso. Pero lo que nos interesa más son las primeras tres líneas aquí, estamos obteniendo la propiedad cuando intentamos hacer un console log. Lo siento, cuando intentamos acceder a la propiedad del proxy del cliente aquí y luego obtenemos true y true para la propiedad. Obtenemos true allí porque acabamos de definir true aquí, si lo cambiamos a false y luego lo guardamos. Esto debería ejecutarse nuevamente. Entonces debería imprimir false, simplemente imprimir lo que queramos. Y luego esto está imprimiendo la propiedad real a la que estamos intentando acceder, que es hello en este caso. Y sí, intentemos resolver este error a continuación. Hello is not a function. Esto se debe a que estamos intentando hacer una llamada a un método de función en las propiedades que se están escribiendo aquí. Para resolver esto rápidamente, simplemente deberíamos hacer un console.log de args en su lugar. Y si, ahora devuelve correctamente y simplemente imprime los args que se pasan aquí, la propiedad y lo mismo para some. Imprime qué método estábamos llamando, que es some, y luego los parámetros que se pasan aquí, que son 3 y 123.
4. Realización de Llamadas a la API y Definición del Tipo Correcto
Ahora vamos a realizar una llamada a la API en nuestro backend y devolver los datos. Usamos fetch para hacer la llamada y pasar los parámetros correctamente. Al acceder a las propiedades a través de las llamadas, se realiza la llamada a la API en el backend. Necesitamos definir el tipo correcto importando el tipo de API desde el backend y aplicándolo al proxy del cliente. El proxy del cliente devuelve una promesa al realizar una llamada al método. Usamos el tipo promiseify para envolver los métodos y asegurarnos de que devuelvan promesas. Los puntos clave para construir ObjectProxies son adoptarlos, experimentar y explorar dónde se pueden encontrar en el mundo real.
Ahora vamos a intentar rápidamente hacer una llamada a la API en nuestro backend y devolver los datos basados en lo que nuestro backend parece, que acepta un parámetro llamado procedimiento y un parámetro params y, en función del nombre del procedimiento y params, realiza la llamada a la API. Por ejemplo, para hello y params siendo una matriz vacía, devuelve world; para el nombre del procedimiento siendo some y los parámetros uno y dos, devuelve tres; y hipotéticamente, para otros params, también debería devolver el resultado correcto. Así que hagámoslo rápidamente.
En este caso, lo que hacemos es hacer una llamada fetch aquí, fetch al backend http://host:3000. También necesitamos pasar los parámetros correctamente. Así que agregaré async aquí. Nuestro nombre de procedimiento necesita URLSearchParams. Entonces, searchParams es igual a new URLSearchParams y luego agregar un nombre de procedimiento que debería ser la propiedad y creo que los args aquí, params, lo siento. Pasamos los params como JSON.stringify(args). Creo que esto debe ser un toString y sí, eso resuelve eso y luego simplemente agregamos esto a la llamada a la API. Y al devolver los datos, debemos hacer response.json, por supuesto. Eso debería devolver nuestro resultado. Así que ahora, cuando ejecutemos el método nuevamente, debería devolver el resultado, pero creo que no estoy registrando aquí. Así que echemos un vistazo rápido a qué salió mal. Sí, como podemos ver aquí, cuando intentamos acceder a estas propiedades a través de las llamadas, en realidad se está llamando a la API en el backend. En el lado del servidor, lo que sucede es que acepta el nombre del procedimiento y los params que estamos pasando a través de nuestros URLSearchParams aquí, y luego los analiza y devuelve la respuesta real a través de procedure.apply. Ahora, en este caso, eso es prácticamente todo en términos de nuestro proxy de objeto, pero todavía nos falta la parte real, que es definir el tipo correcto. Puedo importar el tipo de API desde el backend y luego intentar aplicarlo a nuestro proxy del cliente aquí. Así que intentemos hacer eso. Si hago un tipo de API aquí, entonces hipotéticamente debería aplicar el tipo correcto a mi proxy del cliente aquí y luego, si intento hacer algo como clientProxy.then, también debería devolver el tipo correcto aquí. Pero lo que nos falta es que no devuelve una promesa. En nuestro caso, el proxy del cliente en realidad devuelve una promesa cuando se realiza una llamada al método porque en realidad está haciendo una solicitud fetch. Así que definimos un tipo simple aquí llamado promiseify que extiende un registro y luego, en función de las claves de ese registro, simplemente aplica una promesa a los tipos de retorno de todos los métodos existentes. De esta manera, si envolvemos nuestro método en este genérico que acabamos de definir, lo que obtenemos en lugar de hacer clientProxy. es que todos los resultados realmente devuelven una promesa y así obtenemos el tipo correcto en esto. Y así no confundimos más a nuestros usuarios. Eso es todo para nuestro ObjectProxy. ¿Y cuáles son las conclusiones? Las conclusiones aquí para construir tus propios ObjectProxies son adoptarlos, experimentar y explorar. En primer lugar, adopta los ObjectProxies. Intenta ver, intenta descubrir dónde puedes encontrar ObjectProxies en el mundo real.
5. Conclusion and Thanks
La mayoría de los ORMs y bibliotecas RPC utilizan ObjectProxies. Experimenta, crea tus propias bibliotecas RPC y compártelas. Explora los límites de los ObjectProxies. Gracias por ser un buen público y feliz codificación.
Te sorprenderá descubrir que la mayoría de los ORMs podrían estar utilizando ObjectProxies y luego muchas bibliotecas RPC también los utilizan. Experimenta con ObjectProxies. Intenta crear tus propias bibliotecas RPC. Compártelas en Internet y simplemente diviértete con ellas.
Y finalmente, explora. Intenta empujar los límites de los ObjectProxies. TRPC o Rocket RPC, mi biblioteca, no se habría creado si tú, las personas detrás de ellas, no hubieran intentado explorar los límites de los ObjectProxies y lo que puedes hacer con ellos. Lo mismo ocurre con Prisma y muchas otras bibliotecas disponibles.
Así que sí, gracias. Gracias por esta charla. Gracias por ser un buen público. Y feliz codificación. Puedes encontrarme en mi sitio web, thewriting.dev, o en Twitter en TheWritingDev.
Comments