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

View File

@ -3,35 +3,34 @@ package com.github.michaelbull.result.example.model.domain
/** /**
* All possible things that can happen in the use-cases * All possible things that can happen in the use-cases
*/ */
sealed class DomainMessage { sealed class DomainMessage
/* validation errors */ /* validation errors */
object CustomerRequired : DomainMessage() object CustomerRequired : DomainMessage()
object CustomerIdMustBePositive : DomainMessage() object CustomerIdMustBePositive : DomainMessage()
object FirstNameRequired : DomainMessage() object FirstNameRequired : DomainMessage()
object FirstNameTooLong : DomainMessage() object FirstNameTooLong : DomainMessage()
object LastNameRequired : DomainMessage() object LastNameRequired : DomainMessage()
object LastNameTooLong : DomainMessage() object LastNameTooLong : DomainMessage()
object EmailRequired : DomainMessage() object EmailRequired : DomainMessage()
object EmailTooLong : DomainMessage() object EmailTooLong : DomainMessage()
object EmailInvalid : DomainMessage() object EmailInvalid : DomainMessage()
/* events */ /* events */
object CustomerCreated : DomainMessage() object CustomerCreated : DomainMessage()
class EmailAddressChanged(val old: String, val new: String) : DomainMessage() class EmailAddressChanged(val old: String, val new: String) : DomainMessage()
/* exposed errors */ /* exposed errors */
object CustomerNotFound : DomainMessage() object CustomerNotFound : DomainMessage()
/* internal errors */ /* internal errors */
object SqlCustomerInvalid : DomainMessage() object SqlCustomerInvalid : DomainMessage()
object DatabaseTimeout : DomainMessage() object DatabaseTimeout : DomainMessage()
class DatabaseError(val reason: String?) : DomainMessage() class DatabaseError(val reason: String?) : DomainMessage()
}

View File

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

View File

@ -9,10 +9,10 @@ data class PersonalName(
) { ) {
companion object { companion object {
fun create(first: String?, last: String?) = when { fun create(first: String?, last: String?) = when {
first == null || first.isBlank() -> Err(DomainMessage.FirstNameRequired) first == null || first.isBlank() -> Err(FirstNameRequired)
last == null || last.isBlank() -> Err(DomainMessage.LastNameRequired) last == null || last.isBlank() -> Err(LastNameRequired)
first.length > 10 -> Err(DomainMessage.FirstNameTooLong) first.length > 10 -> Err(FirstNameTooLong)
last.length > 10 -> Err(DomainMessage.LastNameTooLong) last.length > 10 -> Err(LastNameTooLong)
else -> Ok(PersonalName(first, last)) 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.Result
import com.github.michaelbull.result.andThen import com.github.michaelbull.result.andThen
import com.github.michaelbull.result.example.model.domain.Customer 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.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.DomainMessage
import com.github.michaelbull.result.example.model.domain.EmailAddressChanged
import com.github.michaelbull.result.example.model.entity.CustomerEntity import com.github.michaelbull.result.example.model.entity.CustomerEntity
import com.github.michaelbull.result.map import com.github.michaelbull.result.map
import com.github.michaelbull.result.mapBoth import com.github.michaelbull.result.mapBoth
@ -49,24 +54,24 @@ object CustomerService {
private fun createCustomer(entity: CustomerEntity) = private fun createCustomer(entity: CustomerEntity) =
Result.of { repository.insert(entity) } Result.of { repository.insert(entity) }
.map { DomainMessage.CustomerCreated } .map { CustomerCreated }
.mapError(::exceptionToDomainMessage) .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 } 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) { return if (new.email != old.email) {
DomainMessage.EmailAddressChanged(old.email.address, new.email.address) EmailAddressChanged(old.email.address, new.email.address)
} else { } else {
null null
} }
} }
private fun exceptionToDomainMessage(t: Throwable) = when (t) { private fun exceptionToDomainMessage(t: Throwable) = when (t) {
is SQLTimeoutException -> DomainMessage.DatabaseTimeout is SQLTimeoutException -> DatabaseTimeout
else -> DomainMessage.DatabaseError(t.message) else -> DatabaseError(t.message)
} }
} }

View File

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