From 112df79d06e9b0472a2857d5c592a85ebb9c5561 Mon Sep 17 00:00:00 2001 From: junyong Date: Fri, 24 Jul 2026 18:57:26 +0900 Subject: [PATCH 1/9] =?UTF-8?q?feat[SAIO-202]:=20=EC=86=8C=EC=85=9C=20?= =?UTF-8?q?=EA=B3=84=EC=A0=95=20=ED=83=88=ED=87=B4=20-=20=EC=86=8C?= =?UTF-8?q?=EC=85=9C=20=EB=8D=B0=EC=9D=B4=ED=84=B0=20=EC=82=AD=EC=A0=9C=20?= =?UTF-8?q?=EC=B2=98=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../server/domain/member/WithdrawalLog.kt | 5 ++++- .../member/port/out/SocialAccountOutPort.kt | 3 +++ .../member/service/MemberProfileService.kt | 4 ++++ .../member/SocialAccountJpaRepository.kt | 1 + .../member/SocialAccountPersistenceAdapter.kt | 6 ++++++ .../member/WithdrawalLogPersistenceAdapter.kt | 1 + .../member/entity/WithdrawalLogEntity.kt | 10 ++++++++++ ..._add_social_provider_to_withdrawal_log.sql | 13 ++++++++++++ .../server/domain/member/WithdrawalLogTest.kt | 4 ++-- .../member/service/MemberAuthServiceTest.kt | 20 +++++++++++++------ .../service/MemberProfileServiceTest.kt | 18 +++++++++++++++++ .../domain/member/service/MemberSeedTest.kt | 10 +++++++--- 12 files changed, 83 insertions(+), 12 deletions(-) create mode 100644 src/main/resources/db/migration/V1.0.3.8__add_social_provider_to_withdrawal_log.sql diff --git a/src/main/kotlin/com/unicorn/server/domain/member/WithdrawalLog.kt b/src/main/kotlin/com/unicorn/server/domain/member/WithdrawalLog.kt index 54bf45d..50b20bd 100644 --- a/src/main/kotlin/com/unicorn/server/domain/member/WithdrawalLog.kt +++ b/src/main/kotlin/com/unicorn/server/domain/member/WithdrawalLog.kt @@ -1,11 +1,13 @@ package com.unicorn.server.domain.member +import com.unicorn.server.domain.member.enums.SocialProvider import com.unicorn.server.domain.member.vo.MemberId import java.time.LocalDateTime class WithdrawalLog private constructor( val memberId: MemberId, val originalEmail: String?, + val socialProvider: SocialProvider?, val reason: String, val withdrawnAt: LocalDateTime, ) { @@ -13,12 +15,13 @@ class WithdrawalLog private constructor( fun create( memberId: MemberId, originalEmail: String?, + socialProvider: SocialProvider?, reason: String, withdrawnAt: LocalDateTime, ): WithdrawalLog { require(reason.isNotBlank()) { "Reason cannot be blank" } require(reason.length <= 500) { "Reason cannot exceed 500 characters" } - return WithdrawalLog(memberId, originalEmail, reason, withdrawnAt) + return WithdrawalLog(memberId, originalEmail, socialProvider, reason, withdrawnAt) } } } diff --git a/src/main/kotlin/com/unicorn/server/domain/member/port/out/SocialAccountOutPort.kt b/src/main/kotlin/com/unicorn/server/domain/member/port/out/SocialAccountOutPort.kt index db4fcaf..a8b306a 100644 --- a/src/main/kotlin/com/unicorn/server/domain/member/port/out/SocialAccountOutPort.kt +++ b/src/main/kotlin/com/unicorn/server/domain/member/port/out/SocialAccountOutPort.kt @@ -14,4 +14,7 @@ interface SocialAccountOutPort { // 멤버 식별자로 연결된 소셜 계정을 조회한다. fun findByMemberId(memberId: MemberId): SocialAccount? + + // 멤버 탈퇴 시 연결된 소셜 계정을 삭제한다. + fun deleteByMemberId(memberId: MemberId) } diff --git a/src/main/kotlin/com/unicorn/server/domain/member/service/MemberProfileService.kt b/src/main/kotlin/com/unicorn/server/domain/member/service/MemberProfileService.kt index ba54e97..f37bd8b 100644 --- a/src/main/kotlin/com/unicorn/server/domain/member/service/MemberProfileService.kt +++ b/src/main/kotlin/com/unicorn/server/domain/member/service/MemberProfileService.kt @@ -126,11 +126,15 @@ class MemberProfileService( WithdrawalLog.create( memberId = savedMember.id, originalEmail = originalEmail?.value, + socialProvider = socialAccountOutPort.findByMemberId(savedMember.id)?.provider, reason = reason, withdrawnAt = requireNotNull(savedMember.deletedAt) { "deletedAt must not be null after withdrawal" }, ), ) + // 재가입 시 새 소셜 연결을 생성할 수 있도록 기존 연결을 제거한다. + socialAccountOutPort.deleteByMemberId(savedMember.id) + // 탈퇴 이벤트 발행 eventPublisher.publish(MemberWithdrawnEvent(savedMember.id.toString())) } diff --git a/src/main/kotlin/com/unicorn/server/infrastructure/adapter/out/persistence/member/SocialAccountJpaRepository.kt b/src/main/kotlin/com/unicorn/server/infrastructure/adapter/out/persistence/member/SocialAccountJpaRepository.kt index 4a2db1b..243288a 100644 --- a/src/main/kotlin/com/unicorn/server/infrastructure/adapter/out/persistence/member/SocialAccountJpaRepository.kt +++ b/src/main/kotlin/com/unicorn/server/infrastructure/adapter/out/persistence/member/SocialAccountJpaRepository.kt @@ -8,4 +8,5 @@ import org.springframework.data.jpa.repository.JpaRepository interface SocialAccountJpaRepository : JpaRepository { fun findByProviderAndProviderId(provider: SocialProvider, providerId: String): SocialAccountEntity? fun findByMemberId(memberId: String): SocialAccountEntity? + fun deleteByMemberId(memberId: String) } diff --git a/src/main/kotlin/com/unicorn/server/infrastructure/adapter/out/persistence/member/SocialAccountPersistenceAdapter.kt b/src/main/kotlin/com/unicorn/server/infrastructure/adapter/out/persistence/member/SocialAccountPersistenceAdapter.kt index f8fbdea..0cdd843 100644 --- a/src/main/kotlin/com/unicorn/server/infrastructure/adapter/out/persistence/member/SocialAccountPersistenceAdapter.kt +++ b/src/main/kotlin/com/unicorn/server/infrastructure/adapter/out/persistence/member/SocialAccountPersistenceAdapter.kt @@ -35,4 +35,10 @@ class SocialAccountPersistenceAdapter( @Transactional(readOnly = true) override fun findByMemberId(memberId: MemberId): SocialAccount? = socialAccountJpaRepository.findByMemberId(memberId.toString())?.toDomain() + + // 멤버 탈퇴 시 기존 소셜 계정 연결을 삭제한다. + @Transactional + override fun deleteByMemberId(memberId: MemberId) { + socialAccountJpaRepository.deleteByMemberId(memberId.toString()) + } } diff --git a/src/main/kotlin/com/unicorn/server/infrastructure/adapter/out/persistence/member/WithdrawalLogPersistenceAdapter.kt b/src/main/kotlin/com/unicorn/server/infrastructure/adapter/out/persistence/member/WithdrawalLogPersistenceAdapter.kt index 454bda0..bd54f9a 100644 --- a/src/main/kotlin/com/unicorn/server/infrastructure/adapter/out/persistence/member/WithdrawalLogPersistenceAdapter.kt +++ b/src/main/kotlin/com/unicorn/server/infrastructure/adapter/out/persistence/member/WithdrawalLogPersistenceAdapter.kt @@ -16,6 +16,7 @@ class WithdrawalLogPersistenceAdapter( val entity = WithdrawalLogEntity( memberId = log.memberId.toString(), originalEmail = log.originalEmail, + socialProvider = log.socialProvider, reason = log.reason, withdrawnAt = log.withdrawnAt, ) diff --git a/src/main/kotlin/com/unicorn/server/infrastructure/adapter/out/persistence/member/entity/WithdrawalLogEntity.kt b/src/main/kotlin/com/unicorn/server/infrastructure/adapter/out/persistence/member/entity/WithdrawalLogEntity.kt index c9d4718..f54ac33 100644 --- a/src/main/kotlin/com/unicorn/server/infrastructure/adapter/out/persistence/member/entity/WithdrawalLogEntity.kt +++ b/src/main/kotlin/com/unicorn/server/infrastructure/adapter/out/persistence/member/entity/WithdrawalLogEntity.kt @@ -1,7 +1,10 @@ package com.unicorn.server.infrastructure.adapter.out.persistence.member.entity +import com.unicorn.server.domain.member.enums.SocialProvider import jakarta.persistence.Column import jakarta.persistence.Entity +import jakarta.persistence.EnumType +import jakarta.persistence.Enumerated import jakarta.persistence.GeneratedValue import jakarta.persistence.GenerationType import jakarta.persistence.Id @@ -30,6 +33,11 @@ class WithdrawalLogEntity protected constructor() { var originalEmail: String? = null internal set + @Enumerated(EnumType.STRING) + @Column(name = "social_provider", nullable = true, length = 20) + var socialProvider: SocialProvider? = null + internal set + @Column(name = "reason", nullable = false, length = 500) var reason: String = "" internal set @@ -45,11 +53,13 @@ class WithdrawalLogEntity protected constructor() { constructor( memberId: String, originalEmail: String?, + socialProvider: SocialProvider?, reason: String, withdrawnAt: LocalDateTime, ) : this() { this.memberId = memberId this.originalEmail = originalEmail + this.socialProvider = socialProvider this.reason = reason this.withdrawnAt = withdrawnAt } diff --git a/src/main/resources/db/migration/V1.0.3.8__add_social_provider_to_withdrawal_log.sql b/src/main/resources/db/migration/V1.0.3.8__add_social_provider_to_withdrawal_log.sql new file mode 100644 index 0000000..d4ea4ba --- /dev/null +++ b/src/main/resources/db/migration/V1.0.3.8__add_social_provider_to_withdrawal_log.sql @@ -0,0 +1,13 @@ +alter table withdrawal_log add column social_provider varchar(20); + +update withdrawal_log +set social_provider = ( + select social_account.provider + from social_account + where social_account.member_id = withdrawal_log.member_id +) +where exists ( + select 1 + from social_account + where social_account.member_id = withdrawal_log.member_id +); diff --git a/src/test/kotlin/com/unicorn/server/domain/member/WithdrawalLogTest.kt b/src/test/kotlin/com/unicorn/server/domain/member/WithdrawalLogTest.kt index 79ffbf8..7139ea9 100644 --- a/src/test/kotlin/com/unicorn/server/domain/member/WithdrawalLogTest.kt +++ b/src/test/kotlin/com/unicorn/server/domain/member/WithdrawalLogTest.kt @@ -13,7 +13,7 @@ class WithdrawalLogTest { @DisplayName("reason이 blank이면 create에서 예외가 발생한다") fun create_whenReasonIsBlank_throwsException() { assertThatThrownBy { - WithdrawalLog.create(MemberId.generate(), "test@example.com", " ", LocalDateTime.now()) + WithdrawalLog.create(MemberId.generate(), "test@example.com", null, " ", LocalDateTime.now()) }.isInstanceOf(IllegalArgumentException::class.java) } @@ -21,7 +21,7 @@ class WithdrawalLogTest { @DisplayName("reason이 500자를 초과하면 create에서 예외가 발생한다") fun create_whenReasonExceeds500Chars_throwsException() { assertThatThrownBy { - WithdrawalLog.create(MemberId.generate(), "test@example.com", "a".repeat(501), LocalDateTime.now()) + WithdrawalLog.create(MemberId.generate(), "test@example.com", null, "a".repeat(501), LocalDateTime.now()) }.isInstanceOf(IllegalArgumentException::class.java) } } diff --git a/src/test/kotlin/com/unicorn/server/domain/member/service/MemberAuthServiceTest.kt b/src/test/kotlin/com/unicorn/server/domain/member/service/MemberAuthServiceTest.kt index f01f10f..2ba6430 100644 --- a/src/test/kotlin/com/unicorn/server/domain/member/service/MemberAuthServiceTest.kt +++ b/src/test/kotlin/com/unicorn/server/domain/member/service/MemberAuthServiceTest.kt @@ -138,11 +138,9 @@ class MemberAuthServiceTest { } @Test - @DisplayName("login 호출 시 기존 소셜 계정의 멤버가 탈퇴 상태이면 WithdrawnMemberException이 발생한다") - fun login_existingWithdrawnMember_throwsWithdrawnMemberException() { + @DisplayName("탈퇴로 소셜 계정 연결이 삭제된 뒤 같은 소셜 계정으로 로그인하면 신규 가입된다") + fun login_afterSocialAccountDeletion_createsNewMember() { val member = memberOutPort.save(Member.create(Email("withdrawn-login@example.com"), "홍길동", "길동이")) - member.withdraw() - memberOutPort.save(member) socialAccountOutPort.save( SocialAccount.create( member.id, @@ -153,10 +151,16 @@ class MemberAuthServiceTest { "https://example.com/withdrawn.png", ), ) + member.withdraw() + memberOutPort.save(member) + socialAccountOutPort.deleteByMemberId(member.id) val command = SocialLoginCommand(SocialProvider.KAKAO, "kakao-withdrawn", "withdrawn-login@example.com", "홍길동") - assertThatThrownBy { memberAuthService.login(command) } - .isInstanceOf(WithdrawnMemberException::class.java) + val result = memberAuthService.login(command) + + assertThat(result.isNewMember).isTrue() + assertThat(socialAccountOutPort.findByProviderAndProviderId(SocialProvider.KAKAO, "kakao-withdrawn")!!.memberId) + .isNotEqualTo(member.id) } @Test @@ -269,6 +273,10 @@ class MemberAuthServiceTest { override fun findByMemberId(memberId: MemberId): SocialAccount? = store.values.firstOrNull { it.memberId == memberId } + + override fun deleteByMemberId(memberId: MemberId) { + store.entries.removeIf { it.value.memberId == memberId } + } } private class FakeTokenIssuer : TokenIssuer { diff --git a/src/test/kotlin/com/unicorn/server/domain/member/service/MemberProfileServiceTest.kt b/src/test/kotlin/com/unicorn/server/domain/member/service/MemberProfileServiceTest.kt index d87cf6a..ef10c4e 100644 --- a/src/test/kotlin/com/unicorn/server/domain/member/service/MemberProfileServiceTest.kt +++ b/src/test/kotlin/com/unicorn/server/domain/member/service/MemberProfileServiceTest.kt @@ -262,6 +262,20 @@ class MemberProfileServiceTest { assertThat(log.reason).isEqualTo("서비스 불만족") } + @Test + @DisplayName("withdraw 호출 시 소셜 provider를 로그에 남기고 소셜 계정 연결을 삭제한다") + fun withdraw_logsSocialProviderAndDeletesSocialAccount() { + val member = memberOutPort.save(Member.create(Email("social-log@example.com"), "홍길동", "길동이")) + socialAccountOutPort.save( + SocialAccount.create(member.id, SocialProvider.KAKAO, "kakao-social-log", "social-log@example.com", "카카오닉네임", null), + ) + + memberProfileService.withdraw(member.id.toString(), "서비스 불만족") + + assertThat(withdrawalLogOutPort.logs.single().socialProvider).isEqualTo(SocialProvider.KAKAO) + assertThat(socialAccountOutPort.findByMemberId(member.id)).isNull() + } + @Test @DisplayName("기존 프로필 이미지는 트랜잭션 커밋 후에만 삭제한다") fun uploadProfileImage_withActiveTransaction_deletesPreviousImageAfterCommit() { @@ -340,6 +354,10 @@ class MemberProfileServiceTest { override fun findByMemberId(memberId: MemberId): SocialAccount? = store.values.firstOrNull { it.memberId == memberId } + + override fun deleteByMemberId(memberId: MemberId) { + store.entries.removeIf { it.value.memberId == memberId } + } } private class FakeWithdrawalLogOutPort : WithdrawalLogOutPort { diff --git a/src/test/kotlin/com/unicorn/server/domain/member/service/MemberSeedTest.kt b/src/test/kotlin/com/unicorn/server/domain/member/service/MemberSeedTest.kt index cb61543..efcc2ea 100644 --- a/src/test/kotlin/com/unicorn/server/domain/member/service/MemberSeedTest.kt +++ b/src/test/kotlin/com/unicorn/server/domain/member/service/MemberSeedTest.kt @@ -90,7 +90,11 @@ class MemberSeedTest : BaseTest() { override fun findByProviderAndProviderId(provider: SocialProvider, providerId: String): SocialAccount? = store[provider to providerId] - override fun findByMemberId(memberId: MemberId): SocialAccount? = - store.values.firstOrNull { it.memberId == memberId } - } + override fun findByMemberId(memberId: MemberId): SocialAccount? = + store.values.firstOrNull { it.memberId == memberId } + + override fun deleteByMemberId(memberId: MemberId) { + store.entries.removeIf { it.value.memberId == memberId } + } + } } From b6809aeb224382d60cdbac3c730086b90123042c Mon Sep 17 00:00:00 2001 From: junyong Date: Fri, 24 Jul 2026 19:15:31 +0900 Subject: [PATCH 2/9] =?UTF-8?q?refactor[SAIO-202]:=20UpdateProfileInPort?= =?UTF-8?q?=EC=99=80=20UploadProfileImageInPort=20=ED=86=B5=ED=95=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../member/port/in/ManageMemberProfileInPort.kt | 12 ++++++++++++ .../domain/member/port/in/UpdateProfileInPort.kt | 10 ---------- .../member/port/in/UploadProfileImageInPort.kt | 10 ---------- .../domain/member/service/MemberProfileService.kt | 5 ++--- .../adapter/in/web/member/MemberController.kt | 10 ++++------ 5 files changed, 18 insertions(+), 29 deletions(-) create mode 100644 src/main/kotlin/com/unicorn/server/domain/member/port/in/ManageMemberProfileInPort.kt delete mode 100644 src/main/kotlin/com/unicorn/server/domain/member/port/in/UpdateProfileInPort.kt delete mode 100644 src/main/kotlin/com/unicorn/server/domain/member/port/in/UploadProfileImageInPort.kt diff --git a/src/main/kotlin/com/unicorn/server/domain/member/port/in/ManageMemberProfileInPort.kt b/src/main/kotlin/com/unicorn/server/domain/member/port/in/ManageMemberProfileInPort.kt new file mode 100644 index 0000000..8718849 --- /dev/null +++ b/src/main/kotlin/com/unicorn/server/domain/member/port/in/ManageMemberProfileInPort.kt @@ -0,0 +1,12 @@ +package com.unicorn.server.domain.member.port.`in` + +import com.unicorn.server.domain.member.Member +import com.unicorn.server.domain.member.port.dto.UpdateProfileCommand +import com.unicorn.server.domain.member.port.dto.UploadProfileImageCommand + +// ManageMemberProfileInPort - 멤버 프로필 정보와 이미지를 변경하는 유스케이스 진입점을 정의한다. +interface ManageMemberProfileInPort { + fun updateProfile(memberId: String, command: UpdateProfileCommand): Member + + fun uploadProfileImage(memberId: String, command: UploadProfileImageCommand): Member +} diff --git a/src/main/kotlin/com/unicorn/server/domain/member/port/in/UpdateProfileInPort.kt b/src/main/kotlin/com/unicorn/server/domain/member/port/in/UpdateProfileInPort.kt deleted file mode 100644 index 46201d0..0000000 --- a/src/main/kotlin/com/unicorn/server/domain/member/port/in/UpdateProfileInPort.kt +++ /dev/null @@ -1,10 +0,0 @@ -package com.unicorn.server.domain.member.port.`in` - -import com.unicorn.server.domain.member.Member -import com.unicorn.server.domain.member.port.dto.UpdateProfileCommand - -// UpdateProfileInPort - 멤버 프로필 변경 유스케이스 진입점을 정의한다. -interface UpdateProfileInPort { - // 멤버 식별자와 변경 명령으로 프로필을 갱신한다. - fun updateProfile(memberId: String, command: UpdateProfileCommand): Member -} diff --git a/src/main/kotlin/com/unicorn/server/domain/member/port/in/UploadProfileImageInPort.kt b/src/main/kotlin/com/unicorn/server/domain/member/port/in/UploadProfileImageInPort.kt deleted file mode 100644 index da9452b..0000000 --- a/src/main/kotlin/com/unicorn/server/domain/member/port/in/UploadProfileImageInPort.kt +++ /dev/null @@ -1,10 +0,0 @@ -package com.unicorn.server.domain.member.port.`in` - -import com.unicorn.server.domain.member.Member -import com.unicorn.server.domain.member.port.dto.UploadProfileImageCommand - -// UploadProfileImageInPort - 멤버 프로필 이미지 업로드 유스케이스 진입점을 정의한다. -interface UploadProfileImageInPort { - // 멤버 식별자와 업로드 명령으로 프로필 이미지를 갱신한다. - fun uploadProfileImage(memberId: String, command: UploadProfileImageCommand): Member -} diff --git a/src/main/kotlin/com/unicorn/server/domain/member/service/MemberProfileService.kt b/src/main/kotlin/com/unicorn/server/domain/member/service/MemberProfileService.kt index f37bd8b..20874f6 100644 --- a/src/main/kotlin/com/unicorn/server/domain/member/service/MemberProfileService.kt +++ b/src/main/kotlin/com/unicorn/server/domain/member/service/MemberProfileService.kt @@ -14,9 +14,8 @@ import com.unicorn.server.domain.member.exception.WithdrawnMemberException import com.unicorn.server.domain.member.port.`in`.GetMemberInPort import com.unicorn.server.domain.member.port.`in`.GetMemberProfileInPort import com.unicorn.server.domain.member.port.`in`.GetOnboardingInfoInPort +import com.unicorn.server.domain.member.port.`in`.ManageMemberProfileInPort import com.unicorn.server.domain.member.port.`in`.UpdateMemberStateInPort -import com.unicorn.server.domain.member.port.`in`.UpdateProfileInPort -import com.unicorn.server.domain.member.port.`in`.UploadProfileImageInPort import com.unicorn.server.domain.member.port.`in`.WithdrawMemberInPort import com.unicorn.server.domain.member.port.dto.MemberProfileDto import com.unicorn.server.domain.member.port.dto.OnboardingInfoResult @@ -41,7 +40,7 @@ class MemberProfileService( private val eventPublisher: EventPublisher, private val objectStorage: ObjectStorage, private val withdrawalLogOutPort: WithdrawalLogOutPort, -) : GetMemberInPort, GetMemberProfileInPort, GetOnboardingInfoInPort, UpdateProfileInPort, WithdrawMemberInPort, UploadProfileImageInPort, UpdateMemberStateInPort { +) : GetMemberInPort, GetMemberProfileInPort, GetOnboardingInfoInPort, ManageMemberProfileInPort, WithdrawMemberInPort, UpdateMemberStateInPort { // 멤버 식별자로 저장된 멤버를 조회한다. override fun getById(memberId: String): Member = findMemberOrThrow(memberId) diff --git a/src/main/kotlin/com/unicorn/server/infrastructure/adapter/in/web/member/MemberController.kt b/src/main/kotlin/com/unicorn/server/infrastructure/adapter/in/web/member/MemberController.kt index a7756f1..e013591 100644 --- a/src/main/kotlin/com/unicorn/server/infrastructure/adapter/in/web/member/MemberController.kt +++ b/src/main/kotlin/com/unicorn/server/infrastructure/adapter/in/web/member/MemberController.kt @@ -7,9 +7,8 @@ import com.unicorn.server.domain.member.port.`in`.CompleteOnboardingInPort import com.unicorn.server.domain.member.port.`in`.GetMemberInPort import com.unicorn.server.domain.member.port.`in`.GetOnboardingInfoInPort import com.unicorn.server.domain.member.port.`in`.LogoutInPort +import com.unicorn.server.domain.member.port.`in`.ManageMemberProfileInPort import com.unicorn.server.domain.member.port.`in`.UpdateMemberStateInPort -import com.unicorn.server.domain.member.port.`in`.UpdateProfileInPort -import com.unicorn.server.domain.member.port.`in`.UploadProfileImageInPort import com.unicorn.server.domain.member.port.`in`.WithdrawMemberInPort import com.unicorn.server.domain.member.port.dto.CompleteOnboardingCommand import com.unicorn.server.domain.member.port.dto.UpdateProfileCommand @@ -42,8 +41,7 @@ import org.springframework.web.multipart.MultipartFile class MemberController( private val getMemberInPort: GetMemberInPort, private val getOnboardingInfoInPort: GetOnboardingInfoInPort, - private val updateProfileInPort: UpdateProfileInPort, - private val uploadProfileImageInPort: UploadProfileImageInPort, + private val manageMemberProfileInPort: ManageMemberProfileInPort, private val logoutInPort: LogoutInPort, private val withdrawMemberInPort: WithdrawMemberInPort, private val completeOnboardingInPort: CompleteOnboardingInPort, @@ -88,7 +86,7 @@ class MemberController( @AuthenticationPrincipal memberId: String, @RequestBody @Valid request: UpdateProfileRequest, ): ApiResponse { - val member = updateProfileInPort.updateProfile(memberId, UpdateProfileCommand(request.nickname)) + val member = manageMemberProfileInPort.updateProfile(memberId, UpdateProfileCommand(request.nickname)) return ApiResponse.success(toMemberResponse(member)) } @@ -104,7 +102,7 @@ class MemberController( contentLength = image.size, inputStream = image.inputStream, ) - val member = uploadProfileImageInPort.uploadProfileImage(memberId, command) + val member = manageMemberProfileInPort.uploadProfileImage(memberId, command) return ApiResponse.success(toMemberResponse(member)) } From a1841fd45c0fc2c260a0679b97574c006e45d374 Mon Sep 17 00:00:00 2001 From: junyong Date: Fri, 24 Jul 2026 19:22:37 +0900 Subject: [PATCH 3/9] =?UTF-8?q?refactor[SAIO-202]:=20=EC=9D=BC=EC=A0=95=20?= =?UTF-8?q?=ED=8F=AC=ED=8A=B8=20=EC=9D=BC=EB=B6=80=20=EB=B3=91=ED=95=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../port/in/CancelConfirmationInPort.kt | 7 ---- .../schedule/port/in/DeleteScheduleInPort.kt | 7 ---- ...leDetailInPort.kt => GetScheduleInPort.kt} | 5 ++- .../schedule/port/in/GetScheduleListInPort.kt | 12 ------ ...kt => ManageScheduleConfirmationInPort.kt} | 5 ++- ...eduleInPort.kt => ManageScheduleInPort.kt} | 7 +++- .../schedule/port/in/UpdateScheduleInPort.kt | 7 ---- .../service/CancelConfirmationService.kt | 37 ------------------- .../service/ScheduleCommandService.kt | 6 +-- .../service/ScheduleConfirmationService.kt | 21 ++++++++++- .../schedule/service/ScheduleQueryService.kt | 5 +-- .../in/web/schedule/ScheduleController.kt | 35 +++++++----------- .../service/CancelConfirmationServiceTest.kt | 19 +++++++--- 13 files changed, 63 insertions(+), 110 deletions(-) delete mode 100644 src/main/kotlin/com/unicorn/server/domain/schedule/port/in/CancelConfirmationInPort.kt delete mode 100644 src/main/kotlin/com/unicorn/server/domain/schedule/port/in/DeleteScheduleInPort.kt rename src/main/kotlin/com/unicorn/server/domain/schedule/port/in/{GetScheduleDetailInPort.kt => GetScheduleInPort.kt} (58%) delete mode 100644 src/main/kotlin/com/unicorn/server/domain/schedule/port/in/GetScheduleListInPort.kt rename src/main/kotlin/com/unicorn/server/domain/schedule/port/in/{RegisterConfirmationInPort.kt => ManageScheduleConfirmationInPort.kt} (58%) rename src/main/kotlin/com/unicorn/server/domain/schedule/port/in/{CreateScheduleInPort.kt => ManageScheduleInPort.kt} (51%) delete mode 100644 src/main/kotlin/com/unicorn/server/domain/schedule/port/in/UpdateScheduleInPort.kt delete mode 100644 src/main/kotlin/com/unicorn/server/domain/schedule/service/CancelConfirmationService.kt diff --git a/src/main/kotlin/com/unicorn/server/domain/schedule/port/in/CancelConfirmationInPort.kt b/src/main/kotlin/com/unicorn/server/domain/schedule/port/in/CancelConfirmationInPort.kt deleted file mode 100644 index 2bd7632..0000000 --- a/src/main/kotlin/com/unicorn/server/domain/schedule/port/in/CancelConfirmationInPort.kt +++ /dev/null @@ -1,7 +0,0 @@ -package com.unicorn.server.domain.schedule.port.`in` - -import com.unicorn.server.domain.schedule.vo.ScheduleId - -interface CancelConfirmationInPort { - fun cancel(confirmationId: Long, scheduleId: ScheduleId, circleId: String, memberId: String) -} diff --git a/src/main/kotlin/com/unicorn/server/domain/schedule/port/in/DeleteScheduleInPort.kt b/src/main/kotlin/com/unicorn/server/domain/schedule/port/in/DeleteScheduleInPort.kt deleted file mode 100644 index f72869d..0000000 --- a/src/main/kotlin/com/unicorn/server/domain/schedule/port/in/DeleteScheduleInPort.kt +++ /dev/null @@ -1,7 +0,0 @@ -package com.unicorn.server.domain.schedule.port.`in` - -import com.unicorn.server.domain.schedule.vo.ScheduleId - -interface DeleteScheduleInPort { - fun delete(scheduleId: ScheduleId, circleId: String, memberId: String) -} diff --git a/src/main/kotlin/com/unicorn/server/domain/schedule/port/in/GetScheduleDetailInPort.kt b/src/main/kotlin/com/unicorn/server/domain/schedule/port/in/GetScheduleInPort.kt similarity index 58% rename from src/main/kotlin/com/unicorn/server/domain/schedule/port/in/GetScheduleDetailInPort.kt rename to src/main/kotlin/com/unicorn/server/domain/schedule/port/in/GetScheduleInPort.kt index afb853a..81629a6 100644 --- a/src/main/kotlin/com/unicorn/server/domain/schedule/port/in/GetScheduleDetailInPort.kt +++ b/src/main/kotlin/com/unicorn/server/domain/schedule/port/in/GetScheduleInPort.kt @@ -1,8 +1,11 @@ package com.unicorn.server.domain.schedule.port.`in` import com.unicorn.server.domain.schedule.port.dto.ScheduleDetailResult +import com.unicorn.server.domain.schedule.port.dto.ScheduleListResult import com.unicorn.server.domain.schedule.vo.ScheduleId -interface GetScheduleDetailInPort { +interface GetScheduleInPort { + fun getList(circleId: String, memberId: String, cursor: String?, size: Int): ScheduleListResult + fun getDetail(scheduleId: ScheduleId, circleId: String, memberId: String): ScheduleDetailResult } diff --git a/src/main/kotlin/com/unicorn/server/domain/schedule/port/in/GetScheduleListInPort.kt b/src/main/kotlin/com/unicorn/server/domain/schedule/port/in/GetScheduleListInPort.kt deleted file mode 100644 index 29134c2..0000000 --- a/src/main/kotlin/com/unicorn/server/domain/schedule/port/in/GetScheduleListInPort.kt +++ /dev/null @@ -1,12 +0,0 @@ -package com.unicorn.server.domain.schedule.port.`in` - -import com.unicorn.server.domain.schedule.port.dto.ScheduleListResult - -interface GetScheduleListInPort { - fun getList( - circleId: String, - memberId: String, - cursor: String?, - size: Int, - ): ScheduleListResult -} diff --git a/src/main/kotlin/com/unicorn/server/domain/schedule/port/in/RegisterConfirmationInPort.kt b/src/main/kotlin/com/unicorn/server/domain/schedule/port/in/ManageScheduleConfirmationInPort.kt similarity index 58% rename from src/main/kotlin/com/unicorn/server/domain/schedule/port/in/RegisterConfirmationInPort.kt rename to src/main/kotlin/com/unicorn/server/domain/schedule/port/in/ManageScheduleConfirmationInPort.kt index 6feeab6..036de33 100644 --- a/src/main/kotlin/com/unicorn/server/domain/schedule/port/in/RegisterConfirmationInPort.kt +++ b/src/main/kotlin/com/unicorn/server/domain/schedule/port/in/ManageScheduleConfirmationInPort.kt @@ -2,7 +2,10 @@ package com.unicorn.server.domain.schedule.port.`in` import com.unicorn.server.domain.schedule.enums.ConfirmationType import com.unicorn.server.domain.schedule.port.dto.RegisterConfirmationCommand +import com.unicorn.server.domain.schedule.vo.ScheduleId -interface RegisterConfirmationInPort { +interface ManageScheduleConfirmationInPort { fun register(command: RegisterConfirmationCommand): ConfirmationType + + fun cancel(confirmationId: Long, scheduleId: ScheduleId, circleId: String, memberId: String) } diff --git a/src/main/kotlin/com/unicorn/server/domain/schedule/port/in/CreateScheduleInPort.kt b/src/main/kotlin/com/unicorn/server/domain/schedule/port/in/ManageScheduleInPort.kt similarity index 51% rename from src/main/kotlin/com/unicorn/server/domain/schedule/port/in/CreateScheduleInPort.kt rename to src/main/kotlin/com/unicorn/server/domain/schedule/port/in/ManageScheduleInPort.kt index c8af0fc..d7b7ec6 100644 --- a/src/main/kotlin/com/unicorn/server/domain/schedule/port/in/CreateScheduleInPort.kt +++ b/src/main/kotlin/com/unicorn/server/domain/schedule/port/in/ManageScheduleInPort.kt @@ -1,8 +1,13 @@ package com.unicorn.server.domain.schedule.port.`in` import com.unicorn.server.domain.schedule.port.dto.CreateScheduleCommand +import com.unicorn.server.domain.schedule.port.dto.UpdateScheduleCommand import com.unicorn.server.domain.schedule.vo.ScheduleId -interface CreateScheduleInPort { +interface ManageScheduleInPort { fun create(command: CreateScheduleCommand): ScheduleId + + fun update(command: UpdateScheduleCommand) + + fun delete(scheduleId: ScheduleId, circleId: String, memberId: String) } diff --git a/src/main/kotlin/com/unicorn/server/domain/schedule/port/in/UpdateScheduleInPort.kt b/src/main/kotlin/com/unicorn/server/domain/schedule/port/in/UpdateScheduleInPort.kt deleted file mode 100644 index 2fc0c11..0000000 --- a/src/main/kotlin/com/unicorn/server/domain/schedule/port/in/UpdateScheduleInPort.kt +++ /dev/null @@ -1,7 +0,0 @@ -package com.unicorn.server.domain.schedule.port.`in` - -import com.unicorn.server.domain.schedule.port.dto.UpdateScheduleCommand - -interface UpdateScheduleInPort { - fun update(command: UpdateScheduleCommand) -} diff --git a/src/main/kotlin/com/unicorn/server/domain/schedule/service/CancelConfirmationService.kt b/src/main/kotlin/com/unicorn/server/domain/schedule/service/CancelConfirmationService.kt deleted file mode 100644 index 441feda..0000000 --- a/src/main/kotlin/com/unicorn/server/domain/schedule/service/CancelConfirmationService.kt +++ /dev/null @@ -1,37 +0,0 @@ -package com.unicorn.server.domain.schedule.service - -import com.unicorn.server.common.exception.BusinessException -import com.unicorn.server.domain.schedule.exception.ScheduleErrorCode -import com.unicorn.server.domain.schedule.port.`in`.CancelConfirmationInPort -import com.unicorn.server.domain.schedule.port.out.CircleAccessOutPort -import com.unicorn.server.domain.schedule.port.out.ScheduleConfirmationOutPort -import com.unicorn.server.domain.schedule.port.out.ScheduleOutPort -import com.unicorn.server.domain.schedule.vo.ScheduleId -import org.springframework.stereotype.Service -import org.springframework.transaction.annotation.Transactional - -@Service -@Transactional -class CancelConfirmationService( - private val scheduleOutPort: ScheduleOutPort, - private val scheduleConfirmationOutPort: ScheduleConfirmationOutPort, - private val circleAccessOutPort: CircleAccessOutPort, -) : CancelConfirmationInPort { - - override fun cancel(confirmationId: Long, scheduleId: ScheduleId, circleId: String, memberId: String) { - if (!circleAccessOutPort.isMember(circleId, memberId)) { - throw BusinessException(ScheduleErrorCode.CIRCLE_ACCESS_DENIED) - } - - scheduleOutPort.findActiveByIdAndCircleId(scheduleId, circleId) - ?: throw BusinessException(ScheduleErrorCode.SCHEDULE_NOT_FOUND) - - val confirmation = scheduleConfirmationOutPort.findById(confirmationId) - ?: throw BusinessException(ScheduleErrorCode.CONFIRMATION_NOT_FOUND) - if (confirmation.scheduleId != scheduleId || confirmation.memberId != memberId) { - throw BusinessException(ScheduleErrorCode.CONFIRMATION_ACCESS_DENIED) - } - - scheduleConfirmationOutPort.deleteById(confirmationId) - } -} diff --git a/src/main/kotlin/com/unicorn/server/domain/schedule/service/ScheduleCommandService.kt b/src/main/kotlin/com/unicorn/server/domain/schedule/service/ScheduleCommandService.kt index 6c23318..1ba1b23 100644 --- a/src/main/kotlin/com/unicorn/server/domain/schedule/service/ScheduleCommandService.kt +++ b/src/main/kotlin/com/unicorn/server/domain/schedule/service/ScheduleCommandService.kt @@ -6,9 +6,7 @@ import com.unicorn.server.domain.schedule.Schedule import com.unicorn.server.domain.schedule.event.ScheduleCreatedEvent import com.unicorn.server.domain.schedule.event.ScheduleDeletedEvent import com.unicorn.server.domain.schedule.exception.ScheduleErrorCode -import com.unicorn.server.domain.schedule.port.`in`.CreateScheduleInPort -import com.unicorn.server.domain.schedule.port.`in`.DeleteScheduleInPort -import com.unicorn.server.domain.schedule.port.`in`.UpdateScheduleInPort +import com.unicorn.server.domain.schedule.port.`in`.ManageScheduleInPort import com.unicorn.server.domain.schedule.port.dto.CreateScheduleCommand import com.unicorn.server.domain.schedule.port.dto.UpdateScheduleCommand import com.unicorn.server.domain.schedule.port.out.CircleAccessOutPort @@ -27,7 +25,7 @@ class ScheduleCommandService( private val circleAccessOutPort: CircleAccessOutPort, private val scheduleIdGenerator: ScheduleIdGenerator, private val eventPublisher: EventPublisher, -) : CreateScheduleInPort, UpdateScheduleInPort, DeleteScheduleInPort { +) : ManageScheduleInPort { override fun create(command: CreateScheduleCommand): ScheduleId { if (!circleAccessOutPort.existsById(command.circleId)) { diff --git a/src/main/kotlin/com/unicorn/server/domain/schedule/service/ScheduleConfirmationService.kt b/src/main/kotlin/com/unicorn/server/domain/schedule/service/ScheduleConfirmationService.kt index 3540d70..dd048d0 100644 --- a/src/main/kotlin/com/unicorn/server/domain/schedule/service/ScheduleConfirmationService.kt +++ b/src/main/kotlin/com/unicorn/server/domain/schedule/service/ScheduleConfirmationService.kt @@ -6,7 +6,7 @@ import com.unicorn.server.domain.schedule.ScheduleConfirmation import com.unicorn.server.domain.schedule.enums.ConfirmationType import com.unicorn.server.domain.schedule.event.ScheduleConfirmedEvent import com.unicorn.server.domain.schedule.exception.ScheduleErrorCode -import com.unicorn.server.domain.schedule.port.`in`.RegisterConfirmationInPort +import com.unicorn.server.domain.schedule.port.`in`.ManageScheduleConfirmationInPort import com.unicorn.server.domain.schedule.port.`in`.ScheduleConfirmationStatusInPort import com.unicorn.server.domain.schedule.port.dto.RegisterConfirmationCommand import com.unicorn.server.domain.schedule.port.out.CircleAccessOutPort @@ -23,7 +23,7 @@ class ScheduleConfirmationService( private val scheduleConfirmationOutPort: ScheduleConfirmationOutPort, private val circleAccessOutPort: CircleAccessOutPort, private val eventPublisher: EventPublisher, -) : RegisterConfirmationInPort, ScheduleConfirmationStatusInPort { +) : ManageScheduleConfirmationInPort, ScheduleConfirmationStatusInPort { override fun register(command: RegisterConfirmationCommand): ConfirmationType { if (!command.confirmationType.available) { @@ -72,4 +72,21 @@ class ScheduleConfirmationService( override fun hasConfirmed(scheduleId: ScheduleId, memberId: String): Boolean = scheduleConfirmationOutPort.findByScheduleIdAndMemberId(scheduleId, memberId) ?.confirmationType == ConfirmationType.CONFIRMED + + override fun cancel(confirmationId: Long, scheduleId: ScheduleId, circleId: String, memberId: String) { + if (!circleAccessOutPort.isMember(circleId, memberId)) { + throw BusinessException(ScheduleErrorCode.CIRCLE_ACCESS_DENIED) + } + + scheduleOutPort.findActiveByIdAndCircleId(scheduleId, circleId) + ?: throw BusinessException(ScheduleErrorCode.SCHEDULE_NOT_FOUND) + + val confirmation = scheduleConfirmationOutPort.findById(confirmationId) + ?: throw BusinessException(ScheduleErrorCode.CONFIRMATION_NOT_FOUND) + if (confirmation.scheduleId != scheduleId || confirmation.memberId != memberId) { + throw BusinessException(ScheduleErrorCode.CONFIRMATION_ACCESS_DENIED) + } + + scheduleConfirmationOutPort.deleteById(confirmationId) + } } diff --git a/src/main/kotlin/com/unicorn/server/domain/schedule/service/ScheduleQueryService.kt b/src/main/kotlin/com/unicorn/server/domain/schedule/service/ScheduleQueryService.kt index 3342583..86844ac 100644 --- a/src/main/kotlin/com/unicorn/server/domain/schedule/service/ScheduleQueryService.kt +++ b/src/main/kotlin/com/unicorn/server/domain/schedule/service/ScheduleQueryService.kt @@ -6,8 +6,7 @@ import com.unicorn.server.domain.circle.vo.CircleId import com.unicorn.server.domain.schedule.Schedule import com.unicorn.server.domain.schedule.enums.UrgencyLevel import com.unicorn.server.domain.schedule.exception.ScheduleErrorCode -import com.unicorn.server.domain.schedule.port.`in`.GetScheduleDetailInPort -import com.unicorn.server.domain.schedule.port.`in`.GetScheduleListInPort +import com.unicorn.server.domain.schedule.port.`in`.GetScheduleInPort import com.unicorn.server.domain.schedule.port.`in`.GetSchedulesForCircleInPort import com.unicorn.server.domain.schedule.port.dto.MyConfirmationInfo import com.unicorn.server.domain.schedule.port.dto.ScheduleDetailResult @@ -30,7 +29,7 @@ class ScheduleQueryService( private val scheduleOutPort: ScheduleOutPort, private val scheduleConfirmationOutPort: ScheduleConfirmationOutPort, private val circleAccessOutPort: CircleAccessOutPort, -) : GetScheduleListInPort, GetScheduleDetailInPort, GetSchedulesForCircleInPort { +) : GetScheduleInPort, GetSchedulesForCircleInPort { override fun getList( circleId: String, diff --git a/src/main/kotlin/com/unicorn/server/infrastructure/adapter/in/web/schedule/ScheduleController.kt b/src/main/kotlin/com/unicorn/server/infrastructure/adapter/in/web/schedule/ScheduleController.kt index 94e4a1a..4c7bc51 100644 --- a/src/main/kotlin/com/unicorn/server/infrastructure/adapter/in/web/schedule/ScheduleController.kt +++ b/src/main/kotlin/com/unicorn/server/infrastructure/adapter/in/web/schedule/ScheduleController.kt @@ -1,15 +1,10 @@ package com.unicorn.server.infrastructure.adapter.`in`.web.schedule import com.unicorn.server.domain.schedule.enums.ConfirmationType -import com.unicorn.server.domain.schedule.port.`in`.CancelConfirmationInPort -import com.unicorn.server.domain.schedule.port.`in`.CreateScheduleInPort -import com.unicorn.server.domain.schedule.port.`in`.DeleteScheduleInPort -import com.unicorn.server.domain.schedule.port.`in`.GetScheduleDetailInPort -import com.unicorn.server.domain.schedule.port.`in`.GetScheduleListInPort -import com.unicorn.server.domain.schedule.port.`in`.RegisterConfirmationInPort - +import com.unicorn.server.domain.schedule.port.`in`.GetScheduleInPort +import com.unicorn.server.domain.schedule.port.`in`.ManageScheduleConfirmationInPort +import com.unicorn.server.domain.schedule.port.`in`.ManageScheduleInPort import com.unicorn.server.domain.schedule.port.`in`.RequestFamilyScheduleNotificationInPort -import com.unicorn.server.domain.schedule.port.`in`.UpdateScheduleInPort import com.unicorn.server.domain.schedule.port.dto.CreateScheduleCommand import com.unicorn.server.domain.schedule.port.dto.RegisterConfirmationCommand import com.unicorn.server.domain.schedule.port.dto.RequestFamilyScheduleNotificationCommand @@ -39,13 +34,9 @@ import org.springframework.web.bind.annotation.RestController @RestController @RequestMapping("/api/v1/circles/{circleId}/schedules") class ScheduleController( - private val createScheduleInPort: CreateScheduleInPort, - private val updateScheduleInPort: UpdateScheduleInPort, - private val deleteScheduleInPort: DeleteScheduleInPort, - private val getScheduleListInPort: GetScheduleListInPort, - private val getScheduleDetailInPort: GetScheduleDetailInPort, - private val registerConfirmationInPort: RegisterConfirmationInPort, - private val cancelConfirmationInPort: CancelConfirmationInPort, + private val manageScheduleInPort: ManageScheduleInPort, + private val getScheduleInPort: GetScheduleInPort, + private val manageScheduleConfirmationInPort: ManageScheduleConfirmationInPort, private val requestFamilyScheduleNotificationInPort: RequestFamilyScheduleNotificationInPort, ) : ScheduleApiDoc { @@ -55,7 +46,7 @@ class ScheduleController( @PathVariable circleId: String, @RequestBody @Valid request: CreateScheduleRequest, ): ApiResponse { - val scheduleId = createScheduleInPort.create( + val scheduleId = manageScheduleInPort.create( CreateScheduleCommand( memberId = memberId, circleId = circleId, @@ -78,7 +69,7 @@ class ScheduleController( @PathVariable scheduleId: String, @RequestBody @Valid request: UpdateScheduleRequest, ): ApiResponse { - updateScheduleInPort.update( + manageScheduleInPort.update( UpdateScheduleCommand( scheduleId = ScheduleId.of(scheduleId), circleId = circleId, @@ -104,7 +95,7 @@ class ScheduleController( @PathVariable circleId: String, @PathVariable scheduleId: String, ): ApiResponse { - deleteScheduleInPort.delete(ScheduleId.of(scheduleId), circleId, memberId) + manageScheduleInPort.delete(ScheduleId.of(scheduleId), circleId, memberId) return ApiResponse.success() } @@ -115,7 +106,7 @@ class ScheduleController( @RequestParam cursor: String?, @RequestParam(defaultValue = "20") size: Int, ): ApiResponse { - val result = getScheduleListInPort.getList(circleId, memberId, cursor, size) + val result = getScheduleInPort.getList(circleId, memberId, cursor, size) return ApiResponse.success(ScheduleListResponse.from(result)) } @@ -125,7 +116,7 @@ class ScheduleController( @PathVariable circleId: String, @PathVariable scheduleId: String, ): ApiResponse { - val result = getScheduleDetailInPort.getDetail(ScheduleId.of(scheduleId), circleId, memberId) + val result = getScheduleInPort.getDetail(ScheduleId.of(scheduleId), circleId, memberId) return ApiResponse.success(ScheduleDetailResponse.from(result)) } @@ -136,7 +127,7 @@ class ScheduleController( @PathVariable scheduleId: String, @RequestBody @Valid request: RegisterConfirmationRequest, ): ApiResponse { - val type = registerConfirmationInPort.register( + val type = manageScheduleConfirmationInPort.register( RegisterConfirmationCommand( scheduleId = ScheduleId.of(scheduleId), circleId = circleId, @@ -154,7 +145,7 @@ class ScheduleController( @PathVariable scheduleId: String, @PathVariable confirmationId: Long, ): ApiResponse { - cancelConfirmationInPort.cancel(confirmationId, ScheduleId.of(scheduleId), circleId, memberId) + manageScheduleConfirmationInPort.cancel(confirmationId, ScheduleId.of(scheduleId), circleId, memberId) return ApiResponse.success() } diff --git a/src/test/kotlin/com/unicorn/server/domain/schedule/service/CancelConfirmationServiceTest.kt b/src/test/kotlin/com/unicorn/server/domain/schedule/service/CancelConfirmationServiceTest.kt index 9a92ff9..6d267db 100644 --- a/src/test/kotlin/com/unicorn/server/domain/schedule/service/CancelConfirmationServiceTest.kt +++ b/src/test/kotlin/com/unicorn/server/domain/schedule/service/CancelConfirmationServiceTest.kt @@ -1,6 +1,8 @@ package com.unicorn.server.domain.schedule.service import com.unicorn.server.common.exception.BusinessException +import com.unicorn.server.common.domain.Event +import com.unicorn.server.common.port.out.event.EventPublisher import com.unicorn.server.domain.schedule.Schedule import com.unicorn.server.domain.schedule.ScheduleConfirmation import com.unicorn.server.domain.schedule.enums.ConfirmationType @@ -19,16 +21,17 @@ import java.time.LocalDate import java.time.LocalDateTime import java.time.LocalTime -@DisplayName("CancelConfirmationService 단위 테스트") -class CancelConfirmationServiceTest { +@DisplayName("ScheduleConfirmationService 취소 기능 단위 테스트") +class ScheduleConfirmationCancellationTest { private val scheduleOutPort = FakeScheduleOutPort() private val confirmationOutPort = FakeScheduleConfirmationOutPort() private val circleAccessOutPort = FakeCircleAccessOutPort() - private val cancelConfirmationService = CancelConfirmationService( + private val scheduleConfirmationService = ScheduleConfirmationService( scheduleOutPort, confirmationOutPort, circleAccessOutPort, + NoOpEventPublisher, ) @Test @@ -38,7 +41,7 @@ class CancelConfirmationServiceTest { scheduleOutPort.seed(schedule()) confirmationOutPort.seed(confirmation(id = CONFIRMATION_ID, memberId = MEMBER_ID)) - cancelConfirmationService.cancel(CONFIRMATION_ID, SCHEDULE_ID, CIRCLE_ID, MEMBER_ID) + scheduleConfirmationService.cancel(CONFIRMATION_ID, SCHEDULE_ID, CIRCLE_ID, MEMBER_ID) assertThat(confirmationOutPort.deletedIds).containsExactly(CONFIRMATION_ID) assertThat(confirmationOutPort.findById(CONFIRMATION_ID)).isNull() @@ -50,7 +53,7 @@ class CancelConfirmationServiceTest { circleAccessOutPort.seedMember(CIRCLE_ID, MEMBER_ID) scheduleOutPort.seed(schedule()) - assertThatThrownBy { cancelConfirmationService.cancel(CONFIRMATION_ID, SCHEDULE_ID, CIRCLE_ID, MEMBER_ID) } + assertThatThrownBy { scheduleConfirmationService.cancel(CONFIRMATION_ID, SCHEDULE_ID, CIRCLE_ID, MEMBER_ID) } .isInstanceOf(BusinessException::class.java) .extracting { (it as BusinessException).errorCode } .isEqualTo(ScheduleErrorCode.CONFIRMATION_NOT_FOUND) @@ -63,7 +66,7 @@ class CancelConfirmationServiceTest { scheduleOutPort.seed(schedule()) confirmationOutPort.seed(confirmation(id = CONFIRMATION_ID, memberId = "member-2")) - assertThatThrownBy { cancelConfirmationService.cancel(CONFIRMATION_ID, SCHEDULE_ID, CIRCLE_ID, MEMBER_ID) } + assertThatThrownBy { scheduleConfirmationService.cancel(CONFIRMATION_ID, SCHEDULE_ID, CIRCLE_ID, MEMBER_ID) } .isInstanceOf(BusinessException::class.java) .extracting { (it as BusinessException).errorCode } .isEqualTo(ScheduleErrorCode.CONFIRMATION_ACCESS_DENIED) @@ -197,6 +200,10 @@ class CancelConfirmationServiceTest { } companion object { + private object NoOpEventPublisher : EventPublisher { + override fun publish(event: Event) = Unit + } + private const val CIRCLE_ID = "CC202506010000000001" private val SCHEDULE_ID = ScheduleId.of("SC202407070000000001") private const val MEMBER_ID = "member-1" From de93b7931a14ef2311408c2746c743efadac63d2 Mon Sep 17 00:00:00 2001 From: junyong Date: Sat, 25 Jul 2026 17:03:58 +0900 Subject: [PATCH 4/9] =?UTF-8?q?refactor[SAIO-271]:=20=ED=8F=AC=ED=8A=B8?= =?UTF-8?q?=EB=AA=85=20=EC=BB=A8=EB=B2=A4=EC=85=98=20SKILL.md=20=EB=82=B4?= =?UTF-8?q?=EC=9A=A9=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- skills/architecture-patterns/SKILL.md | 36 +++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/skills/architecture-patterns/SKILL.md b/skills/architecture-patterns/SKILL.md index 4b13c32..94f3867 100644 --- a/skills/architecture-patterns/SKILL.md +++ b/skills/architecture-patterns/SKILL.md @@ -618,6 +618,42 @@ This rule applies to: It does **not** apply to adapters under `infrastructure/adapter/out`, configuration classes, or properties classes — those are explicitly infrastructure and may use vendor names freely. +### Port Name Format + +Port interface names follow: `[]Port` + +- ``: the aggregate/bounded-context noun the port belongs to (e.g. `Member`, `Schedule`). +- `` (optional): a noun narrowing the port's responsibility, used only when a domain needs more than one port in the same direction (e.g. `Info`, `Persist`). Omit it when a single `In`/`Out` port per domain is enough. +- ``: driving (`In`) or driven (`Out`) port. +- `Port`: fixed suffix. + +Do not prefix a port name with a verb for a single use case (`Get`, `Create`, `Register`, `Save`, ...). A port is a contract for the domain, not one action — the verbs belong to the methods inside the interface, not the interface name. + +| Wrong | Correct | +|---|---| +| `GetMemberInPort` | `MemberInPort` | +| `GetMemberInfoInPort` | `MemberInfoInPort` | +| `SaveScheduleOutPort` | `ScheduleOutPort` | +| `PersistScheduleOutPort` | `SchedulePersistOutPort` | + +```kotlin +// domain/member/port/in/MemberInPort.kt ✅ +interface MemberInPort { + fun getMember(memberId: MemberId): Member + fun registerMember(request: CreateMemberRequest): Member +} + +// domain/schedule/port/out/ScheduleOutPort.kt ✅ +interface ScheduleOutPort { + fun findById(scheduleId: ScheduleId): Schedule? +} + +// domain/schedule/port/out/SchedulePersistOutPort.kt ✅ (separate purpose from ScheduleOutPort) +interface SchedulePersistOutPort { + fun save(schedule: Schedule): Schedule +} +``` + ## Spring Configuration Management Choose between `@Value`, `@ConfigurationProperties`, and `@Configuration` based on the scope and purpose of the configuration. From 2bfb4fbe121aa6d0d07a9faae0b548c962beb5b4 Mon Sep 17 00:00:00 2001 From: junyong Date: Sat, 25 Jul 2026 17:11:02 +0900 Subject: [PATCH 5/9] =?UTF-8?q?refactor[SAIO-271]:=20=EB=8F=84=EB=A9=94?= =?UTF-8?q?=EC=9D=B8=20=EC=99=B8=20=ED=8F=AC=ED=8A=B8=20=EA=B7=9C=EC=B9=99?= =?UTF-8?q?=20SKILL.md=20=EB=82=B4=EC=9A=A9=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- skills/architecture-patterns/SKILL.md | 42 +++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/skills/architecture-patterns/SKILL.md b/skills/architecture-patterns/SKILL.md index 94f3867..9a66162 100644 --- a/skills/architecture-patterns/SKILL.md +++ b/skills/architecture-patterns/SKILL.md @@ -654,6 +654,48 @@ interface SchedulePersistOutPort { } ``` +This format applies to every driven port under `port/out`, even ones that today read like a plain capability noun (`TokenIssuer`, `TokenStore`, `NotificationSender`, `KakaoAuthPort`, ...). The package location (`port/out`) already marks these as ports; the class name must say so too, so a grep for `OutPort` or an IDE autocomplete on `Port` reliably finds them all: + +| Wrong | Correct | +|---|---| +| `TokenIssuer` | `MemberTokenIssueOutPort` | +| `TokenStore` | `MemberTokenStoreOutPort` | +| `NotificationSender` | `NotificationSendOutPort` | +| `KakaoAuthPort` | `MemberKakaoAuthOutPort` | + +### Port Name Format: Common Ports + +`common/port/out` has no bounded-context domain to anchor ``, so the format becomes `[]OutPort`, where `` is the cross-cutting concern the port abstracts (still vendor-neutral, per [Platform and Service Names Belong in Infrastructure Only](#platform-and-service-names-belong-in-infrastructure-only)). + +| Wrong | Correct | +|---|---| +| `ErrorAlertPort` | `AlertOutPort` | +| `EventPublisher` | `EventOutPort` | +| `ObjectStorage` | `ObjectStorageOutPort` | + +### Port Name Format: Exceptions + +Two categories are intentionally exempt from the `[]Port` suffix rule: + +**ID/token generators.** A generator's only responsibility is producing a new identifier, and `Generator` already communicates both the responsibility and that it is a driven port. Appending `OutPort` adds length without adding information. + +```kotlin +// domain/schedule/port/out/ScheduleIdGenerator.kt ✅ (exception — no OutPort suffix) +interface ScheduleIdGenerator { + fun next(): ScheduleId +} +``` + +**Anti-corruption ports.** When a port exists solely to let one bounded context read another context's aggregate without importing it directly, name the port after the *target* context/capability it exposes, not the package it physically lives in. The name should tell the reader what the port guards against, not where the file happens to sit. + +```kotlin +// domain/schedule/port/out/CircleAccessOutPort.kt ✅ (exception — target-context name, not "Schedule...") +interface CircleAccessOutPort { + fun existsById(circleId: String): Boolean + fun isMember(circleId: String, memberId: String): Boolean +} +``` + ## Spring Configuration Management Choose between `@Value`, `@ConfigurationProperties`, and `@Configuration` based on the scope and purpose of the configuration. From 295c3b5775c541de8fe218fc1778fc08b4f7fb90 Mon Sep 17 00:00:00 2001 From: junyong Date: Sat, 25 Jul 2026 17:16:46 +0900 Subject: [PATCH 6/9] =?UTF-8?q?refactor[SAIO-271]:=20=EC=B6=94=EA=B0=80?= =?UTF-8?q?=ED=95=9C=20=ED=8F=AC=ED=8A=B8=EB=AA=85=20=EC=BB=A8=EB=B2=A4?= =?UTF-8?q?=EC=85=98=20=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../{ErrorAlertPort.kt => AlertOutPort.kt} | 2 +- .../{EventPublisher.kt => EventOutPort.kt} | 2 +- ...jectStorage.kt => ObjectStorageOutPort.kt} | 4 +-- .../common/port/out/storage/ObjectType.kt | 4 +-- .../domain/circle/service/CircleService.kt | 8 ++--- .../domain/home/service/HomeQueryService.kt | 8 ++--- ...ionInPort.kt => InvitationAcceptInPort.kt} | 2 +- ...enInPort.kt => InvitationByTokenInPort.kt} | 2 +- ...nInPort.kt => InvitationDispatchInPort.kt} | 2 +- ...tionInPort.kt => InvitationIssueInPort.kt} | 2 +- .../invitation/service/InvitationService.kt | 18 +++++------ .../{GetMemberInPort.kt => MemberInPort.kt} | 4 +-- ...ginInPort.kt => MemberKakaoLoginInPort.kt} | 4 +-- ...{LogoutInPort.kt => MemberLogoutInPort.kt} | 4 +-- ...t.kt => MemberOnboardingCompleteInPort.kt} | 2 +- ...nPort.kt => MemberOnboardingInfoInPort.kt} | 2 +- ...t.kt => MemberProfileImageUploadInPort.kt} | 4 +-- ...rofileInPort.kt => MemberProfileInPort.kt} | 2 +- ...InPort.kt => MemberProfileUpdateInPort.kt} | 4 +-- ...inInPort.kt => MemberSocialLoginInPort.kt} | 4 +-- ...teInPort.kt => MemberStateUpdateInPort.kt} | 4 +-- ...nInPort.kt => MemberTokenReissueInPort.kt} | 4 +-- ...emberInPort.kt => MemberWithdrawInPort.kt} | 4 +-- ...oAuthPort.kt => MemberKakaoAuthOutPort.kt} | 4 +-- ...enIssuer.kt => MemberTokenIssueOutPort.kt} | 4 +-- ...kenStore.kt => MemberTokenStoreOutPort.kt} | 4 +-- .../member/service/MemberAuthService.kt | 16 +++++----- .../member/service/MemberProfileService.kt | 24 +++++++------- .../member/service/OnboardingService.kt | 12 +++---- .../member/service/SocialLoginService.kt | 12 +++---- ...t => NotificationMessageComposeOutPort.kt} | 2 +- ...onSender.kt => NotificationSendOutPort.kt} | 2 +- .../service/NotificationDispatchService.kt | 12 +++---- ...kt => ScheduleConfirmationCancelInPort.kt} | 2 +- ... => ScheduleConfirmationRegisterInPort.kt} | 2 +- ...eduleInPort.kt => ScheduleCreateInPort.kt} | 2 +- ...eduleInPort.kt => ScheduleDeleteInPort.kt} | 2 +- ...etailInPort.kt => ScheduleDetailInPort.kt} | 2 +- ...cheduleFamilyNotificationRequestInPort.kt} | 2 +- ...leInPort.kt => ScheduleForCircleInPort.kt} | 2 +- ...uleListInPort.kt => ScheduleListInPort.kt} | 2 +- ...eduleInPort.kt => ScheduleUpdateInPort.kt} | 2 +- .../service/CancelConfirmationService.kt | 4 +-- .../FamilyScheduleNotificationService.kt | 8 ++--- .../service/ScheduleCommandService.kt | 12 +++---- .../ScheduleConfirmationRequestService.kt | 4 +-- .../service/ScheduleConfirmationService.kt | 8 ++--- .../schedule/service/ScheduleQueryService.kt | 8 ++--- .../service/ScheduleReminderService.kt | 4 +-- ...tiveTermsInPort.kt => TermActiveInPort.kt} | 2 +- ...AgreeTermsInPort.kt => TermAgreeInPort.kt} | 2 +- .../term/service/TermAgreementService.kt | 4 +-- .../domain/term/service/TermQueryService.kt | 4 +-- .../adapter/in/event/MemberEventListener.kt | 4 +-- .../InvitationNotificationEventListener.kt | 4 +-- .../ScheduleNotificationEventListener.kt | 4 +-- .../in/web/common/GlobalExceptionHandler.kt | 4 +-- .../in/web/invitation/InvitationController.kt | 12 +++---- .../adapter/in/web/member/AuthController.kt | 8 ++--- .../adapter/in/web/member/MemberController.kt | 32 +++++++++---------- .../in/web/schedule/ScheduleController.kt | 32 +++++++++---------- .../adapter/in/web/term/TermController.kt | 8 ++--- .../out/discord/DiscordNotificationAdapter.kt | 4 +-- .../out/event/SpringEventPublisherAdapter.kt | 4 +-- .../adapter/out/kakao/KakaoAuthAdapter.kt | 4 +-- .../notification/fcm/FcmMessageComposer.kt | 4 +-- .../adapter/out/notification/fcm/FcmSender.kt | 4 +-- .../out/storage/S3ObjectStorageAdapter.kt | 6 ++-- .../out/token/InMemoryTokenStoreAdapter.kt | 4 +-- .../adapter/out/token/JwtProvider.kt | 4 +-- .../domain/circle/CircleServiceJoinTest.kt | 8 ++--- .../server/domain/circle/CircleServiceTest.kt | 8 ++--- .../domain/home/HomeQueryServiceTest.kt | 8 ++--- .../invitation/InvitationServiceTest.kt | 8 ++--- .../member/service/MemberAuthServiceTest.kt | 10 +++--- .../service/MemberProfileServiceTest.kt | 8 ++--- .../domain/member/service/MemberSeedTest.kt | 4 +-- .../member/service/OnboardingServiceTest.kt | 8 ++--- .../member/service/SocialLoginServiceTest.kt | 8 ++--- .../NotificationDispatchServiceTest.kt | 12 +++---- .../FamilyScheduleNotificationServiceTest.kt | 4 +-- .../service/ScheduleCommandServiceTest.kt | 4 +-- .../ScheduleConfirmationRequestServiceTest.kt | 4 +-- .../ScheduleConfirmationServiceTest.kt | 4 +-- .../service/ScheduleReminderServiceTest.kt | 4 +-- ...InvitationNotificationEventListenerTest.kt | 4 +-- .../ScheduleNotificationEventListenerTest.kt | 4 +-- .../CircleInvitationFlowIntegrationTest.kt | 16 +++++----- 88 files changed, 267 insertions(+), 267 deletions(-) rename src/main/kotlin/com/unicorn/server/common/port/out/alert/{ErrorAlertPort.kt => AlertOutPort.kt} (76%) rename src/main/kotlin/com/unicorn/server/common/port/out/event/{EventPublisher.kt => EventOutPort.kt} (82%) rename src/main/kotlin/com/unicorn/server/common/port/out/storage/{ObjectStorage.kt => ObjectStorageOutPort.kt} (86%) rename src/main/kotlin/com/unicorn/server/domain/invitation/port/in/{AcceptCircleInvitationInPort.kt => InvitationAcceptInPort.kt} (81%) rename src/main/kotlin/com/unicorn/server/domain/invitation/port/in/{GetInvitationByTokenInPort.kt => InvitationByTokenInPort.kt} (82%) rename src/main/kotlin/com/unicorn/server/domain/invitation/port/in/{DispatchInvitationInPort.kt => InvitationDispatchInPort.kt} (83%) rename src/main/kotlin/com/unicorn/server/domain/invitation/port/in/{IssueInvitationInPort.kt => InvitationIssueInPort.kt} (89%) rename src/main/kotlin/com/unicorn/server/domain/member/port/in/{GetMemberInPort.kt => MemberInPort.kt} (64%) rename src/main/kotlin/com/unicorn/server/domain/member/port/in/{KakaoLoginInPort.kt => MemberKakaoLoginInPort.kt} (54%) rename src/main/kotlin/com/unicorn/server/domain/member/port/in/{LogoutInPort.kt => MemberLogoutInPort.kt} (55%) rename src/main/kotlin/com/unicorn/server/domain/member/port/in/{CompleteOnboardingInPort.kt => MemberOnboardingCompleteInPort.kt} (86%) rename src/main/kotlin/com/unicorn/server/domain/member/port/in/{GetOnboardingInfoInPort.kt => MemberOnboardingInfoInPort.kt} (82%) rename src/main/kotlin/com/unicorn/server/domain/member/port/in/{UploadProfileImageInPort.kt => MemberProfileImageUploadInPort.kt} (68%) rename src/main/kotlin/com/unicorn/server/domain/member/port/in/{GetMemberProfileInPort.kt => MemberProfileInPort.kt} (83%) rename src/main/kotlin/com/unicorn/server/domain/member/port/in/{UpdateProfileInPort.kt => MemberProfileUpdateInPort.kt} (70%) rename src/main/kotlin/com/unicorn/server/domain/member/port/in/{SocialLoginInPort.kt => MemberSocialLoginInPort.kt} (76%) rename src/main/kotlin/com/unicorn/server/domain/member/port/in/{UpdateMemberStateInPort.kt => MemberStateUpdateInPort.kt} (82%) rename src/main/kotlin/com/unicorn/server/domain/member/port/in/{ReissueTokenInPort.kt => MemberTokenReissueInPort.kt} (50%) rename src/main/kotlin/com/unicorn/server/domain/member/port/in/{WithdrawMemberInPort.kt => MemberWithdrawInPort.kt} (63%) rename src/main/kotlin/com/unicorn/server/domain/member/port/out/{KakaoAuthPort.kt => MemberKakaoAuthOutPort.kt} (50%) rename src/main/kotlin/com/unicorn/server/domain/member/port/out/{TokenIssuer.kt => MemberTokenIssueOutPort.kt} (68%) rename src/main/kotlin/com/unicorn/server/domain/member/port/out/{TokenStore.kt => MemberTokenStoreOutPort.kt} (82%) rename src/main/kotlin/com/unicorn/server/domain/notification/port/out/{NotificationMessageComposer.kt => NotificationMessageComposeOutPort.kt} (88%) rename src/main/kotlin/com/unicorn/server/domain/notification/port/out/{NotificationSender.kt => NotificationSendOutPort.kt} (88%) rename src/main/kotlin/com/unicorn/server/domain/schedule/port/in/{CancelConfirmationInPort.kt => ScheduleConfirmationCancelInPort.kt} (82%) rename src/main/kotlin/com/unicorn/server/domain/schedule/port/in/{RegisterConfirmationInPort.kt => ScheduleConfirmationRegisterInPort.kt} (85%) rename src/main/kotlin/com/unicorn/server/domain/schedule/port/in/{CreateScheduleInPort.kt => ScheduleCreateInPort.kt} (88%) rename src/main/kotlin/com/unicorn/server/domain/schedule/port/in/{DeleteScheduleInPort.kt => ScheduleDeleteInPort.kt} (84%) rename src/main/kotlin/com/unicorn/server/domain/schedule/port/in/{GetScheduleDetailInPort.kt => ScheduleDetailInPort.kt} (88%) rename src/main/kotlin/com/unicorn/server/domain/schedule/port/in/{RequestFamilyScheduleNotificationInPort.kt => ScheduleFamilyNotificationRequestInPort.kt} (80%) rename src/main/kotlin/com/unicorn/server/domain/schedule/port/in/{GetSchedulesForCircleInPort.kt => ScheduleForCircleInPort.kt} (93%) rename src/main/kotlin/com/unicorn/server/domain/schedule/port/in/{GetScheduleListInPort.kt => ScheduleListInPort.kt} (87%) rename src/main/kotlin/com/unicorn/server/domain/schedule/port/in/{UpdateScheduleInPort.kt => ScheduleUpdateInPort.kt} (84%) rename src/main/kotlin/com/unicorn/server/domain/term/port/in/{GetActiveTermsInPort.kt => TermActiveInPort.kt} (92%) rename src/main/kotlin/com/unicorn/server/domain/term/port/in/{AgreeTermsInPort.kt => TermAgreeInPort.kt} (84%) diff --git a/src/main/kotlin/com/unicorn/server/common/port/out/alert/ErrorAlertPort.kt b/src/main/kotlin/com/unicorn/server/common/port/out/alert/AlertOutPort.kt similarity index 76% rename from src/main/kotlin/com/unicorn/server/common/port/out/alert/ErrorAlertPort.kt rename to src/main/kotlin/com/unicorn/server/common/port/out/alert/AlertOutPort.kt index 72c4ede..73d646d 100644 --- a/src/main/kotlin/com/unicorn/server/common/port/out/alert/ErrorAlertPort.kt +++ b/src/main/kotlin/com/unicorn/server/common/port/out/alert/AlertOutPort.kt @@ -1,5 +1,5 @@ package com.unicorn.server.common.port.out.alert -interface ErrorAlertPort { +interface AlertOutPort { fun sendErrorAlert(e: Exception) } diff --git a/src/main/kotlin/com/unicorn/server/common/port/out/event/EventPublisher.kt b/src/main/kotlin/com/unicorn/server/common/port/out/event/EventOutPort.kt similarity index 82% rename from src/main/kotlin/com/unicorn/server/common/port/out/event/EventPublisher.kt rename to src/main/kotlin/com/unicorn/server/common/port/out/event/EventOutPort.kt index e0b32f5..89bae1a 100644 --- a/src/main/kotlin/com/unicorn/server/common/port/out/event/EventPublisher.kt +++ b/src/main/kotlin/com/unicorn/server/common/port/out/event/EventOutPort.kt @@ -2,6 +2,6 @@ package com.unicorn.server.common.port.out.event import com.unicorn.server.common.domain.Event -interface EventPublisher { +interface EventOutPort { fun publish(event: Event) } diff --git a/src/main/kotlin/com/unicorn/server/common/port/out/storage/ObjectStorage.kt b/src/main/kotlin/com/unicorn/server/common/port/out/storage/ObjectStorageOutPort.kt similarity index 86% rename from src/main/kotlin/com/unicorn/server/common/port/out/storage/ObjectStorage.kt rename to src/main/kotlin/com/unicorn/server/common/port/out/storage/ObjectStorageOutPort.kt index 9176a10..744e48d 100644 --- a/src/main/kotlin/com/unicorn/server/common/port/out/storage/ObjectStorage.kt +++ b/src/main/kotlin/com/unicorn/server/common/port/out/storage/ObjectStorageOutPort.kt @@ -4,13 +4,13 @@ package com.unicorn.server.common.port.out.storage * Object storage(S3, R2 등)를 다루는 인터페이스. * * HTTP 요청에서 받은 MultipartFile은 web adapter/service 계층에서 ObjectUploadCommand로 변환해서 넘긴다. - * 어떤 Storage를 쓸 지, 스펙( url, bucket 정보 )가 정해지면 ObjectStorage를 구현하는 Adapter를 작성합니다 + * 어떤 Storage를 쓸 지, 스펙( url, bucket 정보 )가 정해지면 ObjectStorageOutPort를 구현하는 Adapter를 작성합니다 * * 이 인터페이스는 파일 유형(이미지/HTML 등)을 모른다. 허용 contentType/최대 용량 검증과 objectKey 생성은 * 호출하는 use-case가 ObjectType으로 미리 끝내고, 이미 검증된 값만 ObjectUploadCommand에 담아 넘긴다. * */ -interface ObjectStorage { +interface ObjectStorageOutPort { fun upload(command: ObjectUploadCommand): StoredObject fun delete(objectKey: String) diff --git a/src/main/kotlin/com/unicorn/server/common/port/out/storage/ObjectType.kt b/src/main/kotlin/com/unicorn/server/common/port/out/storage/ObjectType.kt index 2b8a9eb..8eec944 100644 --- a/src/main/kotlin/com/unicorn/server/common/port/out/storage/ObjectType.kt +++ b/src/main/kotlin/com/unicorn/server/common/port/out/storage/ObjectType.kt @@ -7,9 +7,9 @@ import java.util.UUID /** * 업로드 가능한 파일 유형과 유형별 정책(허용 contentType, 최대 용량, 저장 경로 prefix)을 표현하는 값 객체. * - * ObjectStorage/어댑터(S3 등)는 이 타입을 모른다. 업로드를 요청하는 use-case가 ObjectUploadCommand를 + * ObjectStorageOutPort/어댑터(S3 등)는 이 타입을 모른다. 업로드를 요청하는 use-case가 ObjectUploadCommand를 * 만들기 전에 validate()로 검증하고 generateObjectKey()로 objectKey를 만들어야 한다. - * 새 파일 유형이 필요하면 이 enum에 상수만 추가하면 되고, ObjectStorage 쪽은 수정할 필요가 없다. + * 새 파일 유형이 필요하면 이 enum에 상수만 추가하면 되고, ObjectStorageOutPort 쪽은 수정할 필요가 없다. */ enum class ObjectType( val allowedContentTypes: Set, diff --git a/src/main/kotlin/com/unicorn/server/domain/circle/service/CircleService.kt b/src/main/kotlin/com/unicorn/server/domain/circle/service/CircleService.kt index d1aa237..00abfbf 100644 --- a/src/main/kotlin/com/unicorn/server/domain/circle/service/CircleService.kt +++ b/src/main/kotlin/com/unicorn/server/domain/circle/service/CircleService.kt @@ -1,7 +1,7 @@ package com.unicorn.server.domain.circle.service import com.unicorn.server.common.exception.BusinessException -import com.unicorn.server.common.port.out.event.EventPublisher +import com.unicorn.server.common.port.out.event.EventOutPort import com.unicorn.server.domain.circle.Circle import com.unicorn.server.domain.circle.CircleMember import com.unicorn.server.domain.circle.enums.CircleMemberStatus @@ -24,7 +24,7 @@ import com.unicorn.server.domain.circle.port.out.CircleMemberOutPort import com.unicorn.server.domain.circle.port.out.CircleOutPort import com.unicorn.server.domain.circle.vo.CircleId import com.unicorn.server.domain.member.exception.MemberNotFoundException -import com.unicorn.server.domain.member.port.`in`.GetMemberProfileInPort +import com.unicorn.server.domain.member.port.`in`.MemberProfileInPort import com.unicorn.server.domain.member.vo.MemberId import org.springframework.stereotype.Service import org.springframework.transaction.annotation.Transactional @@ -36,8 +36,8 @@ class CircleService( private val circleMemberOutPort: CircleMemberOutPort, private val circleIdGenerator: CircleIdGenerator, private val circleMemberIdGenerator: CircleMemberIdGenerator, - private val getMemberProfileInPort: GetMemberProfileInPort, - private val eventPublisher: EventPublisher, + private val getMemberProfileInPort: MemberProfileInPort, + private val eventPublisher: EventOutPort, ) : CircleInPort, CircleMemberInPort { override fun create(memberId: String, command: CreateCircleCommand): CircleSummary { val owner = getMemberProfileInPort.getMemberProfile(memberId) ?: throw MemberNotFoundException(memberId) diff --git a/src/main/kotlin/com/unicorn/server/domain/home/service/HomeQueryService.kt b/src/main/kotlin/com/unicorn/server/domain/home/service/HomeQueryService.kt index 24e130d..31e4de0 100644 --- a/src/main/kotlin/com/unicorn/server/domain/home/service/HomeQueryService.kt +++ b/src/main/kotlin/com/unicorn/server/domain/home/service/HomeQueryService.kt @@ -9,8 +9,8 @@ import com.unicorn.server.domain.home.port.dto.HomeCircleDto import com.unicorn.server.domain.home.port.dto.HomeMemberDto import com.unicorn.server.domain.home.port.dto.HomeView import com.unicorn.server.domain.home.port.`in`.HomeQueryInPort -import com.unicorn.server.domain.member.port.`in`.GetMemberProfileInPort -import com.unicorn.server.domain.schedule.port.`in`.GetSchedulesForCircleInPort +import com.unicorn.server.domain.member.port.`in`.MemberProfileInPort +import com.unicorn.server.domain.schedule.port.`in`.ScheduleForCircleInPort import org.springframework.stereotype.Service import org.springframework.transaction.annotation.Transactional import java.time.LocalDate @@ -20,8 +20,8 @@ import java.time.LocalDate class HomeQueryService( private val circleInPort: CircleInPort, private val circleMemberInPort: CircleMemberInPort, - private val getMemberProfileInPort: GetMemberProfileInPort, - private val scheduleQueryInPort: GetSchedulesForCircleInPort, + private val getMemberProfileInPort: MemberProfileInPort, + private val scheduleQueryInPort: ScheduleForCircleInPort, ) : HomeQueryInPort { override fun getHome(circleId: String, requesterId: String): HomeView { val circle = circleInPort.getCircleSummary(circleId) diff --git a/src/main/kotlin/com/unicorn/server/domain/invitation/port/in/AcceptCircleInvitationInPort.kt b/src/main/kotlin/com/unicorn/server/domain/invitation/port/in/InvitationAcceptInPort.kt similarity index 81% rename from src/main/kotlin/com/unicorn/server/domain/invitation/port/in/AcceptCircleInvitationInPort.kt rename to src/main/kotlin/com/unicorn/server/domain/invitation/port/in/InvitationAcceptInPort.kt index 7c1e7da..96b567c 100644 --- a/src/main/kotlin/com/unicorn/server/domain/invitation/port/in/AcceptCircleInvitationInPort.kt +++ b/src/main/kotlin/com/unicorn/server/domain/invitation/port/in/InvitationAcceptInPort.kt @@ -2,6 +2,6 @@ package com.unicorn.server.domain.invitation.port.`in` import com.unicorn.server.domain.invitation.port.dto.AcceptResult -interface AcceptCircleInvitationInPort { +interface InvitationAcceptInPort { fun accept(token: String, memberId: String): AcceptResult } diff --git a/src/main/kotlin/com/unicorn/server/domain/invitation/port/in/GetInvitationByTokenInPort.kt b/src/main/kotlin/com/unicorn/server/domain/invitation/port/in/InvitationByTokenInPort.kt similarity index 82% rename from src/main/kotlin/com/unicorn/server/domain/invitation/port/in/GetInvitationByTokenInPort.kt rename to src/main/kotlin/com/unicorn/server/domain/invitation/port/in/InvitationByTokenInPort.kt index 1fb5985..447e8f9 100644 --- a/src/main/kotlin/com/unicorn/server/domain/invitation/port/in/GetInvitationByTokenInPort.kt +++ b/src/main/kotlin/com/unicorn/server/domain/invitation/port/in/InvitationByTokenInPort.kt @@ -2,6 +2,6 @@ package com.unicorn.server.domain.invitation.port.`in` import com.unicorn.server.domain.invitation.port.dto.InvitationDetailView -interface GetInvitationByTokenInPort { +interface InvitationByTokenInPort { fun getByToken(token: String): InvitationDetailView } diff --git a/src/main/kotlin/com/unicorn/server/domain/invitation/port/in/DispatchInvitationInPort.kt b/src/main/kotlin/com/unicorn/server/domain/invitation/port/in/InvitationDispatchInPort.kt similarity index 83% rename from src/main/kotlin/com/unicorn/server/domain/invitation/port/in/DispatchInvitationInPort.kt rename to src/main/kotlin/com/unicorn/server/domain/invitation/port/in/InvitationDispatchInPort.kt index 072de6c..ebb552d 100644 --- a/src/main/kotlin/com/unicorn/server/domain/invitation/port/in/DispatchInvitationInPort.kt +++ b/src/main/kotlin/com/unicorn/server/domain/invitation/port/in/InvitationDispatchInPort.kt @@ -2,6 +2,6 @@ package com.unicorn.server.domain.invitation.port.`in` import com.unicorn.server.domain.invitation.port.dto.DispatchInvitationCommand -interface DispatchInvitationInPort { +interface InvitationDispatchInPort { fun dispatch(command: DispatchInvitationCommand) } diff --git a/src/main/kotlin/com/unicorn/server/domain/invitation/port/in/IssueInvitationInPort.kt b/src/main/kotlin/com/unicorn/server/domain/invitation/port/in/InvitationIssueInPort.kt similarity index 89% rename from src/main/kotlin/com/unicorn/server/domain/invitation/port/in/IssueInvitationInPort.kt rename to src/main/kotlin/com/unicorn/server/domain/invitation/port/in/InvitationIssueInPort.kt index d83e57a..4e3cd49 100644 --- a/src/main/kotlin/com/unicorn/server/domain/invitation/port/in/IssueInvitationInPort.kt +++ b/src/main/kotlin/com/unicorn/server/domain/invitation/port/in/InvitationIssueInPort.kt @@ -3,6 +3,6 @@ package com.unicorn.server.domain.invitation.port.`in` import com.unicorn.server.domain.invitation.port.dto.IssueInvitationCommand import com.unicorn.server.domain.invitation.port.dto.IssuedInvitationResult -interface IssueInvitationInPort { +interface InvitationIssueInPort { fun issue(inviterMemberId: String, command: IssueInvitationCommand): IssuedInvitationResult } diff --git a/src/main/kotlin/com/unicorn/server/domain/invitation/service/InvitationService.kt b/src/main/kotlin/com/unicorn/server/domain/invitation/service/InvitationService.kt index 4291042..919767b 100644 --- a/src/main/kotlin/com/unicorn/server/domain/invitation/service/InvitationService.kt +++ b/src/main/kotlin/com/unicorn/server/domain/invitation/service/InvitationService.kt @@ -1,7 +1,7 @@ package com.unicorn.server.domain.invitation.service import com.unicorn.server.common.exception.BusinessException -import com.unicorn.server.common.port.out.event.EventPublisher +import com.unicorn.server.common.port.out.event.EventOutPort import com.unicorn.server.domain.circle.exception.CircleErrorCode import com.unicorn.server.domain.circle.port.`in`.CircleInPort import com.unicorn.server.domain.circle.port.`in`.CircleMemberInPort @@ -20,17 +20,17 @@ import com.unicorn.server.domain.invitation.port.dto.DispatchInvitationCommand import com.unicorn.server.domain.invitation.port.dto.InvitationDetailView import com.unicorn.server.domain.invitation.port.dto.IssueInvitationCommand import com.unicorn.server.domain.invitation.port.dto.IssuedInvitationResult -import com.unicorn.server.domain.invitation.port.`in`.AcceptCircleInvitationInPort -import com.unicorn.server.domain.invitation.port.`in`.DispatchInvitationInPort -import com.unicorn.server.domain.invitation.port.`in`.GetInvitationByTokenInPort -import com.unicorn.server.domain.invitation.port.`in`.IssueInvitationInPort +import com.unicorn.server.domain.invitation.port.`in`.InvitationAcceptInPort +import com.unicorn.server.domain.invitation.port.`in`.InvitationDispatchInPort +import com.unicorn.server.domain.invitation.port.`in`.InvitationByTokenInPort +import com.unicorn.server.domain.invitation.port.`in`.InvitationIssueInPort import com.unicorn.server.domain.invitation.port.out.InvitationOutPort import com.unicorn.server.domain.invitation.port.out.InvitationIdGenerator import com.unicorn.server.domain.invitation.port.out.InvitationTokenGenerator import com.unicorn.server.domain.invitation.vo.InvitationId import com.unicorn.server.domain.invitation.vo.InvitationToken import com.unicorn.server.domain.member.exception.MemberNotFoundException -import com.unicorn.server.domain.member.port.`in`.GetMemberProfileInPort +import com.unicorn.server.domain.member.port.`in`.MemberProfileInPort import com.unicorn.server.domain.member.vo.MemberId import org.springframework.stereotype.Service import org.springframework.transaction.annotation.Transactional @@ -44,9 +44,9 @@ class InvitationService( private val tokenGenerator: InvitationTokenGenerator, private val circleInPort: CircleInPort, private val circleMemberInPort: CircleMemberInPort, - private val getMemberProfileInPort: GetMemberProfileInPort, - private val eventPublisher: EventPublisher, -) : IssueInvitationInPort, DispatchInvitationInPort, GetInvitationByTokenInPort, AcceptCircleInvitationInPort { + private val getMemberProfileInPort: MemberProfileInPort, + private val eventPublisher: EventOutPort, +) : InvitationIssueInPort, InvitationDispatchInPort, InvitationByTokenInPort, InvitationAcceptInPort { override fun issue(inviterMemberId: String, command: IssueInvitationCommand): IssuedInvitationResult { val type = InvitationType.CIRCLE circleInPort.getCircleSummary(command.targetId) diff --git a/src/main/kotlin/com/unicorn/server/domain/member/port/in/GetMemberInPort.kt b/src/main/kotlin/com/unicorn/server/domain/member/port/in/MemberInPort.kt similarity index 64% rename from src/main/kotlin/com/unicorn/server/domain/member/port/in/GetMemberInPort.kt rename to src/main/kotlin/com/unicorn/server/domain/member/port/in/MemberInPort.kt index cafb9bb..bc2819f 100644 --- a/src/main/kotlin/com/unicorn/server/domain/member/port/in/GetMemberInPort.kt +++ b/src/main/kotlin/com/unicorn/server/domain/member/port/in/MemberInPort.kt @@ -2,8 +2,8 @@ package com.unicorn.server.domain.member.port.`in` import com.unicorn.server.domain.member.Member -// GetMemberInPort - 멤버 조회 유스케이스 진입점을 정의한다. -interface GetMemberInPort { +// MemberInPort - 멤버 조회 유스케이스 진입점을 정의한다. +interface MemberInPort { // 멤버 식별자로 멤버를 조회한다. fun getById(memberId: String): Member } diff --git a/src/main/kotlin/com/unicorn/server/domain/member/port/in/KakaoLoginInPort.kt b/src/main/kotlin/com/unicorn/server/domain/member/port/in/MemberKakaoLoginInPort.kt similarity index 54% rename from src/main/kotlin/com/unicorn/server/domain/member/port/in/KakaoLoginInPort.kt rename to src/main/kotlin/com/unicorn/server/domain/member/port/in/MemberKakaoLoginInPort.kt index 9e4ad65..1893ae9 100644 --- a/src/main/kotlin/com/unicorn/server/domain/member/port/in/KakaoLoginInPort.kt +++ b/src/main/kotlin/com/unicorn/server/domain/member/port/in/MemberKakaoLoginInPort.kt @@ -2,7 +2,7 @@ package com.unicorn.server.domain.member.port.`in` import com.unicorn.server.domain.member.port.dto.SocialLoginResult -// KakaoLoginInPort - 카카오 ID Token 기반 로그인 유스케이스 진입점을 정의한다. -interface KakaoLoginInPort { +// MemberKakaoLoginInPort - 카카오 ID Token 기반 로그인 유스케이스 진입점을 정의한다. +interface MemberKakaoLoginInPort { fun kakaoLogin(idToken: String): SocialLoginResult } \ No newline at end of file diff --git a/src/main/kotlin/com/unicorn/server/domain/member/port/in/LogoutInPort.kt b/src/main/kotlin/com/unicorn/server/domain/member/port/in/MemberLogoutInPort.kt similarity index 55% rename from src/main/kotlin/com/unicorn/server/domain/member/port/in/LogoutInPort.kt rename to src/main/kotlin/com/unicorn/server/domain/member/port/in/MemberLogoutInPort.kt index 81a821b..575f677 100644 --- a/src/main/kotlin/com/unicorn/server/domain/member/port/in/LogoutInPort.kt +++ b/src/main/kotlin/com/unicorn/server/domain/member/port/in/MemberLogoutInPort.kt @@ -1,7 +1,7 @@ package com.unicorn.server.domain.member.port.`in` -// LogoutInPort - 로그아웃 유스케이스 진입점을 정의한다. -interface LogoutInPort { +// MemberLogoutInPort - 로그아웃 유스케이스 진입점을 정의한다. +interface MemberLogoutInPort { // 멤버의 현재 인증 세션을 무효화한다. fun logout(memberId: String) } diff --git a/src/main/kotlin/com/unicorn/server/domain/member/port/in/CompleteOnboardingInPort.kt b/src/main/kotlin/com/unicorn/server/domain/member/port/in/MemberOnboardingCompleteInPort.kt similarity index 86% rename from src/main/kotlin/com/unicorn/server/domain/member/port/in/CompleteOnboardingInPort.kt rename to src/main/kotlin/com/unicorn/server/domain/member/port/in/MemberOnboardingCompleteInPort.kt index 5594468..a036ea8 100644 --- a/src/main/kotlin/com/unicorn/server/domain/member/port/in/CompleteOnboardingInPort.kt +++ b/src/main/kotlin/com/unicorn/server/domain/member/port/in/MemberOnboardingCompleteInPort.kt @@ -3,6 +3,6 @@ package com.unicorn.server.domain.member.port.`in` import com.unicorn.server.domain.member.port.dto.CompleteOnboardingCommand import com.unicorn.server.domain.member.port.dto.TokenPair -interface CompleteOnboardingInPort { +interface MemberOnboardingCompleteInPort { fun completeOnboarding(memberId: String, command: CompleteOnboardingCommand): TokenPair } diff --git a/src/main/kotlin/com/unicorn/server/domain/member/port/in/GetOnboardingInfoInPort.kt b/src/main/kotlin/com/unicorn/server/domain/member/port/in/MemberOnboardingInfoInPort.kt similarity index 82% rename from src/main/kotlin/com/unicorn/server/domain/member/port/in/GetOnboardingInfoInPort.kt rename to src/main/kotlin/com/unicorn/server/domain/member/port/in/MemberOnboardingInfoInPort.kt index 53c4d3d..aa873ea 100644 --- a/src/main/kotlin/com/unicorn/server/domain/member/port/in/GetOnboardingInfoInPort.kt +++ b/src/main/kotlin/com/unicorn/server/domain/member/port/in/MemberOnboardingInfoInPort.kt @@ -2,6 +2,6 @@ package com.unicorn.server.domain.member.port.`in` import com.unicorn.server.domain.member.port.dto.OnboardingInfoResult -interface GetOnboardingInfoInPort { +interface MemberOnboardingInfoInPort { fun getOnboardingInfo(memberId: String): OnboardingInfoResult } diff --git a/src/main/kotlin/com/unicorn/server/domain/member/port/in/UploadProfileImageInPort.kt b/src/main/kotlin/com/unicorn/server/domain/member/port/in/MemberProfileImageUploadInPort.kt similarity index 68% rename from src/main/kotlin/com/unicorn/server/domain/member/port/in/UploadProfileImageInPort.kt rename to src/main/kotlin/com/unicorn/server/domain/member/port/in/MemberProfileImageUploadInPort.kt index da9452b..fc3dc03 100644 --- a/src/main/kotlin/com/unicorn/server/domain/member/port/in/UploadProfileImageInPort.kt +++ b/src/main/kotlin/com/unicorn/server/domain/member/port/in/MemberProfileImageUploadInPort.kt @@ -3,8 +3,8 @@ package com.unicorn.server.domain.member.port.`in` import com.unicorn.server.domain.member.Member import com.unicorn.server.domain.member.port.dto.UploadProfileImageCommand -// UploadProfileImageInPort - 멤버 프로필 이미지 업로드 유스케이스 진입점을 정의한다. -interface UploadProfileImageInPort { +// MemberProfileImageUploadInPort - 멤버 프로필 이미지 업로드 유스케이스 진입점을 정의한다. +interface MemberProfileImageUploadInPort { // 멤버 식별자와 업로드 명령으로 프로필 이미지를 갱신한다. fun uploadProfileImage(memberId: String, command: UploadProfileImageCommand): Member } diff --git a/src/main/kotlin/com/unicorn/server/domain/member/port/in/GetMemberProfileInPort.kt b/src/main/kotlin/com/unicorn/server/domain/member/port/in/MemberProfileInPort.kt similarity index 83% rename from src/main/kotlin/com/unicorn/server/domain/member/port/in/GetMemberProfileInPort.kt rename to src/main/kotlin/com/unicorn/server/domain/member/port/in/MemberProfileInPort.kt index e7888fb..15460e4 100644 --- a/src/main/kotlin/com/unicorn/server/domain/member/port/in/GetMemberProfileInPort.kt +++ b/src/main/kotlin/com/unicorn/server/domain/member/port/in/MemberProfileInPort.kt @@ -2,6 +2,6 @@ package com.unicorn.server.domain.member.port.`in` import com.unicorn.server.domain.member.port.dto.MemberProfileDto -interface GetMemberProfileInPort { +interface MemberProfileInPort { fun getMemberProfile(memberId: String): MemberProfileDto? } diff --git a/src/main/kotlin/com/unicorn/server/domain/member/port/in/UpdateProfileInPort.kt b/src/main/kotlin/com/unicorn/server/domain/member/port/in/MemberProfileUpdateInPort.kt similarity index 70% rename from src/main/kotlin/com/unicorn/server/domain/member/port/in/UpdateProfileInPort.kt rename to src/main/kotlin/com/unicorn/server/domain/member/port/in/MemberProfileUpdateInPort.kt index 46201d0..cc93ed5 100644 --- a/src/main/kotlin/com/unicorn/server/domain/member/port/in/UpdateProfileInPort.kt +++ b/src/main/kotlin/com/unicorn/server/domain/member/port/in/MemberProfileUpdateInPort.kt @@ -3,8 +3,8 @@ package com.unicorn.server.domain.member.port.`in` import com.unicorn.server.domain.member.Member import com.unicorn.server.domain.member.port.dto.UpdateProfileCommand -// UpdateProfileInPort - 멤버 프로필 변경 유스케이스 진입점을 정의한다. -interface UpdateProfileInPort { +// MemberProfileUpdateInPort - 멤버 프로필 변경 유스케이스 진입점을 정의한다. +interface MemberProfileUpdateInPort { // 멤버 식별자와 변경 명령으로 프로필을 갱신한다. fun updateProfile(memberId: String, command: UpdateProfileCommand): Member } diff --git a/src/main/kotlin/com/unicorn/server/domain/member/port/in/SocialLoginInPort.kt b/src/main/kotlin/com/unicorn/server/domain/member/port/in/MemberSocialLoginInPort.kt similarity index 76% rename from src/main/kotlin/com/unicorn/server/domain/member/port/in/SocialLoginInPort.kt rename to src/main/kotlin/com/unicorn/server/domain/member/port/in/MemberSocialLoginInPort.kt index 245ff3c..c4c565a 100644 --- a/src/main/kotlin/com/unicorn/server/domain/member/port/in/SocialLoginInPort.kt +++ b/src/main/kotlin/com/unicorn/server/domain/member/port/in/MemberSocialLoginInPort.kt @@ -3,9 +3,9 @@ package com.unicorn.server.domain.member.port.`in` import com.unicorn.server.domain.member.port.dto.SocialLoginCommand import com.unicorn.server.domain.member.port.dto.SocialLoginResult -// SocialLoginInPort - 플랫폼별 토큰 검증 이후 공통 소셜 로그인 처리를 정의한다. +// MemberSocialLoginInPort - 플랫폼별 토큰 검증 이후 공통 소셜 로그인 처리를 정의한다. // 현재 HTTP 진입점은 KakaoLoginInPort이며, 이 포트는 테스트와 플랫폼 확장 시 공통 흐름 검증에 사용한다. -interface SocialLoginInPort { +interface MemberSocialLoginInPort { // 검증된 소셜 로그인 명령을 처리하고 서비스 토큰을 발급한다. fun login(command: SocialLoginCommand): SocialLoginResult } diff --git a/src/main/kotlin/com/unicorn/server/domain/member/port/in/UpdateMemberStateInPort.kt b/src/main/kotlin/com/unicorn/server/domain/member/port/in/MemberStateUpdateInPort.kt similarity index 82% rename from src/main/kotlin/com/unicorn/server/domain/member/port/in/UpdateMemberStateInPort.kt rename to src/main/kotlin/com/unicorn/server/domain/member/port/in/MemberStateUpdateInPort.kt index e92c829..a727216 100644 --- a/src/main/kotlin/com/unicorn/server/domain/member/port/in/UpdateMemberStateInPort.kt +++ b/src/main/kotlin/com/unicorn/server/domain/member/port/in/MemberStateUpdateInPort.kt @@ -4,8 +4,8 @@ import com.unicorn.server.domain.member.Member import com.unicorn.server.domain.member.enums.MemberStatus import com.unicorn.server.domain.member.enums.Role -// UpdateMemberStateInPort - 임시로 멤버 상태/역할을 강제 변경하는 유스케이스 진입점을 정의한다. -interface UpdateMemberStateInPort { +// MemberStateUpdateInPort - 임시로 멤버 상태/역할을 강제 변경하는 유스케이스 진입점을 정의한다. +interface MemberStateUpdateInPort { // 멤버 식별자로 상태와 역할을 강제 변경한다. null인 필드는 변경하지 않는다. fun updateMemberState(memberId: String, status: MemberStatus?, role: Role?): Member } diff --git a/src/main/kotlin/com/unicorn/server/domain/member/port/in/ReissueTokenInPort.kt b/src/main/kotlin/com/unicorn/server/domain/member/port/in/MemberTokenReissueInPort.kt similarity index 50% rename from src/main/kotlin/com/unicorn/server/domain/member/port/in/ReissueTokenInPort.kt rename to src/main/kotlin/com/unicorn/server/domain/member/port/in/MemberTokenReissueInPort.kt index badcf53..cd2f889 100644 --- a/src/main/kotlin/com/unicorn/server/domain/member/port/in/ReissueTokenInPort.kt +++ b/src/main/kotlin/com/unicorn/server/domain/member/port/in/MemberTokenReissueInPort.kt @@ -2,7 +2,7 @@ package com.unicorn.server.domain.member.port.`in` import com.unicorn.server.domain.member.port.dto.TokenPair -// ReissueTokenInPort - refresh token으로 새 인증 토큰 쌍을 발급하는 유스케이스를 정의한다. -interface ReissueTokenInPort { +// MemberTokenReissueInPort - refresh token으로 새 인증 토큰 쌍을 발급하는 유스케이스를 정의한다. +interface MemberTokenReissueInPort { fun reissue(refreshToken: String): TokenPair } diff --git a/src/main/kotlin/com/unicorn/server/domain/member/port/in/WithdrawMemberInPort.kt b/src/main/kotlin/com/unicorn/server/domain/member/port/in/MemberWithdrawInPort.kt similarity index 63% rename from src/main/kotlin/com/unicorn/server/domain/member/port/in/WithdrawMemberInPort.kt rename to src/main/kotlin/com/unicorn/server/domain/member/port/in/MemberWithdrawInPort.kt index 4769922..a79c9b6 100644 --- a/src/main/kotlin/com/unicorn/server/domain/member/port/in/WithdrawMemberInPort.kt +++ b/src/main/kotlin/com/unicorn/server/domain/member/port/in/MemberWithdrawInPort.kt @@ -1,7 +1,7 @@ package com.unicorn.server.domain.member.port.`in` -// WithdrawMemberInPort - 회원탈퇴 유스케이스 진입점을 정의한다. -interface WithdrawMemberInPort { +// MemberWithdrawInPort - 회원탈퇴 유스케이스 진입점을 정의한다. +interface MemberWithdrawInPort { // 멤버를 soft delete 상태로 전환한다. fun withdraw(memberId: String, reason: String) } diff --git a/src/main/kotlin/com/unicorn/server/domain/member/port/out/KakaoAuthPort.kt b/src/main/kotlin/com/unicorn/server/domain/member/port/out/MemberKakaoAuthOutPort.kt similarity index 50% rename from src/main/kotlin/com/unicorn/server/domain/member/port/out/KakaoAuthPort.kt rename to src/main/kotlin/com/unicorn/server/domain/member/port/out/MemberKakaoAuthOutPort.kt index 4d49270..fd523d3 100644 --- a/src/main/kotlin/com/unicorn/server/domain/member/port/out/KakaoAuthPort.kt +++ b/src/main/kotlin/com/unicorn/server/domain/member/port/out/MemberKakaoAuthOutPort.kt @@ -2,7 +2,7 @@ package com.unicorn.server.domain.member.port.out import com.unicorn.server.domain.member.port.dto.KakaoUserInfo -// KakaoAuthPort - 카카오 ID Token을 검증하고 사용자 정보를 반환하는 포트를 정의한다. -interface KakaoAuthPort { +// MemberKakaoAuthOutPort - 카카오 ID Token을 검증하고 사용자 정보를 반환하는 포트를 정의한다. +interface MemberKakaoAuthOutPort { fun verify(idToken: String): KakaoUserInfo } diff --git a/src/main/kotlin/com/unicorn/server/domain/member/port/out/TokenIssuer.kt b/src/main/kotlin/com/unicorn/server/domain/member/port/out/MemberTokenIssueOutPort.kt similarity index 68% rename from src/main/kotlin/com/unicorn/server/domain/member/port/out/TokenIssuer.kt rename to src/main/kotlin/com/unicorn/server/domain/member/port/out/MemberTokenIssueOutPort.kt index a1469e0..44d63f1 100644 --- a/src/main/kotlin/com/unicorn/server/domain/member/port/out/TokenIssuer.kt +++ b/src/main/kotlin/com/unicorn/server/domain/member/port/out/MemberTokenIssueOutPort.kt @@ -3,8 +3,8 @@ package com.unicorn.server.domain.member.port.out import com.unicorn.server.domain.member.enums.Role import com.unicorn.server.domain.member.port.dto.TokenPair -// TokenIssuer - 멤버 인증 토큰을 발급하는 포트를 정의한다. -interface TokenIssuer { +// MemberTokenIssueOutPort - 멤버 인증 토큰을 발급하는 포트를 정의한다. +interface MemberTokenIssueOutPort { fun issue(memberId: String, role: Role): TokenPair fun parseRefreshToken(refreshToken: String): String? } diff --git a/src/main/kotlin/com/unicorn/server/domain/member/port/out/TokenStore.kt b/src/main/kotlin/com/unicorn/server/domain/member/port/out/MemberTokenStoreOutPort.kt similarity index 82% rename from src/main/kotlin/com/unicorn/server/domain/member/port/out/TokenStore.kt rename to src/main/kotlin/com/unicorn/server/domain/member/port/out/MemberTokenStoreOutPort.kt index 49ccff0..76cf1ee 100644 --- a/src/main/kotlin/com/unicorn/server/domain/member/port/out/TokenStore.kt +++ b/src/main/kotlin/com/unicorn/server/domain/member/port/out/MemberTokenStoreOutPort.kt @@ -1,8 +1,8 @@ package com.unicorn.server.domain.member.port.out -// TokenStore - refresh token 저장소 포트를 정의한다. +// MemberTokenStoreOutPort - refresh token 저장소 포트를 정의한다. // 로그아웃 시 삭제, 로그인 시 저장, 재발급 시 조회한다. -interface TokenStore { +interface MemberTokenStoreOutPort { // 멤버의 refresh token을 저장한다. // 이미 저장된 refresh token이 있다면 findMemberIdByRefreshToken으로 더 이상 // 조회되지 않도록 역방향 매핑까지 함께 무효화한 뒤 새 토큰으로 교체해야 한다. diff --git a/src/main/kotlin/com/unicorn/server/domain/member/service/MemberAuthService.kt b/src/main/kotlin/com/unicorn/server/domain/member/service/MemberAuthService.kt index ef40d3d..7ba827f 100644 --- a/src/main/kotlin/com/unicorn/server/domain/member/service/MemberAuthService.kt +++ b/src/main/kotlin/com/unicorn/server/domain/member/service/MemberAuthService.kt @@ -8,16 +8,16 @@ import com.unicorn.server.domain.member.exception.DuplicateEmailException import com.unicorn.server.domain.member.exception.InvalidRefreshTokenException import com.unicorn.server.domain.member.exception.MemberNotFoundException import com.unicorn.server.domain.member.exception.WithdrawnMemberException -import com.unicorn.server.domain.member.port.`in`.LogoutInPort -import com.unicorn.server.domain.member.port.`in`.ReissueTokenInPort -import com.unicorn.server.domain.member.port.`in`.SocialLoginInPort +import com.unicorn.server.domain.member.port.`in`.MemberLogoutInPort +import com.unicorn.server.domain.member.port.`in`.MemberTokenReissueInPort +import com.unicorn.server.domain.member.port.`in`.MemberSocialLoginInPort import com.unicorn.server.domain.member.port.dto.SocialLoginCommand import com.unicorn.server.domain.member.port.dto.SocialLoginResult import com.unicorn.server.domain.member.port.dto.TokenPair import com.unicorn.server.domain.member.port.out.MemberOutPort import com.unicorn.server.domain.member.port.out.SocialAccountOutPort -import com.unicorn.server.domain.member.port.out.TokenIssuer -import com.unicorn.server.domain.member.port.out.TokenStore +import com.unicorn.server.domain.member.port.out.MemberTokenIssueOutPort +import com.unicorn.server.domain.member.port.out.MemberTokenStoreOutPort import com.unicorn.server.domain.member.vo.MemberId import org.springframework.stereotype.Service import org.springframework.transaction.annotation.Transactional @@ -28,9 +28,9 @@ import org.springframework.transaction.annotation.Transactional class MemberAuthService( private val memberOutPort: MemberOutPort, private val socialAccountOutPort: SocialAccountOutPort, - private val tokenIssuer: TokenIssuer, - private val tokenStore: TokenStore, -) : SocialLoginInPort, LogoutInPort, ReissueTokenInPort { + private val tokenIssuer: MemberTokenIssueOutPort, + private val tokenStore: MemberTokenStoreOutPort, +) : MemberSocialLoginInPort, MemberLogoutInPort, MemberTokenReissueInPort { // 검증된 소셜 사용자 정보로 신규 가입 또는 기존 로그인을 처리하고 토큰을 발급한다. override fun login(command: SocialLoginCommand): SocialLoginResult { diff --git a/src/main/kotlin/com/unicorn/server/domain/member/service/MemberProfileService.kt b/src/main/kotlin/com/unicorn/server/domain/member/service/MemberProfileService.kt index ba54e97..d1a9489 100644 --- a/src/main/kotlin/com/unicorn/server/domain/member/service/MemberProfileService.kt +++ b/src/main/kotlin/com/unicorn/server/domain/member/service/MemberProfileService.kt @@ -1,7 +1,7 @@ package com.unicorn.server.domain.member.service -import com.unicorn.server.common.port.out.event.EventPublisher -import com.unicorn.server.common.port.out.storage.ObjectStorage +import com.unicorn.server.common.port.out.event.EventOutPort +import com.unicorn.server.common.port.out.storage.ObjectStorageOutPort import com.unicorn.server.common.port.out.storage.ObjectType import com.unicorn.server.common.port.out.storage.ObjectUploadCommand import com.unicorn.server.domain.member.Member @@ -11,13 +11,13 @@ import com.unicorn.server.domain.member.enums.Role import com.unicorn.server.domain.member.event.MemberWithdrawnEvent import com.unicorn.server.domain.member.exception.MemberNotFoundException import com.unicorn.server.domain.member.exception.WithdrawnMemberException -import com.unicorn.server.domain.member.port.`in`.GetMemberInPort -import com.unicorn.server.domain.member.port.`in`.GetMemberProfileInPort -import com.unicorn.server.domain.member.port.`in`.GetOnboardingInfoInPort -import com.unicorn.server.domain.member.port.`in`.UpdateMemberStateInPort -import com.unicorn.server.domain.member.port.`in`.UpdateProfileInPort -import com.unicorn.server.domain.member.port.`in`.UploadProfileImageInPort -import com.unicorn.server.domain.member.port.`in`.WithdrawMemberInPort +import com.unicorn.server.domain.member.port.`in`.MemberInPort +import com.unicorn.server.domain.member.port.`in`.MemberProfileInPort +import com.unicorn.server.domain.member.port.`in`.MemberOnboardingInfoInPort +import com.unicorn.server.domain.member.port.`in`.MemberStateUpdateInPort +import com.unicorn.server.domain.member.port.`in`.MemberProfileUpdateInPort +import com.unicorn.server.domain.member.port.`in`.MemberProfileImageUploadInPort +import com.unicorn.server.domain.member.port.`in`.MemberWithdrawInPort import com.unicorn.server.domain.member.port.dto.MemberProfileDto import com.unicorn.server.domain.member.port.dto.OnboardingInfoResult import com.unicorn.server.domain.member.port.dto.UpdateProfileCommand @@ -38,10 +38,10 @@ import org.springframework.transaction.support.TransactionSynchronizationManager class MemberProfileService( private val memberOutPort: MemberOutPort, private val socialAccountOutPort: SocialAccountOutPort, - private val eventPublisher: EventPublisher, - private val objectStorage: ObjectStorage, + private val eventPublisher: EventOutPort, + private val objectStorage: ObjectStorageOutPort, private val withdrawalLogOutPort: WithdrawalLogOutPort, -) : GetMemberInPort, GetMemberProfileInPort, GetOnboardingInfoInPort, UpdateProfileInPort, WithdrawMemberInPort, UploadProfileImageInPort, UpdateMemberStateInPort { +) : MemberInPort, MemberProfileInPort, MemberOnboardingInfoInPort, MemberProfileUpdateInPort, MemberWithdrawInPort, MemberProfileImageUploadInPort, MemberStateUpdateInPort { // 멤버 식별자로 저장된 멤버를 조회한다. override fun getById(memberId: String): Member = findMemberOrThrow(memberId) diff --git a/src/main/kotlin/com/unicorn/server/domain/member/service/OnboardingService.kt b/src/main/kotlin/com/unicorn/server/domain/member/service/OnboardingService.kt index ff33a91..a3ada00 100644 --- a/src/main/kotlin/com/unicorn/server/domain/member/service/OnboardingService.kt +++ b/src/main/kotlin/com/unicorn/server/domain/member/service/OnboardingService.kt @@ -3,12 +3,12 @@ package com.unicorn.server.domain.member.service import com.unicorn.server.common.exception.BusinessException import com.unicorn.server.domain.member.exception.MemberNotFoundException import com.unicorn.server.domain.member.exception.WithdrawnMemberException -import com.unicorn.server.domain.member.port.`in`.CompleteOnboardingInPort +import com.unicorn.server.domain.member.port.`in`.MemberOnboardingCompleteInPort import com.unicorn.server.domain.member.port.dto.CompleteOnboardingCommand import com.unicorn.server.domain.member.port.dto.TokenPair import com.unicorn.server.domain.member.port.out.MemberOutPort -import com.unicorn.server.domain.member.port.out.TokenIssuer -import com.unicorn.server.domain.member.port.out.TokenStore +import com.unicorn.server.domain.member.port.out.MemberTokenIssueOutPort +import com.unicorn.server.domain.member.port.out.MemberTokenStoreOutPort import com.unicorn.server.domain.member.vo.MemberId import com.unicorn.server.domain.term.exception.TermErrorCode import com.unicorn.server.domain.term.port.out.MemberTermOutPort @@ -23,9 +23,9 @@ class OnboardingService( private val memberOutPort: MemberOutPort, private val termOutPort: TermOutPort, private val memberTermOutPort: MemberTermOutPort, - private val tokenIssuer: TokenIssuer, - private val tokenStore: TokenStore, -) : CompleteOnboardingInPort { + private val tokenIssuer: MemberTokenIssueOutPort, + private val tokenStore: MemberTokenStoreOutPort, +) : MemberOnboardingCompleteInPort { override fun completeOnboarding(memberId: String, command: CompleteOnboardingCommand): TokenPair { val member = memberOutPort.findById(MemberId.of(memberId)) diff --git a/src/main/kotlin/com/unicorn/server/domain/member/service/SocialLoginService.kt b/src/main/kotlin/com/unicorn/server/domain/member/service/SocialLoginService.kt index 214c74f..adcedae 100644 --- a/src/main/kotlin/com/unicorn/server/domain/member/service/SocialLoginService.kt +++ b/src/main/kotlin/com/unicorn/server/domain/member/service/SocialLoginService.kt @@ -1,19 +1,19 @@ package com.unicorn.server.domain.member.service import com.unicorn.server.domain.member.enums.SocialProvider -import com.unicorn.server.domain.member.port.`in`.KakaoLoginInPort -import com.unicorn.server.domain.member.port.`in`.SocialLoginInPort +import com.unicorn.server.domain.member.port.`in`.MemberKakaoLoginInPort +import com.unicorn.server.domain.member.port.`in`.MemberSocialLoginInPort import com.unicorn.server.domain.member.port.dto.SocialLoginCommand import com.unicorn.server.domain.member.port.dto.SocialLoginResult -import com.unicorn.server.domain.member.port.out.KakaoAuthPort +import com.unicorn.server.domain.member.port.out.MemberKakaoAuthOutPort import org.springframework.stereotype.Service // SocialLoginService - 소셜 플랫폼 토큰 검증 후 공통 소셜 로그인 유스케이스로 위임한다. @Service class SocialLoginService( - private val kakaoAuthPort: KakaoAuthPort, - private val socialLoginInPort: SocialLoginInPort, -) : KakaoLoginInPort { + private val kakaoAuthPort: MemberKakaoAuthOutPort, + private val socialLoginInPort: MemberSocialLoginInPort, +) : MemberKakaoLoginInPort { // 카카오 ID Token을 검증하고 공통 소셜 로그인 유스케이스로 넘긴다. override fun kakaoLogin(idToken: String): SocialLoginResult { diff --git a/src/main/kotlin/com/unicorn/server/domain/notification/port/out/NotificationMessageComposer.kt b/src/main/kotlin/com/unicorn/server/domain/notification/port/out/NotificationMessageComposeOutPort.kt similarity index 88% rename from src/main/kotlin/com/unicorn/server/domain/notification/port/out/NotificationMessageComposer.kt rename to src/main/kotlin/com/unicorn/server/domain/notification/port/out/NotificationMessageComposeOutPort.kt index 12ef3ae..22580cc 100644 --- a/src/main/kotlin/com/unicorn/server/domain/notification/port/out/NotificationMessageComposer.kt +++ b/src/main/kotlin/com/unicorn/server/domain/notification/port/out/NotificationMessageComposeOutPort.kt @@ -4,7 +4,7 @@ import com.unicorn.server.domain.notification.Notification import com.unicorn.server.domain.notification.enums.NotificationChannel import com.unicorn.server.domain.notification.port.dto.NotificationMessage -interface NotificationMessageComposer { +interface NotificationMessageComposeOutPort { fun channel(): NotificationChannel fun compose(notification: Notification): NotificationMessage diff --git a/src/main/kotlin/com/unicorn/server/domain/notification/port/out/NotificationSender.kt b/src/main/kotlin/com/unicorn/server/domain/notification/port/out/NotificationSendOutPort.kt similarity index 88% rename from src/main/kotlin/com/unicorn/server/domain/notification/port/out/NotificationSender.kt rename to src/main/kotlin/com/unicorn/server/domain/notification/port/out/NotificationSendOutPort.kt index 59b1734..a2961b9 100644 --- a/src/main/kotlin/com/unicorn/server/domain/notification/port/out/NotificationSender.kt +++ b/src/main/kotlin/com/unicorn/server/domain/notification/port/out/NotificationSendOutPort.kt @@ -3,7 +3,7 @@ package com.unicorn.server.domain.notification.port.out import com.unicorn.server.domain.notification.enums.NotificationChannel import com.unicorn.server.domain.notification.port.dto.NotificationMessage -interface NotificationSender { +interface NotificationSendOutPort { fun channel(): NotificationChannel fun send(message: NotificationMessage) diff --git a/src/main/kotlin/com/unicorn/server/domain/notification/service/NotificationDispatchService.kt b/src/main/kotlin/com/unicorn/server/domain/notification/service/NotificationDispatchService.kt index 18e3c05..1dfecba 100644 --- a/src/main/kotlin/com/unicorn/server/domain/notification/service/NotificationDispatchService.kt +++ b/src/main/kotlin/com/unicorn/server/domain/notification/service/NotificationDispatchService.kt @@ -5,9 +5,9 @@ import com.unicorn.server.domain.notification.enums.NotificationChannel import com.unicorn.server.domain.notification.exception.PermanentNotificationSendException import com.unicorn.server.domain.notification.exception.RetryableNotificationSendException import com.unicorn.server.domain.notification.port.`in`.NotificationDispatchInPort -import com.unicorn.server.domain.notification.port.out.NotificationMessageComposer +import com.unicorn.server.domain.notification.port.out.NotificationMessageComposeOutPort import com.unicorn.server.domain.notification.port.out.NotificationOutPort -import com.unicorn.server.domain.notification.port.out.NotificationSender +import com.unicorn.server.domain.notification.port.out.NotificationSendOutPort import com.unicorn.server.infrastructure.config.NotificationProperties import org.springframework.stereotype.Service import org.springframework.transaction.annotation.Transactional @@ -17,13 +17,13 @@ import java.time.LocalDateTime @Transactional(readOnly = true) class NotificationDispatchService( private val notificationOutPort: NotificationOutPort, - composers: List, - senders: List, + composers: List, + senders: List, private val notificationProperties: NotificationProperties, ) : NotificationDispatchInPort { - private val composerRegistry: Map = + private val composerRegistry: Map = composers.associateBy { it.channel() } - private val senderRegistry: Map = senders.associateBy { it.channel() } + private val senderRegistry: Map = senders.associateBy { it.channel() } @Transactional override fun dispatch(limit: Int) { diff --git a/src/main/kotlin/com/unicorn/server/domain/schedule/port/in/CancelConfirmationInPort.kt b/src/main/kotlin/com/unicorn/server/domain/schedule/port/in/ScheduleConfirmationCancelInPort.kt similarity index 82% rename from src/main/kotlin/com/unicorn/server/domain/schedule/port/in/CancelConfirmationInPort.kt rename to src/main/kotlin/com/unicorn/server/domain/schedule/port/in/ScheduleConfirmationCancelInPort.kt index 2bd7632..5d44986 100644 --- a/src/main/kotlin/com/unicorn/server/domain/schedule/port/in/CancelConfirmationInPort.kt +++ b/src/main/kotlin/com/unicorn/server/domain/schedule/port/in/ScheduleConfirmationCancelInPort.kt @@ -2,6 +2,6 @@ package com.unicorn.server.domain.schedule.port.`in` import com.unicorn.server.domain.schedule.vo.ScheduleId -interface CancelConfirmationInPort { +interface ScheduleConfirmationCancelInPort { fun cancel(confirmationId: Long, scheduleId: ScheduleId, circleId: String, memberId: String) } diff --git a/src/main/kotlin/com/unicorn/server/domain/schedule/port/in/RegisterConfirmationInPort.kt b/src/main/kotlin/com/unicorn/server/domain/schedule/port/in/ScheduleConfirmationRegisterInPort.kt similarity index 85% rename from src/main/kotlin/com/unicorn/server/domain/schedule/port/in/RegisterConfirmationInPort.kt rename to src/main/kotlin/com/unicorn/server/domain/schedule/port/in/ScheduleConfirmationRegisterInPort.kt index 6feeab6..595d4fa 100644 --- a/src/main/kotlin/com/unicorn/server/domain/schedule/port/in/RegisterConfirmationInPort.kt +++ b/src/main/kotlin/com/unicorn/server/domain/schedule/port/in/ScheduleConfirmationRegisterInPort.kt @@ -3,6 +3,6 @@ package com.unicorn.server.domain.schedule.port.`in` import com.unicorn.server.domain.schedule.enums.ConfirmationType import com.unicorn.server.domain.schedule.port.dto.RegisterConfirmationCommand -interface RegisterConfirmationInPort { +interface ScheduleConfirmationRegisterInPort { fun register(command: RegisterConfirmationCommand): ConfirmationType } diff --git a/src/main/kotlin/com/unicorn/server/domain/schedule/port/in/CreateScheduleInPort.kt b/src/main/kotlin/com/unicorn/server/domain/schedule/port/in/ScheduleCreateInPort.kt similarity index 88% rename from src/main/kotlin/com/unicorn/server/domain/schedule/port/in/CreateScheduleInPort.kt rename to src/main/kotlin/com/unicorn/server/domain/schedule/port/in/ScheduleCreateInPort.kt index c8af0fc..53677c2 100644 --- a/src/main/kotlin/com/unicorn/server/domain/schedule/port/in/CreateScheduleInPort.kt +++ b/src/main/kotlin/com/unicorn/server/domain/schedule/port/in/ScheduleCreateInPort.kt @@ -3,6 +3,6 @@ package com.unicorn.server.domain.schedule.port.`in` import com.unicorn.server.domain.schedule.port.dto.CreateScheduleCommand import com.unicorn.server.domain.schedule.vo.ScheduleId -interface CreateScheduleInPort { +interface ScheduleCreateInPort { fun create(command: CreateScheduleCommand): ScheduleId } diff --git a/src/main/kotlin/com/unicorn/server/domain/schedule/port/in/DeleteScheduleInPort.kt b/src/main/kotlin/com/unicorn/server/domain/schedule/port/in/ScheduleDeleteInPort.kt similarity index 84% rename from src/main/kotlin/com/unicorn/server/domain/schedule/port/in/DeleteScheduleInPort.kt rename to src/main/kotlin/com/unicorn/server/domain/schedule/port/in/ScheduleDeleteInPort.kt index f72869d..dd085c3 100644 --- a/src/main/kotlin/com/unicorn/server/domain/schedule/port/in/DeleteScheduleInPort.kt +++ b/src/main/kotlin/com/unicorn/server/domain/schedule/port/in/ScheduleDeleteInPort.kt @@ -2,6 +2,6 @@ package com.unicorn.server.domain.schedule.port.`in` import com.unicorn.server.domain.schedule.vo.ScheduleId -interface DeleteScheduleInPort { +interface ScheduleDeleteInPort { fun delete(scheduleId: ScheduleId, circleId: String, memberId: String) } diff --git a/src/main/kotlin/com/unicorn/server/domain/schedule/port/in/GetScheduleDetailInPort.kt b/src/main/kotlin/com/unicorn/server/domain/schedule/port/in/ScheduleDetailInPort.kt similarity index 88% rename from src/main/kotlin/com/unicorn/server/domain/schedule/port/in/GetScheduleDetailInPort.kt rename to src/main/kotlin/com/unicorn/server/domain/schedule/port/in/ScheduleDetailInPort.kt index afb853a..16d689f 100644 --- a/src/main/kotlin/com/unicorn/server/domain/schedule/port/in/GetScheduleDetailInPort.kt +++ b/src/main/kotlin/com/unicorn/server/domain/schedule/port/in/ScheduleDetailInPort.kt @@ -3,6 +3,6 @@ package com.unicorn.server.domain.schedule.port.`in` import com.unicorn.server.domain.schedule.port.dto.ScheduleDetailResult import com.unicorn.server.domain.schedule.vo.ScheduleId -interface GetScheduleDetailInPort { +interface ScheduleDetailInPort { fun getDetail(scheduleId: ScheduleId, circleId: String, memberId: String): ScheduleDetailResult } diff --git a/src/main/kotlin/com/unicorn/server/domain/schedule/port/in/RequestFamilyScheduleNotificationInPort.kt b/src/main/kotlin/com/unicorn/server/domain/schedule/port/in/ScheduleFamilyNotificationRequestInPort.kt similarity index 80% rename from src/main/kotlin/com/unicorn/server/domain/schedule/port/in/RequestFamilyScheduleNotificationInPort.kt rename to src/main/kotlin/com/unicorn/server/domain/schedule/port/in/ScheduleFamilyNotificationRequestInPort.kt index c7366e8..83a2d6b 100644 --- a/src/main/kotlin/com/unicorn/server/domain/schedule/port/in/RequestFamilyScheduleNotificationInPort.kt +++ b/src/main/kotlin/com/unicorn/server/domain/schedule/port/in/ScheduleFamilyNotificationRequestInPort.kt @@ -2,6 +2,6 @@ package com.unicorn.server.domain.schedule.port.`in` import com.unicorn.server.domain.schedule.port.dto.RequestFamilyScheduleNotificationCommand -interface RequestFamilyScheduleNotificationInPort { +interface ScheduleFamilyNotificationRequestInPort { fun request(command: RequestFamilyScheduleNotificationCommand) } diff --git a/src/main/kotlin/com/unicorn/server/domain/schedule/port/in/GetSchedulesForCircleInPort.kt b/src/main/kotlin/com/unicorn/server/domain/schedule/port/in/ScheduleForCircleInPort.kt similarity index 93% rename from src/main/kotlin/com/unicorn/server/domain/schedule/port/in/GetSchedulesForCircleInPort.kt rename to src/main/kotlin/com/unicorn/server/domain/schedule/port/in/ScheduleForCircleInPort.kt index a413a32..1790f04 100644 --- a/src/main/kotlin/com/unicorn/server/domain/schedule/port/in/GetSchedulesForCircleInPort.kt +++ b/src/main/kotlin/com/unicorn/server/domain/schedule/port/in/ScheduleForCircleInPort.kt @@ -4,7 +4,7 @@ import com.unicorn.server.domain.circle.vo.CircleId import com.unicorn.server.domain.schedule.port.dto.ScheduleSummaryResult import java.time.LocalDate -interface GetSchedulesForCircleInPort { +interface ScheduleForCircleInPort { // 아직 종료되지 않은(예정 + 진행 중) 일정을 임박한 순서로 최대 limit개 반환한다. 첫 항목이 가장 임박한 일정이다. fun findUpcomingSchedulesByCircleId(circleId: CircleId, today: LocalDate, limit: Int): List fun countByCircleId(circleId: CircleId): Long diff --git a/src/main/kotlin/com/unicorn/server/domain/schedule/port/in/GetScheduleListInPort.kt b/src/main/kotlin/com/unicorn/server/domain/schedule/port/in/ScheduleListInPort.kt similarity index 87% rename from src/main/kotlin/com/unicorn/server/domain/schedule/port/in/GetScheduleListInPort.kt rename to src/main/kotlin/com/unicorn/server/domain/schedule/port/in/ScheduleListInPort.kt index 29134c2..d06cb76 100644 --- a/src/main/kotlin/com/unicorn/server/domain/schedule/port/in/GetScheduleListInPort.kt +++ b/src/main/kotlin/com/unicorn/server/domain/schedule/port/in/ScheduleListInPort.kt @@ -2,7 +2,7 @@ package com.unicorn.server.domain.schedule.port.`in` import com.unicorn.server.domain.schedule.port.dto.ScheduleListResult -interface GetScheduleListInPort { +interface ScheduleListInPort { fun getList( circleId: String, memberId: String, diff --git a/src/main/kotlin/com/unicorn/server/domain/schedule/port/in/UpdateScheduleInPort.kt b/src/main/kotlin/com/unicorn/server/domain/schedule/port/in/ScheduleUpdateInPort.kt similarity index 84% rename from src/main/kotlin/com/unicorn/server/domain/schedule/port/in/UpdateScheduleInPort.kt rename to src/main/kotlin/com/unicorn/server/domain/schedule/port/in/ScheduleUpdateInPort.kt index 2fc0c11..652b364 100644 --- a/src/main/kotlin/com/unicorn/server/domain/schedule/port/in/UpdateScheduleInPort.kt +++ b/src/main/kotlin/com/unicorn/server/domain/schedule/port/in/ScheduleUpdateInPort.kt @@ -2,6 +2,6 @@ package com.unicorn.server.domain.schedule.port.`in` import com.unicorn.server.domain.schedule.port.dto.UpdateScheduleCommand -interface UpdateScheduleInPort { +interface ScheduleUpdateInPort { fun update(command: UpdateScheduleCommand) } diff --git a/src/main/kotlin/com/unicorn/server/domain/schedule/service/CancelConfirmationService.kt b/src/main/kotlin/com/unicorn/server/domain/schedule/service/CancelConfirmationService.kt index 441feda..ba0619c 100644 --- a/src/main/kotlin/com/unicorn/server/domain/schedule/service/CancelConfirmationService.kt +++ b/src/main/kotlin/com/unicorn/server/domain/schedule/service/CancelConfirmationService.kt @@ -2,7 +2,7 @@ package com.unicorn.server.domain.schedule.service import com.unicorn.server.common.exception.BusinessException import com.unicorn.server.domain.schedule.exception.ScheduleErrorCode -import com.unicorn.server.domain.schedule.port.`in`.CancelConfirmationInPort +import com.unicorn.server.domain.schedule.port.`in`.ScheduleConfirmationCancelInPort import com.unicorn.server.domain.schedule.port.out.CircleAccessOutPort import com.unicorn.server.domain.schedule.port.out.ScheduleConfirmationOutPort import com.unicorn.server.domain.schedule.port.out.ScheduleOutPort @@ -16,7 +16,7 @@ class CancelConfirmationService( private val scheduleOutPort: ScheduleOutPort, private val scheduleConfirmationOutPort: ScheduleConfirmationOutPort, private val circleAccessOutPort: CircleAccessOutPort, -) : CancelConfirmationInPort { +) : ScheduleConfirmationCancelInPort { override fun cancel(confirmationId: Long, scheduleId: ScheduleId, circleId: String, memberId: String) { if (!circleAccessOutPort.isMember(circleId, memberId)) { diff --git a/src/main/kotlin/com/unicorn/server/domain/schedule/service/FamilyScheduleNotificationService.kt b/src/main/kotlin/com/unicorn/server/domain/schedule/service/FamilyScheduleNotificationService.kt index 0a7d1bf..7c22da8 100644 --- a/src/main/kotlin/com/unicorn/server/domain/schedule/service/FamilyScheduleNotificationService.kt +++ b/src/main/kotlin/com/unicorn/server/domain/schedule/service/FamilyScheduleNotificationService.kt @@ -1,10 +1,10 @@ package com.unicorn.server.domain.schedule.service import com.unicorn.server.common.exception.BusinessException -import com.unicorn.server.common.port.out.event.EventPublisher +import com.unicorn.server.common.port.out.event.EventOutPort import com.unicorn.server.domain.schedule.event.FamilyScheduleNotificationRequestedEvent import com.unicorn.server.domain.schedule.exception.ScheduleErrorCode -import com.unicorn.server.domain.schedule.port.`in`.RequestFamilyScheduleNotificationInPort +import com.unicorn.server.domain.schedule.port.`in`.ScheduleFamilyNotificationRequestInPort import com.unicorn.server.domain.schedule.port.dto.RequestFamilyScheduleNotificationCommand import com.unicorn.server.domain.schedule.port.out.CircleAccessOutPort import com.unicorn.server.domain.schedule.port.out.ScheduleOutPort @@ -19,8 +19,8 @@ import java.util.UUID class FamilyScheduleNotificationService( private val scheduleOutPort: ScheduleOutPort, private val circleAccessOutPort: CircleAccessOutPort, - private val eventPublisher: EventPublisher, -) : RequestFamilyScheduleNotificationInPort { + private val eventPublisher: EventOutPort, +) : ScheduleFamilyNotificationRequestInPort { override fun request(command: RequestFamilyScheduleNotificationCommand) { if (!circleAccessOutPort.isMember(command.circleId, command.memberId)) { diff --git a/src/main/kotlin/com/unicorn/server/domain/schedule/service/ScheduleCommandService.kt b/src/main/kotlin/com/unicorn/server/domain/schedule/service/ScheduleCommandService.kt index 6c23318..f5571f2 100644 --- a/src/main/kotlin/com/unicorn/server/domain/schedule/service/ScheduleCommandService.kt +++ b/src/main/kotlin/com/unicorn/server/domain/schedule/service/ScheduleCommandService.kt @@ -1,14 +1,14 @@ package com.unicorn.server.domain.schedule.service import com.unicorn.server.common.exception.BusinessException -import com.unicorn.server.common.port.out.event.EventPublisher +import com.unicorn.server.common.port.out.event.EventOutPort import com.unicorn.server.domain.schedule.Schedule import com.unicorn.server.domain.schedule.event.ScheduleCreatedEvent import com.unicorn.server.domain.schedule.event.ScheduleDeletedEvent import com.unicorn.server.domain.schedule.exception.ScheduleErrorCode -import com.unicorn.server.domain.schedule.port.`in`.CreateScheduleInPort -import com.unicorn.server.domain.schedule.port.`in`.DeleteScheduleInPort -import com.unicorn.server.domain.schedule.port.`in`.UpdateScheduleInPort +import com.unicorn.server.domain.schedule.port.`in`.ScheduleCreateInPort +import com.unicorn.server.domain.schedule.port.`in`.ScheduleDeleteInPort +import com.unicorn.server.domain.schedule.port.`in`.ScheduleUpdateInPort import com.unicorn.server.domain.schedule.port.dto.CreateScheduleCommand import com.unicorn.server.domain.schedule.port.dto.UpdateScheduleCommand import com.unicorn.server.domain.schedule.port.out.CircleAccessOutPort @@ -26,8 +26,8 @@ class ScheduleCommandService( private val scheduleConfirmationOutPort: ScheduleConfirmationOutPort, private val circleAccessOutPort: CircleAccessOutPort, private val scheduleIdGenerator: ScheduleIdGenerator, - private val eventPublisher: EventPublisher, -) : CreateScheduleInPort, UpdateScheduleInPort, DeleteScheduleInPort { + private val eventPublisher: EventOutPort, +) : ScheduleCreateInPort, ScheduleUpdateInPort, ScheduleDeleteInPort { override fun create(command: CreateScheduleCommand): ScheduleId { if (!circleAccessOutPort.existsById(command.circleId)) { diff --git a/src/main/kotlin/com/unicorn/server/domain/schedule/service/ScheduleConfirmationRequestService.kt b/src/main/kotlin/com/unicorn/server/domain/schedule/service/ScheduleConfirmationRequestService.kt index 9002a09..89c64a5 100644 --- a/src/main/kotlin/com/unicorn/server/domain/schedule/service/ScheduleConfirmationRequestService.kt +++ b/src/main/kotlin/com/unicorn/server/domain/schedule/service/ScheduleConfirmationRequestService.kt @@ -1,6 +1,6 @@ package com.unicorn.server.domain.schedule.service -import com.unicorn.server.common.port.out.event.EventPublisher +import com.unicorn.server.common.port.out.event.EventOutPort import com.unicorn.server.domain.schedule.Schedule import com.unicorn.server.domain.schedule.event.ScheduleConfirmationRequestDueEvent import com.unicorn.server.domain.schedule.port.`in`.ScheduleConfirmationRequestInPort @@ -14,7 +14,7 @@ import java.time.LocalTime @Transactional class ScheduleConfirmationRequestService( private val scheduleOutPort: ScheduleOutPort, - private val eventPublisher: EventPublisher, + private val eventPublisher: EventOutPort, ) : ScheduleConfirmationRequestInPort { override fun dispatchDue(now: LocalDateTime) { diff --git a/src/main/kotlin/com/unicorn/server/domain/schedule/service/ScheduleConfirmationService.kt b/src/main/kotlin/com/unicorn/server/domain/schedule/service/ScheduleConfirmationService.kt index 3540d70..16645bb 100644 --- a/src/main/kotlin/com/unicorn/server/domain/schedule/service/ScheduleConfirmationService.kt +++ b/src/main/kotlin/com/unicorn/server/domain/schedule/service/ScheduleConfirmationService.kt @@ -1,12 +1,12 @@ package com.unicorn.server.domain.schedule.service import com.unicorn.server.common.exception.BusinessException -import com.unicorn.server.common.port.out.event.EventPublisher +import com.unicorn.server.common.port.out.event.EventOutPort import com.unicorn.server.domain.schedule.ScheduleConfirmation import com.unicorn.server.domain.schedule.enums.ConfirmationType import com.unicorn.server.domain.schedule.event.ScheduleConfirmedEvent import com.unicorn.server.domain.schedule.exception.ScheduleErrorCode -import com.unicorn.server.domain.schedule.port.`in`.RegisterConfirmationInPort +import com.unicorn.server.domain.schedule.port.`in`.ScheduleConfirmationRegisterInPort import com.unicorn.server.domain.schedule.port.`in`.ScheduleConfirmationStatusInPort import com.unicorn.server.domain.schedule.port.dto.RegisterConfirmationCommand import com.unicorn.server.domain.schedule.port.out.CircleAccessOutPort @@ -22,8 +22,8 @@ class ScheduleConfirmationService( private val scheduleOutPort: ScheduleOutPort, private val scheduleConfirmationOutPort: ScheduleConfirmationOutPort, private val circleAccessOutPort: CircleAccessOutPort, - private val eventPublisher: EventPublisher, -) : RegisterConfirmationInPort, ScheduleConfirmationStatusInPort { + private val eventPublisher: EventOutPort, +) : ScheduleConfirmationRegisterInPort, ScheduleConfirmationStatusInPort { override fun register(command: RegisterConfirmationCommand): ConfirmationType { if (!command.confirmationType.available) { diff --git a/src/main/kotlin/com/unicorn/server/domain/schedule/service/ScheduleQueryService.kt b/src/main/kotlin/com/unicorn/server/domain/schedule/service/ScheduleQueryService.kt index 3342583..d51fa47 100644 --- a/src/main/kotlin/com/unicorn/server/domain/schedule/service/ScheduleQueryService.kt +++ b/src/main/kotlin/com/unicorn/server/domain/schedule/service/ScheduleQueryService.kt @@ -6,9 +6,9 @@ import com.unicorn.server.domain.circle.vo.CircleId import com.unicorn.server.domain.schedule.Schedule import com.unicorn.server.domain.schedule.enums.UrgencyLevel import com.unicorn.server.domain.schedule.exception.ScheduleErrorCode -import com.unicorn.server.domain.schedule.port.`in`.GetScheduleDetailInPort -import com.unicorn.server.domain.schedule.port.`in`.GetScheduleListInPort -import com.unicorn.server.domain.schedule.port.`in`.GetSchedulesForCircleInPort +import com.unicorn.server.domain.schedule.port.`in`.ScheduleDetailInPort +import com.unicorn.server.domain.schedule.port.`in`.ScheduleListInPort +import com.unicorn.server.domain.schedule.port.`in`.ScheduleForCircleInPort import com.unicorn.server.domain.schedule.port.dto.MyConfirmationInfo import com.unicorn.server.domain.schedule.port.dto.ScheduleDetailResult import com.unicorn.server.domain.schedule.port.dto.ScheduleListResult @@ -30,7 +30,7 @@ class ScheduleQueryService( private val scheduleOutPort: ScheduleOutPort, private val scheduleConfirmationOutPort: ScheduleConfirmationOutPort, private val circleAccessOutPort: CircleAccessOutPort, -) : GetScheduleListInPort, GetScheduleDetailInPort, GetSchedulesForCircleInPort { +) : ScheduleListInPort, ScheduleDetailInPort, ScheduleForCircleInPort { override fun getList( circleId: String, diff --git a/src/main/kotlin/com/unicorn/server/domain/schedule/service/ScheduleReminderService.kt b/src/main/kotlin/com/unicorn/server/domain/schedule/service/ScheduleReminderService.kt index 1b748d3..d2d5c16 100644 --- a/src/main/kotlin/com/unicorn/server/domain/schedule/service/ScheduleReminderService.kt +++ b/src/main/kotlin/com/unicorn/server/domain/schedule/service/ScheduleReminderService.kt @@ -1,6 +1,6 @@ package com.unicorn.server.domain.schedule.service -import com.unicorn.server.common.port.out.event.EventPublisher +import com.unicorn.server.common.port.out.event.EventOutPort import com.unicorn.server.domain.schedule.Schedule import com.unicorn.server.domain.schedule.enums.ScheduleReminderType import com.unicorn.server.domain.schedule.event.ScheduleReminderDueEvent @@ -15,7 +15,7 @@ import java.time.LocalTime @Transactional class ScheduleReminderService( private val scheduleOutPort: ScheduleOutPort, - private val eventPublisher: EventPublisher, + private val eventPublisher: EventOutPort, ) : ScheduleReminderInPort { override fun dispatchDaily(now: LocalDateTime) { diff --git a/src/main/kotlin/com/unicorn/server/domain/term/port/in/GetActiveTermsInPort.kt b/src/main/kotlin/com/unicorn/server/domain/term/port/in/TermActiveInPort.kt similarity index 92% rename from src/main/kotlin/com/unicorn/server/domain/term/port/in/GetActiveTermsInPort.kt rename to src/main/kotlin/com/unicorn/server/domain/term/port/in/TermActiveInPort.kt index 26c8673..e2d5f38 100644 --- a/src/main/kotlin/com/unicorn/server/domain/term/port/in/GetActiveTermsInPort.kt +++ b/src/main/kotlin/com/unicorn/server/domain/term/port/in/TermActiveInPort.kt @@ -8,6 +8,6 @@ term_code별 현재 발효 중인 최신 버전 목록을 조회하는 유스케 회원가입 화면에서 보여줄 약관 목록을 가져오는 용도로 사용하며, 인증이 필요 없는 공개 유스케이스다. */ -interface GetActiveTermsInPort { +interface TermActiveInPort { fun getActiveTerms(): List } diff --git a/src/main/kotlin/com/unicorn/server/domain/term/port/in/AgreeTermsInPort.kt b/src/main/kotlin/com/unicorn/server/domain/term/port/in/TermAgreeInPort.kt similarity index 84% rename from src/main/kotlin/com/unicorn/server/domain/term/port/in/AgreeTermsInPort.kt rename to src/main/kotlin/com/unicorn/server/domain/term/port/in/TermAgreeInPort.kt index 3ebd5bd..018f9e7 100644 --- a/src/main/kotlin/com/unicorn/server/domain/term/port/in/AgreeTermsInPort.kt +++ b/src/main/kotlin/com/unicorn/server/domain/term/port/in/TermAgreeInPort.kt @@ -2,6 +2,6 @@ package com.unicorn.server.domain.term.port.`in` import com.unicorn.server.domain.term.port.dto.AgreeTermsCommand -interface AgreeTermsInPort { +interface TermAgreeInPort { fun agreeTerms(command: AgreeTermsCommand) } diff --git a/src/main/kotlin/com/unicorn/server/domain/term/service/TermAgreementService.kt b/src/main/kotlin/com/unicorn/server/domain/term/service/TermAgreementService.kt index 703e811..2a34ddd 100644 --- a/src/main/kotlin/com/unicorn/server/domain/term/service/TermAgreementService.kt +++ b/src/main/kotlin/com/unicorn/server/domain/term/service/TermAgreementService.kt @@ -4,7 +4,7 @@ import com.unicorn.server.common.exception.BusinessException import com.unicorn.server.domain.member.vo.MemberId import com.unicorn.server.domain.term.MemberTerm import com.unicorn.server.domain.term.exception.TermErrorCode -import com.unicorn.server.domain.term.port.`in`.AgreeTermsInPort +import com.unicorn.server.domain.term.port.`in`.TermAgreeInPort import com.unicorn.server.domain.term.port.dto.AgreeTermsCommand import com.unicorn.server.domain.term.port.out.MemberTermOutPort import com.unicorn.server.domain.term.port.out.TermOutPort @@ -18,7 +18,7 @@ import java.time.LocalDateTime class TermAgreementService( private val termOutPort: TermOutPort, private val memberTermOutPort: MemberTermOutPort, -) : AgreeTermsInPort { +) : TermAgreeInPort { override fun agreeTerms(command: AgreeTermsCommand) { val activeTerms = termOutPort.findAllEffectiveAsOf(LocalDateTime.now()) diff --git a/src/main/kotlin/com/unicorn/server/domain/term/service/TermQueryService.kt b/src/main/kotlin/com/unicorn/server/domain/term/service/TermQueryService.kt index 9bee62d..8f93384 100644 --- a/src/main/kotlin/com/unicorn/server/domain/term/service/TermQueryService.kt +++ b/src/main/kotlin/com/unicorn/server/domain/term/service/TermQueryService.kt @@ -1,7 +1,7 @@ package com.unicorn.server.domain.term.service import com.unicorn.server.domain.term.Term -import com.unicorn.server.domain.term.port.`in`.GetActiveTermsInPort +import com.unicorn.server.domain.term.port.`in`.TermActiveInPort import com.unicorn.server.domain.term.port.out.TermOutPort import org.springframework.stereotype.Service import org.springframework.transaction.annotation.Transactional @@ -21,7 +21,7 @@ import java.time.LocalDateTime @Transactional(readOnly = true) class TermQueryService( private val termOutPort: TermOutPort, -) : GetActiveTermsInPort { +) : TermActiveInPort { override fun getActiveTerms(): List = termOutPort.findAllEffectiveAsOf(LocalDateTime.now()) diff --git a/src/main/kotlin/com/unicorn/server/infrastructure/adapter/in/event/MemberEventListener.kt b/src/main/kotlin/com/unicorn/server/infrastructure/adapter/in/event/MemberEventListener.kt index 5f3197e..156128f 100644 --- a/src/main/kotlin/com/unicorn/server/infrastructure/adapter/in/event/MemberEventListener.kt +++ b/src/main/kotlin/com/unicorn/server/infrastructure/adapter/in/event/MemberEventListener.kt @@ -2,7 +2,7 @@ package com.unicorn.server.infrastructure.adapter.`in`.event import com.unicorn.server.domain.circle.port.`in`.CircleMemberInPort import com.unicorn.server.domain.member.event.MemberWithdrawnEvent -import com.unicorn.server.domain.member.port.out.TokenStore +import com.unicorn.server.domain.member.port.out.MemberTokenStoreOutPort import org.slf4j.LoggerFactory import org.springframework.context.event.EventListener import org.springframework.stereotype.Component @@ -10,7 +10,7 @@ import org.springframework.stereotype.Component // MemberEventListener - 멤버 도메인 이벤트의 인프라 후속 처리를 담당한다. @Component class MemberEventListener( - private val tokenStore: TokenStore, + private val tokenStore: MemberTokenStoreOutPort, private val circleMemberInPort: CircleMemberInPort, ) { // 멤버 탈퇴 이벤트를 수신해 후속 정리 작업의 시작점을 제공한다. diff --git a/src/main/kotlin/com/unicorn/server/infrastructure/adapter/in/event/invitation/InvitationNotificationEventListener.kt b/src/main/kotlin/com/unicorn/server/infrastructure/adapter/in/event/invitation/InvitationNotificationEventListener.kt index 4a956a6..16bf70b 100644 --- a/src/main/kotlin/com/unicorn/server/infrastructure/adapter/in/event/invitation/InvitationNotificationEventListener.kt +++ b/src/main/kotlin/com/unicorn/server/infrastructure/adapter/in/event/invitation/InvitationNotificationEventListener.kt @@ -1,6 +1,6 @@ package com.unicorn.server.infrastructure.adapter.`in`.event.invitation -import com.unicorn.server.common.port.out.event.EventPublisher +import com.unicorn.server.common.port.out.event.EventOutPort import com.unicorn.server.domain.circle.port.`in`.CircleMemberInPort import com.unicorn.server.domain.invitation.event.InvitationRedeemedEvent import com.unicorn.server.domain.notification.enums.NotificationChannel @@ -14,7 +14,7 @@ import org.springframework.stereotype.Component class InvitationNotificationEventListener( private val circleMemberInPort: CircleMemberInPort, private val notificationPushTokenInPort: NotificationPushTokenInPort, - private val eventPublisher: EventPublisher, + private val eventPublisher: EventOutPort, ) { @EventListener fun handle(event: InvitationRedeemedEvent) { diff --git a/src/main/kotlin/com/unicorn/server/infrastructure/adapter/in/event/schedule/ScheduleNotificationEventListener.kt b/src/main/kotlin/com/unicorn/server/infrastructure/adapter/in/event/schedule/ScheduleNotificationEventListener.kt index 637b505..afccfad 100644 --- a/src/main/kotlin/com/unicorn/server/infrastructure/adapter/in/event/schedule/ScheduleNotificationEventListener.kt +++ b/src/main/kotlin/com/unicorn/server/infrastructure/adapter/in/event/schedule/ScheduleNotificationEventListener.kt @@ -1,6 +1,6 @@ package com.unicorn.server.infrastructure.adapter.`in`.event.schedule -import com.unicorn.server.common.port.out.event.EventPublisher +import com.unicorn.server.common.port.out.event.EventOutPort import com.unicorn.server.domain.circle.port.`in`.CircleMemberInPort import com.unicorn.server.domain.circle.port.dto.CircleMemberDto import com.unicorn.server.domain.notification.enums.NotificationChannel @@ -34,7 +34,7 @@ class ScheduleNotificationEventListener( private val notificationPushTokenInPort: NotificationPushTokenInPort, private val notificationSettingInPort: NotificationSettingInPort, private val scheduleConfirmationStatusInPort: ScheduleConfirmationStatusInPort, - private val eventPublisher: EventPublisher, + private val eventPublisher: EventOutPort, ) { @EventListener fun handle(event: ScheduleCreatedEvent) { diff --git a/src/main/kotlin/com/unicorn/server/infrastructure/adapter/in/web/common/GlobalExceptionHandler.kt b/src/main/kotlin/com/unicorn/server/infrastructure/adapter/in/web/common/GlobalExceptionHandler.kt index 3a70e75..c9ab5f1 100644 --- a/src/main/kotlin/com/unicorn/server/infrastructure/adapter/in/web/common/GlobalExceptionHandler.kt +++ b/src/main/kotlin/com/unicorn/server/infrastructure/adapter/in/web/common/GlobalExceptionHandler.kt @@ -2,7 +2,7 @@ package com.unicorn.server.infrastructure.adapter.`in`.web.common import com.unicorn.server.common.exception.BusinessException import com.unicorn.server.common.exception.CommonErrorCode -import com.unicorn.server.common.port.out.alert.ErrorAlertPort +import com.unicorn.server.common.port.out.alert.AlertOutPort import com.unicorn.server.common.port.out.storage.exception.ObjectStorageErrorCode import com.unicorn.server.infrastructure.adapter.`in`.web.common.dto.ApiResponse import org.springframework.http.converter.HttpMessageNotReadableException @@ -16,7 +16,7 @@ import org.springframework.web.servlet.resource.NoResourceFoundException @RestControllerAdvice class GlobalExceptionHandler( - private val errorAlertPort: ErrorAlertPort, + private val errorAlertPort: AlertOutPort, ) { @ExceptionHandler(BusinessException::class) fun handleBusinessException(e: BusinessException): ApiResponse = diff --git a/src/main/kotlin/com/unicorn/server/infrastructure/adapter/in/web/invitation/InvitationController.kt b/src/main/kotlin/com/unicorn/server/infrastructure/adapter/in/web/invitation/InvitationController.kt index ba0f0fd..4ed1add 100644 --- a/src/main/kotlin/com/unicorn/server/infrastructure/adapter/in/web/invitation/InvitationController.kt +++ b/src/main/kotlin/com/unicorn/server/infrastructure/adapter/in/web/invitation/InvitationController.kt @@ -1,9 +1,9 @@ package com.unicorn.server.infrastructure.adapter.`in`.web.invitation import com.unicorn.server.domain.invitation.port.dto.IssueInvitationCommand -import com.unicorn.server.domain.invitation.port.`in`.AcceptCircleInvitationInPort -import com.unicorn.server.domain.invitation.port.`in`.GetInvitationByTokenInPort -import com.unicorn.server.domain.invitation.port.`in`.IssueInvitationInPort +import com.unicorn.server.domain.invitation.port.`in`.InvitationAcceptInPort +import com.unicorn.server.domain.invitation.port.`in`.InvitationByTokenInPort +import com.unicorn.server.domain.invitation.port.`in`.InvitationIssueInPort import com.unicorn.server.infrastructure.adapter.`in`.web.common.dto.ApiResponse import com.unicorn.server.infrastructure.adapter.`in`.web.invitation.dto.AcceptInvitationResponse import com.unicorn.server.infrastructure.adapter.`in`.web.invitation.dto.InvitationDetailResponse @@ -21,9 +21,9 @@ import org.springframework.web.bind.annotation.RestController @RestController @RequestMapping("/api/v1/invitations") class InvitationController( - private val issueInvitationInPort: IssueInvitationInPort, - private val getInvitationByTokenInPort: GetInvitationByTokenInPort, - private val acceptCircleInvitationInPort: AcceptCircleInvitationInPort, + private val issueInvitationInPort: InvitationIssueInPort, + private val getInvitationByTokenInPort: InvitationByTokenInPort, + private val acceptCircleInvitationInPort: InvitationAcceptInPort, ) : InvitationApiDoc { @PostMapping override fun issue( diff --git a/src/main/kotlin/com/unicorn/server/infrastructure/adapter/in/web/member/AuthController.kt b/src/main/kotlin/com/unicorn/server/infrastructure/adapter/in/web/member/AuthController.kt index 9f62741..7de5ec1 100644 --- a/src/main/kotlin/com/unicorn/server/infrastructure/adapter/in/web/member/AuthController.kt +++ b/src/main/kotlin/com/unicorn/server/infrastructure/adapter/in/web/member/AuthController.kt @@ -1,7 +1,7 @@ package com.unicorn.server.infrastructure.adapter.`in`.web.member -import com.unicorn.server.domain.member.port.`in`.KakaoLoginInPort -import com.unicorn.server.domain.member.port.`in`.ReissueTokenInPort +import com.unicorn.server.domain.member.port.`in`.MemberKakaoLoginInPort +import com.unicorn.server.domain.member.port.`in`.MemberTokenReissueInPort import com.unicorn.server.infrastructure.adapter.`in`.web.common.dto.ApiResponse import com.unicorn.server.infrastructure.adapter.`in`.web.member.dto.KakaoLoginRequest import com.unicorn.server.infrastructure.adapter.`in`.web.member.dto.KakaoLoginResponse @@ -17,8 +17,8 @@ import org.springframework.web.bind.annotation.RestController @RestController @RequestMapping("/api/v1/auth") class AuthController( - private val kakaoLoginInPort: KakaoLoginInPort, - private val reissueTokenInPort: ReissueTokenInPort, + private val kakaoLoginInPort: MemberKakaoLoginInPort, + private val reissueTokenInPort: MemberTokenReissueInPort, ) : AuthApiDoc { @PostMapping("/kakao") diff --git a/src/main/kotlin/com/unicorn/server/infrastructure/adapter/in/web/member/MemberController.kt b/src/main/kotlin/com/unicorn/server/infrastructure/adapter/in/web/member/MemberController.kt index a7756f1..48cea97 100644 --- a/src/main/kotlin/com/unicorn/server/infrastructure/adapter/in/web/member/MemberController.kt +++ b/src/main/kotlin/com/unicorn/server/infrastructure/adapter/in/web/member/MemberController.kt @@ -3,14 +3,14 @@ package com.unicorn.server.infrastructure.adapter.`in`.web.member import com.unicorn.server.domain.member.Member import com.unicorn.server.domain.member.enums.MemberStatus import com.unicorn.server.domain.member.enums.Role -import com.unicorn.server.domain.member.port.`in`.CompleteOnboardingInPort -import com.unicorn.server.domain.member.port.`in`.GetMemberInPort -import com.unicorn.server.domain.member.port.`in`.GetOnboardingInfoInPort -import com.unicorn.server.domain.member.port.`in`.LogoutInPort -import com.unicorn.server.domain.member.port.`in`.UpdateMemberStateInPort -import com.unicorn.server.domain.member.port.`in`.UpdateProfileInPort -import com.unicorn.server.domain.member.port.`in`.UploadProfileImageInPort -import com.unicorn.server.domain.member.port.`in`.WithdrawMemberInPort +import com.unicorn.server.domain.member.port.`in`.MemberOnboardingCompleteInPort +import com.unicorn.server.domain.member.port.`in`.MemberInPort +import com.unicorn.server.domain.member.port.`in`.MemberOnboardingInfoInPort +import com.unicorn.server.domain.member.port.`in`.MemberLogoutInPort +import com.unicorn.server.domain.member.port.`in`.MemberStateUpdateInPort +import com.unicorn.server.domain.member.port.`in`.MemberProfileUpdateInPort +import com.unicorn.server.domain.member.port.`in`.MemberProfileImageUploadInPort +import com.unicorn.server.domain.member.port.`in`.MemberWithdrawInPort import com.unicorn.server.domain.member.port.dto.CompleteOnboardingCommand import com.unicorn.server.domain.member.port.dto.UpdateProfileCommand import com.unicorn.server.domain.member.port.dto.UploadProfileImageCommand @@ -40,14 +40,14 @@ import org.springframework.web.multipart.MultipartFile @RestController @RequestMapping("/api/v1/members") class MemberController( - private val getMemberInPort: GetMemberInPort, - private val getOnboardingInfoInPort: GetOnboardingInfoInPort, - private val updateProfileInPort: UpdateProfileInPort, - private val uploadProfileImageInPort: UploadProfileImageInPort, - private val logoutInPort: LogoutInPort, - private val withdrawMemberInPort: WithdrawMemberInPort, - private val completeOnboardingInPort: CompleteOnboardingInPort, - private val updateMemberStateInPort: UpdateMemberStateInPort, + private val getMemberInPort: MemberInPort, + private val getOnboardingInfoInPort: MemberOnboardingInfoInPort, + private val updateProfileInPort: MemberProfileUpdateInPort, + private val uploadProfileImageInPort: MemberProfileImageUploadInPort, + private val logoutInPort: MemberLogoutInPort, + private val withdrawMemberInPort: MemberWithdrawInPort, + private val completeOnboardingInPort: MemberOnboardingCompleteInPort, + private val updateMemberStateInPort: MemberStateUpdateInPort, @param:Value("\${app.server.url}") private val serverUrl: String, ) : MemberApiDoc { diff --git a/src/main/kotlin/com/unicorn/server/infrastructure/adapter/in/web/schedule/ScheduleController.kt b/src/main/kotlin/com/unicorn/server/infrastructure/adapter/in/web/schedule/ScheduleController.kt index 94e4a1a..b49e801 100644 --- a/src/main/kotlin/com/unicorn/server/infrastructure/adapter/in/web/schedule/ScheduleController.kt +++ b/src/main/kotlin/com/unicorn/server/infrastructure/adapter/in/web/schedule/ScheduleController.kt @@ -1,15 +1,15 @@ package com.unicorn.server.infrastructure.adapter.`in`.web.schedule import com.unicorn.server.domain.schedule.enums.ConfirmationType -import com.unicorn.server.domain.schedule.port.`in`.CancelConfirmationInPort -import com.unicorn.server.domain.schedule.port.`in`.CreateScheduleInPort -import com.unicorn.server.domain.schedule.port.`in`.DeleteScheduleInPort -import com.unicorn.server.domain.schedule.port.`in`.GetScheduleDetailInPort -import com.unicorn.server.domain.schedule.port.`in`.GetScheduleListInPort -import com.unicorn.server.domain.schedule.port.`in`.RegisterConfirmationInPort +import com.unicorn.server.domain.schedule.port.`in`.ScheduleConfirmationCancelInPort +import com.unicorn.server.domain.schedule.port.`in`.ScheduleCreateInPort +import com.unicorn.server.domain.schedule.port.`in`.ScheduleDeleteInPort +import com.unicorn.server.domain.schedule.port.`in`.ScheduleDetailInPort +import com.unicorn.server.domain.schedule.port.`in`.ScheduleListInPort +import com.unicorn.server.domain.schedule.port.`in`.ScheduleConfirmationRegisterInPort -import com.unicorn.server.domain.schedule.port.`in`.RequestFamilyScheduleNotificationInPort -import com.unicorn.server.domain.schedule.port.`in`.UpdateScheduleInPort +import com.unicorn.server.domain.schedule.port.`in`.ScheduleFamilyNotificationRequestInPort +import com.unicorn.server.domain.schedule.port.`in`.ScheduleUpdateInPort import com.unicorn.server.domain.schedule.port.dto.CreateScheduleCommand import com.unicorn.server.domain.schedule.port.dto.RegisterConfirmationCommand import com.unicorn.server.domain.schedule.port.dto.RequestFamilyScheduleNotificationCommand @@ -39,14 +39,14 @@ import org.springframework.web.bind.annotation.RestController @RestController @RequestMapping("/api/v1/circles/{circleId}/schedules") class ScheduleController( - private val createScheduleInPort: CreateScheduleInPort, - private val updateScheduleInPort: UpdateScheduleInPort, - private val deleteScheduleInPort: DeleteScheduleInPort, - private val getScheduleListInPort: GetScheduleListInPort, - private val getScheduleDetailInPort: GetScheduleDetailInPort, - private val registerConfirmationInPort: RegisterConfirmationInPort, - private val cancelConfirmationInPort: CancelConfirmationInPort, - private val requestFamilyScheduleNotificationInPort: RequestFamilyScheduleNotificationInPort, + private val createScheduleInPort: ScheduleCreateInPort, + private val updateScheduleInPort: ScheduleUpdateInPort, + private val deleteScheduleInPort: ScheduleDeleteInPort, + private val getScheduleListInPort: ScheduleListInPort, + private val getScheduleDetailInPort: ScheduleDetailInPort, + private val registerConfirmationInPort: ScheduleConfirmationRegisterInPort, + private val cancelConfirmationInPort: ScheduleConfirmationCancelInPort, + private val requestFamilyScheduleNotificationInPort: ScheduleFamilyNotificationRequestInPort, ) : ScheduleApiDoc { @PostMapping diff --git a/src/main/kotlin/com/unicorn/server/infrastructure/adapter/in/web/term/TermController.kt b/src/main/kotlin/com/unicorn/server/infrastructure/adapter/in/web/term/TermController.kt index a17048c..af98023 100644 --- a/src/main/kotlin/com/unicorn/server/infrastructure/adapter/in/web/term/TermController.kt +++ b/src/main/kotlin/com/unicorn/server/infrastructure/adapter/in/web/term/TermController.kt @@ -1,7 +1,7 @@ package com.unicorn.server.infrastructure.adapter.`in`.web.term -import com.unicorn.server.domain.term.port.`in`.AgreeTermsInPort -import com.unicorn.server.domain.term.port.`in`.GetActiveTermsInPort +import com.unicorn.server.domain.term.port.`in`.TermAgreeInPort +import com.unicorn.server.domain.term.port.`in`.TermActiveInPort import com.unicorn.server.domain.term.port.dto.AgreeTermsCommand import com.unicorn.server.infrastructure.adapter.`in`.web.common.dto.ApiResponse import com.unicorn.server.infrastructure.adapter.`in`.web.term.dto.AgreeTermsRequest @@ -23,8 +23,8 @@ import org.springframework.web.bind.annotation.RestController @RestController @RequestMapping("/api/v1/terms") class TermController( - private val getActiveTermsInPort: GetActiveTermsInPort, - private val agreeTermsInPort: AgreeTermsInPort, + private val getActiveTermsInPort: TermActiveInPort, + private val agreeTermsInPort: TermAgreeInPort, ) : TermApiDoc { // GET /api/v1/terms - term_code별 현재 발효 중인 최신 버전 목록을 조회한다. diff --git a/src/main/kotlin/com/unicorn/server/infrastructure/adapter/out/discord/DiscordNotificationAdapter.kt b/src/main/kotlin/com/unicorn/server/infrastructure/adapter/out/discord/DiscordNotificationAdapter.kt index 1992917..7ebf17e 100644 --- a/src/main/kotlin/com/unicorn/server/infrastructure/adapter/out/discord/DiscordNotificationAdapter.kt +++ b/src/main/kotlin/com/unicorn/server/infrastructure/adapter/out/discord/DiscordNotificationAdapter.kt @@ -1,6 +1,6 @@ package com.unicorn.server.infrastructure.adapter.out.discord -import com.unicorn.server.common.port.out.alert.ErrorAlertPort +import com.unicorn.server.common.port.out.alert.AlertOutPort import com.unicorn.server.infrastructure.config.DiscordProperties import org.slf4j.LoggerFactory import org.springframework.http.HttpEntity @@ -13,7 +13,7 @@ import org.springframework.web.client.RestTemplate class DiscordNotificationAdapter( private val discordProperties: DiscordProperties, private val discordRestTemplate: RestTemplate, -) : ErrorAlertPort { +) : AlertOutPort { override fun sendErrorAlert(e: Exception) { if (!discordProperties.enabled) return diff --git a/src/main/kotlin/com/unicorn/server/infrastructure/adapter/out/event/SpringEventPublisherAdapter.kt b/src/main/kotlin/com/unicorn/server/infrastructure/adapter/out/event/SpringEventPublisherAdapter.kt index 523534c..257e0d7 100644 --- a/src/main/kotlin/com/unicorn/server/infrastructure/adapter/out/event/SpringEventPublisherAdapter.kt +++ b/src/main/kotlin/com/unicorn/server/infrastructure/adapter/out/event/SpringEventPublisherAdapter.kt @@ -1,14 +1,14 @@ package com.unicorn.server.infrastructure.adapter.out.event import com.unicorn.server.common.domain.Event -import com.unicorn.server.common.port.out.event.EventPublisher +import com.unicorn.server.common.port.out.event.EventOutPort import org.springframework.context.ApplicationEventPublisher import org.springframework.stereotype.Component @Component class SpringEventPublisherAdapter( private val applicationEventPublisher: ApplicationEventPublisher, -) : EventPublisher { +) : EventOutPort { override fun publish(event: Event) { applicationEventPublisher.publishEvent(event) } diff --git a/src/main/kotlin/com/unicorn/server/infrastructure/adapter/out/kakao/KakaoAuthAdapter.kt b/src/main/kotlin/com/unicorn/server/infrastructure/adapter/out/kakao/KakaoAuthAdapter.kt index 8ed3937..a2bbde3 100644 --- a/src/main/kotlin/com/unicorn/server/infrastructure/adapter/out/kakao/KakaoAuthAdapter.kt +++ b/src/main/kotlin/com/unicorn/server/infrastructure/adapter/out/kakao/KakaoAuthAdapter.kt @@ -2,7 +2,7 @@ package com.unicorn.server.infrastructure.adapter.out.kakao import com.unicorn.server.domain.member.exception.InvalidSocialTokenException import com.unicorn.server.domain.member.port.dto.KakaoUserInfo -import com.unicorn.server.domain.member.port.out.KakaoAuthPort +import com.unicorn.server.domain.member.port.out.MemberKakaoAuthOutPort import org.springframework.beans.factory.annotation.Value import org.springframework.security.oauth2.core.DelegatingOAuth2TokenValidator import org.springframework.security.oauth2.jwt.JwtClaimValidator @@ -17,7 +17,7 @@ class KakaoAuthAdapter( @param:Value("\${app.kakao.app-key}") private val appKey: String, @param:Value("\${app.kakao.jwks-uri}") private val jwksUri: String, @param:Value("\${app.kakao.issuer}") private val issuer: String, -) : KakaoAuthPort { +) : MemberKakaoAuthOutPort { private val jwtDecoder: NimbusJwtDecoder = NimbusJwtDecoder .withJwkSetUri(jwksUri) diff --git a/src/main/kotlin/com/unicorn/server/infrastructure/adapter/out/notification/fcm/FcmMessageComposer.kt b/src/main/kotlin/com/unicorn/server/infrastructure/adapter/out/notification/fcm/FcmMessageComposer.kt index 798584e..2959ceb 100644 --- a/src/main/kotlin/com/unicorn/server/infrastructure/adapter/out/notification/fcm/FcmMessageComposer.kt +++ b/src/main/kotlin/com/unicorn/server/infrastructure/adapter/out/notification/fcm/FcmMessageComposer.kt @@ -4,13 +4,13 @@ import com.unicorn.server.domain.notification.Notification import com.unicorn.server.domain.notification.enums.NotificationChannel import com.unicorn.server.domain.notification.exception.PermanentNotificationSendException import com.unicorn.server.domain.notification.port.dto.NotificationMessage -import com.unicorn.server.domain.notification.port.out.NotificationMessageComposer +import com.unicorn.server.domain.notification.port.out.NotificationMessageComposeOutPort import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty import org.springframework.stereotype.Component @Component @ConditionalOnProperty(prefix = "app.notification.fcm", name = ["enabled"], havingValue = "true") -class FcmMessageComposer : NotificationMessageComposer { +class FcmMessageComposer : NotificationMessageComposeOutPort { override fun channel(): NotificationChannel = NotificationChannel.PUSH override fun compose(notification: Notification): NotificationMessage { diff --git a/src/main/kotlin/com/unicorn/server/infrastructure/adapter/out/notification/fcm/FcmSender.kt b/src/main/kotlin/com/unicorn/server/infrastructure/adapter/out/notification/fcm/FcmSender.kt index 23f5053..09d2f51 100644 --- a/src/main/kotlin/com/unicorn/server/infrastructure/adapter/out/notification/fcm/FcmSender.kt +++ b/src/main/kotlin/com/unicorn/server/infrastructure/adapter/out/notification/fcm/FcmSender.kt @@ -9,7 +9,7 @@ import com.unicorn.server.domain.notification.enums.NotificationChannel import com.unicorn.server.domain.notification.exception.PermanentNotificationSendException import com.unicorn.server.domain.notification.exception.RetryableNotificationSendException import com.unicorn.server.domain.notification.port.dto.NotificationMessage -import com.unicorn.server.domain.notification.port.out.NotificationSender +import com.unicorn.server.domain.notification.port.out.NotificationSendOutPort import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty import org.springframework.stereotype.Component @@ -17,7 +17,7 @@ import org.springframework.stereotype.Component @ConditionalOnProperty(prefix = "app.notification.fcm", name = ["enabled"], havingValue = "true") class FcmSender( private val firebaseMessaging: FirebaseMessaging, -) : NotificationSender { +) : NotificationSendOutPort { override fun channel(): NotificationChannel = NotificationChannel.PUSH override fun send(message: NotificationMessage) { diff --git a/src/main/kotlin/com/unicorn/server/infrastructure/adapter/out/storage/S3ObjectStorageAdapter.kt b/src/main/kotlin/com/unicorn/server/infrastructure/adapter/out/storage/S3ObjectStorageAdapter.kt index cea4a8d..be2d8b3 100644 --- a/src/main/kotlin/com/unicorn/server/infrastructure/adapter/out/storage/S3ObjectStorageAdapter.kt +++ b/src/main/kotlin/com/unicorn/server/infrastructure/adapter/out/storage/S3ObjectStorageAdapter.kt @@ -1,7 +1,7 @@ package com.unicorn.server.infrastructure.adapter.out.storage import com.unicorn.server.common.exception.BusinessException -import com.unicorn.server.common.port.out.storage.ObjectStorage +import com.unicorn.server.common.port.out.storage.ObjectStorageOutPort import com.unicorn.server.common.port.out.storage.ObjectUploadCommand import com.unicorn.server.common.port.out.storage.StoredObject import com.unicorn.server.common.port.out.storage.exception.ObjectStorageErrorCode @@ -15,14 +15,14 @@ import software.amazon.awssdk.services.s3.model.GetUrlRequest import software.amazon.awssdk.services.s3.model.PutObjectRequest /** - * S3ObjectStorageAdapter - S3Client 구성(S3Config)에 따라 AWS S3 또는 MinIO에 접근하는 ObjectStorage 구현체. + * S3ObjectStorageAdapter - S3Client 구성(S3Config)에 따라 AWS S3 또는 MinIO에 접근하는 ObjectStorageOutPort 구현체. * 호출자가 ObjectType으로 이미 검증/objectKey 생성을 끝낸 ObjectUploadCommand만 받는다고 가정한다. */ @Component class S3ObjectStorageAdapter( private val s3Client: S3Client, @param:Value("\${app.s3.bucket}") private val bucket: String, -) : ObjectStorage { +) : ObjectStorageOutPort { override fun upload(command: ObjectUploadCommand): StoredObject { if (command.objectKey.isBlank()) { throw BusinessException(ObjectStorageErrorCode.INVALID_OBJECT_KEY, command.objectKey) diff --git a/src/main/kotlin/com/unicorn/server/infrastructure/adapter/out/token/InMemoryTokenStoreAdapter.kt b/src/main/kotlin/com/unicorn/server/infrastructure/adapter/out/token/InMemoryTokenStoreAdapter.kt index 3a48997..655c9c0 100644 --- a/src/main/kotlin/com/unicorn/server/infrastructure/adapter/out/token/InMemoryTokenStoreAdapter.kt +++ b/src/main/kotlin/com/unicorn/server/infrastructure/adapter/out/token/InMemoryTokenStoreAdapter.kt @@ -1,13 +1,13 @@ package com.unicorn.server.infrastructure.adapter.out.token -import com.unicorn.server.domain.member.port.out.TokenStore +import com.unicorn.server.domain.member.port.out.MemberTokenStoreOutPort import org.springframework.stereotype.Component import java.util.concurrent.ConcurrentHashMap // InMemoryTokenStoreAdapter - refresh token을 메모리에 저장한다. // 추후 RedisTokenStoreAdapter로 교체 가능하다. @Component -class InMemoryTokenStoreAdapter : TokenStore { +class InMemoryTokenStoreAdapter : MemberTokenStoreOutPort { // memberId -> refreshToken private val memberToToken = ConcurrentHashMap() diff --git a/src/main/kotlin/com/unicorn/server/infrastructure/adapter/out/token/JwtProvider.kt b/src/main/kotlin/com/unicorn/server/infrastructure/adapter/out/token/JwtProvider.kt index 31676cb..c8ede90 100644 --- a/src/main/kotlin/com/unicorn/server/infrastructure/adapter/out/token/JwtProvider.kt +++ b/src/main/kotlin/com/unicorn/server/infrastructure/adapter/out/token/JwtProvider.kt @@ -2,7 +2,7 @@ package com.unicorn.server.infrastructure.adapter.out.token import com.unicorn.server.domain.member.enums.Role import com.unicorn.server.domain.member.port.dto.TokenPair -import com.unicorn.server.domain.member.port.out.TokenIssuer +import com.unicorn.server.domain.member.port.out.MemberTokenIssueOutPort import io.jsonwebtoken.Claims import io.jsonwebtoken.Jwts import io.jsonwebtoken.security.Keys @@ -17,7 +17,7 @@ class JwtProvider( @Value("\${app.jwt.secret}") secretKey: String, @param:Value("\${app.jwt.access-token-expiration}") private val accessTokenExpiration: Long, @param:Value("\${app.jwt.refresh-token-expiration}") private val refreshTokenExpiration: Long, -) : TokenIssuer { +) : MemberTokenIssueOutPort { private val key: SecretKey = Keys.hmacShaKeyFor(secretKey.toByteArray(Charsets.UTF_8)) diff --git a/src/test/kotlin/com/unicorn/server/domain/circle/CircleServiceJoinTest.kt b/src/test/kotlin/com/unicorn/server/domain/circle/CircleServiceJoinTest.kt index 95d8092..9bf391c 100644 --- a/src/test/kotlin/com/unicorn/server/domain/circle/CircleServiceJoinTest.kt +++ b/src/test/kotlin/com/unicorn/server/domain/circle/CircleServiceJoinTest.kt @@ -2,7 +2,7 @@ package com.unicorn.server.domain.circle import com.unicorn.server.TestIdFactory import com.unicorn.server.common.exception.BusinessException -import com.unicorn.server.common.port.out.event.EventPublisher +import com.unicorn.server.common.port.out.event.EventOutPort import com.unicorn.server.common.vo.Email import com.unicorn.server.domain.circle.enums.CircleMemberStatus import com.unicorn.server.domain.circle.enums.CircleRole @@ -17,7 +17,7 @@ import com.unicorn.server.domain.circle.vo.CircleId import com.unicorn.server.domain.member.Member import com.unicorn.server.domain.member.enums.Role import com.unicorn.server.domain.member.port.dto.MemberProfileDto -import com.unicorn.server.domain.member.port.`in`.GetMemberProfileInPort +import com.unicorn.server.domain.member.port.`in`.MemberProfileInPort import com.unicorn.server.domain.member.vo.MemberId import org.assertj.core.api.Assertions.assertThat import org.assertj.core.api.Assertions.assertThatThrownBy @@ -264,7 +264,7 @@ class CircleServiceJoinTest { override fun countActiveByCircleId(circleId: CircleId): Long = members.count { it.circleId == circleId && it.status == CircleMemberStatus.ACTIVE && !it.deleted }.toLong() } - private class FakeMemberQueryInPort : GetMemberProfileInPort { + private class FakeMemberQueryInPort : MemberProfileInPort { private val members = linkedMapOf() fun save(member: Member): Member { members[member.id] = member; return member } override fun getMemberProfile(memberId: String): MemberProfileDto? = @@ -280,7 +280,7 @@ class CircleServiceJoinTest { } } - private class RecordingEventPublisher : EventPublisher { + private class RecordingEventPublisher : EventOutPort { override fun publish(event: com.unicorn.server.common.domain.Event) = Unit } } diff --git a/src/test/kotlin/com/unicorn/server/domain/circle/CircleServiceTest.kt b/src/test/kotlin/com/unicorn/server/domain/circle/CircleServiceTest.kt index f3675e9..d3e78a2 100644 --- a/src/test/kotlin/com/unicorn/server/domain/circle/CircleServiceTest.kt +++ b/src/test/kotlin/com/unicorn/server/domain/circle/CircleServiceTest.kt @@ -3,7 +3,7 @@ package com.unicorn.server.domain.circle import com.unicorn.server.TestIdFactory import com.unicorn.server.common.exception.BusinessException import com.unicorn.server.common.domain.Event -import com.unicorn.server.common.port.out.event.EventPublisher +import com.unicorn.server.common.port.out.event.EventOutPort import com.unicorn.server.common.vo.Email import com.unicorn.server.domain.circle.enums.CircleRole import com.unicorn.server.domain.circle.exception.CircleErrorCode @@ -18,7 +18,7 @@ import com.unicorn.server.domain.circle.service.CircleService import com.unicorn.server.domain.circle.vo.CircleId import com.unicorn.server.domain.member.Member import com.unicorn.server.domain.member.port.dto.MemberProfileDto -import com.unicorn.server.domain.member.port.`in`.GetMemberProfileInPort +import com.unicorn.server.domain.member.port.`in`.MemberProfileInPort import com.unicorn.server.domain.member.port.out.MemberOutPort import com.unicorn.server.domain.member.vo.MemberId import org.assertj.core.api.Assertions.assertThat @@ -282,7 +282,7 @@ class CircleServiceTest { override fun countActiveByCircleId(circleId: CircleId) = members.count { it.circleId == circleId && it.status == com.unicorn.server.domain.circle.enums.CircleMemberStatus.ACTIVE && !it.deleted }.toLong() } - private class FakeMemberQueryInPort : GetMemberProfileInPort { + private class FakeMemberQueryInPort : MemberProfileInPort { private val members = linkedMapOf() fun save(member: Member): Member { members[member.id] = member @@ -301,7 +301,7 @@ class CircleServiceTest { } } - private class RecordingEventPublisher : EventPublisher { + private class RecordingEventPublisher : EventOutPort { val events = mutableListOf() override fun publish(event: Event) { diff --git a/src/test/kotlin/com/unicorn/server/domain/home/HomeQueryServiceTest.kt b/src/test/kotlin/com/unicorn/server/domain/home/HomeQueryServiceTest.kt index 5229e9d..25b66f7 100644 --- a/src/test/kotlin/com/unicorn/server/domain/home/HomeQueryServiceTest.kt +++ b/src/test/kotlin/com/unicorn/server/domain/home/HomeQueryServiceTest.kt @@ -10,10 +10,10 @@ import com.unicorn.server.domain.circle.port.`in`.CircleInPort import com.unicorn.server.domain.circle.port.`in`.CircleMemberInPort import com.unicorn.server.domain.circle.vo.CircleId import com.unicorn.server.domain.member.port.dto.MemberProfileDto -import com.unicorn.server.domain.member.port.`in`.GetMemberProfileInPort +import com.unicorn.server.domain.member.port.`in`.MemberProfileInPort import com.unicorn.server.domain.member.enums.AvatarColor import com.unicorn.server.domain.home.service.HomeQueryService -import com.unicorn.server.domain.schedule.port.`in`.GetSchedulesForCircleInPort +import com.unicorn.server.domain.schedule.port.`in`.ScheduleForCircleInPort import org.assertj.core.api.Assertions.assertThat import org.assertj.core.api.Assertions.assertThatThrownBy import org.junit.jupiter.api.DisplayName @@ -106,7 +106,7 @@ class HomeQueryServiceTest { fun put(circleId: String, dto: CircleMemberDto) { memberships.computeIfAbsent(circleId) { mutableListOf() }.add(dto) } } - private class FakeMemberProfileInPort : GetMemberProfileInPort { + private class FakeMemberProfileInPort : MemberProfileInPort { private val profiles = linkedMapOf() override fun getMemberProfile(memberId: String): MemberProfileDto? = profiles[memberId] fun put(memberId: String, active: Boolean, profileImageKey: String? = null) { @@ -121,7 +121,7 @@ class HomeQueryServiceTest { } } - private class FakeScheduleQueryInPort : GetSchedulesForCircleInPort { + private class FakeScheduleQueryInPort : ScheduleForCircleInPort { var listCalled = false var countCalled = false diff --git a/src/test/kotlin/com/unicorn/server/domain/invitation/InvitationServiceTest.kt b/src/test/kotlin/com/unicorn/server/domain/invitation/InvitationServiceTest.kt index c223056..32d0f89 100644 --- a/src/test/kotlin/com/unicorn/server/domain/invitation/InvitationServiceTest.kt +++ b/src/test/kotlin/com/unicorn/server/domain/invitation/InvitationServiceTest.kt @@ -3,7 +3,7 @@ package com.unicorn.server.domain.invitation import com.unicorn.server.TestIdFactory import com.unicorn.server.common.exception.BusinessException import com.unicorn.server.common.domain.Event -import com.unicorn.server.common.port.out.event.EventPublisher +import com.unicorn.server.common.port.out.event.EventOutPort import com.unicorn.server.common.vo.Email import com.unicorn.server.domain.circle.port.dto.CircleMemberDto import com.unicorn.server.domain.circle.port.dto.CircleSummary @@ -27,7 +27,7 @@ import com.unicorn.server.domain.invitation.vo.InvitationToken import com.unicorn.server.domain.member.Member import com.unicorn.server.domain.member.enums.Role import com.unicorn.server.domain.member.port.dto.MemberProfileDto -import com.unicorn.server.domain.member.port.`in`.GetMemberProfileInPort +import com.unicorn.server.domain.member.port.`in`.MemberProfileInPort import com.unicorn.server.domain.member.vo.MemberId import org.assertj.core.api.Assertions.assertThat import org.assertj.core.api.Assertions.assertThatThrownBy @@ -221,7 +221,7 @@ class InvitationServiceTest { fun put(circleId: String, memberId: String) { members.add(circleId to memberId) } } - private class FakeMemberProfileInPort : GetMemberProfileInPort { + private class FakeMemberProfileInPort : MemberProfileInPort { private val members = linkedMapOf() fun save(member: Member): Member { members[member.id] = member; return member } override fun getMemberProfile(memberId: String): MemberProfileDto? = @@ -237,7 +237,7 @@ class InvitationServiceTest { } } - private class RecordingEventPublisher : EventPublisher { + private class RecordingEventPublisher : EventOutPort { val events = mutableListOf() override fun publish(event: Event) { diff --git a/src/test/kotlin/com/unicorn/server/domain/member/service/MemberAuthServiceTest.kt b/src/test/kotlin/com/unicorn/server/domain/member/service/MemberAuthServiceTest.kt index f01f10f..2cb4d94 100644 --- a/src/test/kotlin/com/unicorn/server/domain/member/service/MemberAuthServiceTest.kt +++ b/src/test/kotlin/com/unicorn/server/domain/member/service/MemberAuthServiceTest.kt @@ -13,8 +13,8 @@ import com.unicorn.server.domain.member.port.dto.SocialLoginCommand import com.unicorn.server.domain.member.port.dto.TokenPair import com.unicorn.server.domain.member.port.out.MemberOutPort import com.unicorn.server.domain.member.port.out.SocialAccountOutPort -import com.unicorn.server.domain.member.port.out.TokenIssuer -import com.unicorn.server.domain.member.port.out.TokenStore +import com.unicorn.server.domain.member.port.out.MemberTokenIssueOutPort +import com.unicorn.server.domain.member.port.out.MemberTokenStoreOutPort import com.unicorn.server.domain.member.vo.MemberId import org.assertj.core.api.Assertions.assertThat import org.assertj.core.api.Assertions.assertThatThrownBy @@ -170,7 +170,7 @@ class MemberAuthServiceTest { } @Test - @DisplayName("logout 호출 시 TokenStore에서 refresh token을 삭제한다") + @DisplayName("logout 호출 시 MemberTokenStoreOutPort에서 refresh token을 삭제한다") fun logout_deletesRefreshTokenFromTokenStore() { val member = memberOutPort.save(Member.create(Email("test@example.com"), "홍길동", "길동이")) tokenStore.save(member.id.toString(), "some-refresh-token") @@ -271,7 +271,7 @@ class MemberAuthServiceTest { store.values.firstOrNull { it.memberId == memberId } } - private class FakeTokenIssuer : TokenIssuer { + private class FakeTokenIssuer : MemberTokenIssueOutPort { private val refreshTokens = mutableMapOf() override fun issue(memberId: String, role: Role): TokenPair = @@ -285,7 +285,7 @@ class MemberAuthServiceTest { } } - private class FakeTokenStore : TokenStore { + private class FakeTokenStore : MemberTokenStoreOutPort { private val memberToToken = mutableMapOf() private val tokenToMember = mutableMapOf() diff --git a/src/test/kotlin/com/unicorn/server/domain/member/service/MemberProfileServiceTest.kt b/src/test/kotlin/com/unicorn/server/domain/member/service/MemberProfileServiceTest.kt index d87cf6a..5c145a7 100644 --- a/src/test/kotlin/com/unicorn/server/domain/member/service/MemberProfileServiceTest.kt +++ b/src/test/kotlin/com/unicorn/server/domain/member/service/MemberProfileServiceTest.kt @@ -1,8 +1,8 @@ package com.unicorn.server.domain.member.service import com.unicorn.server.common.domain.Event -import com.unicorn.server.common.port.out.event.EventPublisher -import com.unicorn.server.common.port.out.storage.ObjectStorage +import com.unicorn.server.common.port.out.event.EventOutPort +import com.unicorn.server.common.port.out.storage.ObjectStorageOutPort import com.unicorn.server.common.port.out.storage.ObjectUploadCommand import com.unicorn.server.common.port.out.storage.StoredObject import com.unicorn.server.common.port.out.storage.exception.ObjectSizeExceededException @@ -350,7 +350,7 @@ class MemberProfileServiceTest { } } - private class RecordingEventPublisher : EventPublisher { + private class RecordingEventPublisher : EventOutPort { val events = mutableListOf() override fun publish(event: Event) { @@ -358,7 +358,7 @@ class MemberProfileServiceTest { } } - private class FakeObjectStorage : ObjectStorage { + private class FakeObjectStorage : ObjectStorageOutPort { val deleted = mutableListOf() override fun upload(command: ObjectUploadCommand): StoredObject = diff --git a/src/test/kotlin/com/unicorn/server/domain/member/service/MemberSeedTest.kt b/src/test/kotlin/com/unicorn/server/domain/member/service/MemberSeedTest.kt index cb61543..62c4f52 100644 --- a/src/test/kotlin/com/unicorn/server/domain/member/service/MemberSeedTest.kt +++ b/src/test/kotlin/com/unicorn/server/domain/member/service/MemberSeedTest.kt @@ -9,7 +9,7 @@ import com.unicorn.server.domain.member.enums.SocialProvider import com.unicorn.server.domain.member.port.dto.SocialLoginCommand import com.unicorn.server.domain.member.port.out.MemberOutPort import com.unicorn.server.domain.member.port.out.SocialAccountOutPort -import com.unicorn.server.domain.member.port.out.TokenStore +import com.unicorn.server.domain.member.port.out.MemberTokenStoreOutPort import com.unicorn.server.domain.member.vo.MemberId import com.unicorn.server.infrastructure.adapter.out.token.InMemoryTokenStoreAdapter import com.unicorn.server.infrastructure.adapter.out.token.JwtProvider @@ -23,7 +23,7 @@ class MemberSeedTest : BaseTest() { private val memberOutPort = FakeMemberOutPort() private val socialAccountOutPort = FakeSocialAccountOutPort() - private val tokenStore: TokenStore = InMemoryTokenStoreAdapter() + private val tokenStore: MemberTokenStoreOutPort = InMemoryTokenStoreAdapter() private val jwtProvider = JwtProvider(testJwtSecret, accessTokenExpirationSeconds, refreshTokenExpirationSeconds) private val memberAuthService = MemberAuthService(memberOutPort, socialAccountOutPort, jwtProvider, tokenStore) diff --git a/src/test/kotlin/com/unicorn/server/domain/member/service/OnboardingServiceTest.kt b/src/test/kotlin/com/unicorn/server/domain/member/service/OnboardingServiceTest.kt index db59a89..00b8b55 100644 --- a/src/test/kotlin/com/unicorn/server/domain/member/service/OnboardingServiceTest.kt +++ b/src/test/kotlin/com/unicorn/server/domain/member/service/OnboardingServiceTest.kt @@ -10,8 +10,8 @@ import com.unicorn.server.domain.member.exception.WithdrawnMemberException import com.unicorn.server.domain.member.port.dto.CompleteOnboardingCommand import com.unicorn.server.domain.member.port.dto.TokenPair import com.unicorn.server.domain.member.port.out.MemberOutPort -import com.unicorn.server.domain.member.port.out.TokenIssuer -import com.unicorn.server.domain.member.port.out.TokenStore +import com.unicorn.server.domain.member.port.out.MemberTokenIssueOutPort +import com.unicorn.server.domain.member.port.out.MemberTokenStoreOutPort import com.unicorn.server.domain.member.vo.MemberId import com.unicorn.server.domain.term.MemberTerm import com.unicorn.server.domain.term.Term @@ -174,14 +174,14 @@ class OnboardingServiceTest { store.filter { it.memberId == memberId } } - private class FakeTokenIssuer : TokenIssuer { + private class FakeTokenIssuer : MemberTokenIssueOutPort { override fun issue(memberId: String, role: Role): TokenPair = TokenPair("access-$memberId-$role", "refresh-$memberId-$role") override fun parseRefreshToken(refreshToken: String): String? = null } - private class FakeTokenStore : TokenStore { + private class FakeTokenStore : MemberTokenStoreOutPort { private val memberToToken = mutableMapOf() private val tokenToMember = mutableMapOf() diff --git a/src/test/kotlin/com/unicorn/server/domain/member/service/SocialLoginServiceTest.kt b/src/test/kotlin/com/unicorn/server/domain/member/service/SocialLoginServiceTest.kt index 907968d..bd404c6 100644 --- a/src/test/kotlin/com/unicorn/server/domain/member/service/SocialLoginServiceTest.kt +++ b/src/test/kotlin/com/unicorn/server/domain/member/service/SocialLoginServiceTest.kt @@ -1,12 +1,12 @@ package com.unicorn.server.domain.member.service import com.unicorn.server.domain.member.enums.SocialProvider -import com.unicorn.server.domain.member.port.`in`.SocialLoginInPort +import com.unicorn.server.domain.member.port.`in`.MemberSocialLoginInPort import com.unicorn.server.domain.member.port.dto.KakaoUserInfo import com.unicorn.server.domain.member.port.dto.SocialLoginCommand import com.unicorn.server.domain.member.port.dto.SocialLoginResult import com.unicorn.server.domain.member.port.dto.TokenPair -import com.unicorn.server.domain.member.port.out.KakaoAuthPort +import com.unicorn.server.domain.member.port.out.MemberKakaoAuthOutPort import org.assertj.core.api.Assertions.assertThat import org.junit.jupiter.api.DisplayName import org.junit.jupiter.api.Test @@ -38,7 +38,7 @@ class SocialLoginServiceTest { ) } - private class FakeKakaoAuthPort : KakaoAuthPort { + private class FakeKakaoAuthPort : MemberKakaoAuthOutPort { override fun verify(idToken: String): KakaoUserInfo = KakaoUserInfo( providerId = "fake-kakao-id", @@ -48,7 +48,7 @@ class SocialLoginServiceTest { ) } - private class RecordingSocialLoginInPort : SocialLoginInPort { + private class RecordingSocialLoginInPort : MemberSocialLoginInPort { var command: SocialLoginCommand? = null override fun login(command: SocialLoginCommand): SocialLoginResult { diff --git a/src/test/kotlin/com/unicorn/server/domain/notification/service/NotificationDispatchServiceTest.kt b/src/test/kotlin/com/unicorn/server/domain/notification/service/NotificationDispatchServiceTest.kt index 3e12e33..d934399 100644 --- a/src/test/kotlin/com/unicorn/server/domain/notification/service/NotificationDispatchServiceTest.kt +++ b/src/test/kotlin/com/unicorn/server/domain/notification/service/NotificationDispatchServiceTest.kt @@ -7,8 +7,8 @@ import com.unicorn.server.domain.notification.enums.NotificationStatus import com.unicorn.server.domain.notification.exception.PermanentNotificationSendException import com.unicorn.server.domain.notification.exception.RetryableNotificationSendException import com.unicorn.server.domain.notification.port.dto.NotificationMessage -import com.unicorn.server.domain.notification.port.out.NotificationMessageComposer -import com.unicorn.server.domain.notification.port.out.NotificationSender +import com.unicorn.server.domain.notification.port.out.NotificationMessageComposeOutPort +import com.unicorn.server.domain.notification.port.out.NotificationSendOutPort import com.unicorn.server.domain.notification.port.out.NotificationOutPort import com.unicorn.server.infrastructure.config.NotificationProperties import org.assertj.core.api.Assertions.assertThat @@ -114,7 +114,7 @@ class NotificationDispatchServiceTest { notifications.values.filter { it.isDispatchable(now) }.take(limit) } - private class FakeComposer : NotificationMessageComposer { + private class FakeComposer : NotificationMessageComposeOutPort { override fun channel(): NotificationChannel = NotificationChannel.PUSH override fun compose(notification: Notification): NotificationMessage = @@ -123,7 +123,7 @@ class NotificationDispatchServiceTest { } } - private class RecordingSender : NotificationSender { + private class RecordingSender : NotificationSendOutPort { val messages = mutableListOf() override fun channel(): NotificationChannel = NotificationChannel.PUSH @@ -133,7 +133,7 @@ class NotificationDispatchServiceTest { } } - private class RetryableFailSender : NotificationSender { + private class RetryableFailSender : NotificationSendOutPort { override fun channel(): NotificationChannel = NotificationChannel.PUSH override fun send(message: NotificationMessage) { @@ -141,7 +141,7 @@ class NotificationDispatchServiceTest { } } - private class PermanentFailSender : NotificationSender { + private class PermanentFailSender : NotificationSendOutPort { override fun channel(): NotificationChannel = NotificationChannel.PUSH override fun send(message: NotificationMessage) { diff --git a/src/test/kotlin/com/unicorn/server/domain/schedule/service/FamilyScheduleNotificationServiceTest.kt b/src/test/kotlin/com/unicorn/server/domain/schedule/service/FamilyScheduleNotificationServiceTest.kt index 3293ef0..38cf765 100644 --- a/src/test/kotlin/com/unicorn/server/domain/schedule/service/FamilyScheduleNotificationServiceTest.kt +++ b/src/test/kotlin/com/unicorn/server/domain/schedule/service/FamilyScheduleNotificationServiceTest.kt @@ -2,7 +2,7 @@ package com.unicorn.server.domain.schedule.service import com.unicorn.server.common.domain.Event import com.unicorn.server.common.exception.BusinessException -import com.unicorn.server.common.port.out.event.EventPublisher +import com.unicorn.server.common.port.out.event.EventOutPort import com.unicorn.server.domain.schedule.Schedule import com.unicorn.server.domain.schedule.event.FamilyScheduleNotificationRequestedEvent import com.unicorn.server.domain.schedule.exception.ScheduleErrorCode @@ -143,7 +143,7 @@ class FamilyScheduleNotificationServiceTest { override fun isInitiator(circleId: String, memberId: String): Boolean = false } - private class RecordingEventPublisher : EventPublisher { + private class RecordingEventPublisher : EventOutPort { val events = mutableListOf() override fun publish(event: Event) { diff --git a/src/test/kotlin/com/unicorn/server/domain/schedule/service/ScheduleCommandServiceTest.kt b/src/test/kotlin/com/unicorn/server/domain/schedule/service/ScheduleCommandServiceTest.kt index 4a0f21b..a2f6385 100644 --- a/src/test/kotlin/com/unicorn/server/domain/schedule/service/ScheduleCommandServiceTest.kt +++ b/src/test/kotlin/com/unicorn/server/domain/schedule/service/ScheduleCommandServiceTest.kt @@ -2,7 +2,7 @@ package com.unicorn.server.domain.schedule.service import com.unicorn.server.common.exception.BusinessException import com.unicorn.server.common.domain.Event -import com.unicorn.server.common.port.out.event.EventPublisher +import com.unicorn.server.common.port.out.event.EventOutPort import com.unicorn.server.domain.schedule.Schedule import com.unicorn.server.domain.schedule.ScheduleConfirmation import com.unicorn.server.domain.schedule.event.ScheduleCreatedEvent @@ -351,7 +351,7 @@ class ScheduleCommandServiceTest { override fun isInitiator(circleId: String, memberId: String): Boolean = circleId to memberId in initiators } - private class RecordingEventPublisher : EventPublisher { + private class RecordingEventPublisher : EventOutPort { val events = mutableListOf() override fun publish(event: Event) { diff --git a/src/test/kotlin/com/unicorn/server/domain/schedule/service/ScheduleConfirmationRequestServiceTest.kt b/src/test/kotlin/com/unicorn/server/domain/schedule/service/ScheduleConfirmationRequestServiceTest.kt index d848c04..c96fe9b 100644 --- a/src/test/kotlin/com/unicorn/server/domain/schedule/service/ScheduleConfirmationRequestServiceTest.kt +++ b/src/test/kotlin/com/unicorn/server/domain/schedule/service/ScheduleConfirmationRequestServiceTest.kt @@ -1,7 +1,7 @@ package com.unicorn.server.domain.schedule.service import com.unicorn.server.common.domain.Event -import com.unicorn.server.common.port.out.event.EventPublisher +import com.unicorn.server.common.port.out.event.EventOutPort import com.unicorn.server.domain.schedule.Schedule import com.unicorn.server.domain.schedule.event.ScheduleConfirmationRequestDueEvent import com.unicorn.server.domain.schedule.port.dto.SchedulePageCursor @@ -80,7 +80,7 @@ class ScheduleConfirmationRequestServiceTest { } } - private class RecordingEventPublisher : EventPublisher { + private class RecordingEventPublisher : EventOutPort { val events = mutableListOf() override fun publish(event: Event) { diff --git a/src/test/kotlin/com/unicorn/server/domain/schedule/service/ScheduleConfirmationServiceTest.kt b/src/test/kotlin/com/unicorn/server/domain/schedule/service/ScheduleConfirmationServiceTest.kt index afec10c..d63704e 100644 --- a/src/test/kotlin/com/unicorn/server/domain/schedule/service/ScheduleConfirmationServiceTest.kt +++ b/src/test/kotlin/com/unicorn/server/domain/schedule/service/ScheduleConfirmationServiceTest.kt @@ -2,7 +2,7 @@ package com.unicorn.server.domain.schedule.service import com.unicorn.server.common.exception.BusinessException import com.unicorn.server.common.domain.Event -import com.unicorn.server.common.port.out.event.EventPublisher +import com.unicorn.server.common.port.out.event.EventOutPort import com.unicorn.server.domain.schedule.Schedule import com.unicorn.server.domain.schedule.ScheduleConfirmation import com.unicorn.server.domain.schedule.enums.ConfirmationType @@ -249,7 +249,7 @@ class ScheduleConfirmationServiceTest { override fun isInitiator(circleId: String, memberId: String): Boolean = false } - private class RecordingEventPublisher : EventPublisher { + private class RecordingEventPublisher : EventOutPort { val events = mutableListOf() override fun publish(event: Event) { diff --git a/src/test/kotlin/com/unicorn/server/domain/schedule/service/ScheduleReminderServiceTest.kt b/src/test/kotlin/com/unicorn/server/domain/schedule/service/ScheduleReminderServiceTest.kt index 080b1d8..ff793cd 100644 --- a/src/test/kotlin/com/unicorn/server/domain/schedule/service/ScheduleReminderServiceTest.kt +++ b/src/test/kotlin/com/unicorn/server/domain/schedule/service/ScheduleReminderServiceTest.kt @@ -1,7 +1,7 @@ package com.unicorn.server.domain.schedule.service import com.unicorn.server.common.domain.Event -import com.unicorn.server.common.port.out.event.EventPublisher +import com.unicorn.server.common.port.out.event.EventOutPort import com.unicorn.server.domain.schedule.Schedule import com.unicorn.server.domain.schedule.enums.ScheduleReminderType import com.unicorn.server.domain.schedule.event.ScheduleReminderDueEvent @@ -141,7 +141,7 @@ class ScheduleReminderServiceTest { ): List = error("not used") } - private class RecordingEventPublisher : EventPublisher { + private class RecordingEventPublisher : EventOutPort { val events = mutableListOf() override fun publish(event: Event) { diff --git a/src/test/kotlin/com/unicorn/server/infrastructure/adapter/in/event/invitation/InvitationNotificationEventListenerTest.kt b/src/test/kotlin/com/unicorn/server/infrastructure/adapter/in/event/invitation/InvitationNotificationEventListenerTest.kt index b4652e0..905ef1f 100644 --- a/src/test/kotlin/com/unicorn/server/infrastructure/adapter/in/event/invitation/InvitationNotificationEventListenerTest.kt +++ b/src/test/kotlin/com/unicorn/server/infrastructure/adapter/in/event/invitation/InvitationNotificationEventListenerTest.kt @@ -1,7 +1,7 @@ package com.unicorn.server.infrastructure.adapter.`in`.event.invitation import com.unicorn.server.common.domain.Event -import com.unicorn.server.common.port.out.event.EventPublisher +import com.unicorn.server.common.port.out.event.EventOutPort import com.unicorn.server.domain.circle.port.`in`.CircleMemberInPort import com.unicorn.server.domain.circle.port.dto.CircleMemberDto import com.unicorn.server.domain.circle.port.dto.CircleSummary @@ -113,7 +113,7 @@ class InvitationNotificationEventListenerTest { override fun deactivate(memberId: String, tokenId: Long) = error("not used") } - private class RecordingEventPublisher : EventPublisher { + private class RecordingEventPublisher : EventOutPort { val events = mutableListOf() override fun publish(event: Event) { diff --git a/src/test/kotlin/com/unicorn/server/infrastructure/adapter/in/event/schedule/ScheduleNotificationEventListenerTest.kt b/src/test/kotlin/com/unicorn/server/infrastructure/adapter/in/event/schedule/ScheduleNotificationEventListenerTest.kt index fb422c3..62001ac 100644 --- a/src/test/kotlin/com/unicorn/server/infrastructure/adapter/in/event/schedule/ScheduleNotificationEventListenerTest.kt +++ b/src/test/kotlin/com/unicorn/server/infrastructure/adapter/in/event/schedule/ScheduleNotificationEventListenerTest.kt @@ -1,7 +1,7 @@ package com.unicorn.server.infrastructure.adapter.`in`.event.schedule import com.unicorn.server.common.domain.Event -import com.unicorn.server.common.port.out.event.EventPublisher +import com.unicorn.server.common.port.out.event.EventOutPort import com.unicorn.server.domain.circle.port.`in`.CircleMemberInPort import com.unicorn.server.domain.circle.port.dto.CircleMemberDto import com.unicorn.server.domain.circle.port.dto.CircleSummary @@ -309,7 +309,7 @@ class ScheduleNotificationEventListenerTest { override fun hasConfirmed(scheduleId: ScheduleId, memberId: String): Boolean = memberId in confirmedMembers } - private class RecordingEventPublisher : EventPublisher { + private class RecordingEventPublisher : EventOutPort { val events = mutableListOf() override fun publish(event: Event) { diff --git a/src/test/kotlin/com/unicorn/server/integration/CircleInvitationFlowIntegrationTest.kt b/src/test/kotlin/com/unicorn/server/integration/CircleInvitationFlowIntegrationTest.kt index 8365278..1cc1afa 100644 --- a/src/test/kotlin/com/unicorn/server/integration/CircleInvitationFlowIntegrationTest.kt +++ b/src/test/kotlin/com/unicorn/server/integration/CircleInvitationFlowIntegrationTest.kt @@ -10,10 +10,10 @@ import com.unicorn.server.domain.invitation.exception.InvitationExpiredException import com.unicorn.server.domain.invitation.exception.InvitationSelfApprovalForbiddenException import com.unicorn.server.domain.invitation.port.dto.DispatchInvitationCommand import com.unicorn.server.domain.invitation.port.dto.IssueInvitationCommand -import com.unicorn.server.domain.invitation.port.`in`.AcceptCircleInvitationInPort -import com.unicorn.server.domain.invitation.port.`in`.DispatchInvitationInPort -import com.unicorn.server.domain.invitation.port.`in`.GetInvitationByTokenInPort -import com.unicorn.server.domain.invitation.port.`in`.IssueInvitationInPort +import com.unicorn.server.domain.invitation.port.`in`.InvitationAcceptInPort +import com.unicorn.server.domain.invitation.port.`in`.InvitationDispatchInPort +import com.unicorn.server.domain.invitation.port.`in`.InvitationByTokenInPort +import com.unicorn.server.domain.invitation.port.`in`.InvitationIssueInPort import com.unicorn.server.domain.member.Member import com.unicorn.server.domain.member.enums.Role import com.unicorn.server.domain.member.port.out.MemberOutPort @@ -35,10 +35,10 @@ class CircleInvitationFlowIntegrationTest : BaseTest() { @Autowired private lateinit var memberOutPort: MemberOutPort @Autowired private lateinit var circleInPort: CircleInPort @Autowired private lateinit var homeQueryInPort: HomeQueryInPort - @Autowired private lateinit var issueInvitationInPort: IssueInvitationInPort - @Autowired private lateinit var dispatchInvitationInPort: DispatchInvitationInPort - @Autowired private lateinit var getInvitationByTokenInPort: GetInvitationByTokenInPort - @Autowired private lateinit var acceptCircleInvitationInPort: AcceptCircleInvitationInPort + @Autowired private lateinit var issueInvitationInPort: InvitationIssueInPort + @Autowired private lateinit var dispatchInvitationInPort: InvitationDispatchInPort + @Autowired private lateinit var getInvitationByTokenInPort: InvitationByTokenInPort + @Autowired private lateinit var acceptCircleInvitationInPort: InvitationAcceptInPort @Autowired private lateinit var invitationDispatchLogJpaRepository: InvitationDispatchLogJpaRepository @Autowired private lateinit var invitationClickLogJpaRepository: InvitationClickLogJpaRepository @Autowired private lateinit var invitationRedemptionLogJpaRepository: InvitationRedemptionLogJpaRepository From ba90f05cba0edaaa75ad7aa232f6918973e7b933 Mon Sep 17 00:00:00 2001 From: junyong Date: Sat, 25 Jul 2026 17:34:22 +0900 Subject: [PATCH 7/9] =?UTF-8?q?docs[SAIO-271]:=20=ED=8F=AC=ED=8A=B8=20?= =?UTF-8?q?=EC=9D=91=EC=A7=91=EB=8F=84=20=EA=B8=B0=EC=A4=80=20SKILL.md=20?= =?UTF-8?q?=EB=82=B4=EC=9A=A9=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- skills/architecture-patterns/SKILL.md | 75 +++++++++++++++++++++++++++ 1 file changed, 75 insertions(+) diff --git a/skills/architecture-patterns/SKILL.md b/skills/architecture-patterns/SKILL.md index 9a66162..63ab6d9 100644 --- a/skills/architecture-patterns/SKILL.md +++ b/skills/architecture-patterns/SKILL.md @@ -696,6 +696,81 @@ interface CircleAccessOutPort { } ``` +## Port Cohesion + +Naming tells you how to spell a port. Cohesion tells you whether it should exist as one port or several. Judge every split-or-merge decision — including one that a naming refactor makes tempting — against three independent criteria. All three must match before merging two methods into one port. + +1. **Same actor.** Who or what calls this method: an authenticated end user, an anonymous caller, a refresh-token holder, a scheduled job, an event listener, another bounded context reading through this port? Two methods with different actors stay on different ports even if nothing else differs. +2. **Same call pattern.** Synchronous HTTP request/response, async event handling, and scheduled batch are different call patterns. A method invoked by a controller and a method invoked by a `@Scheduled` job or an event listener do not belong on the same port, even if a human would group them under the same feature name. +3. **Same reason to change.** Would a single business-rule change touch both methods together, or does one method carry side effects/dependencies (issuing tokens, publishing events, enforcing a different validation rule) that the other doesn't share? If the two methods' reasons to change are independent, keep them apart. + +**Do not use "the same class currently implements both" as a merge signal.** In this codebase a driving (`In`) port has exactly one real implementation by construction — the use-case service — so "same implementer" is true almost by default and filters out nothing. The only reliable signals are the three criteria above, checked against the actual caller (open the controller, event listener, or scheduled job — don't infer from the port name). + +### Worked Examples + +**Merge — all three criteria match:** + +```kotlin +// Before: three single-method ports, all called from the same +// ScheduleController endpoint group with @AuthenticationPrincipal, +// all synchronous HTTP writes on the same resource. +interface ScheduleCreateInPort { fun create(command: CreateScheduleCommand): ScheduleId } +interface ScheduleUpdateInPort { fun update(command: UpdateScheduleCommand) } +interface ScheduleDeleteInPort { fun delete(scheduleId: ScheduleId, circleId: String, memberId: String) } + +// After: same actor, same call pattern, same reason to change (schedule CRUD rules). +interface ScheduleCommandInPort { + fun create(command: CreateScheduleCommand): ScheduleId + fun update(command: UpdateScheduleCommand) + fun delete(scheduleId: ScheduleId, circleId: String, memberId: String) +} +``` + +**Keep separate — actor differs despite an identical implementing class:** + +```kotlin +// Both methods happen to be implemented by the same MemberAuthService, +// but the callers are different actors at different auth stages: +// kakaoLogin is called by an anonymous pre-auth caller (AuthController /kakao), +// reissue is called by a refresh-token holder (AuthController /refresh), +// logout is called by an authenticated session (MemberController /me/logout). +// Do not merge these into one MemberAuthInPort just because one class implements all three. +interface MemberKakaoLoginInPort { fun kakaoLogin(idToken: String): SocialLoginResult } +interface MemberTokenReissueInPort { fun reissue(refreshToken: String): TokenPair } +interface MemberLogoutInPort { fun logout(memberId: String) } +``` + +**Keep separate — call pattern differs despite an identical implementing class:** + +```kotlin +// Both methods are implemented by the same ScheduleConfirmationService. +// register() is a synchronous HTTP write from an authenticated circle member. +// hasConfirmed() is called from ScheduleNotificationEventListener — an event +// listener, not a human actor — to decide whether to send a reminder. +interface ScheduleConfirmationRegisterInPort { fun register(command: RegisterConfirmationCommand): ConfirmationType } +interface ScheduleConfirmationStatusInPort { fun hasConfirmed(scheduleId: ScheduleId, memberId: String): Boolean } +``` + +**Keep separate — looks like the same feature, but the caller is a different bounded context:** + +```kotlin +// MemberInPort is called by MemberController (the member managing their own profile). +// MemberProfileInPort with the same-looking getMemberProfile() method is called by +// HomeQueryService, CircleService, and InvitationService — other domains reading a +// member's public profile. Same-sounding name, unrelated actor and reason to change. +interface MemberInPort { fun getById(memberId: String): Member } +interface MemberProfileInPort { fun getMemberProfile(memberId: String): MemberProfileDto? } +``` + +### Checklist + +Before merging ports, or before adding a new method to an existing port instead of creating a new one: + +- Open every adapter that calls the port (controller, event listener, scheduled job, another domain's service) — don't guess from the port's name or the feature it "sounds like" it belongs to. +- Confirm actor, call pattern, and reason to change all match across every method involved. +- If a merge is justified but the methods currently live in different service classes, plan the service consolidation as part of the same change — don't merge the interface while leaving two divergent implementations behind it. +- A port with unusually many single-method siblings implemented by one class is a prompt to *check* cohesion, not a conclusion that they should merge. + ## Spring Configuration Management Choose between `@Value`, `@ConfigurationProperties`, and `@Configuration` based on the scope and purpose of the configuration. From 14e324a0ae742a425060627d347c9ce1292c4834 Mon Sep 17 00:00:00 2001 From: lso5507 Date: Sat, 25 Jul 2026 17:34:35 +0900 Subject: [PATCH 8/9] =?UTF-8?q?refactor[SAIO-273]=20=EC=8D=A8=ED=81=B4?= =?UTF-8?q?=EB=A9=A4=EB=B2=84=20=EC=97=94=ED=8B=B0=ED=8B=B0=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../circle/service/CircleMemberService.kt | 197 ++++++++++++++++++ .../domain/circle/service/CircleService.kt | 170 +-------------- .../domain/circle/CircleServiceJoinTest.kt | 26 ++- .../server/domain/circle/CircleServiceTest.kt | 14 +- 4 files changed, 220 insertions(+), 187 deletions(-) create mode 100644 src/main/kotlin/com/unicorn/server/domain/circle/service/CircleMemberService.kt diff --git a/src/main/kotlin/com/unicorn/server/domain/circle/service/CircleMemberService.kt b/src/main/kotlin/com/unicorn/server/domain/circle/service/CircleMemberService.kt new file mode 100644 index 0000000..7e0c2b6 --- /dev/null +++ b/src/main/kotlin/com/unicorn/server/domain/circle/service/CircleMemberService.kt @@ -0,0 +1,197 @@ +package com.unicorn.server.domain.circle.service + +import com.unicorn.server.common.exception.BusinessException +import com.unicorn.server.common.port.out.event.EventPublisher +import com.unicorn.server.domain.circle.Circle +import com.unicorn.server.domain.circle.CircleMember +import com.unicorn.server.domain.circle.enums.CircleMemberStatus +import com.unicorn.server.domain.circle.enums.CircleRole +import com.unicorn.server.domain.circle.event.CircleInitiatorTransferredEvent +import com.unicorn.server.domain.circle.event.CircleMemberJoinedEvent +import com.unicorn.server.domain.circle.exception.CircleErrorCode +import com.unicorn.server.domain.circle.exception.CircleNotFoundException +import com.unicorn.server.domain.circle.exception.CircleSuccessorNotFoundException +import com.unicorn.server.domain.circle.port.dto.CircleMemberDto +import com.unicorn.server.domain.circle.port.dto.CircleSummary +import com.unicorn.server.domain.circle.port.dto.JoinCircleResult +import com.unicorn.server.domain.circle.port.`in`.CircleMemberInPort +import com.unicorn.server.domain.circle.port.out.CircleMemberIdGenerator +import com.unicorn.server.domain.circle.port.out.CircleMemberOutPort +import com.unicorn.server.domain.circle.port.out.CircleOutPort +import com.unicorn.server.domain.circle.vo.CircleId +import com.unicorn.server.domain.member.exception.MemberNotFoundException +import com.unicorn.server.domain.member.port.`in`.GetMemberProfileInPort +import com.unicorn.server.domain.member.vo.MemberId +import org.springframework.stereotype.Service +import org.springframework.transaction.annotation.Transactional + +@Service +@Transactional +class CircleMemberService( + private val circleOutPort: CircleOutPort, + private val circleMemberOutPort: CircleMemberOutPort, + private val circleMemberIdGenerator: CircleMemberIdGenerator, + private val getMemberProfileInPort: GetMemberProfileInPort, + private val eventPublisher: EventPublisher, +) : CircleMemberInPort { + override fun join(circleId: String, memberId: String): JoinCircleResult { + val targetCircleId = CircleId.of(circleId) + val targetMemberId = MemberId.of(memberId) + circleOutPort.findById(targetCircleId) ?: throw CircleNotFoundException(circleId) + + val existingMembership = circleMemberOutPort.findByCircleAndMember(targetCircleId, targetMemberId) + if (existingMembership != null) { + if (existingMembership.status == CircleMemberStatus.ACTIVE && !existingMembership.deleted) { + throw BusinessException(CircleErrorCode.ALREADY_JOINED) + } + assertNoActiveCircle(targetMemberId) + assertCircleCapacity(targetCircleId) + val member = getMemberProfileInPort.getMemberProfile(memberId) ?: throw MemberNotFoundException(memberId) + existingMembership.rejoin(member.nickname) + circleMemberOutPort.save(existingMembership) + eventPublisher.publish(CircleMemberJoinedEvent(circleId, memberId, existingMembership.role)) + return JoinCircleResult(circleId = circleId) + } + + assertNoActiveCircle(targetMemberId) + assertCircleCapacity(targetCircleId) + val member = getMemberProfileInPort.getMemberProfile(memberId) ?: throw MemberNotFoundException(memberId) + val circleMember = circleMemberOutPort.save( + CircleMember.createMember(circleMemberIdGenerator.next(), targetCircleId, targetMemberId, member.nickname), + ) + eventPublisher.publish(CircleMemberJoinedEvent(circleId, memberId, circleMember.role)) + return JoinCircleResult(circleId = circleId) + } + + override fun leave(circleId: String, memberId: String) { + val targetCircleId = CircleId.of(circleId) + val targetMemberId = MemberId.of(memberId) + val circle = circleOutPort.findById(targetCircleId) + ?: throw CircleNotFoundException(circleId) + if (circle.deleted) { + throw CircleNotFoundException(circleId) + } + + val membership = circleMemberOutPort.findByCircleAndMember(targetCircleId, targetMemberId) + ?: throw BusinessException(CircleErrorCode.CIRCLE_ACCESS_DENIED) + if (membership.status != CircleMemberStatus.ACTIVE || membership.deleted) { + throw BusinessException(CircleErrorCode.CIRCLE_ACCESS_DENIED) + } + + leaveCircleMember(circle, membership, targetMemberId) + } + + override fun getCircleMembers(circleId: String): List = + circleMemberOutPort.findAllActiveByCircleId(CircleId.of(circleId)) + .filter { it.status == CircleMemberStatus.ACTIVE && !it.deleted } + .map { + CircleMemberDto( + memberId = it.memberId.toString(), + nickname = it.nickname, + role = it.role.name, + active = true, + ) + } + + override fun isCircleMember(circleId: String, memberId: String): Boolean = + circleMemberOutPort.existsActiveByCircleAndMember(CircleId.of(circleId), MemberId.of(memberId)) + + override fun transferInitiator( + circleId: String, + currentInitiatorId: String, + newInitiatorId: String, + ): CircleSummary { + val targetCircleId = CircleId.of(circleId) + val requesterId = MemberId.of(currentInitiatorId) + val targetId = MemberId.of(newInitiatorId) + if (requesterId == targetId) { + throw BusinessException(CircleErrorCode.INITIATOR_DELEGATION_SELF_FORBIDDEN) + } + + val circle = circleOutPort.findById(targetCircleId) ?: throw CircleNotFoundException(circleId) + val requesterMembership = circleMemberOutPort.findByCircleAndMember(targetCircleId, requesterId) + ?: throw BusinessException(CircleErrorCode.INITIATOR_DELEGATION_FORBIDDEN) + val targetMembership = circleMemberOutPort.findByCircleAndMember(targetCircleId, targetId) + ?: throw BusinessException(CircleErrorCode.INITIATOR_DELEGATION_TARGET_INVALID) + + circle.transferInitiator(requesterMembership, targetMembership) + circleMemberOutPort.save(requesterMembership) + circleMemberOutPort.save(targetMembership) + val savedCircle = circleOutPort.save(circle) + eventPublisher.publish( + CircleInitiatorTransferredEvent( + circleId = savedCircle.id.toString(), + previousInitiatorMemberId = requesterId.toString(), + newInitiatorMemberId = targetId.toString(), + ), + ) + return CircleSummary(savedCircle.id.toString(), savedCircle.name, savedCircle.ownerId.toString()) + } + + override fun handleMemberWithdrawal(memberId: String) { + val withdrawingMemberId = MemberId.of(memberId) + val memberships = circleMemberOutPort.findAllActiveByMemberId(withdrawingMemberId) + + memberships.forEach { membership -> + val circle = circleOutPort.findById(membership.circleId) + ?: throw CircleNotFoundException(membership.circleId.toString()) + leaveCircleMember(circle, membership, withdrawingMemberId) + } + } + + private fun leaveCircleMember( + circle: Circle, + membership: CircleMember, + departingMemberId: MemberId, + ) { + if (membership.role != CircleRole.INITIATOR) { + circle.leaveMember(membership) + circleMemberOutPort.save(membership) + return + } + + if (!circleMemberOutPort.existsActiveMemberByCircleIdExcludingMemberId(circle.id, departingMemberId)) { + circle.deleteWithLastMember(membership) + circleMemberOutPort.save(membership) + circleOutPort.save(circle) + return + } + + val successor = try { + circleMemberOutPort.findOldestActiveByCircleIdExcludingMemberId(circle.id, departingMemberId) + } catch (_: CircleSuccessorNotFoundException) { + circle.deleteWithLastMember(membership) + circleMemberOutPort.save(membership) + circleOutPort.save(circle) + return + } + + val newInitiator = circle.leaveMember(membership, successor) + circleMemberOutPort.save(membership) + circleMemberOutPort.save(newInitiator) + val savedCircle = circleOutPort.save(circle) + eventPublisher.publish( + CircleInitiatorTransferredEvent( + circleId = savedCircle.id.toString(), + previousInitiatorMemberId = departingMemberId.toString(), + newInitiatorMemberId = newInitiator.memberId.toString(), + ), + ) + } + + private fun assertNoActiveCircle(memberId: MemberId) { + if (circleMemberOutPort.findAllActiveByMemberId(memberId).isNotEmpty()) { + throw BusinessException(CircleErrorCode.ALREADY_HAS_ACTIVE_CIRCLE) + } + } + + private fun assertCircleCapacity(circleId: CircleId) { + if (circleMemberOutPort.countActiveByCircleId(circleId) >= MAX_MEMBERS) { + throw BusinessException(CircleErrorCode.CIRCLE_MEMBER_LIMIT_EXCEEDED) + } + } + + companion object { + private const val MAX_MEMBERS = 10L + } +} diff --git a/src/main/kotlin/com/unicorn/server/domain/circle/service/CircleService.kt b/src/main/kotlin/com/unicorn/server/domain/circle/service/CircleService.kt index d1aa237..3f4d7e7 100644 --- a/src/main/kotlin/com/unicorn/server/domain/circle/service/CircleService.kt +++ b/src/main/kotlin/com/unicorn/server/domain/circle/service/CircleService.kt @@ -4,20 +4,13 @@ import com.unicorn.server.common.exception.BusinessException import com.unicorn.server.common.port.out.event.EventPublisher import com.unicorn.server.domain.circle.Circle import com.unicorn.server.domain.circle.CircleMember -import com.unicorn.server.domain.circle.enums.CircleMemberStatus -import com.unicorn.server.domain.circle.enums.CircleRole import com.unicorn.server.domain.circle.event.CircleCreatedEvent -import com.unicorn.server.domain.circle.event.CircleInitiatorTransferredEvent import com.unicorn.server.domain.circle.event.CircleMemberJoinedEvent import com.unicorn.server.domain.circle.exception.CircleErrorCode import com.unicorn.server.domain.circle.exception.CircleNotFoundException -import com.unicorn.server.domain.circle.exception.CircleSuccessorNotFoundException -import com.unicorn.server.domain.circle.port.dto.CircleMemberDto import com.unicorn.server.domain.circle.port.dto.CircleSummary import com.unicorn.server.domain.circle.port.dto.CreateCircleCommand -import com.unicorn.server.domain.circle.port.dto.JoinCircleResult import com.unicorn.server.domain.circle.port.`in`.CircleInPort -import com.unicorn.server.domain.circle.port.`in`.CircleMemberInPort import com.unicorn.server.domain.circle.port.out.CircleIdGenerator import com.unicorn.server.domain.circle.port.out.CircleMemberIdGenerator import com.unicorn.server.domain.circle.port.out.CircleMemberOutPort @@ -36,9 +29,9 @@ class CircleService( private val circleMemberOutPort: CircleMemberOutPort, private val circleIdGenerator: CircleIdGenerator, private val circleMemberIdGenerator: CircleMemberIdGenerator, - private val getMemberProfileInPort: GetMemberProfileInPort, - private val eventPublisher: EventPublisher, -) : CircleInPort, CircleMemberInPort { + private val getMemberProfileInPort: GetMemberProfileInPort, + private val eventPublisher: EventPublisher, +) : CircleInPort { override fun create(memberId: String, command: CreateCircleCommand): CircleSummary { val owner = getMemberProfileInPort.getMemberProfile(memberId) ?: throw MemberNotFoundException(memberId) val ownerId = MemberId.of(owner.memberId) @@ -79,166 +72,9 @@ class CircleService( return CircleSummary(circle.id.toString(), circle.name, circle.ownerId.toString()) } - override fun join(circleId: String, memberId: String): JoinCircleResult { - val targetCircleId = CircleId.of(circleId) - val targetMemberId = MemberId.of(memberId) - circleOutPort.findById(targetCircleId) ?: throw CircleNotFoundException(circleId) - - val existingMembership = circleMemberOutPort.findByCircleAndMember(targetCircleId, targetMemberId) - if (existingMembership != null) { - if (existingMembership.status == CircleMemberStatus.ACTIVE && !existingMembership.deleted) { - throw BusinessException(CircleErrorCode.ALREADY_JOINED) - } - assertNoActiveCircle(targetMemberId) - assertCircleCapacity(targetCircleId) - val member = getMemberProfileInPort.getMemberProfile(memberId) ?: throw MemberNotFoundException(memberId) - existingMembership.rejoin(member.nickname) - circleMemberOutPort.save(existingMembership) - eventPublisher.publish(CircleMemberJoinedEvent(circleId, memberId, existingMembership.role)) - return JoinCircleResult(circleId = circleId) - } - - assertNoActiveCircle(targetMemberId) - assertCircleCapacity(targetCircleId) - val member = getMemberProfileInPort.getMemberProfile(memberId) ?: throw MemberNotFoundException(memberId) - val circleMember = circleMemberOutPort.save( - CircleMember.createMember(circleMemberIdGenerator.next(), targetCircleId, targetMemberId, member.nickname), - ) - eventPublisher.publish(CircleMemberJoinedEvent(circleId, memberId, circleMember.role)) - return JoinCircleResult(circleId = circleId) - } - - override fun leave(circleId: String, memberId: String) { - val targetCircleId = CircleId.of(circleId) - val targetMemberId = MemberId.of(memberId) - val circle = circleOutPort.findById(targetCircleId) - ?: throw CircleNotFoundException(circleId) - if (circle.deleted) { - throw CircleNotFoundException(circleId) - } - - val membership = circleMemberOutPort.findByCircleAndMember(targetCircleId, targetMemberId) - ?: throw BusinessException(CircleErrorCode.CIRCLE_ACCESS_DENIED) - if (membership.status != CircleMemberStatus.ACTIVE || membership.deleted) { - throw BusinessException(CircleErrorCode.CIRCLE_ACCESS_DENIED) - } - - leaveCircleMember(circle, membership, targetMemberId) - } - - override fun getCircleMembers(circleId: String) = - circleMemberOutPort.findAllActiveByCircleId(CircleId.of(circleId)) - .filter { it.status == CircleMemberStatus.ACTIVE && !it.deleted } - .map { - CircleMemberDto( - memberId = it.memberId.toString(), - nickname = it.nickname, - role = it.role.name, - active = true, - ) - } - - override fun isCircleMember(circleId: String, memberId: String): Boolean = - circleMemberOutPort.existsActiveByCircleAndMember(CircleId.of(circleId), MemberId.of(memberId)) - - override fun transferInitiator( - circleId: String, - currentInitiatorId: String, - newInitiatorId: String - ): CircleSummary { - val targetCircleId = CircleId.of(circleId) - val requesterId = MemberId.of(currentInitiatorId) - val targetId = MemberId.of(newInitiatorId) - if (requesterId == targetId) { - throw BusinessException(CircleErrorCode.INITIATOR_DELEGATION_SELF_FORBIDDEN) - } - - val circle = circleOutPort.findById(targetCircleId) ?: throw CircleNotFoundException(circleId) - val requesterMembership = circleMemberOutPort.findByCircleAndMember(targetCircleId, requesterId) - ?: throw BusinessException(CircleErrorCode.INITIATOR_DELEGATION_FORBIDDEN) - - val targetMembership = circleMemberOutPort.findByCircleAndMember(targetCircleId, targetId) - ?: throw BusinessException(CircleErrorCode.INITIATOR_DELEGATION_TARGET_INVALID) - - circle.transferInitiator(requesterMembership, targetMembership) - circleMemberOutPort.save(requesterMembership) - circleMemberOutPort.save(targetMembership) - val savedCircle = circleOutPort.save(circle) - eventPublisher.publish( - CircleInitiatorTransferredEvent( - circleId = savedCircle.id.toString(), - previousInitiatorMemberId = requesterId.toString(), - newInitiatorMemberId = targetId.toString(), - ), - ) - return CircleSummary(savedCircle.id.toString(), savedCircle.name, savedCircle.ownerId.toString()) - } - - override fun handleMemberWithdrawal(memberId: String) { - val withdrawingMemberId = MemberId.of(memberId) - val memberships = circleMemberOutPort.findAllActiveByMemberId(withdrawingMemberId) - - memberships.forEach { membership -> - val circle = circleOutPort.findById(membership.circleId) - ?: throw CircleNotFoundException(membership.circleId.toString()) - - leaveCircleMember(circle, membership, withdrawingMemberId) - } - } - - private fun leaveCircleMember( - circle: Circle, - membership: CircleMember, - departingMemberId: MemberId, - ) { - if (membership.role != CircleRole.INITIATOR) { - circle.leaveMember(membership) - circleMemberOutPort.save(membership) - return - } - - if (!circleMemberOutPort.existsActiveMemberByCircleIdExcludingMemberId(circle.id, departingMemberId)) { - circle.deleteWithLastMember(membership) - circleMemberOutPort.save(membership) - circleOutPort.save(circle) - return - } - - val successor = try { - circleMemberOutPort.findOldestActiveByCircleIdExcludingMemberId(circle.id, departingMemberId) - } catch (_: CircleSuccessorNotFoundException) { - circle.deleteWithLastMember(membership) - circleMemberOutPort.save(membership) - circleOutPort.save(circle) - return - } - - val newInitiator = circle.leaveMember(membership, successor) - circleMemberOutPort.save(membership) - circleMemberOutPort.save(newInitiator) - val savedCircle = circleOutPort.save(circle) - eventPublisher.publish( - CircleInitiatorTransferredEvent( - circleId = savedCircle.id.toString(), - previousInitiatorMemberId = departingMemberId.toString(), - newInitiatorMemberId = newInitiator.memberId.toString(), - ), - ) - } - private fun assertNoActiveCircle(memberId: MemberId) { if (circleMemberOutPort.findAllActiveByMemberId(memberId).isNotEmpty()) { throw BusinessException(CircleErrorCode.ALREADY_HAS_ACTIVE_CIRCLE) } } - - private fun assertCircleCapacity(circleId: CircleId) { - if (circleMemberOutPort.countActiveByCircleId(circleId) >= MAX_MEMBERS) { - throw BusinessException(CircleErrorCode.CIRCLE_MEMBER_LIMIT_EXCEEDED) - } - } - - companion object { - private const val MAX_MEMBERS = 10L - } } diff --git a/src/test/kotlin/com/unicorn/server/domain/circle/CircleServiceJoinTest.kt b/src/test/kotlin/com/unicorn/server/domain/circle/CircleServiceJoinTest.kt index 95d8092..110449c 100644 --- a/src/test/kotlin/com/unicorn/server/domain/circle/CircleServiceJoinTest.kt +++ b/src/test/kotlin/com/unicorn/server/domain/circle/CircleServiceJoinTest.kt @@ -9,10 +9,9 @@ import com.unicorn.server.domain.circle.enums.CircleRole import com.unicorn.server.domain.circle.exception.CircleErrorCode import com.unicorn.server.domain.circle.exception.CircleSuccessorNotFoundException import com.unicorn.server.domain.circle.port.out.CircleMemberOutPort -import com.unicorn.server.domain.circle.port.out.CircleIdGenerator import com.unicorn.server.domain.circle.port.out.CircleMemberIdGenerator import com.unicorn.server.domain.circle.port.out.CircleOutPort -import com.unicorn.server.domain.circle.service.CircleService +import com.unicorn.server.domain.circle.service.CircleMemberService import com.unicorn.server.domain.circle.vo.CircleId import com.unicorn.server.domain.member.Member import com.unicorn.server.domain.member.enums.Role @@ -31,9 +30,8 @@ class CircleServiceJoinTest { private val circleMemberOutPort = FakeCircleMemberOutPort() private val memberQueryInPort = FakeMemberQueryInPort() private val eventPublisher = RecordingEventPublisher() - private val circleIdGenerator = object : CircleIdGenerator { override fun next() = TestIdFactory.circleId() } private val circleMemberIdGenerator = object : CircleMemberIdGenerator { override fun next() = TestIdFactory.circleMemberId() } - private val circleService = CircleService(circleOutPort, circleMemberOutPort, circleIdGenerator, circleMemberIdGenerator, memberQueryInPort, eventPublisher) + private val circleMemberService = CircleMemberService(circleOutPort, circleMemberOutPort, circleMemberIdGenerator, memberQueryInPort, eventPublisher) @Test @DisplayName("이미 참여한 사용자가 join 하면 예외를 던진다") @@ -47,7 +45,7 @@ class CircleServiceJoinTest { circleOutPort.save(circle) circleMemberOutPort.save(CircleMember.createMember(TestIdFactory.circleMemberId(), circle.id, friend.id, friend.nickname)) - assertThatThrownBy { circleService.join(circle.id.toString(), friend.id.toString()) } + assertThatThrownBy { circleMemberService.join(circle.id.toString(), friend.id.toString()) } .isInstanceOf(BusinessException::class.java) .extracting("errorCode") .isEqualTo(CircleErrorCode.ALREADY_JOINED) @@ -68,7 +66,7 @@ class CircleServiceJoinTest { membership.leave() circleMemberOutPort.save(membership) - val result = circleService.join(circle.id.toString(), friend.id.toString()) + val result = circleMemberService.join(circle.id.toString(), friend.id.toString()) assertThat(result.circleId).isEqualTo(circle.id.toString()) assertThat(circleMemberOutPort.members).hasSize(1) @@ -91,7 +89,7 @@ class CircleServiceJoinTest { circleOutPort.save(targetCircle) circleMemberOutPort.save(CircleMember.createMember(TestIdFactory.circleMemberId(), joinedCircle.id, friend.id, friend.nickname)) - assertThatThrownBy { circleService.join(targetCircle.id.toString(), friend.id.toString()) } + assertThatThrownBy { circleMemberService.join(targetCircle.id.toString(), friend.id.toString()) } .isInstanceOf(BusinessException::class.java) .extracting("errorCode") .isEqualTo(CircleErrorCode.ALREADY_HAS_ACTIVE_CIRCLE) @@ -113,7 +111,7 @@ class CircleServiceJoinTest { circleMemberOutPort.save(CircleMember.createMember(TestIdFactory.circleMemberId(), circle.id, member.id, member.nickname)) } - assertThatThrownBy { circleService.join(circle.id.toString(), friend.id.toString()) } + assertThatThrownBy { circleMemberService.join(circle.id.toString(), friend.id.toString()) } .isInstanceOf(BusinessException::class.java) .extracting("errorCode") .isEqualTo(CircleErrorCode.CIRCLE_MEMBER_LIMIT_EXCEEDED) @@ -133,7 +131,7 @@ class CircleServiceJoinTest { membership.leave() circleMemberOutPort.save(membership) - val result = circleService.isCircleMember(circle.id.toString(), friend.id.toString()) + val result = circleMemberService.isCircleMember(circle.id.toString(), friend.id.toString()) assertThat(result).isFalse() } @@ -149,13 +147,13 @@ class CircleServiceJoinTest { circleMemberOutPort.save(CircleMember.createInitiator(TestIdFactory.circleMemberId(), circle.id, owner.id, owner.nickname)) circleMemberOutPort.save(CircleMember.createMember(TestIdFactory.circleMemberId(), circle.id, member.id, member.nickname)) - circleService.leave(circle.id.toString(), member.id.toString()) + circleMemberService.leave(circle.id.toString(), member.id.toString()) val membership = circleMemberOutPort.findByCircleAndMember(circle.id, member.id) assertThat(membership?.status).isEqualTo(CircleMemberStatus.LEFT) assertThat(membership?.deleted).isTrue() assertThat(membership?.leftAt).isNotNull() - assertThat(circleService.isCircleMember(circle.id.toString(), member.id.toString())).isFalse() + assertThat(circleMemberService.isCircleMember(circle.id.toString(), member.id.toString())).isFalse() } @Test @@ -173,7 +171,7 @@ class CircleServiceJoinTest { circleMemberOutPort.save(circleMember(circle, oldestMember, CircleRole.MEMBER, baseTime.plusMinutes(1))) circleMemberOutPort.save(circleMember(circle, latestMember, CircleRole.MEMBER, baseTime.plusMinutes(2))) - circleService.leave(circle.id.toString(), owner.id.toString()) + circleMemberService.leave(circle.id.toString(), owner.id.toString()) assertThat(circleOutPort.findById(circle.id)?.ownerId).isEqualTo(oldestMember.id) assertThat(circleOutPort.findById(circle.id)?.deleted).isFalse() @@ -191,7 +189,7 @@ class CircleServiceJoinTest { val circle = circleOutPort.save(Circle.create(TestIdFactory.circleId(), "단독방장탈퇴", owner.id)) circleMemberOutPort.save(CircleMember.createInitiator(TestIdFactory.circleMemberId(), circle.id, owner.id, owner.nickname)) - circleService.leave(circle.id.toString(), owner.id.toString()) + circleMemberService.leave(circle.id.toString(), owner.id.toString()) assertThat(circleOutPort.findById(circle.id)?.deleted).isTrue() assertThat(circleMemberOutPort.findByCircleAndMember(circle.id, owner.id)?.status).isEqualTo(CircleMemberStatus.LEFT) @@ -210,7 +208,7 @@ class CircleServiceJoinTest { circleMemberOutPort.save(CircleMember.createMember(TestIdFactory.circleMemberId(), circle.id, member.id, member.nickname)) circleMemberOutPort.successorDisappearsOnLookup = true - circleService.leave(circle.id.toString(), owner.id.toString()) + circleMemberService.leave(circle.id.toString(), owner.id.toString()) assertThat(circleOutPort.findById(circle.id)?.deleted).isTrue() assertThat(circleMemberOutPort.findByCircleAndMember(circle.id, owner.id)?.status).isEqualTo(CircleMemberStatus.LEFT) diff --git a/src/test/kotlin/com/unicorn/server/domain/circle/CircleServiceTest.kt b/src/test/kotlin/com/unicorn/server/domain/circle/CircleServiceTest.kt index f3675e9..bd76bc0 100644 --- a/src/test/kotlin/com/unicorn/server/domain/circle/CircleServiceTest.kt +++ b/src/test/kotlin/com/unicorn/server/domain/circle/CircleServiceTest.kt @@ -15,6 +15,7 @@ import com.unicorn.server.domain.circle.port.out.CircleMemberIdGenerator import com.unicorn.server.domain.circle.port.out.CircleMemberOutPort import com.unicorn.server.domain.circle.port.out.CircleOutPort import com.unicorn.server.domain.circle.service.CircleService +import com.unicorn.server.domain.circle.service.CircleMemberService import com.unicorn.server.domain.circle.vo.CircleId import com.unicorn.server.domain.member.Member import com.unicorn.server.domain.member.port.dto.MemberProfileDto @@ -36,6 +37,7 @@ class CircleServiceTest { private val circleIdGenerator = object : CircleIdGenerator { override fun next() = TestIdFactory.circleId() } private val circleMemberIdGenerator = object : CircleMemberIdGenerator { override fun next() = TestIdFactory.circleMemberId() } private val circleService = CircleService(circleOutPort, circleMemberOutPort, circleIdGenerator, circleMemberIdGenerator, memberQueryInPort, eventPublisher) + private val circleMemberService = CircleMemberService(circleOutPort, circleMemberOutPort, circleMemberIdGenerator, memberQueryInPort, eventPublisher) @Test @DisplayName("써클 생성 시 initiator 멤버십이 함께 생성된다") @@ -94,7 +96,7 @@ class CircleServiceTest { val initiator = circleMemberOutPort.save(com.unicorn.server.domain.circle.CircleMember.createInitiator(TestIdFactory.circleMemberId(), circle.id, owner.id, owner.nickname)) val member = circleMemberOutPort.save(com.unicorn.server.domain.circle.CircleMember.createMember(TestIdFactory.circleMemberId(), circle.id, target.id, target.nickname)) - val result = circleService.transferInitiator(circle.id.toString(), owner.id.toString(), target.id.toString()) + val result = circleMemberService.transferInitiator(circle.id.toString(), owner.id.toString(), target.id.toString()) assertThat(result.ownerId).isEqualTo(target.id.toString()) assertThat(circleOutPort.findById(circle.id)?.ownerId).isEqualTo(target.id) @@ -120,7 +122,7 @@ class CircleServiceTest { circleMemberOutPort.save(com.unicorn.server.domain.circle.CircleMember.createMember(TestIdFactory.circleMemberId(), circle.id, requester.id, requester.nickname)) circleMemberOutPort.save(com.unicorn.server.domain.circle.CircleMember.createMember(TestIdFactory.circleMemberId(), circle.id, target.id, target.nickname)) - assertThatThrownBy { circleService.transferInitiator(circle.id.toString(), requester.id.toString(), target.id.toString()) } + assertThatThrownBy { circleMemberService.transferInitiator(circle.id.toString(), requester.id.toString(), target.id.toString()) } .isInstanceOf(BusinessException::class.java) .extracting("errorCode") .isEqualTo(CircleErrorCode.INITIATOR_DELEGATION_FORBIDDEN) @@ -135,7 +137,7 @@ class CircleServiceTest { val circle = circleOutPort.save(Circle.create(TestIdFactory.circleId(), "셀프위임써클", owner.id)) circleMemberOutPort.save(com.unicorn.server.domain.circle.CircleMember.createInitiator(TestIdFactory.circleMemberId(), circle.id, owner.id, owner.nickname)) - assertThatThrownBy { circleService.transferInitiator(circle.id.toString(), owner.id.toString(), owner.id.toString()) } + assertThatThrownBy { circleMemberService.transferInitiator(circle.id.toString(), owner.id.toString(), owner.id.toString()) } .isInstanceOf(BusinessException::class.java) .extracting("errorCode") .isEqualTo(CircleErrorCode.INITIATOR_DELEGATION_SELF_FORBIDDEN) @@ -155,7 +157,7 @@ class CircleServiceTest { leftMember.leave() circleMemberOutPort.save(leftMember) - assertThatThrownBy { circleService.transferInitiator(circle.id.toString(), owner.id.toString(), target.id.toString()) } + assertThatThrownBy { circleMemberService.transferInitiator(circle.id.toString(), owner.id.toString(), target.id.toString()) } .isInstanceOf(BusinessException::class.java) .extracting("errorCode") .isEqualTo(CircleErrorCode.INITIATOR_DELEGATION_TARGET_INVALID) @@ -219,7 +221,7 @@ class CircleServiceTest { ), ) - circleService.handleMemberWithdrawal(owner.id.toString()) + circleMemberService.handleMemberWithdrawal(owner.id.toString()) assertThat(circleOutPort.findById(circle.id)?.deleted).isFalse() assertThat(circleOutPort.findById(circle.id)?.ownerId).isEqualTo(firstJoiner.id) @@ -242,7 +244,7 @@ class CircleServiceTest { val circle = circleOutPort.save(Circle.create(TestIdFactory.circleId(), "삭제써클", owner.id)) circleMemberOutPort.save(com.unicorn.server.domain.circle.CircleMember.createInitiator(TestIdFactory.circleMemberId(), circle.id, owner.id, owner.nickname)) - circleService.handleMemberWithdrawal(owner.id.toString()) + circleMemberService.handleMemberWithdrawal(owner.id.toString()) assertThat(circleOutPort.findById(circle.id)?.deleted).isTrue() assertThat(circleMemberOutPort.findByCircleAndMember(circle.id, owner.id)?.status).isEqualTo(com.unicorn.server.domain.circle.enums.CircleMemberStatus.LEFT) From f8433105a32ec6b620cfab4743c7025cb9b2fdcf Mon Sep 17 00:00:00 2001 From: junyong Date: Sat, 25 Jul 2026 17:43:22 +0900 Subject: [PATCH 9/9] =?UTF-8?q?refactor[SAIO-271]:=20=ED=8F=AC=ED=8A=B8=20?= =?UTF-8?q?=EC=9D=91=EC=A7=91=EB=8F=84=20=EC=BB=A8=EB=B2=A4=EC=85=98=20?= =?UTF-8?q?=EB=B0=98=EC=98=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/member/port/in/MemberInPort.kt | 17 +- .../port/in/MemberOnboardingInfoInPort.kt | 7 - .../port/in/MemberProfileImageUploadInPort.kt | 10 - .../port/in/MemberProfileUpdateInPort.kt | 10 - .../member/port/in/MemberWithdrawInPort.kt | 7 - .../member/service/MemberProfileService.kt | 6 +- ...eateInPort.kt => ScheduleCommandInPort.kt} | 7 +- .../in/ScheduleConfirmationCancelInPort.kt | 7 - ...nPort.kt => ScheduleConfirmationInPort.kt} | 5 +- .../schedule/port/in/ScheduleDeleteInPort.kt | 7 - .../schedule/port/in/ScheduleListInPort.kt | 12 - ...DetailInPort.kt => ScheduleQueryInPort.kt} | 10 +- .../schedule/port/in/ScheduleUpdateInPort.kt | 7 - .../service/CancelConfirmationService.kt | 37 ---- .../service/ScheduleCommandService.kt | 6 +- .../service/ScheduleConfirmationService.kt | 21 +- .../schedule/service/ScheduleQueryService.kt | 5 +- .../adapter/in/web/member/MemberController.kt | 20 +- .../in/web/schedule/ScheduleController.kt | 35 ++- .../service/CancelConfirmationServiceTest.kt | 205 ------------------ .../ScheduleConfirmationServiceTest.kt | 53 +++++ 21 files changed, 131 insertions(+), 363 deletions(-) delete mode 100644 src/main/kotlin/com/unicorn/server/domain/member/port/in/MemberOnboardingInfoInPort.kt delete mode 100644 src/main/kotlin/com/unicorn/server/domain/member/port/in/MemberProfileImageUploadInPort.kt delete mode 100644 src/main/kotlin/com/unicorn/server/domain/member/port/in/MemberProfileUpdateInPort.kt delete mode 100644 src/main/kotlin/com/unicorn/server/domain/member/port/in/MemberWithdrawInPort.kt rename src/main/kotlin/com/unicorn/server/domain/schedule/port/in/{ScheduleCreateInPort.kt => ScheduleCommandInPort.kt} (51%) delete mode 100644 src/main/kotlin/com/unicorn/server/domain/schedule/port/in/ScheduleConfirmationCancelInPort.kt rename src/main/kotlin/com/unicorn/server/domain/schedule/port/in/{ScheduleConfirmationRegisterInPort.kt => ScheduleConfirmationInPort.kt} (58%) delete mode 100644 src/main/kotlin/com/unicorn/server/domain/schedule/port/in/ScheduleDeleteInPort.kt delete mode 100644 src/main/kotlin/com/unicorn/server/domain/schedule/port/in/ScheduleListInPort.kt rename src/main/kotlin/com/unicorn/server/domain/schedule/port/in/{ScheduleDetailInPort.kt => ScheduleQueryInPort.kt} (57%) delete mode 100644 src/main/kotlin/com/unicorn/server/domain/schedule/port/in/ScheduleUpdateInPort.kt delete mode 100644 src/main/kotlin/com/unicorn/server/domain/schedule/service/CancelConfirmationService.kt delete mode 100644 src/test/kotlin/com/unicorn/server/domain/schedule/service/CancelConfirmationServiceTest.kt diff --git a/src/main/kotlin/com/unicorn/server/domain/member/port/in/MemberInPort.kt b/src/main/kotlin/com/unicorn/server/domain/member/port/in/MemberInPort.kt index bc2819f..75be387 100644 --- a/src/main/kotlin/com/unicorn/server/domain/member/port/in/MemberInPort.kt +++ b/src/main/kotlin/com/unicorn/server/domain/member/port/in/MemberInPort.kt @@ -1,9 +1,24 @@ package com.unicorn.server.domain.member.port.`in` import com.unicorn.server.domain.member.Member +import com.unicorn.server.domain.member.port.dto.OnboardingInfoResult +import com.unicorn.server.domain.member.port.dto.UpdateProfileCommand +import com.unicorn.server.domain.member.port.dto.UploadProfileImageCommand -// MemberInPort - 멤버 조회 유스케이스 진입점을 정의한다. +// MemberInPort - 인증된 본인이 자신의 멤버 정보를 조회/변경하는 유스케이스 진입점을 정의한다. interface MemberInPort { // 멤버 식별자로 멤버를 조회한다. fun getById(memberId: String): Member + + // 온보딩 화면 표시를 위한 소셜 계정 정보와 멤버 기본 색상을 조회한다. + fun getOnboardingInfo(memberId: String): OnboardingInfoResult + + // 멤버 식별자와 변경 명령으로 프로필을 갱신한다. + fun updateProfile(memberId: String, command: UpdateProfileCommand): Member + + // 멤버 식별자와 업로드 명령으로 프로필 이미지를 갱신한다. + fun uploadProfileImage(memberId: String, command: UploadProfileImageCommand): Member + + // 멤버를 soft delete 상태로 전환한다. + fun withdraw(memberId: String, reason: String) } diff --git a/src/main/kotlin/com/unicorn/server/domain/member/port/in/MemberOnboardingInfoInPort.kt b/src/main/kotlin/com/unicorn/server/domain/member/port/in/MemberOnboardingInfoInPort.kt deleted file mode 100644 index aa873ea..0000000 --- a/src/main/kotlin/com/unicorn/server/domain/member/port/in/MemberOnboardingInfoInPort.kt +++ /dev/null @@ -1,7 +0,0 @@ -package com.unicorn.server.domain.member.port.`in` - -import com.unicorn.server.domain.member.port.dto.OnboardingInfoResult - -interface MemberOnboardingInfoInPort { - fun getOnboardingInfo(memberId: String): OnboardingInfoResult -} diff --git a/src/main/kotlin/com/unicorn/server/domain/member/port/in/MemberProfileImageUploadInPort.kt b/src/main/kotlin/com/unicorn/server/domain/member/port/in/MemberProfileImageUploadInPort.kt deleted file mode 100644 index fc3dc03..0000000 --- a/src/main/kotlin/com/unicorn/server/domain/member/port/in/MemberProfileImageUploadInPort.kt +++ /dev/null @@ -1,10 +0,0 @@ -package com.unicorn.server.domain.member.port.`in` - -import com.unicorn.server.domain.member.Member -import com.unicorn.server.domain.member.port.dto.UploadProfileImageCommand - -// MemberProfileImageUploadInPort - 멤버 프로필 이미지 업로드 유스케이스 진입점을 정의한다. -interface MemberProfileImageUploadInPort { - // 멤버 식별자와 업로드 명령으로 프로필 이미지를 갱신한다. - fun uploadProfileImage(memberId: String, command: UploadProfileImageCommand): Member -} diff --git a/src/main/kotlin/com/unicorn/server/domain/member/port/in/MemberProfileUpdateInPort.kt b/src/main/kotlin/com/unicorn/server/domain/member/port/in/MemberProfileUpdateInPort.kt deleted file mode 100644 index cc93ed5..0000000 --- a/src/main/kotlin/com/unicorn/server/domain/member/port/in/MemberProfileUpdateInPort.kt +++ /dev/null @@ -1,10 +0,0 @@ -package com.unicorn.server.domain.member.port.`in` - -import com.unicorn.server.domain.member.Member -import com.unicorn.server.domain.member.port.dto.UpdateProfileCommand - -// MemberProfileUpdateInPort - 멤버 프로필 변경 유스케이스 진입점을 정의한다. -interface MemberProfileUpdateInPort { - // 멤버 식별자와 변경 명령으로 프로필을 갱신한다. - fun updateProfile(memberId: String, command: UpdateProfileCommand): Member -} diff --git a/src/main/kotlin/com/unicorn/server/domain/member/port/in/MemberWithdrawInPort.kt b/src/main/kotlin/com/unicorn/server/domain/member/port/in/MemberWithdrawInPort.kt deleted file mode 100644 index a79c9b6..0000000 --- a/src/main/kotlin/com/unicorn/server/domain/member/port/in/MemberWithdrawInPort.kt +++ /dev/null @@ -1,7 +0,0 @@ -package com.unicorn.server.domain.member.port.`in` - -// MemberWithdrawInPort - 회원탈퇴 유스케이스 진입점을 정의한다. -interface MemberWithdrawInPort { - // 멤버를 soft delete 상태로 전환한다. - fun withdraw(memberId: String, reason: String) -} diff --git a/src/main/kotlin/com/unicorn/server/domain/member/service/MemberProfileService.kt b/src/main/kotlin/com/unicorn/server/domain/member/service/MemberProfileService.kt index d1a9489..0fd5c39 100644 --- a/src/main/kotlin/com/unicorn/server/domain/member/service/MemberProfileService.kt +++ b/src/main/kotlin/com/unicorn/server/domain/member/service/MemberProfileService.kt @@ -13,11 +13,7 @@ import com.unicorn.server.domain.member.exception.MemberNotFoundException import com.unicorn.server.domain.member.exception.WithdrawnMemberException import com.unicorn.server.domain.member.port.`in`.MemberInPort import com.unicorn.server.domain.member.port.`in`.MemberProfileInPort -import com.unicorn.server.domain.member.port.`in`.MemberOnboardingInfoInPort import com.unicorn.server.domain.member.port.`in`.MemberStateUpdateInPort -import com.unicorn.server.domain.member.port.`in`.MemberProfileUpdateInPort -import com.unicorn.server.domain.member.port.`in`.MemberProfileImageUploadInPort -import com.unicorn.server.domain.member.port.`in`.MemberWithdrawInPort import com.unicorn.server.domain.member.port.dto.MemberProfileDto import com.unicorn.server.domain.member.port.dto.OnboardingInfoResult import com.unicorn.server.domain.member.port.dto.UpdateProfileCommand @@ -41,7 +37,7 @@ class MemberProfileService( private val eventPublisher: EventOutPort, private val objectStorage: ObjectStorageOutPort, private val withdrawalLogOutPort: WithdrawalLogOutPort, -) : MemberInPort, MemberProfileInPort, MemberOnboardingInfoInPort, MemberProfileUpdateInPort, MemberWithdrawInPort, MemberProfileImageUploadInPort, MemberStateUpdateInPort { +) : MemberInPort, MemberProfileInPort, MemberStateUpdateInPort { // 멤버 식별자로 저장된 멤버를 조회한다. override fun getById(memberId: String): Member = findMemberOrThrow(memberId) diff --git a/src/main/kotlin/com/unicorn/server/domain/schedule/port/in/ScheduleCreateInPort.kt b/src/main/kotlin/com/unicorn/server/domain/schedule/port/in/ScheduleCommandInPort.kt similarity index 51% rename from src/main/kotlin/com/unicorn/server/domain/schedule/port/in/ScheduleCreateInPort.kt rename to src/main/kotlin/com/unicorn/server/domain/schedule/port/in/ScheduleCommandInPort.kt index 53677c2..2c92c84 100644 --- a/src/main/kotlin/com/unicorn/server/domain/schedule/port/in/ScheduleCreateInPort.kt +++ b/src/main/kotlin/com/unicorn/server/domain/schedule/port/in/ScheduleCommandInPort.kt @@ -1,8 +1,13 @@ package com.unicorn.server.domain.schedule.port.`in` import com.unicorn.server.domain.schedule.port.dto.CreateScheduleCommand +import com.unicorn.server.domain.schedule.port.dto.UpdateScheduleCommand import com.unicorn.server.domain.schedule.vo.ScheduleId -interface ScheduleCreateInPort { +interface ScheduleCommandInPort { fun create(command: CreateScheduleCommand): ScheduleId + + fun update(command: UpdateScheduleCommand) + + fun delete(scheduleId: ScheduleId, circleId: String, memberId: String) } diff --git a/src/main/kotlin/com/unicorn/server/domain/schedule/port/in/ScheduleConfirmationCancelInPort.kt b/src/main/kotlin/com/unicorn/server/domain/schedule/port/in/ScheduleConfirmationCancelInPort.kt deleted file mode 100644 index 5d44986..0000000 --- a/src/main/kotlin/com/unicorn/server/domain/schedule/port/in/ScheduleConfirmationCancelInPort.kt +++ /dev/null @@ -1,7 +0,0 @@ -package com.unicorn.server.domain.schedule.port.`in` - -import com.unicorn.server.domain.schedule.vo.ScheduleId - -interface ScheduleConfirmationCancelInPort { - fun cancel(confirmationId: Long, scheduleId: ScheduleId, circleId: String, memberId: String) -} diff --git a/src/main/kotlin/com/unicorn/server/domain/schedule/port/in/ScheduleConfirmationRegisterInPort.kt b/src/main/kotlin/com/unicorn/server/domain/schedule/port/in/ScheduleConfirmationInPort.kt similarity index 58% rename from src/main/kotlin/com/unicorn/server/domain/schedule/port/in/ScheduleConfirmationRegisterInPort.kt rename to src/main/kotlin/com/unicorn/server/domain/schedule/port/in/ScheduleConfirmationInPort.kt index 595d4fa..fab3742 100644 --- a/src/main/kotlin/com/unicorn/server/domain/schedule/port/in/ScheduleConfirmationRegisterInPort.kt +++ b/src/main/kotlin/com/unicorn/server/domain/schedule/port/in/ScheduleConfirmationInPort.kt @@ -2,7 +2,10 @@ package com.unicorn.server.domain.schedule.port.`in` import com.unicorn.server.domain.schedule.enums.ConfirmationType import com.unicorn.server.domain.schedule.port.dto.RegisterConfirmationCommand +import com.unicorn.server.domain.schedule.vo.ScheduleId -interface ScheduleConfirmationRegisterInPort { +interface ScheduleConfirmationInPort { fun register(command: RegisterConfirmationCommand): ConfirmationType + + fun cancel(confirmationId: Long, scheduleId: ScheduleId, circleId: String, memberId: String) } diff --git a/src/main/kotlin/com/unicorn/server/domain/schedule/port/in/ScheduleDeleteInPort.kt b/src/main/kotlin/com/unicorn/server/domain/schedule/port/in/ScheduleDeleteInPort.kt deleted file mode 100644 index dd085c3..0000000 --- a/src/main/kotlin/com/unicorn/server/domain/schedule/port/in/ScheduleDeleteInPort.kt +++ /dev/null @@ -1,7 +0,0 @@ -package com.unicorn.server.domain.schedule.port.`in` - -import com.unicorn.server.domain.schedule.vo.ScheduleId - -interface ScheduleDeleteInPort { - fun delete(scheduleId: ScheduleId, circleId: String, memberId: String) -} diff --git a/src/main/kotlin/com/unicorn/server/domain/schedule/port/in/ScheduleListInPort.kt b/src/main/kotlin/com/unicorn/server/domain/schedule/port/in/ScheduleListInPort.kt deleted file mode 100644 index d06cb76..0000000 --- a/src/main/kotlin/com/unicorn/server/domain/schedule/port/in/ScheduleListInPort.kt +++ /dev/null @@ -1,12 +0,0 @@ -package com.unicorn.server.domain.schedule.port.`in` - -import com.unicorn.server.domain.schedule.port.dto.ScheduleListResult - -interface ScheduleListInPort { - fun getList( - circleId: String, - memberId: String, - cursor: String?, - size: Int, - ): ScheduleListResult -} diff --git a/src/main/kotlin/com/unicorn/server/domain/schedule/port/in/ScheduleDetailInPort.kt b/src/main/kotlin/com/unicorn/server/domain/schedule/port/in/ScheduleQueryInPort.kt similarity index 57% rename from src/main/kotlin/com/unicorn/server/domain/schedule/port/in/ScheduleDetailInPort.kt rename to src/main/kotlin/com/unicorn/server/domain/schedule/port/in/ScheduleQueryInPort.kt index 16d689f..6d6f823 100644 --- a/src/main/kotlin/com/unicorn/server/domain/schedule/port/in/ScheduleDetailInPort.kt +++ b/src/main/kotlin/com/unicorn/server/domain/schedule/port/in/ScheduleQueryInPort.kt @@ -1,8 +1,16 @@ package com.unicorn.server.domain.schedule.port.`in` import com.unicorn.server.domain.schedule.port.dto.ScheduleDetailResult +import com.unicorn.server.domain.schedule.port.dto.ScheduleListResult import com.unicorn.server.domain.schedule.vo.ScheduleId -interface ScheduleDetailInPort { +interface ScheduleQueryInPort { + fun getList( + circleId: String, + memberId: String, + cursor: String?, + size: Int, + ): ScheduleListResult + fun getDetail(scheduleId: ScheduleId, circleId: String, memberId: String): ScheduleDetailResult } diff --git a/src/main/kotlin/com/unicorn/server/domain/schedule/port/in/ScheduleUpdateInPort.kt b/src/main/kotlin/com/unicorn/server/domain/schedule/port/in/ScheduleUpdateInPort.kt deleted file mode 100644 index 652b364..0000000 --- a/src/main/kotlin/com/unicorn/server/domain/schedule/port/in/ScheduleUpdateInPort.kt +++ /dev/null @@ -1,7 +0,0 @@ -package com.unicorn.server.domain.schedule.port.`in` - -import com.unicorn.server.domain.schedule.port.dto.UpdateScheduleCommand - -interface ScheduleUpdateInPort { - fun update(command: UpdateScheduleCommand) -} diff --git a/src/main/kotlin/com/unicorn/server/domain/schedule/service/CancelConfirmationService.kt b/src/main/kotlin/com/unicorn/server/domain/schedule/service/CancelConfirmationService.kt deleted file mode 100644 index ba0619c..0000000 --- a/src/main/kotlin/com/unicorn/server/domain/schedule/service/CancelConfirmationService.kt +++ /dev/null @@ -1,37 +0,0 @@ -package com.unicorn.server.domain.schedule.service - -import com.unicorn.server.common.exception.BusinessException -import com.unicorn.server.domain.schedule.exception.ScheduleErrorCode -import com.unicorn.server.domain.schedule.port.`in`.ScheduleConfirmationCancelInPort -import com.unicorn.server.domain.schedule.port.out.CircleAccessOutPort -import com.unicorn.server.domain.schedule.port.out.ScheduleConfirmationOutPort -import com.unicorn.server.domain.schedule.port.out.ScheduleOutPort -import com.unicorn.server.domain.schedule.vo.ScheduleId -import org.springframework.stereotype.Service -import org.springframework.transaction.annotation.Transactional - -@Service -@Transactional -class CancelConfirmationService( - private val scheduleOutPort: ScheduleOutPort, - private val scheduleConfirmationOutPort: ScheduleConfirmationOutPort, - private val circleAccessOutPort: CircleAccessOutPort, -) : ScheduleConfirmationCancelInPort { - - override fun cancel(confirmationId: Long, scheduleId: ScheduleId, circleId: String, memberId: String) { - if (!circleAccessOutPort.isMember(circleId, memberId)) { - throw BusinessException(ScheduleErrorCode.CIRCLE_ACCESS_DENIED) - } - - scheduleOutPort.findActiveByIdAndCircleId(scheduleId, circleId) - ?: throw BusinessException(ScheduleErrorCode.SCHEDULE_NOT_FOUND) - - val confirmation = scheduleConfirmationOutPort.findById(confirmationId) - ?: throw BusinessException(ScheduleErrorCode.CONFIRMATION_NOT_FOUND) - if (confirmation.scheduleId != scheduleId || confirmation.memberId != memberId) { - throw BusinessException(ScheduleErrorCode.CONFIRMATION_ACCESS_DENIED) - } - - scheduleConfirmationOutPort.deleteById(confirmationId) - } -} diff --git a/src/main/kotlin/com/unicorn/server/domain/schedule/service/ScheduleCommandService.kt b/src/main/kotlin/com/unicorn/server/domain/schedule/service/ScheduleCommandService.kt index f5571f2..b4277d7 100644 --- a/src/main/kotlin/com/unicorn/server/domain/schedule/service/ScheduleCommandService.kt +++ b/src/main/kotlin/com/unicorn/server/domain/schedule/service/ScheduleCommandService.kt @@ -6,9 +6,7 @@ import com.unicorn.server.domain.schedule.Schedule import com.unicorn.server.domain.schedule.event.ScheduleCreatedEvent import com.unicorn.server.domain.schedule.event.ScheduleDeletedEvent import com.unicorn.server.domain.schedule.exception.ScheduleErrorCode -import com.unicorn.server.domain.schedule.port.`in`.ScheduleCreateInPort -import com.unicorn.server.domain.schedule.port.`in`.ScheduleDeleteInPort -import com.unicorn.server.domain.schedule.port.`in`.ScheduleUpdateInPort +import com.unicorn.server.domain.schedule.port.`in`.ScheduleCommandInPort import com.unicorn.server.domain.schedule.port.dto.CreateScheduleCommand import com.unicorn.server.domain.schedule.port.dto.UpdateScheduleCommand import com.unicorn.server.domain.schedule.port.out.CircleAccessOutPort @@ -27,7 +25,7 @@ class ScheduleCommandService( private val circleAccessOutPort: CircleAccessOutPort, private val scheduleIdGenerator: ScheduleIdGenerator, private val eventPublisher: EventOutPort, -) : ScheduleCreateInPort, ScheduleUpdateInPort, ScheduleDeleteInPort { +) : ScheduleCommandInPort { override fun create(command: CreateScheduleCommand): ScheduleId { if (!circleAccessOutPort.existsById(command.circleId)) { diff --git a/src/main/kotlin/com/unicorn/server/domain/schedule/service/ScheduleConfirmationService.kt b/src/main/kotlin/com/unicorn/server/domain/schedule/service/ScheduleConfirmationService.kt index 16645bb..689795a 100644 --- a/src/main/kotlin/com/unicorn/server/domain/schedule/service/ScheduleConfirmationService.kt +++ b/src/main/kotlin/com/unicorn/server/domain/schedule/service/ScheduleConfirmationService.kt @@ -6,7 +6,7 @@ import com.unicorn.server.domain.schedule.ScheduleConfirmation import com.unicorn.server.domain.schedule.enums.ConfirmationType import com.unicorn.server.domain.schedule.event.ScheduleConfirmedEvent import com.unicorn.server.domain.schedule.exception.ScheduleErrorCode -import com.unicorn.server.domain.schedule.port.`in`.ScheduleConfirmationRegisterInPort +import com.unicorn.server.domain.schedule.port.`in`.ScheduleConfirmationInPort import com.unicorn.server.domain.schedule.port.`in`.ScheduleConfirmationStatusInPort import com.unicorn.server.domain.schedule.port.dto.RegisterConfirmationCommand import com.unicorn.server.domain.schedule.port.out.CircleAccessOutPort @@ -23,7 +23,7 @@ class ScheduleConfirmationService( private val scheduleConfirmationOutPort: ScheduleConfirmationOutPort, private val circleAccessOutPort: CircleAccessOutPort, private val eventPublisher: EventOutPort, -) : ScheduleConfirmationRegisterInPort, ScheduleConfirmationStatusInPort { +) : ScheduleConfirmationInPort, ScheduleConfirmationStatusInPort { override fun register(command: RegisterConfirmationCommand): ConfirmationType { if (!command.confirmationType.available) { @@ -72,4 +72,21 @@ class ScheduleConfirmationService( override fun hasConfirmed(scheduleId: ScheduleId, memberId: String): Boolean = scheduleConfirmationOutPort.findByScheduleIdAndMemberId(scheduleId, memberId) ?.confirmationType == ConfirmationType.CONFIRMED + + override fun cancel(confirmationId: Long, scheduleId: ScheduleId, circleId: String, memberId: String) { + if (!circleAccessOutPort.isMember(circleId, memberId)) { + throw BusinessException(ScheduleErrorCode.CIRCLE_ACCESS_DENIED) + } + + scheduleOutPort.findActiveByIdAndCircleId(scheduleId, circleId) + ?: throw BusinessException(ScheduleErrorCode.SCHEDULE_NOT_FOUND) + + val confirmation = scheduleConfirmationOutPort.findById(confirmationId) + ?: throw BusinessException(ScheduleErrorCode.CONFIRMATION_NOT_FOUND) + if (confirmation.scheduleId != scheduleId || confirmation.memberId != memberId) { + throw BusinessException(ScheduleErrorCode.CONFIRMATION_ACCESS_DENIED) + } + + scheduleConfirmationOutPort.deleteById(confirmationId) + } } diff --git a/src/main/kotlin/com/unicorn/server/domain/schedule/service/ScheduleQueryService.kt b/src/main/kotlin/com/unicorn/server/domain/schedule/service/ScheduleQueryService.kt index d51fa47..edd8a0a 100644 --- a/src/main/kotlin/com/unicorn/server/domain/schedule/service/ScheduleQueryService.kt +++ b/src/main/kotlin/com/unicorn/server/domain/schedule/service/ScheduleQueryService.kt @@ -6,8 +6,7 @@ import com.unicorn.server.domain.circle.vo.CircleId import com.unicorn.server.domain.schedule.Schedule import com.unicorn.server.domain.schedule.enums.UrgencyLevel import com.unicorn.server.domain.schedule.exception.ScheduleErrorCode -import com.unicorn.server.domain.schedule.port.`in`.ScheduleDetailInPort -import com.unicorn.server.domain.schedule.port.`in`.ScheduleListInPort +import com.unicorn.server.domain.schedule.port.`in`.ScheduleQueryInPort import com.unicorn.server.domain.schedule.port.`in`.ScheduleForCircleInPort import com.unicorn.server.domain.schedule.port.dto.MyConfirmationInfo import com.unicorn.server.domain.schedule.port.dto.ScheduleDetailResult @@ -30,7 +29,7 @@ class ScheduleQueryService( private val scheduleOutPort: ScheduleOutPort, private val scheduleConfirmationOutPort: ScheduleConfirmationOutPort, private val circleAccessOutPort: CircleAccessOutPort, -) : ScheduleListInPort, ScheduleDetailInPort, ScheduleForCircleInPort { +) : ScheduleQueryInPort, ScheduleForCircleInPort { override fun getList( circleId: String, diff --git a/src/main/kotlin/com/unicorn/server/infrastructure/adapter/in/web/member/MemberController.kt b/src/main/kotlin/com/unicorn/server/infrastructure/adapter/in/web/member/MemberController.kt index 48cea97..f0bb886 100644 --- a/src/main/kotlin/com/unicorn/server/infrastructure/adapter/in/web/member/MemberController.kt +++ b/src/main/kotlin/com/unicorn/server/infrastructure/adapter/in/web/member/MemberController.kt @@ -5,12 +5,8 @@ import com.unicorn.server.domain.member.enums.MemberStatus import com.unicorn.server.domain.member.enums.Role import com.unicorn.server.domain.member.port.`in`.MemberOnboardingCompleteInPort import com.unicorn.server.domain.member.port.`in`.MemberInPort -import com.unicorn.server.domain.member.port.`in`.MemberOnboardingInfoInPort import com.unicorn.server.domain.member.port.`in`.MemberLogoutInPort import com.unicorn.server.domain.member.port.`in`.MemberStateUpdateInPort -import com.unicorn.server.domain.member.port.`in`.MemberProfileUpdateInPort -import com.unicorn.server.domain.member.port.`in`.MemberProfileImageUploadInPort -import com.unicorn.server.domain.member.port.`in`.MemberWithdrawInPort import com.unicorn.server.domain.member.port.dto.CompleteOnboardingCommand import com.unicorn.server.domain.member.port.dto.UpdateProfileCommand import com.unicorn.server.domain.member.port.dto.UploadProfileImageCommand @@ -40,12 +36,8 @@ import org.springframework.web.multipart.MultipartFile @RestController @RequestMapping("/api/v1/members") class MemberController( - private val getMemberInPort: MemberInPort, - private val getOnboardingInfoInPort: MemberOnboardingInfoInPort, - private val updateProfileInPort: MemberProfileUpdateInPort, - private val uploadProfileImageInPort: MemberProfileImageUploadInPort, + private val memberInPort: MemberInPort, private val logoutInPort: MemberLogoutInPort, - private val withdrawMemberInPort: MemberWithdrawInPort, private val completeOnboardingInPort: MemberOnboardingCompleteInPort, private val updateMemberStateInPort: MemberStateUpdateInPort, @param:Value("\${app.server.url}") private val serverUrl: String, @@ -56,7 +48,7 @@ class MemberController( override fun getMyProfile( @AuthenticationPrincipal memberId: String, ): ApiResponse { - val member = getMemberInPort.getById(memberId) + val member = memberInPort.getById(memberId) return ApiResponse.success(toMemberResponse(member)) } @@ -65,7 +57,7 @@ class MemberController( override fun getOnboardingInfo( @AuthenticationPrincipal memberId: String, ): ApiResponse { - val result = getOnboardingInfoInPort.getOnboardingInfo(memberId) + val result = memberInPort.getOnboardingInfo(memberId) return ApiResponse.success(OnboardingInfoResponse.from(result)) } @@ -88,7 +80,7 @@ class MemberController( @AuthenticationPrincipal memberId: String, @RequestBody @Valid request: UpdateProfileRequest, ): ApiResponse { - val member = updateProfileInPort.updateProfile(memberId, UpdateProfileCommand(request.nickname)) + val member = memberInPort.updateProfile(memberId, UpdateProfileCommand(request.nickname)) return ApiResponse.success(toMemberResponse(member)) } @@ -104,7 +96,7 @@ class MemberController( contentLength = image.size, inputStream = image.inputStream, ) - val member = uploadProfileImageInPort.uploadProfileImage(memberId, command) + val member = memberInPort.uploadProfileImage(memberId, command) return ApiResponse.success(toMemberResponse(member)) } @@ -123,7 +115,7 @@ class MemberController( @AuthenticationPrincipal memberId: String, @RequestBody @Valid request: WithdrawRequest, ): ApiResponse { - withdrawMemberInPort.withdraw(memberId, request.reason) + memberInPort.withdraw(memberId, request.reason) return ApiResponse.success() } diff --git a/src/main/kotlin/com/unicorn/server/infrastructure/adapter/in/web/schedule/ScheduleController.kt b/src/main/kotlin/com/unicorn/server/infrastructure/adapter/in/web/schedule/ScheduleController.kt index b49e801..4ebf309 100644 --- a/src/main/kotlin/com/unicorn/server/infrastructure/adapter/in/web/schedule/ScheduleController.kt +++ b/src/main/kotlin/com/unicorn/server/infrastructure/adapter/in/web/schedule/ScheduleController.kt @@ -1,15 +1,10 @@ package com.unicorn.server.infrastructure.adapter.`in`.web.schedule import com.unicorn.server.domain.schedule.enums.ConfirmationType -import com.unicorn.server.domain.schedule.port.`in`.ScheduleConfirmationCancelInPort -import com.unicorn.server.domain.schedule.port.`in`.ScheduleCreateInPort -import com.unicorn.server.domain.schedule.port.`in`.ScheduleDeleteInPort -import com.unicorn.server.domain.schedule.port.`in`.ScheduleDetailInPort -import com.unicorn.server.domain.schedule.port.`in`.ScheduleListInPort -import com.unicorn.server.domain.schedule.port.`in`.ScheduleConfirmationRegisterInPort - +import com.unicorn.server.domain.schedule.port.`in`.ScheduleCommandInPort +import com.unicorn.server.domain.schedule.port.`in`.ScheduleQueryInPort +import com.unicorn.server.domain.schedule.port.`in`.ScheduleConfirmationInPort import com.unicorn.server.domain.schedule.port.`in`.ScheduleFamilyNotificationRequestInPort -import com.unicorn.server.domain.schedule.port.`in`.ScheduleUpdateInPort import com.unicorn.server.domain.schedule.port.dto.CreateScheduleCommand import com.unicorn.server.domain.schedule.port.dto.RegisterConfirmationCommand import com.unicorn.server.domain.schedule.port.dto.RequestFamilyScheduleNotificationCommand @@ -39,13 +34,9 @@ import org.springframework.web.bind.annotation.RestController @RestController @RequestMapping("/api/v1/circles/{circleId}/schedules") class ScheduleController( - private val createScheduleInPort: ScheduleCreateInPort, - private val updateScheduleInPort: ScheduleUpdateInPort, - private val deleteScheduleInPort: ScheduleDeleteInPort, - private val getScheduleListInPort: ScheduleListInPort, - private val getScheduleDetailInPort: ScheduleDetailInPort, - private val registerConfirmationInPort: ScheduleConfirmationRegisterInPort, - private val cancelConfirmationInPort: ScheduleConfirmationCancelInPort, + private val scheduleCommandInPort: ScheduleCommandInPort, + private val scheduleQueryInPort: ScheduleQueryInPort, + private val scheduleConfirmationInPort: ScheduleConfirmationInPort, private val requestFamilyScheduleNotificationInPort: ScheduleFamilyNotificationRequestInPort, ) : ScheduleApiDoc { @@ -55,7 +46,7 @@ class ScheduleController( @PathVariable circleId: String, @RequestBody @Valid request: CreateScheduleRequest, ): ApiResponse { - val scheduleId = createScheduleInPort.create( + val scheduleId = scheduleCommandInPort.create( CreateScheduleCommand( memberId = memberId, circleId = circleId, @@ -78,7 +69,7 @@ class ScheduleController( @PathVariable scheduleId: String, @RequestBody @Valid request: UpdateScheduleRequest, ): ApiResponse { - updateScheduleInPort.update( + scheduleCommandInPort.update( UpdateScheduleCommand( scheduleId = ScheduleId.of(scheduleId), circleId = circleId, @@ -104,7 +95,7 @@ class ScheduleController( @PathVariable circleId: String, @PathVariable scheduleId: String, ): ApiResponse { - deleteScheduleInPort.delete(ScheduleId.of(scheduleId), circleId, memberId) + scheduleCommandInPort.delete(ScheduleId.of(scheduleId), circleId, memberId) return ApiResponse.success() } @@ -115,7 +106,7 @@ class ScheduleController( @RequestParam cursor: String?, @RequestParam(defaultValue = "20") size: Int, ): ApiResponse { - val result = getScheduleListInPort.getList(circleId, memberId, cursor, size) + val result = scheduleQueryInPort.getList(circleId, memberId, cursor, size) return ApiResponse.success(ScheduleListResponse.from(result)) } @@ -125,7 +116,7 @@ class ScheduleController( @PathVariable circleId: String, @PathVariable scheduleId: String, ): ApiResponse { - val result = getScheduleDetailInPort.getDetail(ScheduleId.of(scheduleId), circleId, memberId) + val result = scheduleQueryInPort.getDetail(ScheduleId.of(scheduleId), circleId, memberId) return ApiResponse.success(ScheduleDetailResponse.from(result)) } @@ -136,7 +127,7 @@ class ScheduleController( @PathVariable scheduleId: String, @RequestBody @Valid request: RegisterConfirmationRequest, ): ApiResponse { - val type = registerConfirmationInPort.register( + val type = scheduleConfirmationInPort.register( RegisterConfirmationCommand( scheduleId = ScheduleId.of(scheduleId), circleId = circleId, @@ -154,7 +145,7 @@ class ScheduleController( @PathVariable scheduleId: String, @PathVariable confirmationId: Long, ): ApiResponse { - cancelConfirmationInPort.cancel(confirmationId, ScheduleId.of(scheduleId), circleId, memberId) + scheduleConfirmationInPort.cancel(confirmationId, ScheduleId.of(scheduleId), circleId, memberId) return ApiResponse.success() } diff --git a/src/test/kotlin/com/unicorn/server/domain/schedule/service/CancelConfirmationServiceTest.kt b/src/test/kotlin/com/unicorn/server/domain/schedule/service/CancelConfirmationServiceTest.kt deleted file mode 100644 index 9a92ff9..0000000 --- a/src/test/kotlin/com/unicorn/server/domain/schedule/service/CancelConfirmationServiceTest.kt +++ /dev/null @@ -1,205 +0,0 @@ -package com.unicorn.server.domain.schedule.service - -import com.unicorn.server.common.exception.BusinessException -import com.unicorn.server.domain.schedule.Schedule -import com.unicorn.server.domain.schedule.ScheduleConfirmation -import com.unicorn.server.domain.schedule.enums.ConfirmationType -import com.unicorn.server.domain.schedule.exception.ScheduleErrorCode -import com.unicorn.server.domain.schedule.port.dto.ConfirmationCountResult -import com.unicorn.server.domain.schedule.port.dto.SchedulePageCursor -import com.unicorn.server.domain.schedule.port.out.CircleAccessOutPort -import com.unicorn.server.domain.schedule.port.out.ScheduleConfirmationOutPort -import com.unicorn.server.domain.schedule.port.out.ScheduleOutPort -import com.unicorn.server.domain.schedule.vo.ScheduleId -import org.assertj.core.api.Assertions.assertThat -import org.assertj.core.api.Assertions.assertThatThrownBy -import org.junit.jupiter.api.DisplayName -import org.junit.jupiter.api.Test -import java.time.LocalDate -import java.time.LocalDateTime -import java.time.LocalTime - -@DisplayName("CancelConfirmationService 단위 테스트") -class CancelConfirmationServiceTest { - - private val scheduleOutPort = FakeScheduleOutPort() - private val confirmationOutPort = FakeScheduleConfirmationOutPort() - private val circleAccessOutPort = FakeCircleAccessOutPort() - private val cancelConfirmationService = CancelConfirmationService( - scheduleOutPort, - confirmationOutPort, - circleAccessOutPort, - ) - - @Test - @DisplayName("본인이 등록한 확인하기를 취소한다") - fun cancel_withOwnConfirmation_deletesConfirmation() { - circleAccessOutPort.seedMember(CIRCLE_ID, MEMBER_ID) - scheduleOutPort.seed(schedule()) - confirmationOutPort.seed(confirmation(id = CONFIRMATION_ID, memberId = MEMBER_ID)) - - cancelConfirmationService.cancel(CONFIRMATION_ID, SCHEDULE_ID, CIRCLE_ID, MEMBER_ID) - - assertThat(confirmationOutPort.deletedIds).containsExactly(CONFIRMATION_ID) - assertThat(confirmationOutPort.findById(CONFIRMATION_ID)).isNull() - } - - @Test - @DisplayName("확인하기가 없으면 CONFIRMATION_NOT_FOUND 예외가 발생한다") - fun cancel_withMissingConfirmation_throwsConfirmationNotFound() { - circleAccessOutPort.seedMember(CIRCLE_ID, MEMBER_ID) - scheduleOutPort.seed(schedule()) - - assertThatThrownBy { cancelConfirmationService.cancel(CONFIRMATION_ID, SCHEDULE_ID, CIRCLE_ID, MEMBER_ID) } - .isInstanceOf(BusinessException::class.java) - .extracting { (it as BusinessException).errorCode } - .isEqualTo(ScheduleErrorCode.CONFIRMATION_NOT_FOUND) - } - - @Test - @DisplayName("다른 멤버의 확인하기면 CONFIRMATION_ACCESS_DENIED 예외가 발생한다") - fun cancel_withOtherMemberConfirmation_throwsConfirmationAccessDenied() { - circleAccessOutPort.seedMember(CIRCLE_ID, MEMBER_ID) - scheduleOutPort.seed(schedule()) - confirmationOutPort.seed(confirmation(id = CONFIRMATION_ID, memberId = "member-2")) - - assertThatThrownBy { cancelConfirmationService.cancel(CONFIRMATION_ID, SCHEDULE_ID, CIRCLE_ID, MEMBER_ID) } - .isInstanceOf(BusinessException::class.java) - .extracting { (it as BusinessException).errorCode } - .isEqualTo(ScheduleErrorCode.CONFIRMATION_ACCESS_DENIED) - } - - private fun schedule(): Schedule = - Schedule.reconstitute( - id = SCHEDULE_ID, - circleId = CIRCLE_ID, - title = "제주도 여행", - startDate = LocalDate.of(2024, 8, 1), - endDate = LocalDate.of(2024, 8, 1), - startTime = LocalTime.of(9, 0), - endTime = LocalTime.of(18, 0), - needConfirm = true, - memo = null, - createdBy = "author", - updatedBy = "author", - createdAt = LocalDateTime.of(2024, 7, 1, 10, 0), - updatedAt = LocalDateTime.of(2024, 7, 1, 10, 0), - isDeleted = false, - ) - - private fun confirmation(id: Long, memberId: String): ScheduleConfirmation = - ScheduleConfirmation.reconstitute( - id = id, - scheduleId = SCHEDULE_ID, - memberId = memberId, - confirmationType = ConfirmationType.CONFIRMED, - createdBy = memberId, - updatedBy = memberId, - createdAt = LocalDateTime.of(2024, 7, 1, 10, 0), - updatedAt = LocalDateTime.of(2024, 7, 1, 10, 0), - ) - - private class FakeScheduleOutPort : ScheduleOutPort { - private val store = linkedMapOf() - - fun seed(schedule: Schedule) { - store[schedule.id] = schedule - } - - override fun save(schedule: Schedule): Schedule = schedule - - override fun findById(scheduleId: ScheduleId): Schedule? = store[scheduleId] - - override fun findActiveByIdAndCircleId(scheduleId: ScheduleId, circleId: String): Schedule? = - store[scheduleId]?.takeIf { it.circleId == circleId && !it.isDeleted } - - override fun findActiveByCircleId( - circleId: String, - today: LocalDate, - cursor: SchedulePageCursor?, - size: Int, - ): List = emptyList() - - override fun findActiveByStartDateAndCreatedBefore( - startDate: java.time.LocalDate, - createdBefore: java.time.LocalDateTime, - ): List = error("not used") - - override fun findActiveAllDayByStartDateAndCreatedBefore( - startDate: java.time.LocalDate, - createdBefore: java.time.LocalDateTime, - ): List = error("not used") - - override fun findActiveTimedByStartAtAndCreatedBefore( - startDate: java.time.LocalDate, - startTime: java.time.LocalTime, - createdBefore: java.time.LocalDateTime, - ): List = error("not used") - - override fun findActiveConfirmationRequiredCreatedBetween( - createdFrom: java.time.LocalDateTime, - createdBefore: java.time.LocalDateTime, - ): List = error("not used") - - override fun findUpcomingByCircleId( - circleId: String, - today: LocalDate, - limit: Int, - ): List = emptyList() - - override fun countActiveByCircleId(circleId: String): Long = 0L - } - - private class FakeScheduleConfirmationOutPort : ScheduleConfirmationOutPort { - private val store = mutableListOf() - val deletedIds = mutableListOf() - - fun seed(confirmation: ScheduleConfirmation) { - store += confirmation - } - - override fun findById(id: Long): ScheduleConfirmation? = - store.firstOrNull { it.id == id } - - override fun findByScheduleIdAndMemberId(scheduleId: ScheduleId, memberId: String): ScheduleConfirmation? = - store.firstOrNull { it.scheduleId == scheduleId && it.memberId == memberId } - - override fun save(confirmation: ScheduleConfirmation): ScheduleConfirmation { - store.removeIf { it.id == confirmation.id } - store += confirmation - return confirmation - } - - override fun deleteById(id: Long) { - deletedIds += id - store.removeIf { it.id == id } - } - - override fun countGroupByType(scheduleId: ScheduleId): List = emptyList() - - override fun deleteAllByScheduleId(scheduleId: ScheduleId) { - store.removeIf { it.scheduleId == scheduleId } - } - } - - private class FakeCircleAccessOutPort : CircleAccessOutPort { - private val members = mutableSetOf>() - - fun seedMember(circleId: String, memberId: String) { - members += circleId to memberId - } - - override fun existsById(circleId: String): Boolean = true - - override fun isMember(circleId: String, memberId: String): Boolean = circleId to memberId in members - - override fun isInitiator(circleId: String, memberId: String): Boolean = false - } - - companion object { - private const val CIRCLE_ID = "CC202506010000000001" - private val SCHEDULE_ID = ScheduleId.of("SC202407070000000001") - private const val MEMBER_ID = "member-1" - private const val CONFIRMATION_ID = 1L - } -} diff --git a/src/test/kotlin/com/unicorn/server/domain/schedule/service/ScheduleConfirmationServiceTest.kt b/src/test/kotlin/com/unicorn/server/domain/schedule/service/ScheduleConfirmationServiceTest.kt index d63704e..4ea8678 100644 --- a/src/test/kotlin/com/unicorn/server/domain/schedule/service/ScheduleConfirmationServiceTest.kt +++ b/src/test/kotlin/com/unicorn/server/domain/schedule/service/ScheduleConfirmationServiceTest.kt @@ -126,6 +126,44 @@ class ScheduleConfirmationServiceTest { .isEqualTo(ScheduleErrorCode.CIRCLE_ACCESS_DENIED) } + @Test + @DisplayName("본인이 등록한 확인하기를 취소한다") + fun cancel_withOwnConfirmation_deletesConfirmation() { + circleAccessOutPort.seedMember(CIRCLE_ID, MEMBER_ID) + scheduleOutPort.seed(schedule(needConfirm = true)) + confirmationOutPort.seed(confirmation(id = CONFIRMATION_ID, memberId = MEMBER_ID)) + + scheduleConfirmationService.cancel(CONFIRMATION_ID, SCHEDULE_ID, CIRCLE_ID, MEMBER_ID) + + assertThat(confirmationOutPort.deletedIds).containsExactly(CONFIRMATION_ID) + assertThat(confirmationOutPort.findById(CONFIRMATION_ID)).isNull() + } + + @Test + @DisplayName("확인하기가 없으면 CONFIRMATION_NOT_FOUND 예외가 발생한다") + fun cancel_withMissingConfirmation_throwsConfirmationNotFound() { + circleAccessOutPort.seedMember(CIRCLE_ID, MEMBER_ID) + scheduleOutPort.seed(schedule(needConfirm = true)) + + assertThatThrownBy { scheduleConfirmationService.cancel(CONFIRMATION_ID, SCHEDULE_ID, CIRCLE_ID, MEMBER_ID) } + .isInstanceOf(BusinessException::class.java) + .extracting { (it as BusinessException).errorCode } + .isEqualTo(ScheduleErrorCode.CONFIRMATION_NOT_FOUND) + } + + @Test + @DisplayName("다른 멤버의 확인하기면 CONFIRMATION_ACCESS_DENIED 예외가 발생한다") + fun cancel_withOtherMemberConfirmation_throwsConfirmationAccessDenied() { + circleAccessOutPort.seedMember(CIRCLE_ID, MEMBER_ID) + scheduleOutPort.seed(schedule(needConfirm = true)) + confirmationOutPort.seed(confirmation(id = CONFIRMATION_ID, memberId = "member-2")) + + assertThatThrownBy { scheduleConfirmationService.cancel(CONFIRMATION_ID, SCHEDULE_ID, CIRCLE_ID, MEMBER_ID) } + .isInstanceOf(BusinessException::class.java) + .extracting { (it as BusinessException).errorCode } + .isEqualTo(ScheduleErrorCode.CONFIRMATION_ACCESS_DENIED) + } + private fun command(): RegisterConfirmationCommand = RegisterConfirmationCommand( scheduleId = SCHEDULE_ID, @@ -134,6 +172,18 @@ class ScheduleConfirmationServiceTest { confirmationType = ConfirmationType.CONFIRMED, ) + private fun confirmation(id: Long, memberId: String): ScheduleConfirmation = + ScheduleConfirmation.reconstitute( + id = id, + scheduleId = SCHEDULE_ID, + memberId = memberId, + confirmationType = ConfirmationType.CONFIRMED, + createdBy = memberId, + updatedBy = memberId, + createdAt = LocalDateTime.of(2024, 7, 1, 10, 0), + updatedAt = LocalDateTime.of(2024, 7, 1, 10, 0), + ) + private fun schedule(needConfirm: Boolean): Schedule = Schedule.reconstitute( id = SCHEDULE_ID, @@ -206,6 +256,7 @@ class ScheduleConfirmationServiceTest { private class FakeScheduleConfirmationOutPort : ScheduleConfirmationOutPort { private val store = mutableListOf() val saved = mutableListOf() + val deletedIds = mutableListOf() fun seed(confirmation: ScheduleConfirmation) { store += confirmation @@ -225,6 +276,7 @@ class ScheduleConfirmationServiceTest { } override fun deleteById(id: Long) { + deletedIds += id store.removeIf { it.id == id } } @@ -261,5 +313,6 @@ class ScheduleConfirmationServiceTest { private const val CIRCLE_ID = "CC202506010000000001" private val SCHEDULE_ID = ScheduleId.of("SC202407070000000001") private const val MEMBER_ID = "member-1" + private const val CONFIRMATION_ID = 1L } }