Merge pull request #609 from usbharu/develop-env

開発環境の整備
This commit is contained in:
usbharu 2024-09-13 14:08:18 +09:00 committed by GitHub
commit 2fc5962ddd
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
27 changed files with 404 additions and 157 deletions

View File

@ -0,0 +1,58 @@
version: "3"
services:
db:
image: postgres:16
ports:
- "5432:5432"
environment:
POSTGRES_USER: "postgres"
POSTGRES_PASSWORD: "password"
POSTGRES_DB: "hideout"
mongodb:
image: mongo:7.0.14
ports:
- "27017:27017"
elasticsearch:
image: docker.elastic.co/elasticsearch/elasticsearch:7.17.24
container_name: elasticsearch
environment:
- discovery.type=single-node
- cluster.name=docker-cluster
- bootstrap.memory_lock=true
- xpack.security.enabled=true
- xpack.monitoring.collection.enabled=true
- ELASTIC_PASSWORD=Passw0rd
- "ES_JAVA_OPTS=-Xms512M -Xmx512M"
ports:
- "9200:9200"
volumes:
- elasticsearch-data:/usr/share/elasticsearch/data
kibana:
image: docker.elastic.co/kibana/kibana:7.17.24
container_name: kibana
ports:
- "5601:5601"
environment:
- ELASTICSEARCH_URL=http://elasticsearch:9200
- ELASTICSEARCH_USERNAME=elastic
- ELASTICSEARCH_PASSWORD=Passw0rd
depends_on:
- elasticsearch
fluentd:
build: ./fluentd
volumes:
- ./fluentd/conf:/fluentd/etc
- ../logs:/in
depends_on:
- "elasticsearch"
ports:
- "24224:24224"
- "24224:24224/udp"
volumes:
elasticsearch-data:
driver: local

View File

@ -0,0 +1,4 @@
FROM fluent/fluentd:v1.16-debian-2
USER root
RUN fluent-gem install fluent-plugin-elasticsearch --no-document
USER fluent

View File

@ -0,0 +1,22 @@
<source>
@type tail
path /in/logFile.log.json
pos_file /in/logFile.pos
tag hideout
<parse>
@type json
</parse>
</source>
<match hideout>
@type elasticsearch
host elasticsearch
include_tag_key true
port 9200
include_timestamp true
user elastic
password Passw0rd
logstash_format true
logstash_prefix hideout
flush_interval 10s
</match>

View File

@ -1,16 +0,0 @@
version: "3"
services:
db:
image: postgres:16
ports:
- "5432:5432"
environment:
POSTGRES_USER: "postgres"
POSTGRES_PASSWORD: "password"
POSTGRES_DB: "hideout"
mongodb:
image: mongo:7.0.14
ports:
- "27017:27017"

View File

@ -1,6 +1,6 @@
distributionBase=GRADLE_USER_HOME distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.1-bin.zip distributionUrl=https\://services.gradle.org/distributions/gradle-8.10-bin.zip
networkTimeout=10000 networkTimeout=10000
validateDistributionUrl=true validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME

View File

@ -1,6 +1,6 @@
distributionBase=GRADLE_USER_HOME distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.1-bin.zip distributionUrl=https\://services.gradle.org/distributions/gradle-8.10-bin.zip
networkTimeout=10000 networkTimeout=10000
validateDistributionUrl=true validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME

View File

@ -5,6 +5,7 @@ import dev.usbharu.hideout.core.domain.shared.domainevent.DomainEventBody
interface DomainEventSubscriber { interface DomainEventSubscriber {
fun <T : DomainEventBody> subscribe(eventName: String, domainEventConsumer: DomainEventConsumer<T>) fun <T : DomainEventBody> subscribe(eventName: String, domainEventConsumer: DomainEventConsumer<T>)
fun getSubscribers(): Map<String, List<DomainEventConsumer<*>>>
} }
typealias DomainEventConsumer<T> = suspend (DomainEvent<T>) -> Unit typealias DomainEventConsumer<T> = suspend (DomainEvent<T>) -> Unit

View File

@ -3,19 +3,22 @@ package dev.usbharu.hideout.core.application.domainevent.subscribers
import dev.usbharu.hideout.core.domain.event.userdetail.UserDetailEvent import dev.usbharu.hideout.core.domain.event.userdetail.UserDetailEvent
import dev.usbharu.hideout.core.domain.event.userdetail.UserDetailEventBody import dev.usbharu.hideout.core.domain.event.userdetail.UserDetailEventBody
import dev.usbharu.hideout.core.domain.model.support.principal.Anonymous import dev.usbharu.hideout.core.domain.model.support.principal.Anonymous
import dev.usbharu.hideout.core.domain.shared.domainevent.DomainEvent
import org.springframework.stereotype.Component import org.springframework.stereotype.Component
@Component @Component
class RegisterLocalUserSetHomeTimelineSubscriber( class RegisterLocalUserSetHomeTimelineSubscriber(
private val domainEventSubscriber: DomainEventSubscriber, private val domainEventSubscriber: DomainEventSubscriber,
private val userRegisterHomeTimelineApplicationService: UserRegisterHomeTimelineApplicationService private val userRegisterHomeTimelineApplicationService: UserRegisterHomeTimelineApplicationService
) : Subscriber { ) : Subscriber, DomainEventConsumer<UserDetailEventBody> {
override fun init() { override fun init() {
domainEventSubscriber.subscribe<UserDetailEventBody>(UserDetailEvent.CREATE.eventName) { domainEventSubscriber.subscribe<UserDetailEventBody>(UserDetailEvent.CREATE.eventName, this)
}
override suspend fun invoke(p1: DomainEvent<UserDetailEventBody>) {
userRegisterHomeTimelineApplicationService.execute( userRegisterHomeTimelineApplicationService.execute(
RegisterHomeTimeline(it.body.getUserDetail().id), RegisterHomeTimeline(p1.body.getUserDetail().id),
Anonymous Anonymous
) )
} }
} }
}

View File

@ -5,17 +5,20 @@ import dev.usbharu.hideout.core.application.timeline.SetTimleineStore
import dev.usbharu.hideout.core.domain.event.timeline.TimelineEvent import dev.usbharu.hideout.core.domain.event.timeline.TimelineEvent
import dev.usbharu.hideout.core.domain.event.timeline.TimelineEventBody import dev.usbharu.hideout.core.domain.event.timeline.TimelineEventBody
import dev.usbharu.hideout.core.domain.model.support.principal.Anonymous import dev.usbharu.hideout.core.domain.model.support.principal.Anonymous
import dev.usbharu.hideout.core.domain.shared.domainevent.DomainEvent
import org.springframework.stereotype.Component import org.springframework.stereotype.Component
@Component @Component
class RegisterTimelineSetTimelineStoreSubscriber( class RegisterTimelineSetTimelineStoreSubscriber(
private val domainEventSubscriber: DomainEventSubscriber, private val domainEventSubscriber: DomainEventSubscriber,
private val setTimelineToTimelineStoreApplicationService: SetTimelineToTimelineStoreApplicationService private val setTimelineToTimelineStoreApplicationService: SetTimelineToTimelineStoreApplicationService
) : Subscriber { ) : Subscriber, DomainEventConsumer<TimelineEventBody> {
override fun init() { override fun init() {
domainEventSubscriber.subscribe<TimelineEventBody>(TimelineEvent.CREATE.eventName) { domainEventSubscriber.subscribe<TimelineEventBody>(TimelineEvent.CREATE.eventName, this)
setTimelineToTimelineStoreApplicationService.execute(SetTimleineStore(it.body.getTimelineId()), Anonymous) }
}
override suspend fun invoke(p1: DomainEvent<TimelineEventBody>) {
setTimelineToTimelineStoreApplicationService.execute(SetTimleineStore(p1.body.getTimelineId()), Anonymous)
} }
} }

View File

@ -1,12 +1,46 @@
package dev.usbharu.hideout.core.application.domainevent.subscribers package dev.usbharu.hideout.core.application.domainevent.subscribers
import org.slf4j.LoggerFactory
import org.springframework.boot.ApplicationArguments import org.springframework.boot.ApplicationArguments
import org.springframework.boot.ApplicationRunner import org.springframework.boot.ApplicationRunner
import org.springframework.stereotype.Component import org.springframework.stereotype.Component
@Component @Component
class SubscriberRunner(private val subscribers: List<Subscriber>) : ApplicationRunner { class SubscriberRunner(
private val subscribers: List<Subscriber>,
private val domainEventSubscriber: DomainEventSubscriber
) : ApplicationRunner {
override fun run(args: ApplicationArguments?) { override fun run(args: ApplicationArguments?) {
subscribers.forEach { it.init() } subscribers.forEach { it.init() }
if (logger.isDebugEnabled) {
val stringListMap = domainEventSubscriber.getSubscribers()
val header = """
|====== Domain Event Subscribers Report =====
|
|
|
""".trimMargin()
val value = stringListMap.map {
it.key + "\n\t" + it.value.joinToString("\n", "[", "]") { suspendFunction1 ->
suspendFunction1::class.qualifiedName.orEmpty()
}
}.joinToString("\n\n\n")
val footer = """
|
|
|
|===== Domain Event Subscribers Report =====
""".trimMargin()
logger.debug("{}{}{}", header, value, footer)
}
}
companion object {
private val logger = LoggerFactory.getLogger(SubscriberRunner::class.java)
} }
} }

View File

@ -5,16 +5,19 @@ import dev.usbharu.hideout.core.application.timeline.TimelineAddPostApplicationS
import dev.usbharu.hideout.core.domain.event.post.PostEvent import dev.usbharu.hideout.core.domain.event.post.PostEvent
import dev.usbharu.hideout.core.domain.event.post.PostEventBody import dev.usbharu.hideout.core.domain.event.post.PostEventBody
import dev.usbharu.hideout.core.domain.model.support.principal.Anonymous import dev.usbharu.hideout.core.domain.model.support.principal.Anonymous
import dev.usbharu.hideout.core.domain.shared.domainevent.DomainEvent
import org.springframework.stereotype.Component import org.springframework.stereotype.Component
@Component @Component
class TimelinePostCreateSubscriber( class TimelinePostCreateSubscriber(
private val timelineAddPostApplicationService: TimelineAddPostApplicationService, private val timelineAddPostApplicationService: TimelineAddPostApplicationService,
private val domainEventSubscriber: DomainEventSubscriber, private val domainEventSubscriber: DomainEventSubscriber,
) : Subscriber { ) : Subscriber, DomainEventConsumer<PostEventBody> {
override fun init() { override fun init() {
domainEventSubscriber.subscribe<PostEventBody>(PostEvent.CREATE.eventName) { domainEventSubscriber.subscribe<PostEventBody>(PostEvent.CREATE.eventName, this)
timelineAddPostApplicationService.execute(AddPost(it.body.getPostId()), Anonymous) }
}
override suspend fun invoke(p1: DomainEvent<PostEventBody>) {
timelineAddPostApplicationService.execute(AddPost(p1.body.getPostId()), Anonymous)
} }
} }

View File

@ -7,6 +7,7 @@ import dev.usbharu.hideout.core.domain.event.relationship.RelationshipEvent
import dev.usbharu.hideout.core.domain.event.relationship.RelationshipEventBody import dev.usbharu.hideout.core.domain.event.relationship.RelationshipEventBody
import dev.usbharu.hideout.core.domain.model.timelinerelationship.Visible import dev.usbharu.hideout.core.domain.model.timelinerelationship.Visible
import dev.usbharu.hideout.core.domain.model.userdetails.UserDetailRepository import dev.usbharu.hideout.core.domain.model.userdetails.UserDetailRepository
import dev.usbharu.hideout.core.domain.shared.domainevent.DomainEvent
import org.slf4j.LoggerFactory import org.slf4j.LoggerFactory
import org.springframework.stereotype.Component import org.springframework.stereotype.Component
@ -15,16 +16,19 @@ class TimelineRelationshipFollowSubscriber(
private val userAddTimelineRelationshipApplicationService: UserAddTimelineRelationshipApplicationService, private val userAddTimelineRelationshipApplicationService: UserAddTimelineRelationshipApplicationService,
private val userDetailRepository: UserDetailRepository, private val userDetailRepository: UserDetailRepository,
private val domainEventSubscriber: DomainEventSubscriber private val domainEventSubscriber: DomainEventSubscriber
) : Subscriber { ) : Subscriber, DomainEventConsumer<RelationshipEventBody> {
override fun init() { override fun init() {
domainEventSubscriber.subscribe<RelationshipEventBody>(RelationshipEvent.ACCEPT_FOLLOW.eventName) { domainEventSubscriber.subscribe<RelationshipEventBody>(RelationshipEvent.ACCEPT_FOLLOW.eventName, this)
val relationship = it.body.getRelationship() }
override suspend fun invoke(p1: DomainEvent<RelationshipEventBody>) {
val relationship = p1.body.getRelationship()
val userDetail = userDetailRepository.findByActorId(relationship.actorId.id) val userDetail = userDetailRepository.findByActorId(relationship.actorId.id)
?: throw InternalServerException("Userdetail ${relationship.actorId} not found by actorid.") ?: throw InternalServerException("Userdetail ${relationship.actorId} not found by actorid.")
if (userDetail.homeTimelineId == null) { if (userDetail.homeTimelineId == null) {
logger.warn("Home timeline for ${relationship.actorId} is not found") logger.warn("Home timeline for ${relationship.actorId} is not found")
return@subscribe return
} }
@Suppress("UnsafeCallOnNullableType") @Suppress("UnsafeCallOnNullableType")
@ -34,10 +38,9 @@ class TimelineRelationshipFollowSubscriber(
relationship.targetActorId, relationship.targetActorId,
Visible.FOLLOWERS Visible.FOLLOWERS
), ),
it.body.principal p1.body.principal
) )
} }
}
companion object { companion object {
private val logger = LoggerFactory.getLogger(TimelineRelationshipFollowSubscriber::class.java) private val logger = LoggerFactory.getLogger(TimelineRelationshipFollowSubscriber::class.java)

View File

@ -6,6 +6,7 @@ import dev.usbharu.hideout.core.domain.event.relationship.RelationshipEvent
import dev.usbharu.hideout.core.domain.event.relationship.RelationshipEventBody import dev.usbharu.hideout.core.domain.event.relationship.RelationshipEventBody
import dev.usbharu.hideout.core.domain.model.timelinerelationship.TimelineRelationshipRepository import dev.usbharu.hideout.core.domain.model.timelinerelationship.TimelineRelationshipRepository
import dev.usbharu.hideout.core.domain.model.userdetails.UserDetailRepository import dev.usbharu.hideout.core.domain.model.userdetails.UserDetailRepository
import dev.usbharu.hideout.core.domain.shared.domainevent.DomainEvent
import org.slf4j.LoggerFactory import org.slf4j.LoggerFactory
import org.springframework.stereotype.Component import org.springframework.stereotype.Component
@ -15,16 +16,19 @@ class TimelineRelationshipUnfollowSubscriber(
private val userRemoveTimelineRelationshipApplicationService: UserRemoveTimelineRelationshipApplicationService, private val userRemoveTimelineRelationshipApplicationService: UserRemoveTimelineRelationshipApplicationService,
private val userDetailRepository: UserDetailRepository, private val userDetailRepository: UserDetailRepository,
private val timelineRelationshipRepository: TimelineRelationshipRepository, private val timelineRelationshipRepository: TimelineRelationshipRepository,
) : Subscriber { ) : Subscriber, DomainEventConsumer<RelationshipEventBody> {
override fun init() { override fun init() {
domainEventSubscriber.subscribe<RelationshipEventBody>(RelationshipEvent.UNFOLLOW.eventName) { domainEventSubscriber.subscribe<RelationshipEventBody>(RelationshipEvent.UNFOLLOW.eventName, this)
val relationship = it.body.getRelationship() }
override suspend fun invoke(p1: DomainEvent<RelationshipEventBody>) {
val relationship = p1.body.getRelationship()
val userDetail = userDetailRepository.findByActorId(relationship.actorId.id) ?: throw IllegalStateException( val userDetail = userDetailRepository.findByActorId(relationship.actorId.id) ?: throw IllegalStateException(
"UserDetail ${relationship.actorId} not found by actorId." "UserDetail ${relationship.actorId} not found by actorId."
) )
if (userDetail.homeTimelineId == null) { if (userDetail.homeTimelineId == null) {
logger.warn("HomeTimeline for ${userDetail.id} not found.") logger.warn("HomeTimeline for ${userDetail.id} not found.")
return@subscribe return
} }
val timelineRelationship = timelineRelationshipRepository.findByTimelineIdAndActorId( val timelineRelationship = timelineRelationshipRepository.findByTimelineIdAndActorId(
@ -35,10 +39,9 @@ class TimelineRelationshipUnfollowSubscriber(
userRemoveTimelineRelationshipApplicationService.execute( userRemoveTimelineRelationshipApplicationService.execute(
RemoveTimelineRelationship(timelineRelationship.id), RemoveTimelineRelationship(timelineRelationship.id),
it.body.principal p1.body.principal
) )
} }
}
companion object { companion object {
private val logger = LoggerFactory.getLogger(TimelineRelationshipUnfollowSubscriber::class.java) private val logger = LoggerFactory.getLogger(TimelineRelationshipUnfollowSubscriber::class.java)

View File

@ -19,6 +19,7 @@ package dev.usbharu.hideout.core.application.shared
import dev.usbharu.hideout.core.domain.model.support.principal.Principal import dev.usbharu.hideout.core.domain.model.support.principal.Principal
import kotlinx.coroutines.CancellationException import kotlinx.coroutines.CancellationException
import org.slf4j.Logger import org.slf4j.Logger
import org.slf4j.MDC
abstract class AbstractApplicationService<T : Any, R>( abstract class AbstractApplicationService<T : Any, R>(
protected val transaction: Transaction, protected val transaction: Transaction,
@ -26,6 +27,7 @@ abstract class AbstractApplicationService<T : Any, R>(
) : ApplicationService<T, R> { ) : ApplicationService<T, R> {
override suspend fun execute(command: T, principal: Principal): R { override suspend fun execute(command: T, principal: Principal): R {
return try { return try {
MDC.put("applicationService", this::class.simpleName)
logger.debug("START {}", command::class.simpleName) logger.debug("START {}", command::class.simpleName)
val response = transaction.transaction<R> { val response = transaction.transaction<R> {
internalExecute(command, principal) internalExecute(command, principal)
@ -39,6 +41,8 @@ abstract class AbstractApplicationService<T : Any, R>(
} catch (@Suppress("TooGenericExceptionCaught") e: Exception) { } catch (@Suppress("TooGenericExceptionCaught") e: Exception) {
logger.warn("Command execution error", e) logger.warn("Command execution error", e)
throw e throw e
} finally {
MDC.remove("applicationService")
} }
} }

View File

@ -16,8 +16,11 @@
package dev.usbharu.hideout.core.config package dev.usbharu.hideout.core.config
import ch.qos.logback.classic.helpers.MDCInsertingServletFilter
import dev.usbharu.hideout.core.infrastructure.springframework.ApplicationRequestLogInterceptor
import dev.usbharu.hideout.core.infrastructure.springframework.SPAInterceptor import dev.usbharu.hideout.core.infrastructure.springframework.SPAInterceptor
import dev.usbharu.hideout.generate.JsonOrFormModelMethodProcessor import dev.usbharu.hideout.generate.JsonOrFormModelMethodProcessor
import org.springframework.boot.web.servlet.FilterRegistrationBean
import org.springframework.context.annotation.Bean import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration import org.springframework.context.annotation.Configuration
import org.springframework.http.converter.HttpMessageConverter import org.springframework.http.converter.HttpMessageConverter
@ -30,7 +33,8 @@ import org.springframework.web.servlet.mvc.method.annotation.ServletModelAttribu
@Configuration @Configuration
class MvcConfigurer( class MvcConfigurer(
private val jsonOrFormModelMethodProcessor: JsonOrFormModelMethodProcessor, private val jsonOrFormModelMethodProcessor: JsonOrFormModelMethodProcessor,
private val spaInterceptor: SPAInterceptor private val spaInterceptor: SPAInterceptor,
private val applicationRequestLogInterceptor: ApplicationRequestLogInterceptor
) : WebMvcConfigurer { ) : WebMvcConfigurer {
override fun addArgumentResolvers(resolvers: MutableList<HandlerMethodArgumentResolver>) { override fun addArgumentResolvers(resolvers: MutableList<HandlerMethodArgumentResolver>) {
resolvers.add(jsonOrFormModelMethodProcessor) resolvers.add(jsonOrFormModelMethodProcessor)
@ -38,6 +42,16 @@ class MvcConfigurer(
override fun addInterceptors(registry: InterceptorRegistry) { override fun addInterceptors(registry: InterceptorRegistry) {
registry.addInterceptor(spaInterceptor) registry.addInterceptor(spaInterceptor)
registry.addInterceptor(applicationRequestLogInterceptor)
}
@Bean
fun mdcFilter(): FilterRegistrationBean<MDCInsertingServletFilter> {
val bean = FilterRegistrationBean<MDCInsertingServletFilter>()
bean.filter = MDCInsertingServletFilter()
bean.addUrlPatterns("/*")
bean.order = Int.MIN_VALUE
return bean
} }
} }

View File

@ -0,0 +1,70 @@
package dev.usbharu.hideout.core.infrastructure.springframework
import dev.usbharu.hideout.core.infrastructure.springframework.oauth2.HideoutUserDetails
import jakarta.servlet.http.HttpServletRequest
import jakarta.servlet.http.HttpServletResponse
import org.slf4j.LoggerFactory
import org.slf4j.MDC
import org.springframework.security.core.context.SecurityContextHolder
import org.springframework.security.oauth2.jwt.Jwt
import org.springframework.stereotype.Component
import org.springframework.web.servlet.AsyncHandlerInterceptor
import java.util.*
@Component
class ApplicationRequestLogInterceptor : AsyncHandlerInterceptor {
override fun preHandle(request: HttpServletRequest, response: HttpServletResponse, handler: Any): Boolean {
MDC.put(requestId, UUID.randomUUID().toString())
MDC.put(Companion.handler, handler.toString())
val userDetailId = when (val principal = SecurityContextHolder.getContext().authentication?.principal) {
is HideoutUserDetails -> {
principal.userDetailsId
}
is Jwt -> {
principal.getClaim<String>("uid")?.toLongOrNull()
}
else -> {
null
}
}
if (userDetailId != null) {
MDC.put(userId, userDetailId.toString())
}
logger.info("START")
return true
}
override fun afterCompletion(
request: HttpServletRequest,
response: HttpServletResponse,
handler: Any,
ex: Exception?
) {
removeMdc()
}
override fun afterConcurrentHandlingStarted(
request: HttpServletRequest,
response: HttpServletResponse,
handler: Any
) {
removeMdc()
}
private fun removeMdc() {
MDC.remove(requestId)
MDC.remove(userId)
MDC.remove(handler)
}
companion object {
const val requestId: String = "requestId"
const val userId: String = "userId"
const val handler: String = "handler"
private val logger = LoggerFactory.getLogger(ApplicationRequestLogInterceptor::class.java)
}
}

View File

@ -18,7 +18,9 @@ package dev.usbharu.hideout.core.infrastructure.springframework.domainevent
import dev.usbharu.hideout.core.domain.shared.domainevent.DomainEvent import dev.usbharu.hideout.core.domain.shared.domainevent.DomainEvent
import dev.usbharu.hideout.core.domain.shared.domainevent.DomainEventPublisher import dev.usbharu.hideout.core.domain.shared.domainevent.DomainEventPublisher
import dev.usbharu.hideout.core.infrastructure.springframework.ApplicationRequestLogInterceptor
import org.slf4j.LoggerFactory import org.slf4j.LoggerFactory
import org.slf4j.MDC
import org.springframework.context.ApplicationEventPublisher import org.springframework.context.ApplicationEventPublisher
import org.springframework.stereotype.Component import org.springframework.stereotype.Component
@ -27,10 +29,19 @@ class SpringFrameworkDomainEventPublisher(private val applicationEventPublisher:
DomainEventPublisher { DomainEventPublisher {
override suspend fun publishEvent(domainEvent: DomainEvent<*>) { override suspend fun publishEvent(domainEvent: DomainEvent<*>) {
logger.trace("Publish ${domainEvent.id} ${domainEvent.name}") logger.trace("Publish ${domainEvent.id} ${domainEvent.name}")
applicationEventPublisher.publishEvent(domainEvent)
val requestId: String? = MDC.get(ApplicationRequestLogInterceptor.requestId)
val springDomainEvent = SpringDomainEvent(
requestId,
domainEvent
)
applicationEventPublisher.publishEvent(springDomainEvent)
} }
companion object { companion object {
private val logger = LoggerFactory.getLogger(SpringFrameworkDomainEventPublisher::class.java) private val logger = LoggerFactory.getLogger(SpringFrameworkDomainEventPublisher::class.java)
} }
} }
data class SpringDomainEvent(val requestId: String?, val domainEvent: DomainEvent<*>)

View File

@ -2,9 +2,14 @@ package dev.usbharu.hideout.core.infrastructure.springframework.domainevent
import dev.usbharu.hideout.core.application.domainevent.subscribers.DomainEventConsumer import dev.usbharu.hideout.core.application.domainevent.subscribers.DomainEventConsumer
import dev.usbharu.hideout.core.application.domainevent.subscribers.DomainEventSubscriber import dev.usbharu.hideout.core.application.domainevent.subscribers.DomainEventSubscriber
import dev.usbharu.hideout.core.domain.shared.domainevent.DomainEvent
import dev.usbharu.hideout.core.domain.shared.domainevent.DomainEventBody import dev.usbharu.hideout.core.domain.shared.domainevent.DomainEventBody
import dev.usbharu.hideout.core.infrastructure.springframework.ApplicationRequestLogInterceptor.Companion.requestId
import kotlinx.coroutines.async
import kotlinx.coroutines.awaitAll
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.slf4j.MDCContext
import org.slf4j.LoggerFactory import org.slf4j.LoggerFactory
import org.slf4j.MDC
import org.springframework.context.event.EventListener import org.springframework.context.event.EventListener
import org.springframework.stereotype.Component import org.springframework.stereotype.Component
@ -17,16 +22,31 @@ class SpringFrameworkDomainEventSubscriber : DomainEventSubscriber {
map.getOrPut(eventName) { mutableListOf() }.add(domainEventConsumer as DomainEventConsumer<*>) map.getOrPut(eventName) { mutableListOf() }.add(domainEventConsumer as DomainEventConsumer<*>)
} }
override fun getSubscribers(): Map<String, List<DomainEventConsumer<*>>> = map
@EventListener @EventListener
suspend fun onDomainEventPublished(domainEvent: DomainEvent<*>) { suspend fun onDomainEventPublished(domainEvent: SpringDomainEvent) {
logger.trace("Domain Event Published: $domainEvent") logger.debug(
map[domainEvent.name]?.forEach { "Domain Event Published: {} id: {} requestId: {}",
domainEvent.domainEvent.name,
domainEvent.domainEvent.id,
domainEvent.requestId
)
coroutineScope {
map[domainEvent.domainEvent.name]?.map {
async(MDCContext()) {
try { try {
it.invoke(domainEvent) MDC.put(requestId, domainEvent.requestId)
it.invoke(domainEvent.domainEvent)
} catch (@Suppress("TooGenericExceptionCaught") e: Exception) { } catch (@Suppress("TooGenericExceptionCaught") e: Exception) {
logger.error("", e) logger.warn("", e)
null
} finally {
MDC.remove(requestId)
} }
} }
}?.awaitAll()
}
} }
companion object { companion object {

View File

@ -21,6 +21,7 @@ import dev.usbharu.hideout.core.config.ApplicationConfig
import dev.usbharu.hideout.core.domain.model.actor.ActorRepository import dev.usbharu.hideout.core.domain.model.actor.ActorRepository
import dev.usbharu.hideout.core.domain.model.userdetails.UserDetailRepository import dev.usbharu.hideout.core.domain.model.userdetails.UserDetailRepository
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.slf4j.MDCContext
import org.springframework.security.core.userdetails.UserDetails import org.springframework.security.core.userdetails.UserDetails
import org.springframework.security.core.userdetails.UserDetailsService import org.springframework.security.core.userdetails.UserDetailsService
import org.springframework.security.core.userdetails.UsernameNotFoundException import org.springframework.security.core.userdetails.UsernameNotFoundException
@ -33,7 +34,7 @@ class UserDetailsServiceImpl(
private val applicationConfig: ApplicationConfig, private val applicationConfig: ApplicationConfig,
private val transaction: Transaction, private val transaction: Transaction,
) : UserDetailsService { ) : UserDetailsService {
override fun loadUserByUsername(username: String?): UserDetails = runBlocking { override fun loadUserByUsername(username: String?): UserDetails = runBlocking(MDCContext()) {
if (username == null) { if (username == null) {
throw UsernameNotFoundException("Username not found") throw UsernameNotFoundException("Username not found")
} }

View File

@ -12,4 +12,8 @@
<appender-ref ref="ECS_JSON_FILE"/> <appender-ref ref="ECS_JSON_FILE"/>
<appender-ref ref="FILE"/> <appender-ref ref="FILE"/>
</root> </root>
<logger name="javax.management" level="INFO"/>
<logger name="sun.rmi.loader" level="INFO"/>
<logger name="sun.rmi" level="INFO"/>
<logger name="java.io.serialization" level="INFO"/>
</configuration> </configuration>

View File

@ -1,6 +1,6 @@
distributionBase=GRADLE_USER_HOME distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.1-bin.zip distributionUrl=https\://services.gradle.org/distributions/gradle-8.10-bin.zip
networkTimeout=10000 networkTimeout=10000
validateDistributionUrl=true validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME

View File

@ -8,6 +8,4 @@ class MastodonReadTimeline(
val localOnly: Boolean, val localOnly: Boolean,
val remoteOnly: Boolean, val remoteOnly: Boolean,
val page: Page val page: Page
) { )
}

View File

@ -38,7 +38,8 @@ class MastodonReadTimelineApplicationService(
val readTimeline = timelineStore.readTimeline(timeline, readTimelineOption, command.page, principal) val readTimeline = timelineStore.readTimeline(timeline, readTimelineOption, command.page, principal)
return PaginationList(readTimeline.map { return PaginationList(
readTimeline.map {
Status( Status(
it.postId.id.toString(), it.postId.id.toString(),
it.post.url.toString(), it.post.url.toString(),
@ -103,7 +104,10 @@ class MastodonReadTimelineApplicationService(
pinned = false, pinned = false,
filtered = emptyList(), filtered = emptyList(),
) )
}, readTimeline.next?.id, readTimeline.prev?.id) },
readTimeline.next?.id,
readTimeline.prev?.id
)
} }
companion object { companion object {

View File

@ -49,6 +49,7 @@ import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.asFlow import kotlinx.coroutines.flow.asFlow
import kotlinx.coroutines.flow.mapNotNull import kotlinx.coroutines.flow.mapNotNull
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.slf4j.MDCContext
import org.springframework.http.ResponseEntity import org.springframework.http.ResponseEntity
import org.springframework.stereotype.Controller import org.springframework.stereotype.Controller
@ -164,7 +165,7 @@ class SpringAccountApi(
id: List<String>?, id: List<String>?,
withSuspended: Boolean withSuspended: Boolean
): ResponseEntity<Flow<Relationship>> { ): ResponseEntity<Flow<Relationship>> {
val principal = runBlocking { principalContextHolder.getPrincipal() } val principal = runBlocking(MDCContext()) { principalContextHolder.getPrincipal() }
return ResponseEntity.ok(id.orEmpty().asFlow().mapNotNull { fetchRelationship(it, principal).body }) return ResponseEntity.ok(id.orEmpty().asFlow().mapNotNull { fetchRelationship(it, principal).body })
} }

View File

@ -28,6 +28,7 @@ import dev.usbharu.hideout.mastodon.interfaces.api.generated.model.Status
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.asFlow import kotlinx.coroutines.flow.asFlow
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.slf4j.MDCContext
import org.springframework.http.ResponseEntity import org.springframework.http.ResponseEntity
import org.springframework.stereotype.Controller import org.springframework.stereotype.Controller
@ -44,7 +45,7 @@ class SpringTimelineApi(
sinceId: String?, sinceId: String?,
minId: String?, minId: String?,
limit: Int? limit: Int?
): ResponseEntity<Flow<Status>> = runBlocking { ): ResponseEntity<Flow<Status>> = runBlocking(MDCContext()) {
val principal = principalContextHolder.getPrincipal() val principal = principalContextHolder.getPrincipal()
val userDetail = transaction.transaction { val userDetail = transaction.transaction {
userDetailRepository.findByActorId(principal.actorId.id) userDetailRepository.findByActorId(principal.actorId.id)
@ -67,7 +68,8 @@ class SpringTimelineApi(
minId?.toLongOrNull(), minId?.toLongOrNull(),
limit limit
) )
), principal ),
principal
).asFlow() ).asFlow()
) )
} }