본문 바로가기
기록, 회고/InFlearn Warming-up 0기 BE

[5일 차] - 내용 정리, 개인 회고

by TwoJun 2024. 2. 21.

인프런에서 주최하는 Warming-up 클럽 0기 백엔드 스터디에 참여하고 있다.

 

스터디에 참여하면서 배우게 된 내용을 전체적으로 정리하고, 참여하면서 느낀 부분을 회고해 보고자 한다.

 

(1) 5일 차 : 2024-02-23(Fri)

 

 

 

 

 

1. 클린 코드(Clean code)

1-1. 좋은 코드란?

(1) 작성된 코드라는 것은 기술적 요구사항, 비즈니스적인 요구사항, 기능을 수행하기 위해 실제로 구현한 언어이다. 이러한 클린 코드는 단순해서 읽기 쉽고, 각 역할마다 정해진 하나의 일만 담당하며 따라서 복잡하거나 모호하지 않은 코드를 의미한다.

 

 

(2) 개발자는 요구사항을 구현하기 위해 기존의 코드를 읽고 작성한다.

- 현업에서는 기존에 존재하는 수많은 코드를 읽고 이해하며 새로운 요구사항을 구현하는 것 보다는 기존의 코드를 리팩토링하거나 수정하는 일이 많음

 

- 작성하는 시간보다 읽는 시간이 더 많음

 

- 팀 단위로 일하는 경우 다른 사람이 작성한 코드를 많이 읽게 된다. 코드를 읽는 것은 필수적이고 피할 수 없는 부분이다.

 

 

 

1-2. 안 좋은 코드가 쌓이면 시간이 지날수록 생산성이 감소한다. 

(1) 이전까지의 문제, 하나의 컨트롤러에서 너무 많은 역할을 수행한다.

 

 

 

1-3. 하나의 컨트롤러에서 왜 많은 기능을 담당할 수 없는 걸까?

(1) 클린 코드에 의하면, 함수는 최대한 작게 구성하고 한 가지 일만 수행하는 것이 좋다.

 

(2) 클래스는 작아야 하며 하나의 책임만을 가져야 한다.

 

 

 

 

1-4. 현재 작성된 컨트롤러의 일부 메서드를 확인해 보자

@PutMapping("/api/v1/fruit")
public void updateSaleState(@RequestBody FruitSaleStateUpdateRequestDto request) {
    String selectSql = "select * from fruit where fruit_id = ?";
    boolean isFruitNotExist = jdbcTemplate.query(selectSql, (rs, rowNum) -> 0, request.getId()).isEmpty();

    if (isFruitNotExist) {
        throw new IllegalStateException("존재하지 않는 과일 정보입니다.");
    }

    String sql = "update fruit set is_sale = 1 where fruit_id = ?";

    jdbcTemplate.update(sql, request.getId());
}

 

(1) 클라이언트 요청의 엔드포인트에서 넘어온 HTTP Body를 객체로 변환하고 있다.

(2) 유저의 존재 여부에 따른 분기에 따라 예외 처리를 수행한다.

(3) 실제 쿼리를 통해 데이터베이스와 통신한다.

(4) 이 부분을 각자의 역할에 맞게 분리하는 것이 중요하다.

 

 

 

 

 

 

2. 컨트롤러의 많은 역할들을 분리하기(Controller - Service - Repository)

2-1. Layered Architecture (계층형 아키텍처)

https://tom-collings.medium.com/controller-service-repository-16e29a4684e5

 

- 위와 같이 각 계층이 서로의 역할에 맞게 독립되어 격리되어 있는 구조를 Layered Architecture라고 부른다.

 

 

(1) Controller (API 엔드포인트, HTTP 관련 처리, 비즈니스 로직이 시작되는 엔드포인트(인터페이스 역할))

- 클라이언트 요청의 엔드포인트에서 넘어온 HTTP Body를 객체로 변환하고 있다.

 

- Controller에서는 Service를 사용

 

 

(2) Service (핵심 비즈니스 로직 처리, 분기, 예외처리 수행 등)

- 유저의 존재 여부에 따른 분기에 따라 예외 처리를 수행한다.

 

- Service에서는 Repository를 사용

 

 

(3) Repository (데이터베이스와의 직접적인 통신, 데이터베이스 접근 영역)

- 실제 쿼리를 통해 데이터베이스와 통신한다.

 

 

(4) 마지막으로 DTO는 계층 간의 정보 전달을 목적으로 사용된다.

 

 

 

 

2-2. UserController, UserService, UserRepository로 분리

(1) UserController.java

@RestController
@RequiredArgsConstructor
public class UserController {

    private final UserService userService;
    
    @PutMapping("/user")
    public void updateUser(@RequestBody UserUpdateRequestDto request) {
        userService.updateUser(request);
    }
}

- 컨트롤러 계층에서는 사용자의 이름을 업데이트하기 위한 서비스 계층의 핵심 비즈니스 메서드를 호출하며 요청으로 넘어온 정보도 함께 파라미터로 전달해준다.(Controller → Service)

 

- 기존 코드에서는 컨트롤러 계층에서 데이터베이스 접근을 위한 JdbcTemplate을 각 계층을 호출할 때마다 계속 넘겨주었는데 이 부분도 데이터베이스와의 통신과 연관되어 있으므로 리포지토리 영역으로 분리시켰다.

 

 

(2) UserService.java

@Service
@RequiredArgsConstructor
public class UserService {

    private final UserRepository userRepository;

    public void updateUser(UserUpdateRequestDto request) {
        if (userRepository.isUserNotExist(request.getId())) {
            throw new IllegalStateException("존재하지 않는 회원입니다.");
        }
        userRepository.updateUserName(request.getName(), request.getId());
    }
}

- 사용자 이름을 업데이트하기 위해서, 존재하지 않는 회원인지 이 부분에 대한 예외를 처리하기 위해 리포지토리 영역에서 isUserNotExist를 호출한다. (Service → Repository)

 

- 존재하는 회원임이 확인되면 다시 리포지토리 영역에서 사용자의 이름을 변경하는 메서드를 호출한다 (Service → Repository)

 

 

 

(3) UserRepository.java

@Repository
@RequiredArgsConstructor
public class UserRepository {

    private final JdbcTemplate jdbcTemplate;

    public boolean isUserNotExist(long id) {
        String selectSql = "select * from user where id = ?";
        return jdbcTemplate.query(selectSql, (rs, rowNum) -> 0, id).isEmpty();
    }

    public void updateUserName(String name, long id) {
        String sql = "update user set name = ? where id = ?";
        jdbcTemplate.update(sql, name, id);
    }
}

- 리포지토리 영역에서 사용자의 요청에 따른 데이터베이스와의 통신을 담당한다.

 

 

 

 

2-3. 사용자 업데이트만이 아닌, 모든 비즈니스 포인트(CRUD)들을 대상으로 Controller - Service - Repository로 분리하자.

(1) UserController.java

@RestController
@RequiredArgsConstructor
public class UserController {

    private final UserService userService;

    @PostMapping("/user")
    public void saveUser(@RequestBody UserCreateRequestDto request) {
        userService.saveUser(request);
    }

    @GetMapping("/user")
    public List<UserListResponseDto> getAllUsers() {
        return userService.findAllUser().stream()
                .map(user -> new UserListResponseDto(user.getId(), user.getName(), user.getAge()))
                .collect(Collectors.toList());
    }

    @PutMapping("/user")
    public void updateUser(@RequestBody UserUpdateRequestDto request) {
        userService.updateUser(request);
    }

    @DeleteMapping("/user")
    public void deleteUser(UserDeleteRequestDto request) {
        userService.deleteUser(request);
    }
}

 

 

 

(2) UserService.java

@Service
@RequiredArgsConstructor
public class UserService {

    private final UserRepository userRepository;

    public void saveUser(UserCreateRequestDto request) {
        userRepository.saveUser(request.getName(), request.getAge());
    }

    public List<User> findAllUser() {
        return userRepository.findAllUser();
    }

    public void updateUser(UserUpdateRequestDto request) {
        if (userRepository.isUserNotExist(request.getId())) {
            throw new IllegalStateException("존재하지 않는 회원입니다.");
        }
        userRepository.updateUserName(request.getName(), request.getId());
    }

    public void deleteUser(UserDeleteRequestDto request) {
        if (userRepository.isUserNotExist(request.getName())) {
            throw new IllegalStateException("존재하지 않는 회원입니다.");
        }
        userRepository.deleteUserByUsername(request.getName());
    }
}

 

 

 

(3) UserRepository.java

@Repository
@RequiredArgsConstructor
public class UserRepository {

    private final JdbcTemplate jdbcTemplate;

    public void saveUser(String name, int age) {
        String sql = "insert into user(name, age) values(?, ?)";
        jdbcTemplate.update(sql, name, age);
    }

    public List<User> findAllUser() {
        String sql = "select * from user";
        return jdbcTemplate.query(sql, (rs, rowNum) ->  {
            long id = rs.getLong("id");
            String name = rs.getString("name");
            int age = rs.getInt("age");
            return new User(id, name, age);
        });
    }

    public boolean isUserNotExist(long id) {
        String selectSql = "select * from user where id = ?";
        return jdbcTemplate.query(selectSql, (rs, rowNum) -> 0, id).isEmpty();
    }

    public boolean isUserNotExist(String name) {
        String selectSql = "select * from user where name = ?";
        return jdbcTemplate.query(selectSql, (rs, rowNum) -> 0, name).isEmpty();
    }

    public void updateUserName(String name, long id) {
        String sql = "update user set name = ? where id = ?";
        jdbcTemplate.update(sql, name, id);
    }

    public void deleteUserByUsername(String name) {
        String sql = "delete from user where name = ?";
        jdbcTemplate.update(sql, name);
    }
}

 

 

 

2-4. 결론

(1) 기존에 계층형 구조를 띄지 않고 모든 역할이 한 곳에 모여있던 컨트롤러 코드와, 이제는 계층별로 역할이 명확하게 분리된 코드들(Controller, Service, Repository) 비교해 보면 코드가 이전에 비해 상당히 가독성도 좋아지고 추후 유지보수 측면에서도 훨씬 수월해진 코드를 확인해 볼 수 있다.

 

 

 

 

 

 

3. 개인 회고

(1) 이처럼 계층형 아키텍처가 모든 상황에서 100% 정답은 아니겠지만, 프로덕트의 품질, 비즈니스 측면, 개발자들과의 협업, 추후 요구사항 변경, 코드 리팩토링, 테스트, 유지보수 등 모든 관점에서 봤을 때 계층형 아키텍처 방식의 설계가 매우 효율적인 설계라는 것을 알 수 있었다.

 

(2) 클린 코드, 오브젝트 등 더 좋은 객체지향의 설계 방법이 어떤 방법인지 더 공부해 보고 싶다.

 

 

 

 

 

4. Reference

(1) 관련 레퍼런스는 인프런에서 활동하고 계시는 최태현 강사님의 온라인 강의를 듣고 내용을 정리했습니다.

https://www.inflearn.com/course/%EC%9E%90%EB%B0%94-%EC%8A%A4%ED%94%84%EB%A7%81%EB%B6%80%ED%8A%B8-%EC%84%9C%EB%B2%84%EA%B0%9C%EB%B0%9C-%EC%98%AC%EC%9D%B8%EC%9B%90/dashboard

 

자바와 스프링 부트로 생애 최초 서버 만들기, 누구나 쉽게 개발부터 배포까지! [서버 개발 올인

Java와 Spring Boot, JPA, MySQL, AWS를 이용해 서버를 개발하고 배포합니다. 웹 애플리케이션을 개발하며 서버 개발에 필요한 배경지식과 이론, 다양한 기술들을 모두 학습할 뿐 아니라, 다양한 옵션들

www.inflearn.com

 

 

※ 해당 포스팅에 대해 내용 추가가 필요하다고 생각되면 기존 포스팅 내용에 다른 내용이 추가될 수 있습니다.

개인적으로 공부하며 정리한 내용이기에 오타나 틀린 부분이 있을 수 있으며, 이에 대해 댓글로 알려주시면 감사하겠습니다

댓글