diff --git a/buildSrc/src/main/kotlin/DependencyVersion.kt b/buildSrc/src/main/kotlin/DependencyVersion.kt index 5835a09ae..4bfc08d7e 100644 --- a/buildSrc/src/main/kotlin/DependencyVersion.kt +++ b/buildSrc/src/main/kotlin/DependencyVersion.kt @@ -13,7 +13,10 @@ object DependencyVersion { const val SPRING_DEPENDENCY_MANAGEMENT = "1.1.5" /** springModulith */ - const val SPRING_MODULITH = "1.3.0" + const val SPRING_MODULITH = "1.3.1" + + /** jmolecules */ + const val JMOLECULES = "0.24.1" /** jwt */ const val JWT = "0.11.5" diff --git a/domain/crm/build.gradle.kts b/domain/crm/build.gradle.kts index 5d1b52606..1fb76d280 100644 --- a/domain/crm/build.gradle.kts +++ b/domain/crm/build.gradle.kts @@ -14,7 +14,6 @@ dependencies { implementation(project(":library:web")) implementation(project(":library:email")) implementation(project(":library:event")) - testImplementation(testFixtures(project(":library:event"))) /** jsoup - html parser */ implementation("org.jsoup:jsoup:1.15.3") diff --git a/domain/crm/src/main/kotlin/com/few/crm/config/package-info.java b/domain/crm/src/main/kotlin/com/few/crm/config/package-info.java new file mode 100644 index 000000000..b08c62cc8 --- /dev/null +++ b/domain/crm/src/main/kotlin/com/few/crm/config/package-info.java @@ -0,0 +1,6 @@ +@org.springframework.modulith.ApplicationModule( + type = ApplicationModule.Type.OPEN +) +package com.few.crm.config; + +import org.springframework.modulith.ApplicationModule; diff --git a/domain/crm/src/main/kotlin/com/few/crm/email/domain/SentEmail.kt b/domain/crm/src/main/kotlin/com/few/crm/email/domain/SentEmail.kt index 488d97ea4..071930fdf 100644 --- a/domain/crm/src/main/kotlin/com/few/crm/email/domain/SentEmail.kt +++ b/domain/crm/src/main/kotlin/com/few/crm/email/domain/SentEmail.kt @@ -2,7 +2,9 @@ package com.few.crm.email.domain import com.few.crm.email.event.send.EmailSentEvent import event.domain.DomainAbstractAggregateRoot +import org.jmolecules.ddd.annotation.AggregateRoot +@AggregateRoot class SentEmail( private val userExternalId: String, private val emailBody: String, diff --git a/domain/crm/src/main/kotlin/com/few/crm/email/event/schedule/ScheduledEvent.kt b/domain/crm/src/main/kotlin/com/few/crm/email/event/schedule/ScheduledEvent.kt index 665380d4c..0b1323b65 100644 --- a/domain/crm/src/main/kotlin/com/few/crm/email/event/schedule/ScheduledEvent.kt +++ b/domain/crm/src/main/kotlin/com/few/crm/email/event/schedule/ScheduledEvent.kt @@ -15,7 +15,7 @@ abstract class ScheduledEvent( eventTime, ) -@EventDetails(publishedLocations = ["com.few.crm.support.schedule.TimeOutEventTaskManager"]) +@EventDetails class CancelScheduledEvent( val targetEventId: String, eventId: String = EventUtils.generateEventId(), diff --git a/domain/crm/src/main/kotlin/com/few/crm/email/event/send/EmailSendEvent.kt b/domain/crm/src/main/kotlin/com/few/crm/email/event/send/EmailSendEvent.kt index 0dbac7364..b023576d6 100644 --- a/domain/crm/src/main/kotlin/com/few/crm/email/event/send/EmailSendEvent.kt +++ b/domain/crm/src/main/kotlin/com/few/crm/email/event/send/EmailSendEvent.kt @@ -42,10 +42,6 @@ abstract class EmailSendEvent( @EventDetails( outBox = false, - publishedLocations = [ - "com.few.crm.email.domain.SentEmail", - "com.few.crm.email.event.send.handler.NotificationEmailSendTimeOutInvokeEventHandler", - ], ) class EmailSentEvent( val userExternalId: String, @@ -65,7 +61,7 @@ class EmailSentEvent( timestamp = timestamp, ) -@EventDetails(outBox = false, publishedLocations = ["com.few.crm.email.relay.send.EmailSendEventMessageMapper"]) +@EventDetails(outBox = false) class EmailDeliveryEvent( eventId: String, eventType: String, @@ -82,7 +78,7 @@ class EmailDeliveryEvent( timestamp = timestamp, ) -@EventDetails(outBox = false, publishedLocations = ["com.few.crm.email.relay.send.EmailSendEventMessageMapper"]) +@EventDetails(outBox = false) class EmailOpenEvent( eventId: String, eventType: String, @@ -99,7 +95,7 @@ class EmailOpenEvent( timestamp = timestamp, ) -@EventDetails(outBox = false, publishedLocations = ["com.few.crm.email.relay.send.EmailSendEventMessageMapper"]) +@EventDetails(outBox = false) class EmailClickEvent( eventId: String, eventType: String, @@ -116,7 +112,7 @@ class EmailClickEvent( timestamp = timestamp, ) -@EventDetails(outBox = false, publishedLocations = ["com.few.crm.email.relay.send.EmailSendEventMessageMapper"]) +@EventDetails(outBox = false) class EmailDeliveryDelayEvent( eventId: String, eventType: String, diff --git a/domain/crm/src/main/kotlin/com/few/crm/email/event/send/NotificationEmailSendTimeOutEvent.kt b/domain/crm/src/main/kotlin/com/few/crm/email/event/send/NotificationEmailSendTimeOutEvent.kt index 832bff9ee..5a3e4cf05 100644 --- a/domain/crm/src/main/kotlin/com/few/crm/email/event/send/NotificationEmailSendTimeOutEvent.kt +++ b/domain/crm/src/main/kotlin/com/few/crm/email/event/send/NotificationEmailSendTimeOutEvent.kt @@ -7,12 +7,7 @@ import event.TimeOutEvent import org.springframework.context.ApplicationEventPublisher import java.time.LocalDateTime -@EventDetails( - publishedLocations = [ - "com.few.crm.email.event.send.replayer.NotificationEmailSendTimeOutEventReplayer", - "com.few.crm.view.email.CrmEmailSendView", - ], -) +@EventDetails open class NotificationEmailSendTimeOutEvent( val templateId: Long, val userIds: List, @@ -73,12 +68,7 @@ open class NotificationEmailSendTimeOutEvent( ) } -@EventDetails( - publishedLocations = [ - "com.few.crm.email.event.send.NotificationEmailSendTimeOutInvokeEvent", - "com.few.crm.email.relay.send.aws.ScheduledEventReverseRelay", - ], -) +@EventDetails() class NotificationEmailSendTimeOutInvokeEvent( val templateId: Long, val userIds: List, @@ -93,7 +83,7 @@ class NotificationEmailSendTimeOutInvokeEvent( eventTime, ) -@EventDetails(publishedLocations = ["com.few.crm.email.event.send.NotificationEmailSendTimeOutEvent"]) +@EventDetails() class AwsNotificationEmailSendTimeOutEvent( templateId: Long, userIds: List, diff --git a/domain/crm/src/main/kotlin/com/few/crm/email/event/template/EmailTemplateTransactionEvent.kt b/domain/crm/src/main/kotlin/com/few/crm/email/event/template/EmailTemplateTransactionEvent.kt index 539c3aada..846f56f45 100644 --- a/domain/crm/src/main/kotlin/com/few/crm/email/event/template/EmailTemplateTransactionEvent.kt +++ b/domain/crm/src/main/kotlin/com/few/crm/email/event/template/EmailTemplateTransactionEvent.kt @@ -25,7 +25,7 @@ abstract class EmailTemplateTransactionAfterCompletionEvent( eventTime = eventTime, ) -@EventDetails(publishedLocations = ["com.few.crm.email.domain.EmailTemplate"]) +@EventDetails class PostEmailTemplateEvent( val templateId: Long, eventId: String = EventUtils.generateEventId(), diff --git a/domain/crm/src/main/kotlin/com/few/crm/email/package-info.java b/domain/crm/src/main/kotlin/com/few/crm/email/package-info.java new file mode 100644 index 000000000..807b2f088 --- /dev/null +++ b/domain/crm/src/main/kotlin/com/few/crm/email/package-info.java @@ -0,0 +1,6 @@ +@org.springframework.modulith.ApplicationModule( + type = ApplicationModule.Type.OPEN +) +package com.few.crm.email; + +import org.springframework.modulith.ApplicationModule; diff --git a/domain/crm/src/main/kotlin/com/few/crm/support/package-info.java b/domain/crm/src/main/kotlin/com/few/crm/support/package-info.java new file mode 100644 index 000000000..c759f2b0d --- /dev/null +++ b/domain/crm/src/main/kotlin/com/few/crm/support/package-info.java @@ -0,0 +1,6 @@ +@org.springframework.modulith.ApplicationModule( + type = ApplicationModule.Type.OPEN +) +package com.few.crm.support; + +import org.springframework.modulith.ApplicationModule; diff --git a/domain/crm/src/main/kotlin/com/few/crm/user/package-info.java b/domain/crm/src/main/kotlin/com/few/crm/user/package-info.java new file mode 100644 index 000000000..79fc89f1c --- /dev/null +++ b/domain/crm/src/main/kotlin/com/few/crm/user/package-info.java @@ -0,0 +1,6 @@ +@org.springframework.modulith.ApplicationModule( + type = ApplicationModule.Type.OPEN +) +package com.few.crm.user; + +import org.springframework.modulith.ApplicationModule; diff --git a/domain/crm/src/main/kotlin/com/few/crm/view/package-info.java b/domain/crm/src/main/kotlin/com/few/crm/view/package-info.java new file mode 100644 index 000000000..d9a45d719 --- /dev/null +++ b/domain/crm/src/main/kotlin/com/few/crm/view/package-info.java @@ -0,0 +1,6 @@ +@org.springframework.modulith.ApplicationModule( + type = ApplicationModule.Type.OPEN +) +package com.few.crm.view; + +import org.springframework.modulith.ApplicationModule; diff --git a/domain/crm/src/test/kotlin/com/few/crm/document/CrmDocument.kt b/domain/crm/src/test/kotlin/com/few/crm/document/CrmDocument.kt index 7120d8b59..a6f6a13a2 100644 --- a/domain/crm/src/test/kotlin/com/few/crm/document/CrmDocument.kt +++ b/domain/crm/src/test/kotlin/com/few/crm/document/CrmDocument.kt @@ -1,10 +1,8 @@ package com.few.crm.document +import com.tngtech.archunit.base.DescribedPredicate import com.tngtech.archunit.core.domain.JavaClass -import com.tngtech.archunit.core.domain.JavaModifier -import com.tngtech.archunit.core.importer.ClassFileImporter import event.Event -import event.EventDetails import io.qameta.allure.Allure import io.qameta.allure.Epic import io.qameta.allure.Feature @@ -22,21 +20,24 @@ class CrmDocument { @Nested inner class DependencyDiagram { @Story("CRM 모듈 의존성 다이어그램") - @Link("https://thetimetube.herokuapp.com/asciidoc/") @Link("https://www.planttext.com/") @Test fun `create dependency diagram`() { - val modules = ApplicationModules.of("com.few.crm") + val modules = + ApplicationModules.of( + "com.few.crm", + DescribedPredicate.describe( + "ignore event classes", + JavaClass.Predicates.assignableTo(Event::class.java), + ), + ) Documenter(modules) - .writeDocumentation() .writeIndividualModulesAsPlantUml() modules .filterNot { it.name == "config" } .forEach { - val adocFile = File("build/spring-modulith-docs/module-${it.name}.adoc") val pumlFile = File("build/spring-modulith-docs/module-${it.name}.puml") - Allure.addAttachment("${it.name} Module Adoc", "text/plain", adocFile.readText()) Allure.addAttachment("${it.name} Module Puml", "text/plain", pumlFile.readText()) } } @@ -46,85 +47,26 @@ class CrmDocument { inner class EventDocument { @Story("CRM 이벤트 발행 문서") @Link("https://thetimetube.herokuapp.com/asciidoc/") - @Link("https://www.planttext.com/") @Test fun `create event document`() { - val classes = ClassFileImporter().importPackages("com.few.crm") - val eventClasses = - classes - .stream() - .filter { it.isAssignableTo(Event::class.java) } - .filter { it.isAnonymousClass.not() } - .filter { it.isInnerClass.not() } - .filter { it.isLocalClass.not() } - .filter { it.modifiers.contains(JavaModifier.ABSTRACT).not() } - .toList() - - val notQualifiedEventClasses = mutableListOf() - val logBuilder = StringBuilder() - - eventClasses.forEach { event -> - if (event.isAnnotatedWith(EventDetails::class.java)) { - val eventDetails = event.getAnnotationOfType(EventDetails::class.java) - val publishedLocations = eventDetails.publishedLocations - if (publishedLocations.isEmpty()) { - notQualifiedEventClasses.add(event) - } else { - publishedLocations - .filter { it != (event.packageName + "." + event.simpleName) } - .forEach { publishedLocation -> - event.directDependenciesToSelf - .find { - it.originClass.fullName == publishedLocation - }?.let { - logBuilder.appendLine("* ${it.originClass.fullName}") - } ?: run { - notQualifiedEventClasses.add(event) - } - } - } - } else { - notQualifiedEventClasses.add(event) - } - } - - if (notQualifiedEventClasses.isNotEmpty()) { - logBuilder.appendLine("\n== Not Qualified Event Classes") - logBuilder.appendLine("_The following event classes are not annotated with @EventDetails:_") - notQualifiedEventClasses.forEach { - logBuilder.appendLine("* ${it.fullName}") - } - throw IllegalStateException(logBuilder.toString()) - } - - val outputFile = File("build/event-docs/event-published-document.adoc") - if (outputFile.exists()) { - outputFile.delete() - } - outputFile.parentFile.mkdirs() - val adocContent = StringBuilder() - - adocContent.appendLine("[%autowidth.stretch, cols=\"h,a\"]") - adocContent.appendLine("|===") - adocContent.appendLine("|Event Class | Published Locations") - - eventClasses.forEach { event -> - val eventDetails = event.getAnnotationOfType(EventDetails::class.java) - val publishedLocations = eventDetails.publishedLocations - - adocContent.appendLine("|`${event.simpleName}`") - adocContent.appendLine("|") - adocContent.appendLine( - publishedLocations.joinToString("\n") { "* `$it`" }, + val modules = + ApplicationModules.of( + "com.few.crm", + ) + Documenter(modules) + .writeModuleCanvases( + Documenter.CanvasOptions + .defaults() + .revealInternals() + .revealEmptyLines(), ) - } - - adocContent.appendLine("|===") - - outputFile.writeText(adocContent.toString()) - Allure.addAttachment("Event Document", "text/plain", adocContent.toString()) - println("Event document generated at: ${outputFile.absolutePath}") + modules + .filterNot { it.name == "config" } + .forEach { + val adocFile = File("build/spring-modulith-docs/module-${it.name}.adoc") + Allure.addAttachment("${it.name} Module Adoc", "text/plain", adocFile.readText()) + } } } } \ No newline at end of file diff --git a/library/event/build.gradle.kts b/library/event/build.gradle.kts index 5109f2fd4..893e21d63 100644 --- a/library/event/build.gradle.kts +++ b/library/event/build.gradle.kts @@ -6,11 +6,7 @@ tasks.getByName("jar") { enabled = true } -plugins { - /** test fixtures */ - id("java-test-fixtures") -} - dependencies { implementation("org.springframework.boot:spring-boot-starter-json") + api("org.jmolecules.integrations:jmolecules-starter-ddd:${DependencyVersion.JMOLECULES}") } \ No newline at end of file diff --git a/library/event/src/main/kotlin/event/EventDetails.kt b/library/event/src/main/kotlin/event/EventDetails.kt index 16bd46b94..7310cab1e 100644 --- a/library/event/src/main/kotlin/event/EventDetails.kt +++ b/library/event/src/main/kotlin/event/EventDetails.kt @@ -1,5 +1,7 @@ package event +import org.jmolecules.event.annotation.DomainEvent + /** * Event details * @@ -9,7 +11,7 @@ package event */ @Target(AnnotationTarget.CLASS) @Retention(AnnotationRetention.RUNTIME) +@DomainEvent annotation class EventDetails( val outBox: Boolean = false, - val publishedLocations: Array = [], ) \ No newline at end of file diff --git a/library/event/src/main/kotlin/event/domain/DomainEventPublishingMethod.kt b/library/event/src/main/kotlin/event/domain/DomainEventPublishingMethod.kt index 1e5902892..042bb32c3 100644 --- a/library/event/src/main/kotlin/event/domain/DomainEventPublishingMethod.kt +++ b/library/event/src/main/kotlin/event/domain/DomainEventPublishingMethod.kt @@ -2,6 +2,7 @@ package event.domain import event.domain.DomainEventPublishingMethod.Companion.NONE import event.domain.util.AnnotationDetectionMethodCallback +import org.jmolecules.ddd.annotation.AggregateRoot import org.springframework.context.ApplicationEventPublisher import org.springframework.lang.Nullable import org.springframework.util.ReflectionUtils @@ -21,6 +22,10 @@ class DomainEventPublishingMethod( throw IllegalArgumentException("Type must extend DomainAbstractAggregateRoot: $type") } + if (!type.isAnnotationPresent(AggregateRoot::class.java)) { + throw IllegalArgumentException("Type must be annotated with @AggregateRoot: $type") + } + val result = from( type, diff --git a/library/event/src/testFixtures/kotlin/event/fixtures/TestEvent.kt b/library/event/src/test/kotlin/event/fixtures/TestEvent.kt similarity index 100% rename from library/event/src/testFixtures/kotlin/event/fixtures/TestEvent.kt rename to library/event/src/test/kotlin/event/fixtures/TestEvent.kt diff --git a/library/event/src/testFixtures/kotlin/event/fixtures/TestLocalMessageRelay.kt b/library/event/src/test/kotlin/event/fixtures/TestLocalMessageRelay.kt similarity index 100% rename from library/event/src/testFixtures/kotlin/event/fixtures/TestLocalMessageRelay.kt rename to library/event/src/test/kotlin/event/fixtures/TestLocalMessageRelay.kt diff --git a/library/event/src/testFixtures/kotlin/event/fixtures/TestLocalMessageReverseRelay.kt b/library/event/src/test/kotlin/event/fixtures/TestLocalMessageReverseRelay.kt similarity index 100% rename from library/event/src/testFixtures/kotlin/event/fixtures/TestLocalMessageReverseRelay.kt rename to library/event/src/test/kotlin/event/fixtures/TestLocalMessageReverseRelay.kt diff --git a/library/event/src/testFixtures/kotlin/event/fixtures/TestLocalMessageSender.kt b/library/event/src/test/kotlin/event/fixtures/TestLocalMessageSender.kt similarity index 100% rename from library/event/src/testFixtures/kotlin/event/fixtures/TestLocalMessageSender.kt rename to library/event/src/test/kotlin/event/fixtures/TestLocalMessageSender.kt diff --git a/library/event/src/testFixtures/kotlin/event/fixtures/TestMessage.kt b/library/event/src/test/kotlin/event/fixtures/TestMessage.kt similarity index 100% rename from library/event/src/testFixtures/kotlin/event/fixtures/TestMessage.kt rename to library/event/src/test/kotlin/event/fixtures/TestMessage.kt diff --git a/library/event/src/testFixtures/kotlin/event/fixtures/TestReverseMessage.kt b/library/event/src/test/kotlin/event/fixtures/TestReverseMessage.kt similarity index 100% rename from library/event/src/testFixtures/kotlin/event/fixtures/TestReverseMessage.kt rename to library/event/src/test/kotlin/event/fixtures/TestReverseMessage.kt diff --git a/library/event/src/testFixtures/kotlin/event/fixtures/TestTimeOutEvent.kt b/library/event/src/test/kotlin/event/fixtures/TestTimeOutEvent.kt similarity index 100% rename from library/event/src/testFixtures/kotlin/event/fixtures/TestTimeOutEvent.kt rename to library/event/src/test/kotlin/event/fixtures/TestTimeOutEvent.kt diff --git a/library/event/src/testFixtures/kotlin/event/EventRule.kt b/library/event/src/testFixtures/kotlin/event/EventRule.kt deleted file mode 100644 index 0e222ffc4..000000000 --- a/library/event/src/testFixtures/kotlin/event/EventRule.kt +++ /dev/null @@ -1,222 +0,0 @@ -package event - -import com.tngtech.archunit.base.DescribedPredicate -import com.tngtech.archunit.core.domain.JavaClass -import com.tngtech.archunit.core.domain.JavaModifier -import com.tngtech.archunit.core.importer.ClassFileImporter -import com.tngtech.archunit.lang.syntax.ArchRuleDefinition.classes -import com.tngtech.archunit.lang.syntax.ArchRuleDefinition.fields -import com.tngtech.archunit.lang.syntax.ArchRuleDefinition.methods -import com.tngtech.archunit.lang.syntax.elements.* -import event.message.Message -import event.message.MessageRelay -import event.message.MessageReverseRelay -import event.message.MessageSender -import event.message.local.LocalSubscribeMessage -import org.springframework.context.event.EventListener - -fun ClassesThat.areEvent(): GivenClassesConjunction = this.areAssignableTo(Event::class.java) - -fun ClassesThat.areNotAbstractClasses(): GivenClassesConjunction = this.doNotHaveModifier(JavaModifier.ABSTRACT) - -/** - * Event rule - * - * 이벤트 모듈의 규칙을 정의 하고 검증 할 수 있도록 지원 한다. - * - * 해당 모듈을 사용하는 프로젝트에서 EventRule을 상속 받아서 이벤트 관련 규칙을 검증 한다. - * - * `검증 예시`: - * ```kotlin - * class ApiEventRule : EventRule() { - * companion object { - * var classes: JavaClasses? = null - * - * @JvmStatic - * @BeforeAll - * fun setUp() { - * classes = ClassFileImporter().importPackages("com.few", "event") - * for (clazz in classes!!) { - * println(clazz.name) - * } - * } - * } - * - * @Test - * fun `TimeOutEvent 클래스와 TimeExpiredEvent는 @EventDetails의 outBox 속성이 false여야 한다`() { - * // 패키지를 지정하여 검증 - * assertDoesNotThrow { - * `규칙 - TimeOutEvent 클래스와 TimeExpiredEvent는 @EventDetails의 outBox 속성이 false여야 한다`("com.few", "event") - * } - * } - * - * @Test - * fun `MessageRelay는 MessageSender를 가지고 있어야 한다`() { - * // check 메서드를 사용하여 검증 - * `규칙 - MessageRelay는 MessageSender를 가지고 있어야 한다`().check(classes) - * } - * } - * - */ -@Suppress("ktlint:standard:function-naming", "ktlint:standard:max-line-length") -abstract class EventRule { - /** - * 이벤트 클래스는 @event details 어노테이션이 붙어있어야 한다 - */ - fun `규칙 - 이벤트 클래스는 @EventDetails 어노테이션이 붙어있어야 한다`(): ClassesShouldConjunction = - classes() - .that() - .areEvent() - .and() - .areNotAbstractClasses() - .should() - .beAnnotatedWith(EventDetails::class.java) - - /** - * time out event 클래스를 상속하면 time expired event도 상속해야 한다 - * - * @param packages 패키지 목록 - */ - fun `규칙 - TimeOutEvent 클래스를 상속하면 TimeExpiredEvent도 상속해야 한다`(vararg packages: String) { - val timeOutEvents = - ClassFileImporter() - .withImportOption { clazz -> - clazz.contains("TimeOutEvent") - }.importPackages(*packages) - - val timeExpiredEvents = - ClassFileImporter() - .withImportOption { clazz -> - clazz.contains("TimeExpiredEvent") - }.importPackages(*packages) - - if (timeOutEvents.size != timeExpiredEvents.size) { - throw AssertionError("TimeOutEvent 클래스의 수와 TimeExpiredEvent 클래스의 수가 다릅니다.") - } - } - - /** - * TimeOutEvent 클래스와 TimeExpiredEvent는 @EventDetails의 outBox 속성이 false여야 한다 - * - * @param packages 패키지 목록 - */ - fun `규칙 - TimeOutEvent 클래스와 TimeExpiredEvent는 @EventDetails의 outBox 속성이 false여야 한다`(vararg packages: String) { - val timeOutEvents = - ClassFileImporter() - .withImportOption { clazz -> - clazz.contains("TimeOutEvent") - }.importPackages(*packages) - - val timeExpiredEvents = - ClassFileImporter() - .withImportOption { clazz -> - clazz.contains("TimeExpiredEvent") - }.importPackages(*packages) - - (timeOutEvents + timeExpiredEvents) - .filterNot { - it.name.contains("$") - }.filterNot { - it.name.split(".").last() == "TimeOutEvent" || it.name.split(".").last() == ("TimeExpiredEvent") - }.forEach { event -> - if (!event.isAnnotatedWith(EventDetails::class.java)) { - throw AssertionError("EventDetails 어노테이션이 없습니다.") - } - - if (event.getAnnotationOfType(EventDetails::class.java).outBox) { - throw AssertionError("outBox 속성이 true입니다.") - } - } - } - - /** - * MessageRelay는 MessageSender를 가지고 있어야 한다 - */ - fun `규칙 - MessageRelay는 MessageSender를 가지고 있어야 한다`(): FieldsShouldConjunction { - val isSubclassOfMessageSender = - object : DescribedPredicate("is a subclass of MessageSender") { - override fun test(t: JavaClass?): Boolean = t?.isAssignableTo(MessageSender::class.java) ?: false - } - - return fields() - .that() - .areDeclaredInClassesThat() - .areAssignableTo(MessageRelay::class.java) - .should() - .haveRawType(isSubclassOfMessageSender) - } - - /** - * local message reverse relay는 on application event를 가지고 있어야 한다 - * - * @param packages 패키지 목록 - */ - fun `규칙 - Local MessageReverseRelay는 onApplicationEvent를 가지고 있어야 한다`(vararg packages: String): Unit = - ClassFileImporter() - .withImportOption { clazz -> - clazz.contains("Local") && clazz.contains("MessageReverseRelay") && !clazz.contains("$") - }.importPackages(*packages) - .groupBy { it } - .mapValues { (_, clazz) -> - clazz.first().methods.map { method -> method.name } - }.forEach { - if (!it.value.contains("onApplicationEvent")) { - throw AssertionError("onApplicationEvent 메서드가 없습니다.") - } - } - - /** - * message reverse relay는 @event listener이 붙어 있는 on application event를 가지고 있어야 한다 - */ - fun `규칙 - MessageReverseRelay는 @EventListener이 붙어 있는 onApplicationEvent를 가지고 있어야 한다`(): MethodsShouldConjunction = - methods() - .that() - .haveName("onApplicationEvent") - .and() - .areDeclaredInClassesThat() - .areAssignableTo(MessageReverseRelay::class.java) - .should() - .beAnnotatedWith(EventListener::class.java) - - /** - * message reverse relay는 @event listener이 붙어 있는 on application event는 message 클래스를 첫 번째 파라미터로 가지고 있어야 한다 - */ - fun `규칙 - MessageReverseRelay는 @EventListener이 붙어 있는 onApplicationEvent는 Message 클래스를 첫 번째 파라미터로 가지고 있어야 한다`(): MethodsShouldConjunction { - val isSubclassOfMessage = - object : DescribedPredicate>("is a subclass of Message") { - override fun test(t: List?): Boolean = t?.first()?.isAssignableTo(Message::class.java) ?: false - } - return methods() - .that() - .haveName("onApplicationEvent") - .and() - .areDeclaredInClassesThat() - .areAssignableTo(MessageReverseRelay::class.java) - .should() - .beAnnotatedWith(EventListener::class.java) - .andShould() - .haveRawParameterTypes(isSubclassOfMessage) - } - - /** - * local message reverse relay의 on application event 파라미터는 @local subscribe message 어노테이션에서 설정한 토픽의 이름을 포함해야 한다 - * - * @param packages 패키지 목록 - */ - fun `규칙 - Local MessageReverseRelay의 onApplicationEvent 파라미터는 @LocalSubscribeMessage 어노테이션에서 설정한 토픽의 이름을 포함해야 한다`( - vararg packages: String, - ): Unit = - ClassFileImporter() - .withImportOption { clazz -> - clazz.contains("Local") && clazz.contains("MessageReverseRelay") && !clazz.contains("$") - }.importPackages(*packages) - .filter { it.methods.any { method -> method.name == "onApplicationEvent" } } - .forEach { - val method = it.methods.first { method -> method.name == "onApplicationEvent" } - val topic = method.getAnnotationOfType(LocalSubscribeMessage::class.java).topic - val message = method.parameterTypes[0] - if (!message.name.contains(topic, ignoreCase = true)) { - throw AssertionError("onApplicationEvent 메서드의 이름에 토픽이 포함되어 있지 않습니다.") - } - } -} \ No newline at end of file