Trello 만들기 프로젝트에서 Column 기능을 맡았다.
Trello에서 Board를 만들 수 있고 Board 내에 여러개의 Column이 있다.
이 Column에 Card들이 들어가는 구조이다.
Card 내에 매우 많은 기능들이 들어가있지만, 일단 내가 맡은 Column 파트에 집중해보자.
- 컬럼 생성
- 보드 내부에 컬럼을 생성할 수 있어야 합니다.
- 컬럼 이름 수정
- 컬럼 삭제
- 컬럼 순서 이동
- 컬럼 순서는 자유롭게 변경될 수 있어야 합니다.
- e.g. Backlog, In Progress, Done → Backlog, Done, In Progress
우선 ERD를 설계하여 Entity 생성을 어떻게 할지 구상했다.
Controller에서는 특별한점이 없었다.
한가지 막혔던점은, entity를 만들 때 MySQL에 'Column'이란 값이 기본언어로 사용되고있어 entity 이름으로 사용할 수 없었다는 것이다. 그래서 이전에 좋아요 기능을 넣을 때 Like를 Likes로 했던 것 같이, 이번에도 Column을 Columns로 바꿨다.
특별했던 점은 요구사항 중 '컬럼 순서 이동' 기능을 만드는 것이었다. 이것을 만들며 쿼리문 작성에 익숙해질 수 있었다.
우선 Column을 만들 때 ColumnOrder가 함께 생성되어야한다. 그리고 Column의 순서는 Board에 따라 각각 1부터 시작될 수 있도록 만들었다.
Board 내에 있는 Column들의 columnOrder 중, 가장 큰 수를 받아온다.
받아온 수에서 +1을 하며 새로운 Column을 생성한다.
이 때 Column이 0개라 null을 받아오면 0을 받아오기 때문에 1부터 시작된다.
public void postColumn(Long boardId, ColumnRequestDto requestDto) {
Board board = checkBoardId(boardId);
Integer lastColumnOrderInBoard = columnRepository.findMaxColumnOrderByBoard(board);
if (lastColumnOrderInBoard==null) {
lastColumnOrderInBoard=0;
}
Columns columns = new Columns(board,requestDto, lastColumnOrderInBoard);
columnRepository.save(columns);
}
public Columns(Board board, ColumnRequestDto requestDto, Integer columnOrder) {
this.board = board;
this.name = requestDto.getName();
isArchived=false;
this.columnOrder = columnOrder+1;
}
앞서 말했던 복잡한 조건을 맞추기위해 쿼리문을 직접 작성했다.
columnOrder 속성 중 Max값을 Columns 테이블에서 찾으며, 이 때 특정 Board가 연관된 Column에서만 조회한다.
@Query("SELECT MAX(c.columnOrder) FROM Columns c WHERE c.board = :board")
Integer findMaxColumnOrderByBoard(@Param("board") Board board);
Board별로 column_order가 예쁘게 잘 생겼다.
우선 Controller의 api는 다음과 같다. 사용자가 요청을 직관적으로 이해 할 수 있는 restful한 api를 작성하기위해 고민했다.
"/v1/boards/{boardId}/columns/{columnId}/order/{columnOrder}"
이것이 '특정 Board 내에 있는 하나의 Column의 순서를 원하는 위치로 옮기는 것'으로 해석되도록 의도했다.
원하는 순서로 컬럼이 옮겨지면 다른 컬럼들의 columnOrder를 순서에 맞게 변형시켜주기위해 다음과 같이 코드를 작성했다.
특정 Board내의 컬럼 중 바꿔져야하는 columnOrder를 포함하는 Colums를 List로 받아왔고, 그것을 stream과 foreach와 람다식을 이용해서 순서를 바꿨다.
(팀원분들이 리뷰하기 쉽게 주석을 달아놨다.)
@Transactional
public void updateColumnOrder(Long boardId, Long columnId, Integer columnOrder) {
... (입력값 검증 로직)
// 칼럼 순서를 바꿔 줄 때 순서를 앞에서 뒤로 바꾼다면
// 그 사이에 있는 순서값들을 모두 -1 하고 순서를 옮김
if (targetColumn.getColumnOrder() < columnOrder) {
columnRepository.findAllByBoardAndColumnOrderGreaterThanAndColumnOrderLessThanEqual(
board, targetColumn.getColumnOrder(), columnOrder)
.stream().forEach(a -> a.updateColumnOder(a.getColumnOrder()-1));
targetColumn.updateColumnOder(columnOrder);
}
// 순서를 뒤에서 앞으로 바꾼다면
// 그 사이에 있는 순서값들을 모두 +1 하고 순서를 옮김
if (targetColumn.getColumnOrder() > columnOrder) {
columnRepository.findAllByBoardAndColumnOrderLessThanAndColumnOrderGreaterThanEqual(
board, targetColumn.getColumnOrder(), columnOrder)
.stream().forEach(a -> a.updateColumnOder(a.getColumnOrder()+1));
targetColumn.updateColumnOder(columnOrder);
}
}
정상적으로 순서 변경이 수행되었다.
Board 스크린과 같이 컬럼이 순서에 따라, Card와 함께 조회 될 수 있도록 코드를 작성했다.
마찬가지로 stream, forEach, 람다식을 이용하며 받아오는 Columns를 ColumnResponseDto 형태로 변환시켰다.
public List<ColumnResponseDto> getColumnList(Long boardId) {
Board board = checkBoardId(boardId);
List<ColumnResponseDto> columnResponseDtoList = new ArrayList<>();
columnRepository.findAllByBoardOrderByColumnOrder(board)
.stream().forEach(a -> {
columnResponseDtoList.add(
new ColumnResponseDto(a.getName(),
cardRepository.findAllByColumns_Id(a.getId())
.stream().map(CardResponseDto::new).toList()
));
});
return columnResponseDtoList;
}
public class ColumnResponseDto {
private String name;
private List<CardResponseDto> cards = new ArrayList<>();
}
Column의 순서별로, cardList와 함께 잘 반환되는 모습이다.
어제 팀원들과 설계한 내용을 바탕으로 Entiry, Request, Response를 작성하였다. 그 형태를 맞추기위해 쿼리문과 DTO 형태 등을 고민하여 요구사항을 맞출 수 있었다.
이제는 배운 것 내에서 요구사항을 충분히 이뤄낼 수 있겠다는 자신감이 생긴다.
내일은 요구사항 이상의 것을 구현해볼 예정이다.