今天我們把三層式架構給做了出來,Service層放業務邏輯,Repo層放與外部接觸如資料庫、外部系統的地方,Controller層專心做User打過來的請求服務。
package Repo
import arrow.core.Either
import arrow.core.left
import arrow.core.right
import model.*
object CustomerRepo {
private val customerList = mutableListOf<Customer>()
fun getCustomer(): Either<MyError.CustomerNotFoundError, List<Customer>> =
if (customerList.isEmpty()) {
MyError.CustomerNotFoundError(customerId = "all").left()
} else {
customerList.right()
}
fun addCustomer(customer: Customer) =
customerList.add(customer).let { customer }
fun findCustomerIndex(customer: Customer): Either<MyError.CustomerNotFoundError, Int> =
when (val index = customerList.indexOfFirst { it.name == customer.name }) {
-1 -> MyError.CustomerNotFoundError(customerId = index.toString()).left()
else -> {
index.right()
}
}
fun updateCustomer(index: Int, customer: Customer) =
customerList.set(index, customer)
fun deleteCustomer(index: Int) =
customerList.removeAt(index)
}
package service
import Repo.CustomerRepo
import Repo.CustomerRepo.addCustomer
import Repo.CustomerRepo.deleteCustomer
import Repo.CustomerRepo.findCustomerIndex
import Repo.CustomerRepo.updateCustomer
import arrow.core.*
import arrow.core.raise.either
import arrow.core.raise.ensure
import arrow.core.raise.zipOrAccumulate
import model.*
object CustomerService {
fun customerVOToCustomer(customerVO: CustomerVO) =
when {
customerVO.contactInfo.contains("@") -> Customer(
Name(customerVO.name),
Email(customerVO.contactInfo),
Age(customerVO.age),
)
else -> Customer(Name(customerVO.name), Phone(customerVO.contactInfo), Age(customerVO.age))
}
fun validateCustomer(customerVo: CustomerVO): Either<MyError.ValidateCustomerError, Customer> =
either<NonEmptyList<MyError.ValidateCustomerError>, Customer> {
zipOrAccumulate(
{ ensure(customerVo.contactInfo.length <= 8) { MyError.ValidateCustomerError("contactInfo Too Long") } },
{ ensure(customerVo.name.isNotBlank()) { MyError.ValidateCustomerError("Name is blank") } },
) { _, _ ->
customerVOToCustomer(customerVo)
}
}.mapLeft {
MyError.ValidateCustomerError(
it.map { validateCustomerError ->
validateCustomerError.value
}.joinToString(),
)
}
fun getCustomer(): Either<MyError.CustomerNotFoundError, List<Customer>> =
CustomerRepo.getCustomer()
fun createCustomer(customerVO: CustomerVO): Either<MyError.ValidateCustomerError, Customer> =
validateCustomer(customerVO).map {
addCustomer(it)
}
fun updateCustomer(customerVO: CustomerVO) =
validateCustomer(customerVO).flatMap { validatedCustomer ->
findCustomerIndex(validatedCustomer).map {
Pair(it, validatedCustomer)
}
}.map { (index: Int, customer: Customer) ->
updateCustomer(index, customer)
}
fun deleteCustomer(customerVO: CustomerVO) =
validateCustomer(customerVO).flatMap { validatedCustomer ->
findCustomerIndex(validatedCustomer).map {
Pair(it, validatedCustomer)
}
}.map { (index: Int, customer: Customer) ->
deleteCustomer(index)
}
}
package controller
import arrow.core.flatMap
import kotlinx.serialization.json.Json
import model.CustomerVO
import model.MyError
import org.springframework.boot.autoconfigure.SpringBootApplication
import org.springframework.http.ResponseEntity
import org.springframework.web.bind.annotation.*
import service.CustomerService
import service.CustomerService.getCustomer
import utils.decodeFromStringEither
import utils.encodeToStringEither
@RestController
@SpringBootApplication
open class CustomerController {
@GetMapping("/api/v1/customers")
fun getAllCustomers(): ResponseEntity<String> {
return getCustomer().flatMap { customerList ->
Json.encodeToStringEither(customerList)
}.fold(
ifLeft = { e -> MyError.errorToResonse(MyError.JsonEncodeError(e)) },
ifRight = { ResponseEntity.ok(it) },
)
}
@PostMapping("/api/v1/customers")
fun createCustomer(@RequestBody newCustomer: String): ResponseEntity<String> {
return Json.decodeFromStringEither<CustomerVO>(newCustomer).flatMap {
CustomerService.createCustomer(it)
}.flatMap {
Json.encodeToStringEither(it)
}.fold(
ifLeft = { MyError.errorToResonse(it) },
ifRight = { ResponseEntity.ok(it) },
)
}
@PutMapping("/api/v1/customers")
fun updateCustomer(@RequestBody newCustomer: String): ResponseEntity<String> {
return Json.decodeFromStringEither<CustomerVO>(newCustomer).flatMap {
CustomerService.updateCustomer(it)
}.fold(
ifLeft = { MyError.errorToResonse(it) },
ifRight = { ResponseEntity.ok("Modify success") },
)
}
@DeleteMapping("/api/v1/customers")
fun DeleteCustomer(@RequestBody newCustomer: String): ResponseEntity<String> {
return Json.decodeFromStringEither<CustomerVO>(newCustomer).flatMap {
CustomerService.deleteCustomer(it)
}.fold(
ifLeft = {
MyError.errorToResonse(it)
},
ifRight = { ResponseEntity.ok("Delete success") },
)
}
}
這邊我們再複習一下分層的目的,
分層有助於提高程式碼的可維護性,每個層級都有各自的職責,使開發者更容易定位和修改特定功能。這也使得單元測試更容易執行(之後我們就會來寫囉XD)
分層的話,我們可以獨立擴展或修改每個層級,而不會影響其他層級。代表我們可以根據需求曾加新的功能或服務,而無需對整個應用程式進行大規模修改。減少Side effect的發生
分層也有助於團隊合作,我們可以分別負責不同的層級來開發,這樣可以平行的進行,加快開發進度。
今天我們將三層式架構拆出來囉,之後如果有什麼變動就是比較容易做改變,進一步節省我們的時間XD
像是今天如果我們要換一個資料庫的話,我們只要將Repo層抓出去重寫,接著就可以抽換囉,這樣我們的Service層完全不會受到干擾,而不會發生改變都寫在同一個function,可能會影響到原本的邏輯。