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

[6일 차] - 과제 수행 : Controller - Service - Repository 분리

by TwoJun 2024. 2. 23.
728x90
반응형

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

 

스터디에 참여하면서 배우게 된 내용을 전체적으로 정리하고 과제로 수행했던 내용들을 정리해 보고자 한다.

 

(1) 6일 차 : 2024-02-26(Mon)

 

(2) 과제 수행 관련 GitHub 코드 : https://github.com/twojun/InFlearn_WarmingUp_Club_BE_0

- @Primary 테스트 이후 FriutService 계층이 기존 FruitRepository에 의존성을 가지도록 수정해 놓았습니다.

 

GitHub - twojun/InFlearn_WarmingUp_Club_BE_0: Inflearn Warming-up Club Back-end Study 0기 (Java, Spring)

Inflearn Warming-up Club Back-end Study 0기 (Java, Spring) - twojun/InFlearn_WarmingUp_Club_BE_0

github.com

 

 

 

 

 

1. Fruit 서비스를 각각의 계층구조인  Controller - Service - Repository로 분리한다. 

1-1. FruitController.java

@RestController
@RequiredArgsConstructor
public class FruitController {

    private final FruitService fruitService;

    @PostMapping("/api/v1/fruit")
    public void createFruit(@RequestBody FruitCreateRequestDto request) {
        fruitService.createFruit(request);
    }

    @GetMapping("/api/v1/fruit/stat")
    public List<FruitSaleNoSaleTotalPriceDto> getSaleAndNoSaleTotalPrice(@RequestParam String name) {
        return fruitService.getSaleAndNoSaleTotalPrice(name);
    }

    @PutMapping("/api/v1/fruit")
    public void updateSaleState(@RequestBody FruitSaleStateUpdateRequestDto request) {
        fruitService.updateSaleState(request);
    }
}

 

 

 

 

1-2. FruitService.java

@Service
@RequiredArgsConstructor
public class FruitService {

    private final FruitRepository fruitRepository;

    public void createFruit(FruitCreateRequestDto request) {
        fruitRepository.createFruit(request);
    }

    public List<FruitSaleNoSaleTotalPriceDto> getSaleAndNoSaleTotalPrice(String name) {
        return fruitRepository.getSaleAndNoSaleTotalPrice(name);
    }

    public void updateSaleState(FruitSaleStateUpdateRequestDto request) {
        if (fruitRepository.isFruitNotExist(request)) {
            throw new IllegalStateException("존재하지 않는 과일 정보입니다.");
        }
        fruitRepository.updateFruitSaleState(request);
    }
}

(1) 서비스 계층에서 핵심 비즈니스 로직 수행 및 예외 처리가 필요하므로 과일 정보가 존재하지 않을 때의 코드를 서비스 계층으로 분리했다.

 

 

 

1-3. FruitRepository.java

@Repository
@RequiredArgsConstructor
public class FruitRepository {

    private final JdbcTemplate jdbcTemplate;

    public void createFruit(FruitCreateRequestDto request) {
        String sql = "insert into fruit (name, warehousing_date, price) values (?, ?, ?)";
        jdbcTemplate.update(sql, request.getName(), request.getWarehousingDate(), request.getPrice());
    }

    public List<FruitSaleNoSaleTotalPriceDto> getSaleAndNoSaleTotalPrice(String name) {
        String sql = "select " +
                "(select sum(price) from fruit where is_sale = 1) as salesAmount, " +
                "(select sum(price) from fruit where is_sale = 0) as notSalesAmount";

//        String sql = "select is_sale, sum(price) as total_price from fruit group by is_sale";

        return jdbcTemplate.query(sql, (rs, rowNum) -> {
            long salesAmount = rs.getLong("salesAmount");
            long notSalesAmount = rs.getLong("notSalesAmount");
            return new FruitSaleNoSaleTotalPriceDto(salesAmount, notSalesAmount);
        });
    }

    public boolean isFruitNotExist(FruitSaleStateUpdateRequestDto request) {
        String selectSql = "select * from fruit where fruit_id = ?";
        return jdbcTemplate.query(selectSql, (rs, rowNum) -> 0, request.getId()).isEmpty();
    }

    public void updateFruitSaleState(FruitSaleStateUpdateRequestDto request) {
        String sql = "update fruit set is_sale = 1 where fruit_id = ?";
        jdbcTemplate.update(sql, request.getId());
    }
}

 

 

 

 

 

 

2. 계층 구조로 분리한 후 정상 동작하는지 확인하기

2-1. 요구사항 1번 확인 : 과일 정보 생성하기

과일 정보 저장

(1) 기존과 동일하게 데이터를 저장한다.

 

 

데이터 정상 저장 확인

(2) 데이터가 정상적으로 저장된 것을 확인할 수 있다.

 

 

 

추가적인 데이터 저장

(3) 추가적인 테스트를 위해 더 많은 데이터를 저장했다.

 

 

 

 

2-2. 요구사항 2번 확인 : 판매된 과일의 판매 정보 업데이트하기

입고된 과일에 대한 판매상태를 업데이트한다.

(1) 바디에 아이디 값을 넘기고 판매 상태를 업데이트한다.

 

(2) 테스트를 위해 1, 3, 5번의 판매상태를 변경했다.

 

 

판매 상태 변경

(4) 1, 3, 5번의 판매 상태가 변경된 것을 확인할 수 있다.

 

 

 

2-3. 존재하지 않는 과일 정보에 대해서는 예외 발생시키기

(1) 만약 존재하지 않는 7번 항목 과일의 판매상태를 업데이트한다고 해보자.

서버에서 예외 발생

(2) 그렇다면 위의 처럼 Internal Server Error를 반환한다.

 

 

IllegalStateException 예외 발생

(3) 실제 서버에서도 로그를 확인해 보면 IllegalStateException이 발생한 것을 확인할 수 있다.

 

 

 

 

2-4. 요구사항 3번 확인 : 동일한 상품을 기준으로 판매/미판매된 과일의 가격 각각 출력하기

(1) 현재 데이터베이스 테이블을 확인해 보면 판매된 과일은 1, 3, 5번, 판매되지 않은 과일은 2, 4, 6번이다.

 

(2) 각각 판매/미판매 금액이 정상적으로 출력되는지 확인해 보자.

 

 

(3) 위와 같이 쿼리 파라미터로 과일 이름을 주어 조회 결과를 확인해 보면 각각의 금액이 정상적으로 반환된 것을 확인할 수 있다.

 

 

 

 

 

3. 과제 수행 :  문제 2번

3-1. FruitRepository의 코드를 FruitMemoryRepository, FruitMysqlRepository로 구분 후 @Primary 어노테이션으로 Repository를 변경해 가며 코드를 실행시킨다.

 

(1) FruitMysqlRepository.java

@Repository
@RequiredArgsConstructor
public class FruitMysqlRepository implements FruitRepositoryInterface {

    private final JdbcTemplate jdbcTemplate;

    public void createFruit(FruitCreateRequestDto request) {
        String sql = "insert into fruit (name, warehousing_date, price) values (?, ?, ?)";
        jdbcTemplate.update(sql, request.getName(), request.getWarehousingDate(), request.getPrice());
    }

    public List<FruitSaleNoSaleTotalPriceDto> getSaleAndNoSaleTotalPrice(String name) {
        String sql = "select " +
                "(select sum(price) from fruit where is_sale = 1) as salesAmount, " +
                "(select sum(price) from fruit where is_sale = 0) as notSalesAmount";

//        String sql = "select is_sale, sum(price) as total_price from fruit group by is_sale";

        return jdbcTemplate.query(sql, (rs, rowNum) -> {
            long salesAmount = rs.getLong("salesAmount");
            long notSalesAmount = rs.getLong("notSalesAmount");
            return new FruitSaleNoSaleTotalPriceDto(salesAmount, notSalesAmount);
        });
    }

    public boolean isFruitNotExist(FruitSaleStateUpdateRequestDto request) {
        String selectSql = "select * from fruit where fruit_id = ?";
        return jdbcTemplate.query(selectSql, (rs, rowNum) -> 0, request.getId()).isEmpty();
    }

    public void updateFruitSaleState(FruitSaleStateUpdateRequestDto request) {
        String sql = "update fruit set is_sale = 1 where fruit_id = ?";
        jdbcTemplate.update(sql, request.getId());
    }
}

 

 

 

(2) FruitMemoryRepository.java (FruitMysqlRepository와 코드는 동일)

@Repository
@RequiredArgsConstructor
public class FruitMemoryRepository implements FruitRepositoryInterface {

    private final JdbcTemplate jdbcTemplate;

    public void createFruit(FruitCreateRequestDto request) {
        String sql = "insert into fruit (name, warehousing_date, price) values (?, ?, ?)";
        jdbcTemplate.update(sql, request.getName(), request.getWarehousingDate(), request.getPrice());
    }

    public List<FruitSaleNoSaleTotalPriceDto> getSaleAndNoSaleTotalPrice(String name) {
        String sql = "select " +
                "(select sum(price) from fruit where is_sale = 1) as salesAmount, " +
                "(select sum(price) from fruit where is_sale = 0) as notSalesAmount";

//        String sql = "select is_sale, sum(price) as total_price from fruit group by is_sale";

        return jdbcTemplate.query(sql, (rs, rowNum) -> {
            long salesAmount = rs.getLong("salesAmount");
            long notSalesAmount = rs.getLong("notSalesAmount");
            return new FruitSaleNoSaleTotalPriceDto(salesAmount, notSalesAmount);
        });
    }

    public boolean isFruitNotExist(FruitSaleStateUpdateRequestDto request) {
        String selectSql = "select * from fruit where fruit_id = ?";
        return jdbcTemplate.query(selectSql, (rs, rowNum) -> 0, request.getId()).isEmpty();
    }

    public void updateFruitSaleState(FruitSaleStateUpdateRequestDto request) {
        String sql = "update fruit set is_sale = 1 where fruit_id = ?";
        jdbcTemplate.update(sql, request.getId());
    }
}

 

(3) 우선 FruitMysqlRepository 리포지토리를 사용한다고 해보자. 해당 리포지토리에 @Primary를 적용한다.

 

 

@Repository
@RequiredArgsConstructor
@Primary
public class FruitMysqlRepository implements FruitRepositoryInterface {

	// ...
}

(5) 위와 같이 적용시키고 코드를 실행해 보자.

(6) 해당 코드를 기준으로 판매/미판매 과일의 가격을 각각 조회해 보자.

 

리포지토리 구현체 변경 후 정상 동작 확인

(7) 정상적으로 결과가 반환됨을 확인할 수 있다.

 

 

@Repository
@RequiredArgsConstructor
@Primary
public class FruitMemoryRepository implements FruitRepositoryInterface {

    // ...
}

(8) 이번엔 FruitMemoryRepository를 사용해 보자.

 

 

FruitMemoryRepository 리포지토리 구현체로 변경 후 정상 동작 확인

 

(9) 마찬가지로 정상적으로 결과가 반환되는 것을 확인할 수 있었다.

 

 

 

 

 

4. 개인 회고

(1) 기존의 코드를 Controller - Service - Repository로 분리하며 각 계층이 가져야 하는 역할을 한 번 정리해 볼 수 있었으며 계층구조로 분리하는 것이 코드의 유지보수성, 협업 측면에서 좋다는 것을 다시 한 번 생각해 볼 수 있었다.

 

(2) 앞으로 프로젝트를 진행하면서 더 좋은 계층 구조로 개발하기 위해 지켜야 할 점은 무엇인지 따로 찾아보고 공부해 봐야 할 것 같다.

 

 

 

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

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

728x90
반응형

댓글