모든 짐을 짊어진 Controller의 상태
이전 최초 버전의 MVC구성은 Controller가 모든 API를 가지고 있으면서 동시에 로직의 실행 문장들, DB와 연결되고 DB의 실제 실행 문장인 sql문까지 모두 포함되어 복잡해보이는 상태였다. 기본적인 기능들만 있기 때문에 그렇게 느끼지 못할 수도 있지만, 기능이 추가 될 수록 점점 Controller가 가지게 되는 코드들이 복잡해지게 되고 시안성부족과 개발자가 실수 할 수 있는 가능성이 상대적으로 높아서 유지보수적으로 불리해지는 형태이다.
Controller의 부담을 분산시키기(3계층)
위와 같은 문제를 해결하기 위해서는 Controller의 역할과 기능을 분리 할 필요가 있다. 아래 3개의 계층으로 세분화 시킨 내용이다.
1.
우선 컨트롤러는 기본적으로 맵핑을 통한 URL(클라이언트측)과의 소통창구이다. 기본적으로 해당 기능만 수행하는 것이 궁극적인 Controller의 목적이다.
2.
Service라는 클래스를 통해 이전 Controller가 가지고 있던 실제 기능 비지니스 로직이 진행되는 부분을 분담하여 가져온다. 이 부분이 실제적으로 기능의 핵심 부분이고, 가장 코드량이 몰려있을 수 밖에 없다. 다만 위 Controller의 창구 역할을 분리했기 때문에 어떠한 비지니스 로직에 대한 수정, 추가 등을 이 부분만 집중해서 공략할 수 있는 장점이 있다. MVC패턴에서 Service는 계속해서 비대해지는 흐름을 타고 있다.
3.
Repository라는 클래스를 통해서 이전 Controller가 가지고 있던 DB 커넥션과 DB로의 CRUD sql문장의 수행을 분담한다. 이는 DB와의 연결은 여기서만 신경 쓰면 되는 것으로 Service 부분에서 필요한 DB와의 연결은 이 부분과 연결되도록 한다.
ex1)Controller.java(아직 JDBC의 DI-IoC는 적용안된 상태)
@RestController
public class UserController {
private final JdbcTemplate jdbcTemplate;
public UserController(JdbcTemplate jdbcTemplate) {
this.jdbcTemplate = jdbcTemplate;
}
/**
* 회원의 전체 목록을 조회하는 API
*/
@GetMapping("/user")
public List<UserResponseDto> getUserList() {
UserService userService = new UserService(jdbcTemplate);
return userService.findAllUser();
}
}
Java
복사
ex2)Service.java(아직 JDBC의 DI-IoC는 적용안된 상태)
public class UserService {
private final JdbcTemplate jdbcTemplate;
public UserService(JdbcTemplate jdbcTemplate) {
this.jdbcTemplate = jdbcTemplate;
}
public List<UserResponseDto> findAllUser() {
// DB 조회
UserRepository memoRepository = new UserRepository(jdbcTemplate);
return memoRepository.findAllUser();
}
}
Java
복사
ex3)Repository.java(아직 JDBC의 DI-IoC는 적용안된 상태)
public class UserRepository {
private final JdbcTemplate jdbcTemplate;
public UserRepository(JdbcTemplate jdbcTemplate) {
this.jdbcTemplate = jdbcTemplate;
}
public List<UserResponseDto> findAllUser() {
// DB 조회
String sql = "SELECT * FROM user";
return jdbcTemplate.query(sql, new RowMapper<UserResponseDto>() { // <--- RowMapper<>() 부분 IDE가 람다식으로 추천해주는데, 람다식 아직잘모르니 나중에 공부하면 바꿔보기!!
@Override
public UserResponseDto mapRow(ResultSet rs, int rowNum) throws SQLException {
// SQL 의 결과로 받아온 User 데이터들을 UserResponseDto 타입으로 변환해줄 메서드
Long id = rs.getLong("id");
String name = rs.getString("name");
String email = rs.getString("email");
String pw = rs.getString("pw");
return new UserResponseDto(id, name, email, pw);
}
});
}
}
Java
복사
왜 분담 시키는 것이 중요한가? 장점은?
당장에는 같은 기능이 돌아가고 있기 때문에 이 차이점을 느끼기 어려울 수 있다.
하지만, 이처럼 Controller를 → Controller(소통창구)+Service(로직)+Repository(DB측)로 3계층으로 분리한 것은 역할과 기능에 맞도록 분리된 것이며 이는 곧 개발자가 필요한 부분에서만 작업 할 수 있도록, 또한 어떠한 새로운 기능을 추가 할 때 모듈화된 각 부분만 신경쓰도록 효율적인 작업 동선을 만들었다.
더 중요한 것은 예로 DB의 종류가 바뀌면 Repository의 DB 커넥션 부분만 바꾼다던지 이러한 방식으로 각 부분이 모듈화되어 있으므로 C-S-R의 연결 부분들이 느슨한 결합(서로 의존성이 낮음)을 통해 유연성, 재사용성, 확장성을 보유하게 된 것이다.
C-S-R 각 연결 부분들마다 Data는 어떻게?
기본적으로 Client 요청 JSON 형태 데이터 딕셔너리를 Controller가 DTO 객체로 받아내면서 그것을 Entity 객체로 변환했었으며, DB관련 기능을 수행하고 다시 Entity가 DTO객체로 변환, Controller의 출구에서는 DTO를 JSON으로 클라이언트에게 반환했었다.
Controller-Service-Repository로 기능이 분리된 상황에서도 마찬가지이다.
Request→(JSON to DTO)
→Controller→(DTO)
→Service→(DTO to Entity)
→Repository→(Entity to DTO)
→Service→(DTO)
→Controller→(DTO to JSON)
→Response
글로는 보기 어렵지만 그냥 JSON이 들어올 것이며 DTO로 내부에서 이동하면서 DB에서만 Entity형태로, 다시 DTO로 이동하다가 나갈 때 JSON으로 나가면 되는 형식이다.