Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
d964fa5
refactor: TDA 법칙 적용
mywnajsldkf Sep 16, 2022
c2a8bb1
chores: 불필요한 코드 삭제
mywnajsldkf Sep 16, 2022
90273dc
feat: 질문하기 화면 불러오기
mywnajsldkf Sep 18, 2022
e2652c9
feat: QuestionEntity 생성
mywnajsldkf Sep 25, 2022
5054a4e
feat: 질문 등록 시간 관리를 위한 BaseTimeEntity 생성
mywnajsldkf Sep 25, 2022
8218760
feat: Question 인터페이스와 구현 객체 생성
mywnajsldkf Sep 25, 2022
97a4aa2
feat: 질문 작성 시, Dto -> Entity을 위한 Dto 생성
mywnajsldkf Sep 25, 2022
123f64e
feat: Question 비즈니스 로직 인터페이스, 구현체 생성
mywnajsldkf Sep 25, 2022
4515430
feat: 질문 서비스 생성자 주입
mywnajsldkf Sep 25, 2022
63c99ce
chores
mywnajsldkf Sep 25, 2022
9e2d8ae
feat: Dto -> entity를 위한 Mapper 생성
mywnajsldkf Sep 25, 2022
382a57d
test: 질문 등록 테스트 추가
mywnajsldkf Sep 25, 2022
92f9a9b
feat: 질문 등록 기능 구현
mywnajsldkf Sep 25, 2022
d913d6e
chores: 경로 수정
mywnajsldkf Oct 1, 2022
52bffd1
feat: Question API 작성
mywnajsldkf Oct 1, 2022
c8b0b11
feat: QuestionMapper 작성
mywnajsldkf Oct 1, 2022
eb1d90c
chores: 불필요한 파일, 어노테이션 삭제
mywnajsldkf Oct 1, 2022
e06bf9c
feat: qna 화면 추가
mywnajsldkf Oct 1, 2022
f2ad0aa
view: 질문 상세보기 연결
mywnajsldkf Oct 1, 2022
ed79049
feat: QuestionEntity 반환
mywnajsldkf Oct 1, 2022
30f2e6a
feat: index가 1부터 시작하도록 함수 작성
mywnajsldkf Oct 1, 2022
9f2fa60
feat: QuestionResponseDto 작성
mywnajsldkf Oct 1, 2022
a1ed8bd
view: 회원 정보 수정 화면 추가, 수정하기와 상세보기 경로 수정
mywnajsldkf Oct 2, 2022
35a173d
feat: 사용자 정보 수정
mywnajsldkf Oct 2, 2022
69b83ea
chores: 메서드명 수정
mywnajsldkf Oct 2, 2022
f8e118b
chores: @Builder 어노테이션 추가
mywnajsldkf Oct 2, 2022
34f467f
test: UserServiceImpl 테스트 코드 작성
mywnajsldkf Oct 2, 2022
0f27f1a
test: QuestionService 테스트 코드 작성
mywnajsldkf Oct 2, 2022
201d490
refactor: Impl 클래스 명 변경
mywnajsldkf Oct 14, 2022
d8268ec
refactor: UserUpdate DTO -> Entity Controller에서 하도록 수정
mywnajsldkf Oct 14, 2022
06ad635
chores: 불필요한 코드 삭제
mywnajsldkf Oct 14, 2022
3df9a5e
refactor: mockito를 사용하여 테스트하도록 수정
mywnajsldkf Oct 14, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 6 additions & 2 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ buildscript {
}

apply plugin: 'java'
apply plugin: 'eclipse'
apply plugin: 'org.springframework.boot'
apply plugin: 'io.spring.dependency-management'

Expand All @@ -23,7 +22,7 @@ repositories {
}

dependencies {
implementation 'org.junit.jupiter:junit-jupiter:5.9.0'
implementation 'org.junit.jupiter:junit-jupiter:5.3.2'
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework.boot:spring-boot-devtools'
Expand All @@ -37,6 +36,11 @@ dependencies {
implementation 'org.modelmapper:modelmapper:3.0.0'
implementation 'org.springdoc:springdoc-openapi-ui:1.6.11'
implementation 'com.h2database:h2'
implementation 'org.mapstruct:mapstruct:1.5.2.Final'
implementation 'org.mockito:mockito-core:2.22.0'
implementation 'org.mockito:mockito-junit-jupiter:2.22.0'
annotationProcessor 'org.mapstruct:mapstruct-processor:1.5.2.Final'
annotationProcessor 'org.projectlombok:lombok:1.18.24'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
testImplementation 'org.assertj:assertj-core:3.23.1'
}
20 changes: 17 additions & 3 deletions src/main/java/codesquad/AppConfig.java
Original file line number Diff line number Diff line change
@@ -1,22 +1,36 @@
package codesquad;

import codesquad.repository.ArrayUserRepository;
import codesquad.repository.QuestionRepositoryImpl;
import codesquad.repository.UserRepositoryImpl;
import codesquad.repository.QuestionRepository;
import codesquad.repository.UserRepository;
import codesquad.service.QuestionService;
import codesquad.service.QuestionServiceImpl;
import codesquad.service.UserService;
import codesquad.service.UserServiceImpl;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class AppConfig {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

컨트롤러는 @controller 어노테이션을 이용해 빈을 등록하고 계시는데, Service와 Repository는 Config 파일에서 빈을 따로 등록해주고 계시는 이유가 궁금합니다!

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

우선 의존성을 주입하여 코드/객체 간의 결합도를 낮추기 위해 Service와 Repository는 자바 코드로 직접 스프링 빈을 등록하였습니다. 이는 개발을 진행하다 Repository를 변경해야하는 상황일 때, Config에 등록된 Repository Bean만 수정하면 되기 때문입니다.

그럼 Controller의 역할을 생각해보겠습니다.
컨트롤러는 View와 Service를 이어주는 역할만 하고 비즈니스 로직을 가지고 있지 않습니다. 따라서 Repository가 변경되거나 비즈니스 로직이 변경되어도 Controller는 변경되지 않으므로 Config에서 등록하지 않았습니다.

ref: https://inf.run/SZEa -> 말 다듬는데 도움 받았습니다:)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

의존성 주입은 스프링이 알아서 해주는 부분입니다 ~
스프링 부트의 DI 방식에 대해 알아보면 좋을 것 같아요

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

제어의 역전(IoC)가 무엇인지도 한 번 알아보시면 좋을 것 같습니다!


@Bean
public UserService userService() {
return new UserServiceImpl(userRepository());
}

@Bean
public UserRepository userRepository() {
return new ArrayUserRepository();
return new UserRepositoryImpl();
}

@Bean
public QuestionService questionService() {
return new QuestionServiceImpl(questionRepository());
}

@Bean
public QuestionRepository questionRepository() {
return new QuestionRepositoryImpl();
}
}
74 changes: 74 additions & 0 deletions src/main/java/codesquad/controller/QuestionController.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
package codesquad.controller;

import codesquad.AppConfig;
import codesquad.dto.question.QuestionRequestDto;
import codesquad.dto.question.QuestionResponseDto;
import codesquad.entity.QuestionEntity;
import codesquad.mapper.QuestionMapper;
import codesquad.service.QuestionService;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;

import java.util.List;
import java.util.stream.Collectors;

@Controller
public class QuestionController {
ApplicationContext applicationContext = new AnnotationConfigApplicationContext(AppConfig.class);
QuestionService questionService = applicationContext.getBean("questionService", QuestionService.class);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

applicationcontext로부터 직접 빈을 가져오셔서 사용하신 이유가 궁금합니다!

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AppConfig에서 @componentscan 같은 Spring Bean을 자동으로 가져오기 위한 어노테이션을 사용하지 않았기에 직접 빈을 가져왔습니다!
(사실 질문 의도를 제대로 이해하지 못했습니다. 부연 설명 해주시면 감사하겠습니다!)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

스프링부트에서 생성자 주입을 하는 방법에 대해 검색해보시면 좋을 것 같아요

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

applicationcontext로부터 직접 빈을 가져오셔서 사용하신 이유가 궁금합니다!

questionService는 다음과 같이 생성자를 통해 주입되었습니다.

AppConfig

@Bean
public QuestionService questionService() {
     return new QuestionServiceImpl(questionRepository());
}

@Bean
public QuestionRepository questionRepository() {
     return new QuestionRepositoryImpl();
}

QuestionServiceImpl

public QuestionServiceImpl(QuestionRepository questionRepository) {
    this.questionRepository = questionRepository;
}

생성자 주입을 한 경우, getBean()을 통해 객체를 얻어와서 사용해야 합니다.


@GetMapping("/questions/post")
public String getQuestionForm() {
return "qna/form";
}

@PostMapping("/questions")
public String postQuestion(QuestionRequestDto questionRequestDto) {
QuestionEntity questionEntity = QuestionMapper.dtoToEntity(questionRequestDto);
questionService.postQuestion(questionEntity);
return "redirect:/questions";
}

@GetMapping("/questions")
public String getQuestionList(Model model) {
List<QuestionResponseDto> questions = questionService.findQuestions().stream()
.map(questionEntity -> QuestionMapper.entityToDto(questionEntity))
.collect(Collectors.toList());

model.addAttribute("questions", questions);

return "qna/list";
}

@GetMapping("/questions/{id}")
public String getQuestionShow(@PathVariable String id, Model model) {
QuestionResponseDto question = new QuestionResponseDto(questionService.findQuestion(id));

model.addAttribute("writer", question.getWriter());
model.addAttribute("title", question.getTitle());
model.addAttribute("contents", question.getContents());
model.addAttribute("date", question.getDate());

return "qna/show";
}

@GetMapping("/")
public String getHome(Model model) {
List<QuestionEntity> questionEntityList = questionService.findQuestions();
if (questionEntityList.size() == 0) {
return "qna/list";
}

List<QuestionResponseDto> questionResponseDtoList = questionService.findQuestions().stream()
.map(questionEntity -> QuestionMapper.entityToDto(questionEntity))
.collect(Collectors.toList());

model.addAttribute("questions", questionResponseDtoList);
return "qna/list";
}
}
21 changes: 15 additions & 6 deletions src/main/java/codesquad/controller/UserController.java
Original file line number Diff line number Diff line change
@@ -1,18 +1,17 @@
package codesquad.controller;

import codesquad.AppConfig;
import codesquad.dto.UserDto;
import codesquad.dto.user.UserDto;
import codesquad.dto.user.UserUpdateRequestDto;
import codesquad.entity.UserEntity;
import codesquad.mapper.UserMapper;
import codesquad.service.UserService;
import org.modelmapper.ModelMapper;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
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.RequestMapping;
import org.springframework.web.bind.annotation.*;

import java.util.List;
import java.util.stream.Collectors;
Expand All @@ -39,7 +38,6 @@ public String registerUser(UserDto userDto) {

@GetMapping()
public String getUserList(Model model) {
System.out.println("userService.findUsers().get(0) = " + userService.findUsers().get(0));
List<UserDto> users = userService.findUsers().stream()
.map(userEntity -> modelMapper.map(userEntity, UserDto.class)).collect(Collectors.toList());
model.addAttribute("users", users);
Expand All @@ -57,4 +55,15 @@ public String getUserProfile(@PathVariable String userId, Model model) {

return "user/profile";
}

@GetMapping("/{userId}/form")
public String getProfileEditForm() {
return "user/update_form";
}

@PutMapping("/{userId}/update")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

업데이트에는 보통 PUT과 PATCH가 사용됩니다. 이 둘의 차이는 무엇일까요?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PUT은 자원 전체를 교체하여, 자원의 모든 필드가 필요하고
PATCH는 자원의 부분만 교체하여, 자원의 일부 필드만 있어도 됩니다.

GOOD

PUT /userId/1
{
    "userId" : "mywnajsldkf",
     "age" : 24
}

GET /userId/1
{    
     "userId" : "mywnajsldkf",
     "age" : 24
}    

BAD

PUT /userId/1
{
    "userId" : "mywnajsldkf",
}

GET /userId/1
{    
     "userId" : "mywnajsldkf",
     "age" : null
}

GOOD

PATCH /userId/1
{
    "userId" : "mywnajsldkf",
}

GET /userId/1
{    
     "userId" : "mywnajsldkf",
     "age" : 24
}

public String updateUserProfile(@PathVariable("userId") String userId, UserUpdateRequestDto requestDto) {
userService.updateUser(userId, UserMapper.dtoToEntity(requestDto));
return "redirect:/users";
}
}
22 changes: 22 additions & 0 deletions src/main/java/codesquad/dto/question/QuestionRequestDto.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package codesquad.dto.question;

import lombok.*;

@AllArgsConstructor
@Getter
@Setter
@Builder
public class QuestionRequestDto {
private String writer;
private String title;
private String contents;

@Override
public String toString() {
return "QuestionDto{" +
"writer='" + writer + '\'' +
", title='" + title + '\'' +
", contents='" + contents + '\'' +
'}';
}
}
23 changes: 23 additions & 0 deletions src/main/java/codesquad/dto/question/QuestionResponseDto.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package codesquad.dto.question;

import codesquad.entity.QuestionEntity;
import codesquad.mapper.QuestionMapper;
import lombok.*;

@AllArgsConstructor
@Getter
@Setter
@Builder
public class QuestionResponseDto {
private String writer;
private String title;
private String contents;
private String date;

public QuestionResponseDto(QuestionEntity questionEntity) {
writer = questionEntity.getWriter();
title = questionEntity.getTitle();
contents = questionEntity.getContents();
date = QuestionMapper.localTimeToString(questionEntity.getCreatedDate());
}
}
Original file line number Diff line number Diff line change
@@ -1,13 +1,11 @@
package codesquad.dto;
package codesquad.dto.user;

import codesquad.entity.UserEntity;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;

@NoArgsConstructor
@AllArgsConstructor
@Getter
@Setter
public class UserDto {
Expand All @@ -23,22 +21,6 @@ public UserDto(UserEntity userEntity) {
email = userEntity.getEmail();
}

public String getUserId() {
return userId;
}

public String getPassword() {
return password;
}

public String getName() {
return name;
}

public String getEmail() {
return email;
}

@Override
public String toString() {
return "UserDto{" +
Expand Down
40 changes: 40 additions & 0 deletions src/main/java/codesquad/dto/user/UserUpdateRequestDto.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package codesquad.dto.user;

import lombok.Builder;

@Builder
public class UserUpdateRequestDto {
private String password;
private String name;
private String email;

public UserUpdateRequestDto(String password, String name, String email) {
this.password = password;
this.name = name;
this.email = email;
}

public String getPassword() {
return password;
}

public void setPassword(String password) {
this.password = password;
}

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public String getEmail() {
return email;
}

public void setEmail(String email) {
this.email = email;
}
}
13 changes: 13 additions & 0 deletions src/main/java/codesquad/entity/BaseTimeEntity.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package codesquad.entity;

import lombok.Getter;
import lombok.Setter;
import java.time.LocalDateTime;

@Getter
@Setter
public abstract class BaseTimeEntity {

private LocalDateTime createdDate;
private LocalDateTime modifiedDate;
}
24 changes: 24 additions & 0 deletions src/main/java/codesquad/entity/QuestionEntity.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package codesquad.entity;

import lombok.*;

@Setter
@Getter
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class QuestionEntity extends BaseTimeEntity {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Entity 라는 postfix 를 붙이지 않아도 될 것 같습니다 ~

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@hyukjin-lee class명 말씀하실까요??

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

넵넵 QuestionEntity -> Question 을 말씀하시는 것 같아요~


private String writer;
private String title;
private String contents;

@Override
public String toString() {
return "QuestionEntity{" +
"writer='" + writer + '\'' +
", title='" + title + '\'' +
", contents='" + contents + '\'' +
'}';
}
}
Loading