Search
Duplicate
πŸ—»

μ–΄μ§ˆμ–΄μ§ˆ 반볡 둜직 μ œκ±°ν•˜κΈ° (ν…œν”Œλ¦Ώ-콜백 νŒ¨ν„΄)

생성일
2022/12/10
νƒœκ·Έ
Spring
Aop
Refactoring
λͺ©μ°¨

λ°°κ²½

저희 ν”„λ‘œμ νŠΈμ˜ κΈ°λŠ₯듀은 그룹을 기반으둜 μš΄μ˜λ©λ‹ˆλ‹€. 이에 따라 μš”μ²­ν•œ νŒ€κ³Ό λ©€λ²„μ˜ 정보λ₯Ό λŒ€λΆ€λΆ„μ˜ API μ—μ„œ λ°›κ³  있고 이λ₯Ό κ²€μ¦ν•˜λŠ” 둜직이 μ‘΄μž¬ν•©λ‹ˆλ‹€. λŒ€λΆ€λΆ„μ˜ μ„œλΉ„μŠ€ 계측 λ©”μ„œλ“œμ—μ„œ λ°˜λ³΅λ˜μ–΄ λ‚˜νƒ€λ‚˜λŠ” ν•΄λ‹Ή 검증 둜직의 λ°˜λ³΅μ„ 쀄이기 μœ„ν•΄ μ΄ˆκΈ°μ—λŠ” 클래슀 λ‚΄μ˜ λ©”μ„œλ“œλ‘œ λΆ„λ¦¬ν•˜λŠ” λ°©μ‹μœΌλ‘œ ν•΄κ²°ν•΄ μ™”μŠ΅λ‹ˆλ‹€. ν•˜μ§€λ§Œ κΈ°λŠ₯듀이 점차 μΆ”κ°€λ˜λ©΄μ„œ μ„œλΉ„μŠ€ 계측이 횑적으둜 ν™•μž₯이 λ˜μ—ˆκ³ , λ™μΌν•œ 검증 λ‘œμ§μ„ μƒˆλ‘œμš΄ μ„œλΉ„μŠ€ κ³„μΈ΅μ—μ„œ λ°˜λ³΅ν•΄μ•Ό ν•˜λŠ” 상황이 λ°œμƒν•˜μ˜€μŠ΅λ‹ˆλ‹€. μ΄λ•ŒλΆ€ν„° 점점 ꡬ쑰 λ³€κ²½μ˜ ν•„μš”μ„±μ„ 느끼고 μžˆμ—ˆλŠ”λ°, 쿼리 μ„±λŠ₯ νŠœλ‹μ„ μ§„ν–‰ν•˜λ©΄μ„œ 검증 둜직 λ‚΄ JPA λ©”μ„œλ“œλ₯Ό λ³€κ²½ν•˜μ˜€μŠ΅λ‹ˆλ‹€. 그러자 이 검증 둜직 λ‚΄ JPA λ©”μ„œλ“œλ₯Ό μ΄μš©ν•˜λ˜ λͺ¨λ“  μ„œλΉ„μŠ€ κ³„μΈ΅μ—μ„œ 빨간쀄이 κ·Έμ–΄μ‘Œκ³ , ν˜„μž¬ κ΅¬μ‘°λŠ” μœ μ§€ λ³΄μˆ˜κ°€ νž˜λ“€λ‹€λŠ” 것을 깨달아 ꡬ쑰 변경을 κ²°μ‹¬ν•˜μ˜€μŠ΅λ‹ˆλ‹€.

문제점

ν˜„μž¬ ꡬ쑰가 κ°€μ§€λŠ” λ¬Έμ œμ μ€ λ‹€μŒκ³Ό κ°™μŠ΅λ‹ˆλ‹€.
1.
전체 μ„œλΉ„μŠ€ κ³„μΈ΅μ—μ„œ 곡톡 둜직이 λ°˜λ³΅λœλ‹€.
β€’
λͺ¨λ“  μ„œλΉ„μŠ€μ—μ„œ 같은 λ‘œμ§μ„ μœ„ν•΄ ν•΄λ‹Ή λ©”μ„œλ“œλ₯Ό μ‚¬μš©μ€‘μž…λ‹ˆλ‹€. 이에 따라 검증 둜직 ν•˜λ‚˜κ°€ λ°”λ€Œλ©΄ 4개의 ν΄λž˜μŠ€μ—μ„œ λ°”λ€ŒλŠ” λ§ˆλ²•β€¦! ν…ŒμŠ€νŠΈμͺ½ 변경은 아직 μ‹œμž‘λ„ μ•ˆν•œ μƒνƒœλΌλŠ” μŠ¬ν”ˆ 사싀,,,
2.
λ§Œμ•½ μƒˆλ‘œμš΄ κΈ°λŠ₯이 μΆ”κ°€λœλ‹€λ©΄?
β€’
검증에 ν•„μš”ν•œ MemberRepository, TeamRepository, TeamMemberRepository λ₯Ό 기본적으둜 μ˜μ‘΄ν•˜λ©° λ™μΌν•œ 검증 λ‘œμ§μ„ 생성해야 ν•©λ‹ˆλ‹€. λ˜ν•œ μƒˆλ‘œμš΄ API λ§ˆλ‹€ 검증 λ‘œμ§μ„ μΆ”κ°€ν•΄μ•Ό ν•˜κΈ°λ•Œλ¬Έμ— μ‹€μ œ λΉ„μ¦ˆλ‹ˆμŠ€ λ‘œμ§μ— 집쀑할 수 μžˆλŠ” 여건이 λ‹€μ†Œ λ–¨μ–΄μ§‘λ‹ˆλ‹€.
문제λ₯Ό μš”μ•½ν•΄λ³΄λ©΄ 두 가지 문제λ₯Ό 생각해볼 수 μžˆλŠ”λ°μš”. 첫째, 검증 둜직이 λ°”λ€Œλ©΄ ν•΄λ‹Ή λ‘œμ§μ„ μ΄μš©ν•˜λŠ” λͺ¨λ“  λ‘œμ§μ—μ„œ μˆ˜μ •μ„ ν•΄μ•Ό ν•©λ‹ˆλ‹€. 예λ₯Ό λ“€μ–΄ μ‚¬μš©μžλ₯Ό ν™•μΈν•˜λŠ” νƒ€μž…μ΄ Long μ—μ„œ UUID 와 같은 λ¬Έμžμ—΄λ‘œ λ°”λ€Œλ©΄ ν•΄λ‹Ή λ‘œμ§μ„ μ΄μš©ν•˜λ˜ λͺ¨λ“  λ©”μ„œλ“œλ₯Ό μ°Ύμ•„ 일일이 μˆ˜μ •μ„ ν•΄μ£Όμ–΄μ•Ό ν•©λ‹ˆλ‹€. 이둜 인해 μœ μ§€, 보수 μΈ‘λ©΄μ—μ„œ 어렀움을 κ²ͺ을 수 μžˆμŠ΅λ‹ˆλ‹€. λ‘˜μ§Έ, μƒˆλ‘œμš΄ APIκ°€ 좔가될 λ•Œ 검증 λ‘œμ§λ„ μΆ”κ°€ν•΄μ•Ό ν•œλ‹€λŠ” μ μž…λ‹ˆλ‹€. 이둜 인해 μƒˆλ‘œμš΄ λΉ„μ¦ˆλ‹ˆμŠ€ λ‘œμ§μ— λŒ€ν•œ 집쀑이 떨어지고 가독성 λΆ€λΆ„μ—μ„œλ„ λΉ„νš¨μœ¨μ μ΄μ—ˆμŠ΅λ‹ˆλ‹€. μ΄λŸ¬ν•œ λ¬Έμ œμ λ“€μ„ 가지고 μžˆμ—ˆκΈ°λ•Œλ¬Έμ— 가독성 μΈ‘λ©΄μœΌλ‘œλ‚˜ μœ μ§€, 보수, ν™•μž₯의 μΈ‘λ©΄μœΌλ‘œλ‚˜ κ°œμ„ μ΄ ν•„μš”ν•˜λ‹€κ³  μƒκ°ν–ˆμŠ΅λ‹ˆλ‹€.

μ„œλΉ„μŠ€ 계측 μ„€κ³„ν•˜κΈ°

μ•žμ„œ μ–ΈκΈ‰λ“œλ Έλ‹€μ‹œν”Ό, λͺ¨λ½ μ„œλΉ„μŠ€μ˜ λͺ¨λ“  κΈ°λŠ₯은 νŒ€μ„ 기반으둜 μ œκ³΅λ©λ‹ˆλ‹€. 이에 따라 λͺ¨λ“  API μ—λŠ” μš”μ²­ν•œ νŒ€μ— μš”μ²­ν•œ μ‚¬μš©μžκ°€ μ†ν•˜λŠ”μ§€ ν™•μΈν•˜λŠ” 둜직이 μ‘΄μž¬ν•©λ‹ˆλ‹€. 이 과정을 μš”μ•½ν•˜λ©΄ λ‹€μŒκ³Ό κ°™μŠ΅λ‹ˆλ‹€.
β€’
interceptor
β—¦
μΈν„°μ…‰ν„°μ—μ„œλŠ” 토큰이 μœ νš¨ν•œμ§€ κ²€μ¦ν•©λ‹ˆλ‹€.
β€’
resolver
β—¦
resolver μ—μ„œλŠ” 받은 토큰을 νŒŒμ‹±ν•˜μ—¬ payload λ₯Ό νšλ“ν•˜κ³ , controller 의 νŒŒλΌλ―Έν„°μ—μ„œ μš”κ΅¬ν•˜λŠ” ν˜•νƒœ(Long)둜 λ³€ν™˜ν•˜μ—¬ μ „λ‹¬ν•©λ‹ˆλ‹€.
β€’
controller
β—¦
path variable 둜 받은 team code 와 νŒŒλΌλ―Έν„°λ‘œ 받은 user id λ₯Ό μ„œλΉ„μŠ€ κ³„μΈ΅μœΌλ‘œ μ „λ‹¬ν•©λ‹ˆλ‹€.
β€’
service
β—¦
μ„œλΉ„μŠ€ κ³„μΈ΅μ—μ„œλŠ” μ‚¬μš©μžκ°€ μš”μ²­ν•œ νŒ€μ— μ†ν•˜λŠ”μ§€ ν™•μΈν•œ λ’€ λΉ„μ¦ˆλ‹ˆμŠ€ λ‘œμ§μ„ μˆ˜ν–‰ν•©λ‹ˆλ‹€.
기쑴의 μ„œλΉ„μŠ€ λ‘œμ§μ€ λ‹€μŒκ³Ό κ°™μŠ΅λ‹ˆλ‹€.
// 기쑴 둜직 @Transactional(readOnly=true) public List<PollResponse> findPolls(String teamCode, Long memberId) { Member member = memberRepository.findById(memberId) .orElseThrow(() -> MemberNotFoundException.of(CustomErrorCode.MEMBER_NOT_FOUND_ERROR, memberId)); Team team = teamRepository.findByCode(teamCode) .orElseThrow(() -> TeamNotFoundException.ofTeam(CustomErrorCode.TEAM_NOT_FOUND_ERROR, teamCode)); validateMemberInTeam(team, member); List<Poll> polls = pollRepository.findAllByTeam(team); return polls.stream() .map(poll -> PollResponse.from(poll, member)) .sorted() .collect(Collectors.toList()); }
Java
볡사
κΈ°μ‘΄μ—λŠ” λ©”μ„œλ“œ 첫 λ‹€μ„― 쀄은 검증 λ‘œμ§μ—κ²Œ 자리λ₯Ό 양보해야 ν–ˆκ³ , 검증이 λλ‚œ 이후에 λΉ„λ‘œμ†Œ λΉ„μ¦ˆλ‹ˆμŠ€ λ‘œμ§μ„ 진행할 수 μžˆμ—ˆμŠ΅λ‹ˆλ‹€. ν•œ λˆˆμ— λ“€μ–΄μ˜€μ§€ μ•ŠλŠ” λΉ„μ¦ˆλ‹ˆμŠ€ λ‘œμ§μ΄μ—ˆκ³ , μ΄λŸ¬ν•œ λ°˜λ³΅λ˜λŠ” 검증 둜직이 μ „μ²΄μ μœΌλ‘œ λ°˜λ³΅λ˜μ–΄ λΆˆνŽΈν•¨μ΄ 계속 λ˜μ—ˆλ˜ κ²ƒμž…λ‹ˆλ‹€.
κ°œμ„  λ°©ν–₯μœΌλ‘œλŠ”, ν˜„μž¬μ˜ 검증 둜직이 λ©”μ„œλ“œλ‘œ 뢄리(validateMemberInTeam())λ˜μ–΄ μžˆλŠ”λ°, 이λ₯Ό λ©”μ„œλ“œμ—μ„œ 검증 둜직 객체 λ˜λŠ” κΈ°λŠ₯으둜 ν•œλ‹¨κ³„ 차원을 λ„“ν˜€λ³΄κΈ°λ‘œ ν•˜μ˜€μŠ΅λ‹ˆλ‹€. ν•΄κ²° λ°©λ²•μœΌλ‘œ 생각해본 방식은 λ‹€μŒ μ„Έ 가지가 μžˆμŠ΅λ‹ˆλ‹€.

AOP

Spring AOP λ₯Ό μ΄μš©ν•˜μ—¬, μ„œλΉ„μŠ€ κ³„μΈ΅μ˜ 클래슀 λ‹¨μœ„ λ˜λŠ” λ©”μ„œλ“œ λ‹¨μœ„μ— AOP λ₯Ό μ μš©ν•˜λŠ” κ²ƒμž…λ‹ˆλ‹€. @Transactional 을 보닀가 생각이 λ‚œ λ°©μ‹μž…λ‹ˆλ‹€. κ·Έλž˜μ„œ 흐름도 @Transactional κ³Ό μœ μ‚¬ν•˜κ²Œ λ™μž‘ν•˜λ„λ‘ μ„œλΉ„μŠ€ κ³„μΈ΅μ˜ λ©”μ„œλ“œκ°€ 호좜되고 λΉ„μ¦ˆλ‹ˆμŠ€ 둜직이 μˆ˜ν–‰λ˜κΈ° μ „, 검증 λ‘œμ§μ„ μˆ˜ν–‰ν•˜λŠ” κ²ƒμž…λ‹ˆλ‹€.
이 λ°©λ²•μ˜ μž₯μ μœΌλ‘œλŠ” λΉ„μ¦ˆλ‹ˆμŠ€ λ‘œμ§μ—μ„œ 검증 둜직 μ½”λ“œλ₯Ό μ œκ±°ν•  수 있고, 클래슀, λ©”μ„œλ“œ μ–΄λ””λ“  μ„€μ •ν•  수 μžˆμŠ΅λ‹ˆλ‹€. λ˜ν•œ μ–΄λ…Έν…Œμ΄μ…˜μ„ μ΄μš©ν•΄ 클래슀 ν˜Ήμ€ λ©”μ„œλ“œμ— ν•΄λ‹Ή 둜직이 λ“€μ–΄κ°„λ‹€λŠ” 것을 μ§κ΄€μ μœΌλ‘œ λͺ…μ‹œν•  수 μžˆμŠ΅λ‹ˆλ‹€.
ν•˜μ§€λ§Œ 생각해본 λ‹¨μ μœΌλ‘œλŠ” 계측간 흐름 λ°©ν–₯κ³Ό 관심사 뢄리 μ°¨μ›μ˜ λ¬Έμ œκ°€ μžˆμ—ˆμŠ΅λ‹ˆλ‹€.
κ·Έλ¦Όκ³Ό 같이 계측간 흐름 λ°©ν–₯μ—μ„œ λ ˆν¬μ§€ν† λ¦¬ 계측을 μ„œλΉ„μŠ€ κ³„μΈ΅μ˜ μ•žμ—μ„œ ν•œλ²ˆ, λΉ„μ¦ˆλ‹ˆμŠ€ λ‘œμ§μ—μ„œ 또 ν•œλ²ˆ μ˜μ‘΄ν•˜λŠ” 뢀뢄은 μ˜μ‘΄μ„± νλ¦„μ˜ κ΄€μ μ—μ„œ λ³΄μ•˜μ„ λ•Œ μ–½ν˜€μžˆλ‹€κ³  νŒλ‹¨ν•˜μ˜€μŠ΅λ‹ˆλ‹€. λ˜ν•œ AOP λŠ” κ³΅ν†΅μ μœΌλ‘œ λΆ€κ°€λ˜λŠ” κΈ°λŠ₯을 λ¬Άκ³  λΆ„λ¦¬ν•˜μ—¬ λΉ„μ¦ˆλ‹ˆμŠ€ λ‘œμ§μ— 집쀑 ν•  수 μžˆλ„λ‘ λͺ¨λ“ˆν™”ν•˜λŠ” λ°©μ‹μž…λ‹ˆλ‹€. μ΄λŸ¬ν•œ κ΄€μ μ—μ„œ β€˜κ²€μ¦ 둜직이 우리의 λΉ„μ¦ˆλ‹ˆμŠ€ 둜직과 λΆ„λ¦¬ν•΄μ•Όν•˜λŠ” 관심사인가?’ 라고 생각해 λ³΄μ•˜μ„ λ•Œ, @Transactional 의 관심사인 νŠΈλžœμž­μ…˜ νšλ“κ³ΌλŠ” 달리, 검증 λ‘œμ§λ„ λΉ„μ¦ˆλ‹ˆμŠ€ 둜직 쀑 일뢀라고 μƒκ°ν•˜μ˜€μŠ΅λ‹ˆλ‹€. 이에 따라 AOP 방식은 단점이 λ‹€μ†Œ λͺ…ν™•ν•˜μ—¬ 일단 보λ₯˜ν•˜μ˜€μŠ΅λ‹ˆλ‹€.

인터셉터 ν™œμš©

κΈ°μ‘΄ μΈν„°μ…‰ν„°μ—μ„œ μˆ˜ν–‰ν•˜λ˜ ν† ν°μ˜ μœ νš¨μ„± 검증을 ν™•μž₯ν•˜μ—¬, 멀버가 νŒ€μ— μ†ν•˜λŠ”μ§€μ— λŒ€ν•œ κ²€μ¦κΉŒμ§€ λͺ¨λ‘ μˆ˜ν–‰ν•˜λŠ” κ²ƒμž…λ‹ˆλ‹€.
μœ„μ˜ AOP λ°©μ‹μ²˜λŸΌ μ„ μˆ˜ ν•™μŠ΅μ΄ ν•„μš”ν•˜μ§€ μ•Šμ•„ κ°œλ°œμ„ λΉ λ₯΄κ²Œ ν•  수 있고, AOP 방식과 λ§ˆμ°¬κ°€μ§€λ‘œ λΉ„μ¦ˆλ‹ˆμŠ€ λ‘œμ§μ—μ„œ 검증 둜직 μ½”λ“œλ₯Ό μ œκ±°ν•  수 μžˆμŠ΅λ‹ˆλ‹€.
ν•˜μ§€λ§Œ AOP 방식보닀 단점이 λ”μš± λͺ…ν™•ν•˜μ˜€μŠ΅λ‹ˆλ‹€. team code λŠ” url path 둜 확인할 수 μžˆκΈ°λ•Œλ¬Έμ— url path λ¬Έμžμ—΄μ— λŒ€ν•œ 뢀가적인 처리 과정이 λ°œμƒν•©λ‹ˆλ‹€. λ˜ν•œ home ν™”λ©΄μ²˜λŸΌ 검증 둜직이 μˆ˜ν–‰λ˜μ§€ μ•ŠλŠ” API κ°€ μžˆλŠ”λ°, ν•΄λ‹Ή μš”μ²­ APIκ°€ 검증 λ‘œμ§μ„ μˆ˜ν–‰ν•΄μ•Ό ν•˜λŠ”μ§€ HTTP METHOD 와 url path 둜 확인해야 ν•˜λŠ” λ²ˆκ±°λ‘œμ›€μ΄ μžˆμŠ΅λ‹ˆλ‹€. 이와 ν•¨κ»˜, λΉ„μ¦ˆλ‹ˆμŠ€ λ‘œμ§μ—μ„œλŠ” 검증 둜직이 μˆ˜ν–‰λ˜μ—ˆλ‹€λŠ” 것을 μ§κ΄€μ μœΌλ‘œ λ‚˜νƒ€λ‚Ό 수 μ—†μ—ˆμŠ΅λ‹ˆλ‹€. 이에 따라 인터셉터 방식도 보λ₯˜ν•˜μ˜€μŠ΅λ‹ˆλ‹€.

μƒˆλ‘œμš΄ μ„œλΉ„μŠ€ 계측

κ²€μ¦λ§Œ μ „λ¬ΈμœΌλ‘œ ν•˜λŠ” μƒˆλ‘œμš΄ μ„œλΉ„μŠ€ 계측을 λ§Œλ“€μ–΄ 검증 둜직 은 검증 μ„œλΉ„μŠ€ κ³„μΈ΅μ—μ„œ, μ΄μ™Έμ˜ λΉ„μ¦ˆλ‹ˆμŠ€ λ‘œμ§μ€ 각각의 μ„œλΉ„μŠ€ κ³„μΈ΅μ—μ„œ μˆ˜ν–‰ν•˜λŠ” κ²ƒμž…λ‹ˆλ‹€. 이 방식은 JdbcTemplate 을 κ΅¬κ²½ν•˜λ‹€κ°€ μœ μ‹¬νžˆ λ³Έ ν…œν”Œλ¦Ώ-콜백 νŒ¨ν„΄ 방식을 μ°¨μš©ν•˜μ˜€μŠ΅λ‹ˆλ‹€.
public class JdbcTemplate extends JdbcAccessor implements JdbcOperations { // ... @Nullable private <T> T execute(StatementCallback<T> action, boolean closeResources) throws DataAccessException { Assert.notNull(action, "Callback object must not be null"); Connection con = DataSourceUtils.getConnection(obtainDataSource()); Statement stmt = null; try { stmt = con.createStatement(); applyStatementSettings(stmt); T result = action.doInStatement(stmt); handleWarnings(stmt); return result; } catch (SQLException ex) { // Release Connection early, to avoid potential connection pool deadlock // in the case when the exception translator hasn't been initialized yet. String sql = getSql(action); JdbcUtils.closeStatement(stmt); stmt = null; DataSourceUtils.releaseConnection(con, getDataSource()); con = null; throw translateException("StatementCallback", sql, ex); } finally { if (closeResources) { JdbcUtils.closeStatement(stmt); DataSourceUtils.releaseConnection(con, getDataSource()); } } } }
Java
볡사
JdbcTemplate 의 λ©”μ„œλ“œ 쀑 μœ„μ˜ execute λ©”μ„œλ“œλŠ” JdbcTemplate 의 λ‹€λ₯Έ λ©”μ„œλ“œμΈ update, execute, query 등이 호좜될 λ•Œ 항상 ν˜ΈμΆœλ˜λŠ” λ©”μ„œλ“œμž…λ‹ˆλ‹€.
각 update, execute, query λ©”μ„œλ“œλŠ” StatementCallback<T> 에 λ§žλŠ” Functional Interface κ΅¬ν˜„ 클래슀λ₯Ό λ§Œλ“€κ³ , μœ„μ˜ execute λ©”μ„œλ“œλŠ” statement λ₯Ό λ§Œλ“œλŠ” 곡톡 λ‘œμ§μ„ μˆ˜ν–‰ ν›„ action.doInStatement(stmt); 을 톡해 인자둜 받은 λ©”μ„œλ“œλ₯Ό μˆ˜ν–‰ν•©λ‹ˆλ‹€.
μœ„μ˜ execute λ©”μ„œλ“œμ™€ μœ μ‚¬ν•˜κ²Œ, 각각의 μ„œλΉ„μŠ€λŠ” λžŒλ‹€μ‹μ„ ν™œμš©ν•˜μ—¬ λΉ„μ¦ˆλ‹ˆμŠ€ λ‘œμ§μ„ λ§Œλ“€κ³ , 검증 μ„œλΉ„μŠ€(Authorization Service)의 λ©”μ„œλ“œλ₯Ό ν˜ΈμΆœν•©λ‹ˆλ‹€. 그리고 검증 μ„œλΉ„μŠ€μ—μ„œλŠ” 곡톡 둜직만 μˆ˜ν–‰ ν›„, 인자둜 받은 λ©”μ„œλ“œλ₯Ό μˆ˜ν–‰ν•˜λŠ” ꡬ쑰λ₯Ό κ΅¬μƒν•˜μ˜€μŠ΅λ‹ˆλ‹€.
β€’
JdbcTemplate.execute() == AuthorizationService.authorize()
β€’
execute() λ‚΄λΆ€μ˜ createStatement() == 검증 둜직
β€’
action.doInStatement(stmt) == 각각의 λΉ„μ¦ˆλ‹ˆμŠ€ 둜직
으둜 생각할 수 μžˆμŠ΅λ‹ˆλ‹€.
μ΄λŸ¬ν•œ ꡬ쑰λ₯Ό 가지면 검증 λ‘œμ§μ„ μˆ˜ν–‰ν•œλ‹€λŠ” 것을 λͺ…μ‹œν•  수 μžˆμœΌλ©΄μ„œ, 검증 λ‘œμ§μ€ 숨길 수 μžˆμŠ΅λ‹ˆλ‹€. λ˜ν•œ 계측간 흐름 λ°©ν–₯도 μ„œλΉ„μŠ€ 계측 λ‹¨μ—μ„œ λ ˆν¬μ§€ν† λ¦¬ 계측 λ‹¨μœΌλ‘œ 흐λ₯΄λŠ” λ°©ν–₯으둜 ꡬ성할 수 있고, 검증 μ„œλΉ„μŠ€μ—μ„œλŠ” 각각의 μ„œλΉ„μŠ€λ₯Ό μ°Έμ‘°ν•˜μ§€ μ•Šμ•„ μ–‘λ°©ν–₯ μ°Έμ‘° λ˜ν•œ λ°œμƒν•˜μ§€ μ•ŠμŠ΅λ‹ˆλ‹€. 그리고 검증 λ‘œμ§μ„ ν•˜λ‚˜μ˜ ν΄λž˜μŠ€μ—λ§Œ μž‘μ„±ν•˜μ—¬ 응집도λ₯Ό 높일 μˆ˜λ„ μžˆμŠ΅λ‹ˆλ‹€. 이에 따라 μ„Έ 번째 방식인 ν…œν”Œλ¦Ώ-콜백 νŒ¨ν„΄μ„ μ μš©ν•œ μƒˆλ‘œμš΄ μ„œλΉ„μŠ€ 계측을 λ§Œλ“€κΈ°λ‘œ κ²°μ •ν•˜μ˜€μŠ΅λ‹ˆλ‹€.

μ„œλΉ„μŠ€ 계측 μž¬μ„€κ³„ 적용

μ„œλΉ„μŠ€ 계측에 μ μš©ν•  κ΅¬μ‘°λŠ” μ•žμ„œ μ •λ¦¬ν•œ 그림을 ν† λŒ€λ‘œ μ μš©ν•˜λ„λ‘ ν•˜κ² μŠ΅λ‹ˆλ‹€.

Authorization Service

@Transactional(readOnly=true) public class AuthorizationService { private final TeamRepository teamRepository; private final MemberRepository memberRepository; private final TeamMemberRepository teamMemberRepository; public <R> R withTeamMemberValidation(Supplier<R> supplier, String teamCode, Long memberId) { Member member = memberRepository.findById(memberId) .orElseThrow(() -> MemberNotFoundException.of(CustomErrorCode.MEMBER_NOT_FOUND_ERROR, memberId)); Team team = teamRepository.findByCode(teamCode) .orElseThrow(() -> TeamNotFoundException.ofTeam(CustomErrorCode.TEAM_NOT_FOUND_ERROR, teamCode)); validateMemberInTeam(team, member); return supplier.get(); }
Java
볡사
검증 μ„œλΉ„μŠ€μ—μ„œλŠ” νŒ€, 멀버, νŒ€λ©€λ²„ λ ˆν¬μ§€ν† λ¦¬λ§Œ μ£Όμž…λ°›κ³ , 검증과 κ΄€λ ¨λœ 둜직만 μˆ˜ν–‰ν•©λ‹ˆλ‹€. 검증 λ‘œμ§μ„ μˆ˜ν–‰ν•œ ν›„μ—λŠ” 인자둜 λ“€μ–΄μ˜¨ λ©”μ„œλ“œλ₯Ό μ‹€ν–‰λ§Œ ν•˜κ³ , μ΄μ™Έμ˜ λΉ„μ¦ˆλ‹ˆμŠ€ λ‘œμ§μ—λŠ” κ°„μ„­ν•˜μ§€ μ•Šλ„λ‘ ν•˜μ˜€μŠ΅λ‹ˆλ‹€. ν•¨μˆ˜ν˜• μΈν„°νŽ˜μ΄μŠ€λ‘œλŠ” 인자 없이 리턴 κ°’λ§Œ μžˆλŠ” Supplier λ₯Ό μ‚¬μš©ν•˜μ˜€κ³ , μ—¬λŸ¬ μ„œλΉ„μŠ€ κ³„μΈ΅μ—μ„œ μ‚¬μš©ν•  수 μžˆλ„λ‘ μ œλ„€λ¦­μ„ ν™œμš©ν•˜μ˜€μŠ΅λ‹ˆλ‹€. ν•¨μˆ˜ν˜• μΈν„°νŽ˜μ΄μŠ€λ‘œ μ‚¬μš©λ˜λŠ” λ‹€λ₯Έ μΈν„°νŽ˜μ΄μŠ€μ— λŒ€ν•œ λ‚΄μš©μ€ Functional Interfaceλž€ 을 μ°Έκ³ ν•΄μ£Όμ„Έμš”!

Poll Service

@Transactional(readOnly=true) public List<PollResponse> findPolls(String teamCode, Long memberId) { return authorizationService.withTeamMemberValidation( () -> pollRepository.findAllByTeamCode(teamCode) .stream() .map(poll -> PollResponse.from(memberId, poll)) .sorted() .collect(Collectors.toList()), teamCode, memberId ); }
Java
볡사
검증 λ‘œμ§μ„ μ„œλΉ„μŠ€μ—μ„œ λΆ„λ¦¬ν•¨μœΌλ‘œμ¨ 각각의 도메인 μ„œλΉ„μŠ€ κ³„μΈ΅μ—μ„œλŠ” λΉ„μ¦ˆλ‹ˆμŠ€ λ‘œμ§μ— λ”μš± 집쀑할 수 있게 λ˜μ—ˆμŠ΅λ‹ˆλ‹€. μ„œλΉ„μŠ€ κ³„μΈ΅μ—μ„œλŠ” 인자둜 λ“€μ–΄μ˜¨ teamCode 와 memberId 에 λŒ€ν•œ 검증을 검증 μ„œλΉ„μŠ€ 계측에 맑기기 μœ„ν•΄ λ©”μ„œλ“œμ˜ νŒŒλΌλ―Έν„°λ‘œ μ „λ‹¬ν•˜κ³ , λΉ„μ¦ˆλ‹ˆμŠ€ λ‘œμ§μ€ λžŒλ‹€μ‹μœΌλ‘œ κ΅¬μ„±ν•˜μ˜€μŠ΅λ‹ˆλ‹€.

κ²°κ³Ό

검증 λ‘œμ§μ„ μœ„μ˜ ν…œν”Œλ¦Ώ-콜백 νŒ¨ν„΄ λ°©μ‹μœΌλ‘œ λ³€κ²½ν•˜λ©΄μ„œ λ‹€μŒκ³Ό 같은 이점을 κ°€μ§ˆ 수 μžˆμ—ˆμŠ΅λ‹ˆλ‹€.
1.
가독성 μ¦λŒ€
검증을 μœ„ν•΄ orElseThrow() κΉŒμ§€ 두 쀄씩 μ΄μ–΄μ§€λ˜ team, member 쑰회 λ‘œμ§μ„ μ œκ±°ν•˜κ³ , 검증 μ„œλΉ„μŠ€μ˜ withTeamMemberValidation μ΄λΌλŠ” λ©”μ„œλ“œλͺ…μœΌλ‘œ 검증 λ‘œμ§μ„ ν‘œν˜„ν•  수 μžˆμ–΄ 가독성 μΈ‘λ©΄μ—μ„œ κ°œμ„ μ΄ λ˜μ—ˆμŠ΅λ‹ˆλ‹€.
2.
λΉ„μ¦ˆλ‹ˆμŠ€ λ‘œμ§μ— 집쀑
검증 λ‘œμ§μ€ 검증 μ„œλΉ„μŠ€μ— μΌμž„ν•˜κ³ , μ„œλΉ„μŠ€ 계측 λ©”μ„œλ“œμ—μ„œλŠ” 콜백 λ°©μ‹μ˜ λΉ„μ¦ˆλ‹ˆμŠ€ 둜직만 μžˆμ–΄ λΉ„μ¦ˆλ‹ˆμŠ€ λ‘œμ§μ—λ§Œ 집쀑할 수 μžˆμ—ˆμŠ΅λ‹ˆλ‹€.
3.
횑적으둜 λͺ¨λ“  도메인 μ„œλΉ„μŠ€ κ³„μΈ΅μ—μ„œ λ°œμƒν•˜λŠ” 검증 λ‘œμ§μ„ ν•œ 곳으둜 집쀑
νŒŒλΌλ―Έν„° λ³€κ²½ λ“± 검증 λ‘œμ§μ— 변경이 λ°œμƒν•˜μ—¬λ„ 검증 μ„œλΉ„μŠ€ λ‚΄λΆ€μ˜ 검증 λ‘œμ§μ—μ„œλ§Œ 변경이 λ°œμƒν•˜κΈ°λ•Œλ¬Έμ— μœ μ§€λ³΄μˆ˜ λ“± 관리 μΈ‘λ©΄μ—μ„œλ„ 이점이 μžˆμ—ˆμŠ΅λ‹ˆλ‹€.