Tem havido muito burburinho em torno das funções como serviço (FaaS), comumente chamadas de sem servidor. Um provedor popular para essas funções é a Amazon com seu AWS Lambda serviço. É possível criar uma função em qualquer uma das tecnologias Lambda compatíveis, por exemplo, Node.js, implantá-la no AWS Lambda, acessá-la por meio do AWS API Gateway e escaloná-la por conta própria para atender à demanda.
Veremos como usar o Node.js com o AWS Lambda para criar funções que podem se comunicar com documentos NoSQL no formato Servidor Couchbase.
A suposição daqui para frente é que você tenha um cluster ou uma única instância do Couchbase Server em execução em algum lugar na nuvem. Em outras palavras, este projeto não funcionará se você estiver executando o Couchbase em seu computador local. Como estamos executando isso no AWS, pode fazer sentido que o Couchbase exista como uma instância do EC2. Isso pode ser feito por usando uma AMI disponível.
Criação de um projeto FaaS para Lambda e API Gateway com a estrutura sem servidor
Se você já trabalhou com o AWS Lambda e o AWS API Gateway antes, sabe que não é a experiência mais agradável começar algo do zero e depois implantar. Para facilitar um pouco nossa vida, usaremos o Estrutura sem servidorque cuidará de grande parte do trabalho pesado, sem alterar a forma como desenvolvemos as funções Lambda.
Supondo que você já tenha o Node.js instalado, execute o seguinte para obter a CLI do Serverless Framework:
|
1 |
npm instalar -g sem servidor |
Com a CLI, podemos gerar novos projetos com base em modelos disponíveis, não limitados ao AWS. Para criar um novo projeto com a CLI, execute o seguinte:
|
1 |
sem servidor criar --modelo aws-nodejs --caminho ./meu-projeto |
Estaremos criando várias funções dentro do meu projeto que foi criado. É aqui que as coisas podem ficar um pouco estranhas.
Nosso projeto Node.js exigirá o SDK do Couchbase, um pacote para gerar valores UUID e um pacote para validação de dados. O problema é que estou usando um Mac e o Lambda está usando uma versão especial do Linux. Se eu tentar fazer o download das dependências no meu Mac, elas não serão compatíveis com o Linux. Em vez disso, temos que passar por alguns obstáculos.
Há um artigo on-line intitulado, Uso de dependências nativas com o AWS Lambdaque explica como o Docker pode ser usado para realizar a tarefa de dependências de projeto nativas que funcionam para o Lambda.
Em resumo, você deve executar o seguinte com o Docker instalado e com a CLI no diretório atual do projeto:
|
1 2 |
doca puxar amazonlinux doca executar -v $(pwd):/lambda-projeto -ele amazonlinux |
Os comandos acima extrairiam a imagem do Amazon Linux e implantariam um contêiner, mapeando o diretório atual do projeto como um volume de contêiner. Dentro do contêiner, o Node.js pode ser instalado e as dependências do nosso pacote podem ser obtidas. Para ver como fazer isso, confira um artigo anterior que escrevi intitulado, Implantação de dependências nativas do Node.js no AWS Lambda.
Você precisará instalar as três dependências a seguir para o projeto:
|
1 2 3 |
npm instalar couchbase --salvar npm instalar joi --salvar npm instalar uuid --salvar |
Não desanime de continuar por causa do requisito do Amazon Linux. Basicamente, você está apenas executando os três comandos de dentro do contêiner e, como o contêiner tem um volume mapeado, todos os arquivos obtidos acabarão diretamente no seu projeto.
Desenvolvimento de funções para operações CRUD no banco de dados NoSQL
Com o projeto Serverless Framework pronto, podemos nos concentrar no que realmente importa. O objetivo aqui é criar quatro funções diferentes, uma para cada criação, recuperação, atualização e exclusão de documentos NoSQL do Couchbase. É isso mesmo, estamos criando um conjunto de funções CRUD.
Abra o arquivo handler.js porque precisamos fazer o bootstrap:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
'use strict'; const Couchbase = exigir("couchbase"); const UUID = exigir("uuid"); const Joi = exigir("joi"); var agrupamento = novo Couchbase.Aglomerado("couchbase://seu-servidor-aqui"); agrupamento.autenticar("demo", "123456"); var balde = agrupamento.openBucket("exemplo"); balde.em("error" (erro), erro => { console.dir(erro); }); módulo.exportações.criar = (evento, contexto, retorno de chamada) => { }; módulo.exportações.recuperar = (evento, contexto, retorno de chamada) => { }; módulo.exportações.atualização = (evento, contexto, retorno de chamada) => { }; módulo.exportações.excluir = (evento, contexto, retorno de chamada) => { }; |
No código acima, importamos as dependências do nosso projeto, nos conectamos a um cluster do Couchbase, nos autenticamos nesse cluster e abrimos um determinado Bucket. Certifique-se de alterar as informações do Couchbase para refletir suas informações reais.
Também criamos as quatro funções mencionadas anteriormente. No entanto, até o momento, essas funções não fazem nada.
Algo importante a ser observado aqui. Nossa lógica de conexão com o banco de dados está acontecendo fora de nossas funções. É uma tarefa dispendiosa conectar-se ao banco de dados a cada invocação de uma função. Ao mover essas informações para fora, elas ainda podem ser acessadas por qualquer contêiner de função Lambda.
Começando com o criar função:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 |
módulo.exportações.criar = (evento, contexto, retorno de chamada) => { contexto.callbackWaitsForEmptyEventLoop = falso; var esquema = Joi.objeto().chaves({ primeiro nome: Joi.string().necessário(), sobrenome: Joi.string().necessário(), tipo: Joi.string().proibido().padrão("pessoa") }); var dados = JSON.analisar(evento.corpo); var resposta = {}; var validação = Joi.validar(dados, esquema); se(validação.erro) { resposta = { statusCode: 500, corpo: JSON.stringify(validação.erro.detalhes) }; retorno retorno de chamada(nulo, resposta); } var id = UUID.v4(); balde.inserir(id, validação.valor, (erro, resultado) => { se(erro) { resposta = { statusCode: 500, corpo: JSON.stringify({ código: erro.código, mensagem: erro.mensagem }) }; retorno retorno de chamada(nulo, resposta); } dados.id = id; resposta = { statusCode: 200, corpo: JSON.stringify(dados) }; retorno de chamada(nulo, resposta); }); }; |
Há muita coisa acontecendo no código acima, portanto, precisamos dividi-lo.
Primeiro, estamos adicionando a seguinte linha:
|
1 |
contexto.callbackWaitsForEmptyEventLoop = falso; |
Ao adicionar a linha acima, estamos mudando a forma como a função espera para responder. Se não a definirmos como false, as coisas assíncronas a seguir provavelmente atingirão o tempo limite antes da conclusão.
|
1 2 3 4 5 6 7 8 |
var esquema = Joi.objeto().chaves({ primeiro nome: Joi.string().necessário(), sobrenome: Joi.string().necessário(), tipo: Joi.string().proibido().padrão("pessoa") }); var dados = JSON.analisar(evento.corpo); var resposta = {}; var validação = Joi.validar(dados, esquema); |
Como criaremos dados com base na entrada do usuário, é uma boa ideia validar essa entrada do usuário. Ao usar JoiSe o evento for um evento, podemos criar um esquema de validação e usá-lo para validar o corpo de dados passado com o evento. Talvez você reconheça que o Joi é uma estrutura de validação que usei em artigos anteriores, como, Criar uma API RESTful com Node.js, Hapi e Couchbase NoSQL.
|
1 2 3 4 5 6 7 |
se(validação.erro) { resposta = { statusCode: 500, corpo: JSON.stringify(validação.erro.detalhes) }; retorno retorno de chamada(nulo, resposta); } |
Se houver um erro de validação, enviaremos um erro 500 como resposta. Caso contrário, prosseguiremos com a geração de uma nova chave de documento e a salvaremos no Couchbase Server:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
var id = UUID.v4(); balde.inserir(id, validação.valor, (erro, resultado) => { se(erro) { resposta = { statusCode: 500, corpo: JSON.stringify({ código: erro.código, mensagem: erro.mensagem }) }; retorno retorno de chamada(nulo, resposta); } dados.id = id; resposta = { statusCode: 200, corpo: JSON.stringify(dados) }; retorno de chamada(nulo, resposta); }); |
Observe como o Lambda espera que as respostas sejam formatadas. As respostas devem ter um statusCode e um corpo.
Agora vamos dar uma olhada no recuperar função:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
módulo.exportações.recuperar = (evento, contexto, retorno de chamada) => { contexto.callbackWaitsForEmptyEventLoop = falso; var resposta = {}; var declaração = "SELECT META().id, `" + balde._nome + "`.* FROM `" + balde._nome + "` WHERE type = 'person'"; var consulta = Couchbase.N1qlQuery.fromString(declaração); balde.consulta(consulta, (erro, resultado) => { se(erro) { resposta = { statusCode: 500, corpo: JSON.stringify({ código: erro.código, mensagem: erro.mensagem }) }; retorno retorno de chamada(nulo, resposta); } resposta = { statusCode: 200, corpo: JSON.stringify(resultado) }; retorno de chamada(nulo, resposta); }); }; |
O objetivo com o recuperar A função Lambda é usar o N1QL para consultar todos os documentos no Couchbase e retorná-los. Dependendo do resultado, formataremos a resposta do Lambda de forma apropriada. Observe que estamos alterando o callbackWaitsForEmptyEventLoop valor. Isso ocorrerá em cada uma de nossas funções.
Como estruturamos o atualização será semelhante à forma como estruturamos a função criar função:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 |
módulo.exportações.atualização = (evento, contexto, retorno de chamada) => { contexto.callbackWaitsForEmptyEventLoop = falso; var esquema = Joi.objeto().chaves({ primeiro nome: Joi.string().opcional(), sobrenome: Joi.string().opcional() }); var dados = JSON.analisar(evento.corpo); var resposta = {}; var validação = Joi.validar(dados, esquema); se(validação.erro) { resposta = { statusCode: 500, corpo: JSON.stringify(validação.erro.detalhes) }; retorno retorno de chamada(nulo, resposta); } var construtor = balde.mutateIn(evento.pathParameters.id); se(dados.primeiro nome) { construtor.substituir("firstname", dados.primeiro nome); } se(dados.sobrenome) { construtor.substituir("lastname" (sobrenome), dados.sobrenome); } construtor.executar((erro, resultado) => { se(erro) { resposta = { statusCode: 500, corpo: JSON.stringify({ código: erro.código, mensagem: erro.mensagem }) }; retorno retorno de chamada(nulo, resposta); } resposta = { statusCode: 200, corpo: JSON.stringify(dados) }; retorno de chamada(nulo, resposta); }); }; |
Observe que começamos fazendo alguma validação de dados com base em nosso esquema. Se os dados forem considerados válidos, poderemos prosseguir com as operações de subdocumentos no Couchbase:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
var construtor = balde.mutateIn(evento.pathParameters.id); se(dados.primeiro nome) { construtor.substituir("firstname", dados.primeiro nome); } se(dados.sobrenome) { construtor.substituir("lastname" (sobrenome), dados.sobrenome); } construtor.executar((erro, resultado) => { se(erro) { resposta = { statusCode: 500, corpo: JSON.stringify({ código: erro.código, mensagem: erro.mensagem }) }; retorno retorno de chamada(nulo, resposta); } resposta = { statusCode: 200, corpo: JSON.stringify(dados) }; retorno de chamada(nulo, resposta); }); |
Basicamente, estamos criando um construtor de mutações com base nas informações encontradas na solicitação. As mutações adicionadas ocorrerão em relação a uma chave quando executadas. O resultado da execução será retornado como uma resposta do Lambda.
Observe o seguinte:
|
1 |
evento.pathParameters.id |
Esses são dados que não vêm do corpo. Ele será configurado em uma das próximas etapas.
Agora ficamos com o excluir função:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
módulo.exportações.excluir = (evento, contexto, retorno de chamada) => { contexto.callbackWaitsForEmptyEventLoop = falso; var esquema = Joi.objeto().chaves({ id: Joi.string().necessário() }); var dados = JSON.analisar(evento.corpo); var resposta = {}; var validação = Joi.validar(dados, esquema); se(validação.erro) { resposta = { statusCode: 500, corpo: JSON.stringify(validação.erro.detalhes) }; retorno retorno de chamada(nulo, resposta); } balde.remover(dados.id, (erro, resultado) => { se(erro) { resposta = { statusCode: 500, corpo: JSON.stringify({ código: erro.código, mensagem: erro.mensagem }) }; retorno retorno de chamada(nulo, resposta); } resposta = { statusCode: 200, corpo: JSON.stringify(dados) }; retorno de chamada(nulo, resposta); }); }; |
Se você acompanhou tudo até agora, o excluir não parecerá nada novo para você. Estamos validando dados e executando-os no banco de dados.
Com as funções criadas, podemos nos concentrar nas informações de configuração da Serverless Framework. Abra a seção serverless.yml e inclua o seguinte:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
serviço: couchbase-lambda provedor: nome: aws tempo de execução: nodejs6.10 funções: criar: manipulador: manipulador.criar eventos: - http: caminho: criar método: postagem recuperar: manipulador: manipulador.recuperar eventos: - http: caminho: recuperar método: obter atualização: manipulador: manipulador.atualização eventos: - http: caminho: atualização/{id} método: colocar excluir: manipulador: manipulador.excluir eventos: - http: caminho: excluir método: excluir |
A parte mais importante do documento YAML acima são as funções. Nomeamos cada função e as vinculamos à função de controle apropriada. A função eventos são como essas funções serão invocadas em um navegador. O eventos são a configuração do AWS API Gateway.
No eventos definimos as informações do caminho, como cada endpoint pode ser acessado, seja a partir de uma solicitação GET ou POST ou qualquer outra coisa, e qualquer tipo de variável de caminho.
Lembra-se da frase que eu lhe disse para lembrar?
|
1 |
evento.pathParameters.id |
Isso é mapeado para o id no valor update/{id} caminho.
Nesse momento, o projeto está pronto para ser implantado no Amazon Web Services.
Implementação das funções no AWS Lambda com suporte ao API Gateway
Não entraremos em detalhes quando se trata de configurar o Serverless Framework com suas chaves públicas e privadas da Amazon, mas nos preocuparemos com a implantação, supondo que elas estejam configuradas.
Na CLI do Serverless Framework, execute o seguinte:
|
1 |
sem servidor implantação |
O comando acima utilizará o código das funções, bem como o módulos_nó que foram gerados a partir do contêiner do Amazon Linux Docker e enviá-los para a nuvem do AWS. O Serverless Framework se preocupará em configurar o AWS Lambda e o API Gateway com base no que estava no contêiner serverless.yml arquivo.
Conclusão
Você acabou de ver como se comunicar com Servidor Couchbase de um conjunto de funções controladas pelo AWS Lambda e pelo AWS API Gateway. Isso é útil se você quiser criar aplicativos que são cobrados pelo tempo em que são usados, e não pelo tempo em que estão ativos. Incluir o Couchbase não foi muito diferente de incluí-lo em qualquer outro aplicativo Node.js, com exceção das dependências nativas do Node.js.
Se você decidir usar o Couchbase na nuvem, certifique-se de que as portas corretas estejam abertas para o Lambda. Cometi o erro de esquecer de abrir todas as portas exigidas pelo SDK e isso me atrasou um pouco.
Para obter mais informações sobre como usar o Couchbase Server, consulte o Portal do desenvolvedor do Couchbase.
Oi Nic,
Estou tentando colocar isso em funcionamento com um exemplo que apenas se conecta a um servidor couchbase (no AWS) e tem uma função que retorna um texto de teste.
Ele está apresentando este erro:
"errorMessage": "/var/lang/lib/libstdc++.so.6: a versão `CXXABI_1.3.9′ não foi encontrada (exigida por /var/task/node_modules/couchbase/build/Release/couchbase_impl.node)",
e meu google-fu está falhando. Você sabe qual é o problema?
Preciso atualizar o c++ na imagem da janela de encaixe?
No entanto, parece que é um problema de versão do compilador na AWS.