UpdatableRegisteredUser.kt
package com.example.realworldkotlinspringbootjdbc.domain
import arrow.core.None
import arrow.core.Option
import arrow.core.Tuple4
import arrow.core.Validated
import arrow.core.ValidatedNel
import arrow.core.invalid
import arrow.core.invalidNel
import arrow.core.valid
import arrow.core.validNel
import arrow.core.zip
import arrow.typeclasses.Semigroup
import com.example.realworldkotlinspringbootjdbc.domain.user.Bio
import com.example.realworldkotlinspringbootjdbc.domain.user.Email
import com.example.realworldkotlinspringbootjdbc.domain.user.Image
import com.example.realworldkotlinspringbootjdbc.domain.user.UserId
import com.example.realworldkotlinspringbootjdbc.domain.user.Username
import com.example.realworldkotlinspringbootjdbc.util.MyError
import java.util.Date
interface UpdatableRegisteredUser {
val userId: UserId
val email: Email
val username: Username
val bio: Bio
val image: Image
val updatedAt: Date
/**
* 実装
*/
private data class ValidatedUpdatableRegisteredUser(
override val userId: UserId,
override val email: Email,
override val username: Username,
override val bio: Bio,
override val image: Image,
override val updatedAt: Date = Date(),
) : UpdatableRegisteredUser
/**
* Factory メソッド
*/
companion object {
/**
* Validation 有り
*/
fun new(
currentUser: RegisteredUser,
email: String?,
username: String?,
bio: String?,
image: String?,
): ValidatedNel<MyError.ValidationError, UpdatableRegisteredUser> {
// TODO: Genericsを使ってリファクタできそう(<T>を利用して処理の共通化できそう)
// Email
val newEmailAllowedNull: () -> ValidatedNel<Email.ValidationError, Email> = { ->
Option.fromNullable(email).fold(
{ currentUser.email.validNel() }, // nullの場合は、currentUserの属性をそのまま利用する
{ Email.new(it) }
)
}
// Username
val newUsernameAllowedNull: () -> ValidatedNel<Username.ValidationError, Username> = { ->
Option.fromNullable(username).fold(
{ currentUser.username.validNel() }, // nullの場合は、currentUserの属性をそのまま利用する
{ Username.new(it) }
)
}
// Bio
val newBioAllowedNull: () -> ValidatedNel<Bio.ValidationError, Bio> = { ->
Option.fromNullable(bio).fold(
{ currentUser.bio.validNel() }, // nullの場合は、currentUserの属性をそのまま利用する
{ Bio.new(it) }
)
}
// Image
val newImageAllowedNull: () -> ValidatedNel<Image.ValidationError, Image> = { ->
Option.fromNullable(image).fold(
{ currentUser.image.validNel() }, // nullの場合は、currentUserの属性をそのまま利用する
{ Image.new(it) }
)
}
@Suppress("DestructuringDeclarationWithTooManyEntries")
return newEmailAllowedNull().zip(
Semigroup.nonEmptyList(),
newUsernameAllowedNull(),
newBioAllowedNull(),
newImageAllowedNull()
) { a, b, c, d -> Tuple4(a, b, c, d) }.fold(
{ it.invalid() }, // 1つでもバリデーションエラーがある場合: Invalid
{
if (it == Tuple4(currentUser.email, currentUser.username, currentUser.bio, currentUser.image)) {
ValidationError.NothingAttributeToUpdatable.invalidNel() // 新旧が同じ場合: Invalid
} else {
val (a, b, c, d) = it
ValidatedUpdatableRegisteredUser(currentUser.userId, a, b, c, d).validNel()
}
}
)
}
}
/**
* ドメインルール
*/
sealed interface ValidationError : MyError.ValidationError {
/**
* 変更が1つもないのは駄目
*/
object NothingAttributeToUpdatable : ValidationError {
override val key: String get() = UpdatableRegisteredUser::class.simpleName.toString()
override val message: String get() = "更新するプロパティが有りません"
fun check(
a: Option<Email>,
b: Option<Username>,
c: Option<Bio>,
d: Option<Image>
): Validated<NothingAttributeToUpdatable, Unit> =
when (Tuple4(a, b, c, d)) {
Tuple4(None, None, None, None) -> { NothingAttributeToUpdatable.invalid() }
else -> { Unit.valid() }
}
}
}
}