iT邦幫忙

2023 iThome 鐵人賽

DAY 7
0
Kotlin

Kotlin魔法:Spring Boot 3的fp奇幻冒險系列 第 7

[小草原] Spring Boot 3 的錯誤處理與Domain type

  • 分享至 

  • xImage
  •  

前情提要

今天我們要繼續進行"錯誤"囉

設計"錯誤"的Domain type

我們今天要來設計錯誤的Domain type,意思是發生錯誤時,會歸類於哪一種錯誤,好像有點繞口XD
假設我們開一間餐廳,那麼會有幾種錯誤呢?

  • 食材不足錯誤 - 沒東西煮給客人
  • 瓦斯不足錯誤 - 沒辦法煮東西啦
  • 零錢不足錯誤 - 無法找錢給客人

好處是甚麼?

這樣做我們可以歸類這些錯誤,當我們看到這些錯誤時,就可以做特別的處理,比如說遇到食材不足,那就進貨,或是不賣那道菜,最慘就是提早打烊囉XD

code

Error type

package model

import kotlinx.serialization.Contextual
import kotlinx.serialization.Serializable
import org.springframework.http.ResponseEntity

sealed class MyError : Error() {
    @Serializable
    sealed class ValidateError : MyError()

    @Serializable
    sealed class ServerError : MyError()


    @Serializable
    data class ContainInfoError(val containInfo: String) : ValidateError()

    @Serializable
    data class DBConnectError(@Contextual val e: Error) : ServerError()

    @Serializable
    data class JsonEncodeError(@Contextual val e: Error) : ServerError()

    @Serializable
    data class CustomerNotFoundError(val customerId: String) : MyError()

    companion object {
        fun errorToResonse(myError: MyError): ResponseEntity<String> {
            return when (myError) {
                is DBConnectError ->
                    ResponseEntity.status(500).body("DB Connect Error: ${myError.e}")

                is JsonEncodeError ->
                    ResponseEntity.status(500).body("Json Encoder Error: ${myError.e}")

                is ContainInfoError ->
                    ResponseEntity.status(400).body("validation Error: ${myError.containInfo}")

                is CustomerNotFoundError ->
                    ResponseEntity.status(404).body("ID: ${myError.customerId} Not Found!")

            }

        }
    }
}

實際修改完長這個樣子

package controller

import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json
import model.*
import org.springframework.boot.autoconfigure.SpringBootApplication
import org.springframework.boot.runApplication
import org.springframework.http.ResponseEntity
import org.springframework.web.bind.annotation.*

fun customerVOToCustomer(customerVO: CustomerVO) =
    when {
        customerVO.contactInfo.contains("@") -> Customer(
            Name(customerVO.name),
            Email(customerVO.contactInfo),
            Age(customerVO.age)
        )

        customerVO.contactInfo.length > 8 -> throw Error("contactInfo Too Long")
        else -> Customer(Name(customerVO.name), Phone(customerVO.contactInfo), Age(customerVO.age))

    }

@RestController
@SpringBootApplication
open class MyApplication {

    private val customerList = mutableListOf<Customer>()

    @GetMapping("/api/v1/customers")
    fun getAllCustomers(): ResponseEntity<String> {
        if (customerList.isEmpty())
            return MyError.errorToResonse(MyError.CustomerNotFoundError(customerId = "all"))
        try {
            Json.encodeToString(customerList)
        } catch (e: Error) {
            return MyError.errorToResonse(MyError.JsonEncodeError(e))
        }.let {
            return ResponseEntity.ok(it)
        }
    }

    @PostMapping("/api/v1/customers")
    fun createCustomer(@RequestBody newCustomer: String): ResponseEntity<String> {
        Json.decodeFromString<CustomerVO>(newCustomer).let {
            try {
                customerVOToCustomer(it)
            } catch (e: Error) {
                return MyError.errorToResonse(MyError.ContainInfoError(e.toString()))

            }
        }.let {
            customerList.add(it)
            it
        }.let {
            try {
                Json.encodeToString(it)
            } catch (e: Error) {
                return MyError.errorToResonse(MyError.JsonEncodeError(e))
            }
        }.let {
            return ResponseEntity.ok(it)
        }
    }


    @PutMapping("/api/v1/customers")
    fun updateCustomer(@RequestBody newCustomer: String): ResponseEntity<String> {
        Json.decodeFromString<CustomerVO>(newCustomer).let {
            customerVOToCustomer(it)
        }.let { validatedCustomer ->
            return when (val index = customerList.indexOfFirst { it.name == validatedCustomer.name }) {
                -1 -> MyError.errorToResonse(MyError.CustomerNotFoundError(customerId = index.toString()))
                else -> {
                    customerList.set(index, validatedCustomer)
                    ResponseEntity.ok("Modify success")
                }
            }
        }
    }

    @DeleteMapping("/api/v1/customers")
    fun DeleteCustomer(@RequestBody newCustomer: String): ResponseEntity<String> {
        Json.decodeFromString<CustomerVO>(newCustomer).let {
            customerVOToCustomer(it)
        }.let { validatedCustomer ->
            return when (val index = customerList.indexOfFirst { it.name == validatedCustomer.name }) {
                -1 -> MyError.errorToResonse(MyError.CustomerNotFoundError(customerId = index.toString()))
                else -> {
                    customerList.removeAt(index)
                    ResponseEntity.ok("Delete success")
                }
            }
        }
    }

}

fun main(args: Array<String>) {
    runApplication<MyApplication>(*args)
}

總結

我們今天將錯誤處理更進一步,定義出屬於我們自己的type,這樣看到錯誤時,可以比較快地知道該怎麼處理! 別人一看到我們的程式碼,也可以很快地知道發生了甚麼事情,例如看到CustomerNotFoundError,第一眼就會想到,一定是顧客在DB中找不到了,那為甚麼找不到呢?我們就可以透過訊息來描述,比如說以這個名字來搜尋,找不到任何顧客,經過這樣的撰寫,我們的錯誤會更能表達真正的意義。


上一篇
[小草原] Spring Boot 3 的錯誤處理
下一篇
[小草原] 錯誤處理超進化-Either
系列文
Kotlin魔法:Spring Boot 3的fp奇幻冒險30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言