๐Ÿ’ป Programming (356)

(SpringBoot3 + Correto 17 (java 17) ํ™˜๊ฒฝ์—์„œ spock test ์‹œ ๋ฐœ์ƒํ•œ

TestEngine with ID 'spock' failed to discover tests ์˜ค๋ฅ˜ ํ•ด๊ฒฐ๋ฐฉ์•ˆ ๊ณต์œ ํ•ฉ๋‹ˆ๋‹ค.

 

IntelliJ ์—์„œ Spring Initializer๋ฅผ ์ด์šฉํ•˜์—ฌ

์Šคํ”„๋ง๋ถ€ํŠธ3 + ์ž๋ฐ” 17 ๊ธฐ๋ฐ˜์˜ ์‹ ๊ทœ ํ”„๋กœ์ ํŠธ๋ฅผ ์ƒ์„ฑ,

๋นŒ๋“œ๊ฐ€ ์ •์ƒ์ ์œผ๋กœ ๋˜๋Š”์ง€ ํ™•์ธ ํ›„

spock ํ…Œ์ŠคํŠธ๋ฅผ ์œ„ํ•œ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ์•„๋ž˜ 3๊ฐœ ์ถ”๊ฐ€ํ–ˆ์Šต๋‹ˆ๋‹ค.

testImplementation 'org.spockframework:spock-core:2.4-M1-groovy-4.0'
testImplementation 'org.spockframework:spock-spring:2.4-M1-groovy-4.0'
testImplementation 'org.apache.groovy:groovy-all:4.0.13'

 

 

๊ทธ๋ฆฌ๊ณ  ์ƒ˜ํ”Œ ํ…Œ์ŠคํŠธ ํด๋ž˜์Šค๋ฅผ ๋งŒ๋“ค์—ˆ์Šต๋‹ˆ๋‹ค.

class SampleSpec extends Specification {

    def "where with variables: #size"() {
        when:
        def list = new ArrayList(size)

        then:
        list.size() == 0

        where:
        size << [1, 2, 3, 4, 5]
    }

    def "using data tables for calculating max, Max of #a and #b is #max"() {
        expect:
        Math.max(a, b) == max

        where:
        a | b || max
        1 | 2 || 2
        7 | 4 || 7
        0 | 0 || 0
    }
}

 

 

์‹คํ–‰์„ ํ•ด๋ณด์•˜๋”๋‹ˆ ์•„๋ž˜์™€ ๊ฐ™์€ ์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค.

Internal Error occurred.
org.junit.platform.commons.JUnitException: TestEngine with ID 'spock' failed to discover tests
...์ค‘๋žต...

Caused by: org.junit.platform.commons.JUnitException: ClassSelector [className = 'com.project.SampleSpec', classLoader = null] resolution failed
...์ค‘๋žต...

Caused by: org.junit.platform.commons.PreconditionViolationException: Could not load class with name: com.project.SampleSpec
...์ค‘๋žต...

Caused by: java.lang.ClassNotFoundException: com.project.SampleSpec

 

 

๊ตฌ๊ธ€๋ง ๊ฒฐ๊ณผ build.gradle ํŒŒ์ผ์˜ plugins ์— id 'groovy' ๋ฅผ ์ถ”๊ฐ€ํ•ด์ฃผ๋ฉด ๋˜๋Š” ๋ฌธ์ œ์˜€์Šต๋‹ˆ๋‹ค.

plugins {
    id 'java'
    id 'org.springframework.boot' version '3.2.1'
    id 'io.spring.dependency-management' version '1.1.4'

    id 'groovy'  <-- ์ด๊ฒŒ ์žˆ๋Š”์ง€ ํ™•์ธ
}

 

์ตœ๊ทผ 2๋…„์ •๋„ IntelliJ์—์„œ Java 11 ๊ธฐ๋ฐ˜์˜ AWS Correto 11 ์„ ์‚ฌ์šฉํ•ด์™”์Šต๋‹ˆ๋‹ค

๊ทธ๋ž˜์„œ ํ˜„์žฌ JAVA_HOME ์€ AWS Correto 11 ์˜ ์œ„์น˜๋ฅผ ๊ฐ€๋ฆฌํ‚ค๊ณ  ์žˆ์—ˆ์Šต๋‹ˆ๋‹ค.

์‹ ๊ทœ ํ”„๋กœ์ ํŠธ ๊ตฌ์„ฑํ•  ์ผ์ด ์ƒ๊ฒผ๊ณ 

AWS Correto 17 ์„ ๋ฏธ๋ฆฌ ๋‹ค์šด๋ฐ›์•„๋‘” ์ƒํƒœ์—์„œ

IntelliJ ์—์„œ SpringBoot Initializer ๋ฅผ ์ด์šฉํ•ด์„œ

์•„๋ž˜์™€ ๊ฐ™์€ ์„ค์ •(Java 17 + gradle groovy)์œผ๋กœ ํ”„๋กœ์ ํŠธ๋ฅผ ์ƒ์„ฑํ•˜๊ณ 

๋นŒ๋“œ๋ฅผ ํ•˜๋‹ˆ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค.

์Šคํ”„๋ง๋ถ€ํŠธ ์‹ ๊ทœ ํ”„๋กœ์ ํŠธ ์ƒ์„ฑ ์„ค์ •ํ™”๋ฉด (Java 17 ์„ค์ •)

 

์•„๋ž˜๋Š” ๋นŒ๋“œ์‹œ ๋ฐœ์ƒํ–ˆ๋˜ ์˜ค๋ฅ˜ ๋ฉ”์‹œ์ง€

A problem occurred configuring root project 'my-new-springboot3-project'.
> Could not resolve all files for configuration ':classpath'.
   > Could not resolve org.springframework.boot:spring-boot-gradle-plugin:3.2.1.
     Required by:
         project : > org.springframework.boot:org.springframework.boot.gradle.plugin:3.2.1
      > No matching variant of org.springframework.boot:spring-boot-gradle-plugin:3.2.1 was found. The consumer was configured to find a library for use during runtime, compatible with Java 11, packaged as a jar, and its dependencies declared externally, as well as attribute 'org.gradle.plugin.api-version' with value '8.5' but:
          - Variant 'apiElements' capability org.springframework.boot:spring-boot-gradle-plugin:3.2.1 declares a library, packaged as a jar, and its dependencies declared externally:
              - Incompatible because this component declares a component for use during compile-time, compatible with Java 17 and the consumer needed a component for use during runtime, compatible with Java 11
              - Other compatible attribute:
                  - Doesn't say anything about org.gradle.plugin.api-version (required '8.5')
          - Variant 'javadocElements' capability org.springframework.boot:spring-boot-gradle-plugin:3.2.1 declares a component for use during runtime, and its dependencies declared externally:
              - Incompatible because this component declares documentation and the consumer needed a library
              - Other compatible attributes:
                  - Doesn't say anything about its target Java version (required compatibility with Java 11)
                  - Doesn't say anything about its elements (required them packaged as a jar)
                  - Doesn't say anything about org.gradle.plugin.api-version (required '8.5')
          - Variant 'mavenOptionalApiElements' capability org.springframework.boot:spring-boot-gradle-plugin-maven-optional:3.2.1 declares a library, packaged as a jar, and its dependencies declared externally:
              - Incompatible because this component declares a component for use during compile-time, compatible with Java 17 and the consumer needed a component for use during runtime, compatible with Java 11
              - Other compatible attribute:
                  - Doesn't say anything about org.gradle.plugin.api-version (required '8.5')
          - Variant 'mavenOptionalRuntimeElements' capability org.springframework.boot:spring-boot-gradle-plugin-maven-optional:3.2.1 declares a library for use during runtime, packaged as a jar, and its dependencies declared externally:
              - Incompatible because this component declares a component, compatible with Java 17 and the consumer needed a component, compatible with Java 11
              - Other compatible attribute:
                  - Doesn't say anything about org.gradle.plugin.api-version (required '8.5')
          - Variant 'runtimeElements' capability org.springframework.boot:spring-boot-gradle-plugin:3.2.1 declares a library for use during runtime, packaged as a jar, and its dependencies declared externally:
              - Incompatible because this component declares a component, compatible with Java 17 and the consumer needed a component, compatible with Java 11
              - Other compatible attribute:
                  - Doesn't say anything about org.gradle.plugin.api-version (required '8.5')
          - Variant 'sourcesElements' capability org.springframework.boot:spring-boot-gradle-plugin:3.2.1 declares a component for use during runtime, and its dependencies declared externally:
              - Incompatible because this component declares documentation and the consumer needed a library
              - Other compatible attributes:
                  - Doesn't say anything about its target Java version (required compatibility with Java 11)
                  - Doesn't say anything about its elements (required them packaged as a jar)
                  - Doesn't say anything about org.gradle.plugin.api-version (required '8.5')

* Try:
> Run with --stacktrace option to get the stack trace.
> Run with --info or --debug option to get more log output.
> Run with --scan to get full insights.
> Get more help at https://help.gradle.org.
BUILD FAILED in 1s

 

์—๋Ÿฌ ๋ฉ”์‹œ์ง€์˜ ๋‚ด์šฉ์„ ์ฝ์–ด๋ณด๋‹ˆ, ์ผ๋‹จ Could not resolve xxx ์˜ ๊ฒฝ์šฐ xxx ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๊ฐ€ ์—†๋‹ค๋ผ๋Š” ๋ฉ”์‹œ์ง€์ธ๋ฐ

์‹ ๊ทœ ์ƒ์„ฑํ•œ ํ”„๋กœ์ ํŠธ์—์„œ ํ•„์ˆ˜๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ dependency์— ์•ˆ๋„ฃ์„ ์ด์œ ๊ฐ€ ์—†๋‹ค๋Š” ์ƒ๊ฐ์ด ์šฐ์„  ๋“ค์—ˆ์Šต๋‹ˆ๋‹ค.

๊ทธ๋Ÿฐ๊ฑฐ๋ผ๋ฉด springboot initializer ์˜ ๋ฒ„๊ทธ์ผํ…Œ๋‹ˆ๊นŒ์š”.

๊ทธ๋ž˜์„œ ๋” ์ฝ์–ด๋ดค์Šต๋‹ˆ๋‹ค.

๊ทธ๋ฆฌ๊ณ  "requierd compatibility with Java 11" ๋ฌธ๊ตฌ๊ฐ€ ๋ˆˆ์— ๋„์—ˆ์Šต๋‹ˆ๋‹ค.

๋‚œ ์ž๋ฐ”17์„ ์“ฐ๋ ค๊ณ  17๋ฒ„์ „์œผ๋กœ JDK์™€ Java ์„ค์ •์„ ๋„ฃ์—ˆ๋Š”๋ฐ ์™œ Java 11 ๊ด€๋ จ๋ฉ”์‹œ์ง€๊ฐ€ ๋œจ๋Š”๊ฑด์ง€ ์ด์ƒํ–ˆ์Šต๋‹ˆ๋‹ค. ๐Ÿค”

์—๋Ÿฌ๋ฉ”์‹œ์ง€ ๋‚ด์šฉ์œผ๋กœ ๊ตฌ๊ธ€๋ง์„ ์ข€ ํ•ด๋ณด๋‹ˆ ์•„๋ž˜ ๋ถ€๋ถ„๋“ค์„ ํ™•์ธํ•ด๋ณด๋ผ๋Š” ๋‚ด์šฉ๋“ค์ด ์žˆ์—ˆ์Šต๋‹ˆ๋‹ค.

  • ํ˜„์žฌ ์‹œ์Šคํ…œ์—์„œ ์‚ฌ์šฉํ•˜๋Š” Java ๋ฒ„์ „ ํ™•์ธ (java -version)
  • build.gradle ํŒŒ์ผ์˜  sourceCompatibility ํ™•์ธ
  • IntelliJ ์˜ project SDK ์„ค์ • ํ™•์ธ
  • gradle JVM ์„ค์ • ํ™•์ธ

์ผ๋‹จ ๋นŒ๋“œ์‹œ์— ๋ฐœ์ƒํ–ˆ๊ณ , ๋นŒ๋“œํˆด์€ gradle ์ด๋ผ๋Š” ๊ฑธ ์ด๋ฏธ ํ”„๋กœ์ ํŠธ ์ƒ์„ฑ์‹œ ์„ค์ •์„ ํ•ด์ฃผ์—ˆ๊ณ , IntelliJ์— gradle ์ด ์‚ฌ์šฉํ•  ์ž๋ฐ”๋ฒ„์ „์„ ์„ค์ •ํ•˜๋Š” ๋‚ด์šฉ์ด ์žˆ๋‹ค๋Š” ๊ฒƒ์„ ์•Œ๊ณ ์žˆ์—ˆ๊ธฐ์— gradle JVM ์„ค์ •์„ ๋จผ์ € ํ™•์ธํ•ด๋ดค์Šต๋‹ˆ๋‹ค.

gradle JVM ์„ค์ •์ด ๊ธฐ์กด ์„ค์ • ๊ทธ๋Œ€๋กœ Java 11์„ ์‚ฌ์šฉํ•˜๋„๋ก ๋˜์–ด์žˆ์—ˆ์Šต๋‹ˆ๋‹ค. ๐Ÿ˜ญ

ํ”„๋กœ์ ํŠธ ์ƒ์„ฑ์‹œ correto 17, Java 17 ๋ฒ„์ „์„ ์„ค์ •ํ•ด์ฃผ์—ˆ๊ธฐ์— sourceCompatibility ์™€ project SDK ๋Š” 17๋ฒ„์ „์„ ์‚ฌ์šฉํ•˜๋„๋ก ๋˜์–ด์žˆ์—ˆ๊ณ , JAVA_HOME์— ์„ค์ •๋˜์–ด์žˆ๋Š” ํ˜„์žฌ ์‹œ์Šคํ…œ์—์„œ ๊ธฐ๋ณธ์œผ๋กœ ์‚ฌ์šฉ์ค‘์ธ Java ๋ฒ„์ „๊ณผ๋Š” ๊ด€๊ณ„๊ฐ€ ์—†์—ˆ์Šต๋‹ˆ๋‹ค.

 

gradle JVM ์„ค์ • ํ™•์ธ ๋ฐฉ๋ฒ•

Preferences/Settings (โŒ˜ + , ) > Build, Execution, Deployment > Build Tools > Gradle

Gradle JVM ์„ค์ • ํ™”๋ฉด

project SDK ์„ค์ • ํ™•์ธ ๋ฐฉ๋ฒ•

File > Project Structure.. (โŒ˜ + ; )

Project SDK ์„ค์ • ํ™”๋ฉด

build.gradle ํŒŒ์ผ์˜ sourceCompatibility ํ™•์ธ ๋ฐฉ๋ฒ•

build.gradle ํŒŒ์ผ์—์„œ sourceCompatibility ์„ค์ • ๋ฐฉ๋ฒ•

SpringBoot ๋ฒ„์ „์„ ์—…๊ทธ๋ ˆ์ด๋“œ ํ•ด์•ผํ•  ์ผ์ด ์ƒ๊ฒจ์„œ 2.3 ๋ฒ„์ „์„ 2.5 ๋ฒ„์ „์œผ๋กœ ์—…๊ทธ๋ ˆ์ด๋“œํ•œ ๊ฒฝํ—˜๋‹ด์„ ๊ณต์œ ํ•ฉ๋‹ˆ๋‹ค.

๊ฐœ๋ฐœ์ธ์ƒ ์ฒ˜์Œ์œผ๋กœ ํ•ด๋ณด๋Š” ๋ ˆ๊ฑฐ์‹œ ์‹œ์Šคํ…œ์˜ ํ”„๋ ˆ์ž„์›Œํฌ ์—…๊ทธ๋ ˆ์ด๋“œ ์ž‘์—…์ด์—ˆ์Šต๋‹ˆ๋‹ค.

๋Œ€๋ถ€๋ถ„์€ ์‹ ๊ทœ ํ”„๋กœ์ ํŠธ๋ฅผ ๋งŒ๋“ค๊ฑฐ๋‚˜ deprecate ๋œ ์ƒํƒœ๋กœ ์œ ์ง€ํ•˜๋Š” ์ •๋„์˜€์ฃ .

๊ทธ๋ž˜๋„ ํ•ด๋ณด๊ณ  ์‹ถ์—ˆ๋˜ ๋ถ€๋ถ„์ด์—ˆ์Šต๋‹ˆ๋‹ค. ์—…๋ฌด๊ฐ€ ๋งŽ์ด ์ง€๋ฃจํ•ด์ง„๊ฒƒ๋„ ํ•œ ๋ชซ ํ–ˆ์ฃ .

์ €๋Š” ์ฃผ๊ธฐ์ ์œผ๋กœ ๋ฐ˜๊ธฐ์— ํ•œ๋ฒˆ์ •๋„๋Š” ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ์—…๊ทธ๋ ˆ์ด๋“œ ์ž‘์—…์„ ์ง„ํ–‰ํ•ด์•ผํ•œ๋‹ค๊ณ  ์ƒ๊ฐํ•˜์ง€๋งŒ

์ €ํฌ ๊ฐœ๋ฐœํŒ€์€ ์ด์ƒํ•˜๊ฒŒ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ์—…๊ทธ๋ ˆ์ด๋“œ ์ž‘์—…์„ ์•ˆํ•ฉ๋‹ˆ๋‹ค. 

๊ทธ๋Ÿฐ๋ฐ ๊ฐ‘์ž๊ธฐ ํ”„๋ ˆ์ž„์›Œํฌ ์—…๊ทธ๋ ˆ์ด๋“œ๋ฅผ ํ•˜๋ ค๋‹ˆ dependency ๊ฐ€ ๋งŽ์ด ๊ฑธ๋ ค์žˆ์„๊ฑฐ๋ผ ์˜ˆ์ƒํ–ˆ์ฃ .

 

์šฐ์„  2.3 -> 2.4 ๋กœ ์˜ฌ๋ผ๊ฐ€๋ฉด์„œ ์–ด๋–ค ๋ถ€๋ถ„์— ๋ณ€ํ™”๊ฐ€ ์ƒ๊ฒผ๋Š”์ง€๋ฅผ ํ™•์ธํ•˜๊ณ  ๋Œ€์‘ํ•˜๊ณ ,

2.4 -> 2.5 ์—์„œ๋Š” ์–ด๋–ค ๋ณ€ํ™”๊ฐ€ ์žˆ์—ˆ๋Š”์ง€ ํ™•์ธํ•˜์—ฌ ๋Œ€์‘ํ•˜๋ฉด ๋ ๊ฑฐ๋ผ ์ƒ๊ฐํ–ˆ๊ณ  

์•„๋ž˜ 2๊ฐœ์˜ spring boot release note ๋ฅผ ํ•˜๋‚˜ํ•˜๋‚˜ ์‚ดํŽด๋ดค์Šต๋‹ˆ๋‹ค.

2.3 -> 2.4 ์—์„œ ๋ณ€๊ฒฝ๋œ ๋ถ€๋ถ„

  • JUnit 5’s Vintage Engine Removed from spring-boot-starter-test
  • Config File Processing (application properties and YAML files)
  • Config Data Imports
  • Embedded database detection
  • User-defined MongoClientSettings no longer customized
  • Logback Configuration Properties
  • Default Servlet Registration
  • HTTP traces no longer include cookie headers by default
  • Undertow Path on Forward
  • Neo4j
  • Hazelcast 4
  • Elasticsearch RestClient
  • R2DBC
  • Flyway
  • Removal of Plugin Management for Flatten Maven Plugin
  • Version management for exec-maven-plugin
  • Spring Boot Gradle Plugin
  • Metrics export in integration tests
  • Deprecations from Spring Boot 2.2 and 2.3

2.4 -> 2.5 ์—์„œ ๋ณ€๊ฒฝ๋œ ๋ถ€๋ถ„

  • SQL Script DataSource Initialization
  • Flyway and Liquibase JDBC URLs
  • Spring Data JPA
  • Spring Data Solr
  • Secure Info Endpoint
  • Task Scheduling Harmonization with Spring Integration
  • Default Expression Language (EL) Implementation
  • Messages in the Default Error View
  • Logging Shutdown Hooks
  • Gradle Default jar and war Tasks
  • Cassandra Throttling Properties
  • Customizing jOOQ’s DefaultConfiguration
  • Groovy 3
  • Minimum Requirements Changes
  • Hibernate Validator 6.2

์œ„์— ๋‚˜์˜จ ๋‚ด์šฉ ์ด์™ธ์—๋„ ์—…๊ทธ๋ ˆ์ด๋“œ ๋˜๋ฉด์„œ deprecated ๋˜๋Š” ๊ฒƒ๋“ค์— ๋Œ€ํ•œ ๋ถ€๋ถ„๋“ค, ๊ทธ๋ฆฌ๊ณ  ์ƒˆ๋กœ ์ถ”๊ฐ€ ๋œ ๋ถ€๋ถ„๋“ค์— ๋Œ€ํ•œ ์„ค๋ช…์„ ํ•˜๋‚˜ํ•˜๋‚˜ ์ฝ์–ด๋ณด๊ณ  ํ˜„์žฌ ๋‚ด๊ฐ€ ๋‹ด๋‹นํ•˜๋Š” ํ”„๋กœ์ ํŠธ์—์„œ ์‚ฌ์šฉ๋˜๋Š” ๊ฒƒ๋“ค๊ณผ ์—ฐ๊ด€๋œ ๊ฒƒ๋“ค์„ ๋ชจ๋‘ ์ฝ์–ด๋ณด์•˜์Šต๋‹ˆ๋‹ค.

๊ทธ๋ฆฌ๊ณ  spring boot ๋ฒ„์ „์„ ๋ณ€๊ฒฝํ•œ ๋’ค ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ•˜๋Š” ๋ถ€๋ถ„์€ ์—†๋Š”์ง€ ๋ฐฐํฌ๋Š” ์ž˜ ๋˜๋Š”์ง€๋ฅผ ํ™•์ธํ–ˆ์Šต๋‹ˆ๋‹ค.

boot ์—…๊ทธ๋ ˆ์ด๋“œ ์ž‘์—…ํ•˜๋ฉด์„œ ๋ณ€๊ฒฝํ–ˆ๋˜ ๋ถ€๋ถ„์€ ์•„๋ž˜ ๋‚ด์šฉ๋“ค์ด์—ˆ์Šต๋‹ˆ๋‹ค.

  • spring.config.use-legacy-processing = true ์„ค์ •์„ ์ถ”๊ฐ€
  • fasterxml.jackson.databind.PropertyNamingStrategy -> PropertyNamingStrategies ํด๋ž˜์Šค ๋ณ€๊ฒฝ
  • third-party dependency ๋“ค ์ค‘์—์„œ ์—…๊ทธ๋ ˆ์ด๋“œ๊ฐ€ ํ•„์š”ํ•œ ๊ฒƒ๋“ค ํ™•์ธ ๋ฐ ์—…๊ทธ๋ ˆ์ด๋“œ
  • gradle ๋ฒ„์ „ ์—…๊ทธ๋ ˆ์ด๋“œ 6.7 -> 6.9

์ž‘์—… ๋‚ด์šฉ๋งŒ๋ณด๋ฉด ์ง„์งœ ๋ณ„๊ฑฐ ์•„๋‹ˆ์—ˆ๋˜ ์ž‘์—…์œผ๋กœ ๋ณด์ด๋„ค์š”. ์ž‘์—…์‹œ๊ฐ„๋ณด๋‹ค ๋ฌธ์„œ ์ฝ์–ด๋ณด๋Š” ์‹œ๊ฐ„์ด ๋” ์˜ค๋ž˜ ๊ฑธ๋ ธ๋˜๊ฒƒ ๊ฐ™์Šต๋‹ˆ๋‹ค. ๋งํฌ๋ฅผ ํƒ€๊ณ ํƒ€๊ณ  ๋“ค์–ด๊ฐ€์•ผ ํ™•์ธํ•  ์ˆ˜ ์žˆ๋Š” ๊ฒƒ๋“ค์ด ๋งŽ๋‹ค๋ณด๋‹ˆ..

์•„๋ฌดํŠผ ๋กœ์ปฌํ™˜๊ฒฝ์—์„œ ์ž˜ ๋Œ์•„๊ฐ€๋Š” ๊ฒƒ๊นŒ์ง€ ํ™•์ธ ํ•˜๊ณ  ๊ฐœ๋ฐœ์— ์˜ฌ๋ ค ํ…Œ์ŠคํŠธ๋ฅผ ํ•ด๋ณด๋‹ˆ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์ด ์ •์ƒ์ ์œผ๋กœ ์‹คํ–‰๋˜์ง€ ๋ชปํ•˜๋Š” ํ˜„์ƒ์ด ํ™•์ธ๋˜์—ˆ์Šต๋‹ˆ๋‹ค. ์™œ ๊ทธ๋Ÿฐ๊ฐ€ ๋ดค๋”๋‹ˆ jar ํŒŒ์ผ๋ช…์ด ์ด์ƒํ•˜๊ฒŒ ๋ฐ”๋€Œ์–ด์žˆ์—ˆ์Šต๋‹ˆ๋‹ค. ๋์— -plain postfix๊ฐ€ ๋ถ™์€ ํŒŒ์ผ๋ช…์ด ๋ฐฐํฌ๋œ ๊ฒƒ์„ ํ™•์ธํ–ˆ๊ณ  ์ด๊ฒŒ ๋ญ”๊ฐ€ ์‹ถ์–ด ๊ตฌ๊ธ€๋ง์„ ํ•ด๋ณด๋‹ˆ release note ์—์„œ ์„ค๋ช…์„ ์ฐพ์•„๋ณผ ์ˆ˜ ์—†์—ˆ์ง€๋งŒ(๋‚ด๊ฐ€ ๋ชป ๋ณธ ๊ฑธ์ˆ˜๋„?) 2.5๋ฒ„์ „๋ถ€ํ„ฐ ๋ฐœ์ƒํ•˜๋Š” ํ˜„์ƒ์ด์—ˆ๊ณ  gradle ์„ค์ •์—์„œ jar task๋ฅผ ๋น„ํ™œ์„ฑํ™” ์‹œํ‚ค๊ฑฐ๋‚˜ ๋ฐฐํฌํŒŒ์ผ์„ ๋งŒ๋“œ๋Š” task ์—์„œ ํ•ด๋‹น ํŒŒ์ผ์„ exclude ์ฒ˜๋ฆฌํ•˜๋ฉด ๋˜๋Š” ๋ฌธ์ œ์˜€์Šต๋‹ˆ๋‹ค.

 

๋งค์ผ ๋ฐ˜๋ณต๋˜๋Š” ํŠน๋ณ„ํ•˜์ง€ ์•Š์€ ์—…๋ฌด๋“ค๋งŒ ํ•˜๋‹ค๊ฐ€ ์ฒ˜์Œ์œผ๋กœ ํ•ด๋ณธ ํ”„๋ ˆ์ž„์›Œํฌ ์—…๊ทธ๋ ˆ์ด๋“œ ์ž‘์—…์€ ๋„ˆ๋ฌด ์žฌ๋ฏธ์žˆ์—ˆ๊ณ  ์ข‹์€ ๊ฒฝํ—˜์ด์—ˆ์Šต๋‹ˆ๋‹ค.

 

๋‚ด๋…„์—๋Š” java 17 + SpringBoot 3 ๊ธฐ๋ฐ˜์˜ ์‹ ๊ทœ ํ”„๋กœ์ ํŠธ๋ฅผ ํ•˜๋‚˜ ๋งŒ๋“ค์–ด์•ผ ํ•˜๋Š”๋ฐ ๋ฒŒ์จ๋ถ€ํ„ฐ ๊ธฐ๋Œ€๋˜๊ณ  ํ˜„์žฌ ์‚ฌ์šฉ์ค‘์ธ java 11 ์—์„œ 17๊นŒ์ง€ ์–ด๋–ค ๋ณ€ํ™”๋“ค์ด ์žˆ์—ˆ๋Š”์ง€ ํ™•์ธํ•˜๋Š” ์‹œ๊ฐ„์„ ๊ฐ€์ ธ์•ผ ํ•  ๊ฒƒ ๊ฐ™์Šต๋‹ˆ๋‹ค.

 

๊ฐœ์ธํ”„๋กœ์ ํŠธ๋ฅผ ํ•˜๋‹ค๋ณด๋ฉด ํ•œ๋™์•ˆ ์ž‘์—…์ง„ํ–‰์„ ๋ชปํ•˜๋‹ค๊ฐ€ ๋‹ค์‹œ ์žฌ๊ฐœํ•˜๋Š” ๊ฒฝ์šฐ๊ฐ€ ๋งŽ์€๋ฐ

์ด๋•Œ ๋…ธํŠธ๋ถ์„ ์ƒˆ๋กœ ๋ฐ”๊พธ์—ˆ๊ฑฐ๋‚˜, ํ”„๋กœ์ ํŠธ ๋””๋ ‰ํ† ๋ฆฌ๋ฅผ ์ž˜๋ชปํ•ด์„œ ์‚ญ์ œํ–ˆ๋‹ค๊ฐ€

๋‹ค์‹œ git clone ํ•  ๋•Œ Invalid username or password ๋ฅผ ๋ณด๊ฒŒ ๋œ๋‹ค.

github ์—์„œ https ์ฃผ์†Œ๋กœ clone ์„ ํ•˜๊ฑฐ๋‚˜

ํ† ๊ทผ์œ ํšจ๊ธฐ๊ฐ„์„ ๋„˜๊ฒจ์„œ git push๋ฅผ ํ•˜๊ฒŒ๋˜๋ฉด

invalid ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ•˜๊ฒŒ ๋œ๋‹ค.

1๋…„์—๋„ ์—ฌ๋Ÿฌ๋ฒˆ ์ด๋Ÿฐ ์ผ์ด ๋ฐœ์ƒํ•˜๋Š”๋ฐ

์–ด๋–ป๊ฒŒ ์ฒ˜๋ฆฌํ•ด์•ผํ•˜๋Š”์ง€ ์ž๊พธ ๊นŒ๋จน์–ด์„œ ๊ธฐ๋ก์šฉ์œผ๋กœ ๋‚จ๊น๋‹ˆ๋‹ค.

Github ๊ณ„์ •์˜ Settings > Developer Settings ๋กœ ๊ฐ€์„œ

Personal Access Token(์ดํ•˜ PAT) ์„ ์žฌ์ƒ์„ฑ(regenerate)ํ•ฉ๋‹ˆ๋‹ค.

๊ทธ๋ฆฌ๊ณ  git clone ์˜ ๊ฒฝ์šฐ ์•„๋ž˜์ฒ˜๋Ÿผ clone ๋ช…๋ น์„ ์ž…๋ ฅํ•ฉ๋‹ˆ๋‹ค.

https://๊ณ„์ •๋ช…:PAT@github.com/ํด๋ก ํ• _repo์˜ ์ฃผ์†Œ

git push ์˜ ๊ฒฝ์šฐ username๊ณผ  password ๋ฅผ ์ž…๋ ฅํ•˜๋Š” ํ”„๋กฌํ”„ํŠธ๊ฐ€ ๋œจ๊ฒŒ๋˜๋Š”๋ฐ

์ด๋•Œ password ์— github ์›น ๋กœ๊ทธ์ธ์‹œ ์ž…๋ ฅํ•˜๋Š” password ๊ฐ€ ์•„๋‹Œ PAT ๋ฅผ ์ž…๋ ฅํ•˜๋ฉด๋ฉ๋‹ˆ๋‹ค. 

ํฌ์ŠคํŒ…์„ ๋„ˆ๋ฌด ์˜ค๋žœ๋งŒ์— ํ•˜๋„ค์š”.

์š”์ฆ˜์€ ํšŒ์‚ฌ์‚ฌ์ •๋„ ๋ณ„๋กœ๊ณ  ํšŒ์‚ฌ์—…๋ฌด์—์„œ ๋ฐฐ์šฐ๋Š” ๊ฒƒ๋„ ๋ญ ์—†๋‹ค๋ณด๋‹ˆ ๋ธ”๋กœ๊ทธ์— ๊ธ€์„ ์ž˜ ์•ˆ์“ฐ๊ฒŒ ๋˜๋„ค์š”.

ํ•˜์ง€๋งŒ!!!! ์„ฑ์žฅ์— ๋Œ€ํ•œ ์š•๊ตฌ๋Š” ์•„์ง๋„ ์ดˆ์‹ฌ๊ณผ ๊ฐ™๋‹ค๋Š”... ใ…Žใ…Ž

flutter ์— ๋งค๋ฃŒ๋œ ์˜ฌํ•ด ์ดˆ๋ถ€ํ„ฐ flame ์„ ์ด์šฉํ•˜์—ฌ ๊ฒŒ์ž„๊ฐœ๋ฐœ์„ ์ง„ํ–‰ํ–ˆ์—ˆ์Šต๋‹ˆ๋‹ค.

1์ผ์ฐจ ์ดํ›„๋กœ ํฌ์ŠคํŒ…์„ ํ•˜์ง€ ์•Š์•˜์ง€๋งŒ

flutter์™€ flame ๊ณต์‹๋ฌธ์„œ ์ฝ์–ด๊ฐ€๋ฉด์„œ ์งฌ์งฌ์ด ๊ฐœ๋ฐœ์„ ์ง„ํ–‰ํ–ˆ๊ณ 

5์›” 31์ผ ๊ตฌ๊ธ€ํ”Œ๋ ˆ์ด์— ์ถœ์‹œ๋์Šต๋‹ˆ๋‹ค.

๋Œ€๋žต ์ฃผ 2ํšŒ ๋ฐค์ƒ˜ ์ž‘์—…์œผ๋กœ ์ง„ํ–‰์„ ํ–ˆ์œผ๋‹ˆ ์‹ค์ œ ๊ฐœ๋ฐœ๊ธฐ๊ฐ„์€ 2๊ฐœ์›”์ •๋„ ๋  ๊ฒƒ ๊ฐ™๋„ค์š”

์ผ๋‹จ ๊ฒฐ๊ณผ๋ฌผ์ด ์–ด๋–ป๊ฒŒ ๋‚˜์™”๋Š”์ง€ ๋ณด๊ณ  ์‹ถ์œผ์‹ค ๊ฒƒ ๊ฐ™์•„ ๊ตฌ๊ธ€ํ”Œ๋ ˆ์ด ๋งํฌ๋ฅผ ํ•˜๋‚˜ ๋‚จ๊น๋‹ˆ๋‹ค

https://play.google.com/store/apps/details?id=com.keichee.exterminate_mosquitoes&hl=ko-KR

 

๋ชจ๊ธฐ ํ—Œํ„ฐ - Google Play ์•ฑ

์ธ๋ฅ˜ ์ตœ๋Œ€์˜ ํ•ด์ถฉ! ๋ชจ๊ธฐ๋ฅผ ๋ฐ•๋ฉธํ•˜๊ณ  ์ง€๊ตฌ๋ฅผ ๊ตฌํ•ด์ฃผ์„ธ์š”

play.google.com

 

๋ชจ๊ธฐ๋ฅผ ์žก์œผ๋ฉด์„œ ์—ฌ์„ฏ๊ฐ€์ง€ ์ข…๋ฅ˜์˜ ๋ชจ๊ธฐ๋“ค์„ ์ˆ˜์ง‘ํ•˜๊ณ ,

๋Šฅ๋ ฅ์น˜๋ฅผ ํ–ฅ์ƒ์‹œํ‚ค๋ฉด์„œ ๋” ๋†’์€ stage ๋กœ ์˜ฌ๋ผ๊ฐ€๊ณ ,

๊ด‘๊ณ ์‹œ์ฒญ์œผ๋กœ ๋ ˆ๋ฒจ์—…์— ํ•„์š”ํ•œ ์ฝ”์ธ(?)์„ ์ถ”๊ฐ€ํš๋“ ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

๋ฆฌ๋”๋ณด๋“œ๋„ ์ถ”๊ฐ€ํ•ด๋ณผ ์˜ˆ์ •์ž…๋‹ˆ๋‹ค.

์š”์ฆ˜ flutter & flame ์œผ๋กœ 1์ธ๊ฒŒ์ž„ ๊ฐœ๋ฐœ์„ ์ง„ํ–‰ํ•˜๊ณ  ์žˆ๋Š”๋ฐ ์•Œ์•„์•ผ ํ• ๊ฒŒ ๋„ˆ๋ฌด๋‚˜๋„ ๋งŽ์•„ ๋ฌธ์„œ ์ฐพ์•„๋ณด๊ณ  ์œ ํˆฝ ์˜์ƒ ์ฐพ์•„๋ณด๋ฉด์„œ ์–•์€ ์ง€์‹์„ ์กฐ๊ธˆ์”ฉ ์กฐ๊ธˆ์”ฉ ๋ชจ์œผ๋‹ค๊ฐ€ ์ง€๊ฒจ์›Œ์ ธ์„œ ์—Š๊ทธ์ œ ํ•˜๋ฃจ ๋‚  ์žก์•„์„œ ํ‡ด๊ทผํ•˜๊ณ  ๋ฐค์ƒˆ์›Œ ๊ตฌ๊ธ€๋งํ•ด๊ฐ€๋ฉด์„œ ๋งŒ๋“ค์–ด๋ดค์Šต๋‹ˆ๋‹ค.

์ธํŠธ๋กœ ํ™”๋ฉด๋„ ๋กœ๋น„ํ™”๋ฉด๋„ ์—†๊ณ  ๊ทธ๋ƒฅ ๋ชจ๊ธฐ ๋‚ ์•„๋‹ค๋‹ˆ๊ฒŒ ๋งŒ๋“ค๊ณ  ํ„ฐ์น˜ํ•˜๋ฉด HP ์ค„์–ด๋“ค๋ฉด์„œ ์‚ฌ๋ผ์ง€๊ณ  ๋‹จ๊ณ„๋ณ„๋กœ ์ •ํ•ด์ง„ ๋ชจ๊ธฐ๋“ค์„ ๋ชจ๋‘ ์žก์œผ๋ฉด ๋‹ค์Œ ๋‹จ๊ณ„๋กœ ๋„˜์–ด๊ฐ€๋„๋ก ํ–ˆ์Šต๋‹ˆ๋‹ค. ์•„๋ž˜๋Š” ์ƒ˜ํ”Œ ์˜์ƒ์ž…๋‹ˆ๋‹ค.

 

์›๋ž˜ ๋‹จ๊ณ„๋ณ„๋กœ ์ผ์ • ๋งˆ๋ฆฌ์ˆ˜์˜ ๋ชจ๊ธฐ๊ฐ€ ๋‚˜์˜ค๋„๋ก ํ•ด๋†“์•˜์œผ๋‚˜ ๋‹จ๊ณ„๋ณ„๋กœ ๋ชจ๊ธฐ๋งˆ๋ฆฌ์ˆ˜๋ฅผ ์ œ์™ธํ•˜๋ฉด ํŠน๋ณ„ํ• ๊ฒŒ ์—†์–ด์„œ ๋‹จ๊ณ„๋‹น ํ•œ๋งˆ๋ฆฌ์”ฉ๋งŒ ์ถœ๋ชฐํ•˜๋„๋ก ์„ค์ •์„ ๋ณ€๊ฒฝํ•œ ๋’ค ๋…นํ™”ํ–ˆ์Šต๋‹ˆ๋‹ค. ๋‹จ๊ณ„๋ณ„๋กœ ๋ชจ๊ธฐ์˜ HP๋„ ๋Š˜์–ด๋‚ฉ๋‹ˆ๋‹ค. ใ…Žใ…Ž

์•„์ง ํ•ด์•ผํ• ๊ฒŒ ๋งŽ์ด ์žˆ์ง€๋งŒ ์ค‘๊ฐ„์ค‘๊ฐ„ ๊ธฐ๋ก์„ ์œ„ํ•ด ํฌ์ŠคํŒ…ํ•ฉ๋‹ˆ๋‹ค.

์•ž์œผ๋กœ ํ•ด์•ผํ•  ๊ฒƒ๋“ค์€... HP๋ฐ” ๋งŒ๋“ค๊ธฐ... ์œ ์ € ๋กœ๊ทธ์ธ ๋ฐ ์ตœ์ข…๊ฒŒ์ž„์ƒํƒœ ์ €์žฅํ•˜๊ธฐ, ๋ฆฌ๋”๋ณด๋“œ, ๋ชจ๊ธฐ์˜ ์œ ์ €๊ณต๊ฒฉ ๊ธฐ๋Šฅ, ์œ ์ € ๋ฐ ๋ชจ๊ธฐ ๊ณต๊ฒฉ๋ ฅ, ์žฅ๋น„&์Šคํ‚ฌ, ๋ณด์Šค๊ธ‰๋ชจ๊ธฐ, ๋ณด์Šค์ถœํ˜„์‹œ ์• ๋‹ˆ๋ฉ”์ด์…˜ ๋“ฑ๋“ฑ ๊ธฐํšํ•˜๊ธฐ ๋ฐ ๊ทธ๋ฆผ๊ทธ๋ฆฌ๊ธฐ ๋“ฑ๋“ฑ... ๋„ˆ๋ฌด ๋งŽ์ด ์žˆ์ง€๋งŒ ์ฒœ์ฒœํžˆ ๋๊นŒ์ง€ ๊ฐ€๋ณด๋ ค๊ณ  ํ•ฉ๋‹ˆ๋‹ค ใ…Ž

 

flutter ์— ์ž…๋ฌธํ•œ์ง€ 2์ฃผ์ •๋„ ๋˜๊ฐ€๋Š”๊ฒƒ ๊ฐ™๋„ค์š”

์šฐ์—ฐํžˆ ์œ ํˆฝํ†ตํ•ด์„œ ์ ‘ํ•œ๋’ค๋กœ ๋ฐ˜ํ•ด๊ฐ€์ง€๊ณ  ์‹ค์ œ ์—…๋ฌด์—์„œ ์“ธ์ผ์€ ์—†์„ ๊ฒƒ ๊ฐ™์ง€๋งŒ ๋ชจ๋ฐ”์ผ์•ฑ์ด๋“  ์›น์ด๋“  1์ธ๊ฐœ๋ฐœ์šฉ์œผ๋กœ ๋„ˆ๋ฌด ์ข‹์„ ๊ฒƒ ๊ฐ™์•„ ์ž…๋ฌธํ–ˆ์Šต๋‹ˆ๋‹ค.

ํ•œ 2์ฃผ๋™์•ˆ ์ฑ… ๊ตฌ์ž…ํ•ด์„œ ์ฝ๊ณ , ๊ณต์‹๋ฌธ์„œ ์ฝ๊ณ , ์ฑ… ์˜ˆ์ œ์™€ ์ฝ”๋“œ๋žฉ ์˜ˆ์ œ๋“ค ๊ทธ๋ฆฌ๊ณ  ์œ ํˆฝ ์˜ˆ์ œ๋“ค ๋”ฐ๋ผํ•ด๋ณด๊ณ ๋‚˜์„œ,

๊ฐ„๋‹จํ•œ ํƒ€์ž์—ฐ์Šต ์•ฑ์„ ๋งŒ๋“ค์–ด ๋ดค๋Š”๋ฐ ์–ด๋–ค ์œ„์ ฏ์„ ์‚ฌ์šฉํ• ์ง€๋ถ€ํ„ฐํ•ด์„œ ์ฒ˜์Œ๋ถ€ํ„ฐ ๋๊นŒ์ง€ ์˜ค๋กœ์ง€ ์ œ ์ƒ๊ฐ๋งŒ์œผ๋กœ ํ•˜๋‚˜ ๋งŒ๋“ค์–ด๋ณด์•˜์Šต๋‹ˆ๋‹ค. (์˜ˆ์ œ ๋”ฐ๋ผํ•˜๊ธฐ๋Š” ์ฝ”๋“œ๋žฉ์ด ์ œ์ผ ๋ฐฐ์šฐ๊ธฐ ์ข‹์•˜๋˜ ๊ฒƒ ๊ฐ™์Šต๋‹ˆ๋‹ค)

ํƒ€์ˆ˜๊ณ„์‚ฐํ• ๋•Œ ํ•œ๊ธ€์€ ์˜์–ด๋ณด๋‹ค ๊นŒ๋‹ค๋กœ์šธ ๊ฒƒ ๊ฐ™์•„ ์˜๋ฌธ๋ฒ„์ „์œผ๋กœ๋งŒ ๋งŒ๋“ค์—ˆ์Šต๋‹ˆ๋‹ค.

์ด๊ฒƒ์ €๊ฒƒ ๋” ์ถ”๊ฐ€ํ•˜๊ณ  ์‹ถ์€ ๊ธฐ๋Šฅ(๋ฆฌ๋”๋ณด๋“œ, ์‹ค์‹œ๊ฐ„ ๋ฐฐํ‹€)๋“ค๋„ ์žˆ๊ณ  flame ์„ ์ด์šฉํ•ด์„œ ํƒ€์ž๋ฅผ ์ด์šฉํ•œ ์—ฌ๋Ÿฌ ๊ฒŒ์ž„๋ชจ๋“œ๋“ค์„ ์ถ”๊ฐ€ํ•˜๋Š” ๋“ฑ ์ข€๋” ๊ฒŒ์ž„์Šค๋Ÿฝ๊ฒŒ ๊พธ๋ฉฐ๋ณด๊ณ ๋„ ์‹ถ์€๋ฐ ์ผ๋‹จ ๊ทธ๋ƒฅ ๊ธฐ๋ก์šฉ๋„๋กœ ๋‚จ๊ฒจ๋ด…๋‹ˆ๋‹ค.

๋งค์šฐ ๊ฐ„๋‹จํ•œ ์•ฑ์ด์ง€๋งŒ flutter์™€ ๋” ์นœ์ˆ™ํ•ด์งˆ ์ˆ˜ ์žˆ์—ˆ๋˜๊ฒƒ ๊ฐ™์•„ ๋งค์šฐ ์œ ์ตํ–ˆ๋˜ ์‹œ๊ฐ„์ด์—ˆ๋„ค์š”.

๋ชจ๋ฐ”์ผ ์•ฑ ๋ฒ„์ „๊ณผ ์›น ๋ฒ„์ „์„ ๋ถ„๊ธฐํƒœ์›Œ์„œ ์กฐ๊ธˆ ๋‹ค๋ฅด๊ฒŒ ๊ฐ€์ ธ๊ฐˆ ์ˆ˜๋„ ์žˆ์—ˆ๋Š”๋ฐ ์•„์ง์€ ์›น๋ณด๋‹ค๋Š” ์•ฑ๊ฐœ๋ฐœ์— ๋” ์ตœ์ ํ™”๋œ ํ”„๋ ˆ์ž„์›Œํฌ์ธ ๊ฒƒ ๊ฐ™์Šต๋‹ˆ๋‹ค.

์ด์ œ ์Šฌ์Šฌ flame ์œผ๋กœ ๋„˜์–ด๊ฐ€๋ณด๋ ค ํ•ฉ๋‹ˆ๋‹ค :)

์žํŒ ์—ฐ์Šต์šฉ ์•ฑ

 

์•„๋ž˜ ์˜์ƒ์€ ์ฒซ ๋‚  ์„ค์น˜ํ•ด์„œ ์‹คํ–‰ํ•ด๋ณธ ๋ฐ๋ชจ์•ฑ์˜ ๋‚ด์šฉ์„ ๋‹ค ์ง€์šฐ๊ณ  ์ฒ˜์Œ๋ถ€ํ„ฐ ์ž‘์„ฑํ•˜๋ฉด์„œ ํ™”๋ฉด ์ด๋™, ๊ฐ์ข… ๋ฒ„ํŠผ ์ถ”๊ฐ€, ๊ทธ๋ฆฌ๊ณ  ๋ฒ„ํŠผ์— ์ด๋ฒคํŠธ ํ•ธ๋“ค๋ง ๊ธฐ๋Šฅ์„ ๋„ฃ์–ด๋ณด๋Š” ๊ธฐ์ดˆ์ ์ธ ๋ถ€๋ถ„์— ๋Œ€ํ•ด์„œ ๋น ๋ฅด๊ฒŒ ๊ฐ€๋ฅด์ณ ์ฃผ๊ณ ์žˆ์Šต๋‹ˆ๋‹ค. ์ฒ˜์Œ ํ”Œ๋Ÿฌํ„ฐ๋ฅผ ์ ‘ํ•˜๋Š” ๋ถ„๋“ค์—๊ฒŒ ๋”ฑ ์ข‹์€ ์˜์ƒ์ธ๊ฒƒ ๊ฐ™์•„์„œ ๊ณต์œ ํ•ฉ๋‹ˆ๋‹ค.

 

https://www.youtube.com/watch?v=C-fKAzdTrLU 

 

๊ทธ๋ฆฌ๊ณ  ํ”Œ๋Ÿฌํ„ฐ ๊ณต์‹๋ฌธ์„œ์—์„œ๋„ Cookbook ์—์„œ ๊ธฐ๋ณธ์ ์œผ๋กœ ํ•„์š”ํ•œ ๊ธฐ๋Šฅ๋“ค์—๋Œ€ํ•ด ์–ด๋–ป๊ฒŒ ๊ตฌํ˜„ํ•ด์•ผํ•˜๋Š”์ง€ ์˜ˆ์ œ์ƒ˜ํ”Œ๋“ค์„ ๊ฐ–์ถ”๊ณ  ์žˆ๊ณ , codelab ์ด๋ผ๋Š” ๋‹จ๊ณ„๋ณ„ ๋”ฐ๋ผํ•ด๋ณด๊ธฐ์™€ ๊ฐ™์€ ์ž๋ฃŒ๋„ ์ œ๊ณตํ•ด์ฃผ๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. ์ฝ”๋“œ๋žฉ์„ ๋”ฐ๋ผํ•ด๋ณด๋ฉด ์•ฑ์„ ๊ฐœ๋ฐœํ• ๋•Œ ์–ด๋–ป๊ฒŒ ๊ฐœ๋ฐœ์ด ์ง„ํ–‰๋˜๋Š”์ง€ ๊ทธ ์ˆœ์„œ๋„ ๋ฐฐ์šธ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

๊ตฌ๊ธ€์—์„œ ์—„์ฒญ๋‚˜๊ฒŒ ๋ฐ€๊ณ ์žˆ๋‹ค๋Š”๊ฒŒ ๋Š๊ปด์งˆ ์ •๋„๋กœ ๊ธฐ๋ฐ˜์ž๋ฃŒ๋“ค์ด ๋งค์šฐ ๋งŽ์ด ์žˆ๋„ค์š”.

ํ”Œ๋Ÿฌํ„ฐ๋ฅผ ์จ๋ณด๋ ค๋‹ค๊ฐ€ ๋‹คํŠธ๋ฅผ ๊ณต๋ถ€ํ•˜๊ธฐ ์‹œ์ž‘..

Dart ๊ฐ€ ์–ด๋–ค ์–ธ์–ด์ธ์ง€์—๋Œ€ํ•œ ๊ฐ„๋žตํ•œ ์†Œ๊ฐœ๋‚ด์šฉ์„ ๊ณต์‹๋ฌธ์„œ์—์„œ ์ฝ์–ด๋ดค์Šต๋‹ˆ๋‹ค.

์–ผํ• ๋ณด๋‹ˆ ์ž๋ฐ”์™€ ๋งค์šฐ ์œ ์‚ฌํ•ฉ๋‹ˆ๋‹ค.

๊ฐ์ฒด์ง€ํ–ฅ ์–ธ์–ด์ด๊ณ  ๋ณ€์ˆ˜, loop, ๋ฉ”์„œ๋“œ, ํด๋ž˜์Šค ๋“ฑ๋“ฑ ๋งค์šฐ ๋งŽ์€ ๋ถ€๋ถ„์—์„œ ์œ ์‚ฌํ•œ ์ ์„ ๋ณด์ด๊ณ  ์žˆ์Œ.

์ž๋ฐ”์™€ ๋‹คํŠธ์˜ ์ฐจ์ด์ ์„ ๋‚˜์—ดํ•ด๋ณด์ž๋ฉด..๋‹คํŠธ๋Š” ์•„๋ž˜์™€ ๊ฐ™์€ ํŠน์ง• ๋˜๋Š” ๊ธฐ๋Šฅ์ด ์žˆ์Šต๋‹ˆ๋‹ค.

  • primitive(์›์‹œ) ํƒ€์ž…์ด ์—†์Œ. ์ž๋ฐ”์˜ ๊ฒฝ์šฐ int, long, double ๊ณผ ๊ฐ™์€ ์›์‹œํƒ€์ž…๊ณผ ์ด ์›์‹œํƒ€์ž…๋“ค์˜ wrapper ํด๋ž˜์Šค์ธ Integer, Long, Double ๋“ฑ์ด ์กด์žฌํ•˜์ง€๋งŒ, ๋‹คํŠธ๋Š” ๋ชจ๋“  ํƒ€์ž…์ด Object ์ž„.
  • ๋ณ€์ˆ˜ ์‚ฌ์šฉ๋ฒ• (์ถœ๋ ฅ์‹œ ๋ฌธ์ž์—ด์„ "์ด๊ฑธ ์ถœ๋ ฅํ• ๊ฑด๋ฐ ๋ณ€์ˆ˜๋ช…์„ ๋ถ™์˜€์–ด์š” $๋ณ€์ˆ˜๋ช…" ์ด๋ ‡๊ฒŒ ์จ์ฃผ๋ฉด $๋ณ€์ˆ˜๋ช… ์ด ๋ณ€์ˆ˜๊ฐ€ ๋“ค๊ณ ์žˆ๋Š” ๊ฐ’์œผ๋กœ ์น˜ํ™˜๋˜์–ด ์ถœ๋ ฅ๋จ. ์ž๋ฐ”์˜ ๊ฒฝ์šฐ String.format ์„ ์ด์šฉํ•ด์•ผ ํ–ˆ์Œ), ๋ณ€์ˆ˜๋ช… ์ž๋ฆฌ์— {expression} ์„ ์‚ฌ์šฉํ•˜์—ฌ ๊ณ„์‚ฐ์‹์„ ์‚ฌ์šฉํ•  ์ˆ˜๋„ ์žˆ์Œ
  • null safety (Dart 2.12 ๋ฒ„์ „๋ถ€ํ„ฐ ์ƒ๊น€. ์ž๋ฐ”์˜ NPE ์˜ˆ๋ฐฉ์„ ๊ฐœ๋ฐœ์ž๊ฐ€ ์•„๋‹Œ ์ปดํŒŒ์ผ๋Ÿฌ๊ฐ€ ํ•ด์ฃผ๋„๋ก ํ•œ ๊ธฐ๋Šฅ์œผ๋กœ ๊ธฐ๋ณธ์ ์œผ๋กœ ๋ชจ๋“  ๊ฐ์ฒด๋Š” non-nullable ๋กœ ์ทจ๊ธ‰ํ•จ. ๋‹ค๋งŒ ? ๊ธฐํ˜ธ๋ฅผ ์ด์šฉํ•ด์„œ nullable ๊ฐ์ฒด๋ผ๊ณ  ๋ช…์‹œ์ ์œผ๋กœ ์„ ์–ธํ•˜๋ฉด null ๊ฐ’๋„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Œ, ๊ณต์‹๋ฌธ์„œ ์ฐธ๊ณ )
  • private, protected, public, default ๊ฐ™์€ ์ ‘๊ทผ์ œํ•œ์ž(access modifier)๊ฐ€ ์—†์Œ. ๋‹ค๋งŒ ์ด๋ฆ„์ด ๋ฐ‘์ค„๋กœ ์‹œ์ž‘ํ•˜๋ฉด ํ•ด๋‹น ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ(ํด๋ž˜์Šค?)์— privateํ•œ ๊ฒƒ์ด ๋จ.
  • ๋‹คํŠธ์˜ int ํƒ€์ž…์€ 64 ๋น„ํŠธ signed ๊ฐ’์ด๊ณ  ํ”Œ๋žซํผ์— ๋”ฐ๋ผ ๋ฒ”์œ„๊ฐ€ ์•ฝ๊ฐ„ ๋‹ค๋ฆ„ (๊ณต์‹๋ฌธ์„œ)

๋ญ ์ด์™ธ์—๋„ lexical scope, lexical closure ๋“ฑ ์—ฌ๋Ÿฌ ๋ถ€๋ถ„์—์„œ ๋‹ค๋ฅธ ๋ถ€๋ถ„๋“ค์ด ๋ณด์˜€๋Š”๋ฐ ์—ฌ๊ธฐ ๋‹ค ๋‚˜์—ดํ•  ์ˆ˜๋Š” ์—†๊ณ ,

์ „์ฒด์ ์ธ ๋Š๋‚Œ์€ ์ž๋ฐ”์™€ ์ž๋ฐ”์Šคํฌ๋ฆฝํŠธ๋ฅผ ํ•ฉ์ณ๋†“์€ ์–ธ์–ด๋ผ๋Š” ๋Š๋‚Œ์„ ๋ฐ›์•˜์Šต๋‹ˆ๋‹ค. (ํ•ด๋ณธ๊ฒŒ Java๋ž‘ Javascript ๋ฟ์ด๋ผ.. ใ…Ž)

 

์ฐธ๊ณ ๋กœ ๋‹คํŠธํŒจ๋“œ์—์„œ ์ด๋Ÿฐ์ €๋Ÿฐ ๋‹คํŠธ์ฝ”๋“œ๋“ค์„ ์‹œํ—˜ํ•ด๋ณผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

์šฐ์—ฐํžˆ ์–ด๋–ค 1์ธ ๊ฐœ๋ฐœ์ž์˜ ์ด์•ผ๊ธฐ๋ฅผ ์œ ํˆฝ์—์„œ ์ ‘ํ•˜๊ณ  ๋‚˜๋„ 1์ธ ๊ฐœ๋ฐœ์ด๋‚˜ ํ•ด์•ผ๊ฒ ๋‹ค ์ƒ๊ฐํ•˜๋˜ ์ฐฐ๋‚˜ ๋˜ ์šฐ์—ฐํžˆ ํ”Œ๋Ÿฌํ„ฐ ๊ฐ•์‚ฌ์˜ ์˜์ƒ์„ ๋ณธ ๋’ค ๋ชฉํ‘œ๋ฅผ ๋‹ฌ์„ฑํ•˜๊ธฐ ์œ„ํ•œ ๋งค์šฐ ๋งค๋ ฅ์ ์ธ ํ”„๋ ˆ์ž„์›Œํฌ๋ผ๋Š” ์ƒ๊ฐ์ด ๋“ค์–ด์„œ ํ”Œ๋Ÿฌํ„ฐ์— ๋Œ€ํ•ด์„œ ์•Œ์•„๋ณด๋ ค๊ณ  ํ•ฉ๋‹ˆ๋‹ค. 

 

์šฐ์„  ๊ณต์‹ ์‚ฌ์ดํŠธ์—์„œ ํ”Œ๋Ÿฌํ„ฐ๊ฐ€ ๋ญ”์ง€๋ถ€ํ„ฐ ํ™•์ธ์„ ์ข€ ํ•ด๋ดค๋Š”๋ฐ์š”

ํ”Œ๋Ÿฌํ„ฐ๋Š” ๊ตฌ๊ธ€์—์„œ ๋งŒ๋“  ํฌ๋กœ์Šคํ”Œ๋žซํผ UI ํ”„๋ ˆ์ž„์›Œํฌ์ž…๋‹ˆ๋‹ค.

๋ชจ๋ฐ”์ผ(์•ˆ๋“œ , IOS ๋‘˜๋‹ค), ๋ฐ์Šคํฌํƒ‘ ์•ฑ(Windows .exe ํŒŒ์ผ, MacOS .dmgํŒŒ์ผ), ์›น ๊นŒ์ง€ ๋ชจ๋‘ ํ•˜๋‚˜์˜ ์†Œ์Šค์ฝ”๋“œ๋กœ ๋ฐฐํฌ๊ฐ€ ๊ฐ€๋Šฅํ•˜์ฃ .

์ด๋Ÿฐ๊ฒŒ ๋ฐ”๋กœ ์Šˆํผ์•ฑ์ด ์•„๋‹Œ๊ฐ€ ์‹ถ๋„ค์š”. ์—ฌ๊ธฐ๊นŒ์ง€ํ•˜๊ณ  ์ผ๋‹จ ๋ฌด์ž‘์ • ๋”ฐ๋ผํ•ด๋ด…๋‹ˆ๋‹ค.

 

์•„๋ž˜ ๋งํฌ์˜ ์„ค๋ช…๋Œ€๋กœ ๋ช…๋ น์–ด๋ฅผ ์‹คํ–‰ํ•œ๋‹ค.

https://docs.flutter.dev/get-started/install/macos

 

macOS install

How to install on macOS.

docs.flutter.dev

 

1.  sudo softwareupdate --install-rosetta --agree-to-license

Password:
By using the agreetolicense option, you are agreeing that you have run this tool with the license only option and have read and agreed to the terms.
If you do not agree, press CTRL-C and cancel this process immediately.
2023-01-18 17:11:11.031 softwareupdate[62998:409413] Package Authoring Error: 012-92132: Package reference com.apple.pkg.RosettaUpdateAuto is missing installKBytes attribute
Install of Rosetta 2 finished successfully

๋ญ”๊ฐ€ ์—๋Ÿฌ๊ฐ€ ๋‚ฌ๋Š”๋ฐ ์„ฑ๊ณต์ ์œผ๋กœ ์„ค์น˜ํ–ˆ๋‹ค๊ณ  ๋‚˜์˜ค๋„ค์š” !?

 

2. flutter docter 

์•„๋ž˜์ฒ˜๋Ÿผ ์ด์˜๊ฒŒ๋„ ๋ถ„์„ ๊ฒฐ๊ณผ๋ฅผ ๋ณด์—ฌ์ฃผ๋Š”๊ตฐ์š”

์ผ๋‹จ Xํ‘œ์‹œ๊ฐ€ ๋œฌ ๊ฒƒ๋“ค์„ ์„ค์น˜ํ•ด์•ผ๊ฒ ์–ด์š”..

 

  • ์•ˆ๋“œ๋กœ์ด๋“œ ์ŠคํŠœ๋””์˜ค ์„ค์น˜ ๋ฐ ์ตœ์ดˆ์‹คํ–‰ํ•˜์—ฌ sdk ๋‹ค์šด๋กœ๋“œ, ๋ผ์ด์„ผํŠธ ๋™์˜, cli tool ์„ค์น˜ ๋“ฑ ์™„๋ฃŒ
  • Xcode ์„ค์น˜ ์™„๋ฃŒ ๋ฐ ๋ผ์ด์„ผ์Šค ๊ด€๋ จ ์˜ค๋ฅ˜ ํ•ด๊ฒฐ

 

ํ˜„์žฌ CocoaPods ์ด์Šˆ์™€ ์•ˆ๋“œ๋กœ์ด๋“œ ์ŠคํŠœ๋””์˜ค์—์„œ ๋ฒˆ๋“ค์ž๋ฐ”๊ฐ€ ์—†๋‹ค๋Š” warning๋งŒ ๋‚จ๊ธฐ๊ณ  ํ•ด๊ฒฐ.

 

VS Code ๋ฅผ IDE๋กœ ์„ ํƒํ•ด์„œ ํ…Œ์ŠคํŠธ์•ฑ ์ƒ์„ฑ, ์‹คํ–‰ ๋ฐ ์ˆ˜์ •ํ•˜์—ฌ hot reload ๊นŒ์ง€ ํ™•์ธ์™„๋ฃŒ.

 

๋‹ค์Œ์—” https://docs.flutter.dev/get-started/codelab ์—ฌ๊ธฐ๋ถ€ํ„ฐ ์ง„ํ–‰ ์˜ˆ์ •.

 

์ด์—ˆ์œผ๋‚˜... ๋ฒ„์ „์ด ๋‹ค๋ฅธ๊ฑด์ง€ ์‹ ๊ทœ ํ”„๋กœ์ ํŠธ ์ƒ์„ฑ์‹œ ์ƒ˜ํ”Œ ์˜ˆ์ œ๊ฐ€ ๋‹ฌ๋ผ์„œ ์ผ๋‹จ dart ์–ธ์–ด์— ๋Œ€ํ•ด ์•Œ์•„๋ณด๊ธฐ๋กœ ํ•จ...

์–ผํ• ๋ดค์„ ๋•Œ๋Š” ์ž๋ฐ”์™€ ๋งค์šฐ ๋น„์Šทํ•จ..

์•ˆ๋…•ํ•˜์„ธ์š”, ์ด๋ฒˆ์—” ์ด๋ฒคํŠธ๋“œ๋ฆฌ๋ธ ์„œ๋น„์Šค์˜ ๊ฐœ์„ ์ž‘์—…์„ ํ•˜๋‹ค๊ฐ€ ์•Œ๊ฒŒ๋œ cloudwatch ํ†ต๊ณ„์ง€ํ‘œ ์กฐํšŒ๋ฐฉ๋ฒ•์„ ๊ณต์œ ๋“œ๋ฆฝ๋‹ˆ๋‹ค.

์ด ์ž‘์—…์„ ํ•˜๊ฒŒ๋œ ์ด์œ ๋ฅผ ๋ง์”€๋“œ๋ฆฌ์ž๋ฉด ์ด๋ ‡์Šต๋‹ˆ๋‹ค.

ํ˜„์žฌ SQS ๋ฅผ ์ด์šฉํ•œ ์ด๋ฒคํŠธ๋“œ๋ฆฌ๋ธ ํ™˜๊ฒฝ์—์„œ ๋™์ž‘ํ•˜๋Š” ์„œ๋น„์Šค๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค. 

์ด๋Ÿฐ ์ €๋Ÿฐ ์ •๋ณด๋“ค์„ ๋™๊ธฐํ™”ํ•˜๊ธฐ ์œ„ํ•œ ๋ชฉ์ ์œผ๋กœ ์‚ฌ์šฉํ•˜๊ณ  ์žˆ๊ณ , ์ด ์„œ๋น„์Šค๋Š” ๋ฉ€ํ‹ฐ์“ฐ๋ ˆ๋“œ ํ™˜๊ฒฝ์—์„œ ๋™์ž‘ํ•˜๋„๋ก ๋˜์–ด์žˆ์Šต๋‹ˆ๋‹ค.

ํ˜น์‹œ๋ผ๋„ ์ด๋ฒคํŠธ๊ฐ€ ์ง€์—ฐ๋  ๊ฒฝ์šฐ๋ฅผ ๋Œ€๋น„ํ•ด์„œ ์“ฐ๋ ˆ๋“œ ๊ฐœ์ˆ˜๋ฅผ ์ˆ˜์‹œ๋กœ ์ˆ˜๋™์กฐ์ ˆํ•  ์ˆ˜ ์žˆ๋„๋ก ๊ตฌ์„ฑํ•ด๋‘์—ˆ์ฃ .

๊ทธ๋ฆฌ๊ณ  ์ง€์—ฐ์ด ๋ฐœ์ƒํ•˜์—ฌ SQS ๋ฉ”์‹œ์ง€๊ฐ€ ๋ฐœํ–‰๋œ ๋’ค ์ผ์ • ์‹œ๊ฐ„๋™์•ˆ ์ฒ˜๋ฆฌ๋ฅผ ๋ชปํ•˜์—ฌ ํ์— ๊ณ„์† ๋‚จ์•„์žˆ๊ฒŒ๋˜๋ฉด alert๊ฐ€ ๋ฐœ์ƒํ•˜๋„๋ก ๋˜์–ด์žˆ์Šต๋‹ˆ๋‹ค.

์ฆ‰, SQS์˜ ApproximateAgeOfOldestMessage ์ง€ํ‘œ๊ฐ’์„๋ณด๊ณ  ๋„ˆ๋ฌด ์˜ค๋ž˜๋™์•ˆ ์ฒ˜๋ฆฌ๊ฐ€ ์•ˆ๋  ๊ฒฝ์šฐ alert๋ฅผ ๋ฐ›๊ณ  ์ˆ˜๋™์œผ๋กœ ์“ฐ๋ ˆ๋“œ ๊ฐœ์ˆ˜๋ฅผ ์กฐ์ ˆํ•˜๋Š” ํ˜•ํƒœ๋กœ ์œ„๊ธฐ๋ฅผ ๋ฒ—์–ด๋‚˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. ๊ทธ๋Ÿฐ๋ฐ ์ด ๋“œ๋ฌผ๋”” ๋“œ๋ฌธ ์‚ฌ๊ฑด์ด๋ผ๋„ ๊ฐœ๋ฐœ์ž๋ผ๋ฉด, ๊ทธ๋ฆฌ๊ณ  ๊ฐ€๋Šฅํ•œ ์ผ€์ด์Šค๋ผ๋ฉด ๊ทธ๋ƒฅ ์ „๋ถ€ ๋‹ค ์ž๋™ํ™”๋ฅผ ํ•ด๋†“์•„์•ผ ํ•˜์ง€ ์•Š์„๊นŒ ์ƒ๊ฐ์ด ๋“ค์–ด ๊ฐœ์„  ์ž‘์—…์— ๋“ค์–ด๊ฐ”์Šต๋‹ˆ๋‹ค.

 

์ผ๋‹จ SQS์—์„œ ์ œ๊ณตํ•˜๋Š” ๋ชจ๋‹ˆํ„ฐ๋ง ์ง€ํ‘œ๋Š” ์•„๋ž˜์™€ ๊ฐ™์Šต๋‹ˆ๋‹ค. (AWS ์ฝ˜์†”ํ™”๋ฉด์˜ ๋ชจ๋‹ˆํ„ฐ๋ง ํƒญ์—์„œ ๋ณผ ์ˆ˜ ์žˆ๋Š” ๊ฒƒ๋“ค์ž…๋‹ˆ๋‹ค)

  • Approximate Number Of Messages Delayed
  • Approximate Number Of Messages Not Visible
  • Approximate Number Of Messages Visible
  • Approximate Age Of Oldest Message
  • Number Of Empty Receives
  • Number Of Messages Deleted
  • Number Of Messages Received
  • Number Of Messages Sent
  • Sent Message Size

์ด ์ง€ํ‘œ์— ํ•ด๋‹นํ•˜๋Š” ํ†ต๊ณ„์ˆ˜์น˜๋Š” cloudwatch ์—์„œ ์ˆ˜์ง‘์ด ๋ฉ๋‹ˆ๋‹ค.

ํด๋ผ์šฐ๋“œ์›Œ์น˜์—์„œ ์ˆ˜์ง‘๋œ ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ€์ง€๊ณ  ๋ชจ๋‹ˆํ„ฐ๋งํƒญ์— ๊ทธ๋ž˜ํ”„๋กœ ๋ณด์—ฌ์ฃผ๋Š” ๊ฒƒ์ด์ฃ .

 

์ด์ œ ์ œ๊ฐ€ ์›ํ•˜๋Š” Approximate Age Of Oldest Message ์— ๋Œ€ํ•œ ๋ฐ์ดํ„ฐ๋ฅผ ๋ฝ‘์•„๋ณด๋„๋ก ํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค.

์ผ๋‹จ ์•ฑ์˜ ๊ตฌ์„ฑ์€ ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค.

  • SpringBoot 2.3.x
  • AWS Java SDK 1.11.x
  • Java 11

AWS java sdk ์—์„œ๋Š” cloudwatch ์„œ๋น„์Šค์—์„œ ์ œ๊ณตํ•˜๋Š” API๋ฅผ ํ˜ธ์ถœํ•˜์—ฌ ํด๋ผ์šฐ๋“œ์›Œ์น˜ ๋ฐ์ดํ„ฐ๋ฅผ ์กฐํšŒํ•  ์ˆ˜ ์žˆ๋„๋ก cloudwatch client๋ฅผ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค. ์ œ์ผ ๋จผ์ € ์ด ํด๋ผ์ด์–ธํŠธ๋ฅผ ๋นˆ์œผ๋กœ ๋“ฑ๋กํ•ฉ๋‹ˆ๋‹ค.

@Configuration
public class AWSConfig {

    private AWSCredentialsProvider awsCredentialsProvider() {
        List<AWSCredentialsProvider> credentialsProviders = new ArrayList<>();
        credentialsProviders.add(new InstanceProfileCredentialsProvider(true));
        credentialsProviders.add(new ProfileCredentialsProvider());
        return new AWSCredentialsProviderChain(credentialsProviders);
    }

    @Bean
    public AmazonCloudWatch cloudWatchClient() {
        return AmazonCloudWatchClientBuilder.standard()
                .withCredentials(awsCredentialsProvider())
                .withRegion(Regions.fromName("ap-northeast-2"))
                .build()
                ;
    }
}

 

๊ทธ๋ฆฌ๊ณ  ์„œ๋น„์Šค ๋ ˆ์ด์–ด์—์„œ ์ด cloudWatchClient๋ฅผ ๊ฐ€์ ธ๋‹ค ์จ๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.

    private void getQueueStatus(String queueName) {
        long currentMillis = System.currentTimeMillis();
        long fiveMinutesInMillis = 5 * 60 * 1000;
        GetMetricStatisticsRequest statisticsRequest = new GetMetricStatisticsRequest()
                .withNamespace("AWS/SQS").withMetricName("ApproximateAgeOfOldestMessage")
                .withStatistics(Statistic.Maximum).withPeriod(300)
                .withStartTime(new Date(currentMillis - fiveMinutesInMillis))
                .withEndTime(new Date(currentMillis))
                .withDimensions(new Dimension().withName("QueueName").withValue(queueName));

        GetMetricStatisticsResult result = cloudWatch.getMetricStatistics(statisticsRequest);
        log.debug("dataPoints: {}", result.getDatapoints());
    }

 

cloudWatchClient ๋ฅผ ์ด์šฉํ•˜์—ฌ ํ†ต๊ณ„์ˆ˜์น˜ ๋ฐ์ดํ„ฐ๋ฅผ ์กฐํšŒํ•˜๋ ค๋ฉด GetMetricStatisticsRequest ๊ฐ์ฒด๋ฅผ ๋งŒ๋“ค์–ด์„œ ๋„ฃ์–ด์ฃผ์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. 

์ด ๊ฐ์ฒด์— ์„ค์ •ํ•ด์ค˜์•ผ ํ•˜๋Š” ๊ฐ’๋“ค ์ค‘ ํ•„์ˆ˜์ ์ธ ๊ฒƒ๋“ค๋งŒ ์„ค์ •ํ•ด๋ณด์•˜์Šต๋‹ˆ๋‹ค. 

๊ฐ„๋žตํžˆ ์„ค๋ช…ํ•˜์ž๋ฉด ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค.

  • withNameSpace: cloudwatch์—์„œ ์„œ๋น„์Šค๋ฅผ ๊ตฌ๋ถ„ํ•˜๋Š” ๊ฐ’ (ex. "AWS/SQS", "AWS/EC2", etc.)
  • withMetricName: ์กฐํšŒํ•˜๊ณ ์žํ•˜๋Š” ๋ฉ”ํŠธ๋ฆญ ๋ช…
  • withStatistics: Statistic ์—์„œ ์ œ๊ณตํ•˜๋Š” ํ†ต๊ณ„๊ธฐ์ค€(?), enum์œผ๋กœ ์ •์˜๋˜์–ด์žˆ์Œ
    • SampleCount
    • Average
    • Sum
    • Minimum
    • Maximum
  • withStartTime, withEndTime: ์กฐํšŒํ•˜๋ ค๋Š” ๋ฐ์ดํ„ฐ ๊ตฌ๊ฐ„ (๋ฐ์ดํ„ฐ์˜ ์‹œ์ž‘ ์‹œ์ ๊ณผ ์ข…๋ฃŒ ์‹œ์ )
  • withPeriod: ์กฐํšŒํ•˜๋ ค๋Š” ๋ฐ์ดํ„ฐ ๊ตฌ๊ฐ„ ๋‚ด์—์„œ์˜ ๋ฐ์ดํ„ฐ ๊ฐ„๊ฒฉ. ์˜ˆ๋ฅผ๋“ค๋ฉด ์ง€๋‚œ 1์‹œ๊ฐ„ ๋™์•ˆ ๋ช‡ ๋ถ„ ๊ฐ„๊ฒฉ์œผ๋กœ ๋ฐ์ดํ„ฐ๋ฅผ ์กฐํšŒํ• ์ง€๋ฅผ ์˜๋ฏธ. ์ดˆ๋‹จ์œ„๊ฐ’
  • withDimensions: SQS์˜ ๊ฒฝ์šฐ "QueueName" ํ•˜๋‚˜๋งŒ ์žˆ๊ณ , ์ด ๊ฐ’์œผ๋กœ ์–ด๋–ค sqs์— ๋Œ€ํ•œ ๋ฐ์ดํ„ฐ์ธ์ง€ ๊ตฌ๋ถ„ ๊ฐ€๋Šฅ.

(์ฐธ๊ณ : Available CloudWatch metrics for Amazon SQS)

 

์„ค์ •๊ฐ’์€ ํ˜„์žฌ ์ง€๋‚œ 5๋ถ„๋™์•ˆ(from endTime to startTime) 5๋ถ„๊ฐ„๊ฒฉ(period)์˜ ๋ฐ์ดํ„ฐ๋ฅผ ์กฐํšŒํ•˜๋„๋ก ๋˜์–ด์žˆ์œผ๋ฏ€๋กœ 1๊ฐœ์˜ data point ๊ฐ€ ์กฐํšŒ๊ฐ€ ๋ฉ๋‹ˆ๋‹ค. ๊ทธ๋ฆฌ๊ณ  unit์€ ์ดˆ๋‹จ์œ„๋กœ ๋‚˜์˜ต๋‹ˆ๋‹ค.

์œ„ ์ฝ”๋“œ๋ฅผ ์‹คํ–‰ํ•ด์„œ ์กฐํšŒํ•œ sqs์˜ Approximate Age Of Oldest Message ์ง€ํ‘œ๊ฐ’์€ ๋‹ค์Œ๊ณผ ๊ฐ™์ด ์ถœ๋ ฅ๋ฉ๋‹ˆ๋‹ค.

dataPoints: [{Timestamp: Thu Dec 29 16:08:00 KST 2022,Maximum: 249.0,Unit: Seconds,}]

 

์„ค์ •๊ฐ’์„ ๋ณ€๊ฒฝํ•˜์—ฌ period๋ฅผ 60์œผ๋กœ ๋„ฃ์–ด์„œ ์‹คํ–‰ํ•˜๋ฉด 5๊ฐœ๊ฐ€ ์กฐํšŒ๋ฉ๋‹ˆ๋‹ค.

dataPoints: [{Timestamp: Thu Dec 29 17:03:00 KST 2022,Maximum: 3254.0,Unit: Seconds,}, {Timestamp: Thu Dec 29 17:07:00 KST 2022,Maximum: 3554.0,Unit: Seconds,}, {Timestamp: Thu Dec 29 17:05:00 KST 2022,Maximum: 3433.0,Unit: Seconds,}, {Timestamp: Thu Dec 29 17:06:00 KST 2022,Maximum: 3491.0,Unit: Seconds,}, {Timestamp: Thu Dec 29 17:04:00 KST 2022,Maximum: 3370.0,Unit: Seconds,}]

 

์กฐํšŒ๋œ 5๊ฐœ์˜ ๋ฐ์ดํ„ฐ๋Š” ์ง€๋‚œ 5๋ถ„ ๊ตฌ๊ฐ„(startTime, endTime)์—์„œ 1๋ถ„ ๊ฐ„๊ฒฉ(period) ๋ฐ์ดํ„ฐ๋ฅผ ์กฐํšŒํ–ˆ์„ ๋•Œ์˜ ๊ฒฐ๊ณผ์ž…๋‹ˆ๋‹ค. ๊ทธ๋ฆฌ๊ณ  ์ด ๊ฐ’์€ Approximate Age Of Oldest Message, ์ฆ‰, ๋Œ€๋žต์ ์œผ๋กœ ์–ผ๋งˆ๋‚˜ ์˜ค๋ž˜๋˜์—ˆ๋Š”๊ฐ€๋ฅผ ๋‚˜ํƒ€๋‚ด๋Š” ๊ฐ’์ด๋ฏ€๋กœ 1๋ถ„ ๊ฐ„๊ฒฉ ๋ฐ์ดํ„ฐ๋ฅผ ์กฐํšŒํ•œ๋‹ค๋ฉด ์•ฝ 1๋ถ„(60์ดˆ)์˜ ์‹œ๊ฐ„์ฐจ์ด๊ฐ€ ์žˆ๊ฒ ์ฃ . ์ถœ๋ ฅ๋œ ๋ฐ์ดํ„ฐ์˜ ์ˆœ์„œ๊ฐ€ ์‹œ๊ฐ„์ˆœ์ด ์•„๋‹ˆ๋‹ˆ ์‹œ๊ฐ„์ˆœ์œผ๋กœ ์ •๋ ฌํ•ด๋ณด๋ฉด ์•ฝ 1๋ถ„ ์ •๋„ ์ฐจ์ด๊ฐ€ ๋‚œ๋‹ค๋Š” ๊ฒƒ์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. 

[
    {Timestamp: Thu Dec 29 17:03:00 KST 2022,Maximum: 3254.0,Unit: Seconds,}, 
    {Timestamp: Thu Dec 29 17:04:00 KST 2022,Maximum: 3370.0,Unit: Seconds,}
    {Timestamp: Thu Dec 29 17:05:00 KST 2022,Maximum: 3433.0,Unit: Seconds,}, 
    {Timestamp: Thu Dec 29 17:06:00 KST 2022,Maximum: 3491.0,Unit: Seconds,}, 
    {Timestamp: Thu Dec 29 17:07:00 KST 2022,Maximum: 3554.0,Unit: Seconds,}, 
]

 

17:03 ์—์„œ 17:04๋Š” ์˜ˆ์™ธ์ ์œผ๋กœ ์•ฝ 2๋ถ„ ์ฐจ์ด๊ฐ€ ๋‚˜๋„ค์š” ^^;;

์ด์ƒ์œผ๋กœ AWS cloudwatch API๋กœ SQS์˜ metric์„ ์กฐํšŒํ•˜๋Š” ๋ฐฉ๋ฒ•์— ๋Œ€ํ•ด ์•Œ์•„๋ณด์•˜์Šต๋‹ˆ๋‹ค.

 

์ €๋Š” ์ด๋ ‡๊ฒŒ ์กฐํšŒํ•œ ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ€์ง€๊ณ  ์ผ์ • ์‹œ๊ฐ„์„ ๋„˜์–ด์„ค ๊ฒฝ์šฐ ์“ฐ๋ ˆ๋“œ ๊ฐœ์ˆ˜๋ฅผ scale in/out ํ•˜๋„๋ก ์„œ๋น„์Šค๋ฅผ ๊ตฌํ˜„ํ–ˆ์Šต๋‹ˆ๋‹ค.

 

๋„์›€์ด ๋˜์…จ๋‹ค๋ฉด ๊ณต๊ฐ๊พน ๋ถ€ํƒ๋“œ๋ ค์š”~

 

์šด์˜ํ™˜๊ฒฝ์— ์ด๋Ÿฐ์ €๋Ÿฐ ๊ฐœ๋ฐœ์ด ๊ณ„์† ์ง„ํ–‰๋˜๊ณ  ๋ฐ์ดํ„ฐ๋„ ์Œ“์ด๊ณ  ํ•˜๋‹ค๋ณด๋‹ˆ ์ ์  ๋ฌด๊ฑฐ์›Œ์ง€๊ณ  ์žˆ๋Š” ์™€์ค‘์— dba์ชฝ์—์„œ slow query ๊ด€๋ จํ•˜์—ฌ ๋ฌธ์˜๊ฐ€ ๋“ค์–ด์™”๊ณ  ํ™•์ธํ•˜๋‹ค๋ณด๋‹ˆ ๋‹ค๋ฅธ ์ด์Šˆ๊ฐ€ ํ™•์ธ๋˜์—ˆ๋‹ค. readOnly ํŠธ๋žœ์žญ์…˜์œผ๋กœ ๋ฌถ์–ด๋†“์€ ์ฟผ๋ฆฌ์ธ๋ฐ reader์ชฝ์—์„œ ์‹คํ–‰๋˜์–ด์•ผ ํ•  ์ฟผ๋ฆฌ๊ฐ€ writer์ชฝ์—์„œ๋„ ์‹คํ–‰์ด ๋˜๊ณ  ์žˆ๋Š” ํ˜„์ƒ์ด ์ง€์†๋˜๊ณ  ์žˆ์—ˆ๋‹ค.

 

ํ˜„์žฌ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์€ SpringBoot 2.x + MariaDB connector 2.7.x + HikariPool + MyBatis ์‚ฌ์šฉํ•˜๋„๋ก ๊ตฌ์„ฑ๋œ ์ƒํƒœ.

compile 'org.mybatis.spring.boot:mybatis-spring-boot-starter:2.1.1'
compile 'org.springframework.boot:spring-boot-starter-jdbc'
compile 'org.mariadb.jdbc:mariadb-java-client:2.7.7'
compile 'org.bgee.log4jdbc-log4j2:log4jdbc-log4j2-jdbc4.1:1.16'

 

db ํด๋Ÿฌ์Šคํ„ฐ๋Š” writer 1๊ฐœ์™€ reader 1๊ฐœ๋กœ ๊ตฌ์„ฑ๋˜์–ด์žˆ๋‹ค. ํด๋Ÿฌ์Šคํ„ฐ ์ฃผ์†Œ๋Š” writer, reader ์šฉ์œผ๋กœ ๊ฐ 1๊ฐœ์”ฉ ๋‘ ๊ฐœ๊ฐ€ ์žˆ๋‹ค.

  • my-cluster.cluster-xxx.ap-northeast-2.rds.amazonaws.com 
  • my-cluster.cluster-ro-xxx.ap-northeast-2.rds.amazonaws.com

 

๊ทธ๋ฆฌ๊ณ  ์•„๋ž˜์™€ ๊ฐ™์ด writer์šฉ๊ณผ reader์šฉ ์ฃผ์†Œ๊ฐ€ ๋ณ„๋„๋กœ ์žˆ๋‹ค.

  • my-cluster-01.xxx.ap-northeast-2.rds.amazonaws.com (writer)
  • my-cluster-02.xxx.ap-northeast-2.rds.amazonaws.com (reader)

 

๊ตต๊ฒŒ ํ‘œ์‹œํ•œ ํด๋Ÿฌ์Šคํ„ฐ ์ฃผ์†Œ์™€ ์‹ค์ œ DB์ฃผ์†Œ์˜ ์ฐจ์ด๋Š” ํ™•์ธํ•˜๊ณ  ๋„˜์–ด๊ฐ€์ž.

 

๊ทธ๋ฆฌ๊ณ  jdbc url์€ ์•„๋ž˜์™€ ๊ฐ™์ด writer ํด๋Ÿฌ์Šคํ„ฐ ์ฃผ์†Œ๋งŒ ๋„ฃ์–ด์„œ ์‚ฌ์šฉํ•˜๊ณ  ์žˆ์—ˆ๋‹ค. ๋‚ด๊ฐ€ ์ด๋ ‡๊ฒŒ ์„ค์ •ํ•œ๊ฑด ์•„๋‹ˆ์—ˆ๊ณ  ์ด๋ ‡๊ฒŒ ํ•ด๋„ aurora ์˜ต์…˜์„ ์“ธ ๊ฒฝ์šฐ ์ž๋™์œผ๋กœ reader์ชฝ์œผ๋กœ readOnly ์ฟผ๋ฆฌ๊ฐ€ ์‹คํ–‰๋œ๋‹ค๊ณ  ๋ˆ„๊ตฐ๊ฐ€๊ฐ€ ๊ทธ๋ž˜์„œ ๋ญ ๊ทธ๋Ÿฌ๋ ค๋‹ˆ ํ•˜๊ณ  ์žˆ์—ˆ๋Š”๋ฐ ์ด๋Ÿฐ ์ด์ƒํ•œ ํ˜„์ƒ์ด ํ™•์ธ๋œ ๊ฒƒ์ด๋‹ค. ( >,.< )

spring:
  datasource:
    url: jdbc:mariadb:aurora://my-cluster.cluster-xxx.ap-northeast-2.rds.amazonaws.com

 

์›์ธ์ถ”์ ์„ ํ•ด๋ณด๊ธฐ ์œ„ํ•ด ์ผ๋‹จ log4jdbc์˜ audit ์„ค์ •์„ ์ข€ ๋ณ€๊ฒฝํ–ˆ๋‹ค. @Transactional(readOnly=true) ์„ค์ •์ด ์ œ๋Œ€๋กœ ์•ˆ๋จนํžˆ๋Š”๊ฑด๊ฐ€?? ์‹ถ์–ด์„œ ๋ง์ด๋‹ค. 

logging:
  level:
    jdbc:
      audit: debug

 

ํ™•์ธํ–ˆ์„ ๋•Œ๋Š” ์•„๋ž˜์ฒ˜๋Ÿผ readOnly ์„ค์ •์€ ์ •์ƒ์ ์œผ๋กœ ๋˜๊ณ  ์žˆ์—ˆ๋‹ค.

DEBUG jdbc.audit : 1. Connection.setReadOnly(true) returned   com.zaxxer.hikari.pool.ProxyConnection.setReadOnly(ProxyConnection.java:423)
DEBUG jdbc.audit : 1. Connection.getAutoCommit() returned true  com.zaxxer.hikari.pool.HikariProxyConnection.getAutoCommit(HikariProxyConnection.java:-1)
DEBUG jdbc.audit : 1. Connection.setAutoCommit(false) returned   com.zaxxer.hikari.pool.ProxyConnection.setAutoCommit(ProxyConnection.java:414)
DEBUG jdbc.audit : 1. Connection.getAutoCommit() returned false

 

๊ทธ๋Ÿผ ์™œ ์ด๋Ÿด๊นŒ? ๋‹ค์Œ์œผ๋กœ๋Š” jdbc url์— ์•„๋ž˜์™€ ๊ฐ™์ด log ํŒŒ๋ผ๋ฏธํ„ฐ๋ฅผ ์ถ”๊ฐ€ํ•˜๊ณ  ๋กœ๊ฑฐ๋ฅผ ์ถ”๊ฐ€ํ•˜์—ฌ mariaDB ์ชฝ ๋กœ๊ทธ๋ฅผ ํ™•์ธํ•ด๋ณด์•˜๋‹ค.

spring:
  datasource:
    url: jdbc:mariadb:aurora://my-cluster.cluster-xxx.ap-northeast-2.rds.amazonaws.com?log=true

 

<logger name="org.mariadb.jdbc" level="info">
    <appender-ref ref="stdout"/>
</logger>

 

DEBUG 88239 --- [nio-8080-exec-1] org.mariadb.jdbc.MariaDbConnection       : conn=817636(M) - set read-only to value true
DEBUG 88239 --- [nio-8080-exec-1] o.m.j.i.protocol.AbstractQueryProtocol   : System variable change :  autocommit = OFF
{{์ฟผ๋ฆฌ ์‹คํ–‰}}
DEBUG 88239 --- [nio-8080-exec-1] o.m.j.i.protocol.AbstractQueryProtocol   : System variable change :  autocommit = ON
DEBUG 88239 --- [nio-8080-exec-1] org.mariadb.jdbc.MariaDbConnection       : conn=53856(S) - set read-only to value false

 

๋กœ๊ทธ์ƒ์œผ๋กœ๋Š” ๋„๋Œ€์ฒด ์‹ค์ œ๋กœ ์–ด๋Š ํด๋Ÿฌ์Šคํ„ฐ ์ฃผ์†Œ์—์„œ ์ฟผ๋ฆฌ๊ฐ€ ์‹คํ–‰๋˜๋Š”์ง€ ์•Œ ์ˆ˜๊ฐ€ ์—†์—ˆ๋‹ค.

๊ทธ๋ž˜์„œ ์šด์˜ํ™˜๊ฒฝ์—์„œ ์‚ฌ์šฉํ•˜๊ณ ์žˆ๋Š” mariaDB ์ปค๋„ฅํ„ฐ์™€ ๋™์ผํ•œ ๋ฒ„์ „์˜ ์†Œ์Šค์ฝ”๋“œ์— ๋กœ๊ทธ๋ฅผ ์—ฌ๊ธฐ์ €๊ธฐ ์ถ”๊ฐ€ํ•˜๋ฉด์„œ ์ถ”์ ์„ ์‹œ์ž‘ํ–ˆ๊ณ ,

๋‹ค์Œ๊ณผ ๊ฐ™์€ ํ˜„์ƒ์„ ํ™•์ธํ–ˆ๋‹ค.

  1. writer ํด๋Ÿฌ์Šคํ„ฐ ์ฃผ์†Œ๋ฅผ ๊ธฐ์ค€์œผ๋กœ ์‹ ๊ทœ ์ปค๋„ฅ์…˜์„ ์ƒ์„ฑ๋œ๋‹ค. 
  2. readOnly ์ฟผ๋ฆฌ๊ฐ€ writer์ชฝ์—์„œ ์‹คํ–‰๋œ ๋’ค์— failover ํ”„๋กœ์„ธ์Šค๊ฐ€ ์‹คํ–‰๋˜๋ฉด์„œ mariaDB FailoverLoop์—์„œ reader์ชฝ ์ปค๋„ฅ์…˜์ด ์ƒ์„ฑ๋˜์–ด pool์— ๋“ค์–ด๊ฐ„๋‹ค.
  3. ์ดํ›„์˜ readOnly ์ฟผ๋ฆฌ๋“ค์€ ์ •์ƒ์ ์œผ๋กœ reader์ชฝ์—์„œ ์‹คํ–‰๋œ๋‹ค.

 

์ด์ œ ๊ถ๊ธˆํ•œ ์ ์ด ์ƒ๊ฒผ๋‹ค.

  1. ์ปค๋„ฅ์…˜์˜ ์ƒ์„ฑ์ฃผ๊ธฐ์— ๋”ฐ๋ผ ์‹ ๊ทœ ์ปค๋„ฅ์…˜์„ ์ƒ์„ฑํ•˜๊ฒŒ๋˜๋ฉด reader์ชฝ ์ปค๋„ฅ์…˜์€ ์‚ฌ๋ผ์ง€๋Š”์ง€?
  2. ์‹ ๊ทœ ์ปค๋„ฅ์…˜ ์ƒ์„ฑ์‹œ reader host๊ฐ€ ํฌํ•จ๋˜๋„๋ก ํ•  ์ˆ˜ ์žˆ์„์ง€..? (์ด๋ ‡๊ฒŒ ํ•  ์ˆ˜ ์žˆ์œผ๋ฉด 1๋ฒˆ๊ณผ ๊ฐ™์€ ํ˜„์ƒ์€ ์—†์–ด์งˆํ…Œ๋‹ˆ)
  3. ๊ทธ๋ ‡๊ฒŒํ•˜๋ฉด readOnly ์ฟผ๋ฆฌ๊ฐ€ ํ•ญ์ƒ reader๋กœ ๋“ค์–ด๊ฐ€๋Š”์ง€?

 

1๋ฒˆ ์ผ€์ด์Šค์˜ ๊ฒฝ์šฐ maxLifeTime ์„ค์ •๊ฐ’์„ 30์ดˆ๋กœ ์„ค์ •ํ•ด์„œ ํ…Œ์ŠคํŠธํ•ด๋ณด์•˜๊ณ , reader์ชฝ ์ปค๋„ฅ์…˜์€ ์‚ฌ๋ผ์ง€๊ฒŒ๋œ๋‹ค. readOnly ์ฟผ๋ฆฌ๊ฐ€ reader์—์„œ ์‹คํ–‰๋˜๋ ค๋ฉด ์œ„์— ๋งํ•œ failover ๊ณผ์ •์ด ๋‹ค์‹œ ํ•„์š”ํ•˜๋‹ค. ์ฆ‰, maxLifeTime์ด ์ง€๋‚˜ ์ปค๋„ฅ์…˜์„ ์ƒˆ๋กœ ์ƒ์„ฑํ•˜๊ฒŒ ๋  ๊ฒฝ์šฐ failover ํ”„๋กœ์„ธ์Šค๊ฐ€ ์‹คํ–‰๋˜์–ด์•ผ reader๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๊ฒŒ ๋œ๋‹ค. (์•„๋ž˜ 2๋ฒˆ ์ผ€์ด์Šค์— ๋Œ€ํ•œ ๋‚ด์šฉ๊นŒ์ง€ ํ™•์ธํ•˜๋ฉด ์•Œ๊ฒ ์ง€๋งŒ ์–ด๋””๊นŒ์ง€๋‚˜ url ์„ค์ •์— ๋ฉ”์ธ ํด๋Ÿฌ์Šคํ„ฐ ์ฃผ์†Œ๋งŒ ๋„ฃ์—ˆ์„ ๊ฒฝ์šฐ์ด๋‹ค.)

 

2๋ฒˆ ์ผ€์ด์Šค์˜ ๊ฒฝ์šฐ jdbc url ์„ค์ •์— reader ํด๋Ÿฌ์Šคํ„ฐ์˜ ์ฃผ์†Œ๋ฅผ ์ถ”๊ฐ€ํ•ด์ฃผ๋ฉด ๋œ๋‹ค. ๊ธฐ์กด ์„ค์ •๊ณผ ๋ณ€๊ฒฝํ•œ ์„ค์ •์€ ๋‹ค์Œ๊ณผ ๊ฐ™๋‹ค.

spring:
  datasource:
    url: jdbc:mariadb:aurora://my-cluster.cluster-xxx.ap-northeast-2.rds.amazonaws.com

 

spring:
  datasource:
    url: jdbc:mariadb:aurora://my-cluster.cluster-xxx.ap-northeast-2.rds.amazonaws.com,my-cluster.cluster-ro-xxx.ap-northeast-2.rds.amazonaws.com

์ด๋ ‡๊ฒŒ reader cluster์˜ ์ฃผ์†Œ๊ฐ€ ์ถ”๊ฐ€๋œ jdbc url์„ ์‚ฌ์šฉํ•  ๊ฒฝ์šฐ ํ•ญ์ƒ reader ํด๋Ÿฌ์Šคํ„ฐ์ชฝ์œผ๋กœ readOnly ์ฟผ๋ฆฌ๊ฐ€ ์‹คํ–‰๋˜๋Š” ๊ฒƒ์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ์—ˆ๋‹ค. ํ˜น์‹œ๋ผ๋„ url ๋’ค์ชฝ์— ํŒŒ๋ผ๋ฏธํ„ฐ๊ฐ’์„ ์‚ฌ์šฉํ•œ๋‹ค๋ฉด ํŒŒ๋ผ๋ฏธํ„ฐ๊ฐ’๋“ค์€ ์ œ์ผ ๋งˆ์ง€๋ง‰์— ํ•œ๋ฒˆ๋งŒ ๋ถ™์—ฌ์ฃผ๋ฉด ๋œ๋‹ค. ์˜ˆ๋ฅผ๋“ค์–ด ํŒŒ๋ผ๋ฏธํ„ฐ๊ฐ’์„ ์•„๋ž˜์ฒ˜๋Ÿผ ๋„ฃ๊ฒŒ๋˜๋ฉด mariaDB ์ปค๋„ฅํ„ฐ์—์„œ url ํŒŒ์‹ฑํ•  ๋•Œ ๋’ค ์ชฝ url์ด ์‚ฌ๋ผ์ง€๊ฒŒ ๋˜๋‹ˆ ์ฃผ์˜!!! ์ฆ‰, ๋ชจ๋“  ํด๋Ÿฌ์Šคํ„ฐ์— ๋™์ผํ•œ ํŒŒ๋ผ๋ฏธํ„ฐ๊ฐ’์„ ์‚ฌ์šฉํ•ด์•ผ ํ•œ๋‹ค๋Š” ๊ฒƒ !!

url: my-cluster.cluster-xxx.ap-northeast-2.rds.amazonaws.com?log=true,my-cluster.cluster-ro-xxx.ap-northeast-2.rds.amazonaws.com?log=true

 

๊ทธ๋ƒฅ ์–ผํ• ๋“ฃ๊ธฐ๋กœ๋Š” ๋ฉ”์ธํด๋Ÿฌ์Šคํ„ฐ ์ฃผ์†Œ๋งŒ ๋„ฃ์–ด์ฃผ์†Œ jdbc url์— aurora ์˜ต์…˜์„ ๋„ฃ์–ด์ค„ ๊ฒฝ์šฐ readOnly ํŠธ๋žœ์žญ์…˜์€ ์ž๋™์œผ๋กœ reader ์ชฝ์œผ๋กœ ๋“ค์–ด๊ฐ„๋‹ค๊ณ  ๋“ค์—ˆ์—ˆ์œผ๋‚˜ ๊ทธ๋ ‡์ง€ ์•Š๋‹ค๋Š” ๊ฒƒ์„ ํ™•์ธํ•œ ๋””๋ฒ„๊น…์ด์—ˆ๋‹ค.

 

๊ฒฐ๊ตญ ๊ฒฐ๋ก ์€ ์ด๋ ‡๋‹ค. ํด๋Ÿฌ์Šคํ„ฐ ์ฃผ์†Œ๊ฐ€ writer, reader๊ฐ€ ๋ณ„๋„๋กœ ์žˆ์„ ๊ฒฝ์šฐ (์•„๋งˆ AWS aurora๋ฅผ ํด๋Ÿฌ์Šคํ„ฐ๋งํ•ด์„œ ์‚ฌ์šฉํ•  ๊ฒฝ์šฐ ๋‹ค ์ด๋Ÿด ๊ฒƒ์ด๋‹ค) ๊ฐœ๋ฐœ์ž ์ž…์žฅ์—์„œ๋Š” writer, reader ๋‘ ํด๋Ÿฌ์Šคํ„ฐ ์ฃผ์†Œ๋ฅผ jdbc url์— ๋„ฃ์–ด์ฃผ์–ด์•ผ readOnly ํŠธ๋žœ์žญ์…˜์ด ํ•ญ์ƒ ์ •์ƒ์ ์œผ๋กœ reader ํด๋Ÿฌ์Šคํ„ฐ์—์„œ ์‹คํ–‰๋˜๊ฒŒ ๋œ๋‹ค.

 

์ฐธ๊ณ ๋กœ...mariaDB connector 3.x ๋ฒ„์ „์—์„œ๋Š” aurora ์˜ต์…˜์„ ์ง€์›ํ•˜์ง€ ์•Š๋Š”๋‹จ๋‹ค...

 

์š”์ฆ˜ ์ดํŽ™ํ‹ฐ๋ธŒ ์ž๋ฐ” Third ์—๋””์…˜ ์ฑ…์„ ๋™๋„ค ๋„์„œ๊ด€์—์„œ ๋Œ€์—ฌํ•ด์„œ ์ฝ๊ณ ์žˆ์Šต๋‹ˆ๋‹ค.

์ŠคํŠธ๋ฆผ ๋ณ‘๋ ฌํ™”์— ๋Œ€ํ•œ ๊ธ€์„ ์ฝ๋‹ค๊ฐ€ ์†Œ์ˆ˜๊ตฌํ•˜๋Š” ๋กœ์ง์„ ์ŠคํŠธ๋ฆผ์œผ๋กœ ์ž‘์„ฑํ•œ ๋ถ€๋ถ„์„ ๋ณด๊ฒŒ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.

์†Œ์ˆ˜๊ตฌํ•˜๊ธฐ ๋กœ์ง์€ ์ด์ง์ค€๋น„ ํ•  ๋•Œ๋‚˜ ๋‹ค์‹œ ๋“ค์—ฌ๋‹ค๋ณผ๋งŒํ•œ๊ฑฐ๋ผ ์˜ค๋ž˜์ „์— for-loop ๋กœ ๊ตฌํ˜„ํ•ด๋ณธ ๊ธฐ์–ต๋งŒ ์žˆ๋‹ค๋ณด๋‹ˆ ์‹ ์„ ํ•˜๊ฒŒ ๋Š๊ปด์กŒ๋„ค์š”.

์•„์ง๋„ ๋งŽ์€ ๋ธ”๋กœ๊ทธ๋“ค์—์„œ for-loop๋ฅผ ์ด์šฉํ•œ ๋ฐฉ๋ฒ•๋“ค๋งŒ ๋งŽ์ด ์†Œ๊ฐœํ•˜๊ณ  ์žˆ๊ธฐ๋„ ํ•˜๊ณ ํ•ด์„œ ํฌ์ŠคํŒ… ์ฃผ์ œ๋กœ ์‚ผ์•„๋ดค์Šต๋‹ˆ๋‹ค. 

๊ฐœ์ธ์ ์ธ ๊ธฐ๋ก๋„ ํ• ๊ฒธ...

์ฑ…์„ ์ฝ๊ณ  ์ƒˆ๋กœ ์•Œ๊ฒŒ๋œ ๋ถ€๋ถ„์€ ์ŠคํŠธ๋ฆผ์— ๋Œ€ํ•œ ๋ถ€๋ถ„๋„ ์žˆ์ง€๋งŒ ์ด๋ฏธ BigInteger ํด๋ž˜์Šค์— isProbablePrime ์ด๋ž€ ๋ฉ”์„œ๋“œ๊ฐ€ ์žˆ์—ˆ๋‹ค๋Š” ๊ฒ๋‹ˆ๋‹ค..

 

์•„๋ž˜๋Š” ํŠน์ • ์ˆซ์ž n ๊นŒ์ง€ ์†Œ์ˆ˜๊ฐ€ ๋ช‡ ๊ฐœ์ธ์ง€๋ฅผ ์ถœ๋ ฅํ•˜๊ณ , ๊ทธ ๋ชฉ๋ก๋„ ์ถœ๋ ฅํ•˜๋Š” ๊ฒƒ์„ ํ…Œ์ŠคํŠธํ•œ ์ฝ”๋“œ์ž…๋‹ˆ๋‹ค.

public static void main(String[] args) {
    int n = (int) Math.pow(10, 8);
    // n ๊นŒ์ง€์˜ ์ˆ˜ ์ค‘์— ์†Œ์ˆ˜์˜ ๊ฐœ์ˆ˜ ์ถœ๋ ฅํ•˜๊ธฐ
    long cnt = LongStream.rangeClosed(2, n)
    	    .parallel()
            .mapToObj(BigInteger::valueOf)
            .filter(i -> i.isProbablePrime(50))
            .count();
    System.out.println(cnt);

    // n ๊นŒ์ง€์˜ ์ˆ˜ ์ค‘์— ์†Œ์ˆ˜ ๋ชฉ๋ก ์ถœ๋ ฅํ•˜๊ธฐ
    System.out.println(LongStream.rangeClosed(2, n)
            .mapToObj(BigInteger::valueOf)
            .filter(i -> i.isProbablePrime(50))
            .collect(Collectors.toList()));
}

LongStream์„ ์ด์šฉํ•˜์—ฌ ์ˆซ์ž์˜ ๋ฒ”์œ„๋ฅผ ์ •ํ•˜๊ณ  mapToObj ๋ฅผ ์ด์šฉํ•˜์—ฌ BigInteger๋กœ ๋ณ€ํ˜•ํ•œ๋’ค BigInteger.isProbablePrime(int certainty) ๋ฉ”์„œ๋“œ๋ฅผ ํ•„ํ„ฐ๋กœ ์ „๋‹ฌํ•˜์—ฌ ์†Œ์ˆ˜๋ฅผ ๊ตฌํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค.

BigInteger.isProbablePrime(int certainty) ๋ฉ”์„œ๋“œ๋Š” ์†Œ์ˆ˜๋ฅผ ๊ตฌํ•˜๊ธฐ์œ„ํ•ด ๋‚ด๋ถ€์ ์œผ๋กœ ๋ฐ€๋Ÿฌ-๋ผ๋นˆ(Miller-Rabin) ํ…Œ์ŠคํŠธ์™€ ๋ฃจ์นด์Šค-๋ ˆ๋จธ(Lucas-Lehmer) ํ…Œ์ŠคํŠธ๋ฅผ ์‚ฌ์šฉํ•˜๊ณ  ์žˆ๊ณ ์š”. ์—ฌ๊ธฐ์„œ ์‚ฌ์šฉ๋œ ๋ฐ€๋Ÿฌ๋ผ๋นˆ ํ…Œ์ŠคํŠธ๋Š” DSA(Digital Signiture Algorithm) ์ŠคํŽ™ (NIST FIPS 186-2)์„ ๊ธฐ๋ฐ˜์œผ๋กœ ํ–ˆ๋‹ค๊ณ  ํ•ฉ๋‹ˆ๋‹ค. ์ด ์ŠคํŽ™์€ ํŒŒ์ผ๋กœ ์ฒจ๋ถ€ํ•˜๋‹ˆ ๊ด€์‹ฌ์žˆ์œผ์‹  ๋ถ„๋“ค์€ ๋‹ค์šด๋ฐ›์•„ ๋ณด์…”๋„ ๋  ๊ฒƒ ๊ฐ™๋„ค์š”. 

 

fips186-2.pdf
0.35MB

 

์ฐธ๊ณ ๋กœ .parallel() ์€ ์ŠคํŠธ๋ฆผ์—์„œ ์‚ฌ์šฉํ•  ๋•Œ ๋งค์šฐ ์ฃผ์˜๋ฅผ ์š”ํ•˜๋Š” ๊ธฐ๋Šฅ์ž…๋‹ˆ๋‹ค. ์ฑ…์—์„œ๋Š” ์ด๋ ‡๊ฒŒ ์–˜๊ธฐํ•ฉ๋‹ˆ๋‹ค. Stream.iterate ๋ฅผ ๋ฐ์ดํ„ฐ ์†Œ์Šค๋กœ ์ด์šฉํ•˜๊ฑฐ๋‚˜ ์ค‘๊ฐ„ ์—ฐ์‚ฐ์œผ๋กœ limit ์„ ์‚ฌ์šฉํ•˜๋Š” ์ŠคํŠธ๋ฆผ ํŒŒ์ดํ”„๋ผ์ธ์—์„œ๋Š” parallel์„ ์ด์šฉํ•œ ์„ฑ๋Šฅ๊ฐœ์„ ์„ ๊ธฐ๋Œ€ํ•  ์ˆ˜ ์—†์œผ๋ฉฐ ์˜คํžˆ๋ ค ์•ˆ์ข‹์•„์งˆ ์ˆ˜๋„ ์žˆ๋‹ค๊ณ  ๋ง์ด์ฃ . ์‹ค์ œ๋กœ ์šด์˜ํ™˜๊ฒฝ์—์„œ ์ €๊ฒƒ ๋•Œ๋ฌธ์— ์ด์Šˆ๊ฐ€ ๋ฐœ์ƒํ•œ ์ ๋„ ์žˆ์—ˆ์Šต๋‹ˆ๋‹ค. ๊ทธ๋ƒฅ ๋ฌดํ•œ๋ฃจํ”„์— ๋น ์ง„๊ฒƒ์ฒ˜๋Ÿผ ์“ฐ๋ ˆ๋“œ ํ•˜๋‚˜๊ฐ€ ๋จนํ†ต์ด ๋˜์–ด๋ฒ„๋ฆฌ๋”๊ตฐ์š”. ์ŠคํŠธ๋ฆผ์—์„œ ๋ณ‘๋ ฌ์—ฐ์‚ฐ์— ์ ํ•ฉํ•œ ์ ์€ reduce, min, max, count, sum ๋“ฑ์˜ ์—ฐ์‚ฐ์ด๋ฉฐ, collect์ฒ˜๋Ÿผ ๊ฐ€๋ณ€์ถ•์†Œ๋ฅผ ์ˆ˜ํ–‰ํ•˜๋Š” ๋ฉ”์„œ๋“œ๋Š” ๋ณ‘๋ ฌ์—ฐ์‚ฐ์— ์ ํ•ฉํ•˜์ง€ ์•Š๋‹ค๊ณ  ํ•ฉ๋‹ˆ๋‹ค. ์ฐธ๊ณ ํ•˜์‹œ๊ธฐ ๋ฐ”๋ž๋‹ˆ๋‹ค.

์ง€๋‚œ ํฌ์ŠคํŒ…์—์„œ ์š”์ฒญ๊ณผ ์‘๋‹ต์— ๋Œ€ํ•œ ๋กœ๊ทธ๋ฅผ ๋‚จ๊ธฐ๋Š” ๋ฒ•์— ๋Œ€ํ•ด์„œ ์•Œ์•„๋ณด์•˜์—ˆ๋Š”๋ฐ์š”

์ด์–ด์„œ ์ด๋ฒˆ์—๋Š” ์ถœ๋ ฅ๋˜๋Š” ๋กœ๊ทธ๋ฉ”์‹œ์ง€์˜ ๋‚ด์šฉ์„ ๋ฐ”๊ฟ”๋ณด๋ ค๊ณ  ํ•ฉ๋‹ˆ๋‹ค.

 

ํ˜น์‹œ๋ผ๋„ ๊ฒ€์ƒ‰์œผ๋กœ ์ด ํฌ์ŠคํŒ…์„ ๋จผ์ € ๋ณด๊ฒŒ๋˜์‹œ๋Š” ๋ถ„์€ ์•„๋ž˜ ํฌ์ŠคํŒ…์„ ๋จผ์ € ์ฝ์–ด๋ณด์‹œ๊ธธ ์ถ”์ฒœ๋“œ๋ฆฝ๋‹ˆ๋‹ค.

[5๋ถ„์ฝ”๋”ฉ] Filter๋ฅผ ์ด์šฉํ•œ ์š”์ฒญ,์‘๋‹ต ๋กœ๊น… (How to log request, response info inlcuding payload using filter) - (1/2)

 

๋กœ๊ทธ ๋ฉ”์‹œ์ง€ ๋‚ด๋ง˜๋Œ€๋กœ ๊พธ๋ฏธ๊ธฐ

์šฐ์„  ์ง€๋‚œ ์‹œ๊ฐ„์— ๋งˆ์ง€๋ง‰์œผ๋กœ ํ™•์ธํ•œ ๋กœ๊ทธ ์ถœ๋ ฅ ๋‚ด์šฉ์„ ๋‹ค์‹œ ํ™•์ธํ•ด๋ณด๋ฉด ์•„๋ž˜์™€ ๊ฐ™์Šต๋‹ˆ๋‹ค.

2022-09-16 07:32:49.190  INFO 31518 --- [nio-8080-exec-5] c.k.demo.filter.RequestLoggingFilter     : Before request [POST /test/request-body-log, client=0:0:0:0:0:0:0:1]
2022-09-16 07:32:49.226  INFO 31518 --- [nio-8080-exec-5] c.k.demo.filter.RequestLoggingFilter     : After request [POST /test/request-body-log, client=0:0:0:0:0:0:0:1, payload={
    "id":1000,
    "name":"์ผ€์ด์น˜",
    "age":20
}]

 

๊ทธ๋Ÿผ ์ € ๋ฉ”์‹œ์ง€๋ฅผ ์–ด๋–ป๊ฒŒ ๋ฐ”๊ฟ€ ์ˆ˜ ์žˆ์„์ง€ ํ•œ๋ฒˆ ์•Œ์•„๋ณผ๊ฒŒ์š”.

์š”์ฒญ/์‘๋‹ต ๋กœ๊น…์„ ์œ„ํ•ด์„œ ์šฐ๋ฆฌ๋Š” AbstractRequestLoggingFilter ๋ฅผ ์ƒ์†ํ•˜์—ฌ ํ•„ํ„ฐ๋ฅผ ํ•˜๋‚˜ ๋งŒ๋“ค์—ˆ์—ˆ์Šต๋‹ˆ๋‹ค.

๋กœ๊ทธ ๋ฉ”์‹œ์ง€๋ฅผ ๋ฐ”๊พธ๋ ค๋ฉด ์ € ํด๋ž˜์Šค์—์„œ ์–ด๋–ค ๋ฉ”์„œ๋“œ๋“ค์„ ์ œ๊ณตํ•˜๊ณ  ์žˆ๋Š”์ง€ ๋“ค์—ฌ๋‹ค๋ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. 

AbstractRequestLoggingFilter๋ฅผ ๊นŒ๋ณด๋ฉด ์•„๋ž˜ ๋ฉ”์„œ๋“œ๋ฅผ ์˜ค๋ฒ„๋ผ์ด๋“œํ•ด์„œ ์žฌ์ •์˜ํ•˜๊ณ  ์žˆ๋Š” ๊ฒƒ์„ ๋ณผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)

 

doFilterInternal ๋ฉ”์„œ๋“œ์—์„œ ํ•˜๋Š” ์ผ์€ ์š”์ฒญ์— ๋Œ€ํ•œ ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง์ด ์‹คํ–‰๋˜๊ธฐ ์ „ํ›„๋กœ ๋กœ๊ทธ๋ฅผ ๋‚จ๊ธฐ๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค.

์ฝ”๋“œ๋ฅผ ์ž ์‹œ ์‚ดํŽด๋ณผ๊ฒŒ์š”

@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
      throws ServletException, IOException {

   boolean isFirstRequest = !isAsyncDispatch(request);
   HttpServletRequest requestToUse = request;

   if (isIncludePayload() && isFirstRequest && !(request instanceof ContentCachingRequestWrapper)) {
      requestToUse = new ContentCachingRequestWrapper(request, getMaxPayloadLength());
   }

   boolean shouldLog = shouldLog(requestToUse);
   if (shouldLog && isFirstRequest) {
      beforeRequest(requestToUse, getBeforeMessage(requestToUse));
   }
   try {
      filterChain.doFilter(requestToUse, response);
   }
   finally {
      if (shouldLog && !isAsyncStarted(requestToUse)) {
         afterRequest(requestToUse, getAfterMessage(requestToUse));
      }
   }
}

 

beforeRequest(requestToUse, getBeforeMessage(requestToUse)) ์—์„œ Before request ... ๋กœ๊ทธ๋ฅผ ๋‚จ๊ธฐ๊ณ  ์žˆ๊ณ 

afterRequest(requestToUse, getAfterMessage(requestToUse)) ์—์„œ After request ... ๋กœ๊ทธ๋ฅผ ๋‚จ๊ธฐ๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค.

๋”ฐ๋ผ์„œ ์šฐ๋ฆฌ๊ฐ€ ์›ํ•˜๋Š”๋Œ€๋กœ ๋ฉ”์‹œ์ง€์˜ ๋‚ด์šฉ์„ ๋ฐ”๊พธ๋ ค๋ฉด ์ € ๋‘ ๊ตฐ๋ฐ๋ฅผ ์ˆ˜์ •ํ•ด์ฃผ๋ฉด ๋ฉ๋‹ˆ๋‹ค.

 

์šฐ์„  ๋กœ๊ทธ ๋ฉ”์‹œ์ง€์— ๋Œ€ํ•œ ์ˆ˜์ • ์š”๊ตฌ์‚ฌํ•ญ์ด ์•„๋ž˜์™€ ๊ฐ™๋‹ค๊ณ  ๊ฐ€์ •ํ•ด๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.

  1. ํ˜ธ์ถœ๋ฐ›์€ API ์ •๋ณด์™€ ํด๋ผ์ด์–ธํŠธIP ์ •๋ณด๋ฅผ ์ค‘๋ณต ์ถœ๋ ฅ ํ•˜์ง€ ์•Š๋Š”๋‹ค
  2. ํ˜ธ์ถœ๋ฐ›์€ API uri ์™€ ํด๋ผ์ด์–ธํŠธIP, payload ์ •๋ณด๋Š”  before request ์—์„œ ๋‚จ๊ธด๋‹ค.  ๐ŸŒŸ
  3. after request ์—๋Š” ์‘๋‹ต์†Œ์š”์‹œ๊ฐ„๊ณผ http status ์ฝ”๋“œ๊ฐ’์„ ๋‚จ๊ธด๋‹ค.
  4. ์ตœ์ข…์ ์œผ๋กœ ์ถœ๋ ฅ๋˜๋Š” ๋ชจ์Šต์€ ๋‹ค์Œ๊ณผ ๊ฐ™๋‹ค.
2022-09-16 08:28:08.053  INFO 41196 --- [nio-8080-exec-2] c.k.demo.filter.RequestLoggingFilter     : REQ: POST uri=/test/request-body-log;client=0:0:0:0:0:0:0:1;payload={
    "id":1000,
    "name":"์ผ€์ด์น˜",
    "age":20
}
2022-09-16 08:28:08.076  INFO 41196 --- [nio-8080-exec-2] c.k.demo.filter.RequestLoggingFilter     : RES: 22ms, 400

 

์ž, ์ด์ œ ์œ„ ์š”๊ตฌ์‚ฌํ•ญ์— ๋งž๊ฒŒ ์šฐ๋ฆฌ์˜ ํ•„ํ„ฐํด๋ž˜์Šค๋ฅผ ์ˆ˜์ •ํ•ด๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค. 

RequestLoggingFilter ํด๋ž˜์Šค์— ์•„๋ž˜ ์ฝ”๋“œ๋ฅผ ์ถ”๊ฐ€ํ•ด์ฃผ์„ธ์š”.

@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
        throws ServletException, IOException {

    boolean isFirstRequest = !isAsyncDispatch(request);
    HttpServletRequest requestToUse = request;

    if (isIncludePayload() && isFirstRequest && !(request instanceof ContentCachingRequestWrapper)) {
        requestToUse = new ContentCachingRequestWrapper(request);
    }

    long start = System.currentTimeMillis();

    if (isFirstRequest) {
        beforeRequest(requestToUse, getBeforeMessage(requestToUse));
    }

    try {
        filterChain.doFilter(requestToUse, response);
    } finally {
        if (!isAsyncStarted(requestToUse)) {
            afterRequest(requestToUse, getAfterMessage(System.currentTimeMillis() - start, response.getStatus()));
        }
    }
}

private String getBeforeMessage(HttpServletRequest request) {
    return createMessage(request, "REQ: ", "");
}

private String getAfterMessage(long elapsed, int status) {
    return "RES: " + elapsed + "ms, " + status;
}

 

AbstractRequestLoggingFilter ์— ๊ตฌํ˜„๋˜์–ด์žˆ๋Š” doFilterInternal ๋ฉ”์„œ๋“œ ๋‚ด์šฉ์„ ๊ทธ๋Œ€๋กœ ๊ฐ€์ง€๊ณ ์™€์„œ beforeRequest์™€ afterRequest ๋ฉ”์„œ๋“œ์˜ ๋‘ ๋ฒˆ์งธ ํŒŒ๋ผ๋ฏธํ„ฐ๋กœ ์ „๋‹ฌํ•˜๋Š” message ๋ฅผ ์กฐ๋ฆฝํ•˜๋Š” ๋ฉ”์„œ๋“œ๋ฅผ ์ง์ ‘ ๊ตฌํ˜„ํ•ฉ๋‹ˆ๋‹ค. getBeforeMessage์˜ createMessage๋Š” superํด๋ž˜์Šค, ์ฆ‰, AbstractRequestLoggingFilter ์—์„œ ์ œ๊ณตํ•˜๋Š” ๊ฒƒ์„ ๊ทธ๋Œ€๋กœ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค. createMessage ์—์„œ๋Š” prefix์™€ suffix ๊ทธ๋ฆฌ๊ณ  request ์ •๋ณด๋ฅผ ๊ฐ€์ง€๊ณ  ๋ฉ”์‹œ์ง€๋ฅผ ์•Œ์•„์„œ ๋งŒ๋“ค์–ด ์ค๋‹ˆ๋‹ค. payload๋„ ์—ญ์‹œ ์—ฌ๊ธฐ ํฌํ•จ๋˜์–ด์žˆ์ฃ .

์ž, ๊ทธ๋Ÿผ ์•ฑ์„ ์žฌ๊ธฐ๋™ํ•˜๊ณ  ๋กœ๊น…์ด ์–ด๋–ป๊ฒŒ ๋˜๋Š”์ง€ ๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.

2022-09-16 08:39:20.270  INFO 43287 --- [nio-8080-exec-1] c.k.demo.filter.RequestLoggingFilter     : REQ: POST /test/request-body-log, client=0:0:0:0:0:0:0:1
2022-09-16 08:39:20.306  INFO 43287 --- [nio-8080-exec-1] c.k.demo.filter.RequestLoggingFilter     : RES: 35ms, 200

 

์š”๊ตฌ์‚ฌํ•ญ ๋Œ€๋ถ€๋ถ„์ด ๋ฐ˜์˜๋œ ๊ฒƒ์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์–ด๋ผ? ๊ทผ๋ฐ payload ๊ฐ€ request ๋กœ๊ทธ์—์„œ ๋น ์ ธ์žˆ์Šต๋‹ˆ๋‹ค.

๋ถ„๋ช… after message ๋กœ๊ทธ๊ฐ€ ์ถœ๋ ฅ๋  ๋•Œ๋Š” ์ž˜ ์ถœ๋ ฅ์ด ๋์—ˆ๋Š”๋ฐ ๋ง์ด์ฃ . ๋˜‘๊ฐ™์€ createMessage ๋ฉ”์„œ๋“œ๋ฅผ ์ด์šฉํ–ˆ๋Š”๋ฐ before request ์—์„œ๋Š” payload๊ฐ€ ์ถœ๋ ฅ์ด ์•ˆ๋ฉ๋‹ˆ๋‹ค ใ…œใ…œ

ํ™•์ธํ•ด๋ณด๋‹ˆ AbstractRequestLoggingFilter.getMessagePayload ์—์„œ ์•„๋ž˜ wrapper ๊ฐ€ null ๋กœ ๋ฐ˜ํ™˜์ด ๋˜๊ณ ์žˆ์—ˆ์Šต๋‹ˆ๋‹ค.

ContentCachingRequestWrapper wrapper =
      WebUtils.getNativeRequest(request, ContentCachingRequestWrapper.class);

 

res๋กœ๊ทธ์—์„œ๋Š” ์ž˜ ๋‚˜์˜ค๋˜๊ฒŒ req๋กœ๊ทธ์—์„œ ์ถœ๋ ฅํ•˜๋ ค๋‹ˆ null์ด ๋ฐ˜ํ™˜์ด ๋ฉ๋‹ˆ๋‹ค.

์ด๊ฑธ ํ•ด๊ฒฐํ•˜๋ ค๋ฉด ์ถ”๊ฐ€์ ์ธ ์ž‘์—…์ด ํ•„์š”ํ•œ๋ฐ ์šฐ์„  ์•„๋ž˜ ๋‘ ๊ฐœ์˜ ํด๋ž˜์Šค๋ฅผ ์ถ”๊ฐ€ํ•ด์ค๋‹ˆ๋‹ค.

package com.keichee.demo.filter;

import org.springframework.stereotype.Component;
import org.springframework.web.filter.GenericFilterBean;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;

@Component
public class RequestBodyCacheFilter extends GenericFilterBean {

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain chain)
            throws IOException, ServletException {
        chain.doFilter(new RequestBodyCacheWrapper((HttpServletRequest) servletRequest), servletResponse);
    }
}

 

package com.keichee.demo.filter;

import lombok.extern.slf4j.Slf4j;

import javax.servlet.ReadListener;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import java.io.*;
import java.nio.charset.StandardCharsets;

@Slf4j
public class RequestBodyCacheWrapper extends HttpServletRequestWrapper {

    private final ByteArrayInputStream byteArrayInputStream;

    public RequestBodyCacheWrapper(HttpServletRequest request) throws IOException {
        super(request);
        InputStream is = super.getInputStream();
        byteArrayInputStream = new ByteArrayInputStream(is.readAllBytes());
    }

    @Override
    public ServletInputStream getInputStream() {
        byteArrayInputStream.reset();
        return new MyServletInputStream(byteArrayInputStream);
    }

    @Override
    public BufferedReader getReader() {
        byteArrayInputStream.reset();
        return new BufferedReader(new InputStreamReader(byteArrayInputStream, StandardCharsets.UTF_8));
    }


    private static class MyServletInputStream extends ServletInputStream {

        private final InputStream is;

        public MyServletInputStream(InputStream bis) {
            is = bis;
        }

        @Override
        public int read() throws IOException {
            return is.read();
        }

        @Override
        public int read(byte[] b) throws IOException {
            return is.read(b);
        }

        @Override
        public boolean isFinished() {
            return false;
        }

        @Override
        public boolean isReady() {
            return false;
        }

        @Override
        public void setReadListener(ReadListener readListener) {
        }
    }
}

 

์ด์ œ ๋‹ค์‹œ RequestLoggingFilter ํด๋ž˜์Šค๋กœ ๋Œ์•„์™€์„œ AbstractRequestLoggingFilter.createMessage ๋ฉ”์„œ๋“œ๋ฅผ ๊ทธ๋Œ€๋กœ ๋ณต์‚ฌํ•ด์„œ ๊ฐ€์ง€๊ณ  ์˜ต๋‹ˆ๋‹ค. ๊ทธ๋ฆฌ๊ณ  getMessagePayload ๋ถ€๋ถ„์„ ์•„๋ž˜์™€ ๊ฐ™์ด ์ˆ˜์ •ํ•ด์ฃผ์„ธ์š”. ๋‹ค๋ฅธ ๋ถ€๋ถ„์€ ๊ทธ๋Œ€๋กœ ๋†”๋‘ก๋‹ˆ๋‹ค.

String payload = null;
try {
    payload = new String(request.getInputStream().readAllBytes(), StandardCharsets.UTF_8);
} catch (IOException e) {
    log.error("failed to read payload", e);
}

 

 

์ˆ˜์ •์ด ์™„๋ฃŒ๋œ createMessage ๋ฉ”์„œ๋“œ๋Š” ์•„๋ž˜์™€ ๊ฐ™์Šต๋‹ˆ๋‹ค.

@Override
protected String createMessage(HttpServletRequest request, String prefix, String suffix) {
    StringBuilder msg = new StringBuilder();
    msg.append(prefix);
    msg.append(request.getMethod()).append(' ');
    msg.append(request.getRequestURI());

    if (isIncludeQueryString()) {
        String queryString = request.getQueryString();
        if (queryString != null) {
            msg.append('?').append(queryString);
        }
    }

    if (isIncludeClientInfo()) {
        String client = request.getRemoteAddr();
        if (StringUtils.hasLength(client)) {
            msg.append(", client=").append(client);
        }
        HttpSession session = request.getSession(false);
        if (session != null) {
            msg.append(", session=").append(session.getId());
        }
        String user = request.getRemoteUser();
        if (user != null) {
            msg.append(", user=").append(user);
        }
    }

    if (isIncludeHeaders()) {
        HttpHeaders headers = new ServletServerHttpRequest(request).getHeaders();
        if (getHeaderPredicate() != null) {
            Enumeration<String> names = request.getHeaderNames();
            while (names.hasMoreElements()) {
                String header = names.nextElement();
                if (!getHeaderPredicate().test(header)) {
                    headers.set(header, "masked");
                }
            }
        }
        msg.append(", headers=").append(headers);
    }

    if (isIncludePayload()) {
        String payload = null;
        try {
            payload = new String(request.getInputStream().readAllBytes(), StandardCharsets.UTF_8);
        } catch (IOException e) {
            log.error("failed to read payload", e);
        }
        if (payload != null) {
            msg.append(", payload=").append(payload);
        }
    }

    msg.append(suffix);
    return msg.toString();
}

 

๋งˆ์ง€๋ง‰์œผ๋กœ doFilterInternal ๋ฉ”์„œ๋“œ ๋กœ์ง ์ค‘ filterChain.doFilter์—์„œ ์‚ฌ์šฉํ•˜๋Š” request ๊ฐ์ฒด๋ฅผ ContentCachingRequestWrapper ๊ฐ€ ์•„๋‹Œ ๋ฉ”์„œ๋“œ ํŒŒ๋ผ๋ฏธํ„ฐ๋กœ ์ „๋‹ฌ๋ฐ›์€ request๋ฅผ ๊ทธ๋Œ€๋กœ ์‚ฌ์šฉํ•˜๋„๋ก ์ˆ˜์ •ํ•ฉ๋‹ˆ๋‹ค. (reuqestToUse -> request)

์ตœ์ข…์ ์œผ๋กœ RequestLoggingFilter ํด๋ž˜์Šค์˜ ์†Œ์Šค์ฝ”๋“œ๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค.

package com.keichee.demo.filter;

import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpHeaders;
import org.springframework.http.server.ServletServerHttpRequest;
import org.springframework.util.StringUtils;
import org.springframework.web.filter.AbstractRequestLoggingFilter;
import org.springframework.web.util.ContentCachingRequestWrapper;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.Enumeration;

@Slf4j
public class RequestLoggingFilter extends AbstractRequestLoggingFilter {

    @Override
    protected void beforeRequest(HttpServletRequest request, String message) {
        logger.info(message);
    }

    @Override
    protected void afterRequest(HttpServletRequest request, String message) {
        logger.info(message);
    }

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
            throws ServletException, IOException {

        boolean isFirstRequest = !isAsyncDispatch(request);
        HttpServletRequest requestToUse = request;

        if (isIncludePayload() && isFirstRequest && !(request instanceof ContentCachingRequestWrapper)) {
            requestToUse = new ContentCachingRequestWrapper(request);
        }

        long start = System.currentTimeMillis();

        if (isFirstRequest) {
            beforeRequest(requestToUse, getBeforeMessage(requestToUse));
        }

        try {
            filterChain.doFilter(request, response);
        } finally {
            if (!isAsyncStarted(requestToUse)) {
                afterRequest(requestToUse, getAfterMessage(System.currentTimeMillis() - start, response.getStatus()));
            }
        }
    }

    private String getBeforeMessage(HttpServletRequest request) {
        return createMessage(request, "REQ: ", "");
    }

    private String getAfterMessage(long elapsed, int status) {
        return "RES: " + elapsed + "ms, " + status;
    }

    @Override
    protected String createMessage(HttpServletRequest request, String prefix, String suffix) {
        StringBuilder msg = new StringBuilder();
        msg.append(prefix);
        msg.append(request.getMethod()).append(' ');
        msg.append(request.getRequestURI());

        if (isIncludeQueryString()) {
            String queryString = request.getQueryString();
            if (queryString != null) {
                msg.append('?').append(queryString);
            }
        }

        if (isIncludeClientInfo()) {
            String client = request.getRemoteAddr();
            if (StringUtils.hasLength(client)) {
                msg.append(", client=").append(client);
            }
            HttpSession session = request.getSession(false);
            if (session != null) {
                msg.append(", session=").append(session.getId());
            }
            String user = request.getRemoteUser();
            if (user != null) {
                msg.append(", user=").append(user);
            }
        }

        if (isIncludeHeaders()) {
            HttpHeaders headers = new ServletServerHttpRequest(request).getHeaders();
            if (getHeaderPredicate() != null) {
                Enumeration<String> names = request.getHeaderNames();
                while (names.hasMoreElements()) {
                    String header = names.nextElement();
                    if (!getHeaderPredicate().test(header)) {
                        headers.set(header, "masked");
                    }
                }
            }
            msg.append(", headers=").append(headers);
        }

        if (isIncludePayload()) {
            String payload = null;
            try {
                payload = new String(request.getInputStream().readAllBytes(), StandardCharsets.UTF_8);
            } catch (IOException e) {
                log.error("failed to read payload", e);
            }
            if (payload != null) {
                msg.append(", payload=").append(payload);
            }
        }

        msg.append(suffix);
        return msg.toString();
    }

}

 

์ž, ์ด์ œ ์•ฑ์„ ์žฌ๊ธฐ๋™ํ•˜๊ณ  ๋‹ค์‹œ API๋ฅผ ํ˜ธ์ถœํ•ด๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.

2022-09-16 10:04:36.918 DEBUG 57590 --- [           main] com.keichee.demo.DemoLoggingApplication  : Running with Spring Boot v2.7.3, Spring v5.3.22
2022-09-16 10:04:36.918  INFO 57590 --- [           main] com.keichee.demo.DemoLoggingApplication  : The following 1 profile is active: "local"
2022-09-16 10:04:37.301 DEBUG 57590 --- [           main] c.k.demo.filter.RequestBodyCacheFilter   : Filter 'requestBodyCacheFilter' configured for use
2022-09-16 10:04:37.301 DEBUG 57590 --- [           main] c.k.demo.filter.RequestLoggingFilter     : Filter 'loggingFilter' configured for use
2022-09-16 10:04:37.416  INFO 57590 --- [           main] com.keichee.demo.DemoLoggingApplication  : Started DemoLoggingApplication in 0.642 seconds (JVM running for 0.873)
2022-09-16 10:04:39.588  INFO 57590 --- [nio-8080-exec-2] c.k.demo.filter.RequestLoggingFilter     : REQ: POST /test/request-body-log, client=0:0:0:0:0:0:0:1, payload={
    "id":1000,
    "name":"์ผ€์ด์น˜",
    "age":20
}
2022-09-16 10:04:39.627  INFO 57590 --- [nio-8080-exec-2] c.k.demo.filter.RequestLoggingFilter     : RES: 40ms, 200

 

request payload๊ฐ€ ์ •์ƒ์ ์œผ๋กœ ์ถœ๋ ฅ๋˜๋Š” ๊ฒƒ์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์‘๋‹ต status๋„ 200์ด ๋‚˜์˜จ๊ฒƒ์„ ํ™•์ธํ–ˆ์Šต๋‹ˆ๋‹ค.

 

์ž, ์ž˜ ๋”ฐ๋ผ ์˜ค์…จ๋‚˜์š”? 

์ด ๋ถ€๋ถ„์€ trickyํ•œ ๋ถ€๋ถ„์ด๋ผ ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง์ด ์‹คํ–‰๋˜๊ธฐ ์ „์— request body๋ฅผ ์ถœ๋ ฅํ•˜๋Š” ๋ถ€๋ถ„์—์„œ ์• ๋ฅผ ๋จน๋Š” ๋ถ„๋“ค์ด ๋งŽ์Šต๋‹ˆ๋‹ค. stackoverflow์—์„œ ํ•ด๊ฒฐ์ฑ…์„ ์ฐพ๊ธฐ๋„ ํž˜๋“ค๋”๊ตฐ์š”. ์˜๋„ํ•œ๊ฑด์ง€๋Š” ๋ชจ๋ฅด๊ฒ ์œผ๋‚˜ springboot์— ๋‚ด์žฅ๋œ tomcat์— request body๋ฅผ ํ•œ๋ฒˆ๋งŒ ์ฝ์–ด์„œ ์“ธ ์ˆ˜ ์žˆ๋„๋ก ์ œํ•œ(?)๋˜์–ด์žˆ๊ธฐ ๋•Œ๋ฌธ์— ์ž˜๋ชป ์„ค์ •ํ•˜๋ฉด ๋กœ๊ทธ์—๋Š” payload๊ฐ€ ์ž˜ ์ถœ๋ ฅ๋˜์ง€๋งŒ ๋น„์ฆˆ๋‹ˆ์Šค๋กœ์ง ์‹คํ–‰์‹œ request body๋ฅผ ๋ชป ์ฝ์–ด์™€์„œ 400 bad request๊ฐ€ ๋ฐœ์ƒํ•˜๊ธฐ๋„ ํ•ฉ๋‹ˆ๋‹ค.

 

์ด์ƒ์œผ๋กœ request, response ๋กœ๊ทธ๋ฅผ ๋‚ด๋ง˜๋Œ€๋กœ ๋ฐ”๊ฟ”๋ณด๋Š” ๋ฐฉ๋ฒ•์— ๋Œ€ํ•ด์„œ ์•Œ์•„๋ณด์•˜์Šต๋‹ˆ๋‹ค.

 

์ฝ์–ด์ฃผ์…”์„œ ๊ฐ์‚ฌํ•ฉ๋‹ˆ๋‹ค~ ๐Ÿ™‡

์ข‹์•„์š” ๊พน ๋ˆŒ๋Ÿฌ์ฃผ๊ณ  ๊ฐ€์„ธ์š”~ 

๊ฐœ๋ฐœ์ž๋กœ์„œ ์šด์˜์ด์Šˆ๋ฅผ ํ•ด๊ฒฐํ•˜๊ธฐ ์œ„ํ•ด์„œ๋Š” ๋กœ๊ทธ๋ฉ”์‹œ์ง€๊ฐ€ ๋””๋ฒ„๊น…์„ ์œ„ํ•ด์„œ ๊ผญ ํ•„์š”ํ•˜๋‹ค๋Š”๊ฑด ๋ˆ„๊ตฌ๋‚˜ ์•Œ๊ณ ๊ณ„์‹ค๊ฒ๋‹ˆ๋‹ค.

ํ•˜์ง€๋งŒ ๋กœ๊น…์„ ์ž˜ ํ•˜๊ธฐ ์œ„ํ•ด์„œ๋Š” ๋งŽ์€ ๊ณ ๋ฏผ์ด ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค.

๋กœ๊ทธ๋ ˆ๋ฒจ์€ ์–ด๋–ป๊ฒŒ ํ• ์ง€ ๋กœ๊ทธ์˜ ๋‚ด์šฉ์€ ์–ด๋–ค ์ •๋ณด๋“ค๋กœ ์ฑ„์›Œ๋„ฃ์„์ง€ ๋“ฑ๋“ฑ ๋ง์ด์ฃ .

๋กœ๊ทธ๋Š” ๋งŽ์ด ๋‚จ๊ธด๋‹ค๊ณ  ์ข‹์€๊ฒŒ ์•„๋‹ˆ์ฃ .

์–ด๋–ค ๋ถ„๋“ค์€ ์•„๋ฌด๋Ÿฐ ์˜๋ฏธ๋„ ์—†๋Š” ์ •๋ณด๋ฅผ ๋กœ๊น…ํ•˜๊ธฐ๋„ ํ•ฉ๋‹ˆ๋‹ค.

๋น„์šฉ์ ์ธ ์ธก๋ฉด์ด ๋ฌธ์ œ๋˜์ง€ ์•Š๋Š”๋‹ค๋ฉด ๋Œ€๋Ÿ‰์˜ ๋กœ๊ทธ๋ฅผ ๋งˆ์Œ๊ป ๋‚จ๊ฒจ๋„ ๋˜๊ฒ ์ง€๋งŒ ํ˜„์‹ค์€ ๊ทธ๋ ‡์ง€ ์•Š์ฃ .

๋”ฐ๋ผ์„œ ์ค‘์š”๋„์— ๋”ฐ๋ผ์„œ ์ ๋‹น~~ํžˆ ์ตœ์†Œํ•œ์˜ ๋‚ด์šฉ๋งŒ ๋กœ๊น…ํ•˜๋Š”๊ฒŒ ์ข‹์Šต๋‹ˆ๋‹ค.

๋กœ๊ทธ ์ค‘์—์„œ ๊ฐ€์žฅ ์ค‘์š”ํ•œ ๋กœ๊ทธ๋Š” ์š”์ฒญ๊ณผ ์‘๋‹ต์— ๋Œ€ํ•œ ๊ฒƒ์ž…๋‹ˆ๋‹ค.

API ์š”์ฒญ์‹œ ์–ด๋–ค ์ •๋ณด๋ฅผ ์ „๋‹ฌ๋ฐ›์•˜๋Š”์ง€์— ๋Œ€ํ•œ request log๊ณผ

๊ทธ ๋ฐ์ดํ„ฐ๋ฅผ ๊ธฐ์ค€์œผ๋กœ ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง์„ ํƒœ์šด ๋’ค์— ์–ด๋–ค ์‘๋‹ต์„ ๋‚ด์ฃผ์—ˆ๋Š”์ง€

response log ๋งŒ ์žˆ์–ด๋„ ๋””๋ฒ„๊น…์€ ์ถฉ๋ถ„ํžˆ ๊ฐ€๋Šฅํ•˜์ฃ .

๋ฌผ๋ก  ์š”์ฒญ๊ณผ ์‘๋‹ต ์ค‘๊ฐ„์— ๋กœ์ง์ด ๋ณต์žกํ•˜๋‹ค๋ฉด ์ค‘๊ฐ„์ค‘๊ฐ„ ์ถ”๊ฐ€์ ์ธ ๋กœ๊ทธ๋ฅผ ์‹ฌ์–ด์ฃผ๋Š”๊ฒŒ ๋งŽ์€ ๋„์›€์ด ๋  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

 

๊ทธ๋ž˜์„œ ์ด๋ฒˆ ํฌ์ŠคํŒ…์—์„œ๋Š” ์š”์ฒญ๊ณผ ์‘๋‹ต์„ ์–ด๋–ป๊ฒŒ ๋กœ๊น…ํ•˜๋Š”์ง€์— ๋Œ€ํ•ด ๊ณต์œ ๋“œ๋ฆฌ๊ณ ์ž ํ•ฉ๋‹ˆ๋‹ค.

 

1. ํ”„๋กœ์ ํŠธ ์ƒ์„ฑ

 

๊ธฐ๋ณธ์ ์ธ ๊ฐœ๋ฐœํ™˜๊ฒฝ์€ ์•„๋ž˜์™€ ๊ฐ™์Šต๋‹ˆ๋‹ค.

  • IntelliJ IDEA 2021.3.3 (Ultimate Edition)
  • SpringBoot 2.7.3
  • Java 18 (OpenJDK)

์ž, ์šฐ์„  ๊นกํ†ต ํ”„๋กœ์ ํŠธ๋ฅผ ๋งŒ๋“ค๊ฒ ์Šต๋‹ˆ๋‹ค.

์ž, ๊นกํ†ต ํ”„๋กœ์ ํŠธ๊ฐ€ ์™„์„ฑ๋˜์—ˆ์Šต๋‹ˆ๋‹ค. 

 

2. ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ์ถ”๊ฐ€

์ด์ œ ๊ฐœ๋ฐœํŽธ์˜๋ฅผ ์œ„ํ•ด lombok๊ณผ ๋กœ๊น…์„ ์œ„ํ•œ logback ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ์ถ”๊ฐ€ํ•˜๊ณ , API ๊ฐœ๋ฐœ์„ ์œ„ํ•ด spring-boot-starter-web ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ์ถ”๊ฐ€ํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค.

implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.projectlombok:lombok:1.18.24'
annotationProcessor 'org.projectlombok:lombok:1.18.24'
implementation 'ch.qos.logback:logback-classic:1.2.11'

 

3. API ์ž‘์„ฑ

๊ทธ๋ฆฌ๊ณ  ์•„๋ž˜์™€ ๊ฐ™์ด ํ…Œ์ŠคํŠธ์šฉ ์ปจํŠธ๋กค๋Ÿฌ๋ฅผ ํ•˜๋‚˜ ์ž‘์„ฑํ•ด์ค๋‹ˆ๋‹ค.

package com.keichee.demo.controller;

import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.*;

@Slf4j
@RestController
@RequestMapping("/test")
public class TestController {

    @GetMapping("/log")
    public int requestResponseLog(@RequestParam int code) {
        log.info("requestResponseLog method called with code {}", code);
        return code;
    }
}

 

์—ฌ๊ธฐ๊นŒ์ง€ํ•˜๊ณ  ์•ฑ์„ ๊ธฐ๋™ํ•œ ๋’ค API๋ฅผ ํ˜ธ์ถœํ•˜์—ฌ ๋กœ๊ทธ๊ฐ€ ์ž˜ ๋‚จ๋Š”์ง€ ํ™•์ธํ•ด๋ณผ๊ฒŒ์š”.

2022-09-15 17:04:50.580  INFO 92637 --- [           main] com.keichee.demo.DemoLoggingApplication  : No active profile set, falling back to 1 default profile: "default"
2022-09-15 17:04:51.034  INFO 92637 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat initialized with port(s): 8080 (http)
2022-09-15 17:04:51.038  INFO 92637 --- [           main] o.apache.catalina.core.StandardService   : Starting service [Tomcat]
2022-09-15 17:04:51.038  INFO 92637 --- [           main] org.apache.catalina.core.StandardEngine  : Starting Servlet engine: [Apache Tomcat/9.0.65]
2022-09-15 17:04:51.105  INFO 92637 --- [           main] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring embedded WebApplicationContext
2022-09-15 17:04:51.107  INFO 92637 --- [           main] w.s.c.ServletWebServerApplicationContext : Root WebApplicationContext: initialization completed in 495 ms
2022-09-15 17:04:51.306  INFO 92637 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat started on port(s): 8080 (http) with context path ''
2022-09-15 17:04:51.312  INFO 92637 --- [           main] com.keichee.demo.DemoLoggingApplication  : Started DemoLoggingApplication in 0.879 seconds (JVM running for 1.294)
2022-09-15 17:05:56.173  INFO 92637 --- [nio-8080-exec-1] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring DispatcherServlet 'dispatcherServlet'
2022-09-15 17:05:56.173  INFO 92637 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet        : Initializing Servlet 'dispatcherServlet'
2022-09-15 17:05:56.174  INFO 92637 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet        : Completed initialization in 1 ms
2022-09-15 17:05:56.186  INFO 92637 --- [nio-8080-exec-1] c.k.demo.controller.TestController       : requestResponseLog method called with code 2

 

์ œ์ผ ๋งˆ์ง€๋ง‰์ค„์— ๋ณด๋ฉด TestController์—์„œ ์ถœ๋ ฅํ•œ ๋กœ๊ทธ๊ฐ€ ๋ณด์ด๋„ค์š”.

๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง์˜ ๋กœ๊ทธ๋Š” ์ž˜ ์ถœ๋ ฅ๋˜๊ณ  ์žˆ์œผ๋‹ˆ ์ด์ œ ๊ธฐ๋ณธ์ ์ธ ์ค€๋น„๊ฐ€ ๋๋‚ฌ์Šต๋‹ˆ๋‹ค.

 

4. Filter ๊ตฌํ˜„

์š”์ฒญ๊ณผ ์‘๋‹ต์— ๋Œ€ํ•œ ๋กœ๊ทธ์ถœ๋ ฅ์„ ์œ„ํ•ด์„œ ์šฐ๋ฆฌ๋Š” Filter๋ฅผ ์ด์šฉํ•  ๊ฒ๋‹ˆ๋‹ค. 

spring-web์—์„œ๋Š” ์•„๋ž˜์™€ ๊ฐ™์ด ์—ฌ๋Ÿฌ ์ข…๋ฅ˜์˜ filter๋ฅผ ์ œ๊ณตํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค.

AbstractRequestLoggingFilter
CharacterEncodingFilter
CommonsRequestLoggingFilter
CompositeFilter
CorsFilter
DelegatingFilterProxy
FormContentFilter
ForwardedHeaderFilter
GenericFilterBean
HiddenHttpMethodFilter
HttpPutFormContentFilter
OncePerRequestFilter
RelativeRedirectFilter
RelativeRedirectResponseWrapper
RequestContextFilter
ServletContextRequestLoggingFilter
ServletRequestPathFilter
ShallowEtagHeaderFilter

 

์—ฌ๊ธฐ์„œ request logging๊ณผ ๊ด€๋ จ๋œ filter๋Š” AbstractRequestLoggingFilter, CommonsRequestLoggingFilter, ๊ทธ๋ฆฌ๊ณ  ServletContextRequestLoggingFilter ๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค.

์šฐ๋ฆฌ๋Š” AbstractRequestLoggingFilter๋ฅผ ์ƒ์†ํ•ด์„œ ์šฐ๋ฆฌ๋งŒ์˜ ํ•„ํ„ฐ๋ฅผ ๋งŒ๋“ค์–ด ๋ณผ๊ฑฐ์—์š”.

com.keichee.demo ํŒจํ‚ค์ง€ ์•„๋ž˜์— filter ํŒจํ‚ค์ง€๋ฅผ ๋งŒ๋“ค๊ณ  ์ด ์•ˆ์— RequestLoggingFilter ๋ผ๋Š” ํด๋ž˜์Šค๋ฅผ ์•„๋ž˜์™€ ๊ฐ™์ด ๋งŒ๋“ค์–ด์ฃผ์„ธ์š”

package com.keichee.demo.filter;

import lombok.extern.slf4j.Slf4j;
import org.springframework.web.filter.AbstractRequestLoggingFilter;
import org.springframework.web.util.ContentCachingRequestWrapper;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

@Slf4j
public class RequestLoggingFilter extends AbstractRequestLoggingFilter {

    @Override
    protected void beforeRequest(HttpServletRequest request, String message) {
        logger.info(message);
    }

    @Override
    protected void afterRequest(HttpServletRequest request, String message) {
        logger.info(message);
    }
}

 

์ด์ œ ์ด ํ•„ํ„ฐ๋ฅผ ๋นˆ์œผ๋กœ ๋“ฑ๋กํ•ด์ค˜์•ผ ํ•ฉ๋‹ˆ๋‹ค.

com.keichee.demo ํŒจํ‚ค์ง€ ์•„๋ž˜์— config ํŒจํ‚ค์ง€๋ฅผ ๋งŒ๋“ค๊ณ  ์ด ์•ˆ์— RequestLoggingConfig ํด๋ž˜์Šค๋ฅผ ๋งŒ๋“ค๊ฒ ์Šต๋‹ˆ๋‹ค.

package com.keichee.demo.config;

import com.keichee.demo.filter.RequestLoggingFilter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class RequestLoggingConfig {

    public static int MAX_PAYLOAD_LENGTH = 1000;

    @Bean
    public RequestLoggingFilter loggingFilter() {
        RequestLoggingFilter filter = new RequestLoggingFilter();
        filter.setIncludeClientInfo(true);
        filter.setIncludeHeaders(false);
        filter.setIncludePayload(true);
        filter.setIncludeQueryString(true);
        filter.setMaxPayloadLength(MAX_PAYLOAD_LENGTH);
        return filter;
    }
}

 

ํ—ค๋”์ •๋ณด ๋กœ๊น…์ด ํ•„์š”ํ•  ๊ฒฝ์šฐ setIncludeHeaders(true)๋กœ ์„ค์ •ํ•ด์ฃผ์‹œ๋ฉด ๋ฉ๋‹ˆ๋‹ค.

payload์˜ max length๋Š” ๋กœ๊น…๋Ÿ‰๊ณผ ๋กœ๊ทธ๋ฅผ ์ €์žฅํ•˜๋Š” ์‹œ์Šคํ…œ์˜ ์šฉ๋Ÿ‰ ๋“ฑ์— ๋”ฐ๋ผ ์ ๋‹นํžˆ ์„ค์ •ํ•ด์ฃผ์‹œ๋ฉด๋ฉ๋‹ˆ๋‹ค. 

 

์ด์ œ ๋งˆ์ง€๋ง‰ ํ•œ์Šคํ…์ด ๋‚จ์•˜์Šต๋‹ˆ๋‹ค.

logback-spring.xml ํŒŒ์ผ์„ resources ๋””๋ ‰ํ† ๋ฆฌ์— ์ถ”๊ฐ€ํ•ด์ค๋‹ˆ๋‹ค.

<?xml version="1.0" encoding="UTF-8"?>

<configuration>

    <include resource="org/springframework/boot/logging/logback/defaults.xml"/>
    <include resource="org/springframework/boot/logging/logback/console-appender.xml"/>

    <springProfile name="local">
        <logger name="com.keichee.demo" level="debug" additivity="false">
            <appender-ref ref="CONSOLE"/>
        </logger>

        <root level="error">
            <appender-ref ref="CONSOLE"/>
        </root>
    </springProfile>

</configuration>

 

์ง€๊ธˆ๊นŒ์ง€ ์ž‘์„ฑํ•œ ํŒŒ์ผ๋“ค์„ ํฌํ•จํ•˜์—ฌ ์ตœ์ข…์ ์œผ๋กœ ํŒจํ‚ค์ง€ ํŠธ๋ฆฌ๋ฅผ ๋ณด๋ฉด ์•„๋ž˜์™€ ๊ฐ™์Šต๋‹ˆ๋‹ค.

์ด์ œ active profile ๊ฐ’์— local ์„ ์ฃผ๊ณ  ์•ฑ์„ ์žฌ๊ธฐ๋™ํ•œ๋’ค ํ•„ํ„ฐ๊ฐ€ ์ •์ƒ์ ์œผ๋กœ ์ ์šฉ๋˜์—ˆ๋Š”์ง€ ํ™•์ธํ•ด๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.

2022-09-16 07:28:54.399 DEBUG 31518 --- [           main] com.keichee.demo.DemoLoggingApplication  : Running with Spring Boot v2.7.3, Spring v5.3.22
2022-09-16 07:28:54.399  INFO 31518 --- [           main] com.keichee.demo.DemoLoggingApplication  : The following 1 profile is active: "local"
2022-09-16 07:28:54.751 DEBUG 31518 --- [           main] c.k.demo.filter.RequestLoggingFilter     : Filter 'loggingFilter' configured for use
2022-09-16 07:28:54.866  INFO 31518 --- [           main] com.keichee.demo.DemoLoggingApplication  : Started DemoLoggingApplication in 0.607 seconds (JVM running for 0.819)

์ถœ๋ ฅ๋˜๋Š” ๋กœ๊ทธ ์ค‘์— Filter 'loggingFilter' configured for use ๊ฐ€ ์ถœ๋ ฅ๋œ๊ฑธ๋กœ ๋ณด์•„ ์ •์ƒ์ ์œผ๋กœ ์ ์šฉ์ด ๋œ ๊ฒƒ์œผ๋กœ ํ™•์ธ๋ฉ๋‹ˆ๋‹ค.

 

์ด์ œ ๊ธฐ์กด์— ๋งŒ๋“ค์–ด ๋‘์—ˆ๋˜ API๋ฅผ ๋‹ค์‹œ ํ˜ธ์ถœํ•ด๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.

2022-09-16 07:29:18.541  INFO 31518 --- [nio-8080-exec-2] c.k.demo.filter.RequestLoggingFilter     : Before request [GET /test/log?code=2, client=0:0:0:0:0:0:0:1]
2022-09-16 07:29:18.550  INFO 31518 --- [nio-8080-exec-2] c.k.demo.controller.TestController       : requestResponseLog method called with code 2
2022-09-16 07:29:18.560  INFO 31518 --- [nio-8080-exec-2] c.k.demo.filter.RequestLoggingFilter     : After request [GET /test/log?code=2, client=0:0:0:0:0:0:0:1]

์š”์ฒญ์— ๋Œ€ํ•œ ์ •๋ณด uri์™€ query parameter ๊ทธ๋ฆฌ๊ณ  client์— X-Forwarded-For ๊ฐ’์ด ์ถœ๋ ฅ๋œ ๊ฒƒ์„ ๋ณผ ์ˆ˜ ์žˆ๊ณ , ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง ์‹คํ–‰์ด ๋๋‚œ ๋’ค ์†Œ์š”์‹œ๊ฐ„์ด ์–ผ๋งˆ์ธ์ง€ http status ๊ฐ’์ด ๋ญ”์ง€ ์ถœ๋ ฅ๋˜๋Š” ๊ฒƒ์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

 

์ง€๊ธˆ๊นŒ์ง€ ์š”์ฒญ์— ๋Œ€ํ•œ uri, query parameter์™€ API๋ฅผ ์š”์ฒญํ•œ ํด๋ผ์ด์–ธํŠธ์˜ IP ๋ฅผ ์ถœ๋ ฅํ•˜๊ณ ,

์‘๋‹ต์„ ์ฃผ๊ธฐ๊นŒ์ง€์˜ ์†Œ์š”์‹œ๊ฐ„๊ณผ ์‘๋‹ต ์ƒํƒœ๊ฐ’์„ ๋กœ๊น…ํ•˜๋„๋ก ์„ค์ •์„ ํ•ด๋ณด์•˜์Šต๋‹ˆ๋‹ค. ์—ฌ๊ธฐ๊นŒ์ง€ ์ž˜ ๋”ฐ๋ผ ์˜ค์…จ๋‚˜์š”? ๐Ÿ˜‹

 

์ด์ œ request body ๋ฅผ ์ž…๋ ฅ๋ฐ›์•„ payload๊ฐ€ ์ž˜ ์ถœ๋ ฅ๋˜๋Š”์ง€ ๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.

TestController ์— request body๋ฅผ ๋ฐ›๋Š” API๋ฅผ ํ•˜๋‚˜ ์ถ”๊ฐ€ํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค.

@PostMapping("/request-body-log")
public long requestBodyLog(@RequestBody Employee employee) {
    return employee.getId();
}

Employee ์ •๋ณด๋ฅผ ๋ฐ›์•„์„œ id๋ฅผ ๊ทธ๋Œ€๋กœ ๋ฆฌํ„ดํ•ด์ฃผ๋Š” ๊ฐ„๋‹จํ•œ API์ž…๋‹ˆ๋‹ค. (์–ด์ฐจํ”ผ request body ๋กœ๊น…์„ ์œ„ํ•œ๊ฑฐ๋‹ˆ ์‹ฌํ”Œํ•˜๊ฒŒ 

 

๊ทธ๋ฆฌ๊ณ  domain ํŒจํ‚ค์ง€ ํ•˜์œ„์— Employee ํด๋ž˜์Šค๋ฅผ ์•„๋ž˜์™€ ๊ฐ™์ด ์ƒ์„ฑํ•ด์ฃผ๊ฒ ์Šต๋‹ˆ๋‹ค.

@Getter
@Setter
@FieldDefaults(level = AccessLevel.PRIVATE)
public class Employee {
    long id;
    String name;
    int age;
}

 

์ด์ œ  ์•ฑ์„ ๊ธฐ๋™ํ•ด์„œ postman์œผ๋กœ ํ…Œ์ŠคํŠธ๋ฅผ ํ•ด๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.

 

๋กœ๊ทธ๋Š” ์•„๋ž˜์™€ ๊ฐ™์ด ์ถœ๋ ฅ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.

2022-09-16 07:32:49.190  INFO 31518 --- [nio-8080-exec-5] c.k.demo.filter.RequestLoggingFilter     : Before request [POST /test/request-body-log, client=0:0:0:0:0:0:0:1]
2022-09-16 07:32:49.226  INFO 31518 --- [nio-8080-exec-5] c.k.demo.filter.RequestLoggingFilter     : After request [POST /test/request-body-log, client=0:0:0:0:0:0:0:1, payload={
    "id":1000,
    "name":"์ผ€์ด์น˜",
    "age":20
}]

 

๊ทธ๋Ÿฐ๋ฐ ๋กœ๊ทธ๋ฅผ ๋ณด๋‹ˆ uri ์ •๋ณด์™€ client ์ •๋ณด๊ฐ€ ์ค‘๋ณต๋˜์„œ ์ถœ๋ ฅ์ด ๋˜๊ณ  ์žˆ๊ณ , request body๋กœ ๋„˜๊ฒจ์ค€ ๋ฐ์ดํ„ฐ๊ฐ€ ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง์ด ๋ชจ๋‘ ์‹คํ–‰๋˜๊ณ  ๋‚œ ๋’ค์— ์ถœ๋ ฅ๋˜๋Š” After request ... ๋ถ€๋ถ„์— ์ถœ๋ ฅ์ด ๋˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. ๋ญ”๊ฐ€ ์ถœ๋ ฅ๋˜๋Š” ๋‚ด์šฉ์ด ๋งˆ์Œ์— ๋“ค์ง€ ์•Š๋„ค์š”. 

 

๋‹ค์Œ ํฌ์ŠคํŒ…์—์„œ๋Š” ๋กœ๊ทธ๋ฉ”์‹œ์ง€๋ฅผ ๋ฐ”๊ฟ”๋ณด๋„๋ก ํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค.

 

์ฝ์–ด์ฃผ์…”์„œ ๊ฐ์‚ฌํ•ฉ๋‹ˆ๋‹ค~ ๐Ÿ™‡

 

๋„์›€์ด ๋˜์…จ๋‹ค๋ฉด ์ข‹์•„์š” ๊พน ๋ˆŒ๋Ÿฌ์ฃผ๊ณ  ๊ฐ€์„ธ์š”~

 

[ ๋‹ค์Œ ํฌ์ŠคํŒ… ๋ฐ”๋กœ ์ฝ๊ธฐ ]

 

[5๋ถ„์ฝ”๋”ฉ] Filter๋ฅผ ์ด์šฉํ•œ ์š”์ฒญ,์‘๋‹ต ๋กœ๊น… (How to log request, response info inlcuding payload using filter) - (2/2)

์ง€๋‚œ ํฌ์ŠคํŒ…์—์„œ ์š”์ฒญ๊ณผ ์‘๋‹ต์— ๋Œ€ํ•œ ๋กœ๊ทธ๋ฅผ ๋‚จ๊ธฐ๋Š” ๋ฒ•์— ๋Œ€ํ•ด์„œ ์•Œ์•„๋ณด์•˜์—ˆ๋Š”๋ฐ์š” ์ด์–ด์„œ ์ด๋ฒˆ์—๋Š” ์ถœ๋ ฅ๋˜๋Š” ๋กœ๊ทธ๋ฉ”์‹œ์ง€์˜ ๋‚ด์šฉ์„ ๋‚ด๋ง˜๋Œ€๋กœ ๋ฐ”๊ฟ”๋ณด๋ ค๊ณ  ํ•ฉ๋‹ˆ๋‹ค. ํ˜น์‹œ๋ผ๋„ ๊ฒ€์ƒ‰์œผ๋กœ ์ด ํฌ์ŠคํŒ…

keichee.tistory.com

 

์•ˆ๋…•ํ•˜์„ธ์š”~ ์˜ค๋žœ๋งŒ์— ํฌ์ŠคํŒ…์„ ํ•˜๊ฒŒ๋˜๋„ค์š”.

์˜ค๋Š˜์€ ์Šคํ”„๋ง๋ถ€ํŠธ ๊ธฐ๋ฐ˜์˜ ํ”„๋กœ์ ํŠธ์—์„œ ์Šคํ”„๋ง์บ์‹œ๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๋ฐฉ๋ฒ•์— ๋Œ€ํ•ด์„œ ๊ณต์œ ๋“œ๋ฆฌ๋ ค๊ณ  ํ•ฉ๋‹ˆ๋‹ค.

์Šคํ”„๋ง์บ์‹œ๋Š” ๋ ˆ๋””์Šค, ์นดํŽ˜์ธ, ehcache ๋“ฑ๊ณผ ์—ฐ๊ณ„ํ•˜์—ฌ ์‚ฌ์šฉํ•˜๊ฒŒ ๋˜๋Š”๋ฐ์š”,

์ด๋ฒˆ  ํฌ์ŠคํŒ…์—์„œ๋Š” ehcache์™€ ์—ฐ๋™ํ•˜์—ฌ ์‚ฌ์šฉํ•  ์˜ˆ์ •์ž…๋‹ˆ๋‹ค.

์šฐ์„  ๊ธฐ๋ณธ์ ์ธ ๊ฐœ๋ฐœํ™˜๊ฒฝ์€ ์•„๋ž˜์™€ ๊ฐ™์Šต๋‹ˆ๋‹ค.

  • IntelliJ IDEA 2021.3.3 (Ultimate Edition)
  • SpringBoot 2.7.3
  • Java 18 (OpenJDK)

1. ํ”„๋กœ์ ํŠธ ์ƒ์„ฑ

์šฐ์„  ๋นˆ ๊นกํ†ต ํ”„๋กœ์ ํŠธ๋ฅผ ๋งŒ๋“ค์–ด ๋ณผ๊ฒŒ์š”.

์ด๋ฏธ ์‚ฌ์šฉํ•  ํ”„๋กœ์ ํŠธ๊ฐ€ ์žˆ๋‹ค๋ฉด ๊ฑด๋„ˆ๋›ฐ์…”๋„ ์ข‹์Šต๋‹ˆ๋‹ค :)

์ž๋ฐ” 18, ๊ทธ๋ž˜๋“ค ์„ ํƒ
์•„๋ฌด๋Ÿฐ ์˜์กด์„ฑ์„ ์„ ํƒํ•˜์ง€ ์•Š๊ณ  ๊นจ๋—ํ•œ ํ”„๋กœ์ ํŠธ๋กœ ๋งŒ๋“ญ๋‹ˆ๋‹ค
ํ”„๋กœ์ ํŠธ ๋นŒ๋“œ๊ฐ€ ์™„๋ฃŒ๋œ ํ›„ ํ”„๋กœ์ ํŠธ ํŠธ๋ฆฌ

์ž, ๋นˆ๊นกํ†ต ํ”„๋กœ์ ํŠธ ์ƒ์„ฑ์ด ์™„๋ฃŒ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.

2. ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ์ถ”๊ฐ€ 

์ด์ œ ๊ตฌํ˜„ ๋ฐ ํ…Œ์ŠคํŠธ์— ํ•„์š”ํ•œ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋“ค์„ ์ถ”๊ฐ€ํ•ด๋ณผ๊ฒŒ์š”.

์šฐ์„  ํ˜„์žฌ build.gradle ํŒŒ์ผ์€ ์•„๋ž˜์™€ ๊ฐ™์ด ๋˜์–ด์žˆ์„๊ฑฐ์—์š”

plugins {
    id 'org.springframework.boot' version '2.7.3'
    id 'io.spring.dependency-management' version '1.0.13.RELEASE'
    id 'java'
}

group = 'com.example'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '18'

repositories {
    mavenCentral()
}

dependencies {
    implementation 'org.springframework.boot:spring-boot-starter'
    implementation 'org.springframework.boot:spring-boot-starter-web'
    testImplementation 'org.springframework.boot:spring-boot-starter-test'
}

tasks.named('test') {
    useJUnitPlatform()
}

์ €๊ธฐ์— ์•„๋ž˜ 5๊ฐœ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋“ค์„ ์ถ”๊ฐ€ํ•ด์ค๋‹ˆ๋‹ค.

implementation 'org.ehcache:ehcache:3.10.1'
implementation 'org.springframework:spring-context-support:5.3.22'

implementation 'org.projectlombok:lombok:1.18.24'
annotationProcessor 'org.projectlombok:lombok:1.18.24'

implementation 'ch.qos.logback:logback-classic:1.2.11'

์™œ ์ € ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋“ค์ด ํ•„์š”ํ•œ์ง€ ํ•˜๋‚˜์”ฉ ๋ณผ๊ฒŒ์š”.

implementation 'org.ehcache:ehcache:3.10.1' jcache ๊ตฌํ˜„์ฒด์ธ ehcache ์‚ฌ์šฉ์„ ์œ„ํ•จ
implementation 'org.springframework:spring-context-support:5.3.22' ์Šคํ”„๋ง์บ์‹œ์˜ ์–ด๋…ธํ…Œ์ด์…˜์„ ์‚ฌ์šฉํ•˜๊ธฐ ์œ„ํ•จ
implementation 'org.projectlombok:lombok:1.18.24'
annotationProcessor 'org.projectlombok:lombok:1.18.24'
lombok ์–ด๋…ธํ…Œ์ด์…˜์„ ์‚ฌ์šฉํ•˜๊ธฐ ์œ„ํ•จ
implementation 'ch.qos.logback:logback-classic:1.2.11' ๋กœ๊น…์„ ์œ„ํ•จ

์ด๋ ‡๊ฒŒ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ์ถ”๊ฐ€ํ•œ ๋’ค ์บ์‹œ์„ค์ •์„ ์ข€ ํ•ด์ฃผ๋„๋ก ํ• ๊ฒŒ์š”

3. ์บ์‹œ ์„ค์ •

com.example.demo.config ํŒจํ‚ค์ง€๋ฅผ ๋งŒ๋“  ๋’ค ์•„๋ž˜์™€ ๊ฐ™์ด CacheConfig ํด๋ž˜์Šค๋ฅผ ์ž‘์„ฑํ•ด์ค๋‹ˆ๋‹ค.

package com.example.demo.config;

import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.cache.jcache.JCacheCacheManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import javax.cache.Cache;
import javax.cache.Caching;
import javax.cache.configuration.MutableConfiguration;
import javax.cache.expiry.CreatedExpiryPolicy;
import javax.cache.expiry.Duration;
import javax.cache.spi.CachingProvider;
import java.util.concurrent.TimeUnit;

@EnableCaching
@Configuration
public class CacheConfig {

    private final CachingProvider cachingProvider = Caching.getCachingProvider();
    private final javax.cache.CacheManager cacheManager = cachingProvider.getCacheManager();

    @Bean
    public CacheManager cacheManager() {
        return new JCacheCacheManager(cacheManager);
    }

    @Bean
    public Cache<Integer, Integer> commonCache() {
        MutableConfiguration<Integer, Integer> configuration =
                new MutableConfiguration<Integer, Integer>()
                        .setTypes(Integer.class, Integer.class)
                        .setStoreByValue(false)
                        .setExpiryPolicyFactory(CreatedExpiryPolicy.factoryOf(new Duration(TimeUnit.SECONDS, 5)));
        return cacheManager.createCache("commonCache", configuration);
    }
}

์œ„ ๋‚ด์šฉ์„ ๊ฐ„๋žตํžˆ ์‚ดํŽด๋ณด๋ฉด cacheManager ๋นˆ์„ ํ•˜๋‚˜ ๋“ฑ๋กํ•ด์ฃผ๊ณ  ๊ทธ ์บ์‹œ๋งค๋‹ˆ์ €์— commonCache๋ผ๋Š” ์ด๋ฆ„์˜ ์บ์‹œ ๋นˆ์„ ํ•˜๋‚˜ ๋“ฑ๋กํ•ด์ฃผ๋Š” ๊ฒ๋‹ˆ๋‹ค. ์ด commonCache๊ฐ€ key, value ํŽ˜์–ด๋ฅผ ์ €์žฅํ•˜๋Š” ํ•˜๋‚˜์˜ ์„œ๋ž์ด๋ผ๊ณ  ์ƒ๊ฐํ•˜์‹œ๋ฉด๋˜๊ณ ์š”. ์—ฌ๊ธฐ์„œ๋Š” ํ‚ค์™€ ๊ฐ’์˜ ํƒ€์ž…์ด ๋ชจ๋‘ Integer ์ด๊ณ  ๋งŒ๋ฃŒ์ •์ฑ…์€ ์ƒ์„ฑ์‹œ๊ฐ„์„ ๊ธฐ์ค€์œผ๋กœ 5์ดˆ๋กœ ์„ค์ •ํ•ด์ฃผ์—ˆ์Šต๋‹ˆ๋‹ค.

 

4. API ๊ตฌํ˜„ ๋ฐ ํ…Œ์ŠคํŠธ

์ด์ œ commonCache์— ๊ฐ’์„ ์ €์žฅํ•˜๊ณ  ๋งŒ๋ฃŒ๋˜๊ธฐ ์ „๊นŒ์ง€ ์บ์‹œ์˜ ๋ฐ์ดํ„ฐ๋ฅผ ์‚ฌ์šฉํ•˜๋„๋ก ์„œ๋น„์Šค ํด๋ž˜์Šค๋ฅผ ํ•˜๋‚˜ ๋งŒ๋“ค์–ด์ฃผ๊ฒ ์Šต๋‹ˆ๋‹ค.

package com.example.demo.service;

import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;

@Service
public class CacheService {

    @Cacheable(cacheNames = "commonCache", cacheManager = "cacheManager", key = "#code")
    public Integer getCachedValue(int code) {
        System.out.println("๊ณ„์‚ฐ์ค‘....");
        // DB ์กฐํšŒ ๋“ฑ์˜ ๋กœ์ง...
        return (int) (Math.random() * 10);
    }
}

com.example.demo.service ํŒจํ‚ค์ง€๋ฅผ ๋งŒ๋“ค๊ณ  CacheService ํด๋ž˜์Šค๋ฅผ ์ž‘์„ฑํ–ˆ์Šต๋‹ˆ๋‹ค.

getCachedValue ๋ฉ”์„œ๋“œ์— @Cacheable ์–ด๋…ธํ…Œ์ด์…˜์„ ๋ถ™์—ฌ์ฃผ๊ณ  commonCache๋ฅผ ์‚ฌ์šฉํ•˜๊ณ  cacheManager๋Š” CacheConfig์— ๋“ฑ๋กํ–ˆ๋˜ cacheManager ์ด๋ฆ„์„ ์ ์–ด์ฃผ์—ˆ์Šต๋‹ˆ๋‹ค. ๊ทธ๋ฆฌ๊ณ  key๋กœ ํŒŒ๋ผ๋ฏธํ„ฐ๋กœ ์ „๋‹ฌ๋ฐ›๋Š” code ๊ฐ’์„ ์‚ฌ์šฉํ•˜๊ฒ ๋‹ค๊ณ  ์„ ์–ธํ•ด์ฃผ์—ˆ์Šต๋‹ˆ๋‹ค. ๋งŒ์•ฝ key๋กœ ์–ด๋–ค ๊ฐ์ฒด์˜ ๋ฉค๋ฒ„๋ณ€์ˆ˜๋ฅผ ์‚ฌ์šฉํ•ด์•ผํ•œ๋‹ค๋ฉด key = "#obj.employeeId" ์™€ ๊ฐ™์ด ์‚ฌ์šฉ๊ฐ€๋Šฅํ•ฉ๋‹ˆ๋‹ค. (๊ณต์‹๋ฌธ์„œ ์ฐธ๊ณ )

CacheService.getCachedValue ๊ฐ€ ํ˜ธ์ถœ๋˜๋ฉด code ๊ฐ’์„ key๋กœํ•˜์—ฌ return๋œ Integer ๊ฐ’์„ commonCache์— ์ €์žฅํ•˜๊ฒŒ๋˜๊ณ  ๋งŒ๋ฃŒ์‹œ๊ฐ„์ธ 5์ดˆ ์ด๋‚ด์— ๋™์ผํ•œ key๊ฐ’(์—ฌ๊ธฐ์„œ๋Š” code ํŒŒ๋ผ๋ฏธํ„ฐ ๊ฐ’)์œผ๋กœ ๋ฉ”์„œ๋“œ ํ˜ธ์ถœ์ด ๋ฐœ์ƒํ•  ๊ฒฝ์šฐ ๋ฉ”์„œ๋“œ ๋‚ด๋ถ€์˜ ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง์„ ํƒ€์ง€์•Š๊ณ  ๋ฐ”๋กœ ์บ์‹œ์— ์ €์žฅ๋˜์–ด์žˆ๋Š” ๊ฐ’์„ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค. ์ด๋ฅผ ํ™•์ธํ•˜๊ธฐ์œ„ํ•ด ๊ณ„์‚ฐ์ค‘.... ๋ฉ”์‹œ์ง€๋ฅผ ์ถœ๋ ฅํ•˜๋„๋ก ํ–ˆ์Šต๋‹ˆ๋‹ค.

 

๋งˆ์ง€๋ง‰์œผ๋กœ ์œ„ ์„œ๋น„์Šค๋ฅผ ํ˜ธ์ถœํ•˜๋Š” ํ…Œ์ŠคํŠธ API ๋ฅผ ๋งŒ๋“ค์–ด ์ค„๊ฒŒ์š”.

com.example.demo.controller ํŒจํ‚ค์ง€๋ฅผ ์‹ ๊ทœ์ถ”๊ฐ€ํ•˜๊ณ  TestController๋ฅผ ์ƒ์„ฑํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค.

package com.example.demo.controller;

import com.example.demo.service.CacheService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@Slf4j
@RestController
@RequestMapping("/test")
@RequiredArgsConstructor
public class TestController {

    private final CacheService cacheService;

    @GetMapping("/cache")
    public Integer getCachedValue(int code) {
        log.info("Request accepted for code {}", code);
        return cacheService.getCachedValue(code);
    }
}

API ํ˜ธ์ถœ์ด ๋“ค์–ด์˜ฌ๋•Œ๋งˆ๋‹ค Request accepted for code X ๋กœ๊ทธ๊ฐ€ ์ถœ๋ ฅ๋˜๋„๋ก ํ•˜๊ณ , ์บ์‹œ์„œ๋น„์Šค์˜ ๋ฉ”์„œ๋“œ๋ฅผ ํ˜ธ์ถœํ•˜๋„๋ก ํ–ˆ์Šต๋‹ˆ๋‹ค.

์ด์ œ DemoApplication์„ ์‹คํ–‰ํ•˜๊ณ  API๋ฅผ ํ˜ธ์ถœํ•ด๋ณผ๊ฒŒ์š”.

2022-09-14 12:32:59.783  INFO 47720 --- [           main] com.example.demo.DemoApplication         : Started DemoApplication in 0.778 seconds (JVM running for 0.997)
2022-09-14 12:33:06.871  INFO 47720 --- [nio-8080-exec-1] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring DispatcherServlet 'dispatcherServlet'
2022-09-14 12:33:06.871  INFO 47720 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet        : Initializing Servlet 'dispatcherServlet'
2022-09-14 12:33:06.872  INFO 47720 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet        : Completed initialization in 1 ms
2022-09-14 12:33:06.882  INFO 47720 --- [nio-8080-exec-1] c.e.demo.controller.TestController       : Request accepted for code 1
๊ณ„์‚ฐ์ค‘....
2022-09-14 12:33:08.270  INFO 47720 --- [nio-8080-exec-2] c.e.demo.controller.TestController       : Request accepted for code 1
2022-09-14 12:33:09.186  INFO 47720 --- [nio-8080-exec-3] c.e.demo.controller.TestController       : Request accepted for code 1
2022-09-14 12:33:10.017  INFO 47720 --- [nio-8080-exec-4] c.e.demo.controller.TestController       : Request accepted for code 1
2022-09-14 12:33:10.880  INFO 47720 --- [nio-8080-exec-5] c.e.demo.controller.TestController       : Request accepted for code 1
2022-09-14 12:33:11.764  INFO 47720 --- [nio-8080-exec-6] c.e.demo.controller.TestController       : Request accepted for code 1
2022-09-14 12:33:12.631  INFO 47720 --- [nio-8080-exec-7] c.e.demo.controller.TestController       : Request accepted for code 1
๊ณ„์‚ฐ์ค‘....
2022-09-14 12:33:13.538  INFO 47720 --- [nio-8080-exec-8] c.e.demo.controller.TestController       : Request accepted for code 1
2022-09-14 12:33:14.408  INFO 47720 --- [nio-8080-exec-9] c.e.demo.controller.TestController       : Request accepted for code 1

ํ˜ธ์ถœ์€ ๋ชจ๋‘ code๊ฐ’ 1์„ ๋„ฃ์–ด์„œ ํ•˜์˜€๊ณ , ์ตœ์ดˆ ํ˜ธ์ถœ์„ 12:33:06.882 ์— ํ•˜๋ฉด์„œ ์„œ๋น„์Šค ๋‚ด๋ถ€๋กœ์ง์„ ์‹คํ–‰ํ•˜์—ฌ ๊ฐ’์„ ๋ฐ˜ํ™˜ํ•˜์˜€์Šต๋‹ˆ๋‹ค. (๊ณ„์‚ฐ์ค‘.... ์ถœ๋ ฅ์œผ๋กœ ํ™•์ธ)

์ดํ›„ 12:33:11.764 ๊นŒ์ง€๋Š” ๊ณ„์‚ฐ์ค‘.... ์ด ์ถœ๋ ฅ๋˜์ง€ ์•Š์€ ๊ฒƒ์œผ๋กœ ๋ณด์•„ ์บ์‹ฑ๋˜์–ด์žˆ๋Š” ๊ฐ’์„ ๊ทธ๋Œ€๋กœ ์‘๋‹ตํ•œ๊ฑธ๋กœ ๋ณผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

5์ดˆ๊ฐ€ ์ง€๋‚œ 12:33:12.631 ์— ๋“ค์–ด์˜จ ํ˜ธ์ถœ๊ฑด์€ ๊ณ„์‚ฐ์ค‘.... ์ด ์ถœ๋ ฅ๋œ๊ฑธ๋กœ ๋ณด์•„ ์‘๋‹ต๊ฐ’์— ๋ณ€๋™์ด ๋ฐœ์ƒํ–ˆ์„ ๊ฒƒ์œผ๋กœ ์ถ”์ธก๋ฉ๋‹ˆ๋‹ค. (๋žœ๋ค๊ฐ’์œผ๋กœ ์‘๋‹ตํ•˜๋Š” ์„œ๋น„์Šค๋‹ˆ ์‘๋‹ต๊ฐ’์— ๋ณ€๋™์ด ์—†์„ ์ˆ˜๋„ ์žˆ์ฃ )

์—ฌ๊ธฐ์„œ ์ค‘์š”ํ•œ ๊ฒƒ์€ ์‘๋‹ต๊ฐ’์— ๋ณ€๋™์ด ์žˆ๋Š๋ƒ๊ฐ€ ์•„๋‹ˆ๋ผ ์บ์‹œ๋˜์–ด์žˆ๋Š” ๋ฐ์ดํ„ฐ๊ฐ€ ๋งŒ๋ฃŒ๋˜๊ธฐ ์ „๊นŒ์ง€ ๋ฉ”์„œ๋“œ ๋‚ด๋ถ€๋กœ์ง์„ ์‹คํ–‰ํ•˜์ง€ ์•Š๊ณ  ์บ์‹œ์—์„œ ์กฐํšŒ๋œ ๋ฐ์ดํ„ฐ๋ฅผ ๋ฆฌํ„ดํ•ด์ค€๋‹ค๋Š” ์ ์ž…๋‹ˆ๋‹ค.

 

์ด์ƒ์œผ๋กœ 5๋ถ„๋งŒ์— ์Šคํ”„๋ง์บ์‹œ์™€ ehcache๋ฅผ ์ด์šฉํ•˜์—ฌ ์บ์‹œ๋ฅผ ๊ตฌํ˜„ํ•˜๋Š” ๋ฐฉ๋ฒ•์— ๋Œ€ํ•ด ์•Œ์•„๋ณด์•˜์Šต๋‹ˆ๋‹ค.

 

์ฝ์–ด์ฃผ์…”์„œ ๊ฐ์‚ฌํ•ฉ๋‹ˆ๋‹ค~  ๐Ÿ™‡

์•„์ง redis-cli ์„ค์น˜๊ฐ€ ์•ˆ๋˜์–ด์žˆ๋‹ค๋ฉด redis-cli ์„ค์น˜๋ฅผ ์ฐธ๊ณ ํ•ด์ฃผ์„ธ์š”

 

1) ์ „์ฒด key ๊ฐœ์ˆ˜ ์กฐํšŒ

redis.0001.apn.cache..amazonaws.com:6379> info keyspace
# Keyspace
db0:keys=23976,expires=2023,avg_ttl=125758665

2) ์ „์ฒด key ๋ชฉ๋ก ์กฐํšŒ

redis.0001.apn.cache..amazonaws.com:6379> keys *
1) "EmployeeName:Tom Johnson"
2) "ZipCode:67410"
3) "EmployeeName:Tom Hanks"
4) "EmployeeName:Tom Thumb"
5) "ZipCode:15206"

 

keys ๋ช…๋ น์–ด ์‚ฌ์šฉ์‹œ ์ฃผ์˜์ : ์šด์˜ํ™˜๊ฒฝ์—์„œ๋Š” ์‹ ์ค‘ํžˆ ์‚ฌ์šฉํ•  ๊ฒƒ !!

Warning: consider KEYS as a command that should only be used in production environments with extreme care. It may ruin performance when it is executed against large databases. This command is intended for debugging and special operations, such as changing your keyspace layout. Don't use KEYS in your regular application code. If you're looking for a way to find keys in a subset of your keyspace, consider using SCAN or sets.

3) value ํƒ€์ž… ํ™•์ธ (value ํƒ€์ž…์— ๋”ฐ๋ผ ์กฐํšŒ ๋ช…๋ น์–ด๊ฐ€ ๋‹ค๋ฆ„)

redis.0001.apn.cache..amazonaws.com:6379> get "EmployeeName:Tom Johnson"
(error) WRONGTYPE Operation against a key holding the wrong kind of value

redis.0001.apn.cache..amazonaws.com:6379> type "EmployeeName:Tom Johnson"

hash

4) ํƒ€์ž… ์œ ํ˜•๋ณ„ ์กฐํšŒ command

string : get <key>
hash : hgetall <key>
lists : lrange <key> <start> <end>
sets :  smembers <key>
sorted sets : ZRANGEBYSCORE <key> <min> <max>

๋ฐ์ดํ„ฐ ํƒ€์ž…์— ๋”ฐ๋ฅธ ๋” ๋‹ค์–‘ํ•œ ๋ช…๋ น์–ด๋Š” Redis ๊ณต์‹๋ฌธ์„œ ์ฐธ๊ณ 

Redis ๋ชจ๋“  ๋ช…๋ น์–ด ๋ชฉ๋ก

5) ํŠน์ • key์— ๋Œ€ํ•œ value ์กฐํšŒ (hashํƒ€์ž… ๋ฐ์ดํ„ฐ)

redis.0001.apn.cache..amazonaws.com:6379> hgetall "EmployeeName:Tom Johnson"
1) "_class"
2) "com.mycompany.employee.EmployeeName"
3) "name"
4) "Tom Johnson"
5) "id"
6) "2267849"

redis.0001.apn.cache..amazonaws.com:6379> hget "EmployeeName:Tom Johnson" name

"Tom Johnson"

sudo yum install -y gcc 
wget http://download.redis.io/redis-stable.tar.gz && tar xvzf redis-stable.tar.gz && cd redis-stable && make
sudo cp src/redis-cli /usr/bin
redis-cli -h {redis-endpoint-address}

1) EC2์— build๋ฅผ ์œ„ํ•œ gcc ์„ค์น˜

$ sudo yum install -y gcc 

2) redis stable ๋ฒ„์ „ ๋‹ค์šด๋กœ๋“œ ๋ฐ ๋นŒ๋“œ

$ wget http://download.redis.io/redis-stable.tar.gz && tar xvzf redis-stable.tar.gz && cd redis-stable && make

3) redis-cli ๋กœ ์ ‘์†ํ•ด๋ณด๊ธฐ (๊ธฐ๋ณธํฌํŠธ ์‚ฌ์šฉ์‹œ ํฌํŠธ์ •๋ณด ํ•„์š”์—†์Œ)

$ ./src/redis-cli -h {๋ ˆ๋””์Šค์—”๋“œํฌ์ธํŠธ์ฃผ์†Œ} -p {ํฌํŠธ๋ฒˆํ˜ธ}

4) ์‚ฌ์šฉ์˜ ํŽธ์˜์„ฑ์„ ์œ„ํ•ด usr/bin ๋””๋ ‰ํ† ๋ฆฌ์— redis-cli ๋ช…๋ น์–ด ๋ณต์‚ฌํ•ด๋†“๊ธฐ

$ sudo cp src/redis-cli /usr/bin

์•ˆ๋…•ํ•˜์„ธ์š”, ์˜ค๋žœ๋งŒ์— ๊ธ€์„ ์”๋‹ˆ๋‹ค.

 

์˜ค๋Š˜ ๊ณต์œ ๋“œ๋ฆฌ๋Š” ๋‚ด์šฉ์€ ๋ถ„์‚ฐ์‹œ์Šคํ…œ์—์„œ DLQ ์ฒ˜๋ฆฌํ•˜๋Š” ๋ฐฉ๋ฒ•์— ๋Œ€ํ•œ ๊ฒƒ์ž…๋‹ˆ๋‹ค.

์ œ๊ฐ€ ๊ด€๋ฆฌํ•˜๋Š” ์‹œ์Šคํ…œ ์ค‘ ํ•˜๋‚˜๊ฐ€ ์ฃผ๋กœ ์นดํ”„์นด ๋ฉ”์‹œ์ง€๋ฅผ ์ปจ์ˆจํ•˜์—ฌ ์ฒ˜๋ฆฌํ•˜๋Š” ์ผ์„ ํ•˜๋Š” ์‹œ์Šคํ…œ์ž…๋‹ˆ๋‹ค.

์—ฌ๋Ÿฌ ๊ฐœ์˜ ์ธ์Šคํ„ด์Šค๊ฐ€ ์กด์žฌํ•˜๊ณ  ๋ชจ๋‘ ๋™์ผํ•œ ์†Œ์Šค์ฝ”๋“œ๊ฐ€ ๋ฐฐํฌ๋˜์–ด์žˆ์ฃ .

์†Œ์Šค์ฝ”๋“œ์—๋Š” JPA๋ฅผ ์ด์šฉํ•œ CRUD ๋กœ์ง์ด ๋“ค์–ด์žˆ์Šต๋‹ˆ๋‹ค.

 

๋ฌธ์ œ๊ฐ€ ์—†๋˜ ์‹œ์Šคํ…œ์ธ๋ฐ ๊ด€๋ จ๋ถ€์„œ์—์„œ ํŒŒํ‹ฐ์…˜ ๊ฐœ์ˆ˜๋ฅผ ๋Š˜๋ฆฌ๋ฉด์„œ ๋ฌธ์ œ๊ฐ€ ์ƒ๊ฒผ์Šต๋‹ˆ๋‹ค.

๋‹จ์ˆœํžˆ ํŒŒํ‹ฐ์…˜ ๊ฐœ์ˆ˜๊ฐ€ ๋Š˜์–ด๋‚œ๊ฒŒ ๋ฌธ์ œ๊ฐ€ ์•„๋‹ˆ๋ผ ๊ทธ์ชฝ์—์„œ ๋ณด๋‚ด๋Š” ์นดํ”„์นด ๋ฉ”์‹œ์ง€์˜ ํŠน์„ฑ๊ณผ๋„ ๊ด€๋ จ์ด ์žˆ์—ˆ์ฃ .

ํŒŒํ‹ฐ์…˜๋งˆ๋‹ค ์„œ๋กœ ๋‹ค๋ฅธ ์ธ์Šคํ„ด์Šค์—์„œ ์ฒ˜๋ฆฌํ•˜์ง€๋งŒ ๊ฐ ๋ฉ”์‹œ์ง€์—๋Š” ์„œ๋กœ ์ค‘๋ณต๋œ ๋ฐ์ดํ„ฐ์˜ ๋‚ด์šฉ์ด ๋“ค์–ด๊ฐ€ ์žˆ๋Š” ํ˜•ํƒœ์˜€๊ณ  (์ด๊ฑด ๋ญ ์ œ๊ฐ€ ์–ด์ฐŒ ํ•  ์ˆ˜ ์—†๋Š” ๊ฒƒ์ด์ฃ ), ๋™์ผํ•œ ๋ฐ์ดํ„ฐ๋ฅผ ์„œ๋กœ ๋‹ค๋ฅธ ์ธ์Šคํ„ด์Šค์—์„œ JPA๋ฅผ ์ด์šฉํ•ด์„œ ์ฒ˜๋ฆฌํ•˜๋‹ค๊ฐ€ ๋“œ๋ฌผ๊ฒŒ ObjectOptimisticLockingFailureException ์ด ๋ฐœ์ƒํ•˜๊ธฐ ์‹œ์ž‘ํ–ˆ์Šต๋‹ˆ๋‹ค. 

์˜ˆ๋ฅผ๋“ค์–ด ์ธ์Šคํ„ด์Šค 1๋ฒˆ์—์„œ {A, B} ๋ผ๋Š” ๋ฉ”์‹œ์ง€๋ฅผ ์ฒ˜๋ฆฌํ•  ๋•Œ ์ธ์Šคํ„ด์Šค 2๋ฒˆ์—์„œ {A,C}, 3๋ฒˆ ์ธ์Šคํ„ด์Šค์—์„œ {A,D} ๋ฉ”์‹œ์ง€๋ฅผ ์ฒ˜๋ฆฌํ•˜๋ฉด์„œ A ๋ฐ์ดํ„ฐ๋ฅผ 3๋Œ€์˜ ์ธ์Šคํ„ด์Šค์—์„œ ๋™์‹œ์— ์ฒ˜๋ฆฌํ•˜๋ ค๊ณ  ํ•œ๊ฑฐ์ฃ . ์„ธ ๊ฐœ์˜ ๋ฉ”์‹œ์ง€๋Š” ์„œ๋กœ ๋‹ค๋ฅธ ๋ฐ์ดํ„ฐ๋„ ๋“ค๊ณ ์žˆ์œผ๋ฏ€๋กœ ๋ฌด์กฐ๊ฑด ์ฒ˜๋ฆฌ๋ฅผ ํ•ด์ค˜์•ผ ํ•˜๋Š” ๊ฒƒ๋“ค์ด์—ˆ์Šต๋‹ˆ๋‹ค.

์ด๊ฑธ ์–ด๋–ป๊ฒŒ ํ•  ๊นŒ ๊ณ ๋ฏผํ•˜๋‹ค๊ฐ€ SQS๋ฅผ DLQ๋ฅผ ์ด์šฉํ•˜์—ฌ Lambda ์™€ ์—ฐ๊ฒฐ์‹œ์ผœ ์ผ์ • ์‹œ๊ฐ„์ด ์ง€๋‚œ ๋’ค์— ์ˆ˜๋™๋™๊ธฐํ™” API๋ฅผ ํ˜ธ์ถœํ•˜๋„๋ก ๊ตฌ์„ฑํ•˜๊ธฐ๋กœ ํ–ˆ์Šต๋‹ˆ๋‹ค.

์ด๋ ‡๊ฒŒ ํ•œ ์ด์œ ๋Š” ๊ฐ€์žฅ ๋น ๋ฅธ์‹œ๊ฐ„์— ์ถ”๊ฐ€์ ์ธ ์†Œ์Šค๊ตฌํ˜„ ์—†์ด ํšจ๊ณผ์ ์œผ๋กœ ๋ชฉ์ ์„ ๋‹ฌ์„ฑํ•  ์ˆ˜ ์žˆ์—ˆ๊ธฐ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค.

๋งŒ์•ฝ DLQ๋กœ ๋“ค์–ด์˜ค๋Š” ๋ฉ”์‹œ์ง€๊ฐ€ ๋งŽ์•„์ง„๋‹ค๋ฉด ์†Œ์Šค์ฝ”๋“œ๋ฅผ ๊ตฌํ˜„ํ•˜๋Š”๊ฒŒ ๋น„์šฉ์ธก๋ฉด์—์„  ๋” ํšจ์œจ์ ์ž…๋‹ˆ๋‹ค.

์ธ์Šคํ„ด์Šค๋Š” ์–ด์ฐจํ”ผ ๋– ์žˆ์–ด์•ผ ํ•˜๋Š”๊ฑฐ๊ณ  ๋žŒ๋‹ค๋ฅผ ์ œ๊ฑฐํ•  ์ˆ˜ ์žˆ์œผ๋‹ˆ ๋žŒ๋‹ค์—์„œ ๋ฐœ์ƒํ•˜๋Š” ๋น„์šฉ์ด ์ค„์–ด๋“ญ๋‹ˆ๋‹ค.

ํ•˜์ง€๋งŒ ์†Œ์Šค์ฝ”๋“œ ๊ตฌํ˜„์‹œ ๊ณ ๋ คํ•ด์•ผํ•  ์ ์ด ์žˆ์ฃ . ์ธ์Šคํ„ด์Šค๊ฐ€ ์—ฌ๋Ÿฌ๊ฐœ ๋– ์žˆ์ง€๋งŒ ํ•˜๋‚˜์˜ ์ธ์Šคํ„ด์Šค์—์„œ๋งŒ DLQ์˜ ๋ฉ”์‹œ์ง€๋ฅผ ์ฒ˜๋ฆฌํ•˜๋„๋ก ํ•ด์•ผํ•œ๋‹ค๋Š” ๊ฑฐ์ฃ . ๊ทธ๋ ‡์ง€ ์•Š์œผ๋ฉด ๋˜ ์ฒ˜๋ฆฌ์— ์‹คํŒจํ•˜๋Š” ๊ฒฝ์šฐ๊ฐ€ ๋ฐœ์ƒํ•  ํ…Œ๋‹ˆ๊นŒ์š”. ๊ทธ๋ฆฌ๊ณ  ๋ชจ๋“  ์ธ์Šคํ„ด์Šค์— ๋™์ผํ•œ ์†Œ์Šค์ฝ”๋“œ๊ฐ€ ๋ฐฐํฌ๋˜์–ด์•ผ ํ•˜๋Š”๋ฐ ํ•˜๋‚˜์˜ ์ธ์Šคํ„ด์Šค์—์„œ๋งŒ ์‹คํ–‰์ด ๋˜๋„๋ก ํ•˜๋ ค๋ฉด ๋˜ ์ด๋Ÿฐ์ €๋Ÿฐ ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•ด์•ผ ํ•˜๋‹ค๋ณด๋‹ˆ ๋žŒ๋‹ค๋ฅผ ์ด์šฉํ•˜๊ธฐ๋กœ ํ–ˆ์Šต๋‹ˆ๋‹ค.

 

์•„๋ฌดํŠผ, SQS๋ฅผ DLQ๋กœ ์ด์šฉํ•˜๊ณ , Lambda ํ•จ์ˆ˜๋ฅผ ์—ฐ๊ฒฐ์‹œ์ผœ ์ผ์ • ์‹œ๊ฐ„๋’ค์— ์ˆ˜๋™API๋ฅผ ํ˜ธ์ถœํ•˜์—ฌ ์‹คํŒจํ–ˆ๋˜ ๋ฉ”์‹œ์ง€์— ๋Œ€ํ•œ ์ •๋ณด๋ฅผ ์ฝ์–ด์™€์„œ ์ฒ˜๋ฆฌํ•˜๋„๋ก ํ–ˆ์Šต๋‹ˆ๋‹ค.

SQS Lambda Trigger ์„ค์ •

์ด๋ ‡๊ฒŒ ์„ค์ •ํ•ด๋†“์œผ๋ฉด SQS ๋ฉ”์‹œ์ง€๊ฐ€ ๋ฐœํ–‰๋  ๋•Œ๋งˆ๋‹ค Lambda ํ•จ์ˆ˜๋ฅผ ์‹คํ–‰ํ•˜๋Š”๋ฐ Lambda ํ•จ์ˆ˜์—์„œ๋Š” API๋ฅผ ํ˜ธ์ถœํ•˜๋„๋ก ๊ตฌ์„ฑํ–ˆ์Šต๋‹ˆ๋‹ค. ๋žŒ๋‹ค ํŠธ๋ฆฌ๊ฑฐ์— ๋Œ€ํ•œ ๋‚ด์šฉ์„ AWS์—์„œ๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์ด ์„ค๋ช…ํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค.

You can configure your Amazon SQS queue to trigger an AWS Lambda function when new messages arrive in the queue.
Your queue and Lambda function must be in the same AWS Region.
A Lambda function can use at most one queue as an event source. You can use the same queue with multiple Lambda functions.
You can't associate an encrypted queue that uses the AWS-managed CMK for Amazon SQS with a Lambda function in a different AWS account.

์ €์˜ ๊ฒฝ์šฐ์—๋Š” ํ•„์š”์—†๊ธดํ•˜์ง€๋งŒ, ํ•˜๋‚˜์˜ SQS์— ์—ฌ๋Ÿฌ๊ฐœ์˜ ๋žŒ๋‹คํ•จ์ˆ˜๋ฅผ ์—ฐ๊ฒฐํ•˜์—ฌ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค๋Š” ์ ์ด ์ข‹์•„๋ณด์ด๋„ค์š”.

 

์ž, ๊ทธ๋ฆฌ๊ณ  SQS์™€ ์—ฐ๊ฒฐํ•œ Lambda ํ•จ์ˆ˜๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค.

import logging
import json
from urllib.request import Request, urlopen
from urllib.error import URLError, HTTPError

logger = logging.getLogger()
logger.setLevel(logging.INFO)

API_URL = "https://company.com/employees/{}"

def lambda_handler(event, context):

    message = json.loads(event['Records'][0]['body'])
    
    url = API_URL.format(message['employeeId'])
    headers = {'Content-type':'application/json'}
    
    req = Request(url, headers=headers, data=b'', method='PUT')
    
    try:
        response = urlopen(req)
        return response.getcode()
    except HTTPError as e:
        logger.error("Request failed: %d %s, sqsBody:%s", e.code, e.reason, message)
        return e.code
    except URLError as e:
        logger.error("Server connection failed: %s, sqsBody:%s", e.reason, message)
    return None

Python์€ ๊ณต๋ถ€ํ•œ์  ์—†์œผ๋‚˜, ์ž๋ฐ”๋Š” ์ปดํŒŒ์ผ์–ธ์–ด๋ผ ์ฝ”๋“œ๋ฅผ ์ง์ ‘ AWS์ฝ˜์†”์—์„œ ์ž‘์„ฑํ•  ์ˆ˜ ์—†์–ด์„œ python์œผ๋กœ ์ž‘์„ฑํ–ˆ์Šต๋‹ˆ๋‹ค.

 

๋‚ด์šฉ์€ ๋ณ„๊ฑฐ ์—†๊ณ  event์— SQS ๋ฉ”์‹œ์ง€์˜ ๋‚ด์šฉ์ด ๋“ค์–ด์˜ค๊ธฐ ๋•Œ๋ฌธ์— event์—์„œ ๋ฉ”์‹œ์ง€์˜ body ๋‚ด์šฉ์„ ์ถ”์ถœํ•˜์—ฌ message ๋ณ€์ˆ˜์— ๋„ฃ๊ณ  ์ด๊ฒƒ์„ API ํ˜ธ์ถœํ•  ๋•Œ ์‚ฌ์šฉํ•œ๊ฒƒ์ด ๋‹ค์ž…๋‹ˆ๋‹ค. ์—ฌ๊ธฐ์„œ๋Š” employeeId ๊ฐ’์ด SQS ๋ฉ”์‹œ์ง€์— {"employeeId":1234} ํ˜•ํƒœ๋กœ ๋“ค์–ด์žˆ์—ˆ๋‹ค๋ฉด, ์ €๊ธฐ์„œ 1234 ๊ฐ’์„ ์ถ”์ถœํ•˜์—ฌ api ํ˜ธ์ถœํ• ๋•Œ path variable๋กœ ๋„ฃ์–ด์„œ https://company.com/employees/1234 ๋ฅผ ํ˜ธ์ถœํ•˜๋„๋ก ํ•œ๊ฑฐ์ฃ . 

 

์œ„ ์ฝ”๋“œ์ƒ์—์„  ์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒํ•˜๋ฉด ๊ทธ๋ƒฅ ๋กœ๊น…ํ•˜๊ฑฐ๋‚˜ ์—๋Ÿฌ์ฝ”๋“œ ๋ฆฌํ„ดํ•˜๋„๋ก ํ•ด๋†“์•˜๋Š”๋ฐ, ์—๋Ÿฌ๊ฑด์ด ๋งŽ์ด ๋ฐœ์ƒํ•œ๋‹ค๋ฉด ์Šฌ๋ž™์•Œ๋žŒ์„ ๋ณด๋‚ด๋„๋ก ํ•˜๋Š” ๊ฒƒ๋„ ์ข‹์„ ๊ฒƒ ๊ฐ™์Šต๋‹ˆ๋‹ค.

 

 

์ด์ƒ์œผ๋กœ SQS์™€ Lambda๋ฅผ ์ด์šฉํ•œ ๋ถ„์‚ฐ์ฒ˜๋ฆฌ์‹œ์Šคํ…œ์—์„œ์˜ DLQ ์ฒ˜๋ฆฌ์— ๋Œ€ํ•œ ๊ธฐ๋ก์„ ๋งˆ์นฉ๋‹ˆ๋‹ค.

์‚ฌ์‹ค ์ด๊ฑธ git ๋ช…๋ น์–ด๋กœ ํ™•์ธํ•  ์ผ์ด ์–ผ๋งˆ๋‚˜ ์žˆ๊ฒ ๋ƒ๋งˆ๋Š” ๋‹ค์Œ์— ํ˜น์‹œ๋ผ๋„ ๋˜ ์“ธ์ผ์ด ์žˆ์„๊นŒ๋ด ๊ธฐ๋ก์šฉ์œผ๋กœ ๋‚จ๊น๋‹ˆ๋‹ค.

 

git shortlog -sne --since="01 Apr 2022" --before="01 May 2022" --no-merges

์œ„ ๋ช…๋ น์–ด๋ฅผ ์ด์šฉํ•˜๋ฉด 22๋…„ 4์›” 1์ผ๋ถ€ํ„ฐ 22๋…„ 5์›” 1์ผ ์ „๊นŒ์ง€, ์ฆ‰, 4์›” ํ•œ๋‹ฌ๋™์•ˆ merge ์ปค๋ฐ‹์„ ์ œ์™ธํ•œ ์‹ค์งˆ์ ์ธ ๊ฐœ๋ฐœ์ž‘์—…์˜ ์ปค๋ฐ‹๊ฑด์ˆ˜๋ฅผ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

๊ฒฐ๊ณผ๋Š” ์•„๋ž˜์™€ ๊ฐ™์ด ์ถœ๋ ฅ๋ฉ๋‹ˆ๋‹ค.

 53  ํ™๊ธธ๋™ <gildong.hong@company.com>
 49  ๊น€์ฒ ์ˆ˜ <cheolsoo.kim@company.com>
 31  ์ž„๊บฝ์ • <kj.lim@company.com>