Merge remote-tracking branch 'origin/ddd' into ddd

# Conflicts:
#	hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/event/actor/ActorEvent.kt
#	hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/event/actorinstancerelationship/ActorInstanceRelationshipEvent.kt
#	hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/event/instance/InstanceEvent.kt
#	hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/event/post/PostEvent.kt
#	hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/actor/Actor2.kt
#	hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/actor/ActorId.kt
#	hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/actor/ActorName.kt
#	hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/actor/ActorPostsCount.kt
#	hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/actorinstancerelationship/ActorInstanceRelationship.kt
#	hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/instance/Instance.kt
#	hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/model/post/Post2.kt
#	hideout-core/src/main/kotlin/dev/usbharu/hideout/core/domain/service/actor/RemoteActorCheckDomainService.kt
#	hideout-core/src/main/kotlin/dev/usbharu/hideout/core/infrastructure/factory/Actor2FactoryImpl.kt
This commit is contained in:
usbharu 2024-06-01 10:25:23 +09:00
commit 5193a3d2e3
29 changed files with 383 additions and 23 deletions

View File

@ -17,14 +17,15 @@
package dev.usbharu.hideout.core.domain.event.actor
import dev.usbharu.hideout.core.domain.model.actor.Actor2
import dev.usbharu.hideout.core.domain.model.shared.domainevent.DomainEvent
import dev.usbharu.hideout.core.domain.model.shared.domainevent.DomainEventBody
import dev.usbharu.hideout.core.domain.shared.domainevent.DomainEvent
import dev.usbharu.hideout.core.domain.shared.domainevent.DomainEventBody
class ActorDomainEventFactory(private val actor: Actor2) {
fun createEvent(actorEvent: ActorEvent): DomainEvent {
return DomainEvent.create(
actorEvent.eventName,
ActorEventBody(actor)
ActorEventBody(actor),
actorEvent.collectable
)
}
}
@ -35,7 +36,7 @@ class ActorEventBody(actor: Actor2) : DomainEventBody(
)
)
enum class ActorEvent(val eventName: String) {
enum class ActorEvent(val eventName: String, val collectable: Boolean = true) {
update("ActorUpdate"),
delete("ActorDelete"),
checkUpdate("ActorCheckUpdate"),

View File

@ -17,8 +17,8 @@
package dev.usbharu.hideout.core.domain.event.actorinstancerelationship
import dev.usbharu.hideout.core.domain.model.actorinstancerelationship.ActorInstanceRelationship
import dev.usbharu.hideout.core.domain.model.shared.domainevent.DomainEvent
import dev.usbharu.hideout.core.domain.model.shared.domainevent.DomainEventBody
import dev.usbharu.hideout.core.domain.shared.domainevent.DomainEvent
import dev.usbharu.hideout.core.domain.shared.domainevent.DomainEventBody
class ActorInstanceRelationshipDomainEventFactory(private val actorInstanceRelationship: ActorInstanceRelationship) {
fun createEvent(actorInstanceRelationshipEvent: ActorInstanceRelationshipEvent): DomainEvent {

View File

@ -17,8 +17,8 @@
package dev.usbharu.hideout.core.domain.event.instance
import dev.usbharu.hideout.core.domain.model.instance.Instance
import dev.usbharu.hideout.core.domain.model.shared.domainevent.DomainEvent
import dev.usbharu.hideout.core.domain.model.shared.domainevent.DomainEventBody
import dev.usbharu.hideout.core.domain.shared.domainevent.DomainEvent
import dev.usbharu.hideout.core.domain.shared.domainevent.DomainEventBody
class InstanceEventFactory(private val instance: Instance) {
fun createEvent(event: InstanceEvent): DomainEvent {

View File

@ -17,8 +17,8 @@
package dev.usbharu.hideout.core.domain.event.post
import dev.usbharu.hideout.core.domain.model.post.Post2
import dev.usbharu.hideout.core.domain.model.shared.domainevent.DomainEvent
import dev.usbharu.hideout.core.domain.model.shared.domainevent.DomainEventBody
import dev.usbharu.hideout.core.domain.shared.domainevent.DomainEvent
import dev.usbharu.hideout.core.domain.shared.domainevent.DomainEventBody
class PostDomainEventFactory(private val post: Post2) {
fun createEvent(postEvent: PostEvent): DomainEvent {

View File

@ -26,6 +26,7 @@ import org.springframework.stereotype.Component
import java.time.Instant
import kotlin.math.max
@Deprecated("Actor2を使う")
data class Actor private constructor(
@get:NotNull
@get:Positive

View File

@ -20,7 +20,7 @@ import dev.usbharu.hideout.core.domain.event.actor.ActorDomainEventFactory
import dev.usbharu.hideout.core.domain.event.actor.ActorEvent.*
import dev.usbharu.hideout.core.domain.model.instance.InstanceId
import dev.usbharu.hideout.core.domain.model.shared.Domain
import dev.usbharu.hideout.core.domain.model.shared.domainevent.DomainEventStorable
import dev.usbharu.hideout.core.domain.shared.domainevent.DomainEventStorable
import java.net.URI
import java.time.Instant
@ -47,7 +47,7 @@ class Actor2 private constructor(
var lastPostDate: Instant? = null,
suspend: Boolean,
var lastUpdate: Instant = createdAt,
alsoKnownAs: List<ActorId> = emptyList(),
alsoKnownAs: Set<ActorId> = emptySet(),
moveTo: ActorId? = null,
) : DomainEventStorable() {
@ -64,7 +64,7 @@ class Actor2 private constructor(
var alsoKnownAs = alsoKnownAs
set(value) {
require(value.find { it == id } == null)
field = value.distinct()
field = value
}
var moveTo = moveTo
@ -99,7 +99,7 @@ class Actor2 private constructor(
}
abstract class Actor2Factory {
protected suspend fun create(
protected suspend fun internalCreate(
id: ActorId,
name: ActorName,
domain: Domain,

View File

@ -18,6 +18,9 @@ package dev.usbharu.hideout.core.domain.model.actor
@JvmInline
value class ActorId(val id: Long) {
init {
require(0 <= id)
}
companion object {
val ghost = ActorId(0L)
}

View File

@ -17,4 +17,8 @@
package dev.usbharu.hideout.core.domain.model.actor
@JvmInline
value class ActorName(val name: String)
value class ActorName(val name: String) {
init {
}
}

View File

@ -17,7 +17,7 @@
package dev.usbharu.hideout.core.domain.model.actor
@JvmInline
value class ActorPostsCount(private val postsCount: Long) {
value class ActorPostsCount(private val postsCount: Int) {
init {
require(0 <= this.postsCount) { "Posts count must be greater than 0" }
}

View File

@ -20,7 +20,7 @@ import dev.usbharu.hideout.core.domain.event.actorinstancerelationship.ActorInst
import dev.usbharu.hideout.core.domain.event.actorinstancerelationship.ActorInstanceRelationshipEvent.*
import dev.usbharu.hideout.core.domain.model.actor.ActorId
import dev.usbharu.hideout.core.domain.model.instance.InstanceId
import dev.usbharu.hideout.core.domain.model.shared.domainevent.DomainEventStorable
import dev.usbharu.hideout.core.domain.shared.domainevent.DomainEventStorable
data class ActorInstanceRelationship(
val actorId: ActorId,

View File

@ -18,7 +18,7 @@ package dev.usbharu.hideout.core.domain.model.instance
import dev.usbharu.hideout.core.domain.event.instance.InstanceEvent
import dev.usbharu.hideout.core.domain.event.instance.InstanceEventFactory
import dev.usbharu.hideout.core.domain.model.shared.domainevent.DomainEventStorable
import dev.usbharu.hideout.core.domain.shared.domainevent.DomainEventStorable
import java.net.URI
import java.time.Instant

View File

@ -24,6 +24,7 @@ import org.hibernate.validator.constraints.URL
import org.springframework.stereotype.Component
import java.time.Instant
@Deprecated("Post2を使う")
data class Post private constructor(
@get:Positive
val id: Long,

View File

@ -20,7 +20,7 @@ import dev.usbharu.hideout.core.domain.event.post.PostDomainEventFactory
import dev.usbharu.hideout.core.domain.event.post.PostEvent
import dev.usbharu.hideout.core.domain.model.actor.ActorId
import dev.usbharu.hideout.core.domain.model.media.MediaId
import dev.usbharu.hideout.core.domain.model.shared.domainevent.DomainEventStorable
import dev.usbharu.hideout.core.domain.shared.domainevent.DomainEventStorable
import java.net.URI
import java.time.Instant

View File

@ -17,14 +17,14 @@
package dev.usbharu.hideout.core.domain.service.actor
import dev.usbharu.hideout.application.config.ApplicationConfig
import dev.usbharu.hideout.core.domain.model.actor.Actor
import dev.usbharu.hideout.core.domain.model.actor.Actor2
import org.springframework.stereotype.Service
interface IRemoteActorCheckDomainService {
fun isRemoteActor(actor: Actor): Boolean
fun isRemoteActor(actor: Actor2): Boolean
}
@Service
class RemoteActorCheckDomainService(private val applicationConfig: ApplicationConfig) : IRemoteActorCheckDomainService {
override fun isRemoteActor(actor: Actor): Boolean = actor.domain == applicationConfig.url.host
override fun isRemoteActor(actor: Actor2): Boolean = actor.domain.domain == applicationConfig.url.host
}

View File

@ -0,0 +1,53 @@
/*
* Copyright (C) 2024 usbharu
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dev.usbharu.hideout.core.domain.shared.domainevent
import java.time.Instant
import java.util.*
/**
* エンティティで発生したドメインイベント
*
* @property id ID
* @property name ドメインイベント名
* @property occurredOn 発生時刻
* @property body ドメインイベントのボディ
* @property collectable trueで同じドメインイベント名でをまとめる
*/
data class DomainEvent(
val id: String,
val name: String,
val occurredOn: Instant,
val body: DomainEventBody,
val collectable: Boolean = false
) {
companion object {
fun create(name: String, body: DomainEventBody, collectable: Boolean = false): DomainEvent {
return DomainEvent(UUID.randomUUID().toString(), name, Instant.now(), body, collectable)
}
fun reconstruct(
id: String,
name: String,
occurredOn: Instant,
body: DomainEventBody,
collectable: Boolean
): DomainEvent {
return DomainEvent(id, name, occurredOn, body, collectable)
}
}
}

View File

@ -0,0 +1,23 @@
/*
* Copyright (C) 2024 usbharu
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dev.usbharu.hideout.core.domain.shared.domainevent
abstract class DomainEventBody(val map: Map<String, Any>) {
fun toMap(): Map<String, Any> {
return map
}
}

View File

@ -0,0 +1,5 @@
package dev.usbharu.hideout.core.domain.shared.domainevent
interface DomainEventPublisher {
suspend fun publishEvent(domainEvent: DomainEvent)
}

View File

@ -0,0 +1,29 @@
/*
* Copyright (C) 2024 usbharu
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dev.usbharu.hideout.core.domain.shared.domainevent
abstract class DomainEventStorable {
private val domainEvents: MutableList<DomainEvent> = mutableListOf()
protected fun addDomainEvent(domainEvent: DomainEvent) {
domainEvents.add(domainEvent)
}
fun clearDomainEvents() = domainEvents.clear()
fun getDomainEvents(): List<DomainEvent> = domainEvents.toList()
}

View File

@ -0,0 +1,7 @@
package dev.usbharu.hideout.core.domain.shared.domainevent
interface DomainEventSubscriber {
fun subscribe(eventName: String, domainEventConsumer: DomainEventConsumer)
}
typealias DomainEventConsumer = (DomainEvent) -> Unit

View File

@ -0,0 +1,22 @@
package dev.usbharu.hideout.core.domain.shared.repository
import dev.usbharu.hideout.core.domain.shared.domainevent.DomainEventPublisher
import dev.usbharu.hideout.core.domain.shared.domainevent.DomainEventStorable
import org.springframework.stereotype.Repository
@Repository
interface DomainEventPublishableRepository<T : DomainEventStorable> {
val domainEventPublisher: DomainEventPublisher
suspend fun update(entity: T) {
entity.getDomainEvents().distinctBy {
if (it.collectable) {
it.name
} else {
it.id
}
}.forEach {
domainEventPublisher.publishEvent(it)
}
entity.clearDomainEvents()
}
}

View File

@ -0,0 +1,37 @@
package dev.usbharu.hideout.core.infrastructure.exposedrepository
import dev.usbharu.hideout.core.domain.model.actor.Actor2
import dev.usbharu.hideout.core.domain.model.actor.Actor2Repository
import dev.usbharu.hideout.core.domain.model.actor.ActorId
import dev.usbharu.hideout.core.domain.shared.domainevent.DomainEventPublisher
import dev.usbharu.hideout.core.domain.shared.repository.DomainEventPublishableRepository
import org.slf4j.Logger
import org.slf4j.LoggerFactory
import org.springframework.stereotype.Repository
@Repository
class ExposedActor2Repository(override val domainEventPublisher: DomainEventPublisher) : AbstractRepository(),
DomainEventPublishableRepository<Actor2>, Actor2Repository {
override val logger: Logger
get() = Companion.logger
companion object {
private val logger = LoggerFactory.getLogger(ExposedActor2Repository::class.java)
}
override suspend fun save(actor: Actor2): Actor2 {
query {
}
update(actor)
return actor
}
override suspend fun delete(actor: Actor2) {
TODO("Not yet implemented")
}
override suspend fun findById(id: ActorId): Actor2? {
TODO("Not yet implemented")
}
}

View File

@ -39,7 +39,7 @@ class Actor2FactoryImpl(
): Actor2 {
val actorName = ActorName(name)
val userUrl = "${applicationConfig.url}/users/${actorName.name}"
return super.create(
return super.internalCreate(
id = ActorId(idGenerateService.generateId()),
name = actorName,
domain = Domain(applicationConfig.url.host),

View File

@ -0,0 +1,26 @@
package dev.usbharu.hideout.core.domain.model.actor
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.assertThrows
class Actor2Test {
@Test
fun alsoKnownAsに自分自身が含まれてはいけない() {
val actor = TestActor2Factory.create(publicKey = ActorPublicKey(""))
assertThrows<IllegalArgumentException> {
actor.alsoKnownAs = setOf(actor.id)
}
}
@Test
fun moveToに自分自身が設定されてはいけない() {
val actor = TestActor2Factory.create(publicKey = ActorPublicKey(""))
assertThrows<IllegalArgumentException> {
actor.moveTo = actor.id
}
}
}

View File

@ -0,0 +1,5 @@
package dev.usbharu.hideout.core.domain.model.actor
class ActorDescriptionTest {
}

View File

@ -0,0 +1,12 @@
package dev.usbharu.hideout.core.domain.model.actor
import org.junit.jupiter.api.Test
class ActorIdTest {
@Test
fun idを負の数にすることはできない() {
org.junit.jupiter.api.assertThrows<IllegalArgumentException> {
ActorId(-1)
}
}
}

View File

@ -0,0 +1,3 @@
package dev.usbharu.hideout.core.domain.model.actor
class ActorPublicKeyTest

View File

@ -0,0 +1,5 @@
package dev.usbharu.hideout.core.domain.model.actor
class ActorScreenNameTest {
}

View File

@ -0,0 +1,82 @@
package dev.usbharu.hideout.core.domain.model.actor
import dev.usbharu.hideout.application.service.id.TwitterSnowflakeIdGenerateService
import dev.usbharu.hideout.core.domain.model.instance.InstanceId
import dev.usbharu.hideout.core.domain.model.shared.Domain
import kotlinx.coroutines.runBlocking
import java.net.URI
import java.time.Instant
object TestActor2Factory : Actor2.Actor2Factory() {
private val idGenerateService = TwitterSnowflakeIdGenerateService
fun create(
id: Long = generateId(),
actorName: String = "test-$id",
domain: String = "example.com",
actorScreenName: String = actorName,
description: String = "test description",
inbox: URI = URI.create("https://example.com/$id/inbox"),
outbox: URI = URI.create("https://example.com/$id/outbox"),
uri: URI = URI.create("https://example.com/$id"),
publicKey: ActorPublicKey,
privateKey: ActorPrivateKey? = null,
createdAt: Instant = Instant.now(),
keyId: String = "https://example.com/$id#key-id",
followersEndpoint: URI = URI.create("https://example.com/$id/followers"),
followingEndpoint: URI = URI.create("https://example.com/$id/following"),
instanceId: Long = 1L,
locked: Boolean = false,
followersCount: Int = 0,
followingCount: Int = 0,
postCount: Int = 0,
lastPostDate: Instant? = null,
suspend: Boolean = false
): Actor2 {
return runBlocking {
super.internalCreate(
id = ActorId(id),
name = ActorName(actorName),
domain = Domain(domain),
screenName = TestActorScreenNameFactory.create(actorScreenName),
description = TestActorDescriptionFactory.create(description),
inbox = inbox,
outbox = outbox,
url = uri,
publicKey = publicKey,
privateKey = privateKey,
createdAt = createdAt,
keyId = ActorKeyId(keyId),
followersEndpoint = followersEndpoint,
followingEndpoint = followingEndpoint,
InstanceId(instanceId),
locked,
followersCount = ActorRelationshipCount(followersCount),
followingCount = ActorRelationshipCount(followingCount),
postsCount = ActorPostsCount(postCount),
lastPostDate = lastPostDate,
suspend = suspend
)
}
}
private fun generateId(): Long = runBlocking {
idGenerateService.generateId()
}
}
object TestActorScreenNameFactory : ActorScreenName.ActorScreenNameFactory() {
fun create(name: String): ActorScreenName {
return runBlocking {
super.create(name, emptyList())
}
}
}
object TestActorDescriptionFactory : ActorDescription.ActorDescriptionFactory() {
fun create(description: String): ActorDescription {
return runBlocking {
super.create(description, emptyList())
}
}
}

View File

@ -0,0 +1,41 @@
package dev.usbharu.hideout.core.domain.service.actor
import dev.usbharu.hideout.application.config.ApplicationConfig
import dev.usbharu.hideout.core.domain.model.actor.ActorPublicKey
import dev.usbharu.hideout.core.domain.model.actor.TestActor2Factory
import org.junit.jupiter.api.Test
import java.net.URI
import kotlin.test.assertFalse
import kotlin.test.assertTrue
class RemoteActorCheckDomainServiceTest {
@Test
fun リモートのドメインならtrueを返す() {
val actor = TestActor2Factory.create(publicKey = ActorPublicKey(""))
val remoteActor = RemoteActorCheckDomainService(
ApplicationConfig(
URI.create("https://local.example.com").toURL()
)
).isRemoteActor(
actor
)
assertTrue(remoteActor)
}
@Test
fun ローカルのActorならfalseを返す() {
val actor = TestActor2Factory.create(domain = "local.example.com", publicKey = ActorPublicKey(""))
val localActor = RemoteActorCheckDomainService(
ApplicationConfig(
URI.create("https://local.example.com").toURL()
)
).isRemoteActor(
actor
)
assertFalse(localActor)
}
}