Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

✏️ [Spring Boot] Kotlin으로 Spring AOP 탈출하기 #13

Closed
psychology50 opened this issue Jan 9, 2025 · 2 comments
Closed

✏️ [Spring Boot] Kotlin으로 Spring AOP 탈출하기 #13

psychology50 opened this issue Jan 9, 2025 · 2 comments
Assignees
Labels
검토 중 검토가 진행중인 글 초안 검토를 받기 전 글

Comments

@psychology50
Copy link

내용 (한줄 요약)

Spring Boot를 너무 사랑한 나머지, Spring Boot를 부정하고자 하는 고군분투. Spring AOP의 좀스런 방식을 탈피하기 위한 내용.

예상 독자

Spring AOP를 탈출하고 싶은 코프링 개발자

블로그 링크

https://jaeseo0519.tistory.com/439

@psychology50 psychology50 added the 초안 검토를 받기 전 글 label Jan 9, 2025
@youngsu5582
Copy link
Contributor

youngsu5582 commented Jan 10, 2025

재서님 글 잘봤습니다~
Trailing Lambda 에 대한 사용 방법만 알고 개념에 대해서는 자세히 몰랐는데 배우고 갔습니당.
이번에도 블로깅 내용 부분보다 코드적으로 궁금한게 있어서 질문합니다.

lateinit 을 통해서

  private fun savePayment(payment: Payment) = Tx.execute { 
        return paymentRepository.save(payment)  
    };

와 같이 코드를 분리가 가능한건 확실히 느꼈는데
결국은 코드 단에 침범이 되는게 아닌가? 라는 생각은 드네요.

AOP 의 관점의 차이일 거 같으나
저도 Spring 의 매우 불편한 SPEL 과 불안전한 타입 검사에는 동의하나,
구현부에서 부가적인 관점을 모르게 하는것은 매우 매력적이라고 생각합니다.

공통 관심사를 분리하기 위해
AOP 가 나오고 -> 이를 매번 반복적으로 사용하는걸 피하기 위해 메타적인 어노테이션 ( @Transactional ) 을 사용
한다로 이해했는데

매 구현체마다

fun execute() = logging { 
       this.executeInternal()
    }
Tx.execute { 
        return paymentRepository.save(payment)  
    };

execute,logging 을 아는게 더 어려움을 줄 수도 있지 않을까.. 생각은 드네용.
( 추가로, logging - cache - transaction 과 같은 요소들이 한 로직에 다 적용이 되어야 한다면, 순서 제어나 제어는 어떻게 하나요? )


사소한 부분인데

@Test
fun `인가된 사용자는 정상적으로 진행된다`() {
    // given
    val (user, userPrincipal) = createValidFixture()
    
    // whenb
    val result = authorize(MockManager::class, MockManager::execute.name, 1L) {
        "some result"
    }

    // then
    assertEquals("some result", result)
}

해당 부분에서 createValidFixture 를 통해 값을 �만드는데 왜 Mocking 을 넣지? 라는 궁금함은 생길수도 있을거 같아용.

성능적으로도 좋아진 부분은 되게 인상 깊네요.
( 무거운 스프링 부분을 덜어낸만큼 늘어난게 보이네요. ㅋㄱㅋㄱㄲ )
다시 한번, 잘 보고 갑니다! 🫡

@github-actions github-actions bot added the 검토 중 검토가 진행중인 글 label Jan 10, 2025
@psychology50
Copy link
Author

psychology50 commented Jan 10, 2025

일단, kotlin에 매우 익숙치 않은 상태로 억지로 끼워 맞춘 거다 보니, 영양가 있는 답변을 드릴 수 있을 지는 잘 모르겠습니다. ㅋㅋㅋㅋ


1️⃣ 결국은 코드 단에 침범이 되는게 아닌가? 라는 생각은 드네요.

이 의견에 저도 동의합니다!
우선, 개인적으로 지연 초기화라는 전략을 택한 것부터 마음에 안 들기는 해요. ㅋㅋ

다만, 말씀하신 부분에서 구현부에서 부가적인 관점을 모르게 하는 것매우 불편한 SPEL 과 불안전한 타입 검사에 대한 저의 관점은 이렇습니다.

🟡 첫째, 구현부에서 부가적인 관점을 모르게 하는 것은 동일하게 적용할 수 있습니다.

@Transactional을 선언하는 것과, = Tx.execute { ... } 둘 모두, "어떻게 동작하는 지는 모르겠지만, 어쨌든 Tx가 적용된다는 것" 정도만 알면 되니까요.
아마 execute()가 대체 뭐 하는 역할인 지를 해석해야 한다는 번거로움이 발생하실 거라 우려하신 것 같으나, 전 오히려 함수명을 보다 직관적으로 수정함으로써, @Transactional의 격리성 모드를 상수로 제어하는 방법보다 효과적으로 만들 수 있다고 생각했습니다.
이는 카카오 기술 블로그에도 나와 있는 내용인데, Tx의 함수명을 다음 식으로 작명하는 방법입니다.

Tx.writeble {} // @Transactional
Tx.readable {} // @Transactional(readOnly=true)
Tx.withSecureDB{} // @Transactional(transactionManager = "SecureDBTransactionManager")

🟡 둘째, 둘 중 우선 순위를 결정한다면, 보다 critical한 이슈를 최소화할 수 있는 방향으로 개선하는 게 더 낫다고 판단했습니다.

여기서 제가 생각한 ciritical한 지점은 runtime 예외 가능성 측면이었습니다.

프로덕트 코드에 무분별한 관심사가 침범하는 것은 지양해야 하는 것이 지당한 판단입니다.
저 또한 언제나 "성능 보다는 유지/보수 관점을 고려해야 한다. 성능은 나중에 언제든 개선할 수 있다"고 주장하는 편에 속합니다.
다만, 이를 위해 런타임에 발생할 이슈를 기술 부채로 남겨둔다면, 유지/보수를 고민도 하기 전에 사용자에게 불편한 경험을 주는 비지니스적 손실을 입게 될 가능성 또한 배제할 수 없습니다.
또한 이런 런타임 예외가 쉽게 발생할 수 있는 환경에서는 필연적으로 더 많은 고민과 시간을 할애하게 되고, 이는 제품 생산성에 큰 영향을 주게 됩니다.

만약, 구현부에서 부가적인 관점을 모르게 하는 것을 포기하게 됨으로써, 도메인 규칙의 순수성을 침해하는 행위라면 포스팅을 하지도 않았을 겁니다.
하지만 제 개인적인 의견으론, 이 정도면 여전히 구현부에서 부가적인 관점을 모르게 하면서, 불안전한 타입 검사도 제어할 수 있는 충분한 대안이 될 수 있지 않을까라는 생각이 있었습니다.

물론 요건 관점 차이라서, 영수님의 의견도 다양한 관점에서 볼 수 있어서 좋았어요. :)


2️⃣ execute, logging 을 아는게 더 어려움을 줄 수도 있지 않을까..

제가 현직자 분들께 정말 많이 들었던 부분인데, "실무에선 도구를 직접 만드는 걸 선호하지 않는다" 였습니다.
(그래서 배치에서 커스텀 ItemReader 같은 도구 만들었다고 하면, 별로 안 좋아하시더라구요 🥲)

그래서 저 또한 이 방법이 최고라고 생각한다기 보다는, 더 개선할 수 있는 점, 혹은 적어도 나 혼자 개발할 때 만이라도 더 나은 코드를 작성할 수 있는 관점을 기른다는 목적으로 작성했습니다.

말씀하신 것처럼, 제가 보기엔 더 좋아보여도, 팀원이 보기엔 결국 새로운 도구에 다시 익숙해져야 한다는 부담으로 작용할 수도 있고, 남들에겐 제 생각만큼 가치있어 보인다고 느껴지지 않을 수도 있기 때문이니까요.
그 점은 충분히 이해하고 있고, 이런 건 팀에서 잘 풀어나가야 할 숙제라고 생각합니다.

이런 방법이 더 나은 제품 코드를 만드는 데 도움이 된다고 판단하면 사용할 거고, 본인들의 문화나 현실적인 이유로 비용만 더 커질 우려가 있다면 사용하지 않을 테니까요.


3️⃣ logging - cache - transaction 과 같은 요소들이 한 로직에 다 적용이 되어야 한다면, 순서 제어나 제어는 어떻게?

사실 이건 아직 연구 중입니다 ㅋㅋㅋ....
제가 kotlin의 init과 constructor 생성자 동작 방식이나, 타입 결정 방법 조차 이번에 병행하면서 공부한 터라,
현재 식견으로는 감히 함부로 대안을 제시하지 못 하겠어요. 🥲

지금 코드도 너무 java스럽게 작성한 부분도 많아서, 공부를 더 하고, 재밌는 문법들을 많이 알게 되면 다시 연구해볼 주제로 보류해두었습니다.

저도 블로그 쓰면서 의문이 들었던 부분인데, 항상 질문이 예리하셔서 놀랍네요. ㅋㅋㅋㅋㅋ

  • 추가로 궁금했던 건, @Transactional@Cacheable 같은 여러 어노테이션이 선언되어 있을 때, 어떤 AOP가 먼저 동작하느냐? Spring이 이를 어떻게 순서를 결정하고 제어하느냐? 라는 고민에 해답을 구해지 못해서, 함부로 방법을 제시하지 못한 거기도 합니다. 혹시 영수님은 이에 대해 알고 계신가요??

4️⃣ createValidFixture 를 통해 값을 �만드는데 왜 Mocking 을 넣지?

실수입니다. 허허 ㅋㅋㅋㅋㅋㅋㅋ

테스트 케이스가 너무 많아서, 나중에는 귀찮아서 복붙하다보니 필요없는 given 절이 들어갔네요;;;

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
검토 중 검토가 진행중인 글 초안 검토를 받기 전 글
Projects
None yet
Development

No branches or pull requests

4 participants