본문 바로가기
Coding

[Spring]MVC 패턴을 이용해서, 상품 만들기, 불러오기 기능해보기

by 우지uz 2024. 1. 12.

https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-%EC%9E%85%EB%AC%B8-%EC%8A%A4%ED%94%84%EB%A7%81%EB%B6%80%ED%8A%B8/dashboard

 

[지금 무료] 스프링 입문 - 코드로 배우는 스프링 부트, 웹 MVC, DB 접근 기술 강의 - 인프런

스프링 입문자가 예제를 만들어가면서 스프링 웹 애플리케이션 개발 전반을 빠르게 학습할 수 있습니다., 스프링 학습 첫 길잡이! 개발 공부의 길을 잃지 않도록 도와드립니다. 📣 확인해주세

www.inflearn.com

인프런 김영한 강사님의 "스프링 입문- 코드로 배우는 스프링 부트, 웹 MVC, DB 접근 기술 " 강의를 들었고 

자료 또한, 강의에서 나온 자료들 임을 알립니다! 

스프링 프레임워크에서 "컨트롤러, 도메인, 레퍼지토리(DB가 아닌 메모리로 확인), 서비스"의 작동원리를 

 

혼자서 과제를 하면서 익혀보고자 진행하였습니다. 

깃허브 링크는 다음과 같습니다. 

https://github.com/ksw4060/KYH_HelloSpring

 

GitHub - ksw4060/KYH_HelloSpring

Contribute to ksw4060/KYH_HelloSpring development by creating an account on GitHub.

github.com

 

우선 깃허브에 올라와있는 venv 파일에 대한 해명을 하자면 ...

스프링 강의를 듣기 전에, 예전에 파이썬 Django 프로젝트를 하던 습관?이 남아서 였습니다. 

바로 2시간뒤에 , 김영한님의 설명을 듣고 깨달았죠. 애초에 스프링은 맨처음 스프링 셋팅을 할때 라이브러리들을 함께 설치해주는 구나 .. 

 

그리고 venv 라는 것 자체가 파이썬 가상환경을 말하는 것인데, 스프링에서도 가상환경을 따로 설정해서 라이브러리들을 관리해야 하는 줄 알았습니다 ㅎㅎ ! 해명은 끝났구요 . venv 있다고해서 크게 문제될 건 없으니까 그냥 냅뒀는데 ... 
스프링 MVC 강의 복습이 끝나는대로, 다시 깃허브 레퍼지토리를 생성해서 넣도록 하겠습니다.

 

그리고 , 현재 까지 DB 를 따로 셋팅하지 않고 메모리를 통해 API를 불러오고 있다보니 
상품 모델에 대해 리로드가 되면, 싹다 삭제가 되어서 
장바구니 모델을 할때 JPA 관련 DB를 생성해서 확인해볼 수 있도록 하겠습니다. 
현재 진행형입니다. 

 

스프링 MVC 와 템플릿 엔진

여기서 MVC 란 Model, View, Controller 를 의미하고

이 MVC 구조를 , 스프링 컨테이너가 처리해줍니다. 

클라이언트를 통해 요청된 주소는, 내장 톰켓 서버를 거쳐 스프링 컨테이너로 전달되고

컨트롤러가 해당 url에 대한 요청을 처리해준다면, 컨트롤러가 반환해주는 hello-template 라는 파일 이름으로 

viewResolver가 클라이언트에게 반환해줍니다. 

 

스프링 컨테이너에서는, 해당 url에 대한 컨트롤러를 먼저 확인한 후 

컨트롤러에 해당 url 에 대한 HTTP 메소드가 내장 톰켓 서버에 없는 경우 

hello-static.html 정적 템플릿으로 반환해주는 것도 신기했습니다. 

스프링 컨테이너에서 이미, 정적 템플릿에 대한 처리도 해주는 것 같네요. 

서론이 길었는데요, 제가 강의에서 들었던 내용들을 모두다 기재하는 것은, 아직 초보자로서 부족하다고 생각이 들어서 

배운 것들을 토대로 코드를 작성해보았습니다. 강의를 보셨던 분이라면, 충분히 이해할만한 내용이라 생각이 드는데요 

 

뭐 ... 결국엔 강의에서 Member 에 대해 MVC 패턴을 설명해주셔서 ,
여차저차 강의도 돌려보기도 하고 이해가 안되는 부분들은 구글링 + 코드를 보면서 이해하긴 했는데 
이걸 정확히 따라할 순 없겠다? 라는 생각이 들어서 

"상품" 모델에 대해서 자체 프로젝트를 진행하였습니다. 

거기서도, 계층 구조에 "KEY point"를 잡고 이해하려고 노력했구요 

거기서 Dependency Injection , 의존성 주입에 대한 개념을 이해하기 위해서 

유튜브 강의도 5개정도는 찾아보고, 공식 문서나 나무위키, 블로그도 꽤 찾아봤습니다. 

 

결국 컨트롤러 > 서비스 > 레퍼지토리 > DB의 구조와 같이 설계를 하는 것에 대해서

개발자는 어떻게 의존성을 가질 지 전체적인 설계가 되어진 상태에서

의존성에 대한 이해 & 구현이었습니다. 

 

의존과 객체지향 프로그래밍에 대해 조금 더 이해하고자 

Wanted 사이트에서 진행하는 
2024년 백엔드 첼린지 온보딩 - 객체지향 프로그래밍을 수강중에 있습니다.
1월 2일부터 1월 15일까지 진행되고 있습니다. 

상품 관련 컨트롤러

package hello.hellospring.controller;

import hello.hellospring.domain.Member;
import hello.hellospring.domain.Product;
import hello.hellospring.service.ProductService;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;

import java.util.List;

@Controller
public class ProductController {
    private final ProductService productService;

    public ProductController(ProductService productService) {
        this.productService = productService;
    }

    @GetMapping("/products/new")
    public String createForm() {
        return "products/createProductsForm";
    }

    @PostMapping("/products/new")
    public String create(ProductsForm form) {
        Product product = new Product();
        product.setName(form.getName());
        product.setPrice(form.getPrice());

        productService.createProduct(product);

        return "redirect:/";
    }

    @GetMapping("/products")
    public String userList(Model model) {
        List<Product> products = productService.findProducts();
        model.addAttribute("products", products);
        return "products/productsList";
    }
}

ProductController 를 통해서 , HTTP method 에 대한 맵핑이 이루어 지고 있는데요. 

먼저 DI 를 통해, 서비스 클래스를 주입하고 있는 것을 확인할 수 있습니다. 

 

서비스는 , 상품에 대한 서비스 로직들을 담당하고 있는데 

HTTP method 를 통해서 상품을 생성하고, 조회하는 로직등을 처리해야 하기 때문에 

서비스를 의존성 주입하였습니다. 

 

상품 관련 서비스로직

package hello.hellospring.service;

import hello.hellospring.domain.Member;
import hello.hellospring.domain.Product;
import hello.hellospring.repository.ProductRepository;
import org.springframework.stereotype.Service;

import java.util.List;
import java.util.Optional;
@Service
public class ProductService {

    private final ProductRepository productRepository;

    public ProductService(ProductRepository productRepository) {
        this.productRepository = productRepository;
    }

    public Long createProduct(Product product) {
        // 같은 이름이 있는 중복 회원이 있으면 안된다.
        validateDuplicateProduct(product);

        productRepository.save(product);
        return product.getId();
    }

    /**
     *
     * 상품이름 중복에 대한 유효성 검사
     */
    private void validateDuplicateProduct(Product product) {
        productRepository.findByName(product.getName())
                .ifPresent(p -> {
                    throw new IllegalStateException("이미 존재하는 상품입니다.");
                });
    }

    /**
     * 전체 상품 조회
     */
    public List<Product> findProducts() {

        return productRepository.findProductAll();
    }
    /**
     * 회원 조회
     */
    public Optional<Product> findOneProduct(Long productId) {
        return productRepository.findById(productId);
    }
}

서비스 클래스에서는, 실제로 클라이언트 입장에서 처리되는 로직들을 수행합니다.

여기서는 상품을 생성하고, 
상품 전체를 조회하고 
특정 상품 id 에 대해서, 한 상품에 대해서 조회하는 기능을 수행합니다. 

컨트롤러 클래스와는 다르게, 
서비스는 레퍼지토리 클래스를 의존성 주입하게 됩니다. 

왜냐하면, 서비스 로직을 수행하면서 
레퍼지토리 , 즉 DB나 메모리에 상품을 생성, 조회하는 값들에 대해서 
데이터를 처리해야하는 로직이 필요하기 때문입니다. 

 

도메인(상품 모델)

package hello.hellospring.domain;

public class Product {
    private Long id;
    private String name;
    private Long price;

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Long getPrice() {
        return price;
    }

    public void setPrice(Long price) {
        this.price = price;
    }
}

 

 

도메인에서는, 상품 모델을 생성해서 

상품에 대한 id, name, price 를 가져오거나, 생성해주는 역할을 구현합니다. 

 

도메인에 있는 상품모델은, 컨트롤러, 서비스, 레퍼지토리에서 모두 사용합니다. 

 

해당 서비스 로직에 대해서 , 레퍼지토리 테스트도 진행해보았는데 

레퍼지토리 save 함수에서 , 저장해주는 로직을 넣어주지 않아 

테스트에 실패하는 디버깅이 있었지만 

 

무사히 잘 진행되었습니다. 

package hello.hellospring.repository;

import hello.hellospring.domain.Product;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Test;

import java.util.List;

import static org.assertj.core.api.Assertions.assertThat;

public class MemoryProductRepositoryTest {
    MemoryProductRepository productRepository = new MemoryProductRepository();

    @AfterEach
    public void afterEach() { productRepository.clearProductStore();
    }




    @Test
    public void save() {
        Product product = new Product();
        product.setName("product1");
        product.setPrice(1000L);

        productRepository.save(product);

        Product result = productRepository.findById(product.getId()).get();
        assertThat(product).isEqualTo(result);
    }

    @Test
    public void findByName() {
        Product product1 = new Product();
        product1.setName("product1");
        product1.setPrice(1000L);
        productRepository.save((product1));

        Product product2 = new Product();
        product2.setName("product2");
        product2.setPrice(2000L);
        productRepository.save((product2));

        Product result1 = productRepository.findByName("product1").get();
        Product result2 = productRepository.findByName("product2").get();

        assertThat(result1).isEqualTo(product1);
        assertThat(result2).isEqualTo(product2);
    }

    @Test
    public void findProductAll() {
        Product product3 = new Product();
        product3.setName("product3");
        productRepository.save((product3));

        Product product4 = new Product();
        product4.setName("product4");
        productRepository.save((product4));

        List<Product> result = productRepository.findProductAll();
        assertThat(result.size()).isEqualTo(2);
    }
}

 

향후 , 과제를 수행하는 방향은 

메모리 레포지토리 -> DB 레포지토리로 변경해서 

등록된 상품에 대해서, 장바구니에 담고 

결제정보를 생성하는 ? 것까지 진행해보려고 합니다. 

 

감사합니다