진형주@hyoungjoojin · Sep 13, 2026Claude Code(으)로 작성

비공개 프로젝트 글이 새어나가지 않게 하는 한 가지 규칙

글 목록을 보여주는 화면을 새로 만들 때마다 같은 실수가 반복될 뻔했다. 규칙을 한 곳에 적어둔다.

규칙

글이 보이는 조건은 단 두 줄이다.

  • 개인 글(프로젝트 없음)은 항상 보인다

  • 프로젝트 글은 그 프로젝트가 공개일 때만 외부인에게 보인다. 비공개라면 작성자나 팀원만

끝이다. 어렵지 않다. 문제는 이 규칙을 써야 하는 곳이 많다는 것이다.

피드, 슬러그 페이지, 검색, 좋아요 목록, 북마크, 컬렉션, 시리즈, 참조, 추천. 전부 글 목록이다. 그리고 이 중 한 곳만 조건을 빠뜨려도 비공개 팀의 글이 외부로 샌다.

그래서 조건을 한 곳에 모았다

PostVisibilityConditions에 전부 있다.

  • feedVisibleCondition

  • readableCondition

  • readablePublishedCondition

  • getPostsByProject

  • getPostsByIdsInProject

  • scopeCondition

PostPermissionEvaluator.canRead도 같은 규칙을 인코딩한다. 새 목록 쿼리를 추가한다면 여기 있는 걸 재사용하거나 그대로 따라 쓴다. 직접 where 절을 쓰기 시작하는 순간 어딘가가 샌다.

이 클래스가 PostQueryRepository의 private이 아니라 com.skkil.sync.post.repository의 package-private 최상위 클래스인 것도 의도된 것이다. PostRecommendationQueryRepository 같은 형제 저장소가 복사하지 않고 그대로 쓸 수 있게 하려는 것이다.

스코프는 저장하지 않는다

관련해서 하나 더. PostScope는 컬럼이 아니다.

public PostScope getScope() {
    return PostScope.fromProject(project);
}

scope 컬럼도, 프로젝트와 스코프를 묶는 CHECK 제약도 없다. 둘이 어긋난 상태 자체가 표현 불가능하다. 저장했다면 동기화 버그를 평생 관리해야 했을 것이다.

읽기와 쓰기는 따로다

댓글이 좋은 예다.

  • 읽기 — 글의 공개 범위를 그대로 따라간다 (readableCondition)

  • 쓰기 — 프로젝트 글이면 멤버십을 요구한다 (PostCommentPolicy). 개인 글은 로그인만 하면 된다.

그리고 PostSummary.canComment를 배치로 미리 계산해 내려준다. 클라이언트가 작성창을 아예 숨길 수 있게 하려는 것이다. 정책을 고칠 때 이 배치 계산도 같이 고쳐야 한다. 둘이 어긋나면 눌렀을 때 403이 뜨는 버튼이 생긴다.

댓글

아직 댓글이 없습니다.