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

프로젝트 패키지 디렉토리 구조 설계 #2

Merged
merged 10 commits into from
Jan 1, 2024
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
package kr.co.fastcampus.Yanabada;
package kr.co.fastcampus.yanabada;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.data.jpa.repository.config.EnableJpaAuditing;

@SpringBootApplication
@EnableJpaAuditing
public class YanabadaApplication {

public static void main(String[] args) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package kr.co.fastcampus.yanabada.common.baseentity;

import jakarta.persistence.Column;
import jakarta.persistence.EntityListeners;
import jakarta.persistence.MappedSuperclass;
import java.time.LocalDateTime;
import org.springframework.data.annotation.CreatedDate;
import org.springframework.data.annotation.LastModifiedDate;
import org.springframework.data.jpa.domain.support.AuditingEntityListener;

@MappedSuperclass
@EntityListeners(AuditingEntityListener.class)
public abstract class BaseEntity {

@CreatedDate
@Column(updatable = false)
private LocalDateTime createdDate;

@LastModifiedDate
private LocalDateTime updatedDate;

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package kr.co.fastcampus.yanabada.common.config;

import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class AppConfig {
@Bean
public ObjectMapper objectMapper() {
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
objectMapper.registerModule(new JavaTimeModule());
objectMapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
return objectMapper;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package kr.co.fastcampus.yanabada.common.exception;

import lombok.Getter;

@Getter
public abstract class BaseException extends RuntimeException {

public BaseException() {
super();
}

public BaseException(String message) {
super(message);
}

public BaseException(String message, Throwable cause) {
super(message, cause);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package kr.co.fastcampus.yanabada.common.exception;

import kr.co.fastcampus.yanabada.common.response.ErrorCode;

public class CommonEntityNotFoundException extends BaseException {
public CommonEntityNotFoundException() {
super(ErrorCode.COMMON_ENTITY_NOT_FOUND.getErrorMsg());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
package kr.co.fastcampus.yanabada.common.exception.handler;

import java.lang.reflect.Field;
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;
import kr.co.fastcampus.yanabada.common.exception.BaseException;
import kr.co.fastcampus.yanabada.common.response.ResponseBody;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.NestedExceptionUtils;
import org.springframework.http.HttpStatus;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.validation.BindingResult;
import org.springframework.validation.FieldError;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.MissingServletRequestParameterException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException;

@Slf4j
@RestControllerAdvice
public class GlobalExceptionHandler {

@ResponseStatus(HttpStatus.BAD_REQUEST)
@ExceptionHandler(value = BaseException.class)
public ResponseBody<Void> handleBaseException(BaseException e) {
log.warn("[BaseException] Message = {}", e.getMessage());
return ResponseBody.fail(e.getMessage());
}

@ResponseStatus(HttpStatus.BAD_REQUEST)
@ExceptionHandler(value = MethodArgumentNotValidException.class)
public ResponseBody<Void> handleValidException(MethodArgumentNotValidException e) {
log.warn("[MethodArgumentNotValidException] Message = {}",
NestedExceptionUtils.getMostSpecificCause(e).getMessage());

BindingResult bindingResult = e.getBindingResult();
List<FieldError> fieldErrors = getSortedFieldErrors(bindingResult);

String response = "[Request error] "
+ fieldErrors.stream()
.map(fieldError -> String.format("%s",
fieldError.getDefaultMessage()
)
).collect(Collectors.joining());
return ResponseBody.fail(response);
}

private List<FieldError> getSortedFieldErrors(BindingResult bindingResult) {
List<String> declaredFields = Arrays.stream(
Objects.requireNonNull(bindingResult.getTarget()).getClass().getDeclaredFields())
.map(Field::getName)
.toList();

return bindingResult.getFieldErrors().stream()
.filter(fieldError -> declaredFields.contains(fieldError.getField()))
.sorted(Comparator.comparingInt(fe -> declaredFields.indexOf(fe.getField())))
.collect(Collectors.toList());
}

@ResponseStatus(HttpStatus.BAD_REQUEST)
@ExceptionHandler(value = IllegalArgumentException.class)
public ResponseBody<Void> illegalArgumentException(IllegalArgumentException e) {
log.error("[IllegalArgumentException] Message = {}", e.getMessage());
return ResponseBody.fail(e.getMessage());
}

@ResponseStatus(HttpStatus.BAD_REQUEST)
@ExceptionHandler(value = HttpMessageNotReadableException.class)
public ResponseBody<Void> httpMessageNotReadableException(HttpMessageNotReadableException e) {
log.error("[HttpMessageNotReadableException] Message = {}", e.getMessage());
return ResponseBody.fail(e.getMessage());
}

@ResponseStatus(HttpStatus.BAD_REQUEST)
@ExceptionHandler(value = MethodArgumentTypeMismatchException.class)
public ResponseBody<Void> methodArgumentTypeMismatchException(
MethodArgumentTypeMismatchException e
) {
log.error("[MethodArgumentTypeMismatchException] Message = {}", e.getMessage());
return ResponseBody.fail(e.getMessage());
}

@ExceptionHandler(MissingServletRequestParameterException.class)
@ResponseStatus(HttpStatus.BAD_REQUEST)
public ResponseBody<Void> missingServletRequestParameterException(
MissingServletRequestParameterException e
) {
log.error("[MissingServletRequestParameterException] Message = {}", e.getMessage());
return ResponseBody.fail(e.getParameterName()
+ " 파라미터가 빈 값이거나 잘못된 유형입니다.");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package kr.co.fastcampus.yanabada.common.response;

import lombok.Getter;
import lombok.RequiredArgsConstructor;

@Getter
@RequiredArgsConstructor
public enum ErrorCode {
COMMON_ENTITY_NOT_FOUND("존재하지 않는 엔티티입니다."),
;

private final String errorMsg;

public String getErrorMsg(Object... arg) {
return String.format(errorMsg, arg);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package kr.co.fastcampus.yanabada.common.response;

import lombok.Getter;
import lombok.RequiredArgsConstructor;

@Getter
@RequiredArgsConstructor
public class ResponseBody<T> {

private final Status status;
private final String errorMessage;
private final T data;

public static ResponseBody<Void> ok() {
return new ResponseBody<>(Status.SUCCESS, null, null);
}

public static <T> ResponseBody<T> ok(T data) {
return new ResponseBody<>(Status.SUCCESS, null, data);
}

public static ResponseBody<Void> fail(String errorMessage) {
return new ResponseBody<>(Status.FAIL, errorMessage, null);
}

public static <T> ResponseBody<T> fail(String errorMessage, T data) {
return new ResponseBody<>(Status.FAIL, errorMessage, data);
}

public static ResponseBody<Void> error(String errorMessage) {
return new ResponseBody<>(Status.ERROR, errorMessage, null);
}

public static <T> ResponseBody<T> error(String errorMessage, T data) {
return new ResponseBody<>(Status.ERROR, errorMessage, data);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package kr.co.fastcampus.yanabada.common.response;

public enum Status {
SUCCESS, FAIL, ERROR
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package kr.co.fastcampus.yanabada.domain.test.controller;

import kr.co.fastcampus.yanabada.common.response.ResponseBody;
import kr.co.fastcampus.yanabada.domain.test.dto.TestInfoResponse;
import kr.co.fastcampus.yanabada.domain.test.dto.TestSaveRequest;
import kr.co.fastcampus.yanabada.domain.test.service.TestService;
import lombok.RequiredArgsConstructor;
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.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/api/tests")
@RequiredArgsConstructor
public class TestController {

private final TestService testService;

@GetMapping("/{testId}")
public ResponseBody<TestInfoResponse> getTest(@PathVariable Long testId) {
return ResponseBody.ok(testService.findById(testId));
}

@PostMapping
public ResponseBody<TestInfoResponse> addTest(@RequestBody TestSaveRequest request) {
return ResponseBody.ok(testService.save(request));
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package kr.co.fastcampus.yanabada.domain.test.dto;

import kr.co.fastcampus.yanabada.domain.test.entity.TestEntity;
import lombok.Builder;

@Builder
public record TestInfoResponse(
Long id,
String name,
String email
) {

public static TestInfoResponse from(TestEntity testEntity) {
return TestInfoResponse.builder()
.id(testEntity.getId())
.name(testEntity.getName())
.email(testEntity.getEmail())
.build();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package kr.co.fastcampus.yanabada.domain.test.dto;

import kr.co.fastcampus.yanabada.domain.test.entity.TestEntity;

public record TestSaveRequest(
String name,
String email
) {

public TestEntity toEntity() {
return TestEntity.create(name, email);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package kr.co.fastcampus.yanabada.domain.test.entity;

import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import kr.co.fastcampus.yanabada.common.baseentity.BaseEntity;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.NoArgsConstructor;

@Entity
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Getter
public class TestEntity extends BaseEntity {

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;

@Column(nullable = false, length = 100)
private String name;

@Column(nullable = false, length = 100)
private String email;

private TestEntity(String name, String email) {
this.name = name;
this.email = email;
}

public static TestEntity create(String name, String email) {
return new TestEntity(name, email);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package kr.co.fastcampus.yanabada.domain.test.repository;

import kr.co.fastcampus.yanabada.domain.test.entity.TestEntity;
import org.springframework.data.repository.CrudRepository;

public interface TestRepository extends CrudRepository<TestEntity, Long> {

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package kr.co.fastcampus.yanabada.domain.test.service;

import kr.co.fastcampus.yanabada.domain.test.dto.TestInfoResponse;
import kr.co.fastcampus.yanabada.domain.test.dto.TestSaveRequest;
import kr.co.fastcampus.yanabada.domain.test.entity.TestEntity;
import kr.co.fastcampus.yanabada.domain.test.repository.TestRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service
@RequiredArgsConstructor
@Transactional(readOnly = true)
public class TestService {

private final TestRepository testRepository;

public TestInfoResponse findById(Long testId) {
TestEntity testEntity = testRepository.findById(testId).orElseThrow();
return TestInfoResponse.from(testEntity);
}

@Transactional
public TestInfoResponse save(TestSaveRequest request) {
TestEntity testEntity = request.toEntity();
TestEntity result = testRepository.save(testEntity);
return TestInfoResponse.from(result);
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package kr.co.fastcampus.Yanabada;
package kr.co.fastcampus.yanabada;

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