인프런 김영한 강사님의 "스프링 입문- 코드로 배우는 스프링 부트, 웹 MVC, DB 접근 기술 " 강의를 들었고
자료 또한, 강의에서 나온 자료들 임을 알립니다!
스프링 프레임워크에서 "컨트롤러, 도메인, 레퍼지토리(DB가 아닌 메모리로 확인), 서비스"의 작동원리를
혼자서 과제를 하면서 익혀보고자 진행하였습니다.
깃허브 링크는 다음과 같습니다.
https://github.com/ksw4060/KYH_HelloSpring
우선 깃허브에 올라와있는 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 레포지토리로 변경해서
등록된 상품에 대해서, 장바구니에 담고
결제정보를 생성하는 ? 것까지 진행해보려고 합니다.
감사합니다
'Coding' 카테고리의 다른 글
[MySQL]왕초보용 갖고노는 MySQL 1주차 (0) | 2023.11.30 |
---|---|
[DRF 심화 팀 프로젝트][KPT회고록][AI웹개발][50일차TIL] 집사는 집사하느라 집못사 (0) | 2023.05.15 |
[장고 기초 프로젝트 S.A] Startinig Assignment (0) | 2023.04.10 |
[KPT회고록][독서하는12개월 팀 소개 페이지 미니프로젝트] (0) | 2023.03.19 |