DOORE 서비스의 고민을 정리하며
드디어 약 9개월 간의 기획 및 개발 끝에 ‘두레’ 서비스 알파 버전 배포가 완료되었다.
중간에 정규 학기 기간이 있어 개발 진행이 늦어졌지만, 기획부터 개발까지 직접 진행했기에 애정이 가는 프로젝트이다.
오늘은 개발을 하며 팀원들과 깊게 논의했던 부분, 적용했던 부분 그리고 그것에 대한 장단점에 대해 기록해보고자 한다.
1. final에 대한 논의
처음 코드를 작성할 당시, final
에 대한 논의가 애매하게 끝이 났다. final
을 왜 써야하는가? 에 대한 답을 잘 몰랐기 때문이다. 하지만 프로젝트를 진행하며 final
이 필요한 이유에 대해 공부하였고, 리팩토링을 진행하게 되었다.
JDK 8
이 들어오며 함수형 프로그래밍이라는 새로운 패러다임이 등장한다. 함수형 언어의 기본적인 특징은capturing
을 하는 것이다.capturing
이 발생하면 실행 속도에 영향을 미친다. 따라서 자유 변수가 생기지 않도록 코드를 짜야한다.JVM
에서는final
이 아님에도final
처럼 동작하는 경우가 있기 때문에GC
에 미치는 영향이 크다.- 속성값을 마음대로 변경할 수 있는 경우 속도 문제로 대용량 처리를 빠르게 할 수 없다.
final
을 사용하면 위에 기재된 내용을 가정하지 않아도 된다. 따라서 매개변수를 비롯한 모든 부분에 final
을 적는 것으로 결정했다. 아래는 리팩토링 한 코드의 일부이다.
1
2
3
4
5
6
7
8
9
10
11
12
public void manageCurriculum(final CurriculumItemManageRequest request, final Long studyId, final Long memberId) {
validateExistStudyLeader(studyId, memberId);
final List<CurriculumItemManageDetailRequest> curriculumItems = request.curriculumItems();
checkItemOrderDuplicate(curriculumItems);
checkItemOrderRange(curriculumItems);
createCurriculum(studyId, curriculumItems);
updateCurriculum(curriculumItems);
final List<CurriculumItemManageDetailRequest> deletedCurriculumItems = request.deletedCurriculumItems();
deleteCurriculum(deletedCurriculumItems);
sortCurriculum();
}
2. Soft delete 방식의 장단점
두레 서비스는 데이터를 삭제할 때 hard delete 방식이 아닌 soft delete 방식을 사용한다. boolean으로 선언된is_delete
라는 컬럼을 이용해서 데이터 삭제 여부를 판단하는 방식이다.
해당 방식을 채택한 이유는 데이터는 소중하기 때문이다. soft delete 방식을 사용한다면 데이터 복구가 용이하다. 하지만 삭제 로직을 모두 구현해야 한다는 단점이 있다. 쉽게 말해 human error가 발생할 위험이 높다. 그리고 cascade
사용이 불가하다.
예를 들어 다음 코드와 같다.
1
2
3
4
5
6
7
8
9
10
private void deleteMemberTeamAndParticipant(final Long teamId) {
final List<MemberTeam> memberTeams = memberTeamRepository.findAllByTeamId(teamId);
memberTeamRepository.deleteAll(memberTeams);
final List<Study> studies = studyRepository.findAllByTeamId(teamId);
studies.forEach(study -> {
final List<Participant> participants = participantRepository.findAllByStudyId(study.getId());
participantRepository.deleteAll(participants);
});
}
팀을 삭제하는 코드의 일부인데, 팀을 삭제하는데 팀원과 스터디 참여자도 모두 직접 삭제해주어야 한다.
해당 코드를 깜빡하면 팀은 없는데 팀원이랑 스터디원이 그대로 남아있는 문제가 발생한다. (경험담이다. 진짜 발생했던 문제였다…)
하지만 데이터베이스의 삭제 이상이 발생할 위험이 줄어든다는 것과 데이터가 보존된다는 큰 장점 때문에 다른 프로젝트를 기획하게 된다면 soft delete방식을 또 사용할 것 같다.
3. 직접 참조와 간접 참조
불필요한 데이터 로드를 줄이기 위해 같은 도메인 내 직접 참조, 다른 도메인 간 간접 참조 방식을 채택하였다. 예를 들어 간접 참조 대상의 Id가 컬럼으로 존재한다. 데이터베이스 설계를 잘 한다면 해당 방법의 장점이 부각되지만, 개발할 때 생각이 꼬일 수 있고 체감되는 큰 장점은 없었다. (생각이 꼬일 수 있다는 단점이 커서 장점이 잘 안느껴지는 것 같다.) 만약 다음에 데이터베이스를 설계한다면 직접 참조만 사용할 것 같다. 단 서비스의 크기는 고려해야 할 것 같다.
4. 테스트 코드를 작성하며
테스트는 다음과 같이 진행되었다.
- Controller api 테스트 진행 (@SpringBootTest)
- Rest Docs 테스트 진행 (Mocking하여 진행)
- Service 테스트 진행 (Mocking 하지 않고, 필요한 경우에만 Mocking. @SpringBootTest)
- Repository 테스트 진행 (직접 작성한 쿼리만. @DataJpaTest)
테스트 방식에 큰 어려움은 없었던 것 같다. 개발 중 접근 제어자가 통일 되지 않았던 문제가 있었는데 모두 수정하였다.
5. 권한 부여 방식에 대해
직위(권한) 코드를 짜는데 굉장히 심혈을 기울였던 것 같다. 처음에는 늘 이용해왔던 것처럼 스프링 시큐리티를 이용하려 했다. 하지만 서비스가 확장될 가능성을 고려하여 studyRole
, teamRole
등 역할별 테이블을 따로 두었던 것과 권한이 메서드 별로 매우 복잡하기 때문에 시큐리티를 활용하는 방법보다 토큰에서 member
정보를 추출하여 DB에 저장되어있는 role
을 확인하는 방법이 더욱 적절하다고 느꼈다. 또한 개인정보 관리를 위해 두레서비스에서 인증은 하지 않고 인가만 하기로 결정했던 점도 있다. 추가로 커스텀 어노테이션을 만들어서 메서드에서 사용하기 쉽게 하였다.
다시 생각해보면 studyRole
, teamRole
에 늘어날 역할이 별로 없는데 하나의 엔티티로 구성한 점, 다른 도메인 기능이 개발 되었을 때 그에 맞는 Role
이 필요하다면 또 엔티티를 만들어야 한다는 점 등 현재 구조에 개선할 부분이 있어 보인다.
6. 메서드, 잘 작성하였는가?
교수님께서 추천해주신 방법이다. 주니어 시절에는 메서드를 짧게 작성하는 연습을 해보자. (10줄 내외로) 초벌 코드를 작성해두고 리팩토링 하는 방향으로 연습해보자. 이런 연습을 한다면 메서드 별 역할이 분명해지고 가독성 좋은 코드를 짤 수 있다.
해당 내용에 맞게 내가 개발했던 커리큘럼 관련 코드 리팩토링을 진행하였다. 훨씬 보기 좋아진 것 같다. 다른 도메인 코드도 리팩토링을 진행할 예정이다.
정리하며..
알파 버전에 들어갈 기능 개발은 완료되었지만, 아직 기획은 되었으나 구현은 되지 않은 기능이 많다. 아직 고민해 보아야 할 문제도 많고, 리팩토링 해야 할 부분도 많다.
누군가 ‘너 포트폴리오 제출할 때 어떻게 제출해?’ 혹은 ‘너는 스터디 관리할 때 어떤 플랫폼 써?’라고 물어본다면 ‘난 두레 써!’라는 답이 나오는 날을 희망하며 열심히 개발해 보아야겠다.