우당탕탕 베타 버전을 완성시킬 때 동작 여부를 확인하는데 급급해서 servcie를 따로 만들지 않고 Repository에서 메서드를 직접 사용해 Controller를 작성했다. 이것만 하고 Service단을 만들어야지 하다가 걷잡을 수 없이 많아져서 미루다가 미래의 내가 고생했다.. 핑계를 말해보자면 명확하게 비즈니스 로직을 설계하지 않아서 그때그때 기능 구현을 하다 보니..
핑계 접어두고 시작하겠다.
entity, repository, service, controller, view의 기본 구조가 필요하다는 것을 알고 repository와 service를 무의식적으로 만들다가 이 김에 왜 필요한지 정리하려한다. 왜냐면 내가 처음 작성했던 것처럼 바로 repository에서 메서드를 가져와
써도 동작하기 때문이다. 본격적으로 고고고~ 내가 개발하면서 필요를 느꼈던 점을 위주로 정리할 것이어서 더 많은 이유가
존재할 수 있다는 점 미리 작성한다.
단일 책임 원칙(SRP)
객체 지향적으로 모듈을 역할에 맡게 분리 한다...라는 생각을 갖고 찾아보니 멋있는 말이 있었다.
Single Responsibilty Principle 단일 책임 원칙은 하나의 클래스는 단 하나의 책임만을 가져야 한다는 것이다.
객체 지향을 맛만 보더라도 비슷한 내용을 많이 들어 봤을 것이다. 그렇다면 service와 repository의 책임은 각각 무엇일까.
Repository : DB 접근 역할을 수행한다. DB에 저장하고 불러오는 역할을 하는 메서드들이 모여있다.
Service : 비지니스 로직의 역할을 수행하는 메서드들이 모여있다.
객체 지향적 개발을 하기 위해 각자의 역할을 명확하게 구분해야 한다. service는 비즈니스 로직의 역할을 수행하므로 repository에서 가져온 데이터를 요구 사항에 맞게 가공할 수 있다.
코드 재사용?
여기서부터는 내가 느낀 필요성이다. 하지만 따져보면 SRP를 지키면 따라오는 이점들이라고 할 수 도 있다.
앞서 말했듯이 repository에서 메서드를 가져와 기능을 구현해도 된다. 하지만 같은 기능을 여러 곳에서 사용해야 할 경우 여러 줄의 코드를 반복해서 작성해야 한다. UserRepository에서 findByName(name)해서 user 찾고,, 찾은 user에서 .getId()해서 id 찾은 후 그 id로 EnrollRepository에서 findByUserId(id)해서 수강 신청 엔티티 찾고,,, 서비스가 복잡해질수록 여러 줄의 코드를 반복 작성 해야 한다. 귀찮다. 그러므로 service에서 비즈니스 요구 사항에 맞게 필요한 Repository들을 DI 하여 적절한 메서드를 한번 작성해 두고 호출해 사용하는 것이 효율적이다.
유지 보수
모듈화 한 class를 사용하는 이유와 비슷하다. Course(강좌)를 불러와 가격을 출력한다. 이때 할인 기간이여서 모든 강좌의 가격을 DB에 저장된 값보다 일시적으로 1000원 할인하여 보여주려 할 때 Controller에 직접 repository 메서드들로 작성했으면 모든 줄을 찾아서 직접 - 1000을 해주어야 한다. 하지만 service가 있으면 메서드 내용에만 -1000을 해주면 호출한 곳에서 동작한다.
또한 비지니스 로직 관련 문제가 발생하면 service를 확인하면 되고 db 관련 문제가 있으면 repository를 확인하면 되기에 유지 보수에 매우 수월하다.
가독성
개발은 혼자 하는 것이 아니다. 심지어 혼자 하더라도 어제의 나는 내가 아니다. (어제 내가 작성한 코드를 오늘의 나는 해석 못할 수 있다.) 가독성 역시 중요한 요소이다.
List<Course> courseList = new ArrayList<>();
courseList.addAll(courseRepository.findAllByTitleContaining(keyword));
List<Teacher> teachersByName = teacherRepository.findAllByNameContaining(keyword);
for (Teacher teacher : teachersByName) {
courseList.addAll(courseRepository.findAllByTeacher(teacher));
}
List<Category> categoriesByName = categoryRepository.findAllByNameContaining(keyword);
for (Category category : categoriesByName) {
courseList.addAll(courseRepository.findAllByCategoryList(category));
}
return courseList;
무슨 기능을 하는 코드일까? course에서 keyword라는 카테고리를 포함한 강좌를 찾고.. teacher 이름으로 teacher를 찾아서.. 실제 비즈니스 코드는 이보다 복잡한 것이 더 많을 것이다. 이 코드가 controller에 있다면 무슨 역할을 하는 것인지 알아보기 너무 힘들다.
public List<Course> showCoursesByKeyword(String keyword) {
List<Course> courseList = new ArrayList<>();
courseList.addAll(courseRepository.findAllByTitleContaining(keyword));
List<Teacher> teachersByName = teacherRepository.findAllByNameContaining(keyword);
for (Teacher teacher : teachersByName) {
courseList.addAll(courseRepository.findAllByTeacher(teacher));
}
List<Category> categoriesByName = categoryRepository.findAllByNameContaining(keyword);
for (Category category : categoriesByName) {
courseList.addAll(courseRepository.findAllByCategoryList(category));
}
return courseList;
}
하지만 service에 메서드를 작성해두었다면 아 show(보여주는구나) Courses(Course들을) ByKeyword(키워드로 찾아서) 바로 알아보고 controller에서는 showCoursesByKeyWord()를 호출해 사용하면 된다. 가독성도 챙기고 위에 말하였던 코드 재사용도 활용하는 예제라 볼 수 있다. (메서드명을 show 대신 get을 사용하는 것이 더 적절한가?????)
정리
분리해 쓰는 것이 당연히 유용하다. 물론 repository에 메서드가 1개뿐이라면 service가 필요 없을 수도 있다. 하지만 이 역시도 가져온 값을 가공해야 한다면 같은 코드들이 controller에 여러 번 작성될 것이고 유지 보수, 가독성에 문제가 있을 수 있다.
베타 버전을 만들면서도 느꼈지만 시작 전 설계가 탄탄해야 한다. 그렇지 않으면 다시 뒤로 돌아와서 수정하고 다시 만들고가 생각보다 더 많이 반복된다...... 설계가 더 꼼꼼했다면 필요한 메서드 탁탁 repository에 작성하고 요구 사항에 맞게 service 메서드 탁탁 작성하고 할 수 있다. 그렇지 않았기 때문에 당장 필요하면 repository에 선언하고 하다 보니 service가 완벽하게 작성되지 않았던 것 같다.
'My project > inflearn_clone?' 카테고리의 다른 글
다대다 관계 해결하기. (@ManyToMany, @JoinTable, 연결 테이블 기본키) (0) | 2023.08.11 |
---|---|
스프링 부트 상태코드 999??? (에러페이지, 쉬어가는 타임) (0) | 2023.07.30 |
@Transactional 이해하고 사용하기 (트랜잭션이란?) (0) | 2023.07.24 |
절대 경로 VS 상대 경로 (a 태그 href 속성에 /를 작성해야 할까?) (0) | 2023.07.19 |
JpaRepository Query Method (엔티티 필드에 _가 들어있으면?) (1) | 2023.07.18 |