Skip to content

boiled-potatoes-kdt/be

Folders and files

NameName
Last commit message
Last commit date

Latest commit

ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 

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

  •  
  •  
  •  
  •