ampliar la API existente con puntos finales personalizados

12

Estoy creando una API para múltiples clientes. Los puntos finales centrales /usersson utilizados por cada cliente, pero algunos puntos finales dependen de la personalización individual. Por lo tanto, podría ser que el usuario A quiera un punto final especial /groupsy ningún otro cliente tendrá esa característica. Como nota al margen , cada cliente también usaría su propio esquema de base de datos debido a esas características adicionales.

Yo personalmente uso NestJs (Express debajo del capó). Entonces, app.moduleactualmente registra todos mis módulos principales (con sus propios puntos finales, etc.)

import { Module } from '@nestjs/common';

import { UsersModule } from './users/users.module'; // core module

@Module({
  imports: [UsersModule]
})
export class AppModule {}

Creo que este problema no está relacionado con NestJs, entonces, ¿cómo manejarías eso en teoría?

Básicamente necesito una infraestructura que pueda proporcionar un sistema básico. Ya no hay puntos finales centrales porque cada extensión es única y /userspodrían ser posibles implementaciones múltiples . Al desarrollar una nueva característica, no se debe tocar la aplicación principal. Las extensiones deben integrarse o integrarse al inicio. El sistema central se envía sin puntos finales, pero se extenderá desde esos archivos externos.

Algunas ideas vienen a mi mente


Primer enfoque:

Cada extensión representa un nuevo repositorio. Defina una ruta a una carpeta externa personalizada que contenga todos esos proyectos de extensión. Este directorio personalizado contendría una carpeta groupscon ungroups.module

import { Module } from '@nestjs/common';

import { GroupsController } from './groups.controller';

@Module({
  controllers: [GroupsController],
})
export class GroupsModule {}

Mi API podría recorrer ese directorio e intentar importar cada archivo de módulo.

  • pros:

    1. El código personalizado se mantiene alejado del repositorio central
  • contras:

    1. NestJs usa Typecript, así que primero tengo que compilar el código. ¿Cómo gestionaría la compilación de API y las compilaciones de las aplicaciones personalizadas? (Sistema plug and play)

    2. Las extensiones personalizadas son muy sueltas porque solo contienen algunos archivos de texto mecanografiado. Debido al hecho de que no tienen acceso al directorio node_modules de la API, mi editor me mostrará errores porque no puede resolver las dependencias de paquetes externos.

    3. Algunas extensiones pueden obtener datos de otra extensión. Quizás el servicio de grupos necesita acceder al servicio de usuarios. Las cosas pueden ponerse difíciles aquí.


Segundo enfoque: mantenga cada extensión dentro de una subcarpeta de la carpeta src de la API. Pero agregue esta subcarpeta al archivo .gitignore. Ahora puede mantener sus extensiones dentro de la API.

  • pros:

    1. Su editor puede resolver las dependencias.

    2. Antes de implementar su código, puede ejecutar el comando de compilación y tendrá una única distribución

    3. Puede acceder a otros servicios fácilmente ( /groupsnecesita encontrar un usuario por id)

  • contras:

    1. Al desarrollar, debe copiar sus archivos de repositorio dentro de esa subcarpeta. Después de cambiar algo, debe volver a copiar estos archivos y anular los archivos del repositorio con los actualizados.

Tercer enfoque:

Dentro de una carpeta personalizada externa, todas las extensiones son API independientes completas. Su API principal solo proporcionaría las cosas de autenticación y podría actuar como un proxy para redirigir las solicitudes entrantes a la API de destino.

  • pros:

    1. Se pueden desarrollar y probar nuevas extensiones fácilmente
  • contras:

    1. La implementación será complicada. Tendrá una API principal y n API de extensión que comenzarán su propio proceso y escucharán un puerto.

    2. El sistema proxy podría ser complicado. Si el cliente solicita que /usersel proxy necesite saber qué extensión API escucha ese punto final, llama a esa API y reenvía esa respuesta al cliente.

    3. Para proteger las API de extensión (la autenticación es manejada por la API principal), el proxy necesita compartir un secreto con esas API. Por lo tanto, la API de extensión solo pasará las solicitudes entrantes si ese proxy coincidente se proporciona desde el proxy.


Cuarto enfoque:

Los microservicios pueden ayudar. Tomé una guía de aquí https://docs.nestjs.com/microservices/basics

Podría tener un microservicio para la gestión de usuarios, la gestión de grupos, etc. y consumir esos servicios creando una pequeña api / gateway / proxy que llame a esos microservicios.

  • pros:

    1. Se pueden desarrollar y probar nuevas extensiones fácilmente

    2. Preocupaciones separadas

  • contras:

    1. La implementación será complicada. Tendrá una API principal yn microservicios que comenzarán su propio proceso y escucharán un puerto.

    2. Parece que tendría que crear una nueva API de puerta de enlace para cada cliente si quisiera personalizarla. Entonces, en lugar de extender una aplicación, tendría que crear una API de resumen personalizada cada vez. Eso no resolvería el problema.

    3. Para proteger las API de extensión (la autenticación es manejada por la API principal), el proxy necesita compartir un secreto con esas API. Por lo tanto, la API de extensión solo pasará las solicitudes entrantes si ese proxy coincidente se proporciona desde el proxy.

hrp8sfH4xQ4
fuente
2
esto podría ayudar a github.com/nestjs/nest/issues/3277
Question3r
Gracias por el enlace. Pero no creo que deba tener las extensiones personalizadas dentro de mi código. Comprobaré si los microservicios resolverán el problema docs.nestjs.com/microservices/basics
hrp8sfH4xQ4
Creo que su problema está relacionado con la autorización más que con el resto.
adnanmuttaleb
@ adnanmuttaleb ¿te importaría explicar por qué =?
hrp8sfH4xQ4

Respuestas:

6

Hay varios enfoques para esto. Lo que debe hacer es determinar qué flujo de trabajo se adapta mejor a su equipo, organización y clientes.

Si esto dependiera de mí, consideraría usar un repositorio por módulo, y usaría un administrador de paquetes como NPM con paquetes privados o de organización para manejar la configuración. Luego configure las tuberías de lanzamiento de compilación que se envían al repositorio de paquetes en las nuevas compilaciones.

De esta manera, todo lo que necesita es el archivo principal y un archivo de manifiesto del paquete por instalación personalizada. Puede desarrollar e implementar nuevas versiones de forma independiente, y puede cargar nuevas versiones cuando lo necesite en el lado del cliente.

Para una mayor suavidad, puede usar un archivo de configuración para asignar módulos a rutas y escribir un script genérico de generador de ruta para realizar la mayor parte del arranque.

Como un paquete puede ser cualquier cosa, las dependencias cruzadas dentro de los paquetes funcionarán sin mucha molestia. Solo necesita ser disciplinado cuando se trata de cambios y gestión de versiones.

Lea más sobre paquetes privados aquí: Paquetes privados NPM

Ahora los registros privados de NPM cuestan dinero, pero si eso es un problema, también hay varias otras opciones. Revise este artículo para conocer algunas alternativas, tanto gratuitas como de pago.

Formas de tener su registro privado de npm

Ahora, si desea crear su propio administrador, puede escribir un localizador de servicios simple, que tome un archivo de configuración que contenga la información necesaria para extraer el código del repositorio, cargarlo y luego proporcionar algún tipo de método para recuperar un instancia a ello.

He escrito una implementación de referencia simple para dicho sistema:

El marco: localizador de servicios de locomoción

Un complemento de ejemplo que busca palíndromos: ejemplo de complemento de locomoción

Una aplicación que usa el marco para localizar complementos: ejemplo de aplicación de locomoción

Puede jugar con esto obteniéndolo de npm usando npm install -s locomotiontendrá que especificar un plugins.jsonarchivo con el siguiente esquema:

{
    "path": "relative path where plugins should be stored",
    "plugins": [
        { 
           "module":"name of service", 
           "dir":"location within plugin folder",
           "source":"link to git repository"
        }
    ]
}

ejemplo:

{
    "path": "./plugins",
    "plugins": [
        {
            "module": "palindrome",
            "dir": "locomotion-plugin-example",
            "source": "https://github.com/drcircuit/locomotion-plugin-example.git"
        }
    ]
}

cargarlo así: const loco = require ("locomoción");

Luego devuelve una promesa que resolverá el objeto localizador de servicios, que tiene el método de localización para obtener sus servicios:

loco.then((svc) => {
    let pal = svc.locate("palindrome"); //get the palindrome service
    if (pal) {
        console.log("Is: no X in Nixon! a palindrome? ", (pal.isPalindrome("no X in Nixon!")) ? "Yes" : "no"); // test if it works :)
    }
}).catch((err) => {
    console.error(err);
});

Tenga en cuenta que esta es solo una implementación de referencia y no es lo suficientemente robusta para una aplicación seria. Sin embargo, el patrón sigue siendo válido y muestra la esencia de escribir este tipo de marco.

Ahora, esto debería extenderse con soporte para configuración de complementos, inicializaciones, verificación de errores, tal vez agregar soporte para inyección de dependencia, etc.

Espen
fuente
Gracias. Surgen dos problemas, parece que confiamos en npm y tenemos que configurar un registro autohospedado. Lo segundo es que el npm privado ya no es gratis. Esperaba encontrar una solución técnica básica. Pero +1 para la idea :)
hrp8sfH4xQ4
1
Se agregó una implementación de referencia de una solución rudimentaria para este tipo de sistema.
Espen
3

Yo iría por la opción de paquetes externos.

Puede estructurar su aplicación para tener una packagescarpeta. Tendría compilaciones compiladas de UMD de paquetes externos en esa carpeta para que su mecanografiado compilado no tenga ningún problema con los paquetes. Todos los paquetes deben tener un index.jsarchivo en la carpeta raíz de cada paquete.

Y su aplicación puede ejecutar un bucle a través de la carpeta de paquetes usando fsy requiretodos los paquetes index.jsen su aplicación.

Por otra parte, la instalación de dependencias es algo de lo que debe encargarse. Creo que un archivo de configuración en cada paquete también podría resolver eso. Puede tener un npmscript personalizado en la aplicación principal para instalar todas las dependencias del paquete antes de iniciar la aplicación.

De esta manera, puede agregar nuevos paquetes a su aplicación copiando y pegando el paquete en la carpeta de paquetes y reiniciando la aplicación. Sus archivos mecanografiados compilados no serán tocados y no tiene que usar npm privado para sus propios paquetes.

Kalesh Kaladharan
fuente
gracias por su respuesta. Creo que esto suena como la solución @ Espen ya publicada, ¿no?
hrp8sfH4xQ4
@ hrp8sfH4xQ4 Sí, hasta cierto punto. Lo agregué después de leer tu comentario sobre no querer usar npm. Lo anterior es una solución que puede hacer para evitar una cuenta privada npm. Además, creo que no necesita agregar paquetes creados por alguien fuera de su organización. ¿Derecha?
Kalesh Kaladharan el
por cierto. pero sería increíble apoyar eso también ... de alguna manera ...
hrp8sfH4xQ4
Si necesita la opción de agregar paquetes de terceros, entonces npmes la forma de hacerlo o de cualquier otro administrador de paquetes. En tales casos, mi solución no será suficiente.
Kalesh Kaladharan el
Si no le importa, me gustaría esperar para reunir tantos enfoques como sea posible
hrp8sfH4xQ4