https://github.com/kimjuls/best-practice-java-spring-boot
Best practice of project template for Java Spring Boot
https://github.com/kimjuls/best-practice-java-spring-boot
Last synced: 3 months ago
JSON representation
Best practice of project template for Java Spring Boot
- Host: GitHub
- URL: https://github.com/kimjuls/best-practice-java-spring-boot
- Owner: kimjuls
- Created: 2024-06-21T06:43:09.000Z (11 months ago)
- Default Branch: master
- Last Pushed: 2025-01-31T04:16:26.000Z (4 months ago)
- Last Synced: 2025-01-31T05:20:29.283Z (4 months ago)
- Language: Java
- Size: 41 KB
- Stars: 3
- Watchers: 1
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
Awesome Lists containing this project
README
# best-practice-java-spring-boot
이 프로젝트는 Java 기반의 Spring Boot 오픈소스 템플릿 프로젝트이다.
이 템플릿 프로젝트의 목표는 간단한 Spring Boot 애플리케이션 아키텍처에 추가 구성 요소를 통합할 때 참조하거나, 직접 사용할 수 있는 모범 사례 예제를 제공하며 프로젝트 설정을 간소화한다. 이 템플릿을 사용하면 개발자는 클라우드 배포에 필요한 모든 구성 요소를 갖춘 견고한 Spring Boot 애플리케이션을 신속하게 설정할 수 있다.
## Summary
### Spring Boot Project
- Envelope-pattern 구현([AppResponseBodyAdvice.java](src/main/java/com/kimjuls/best_practice_java_spring_boot/advice/AppResponseBodyAdvice.java)): ResponseBodyAdvice 인터페이스를 구현하는 RestControllerAdvice Bean 클래스를 생성하고, JSON (역)직렬화가 가능한지, 가능하다면 약속된 응답 객체의 인스턴스 타입인지 등의 조건을 확인하여 적절한 응답을 내린다. 특히, 직렬화 가능여부를 MappingJackson2HttpMessageConverter 클래스로 확인하는 부분이 핵심이다. 체크하지 않는다면, AppResponse 생성자에 직렬화가 불가능한 body가 인자로 입력되고, 에러가 발생할 것이다.
```java
@RestControllerAdvice
public class AppResponseBodyAdvice implements ResponseBodyAdvice {
...
@Override
public Object beforeBodyWrite(...) {
boolean isAssignable = MappingJackson2HttpMessageConverter.class.isAssignableFrom(converterType);
if (isAssignable) {
...
}
...
}
```- 글로벌 에러 핸들러를 이용한 예외 처리([AppExceptionHandlerAdvice.java](src/main/java/com/kimjuls/best_practice_java_spring_boot/advice/AppExceptionHandlerAdvice.java)): RestControllerAdvice로 애노테이션된 Advice Bean 클래스를 생성하고, 모든 에러가 이곳으로 집중되도록 한다. 여기서의 핵심은 인자로 넘어온 Exception 객체 e가 AppException인지 확인하고, AppException에서 응답할 HTTP status code와 Body에 포함될 code와 message 값을 추출하는 과정이다. Frontend와 약속한 코드와 메시지는 [ExceptionDef.java](src/main/java/com/kimjuls/best_practice_java_spring_boot/classes/ExceptionDef.java)에 Enum으로 정의되어있다.
```java
// AppExceptionHandlerAdvice.java
@RestControllerAdvice
public class AppExceptionHandlerAdvice {
...
@ExceptionHandler
public ResponseEntity handleCommonException(Exception e) {
...
if (e instanceof AppException) {
int code = ((AppException) e).getCode();
String message = e.getMessage();
HttpStatus status = ((AppException) e).getStatus();
return new ResponseEntity<>(new BaseResponse(code, message), status);
} else {
...
}
}// AppException.java
public class AppException extends Exception {
private int code;
private HttpStatus status;
private String message;
...
}
```- JPA+Hibernate([UserRepository.java](src/main/java/com/kimjuls/best_practice_java_spring_boot/repository/UserRepository.java)): 간단하게 JpaRepository 인터페이스를 상속받은 인터페이스를 정의해두었다. 이 Repository 인터페이스를 사용하면 자동으로 JPA Bean을 생성하여 의존성을 주입하여 준다. 어느 정도 범위 안에서 메서드명을 규칙에 맞게 정의하면, 기본 쿼리 메서드 외 추가 쿼리를 자동으로 생성해준다.
```java
public interface UserRepository extends JpaRepository {
// Example: List findByEmail(String email);
}
```### Configuration For AWS Cloud Deployment
- Dockerfile: OpenJDK base 이미지로부터 이미지를 빌드하고, 실행 시 Java JAR 패키지를 실행한다. 플랫폼을 명시한 것은 테스트 환경이 macOS로 arm 아키텍처가 default이기 때문에 실제 Dockerhub에 push된 태그의 아키텍처를 명시한 것이다. 추가로, Task Definition에서 실행 명령어를 정의할 것이므로 Entrypoint를 정의하지 않았다.
```sh
FROM --platform=linux/amd64 openjdk:17-jdk-alpineCOPY . .
RUN ./mvnw clean packageARG JAR_FILE=target/*.jar
COPY ${JAR_FILE} app.jar
```- CloudFormation(SAM) YAML: 빌드한 이미지를 ECR에 push한 상태에서, 다음 YAML를 정의한다. AWS CLI와 SAM CLI가 설치 완료되어있어야 한다. 해당 솔루션은 이 [회고 프로젝트](https://github.com/kimjuls/sam-templates-ecs-private)를 참고하였다.
1. [sam.yaml](templates/sam.yaml): 구성되는 stack은 VPC, ECS Cluster, ECS Service이다. 단, 서비스를 실행하는 자체에는 Public 접근이 필요없으므로 이 템플릿 프로젝트에서는 Public Subnet 정의를 생략하였다.
```yaml
...
Resources:
VpcStack:
Type: AWS::Serverless::Application
Properties:
Location: vpc.yamlClusterStack:
Type: AWS::Serverless::Application
Properties:
Location: cluster.yamlServiceStack:
Type: AWS::Serverless::Application
Properties:
Location: service.yaml
...
```2. 다음 명령어로 실행한다.
```sh
sam deploy \
--template-file sam.yaml \
--stack-name spring-boot-app \
--capabilities CAPABILITY_IAM \
--parameter-overrides ImageUri= \
--resolve-s3
```