[인프런 워밍업 스터디 클럽] 0기 - 백엔드 과제 #6

[인프런 워밍업 스터디 클럽] 0기 - 백엔드 과제 #6

우리는 스프링 컨테이너의 개념을 배우고, 기존에 작성했던 Controller 코드를 3단 분리해보았습니다. 앞으로 API를 개발할 때는 이 계층에 맞게 각 코드가 작성되어야 합니다! 🙂

과제 #4 에서 만들었던 API를 분리해보며, Controller - Service - Repository 계층에 익숙해져 봅시다! 👍

문제 1

과제 #4 에서 만들었던 API를 강의 내용 처럼 Controller - Service - Repository로 분리해보세요!

과제 #4에서 만들었던 API

@RequestMapping("/api/v1")
@RestController
public class FruitController {
​
    private final JdbcTemplate jdbcTemplate;
​
    public FruitController(JdbcTemplate jdbcTemplate) {
        this.jdbcTemplate = jdbcTemplate;
    }
​
    @PostMapping("/fruit")
    public void createFruit(@RequestBody FruitCreateRequest request) {
        String sql = "INSERT INTO fruit(name, warehousing_date, price) VALUES (?, ?, ?)";
        jdbcTemplate.update(sql, request.getName(), request.getDate(), request.getPrice());
    }
​
    @PutMapping("/fruit")
    public void updateSoldFruitInformation(@RequestBody FruitUpdateRequest request) {
        String readSql = "SELECT * FROM fruit WHERE id = ?";
        boolean isEmpty = jdbcTemplate.query(readSql, (rs, rowNum) -> 0, request.getId()).isEmpty();
        if (isEmpty) {
            throw new IllegalArgumentException();
        }
        String sql = "UPDATE fruit SET is_sold = 1 WHERE id = ?";
        jdbcTemplate.update(sql, request.getId());
    }
​
    @GetMapping("/fruit/stat")
    public FruitReadSalesAmountRespond readSalesFruitAmount(@RequestParam String name) {
        String salesAmountSql = "SELECT SUM(price) FROM fruit WHERE name = ? GROUP BY is_sold";
        List<Long> salesAmounts = jdbcTemplate.query(salesAmountSql, (rs, rowNum) -> rs.getLong(1), name);
​
        Long salesAmount = salesAmounts.get(0);
        Long notSalesAmount = salesAmounts.get(1);
​
        return new FruitReadSalesAmountRespond(salesAmount, notSalesAmount);
    }
}

첫번째

Controller에서 한 메서드가 가진 역할이 많다.

  • API 관련해 HTTP Body를 객체로 변환하는 역할 -> FruitController

  • 과일이 있는지 없는지 확인 후 예외처리 하는 역할 -> FruitService

  • sql문을 사용해 데이터베이스에 접근하는 역할 -> FruitRepository

@RequestMapping("/api/v1")
@RestController
public class FruitController {
​
    private final FruitService fruitService = new FruitService();
​
    private final JdbcTemplate jdbcTemplate;
​
    public FruitController(JdbcTemplate jdbcTemplate) {
        this.jdbcTemplate = jdbcTemplate;
    }
​
    @PostMapping("/fruit")
    public void saveFruit(@RequestBody FruitCreateRequest request) {
        fruitService.saveFruit(jdbcTemplate, request);
    }
​
    @GetMapping("/fruit/stat")
    public FruitReadSalesAmountRespond readSalesFruitAmount(@RequestParam String name) {
        return fruitService.readSalesFruitAmount(jdbcTemplate, name);
    }
​
    @PutMapping("/fruit")
    public void updateSoldFruitInformation(@RequestBody FruitUpdateRequest request) {
        fruitService.updateSoldFruitInformation(jdbcTemplate, request.getId());
    }
​
}
public class FruitService {
​
    private final FruitRepository fruitRepository = new FruitRepository();
​
    public void saveFruit(JdbcTemplate jdbcTemplate, FruitCreateRequest request) {
        fruitRepository.saveFruit(jdbcTemplate, request);
    }
​
    public FruitReadSalesAmountRespond readSalesFruitAmount(JdbcTemplate jdbcTemplate, String name) {
        return fruitRepository.getSalesFruitAmount(jdbcTemplate, name);
    }
​
    public void updateSoldFruitInformation(JdbcTemplate jdbcTemplate, long id) {
        if (fruitRepository.isNotExistFruit(jdbcTemplate, id)) {
            throw new IllegalArgumentException();
        }
​
        fruitRepository.updateFruit(jdbcTemplate, id);
    }
​
}
public class FruitRepository {
​
    public void saveFruit(JdbcTemplate jdbcTemplate, FruitCreateRequest request) {
        String sql = "INSERT INTO fruit(name, warehousing_date, price) VALUES (?, ?, ?)";
        jdbcTemplate.update(sql, request.getName(), request.getDate(), request.getPrice());
    }
​
    public FruitReadSalesAmountRespond getSalesFruitAmount(JdbcTemplate jdbcTemplate, String name) {
        String salesAmountSql = "SELECT SUM(price) FROM fruit WHERE name = ? GROUP BY is_sold";
        List<Long> salesAmounts = jdbcTemplate.query(salesAmountSql, (rs, rowNum) -> rs.getLong(1), name);
​
        Long salesAmount = salesAmounts.get(0);
        Long notSalesAmount = salesAmounts.get(1);
​
        return new FruitReadSalesAmountRespond(salesAmount, notSalesAmount);
    }
​
    public boolean isNotExistFruit(JdbcTemplate jdbcTemplate, long id) {
        String readSql = "SELECT * FROM fruit WHERE id = ?";
        return jdbcTemplate.query(readSql, (rs, rowNum) -> 0, id).isEmpty();
    }
​
    public void updateFruit(JdbcTemplate jdbcTemplate, long id) {
        String sql = "UPDATE fruit SET is_sold = 1 WHERE id = ?";
        jdbcTemplate.update(sql, id);
    }
​
}

두번째

Repository 가 데이터베이스 관련 로직이 들어가 있어야 되는거라 JdbcTemplate 객체가 있는건 괜찮다. 하지만 Service와 Controller에도 있으니 없애준다.

@RequestMapping("/api/v1")
@RestController
public class FruitController {
​
    private final FruitService fruitService;
​
    public FruitController(JdbcTemplate jdbcTemplate) {
        this.fruitService = new FruitService(jdbcTemplate);
    }
​
    @PostMapping("/fruit")
    public void saveFruit(@RequestBody FruitCreateRequest request) {
        fruitService.saveFruit(request);
    }
​
    @GetMapping("/fruit/stat")
    public FruitReadSalesAmountRespond readSalesFruitAmount(@RequestParam String name) {
        return fruitService.readSalesFruitAmount(name);
    }
​
    @PutMapping("/fruit")
    public void updateSoldFruitInformation(@RequestBody FruitUpdateRequest request) {
        fruitService.updateSoldFruitInformation(request.getId());
    }
​
}
public class FruitService {
​
    private final FruitRepository fruitRepository;
​
    public FruitService(JdbcTemplate jdbcTemplate) {
        this.fruitRepository = new FruitRepository(jdbcTemplate);
    }
​
    public void saveFruit(FruitCreateRequest request) {
        fruitRepository.saveFruit(request);
    }
​
    public FruitReadSalesAmountRespond readSalesFruitAmount(String name) {
        return fruitRepository.getSalesFruitAmount(name);
    }
​
    public void updateSoldFruitInformation(long id) {
        if (fruitRepository.isNotExistFruit(id)) {
            throw new IllegalArgumentException();
        }
​
        fruitRepository.updateFruit(id);
    }
​
}
public class FruitRepository {
​
    private final JdbcTemplate jdbcTemplate;
​
    public FruitRepository(JdbcTemplate jdbcTemplate) {
        this.jdbcTemplate = jdbcTemplate;
    }
​
    public void saveFruit(FruitCreateRequest request) {
        String sql = "INSERT INTO fruit(name, warehousing_date, price) VALUES (?, ?, ?)";
        jdbcTemplate.update(sql, request.getName(), request.getDate(), request.getPrice());
    }
​
    public FruitReadSalesAmountRespond getSalesFruitAmount(String name) {
        String salesAmountSql = "SELECT SUM(price) FROM fruit WHERE name = ? GROUP BY is_sold";
        List<Long> salesAmounts = jdbcTemplate.query(salesAmountSql, (rs, rowNum) -> rs.getLong(1), name);
​
        Long salesAmount = salesAmounts.get(0);
        Long notSalesAmount = salesAmounts.get(1);
​
        return new FruitReadSalesAmountRespond(salesAmount, notSalesAmount);
    }
​
    public boolean isNotExistFruit(long id) {
        String readSql = "SELECT * FROM fruit WHERE id = ?";
        return jdbcTemplate.query(readSql, (rs, rowNum) -> 0, id).isEmpty();
    }
​
    public void updateFruit(long id) {
        String sql = "UPDATE fruit SET is_sold = 1 WHERE id = ?";
        jdbcTemplate.update(sql, id);
    }
​
}

문제 2

문제 1에서 코드가 분리되면 FruitController/FruitService/FruitRepository 가 생겼을 것입니다.

기존에 작성했던 FruitRepositoryFruitMemoryRepositoryFruitMySqlRepository 로 나누고 @Primary 어노테이션을 활용해 두 Repostory를 바꿔가며 동작시킬 수 있도록 코드를 변경해보세요!😊

📌 @Qualifier 어노테이션을 사용해도 좋습니다! 🙂

FruitController

@RequestMapping("/api/v1")
@RestController
public class FruitController {
​
    private final FruitService fruitService;
​
    public FruitController(FruitService fruitService) {
        this.fruitService = fruitService;
    }
​
    @PostMapping("/fruit")
    public void saveFruit(@RequestBody FruitCreateRequest request) {
        fruitService.saveFruit(request);
    }
​
    @GetMapping("/fruit/stat")
    public FruitReadSalesAmountRespond readSalesFruitAmount(@RequestParam String name) {
        return fruitService.readSalesFruitAmount(name);
    }
​
    @PutMapping("/fruit")
    public void updateSoldFruitInformation(@RequestBody FruitUpdateRequest request) {
        fruitService.updateSoldFruitInformation(request.getId());
    }
​
}

FruitService

@Service
public class FruitService {
​
    private final FruitRepository fruitRepository;
​
    public FruitService(FruitRepository fruitRepository) {
        this.fruitRepository = fruitRepository;
    }
​
    public void saveFruit(FruitCreateRequest request) {
        fruitRepository.saveFruit(request);
    }
​
    public FruitReadSalesAmountRespond readSalesFruitAmount(String name) {
        return fruitRepository.getSalesFruitAmount(name);
    }
​
    public void updateSoldFruitInformation(long id) {
        if (fruitRepository.isNotExistFruit(id)) {
            throw new IllegalArgumentException();
        }
​
        fruitRepository.updateFruit(id);
    }
​
}

FruitRepository

@Repository
public interface FruitRepository {
​
    void saveFruit(FruitCreateRequest request);
​
    FruitReadSalesAmountRespond getSalesFruitAmount(String name);
​
    boolean isNotExistFruit(long id);
​
    void updateFruit(long id);
    
}

FruitMemoryRepository

@Primary
@Repository
public class FruitMemoryRepository implements FruitRepository {
​
    private final List<Fruit> fruits = new ArrayList<>();
    private static Long id = 1L;
​
    @Override
    public void saveFruit(FruitCreateRequest request) {
        fruits.add(new Fruit(id++, request.getName(), request.getDate(), request.getPrice()));
    }
​
    @Override
    public FruitReadSalesAmountRespond getSalesFruitAmount(String name) {
        long salesAmount = calculateSalesAmount(name);
        long notSalesAmount = calculateNotSalesAmount(name);
        return new FruitReadSalesAmountRespond(salesAmount, notSalesAmount);
    }
​
    @Override
    public boolean isNotExistFruit(long id) {
        return fruits.stream().noneMatch(fruit -> fruit.getId() == id);
    }
​
    @Override
    public void updateFruit(long id) {
        fruits.stream()
                .filter(fruit -> fruit.getId() == id)
                .forEach(fruit -> fruit.is_sold = true);
    }
​
    private long calculateSalesAmount(String name) {
        return fruits.stream()
                .filter(fruit -> fruit.getName().equals(name) && fruit.is_sold())
                .mapToLong(Fruit::getPrice)
                .sum();
    }
​
    private long calculateNotSalesAmount(String name) {
        return fruits.stream()
                .filter(fruit -> fruit.getName().equals(name) && !fruit.is_sold())
                .mapToLong(Fruit::getPrice)
                .sum();
    }
​
}

FruitMySqlRepository

@Repository
public class FruitMySqlRepository implements FruitRepository {
​
    private final JdbcTemplate jdbcTemplate;
​
    public FruitMySqlRepository(JdbcTemplate jdbcTemplate) {
        this.jdbcTemplate = jdbcTemplate;
    }
​
    @Override
    public void saveFruit(FruitCreateRequest request) {
        String sql = "INSERT INTO fruit(name, warehousing_date, price) VALUES (?, ?, ?)";
        jdbcTemplate.update(sql, request.getName(), request.getDate(), request.getPrice());
    }
​
    @Override
    public FruitReadSalesAmountRespond getSalesFruitAmount(String name) {
        String salesAmountSql = "SELECT SUM(price) FROM fruit WHERE name = ? GROUP BY is_sold";
        List<Long> salesAmounts = jdbcTemplate.query(salesAmountSql, (rs, rowNum) -> rs.getLong(1), name);
​
        Long salesAmount = salesAmounts.get(0);
        Long notSalesAmount = salesAmounts.get(1);
​
        return new FruitReadSalesAmountRespond(salesAmount, notSalesAmount);
    }
​
    @Override
    public boolean isNotExistFruit(long id) {
        String readSql = "SELECT * FROM fruit WHERE id = ?";
        return jdbcTemplate.query(readSql, (rs, rowNum) -> 0, id).isEmpty();
    }
​
    @Override
    public void updateFruit(long id) {
        String sql = "UPDATE fruit SET is_sold = 1 WHERE id = ?";
        jdbcTemplate.update(sql, id);
    }
​
}

댓글을 작성해보세요.

채널톡 아이콘