Skip to content

boiled-potatoes-kdt/be

Repository files navigation

🥔 Team.삶은감자


자영업자들과 인플루언서를 주대상으로 한 매장 홍보 플랫폼 구축

(주)소프트랩스

RFP 3번 프로젝트


🎒 아키텍쳐



📒 CI/CD


Deploy 배포 파일
DockerFiles 도커 파일들

| EC2 Scripts

Spring 서버 + 무중단 배포 Script

/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

Database 서버

/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

Redis 서버

/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

👩🏻‍💻 개발 기획


⚒️ 기술 Stack

  • 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(도메인명)
    • global
      • config
      • exception
      • model
      • util
      • api
      • validation

💬 코드 컨벤션

  • 카멜 표기법
  • Entity 생성시에 → Entity X → 명사
  • DTO class -> Record class
    • request, response → **Request.java, **Response.java
  • Controller, Service 매서드명
    • Controller
      • 예시: getUserById, createUser, updateUser, deleteUser 등
    • Service
      • 예시: processOrder, calculateTotalPrice, cancelReservation 등

📜 Git Branch

  • main (최종본)
  • release (배포)
  • develop (통합)
  • feature/... (기능들)
  • refactor/... (수정 브랜치)
  • 예시 → feature/trip - feature/trip/discord

📜 Git commit 컨벤션

  • feat:새로운 기능 추가
  • fix:버그 수정
  • docs:문서 수정
  • style:코드 formatting, 세미콜론(;)누락, 코드 변경이 없는 경우
  • refactor:코드 리팩터링
  • test:테스트 코드, 리팩터링 테스트 코드 추가(프로덕션 코드 변경 X)
  • chore:빌드 업무 수정, 패키지 매니저 수정(프로덕션 코드 변경 X)
  • comment:필요한 주석 추가 및 변경
  • rename:파일 혹은 폴더명을 수정하거나 옮기는 작업만인 경우
  • remove:파일을 삭제하는 작업만 수행한 경우
  • !BREAKING CHANGE:커다란 API 변경의 경우
  • !HOTFIX:급하게 치명적인 버그를 고쳐야 하는 경우

🚇 URL 컨벤션

  • /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
      ) {
      }

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Contributors 4

  •  
  •  
  •  
  •