Replies: 1 comment
-
문제 상황: woowacourse-teams/2024-ody#534 제리: 일단 저는 1번이었고요. 그때 당시에 콜리는 2번이긴 했었어. 맞나요? 콜리: 그리고 이게 토론이어서 그런 걸 수도 있는데 1번이랑 2번을 쳤을 때 사이드 이펙트가 너무 많이 나오더라고요. 그래서 그거 위주로 좀 준비를 해오긴 했다, 근데 그게 제리가 구현한 코드에 대한 부정은 아니다. 리비: 내가 제일 싫어하는 게 딱 이런 스타일인데 이 어노테이션 자체가 싫다는 게 아니라 어디는 되고 어디는 안 되는 그런 맥락에 들어가면 그때부터 다른 개발자가 이해하기 힘들어진다고 생각해. 왜 여기에는 이게 또 추가적으로 붙어야 되지라는 거를 이해시키기가 힘들 거라고 생각해서 나 같으면 그냥 시원하게 생 쿼리를 쓸 수밖에. 콜리: 그러면 왜 거기에만 네이티브 커리를 쓰는지에 대한 맥락도 팀원들이 공유해야 되지. 리비: 그렇지. 우선 내가 생각할 때는 삭제된 데이터를 조회한다는 건 되게 특수한 로직이라고 생각을 하거든. 그러면 그 특수한 로직을 위해서 되게 특수한 처리가 들어간다고 해도 난 상관없어. 그렇기 때문에 완전 엣지 케이스를 일반적으로 처리하지 못해도 난 충분히 합리적이라고 생각해. 에버: 나는 필터 어노테이션을 쓴다. 쿼리 메서드로 짰을 때 리소스가 너무 많이 들 것 같아. 그리고 레포지토리 단에서 처리하는 것보다 서비스 단에서 하는 게 더 적절하지 않나라는 생각했어. 콜리: 나는 그때 제이슨이 주신 의견이 좋다고 생각한다. 근데 그게 이제 스포가 될 수 있으니.. 제리: 그거는 기억에서 지워. 그건 지금 언급하면 안 돼. 언급하면 토론이 안 되잖아. 콜리: 그래서 우선은 두 개 중에 찾자고 하면 나는 그래도 아직 2번. 왜냐하면 1번이 너무 밴더 종속적인 느낌도 있고 그래서 이후에 좀 더 얘기를 하긴 하겠지만 이 문제를 이해하기까지가 좀 오래 걸린다. 근데 결론적으로는 제이슨이 주신 답이 맞다고 생각해. 제리: 나는 삭제된 데이터가 클라이언트한테 보여진다는 게 치명적이라고 생각하거든. 그러면 @SQLRestriction을 쓰지 않는 이상 모든 메서드에서 삭제된 데이터가 원하지 않는 상황에서 조회되는지 검증을 꼭 해야 된다고 생각해. 이게 코드 리뷰 상에서 다 잡힐 수 있을까에 대해 부정적이고, 그러면 테스트 코드를 추가해줘야 되는데 관리해야 하는 포인트가 너무 많아져서 개발 생산성이 떨어진다고 생각해. 그래서 임의로 삭제된 데이터는 조회되지 않는다는 전제를 깔고 조회를 하고 싶을 때에만 AOP를 이용해 필터를 비활성화한다는 어노테이션을 다는 게 개발적으로는 생산성이 높다고 생각했어. 사실 근데 이렇게 하면 findById에 대한 치명적인 단점도 있고 어쨌든 문제가 있으니까, 벤더에 종속되어 쿼리를 쓰는 거라 싫지만 차라리 차안을 찾는다고 하면 네이티브 쿼리를 쏠 것 같아. 콜리: 근데 우선은 제리가 Filter랑 FilterDef를 도입한 의도는 내가 찾아보니까 정확히 알겠더라고요. 첫 번째로, 유연한 soft delete가 가능하고 필터 온오프가 가능하다. 두 번째로, 파라미터 설정이 가능해서 다이나믹한 쿼리 작성이 가능하다. 세 번째로는 크로스 커팅이 가능하다. soft delete를 쿼리 메서드마다 적용을 하는 것보다 공통적인 부분을 하나의 AOP로 들어내는 과정이니까. 근데 좀 걸렸던 거는 위에 의존성을 보니까 hibernate로 시작하더라고. 예전에 구구한테 코드 리뷰 받을 때 그것 때문에 크게 혼난 적 있었는데, jakarta까지는 괜찮은데 hibernate로 시작하는 의존성은 도메인이나 엔티티 상에는 없는 게 좋다고 얘기해주셨어서 이 부분이 조금 걸렸어. 리비: hibernate가 있으면 안 된대? 콜리: 그때 어떤 걸 썼었냐면 프록시 equals 비교가 안 되는 것 때문에 hibernate.getClass 라는 식의 메서드를 썼었는데 그때 구구가 hibernate로 시작하는 건 다 빼세요 라고 이야기했어. 에버: 그럼 soft delete 구현하는 것도 어노테이션 안 썼어? 콜리: 썼지. 근데 이제 Filter랑 FilterDef는 그 중에서도 더 hibernate에 의존하게 되는 방식이고, 그럼 hibernate에 의존한다고 해서 그게 깔끔하게 처리되는가 하면 또 SimpleRepository의 구현에 따라 우리가 취해야 하는 전략이 바뀌게 되잖아. 그러니까 의존을 해서 해결할 문제도 해결을 못하고 있다는 단점이 느껴진다는 거지. 리비: 생쿼리보다 더 좋은 해결책이 있어. 나였으면 생쿼리 쓰자고 일단 무조건 팀원들 설득했을 거고. 콜리: 그리고 그 생쿼리에 대한 반론을 한 번 해볼게. 왜냐면 내가 주장했었거든. 제리: 심지어 내가 네이티브 쿼리를 썼다가 지웠는데 네이티브 쿼리를 쓰는 순간 원래의 쿼리는 더 이상 쓸 수 없어. 우리는 두 가지 벤더를 쓰고 있는 상황이라 둘 중에 한 군데는 안 될 수 있어. 콜리: 근데 좀 더 찾아보니까 문제가 있더라고요. 지연 로딩을 하게 되면 이 객체를 건드렸을 때 쿼리가 나가게 되잖아. 그 과정에서 문제가 생기게 된다는 거지. 그러니까 내가 이 쿼리를 통해 가지고 올 때는 괜찮은데 그 엔티티를 지연 로딩을 통해 사용할 때는 연관관계상에서 네이티브 쿼리가 문제가 될 수 있다는 식의 아티클이 있거든. 리비: 네이티브 쿼리에서 프로젝션을 빡빡하게 해놓으면 안 돼? 콜리: 다 결합해서 fetch join하면? 그러면 되지. 근데 그거는 eager loading에서도 가능하니까. 리비: 어쨌든 이거는 네이티브 쿼리에서 조인 치면 엔티티 만들어주느 건 JPA가 하니까 그런 해결책은 우선 있을 것 같다는 생각. 그래서 만약에 연관 관계가 얼마나 깊은지 모르겠지만 그냥 조인 한, 두 번이다 그러면 바로.. 그리고 첫 번째로 얘기했던 벤더 종속적은 우선 h2랑 MySQL은 어느 정도 비슷하게 생겼거든. 그래서 호환 안 되지 않을걸? 제리: 리비한테는 필터에서 엔티티 매니저 접근하고 이런 게 너무 하위 구현이잖아. 그런 것처럼 SQL문 자체가 박히는 게 나한테는 너무 하위 구현으로 느껴졌어. 리비: 눈을 감자. 눈 감고 한 번 쓰면 행복해질 수 있겠는데. 제리: 그치. 이런 상황이 적다는 전제에서는 그렇지. 근데 그걸 개발할 당시에 처음에 SQLRestriction을 쓰면서 삭제된 데이터를 조회할 일이 뭐가 있겠어, 나중에 조회해야 될 상황이 생기면 그때 고민하자고 하자마자 다음 날 그럴 일이 생겼어. 그래서 이게 한 개로 안 끝날 수도 있겠구나 생각이 들어서 이 대안은 너무 확장성이 떨어진다고 생각했어. 그래서 AOP까지 간 거야. 그렇지만 코드가 정말 별로다. 리비: AOP 자체는 좋은데 지금은 포인트컷이 너무 방대하다는 거야. 그게 싫은 거야. 콜리: 그리고 나는 뭔가 팀원들끼리 암묵적으로 알고 있어야 하는 룰이 점점 많아지는 느낌. 커팅 관점에서는 되게 응집성 있는 코드라고 볼 수도 있지만 단순히 서비스 내에서 그거를 바라보기에는 조금 힘들 수도 있다. 근데 또 이것도 애매한 게 제리가 필터 끄는 어노테이션도 만들어 놔가지고 또 어느 정도 합리적인 절충안이었다고 생각해. 리비: 근데 이쯤 되니까 제이슨 의견이 참 궁금하다. 힌트 살짝 주면 안 돼? 맞춰보고 싶은데. 에버: 그래. 나는 정답이 있다고 하니까 사기가 떨어져. 리비: 삭제된 데이트를 넣는 DB를 따로 만든다? 그러면 복잡성이 너무 올라가니까 그런 것도 할 수 있을 것 같지만 난 안 할 것 같고. 콜리: 근데 제이슨이 이 얘기하신 거 아니었나요? 제리: 테이블 분리하는 거? 이게 답은 아니었어. 콜리: 삭제된 멤버가 필요할 때만 우리가 유니온을 통해서 그 레퍼지토리 로직을 작성해주면 되잖아. 그러면 서비스 로직에서 암묵적으로 알고 있어야 하는 필드에 대한 지식이 없고 호출하는 메서드 명에서 드러나서 서비스 코드 안에서 그게 다 읽힌다는 점이 나는 좋다고 생각했어. 두 번째로, 벤더나 하이버네이트에 의존적이지 않게 된다는 점에서 괜찮다고 생각했어. 세 번째는, 아까 얘기했던 것처럼 연관 관계에 따른 예외 케이스나 JPA 구현의 세부 사항을 고려하지 않아도 된다는 점, 기존에 있었던 JPA로 협력하는 방식을 그대로 활용할 수 있다는 점이 좋다고 생각했고, 무엇보다 가장 공감하는 게 유니크 제약 조건이 DB에 걸려 있으니까 soft delete할 때 이게 좀 애매해지거든. 근데 삭제된 멤버 테이블을 분리하게 되면 이런 유니크 제약 조건에 대한 부분도 보존할 수 있게 되죠. 리비: 봐봐. DB 제약 조건 안 좋지? 제리: 나는 진짜 이거 하면서 DB 제약 조건을 더 이상 못 걸게 된 거야. 그래서 어떤 거는 DB 단까지 관리가 되고 어떤 건 안 되고 이러면 일관성이 너무 깨지니까 다 떼고 애플리케이션에서 일괄로 관리해야겠다는 생각이 들긴 했어요. 에버: 근데 난 리비가 얘기를 했던 게 우리 팀에서도 soft delete 얘기할 때 나왔거든. 근데 완전 반대를 했던 게 걔한테 맺어져 있는 연관 관계들을 다 끊어내야 되잖아. 제리: 그래서 제이슨이 이 얘기 듣자마자 하신 답은 삭제된 데이터를 왜 조회해야 해요? 였고, 그 상황을 설명했더니 그러면 Notification 테이블에 닉네임을 필드로 박아서 삭제가 되는 거랑 상관없이 테이블 자체만으로 닉네임까지 조회가 될 수 있도록 테이블 구조를 짤 것 같다, 이게 다였어. 리비: 로그 정책 따로 둘 수 있고, 나중에 배치까지 올릴 수도 있고. 그러네. 애초에 지금은 동적으로 해야 되는 상황인 거지? 어떻게 보면 반정규화 비슷한 맥락이네. 제리: 그리고 제이슨이 soft delete를 현업에서 많이 쓰는지에 대해서도 생각을 해보라고 했는데, 많이 쓴다고 생각했는데 막상 구현해보니 고려해야 될 포인트가 많아서. 차라리 삭제된 애들은 따로 테이블 분리해서 보관하는 게 나을 수도 있겠다 생각했어요. 리비: 나 이거 무조건 좋다고 생각하는 게 그렇게 되면 AOP 적용하기도 쉽고, 테이블에 넣어놓고 테이블은 3개월마다 삭제되도록 배치 걸어놓고, 깔끔한데? 제리: 그래서 결론은 이거였습니다. |
Beta Was this translation helpful? Give feedback.
-
Spring JPA에서 삭제된 데이터를 조회하고 싶을 때(삭제는 soft delete로 구현되어 있다)
@FilterDef
,@Filter
어노테이션을 이용한다.Beta Was this translation helpful? Give feedback.
All reactions