Skip to content

Commit d4e3b22

Browse files
robinjoonclaude
andauthored
refactor: auth BC Command/Query 패턴 적용 (#26) (#30)
- MemberCommand에 Login variant 추가 - Register에 encodedPassword 필드 포함 - MemberRepository.save()에서 encodedPassword 별도 파라미터 제거 - AuthService.login()이 MemberCommand.Login 수신하도록 변경 - AuthDataFetcher에서 login Command 변환 패턴 적용 Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
1 parent dea633e commit d4e3b22

9 files changed

Lines changed: 104 additions & 23 deletions

File tree

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
# auth BC: Command/Query 패턴 미적용 수정 검증 체크리스트
2+
3+
## 필수 항목
4+
- [x] 아키텍처 원칙 준수 (docs/architecture.md 기준)
5+
- [x] 레이어 의존성 규칙 위반 없음
6+
- [x] 테스트 코드 작성 완료 (Domain, Application 필수)
7+
- [x] 모든 테스트 통과
8+
- [x] 기존 테스트 깨지지 않음
9+
10+
## 선택 항목 (해당 시)
11+
- [x] auth BC 파일만 수정 (다른 BC 미수정)
12+
- [x] Command/Query 데이터 흐름 일관성 확보
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
# auth BC: Command/Query 패턴 미적용 수정 계획
2+
3+
> Issue: #26
4+
5+
## 단계
6+
7+
- [x] 1단계: Domain — MemberCommand에 Login variant 추가, Register에 encodedPassword 포함 (TDD)
8+
- [x] 2단계: Domain — MemberRepository.save() 시그니처에서 encodedPassword 파라미터 제거
9+
- [x] 3단계: Application — AuthService.login()이 MemberCommand.Login을 수신하도록 변경, register()에서 command.copy로 encodedPassword 전달 (TDD)
10+
- [x] 4단계: Infrastructure — ExposedMemberRepository.save() 구현 수정
11+
- [x] 5단계: Presentation — AuthDataFetcher.login()에서 MemberCommand.Login 변환
12+
- [x] 6단계: 전체 테스트 통과 검증

src/main/kotlin/kr/io/team/loop/auth/application/service/AuthService.kt

Lines changed: 5 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
package kr.io.team.loop.auth.application.service
22

33
import kr.io.team.loop.auth.application.dto.AuthTokenDto
4-
import kr.io.team.loop.auth.domain.model.LoginId
54
import kr.io.team.loop.auth.domain.model.MemberCommand
65
import kr.io.team.loop.auth.domain.repository.MemberRepository
76
import kr.io.team.loop.common.config.JwtTokenProvider
@@ -24,20 +23,17 @@ class AuthService(
2423
throw DuplicateEntityException("LoginId already exists: ${command.loginId.value}")
2524
}
2625
val encodedPassword = checkNotNull(passwordEncoder.encode(command.rawPassword))
27-
val member = memberRepository.save(command, encodedPassword)
26+
val member = memberRepository.save(command.copy(encodedPassword = encodedPassword))
2827
val token = jwtTokenProvider.generateToken(member.id.value)
2928
return AuthTokenDto(accessToken = token)
3029
}
3130

3231
@Transactional(readOnly = true)
33-
fun login(
34-
loginId: LoginId,
35-
rawPassword: String,
36-
): AuthTokenDto {
32+
fun login(command: MemberCommand.Login): AuthTokenDto {
3733
val member =
38-
memberRepository.findByLoginId(loginId)
39-
?: throw EntityNotFoundException("Member not found: ${loginId.value}")
40-
if (!passwordEncoder.matches(rawPassword, member.password)) {
34+
memberRepository.findByLoginId(command.loginId)
35+
?: throw EntityNotFoundException("Member not found: ${command.loginId.value}")
36+
if (!passwordEncoder.matches(command.rawPassword, member.password)) {
4137
throw AuthenticationException("Password does not match")
4238
}
4339
val token = jwtTokenProvider.generateToken(member.id.value)

src/main/kotlin/kr/io/team/loop/auth/domain/model/MemberCommand.kt

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,5 +5,11 @@ sealed interface MemberCommand {
55
val loginId: LoginId,
66
val nickname: Nickname,
77
val rawPassword: String,
8+
val encodedPassword: String? = null,
9+
) : MemberCommand
10+
11+
data class Login(
12+
val loginId: LoginId,
13+
val rawPassword: String,
814
) : MemberCommand
915
}

src/main/kotlin/kr/io/team/loop/auth/domain/repository/MemberRepository.kt

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,7 @@ import kr.io.team.loop.auth.domain.model.Member
55
import kr.io.team.loop.auth.domain.model.MemberCommand
66

77
interface MemberRepository {
8-
fun save(
9-
command: MemberCommand.Register,
10-
encodedPassword: String,
11-
): Member
8+
fun save(command: MemberCommand.Register): Member
129

1310
fun findByLoginId(loginId: LoginId): Member?
1411

src/main/kotlin/kr/io/team/loop/auth/infrastructure/persistence/ExposedMemberRepository.kt

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,8 @@ import java.time.OffsetDateTime
1515

1616
@Repository
1717
class ExposedMemberRepository : MemberRepository {
18-
override fun save(
19-
command: MemberCommand.Register,
20-
encodedPassword: String,
21-
): Member {
18+
override fun save(command: MemberCommand.Register): Member {
19+
val encodedPassword = checkNotNull(command.encodedPassword)
2220
val now = OffsetDateTime.now()
2321
val row =
2422
MemberTable.insert {

src/main/kotlin/kr/io/team/loop/auth/presentation/datafetcher/AuthDataFetcher.kt

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,11 +33,12 @@ class AuthDataFetcher(
3333
fun login(
3434
@InputArgument input: LoginInput,
3535
): AuthToken {
36-
val result =
37-
authService.login(
36+
val command =
37+
MemberCommand.Login(
3838
loginId = LoginId(input.loginId),
3939
rawPassword = input.password,
4040
)
41+
val result = authService.login(command)
4142
return AuthToken(accessToken = result.accessToken)
4243
}
4344
}

src/test/kotlin/kr/io/team/loop/auth/application/service/AuthServiceTest.kt

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,9 @@ class AuthServiceTest :
4848
When("유효한 정보이면") {
4949
every { memberRepository.existsByLoginId(registerCommand.loginId) } returns false
5050
every { passwordEncoder.encode("password123") } returns "encoded_password"
51-
every { memberRepository.save(registerCommand, "encoded_password") } returns savedMember
51+
every {
52+
memberRepository.save(registerCommand.copy(encodedPassword = "encoded_password"))
53+
} returns savedMember
5254
every { jwtTokenProvider.generateToken(1L) } returns "jwt-token"
5355

5456
val result = authService.register(registerCommand)
@@ -76,7 +78,12 @@ class AuthServiceTest :
7678
every { passwordEncoder.matches("password123", "encoded_password") } returns true
7779
every { jwtTokenProvider.generateToken(1L) } returns "jwt-token"
7880

79-
val result = authService.login(LoginId("testuser"), "password123")
81+
val loginCommand =
82+
MemberCommand.Login(
83+
loginId = LoginId("testuser"),
84+
rawPassword = "password123",
85+
)
86+
val result = authService.login(loginCommand)
8087

8188
Then("accessToken을 반환한다") {
8289
result.accessToken.shouldNotBeBlank()
@@ -88,8 +95,13 @@ class AuthServiceTest :
8895
every { memberRepository.findByLoginId(LoginId("unknown")) } returns null
8996

9097
Then("예외가 발생한다") {
98+
val loginCommand =
99+
MemberCommand.Login(
100+
loginId = LoginId("unknown"),
101+
rawPassword = "password123",
102+
)
91103
shouldThrow<EntityNotFoundException> {
92-
authService.login(LoginId("unknown"), "password123")
104+
authService.login(loginCommand)
93105
}
94106
}
95107
}
@@ -99,8 +111,13 @@ class AuthServiceTest :
99111
every { passwordEncoder.matches("wrongpassword", "encoded_password") } returns false
100112

101113
Then("예외가 발생한다") {
114+
val loginCommand =
115+
MemberCommand.Login(
116+
loginId = LoginId("testuser"),
117+
rawPassword = "wrongpassword",
118+
)
102119
shouldThrow<AuthenticationException> {
103-
authService.login(LoginId("testuser"), "wrongpassword")
120+
authService.login(loginCommand)
104121
}
105122
}
106123
}

src/test/kotlin/kr/io/team/loop/auth/domain/model/MemberTest.kt

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,5 +47,47 @@ class MemberTest :
4747
command.rawPassword shouldBe "password123"
4848
}
4949
}
50+
51+
When("encodedPassword를 포함하면") {
52+
val command =
53+
MemberCommand.Register(
54+
loginId = LoginId("newuser"),
55+
nickname = Nickname("새사용자"),
56+
rawPassword = "password123",
57+
encodedPassword = "encoded_password",
58+
)
59+
60+
Then("encodedPassword가 설정된다") {
61+
command.encodedPassword shouldBe "encoded_password"
62+
}
63+
}
64+
65+
When("encodedPassword를 생략하면") {
66+
val command =
67+
MemberCommand.Register(
68+
loginId = LoginId("newuser"),
69+
nickname = Nickname("새사용자"),
70+
rawPassword = "password123",
71+
)
72+
73+
Then("encodedPassword는 null이다") {
74+
command.encodedPassword shouldBe null
75+
}
76+
}
77+
}
78+
79+
Given("MemberCommand.Login 생성 시") {
80+
When("유효한 값이면") {
81+
val command =
82+
MemberCommand.Login(
83+
loginId = LoginId("testuser"),
84+
rawPassword = "password123",
85+
)
86+
87+
Then("정상 생성된다") {
88+
command.loginId.value shouldBe "testuser"
89+
command.rawPassword shouldBe "password123"
90+
}
91+
}
5092
}
5193
})

0 commit comments

Comments
 (0)