Post

[TIL]Jitpack 공통 라이브러리 배포

오늘 진행한 작업

🎉완료

  • Jitpack 으로 이번 프로젝트에서 사용할 공통 라이브러리 배포

발생한 ISSUE 및 해결 방법

ISSUE1. 서비스에서 공통으로 사용되는 모듈을 반복적으로 구현해야할까?

문제상황

  • 이번에 Signature서버, Wallet서버, VAN서버를 각각 띄운다.
  • 이때 공통되는 모듈이 있는데 똑같은 코드를 복붙하는게 비효율적이라고 생각되었다. 그래서 공통모듈을 배포하여 라이브러리로 사용하려고한다.
  • 그렇다면 어떻게 자바코드를 외부 라이브러리로 배포할 수 있을까?

해결방법

Jitpack

  • 간단하게 말하자면, JVM와 안드로이드 jar파일을 깃허브에서 릴리즈하면 외부 라이브러리로 등록할 수 있게 해준다.
  • github에 올리고 tag를 생성하면 jitpack에서 자동적으로 라이브러리를 생성해준다.

서버 비동기 통신을 위한 ApiClient

  • GET method
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
	/**
	 * Sends a GET request and logs the completion.
	 *
	 * @param webClient the WebClient instance used to send the request.
	 * @param uri the URI to send the request to.
	 * @param returnType the request body.
	 * @param <T> the type of the request body.
	 */
	public static <T> Mono<T> get(
		WebClient webClient,
		String uri,
		Class<T> returnType
	) {
		return webClient
			.get()
			.uri(uri)
			.retrieve()
			.bodyToMono(returnType)
			;
	}
  • 서비스마다 싱글톤패턴으로 webclient생성 후 사용하는 GET 메소드
  • 일단은 결과가 0~1개일 것같아 Mono로 구현해놓았는데 추후에 더많은 반환값이 필요하면 Flux도 추가할 예정이다.

  • Post method
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
	/**
	 * Sends a POST request and logs the completion.
	 *
	 * @param webClient the WebClient instance used to send the request.
	 * @param uri the URI to send the request to.
	 * @param body the request body.
	 * @param <T> the type of the request body.
	 * @param <U> the type of the response body.
	 */
	public static <T, U> Mono<U> post(
		WebClient webClient,
		String uri,
		T body,
		Class<U> returnType
	) {
		return webClient
			.post()
			.uri(uri)
			.bodyValue(body)
			.retrieve()
			.bodyToMono(returnType)
			;
	}

데이터 조회 삽입하는 RedisUtils

  • 마찬가지로 모든 서버에서는 redis에 접근한다. 이를 하나로 통합하면 key를 설정하기도 편해 구현해보았다.

  • Redis의 데이터 조회

1
2
3
4
5
6
7
8
9
10
11
12
13
	public static <T> T get(RedisTemplate<byte[], byte[]> redisTemplate,
		ObjectMapper objectMapper,
		Class<T> classType,
		Object... keys
		) {
		String key = makeKey(keys);
		byte[] redisValue = redisTemplate.opsForValue()
																			.get(key.getBytes(StandardCharsets.UTF_8));
		if (ObjectUtils.isEmpty(redisValue)) {
			throw new SignatureException(ServerErrorCode.BAD_REQUEST);
		}
		return readValue(objectMapper, redisValue, classType);
	}
  • Redis의 데이터 삽입
1
2
3
4
5
6
7
8
9
10
11
12
13
	public static <T> void put(RedisTemplate<byte[], byte[]> redisTemplate,
		ObjectMapper objectMapper,
		T value,
		Object... keys) {
		try {
			redisTemplate.opsForValue()
									.set(makeKey(keys).getBytes(StandardCharsets.UTF_8),
					objectMapper.writeValueAsString(value)
											.getBytes(StandardCharsets.UTF_8));
		} catch (JsonProcessingException ex) {
			throw new SignatureException(ServerErrorCode.BAD_REQUEST);
		}
	}
  • byte형태의 value를 dto에 매칭할때 JsonProcessingException이 발생할 수 있어 try-catch처리

  • 정해지지 않은 길이의 key값을 만들어주는 makeKey

1
2
3
4
5
6
7
	private static String makeKey(Object... keys) {
		StringBuilder sb = new StringBuilder();
		for (Object key : keys) {
			sb.append(key.toString());
		}
		return sb.toString();
	}
  • 이 메소드는 이 모듈에서만 사용되기 때문에 캡슐화

  • 코드 depth를 줄이기 위한 readValue

1
2
3
4
5
6
7
8
9
	private static <T> T readValue(ObjectMapper objectMapper,
		byte[] redisValue,
		Class<T> classType) {
		try {
			return objectMapper.readValue(redisValue, classType);
		} catch (IOException ex) {
			throw new SignatureException(ServerErrorCode.BAD_REQUEST);
		}
	}

이외에도 백엔드, 프론트엔드의 api 응답형태를 통일하기 위하여 ResponseEntityFactory를 구현하였다.

ResponseEntityFactory

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
	public static ResponseEntity<MessageBody<Void>> ok(
		String message
	) {
		return ResponseEntity.ok(MessageBody.of(message, null));
	}

	public static <T> ResponseEntity<MessageBody<T>> ok(
		String message,
		T body
	) {
		return ResponseEntity.ok(MessageBody.of(message, body));
	}

	public static ResponseEntity<MessageBody<Void>> created(
		String message
	) {
		return ResponseEntity.status(HttpStatus.CREATED)
			.body(MessageBody.of(message, null));
	}

	public static <T> ResponseEntity<MessageBody<T>> created(
		String message,
		T body
	) {
		return ResponseEntity.status(HttpStatus.CREATED)
			.body(MessageBody.of(message, body));
	}

	public static ResponseEntity<MessageBody<Void>> status(
		int status,
		String message
	) {
		return ResponseEntity.status(status)
			.body(MessageBody.of(message, null));
	}

	public static <T> ResponseEntity<MessageBody<T>> status(
		int status,
		String message,
		T body
	) {
		return ResponseEntity.status(status)
			.body(MessageBody.of(message, body));
	}

	public static ResponseEntity<MessageBody<Void>> status(
		HttpStatus status,
		String message
	) {
		return ResponseEntity.status(status)
			.body(MessageBody.of(message, null));
	}

	public static <T> ResponseEntity<MessageBody<T>> status(
		HttpStatus status,
		String message,
		T body
	) {
		return ResponseEntity.status(status)
			.body(MessageBody.of(message, body));
	}

👩‍💻얻은점

  • 아무래도 공통 모듈이기 때문에 어떤 형태의 클래스가 들어올지 알 수 없다. 확장성을 열어두기 위해서 제네릭을 사용할 수 있었던 경험이었다.
  • 또한, 다형성을 보장하기 위해 Overload를 써본 경험이었다.
This post is licensed under CC BY 4.0 by the author.

Comments powered by Disqus.