728x90

빌더 패턴은 복잡한 객체들을 단계별로 생성할 수 있도록 하는 생성 디자인 패턴입니다.

 

만들어봅니다!

 

Entity 역할을 하는 Person의 DTO를 Builder로 변환해 봅시다!

@Getter
@AllArgsConstructor
public class Person {
  private String name;
  private LocalDate birthday;
  private Address address;
}

 

Person을 Famil, Friend, Coworker 등으로 확장하면서 구현해보기 위해 Interface를 만들어봅니다.

public interface PersonDTO {
  String getName();
  LocalDate getBirthday();
  Address getAddress();
}

 

PersonDTO를 구현한 FriendDTO는 아래와 같습니다.

nickname 필드를 추가했습니다.

@Getter
@AllArgsConstructor
@ToString
public class FriendDTO implements PersonDTO {
  private String name;
  private String nickname;
  private LocalDate birthday;
  private Address address;
}

 

빌더 패턴을 사용하면, 매개변수가 많을 경우, 매개변수의 조합이 많을 경우 간편하게 불변 객체를 만들 수 있습니다.

 

빌더 클래스도 DTO 클래스와 동일하게 interface를 만들고 구현 객체를 만듭니다.

🙅‍♂️ 아래 코드는 나중에 수정이 될 것입니다^^,,,

public interface PersonDTOBuilder {
  PersonDTOBuilder name(String name);
  PersonDTOBuilder birthday(LocalDate birthday);
  PersonDTOBuilder address(Address address);

  PersonDTO build();
}

 

public class FriendDTOBuilder implements PersonDTOBuilder {
  private String name;
  private String nickname;
  private LocalDate birthday;
  private Address address;

  @Override
  public FriendDTOBuilder name(String name) {
    this.name = name;
    return this;
  }

  public FriendDTOBuilder nickname(String nickname) {
    this.nickname = nickname;
    return this;
  }

  @Override
  public FriendDTOBuilder birthday(LocalDate birthday) {
    this.birthday = birthday;
    return this;
  }

  @Override
  public FriendDTOBuilder address(Address address) {
    this.address = address;
    return this;
  }

  @Override
  public PersonDTO build() {
    return new FriendDTO(name, nickname, birthday, address);
  }
}

 

이렇게 작성하면 Builder 패턴으로 객체를 만들 수 있으나..

모든 새로운 빌더마다 반환 타입을 다시 지정해줘야 하는 번거로움이 생기고, 반환 타입을 실수할 가능성이 높아집니다..

 

제네릭을 사용해서 조금 더 안전한 코드를 만들어봅시다.

public interface PersonDTOBuilder<T extends PersonDTOBuilder<T>> {
  T name(String name);
  T birthday(LocalDate birthday);
  T address(Address address);

  PersonDTO build();
  PersonDTO getPersonDTO();
}

 

 

여기서 그치지 않고, Builder를 품은 DTO도 만들 수 있습니다,,🤗

@Getter
@Setter
@ToString
@AllArgsConstructor
public class CafeDTO {
  private String name;
  private String address;
  private String signature;
  private String rating;

  public static Builder builder() {
    return new Builder();
  }

  public static class Builder {
    private String name;
    private String address;
    private String signature;
    private String rating;

    private CafeDTO cafeDTO;

    public Builder name(String name) {
      this.name = name;
      return this;
    }

    public Builder address(Address address) {
      this.address = "(" + address.getZipCode() + ") " + address.getCity() + " " + address.getState();
      return this;
    }

    public Builder signature(String signature) {
      this.signature = signature;
      return this;
    }

    public Builder rating(int rating) {
      this.rating = "★".repeat(rating);
      return this;
    }

    public CafeDTO build() {
      this.cafeDTO = new CafeDTO(name, address, signature, rating);
      return this.cafeDTO;
    }

    public CafeDTO getCafeDTO() {
      return this.cafeDTO;
    }
  }
}

 

이제야 자주보던 @Builder를 선언했을 때와 같은 모양으로 쓸 수 있습니다,,

CafeDTO cafe = CafeDTO.builder()
                      .name("starbucks")
                      .address(address)
                      .signature("americano")
                      .rating(5)
                      .build();

 

여기에 마지막으로 Entity를 DTO로 바꿔주는 static 메서드도 추가가 가능하죠,,

public static CafeDTO toDTO(Cafe cafe) {
    return CafeDTO.builder()
                  .name(cafe.getName())
                  .address(cafe.getAddress())
                  .signature(cafe.getSignature())
                  .rating(cafe.getRating())
                  .build();
}

 

Entity를 DTO로,,

Cafe cafe = new Cafe("two-some", new Address("12345", "seoul", "bangbae"), "milktea", 5);
CafeDTO cafeDTOFromEntity = CafeDTO.toDTO(cafe);

 

 

 

📑

참고 자료

Chat GPT

https://inpa.tistory.com/entry/GOF-%F0%9F%92%A0-%EB%B9%8C%EB%8D%94Builder-%ED%8C%A8%ED%84%B4-%EB%81%9D%ED%8C%90%EC%99%95-%EC%A0%95%EB%A6%AC

 

💠 빌더(Builder) 패턴 - 완벽 마스터하기

Builder Pattern 빌더 패턴(Builder Pattern)은 복잡한 객체의 생성 과정과 표현 방법을 분리하여 다양한 구성의 인스턴스를 만드는 생성 패턴이다. 생성자에 들어갈 매개 변수를 메서드로 하나하나 받아

inpa.tistory.com

https://refactoring.guru/ko/design-patterns/builder

 

빌더 패턴

/ 디자인 패턴들 / 생성 패턴 빌더 패턴 다음 이름으로도 불립니다: Builder 의도 빌더는 복잡한 객체들을 단계별로 생성할 수 있도록 하는 생성 디자인 패턴입니다. 이 패턴을 사용하면 같은 제작

refactoring.guru

https://www.udemy.com/course/design-patterns-in-java-concepts-hands-on-projects/?couponCode=PLOYALTY0923

 

 

 

728x90
728x90

 

요새 날이 넘 춥네요..

 

안그래도 추운 날씨에 밖에 나가기가 무서워진 요즘..

 

Handle이 고장한 8톤 트럭,,도 아닌데, throws를 선언해도 처리되지 않았다며 우기는 Controller와 화해한 이야기 풉니다,,

 

 

1. 문제의 코드

@GetMapping("/touch")
Mono<?> touch(@RequestParam(name = "queue", defaultValue = "default") String queue,
      @RequestParam(name = "user_id") Long userId,
      ServerWebExchange exchange) throws NoSuchAlgorithmException {
    return Mono.defer(() -> userQueueService.generateToken(queue, userId))
        .map(token -> {
          exchange.getResponse().addCookie(
              ResponseCookie.from("user-queue-%s-token".formatted(queue), token)
                  .maxAge(Duration.ofSeconds(300))
                  .path("/")
                  .build()
          );

          return token;
        });
  }
  
public Mono<String> generateToken(final String queue, final Long userId) throws NoSuchAlgorithmException{
    MessageDigest digest = MessageDigest.getInstance("SHA-256");
    String input = "user-queue-%s-%d".formatted(queue, userId);
    byte[] encodedHash = digest.digest(input.getBytes(StandardCharsets.UTF_8));

    StringBuilder hexString = new StringBuilder();
    for (byte b : encodedHash) {
      hexString.append(String.format("%02x", b));
    }
    
    return Mono.just(hexString.toString());
}

 

 

2. 문제의 상황

generateToken 메서드는 SHA-256 암호화 알고리즘을 이용해서 토큰값을 생성합니다.

MessageDigest 클래스의 getInstance를 사용하려면 Checked Exception인 NoSuchAlgorithmException에 대한 처리를 해줘야 합니다.

 

generateToken()를 호출하는 "/touch" API 또한 NoSuchAlgorithmException이 발생한다고 throws를 적으면 NoSuchAlgorithmException이 Unhandled exception이라며 오류가 발생합니다^^!

 

 

3. 문제의 원인

Spring WebFlux에서 Mono 또는 Flux 내부에서 발생하는 Checked 예외는 반드시 Mono.error() 또는 onErrorResume() 등을 통해 처리해야 합니다.

⭐ 즉, 비동기 코드(WebFlux)에서는 throws가 먹히지 않고 Mono.error(e)를 사용해야 합니다.

 

 

4. 해결 방법

Mono.defer() 내부에서 예외를 처리
@GetMapping("/touch")
Mono<?> touch(@RequestParam(name = "queue", defaultValue = "default") String queue,
              @RequestParam(name = "user_id") Long userId,
              ServerWebExchange exchange) {
    return Mono.defer(() -> {
        try {
            return userQueueService.generateToken(queue, userId);
        } catch (NoSuchAlgorithmException e) {
            return Mono.error(e);
        }
    }).map(token -> {
        exchange.getResponse().addCookie(
            ResponseCookie.from("user-queue-%s-token".formatted(queue), token)
                .maxAge(Duration.ofSeconds(300))
                .path("/")
                .build()
        );
        
        return token;
    });
}

 

Checked 예외를 Unchecked 예외로 변경
public Mono<String> generateToken(final String queue, final Long userId) {
    MessageDigest digest = null;
    try {
        digest = MessageDigest.getInstance("SHA-256");
    } catch (NoSuchAlgorithmException e) {
        throw new RuntimeException(e);
    }

    String input = "user-queue-%s-%d".formatted(queue, userId);
    byte[] encodedHash = digest.digest(input.getBytes(StandardCharsets.UTF_8));

    StringBuilder hexString = new StringBuilder();
    for (byte b : encodedHash) {
        hexString.append(String.format("%02x", b));
    }
    return Mono.just(hexString.toString());
}

 

 

🤗 정리

예외 유형 Mono에서 처리 방식
Checked Exception (NoSuchAlgorithmException) Mono.error(e)를 써야 Mono 체인에서 처리 가능
Unchecked Exception (RuntimeException)
따로 감싸지 않아도 Mono 체인에서 자동으로 예외 감지

 

 

🚀 결론

예외를 클라이언트에게 전달할 필요가 있을 경우 → Mono.error(e)로 감싸서 처리하는 것이 좋음
500 에러로 내보내면 충분할 경우 → try-catch 후 RuntimeException 던지는 것이 더 간결하고 좋음

 

 

 

📑

참고 자료

Chat GPT

https://fastcampus.co.kr/dev_online_traffic_data

 

9개 프로젝트로 경험하는 대용량 트래픽 & 데이터 처리 완벽 마스터하기 | 패스트캠퍼스

실무에서 자주 일어나는 대용량 트래픽 & 데이터 처리 업무를 한번에 마스터할 수 있도록 모든 것을 담았습니다. 대기업 & 빅테크 현업 강사진 8인과 함께 하는 고퀄리티 현업 대비형 강의! 타사

fastcampus.co.kr

 

 

728x90
728x90

Chill한 루피를 데려왔습니다.

이유는 없고, 요즘 유행인 것 같길래 우선 넣어봤습니다^^!

 

평소보다 이미지가 길어서 깔끔하지 않은 모양새로 글을 시작하게 됬지만, Chill한 루피와 함께라면 이 정도는 Chill하게 넘어갈 수 있지 않을까,, 생각합니다,,

 

오늘은 간단하게 테스트 코드 작성 시, Embedded Redis를 사용하는 방법을 정리해 보고자 합니다.

 

1. Spring Boot + Redis 환경에서 테스트 코드 작성 시 Embedded Redis를 사용하는 이유

실제 Redis 서버에 의존하지 않고 테스트를 실행할 수 있기 때문입니다. 즉, Redis를 설치하지 않아도 테스트 가능합니다.
Embedded Redis는 테스트가 끝나면 자동으로 종료되므로, 테스트 데이터 정리를 신경 쓸 필요 없습니다.
클라이언트-서버 통신이 필요없어서 네트워크 I/O 오버헤드가 발생하지 않으므로 실제 Redis 서버보다 좀 더 빠릅니다.

 

* 네트워크 I/O 오버헤드: 데이터를 전송할 때 발생하는 추가적인 시간/비용/리소스 소비

 

 

2. Dependancy 추가

build.gradle
dependencies {
	testImplementation 'com.github.codemonstur:embedded-redis:1.4.3' // test 전용 embedded redis 추가
}

 

 

3. Configuration 파일 작성

EmbeddedRedis.java
@TestConfiguration
public class EmbeddedRedis {
  private final RedisServer redisServer;

  public EmbeddedRedis() throws IOException {
    this.redisServer = new RedisServer(63790);
  }

  // 1. Spring이 EmbeddedRedis 객체를 생성
  @PostConstruct
  public void start() throws IOException {
    // 2️. EmbeddedRedis 생성 후 → start() 호출 (Redis 서버 시작)
    this.redisServer.start();
  }

  // 3. 테스트 실행

  @PreDestroy
  public void stop() throws IOException {
    // 4. EmbeddedRedis 소멸 전 → stop() 호출 (Redis 서버 종료)  
    this.redisServer.stop();
  }
}

🚨 Local에서 Redis를 기본 포트(6379)로 실행하고 있어서 Embedded Redis는 다른 포트를 지정해주었습니다.

 

 

4. 환경 설정 파일 추가

application.yml
spring:
  data:
    redis:
      host: 127.0.0.1
      port: 6379

---
spring:
  config:
    activate:
      on-profile: test
  data:
    redis:
      host: 127.0.0.1
      port: 63790

test 환경에서는 redis의 port가 다르므로 환경 설정 파일에 해당 내용을 추가해줍니다.

 

이를 통해 알 수 있는 건 환경마다 환경 설정을 다르게 할 수 있다는 사실입니다🤗!

 

 

5. 테스트 코드 작성

Test.java
@SpringBootTest
@Import(EmbeddedRedis.class)
@ActiveProfiles("test")
class UserQueueServiceTest {}

작성한 Redis Configuration을 Import 해주고, Profile을 적용하기 위해 ActiveProfiles도 추가해줍니다.

 

 

 

📑

참고 자료

Chat GPT

https://fastcampus.co.kr/dev_online_traffic_data

 

9개 프로젝트로 경험하는 대용량 트래픽 & 데이터 처리 완벽 마스터하기 | 패스트캠퍼스

실무에서 자주 일어나는 대용량 트래픽 & 데이터 처리 업무를 한번에 마스터할 수 있도록 모든 것을 담았습니다. 대기업 & 빅테크 현업 강사진 8인과 함께 하는 고퀄리티 현업 대비형 강의! 타사

fastcampus.co.kr

 

728x90
728x90

헤헷,, 제가 한 건 아니고,, Spring Boot Scheduler가 한 것입니다,,

를 나타나내는 잔망루피입니다..

 

사용 방법이 간단해서 짧게 정리하면서 git bash에서 하염없이 백그라운드 요청을 했던 조그마한 실수도 공유합니다^^..

 

1. Spring boot Scheduler란

주로 백그라운드에서 반복적으로 수행해야 하는 작업을 위해 사용되며, 정해진 시간이나 주기에 따라 작업을 실행할 수 있게 해줍니다.

대기열에 계신 고객님들을 손수 대기열에서 제거해드릴 수 있지만, 저보단 Spring Boot Scheduler가 더 잘 할 것이라 믿습니다^^!

 

 

2. 설정

FlowApplication.java
@EnableScheduling
@SpringBootApplication
public class FlowApplication {

	public static void main(String[] args) {
		SpringApplication.run(FlowApplication.class, args);
	}
}

 

@EnableScheduling을 통해 Scheduler 사용을 활성화힙니다.

 

 

3. Scheduler를 실행할 메서드 지정

UserQueueService.java
public class UserQueueService {
  @Scheduled(initialDelay = 5000, fixedDelay = 10000) // 서버 시작 후 5초 뒤, 이후 10초마다 메서드 실행
  public void scheduleAllowUser(){
    log.info("called Scheduling...");
  }
}

 

 

4. local/test 환경에서 Scheduler 동작 설정

application.yml
server:
  port: 9010

spring:
  data:
    redis:
      host: 127.0.0.1
      port: 6379

scheduler:
  enabled: true

---
spring:
  config:
    activate:
      on-profile: test
  data:
    redis:
      host: 127.0.0.1
      port: 63790

scheduler:
  enabled: false

테스트를 Scheduler를 실행하면 예상과는 다르게 동작할 수 있으므로 잠시 실행을 꺼둡니다.

 

public class UserQueueService {
  @Value("${scheduler.enabled}")
  private Boolean scheduling = false;
  
  @Scheduled(initialDelay = 5000, fixedDelay = 10000)
  public void scheduleAllowUser(){
    if(!scheduling){
      log.info("passed scheduling...");
      return;
    }

    log.info("called Scheduling...");
  }
}

환경 설정 value를 받아서 scheduling값에 할당해줍니다.

test 환경에서 enabled를 false로 설정해두었으니, passed scheduling을 호출하고 메서드는 종료될 것입니다.

 

🌹CMD를 이용해서 API 요청을 보낼 때, 주의할 점

$ curl -X POST localhost:9010/api/v1/queue?user_id=1001&queue=test1
[1]+  Done                    curl -X POST localhost:9010/api/v1/queue?user_id=1001

위에 처럼 요청하면, 의문의 [1]+ 함께 제 queue 파라미터를 무시합니다.

자판기에 동전을 넣고 음료수를 뽑아 먹으려했지만, 자판기가 동전을 먹은 경우랑 비슷한 것입니다..

하지만, 이 경우에는 제 탓도 있던^^...

 

Shell에서는 &가 명령어를 백그라운드에서 실행하도록 만들기 때문에, curl 명령어에서 &를 사용하면 URL의 쿼리 파라미터가 아니라 셸 명령어로 해석될 수 있습니다.

따라서 뒤에 붙은 queue=test1은 무시되거나 처리되지 않아 user_id 파라미터만 전달됩니다.

 

$ curl -X POST "localhost:9010/api/v1/queue?user_id=1001&queue=test1"

이런 경우에는 간단히, URL을 큰따옴표로 감싸서 셸이 URL을 제대로 인식하도록 하면 됩니다.

 

 

 

📑

참고 자료

Chat GPT

https://fastcampus.co.kr/dev_online_traffic_data

 

9개 프로젝트로 경험하는 대용량 트래픽 & 데이터 처리 완벽 마스터하기 | 패스트캠퍼스

실무에서 자주 일어나는 대용량 트래픽 & 데이터 처리 업무를 한번에 마스터할 수 있도록 모든 것을 담았습니다. 대기업 & 빅테크 현업 강사진 8인과 함께 하는 고퀄리티 현업 대비형 강의! 타사

fastcampus.co.kr

https://velog.io/@ktf1686/Spring-Spring-Scheduler-%EC%A0%81%EC%9A%A9%ED%95%B4%EB%B3%B4%EA%B8%B0

 

[Spring] Spring Scheduler 적용해보기

스프링 스케줄러는 Java에서 제공하는 스프링 프레임워크의 일부로, 애플리케이션 내에서 정기적으로 실행되어야 하는 작업을 스케줄링하는데 사용됩니다.스프링 스케줄러는 주로 백그라운드

velog.io

https://spring.io/guides/gs/scheduling-tasks

 

Getting Started | Scheduling Tasks

Although scheduled tasks can be embedded in web applications, the simpler approach (shown in this guide) creates a standalone application. To do so, package everything in a single, executable JAR file, driven by a Java main() method. The following snippet

spring.io

 

728x90
728x90

대기열 시스템 강의가 마무리에 들어가고 있습니다.

 

기존에 마실가실 리팩토링을 진행하면서 Custom Exception을 정리했던 적이 있는데, 이번에 새로 알게된 Record 객체와 더불어 다시 한 번 Custom Exception을 정리해 보고자합니다.

 

(+ 이전에 쓴 글: [1년 후 마실가실] Custom Error 처리)

 

 

1. ApplicationException.java

RuntimeException을 상속하는 Custom Exception을 선언합니다.

Exception이 발생했을 때, 고갱님께 의도하는 ErrorCode와 메시지를 보여드리기 위함입니다.

@AllArgsConstructor
@Getter
public class ApplicationException extends RuntimeException {
  private HttpStatus status;
  private String code;
  private String reason;

}

 

왜냐면 고객님께 아래와 같은 순수한 에러를 보여드리기에는 사이가 멀어질 수 있기 때문입니다.

"status":500,"error":"Internal Server Error",
"requestId":"679d92c0-1","message":"already registered...",
"trace":"java.lang.Exception: already registered...\r\n
\tat com.traffic.flow.service.UserQueueService.registerWaitQueue(UserQueueService.java:22)\r\n\t
Suppressed: The stacktrace has been enhanced by Reactor, 
refer to additional information below: \r\n
Error has been observed at the following site(s):\r\n
\t*__checkpoint ⇢ Handler com.traffic.flow.controller.UserQueueController#registerUser(Long) [DispatcherHandler]
...

 

 

2. ErrorCode.java

여러 에러 코드를 Enum 형식으로 정리해놓으면 기분이 좋습니다.

농담이고, enum을 사용하면 한 곳(ErrorCode)에서 예외를 관리할 수 있어서 유지보수가 용이해지기 때문입니다.

또한, build() 메서드를 활용해서 status, code, reason을 계속해서 직접 입력했을 때, 실수할 가능성도 낮출 수 있습니다.

@AllArgsConstructor
public enum ErrorCode {
  QUEUE_ALREADY_REGISTERED_USER(HttpStatus.CONFLICT, "UQ-0001", "Already Registered in Queue"),

  private final HttpStatus status;
  private final String code;
  private final String reason;

  public ApplicationException build(){
    return new ApplicationException(status, code, reason);
    // ErrorCode.QUEUE_ALREADY_REGISTERED_USER.build()를 호출하면 ApplicationException이 생성
  }
}

 

⭐ build() 메서드 동작 과정

Java의 enum은 내부적으로 객체처럼 동작하며, 생성된 인스턴스를 통해 필드 값을 참조할 수 있습니다.
ErrorCode.QUEUE_ALREADY_REGISTERED_USER.build()를 호출하면 QUEUE_ALREADY_REGISTERED_USER의 필드 값이 자동으로 ApplicationException 생성자에 전달됩니다.
따라서, 각 예외 코드마다 status, code, reason을 직접 넘겨줄 필요 없이 enum을 활용해 깔끔하게 관리할 수 있습니다.

 

🍀 동적 메시지 포맷팅 지원

@AllArgsConstructor
public enum ErrorCode {
  QUEUE_ALREADY_REGISTERED_USER_IN_QUEUE(HttpStatus.CONFLICT, "UQ-0002", "Already Registered in Queue-%s");
  // ErrorCode.QUEUE_ALREADY_REGISTERED_USER_IN_QUEUE.build("A"); -> Already Registered in Queue-A
  // 인자를 초과해서 넘길 경우 -> 무시, 인자보다 부족하게 넘김 경우 -> MissingFormatArgumentException 발생

  private final HttpStatus status;
  private final String code;
  private final String reason;

  public ApplicationException build(Object ...args){ // 0개 이상의 인자를 받을 수 있음
    return new ApplicationException(status, code, reason.formatted(args));
  }
}

🚨 formatted 메서드는 Java15 이상부터 사용 가능

 

 

3. applicationExceptionHandler.java

애플리케이션 전역에서 발생하는 예외를 처리하는 @RestControllerAdvice를 사용하여 applicationExceptionHandler를 만듭니다.

@RestControllerAdvice
// Spring에서 전역 예외 처리를 담당하는 클래스
// 컨트롤러에서 발생하는 예외를 가로채고, 적절한 응답을 반환
public class ApplicationAdvice {

  @ExceptionHandler(ApplicationException.class)
  // ApplicationException이 발생했을 때 실행되는 핸들러
  Mono<ResponseEntity<ServerExceptionResponse>> applicationExceptionHandler(ApplicationException e) {
    return Mono.just(ResponseEntity // ResponseEntity: HTTP 응답을 생성하는 객체
               .status(e.getStatus())
               .body(new ServerExceptionResponse(e.getCode(), e.getReason()))); // JSON 응답으로 변환할 데이터 구조
  }

  public record ServerExceptionResponse(String code, String reason) {

  }

 

⭐ record ServerExceptionResponse를 사용하는 이유

단순 문자열로 처리할 경우,

  1. JSON이 아니기 때문에 프론트엔드에서 파싱할 때 문제가 발생할 수 있음

  2. 새로운 정보를 추가하기 어려워 확장성이 떨어짐

방식 JSON 응답 확장성 유지보수
e.getCode() + e.getReason() ❌ 단순 문자열 ❌ 새로운 필드 추가 어려움 ❌ 가독성 떨어짐
new ServerExceptionResponse
(e.getCode(), e.getReason())
✅ JSON 자동 변환 ✅ 필드 추가 쉬움 ✅ 직관적

* Jackson 라이브러리가 있으면 record는 기본적으로 자동으로 JSON 직렬화됨

  * webflux, mvc 모두 jackson 라이브러리를 포함하고 있음

 

⭐  Java record

불변(immutable) 데이터 객체를 간단하게 정의하기 위한 새로운 클래스 유형으로, Java 16부터 정식 기능으로 사용

  * getter, toString(), equals(), hashCode() 등을 자동으로 생성

  * 데이터 전달용 객체(DTO)나 불변 객체를 만들 때 사용

public record RegisterUserResponse(Long rank) {}

// record -> class
public final class RegisterUserResponse {
// final class로 다른 클래스 extends 불가, 인터페이스 implements만 가능
    private final Long rank;
    // 필드 값을 변경할 수 없음

    public RegisterUserResponse(Long rank) {
        this.rank = rank;
    }

    public Long rank() { // getter 역할, *필드명과 동일한 이름을 가짐
        return rank;
    }

    @Override
    public String toString() {
        return "RegisterUserResponse[rank=" + rank + "]";
    }

    @Override
    public boolean equals(Object o) { /* equals() 자동 생성 */ }

    @Override
    public int hashCode() { /* hashCode() 자동 생성 */ }
}

 

 

4. 결과

* Connected to localhost (127.0.0.1) port 9010 (#0)
> POST /api/v1/queue?user_id=85 HTTP/1.1
> Host: localhost:9010
> User-Agent: curl/7.87.0 #요청을 보낸 애플리케이션
> Accept: */*
>
* Mark bundle as not supporting multiuse
< HTTP/1.1 409 Conflict
< Content-Type: application/json
< Content-Length: 57
<
{ [57 bytes data] 100    57  100    57    0     0   3879      0 --:--:-- --:--:-- --:--:--  4071
# 100: 총 데이터의 100%를 받았다는 의미
# 57: 총 데이터 크기 (57 바이트)
# 100: 총 데이터의 100%를 보냈다는 의미
# 57: 전송된 데이터 크기 (57 바이트)
# 0: 업로드 크기 (현재 요청이 POST지만, 업로드할 데이터가 없으면 0)
# 0: 현재 업로드 속도(body에 데이터가 있는 경우)
# 3879: 다운로드 속도 (3.879 KB/s)
# 0: 예상 남은 시간 (이미 완료되었으면 0)
{"code":"UQ-0001","reason":"Already Registered in Queue"}
* Connection #0 to host localhost left intact #서버가 연결을 유지한 상태

 

 

📑

참고 자료

Chat GPT

https://fastcampus.co.kr/dev_online_traffic_data

 

9개 프로젝트로 경험하는 대용량 트래픽 & 데이터 처리 완벽 마스터하기 | 패스트캠퍼스

실무에서 자주 일어나는 대용량 트래픽 & 데이터 처리 업무를 한번에 마스터할 수 있도록 모든 것을 담았습니다. 대기업 & 빅테크 현업 강사진 8인과 함께 하는 고퀄리티 현업 대비형 강의! 타사

fastcampus.co.kr

 

 

728x90