Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[kwj1270-issue5] 회원가입 기능 #9

Merged
merged 31 commits into from
Aug 6, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
53d5b56
[#5] feat: build.gradle jpa, validation 의존성 추가
kwj1270 Jul 31, 2021
6297426
[#5] feat: hibernate에 대한 Logging Level 추가
kwj1270 Jul 31, 2021
70f6637
[#5] test: User 인스턴스 기본 생성자 테스트 작성
kwj1270 Jul 31, 2021
affb9b9
[#5] test: User 인스턴스 빌더 테스트 작성
kwj1270 Jul 31, 2021
27cd41d
[#5] feat: User 인스턴스 빌더 기능 구현
kwj1270 Jul 31, 2021
b2a3384
[#5] test: UserRepository 인스턴스 save() 테스트 작성
kwj1270 Jul 31, 2021
e37e0d3
[#5] test: UserRepository 인스턴스 findById() 테스트 작성
kwj1270 Jul 31, 2021
6d0ffa5
[#5] test: UserRepository 인스턴스 findAll() 테스트 작성
kwj1270 Jul 31, 2021
1075bce
[#5] test: UserRepository 인스턴스 delete() 테스트 작성
kwj1270 Jul 31, 2021
3fafb51
[#5] test: UserJoinRequest 인스턴스 생성자 테스트 작성
kwj1270 Jul 31, 2021
d53567b
[#5] test: User 인스턴스 getter() 테스트 작성
kwj1270 Jul 31, 2021
e0a2b69
[#5] feat: User 인스턴스 getter() 기능 구현
kwj1270 Jul 31, 2021
3c3ce07
[#5] test: User 인스턴스 checkPassword() 테스트 작성
kwj1270 Jul 31, 2021
b5bbbea
[#5] feat: User 인스턴스 checkPassword() 기능 구현
kwj1270 Jul 31, 2021
5010571
[#5] test: UserJoiningService 인스턴스 생성자 테스트 작성
kwj1270 Jul 31, 2021
755a31c
[#5] feat: UserJoiningService 인스턴스 생성자 기능 구현
kwj1270 Jul 31, 2021
67bf286
[#5] test: UserJoinResponse 인스턴스 fromUser() 테스트 작성
kwj1270 Jul 31, 2021
80dc4fe
[#5] feat: UserJoinResponse 인스턴스 fromUser() 테스트 작성
kwj1270 Jul 31, 2021
684c204
[#5] test: UserJoiningService 인스턴스 join() 테스트 작성
kwj1270 Jul 31, 2021
1e0bdcb
[#5] feat: UserJoiningService 인스턴스 join() 기능 구현
kwj1270 Jul 31, 2021
69545f3
[#5] test: UserRestController 인스턴스의 join() 테스트 작성
kwj1270 Jul 31, 2021
d232a3b
[#5] feat: UserRestController 인스턴스의 join() 기능 구현
kwj1270 Jul 31, 2021
0637539
[#5] fix: repository 테스트에서 정적 인스터스는 롤백 안되는 버그 해결
kwj1270 Jul 31, 2021
c251bb1
[#5] feat: BaseTimeEntity 추가
kwj1270 Jul 31, 2021
31a5d7d
[#5] style: 코드 컨벤션 적용
kwj1270 Jul 31, 2021
e6e9ed2
[#5] test: BaseTimeEntity 인스턴스 생성 테스트 작성
kwj1270 Aug 1, 2021
d965e42
[#5] test: BaseTimeEntity 인스턴스 삭제 값 변경 테스트 작성
kwj1270 Aug 1, 2021
2112888
[#5] test: BaseTimeEntity 인스턴스 getter 테스트 작성
kwj1270 Aug 1, 2021
027dd4c
[#5] refactor: [#9] 피드백 반영
kwj1270 Aug 1, 2021
93dbc5f
[#5] refactor: build.gradle 사용하지 않는 의존성 제거
kwj1270 Aug 1, 2021
4984ede
[#5] refactor: 개행 컨벤션 적용
kwj1270 Aug 1, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
5 changes: 3 additions & 2 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,9 @@ repositories {
}

dependencies {
implementation 'org.springframework.boot:spring-boot-starter-data-jdbc'
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework.boot:spring-boot-starter-validation'
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
runtimeOnly 'com.h2database:h2'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
}
Expand All @@ -24,4 +25,4 @@ apply from: 'test.gradle'

test {
useJUnitPlatform()
}
}
Copy link
Contributor

Choose a reason for hiding this comment

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

이거 아래 한 줄 개행이 필요할듯해요! (아래 Test code들도)

Copy link
Author

Choose a reason for hiding this comment

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

흠.. 개행 컨벤션은 여태 의식하지 않았는데 의식해야겠군요

44 changes: 44 additions & 0 deletions src/main/java/com/study/realworld/domain/BaseTimeEntity.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package com.study.realworld.domain;

import org.springframework.data.annotation.CreatedDate;
import org.springframework.data.annotation.LastModifiedDate;
import org.springframework.data.jpa.domain.support.AuditingEntityListener;

import javax.persistence.Column;
import javax.persistence.EntityListeners;
import javax.persistence.MappedSuperclass;
import java.time.LocalDateTime;

@MappedSuperclass
@EntityListeners(AuditingEntityListener.class)
public class BaseTimeEntity {

@CreatedDate
@Column(name = "created_at", updatable = false)
Copy link
Contributor

Choose a reason for hiding this comment

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

배워갑니다 👍

private LocalDateTime createdAt;

@LastModifiedDate
@Column(name = "updated_at")
private LocalDateTime updatedAt;

@Column(name = "deleted_at")
private LocalDateTime deletedAt;

public LocalDateTime createdAt() {
return createdAt;
}

public LocalDateTime updatedAt() {
return updatedAt;
}

public LocalDateTime deletedAt() {
return deletedAt;
}

// 추후 삭제시 읽어서 기록하는 방식으로 변경 예정
public void recordDeletedTime(final LocalDateTime deletedAt) {
this.deletedAt = deletedAt;
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package com.study.realworld.domain.user.application;

import com.study.realworld.domain.user.domain.User;
import com.study.realworld.domain.user.domain.UserRepository;
import com.study.realworld.domain.user.dto.UserJoinRequest;
import com.study.realworld.domain.user.dto.UserJoinResponse;
import org.springframework.stereotype.Service;

import javax.validation.Valid;

@Service
public class UserJoinService {

private final UserRepository userRepository;

public UserJoinService(final UserRepository userRepository) {
this.userRepository = userRepository;
}

@Valid
public UserJoinResponse join(final UserJoinRequest userJoinRequest) {
final User user = userRepository.save(userJoinRequest.toUser());
return UserJoinResponse.fromUser(user);
Copy link

Choose a reason for hiding this comment

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

개인적인 견해지만 여기서 그냥 User를 Controller에 Return해줘도 좋지 않을까? 하는 생각이 있습니다~
표현 영역에서 데이터에 대한 표현을 수정하는 게 맞다고 보는데 우지님은 어떻게 생각하시는지 궁금합니다.

Copy link
Contributor

Choose a reason for hiding this comment

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

저도 비슷한 의견인데, DDD에서는 application 단에서는 presentation 단의 객체를 가지지 않는 것이 좋아요
presentation 에서 내려보낼 때는 가공해서 내려보내고
application 단에서 올려보낼 때는 그냥 바로 올려보내고 presentation 단에서 가공하는게 좋습니다 :D

Copy link
Author

Choose a reason for hiding this comment

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

@povia , @chance0523
사실, 저번 스터디에서 언급했듯이 저도 presentation 영역에서 바꿔서 진행을 하는게 좋다고 생각해요.
앞서 찬스님이 언급해주신 내용과 더불어 제 개인적인 견해를 덧붙이자면
Service가 Dto에 의존되어 버리면, 다른 Controller에서도 해당 Dto 형식을 맞춰서 사용해야 하기 때문이죠
(다양한 컨트롤러에서 해당 서비스 사용하기 힘들어짐)

Copy link
Author

Choose a reason for hiding this comment

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

이번 스터디에서 피드백을 통해 사용하면 좋을 것, 사용하기 조금 껄끄러운 것들을 정의하기 위해서
조금 다양하게 시도해보려 하는데 dto 사용은 확실히 좋지 않을 것 같네요 :) 👍

Copy link
Contributor

Choose a reason for hiding this comment

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

Service가 Dto에 의존되어 버리면, 다른 Controller에서도 해당 Dto 형식을 맞춰서 사용해야 하기 때문이죠
(다양한 컨트롤러에서 해당 서비스 사용하기 힘들어짐)

오 이렇게 생각은 못해봤는데 좋은 생각이네요 👍

Copy link
Author

Choose a reason for hiding this comment

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

@chance0523
DTO 공부할 때 참고했던 사이트 인데 여기서 인사이트 얻었습니다.

Copy link
Member

Choose a reason for hiding this comment

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

Service가 Dto에 의존되어 버리면, 다른 Controller에서도 해당 Dto 형식을 맞춰서 사용해야 하기 때문이죠
(다양한 컨트롤러에서 해당 서비스 사용하기 힘들어짐)

간결하게 모든걸 정리해줄 수 있는 좋은 문장인것 같아요 :)

}

}
106 changes: 106 additions & 0 deletions src/main/java/com/study/realworld/domain/user/domain/User.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
package com.study.realworld.domain.user.domain;

import com.study.realworld.domain.BaseTimeEntity;

import javax.persistence.*;

@Entity
public class User extends BaseTimeEntity {

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "user_id")
private Long id;

@Column(name = "user_email", length = 50, nullable = false, unique = true)
private String email;

@Column(name = "user_username", length = 20, nullable = false)
private String username;

@Column(name = "uesr_password", nullable = false)
private String password;

@Column(name = "user_bio")
private String bio;

@Column(name = "user_image")
private String image;

protected User() {
}

private User(final UserBuilder userBuilder) {
Copy link
Member

Choose a reason for hiding this comment

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

Builder를 이런 형태로 반환할 수도 있겠네요 👍

Copy link
Author

Choose a reason for hiding this comment

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

개인적으로 생성자 오버로딩은 많을수록 유연함을 준다고 생각해서 빌더를 넘겨서 사용하도록 했습니다! 👍

this.email = userBuilder.email;
this.username = userBuilder.username;
this.password = userBuilder.password;
this.bio = userBuilder.bio;
this.image = userBuilder.image;
}

public String email() {
return email;
}

public String username() {
return username;
}

public String bio() {
return bio;
}

public String image() {
return image;
}

public static UserBuilder Builder() {
return new UserBuilder();
}

public boolean checkPassword(final String otherPassword) {
return password.equals(otherPassword);
}

public static class UserBuilder {
private String email;
private String username;
private String password;
private String bio;
private String image;

Copy link
Member

Choose a reason for hiding this comment

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

정적 팩토리 생성자를 사용하셨는데 그럼 private constructor가 필요하지 않을까요? :)

Copy link
Author

Choose a reason for hiding this comment

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

이건 생각도 못했네요!!! 👍 다음에 반영해보겠습니다.

Copy link
Member

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.

네네 맞아요

private UserBuilder() {
}

public UserBuilder email(final String email) {
this.email = email;
return this;
}

public UserBuilder username(final String username) {
this.username = username;
return this;
}

public UserBuilder password(final String password) {
this.password = password;
return this;
}

public UserBuilder bio(final String bio) {
this.bio = bio;
return this;
}

public UserBuilder image(final String image) {
this.image = image;
return this;
}

public User build() {
return new User(this);
}

}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package com.study.realworld.domain.user.domain;

import org.springframework.data.repository.CrudRepository;

public interface UserRepository extends CrudRepository<User, Long> {
Copy link
Member

Choose a reason for hiding this comment

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

CrudRepository를 찾아보니까 아래처럼 되어 있네요 ! (참고)
JpaRepository extends PagingAndSortingRepository extends CrudRepository extends Repository

Copy link
Member

Choose a reason for hiding this comment

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

@Repository는 구현체에 있지 않나용?

Copy link
Author

Choose a reason for hiding this comment

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

네 감사합니다 :)
현 요구사항 단계에서는 JpaRepository 까지 구현하는건 스펙 오버인 것 같고
이후 점진적으로 구현하면서 필요하다 판단되면 추가하려고요!

Copy link
Author

Choose a reason for hiding this comment

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

@DolphaGo
이 부분에 대해서는 명시적으로 붙여줘야 될지 말아야 될지 고민이 되어서 붙여서 작성해봤습니다.
다음에는 제거해서 작성해볼게요 👍

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
package com.study.realworld.domain.user.dto;

import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.annotation.JsonTypeName;
import com.study.realworld.domain.user.domain.User;

import javax.validation.constraints.Email;
import javax.validation.constraints.NotBlank;

@JsonTypeName("user")
@JsonTypeInfo(include = JsonTypeInfo.As.WRAPPER_OBJECT, use = JsonTypeInfo.Id.NAME)
public final class UserJoinRequest {

@NotBlank
@JsonProperty("username")
private String username;

@Email
@NotBlank
@JsonProperty("email")
private String email;

@NotBlank
@JsonProperty("password")
private String password;

private UserJoinRequest() {
}

// 테스트용 오버로딩 생성자 -> 주관적인 생각으로 이런 상황은 오버로딩으로 유연성을 주는게 좋다고 생각합니다.
UserJoinRequest(final String username, final String email, final String password) {
this.username = username;
this.email = email;
this.password = password;
}

public final User toUser() {
return User.Builder()
.email(email)
.username(username)
.password(password)
.build();
}

@Override
public final String toString() {
return "UserJoinRequest{" +
"username='" + username + '\'' +
", email='" + email + '\'' +
", password='" + password + '\'' +
'}';
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package com.study.realworld.domain.user.dto;

import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.annotation.JsonTypeName;
import com.study.realworld.domain.user.domain.User;

import javax.validation.constraints.Email;
import javax.validation.constraints.NotBlank;

@JsonTypeName("user")
@JsonTypeInfo(include = JsonTypeInfo.As.WRAPPER_OBJECT, use = JsonTypeInfo.Id.NAME)
public final class UserJoinResponse {

@NotBlank
@JsonProperty("username")
private String username;

@Email
@NotBlank
@JsonProperty("email")
private String email;

@JsonProperty("bio")
private String bio;

@JsonProperty("image")
private String image;

public static final UserJoinResponse fromUser(final User user) {
return new UserJoinResponse(user);
}

private UserJoinResponse() {
}

private UserJoinResponse(final User user) {
this.email = user.email();
this.username = user.username();
this.bio = user.bio();
this.image = user.image();
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package com.study.realworld.domain.user.ui;
Copy link
Contributor

Choose a reason for hiding this comment

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

이건 잘 몰라서 그러는데,
물론 패키지 명은 엄청난 제약이 없겠지만,,, ui는 뭔가 '응?' 하게 되네요!
이렇게도 원래 쓰나요? 저는 presentation, controller만 봐서...!

Copy link
Author

Choose a reason for hiding this comment

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

우선 ddd start와 nextStep DDD 기반으로 디렉토리를 구성했습니다.
실무적으로 더 좋은 방법이 있다면 반영할게요 :)

Copy link
Contributor

Choose a reason for hiding this comment

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

앗 거기서 ui로 썼으면 그렇게 써도 되는 것 같아요!
저는 application layer는 application (우지님이 쓰신 것처럼)
presentation layer는 presentation으로만 써봤어요!

Copy link
Author

Choose a reason for hiding this comment

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

DDD 초보자여서 사실 어느 방법으로 써야 되는지 많이 헷갈리더라고요
하지만 확실히 제가 쓴 ui 보다 presentation이 보다 의미 전달이 잘 될 것 같네요!! 하나 배워갑니다 😉

Copy link

Choose a reason for hiding this comment

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

찬스님 말씀처럼 패키지명에 제약은 없지만 ui로 패키지명은 프론트의 그 ui와 헷갈려하는 분들이 있을 수도 있다고 생각합니다. 근데 사실 백엔드 개발자가 보기엔 ui, controller, presentation 모두 같은 의미를 담고 있다고 생각할 수도 있으니 요건 고려만 해보셔요


import com.study.realworld.domain.user.application.UserJoinService;
import com.study.realworld.domain.user.dto.UserJoinRequest;
import com.study.realworld.domain.user.dto.UserJoinResponse;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;

import javax.validation.Valid;

@RestController
public class UserRestController {

private static final String JOIN_REQUEST_LOG_MESSAGE = "join request: {}";
private static final Logger log = LoggerFactory.getLogger(UserRestController.class);

private final UserJoinService userJoinService;

public UserRestController(final UserJoinService userJoinService) {
this.userJoinService = userJoinService;
}

@PostMapping("/api/users")
public ResponseEntity<UserJoinResponse> join(@Valid @RequestBody final UserJoinRequest userJoinRequest) {
log.info(JOIN_REQUEST_LOG_MESSAGE, userJoinRequest.toString());
return ResponseEntity.ok().body(userJoinService.join(userJoinRequest));
Copy link

Choose a reason for hiding this comment

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

요런 방식으로 넘길 수도 있었네요 저도 반영하겠습니다~ 👍

}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package com.study.realworld.global.config;

import org.springframework.context.annotation.Configuration;
import org.springframework.data.jpa.repository.config.EnableJpaAuditing;

@EnableJpaAuditing
@Configuration
public class JpaConfig {
}
5 changes: 2 additions & 3 deletions src/main/resources/application.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ spring:
encoding: UTF-8
cache-duration: PT1H
datasource:
platform: h2
driver-class-name: org.h2.Driver
url: jdbc:h2:mem:real-world;MODE=MYSQL;DB_CLOSE_DELAY=-1
username: sa
Expand All @@ -19,7 +18,7 @@ spring:
console:
enabled: true
path: /h2-console

logging.level:
org.hibernate.SQL: debug
server:
port: 8080

Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package com.study.realworld;

import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;

class RealworldApplicationTests {

Expand Down