우아한테크코스 팀 프로젝트에서 게시글 조회수 기능을 맡아 구현했다.
조회수를 구현하면서 고민한 내용을 정리해보자.
맨 처음 떠오른 생각은 Post(게시글) 엔티티에 필드로 저장하는 것이다. 팀 프로젝트에선 JPA를 사용했으니 Post에 viewCount 필드를 추가하면 DB에도 자동으로 반영이 될 거라 생각했다.
하지만 한 가지 문제가 생겼다. DB에는 의도한대로 조회수가 잘 반영되었지만, Post 엔티티에 수정날짜를 식별하기 위해 추가한 @LastModifiedDate 필드가 조회수가 업데이트 될 때마다 변했다.
구글링을 해 보니 jpql이나 native query를 사용해서 db를 직접 찌르는 방식을 이용하면 해결할 수 있다고 했다.
하지만 영속성 컨텍스트와 db의 불일치가 일어나는 것을 최대한 피하고 싶었기에 viewCount를 새로운 객체로 분리하는 방법을 시도해보았다.
ViewCount 객체를 @Embeddable 타입으로 만들고, post에서 @Embedded로 ViewCount객체를 필드로 가지게 했다. 이렇게 변경하니 조회수가 증가할때마다 @LastModifiedDate 필드가 바뀌는 사이드이펙트는 막을 수 있었다.
ViewCount를 객체로 바꾸고 나니 동시성 문제라는 새로운 고민거리가 생겼다. Post엔티티를 가져오고 조회수를 변경하면 db에는 영속성 컨텍스트가 끝나야 반영이 된다. 하지만 수 많은 사용자들이 사용한다면? 모든 사용자마다 각각의 영속성 컨텍스트가 열리게 될 것이다. 영속성 컨텍스트의 생명주기가 길어진다면 충분히 동시성 문제가 발생할 수 있다는 생각이 들었다. 그래서 조회수를 update하는 쿼리에 lock을 걸어 바로 db에 반영될 수 있도록 변경할 필요가 있었다.
직접 쿼리를 작성하는 일이 불가피해지자 굳이 viewCount를 객체로 가질 이유가 없어졌다. 그래서 다시 Post엔티티에 primitive 타입으로 viewCount를 가지게 변경했다.
어뷰징 처리에 대한 피드백이 들어왔다. 어뷰징을 어떻게 막을지 고민해보자. 구글링을 해보고 고민해보니 대략 5가지 방법으로 추릴 수 있었다.
사용자 정보와 조회한 게시글 목록, 조회한 시간을 db에 저장한다.
쿠키에 조회한 게시글 목록, 조회한 시간을 담는다.
사용자 ip와 게시글 목록, 조회한 시간을 db에 저장한다.
맨 처음 시도한 방법은 사용자의 MAC address를 이용해 식별하는 방법이었다. 하지만 MAC address를 가져올 방법이 막막했다. 구글링을 해보아도 자바레벨에선 가져올 수 없다고 했다. 몇몇 코드를 찾았지만 학습테스트 해보았는데 원하는 결과를 얻지 못했다. 배포 일정을 맞춰야 되기 떄문에 이 방법은 포기했다.
그 다음으로 쿠키를 이용한 방법을 선택했다. 정말 악의적인 사용자는 쿠키를 지우면서 어뷰징을 할 수 있겠지만, 우리 서비스에서 조회수가 수익이 발생한다거나 다른 기능을 좌우할만한 중요한 기능이 아니였기에 이정도에 만족하기로 했다. 또한 학습 목적의 프로젝트이니 쿠키를 사용해보는 것도 나쁘지 않겠다는 생각이 들었다. ViewCountManager라는 도메인 객체를 생성하고, 쿠키에 저장될 Post조회 로그를 파싱하는 책임을 위임했다. Post를 조회할때마다 db에 쿼리를 날려 조회수를 업데이트하고, ViewCountManager를 이용해 업데이트 된 쿠키 값을 구했다. 인수테스트까지 작성하니 의도한대로 잘 동작했다. 내일 코드리뷰를 받고 팀원들과 함께 QA를 진행해보아야겠다.
Cookie의 maxAge를 설정하지 않으니 브라우저가 종료되면 쿠키가 사라지는 문제가 발생했다. 따라서 cookie의 maxAge를 하루로 설정해주었다.