[Spring Boot 스터디] 4회차

minhyeok·2023년 3월 15일
1
post-thumbnail

학습 내용

Controller, Service, Repository 역할 구분 , 분리

1. Controller (⊂ Presentation Layer)

  • 클라이언트의 요청 및 응답을 처리
  • 들어온 요청을 Mapping 해주고, 요청자에게 응답을 전달하는 역할
  • @Controller 어노테이션을 사용하여 작성된 Controller 클래스가 이 계층에 포함됨

2. Service (⊂ Business/Service Layer)

  • 애플리케이션 비즈니스 로직 처리와 비즈니스와 관련된 적합성 검증
  • Controller와 Repository 사이를 연결하는 역할로 두 계층이 직접적으로 통신하지 않게함
  • @Service 어노테이션을 사용하여 작성된 Service 구현 클래스가 이 계층에 포함됨

3. Repository (⊂ Data Access Layer)

  • 데이터베이스에 접근하여 데이터를 CRUD하는 계층
  • 영구 데이터를 빼내어 객체화 시키며, 영구 저장소에 데이터를 저장, 수정, 삭제하는 계층
  • @Repository 어노테이션을 사용하여 작성된 Repository 구현 클래스가 이 계층에 속함

MVC 패턴

MVC패턴은 디자인패턴 중 하나이다. 디자인 패턴이란 프로그램이나 어떤 특정한 것을 개발하는 중에 발생했던 문제점들을 정리해서 상황에 따라 간편하게 적용해서 쓸 수 있는 것을 정리하여 특정한 "규약"을 통해 쉽게 쓸 수 있는 형태로 만든 것을 말한다.

MVC 는 Model, View, Controller의 약자 이다. 하나의 애플리케이션, 프로젝트를 구성할 때 그 구성요소를 세가지의 역할로 구분한 패턴이다.

참고 링크 : Controller 와 Service 의 책임 나누기

상품 등록, 조회 API 역할 구분하기

(연습문제) 상품 등록 api 역할 구분하기

상품 이름과 가격을 Body에 담아서 Post 요청을 보내면 랜덤으로 id값을 추가해서 데이터베이스에 설계해둔 Product 테이블에 데이터가 추가되는 기능이 필요하다.

{
	name: 키보드,
	price: 60,000
}
Product {
	id: 13413
	name: 키보드
	price: 60,000
}

이때 이 기능들을 역할에 나누어 분리해보자.

  1. [POST] api/products 로 들어온 요청 mapping → Controller
  2. 요청에서 받은 값에 랜덤 id값을 추가해서 Product 객체를 만들어 repository에 전달 → Service
  3. Product 데이터 실제 저장소(데이터베이스)에 저장 → Repository
  4. 생성한 랜덤 id 값을 요청에 대한 응답으로 return → Controller

(연습문제) 상품 조회 api 역할 구분하기

상품 id를 query parameter로 추가해서 Get 요청을 보내면 데이터베이스에서 해당 상품에 대한 정보를 가져오고, 가격 정보를 ‘원’이 아닌 ‘달러’ 단위로 변경해서 조회되는 기능이 필요하다.

[GET] api/products/13413
{
	 id: 13413,
	 name: 키보드,
	 price: 45.98$
}

이때 이 기능들을 역할에 나누어 분리해보자.

  1. [GET] api/products/13413 로 들어온 요청 mapping → Controller
  2. 요청에서 받은 id값 repository에 전달 → Service
  3. 전달받은 id값으로 데이터베이스에서 상품 조회 → Repository
  4. repository에서 받은 상품데이터에서 가격을 달러 단위로 변경 → Service
  5. 요청자에게 Service에서 받은 데이터 응답을 return → Controller

Bean 이란?

아직 이해가 잘 가지 않아 추후 내용 추가 예정!

4회차 미션

요구사항을 만족하는 api 만들기

요구사항

  • 상품에 대한 정보는 상품명과, 가격을 포함해야 한다.
  • 아직 데이터베이스를 연동하지 않고 Repository에 List 형태로 상품들을 저장한다. (서버 재실행 시 휘발)
    • Repository에 상품 정보들을 미리 저장해둔다.

단계별 구현

@RestController
@RequestMapping("api/products")
public class ProductController {

    @PostMapping("")
    public Product getProduct(@RequestBody Product product){
        return product;
    }
public class Product {
    private final String name;
    private final Long price;

    public Product(String name,Long price) {
        this.name = name;
        this.price = price;
    }
}

위와 같은 코드를 통해 Controller 에서 요청 받은 name과 price를 그대로 return 하도록 하였다.
이 때 id를 랜덤으로 생성해야하는데, 이 부분이 구현이 어려워 계속 고민중이다. -> 구현하지 않아도 된다.

@GetMapping("")
    public String getID(@RequestParam int id){
        return "ID: " + id;
    }

id를 GET을 통해 파라미터로 받을 때, 위 코드를 통해 그대로 return 하도록 하였다. 랜덤으로 생성하지는 못해 일단 다음 순서로 진행해보았다.
products 라는 이름의 ArrayList 에 Product 객체 하나를 추가할 수 있는 메소드를 구현해보았다. add를 통해 구현하였다. 인자로 product를 넘긴다.

public void saveProduct(Product product) {
        this.products.add(product);
    }

위와 같이 POST 를 통해 Product를 저장하고, GET 을 통해 findAll 함수로 지금까지 저장된 Product 들을 호출하도록 구현했다.
이제, 특정 상품을 상세 조회하도록 하는 findOne 함수를 구현해볼 것이다. findOne 함수를 호출하면 해당 특정 상품을 상세 조회하도록 해야한다.

진행하는데 문제가 있던 부분은 아래에 있다.

@GetMapping("")
    public List<Product> findAll() {
        return productService.findAll();
    }

    @GetMapping("")
    public Product findOne(@RequestParam Integer id) {
        return productService.findOne(id);
    }

이 부분과 같이 GetMapping 을 두번해야하는데, 처음에는 products/id 패스로 따로 주는 줄 알았지만
그게 아니라 [get]api/products 를 GET 할 때는 전체 product가 조회되도록 해야하고,
[get]api/products/id=1 를 GET 할 때는 특정 product가 조회되도록 해야한다. ID 값을 파라미터로 줘야하는데, 이 부분이 어려워 구현 중에 있다.
멘토님의 도움을 통해 구현하였다.

@GetMapping("")
    public List<Product> findAll() {
        return productService.findAll();
    }

    //@GetMapping(params = "id")
    @GetMapping(value = "", params = "name")
    public Product findOne(@RequestParam String name) {
        return productService.findOne(name);
    }

위와 같이 @GetMapping 어노테이션을 두번 쓰지만 중복된 URL 이므로 , params 를 지정하여 추가 처리를 해주어야한다.
@Getmapping에서 하나의 인자만 넣어줄 때는 그 값이 value이다.
예를 들어 @GetMapping("api/products") 와 @GetMapping(value="api/products")가 같다.
그런데 params라는 인자를 하나 더 넣어야하기 때문에 value의 명시가 필요하게 된다. 내가 작성한 코드는 @RequestMapping("api/products") 에서 path를 지정해주었다.
그런데 주석처리한 부분과 같이 params 만 지정해주어도 동작 한다!?

이것 저것 넣을 때 나도 params 를 지정했었는데 아마 그때 안된건 params = "{id}" 이런식으로 해서 동작이 되지 않았던 것 같다. 제대로 알아야겠다,, 이것만 몇시간 붙잡고 있었다 ㅠ

참고 링크 1

참고 링크 2

참고 링크 3

API 추가 기능 (이름으로 조회)

public Product findOne(String name) {
        //return products.get(id);
        //이름으로 입력받을때 상세조회
        for (Product product : this.products) {
            if(product.getName().equals(name)) {
                return product;
            }
        }
        return null;
    }

위와 같이 이름으로 상세조회하는 findOne 함수를 구현하였다. id(index)를 통해 구현하는 것은 GetMapping 부분이 어려웠지 여기는 간단하여 금방 구현하였다.
이름으로 입력받을 때 상세 조회를 하도록 변경하여 코드를 작성하였다.

위와 같이 특정 상품인 키보드를 쿼리 스트링으로 주어 조회하도록 하였다!

위 미션을 구현한 프로젝트는 깃허브에 push 해 놓았다.
깃허브 링크

정리

  • 배운 내용, 깨달은 점
    이번 시간에는 직접 API를 구현해보았다. Postman 프로그램을 통해 직접 그 과정을 확인해보았다. 구현 중 가장 애먹은 부분이 @GetMapping 할 때, 여러개가 되는지, 그렇다면 [get]/api/products 일때와 [get]/api/products?id=1 과 같은 요청을 할 때 어떻게 구별되어야 하는지가 가장 힘들었다.
    정말 몇시간 동안 구글링 하고 명확한 답을 주는 예제가 없어 이것 저것 정말 다 해보았다고 생각했는데 계속 안돼서 너무 답답했지만,, 멘토분과 친구에게 물어보아 해결했다!!
    생각보다 간단했다. params 를 지정해주는 방법은 나도 했었는데 아마 이상한 중괄호를 넣어 제대로 동작하지 않았던 것 같다! 이게 해결되니 나머지는 금방금방 해결하였고 미션을 마무리 하였다.
    사실 욕심상 [이미 동일한 이름의 상품이 있다면 저장 실패]이나, [가격을 원화와 달러를 구분해서 조회하는 api 로 수정] 등과 같은 다른 심화 미션까지 진행하고 싶었는데 시간이 예정보다 너무 오래 소요되어 진행하지 못했다 ㅠㅠ 이 부분은 따로 공부하며 진행해볼 생각이다!

  • 어려웠던 점, 반성하고 싶은 점 / 개선할 방법
    위에 적어놓은 부분이 어려웠지만 잘 해결하였다, 구글링을 잘 한다고 했는데 부족했던 것 같다. 그리고 좀 더 인자에 대한 조건을 확인해야겠다. 이 부분이 굉장히 약한것 같다..

  • 궁금한 점

@GetMapping(params = "id")

@GetMapping(value = "", params = "id")

이 때 둘다 정상적으로 동작이 되는데요! value 는 따로 명시하지 않아도 되는건가요?
그리고 이러한 부분을 검색할 때 어떤 식으로 검색하면서 찾아보면 좋을까요?
저는 GetMapping 사용법, 다중매핑, GetMapping RequestParam 이런식으로 구글링 했었는데 GetMapping의 정확한 사용법 같은 걸 찾는게 어렵더라구요.. 책을 사는게 좋을까요?

1개의 댓글

comment-user-thumbnail
2023년 3월 19일

네네 value는 따로 명시하지 않아도 됩니다!
제일 좋은 방법은 공식 문서를 보고, 직접 사용해보는 겁니다~ 책도 좋지만 원하는 답이 있는 책을 찾는건 또 구글링이 필요하고, 매번 사기 어려우니까요 ㅎㅎ

답글 달기