iT邦幫忙

2022 iThome 鐵人賽

DAY 16
0

https://ithelp.ithome.com.tw/upload/images/20220922/20135701CJVYWcqWvY.png

在Service Layer 與 Repo Layer的處理過程中,會利用到 map, flatMap, zip 來 compose Either,最後通常會在 the end of the world 鐵軌的盡頭處理錯誤。 今天的程式碼可以參照 GITHUB

這裡會用上 fold 來把左值與右值變成一樣型別的 outpout type, 像是三星手機fold z4,把左右邊合起來。(我沒有業配)

inline fun <C> fold(ifLeft: (A) -> C, ifRight: (B) -> C): C

以 RESTful service 來說,最終的結果就是 RestResponse<String>, 右的 type 都會轉成 json string, 左邊會回 error 5xx 或 4xx。所以我們直接準備了一個通用的 Either extension function.

fun Either<AppError, Any>.toRestResponse(): RestResponse<String> =
        this.fold(
            ifRight = { obj ->
                mapper.writeValueAsString(obj).let { RestResponse.ok(it) }
            },
            ifLeft = { AppError.toResponse(it) }
        )
ifRight

把值變成 json string 並回 200 ok. 這邊有把 json lib 換成 jackson,明天再來交待。

ifLeft

AppError 是個 sealed class,在 toResponse 這個 function 會先用 when 判斷所有可能。 when 面對到 enum 或是 sealed class 時,在 compiler time 會檢查是否有窮舉或是有 else,可以利用這點知道是否所有錯誤的可能都有好好的被處理。

sealed class AppError {
    data class DatabaseProblem(val e: Throwable) : AppError()
    class JsonSerializationFail(val e: Throwable) : AppError()
    data class NoThisFilm(val epId: Int) : AppError()
    data class CastToFilmFail(val obj: Any) : AppError()

    companion object {
        private val LOG: Logger = Logger.getLogger(AppError::class.java)
        fun toResponse(err: AppError): RestResponse<String> = when (err) {
            is JsonSerializationFail -> {
                LOG.error("Json Serialization Failed", err.e)
                ......
                )
            }
            is DatabaseProblem -> {
                LOG.error("db error", err.e)
                RestResponse.status(
                    RestResponse.Status.INTERNAL_SERVER_ERROR,
                    "Db Connect Error \n ${err.e.stackTraceToString()}"
                )
            }

            is NoThisFilm -> .....
            is CastToFilmFail -> .....)
        }
    }
}

Fold 的操作結果不一定是 RestResponse ,主要是看 the end of the world 面對的是什麼 interface.

負向測試

有了 Either 的 Left ,表示可以很容易進行 unhappy path 的測試。如以下方式就可以驗證 delete 時是否有正確的處理錯誤。

    @Inject
    lateinit var filmService: FilmService

    @Test
    fun `test delete no this item`() {
        GlobalScope.async(Infrastructure.getDefaultExecutor().asCoroutineDispatcher()) {
            assertEquals(AppError.NoThisFilm(1000).left(), filmService.delete(1000))
        }
    }

目前已經有一個簡單的 service 。明天可以來打包成 jar 甚至是 native


上一篇
用 Arrow KT 作錯誤處理,重構 Repository - Day 15
下一篇
打包 Quarkus ,Java/Kotlin 應用可以 compile 成執行檔 - Day17
系列文
Quarkus, Kotlin, Reactive 雲原生服務開發32
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言