애플리케이션을 개발하다보면 자신이 생성한 컨텐츠인지 여부를 체크해 자신이 생성한 컨텐츠에 한해 수정, 삭제 등이 가능한지를 체크하는 중복 로직이 많이 발생한다.
예를 들어 kotlin으로 구현한다면 다음과 같이 구현할 수 있다.
fun addLecture(loginUser: NsUser, lecture: Lecture): CourseLecture {
if (!isOwner(loginUser)) {
throwPermissionException()
}
return courseLectures.addLecture(this, lecture)
}
fun moveLecture(loginUser: NsUser, lectureId: String, position: Int): CourseLecture {
if (!isOwner(loginUser)) {
throwPermissionException()
}
return courseLectures.move(lectureId, position)
}
위와 같이 단순 반복적으로 권한 체크하는 부분을 어떻게 제거하면 좋을까? AOP를 적용해도 되겠지만 굳이 AOP까지 적용하고 싶지 않아 다음과 같이 구현해 봤다.
권한 체크를 위해 메소드 인자로 전달하는 NsUser 객체에 다음 메소드를 추가했다.
@Entity
open class NsUser(
fun isSameUser(actor: NsUser) = actor == this
fun <T:Any> letHasOwnerRole(actor: NsUser, f: () -> T): T {
if (!isSameUser(actor)) {
throw HasNotPermissionException("권한이 없습니다.")
}
return f()
}
}
위와 같이 함수를 인자로 받는 메소드를 추가한 후 앞의 권한 체크 로직을 다음과 같이 구현해 봤다.
fun addLecture(loginUser: NsUser, lecture: Lecture): CourseLecture {
return loginUser.letHasOwnerRole(creator) {
courseLectures.addLecture(this, lecture)
}
}
fun moveLecture(loginUser: NsUser, lectureId: String, position: Int): CourseLecture {
return loginUser.letHasOwnerRole(creator) {
courseLectures.move(lectureId, position)
}
}
kotlin 기반으로 구현하니 이와 같은 구현이 가능해 나름 괜찮다 생각하는데 또 다른 좋은 접근 방식이 있을까?
2개의 의견 from SLiPP
외부 구현 없이 Kotlin standards 로는 이렇게도 가능할것 같긴 합니다만 결국
if
가 없을순 없네요. 다른 분들이 더 좋은 아이디어를 남겨 주시길 바라며 전 이만 텨텨텨hasRole(NsUser, Role)
이라는 함수는 있다고 가정했습니다. extension function 을 이용하면loginUser.hasRole(role)
처럼 좀더 자연스럽게 쓸 수 있겠네요.개인적으로 현재 테스트하기 힘든 레거시 코드를 많이 다루고 있다보니 다른 형태의 접근이 먼저 떠올랐습니다. 코드가 잘 짜여있고 테스트가 가능한 구조라면 상관없겠지만 추가할 체크 로직 또한 테스트가 필요하고 로직을 추가할 클래스가 굉장히 복잡해서 테스트하기 힘든 경우에는 인터페이스로 분리하는 것이 테스트하기 좋습니다.
위와 같은 테스트하기 힘든 클래스
UntestableUser
가 있고 여기서email
,nickname
,roles
에 대해서 유효성 검사가 필요한 상황이라고 가정하겠습니다.아래와 같이 default interface method를 통해 구현한다던가 코틀린이라면 추가적으로 확장함수로 구현할 수도 있습니다.
그리고 유효성 검사를 담당해야 할 클래스들에게 해당 인터페이스를 상속받도록 명시해주면 끝입니다. 테스트 코드에서도 마찬가지입니다.
여기서부터는 코틀린에만 해당되는 내용입니다.
현실의 코드가 대부분 그렇듯이 인터페이스에서도 내부적으로 쓰여야 할 의존성이 생길 수 있습니다. 이런 경우 자바에서는 추상 클래스 또는 delegation 패턴을 구현하던가 눈물을 흘리며 메서드의 매개변수로 추가하기도 합니다.
코틀린에서는 인터페이스에도 필드를 추가할 수 있습니다. 다만 그렇게 되면 아래와 같이 상속받는 클래스에서 인터페이스에서만 쓰이고 클래스의 다른 곳에서는 필요가 없는 의미없는 멤버 필드가 생길 수 있습니다.
이런 경우를 피하기 위해서는 미리 인터페이스의 구현체를 만들고 생성자로 구현체를 넣어준 뒤에
by
키워드를 통해서 클래스 위임을 하면 됩니다.테스트 코드를 포함한 모든 샘플 코드는 아래 링크에 올려두었습니다. https://gist.github.com/galcyurio/4f0433b535b146cef327a268b15e3486
의견을 남기기 위해서는 SLiPP 계정이 필요합니다.
안심하세요! 회원가입/로그인 후에도 작성하시던 내용은 안전하게 보존됩니다.
SLiPP 계정으로 로그인하세요.
또는, SNS 계정으로 로그인하세요.