프론트엔드와 백엔드 3:3으로 매칭되어 stack overflow 클론 프로젝트를 시작했다. 깃 칸반 보드와 브랜치 등 깃에서 제공하는 서비스들을 최대한 활용하여 진행할 예정이다.
DB 연결은 Spring date Jpa, DB는 우선적으로 h2를 사용하며 aws RDS 구현 후 mysql을 연결할 예정.
우선 이런 형태로 엔티티간 관계를 정의했고 이것을 바탕으로 구현 중이다.
주어진 기간이 너무 짧아 기본적인 CRUD 기능에 인증, 인가 OAuth 같은 보안 요소들을 우선적으로 구현하고 회원 별 평판, 게시물 조회수, 추천 수 같은 세부적인 기능들을 구현해볼 계획이다. log 같은 경우 테이블을 하나 따로 만들어서 관리를 하면 유용할 것 같다. 여유가 되면 반드시 해봐야겠다.
사실 프론트엔드 쪽은 흐름이 어떻게 되는지 감을 못잡겠어서 우선 각 도메인별로 분업하여 구현 중이다.
맡은 Question 부분을 간단하게 리뷰해보겠다.
import com.gujo.stackoverflow.member.entity.Member;
import lombok.Getter;
import lombok.Setter;
import javax.persistence.*;
import java.time.LocalDateTime;
@Entity
@Getter
@Setter
public class Question {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long questionId;
@ManyToOne // 유저 한명이 여러 질문 작성 가능 -> N:1
@JoinColumn(name = "MEMBER_ID") // 현재 테이블에서 외래키에 해당하는 컬럼의 이름
private Member member;
@Column(nullable = false, length = 50)
private String title;
@Column(nullable = false)
private String content;
@Column(nullable = false)
private Long point = 0L; // 초기값 설정
@Column(nullable = false)
private LocalDateTime createdAt = LocalDateTime.now();
@Column(nullable = true)
private LocalDateTime modifiedAt;
}
질문글 post 요청이 왔을 때 해당 요청을 한 클라이언트를 식별하고 해당 값을 필드에 넣어주는 과정을 어떻게 구현하여야 하는 지 정리가 안된다.....
import com.gujo.stackoverflow.member.entity.Member;
import lombok.Getter;
import lombok.Setter;
import java.time.LocalDateTime;
public class QuestionDto {
@Getter
public static class PostDto {
private String title;
private String content;
}
@Getter
@Setter
public static class ResponseDto {
private Long questionId;
private Member member;
private String title;
private String content;
private Long point;
private LocalDateTime createdAt;
private LocalDateTime modifiedAt;
}
@Getter
@Setter
public static class getQuestionsResponseDto {
private Long questionId;
private Member member;
private String title;
private LocalDateTime createdAt;
private LocalDateTime modifiedAt;
}
@Getter
public static class PatchDto {
private String title;
private String content;
}
}
import com.gujo.stackoverflow.question.dto.QuestionDto;
import com.gujo.stackoverflow.question.entity.Question;
import com.gujo.stackoverflow.question.mapper.QuestionMapper;
import com.gujo.stackoverflow.question.service.QuestionService;
import org.springframework.data.domain.Pageable;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.ArrayList;
import java.util.List;
@RestController
@RequestMapping("/questions")
public class QuestionController {
private final QuestionService service;
private final QuestionMapper mapper;
public QuestionController(QuestionService questionService, QuestionMapper mapper) {
this.service = questionService;
this.mapper = mapper;
}
@PostMapping
public ResponseEntity postQuestion(@RequestBody QuestionDto.PostDto postDto) {
Question question = mapper.questionPostDtoToQuestion(postDto);
Question created = service.createQuestion(question);
QuestionDto.ResponseDto responseDto = mapper.questionToQuestionResponseDto(created);
return new ResponseEntity(responseDto, HttpStatus.CREATED);
}
@GetMapping
public ResponseEntity getQuestions(Pageable pageable) {
List<Question> questions = service.getQuestions(pageable);
// 전체 질문 조회 시 게시물의 제목 이름 등 표시 -> 내용 미포함
// List<Question>으로 질문들을 받아오고 반복자를 활용해 전체질문 조회 요청용 응답 DTO로 변환
List<QuestionDto.getQuestionsResponseDto> result = new ArrayList<>();
for (Question question : questions) {
result.add(mapper.questionToGetQuestionsResponseDto(question));
}
return new ResponseEntity(result, HttpStatus.OK);
}
@GetMapping("/{question-id}")
public ResponseEntity getQuestion(@PathVariable("question-id") Long questionId) {
Question question = service.getQuestion(questionId);
QuestionDto.ResponseDto responseDto = mapper.questionToQuestionResponseDto(question);
return new ResponseEntity(responseDto, HttpStatus.OK);
}
@PatchMapping("/{question-id}")
public ResponseEntity patchQuestion(@PathVariable("question-id") Long questionId,
@RequestBody QuestionDto.PatchDto patchDto) {
Question question = mapper.questionPatchDtoToQuestion(patchDto);
Question updated = service.updateQuestion(questionId, question);
QuestionDto.ResponseDto responseDto = mapper.questionToQuestionResponseDto(updated);
return new ResponseEntity(responseDto, HttpStatus.OK);
}
@DeleteMapping("/{question-id}")
public ResponseEntity deleteQuestion(@PathVariable("question-id") Long questionId) {
service.deleteQuestion(questionId);
return new ResponseEntity<>(HttpStatus.OK);
}
}
import com.gujo.stackoverflow.question.entity.Question;
import com.gujo.stackoverflow.question.repository.QuestionRepository;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.time.LocalDateTime;
import java.util.List;
import java.util.Optional;
@Service
public class QuestionService {
private final QuestionRepository repository;
public QuestionService(QuestionRepository repository) {
this.repository = repository;
}
public Question createQuestion(Question question) {
return repository.save(question);
}
public List<Question> getQuestions(Pageable pageable) {
return repository.findAll(pageable).getContent();
}
public Question getQuestion(Long questionId) {
return repository.findById(questionId).orElseThrow();
}
@Transactional // repository.save 하지 않아도 DB 반영됨
public Question updateQuestion(Long questionId, Question question) {
Question findQuestion = repository.findById(questionId).orElseThrow();
// patchDto에 title, content 각각 항목에 값이 null이 아닐경우 수정사항 반영
// if(question.getTitle() != null) {
// findQuestion.setTitle(question.getTitle());
// }
// if (question.getContent() != null) {
// findQuestion.setContent(question.getContent());
// }
Optional.ofNullable(question.getTitle()).ifPresent(findQuestion::setTitle);
Optional.ofNullable(question.getContent()).ifPresent(findQuestion::setContent);
// 수정시간 반영
findQuestion.setModifiedAt(LocalDateTime.now());
return findQuestion;
}
public void deleteQuestion(Long questionId) {
repository.deleteById(questionId);
}
}
import com.gujo.stackoverflow.question.dto.QuestionDto;
import com.gujo.stackoverflow.question.entity.Question;
import org.mapstruct.Mapper;
@Mapper(componentModel = "spring")
public interface QuestionMapper {
Question questionPostDtoToQuestion(QuestionDto.PostDto postDto);
QuestionDto.ResponseDto questionToQuestionResponseDto(Question question);
QuestionDto.getQuestionsResponseDto questionToGetQuestionsResponseDto(Question question);
Question questionPatchDtoToQuestion(QuestionDto.PatchDto patchDto);
}
import com.gujo.stackoverflow.question.entity.Question;
import org.springframework.data.jpa.repository.JpaRepository;
public interface QuestionRepository extends JpaRepository<Question, Long> {
}
첫 프로젝트여서 아무것도 모르겠다. 근데 옆에 사람들도 그래보인다. 일단 팀장을 맡아 뭐라도 더 해보려고 노력중이다. 이번 기회에 aws IAM 계정도 생성해서 팀원들과 사용해봐야겠다.
프론트엔드 팀원들과 어떻게 협업해야하는 지는 정말 모르겠다. 얘기를 많이 나눠봐야 할 것 같은데 무엇을 물어봐야하는지가 제일 어려운 것 같다.
혼자서 무언가 끄적이는 것보다 함께 하는 사람들이 있다는게 다른 사람들을 보며 동기부여 받고 스스로 조금더 채찍질할 수 있게 해주는거 같아서 좋다.
첫 프로젝트이니 목표한 부분까지는 어떻게든 완성하고 배포해보고싶다.
아직 많이 진행하진 않았지만 팀원들이 잘 따라주고 말도 잘 통하는 것 같아 너무 감사하다..
많은 것을 배웠습니다, 감사합니다.