RFP 3번 프로젝트
Deploy 배포 파일
DockerFiles 도커 파일들
| EC2 Scripts
/home/ubuntu/spring/docker_script.sh
docker login ghcr.io -u Domae-back-end -p {token}
docker pull ghcr.io/boiled-potatoes-kdt/be/spring:latest
BLUE_SPRING="spring-blue"
GREEN_SPRING="spring-green"
if [ "$(docker ps -q -f name=$BLUE_SPRING)" ]; then
ACTIVE_SPRING=$BLUE_SPRING
IDELE_SPRING=$GREEN_SPRING
OLD_PORT="8081"
PORT=8080
else
ACTIVE_SPRING=$GREEN_SPRING
IDELE_SPRING=$BLUE_SPRING
OLD_PORT="8080"
PORT=8081
fi
docker run -d \
--name $IDELE_SPRING \
-p $PORT:8080 \
"ghcr.io/boiled-potatoes-kdt/be/spring"
sleep 30
sudo sed -i "s/http:\/\/localhost:$OLD_PORT/http:\/\/localhost:$PORT/g" "/etc/nginx/sites-available/default"
sudo nginx -t
sudo systemctl restart nginx
docker stop $ACTIVE_SPRING
docker rm $ACTIVE_SPRING
/home/ubuntu/db/docker_script.sh
docker login ghcr.io -u Domae-back-end -p {TOKEN}
docker pull ghcr.io/boiled-potatoes-kdt/be/mysql:latest
CONTAINER_NAME="mysql"
IMAGE_NAME="ghcr.io/boiled-potatoes-kdt/be/mysql:latest"
if [ "$(docker ps -q -f name=$CONTAINER_NAME)" ]; then
echo "컨테이너가 실행중입니다."
else
echo "컨테이너가 실행중이지 않습니다. 새로 시작합니다람쥐"
docker rm mysql
docker run -d --name $CONTAINER_NAME -p 3306:3306 -e MYSQL_ROOT_PASSWORD={PASSWORD} $IMAGE_NAME
fi
/home/ubuntu/redis/docker_script.sh
docker login ghcr.io -u Domae-back-end -p {TOKEN}
docker pull ghcr.io/boiled-potatoes-kdt/be/redis:latest
CONTAINER_NAME="redis"
IMAGE_NAME="ghcr.io/boiled-potatoes-kdt/be/redis:latest"
if [ "$(docker ps -q -f name=$CONTAINER_NAME)" ]; then
echo "컨테이너가 실행중입니다."
else
echo "컨테이너가 실행중이지 않습니다. 새로 시작합니다람쥐"
docker rm redis
docker run -d --name $CONTAINER_NAME -p 6379:6379 -e REDIS_PASSWORD={PASSWORD} $IMAGE_NAME
fi
- Spring Boot, Security, JPA
- MySQL
- Redis
- AWS, Docker
- Github, GithubAction
- Discord, Slack, Notion
- main (DDD 기법)
- domain
- test1(도메인명)
- controller
- service
- model
- request
- response
- type
- entity
- repository
- exception
- test2(도메인명)
- test1(도메인명)
- global
- config
- exception
- model
- util
- api
- validation
- domain
- 카멜 표기법
- Entity 생성시에 → Entity X → 명사
- DTO class -> Record class
- request, response → **Request.java, **Response.java
- Controller, Service 매서드명
- Controller
- 예시: getUserById, createUser, updateUser, deleteUser 등
- Service
- 예시: processOrder, calculateTotalPrice, cancelReservation 등
- Controller
- main (최종본)
- release (배포)
- develop (통합)
- feature/... (기능들)
- refactor/... (수정 브랜치)
- 예시 → feature/trip - feature/trip/discord
- feat:새로운 기능 추가
- fix:버그 수정
- docs:문서 수정
- style:코드 formatting, 세미콜론(;)누락, 코드 변경이 없는 경우
- refactor:코드 리팩터링
- test:테스트 코드, 리팩터링 테스트 코드 추가(프로덕션 코드 변경 X)
- chore:빌드 업무 수정, 패키지 매니저 수정(프로덕션 코드 변경 X)
- comment:필요한 주석 추가 및 변경
- rename:파일 혹은 폴더명을 수정하거나 옮기는 작업만인 경우
- remove:파일을 삭제하는 작업만 수행한 경우
- !BREAKING CHANGE:커다란 API 변경의 경우
- !HOTFIX:급하게 치명적인 버그를 고쳐야 하는 경우
- /api 공통적으로 들어가고 다음으로는 권한 그 다음으로는 API 명세서에 따른 URL
- /api/customer/..
- /api/influence/..
- Entity ←→ DTO
public record TestRequest(
String name,
String title
) {
public static TestRequest from(TestEntity entity) {
return new TestRequest(
entity.getName(),
entity.getTitle()
);
}
}@Entity
@Getter
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class TestEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String title;
private String name;
public static TestEntity from(TestRequest request) {
return TestEntity.builder()
.title(request.title())
.name(request.name())
.build();
}
}- Exception handling
public interface ErrorCode {
HttpStatus getStatus();
String getMsg();
}@RequiredArgsConstructor
public abstract class GlobalException extends RuntimeException {
@Getter
private final ErrorCode errorCode;
public abstract void exceptionHandling();
public GlobalResponse getErrorResponse() {
return new GlobalResponse(
errorCode.getMsg(),
errorCode.getStatus()
);
}
}public record GlobalResponse(
String msg,
HttpStatus status
) {
}@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(GlobalException.class)
public ResponseEntity errorResponse(GlobalException ex) {
ex.exceptionHandling();
return API.ERROR(ex);
}
}- API 응답
@Data
@AllArgsConstructor
@NoArgsConstructor
public class API {
public static ResponseEntity OK(Object data) {
return ResponseEntity.ok(data);
}
public static ResponseEntity OK() {
return ResponseEntity.ok(new GlobalResponse("성공", HttpStatus.OK));
}
public static ResponseEntity ERROR(GlobalException ex) {
return ResponseEntity.status(ex.getErrorCode().getStatus())
.body(ex.getErrorResponse());
}
}- validation - 기존 라이브러리에 원하는 validation 존재하지 않을 경우 커스텀 어노테이션 대체
-
커스텀 어노테이션
@Target({ElementType.PARAMETER, ElementType.FIELD}) @Retention(RetentionPolicy.RUNTIME) @Constraint(validatedBy = TestValidator.class) public @interface TestValidation { String message() default "Invalid category"; Class[] groups() default {}; Class[] payload() default {}; }
public class TestValidator implements ConstraintValidator<TestValidation, String> { @Override public void initialize(TestValidation constraintAnnotation) { ConstraintValidator.super.initialize(constraintAnnotation); } @Override public boolean isValid(String arg, ConstraintValidatorContext constraintValidatorContext) { return true; } }
-
기존 라이브러리
@RestController public class TestController { @PostMapping public ResponseEntity test( @RequestBody @Valid TestRequest request ) { return API.OK(); } }
public record TestRequest( @Length(max = 1, message = "내가 만든 에러") String name ) { }
-
