728x90
반응형

https://www.docker.com/101-tutorial

Dockerize는 이제 더이상 선택이 아니라 필수라고 생각한다. 너무나 많이 사용되고 있고 Microservice Architecture에서는 Docker container는 필수라고 생각된다. 특히 Kubernetes 와 같은 좋은 툴이 나와서 Container orchestration도 쉽게할 수 있으니 사용하지 않을 수가 없다. 

 

Docker is an open platform for developing, shipping, and running applications. Docker enables you to separate your applications from your infrastructure so you can deliver software quickly. With Docker, you can manage your infrastructure in the same ways you manage your applications. By taking advantage of Docker’s methodologies for shipping, testing, and deploying code quickly, you can significantly reduce the delay between writing code and running it in production.

 

특히, 혼자서 1대의 Laptop 또는 Desktop으로 개발을 진행한다면 반드시 써야 한다. 나의 개인 프로젝트의 목표가 MSA 서버를 만드는 것이니 지금 개발 중인 Spring 기반의 서버도 Docker로 빌드를 해야한다. 

 

Docker는 Documentation이 아주 잘되어 있다. 자세한 설명을 보고 싶으면 여기로 가면 된다. 여기에서는 Spring 서버를 간단하게 빌드하고 띄워보는 방법을 공유하고자 한다. 

 

Docker container를 만들고 띄우기 위해서는 당연히 Docker가 필요하다. Docker 설치는 아래 문서를 참고하자. (Docker Desktop 설치)

2022.01.12 - [Infrastructure/kubernetes] - kubernetes 설치하기 with Docker desktop

 

kubernetes 설치하기 with Docker desktop

1인 개발을 위해서 환경을 꾸밀 때에는 많은 제약이 있다. 여유 자금이 많아서 서버를 사놓고 VM을 여러 개 설치하여 사용할 수 있겠지만, 난 가난한 가장으로 그러한 환경을 꾸밀 수가 없었다.

corono.tistory.com

 

Docker Architecture : https://docs.docker.com/get-started/overview/

Dockerfile

Docker container를 실행하기 위해서는 Docker image를 만들어야 한다. 우리가 Virtual Machine을 띄우기 위해 OS image가 필요한 것과 같은 원리이다. 대신 Container의 경우 머신의 OS를 그대로 사용하기 때문에 VM보다는 가볍다라고 보면된다. 그래서 이미지도 OS보다 용량이 적다. 

 

이 이미지를 만들기 위해서는 Dockerfile이라고 하는 image build script가 필요하다. 

아래는 간단한 Spring 용 Dockerfile이다. 

FROM adoptopenjdk:11-jre-hotspot

ARG WAR_FILE=./target/*.war
ARG PROFILE

ENV SPRING_PROFILE=$PROFILE
COPY ${WAR_FILE} webapp.war

CMD ["java", "-Dspring.profiles.active=${SPRING_PROFILE}", "-jar", "webapp.war"]

나의 프로젝트의 경우 `war` 파일로 빌드를 하기 때문에 위와 같은 Commad를 사용하였다. 

그리고 현재 Project에 Profile을 추가해 둔 상태기 때문에 Profile에 따라 서버를 실행한다. 

 

Docker build

$ docker build --build-arg PROFILE=local --no-cache . -t spring-micro-session:latest

[+] Building 1.5s (7/7) FINISHED                                                                                                                                                                                                                        
 => [internal] load build definition from Dockerfile                                                                                                                                                                                               0.0s
 => => transferring dockerfile: 256B                                                                                                                                                                                                               0.0s
 => [internal] load .dockerignore                                                                                                                                                                                                                  0.0s
 => => transferring context: 2B                                                                                                                                                                                                                    0.0s
 => [internal] load metadata for docker.io/library/adoptopenjdk:11-jre-hotspot                                                                                                                                                                     0.0s
 => [internal] load build context                                                                                                                                                                                                                  1.2s
 => => transferring context: 42.15MB                                                                                                                                                                                                               1.2s
 => CACHED [1/2] FROM docker.io/library/adoptopenjdk:11-jre-hotspot                                                                                                                                                                                0.0s
 => [2/2] COPY ./target/*.war webapp.war                                                                                                                                                                                                           0.2s
 => exporting to image                                                                                                                                                                                                                             0.1s
 => => exporting layers                                                                                                                                                                                                                            0.1s
 => => writing image sha256:58639db680aa854a72685b1a1846036cc46f9cae59d3ec7d1e91e3b0230be9f0                                                                                                                                                       0.0s
 => => naming to docker.io/library/spring-micro-session:latest

위와 같이 Docker를 빌드하면 이미지가 생성된다. 

`--no-cache`는 빌드시에 cache되어 있는 정보를 사용하는 것이 아니라 매번 새롭게 빌드하는 옵션이다. 

`-t`는 이미지의 이름과 Tag를 정의해주는 옵션이다. 여기에서 이미지 이름은 spring-micro-session 이고 tag는 latest이다. 

 

Docker Run

`docker run` 명령어는 Container를 실행하는 명령어이다. 방금 생성한 이미지로 실행을 해보았다. 실행이 잘된다. 

docker run --rm --name session spring-micro-session:latest                                                                                       ✔  6045  21:32:57

 ,---.                           ,--.
'   .-'   ,---.   ,---.   ,---.  `--'  ,---.  ,--,--,
`.  `-.  | .-. : (  .-'  (  .-'  ,--. | .-. | |      \
.-'    | \   --. .-'  `) .-'  `) |  | ' '-' ' |  ||  |
`-----'   `----' `----'  `----'  `--'  `---'  `--''--'

spring-micro-session 0.0.1-SNAPSHOT
Powered by Spring Boot 2.6.2

2022-01-15 05:33:19.070  INFO 1 --- [           main] i.c.session.SessionApplication           : Starting SessionApplication v0.0.1-SNAPSHOT using Java 11.0.11 on b1f2c1d52bff with PID 1 (/webapp.war started by root in /)
2022-01-15 05:33:19.072 DEBUG 1 --- [           main] i.c.session.SessionApplication           : Running with Spring Boot v2.6.2, Spring v5.3.14
2022-01-15 05:33:19.072  INFO 1 --- [           main] i.c.session.SessionApplication           : The following profiles are active: local
2022-01-15 05:33:19.393  INFO 1 --- [           main] .s.d.r.c.RepositoryConfigurationDelegate : Multiple Spring Data modules found, entering strict repository configuration mode!
2022-01-15 05:33:19.393  INFO 1 --- [           main] .s.d.r.c.RepositoryConfigurationDelegate : Bootstrapping Spring Data Redis repositories in DEFAULT mode.
2022-01-15 05:33:19.405  INFO 1 --- [           main] .s.d.r.c.RepositoryConfigurationDelegate : Finished Spring Data repository scanning in 3 ms. Found 0 Redis repository interfaces.
2022-01-15 05:33:19.528  INFO 1 --- [           main] .s.d.r.c.RepositoryConfigurationDelegate : Multiple Spring Data modules found, entering strict repository configuration mode!
2022-01-15 05:33:19.528  INFO 1 --- [           main] .s.d.r.c.RepositoryConfigurationDelegate : Bootstrapping Spring Data Redis repositories in DEFAULT mode.
2022-01-15 05:33:19.616  INFO 1 --- [           main] .s.d.r.c.RepositoryConfigurationDelegate : Finished Spring Data repository scanning in 88 ms. Found 1 Redis repository interfaces.
2022-01-15 05:33:19.923  INFO 1 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat initialized with port(s): 8180 (http)
2022-01-15 05:33:19.931  INFO 1 --- [           main] o.apache.catalina.core.StandardService   : Starting service [Tomcat]
2022-01-15 05:33:19.931  INFO 1 --- [           main] org.apache.catalina.core.StandardEngine  : Starting Servlet engine: [Apache Tomcat/9.0.56]
2022-01-15 05:33:20.536  INFO 1 --- [           main] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring embedded WebApplicationContext
2022-01-15 05:33:20.536  INFO 1 --- [           main] w.s.c.ServletWebServerApplicationContext : Root WebApplicationContext: initialization completed in 1426 ms
2022-01-15 05:33:21.708  INFO 1 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat started on port(s): 8180 (http) with context path ''
2022-01-15 05:33:21.719  INFO 1 --- [           main] i.c.session.SessionApplication           : Started SessionApplication in 3.026 seconds (JVM running for 3.32)

`--rm` 옵션은 Container 실행을 중지하면 자동으로 Container를 삭제해주는 명령어이다. 

`--name`은 Container의 이름을 지정한다.

 

`-d` 옵션으로 주면 daemon 형태로 실행할 수 있다. 

$ docker run -d --rm --name session spring-micro-session:latest

 

Docker ps

`docker ps` 명령어는 현재 수행되는 Container의 정보를 확인할 수 있다. 

$ docker ps

CONTAINER ID   IMAGE                         COMMAND                  CREATED         STATUS         PORTS                           NAMES
3b8bf1923dca   spring-micro-session:latest   "java -Dspring.profi…"   2 minutes ago   Up 2 minutes                                   session

 

Docker stop

`docker stop` 명령어는 현재 동작 중인 docker container를 중지시킨다. container id와 함께 실행하면 된다. 

$ docker stop 3b8bf1923dca                                                                                                                     ✔  6053  21:42:12
3b8bf1923dca

 

 

728x90
반응형
728x90
반응형
An OpenAPI definition can then be used by documentation generation tools to display the API, code generation tools to generate servers and clients in various programming languages, testing tools, and many other use cases.
A document (or set of documents) that defines or describes an API. An OpenAPI definition uses and conforms to the OpenAPI Specification

Open API는 간단하게 Restful API를 작성하여 테스트할 수 있는 툴이다. 하지만 Open API의 더 강력한 점은 개발하고 있는 웹서버와 통합하여 웹서버상의 Restful API의 문서 겸 테스트를 할 수 있는 환경을 제공한다는 것이다. 

 

즉, Open API Document는 Restful API 개발을 할 때 Client 개발자가 참조할 수 있는 문서를 자동으로 만들어준다. 이 문서를 통해서 API URL, Request body schema, and Responce body schema 를 확인할 수 있다. 아래의 예제를 확인하면 이해가 바로 될 것이다. 

(Open API Document 는 Swagger UI를 사용한다.)

 

이 문서에는 간단한 설정으로 문서를 작성해 보겠다. 

 

Example

 

Open API 설정은 Dependency 추가, application.yml에 Configuration 추가, Annotation 추가. 이 세가지만 하면 완료된다. 

 

Dependency 추가

아래와 같이 open api dependency를 추가해준다. 

<properties>
	<openapi.version>1.6.3</openapi.version>
</properties>

<!-- Documentation-->
<dependency>
    <groupId>org.springdoc</groupId>
    <artifactId>springdoc-openapi-ui</artifactId>
    <version>${openapi.version}</version>
</dependency>

 

Configuration 추가

아래 Configuration은 swagger ui의 path를 설정한다. 

# documentation
springdoc:
  swagger-ui:
    path: /swagger-ui.html

 

Annotation 추가

API Document의 내용을 추가하기 위해서 보통 아래와 같은 Annotation을 추가한다. Request와 Successfult Response의 경우는 코드를 분석하여 추가를 하지만 Error 상태의 응답은 Annotation을 추가하여 문서에 추가할 수 있다. 

@Operation(summary = "Token Cache Creation", description = "Token Cache Creation", responses = {
        @ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = JwtTokenDTO.JwtTokenInfo.class))),
        @ApiResponse(responseCode = "400", description = "Bad Request", content = @Content(schema = @Schema(example = ""))),
        @ApiResponse(responseCode = "404", description = "Not Found", content = @Content(schema = @Schema(example = "")))
})
@PostMapping("/token")
public JwtTokenDTO.JwtTokenInfo createToken(@RequestBody JwtTokenDTO.JwtTokenCreateRequest request) {
    JwtToken jwtToken = modelMapper.map(request, JwtToken.class);
    JwtToken createdToken = jwtTokenService.create(jwtToken);

    return JwtTokenDTO.JwtTokenInfo.from(createdToken, modelMapper);
}

DTO의 각 항목에 예제를 추가하여 이해를 도울 수 있다. 

@Getter
@Setter
@Accessors(chain = true)
@AllArgsConstructor
@NoArgsConstructor
@ToString
@Schema(description = "JwtToken Info")
public static class JwtTokenInfo {
    @Schema(example = "ff6681f0-50f8-4110-bf96-ef6cec45780e")
    private String id;
    @Schema(example = "1L")
    private Long accountId;
    @Schema(example = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.....")
    private String jwtToken;
    @Schema(example = "2021-01-01T00:00:00")
    @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd'T'HH:mm:ss")
    private LocalDateTime updatedAt;

    public static JwtTokenInfo from(JwtToken jwtToken, ModelMapper modelMapper) {
        return modelMapper.map(jwtToken, JwtTokenInfo.class);
    }
}

 

추가된 결과 

설정이 완료되고 서버를 실행한 후 http://localhost:8080/swagger-ui.html 로 접속으로 하면 아래와 같이 자동 생성된 문서를 볼 수 있다. 

 

아래와 같이 API Request를 테스트 할 수도 있다. 

Execute 버튼을 누르면 아래와 같이 응답을 받을 수 있다. 

Schema

728x90
반응형
728x90
반응형

https://spring.io/projects/spring-data-redis

 

Spring Data Redis

Spring Data Redis, part of the larger Spring Data family, provides easy configuration and access to Redis from Spring applications. It offers both low-level and high-level abstractions for interacting with the store, freeing the user from infrastructural c

spring.io

Spring Data Redis 란 무엇인가?

The Spring Data Redis (SDR) framework makes it easy to write Spring applications that use the Redis key-value store by eliminating the redundant tasks and boilerplate code required for interacting with the store through Spring’s excellent infrastructure support.

Spring Data Redis 는 Spring project 에서 Redis의 데이터를 쉽게 관리할 수 있게 해주는 Framework이다. RDB나 NoSQL의 경우도 각각의 Framework을 가지고 있어서 개발을 편리하게 해준다. 

 

Spring Data Redis Dependency 추가

<dependencies>

  <!-- other dependency elements omitted -->

  <dependency>
    <groupId>org.springframework.data</groupId>
    <artifactId>spring-data-redis</artifactId>
    <version>2.6.0</version>
  </dependency>

</dependencies>

Dependency는 위와 같이 추가하면 되는데, Spring boot framework을 사용중이라면 아래와 같이 추가하면 된다. 

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

 

Spring boot dependency를 추가하면 좋은 점은 많이 사용되고 있는 Redis client인 LettuceJedis가 같이 추가된다. 

 

이 문서에서는 Redis 연결을 위한 작업이 아니라 Redis key를 어떻게 원하는 방식으로 추가할 것인가를 고민해보도록 하겠다. 

 

Spring Data Redis의 사용

Spring Data Redis는 사용하면 JPA 처럼 코드를 통해 Redis의 값을 저장하고 읽어 올 수 있다. 그것이 복잡한 Object 형태라고 하더라도 세분화하여 자체적으로 Key값을 만들어서 저장한다. 

 

Default Key Value

Spring Data Redis에서의 기본 key 값은 Model의 구조에 결정된다. 

@RedisHash("people")
public class Person {

  @Id String id;
  String firstname;
  String lastname;
  Address hometown;
}

유저의 Person을 저장한다고 하였을 때 간단하게 위와 같이 Model을 만들 수 있다. Key 명에 대해서 생각할 때 중요한 부분은 

`@RedisHash` annotation이다. 이 annotation은 이 Model이 Redis의 Entity라는 것을 나타내줄 뿐더러 Key의 Prefix를 결정한다. 위의 예제에서는 @RedisHash("people")이라고 되어 있는데 이럴 경우 jwtToken 이라는 이름으로 Key이름이 시작되게 된다. 

`@RedisHash`만 사용할 경우 `getClass().getName()`의 리턴값이 기본으로 들어가게 된다. 

 

즉 아래와 같은 key 값이 만들어진다. (ex. e2c7dcee-b8cd-4424-883e-736ce564363e 는 생성된 Id 값)

 

SADD people e2c7dcee-b8cd-4424-883e-736ce564363e

 

@Indexed annotation

@RedisHash("people")
public class Person {

  @Id String id;
  @Indexed String firstname;
  String lastname;
  Address hometown;
}

위의 예제의 경우 `@Indexed` annotation이 추가된 것을 볼 수 있다. `@Indexed`을 추가되면 Secondary Index가 추가된다고 생각하면 된다. 이 말은 사용자가 Key 값에 원하는 구분자를 추가하여 검색을 용이하게 하는 역할을 한다. 

 

예를 들어 아래와 같이 Redis에 Data를 저장시킨다. 

repository.save(new Person("rand", "althor"));

이 경우 실제 Redis에서는 아래와 같은 명령들이 수행된다. 

HMSET "people:19315449-cda2-4f5c-b696-9cb8018fa1f9" "_class" "Person" "id" "19315449-cda2-4f5c-b696-9cb8018fa1f9" "firstname" "rand" "lastname" "althor" 
SADD  "people" "19315449-cda2-4f5c-b696-9cb8018fa1f9"                           
SADD  "people:firstname:rand" "19315449-cda2-4f5c-b696-9cb8018fa1f9"            
SADD  "people:19315449-cda2-4f5c-b696-9cb8018fa1f9:idx" "people:firstname:rand"

우선 HMSET 명령어를 통해서 Model에 입력된 전체 정보가 HashMap으로 저장된다. 그때의 key값은 `Prefix:Id` 구조로 저장된다. 

하지만 사용자의 검색을 위해서 SADD "people:firstname:rand" 라는 Set에 Id를 저장해 두고 사용자의 `repository.findByFirstname()` 검색이 가능하도록 저장을 한다. 

마지막 라인의 경우 Secondary Index의 Update 와 Delete를 위해 Set을 저장한다. 이 곳에는 전체 Secondary Index들을 저장한다고 생각하면 된다. 

 

오늘은 간단하게 어떻게 Key가 생성되는지 알아보았다. 

 

사실 이 내용에 대해서 공부하게 된 이유는 내가 개발하고 있는 서비스에서 Redis Key Convention(Key naming rule)을 가져갈 것인지 고민하면서 시작되었다. MSA(Micro Service Architecture)에서 여러 Service에서 Redis에 정보를 저장할 경우 Key 값의 구조를 잘 만들어야지 나중에 어떠한 이유로 인해 key값을 확인해야할 때 또는 서비스에서 값을 가져오기 위해 Key값을 만들 때 쉽게 할 수 있을 것 같기 때문이다. 

 

이 부분에 대한 고민은 다음 포스트로 넘기겠다. 

728x90
반응형
728x90
반응형

Server API의 Request 나 Response 의 String이 모두 영어로 되어 있을 경우에는 문제가 없지만, 에러메세지나 Validation 메세지 등을 한글로 보낼 경우에는 `UTP-8` Encoding 을 사용해야 한다. 그럼 언어를 Character Encoding을 설정하는 방법에 대해서 알아보자. 

 

Spring에서 Character Encoding을 설정하기 위해서는 Configuration annotation을 활용하여 Bean으로 Character Encoding Filter를 등록시키면 된다. 

 

내 개인 프로젝트에서는 아래와 같이 간단하게 AppConfig class를 만들어 두었다. 

 

 

 

728x90
반응형

+ Recent posts