Visão geral
Neste tutorial, você aprenderá como configurar uma API executável usando Ktor, o driver Kotlin e MongoDB Atlas.
Tutorial
Configurar um projeto do Ktor
Crie um novo projeto Ktor usando o Gerador de Projeto Ktor. Nomeie o artefato do projeto com.mongodb.fitness-tracker usando o campo de entrada de texto . Clique no Configure botão e especifique as seguintes opções:
Sistema de construção: Gradle Kotlin
Mecanismo: Tomcat
Configuração: arquivo HOCON
Usando o menu Plugins na página Novo Projeto Ktor, adicione os seguintes plug-ins ao seu projeto:
Negociação de conteúdo: negociação de tipos de mídia entre o cliente e o servidor
GSON: oferece suporte a serialização e desserialização
Roteamento: gerencia solicitações recebidas
MongoDB: gera documentação da API
Clique no botão Generate Project para baixar um arquivo ZIP contendo seu novo projeto Ktor.
Descompacte o arquivo baixado e abra o arquivo build.gradle.kts no diretório raiz do diretório do projeto . Adicione os seguintes plugins ao bloco plugins:
plugins { kotlin("jvm") version "1.9.22" id("io.ktor.plugin") version "2.3.7" id("org.jetbrains.kotlin.plugin.serialization") version "1.9.22" }
Adicione as seguintes dependências ao bloco dependencies no mesmo arquivo:
dependencies { implementation("io.ktor:ktor-server-core-jvm") implementation("io.ktor:ktor-server-swagger-jvm") implementation("io.ktor:ktor-server-content-negotiation-jvm") implementation("io.ktor:ktor-serialization-gson-jvm") implementation("io.ktor:ktor-server-tomcat-jvm") implementation("ch.qos.logback:logback-classic:$logback_version") implementation("io.ktor:ktor-server-config-yaml:2.3.8") // MongoDB implementation("org.mongodb:mongodb-driver-kotlin-coroutine:5.6.1") // Koin dependency injection implementation("io.insert-koin:koin-ktor:3.5.3") implementation("io.insert-koin:koin-logger-slf4j:3.5.3") // Client implementation("io.ktor:ktor-client-core:$ktor_version") implementation("io.ktor:ktor-client-cio:$ktor_version") }
Implementar operações CRUD
Crie os seguintes três pacotes no diretório src/main/kotlin:
applicationdomaininfrastructure
Dentro do pacote domain , crie um novo sub-pacote denominado entity, então crie um arquivo dentro de entity denominado Fitness.kt. Copie o seguinte código no arquivo:
package com.mongodb.domain.entity import com.mongodb.application.response.FitnessResponse import org.bson.codecs.pojo.annotations.BsonId import org.bson.types.ObjectId data class Fitness( val id: ObjectId, val exerciseType: String, val notes: String, val details: FitnessDetails ){ fun toResponse() = FitnessResponse( id = id.toString(), exerciseType = exerciseType, notes = notes, details = details ) } data class FitnessDetails( val durationMinutes: Int, val distance: Double, val caloriesBurned: Int )
O método toResponse() no código anterior gera um erro porque você ainda não criou a classe FitnessResponse. Crie pacotes request e response no pacote application/request e crie dois arquivos chamados FitnessRequest.kt e FitnessResponse.kt no pacote correspondente.
Copie o seguinte código para o arquivo FitnessRequest.kt:
package com.mongodb.application.response import com.mongodb.domain.entity.FitnessDetails data class FitnessResponse( val id: String, val exerciseType: String, val notes: String, val details: FitnessDetails )
Copie o seguinte código para o arquivo FitnessResponse.kt:
package com.mongodb.application.response import com.mongodb.domain.entity.FitnessDetails data class FitnessResponse( val id: String, val exerciseType: String, val notes: String, val details: FitnessDetails )
Crie um pacote ports no pacote domain e crie um arquivo denominado FitnessRepository.kt no pacote ports. Este arquivo representa a interface que se comunicará com o banco de dados. Copie o seguinte código para o arquivo FitnessRepository.kt:
package com.mongodb.domain.ports import com.mongodb.domain.entity.Fitness import org.bson.BsonValue import org.bson.types.ObjectId interface FitnessRepository { suspend fun insertOne(fitness: Fitness): BsonValue? suspend fun deleteById(objectId: ObjectId): Long suspend fun findById(objectId: ObjectId): Fitness? suspend fun updateOne(objectId: ObjectId, fitness: Fitness): Long }
Crie um pacote repository no pacote infrastructure e crie um arquivo denominado FitnessRepositoryImpl.kt no pacote repository. Este arquivo implementa os métodos da interface. Copie o seguinte código para o arquivo FitnessRepositoryImpl.kt:
package com.mongodb.infrastructure.repository import com.mongodb.MongoException import com.mongodb.client.model.Filters import com.mongodb.client.model.UpdateOptions import com.mongodb.client.model.Updates import com.mongodb.domain.entity.Fitness import com.mongodb.domain.ports.FitnessRepository import com.mongodb.kotlin.client.coroutine.MongoDatabase import kotlinx.coroutines.flow.firstOrNull import org.bson.BsonValue import org.bson.types.ObjectId class FitnessRepositoryImpl( private val mongoDatabase: MongoDatabase ) : FitnessRepository { companion object { const val FITNESS_COLLECTION = "fitness" } override suspend fun insertOne(fitness: Fitness): BsonValue? { try { val result = mongoDatabase.getCollection<Fitness>(FITNESS_COLLECTION).insertOne( fitness ) return result.insertedId } catch (e: MongoException) { System.err.println("Unable to insert due to an error: $e") } return null } override suspend fun deleteById(objectId: ObjectId): Long { try { val result = mongoDatabase.getCollection<Fitness>(FITNESS_COLLECTION).deleteOne(Filters.eq("_id", objectId)) return result.deletedCount } catch (e: MongoException) { System.err.println("Unable to delete due to an error: $e") } return 0 } override suspend fun findById(objectId: ObjectId): Fitness? = mongoDatabase.getCollection<Fitness>(FITNESS_COLLECTION).withDocumentClass<Fitness>() .find(Filters.eq("_id", objectId)) .firstOrNull() override suspend fun updateOne(objectId: ObjectId, fitness: Fitness): Long { try { val query = Filters.eq("_id", objectId) val updates = Updates.combine( Updates.set(Fitness::exerciseType.name, fitness.exerciseType), Updates.set(Fitness::notes.name, fitness.notes), Updates.set(Fitness::details.name, fitness.details) ) val options = UpdateOptions().upsert(true) val result = mongoDatabase.getCollection<Fitness>(FITNESS_COLLECTION) .updateOne(query, updates, options) return result.modifiedCount } catch (e: MongoException) { System.err.println("Unable to update due to an error: $e") } return 0 } }
Configurar plugins
Cole o seguinte código no arquivo Application.kt. Este código executa as seguintes ações:
Define o plugin ContentNegotiation para lidar com a serialização e a desserialização JSON usando o formatador Gson
Habilita a injeção de dependência usando o Koin
Configura a rota da API MongoDB, que está disponível no endpoint
/swagger
package com.mongodb import com.mongodb.application.routes.fitnessRoutes import com.mongodb.domain.ports.FitnessRepository import com.mongodb.infrastructure.repository.FitnessRepositoryImpl import com.mongodb.kotlin.client.coroutine.MongoClient import io.ktor.serialization.gson.gson import io.ktor.server.application.Application import io.ktor.server.application.install import io.ktor.server.plugins.contentnegotiation.ContentNegotiation import io.ktor.server.plugins.swagger.swaggerUI import io.ktor.server.routing.routing import io.ktor.server.tomcat.EngineMain import org.koin.dsl.module import org.koin.ktor.plugin.Koin import org.koin.logger.slf4jLogger fun main(args: Array<String>): Unit = EngineMain.main(args) fun Application.module() { install(ContentNegotiation) { gson { } } install(Koin) { slf4jLogger() modules(module { single { MongoClient.create( environment.config.propertyOrNull("ktor.mongo.uri")?.getString() ?: throw RuntimeException("Failed to access MongoDB URI.") ) } single { get<MongoClient>().getDatabase(environment.config.property("ktor.mongo.database").getString()) } }, module { single<FitnessRepository> { FitnessRepositoryImpl(get()) } }) } routing { swaggerUI(path = "swagger-ui", swaggerFile = "openapi/documentation.yaml") { version = "4.15.5" } fitnessRoutes() } }
Abra o arquivo application.conf e cole o seguinte:
ktor { deployment { port = 8080 } application { modules = [ com.mongodb.ApplicationKt.module ] } mongo { uri = ${?MONGO_URI} database = ${?MONGO_DATABASE} } }
Abra o arquivo documentation.yaml e cole o seguinte:
openapi: 3.0.0 info: title: Fitness API version: 1.0.0 description: | This Swagger documentation file outlines the API specifications for a Fitness Tracker application built with Ktor and MongoDB. The API allows users to manage fitness records including creating new records, updating and deleting records by ID. The API uses the Fitness and FitnessDetails data classes to structure the fitness-related information. paths: /fitness: post: summary: Create a new fitness record requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/FitnessRequest' responses: '201': description: Fitness created successfully '400': description: Bad request /fitness/{id}: get: summary: Retrieve fitness record by ID parameters: - name: id in: path required: true schema: type: string responses: '200': description: Successful response content: application/json: example: {} '404': description: Fitness not found delete: summary: Delete fitness record by ID parameters: - name: id in: path required: true schema: type: string responses: '200': description: Fitness deleted successfully '400': description: Bad request '404': description: Fitness not found patch: summary: Update fitness record by ID parameters: - name: id in: path required: true schema: type: string requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/FitnessRequest' responses: '200': description: Fitness updated successfully '400': description: Bad request '404': description: Fitness not found components: schemas: Fitness: type: object properties: id: type: string format: uuid exerciseType: type: string notes: type: string details: $ref: '#/components/schemas/FitnessDetails' required: - id - notes - details FitnessDetails: type: object properties: durationMinutes: type: integer format: int32 distance: type: number format: double caloriesBurned: type: integer format: int32 required: - durationMinutes - distance - caloriesBurned FitnessRequest: type: object properties: exerciseType: type: string notes: type: string details: $ref: '#/components/schemas/FitnessDetails' required: - exerciseType - notes - details
Esse arquivo define quais métodos nossa API fornece por meio do endpoint /swagger-ui.
Implementar endpoints de API
Esta etapa implementa os seguintes endpoints da API:
GET /ftness/{id}: recupera uma entrada de preparo físico por ID
POSTAR / formatação: cria uma nova entrada de preparação física
PATCH /Fitness/{id}: atualiza a entrada de preparação física especificada
DELETE /ftness/{id}: exclui a entrada de preparação física especificada
Crie um pacote routes no pacote application e crie um arquivo denominado FitnessRoutes.kt no pacote routes. Copie o seguinte código para o arquivo FitnessRoutes.kt:
package com.mongodb.application.routes import com.mongodb.application.request.FitnessRequest import com.mongodb.application.request.toDomain import com.mongodb.domain.ports.FitnessRepository import io.ktor.http.HttpStatusCode import io.ktor.server.application.call import io.ktor.server.request.receive import io.ktor.server.response.respond import io.ktor.server.response.respondText import io.ktor.server.routing.route import io.ktor.server.routing.Route import io.ktor.server.routing.post import io.ktor.server.routing.delete import io.ktor.server.routing.get import io.ktor.server.routing.patch import org.bson.types.ObjectId import org.koin.ktor.ext.inject fun Route.fitnessRoutes() { val repository by inject<FitnessRepository>() route("/fitness") { post { val fitness = call.receive<FitnessRequest>() val insertedId = repository.insertOne(fitness.toDomain()) call.respond(HttpStatusCode.Created, "Created fitness with id $insertedId") } delete("/{id?}") { val id = call.parameters["id"] ?: return@delete call.respondText( text = "Missing fitness id", status = HttpStatusCode.BadRequest ) val delete: Long = repository.deleteById(ObjectId(id)) if (delete == 1L) { return@delete call.respondText("Fitness Deleted successfully", status = HttpStatusCode.OK) } return@delete call.respondText("Fitness not found", status = HttpStatusCode.NotFound) } get("/{id?}") { val id = call.parameters["id"] if (id.isNullOrEmpty()) { return@get call.respondText( text = "Missing id", status = HttpStatusCode.BadRequest ) } repository.findById(ObjectId(id))?.let { call.respond(it.toResponse()) } ?: call.respondText("No records found for id $id") } patch("/{id?}") { val id = call.parameters["id"] ?: return@patch call.respondText( text = "Missing fitness id", status = HttpStatusCode.BadRequest ) val updated = repository.updateOne(ObjectId(id), call.receive()) call.respondText( text = if (updated == 1L) "Fitness updated successfully" else "Fitness not found", status = if (updated == 1L) HttpStatusCode.OK else HttpStatusCode.NotFound ) } } }
O código anterior define os pontos de conexão da API e seus manipuladores.
Antes de executar o aplicação, exclua a pasta plugins que contém os arquivos HTTP.kt, Routing.kt e Serialization.kt, pois já os incluímos no arquivo Application.kt.
Executar o aplicativo
Recupere seu URI de conexão do cluster do MongoDB Atlas . Para obter mais informações sobre como fazer isso, consulte a seçãoURI de conexão do guia Conectar ao MongoDB .
Abra o arquivo application.conf e adicione os seguintes valores de configuração:
-DMONGO_URI= <your connection URI> -DMONGO_DATABASE= <your database name>
Navegue até o arquivo Application.kt no IntelliJ e clique no botão Run. Agora você pode acessar a API navegando até http://localhost:8080/swagger-ui/ em um navegador da web.
Informações adicionais
Para saber mais sobre o Ktor, consulte a documentação do Ktor.
Para visualizar o código fonte deste tutorial, consulte o repositório Kotlin Ktor MongoDB Vector Search no GitHub.