오늘 프로젝트 진도는 많이 나가지 않았다. 어제 AOP를 활용한 권한 확인을 수행하기 위해서, 시간을 많이 소모해버렸기에, 가야할 길이 많음에도 많은 것을 하지 못한것이 아쉽다. 하지만, 여러가지로 배운 것은 있다.
일단 어제는 pointcut을 execution 문법을 사용할려했었는데, 이것 때문에, 메소드 이름을 바꾸는 것이 좀 번거롭게 느껴졌다. 그래서 @Annotation을 활용해서 AOP를 적용하기로 결심했다. 우선 어노테이션을 정의하고,
@Inherited
@Retention(AnnotationRetention.RUNTIME)
@Target(AnnotationTarget.FUNCTION)
annotation class CheckEntityCreator
.@Aspect로 정의한 클래스 안에 어드바이저를 정의하였다.
그런데, 여기서 문제가 발생했다. AOP 적용이 너무 애매하다. 일단 당연히 서비스 메소드를 실행하기 전에 권한 확인을 해서 막아야한다. 그렇기 위해서 @Before을 사용하는 것이 당연하다고 생각했다. 그런데, 권한 확인을 위해 Entity를 접근하기 위해서는 Repository가 필요하다. 메소드 파라미터는 접근이 가능하지만, 이 Advisor에 Repository를 넘길 방법을 찾기 어렵다. 여기서 여러 시도를 했었는데, 이것은 후에 얘기하도록 하자.
결국 @Before을 단념하고, @After로 고개를 돌렸다. 왜냐하면 서비스 메소드는 엔티티 정보를 담은 DTO 객체를 반환하기 때문에 이것을 활용해 볼 순 없을까에 대한 기대였다. 하지만, @After는 객체값을 반환하는 ProceedingJoinPoint를 지원하지 않는다. 이 객체는 @Around에서만 지원을 한다. 어쩔 수 없이 찜찜하지만 일단은 @Around를 사용을 하고 해당 DTO 객체를 받아서 AOP에서 권한을 확인하는 작업을 구현하였다.
여기서 또 문제가 발생했는데, 각각의 DTO는 별개의 객체라서, 통일성있게 권한을 확인 할 수 없다. 결국 DTO 상위 클래스를 정의하고, 각 DTO가 이 클래스를 상속받도록 수정하였다. 한 가지 알게 된 사실은 데이터 클래스끼리 상속이 불가능하다는 것이여서, abstract 클래스를 사용을 하였다.
abstract class CreatorResponseDTO(
open val creatorUsername: String
)
이러한 과정을 거쳐서 드디어 Advisor를 구현하였다.
@Around("@annotation(com.ricu.ricukotlin.global.infra.aop.annotation.CheckEntityCreator)")
fun checkAuthority(jp: ProceedingJoinPoint): Any
{
val ret = jp.proceed()
val creatorEntity = (ret as? CreatorResponseDTO) ?: throw IllegalArgumentException("잘못된 엔티티입니다.")
if(creatorEntity.creatorUsername != SecurityUtil.getUsername())
throw NotHaveAuthorityException(SecurityUtil.getUsername(), creatorEntity.javaClass.typeName)
return ret
}
여기서 끝인가? 아니다. Controller에서 유일하게 DTO를 반환하지 않는 메소드가 있다. 바로 delete 메소드다. delete 메소드는 DTO를 반환하지 않는게 정상이지만, 일단 통일성을 위해서 DTO를 반환해보자.
@CheckEntityCreator
@Transactional
override fun deleteGallery(id: Long): GalleryResponse {
val gallery = RepositoryUtil.getValidatedEntity(galleryRepository, id)
galleryRepository.delete(gallery)
return GalleryResponse.from(gallery)
}
그리고 중간에 엔티티를 저장하는 save도 있으니까, 커밋 되지 전에 전파속성을 늘려야하니, Advisor에 @Transactional까지 붙였다.
그리고 테스트를 한 결과, 그제서야 정상적으로 작동한다.
하지만, 뭔가가 많이 이상하다.
1. delete 메소드가 DTO를 반환한다.
2. 권한 확인은 메소드가 들어가기 전에 하는 것이 Best다.
3. Advisor에 @Transactional이 붙였는데, 이게 어디로 튈 지 모른다.
4. @Around를 붙였음에도, 실질적인 행동은 @After이다.
나는 이러한 억지가 통할지 궁금해져서 튜터님께 문의를 해보았는데, 튜터님께서도, 비슷한 의문을 제시하셨고, 다른 방식을 시도해보라고 권유를 하셨다. 하지만, 어떻게 해결할지가 애매해서 구체적인 해결방식을 제시하진 않으신게 아쉽다.
제너릭을 잘 활용하는 것이 어떤가를 얘기하셔서 제너릭에 대해 공부를 좀 하였다.
사실 제너릭은 위에서 언급했듯 repository를 넘길 방법을 연구할 때도 좀 배웠었지만, 이제는 제대로 좀 해볼려고 했다.
결과부터 말하자면 실패했다. Annotation에 넘길 수 있는 멤버변수는 자료 기본형, Enum, KClass, Array 정도이다. 여기서 내가 제너릭을 잘 사용해보려 노력은 했지만 수포로 돌아갔다.
하지만, 제너릭에 관해 흥미로운 개념을 알게 되었다. 가변성이란 개념인데, 간단하게 정리를 해보려 한다.
Type A가 Type B의 자식 클래스라고 할 때, Class<A>와 Class<B>는 기본적으로 아무런 관계가 없다.
이것을 무공변(invariance)라 부른다.
그런데, B타입이 A타입 등의 자식 클래스를 허용할 수 있다. Java에서는 <? extends B>로 표현할 수 있고,
Kotlin에서는 <out B>로 표현할 수 있다 이것을 공변(covariance)라 부른다. Kotlin에서는 이 out이란 수식어를 Variance Annotation이라 부른다고 한다.
반면에 이와 반대로, 자식 클래스의 부모 클래스를 허용할 수 있는데, Java에서는 <? super A>로 Kotlin에서는 <in A>로 표현 할 수 있다. 이것을 반공변(contravariance)라 부른다.
어쨌든 이래저래 뻘짓은 했지만, 결국 AOP를 포기하고 초기로 돌아가야할 것 같다. 다행히 아직 초기코드는 남아있으니까, 수정엔 오래 걸리진 않을 것 같다. 그럼에도, 삽질을 하며 배운게 있어서 다행이다.
참조: https://velog.io/@evergreen_tree/Kotlin-Generic-And-Variance
[Kotlin] Generic And Variance
자바에서도 Generic에 대해 얕게 이해하고 있었는데, Kotlin에서 in, out를 만나면서 제대로 알아봐야겠다는 생각이 들게 되었습니다.Data Type Generalize클래스 내부에서 사용할 Data Type을 컴파일 시 미리
velog.io
'부트캠프 일지' 카테고리의 다른 글
부트캠프 80일차 후기 (0) | 2024.03.22 |
---|---|
부트캠프 79일차 후기 (0) | 2024.03.21 |
부트캠프 77일차 후기 (0) | 2024.03.19 |
부트캠프 76일차 후기 (1) | 2024.03.18 |
부트캠프 75일차 후기 (0) | 2024.03.15 |