시작하기 전에
싸다9는 2023년 8월부터 11월까지 진행했던 프로젝트로 자취생을 위한 할인 판매 서비스이다. 과도한 트래픽을 처리해보는 경험을 하고 싶어 오후 9시부터 여러 자취생품을 80% 할인해서 선착순으로 판매하자는 전략을 세웠다. 결과는 1분 안에 모든 재고가 다 팔릴 정도로 인기가 많았으며 단시간에 매우 많은 요청이 들어오게 하는 데 성공하였다.
이 프로젝트를 다시 개발해보면서 Spring 지식, 트래픽 처리를 위한 Lock 개념, AWS를 활용한 서버와 DB 세팅, 프런트 스킬까지 되돌아보려고 한다.
I. Feedback 엔티티
Feedback은 홈페이지에서 사용자들의 의견을 받기 위해 칸이 존재했다. 그 부분을 위해 만들어볼 것이다. 항상 그랬듯이 domain 패키지 안에 Feedback.java, repository 안에 FeedbackRepository.java, service 안에 FeedbackService.java 파일을 생성한다.
domain / Feedback.java
package powersell.cheapat9.domain;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.Id;
import lombok.Getter;
import lombok.Setter;
@Entity
@Getter @Setter
public class Feedback {
@Id @GeneratedValue
@Column(name = "feedback_id")
private Long id;
private String content;
}
repository / FeedbackRepository.java
package powersell.cheapat9.repository;
import jakarta.persistence.EntityManager;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Repository;
import powersell.cheapat9.domain.Feedback;
import java.util.List;
@Repository
@RequiredArgsConstructor
public class FeedbackRepository {
private final EntityManager em;
public void save(Feedback feedback) { em.persist(feedback); }
public List<Feedback> findAll() {
return em.createQuery("select f from Feedback f", Feedback.class)
.getResultList();
}
}
service / FeedbackService.java
package powersell.cheapat9.service;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import powersell.cheapat9.domain.Feedback;
import powersell.cheapat9.repository.FeedbackRepository;
import java.util.List;
@Service
@Transactional(readOnly = true)
@RequiredArgsConstructor
public class FeedbackService {
private final FeedbackRepository feedbackRepository;
@Transactional
public Long saveFeedback(Feedback feedback) {
feedbackRepository.save(feedback);
return feedback.getId();
}
public List<Feedback> findFeedbacks() { return feedbackRepository.findAll(); }
}
II. Controller 추가
프런트엔드에서 UI를 담당하므로, Controller는 RESTful API를 통해 요청을 처리하기만 하면 된다.
controller / HomeController.java
package powersell.cheapat9.controller;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
@Controller
@Slf4j
public class HomeController {
@RequestMapping("/")
public String home() {
log.info("Home page");
return "home";
}
}
- @Slf4j: Lombok에서 제공하는 로그 기능을 사용하기 위한 어노테이션이다. 클래스 내부에서 log 객체(log.info(), log.debug() 등)를 자동으로 생성한다.
- @RequestMapping: Spring MVC에서 특정 URL과 해당하는 컨트롤러 메서드를 매핑하는 어노테이션이다.
controller / ItemForm.java
package powersell.cheapat9.controller;
import lombok.Getter;
import lombok.Setter;
@Getter @Setter
public class ItemForm {
public Long id;
public String name;
public int originalPrice;
public int price;
public int stockQuantity;
}
controller / ItemController.java
package powersell.cheapat9.controller;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import powersell.cheapat9.domain.Item;
import powersell.cheapat9.service.ItemService;
import java.util.List;
@Controller
@RequiredArgsConstructor
public class ItemController {
private final ItemService itemService;
@GetMapping("/")
public String list(Model model) {
List<Item> items = itemService.findItems();
model.addAttribute("items", items);
return "home";
}
@GetMapping("/items/new")
public String createForm(Model model) {
model.addAttribute("form", new ItemForm());
return "items/createItemForm";
}
@PostMapping("/items/new")
public String create(ItemForm form) {
Item item = new Item();
item.setName(form.getName());
item.setOriginalPrice(form.getOriginalPrice());
item.setPrice(form.getPrice());
item.setDiscountRate((form.getOriginalPrice() - form.getPrice()) * 100 / form.getOriginalPrice());
item.setStockQuantity(form.getStockQuantity());
itemService.saveItem(item);
return "redirect:/";
}
@PostMapping("/items/{itemId}/detail")
public String itemDetailForm(@PathVariable("itemId") Long itemId, Model model) {
Item item = itemService.findOne(itemId);
model.addAttribute("item", item);
return "items/detailItemForm";
}
}
- @GetMapping: 클라이언트가 GET 요청을 보내면 해당 메서드가 실행된다.
- @PostMapping: 클라이언트가 POST 요청을 보내면 해당 메서드가 실행된다.
III. 전체 구조 수정 필요
이렇게 Controller를 만들다 보니 RestController로 만들면 복잡하게 만들 필요가 없다는 점이 기억났다. Controller를 RestController로 바꾸다 보니, Repository에서도 JPARepository를 extend해서 받으면 CRUD(Create, Read, Update, Delete) 요청에 대해 코드를 따로 작성할 필요가 없다는 점도 기억났다. 아무래도 전체 구조에 공사가 필요하겠다. 다음 글에서 해보자.
'Study & Review > Project Refinement' 카테고리의 다른 글
[프로젝트 재완성] 싸다9 - 5부: Item, Order 구조 공사 (0) | 2025.02.15 |
---|---|
[프로젝트 재완성] 싸다9 - 3부: Order 데이터 처리해보기 (+N+1 문제) (0) | 2025.02.13 |
[프로젝트 재완성] 싸다9 - 2부: Item 데이터 처리해보기 (+Transaction, Lock) (0) | 2025.02.12 |
[프로젝트 재완성] 싸다9 - 1부: 환경 및 도메인·컨트롤러 세팅 (0) | 2025.02.12 |