Lift DomainMessage subtypes to top-level declarations in example

This commit is contained in:
Michael Bull 2018-01-24 13:03:48 +00:00
parent 687c4c9d7f
commit 9d7c5f07f7
7 changed files with 79 additions and 55 deletions

View File

@ -1,11 +1,31 @@
package com.github.michaelbull.result.example
import com.github.michaelbull.result.*
import com.github.michaelbull.result.Err
import com.github.michaelbull.result.Ok
import com.github.michaelbull.result.Result
import com.github.michaelbull.result.andThen
import com.github.michaelbull.result.example.model.domain.Customer
import com.github.michaelbull.result.example.model.domain.CustomerCreated
import com.github.michaelbull.result.example.model.domain.CustomerId
import com.github.michaelbull.result.example.model.domain.CustomerIdMustBePositive
import com.github.michaelbull.result.example.model.domain.CustomerNotFound
import com.github.michaelbull.result.example.model.domain.CustomerRequired
import com.github.michaelbull.result.example.model.domain.DatabaseError
import com.github.michaelbull.result.example.model.domain.DatabaseTimeout
import com.github.michaelbull.result.example.model.domain.DomainMessage
import com.github.michaelbull.result.example.model.domain.EmailAddressChanged
import com.github.michaelbull.result.example.model.domain.EmailInvalid
import com.github.michaelbull.result.example.model.domain.EmailRequired
import com.github.michaelbull.result.example.model.domain.EmailTooLong
import com.github.michaelbull.result.example.model.domain.FirstNameRequired
import com.github.michaelbull.result.example.model.domain.FirstNameTooLong
import com.github.michaelbull.result.example.model.domain.LastNameRequired
import com.github.michaelbull.result.example.model.domain.LastNameTooLong
import com.github.michaelbull.result.example.model.domain.SqlCustomerInvalid
import com.github.michaelbull.result.example.model.dto.CustomerDto
import com.github.michaelbull.result.example.service.CustomerService
import com.github.michaelbull.result.mapBoth
import com.github.michaelbull.result.mapError
import io.ktor.application.Application
import io.ktor.application.call
import io.ktor.application.install
@ -72,36 +92,36 @@ fun Application.main() {
private fun readId(values: ValuesMap): Result<Long, DomainMessage> {
val id = values["id"]?.toLongOrNull()
return if (id != null) Ok(id) else Err(DomainMessage.CustomerRequired)
return if (id != null) Ok(id) else Err(CustomerRequired)
}
private fun messageToResponse(message: DomainMessage) = when (message) {
DomainMessage.CustomerRequired,
DomainMessage.CustomerIdMustBePositive,
DomainMessage.FirstNameRequired,
DomainMessage.FirstNameTooLong,
DomainMessage.LastNameRequired,
DomainMessage.LastNameTooLong,
DomainMessage.EmailRequired,
DomainMessage.EmailTooLong,
DomainMessage.EmailInvalid ->
CustomerRequired,
CustomerIdMustBePositive,
FirstNameRequired,
FirstNameTooLong,
LastNameRequired,
LastNameTooLong,
EmailRequired,
EmailTooLong,
EmailInvalid ->
Pair(HttpStatusCode.BadRequest, "There is an error in your request")
// events
DomainMessage.CustomerCreated ->
CustomerCreated ->
Pair(HttpStatusCode.Created, "Customer created")
is DomainMessage.EmailAddressChanged ->
is EmailAddressChanged ->
Pair(HttpStatusCode.OK, "Email address changed from ${message.old} to ${message.new}")
// exposed errors
DomainMessage.CustomerNotFound ->
CustomerNotFound ->
Pair(HttpStatusCode.NotFound, "Unknown customer")
// internal errors
DomainMessage.SqlCustomerInvalid,
DomainMessage.DatabaseTimeout,
is DomainMessage.DatabaseError ->
SqlCustomerInvalid,
DatabaseTimeout,
is DatabaseError ->
Pair(HttpStatusCode.InternalServerError, "Internal server error occurred")
}

View File

@ -6,8 +6,8 @@ import com.github.michaelbull.result.Ok
data class CustomerId(val id: Long) {
companion object {
fun create(id: Long?) = when {
id == null -> Err(DomainMessage.CustomerRequired)
id < 1 -> Err(DomainMessage.CustomerIdMustBePositive)
id == null -> Err(CustomerRequired)
id < 1 -> Err(CustomerIdMustBePositive)
else -> Ok(CustomerId(id))
}
}

View File

@ -3,7 +3,7 @@ package com.github.michaelbull.result.example.model.domain
/**
* All possible things that can happen in the use-cases
*/
sealed class DomainMessage {
sealed class DomainMessage
/* validation errors */
@ -34,4 +34,3 @@ sealed class DomainMessage {
object SqlCustomerInvalid : DomainMessage()
object DatabaseTimeout : DomainMessage()
class DatabaseError(val reason: String?) : DomainMessage()
}

View File

@ -10,9 +10,9 @@ data class EmailAddress(
private val pattern = ".+@.+\\..+".toRegex() // crude validation
fun create(address: String?) = when {
address == null || address.isBlank() -> Err(DomainMessage.EmailRequired)
address.length > 20 -> Err(DomainMessage.EmailTooLong)
!address.matches(pattern) -> Err(DomainMessage.EmailInvalid)
address == null || address.isBlank() -> Err(EmailRequired)
address.length > 20 -> Err(EmailTooLong)
!address.matches(pattern) -> Err(EmailInvalid)
else -> Ok(EmailAddress(address))
}
}

View File

@ -9,10 +9,10 @@ data class PersonalName(
) {
companion object {
fun create(first: String?, last: String?) = when {
first == null || first.isBlank() -> Err(DomainMessage.FirstNameRequired)
last == null || last.isBlank() -> Err(DomainMessage.LastNameRequired)
first.length > 10 -> Err(DomainMessage.FirstNameTooLong)
last.length > 10 -> Err(DomainMessage.LastNameTooLong)
first == null || first.isBlank() -> Err(FirstNameRequired)
last == null || last.isBlank() -> Err(LastNameRequired)
first.length > 10 -> Err(FirstNameTooLong)
last.length > 10 -> Err(LastNameTooLong)
else -> Ok(PersonalName(first, last))
}
}

View File

@ -5,8 +5,13 @@ import com.github.michaelbull.result.Ok
import com.github.michaelbull.result.Result
import com.github.michaelbull.result.andThen
import com.github.michaelbull.result.example.model.domain.Customer
import com.github.michaelbull.result.example.model.domain.CustomerCreated
import com.github.michaelbull.result.example.model.domain.CustomerId
import com.github.michaelbull.result.example.model.domain.CustomerNotFound
import com.github.michaelbull.result.example.model.domain.DatabaseError
import com.github.michaelbull.result.example.model.domain.DatabaseTimeout
import com.github.michaelbull.result.example.model.domain.DomainMessage
import com.github.michaelbull.result.example.model.domain.EmailAddressChanged
import com.github.michaelbull.result.example.model.entity.CustomerEntity
import com.github.michaelbull.result.map
import com.github.michaelbull.result.mapBoth
@ -49,24 +54,24 @@ object CustomerService {
private fun createCustomer(entity: CustomerEntity) =
Result.of { repository.insert(entity) }
.map { DomainMessage.CustomerCreated }
.map { CustomerCreated }
.mapError(::exceptionToDomainMessage)
private fun findCustomer(customers: Collection<Customer>, id: CustomerId): Result<Customer, DomainMessage.CustomerNotFound> {
private fun findCustomer(customers: Collection<Customer>, id: CustomerId): Result<Customer, CustomerNotFound> {
val customer = customers.find { it.id == id }
return if (customer != null) Ok(customer) else Err(DomainMessage.CustomerNotFound)
return if (customer != null) Ok(customer) else Err(CustomerNotFound)
}
private fun differenceBetween(old: Customer, new: Customer): DomainMessage.EmailAddressChanged? {
private fun differenceBetween(old: Customer, new: Customer): EmailAddressChanged? {
return if (new.email != old.email) {
DomainMessage.EmailAddressChanged(old.email.address, new.email.address)
EmailAddressChanged(old.email.address, new.email.address)
} else {
null
}
}
private fun exceptionToDomainMessage(t: Throwable) = when (t) {
is SQLTimeoutException -> DomainMessage.DatabaseTimeout
else -> DomainMessage.DatabaseError(t.message)
is SQLTimeoutException -> DatabaseTimeout
else -> DatabaseError(t.message)
}
}

View File

@ -1,7 +1,7 @@
package com.github.michaelbull.result.example.service
import com.github.michaelbull.result.example.model.entity.CustomerEntity
import com.github.michaelbull.result.example.model.domain.repository.CustomerRepository
import com.github.michaelbull.result.example.model.entity.CustomerEntity
import java.sql.SQLException
import java.sql.SQLTimeoutException