fridge-web 프로젝트에서도 개인화 작업을 진행한다.
먼저, FridgeCommand 클래스에 userId 필드를 추가한다.
다음은 FridgeController에서 fridge POST API를 호출하는 부분을 수정한다.
processRegistrationFridge 메서드는 파라미터에 fridgeCommand 객체 바인딩으로 요청변수들을 전달받아서 nickname 만을 restTemplate postForEntity에 전달했었다. 변경된 점은 fridgeCommand 파라미터를 그대로 요청파라미터에 전달하는것이다.
registerFridgeForm, updateFridgeForm 뷰페이지에서 userId를 input 필드로 추가한다.
물론, 사용자가 회원번호를 알수도없고, 입력할 필요도 없지만 지금은 테스트 수행을 위해서 임시로 input 값을 생성한다. 나중에 회원번호 input필드는 security 가 적용되면 사라진다.
FridgeController에서 개인화된 냉장고를 표현하는 메서드를 추가한다. GET /fridges/me 이렇게 요청하면 개인별 회원번호를 API서버에 전달해서 데이터가 있으면 fridge 데이터를 받고, 없다면 생성해서 응답받는다.
myFridge 메서드는 API서버에 GET /fridges/me/{userId} 방식으로 HTTP 통신해서 응답상태코드가 204 NO_CONTENT 일 경우, 새로운 Fridge 객체를 생성해서 뷰페이지로 객체를 전달한다.
회원번호는 하드코딩해서 전달한다. 닉네임은 기본값으로 myFridge를 전달한다.
개인화가 되고, 개인당 하나의 Fridge객체만을 가지도록 제약사항을 가지므로 fridges 목록/등록/수정 페이지는 더이상 필요없어졌다. 코드를 정리한다.
코드가 정리되고나면 테스트를 실행해서 문제가 없는지 확인한다.
Fridge 컨트롤러에 restTemplate을 이용한 API 서버와의 통신을 별도의 서비스 클래스로 옮기고 서비스클래스를 호출하는 방식으로 수정한다.
FridgeRestService 클래스명으로 서비스를 작성한다.
loadByUserId, generate 두 메서드는 private으로 감추고 loadMyFridge 메서드가 두 메서드를 조합해서 사용한다. 다음은 FridgeController의 수정 코드이다.
식품목록 기능은 fridges에서 서비스되므로 삭제하고 식품의 추가/수정/삭제 뷰는 그대로 사용한다.
FoodController 에서 사용하는 RestTemplate 관련 기능들도 서비스 클래스로 옮긴다.
FoodRestService 클래스를 작성한다.
FoodController에서 각각의 restTemplate 기능들을 옮겼다. 다음은 FoodController 수정 소스코드다.
클래스 선언부에 매핑 URL이 /foods에서 /fridges/foods 로 변경했고, SessionAttributes 애너테이션이 추가했다. SessionAttribute 애너테이션으로 ModelAttribute 에 담기는 객체를 HTTP 세션에 저장해서 동일한 키를 통한 호출은 세션에 객체가 저장되어 있으면, 세션 데이터를 사용해서 통신 빈도를 줄인다.
foods 메서드(목록 기능)가 삭제되었고 나머지 메서드들은 그대로 사용한다. 방금 작성한 FoodRestService 클래스 메서드들을 이용해서 코드를 정리했다.
등록/수정/삭제 처리 후 리다이렉트할 URL이 /fridges/me 로 변경됬다.
테스트를 실행해서 문제가 없는지 확인한다.
이전글: 냉장고 가계부 프로젝트 22
다음글: 냉장고 가계부 프로젝트 24
먼저, FridgeCommand 클래스에 userId 필드를 추가한다.
public class FridgeCommand { private Integer id; @NotNull @Size(min=2, max=15) private String nickname; private List<FoodCommand> foods; private Long userId; public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public String getNickname() { return nickname; } public void setNickname(String nickname) { this.nickname = nickname; } public List<FoodCommand> getFoods() { return foods; } public void setFoods(List<FoodCommand> foods) { this.foods = foods; } public Long getUserId() { return userId; } public void setUserId(Long userId) { this.userId = userId; } }
다음은 FridgeController에서 fridge POST API를 호출하는 부분을 수정한다.
@Controller @RequestMapping("/fridges") public class FridgeController { ... @PostMapping("/add") public String processRegistrationFridge(@Valid FridgeCommand fridgeCommand, Errors errors, RedirectAttributes ra) { if(errors.hasErrors()) { return "fridges/registerFridgeForm"; } ResponseEntity<FridgeCommand> response = restTemplate.postForEntity("/fridges", fridgeCommand, FridgeCommand.class); if(response.getStatusCode().is2xxSuccessful()) { ra.addFlashAttribute("registerMessage", fridgeCommand.getNickname() + "을 생성했습니다."); } return "redirect:/fridges"; } ... }
processRegistrationFridge 메서드는 파라미터에 fridgeCommand 객체 바인딩으로 요청변수들을 전달받아서 nickname 만을 restTemplate postForEntity에 전달했었다. 변경된 점은 fridgeCommand 파라미터를 그대로 요청파라미터에 전달하는것이다.
registerFridgeForm, updateFridgeForm 뷰페이지에서 userId를 input 필드로 추가한다.
물론, 사용자가 회원번호를 알수도없고, 입력할 필요도 없지만 지금은 테스트 수행을 위해서 임시로 input 값을 생성한다. 나중에 회원번호 input필드는 security 가 적용되면 사라진다.
<label for="userId">회원번호: <input type="text" th:field="*{userId}" /> </label>
FridgeController에서 개인화된 냉장고를 표현하는 메서드를 추가한다. GET /fridges/me 이렇게 요청하면 개인별 회원번호를 API서버에 전달해서 데이터가 있으면 fridge 데이터를 받고, 없다면 생성해서 응답받는다.
private static final String DEFAULT_NICKNAME = "myFridge"; private static final long USER_ID = 1004L; @GetMapping("/me") public String myFridge(Model model) { ResponseEntity<FridgeCommand> response = restTemplate.getForEntity("/fridges/me/" + USER_ID, FridgeCommand.class); if(response.getStatusCode().is2xxSuccessful()) { if(response.getStatusCode() == HttpStatus.OK) { model.addAttribute("fridge", response.getBody()); } else if(response.getStatusCode() == HttpStatus.NO_CONTENT) { FridgeCommand fridgeCommand = new FridgeCommand(); fridgeCommand.setNickname(DEFAULT_NICKNAME); fridgeCommand.setUserId(USER_ID); ResponseEntity<FridgeCommand> createResponse = restTemplate.postForEntity("/fridges", fridgeCommand, FridgeCommand.class); if(createResponse.getStatusCode().is2xxSuccessful()) { model.addAttribute("fridge", response.getBody()); } } } return "fridges/fridge"; }
myFridge 메서드는 API서버에 GET /fridges/me/{userId} 방식으로 HTTP 통신해서 응답상태코드가 204 NO_CONTENT 일 경우, 새로운 Fridge 객체를 생성해서 뷰페이지로 객체를 전달한다.
회원번호는 하드코딩해서 전달한다. 닉네임은 기본값으로 myFridge를 전달한다.
개인화가 되고, 개인당 하나의 Fridge객체만을 가지도록 제약사항을 가지므로 fridges 목록/등록/수정 페이지는 더이상 필요없어졌다. 코드를 정리한다.
코드가 정리되고나면 테스트를 실행해서 문제가 없는지 확인한다.
Fridge 컨트롤러에 restTemplate을 이용한 API 서버와의 통신을 별도의 서비스 클래스로 옮기고 서비스클래스를 호출하는 방식으로 수정한다.
FridgeRestService 클래스명으로 서비스를 작성한다.
@Service public class FridgeRestService { @Autowired private RestTemplate restTemplate; private static final String DEFAULT_NICKNAME = "myFridge"; private static final long USER_ID = 1004L; public FridgeCommand loadMyFridge() { FridgeCommand fridge = loadByUserId(USER_ID); if(fridge == null) { return generate(DEFAULT_NICKNAME, USER_ID); } return fridge; } FridgeCommand loadByUserId(long userId) { ResponseEntity<FridgeCommand> response = restTemplate.getForEntity("/fridges/me/" + USER_ID, FridgeCommand.class); if(response.getStatusCode() == HttpStatus.OK) { return response.getBody(); } return null; } FridgeCommand generate(String nickname, long userId) { FridgeCommand fridgeCommand = new FridgeCommand(); fridgeCommand.setNickname(nickname); fridgeCommand.setUserId(userId); ResponseEntity<FridgeCommand> response = restTemplate.postForEntity("/fridges", fridgeCommand, FridgeCommand.class); if(response.getStatusCode() == HttpStatus.CREATED) { return response.getBody(); } return null; } }
loadByUserId, generate 두 메서드는 private으로 감추고 loadMyFridge 메서드가 두 메서드를 조합해서 사용한다. 다음은 FridgeController의 수정 코드이다.
@Controller @RequestMapping("/fridges") public class FridgeController { @Autowired private FridgeRestService fridgeRestService; @GetMapping("/me") public String myFridge(Model model) { model.addAttribute("fridge", fridgeRestService.loadMyFridge()); return "fridges/fridge"; } }
식품목록 기능은 fridges에서 서비스되므로 삭제하고 식품의 추가/수정/삭제 뷰는 그대로 사용한다.
FoodController 에서 사용하는 RestTemplate 관련 기능들도 서비스 클래스로 옮긴다.
FoodRestService 클래스를 작성한다.
@Service public class FoodRestService { @Autowired private RestTemplate restTemplate; public FoodCommand create(FoodCommand foodCommand) { ResponseEntity<FoodCommand> response = restTemplate.postForEntity("/foods", foodCommand, FoodCommand.class); if(response.getStatusCode() == HttpStatus.CREATED) { return response.getBody(); } return null; } public FoodCommand loadById(long id) { ResponseEntity<FoodCommand> response = restTemplate.getForEntity("/foods/{id}", FoodCommand.class, id); if(response.getStatusCode() == HttpStatus.OK) { return response.getBody(); } return null; } public void update(FoodCommand foodCommand, long id) { restTemplate.put("/foods/{id}", foodCommand, id); } public void delete(long id) { restTemplate.delete("/foods/{id}", id); } }
FoodController에서 각각의 restTemplate 기능들을 옮겼다. 다음은 FoodController 수정 소스코드다.
@Controller @RequestMapping("/fridges/foods") @SessionAttributes("foodCommand") public class FoodController { @Autowired private FoodRestService foodRestService; @GetMapping("/add") public String registerFoodForm(FoodCommand foodCommand, Model model) { model.addAttribute("foodCommand", foodCommand); return "foods/registerFoodForm"; } @PostMapping("/add") public String processRegistrationFood(@ModelAttribute @Valid FoodCommand foodCommand, Errors errors, RedirectAttributes ra, SessionStatus sessionStatus) { if(errors.hasErrors()) { return "foods/registerFoodForm"; } if(foodRestService.create(foodCommand) != null) { ra.addFlashAttribute("message", "식품을 저장했습니다."); sessionStatus.setComplete(); } return "redirect:/fridges/me"; } @GetMapping("/{id}") public String updateFoodForm(@PathVariable long id, Model model) { model.addAttribute("foodCommand", foodRestService.loadById(id)); return "foods/updateFoodForm"; } @PutMapping("/{id}") public String processUpdateFood(@PathVariable long id, @ModelAttribute @Valid FoodCommand foodCommand, Errors errors, RedirectAttributes ra, SessionStatus sessionStatus) { if(errors.hasErrors()) { return "foods/updateFoodForm"; } foodRestService.update(foodCommand, id); ra.addFlashAttribute("message", "식품을 저장했습니다."); sessionStatus.setComplete(); return "redirect:/fridges/me"; } @GetMapping("/delete/{id}") public String deleteFood(@PathVariable long id, RedirectAttributes ra) { foodRestService.delete(id); ra.addFlashAttribute("message", "식품을 삭제했습니다."); return "redirect:/fridges/me"; } }
클래스 선언부에 매핑 URL이 /foods에서 /fridges/foods 로 변경했고, SessionAttributes 애너테이션이 추가했다. SessionAttribute 애너테이션으로 ModelAttribute 에 담기는 객체를 HTTP 세션에 저장해서 동일한 키를 통한 호출은 세션에 객체가 저장되어 있으면, 세션 데이터를 사용해서 통신 빈도를 줄인다.
foods 메서드(목록 기능)가 삭제되었고 나머지 메서드들은 그대로 사용한다. 방금 작성한 FoodRestService 클래스 메서드들을 이용해서 코드를 정리했다.
등록/수정/삭제 처리 후 리다이렉트할 URL이 /fridges/me 로 변경됬다.
테스트를 실행해서 문제가 없는지 확인한다.
이전글: 냉장고 가계부 프로젝트 22
다음글: 냉장고 가계부 프로젝트 24