Skip to content

Commit

Permalink
Merge bf65945 into 8c7c91b
Browse files Browse the repository at this point in the history
  • Loading branch information
jaeyeonling committed Jul 19, 2019
2 parents 8c7c91b + bf65945 commit 56581ba
Show file tree
Hide file tree
Showing 13 changed files with 48 additions and 48 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
## 목차
1. [step-01 : Account 생성, 조회, 수정 API를 간단하게 만드는 예제](https://github.com/cheese10yun/spring-jpa/blob/master/doc/step-01.md)
2. [step-02 : 효과적인 validate, 예외 처리 (1)](https://github.com/cheese10yun/spring-jpa/blob/master/doc/step-02.md)
3. [step-03 : 효과적인 validate, 예외 처리 처리 (2)](https://github.com/cheese10yun/spring-jpa-best-practices/blob/master/doc/step-03.md)
3. [step-03 : 효과적인 validate, 예외 처리 (2)](https://github.com/cheese10yun/spring-jpa-best-practices/blob/master/doc/step-03.md)
4. [step-04 : Embedded를 이용한 Password 처리](https://github.com/cheese10yun/spring-jpa-best-practices/blob/master/doc/step-04.md)
5. [step-05: OneToMany 관계 설정 팁](https://github.com/cheese10yun/spring-jpa-best-practices/blob/master/doc/step-05.md)
6. [step-06: Setter 사용하지 않기](https://github.com/cheese10yun/spring-jpa-best-practices/blob/master/doc/step-06.md)
Expand Down
2 changes: 1 addition & 1 deletion doc/step-01.md
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,7 @@ public class AccountDto {
Account에 정보를 변경하는 API가 있다고 가정 했을 경우 RequestBody를 Account 클래스로 받게 된다면 다음과 같은 문제가 발생합니다.

* 데이터 안전성
- 정보 변경 API에서는 firstName, lastName 두 속성만 변경할 수 있다고 했으면 Account 클래스로 RequestBody를 받게 된다면 email, password, Account 클래의의 모든 속성값들을 컨트롤러를 통해서 넘겨받을 수 있게 되고 원치 않은 데이터 변경이 발생할 수 있습니다.
- 정보 변경 API에서는 firstName, lastName 두 속성만 변경할 수 있다고 했으면 Account 클래스로 RequestBody를 받게 된다면 email, password, Account 클래스의 모든 속성값들을 컨트롤러를 통해서 넘겨받을 수 있게 되고 원치 않은 데이터 변경이 발생할 수 있습니다.
- firstName, lastName 속성 이외의 값들이 넘어온다면 그것은 잘못된 입력값이고 그런 값들을 넘겼을 경우 Bad Request 처리하는 것이 안전합니다.
- Response 타입이 Account 클래스일 경우 계정의 모든 정보가 노출 되게 됩니다. JsonIgnore 속성들을 두어 임시로 막는 것은 바람직하지 않습니다.속성들을 두어 임시로 막는 것은 바람직하지 않습니다.
* 명확해지는 요구사항
Expand Down
22 changes: 11 additions & 11 deletions doc/step-02.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ API을 개발하다 보면 프런트에서 넘어온 값에 대한 유효성 검
## 중요 포인트

* `@Valid`를 통한 유효성검사
* `@ControllerAdvice`를 이용한 Exception 헨들링
* `@ControllerAdvice`를 이용한 Exception 핸들링
* `ErrorCode` 에러 메시지 통합

## @Valid 를 통한 유효성검사
Expand All @@ -24,7 +24,7 @@ public static class SignUpReq {
private String zip;
}
```
이전 단계에서 작성한 회원가입을 위한 SignUpReq.class에 새롭게 추가된 `@Email`, `@NotEmpty` 어 로테이션을 추가했습니다. 이 밖에 다양한 어노테이션들이 있습니다. 아래의 컨트롤러에서 `@Valid` 어 로테이션을 통해서 유효성 검사 가를 진행하고 유효성 검사를 실패하면 `MethodArgumentNotValidException` 예외가 발생합니다.
이전 단계에서 작성한 회원가입을 위한 SignUpReq.class에 새롭게 추가된 `@Email`, `@NotEmpty` 어노테이션을 추가했습니다. 이 밖에 다양한 어노테이션들이 있습니다. 아래의 컨트롤러에서 `@Valid` 어노테이션을 통해서 유효성 검사를 진행하고 유효성 검사를 실패하면 `MethodArgumentNotValidException` 예외가 발생합니다.

### Controller에서 유효성 검사
```java
Expand All @@ -34,10 +34,10 @@ public AccountDto.Res signUp(@RequestBody @Valid final AccountDto.SignUpReq dto)
return new AccountDto.Res(accountService.create(dto));
}
```
컨트롤러에 `@Valid` 어 로테이션을 추가했습니다. `SignUpReq` 클래스의 유효성 검사가 실패했을 경우 `MethodArgumentNotValidException` 예외가 발생하게 됩니다. **프론트에서 넘겨받은 값에 대한 유효성 검사는 엄청난 반복적인 작업이며 실패했을 경우 사용자에게 적절한 Response 값을 리턴해주는 것 또한 중요 비즈니스 로직이 아님에도 불과하고 많은 시간을 할애하게 됩니다.** 다음 부분은 `MethodArgumentNotValidException` 발생시 공통적으로 **사용자에게 적절한 Response 값을 리턴해주는 작업을 진행하겠습니다.**
컨트롤러에 `@Valid` 어노테이션을 추가했습니다. `SignUpReq` 클래스의 유효성 검사가 실패했을 경우 `MethodArgumentNotValidException` 예외가 발생하게 됩니다. **프론트에서 넘겨받은 값에 대한 유효성 검사는 엄청난 반복적인 작업이며 실패했을 경우 사용자에게 적절한 Response 값을 리턴해주는 것 또한 중요 비즈니스 로직이 아님에도 불과하고 많은 시간을 할애하게 됩니다.** 다음 부분은 `MethodArgumentNotValidException` 발생시 공통적으로 **사용자에게 적절한 Response 값을 리턴해주는 작업을 진행하겠습니다.**


## @ControllerAdvice를 이용한 Exception 헨들링
## @ControllerAdvice를 이용한 Exception 핸들링

```Java
@ControllerAdvice
Expand All @@ -49,7 +49,7 @@ public class ErrorExceptionController {
}
}
```
`@ControllerAdvice` 어 로테이션을 추가하면 특정 Exception을 핸들링하여 적절한 값을 Response 값으로 리턴해줍니다. 위처럼 별다른 `MethodArgumentNotValidException` 핸들링을 하지 않으면 스프링 자체의 에러 Response 값을 아래와 같이 리턴해줍니다.
`@ControllerAdvice` 어노테이션을 추가하면 특정 Exception을 핸들링하여 적절한 값을 Response 값으로 리턴해줍니다. 위처럼 별다른 `MethodArgumentNotValidException` 핸들링을 하지 않으면 스프링 자체의 에러 Response 값을 아래와 같이 리턴해줍니다.

### Error Response
```json
Expand Down Expand Up @@ -97,7 +97,7 @@ public class ErrorExceptionController {
"path": "/accounts"
}
```
너무나 많은 값을 돌려보내 주고 있으며 시스템 정보에 대한 값들도 포함되고 있어 위처럼 Response 값을 돌려보내는 것은 바람직하지 않습니다. 또 자체적으로 돌려보내 주는 Response 결과를 공통적인 포맷으로 가져가는 것은 최종적으로 프론트 엔드에서 처리해야 하므로 항상 공통적인 Response 포맷일 유지해야 합니다. 아래 `Error Response` 클래스를 통해서 공통적인 예외 Response 값을 갖도록 하겠습니다.
너무나 많은 값을 돌려보내 주고 있으며 시스템 정보에 대한 값들도 포함되고 있어 위처럼 Response 값을 돌려보내는 것은 바람직하지 않습니다. 또 자체적으로 돌려보내 주는 Response 결과를 공통적인 포맷으로 가져가는 것은 최종적으로 프론트 엔드에서 처리해야 하므로 항상 공통적인 Response 포맷을 유지해야 합니다. 아래 `Error Response` 클래스를 통해서 공통적인 예외 Response 값을 갖도록 하겠습니다.

### MethodArgumentNotValidException의 Response 처리

Expand Down Expand Up @@ -170,7 +170,7 @@ public class ErrorResponse {
]
}
```
동일한 ErrorResponse 값을 갖게 되었으며 어느 칼럼에서 무슨 무슨 문제들이 발생했는지 알 수 있게 되었습니다. `@Valid` 어 로테이션으로 발생하는 `MethodArgumentNotValidException`들은 모두 handleMethodArgumentNotValidException 메서드를 통해서 공통된 Response 값을 리턴합니다. **이제부터는 @Valid, 해당 필드에 맞는 어 로테이션을 통해서 모든 유효성 검사를 진행할 수 있습니다.**
동일한 ErrorResponse 값을 갖게 되었으며 어느 칼럼에서 무슨 무슨 문제들이 발생했는지 알 수 있게 되었습니다. `@Valid` 어노테이션으로 발생하는 `MethodArgumentNotValidException`들은 모두 handleMethodArgumentNotValidException 메서드를 통해서 공통된 Response 값을 리턴합니다. **이제부터는 @Valid, 해당 필드에 맞는 어노테이션을 통해서 모든 유효성 검사를 진행할 수 있습니다.**



Expand All @@ -192,7 +192,7 @@ public Account findById(long id) {
}
```

### handleAccountNotFoundException : 헨들링
### handleAccountNotFoundException : 핸들링
```java
@ExceptionHandler(value = {
AccountNotFoundException.class
Expand Down Expand Up @@ -241,15 +241,15 @@ public enum ErrorCode {
1. 중복적으로 작성되는 메시지들이 너무 많습니다.
- 예를 들어 `해당 회원을 찾을 수 없습니다.` 메시지를 로그에 남기는 메시지 형태는 너무나도 많은 형태입니다.
2. 메시지 변경이 힘듭니다.
- 메시지가 스트링 형식으로 모든 소스에 흩어져있을 경우 메시지 변경 시에 모든 곳을 다 찾아서 변경해야 합니다.모든 곳을 다 찾아서 변경해야 합니다.
- 메시지가 스트링 형식으로 모든 소스에 흩어져있을 경우 메시지 변경 시에 모든 곳을 다 찾아서 변경해야 합니다.


## 단점
위의 유효성 검사의 단점은 다음과 같습니다.

1. 모든 Request Dto에 대한 반복적인 유효성 검사의 어노테이션이 필요합니다.
- 회원 가입, 회원 정보 수정 등등 지속적으로 DTO 클래스가 추가되고 그때마다 반복적으로 어 로테이션이 추가됩니다.
- 회원 가입, 회원 정보 수정 등등 지속적으로 DTO 클래스가 추가되고 그때마다 반복적으로 어노테이션이 추가됩니다.
2. 유효성 검사 로직이 변경되면 모든 곳에 변경이 따른다.
- 만약 비밀번호 유효성 검사가 특수문자가 추가된다고 하면 비밀번호 변경에 따른 유효성 검사를 정규 표현식의 변경을 모든 DTO마다 해줘야 합니다.

이러한 단점들은 다음 `step-03 : 효과적인 validate, 예외 처리 처리 (2)`에서 다루어 보겠습니다. 지속적으로 포스팅이 어 가겠습니다. 긴 글 읽어주셔서 감사합니다.
이러한 단점들은 다음 `step-03 : 효과적인 validate, 예외 처리 처리 (2)`에서 다루어 보겠습니다. 지속적으로 포스팅이어 가겠습니다. 긴 글 읽어주셔서 감사합니다.
14 changes: 7 additions & 7 deletions doc/step-03.md
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
# step-03 : 효과적인 validate, 예외 처리 처리 (2)
# step-03 : 효과적인 validate, 예외 처리 (2)

이전 포스팅의 단점을 해결해서 더 효과적은 효과적인 validate, 예외 처리 처리 작업을 진행해보겠습니다.
이전 포스팅의 단점을 해결해서 더 효과적은 효과적인 validate, 예외 처리 작업을 진행해보겠습니다.

## [step-02 : 이전 포스팅의 단점](https://github.com/cheese10yun/spring-jpa/blob/master/doc/step-02.md)

1. 모든 Request Dto에 대한 반복적인 유효성 검사의 어노테이션이 필요합니다.
- 회원 가입, 회원 정보 수정 등등 지속적으로 DTO 클래스가 추가되고 그때마다 반복적으로 어 로테이션이 추가됩니다.
- 회원 가입, 회원 정보 수정 등등 지속적으로 DTO 클래스가 추가되고 그때마다 반복적으로 어노테이션이 추가됩니다.
2. 유효성 검사 로직이 변경되면 모든 곳에 변경이 따른다.
- 만약 비밀번호 유효성 검사가 특수문자가 추가된다고 하면 비밀번호 변경에 따른 유효성 검사를 정규 표현식의 변경을 모든 DTO마다 해줘야 합니다.

Expand Down Expand Up @@ -34,7 +34,7 @@ public class Email {
}
```

임베디드 키워드를 통해서 새로운 값 타입을 집적 정의해서 사용할 수 있습니다. Email 클래스를 새로 생성하고 거기에 Email 칼럼에 매핑하는 하였습니다.
임베디드 키워드를 통해서 새로운 값 타입을 집적 정의해서 사용할 수 있습니다. Email 클래스를 새로 생성하고 거기에 Email 칼럼에 매핑하였습니다.

## DTO 변경

Expand Down Expand Up @@ -66,7 +66,7 @@ public static class SignUpReq {
```


모든 Request Dto에 대한 반복적인 유효성 검사의 어 로테이션이 필요했었지만 **새로운 Email 클래스를 바라보게 변경하면 해당 클래스의 이메일 유효성 검사를 바라보게 됩니다.**그 결과 이메일에 대한 유효성 검사는 Embeddable 타입의 Email 클래스가 관리하게 됩니다. 물론 이메일 유효성 검사는 로직이 거의 변경할 일이 없지만 다른 입력값들은 변경할 일들이 자주 생깁니다. 이럴 때 모든 DTO에 가서 유효성 로직을 변경하는 것은 불편 것을 넘어서 불안한 구조를 갖게 됩니다. 관리 포인트를 줄이는 것은 제가 생각했을 때는 되게 중요하다고 생각합니다.
모든 Request Dto에 대한 반복적인 유효성 검사의 어노테이션이 필요했었지만 **새로운 Email 클래스를 바라보게 변경하면 해당 클래스의 이메일 유효성 검사를 바라보게 됩니다.** 그 결과 이메일에 대한 유효성 검사는 Embeddable 타입의 Email 클래스가 관리하게 됩니다. 물론 이메일 유효성 검사는 로직이 거의 변경할 일이 없지만 다른 입력값들은 변경할 일들이 자주 생깁니다. 이럴 때 모든 DTO에 가서 유효성 로직을 변경하는 것은 불편 것을 넘어서 불안한 구조를 갖게 됩니다. 관리 포인트를 줄이는 것은 제가 생각했을 때는 되게 중요하다고 생각합니다.


## 단점
Expand All @@ -86,7 +86,7 @@ public static class SignUpReq {
```

## 결론
포스팅에는 유효성 검사를 하기 위해서 임베디드 타입을 분리했지만 사실 이런 이점보다는 다른 이점들이 많습니다. 또 이러한 이유로만 분리하지도 않는 걸로 알고 있습니다. 잘 설계된 ORM 애플리케이션은 매핑 한 테이블의 수보다 클래스의 수가 더 많다고들 합니다. 제가 생했을 때 진정한 장점은 다음과 같다고 생각합니다.
포스팅에는 유효성 검사를 하기 위해서 임베디드 타입을 분리했지만 사실 이런 이점보다는 다른 이점들이 많습니다. 또 이러한 이유로만 분리하지도 않는 걸로 알고 있습니다. 잘 설계된 ORM 애플리케이션은 매핑한 테이블의 수보다 클래스의 수가 더 많다고들 합니다. 제가 생각했을 때 진정한 장점은 다음과 같다고 생각합니다.

Account 엔티티는 fistName, lastName, password, address1, address2, zip 갖는 자입니다. 하지만 이러한 단순한 정보로 풀어 둔 것 일뿐. 데이터의 연관성이 없습니다. 아래처럼 정리하는 것이 더 바람직하다고 생각합니다.

Expand All @@ -109,7 +109,7 @@ Account 엔티티는 이름, 비밀번호, 주소를 갖는다. 여기에 필요
"password": "string"
}
```
Account가 상세한 데이터를 그대로 가지고 있는 것은 객체지향적이지 않으며 응집 력만 떨어뜨리는 결과를 갖는다고 생각합니다. 저는 ORM JPA 기술은 단순히 반복적인 쿼리문을 대신 작성해주는 것이라고 생각하지는 않고 데이터를 데이터베이스에서만 생각하지 않고 그것을 객체지향 적으로 바라보게 결국 객체지향 프로그래밍을 돕는다고 생각합니다.
Account가 상세한 데이터를 그대로 가지고 있는 것은 객체지향적이지 않으며 응집력만 떨어뜨리는 결과를 갖는다고 생각합니다. 저는 ORM JPA 기술은 단순히 반복적인 쿼리문을 대신 작성해주는 것이라고 생각하지는 않고 데이터를 데이터베이스에서만 생각하지 않고 그것을 객체지향적으로 바라보아 결국 객체지향 프로그래밍을 돕는다고 생각합니다.


## 참고
Expand Down
6 changes: 3 additions & 3 deletions doc/step-04.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@
* 비밀번호 만료 기본 14일 기간이 있다.
* 비밀번호 만료 기간이 지나는 것을 알 수 있어야 한다.
* 비밀번호 5회 이상 실패했을 경우 더 이상 시도를 못하게 해야 한다.
* 비밀번호가 일치하는 경우 실패 카운트를 초기화 해야한다
* 비밀번호 변경시 만료일이 현재시간 기준 14로 연장되어야한다.
* 비밀번호가 일치하는 경우 실패 카운트를 초기화 해야한다.
* 비밀번호 변경시 만료일이 현재시간 기준 14일로 연장되어야한다.


```java
Expand Down Expand Up @@ -67,4 +67,4 @@ public class Password {
* LocalDateTime.now().plusSeconds(ttl); 현재 시간에서 시간 초만큼 더하는 함수입니다. 정말 직관적이며 다른 좋은 함수들이 있어 꼭 프로젝트에 도입해보시는 것을 추천드립니다.

## 결론
굳이 Password 에민 해당하는 경우가 아니라 핵심 도메인들을 Embeddable을 분리해서 책임을 분리하고 응집력, 재사용성을 높이는 것이 핵심 주제였습니다. 꼭 개인 프로젝트에서라도 핵 심도 메인을 성격에 맞게끔 분리해 보시는 것을 경험해보시길 바랍니다.
굳이 Password 에만 해당하는 경우가 아니라 핵심 도메인들을 Embeddable을 분리해서 책임을 분리하고 응집력, 재사용성을 높이는 것이 핵심 주제였습니다. 꼭 개인 프로젝트에서라도 핵심 도메인을 성격에 맞게끔 분리해 보시는 것을 경험해보시길 바랍니다.
4 changes: 2 additions & 2 deletions doc/step-05.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ public class DeliveryLog {
**지금부터는 1:N 관계 팁에 관한 이야기를 진행하겠습니다.**

* Delivery를 통해서 DeliveryLog를 관리함으로 `CascadeType.PERSIST` 설정을 주었습니다.
* 1: N 관계를 맺을 경우 List를 주로 사용하는데 객체 생성을 null로 설정하는 것보다 `new ArrayList&amplt&ampgt();`설정하는 것이 바람직합니다. 이유는 다음과 같습니다.
* 1: N 관계를 맺을 경우 List를 주로 사용하는데 객체 생성을 null로 설정하는 것보다 `new ArrayList<>();`설정하는 것이 바람직합니다. 이유는 다음과 같습니다.

```java
private void verifyStatus(DeliveryStatus status, Delivery delivery) {
Expand All @@ -75,7 +75,7 @@ public class DeliveryLog {
}
}
```
* 초기화하지 않았을 경우 null로 초기화되며 ArrayList에서 지원해주는 함수를 사용할 수 없습니다. 1:N 관계에서 N이 없는 경우 null인 상태인 보다 Empty 상태가 훨씬 직관적입니다. null의 경우 값을 못가져 온것인지 값이 없는 것인지 의미가 분명하지 않습니다.
* 초기화하지 않았을 경우 null로 초기화되며 ArrayList에서 지원해주는 함수를 사용할 수 없습니다. 1:N 관계에서 N이 없는 경우 null인 상태인 보다 Empty 상태가 훨씬 직관적입니다. null의 경우 값을 못 가져온 것인지 값이 없는 것인지 의미가 분명하지 않습니다.

```java
public void addLog(DeliveryStatus status) {
Expand Down
1 change: 1 addition & 0 deletions doc/step-06.md
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ public void updateMyAccount(AccountDto.MyAccountReq dto) {
위의 예제와 같은 예제 코드입니다. findById 메소드를 통해서 영속성을 가진 객체를 가져오고 도메인에 작성된 updateMyAccount를 통해서 업데이트를 진행하고 있습니다.

**repository.save() 메소드를 사용하지 않았습니다. 다시 말해 메소드들은 객체 그 자신을 통해서 데이터베이스 변경작업을 진행하고, create 메서드에 대해서만 repository.save()를 사용합니다**

### create
```java
// 전체 코드를 보시는 것을 추천드립니다.
Expand Down
Loading

0 comments on commit 56581ba

Please sign in to comment.