코딩항해기
[Team/붕어빵원정대(최프)] 기능 검증 및 종단 개발 방식 병행 시작 본문
기존에는 MVC 파트를 나눠 횡단으로 개발을 진행했다. 횡단 개발은 계층 별로 전체 기능을 개발하기 때문에 일관성 확보가 용이하고 기능을 빠르게 완성하기 용이한 방법이어서 팀 프로젝트를 진행할 때 횡단 개발 방식으로 진행했었다.
그러나 완성된 기능 검증을 하기 위해 기능 테스트를 해본 결과 개발 기간이 얼마 남지 않았는데, 사소한 문제들이 너무 많이 발생해서 최대한 중요한 서비스를 살리기 위해 종단 개발 방식을 병행하기로 했다.
종단 개발 방식을 독단으로 진행하면 팀에 혼선을 줄 수 있기 때문에 해당 문제를 종단으로 해결할 것임을 사전에 팀원들에게 공유했으며 큰 구조 변경은 횡단 개발 방식을 유지하기로 했다.
1. 설계확인하기
현재 기능 검증할 페이지는 메인 페이지이다.
로컬 서버를 실행하면 가장 먼저 뜨는 페이지인데, 현재 500에러 (백단 에러)가 발생해 메인페이지를 확인할 수 없는 상황이다. 기능 검증을 위해 관련한 설계부터 확인하기로 했다.
먼저 VC파트 설계자료를 보면 메인페이지의 요청은 main.do로 들어가며 4가지의 데이터를 필요로 한다.
하지만 가게 목록은 비동기로 구현될 예정이라 빠지게 되며, 추천목록과 최근 본 상품은 쿠키에서 데이터를 받아와 조회한다. 그리고 인기글은 DB로부터 데이터를 받아와 전달하게 된다.
DB를 조회하는 기능은 인기글 뿐이므로 MC파트 설계자료에서는 인기글을 조회하는 설계만 확인하면 된다.
2. 기능 확인하기
기능이 정상작동하지 않음을 확인했다.
3. 오류 찾기
view 파트를 확인했을 때 view는 아직 main 페이지 작업이 이뤄지진 않았지만 해당 페이지가 존재하는 것은 확인됐다. 종단 개발을 한다고 해도 단순 수정 작업이 아닌 전체 페이지 제작은 시간 상 비효율적인 측면이 있기 때문에 View 파트에 요청사항을 전달하기로 했다.
Controller에서 잘못 됐을 경우도 있으므로 설계대로 작성되었는지 확인한다.
//메인페이지 이동
@RequestMapping("/main.do")
public String main(HttpServletRequest request, HttpServletResponse response, Model model) {
log.info("log: /main.do main - start");
//메인파트는 변동을 고려해 데이터 종류 별로 정리
//가게 검색은 비동기
//DAO 호출하여 인기 게시물 TOP 3을 가져오기///////////////////////////////////////////////////////////////
BoardDTO boardDTO = new BoardDTO(); //게시글 조회 객체
boardDTO.setCondition("HOT_BOARD"); //인기글 조회 설정
BoardCateDTO boardCateDTO = new BoardCateDTO(); //카테고리 번호 조회용 객체
boardCateDTO.setBoardCateName(BOARD_LIST); //커뮤니티게시판으로 고정
boardDTO.setBoardCategoryNum(boardCateService.selectOne(boardCateDTO).getBoardCateNum());
ArrayList<BoardDTO> hotBoardList = boardService.selectAll(boardDTO); // DAO에서 인기 게시물 조회
model.addAttribute("hotBoardList", hotBoardList); // 상위 3개 인기 게시물 리스트를 request에 전달
//확인
log.info("log: main - send hotBoardList [{}]", hotBoardList);
/////////////////////////////////////////////////////////////////////////////////////////////////////
//쿠키 조회 상품 : 추천상품, 최근에 본 상품 ///////////////////////////////////////////////////////////////
ArrayList<ProductDTO> recommendedProduct = null; //추천 상품 목록을 담을 리스트
ArrayList<ProductDTO> resentProduct = null; //쿠키 저장용
HashMap<String, String> filterList; //필터 검색용
List<String> viewedProductList; //상품 조회용
Map<Integer, Integer> categoryCount; //상품 카테고리 계산용
ArrayList<String> deleteList; //쿠키 삭제용
String viewedProducts = null; //쿠키 상품 리스트
ProductDTO productDTO = new ProductDTO(); //조회용 재사용 객체
//쿠키 가져와서 디코딩 : 사용자가 최근에 조회한 상품 목록
Cookie[] cookies = request.getCookies();
Cookie cookie = CookieUtil.cookieData(cookies, COOKIE_NAME);
if (cookie != null) {//NPE 방지
viewedProducts = URLDecoder.decode(cookie.getValue(), StandardCharsets.UTF_8);
log.info("log: main - viewedProducts : [{}]", viewedProducts);
}
// 쿠키에서 가져온 상품 목록이 있다면 리스트에 저장
if (viewedProducts != null && !viewedProducts.isEmpty()) {
//","를 통해 데이터 구분
viewedProductList = Arrays.asList(viewedProducts.split(","));
log.info("log: main - viewedProductList : [{}]", viewedProductList);
deleteList = new ArrayList<>(); //삭제용 리스트
resentProduct = new ArrayList<>(); //추천 상품 목록
categoryCount = new HashMap<>(); //카테고리 계산용
// 쿠키에서 가져온 상품 ID를 기반으로 상품을 조회하고 카테고리별로 개수 카운팅
for (String pNum : viewedProductList) {
productDTO.setProductNum(Integer.parseInt(pNum));
ProductDTO product = productService.selectOne(productDTO); // 상품 정보 조회
log.info("log: main - viewedProductList for product : [{}]", product);
//존재하지 않는 상품일 경우
if (product == null) {
log.error("log: main - deleteList.add : [{}]", pNum);
//추후 쿠키에서 제거 작업하기 위해 저장
deleteList.add(pNum);
continue;
}
//존재하는 상품일 경우 해당 상품을 저장
resentProduct.add(product);
log.info("log: main - resentProducts.add");
// 카테고리 번호별로 상품 수 계산
int categoryNum = product.getProductCategoryNum();
log.info("log: main - categoryNum : [{}]", categoryNum);
categoryCount.put(categoryNum, categoryCount.getOrDefault(categoryNum, 0) + 1);
}
//없는 상품 쿠키에서 제거
if(CookieUtil.cookieDataDelete(response, cookie, deleteList)){
//쿠키 제거에 실패했더라도 상품리스트를 사용자가 볼 수 있도록 개발자에게 안내만 제공
log.error("log: [ERROR] main - delete history Cookie fail!");
};
// 가장 많이 본 카테고리
// categoryCount Map에 저장된 카테고리별 조회 횟수를 기준으로 가장 많이 조회된 카테고리를 찾는다.
// Map.Entry<Integer, Integer>는 카테고리 번호(Integer)와 해당 카테고리의 조회 수(Integer)를 의미.
// max() 메서드는 가장 큰 값을 가진 카테고리를 찾는다.
Optional<Map.Entry<Integer, Integer>> mostViewedCategoryOpt = categoryCount.entrySet().stream()
.max(Map.Entry.comparingByValue());
// Optional이 비어있지 않으면 (가장 많이 본 카테고리가 존재할 경우)
if (mostViewedCategoryOpt.isPresent()) {
// 가장 많이 본 카테고리의 key 값을 가져옴 (카테고리 번호)
int mostViewedCategory = mostViewedCategoryOpt.get().getKey();
// 가장 많이 본 카테고리에 속하는 상품을 필터링하여 추천 상품으로 설정
filterList = new HashMap<>(); //필터 검색용
filterList.put("GET_MD_CATEGORY", "" + mostViewedCategory); // 카테고리 필터 추가
// ProductDTO 객체 생성 후 필터 설정
productDTO.setFilterList(filterList); // 필터를 설정하여 해당 카테고리의 상품만 검색
// 데이터 범위 설정 (추천 상품 수를 제한)
productDTO.setStartNum(1);
productDTO.setEndNum(RECOMM_CNT);
//확인
log.info("log: main - mostViewedCategory : [{}]", mostViewedCategory);
log.info("log: main - filter search filterList : [{}]", filterList);
// 필터링된 상품 목록을 조회하여 추천 상품 리스트 생성
recommendedProduct = productService.selectAll(productDTO); // 상품 목록 조회
}
}
//데이터 전달
model.addAttribute("viewProductList", resentProduct); // 최근에 본 상품
model.addAttribute("recommProductList", recommendedProduct); // 추천 상품
//확인
log.info("log: main - send resentProducts : [{}]", resentProduct);
log.info("log: main - send recommendedProducts : [{}]", recommendedProduct);
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
log.info("log: /main.do main - end");
return "main";
}
View에서 필요한 데이터 3가지를 전달하고 있으며, 설계대로 DB에 데이터를 요청하고 있다.
또한 Service에는 별다른 작업을 하지 않은 것을 확인할 수 있었다.
Model파트가 설계와 일치하는지 확인하기 위해서는 테이블부터 확인한다.
테이블 컬럼명이 변경된 업데이트 사항이 있다는 것을 확인하고, DAO를 확인했다.
DAO가 설계와 다른 것을 확인했고, 쿼리 문법 자체가 오류가 있는 부분을 확인했다.
[문제점]
- 설계와 다른 데이터 종류 반환
- FROM 키워드가 'FORM'으로 잘못 작성
- WHERE절 누락으로 인한 쿼리 문법 오류
- 컬럼명 불일치
횡단 개발이라면 이 수정도 Model파트에 요청해야하지만 남은 개발 기간이 2주 미만으로 짧고, 수정 범위가 DAO 쿼리문과 관련 메서드 수정으로 한정적이기 때문에 종단 개발 방식을 적용해 수정하기로 했다.
그 외에도 오탈자, boardDAO등을 맞게 수정해주고 나니 정상적으로 페이지가 로드 되는 것을 확인할 수 있었다.
4. 기능 완성 (View 제외)
인기 게시물 데이터를 받아올 수 있지만, 현재 view가 완성되지 않아 프로그램 비정상종료 문제만 제거되어 보이는 문제를 확인할 수 있다.
남은 기능도 종단 개발 방식을 적용해서 남은 기한 안에 최대한 완성할 수 있도록 진행해야겠다.
'Project' 카테고리의 다른 글
[Team/붕어빵원정대(최프)] 로그 관리 (0) | 2024.11.01 |
---|---|
[Team/붕어빵원정대(최프)] Model파트 DAO 오류해결 기록 (0) | 2024.10.27 |
[Team/붕어빵원정대(최프)] Model와 설계 조율 (2) (0) | 2024.10.24 |
[Team/붕어빵원정대(최프)] View파트와 설계 조율 (2) (4) | 2024.10.23 |
[Team/붕어빵원정대(최프)] 동적 경로를 활용한 FileUtil 작업 (0) | 2024.10.22 |