Skip to content

Commit

Permalink
Feat/#51 로그 관련 도메인와 로그 API 요청의 필드명 변경 (#53)
Browse files Browse the repository at this point in the history
* refactor: 로그 레벨, 데이터 컬럼명 변경

- CreateLogRequest
  - logLevel -> level
  - logData -> data
- CreateLogServiceRequest
  - logLevel -> level
  - logData -> data
- Log
  - logData -> data
- 필드 변경에 따른 테스트 코드 수정

* feat: UUID와 byte 배열 변환을 위한 UUIDUtil 클래스 추가

UUID 문자열과 byte 배열 간의 변환을 위한 UUIDUtil 클래스를 구현했습니다.
- uuidStringToBytes: UUID 문자열을 byte 배열로 변환
- bytesToUuidString: byte 배열을 UUID 문자열로 변환

* test: UUID 변환을 검증하는 UUIDUtilTest 클래스 추가

UUID 변환을 검증하기 위한 UUIDUtilTest 클래스 생성:
- uuidToBytes_ShouldConvertUUIDStringToByteArray: UUID 문자열을 byte 배열로 변환하는 것을 검증
- bytesToUUIDString_ShouldConvertByteArrayToUUIDString: byte 배열을 UUID 문자열로 변환하는 것을 검증

* refactor: application_id를 appKey로 변경

- CreateLogServiceRequest applicationId -> appKey로 변경
- Log 도메인 객체에서 applicationId -> appKey
- LogController app-id -> appKey로 변경, Validation NotBlank로 변경
- LogSerivce에서 applicationId -> appKey로 변경
- 관련 테스트 변경

* refactor: UUIDUtil final 클래스로 변경, 기본 생성자 Private 추가

* feat: LogController 201 반환하도록 변경

* chore: bb -> byteBuffer로 변경

* test: Displayname 추가

* test: created로 response가 변경됨에 따라 테스트코드 변경

* test: 여러 개의 assertThat을 assertAll로 묶음

* refactor: Response 상태 코드를 어노테이션으로 처리 후 리턴 타입으로 void로 수정
  • Loading branch information
miiiinju1 authored Aug 15, 2024
1 parent de864c2 commit a1099c1
Show file tree
Hide file tree
Showing 12 changed files with 192 additions and 82 deletions.
28 changes: 28 additions & 0 deletions logbat/src/main/java/info/logbat/common/util/UUIDUtil.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package info.logbat.common.util;

import java.nio.ByteBuffer;
import java.util.UUID;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;

@NoArgsConstructor(access = AccessLevel.PRIVATE)
public final class UUIDUtil {

private static final int UUID_BYTE_LENGTH = 16;

public static byte[] uuidStringToBytes(String uuidStr) {
UUID uuid = UUID.fromString(uuidStr);
ByteBuffer byteBuffer = ByteBuffer.wrap(new byte[UUID_BYTE_LENGTH]);
byteBuffer.putLong(uuid.getMostSignificantBits());
byteBuffer.putLong(uuid.getLeastSignificantBits());
return byteBuffer.array();
}

public static String bytesToUuidString(byte[] bytes) {
ByteBuffer byteBuffer = ByteBuffer.wrap(bytes);
long high = byteBuffer.getLong();
long low = byteBuffer.getLong();
UUID uuid = new UUID(high, low);
return uuid.toString();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,13 @@ public class LogService {
private final LogRepository logRepository;

public long saveLog(CreateLogServiceRequest request) {
Long appId = request.applicationId();
String logLevel = request.logLevel();
String logData = request.logData();
String appKey = request.appKey();
String level = request.level();
String data = request.data();
LocalDateTime timestamp = request.timestamp();
// TODO Log 저장 전 Application ID 체크 로직 추가 필요

Log log = Log.of(appId, logLevel, logData, timestamp);
Log log = Log.of(appKey, level, data, timestamp);

return logRepository.save(log);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,17 @@
import java.time.LocalDateTime;

public record CreateLogServiceRequest(
Long applicationId,
String logLevel,
String logData,
String appKey,
String level,
String data,
LocalDateTime timestamp
) {

public static CreateLogServiceRequest of(Long applicationId, CreateLogRequest request) {
public static CreateLogServiceRequest of(String appKey, CreateLogRequest request) {
return new CreateLogServiceRequest(
applicationId,
request.logLevel(),
request.logData(),
appKey,
request.level(),
request.data(),
request.timestamp()
);
}
Expand Down
26 changes: 13 additions & 13 deletions logbat/src/main/java/info/logbat/domain/log/domain/Log.java
Original file line number Diff line number Diff line change
Expand Up @@ -9,33 +9,33 @@
public class Log {

private final Long logId;
private final Long applicationId;
private final String appKey;
private final Level level;
private final LogData logData;
private final LogData data;
private final LocalDateTime timestamp;

public static Log of(Long applicationId, String level, String logData, LocalDateTime timestamp) {
return new Log(applicationId, level, logData, timestamp);
public static Log of(String appKey, String level, String logData, LocalDateTime timestamp) {
return new Log(appKey, level, logData, timestamp);
}

public Log(Long applicationId, String level, String logData, LocalDateTime timestamp) {
this(null, applicationId, level, logData, timestamp);
public Log(String appKey, String level, String data, LocalDateTime timestamp) {
this(null, appKey, level, data, timestamp);
}

public Log(Long logId, Long applicationId, String level, String logData,
public Log(Long logId, String appKey, String level, String data,
LocalDateTime timestamp) {
this.logId = logId;
validateApplicationId(applicationId);
this.applicationId = applicationId;
validateAppKey(appKey);
this.appKey = appKey;
this.level = Level.from(level);
this.logData = LogData.from(logData);
this.data = LogData.from(data);
validateTimestamp(timestamp);
this.timestamp = timestamp;
}

private void validateApplicationId(Long applicationId) {
if (applicationId == null) {
throw new IllegalArgumentException("applicationId는 null이 될 수 없습니다.");
private void validateAppKey(String appKey) {
if (appKey == null || appKey.isBlank()) {
throw new IllegalArgumentException("appKey는 null이거나 빈 문자열이 될 수 없습니다.");
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,14 @@
import info.logbat.domain.log.application.payload.request.CreateLogServiceRequest;
import info.logbat.domain.log.presentation.payload.request.CreateLogRequest;
import jakarta.validation.Valid;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Positive;
import jakarta.validation.constraints.NotBlank;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestController;

@RestController
Expand All @@ -22,18 +22,15 @@ public class LogController {
private final LogService logService;

@PostMapping
public ResponseEntity<Void> saveLog(
@RequestHeader("app-id")
@NotNull(message = "Application ID가 비어있습니다.")
@Positive(message = "Application ID는 양수여야 합니다.") Long applicationId,
@ResponseStatus(HttpStatus.CREATED)
public void saveLog(
@RequestHeader("appKey")
@NotBlank(message = "appKey가 비어있습니다.") String appKey,

@Valid @RequestBody CreateLogRequest request
) {

logService.saveLog(CreateLogServiceRequest.of(applicationId, request));

return ResponseEntity.ok()
.build();
logService.saveLog(CreateLogServiceRequest.of(appKey, request));
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,10 @@

public record CreateLogRequest(
@NotBlank(message = "로그 레벨이 비어있습니다.")
String logLevel,
String level,

@NotBlank(message = "로그 데이터가 비어있습니다.")
String logData,
String data,

@NotNull(message = "타임스탬프가 비어있습니다.")
LocalDateTime timestamp
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package info.logbat.domain.log.repository;

import info.logbat.common.util.UUIDUtil;
import info.logbat.domain.log.domain.Log;
import java.sql.PreparedStatement;
import java.sql.Statement;
Expand All @@ -20,15 +21,15 @@ public class LogRepository {
private final JdbcTemplate jdbcTemplate;

public long save(Log log) {
String sql = "INSERT INTO logs (application_id, level, log_data, timestamp) VALUES (?, ?, ?, ?)";
String sql = "INSERT INTO logs (app_key, level, data, timestamp) VALUES (?, ?, ?, ?)";

KeyHolder keyHolder = new GeneratedKeyHolder();

jdbcTemplate.update(connection -> {
PreparedStatement ps = connection.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS);
ps.setLong(1, log.getApplicationId());
ps.setBytes(1, UUIDUtil.uuidStringToBytes(log.getAppKey()));
ps.setString(2, log.getLevel().name());
ps.setString(3, log.getLogData().getValue());
ps.setString(3, log.getData().getValue());
ps.setTimestamp(4, Timestamp.valueOf(log.getTimestamp()));
return ps;
}, keyHolder);
Expand All @@ -55,9 +56,9 @@ public Optional<Log> findById(Long logId) {

private static final RowMapper<Log> LOG_ROW_MAPPER = (rs, rowNum) -> new Log(
rs.getLong("log_id"),
rs.getLong("application_id"),
UUIDUtil.bytesToUuidString(rs.getBytes("app_key")),
rs.getString("level"),
rs.getString("log_data"),
rs.getString("data"),
rs.getTimestamp("timestamp").toLocalDateTime()
);
}
51 changes: 51 additions & 0 deletions logbat/src/test/java/info/logbat/common/util/UUIDUtilTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package info.logbat.common.util;

import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.assertAll;

import java.util.stream.Stream;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;

@DisplayName("UUIDUtil에서 ")
public class UUIDUtilTest {

@ParameterizedTest
@DisplayName("UUID 문자열은 byte 배열로 변환할 수 있다.")
@MethodSource("validUUIDProvider")
void uuidToBytes_ShouldConvertUUIDStringToByteArray(String uuidStr, byte[] expectedBytes) {
// when
byte[] bytes = UUIDUtil.uuidStringToBytes(uuidStr);

assertAll(
() -> assertThat(bytes).isNotNull(),
() -> assertThat(bytes.length).isEqualTo(16),
() -> assertThat(bytes).isEqualTo(expectedBytes)
);
}

@ParameterizedTest
@DisplayName("bytesToUUIDString은 byte 배열을 UUID 문자열로 정확하게 변환해야 한다.")
@MethodSource("validUUIDProvider")
void bytesToUUIDString_ShouldConvertByteArrayToUUIDString(String expectedUUIDStr, byte[] bytes) {
// when
String resultUUIDStr = UUIDUtil.bytesToUuidString(bytes);

// then
assertThat(resultUUIDStr).isEqualTo(expectedUUIDStr);
}


private static Stream<Arguments> validUUIDProvider() {
return Stream.of(
Arguments.of("550e8400-e29b-41d4-a716-446655440000",
new byte[]{85, 14, -124, 0, -30, -101, 65, -44, -89, 22, 68, 102, 85, 68, 0, 0}),
Arguments.of("123e4567-e89b-12d3-a456-426614174000",
new byte[]{18, 62, 69, 103, -24, -101, 18, -45, -92, 86, 66, 102, 20, 23, 64, 0}),
Arguments.of("00000000-0000-0000-0000-000000000000",
new byte[]{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0})
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import info.logbat.domain.log.repository.LogRepository;
import java.time.LocalDateTime;
import java.util.Optional;
import java.util.UUID;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
Expand All @@ -27,16 +28,17 @@ class LogServiceTest {
@Autowired
private LogRepository logRepository;

private static final String__문자열 = UUID.randomUUID().toString();

@DisplayName("Log를 저장할 수 있다.")
@Test
void saveLog() {
// given
Long 어플리케이션_ID = 1L;
String 로그_레벨 = "INFO";
String 로그_데이터 = "테스트_로그_데이터";
LocalDateTime 타임스탬프 = LocalDateTime.of(2021, 1, 1, 0, 0, 0);

CreateLogServiceRequest 요청_DTO = new CreateLogServiceRequest(어플리케이션_ID, 로그_레벨, 로그_데이터, 타임스탬프);
CreateLogServiceRequest 요청_DTO = new CreateLogServiceRequest(__문자열, 로그_레벨, 로그_데이터, 타임스탬프);

// when
long 저장된_ID = logService.saveLog(요청_DTO);
Expand All @@ -46,8 +48,8 @@ void saveLog() {

assertThat(찾은_로그).isPresent()
.get()
.extracting("logId", "applicationId", "level", "logData.value", "timestamp")
.contains(저장된_ID, 1L, Level.INFO, "테스트_로그_데이터", 타임스탬프);
.extracting("logId", "appKey", "level", "data.value", "timestamp")
.contains(저장된_ID, __문자열, Level.INFO, "테스트_로그_데이터", 타임스탬프);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import info.logbat.domain.common.ControllerTestSupport;
import info.logbat.domain.log.presentation.payload.request.CreateLogRequest;
import java.time.LocalDateTime;
import java.util.UUID;
import java.util.stream.Stream;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
Expand All @@ -19,11 +20,12 @@

class LogControllerTest extends ControllerTestSupport {

private static final String__문자열 = UUID.randomUUID().toString();

@DisplayName("[POST] 로그를 정상적으로 생성한다.")
@Test
void createLog() throws Exception {
// given
Long 어플리케이션_ID = 1L;
String 로그_레벨 = "INFO";
String 로그_데이터 = "테스트_로그_데이터";
LocalDateTime 타임스탬프 = LocalDateTime.of(2021, 1, 1, 0, 0, 0);
Expand All @@ -36,11 +38,11 @@ void createLog() throws Exception {
// when
ResultActions perform = mockMvc.perform(post("/logs")
.contentType(MediaType.APPLICATION_JSON)
.header("app-id", 어플리케이션_ID)
.header("appKey", __문자열)
.content(objectMapper.writeValueAsString(요청_DTO)));

// then
perform.andExpect(status().isOk());
perform.andExpect(status().isCreated());
}

@DisplayName("[POST] 어플리케이션_ID가 없으면 400 에러를 반환한다.")
Expand Down Expand Up @@ -68,7 +70,7 @@ void createLogWithoutAppId() throws Exception {
@DisplayName("[POST] 잘못된 입력으로 로그 생성 시 400 에러를 반환한다.")
@ParameterizedTest(name = "{index}: {0}")
@MethodSource("invalidLogCreationInputs")
void createLogWithInvalidInput(String testCase, Long appId, String level, String data,
void createLogWithInvalidInput(String testCase, String appKey, String level, String data,
LocalDateTime timestamp) throws Exception {
// given
CreateLogRequest 요청_DTO = new CreateLogRequest(level, data, timestamp);
Expand All @@ -77,7 +79,7 @@ void createLogWithInvalidInput(String testCase, Long appId, String level, String
// when
ResultActions perform = mockMvc.perform(post("/logs")
.contentType(MediaType.APPLICATION_JSON)
.header("app-id", appId)
.header("appKey", __문자열)
.content(objectMapper.writeValueAsString(요청_DTO)));

// then
Expand All @@ -86,17 +88,15 @@ void createLogWithInvalidInput(String testCase, Long appId, String level, String

private static Stream<Arguments> invalidLogCreationInputs() {
return Stream.of(
Arguments.of("어플리케이션_ID가 음수인 경우", -1L, "INFO", "테스트_로그_데이터",
LocalDateTime.of(2021, 1, 1, 0, 0, 0)),
Arguments.of("로그_레벨이 null인 경우", 1L, null, "테스트_로그_데이터",
Arguments.of("로그_레벨이 null인 경우", 앱__문자열, null, "테스트_로그_데이터",
LocalDateTime.of(2021, 1, 1, 0, 0, 0)),
Arguments.of("로그_레벨이 빈 문자열인 경우", 1L, " ", "테스트_로그_데이터",
Arguments.of("로그_레벨이 빈 문자열인 경우", __문자열, " ", "테스트_로그_데이터",
LocalDateTime.of(2021, 1, 1, 0, 0, 0)),
Arguments.of("로그_데이터가 null인 경우", 1L, "INFO", null,
Arguments.of("로그_데이터가 null인 경우", __문자열, "INFO", null,
LocalDateTime.of(2021, 1, 1, 0, 0, 0)),
Arguments.of("로그_데이터가 빈 문자열인 경우", 1L, "INFO", " ",
Arguments.of("로그_데이터가 빈 문자열인 경우", __문자열, "INFO", " ",
LocalDateTime.of(2021, 1, 1, 0, 0, 0)),
Arguments.of("타임스탬프가 null인 경우", 1L, "INFO", "테스트_로그_데이터", null)
Arguments.of("타임스탬프가 null인 경우", __문자열, "INFO", "테스트_로그_데이터", null)
);
}
}
Loading

0 comments on commit a1099c1

Please sign in to comment.