본문 바로가기
Project/Kanban Board

[JAVA / Spring / JPA / Project] - 협업 도구 프로젝트 2일차

by nam_ji 2024. 3. 19.

협업 도구 프로젝트 2일차

개발환경

  1. Spring Boot 3.2.3
    1. Spring Security
    2. Spring Data JPA
    3. Spring Lombok
  2. Gradle 8.6
  3. JAVA 17
  4. MySQL 8.3

진행상황

  • 저는 협업 툴 프로젝트에서 보드와 댓글 기능을 담당했습니다.
  • 먼저 CRUD가 가능한 상태만 될 수 있도록 기능 구현을 하고 필요한 기능들을 조금씩 추가해 나가기로 결정했습니다.

코드

  • 유저 기능이 구현되지 않은 상태이기 때문에 필요한 userId는 임의로 1을 주었습니다.
  •  생성시간과 수정시간은 따로 시간을 관리할 수 있는 class로 분리하여 처리하려고 생각했습니다. (앞으로 해야할 기능구현 중 하나)
  • 기능구현을 하면서 필요한 부분은 todo로 한번에 볼 수 있게 했습니다. (밑에 코드에는 지워둔 상태입니다.)
  • 이제 협업 인원들을 초대하는 기능과 생성자만 수정 삭제할 수 있게 하는 기능 구현을 해보려고 합니다.
  • board (협업이 이루어지는 공간)
    • 더보기
      entity
      package com.fis.mylittleboard.domain.board.entity;
      
      import com.fis.mylittleboard.domain.board.dto.BoardRequestDto;
      import jakarta.persistence.Column;
      import jakarta.persistence.Entity;
      import jakarta.persistence.GeneratedValue;
      import jakarta.persistence.GenerationType;
      import jakarta.persistence.Id;
      import jakarta.persistence.Table;
      import java.time.LocalDateTime;
      import lombok.Getter;
      import lombok.NoArgsConstructor;
      import org.springframework.data.annotation.CreatedDate;
      
      @Entity
      @Getter
      @NoArgsConstructor
      @Table(name = "boards")
      public class Board {
        @Id
        @GeneratedValue(strategy = GenerationType.IDENTITY)
        private Long id;
      
        @Column(nullable = false)
        private String boardName;
      
        @Column(nullable = false)
        private String boardDescription;
      
        @Column(nullable = false)
        private String boardColor;
      
        @Column(nullable = false)
        private Long userId;
      
        @CreatedDate
        @Column(nullable = false)
        private LocalDateTime dueDate = LocalDateTime.now();
      
        @Column(nullable = false)
        private boolean classification;
      
        public Board(String boardName, String boardDescription, String boardColor, Long userId) {
          this.boardName = boardName;
          this.boardDescription = boardDescription;
          this.boardColor = boardColor;
          this.userId = userId;
        }
      
        public void update(BoardRequestDto requestDto) {
          this.boardName = requestDto.getBoardName();
          this.boardDescription = requestDto.getBoardDescription();
          this.boardColor = requestDto.getBoardColor();
          this.dueDate = LocalDateTime.now();
        }
      }
      ​​

      dto

      package com.fis.mylittleboard.domain.board.dto;
      
      import lombok.Getter;
      
      @Getter
      public class BoardRequestDto {
        private String boardName;
        private String boardDescription;
        private String boardColor;
      }
      ​
      package com.fis.mylittleboard.domain.board.dto;
      
      import java.time.LocalDateTime;
      import lombok.Getter;
      
      @Getter
      public class BoardResponseDto {
        private String boardName;
        private String boardDescription;
        private String boardColor;
        private LocalDateTime dueDate;
      
        public BoardResponseDto(
            String boardName, String boardDescription, String boardColor, LocalDateTime dueDate) {
          this.boardName = boardName;
          this.boardDescription = boardDescription;
          this.boardColor = boardColor;
          this.dueDate = dueDate;
        }
      }


      controller

      package com.fis.mylittleboard.domain.board.controller;
      
      import com.fis.mylittleboard.domain.board.dto.BoardRequestDto;
      import com.fis.mylittleboard.domain.board.dto.BoardResponseDto;
      import com.fis.mylittleboard.domain.board.serivce.BoardService;
      import java.util.List;
      import lombok.RequiredArgsConstructor;
      import org.apache.coyote.Response;
      import org.springframework.http.HttpStatus;
      import org.springframework.http.ResponseEntity;
      import org.springframework.web.bind.annotation.DeleteMapping;
      import org.springframework.web.bind.annotation.GetMapping;
      import org.springframework.web.bind.annotation.PathVariable;
      import org.springframework.web.bind.annotation.PostMapping;
      import org.springframework.web.bind.annotation.PutMapping;
      import org.springframework.web.bind.annotation.RequestBody;
      import org.springframework.web.bind.annotation.RequestMapping;
      import org.springframework.web.bind.annotation.RestController;
      
      @RestController
      @RequiredArgsConstructor
      @RequestMapping("/api/boards")
      public class BoardController {
      
        private final BoardService boardService;
      
        @PostMapping
        public String createBoard (
            @RequestBody BoardRequestDto requestDto) {
          boardService.createBoard(requestDto);
      
          return "작업공간이 생성되었습니다.";
        }
      
        @GetMapping("/progress")
        public ResponseEntity<List<BoardResponseDto>> getBoardProgressing() {
          List<BoardResponseDto> responseDtoList = boardService.getBoardProgressing();
      
          return new ResponseEntity<>(responseDtoList, HttpStatus.OK);
        }
      
        @GetMapping("/close")
        public ResponseEntity<List<BoardResponseDto>> getBoardClosing() {
          List<BoardResponseDto> responseDtoList = boardService.getBoardClosing();
      
          return new ResponseEntity<>(responseDtoList, HttpStatus.OK);
        }
      
        @PutMapping("/{boardId}")
        public String updateBoard(
            @PathVariable Long boardId,
            @RequestBody BoardRequestDto requestDto) {
          boardService.updateBoard(boardId, requestDto);
          
          return "워크스페이스가 수정되었습니다.";
        }
      
        @DeleteMapping("/{boardId}")
        public String deleteBoard(
            @PathVariable Long boardId) {
          boardService.deleteBoard(boardId);
      
          return "워크스페이스가 삭제되었습니다.";
        }
      }
      ​

      service

      package com.fis.mylittleboard.domain.board.serivce;
      
      import com.fis.mylittleboard.domain.collaboration.entity.Collaboration;
      import com.fis.mylittleboard.domain.collaboration.repository.CollaborationRepository;
      import com.fis.mylittleboard.domain.board.dto.BoardRequestDto;
      import com.fis.mylittleboard.domain.board.dto.BoardResponseDto;
      import com.fis.mylittleboard.domain.board.entity.Board;
      import com.fis.mylittleboard.domain.board.repository.BoardRepository;
      import com.fis.mylittleboard.domain.hahaboard.entity.Hahaboard;
      import com.fis.mylittleboard.domain.hahaboard.repository.HahaboardRepository;
      import java.util.ArrayList;
      import java.util.List;
      import java.util.Optional;
      import lombok.RequiredArgsConstructor;
      import lombok.extern.slf4j.Slf4j;
      import org.springframework.stereotype.Service;
      import org.springframework.transaction.annotation.Transactional;
      
      @Slf4j
      @Service
      @RequiredArgsConstructor
      public class BoardService {
      
        private final BoardRepository boardRepository;
        private final CollaborationRepository collaborationRepository;
        private final HahaboardRepository hahaboardRepository;
      
      
        @Transactional
        public void createBoard(BoardRequestDto requestDto) {
      
          Long userId = 1L;
      
          String boardName = requestDto.getBoardName();
          Optional<Board> checkBoardName = boardRepository.findByBoardName(boardName);
          if (checkBoardName.isPresent()) {
            throw new IllegalArgumentException("사용중인 작업공간입니다.");
          }
          Board board = new Board(
              requestDto.getBoardName(),
              requestDto.getBoardDescription(),
              requestDto.getBoardColor(),
              userId
          );
      
          Board newBoard = boardRepository.save(board);
      
          Hahaboard hahaboard = new Hahaboard(newBoard.getId());
      
          hahaboardRepository.save(hahaboard);
      
          Collaboration collaboration = new Collaboration(newBoard.getId(), userId);
      
          collaborationRepository.save(collaboration);
        }
      
        @Transactional(readOnly = true)
        public List<BoardResponseDto> getBoardProgressing() {
          List<BoardResponseDto> boardResponseDtoList = new ArrayList<>();
          for (Board board : boardRepository.findAll()) {
            if (board.isClassification()) {
              boardResponseDtoList.add(
                  new BoardResponseDto(
                      board.getBoardName(),
                      board.getBoardDescription(),
                      board.getBoardColor(),
                      board.getDueDate()));
            }
          }
          return boardResponseDtoList;
        }
      
        @Transactional(readOnly = true)
      
        public List<BoardResponseDto> getBoardClosing() {
          List<BoardResponseDto> boardResponseDtoList = new ArrayList<>();
          for(Board board : boardRepository.findAll()) {
            if (!board.isClassification()) {
              boardResponseDtoList.add(
                  new BoardResponseDto(
                      board.getBoardName(),
                      board.getBoardDescription(),
                      board.getBoardColor(),
                      board.getDueDate()));
            }
          }
          return boardResponseDtoList;
        }
      
        @Transactional
        public void updateBoard(Long boardId, BoardRequestDto requestDto) {
          Board board = boardRepository.findById(boardId).orElseThrow(() ->
              new IllegalArgumentException("일치하는 작업공간이 없습니다."));
      
          board.update(requestDto);
        }
      
        @Transactional
        public void deleteBoard(Long boardId) {
          boardRepository.deleteById(boardId);
        }
      }
      ​

      repository

      package com.fis.mylittleboard.domain.board.repository;
      
      import com.fis.mylittleboard.domain.board.entity.Board;
      import java.util.Optional;
      import org.springframework.data.jpa.repository.JpaRepository;
      
      public interface BoardRepository extends JpaRepository<Board, Long> {
      
        Optional<Board> findByBoardName(String boardName);
      }
      ​
  • collaboration (사용자와 보드의 중간 테이블)
    • 더보기
      entity
      package com.fis.mylittleboard.domain.board.repository;
      
      import com.fis.mylittleboard.domain.board.entity.Board;
      import java.util.Optional;
      import org.springframework.data.jpa.repository.JpaRepository;
      
      public interface BoardRepository extends JpaRepository<Board, Long> {
      
        Optional<Board> findByBoardName(String boardName);
      }
      ​

       

      repository
      package com.fis.mylittleboard.domain.board.repository;
      
      import com.fis.mylittleboard.domain.board.entity.Board;
      import java.util.Optional;
      import org.springframework.data.jpa.repository.JpaRepository;
      
      public interface BoardRepository extends JpaRepository<Board, Long> {
      
        Optional<Board> findByBoardName(String boardName);
      }
      ​

 

  • hahacontent (소소한 일들을 공유할 수 있는 공간)
    • 더보기
      entity
      package com.fis.mylittleboard.domain.hahacontent.entity;
      
      import com.fis.mylittleboard.domain.hahacontent.dto.HahaContentRequestDto;
      import jakarta.persistence.Column;
      import jakarta.persistence.Entity;
      import jakarta.persistence.GeneratedValue;
      import jakarta.persistence.GenerationType;
      import jakarta.persistence.Id;
      import jakarta.persistence.Table;
      import java.time.LocalDateTime;
      import lombok.Getter;
      import lombok.NoArgsConstructor;
      import org.springframework.data.annotation.CreatedDate;
      import org.springframework.data.annotation.LastModifiedDate;
      
      @Entity
      @NoArgsConstructor
      @Getter
      @Table(name = "hahacontents")
      public class HahaContent {
        @Id
        @GeneratedValue(strategy = GenerationType.IDENTITY)
        private Long id;
      
        @Column(nullable = false)
        private String content;
      
        @Column
        private Long hahaboardId;
      
        @CreatedDate
        private LocalDateTime createdAt;
      
        @LastModifiedDate
        private LocalDateTime modifiedAt;
      
        public HahaContent(Long hahaboardId, HahaContentRequestDto requestDto) {
          this.content = requestDto.getContent();
          this.hahaboardId = hahaboardId;
          this.createdAt = LocalDateTime.now();
          this.modifiedAt = LocalDateTime.now();
        }
      }
      ​

      dto
      package com.fis.mylittleboard.domain.hahacontent.dto;
      
      import lombok.Getter;
      
      @Getter
      public class HahaContentRequestDto {
      
        private String content;
      
      }
      ​
      package com.fis.mylittleboard.domain.hahacontent.dto;
      
      import lombok.Getter;
      
      @Getter
      public class HahaContentResponseDto {
      
        private String content;
      
      }
      

      controller
      package com.fis.mylittleboard.domain.hahacontent.controller;
      
      import com.fis.mylittleboard.domain.hahacontent.dto.HahaContentRequestDto;
      import com.fis.mylittleboard.domain.hahacontent.service.HahaContentService;
      import lombok.RequiredArgsConstructor;
      import org.springframework.web.bind.annotation.PathVariable;
      import org.springframework.web.bind.annotation.PostMapping;
      import org.springframework.web.bind.annotation.RequestBody;
      import org.springframework.web.bind.annotation.RequestMapping;
      import org.springframework.web.bind.annotation.RestController;
      
      @RestController
      @RequiredArgsConstructor
      @RequestMapping("/api")
      public class HahaContentController {
      
        private final HahaContentService hahaContentService;
      
        @PostMapping("/hahaboards/{hahaboardId}")
        public String createHahaContent (
            @PathVariable Long hahaboardId,
            @RequestBody HahaContentRequestDto requestDto) {
          hahaContentService.createHahaContent(hahaboardId, requestDto);
      
          return "잡담글이 생성되었읍니다.";
        }
      
      }
      ​

      service
      package com.fis.mylittleboard.domain.hahacontent.service;
      
      import com.fis.mylittleboard.domain.hahacontent.dto.HahaContentRequestDto;
      import com.fis.mylittleboard.domain.hahacontent.entity.HahaContent;
      import com.fis.mylittleboard.domain.hahacontent.repository.HahaContentRepository;
      import lombok.RequiredArgsConstructor;
      import org.springframework.stereotype.Service;
      
      @Service
      @RequiredArgsConstructor
      public class HahaContentService {
      
        private final HahaContentRepository hahaContentRepository;
      
        public void createHahaContent(Long hahaboardId, HahaContentRequestDto requestDto) {
          HahaContent content = new HahaContent(hahaboardId, requestDto);
      
          hahaContentRepository.save(content);
        }
      }
      ​

      repository
      package com.fis.mylittleboard.domain.hahacontent.repository;
      
      import com.fis.mylittleboard.domain.hahacontent.entity.HahaContent;
      import org.springframework.data.jpa.repository.JpaRepository;
      
      public interface HahaContentRepository extends JpaRepository<HahaContent, Long> {
      
      }
      ​
  • hahaboard
    • 더보기
      entity
      package com.fis.mylittleboard.domain.hahaboard.entity;
      
      import jakarta.persistence.Column;
      import jakarta.persistence.Entity;
      import jakarta.persistence.GeneratedValue;
      import jakarta.persistence.GenerationType;
      import jakarta.persistence.Id;
      import jakarta.persistence.IdClass;
      import jakarta.persistence.Table;
      import java.awt.dnd.InvalidDnDOperationException;
      import java.time.LocalDateTime;
      import lombok.Getter;
      import lombok.NoArgsConstructor;
      import org.springframework.data.annotation.CreatedDate;
      
      @Entity
      @NoArgsConstructor
      @Getter
      @Table(name = "hahaboard")
      public class Hahaboard {
        @Id
        @GeneratedValue(strategy = GenerationType.IDENTITY)
        private Long id;
      
        @Column(nullable = false)
        private Long InBoardId;
      
        @CreatedDate
        private LocalDateTime createdAt;
      
        public Hahaboard(Long id) {
          this.InBoardId = id;
          this.createdAt = LocalDateTime.now();
        }
      }
      ​

      repository
      package com.fis.mylittleboard.domain.hahaboard.repository;
      
      import com.fis.mylittleboard.domain.hahaboard.entity.Hahaboard;
      import org.springframework.data.jpa.repository.JpaRepository;
      
      public interface HahaboardRepository extends JpaRepository<Hahaboard, Long> {
      
      }
      ​
  • comment (댓글)
    • 더보기
      entity
      package com.fis.mylittleboard.domain.comment.entity;
      
      import com.fis.mylittleboard.domain.comment.dto.CommentRequestDto;
      import jakarta.persistence.Column;
      import jakarta.persistence.Entity;
      import jakarta.persistence.GeneratedValue;
      import jakarta.persistence.GenerationType;
      import jakarta.persistence.Id;
      import jakarta.persistence.Table;
      import lombok.Getter;
      import lombok.NoArgsConstructor;
      
      @Entity
      @Getter
      @NoArgsConstructor
      @Table(name = "comments")
      public class Comment {
        @Id
        @GeneratedValue(strategy = GenerationType.IDENTITY)
        private Long id;
      
        @Column(nullable = false)
        private String content;
      
        public Comment(CommentRequestDto requestDto) {
          this.content = requestDto.getContent();
        }
      
        public void update(CommentRequestDto requestDto) {
          this.content = requestDto.getContent();
        }
      }
      ​

      dto
      package com.fis.mylittleboard.domain.comment.dto;
      
      import lombok.Getter;
      
      @Getter
      public class CommentRequestDto {
        private String content;
      
      }
      ​
      package com.fis.mylittleboard.domain.comment.dto;
      
      import lombok.Getter;
      
      @Getter
      public class CommentResponseDto {
        private String content;
      }
      

      controller
      package com.fis.mylittleboard.domain.comment.controller;
      
      import com.fis.mylittleboard.domain.comment.dto.CommentRequestDto;
      import com.fis.mylittleboard.domain.comment.service.CommentService;
      import lombok.RequiredArgsConstructor;
      import org.springframework.web.bind.annotation.DeleteMapping;
      import org.springframework.web.bind.annotation.PathVariable;
      import org.springframework.web.bind.annotation.PostMapping;
      import org.springframework.web.bind.annotation.PutMapping;
      import org.springframework.web.bind.annotation.RequestBody;
      import org.springframework.web.bind.annotation.RequestMapping;
      import org.springframework.web.bind.annotation.RestController;
      
      @RestController
      @RequiredArgsConstructor
      @RequestMapping("/api/comments")
      public class CommentController {
      
        private final CommentService commentService;
      
        @PostMapping
        public String createComment (
            @RequestBody CommentRequestDto requestDto) {
          commentService.createCommnet(requestDto);
      
          return "댓글이 생성되었습니다";
        }
      
        @PutMapping("/{commentId}")
        public String updateComment (
            @PathVariable Long commentId,
            @RequestBody CommentRequestDto requestDto) {
          commentService.updateComment(commentId, requestDto);
      
          return "댓글이 수정되었습니다.";
        }
      
        @DeleteMapping("/{commentId}")
        public String deleteComment (
            @PathVariable Long commentId) {
          commentService.deleteComment(commentId);
      
          return "댓글이 삭제되었습니다.";
        }
      }
      ​

      service
      package com.fis.mylittleboard.domain.comment.service;
      
      import com.fis.mylittleboard.domain.comment.dto.CommentRequestDto;
      import com.fis.mylittleboard.domain.comment.entity.Comment;
      import com.fis.mylittleboard.domain.comment.repository.CommentRepository;
      import lombok.RequiredArgsConstructor;
      import org.springframework.stereotype.Service;
      import org.springframework.transaction.annotation.Transactional;
      
      @Service
      @RequiredArgsConstructor
      public class CommentService {
      
        private final CommentRepository commentRepository;
      
        public void createCommnet(CommentRequestDto requestDto) {
          Comment comment = new Comment(requestDto);
      
          commentRepository.save(comment);
        }
      
        @Transactional
        public void updateComment(Long commentId, CommentRequestDto requestDto) {
          Comment comment = commentRepository.findById(commentId).orElseThrow(() ->
              new IllegalArgumentException("존재하지 않는 댓글입니다."));
      
          comment.update(requestDto);
        }
      
        public void deleteComment(Long commentId) {
          commentRepository.deleteById(commentId);
        }
      }
      ​

      repository
      package com.fis.mylittleboard.domain.comment.repository;
      
      import com.fis.mylittleboard.domain.comment.entity.Comment;
      import org.springframework.data.jpa.repository.JpaRepository;
      
      public interface CommentRepository extends JpaRepository<Comment, Long> {
      
      }
      ​

회고

그 동안의 프로젝트를 진행하면서 ERD와 API 설계의 중요성을 매번 느끼면서 이번에는 최대한 완벽하게 설계하겠다는 생각으로 설계를 했지만 중간에 계속되는 추가와 수정이 있었습니다.
개발을 하는 것도 중요하지만 처음 설계의 중요성을 프로젝트마다 느낄 수 있고 조금씩 성장하는 것을 느낄 수 있어 힘들지만 재밌게 프로젝트를 할 수 있는 것 같습니다.