iT邦幫忙

2022 iThome 鐵人賽

DAY 25
0
Software Development

Quarkus, Kotlin, Reactive 雲原生服務開發系列 第 25

整合登入者資訊, 記錄是誰修改了資料, 用 Compose Either 達成吧

  • 分享至 

  • xImage
  •  

大家好,我是 Brandy, 歡迎來到 Quarkus x Kotlin x Arrow KT 系列
昨天的文章中,在 Quarkus 中利用了 OIDC 與 Access Token 可以換到使用者資訊。在修改資料時,常常會需要記錄 updater 是誰,這時不能直接由前端傳 userName 過來,應該是在 Quarkus 後端解 token 後才能確信是這個人。

所以本篇會講述如何把 updater 在新增、修改 FilmResource 時,記錄在 Mongodb 資料庫中。

如果要用原始碼比對差距,可以把 Day 15 branchDay 21 branch 互相比較

1.增加 updater field 到 FilmEntity 中

修改 FilmEntity 增加 updater,此時其他相關程式碼會開始報錯,不過沒關係,compiler 會幫我們提醒
/src/main/kotlin/tw/brandy/ironman/entity/FilmEntity.kt

data class FilmEntity @BsonCreator constructor(
    ....
    @BsonProperty("updater") var updater: String
) : PanacheMongoEntityBase()

2.需要增加 UpsertFilm -> Film 的轉換

因為使用者資訊從 request 取得,所以在 add /updater 時, Request 所需要屬性不變。但傳給 Service 時需要有登入者資訊。所以新增一個 UpsertFilm 作為 request 時的 DTO,在 Kotlin 中有 data class ,所以不需吝嗇新增 Type, 而是利用多個 Type 清楚的知道每一次的 transform, 在 Design 時,也容易用 Data Type 表達

data class UpsertFilm(val title: String, val episodeID: Int, val director: String, val releaseDate: LocalDate)

data class Film(val title: String, val episodeID: Int, val director: String, val releaseDate: LocalDate, val updater: String)

等下在上傳時,會有這樣的一個 transform

val add = (UpsertFilm,User) -> Film

3. TDD 修改 FilmResourceTest

因為我們走 TDD 要,先來改 Test FilmResourceTest,登入者是 alice 並且判斷新增後的 updater 是 alice.

    @Test
    @Order(1)
    fun `test add`() {
        Given {
            contentType("application/json")
            auth().oauth2(TestUtil.getAccessToken("alice"))
            body(
                """{"title":"Spider Man","episodeID":100,"director":"Sam Raimi","releaseDate":"2002-04-29"}"""
            )
        } When {
            post("/films")
        } Then {
            statusCode(200)
            body("title", `is`("Spider Man"))
            body("director", equalTo("Sam Raimi"))
            body("updater", equalTo("alice"))
        }
    }

4. 一致方法取得使用者資訊

從 accessToken 得到 userName, 因為 token 可能為空或解不開,所以增加了兩個 Error type
AppError.kt


    class idTokenEmpty : AppError()
    class CreateUserFail(val e: Throwable) : AppError()

建立一個 UserSerivce.fromIdToken 方法利用 Either 來串接。這個 Service 不需依靠外部注入。可以用 object 直接宣告為 singleton class

UserService


object UserService {

    fun fromAccessToken(accessToken: JsonWebToken?) = accessToken.toOption().toEither { AppError.idTokenEmpty() }.flatMap {
        Either.catch {
            User(it)
        }.mapLeft { AppError.CreateUserFail(it) }
    }
}

5.修改RESTful 的 add 與 update

上傳與修改的接收端改成 UpsertFilm,並且整合登入者資訊


    @Inject
    var accessToken: JsonWebToken? = null
    
    ...
    // (UpsertFilm -> (User) -> Film
    suspend fun add(film: UpsertFilm) = UserService
        .fromAccessToken(accessToken).map { user ->
            Film(film.title, film.episodeID, film.director, film.releaseDate, user.userName)
        }.flatMap { filmService.save(it) }
        .toRestResponse()

Film(film.title, ... , user.userName) 會用到兩次,我們可以借助 IntelliJ Refactor 提成 function

https://ithelp.ithome.com.tw/upload/images/20221001/20135701XnvDJDPR0S.jpg

並且因為在 Update 有相同的操作, IntelliJ 會幫你一併找到,並改成 function

    @POST
    suspend fun add(upsertFilm: UpsertFilm) = UserService
        .fromAccessToken(accessToken).map { user ->
            tofilm(upsertFilm, user)
        }.flatMap { filmService.save(it) }
        .toRestResponse()

    @PUT
    @Path("/{id}")
    suspend fun update(upsertFilm: UpsertFilm) = UserService
        .fromAccessToken(accessToken).map { user ->
            tofilm(upsertFilm, user)
        }.flatMap { filmService.update(it) }
        .toRestResponse()

    private fun tofilm(
        film: UpsertFilm,
        user: User
    ) = Film(film.title, film.episodeID, film.director, film.releaseDate, user.userName)

跑過測試

最後 serivce 層與 test 會經過必要的修改,compiler 都會提醒我們。改完後可以看到我們成功的跑過所有測試了!

https://ithelp.ithome.com.tw/upload/images/20221001/20135701cdCau8IccD.jpg


上一篇
Quarkus HTTP Endpoint 保護與 OIDC 授權碼流程機制
下一篇
微單體 Quarkus 與 React Route 前後端整合
系列文
Quarkus, Kotlin, Reactive 雲原生服務開發32
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言