昨天的文章我們增加 MongoDB 的支持,也得到了 local MongoDB,可以來寫 CRUD 的操作。 Panache Mongodb 提供了 Repository 風格的資料庫存取方式而不需操作 MongoDB 的 Document. 不過這裡的 Entity 比較需要是 var 的 變數 , 所以還是跟本來的 model 有所區別,但我們可以利用 Kotlin 的 map, let 輕鬆的轉換。
以下會說明 Resource -> Service -> MongoRepository 的操作
程式碼已放置於 Github : https://github.com/hmchangm/getting-start-QK/tree/ironman2022-d8
建立 FilmEntity , 而且要用 MongoEntity 宣告 db 的 collection
//FilmEntity
@MongoEntity(collection = "film")
data class FilmEntity(
var id: ObjectId? = null,
var title: String,
var episodeId: Int,
var director: String,
var releaseDate: LocalDate
)
Panache 提供了 Reactive 的實現,所以我們會 extends ReactivePanacheMongoRepository, 基本的 persist, update, delete, findAll 會提供,也很容易擴展。
@ApplicationScoped
class FilmRepository : ReactivePanacheMongoRepository<FilmEntity> {
suspend fun findByEpisodeId(id: Int) = find("episodeId", id).firstResult().awaitSuspending()
suspend fun findAllAsync() = findAll().list().awaitSuspending()
}
ReactivePanacheMongoRepository 的方法回的都是一個 Uni 的封裝。但我們可利用 awaitSuspending 方法解成 suspending function,這就是 Kotlin 能夠簡化 async 操作。 這樣就可以跟 API suspend function 一路串下來。因為 suspend function 一定要call suspend function。
所以可以看到我們的 CRUD 的操作就都會穿插 awaitSuspending()
@ApplicationScoped
class FilmService(val filmRepository: FilmRepository) {
suspend fun getAllFilms() = filmRepository.findAllAsync().map(entityToModel)
suspend fun getFilmCount() = filmRepository.count().awaitSuspending()
suspend fun getFilm(id: Int) = filmRepository.findByEpisodeId(id)?.let(entityToModel)
suspend fun save(film: Film) = film.let(modelToEntity)
.let(filmRepository::persist)
.awaitSuspending().let(entityToModel)
....
}
因為對 Mongodb 操作我們等於是有一個 DTO 的操作。所以會有 Entity -> Model , Model -> Entity 的function 需要。Kotlin 可以直接用變數 function type 來達成。對我們的語意表達也比較容易。
private val modelToEntity: (Film) -> (FilmEntity) = { film ->
FilmEntity(
title = film.title,
episodeId = film.episodeID,
director = film.director,
releaseDate = film.releaseDate.toJavaLocalDate()
)
}
private val entityToModel: (FilmEntity) -> (Film) = {
Film(it.title, it.episodeId, it.director, it.releaseDate.toKotlinLocalDate())
}
這些 funciton type 就可以很容易的被 map, let 所利用。例如把從 mongodb 來的所有 entity 都轉成 model,就可以直接 apply function type。所謂的 High Order Function 操作
suspend fun getAllFilms() = filmRepository.findAllAsync().map(entityToModel)
suspend fun getFilm(id: Int) = filmRepository.findByEpisodeId(id)?.let(entityToModel)
那 API Layer 就會變的很簡單,主要是 call service layer 操作
@Path("/films")
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
class FilmResource(val filmService: FilmService) {
@GET
suspend fun list() = filmService.getAllFilms()
@GET
@Path("/{id}")
suspend fun getById(id: Int) = filmService.getFilm(id)
@POST
suspend fun add(film: Film) = filmService.save(film)
@PUT
@Path("/{id}")
suspend fun update(film: Film) = filmService.update(film)
@DELETE
@Path("/{id}")
suspend fun delete(id: Int) = filmService.delete(id)
@GET
@Path("/count")
suspend fun count(): Long = filmService.getFilmCount()
}
最後我們還需要在 application.properties 加上 quarkus.mongodb.database=ironman
指定 database
這樣我們就完成了基本的 CRUD,而且前後都是用 kotlin suspend function 連起來,保證了 reactive.