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