estender a API existente com terminais personalizados

12

Estou criando uma API para vários clientes. Os pontos finais principais /userssão usados ​​por todos os clientes, mas alguns pontos finais dependem da personalização individual. Portanto, o usuário A deseja um terminal especial /groupse nenhum outro cliente terá esse recurso. Assim como uma nota lateral , cada cliente também usaria seu próprio esquema de banco de dados por causa desses recursos extras.

Eu pessoalmente uso NestJs (Express sob o capô). Então, app.moduleatualmente, registra todos os meus módulos principais (com seus próprios terminais etc.)

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

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

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

Eu acho que esse problema não está relacionado aos NestJs, então como você lidaria com isso em teoria?

Basicamente, preciso de uma infraestrutura capaz de fornecer um sistema básico. Não há mais pontos de extremidade principais porque cada extensão é única e várias /usersimplementações podem ser possíveis. Ao desenvolver um novo recurso, o aplicativo principal não deve ser tocado. As extensões devem se integrar ou se integrar na inicialização. O sistema principal é fornecido sem pontos de extremidade, mas será estendido desses arquivos externos.

Algumas idéias vêm à minha mente


Primeira abordagem:

Cada extensão representa um novo repositório. Defina um caminho para uma pasta externa personalizada contendo todos os projetos de extensão. Esse diretório personalizado conteria uma pasta groupscom umgroups.module

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

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

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

Minha API pode percorrer esse diretório e tentar importar cada arquivo de módulo.

  • profissionais:

    1. O código customizado é mantido longe do repositório principal
  • contras:

    1. Os NestJs usam o Typecript, então eu tenho que compilar o código primeiro. Como gerenciar a compilação da API e as compilações dos aplicativos personalizados? (Sistema Plug and Play)

    2. As extensões personalizadas são muito frouxas porque contêm apenas alguns arquivos datilografados. Como eles não têm acesso ao diretório node_modules da API, meu editor me mostrará erros porque não pode resolver dependências externas de pacotes.

    3. Algumas extensões podem buscar dados de outra extensão. Talvez o serviço de grupos precise acessar o serviço de usuários. As coisas podem ficar complicadas aqui.


Segunda abordagem: mantenha cada extensão dentro de uma subpasta da pasta src da API. Mas adicione essa subpasta ao arquivo .gitignore. Agora você pode manter suas extensões dentro da API.

  • profissionais:

    1. Seu editor é capaz de resolver as dependências

    2. Antes de implantar seu código, você pode executar o comando build e terá uma única distribuição

    3. Você pode acessar outros serviços facilmente ( /groupsprecisa encontrar um usuário por ID)

  • contras:

    1. Ao desenvolver, você deve copiar seus arquivos de repositório dentro dessa subpasta. Depois de alterar algo, você deve copiar esses arquivos novamente e substituir os arquivos do repositório pelos atualizados.

Terceira abordagem:

Dentro de uma pasta personalizada externa, todas as extensões são APIs autônomas. Sua API principal forneceria apenas o material de autenticação e poderia atuar como um proxy para redirecionar as solicitações recebidas para a API de destino.

  • profissionais:

    1. Novas extensões podem ser desenvolvidas e testadas facilmente
  • contras:

    1. A implantação será complicada. Você terá um dos principais API e n de extensão APIs começando seu próprio processo e ouvir uma porta.

    2. O sistema proxy pode ser complicado. Se o cliente solicitar que /userso proxy precise saber qual API de extensão atende esse ponto de extremidade, chama a API e encaminha a resposta de volta ao cliente.

    3. Para proteger as APIs de extensão (a autenticação é manipulada pela API principal), o proxy precisa compartilhar um segredo com essas APIs. Portanto, a API de extensão só passará solicitações de entrada se esse segredo correspondente for fornecido a partir do proxy.


Quarta abordagem:

Os microsserviços podem ajudar. Peguei um guia a partir daqui https://docs.nestjs.com/microservices/basics

Eu poderia ter um microsserviço para gerenciamento de usuários, gerenciamento de grupos etc. e consumir esses serviços criando uma pequena API / gateway / proxy que chama esses microsserviços.

  • profissionais:

    1. Novas extensões podem ser desenvolvidas e testadas facilmente

    2. Preocupações separadas

  • contras:

    1. A implantação será complicada. Você terá uma API principal e n microservices começar seu próprio processo e ouvir uma porta.

    2. Parece que eu precisaria criar uma nova API de gateway para cada cliente, se quiser personalizá-la. Então, em vez de estender um aplicativo, eu teria que criar uma API de consumo personalizada a cada vez. Isso não resolveria o problema.

    3. Para proteger as APIs de extensão (a autenticação é manipulada pela API principal), o proxy precisa compartilhar um segredo com essas APIs. Portanto, a API de extensão só passará solicitações de entrada se esse segredo correspondente for fornecido a partir do proxy.

hrp8sfH4xQ4
fonte
2
isso pode ajudar github.com/nestjs/nest/issues/3277
Question3r
Obrigado pelo link. Mas acho que não deveria ter as extensões personalizadas dentro do meu código. Vou verificar se microservices vai resolver o problema docs.nestjs.com/microservices/basics
hrp8sfH4xQ4
Acho que seu problema está relacionado à autorização, e não ao descanso.
precisa saber é o seguinte
@ adnanmuttaleb você se importaria de explicar por que =?
Hrp8sfH4xQ4 1/11

Respostas:

6

Existem várias abordagens para isso. O que você precisa fazer é descobrir qual fluxo de trabalho é mais adequado para sua equipe, organização e clientes.

Se isso dependesse de mim, consideraria usar um repositório por módulo e usar um gerenciador de pacotes como o NPM com pacotes com escopo privado ou organizacional para lidar com a configuração. Em seguida, configure os pipelines de liberação de compilação que são enviados para o repositório de pacotes em novas compilações.

Dessa forma, tudo o que você precisa é o arquivo principal e um arquivo de manifesto do pacote por instalação personalizada. Você pode desenvolver e implantar novas versões independentemente e carregar novas versões quando precisar no lado do cliente.

Para maior suavidade, você pode usar um arquivo de configuração para mapear módulos para rotas e escrever um script gerador de rotas genérico para executar a maior parte do processo de inicialização.

Como um pacote pode ser qualquer coisa, as dependências cruzadas dentro dos pacotes funcionarão sem muito aborrecimento. Você só precisa ser disciplinado quando se trata de gerenciamento de alterações e versões.

Leia mais sobre pacotes particulares aqui: Pacotes Particulares NPM

Agora, os registros privados do NPM custam dinheiro, mas se esse for um problema, também existem várias outras opções. Consulte este artigo para obter algumas alternativas - gratuitas e pagas.

Maneiras de ter seu registro npm privado

Agora, se você quiser rolar seu próprio gerente, poderá escrever um localizador de serviço simples, que inclua um arquivo de configuração que contenha as informações necessárias para extrair o código do repositório, carregá-lo e, em seguida, fornecer algum tipo de método para recuperar um instância para ele.

Eu escrevi uma implementação de referência simples para esse sistema:

A estrutura: localizador de serviços de locomoção

Um exemplo de plugin que verifica palíndromos: exemplo de plugin de locomoção

Um aplicativo usando a estrutura para localizar plugins: exemplo de aplicativo de locomoção

Você pode brincar com isso obtendo-o do npm usando. npm install -s locomotionVocê precisará especificar um plugins.jsonarquivo com o seguinte esquema:

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

exemplo:

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

carregue-o assim: const loco = require ("locomotion");

Em seguida, ele retorna uma promessa que resolverá o objeto localizador de serviço, que possui o método localizador para obter seus serviços:

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);
});

Observe que essa é apenas uma implementação de referência e não é robusta o suficiente para aplicações sérias. No entanto, o padrão ainda é válido e mostra a essência de escrever esse tipo de estrutura.

Agora, isso precisaria ser estendido com suporte para configuração de plugins, inicializações, verificação de erros, talvez adicionar suporte para injeção de dependência e assim por diante.

Espen
fonte
Obrigado. Surgem dois problemas, parece que dependemos do npm e precisamos configurar um registro auto-hospedado. A segunda coisa é que o NPM privado não é mais gratuito. Eu esperava encontrar uma solução técnica básica. Mas +1 para a ideia :)
hrp8sfH4xQ4
11
adicionou uma implementação de referência de uma solução rudimentar para esse tipo de sistema.
Espen
3

Eu iria para a opção de pacotes externos.

Você pode estruturar seu aplicativo para ter uma packagespasta. Eu teria compilações UMD compiladas de pacotes externos nessa pasta para que seu texto datilografado compilado não tenha nenhum problema com os pacotes. Todos os pacotes devem ter um index.jsarquivo na pasta raiz de cada pacote.

E seu aplicativo pode executar um loop pela pasta packages usando fse requiretodos os pacotes index.jsem seu aplicativo.

Então, novamente, a instalação de dependência é algo que você precisa cuidar. Eu acho que um arquivo de configuração em cada pacote também pode resolver isso. Você pode ter um npmscript personalizado no aplicativo principal para instalar todas as dependências do pacote antes de iniciar o aplicativo.

Dessa forma, você pode adicionar novos pacotes ao seu aplicativo copiando e colando o pacote na pasta packages e reiniciando o aplicativo. Seus arquivos datilografados compilados não serão tocados e você não precisará usar o npm privado para seus próprios pacotes.

Kalesh Kaladharan
fonte
Obrigado pela sua resposta. Eu acho que isso soa como a solução @ Espen já postou, não?
Hrp8sfH4xQ4
@ hrp8sfH4xQ4 Sim, até um certo ponto. Eu adicionei depois de ler o seu comentário sobre não querer usar npm. Acima está uma solução que você pode fazer para evitar uma conta npm privada. Além disso, acredito que você não precisa adicionar pacotes criados por alguém de fora da sua organização. Direita?
Kalesh Kaladharan 4/11/19
btw. mas seria incrível para suportar isso também ... de alguma forma ...
hrp8sfH4xQ4
Se você precisar da opção de adicionar pacotes de terceiros, npmé esse o caminho ou qualquer outro gerenciador de pacotes. Nesses casos, minha solução não será suficiente.
Kalesh Kaladharan 4/11/19
Se você não se importa que eu gostaria de esperar para recolher o maior número aproxima-se possível
hrp8sfH4xQ4