This commit is contained in:
usbharu 2024-05-29 11:56:51 +09:00
parent 75f60a7a62
commit 997e28bdf6
30 changed files with 288 additions and 60 deletions

View File

@ -17,14 +17,15 @@
package dev.usbharu.hideout.core.domain.event.actor package dev.usbharu.hideout.core.domain.event.actor
import dev.usbharu.hideout.core.domain.model.actor.Actor2 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.shared.domainevent.DomainEvent
import dev.usbharu.hideout.core.domain.model.shared.domainevent.DomainEventBody import dev.usbharu.hideout.core.domain.shared.domainevent.DomainEventBody
class ActorDomainEventFactory(private val actor: Actor2) { class ActorDomainEventFactory(private val actor: Actor2) {
fun createEvent(actorEvent: ActorEvent): DomainEvent { fun createEvent(actorEvent: ActorEvent): DomainEvent {
return DomainEvent.create( return DomainEvent.create(
actorEvent.eventName, 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"), update("ActorUpdate"),
delete("ActorDelete"), delete("ActorDelete"),
checkUpdate("ActorCheckUpdate"), checkUpdate("ActorCheckUpdate"),

View File

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

View File

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

View File

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

View File

@ -26,6 +26,7 @@ import org.springframework.stereotype.Component
import java.time.Instant import java.time.Instant
import kotlin.math.max import kotlin.math.max
@Deprecated("Actor2を使う")
data class Actor private constructor( data class Actor private constructor(
@get:NotNull @get:NotNull
@get:Positive @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.event.actor.ActorEvent.*
import dev.usbharu.hideout.core.domain.model.instance.InstanceId 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.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.net.URI
import java.time.Instant import java.time.Instant
@ -99,7 +99,7 @@ class Actor2 private constructor(
} }
abstract class Actor2Factory { abstract class Actor2Factory {
protected suspend fun create( protected suspend fun internalCreate(
id: ActorId, id: ActorId,
name: ActorName, name: ActorName,
domain: Domain, domain: Domain,

View File

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

View File

@ -17,4 +17,8 @@
package dev.usbharu.hideout.core.domain.model.actor package dev.usbharu.hideout.core.domain.model.actor
@JvmInline @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 package dev.usbharu.hideout.core.domain.model.actor
@JvmInline @JvmInline
value class ActorPostsCount(private val postsCount: Long) { value class ActorPostsCount(private val postsCount: Int) {
init { init {
require(0 <= this.postsCount) { "Posts count must be greater than 0" } 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.event.actorinstancerelationship.ActorInstanceRelationshipEvent.*
import dev.usbharu.hideout.core.domain.model.actor.ActorId 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.instance.InstanceId
import dev.usbharu.hideout.core.domain.model.shared.domainevent.DomainEventStorable import dev.usbharu.hideout.core.domain.shared.domainevent.DomainEventStorable
data class ActorInstanceRelationship( data class ActorInstanceRelationship(
val actorId: ActorId, 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.InstanceEvent
import dev.usbharu.hideout.core.domain.event.instance.InstanceEventFactory 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.net.URI
import java.time.Instant import java.time.Instant

View File

@ -24,6 +24,7 @@ import org.hibernate.validator.constraints.URL
import org.springframework.stereotype.Component import org.springframework.stereotype.Component
import java.time.Instant import java.time.Instant
@Deprecated("Post2を使う")
data class Post private constructor( data class Post private constructor(
@get:Positive @get:Positive
val id: Long, 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.event.post.PostEvent
import dev.usbharu.hideout.core.domain.model.actor.ActorId 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.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.net.URI
import java.time.Instant import java.time.Instant

View File

@ -1,37 +0,0 @@
/*
* 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.model.shared.domainevent
import java.time.Instant
import java.util.*
data class DomainEvent(
private val id: String,
private val name: String,
private val occurredOn: Instant,
private val body: DomainEventBody,
) {
companion object {
fun create(name: String, body: DomainEventBody): DomainEvent {
return DomainEvent(UUID.randomUUID().toString(), name, Instant.now(), body)
}
fun reconstruct(id: String, name: String, occurredOn: Instant, body: DomainEventBody): DomainEvent {
return DomainEvent(id, name, occurredOn, body)
}
}
}

View File

@ -17,14 +17,14 @@
package dev.usbharu.hideout.core.domain.service.actor package dev.usbharu.hideout.core.domain.service.actor
import dev.usbharu.hideout.application.config.ApplicationConfig 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 import org.springframework.stereotype.Service
interface IRemoteActorCheckDomainService { interface IRemoteActorCheckDomainService {
fun isRemoteActor(actor: Actor): Boolean fun isRemoteActor(actor: Actor2): Boolean
} }
@Service @Service
class RemoteActorCheckDomainService(private val applicationConfig: ApplicationConfig) : IRemoteActorCheckDomainService { 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

@ -14,7 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */
package dev.usbharu.hideout.core.domain.model.shared.domainevent package dev.usbharu.hideout.core.domain.shared.domainevent
abstract class DomainEventBody(val map: Map<String, Any>) { abstract class DomainEventBody(val map: Map<String, Any>) {
fun toMap(): Map<String, Any> { fun toMap(): Map<String, Any> {

View File

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

View File

@ -14,7 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */
package dev.usbharu.hideout.core.domain.model.shared.domainevent package dev.usbharu.hideout.core.domain.shared.domainevent
abstract class DomainEventStorable { abstract class DomainEventStorable {
private val domainEvents: MutableList<DomainEvent> = mutableListOf() private val domainEvents: MutableList<DomainEvent> = mutableListOf()

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 { ): Actor2 {
val actorName = ActorName(name) val actorName = ActorName(name)
val userUrl = "${applicationConfig.url}/users/${actorName.name}" val userUrl = "${applicationConfig.url}/users/${actorName.name}"
return super.create( return super.internalCreate(
id = ActorId(idGenerateService.generateId()), id = ActorId(idGenerateService.generateId()),
name = actorName, name = actorName,
domain = Domain(applicationConfig.url.host), domain = Domain(applicationConfig.url.host),

View File

@ -0,0 +1,10 @@
package dev.usbharu.hideout.core.domain.model.actor
import org.junit.jupiter.api.Test
class Actor2Test {
@Test
fun alsoKnownAsに自分自身が含まれてはいけない() {
TestActor2Factory.create()
}
}

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?,
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,14 @@
package dev.usbharu.hideout.core.domain.service.actor
import dev.usbharu.hideout.application.config.ApplicationConfig
import org.junit.jupiter.api.Test
import java.net.URI
class RemoteActorCheckDomainServiceTest {
@Test
fun リモートのドメインならtrueを返す() {
val actor =
RemoteActorCheckDomainService(ApplicationConfig(URI.create("https://example.com").toURL())).isRemoteActor()
}
}