Desenvolvimento sem servidor com AWS Lambda e MongoDB Atlas usando Java
Avalie esse Tutorial
Então, você precisa criar um aplicativo que seja dimensionado com a demanda e um banco de dados para ser dimensionado com ele? Pode fazer sentido explorar funções sem servidor, como as oferecidas pelo AWS Lambda, e um banco de dados em cloud como o MongoDB Atlas.
As funções sem servidor são ótimas porque você pode implementar uma lógica muito específica na forma de uma função e a infraestrutura será dimensionada automaticamente para atender à demanda dos seus usuários. Isso evitará que você tenha que gastar quantias potencialmente grandes de dinheiro em infraestrutura sempre ativa, mas nem sempre necessária. Combine isso com um banco de dados elasticamente escalável como o MongoDB Atlas e você terá uma coisa surpreendente em andamento.
Neste tutorial, vamos explorar como criar uma função sem servidor com AWS Lambda e MongoDB, mas vamos nos concentrar no uso de Java, um dos tempos de execução AWS Lambda disponíveis.
Para ter sucesso com este tutorial, existem alguns requisitos que devem ser atendidos antes de continuar.
- Deve ter uma versãode Javacompatível com AWS Lambda instalada e configurada em seu computador local.
- Deve ter uma conta da Amazon Web Services (AWS).
- Deve ter o Gradle ou o Maven, mas o Gradle será o foco do gerenciamento de dependências.
Para este tutorial, o tamanho da instância ou camada do MongoDB Atlas não é muito importante. Na verdade, uma instância M0 , que é gratuita, funcionará bem. Você também pode usar uma instância sem servidor que combina muito bem com a arquitetura sem servidor do AWS Lambda. Como a configuração do Atlas está fora do escopo deste tutorial, você já precisará ter suas regras de usuário e de acesso à rede em vigor. Se precisar de ajuda para configurar o MongoDB Atlas, considere conferir o guiade introdução.
Entrando neste tutorial, você pode começar com o seguinte código boilerplate da AWS Lambda para Java:
1 package example; 2 3 import com.amazonaws.services.lambda.runtime.Context; 4 import com.amazonaws.services.lambda.runtime.RequestHandler; 5 6 public class Handler implements RequestHandler<Map<String,String>, Void>{ 7 8 9 public void handleRequest(Map<String,String> event, Context context) { 10 // Code will be in here... 11 return null; 12 } 13 }
Você pode usar um IDE de desenvolvimento popular como o IntelliJ, mas isso não importa, desde que você tenha acesso ao Gradle ou Maven para criar seu projeto.
Falando em Gradle, o seguinte pode ser usado como padrão para nossas tarefas e dependências:
1 plugins { 2 id 'java' 3 } 4 5 group = 'org.example' 6 version = '1.0-SNAPSHOT' 7 8 repositories { 9 mavenCentral() 10 } 11 12 dependencies { 13 testImplementation platform('org.junit:junit-bom:5.9.1') 14 testImplementation 'org.junit.jupiter:junit-jupiter' 15 implementation 'com.amazonaws:aws-lambda-java-core:1.2.2' 16 implementation 'com.amazonaws:aws-lambda-java-events:3.11.1' 17 implementation 'org.slf4j:slf4j-log4j12:1.7.36' 18 runtimeOnly 'com.amazonaws:aws-lambda-java-log4j2:1.5.1' 19 } 20 21 test { 22 useJUnitPlatform() 23 } 24 25 task buildZip(type: Zip) { 26 into('lib') { 27 from(jar) 28 from(configurations.runtimeClasspath) 29 } 30 } 31 32 build.dependsOn buildZip
Observe que temos nossas dependências do Amazon Web Services Lambda incluídas, bem como uma tarefa para agrupar tudo em um arquivo ZIP quando construirmos.
Com a função de linha de base do AWS Lambda em vigor, podemos nos concentrar no lado de desenvolvimento do MongoDB das coisas.
Para começar, precisaremos do driver do MongoDB para Java disponível para nós. Essa dependência pode ser adicionada ao arquivobuild.gradledo nosso projeto:
1 dependencies { 2 // Previous boilerplate dependencies ... 3 implementation 'org.mongodb:bson:4.10.2' 4 implementation 'org.mongodb:mongodb-driver-sync:4.10.2' 5 }
As duas linhas acima indicam que queremos usar o driver para interagir com o MongoDB e também queremos poder interagir com o BSON.
Com o driver e os componentes relacionados disponíveis para nós, vamos revisar o código Java que vimos anteriormente. Neste exemplo específico, o código Java será encontrado em um src/main/Java/example/Handler. Arquivo Java.
1 package example; 2 3 import com.amazonaws.services.lambda.runtime.Context; 4 import com.amazonaws.services.lambda.runtime.RequestHandler; 5 import com.mongodb.client.MongoClient; 6 import com.mongodb.client.MongoClients; 7 import com.mongodb.client.MongoCollection; 8 import com.mongodb.client.MongoDatabase; 9 import com.mongodb.client.model.Filters; 10 import org.bson.BsonDocument; 11 import org.bson.Document; 12 import org.bson.conversions.Bson; 13 14 import java.util.ArrayList; 15 import java.util.List; 16 import java.util.Map; 17 18 public class Handler implements RequestHandler<Map<String,String>, Void>{ 19 20 private final MongoClient mongoClient; 21 22 public Handler() { 23 mongoClient = MongoClients.create(System.getenv("MONGODB_ATLAS_URI")); 24 } 25 26 27 public void handleRequest(Map<String,String> event, Context context) { 28 MongoDatabase database = mongoClient.getDatabase("sample_mflix"); 29 MongoCollection<Document> collection = database.getCollection("movies"); 30 31 // More logic here ... 32 33 return null; 34 } 35 }
No código acima, importamos algumas classes, mas também fizemos algumas alterações referentes à forma como planejamos interagir com o MongoDB.
A primeira coisa que você notará é o nosso uso do método construtor
Handler
:1 public Handler() { 2 mongoClient = MongoClients.create(System.getenv("MONGODB_ATLAS_URI")); 3 }
Estamos estabelecendo nosso cliente, não nossa conexão, fora da própria função do manipulador. Estamos fazendo isso para que nossas conexões possam ser reutilizadas e não estabelecidas em cada invocação, o que potencialmente nos sobrecarregaria com muitas conexões simultâneas. Também estamos referenciando uma variável de ambiente para nossa string de URI do MongoDB Atlas. Isso será definido posteriormente no portal do AWS Lambda.
É uma prática ruim codificar sua string de URI em seu aplicativo. Use um arquivo de configuração ou variável de ambiente sempre que possível.
Em seguida, temos a lógica da função onde pegamos uma referência ao nosso banco de dados e coleção:
1 2 public void handleRequest(Map<String,String> event, Context context) { 3 MongoDatabase database = mongoClient.getDatabase("sample_mflix"); 4 MongoCollection<Document> collection = database.getCollection("movies"); 5 6 // More logic here ... 7 8 return null; 9 }
Como este exemplo foi feito para ser suficiente para você começar, estamos usando os conjuntos de dados de amostra que estão disponíveis para usuários do MongoDB Atlas. Não importa o que você usa neste exemplo, desde que tenha uma collection com alguns dados.
Estamos a caminho do sucesso com o MongoDB e Amazon Web Services Lambda!
Com a configuração do cliente em vigor, podemos nos concentrar na interação com o MongoDB. Antes de fazermos isso, algumas coisas precisam mudar no design de nossa função:
1 public class Handler implements RequestHandler<Map<String,String>, List<Document>>{ 2 3 private final MongoClient mongoClient; 4 5 public Handler() { 6 mongoClient = MongoClients.create(System.getenv("MONGODB_ATLAS_URI")); 7 } 8 9 10 public List<Document> handleRequest(Map<String,String> event, Context context) { 11 MongoDatabase database = mongoClient.getDatabase("sample_mflix"); 12 MongoCollection<Document> collection = database.getCollection("movies"); 13 14 // More logic here ... 15 16 return null; 17 } 18 }
Observe que o
RequestHandler
implementado agora utiliza List<Document>
ao invés de Void
. O tipo de retorno da função handleRequest
também foi alterado de void
para List<Document>
para nos ajudar a retornar uma array de documentos ao cliente solicitante.Embora você possa usar uma abordagem POJO em sua função, em vez disso,usaremos
Document
.Se quisermos consultar o MongoDB e retornar os resultados, podemos fazer algo assim:
1 2 public List<Document> handleRequest(Map<String,String> event, Context context) { 3 MongoDatabase database = mongoClient.getDatabase("sample_mflix"); 4 MongoCollection<Document> collection = database.getCollection("movies"); 5 6 Bson filter = new BsonDocument(); 7 8 if(event.containsKey("title") && !event.get("title").isEmpty()) { 9 filter = Filters.eq("title", event.get("title")); 10 } 11 12 List<Document> results = new ArrayList<>(); 13 collection.find(filter).limit(5).into(results); 14 15 return results; 16 }
No exemplo acima, estamos verificando se os dados de entrada do usuário
event
contêm uma propriedade "title" e, se contiverem, use-os como parte de nosso filtro. Caso contrário, vamos apenas devolver tudo na collection especificada.Por falar em retornar tudo, o conjunto de dados de amostra é bem grande, então vamos limitar os resultados a cinco documentos ou menos. Além disso, em vez de usar um cursor, vamos despejar todos os resultados da operação
find
em um List<Document>
que retornaremos ao cliente solicitante.Não fizemos muito em termos de validação de dados e nossa query foi bem simples, mas é um ponto de partida para coisas maiores e melhores.
O projeto deste exemplo está concluído, então é Go empacotá-lo e prepará-lo para implantação na cloud da AWS.
Como estamos usando o Gradle neste projeto e temos uma tarefa definida para agrupamento, execute o script de construção fazendo algo como o seguinte:
1 ./gradlew build
Se tudo tiver sido compilado corretamente, você deverá ter um arquivobuild/distributions/*.zip. O nome desse arquivo dependerá de toda a nomenclatura que você usou em seu projeto.
Com esse arquivo em mãos, Go até o dashboard da AWS para o Lambda e crie uma nova função.
Há três coisas que você deseja fazer para uma implementação bem-sucedida:
- Adicione a variável de ambiente para o URI do MongoDB Atlas.
- Carregue o arquivo ZIP.
- Renomeie as informações do "Manipulador" para refletir seu projeto real.
No painel do AWS Lambda para sua nova função, clique na guia "Configuração" seguida pelo item de navegação "Variáveis de ambiente". Adicione as informações da variável de ambiente e verifique se o nome da chave corresponde ao nome usado no código.
Usamos
MONGODB_ATLAS_URI
no código, e o valor real seria algo assim:1 mongodb+srv://<username>:<password>@examples.170lwj0.mongodb.net/?retryWrites=true&w=majority
Lembre-se de usar seu nome de usuário, senha e URL da instância.
Em seguida, você pode fazer upload do seu arquivo ZIP na guia "Código" do painel.
Quando o upload for concluído, na guia " Code ", procure a seção " Runtime Settings " e opte por editá-la. Em nosso exemplo, o nome do pacote era example, o arquivo Java foi chamado Handler e a função com a lógica foi chamada HandleRequest. Com isso em mente, nosso " Handler " deve ser Example.handler ::handleRequest. Se você estiver usando outra coisa para sua nomenclatura, certifique-se de que ela reflita adequadamente, caso contrário, o Lambda não saberá o que fazer quando invocado.
Pegue a função para dar uma volta!
Usando a guia "Test", tente invocar a função sem nenhuma entrada de usuário e, em seguida, invoque-a usando o seguinte:
1 { 2 "title": "Batman" 3 }
Você deverá ver resultados diferentes refletindo o que foi adicionado no código.
Você acabou de ver como criar uma função sem servidor com o AWS Lambda que interage com o MongoDB. Neste exemplo específico, Java foi a estrela do show, mas lógica e etapas semelhantes podem ser aplicadas para qualquer um dos outros tempos de execução do AWS Lambda ou drivers MongoDBsuportados.
Se você tiver dúvidas ou quiser ver como outras pessoas estão usando o MongoDB Atlas com o AWS Lambda, confira os Fóruns da MongoDB Community.