์ ํ๋ฆฌ์ผ์ด์
๊ฐ๋ฐ์ ํ๋ฉด์ ๋ก์ปฌ์ docker๋ก ์ธํ๋ผ ํ๊ฒฝ์ ๊ตฌ์ฑํ๊ณ ๋ก์ปฌ์์ ๊ธฐ๋ฅ ํ
์คํธ๋ฅผ ์ํํด ๋ณธ ์ ์ด ์์ ๊ฒ์ด๋ค. ๋ก์ปฌ์ ์ธํ๋ผ ํ๊ฒฝ์ ๊ตฌ์ฑํ๊ธฐ ์ํด์ docker ์์ง์ ๊ตฌ๋ํ๊ณ docker compose๋ฅผ ์์ฑํ๊ณ ๋ก์ปฌ์ ์ ํ๋ฆฌ์ผ์ด์
๊ตฌ๋ ์ ์ docker compose๋ฅผ ์คํํ์ฌ ์ปจํ
์ด๋๋ก ์ฌ๋ฆฌ๋ ์ผ์ ๋งค์ฐ ๋ฒ๊ฑฐ๋กญ๋ค.
ํ์ง๋ง spring boot docker compose support๋ฅผ ํตํด์ ์ฝ๊ฒ ๋ก์ปฌ์์ ์ ํ๋ฆฌ์ผ์ด์
์ ๊ตฌ๋ํ ์ ์๋ค. spring boot 3.1๋ถํฐ ์ง์๋๋ docker compose๋ฅผ ์ด๋ป๊ฒ ์ฌ์ฉํ๋ฉด ๋๋์ง ๊ทธ ์ฌ์ฉ๋ฒ์ ๋ํด์ ์ ๋ฆฌํด ๋ณด๊ณ ์ ํ๋ค.
spring boot docker compose๋ docker compose๋ฅผ ์คํํ๊ธฐ ์ํด์ yaml์ ๊ด๋ จ ์ค์ ์ ์ ์๋ง ํ๋ฉด ์ง์ ๋ docker compose ํ์ผ์ ํตํด container๋ฅผ ์ฌ๋ฆฌ๊ณ ConnectionDetails ์ถ์ํ๋ฅผ ํตํด์ ๋ด๋ถ์ ์ผ๋ก ์๋ ์ฐ๊ฒฐ์ ์ค์ ํ๋ค. ์ด๋ฅผ ํตํด์ ๋ก์ปฌ ํ๊ฒฝ์์ ์ ํ๋ฆฌ์ผ์ด์ ๊ตฌ๋์ ์ํด์ ์๋์ผ๋ก docker container๋ฅผ ์คํ์ํค๊ณ ์ ํ๋ฆฌ์ผ์ด์ ์ค์ ์์ ์ธ๋ถ ์๋น์ค ์ฐ๊ฒฐ์ ๋ํ ์ค์ ์ ๋ฐ๋ก ์ง์ ํ์ง ์์๋ ๋๋ค.
ConnectionDetails ์ถ์ํ์ ๋ํ ์ค๋ช
์
Spring Boot 3.1’s ConnectionDetails abstraction ๋ฌธ์๋ฅผ ์ฐธ๊ณ ํ๊ธฐ ๋ฐ๋๋ค.
Spring Boot docker compose Dependency
spring boot docker compose๋ฅผ ์ฌ์ฉํ๊ธฐ ์ํ dependency๋ ๋ค์๊ณผ ๊ฐ๋ค.
maven
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-docker-compose</artifactId>
<optional>true</optional>
</dependency>
</dependencies>XMLgradle
dependencies {
developmentOnly("org.springframework.boot:spring-boot-docker-compose")
testAndDevelopmentOnly("org.springframework.boot:spring-boot-docker-compose")
}GroovytestAndDevelopmentOnly(“org.springframework.boot:spring-boot-docker-compose”)
๋ ํ
์คํธ ์ฝ๋ ์คํ์์๋ spring boot docker compose ๋ฅผ ์ด์ฉํ์ฌ docker container๋ฅผ ์คํํ๋๋ก ํ๋ค.
spring-boot-docker-compose ์ข
์์ฑ์ด ์ถ๊ฐ๋๋ฉด ๋ค์๊ณผ ๊ฐ์ ๋์์ ํ๋ค.
- ์ ํ๋ฆฌ์ผ์ด์ ๋๋ ํ ๋ฆฌ์์ compose.yaml ๋ฐ ๊ธฐํ ์ผ๋ฐ์ ์ธ ์์ฑ ํ์ผ ์ด๋ฆ์ ๊ฒ์ํ๋ค.
- ๊ฒ์๋ docker compose ์์ฑ ํ์ผ์ ๋ํด์ docker compose up ๋ช ๋ น์ ์คํํ๋ค.
- ์ง์๋๋ ๊ฐ ์ปจํ ์ด๋์ ๋ํ ์๋น์ค ์ฐ๊ฒฐ Bean์ ์์ฑํ๋ค. (ConnectionDetails ์ถ์ํ)
- ์ ํ๋ฆฌ์ผ์ด์ ์ด shutdown ๋ ๋ docker compose stop ๋ช ๋ น์ ์คํํ๋ค.
starter.spring.io๋ฅผ ํตํด์ ํ๋ก์ ํธ๋ฅผ ์์ฑํ ๋ ‘Docker Compose support’ ์ข ์์ฑ์ ์ถ๊ฐํ๊ณ ์ง์๋๋ Docker Image์ ๊ด๋ จ๋ ์๋น์ค driver๊ฐ ์ข ์์ฑ์ผ๋ก ์ถ๊ฐ๋๋ฉด ์๋์ผ๋ก compose.yaml ํ์ผ์ ์์ฑํด ์ค๋ค.
Service Connections
Container ์๋น์ค์ ๋ํ ์ฐ๊ฒฐ์ ํ์ฌ ์ง์๋๋ Container์ธ ๊ฒฝ์ฐ ConnectionDetails ์ถ์ํ๋ฅผ ํตํด์ ์๋์ผ๋ก ์ฐ๊ฒฐ ์ค์ ์ ํ๋ค. ์ผ๋ฐ์ ์ผ๋ก Container ๋ด๋ถ์ ํฌํธ์ ํธ์คํธ ํฌํธ๊ฐ ๋งตํ๋๋ ๋ฐฉ์์ผ๋ก์จ ๋ก์ปฌ์์ ์ฐ๊ฒฐ์ ํ ๋๋ ํธ์คํธ ํฌํธ๋ฅผ ํตํด์ ์ ์์ ํด์ผ ํ๋๋ฐ ์ด ํธ์คํธ ํฌํธ๋ Container๊ฐ ์๋ก ์คํ๋ ๋๋ง๋ค ๋ณ๊ฒฝ๋๋๋ฐ spring boot์์ ๋งตํ๋ ํธ์คํธ ํฌํธ๋ฅผ ๊ฐ์งํ์ฌ ์๋ ์ค์ ํ๋ค. ์ง์๋์ง ์๋ Container์ ๊ฒฝ์ฐ์๋ ๋ณ๋๋ก ์ฐ๊ฒฐ ์ค์ ์ด ํ์ํ๊ณ docker compose ์ ์์ ํธ์คํธ ํฌํธ๋ฅผ ๊ณ ์ ํฌํธ๋ก ์ค์ ํด์ผ ํ๋ค. ํ์ฌ ์ง์๋๋ container image๋ ๋ค์๊ณผ ๊ฐ๋ค.
| Connection Details | Matched on |
|---|---|
| CassandraConnectionDetails | Containers named “cassandra” |
| ElasticsearchConnectionDetails | Containers named “elasticsearch” |
| JdbcConnectionDetails | Containers named “gvenzl/oracle-xe”, “mariadb”, “mssql/server”, “mysql”, or “postgres” |
| MongoConnectionDetails | Containers named “mongo” |
| R2dbcConnectionDetails | Containers named “gvenzl/oracle-xe”, “mariadb”, “mssql/server”, “mysql”, or “postgres” |
| RabbitConnectionDetails | Containers named “rabbitmq” |
| RedisConnectionDetails | Containers named “redis” |
| ZipkinConnectionDetails | Containers named “openzipkin/zipkin” |
์ฃผ์ํด์ผ ํ ๊ฒ์ ์ด๋ฏธ์ง ์ด๋ฆ์ Matched on์ ๊ธฐ์ฌ๋ ์ด๋ฆ์ด์ด์ผ ์๋น์ค๋ก ์๋ ์ฐ๊ฒฐ์ด ๋๋ค. ํ์ง๋ง ์ด๋ฏธ์ง์ ๋ค๋ฅธ ์ด๋ฆ์ ์ฌ์ฉํ๋ ๊ฒฝ์ฐ compose.yaml ํ์ผ์ label์ ์ฌ์ฉํ์ฌ ์ด๋ฅผ ํด๊ฒฐ ํ ์ ์๋ค.
services:
redis:
image: 'mycompany/mycustomredis:7.0'
ports:
- '6379'
labels:
org.springframework.boot.service-connection: redis
YAMLmycompany/mycustomredis ์ด๋ฏธ์ง๋ฅผ spring boot์ redis ๋ผ๋ ๊ฒ์ ์๋ ค์ค RedisConnectionDetails๋ฅผ ํตํด ์๋ ์ฐ๊ฒฐ ๋๋ค. ์ค์ ๋ก spring-boot-docker-compose ๋ํ๋์์ org.springframework.boot.docker.compose.service.connection ํจํค์ง์ ๋ณด๋ฉด ๋ค์๊ณผ ๊ฐ์ ์๋น์ค๋ค์ด ํ์ ํจํค์ง์ ์ ์๋์ด ์๋ค.

๋ํ DockerComposeConnectionDetailsFactory์ DockerComposeConnectionDetails inner class๊ฐ ์ ์๋์ด ์๋๋ฐ ์ด๋ฅผ ์์ํ๋ class๋ ๋ค์๊ณผ ๊ฐ๋ค. ์๋ ํด๋์ค๋ค์ด ์ ํ์ ์ง์ ๋ container image์ ์ผ๋งฅ ์ํตํ๋ค.

๊ฐ DockerComposeConnectionDetails ์์ ํด๋์ค๋ค์ ๊ฐ ์๋น์ค์ ํด๋นํ๋ ConnectionDetails ์ธํฐํ์ด์ค๋ฅผ ๊ตฌํํ๊ณ ์๋ค.
Docker Compose Lifecycle
๊ธฐ๋ณธ์ ์ผ๋ก spring boot๋ ์ ํ๋ฆฌ์ผ์ด์ ์ด ์์๋ ๋ docker compose up์ ํธ์ถํ๊ณ ์ ํ๋ฆฌ์ผ์ด์ ์ด ์ค์ง๋ ๋ docker compose stop์ ํธ์ถํ๋ค. ๋ค๋ฅธ lifecycle ๊ด๋ฆฌ๋ฅผ ํ๊ณ ์ ํ ๋๋ spring.docker.compose.lifecycle-management ์์ฑ์ ์ฌ์ฉํ ์ ์๋ค. ์ง์ ํ ์ ์๋ ๊ฐ์ ๋ค์๊ณผ ๊ฐ๋ค.
- none : docker compose๋ฅผ ์์ํ๊ฑฐ๋ ์ค์งํ์ง ์๋๋ค.
- start-only : ์ ํ๋ฆฌ์ผ์ด์ ์ด ์์๋ ๋๋ง docker compose๋ฅผ ์คํํ๋ค.
- start-and-stop : ์ ํ๋ฆฌ์ผ์ด์
์ด ์์๋ ๋ docker compose๋ฅผ ์์ํ๊ณ ์ค์ง๋ ๋ docker compose๋ฅผ ์ค์งํ๋ค. spring.docker.compose.start.command ์์ฑ์ ์ฌ์ฉํ์ฌ docker compose up ํน์ docker compose start๋ฅผ ์ค์ ํ๋ค.
spring.docker.compose.stop.command ์์ฑ์ ์ฌ์ฉํ์ฌ docker compose down ํน์ docker compose stop์ ์ค์ ํ๋ค.
spring:
docker:
compose:
lifecycle-management: start-and-stop
start:
command: up
stop:
command: down
timeout: 1mYAMLSpring Boot Docker Compose for Test
ํ ์คํธ ์ฝ๋ ์คํ์์คํ ์ docker compose๋ ๊ธฐ๋ณธ์ ์ผ๋ก ์คํ๋์ง ์์ง๋ง ์๋ ์ค์ ์ผ๋ก ํ ์คํธ ์ฝ๋ ์คํ ์ ์ ์ฉํ ์ ์๋ค.
spring.docker.compose.skip.in-tests=falseYAML์ํ ์ฝ๋
docker compose๋ฅผ ์ฌ์ฉํ๋ ์ํ ์ฝ๋๋ฅผ ์์ฑํด ๋ณด์. ์ํ ์ฝ๋๋
- kafka, zookeeper, mariadb ์ปจํ ์ด๋๋ฅผ ์์ฑํ๋ compose.yaml ํ์ผ์ ์ ์ํ๋ค.
- Member ์์ฑ ์์ฒญ API๋ฅผ ํธ์ถํ๋ฉด Kafka์ test-topic ์ผ๋ก ๋ฉ์์ง๋ฅผ ์ ์กํ๋ค.
- Kafka์ test-topic ์ผ๋ก๋ถํฐ ๋ฉ์์ง๋ฅผ ์์ ํ์ฌ Member ํ ์ด๋ธ์ ์ ์ฅํ๋ ์ฝ๋์ด๋ค.
๋ฐ์ดํฐ๋ฅผ kafka๋ฅผ ํตํด์ ์ก์์ ํ ๋ค์ DB์ ์ ์ฅํ๋ ๊ฒ์ด ์ด์ํ๊ธด ํ์ง๋ง ์ด๋๊น์ง๋ docker compose ์ฌ์ฉ์ ๋ํ ์ํ์ด๋ฏ๋ก docker compose ์ฌ์ฉ ๋ฐฉ๋ฒ์ ์ค์ ์ ๋๊ธธ ๋ฐ๋๋ค.
dependency
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.kafka</groupId>
<artifactId>spring-kafka</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<!-- spring boot docker compose๋ฅผ ์ฌ์ฉํ๊ธฐ ์ํด์ ํ์ํ dependency -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-docker-compose</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.mariadb.jdbc</groupId>
<artifactId>mariadb-java-client</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.kafka</groupId>
<artifactId>spring-kafka-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>XMLstarter.spring.io๋ฅผ ํตํด์ ํ๋ก์ ํธ๋ฅผ ์์ฑํ ๋ spring-boot-docker-compose ๋ํ๋์์ ํ์ฌ ์ง์๋๋ container ์ข ๋ฅ์ธ mariadb์ ๋ํ mariadb-java-client ๋ํ๋์๊ฐ ์๊ธฐ ๋๋ฌธ์ ์๋์ ๊ฐ์ด compose.yaml ํ์ผ์ด ์๋์ผ๋ก ์์ฑ๋๋ค.
services:
mariadb:
image: 'mariadb:latest'
environment:
- 'MARIADB_DATABASE=mydatabase'
- 'MARIADB_PASSWORD=secret'
- 'MARIADB_ROOT_PASSWORD=verysecret'
- 'MARIADB_USER=myuser'
ports:
- '3306'YAML์ฌ๊ธฐ์ ์ถ๊ฐ๋ก kafka๋ฅผ ์ฌ์ฉํ ๊ฒ์ด๊ธฐ ๋๋ฌธ์ kafka container ์์ฑ์ ์ํ ์ ์๋ ์ถ๊ฐํด์ฃผ์.
services:
mariadb:
image: 'mariadb:latest'
environment:
- 'MARIADB_DATABASE=mydatabase'
- 'MARIADB_PASSWORD=secret'
- 'MARIADB_ROOT_PASSWORD=verysecret'
- 'MARIADB_USER=myuser'
ports:
- '3306'
zookeeper:
image: docker.io/bitnami/zookeeper:3.8
ports:
- "2181:2181"
environment:
- ALLOW_ANONYMOUS_LOGIN=yes
kafka:
image: docker.io/bitnami/kafka:3.4
ports:
- "9092:9092"
- "9094:9094"
environment:
- ALLOW_PLAINTEXT_LISTENER=yes
- KAFKA_ENABLE_KRAFT=no
- KAFKA_CFG_LISTENER_SECURITY_PROTOCOL_MAP=INTERNAL:PLAINTEXT,EXTERNAL:PLAINTEXT
- KAFKA_CFG_ZOOKEEPER_CONNECT=zookeeper:2181
- KAFKA_CFG_LISTENERS=INTERNAL://kafka:9094,EXTERNAL://kafka:9092
- KAFKA_CFG_ADVERTISED_LISTENERS=INTERNAL://kafka:9094,EXTERNAL://127.0.0.1:9092
- KAFKA_CFG_INTER_BROKER_LISTENER_NAME=INTERNAL
depends_on:
- zookeeper
YAMLapplication.yml
spring:
profiles:
active: local
kafka:
producer:
bootstrap-servers: localhost:9092
key-serializer: org.apache.kafka.common.serialization.StringSerializer
value-serializer: org.springframework.kafka.support.serializer.JsonSerializer
client-id: kafka-test-producer
consumer:
bootstrap-servers: localhost:9092
key-deserializer: org.apache.kafka.common.serialization.StringDeserializer
value-deserializer: org.springframework.kafka.support.serializer.JsonDeserializer
group-id: test-group-id
auto-offset-reset: earliest
client-id: kafka-test-consumer
properties:
spring.json.trusted.packages: com.example.spring.dockercompose.dto
listener:
concurrency: 3
ack-mode: record
---
# ๋ก์ปฌ ํ๊ฒฝ์์ ์ ํ๋ฆฌ์ผ์ด์
์ ๊ตฌ๋ํ๋ ๊ฒฝ์ฐ์๋ง docker compose๋ฅผ ์ฌ์ฉํ๋ค.
# docker compose mariadb container๊ฐ ์ง์ ๋๋ฏ๋ก ๋ณ๋์ ์ฐ๊ฒฐ์ค์ (datasource)์ ํ์์๋ค.
# ์ด์ ๋ ConnectionDetails ์ถ์ํ๋ฅผ ํตํด์ spring boot์์ ์๋์ผ๋ก ์ฐ๊ฒฐ์ ํด์ค๋ค.
spring:
config:
activate:
on-profile: local
jpa:
hibernate:
ddl-auto: create
# spring boot docker compose enable ์ค์
docker:
compose:
enabled: true
lifecycle-management: start_and_stop
stop:
command: down
timeout: 1m
---
# production ํ๊ฒฝ์์๋ docker compose disable
# ์ค์ ์ผ๋ก docker compose๊ฐ ์คํ๋์ง ์๋๋ก ํ๋ค.
# mariadb datasource ์ฐ๊ฒฐ ์ ๋ณด๋ฅผ ์ค์ ํด์ผ ํ ๊ฒ์ด๋ค.
spring:
config:
activate:
on-profile: production
docker:
compose:
enabled: falseYAMLlocal profile์ธ ๊ฒฝ์ฐ์๋ง docker compose๋ฅผ ์ฌ์ฉํ๋๋ก ์ค์ ํ์๋ค.
production profile์ ๊ฒฝ์ฐ์๋ docker compose ๊ฐ ์คํ๋์ง ์๋๋ค. kafka๋ ํ์ฌ spring boot docker compose์์ ์ง์๋์ง ์๋ Container์ด๊ธฐ ๋๋ฌธ์ ConnectionDetails ์ถ์ํ๋ฅผ ํตํ ์๋ ์ฐ๊ฒฐ์ด ๋์ง ์์ผ๋ฏ๋ก ์ฐ๊ฒฐ ์ ๋ณด ์ค์ ์ ํด์ค์ผ ํ๋ค.
Class ์ฝ๋
@Configuration
@Slf4j
@EnableKafka
public class TopicConfig {
public static final String TOPIC_NAME = "test-topic";
@Bean
public NewTopic testTopic() {
return new NewTopic(TOPIC_NAME, 3, CreateTopicsRequest.NO_REPLICATION_FACTOR);
}
}Javakafka์ ‘test-topic’์ด๋ฆ์ ํ ํฝ์ ์์ฑํ๋ค.
@Entity
@Table(name = "Member")
@Getter
@Setter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@ToString
public class Member{
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(name = "name", nullable = false, unique = true)
private String userName;
@Column(name = "age", nullable = false, length = 4)
private Integer age;
public static Member of(Long id) {
return new Member(id);
}
private Member(Long id) {
this.id = id;
}
@Builder
protected Member(Long id, String userName, Integer age) {
this.id = id;
this.userName = userName;
this.age = age;
}
}JavaMember ํ ์ด๋ธ ์ ์ entity ํด๋์ค๋ค.
@Builder
public record MemberDto(@JsonProperty("id") Long id,
@JsonProperty("userName") String userName,
@JsonProperty("age") Integer age) {
public Member entity() {
return Member.builder()
.userName(userName)
.age(age)
.build();
}
}JavaMember entity ๋ณํ์ ์ํ DTO ํด๋์ค๋ค.
@Repository
public interface MemberRepository extends JpaRepository<Member, Long> {
}JavaMember entity ์กฐํ ๋ฐ ์ ์ฅ์ ์ํ repository ํด๋์ค๋ค.
@Slf4j
@Service
@RequiredArgsConstructor
public class ConsumerService {
private final MemberRepository memberRepository;
@KafkaListener(
topics = TopicConfig.TOPIC_NAME,
clientIdPrefix = "topic1-listener",
groupId = "${spring.kafka.consumer.group-id}"
)
public void listen(MemberDto memberDto, ConsumerRecordMetadata metadata) {
log.info("received message: {}", memberDto);
log.info("received topic: {}", metadata.topic());
log.info("received partition: {}", metadata.partition());
log.info("received offset: {}", metadata.offset());
Member saved = memberRepository.save(memberDto.entity());
log.info("saved member {}", saved);
}
}Javakafka ๋ฉ์์ง ์์ ์ ์ํ consumer ํด๋์ค๋ค.
@Service
@Slf4j
@RequiredArgsConstructor
public class ProducerService {
private final KafkaTemplate<String, Object> kafkaTemplate;
public void produce(final MemberDto memberDto) {
String key = UUID.randomUUID().toString();
kafkaTemplate.send(TopicConfig.TOPIC_NAME, key, memberDto)
.whenComplete((result, throwable) -> {
if (throwable != null) {
log.error("fail to send message, {}", throwable.getMessage());
} else {
RecordMetadata metadata = result.getRecordMetadata();
log.info("send message: {}", memberDto);
log.info("send topic: {}", metadata.topic());
log.info("send partition: {}", metadata.partition());
log.info("send offset: {}", metadata.offset());
}
});
}
}Javakafka์ ‘test-topic’์ผ๋ก ๋ฉ์์ง๋ฅผ ์ ์กํ๊ธฐ ์ํ producer ํด๋์ค๋ค.
@RestController
@RequiredArgsConstructor
public class MemberController {
private final MemberRepository memberRepository;
private final ProducerService producerService;
@GetMapping("/member/{id}")
public ResponseEntity<Member> getMember(@PathVariable Long id) {
Optional<Member> optionalMember = memberRepository.findById(id);
Member member = optionalMember.orElseThrow(NoSuchElementException::new);
return ResponseEntity.ok(member);
}
@PostMapping("/member")
public void postMember(@RequestBody MemberDto memberDto) {
producerService.produce(memberDto);
}
}JavaREST API controller ํด๋์ค๋ค.
์ ํ๋ฆฌ์ผ์ด์ ์คํ
spring boot ์์ docker compose๋ฅผ ์คํํด์ผ ํ๊ธฐ ๋๋ฌธ์ ์ ํ๋ฆฌ์ผ์ด์ ์คํ ์ ์ ์๋ ์กฐ๊ฑด์ด ์ถฉ์กฑ๋์ด ์์ด์ผ ํ๋ค.
- docker ์์ง์ด ๊ตฌ๋๋์ด ์์ด์ผ ํ๋ค.
- docker compose ํน์ docker-compose CLI ๋ช ๋ น์ ์คํํ ์ ์์ด์ผ ํ๋ค.
local profile์์ ์ ํ๋ฆฌ์ผ์ด์ ์ ์คํํ๋ฉด ๋ค์๊ณผ ๊ฐ์ด container๊ฐ ์๋์ผ๋ก ์์ฑ๋๋ ๋ก๊ทธ๋ฅผ ํ์ธํ ์ ์๋ค.
[utReader-stderr] o.s.boot.docker.compose.core.DockerCli : Network spring-docker-compose_default Creating
[utReader-stderr] o.s.boot.docker.compose.core.DockerCli : Network spring-docker-compose_default Created
[utReader-stderr] o.s.boot.docker.compose.core.DockerCli : Container spring-docker-compose-zookeeper-1 Creating
[utReader-stderr] o.s.boot.docker.compose.core.DockerCli : Container spring-docker-compose-mariadb-1 Creating
[utReader-stderr] o.s.boot.docker.compose.core.DockerCli : Container spring-docker-compose-mariadb-1 Created
[utReader-stderr] o.s.boot.docker.compose.core.DockerCli : Container spring-docker-compose-zookeeper-1 Created
[utReader-stderr] o.s.boot.docker.compose.core.DockerCli : Container spring-docker-compose-kafka-1 Creating
[utReader-stderr] o.s.boot.docker.compose.core.DockerCli : Container spring-docker-compose-kafka-1 Created
[utReader-stderr] o.s.boot.docker.compose.core.DockerCli : Container spring-docker-compose-zookeeper-1 Starting
[utReader-stderr] o.s.boot.docker.compose.core.DockerCli : Container spring-docker-compose-mariadb-1 Starting
[utReader-stderr] o.s.boot.docker.compose.core.DockerCli : Container spring-docker-compose-zookeeper-1 Started
[utReader-stderr] o.s.boot.docker.compose.core.DockerCli : Container spring-docker-compose-kafka-1 Starting
[utReader-stderr] o.s.boot.docker.compose.core.DockerCli : Container spring-docker-compose-mariadb-1 Started
[utReader-stderr] o.s.boot.docker.compose.core.DockerCli : Container spring-docker-compose-kafka-1 Started
[utReader-stderr] o.s.boot.docker.compose.core.DockerCli : Container spring-docker-compose-mariadb-1 Waiting
[utReader-stderr] o.s.boot.docker.compose.core.DockerCli : Container spring-docker-compose-zookeeper-1 Waiting
[utReader-stderr] o.s.boot.docker.compose.core.DockerCli : Container spring-docker-compose-kafka-1 Waiting
[utReader-stderr] o.s.boot.docker.compose.core.DockerCli : Container spring-docker-compose-mariadb-1 Healthy
[utReader-stderr] o.s.boot.docker.compose.core.DockerCli : Container spring-docker-compose-zookeeper-1 Healthy
[utReader-stderr] o.s.boot.docker.compose.core.DockerCli : Container spring-docker-compose-kafka-1 HealthyJavaํ์ฌ ์คํ ์ค์ธ container ์ํ๋ฅผ ํ์ธํด ๋ณด๋ฉด ๋ค์๊ณผ ๊ฐ์ด container๊ฐ ์์ฑ๋จ์ ํ์ธํ ์ ์๋ค.
mariadb container๋ฅผ ์ฌ์ฉํ๊ธฐ ๋๋ฌธ์ MariaDbJdbcDockerComposeConnectionDetails ํด๋์ค๋ฅผ ๋๋ฒ๊น
ํด๋ณด๋ฉด ๋ค์๊ณผ
๊ฐ์ด host port์ container port ๋งตํ์ด ์๋์ผ๋ก ์ด๋ฃจ์ด์ง๋ค๋ ๊ฒ์ ํ์ธํ ์ ์๋ค.



mariadb container์ port ๋งตํ์ ๋ณด๋ฉด 0.0.0.0:50730 -> 3306/tcp๋ก ํธ์คํธํฌํธ 50730๊ณผ ์ปจํ ์ด๋ํฌํธ 3306์ด ๋งตํ๋ ๊ฒ์ ์ ์ ์๋ค. ๋ํ ๋๋ฒ๊น ์ ํตํด์ portMappings๋ฅผ ์ดํด๋ณด๋ฉด 50730 port์ 3306 port๊ฐ ๋งตํ๋์ด ์ฐ๊ฒฐ ์ ๋ณด๊ฐ ์ค์ ๋ ๊ฒ์ ํ์ธํ ์ ์๋ค. spring boot์์๋ docker compose๋ฅผ ํตํด์ ์์ฑ๋ container์ ๋ํ inspect ์ ๋ณด๋ฅผ ๊ธฐ๋ฐ์ผ๋ก ์ฐ๊ฒฐ ์ ๋ณด ์ค์ ์ด ๋๋ ๊ฒ ์๋๊ฐ ์ถ๋ค.
postman์ ํตํด์ Member ์์ฑ API๋ฅผ ํธ์ถํ๋ค.
๋ก๊ทธ๋ ์๋์ ๊ฐ์ด ์์ฑ๋๋ค.
send message: MemberDto[id=null, userName=test-member, age=100]
send topic: test-topic
send partition: 2
send offset: 0
received message: MemberDto[id=null, userName=test-member, age=100]
received topic: test-topic
received partition: 2
received offset: 0
saved member Member(id=1, userName=test-member, age=100)Javakafka container๋ฅผ ํตํด์ test-topic์ผ๋ก ๋ฉ์์ง๊ฐ ์ก์์ ๋๊ณ DB์ member๊ฐ ์ ์ฅ๋ ๊ฒ์ ์ ์ ์๋ค.
postman์ ํตํด์ Member ์ ๋ณด๋ฅผ ์์ฒญํ๋ค.

์ ์ฒด ์ฝ๋๋ย gitlab spring-docker-compose ์์ ํ์ธํด ๋ณด๊ธฐ ๋ฐ๋๋ค. ์ํ์ฝ๋๋ฅผ ์์ฑํ๋ฉด์ spring boot docker compose๋ฅผ ํตํด์ ์ ๋ง ์์ฝ๊ฒ ๋ก์ปฌ ํ
์คํธ ํ๊ฒฝ์ ๊ตฌ์ถํ ์ ์์๋ค. ์์ง ConnectionDetails ์ถ์ํ๊ฐ ์ง์๋๋ container๊ฐ ๋ง์ง๋ ์์ง๋ง ์์ผ๋ก ์ง์๋๋ container๊ฐ ๋์ด๋ ๊ฒ์ด๋ผ ๊ธฐ๋ํ๋ค.
์ฐธ๊ณ ๋งํฌ
https://spring.io/blog/2023/06/21/docker-compose-support-in-spring-boot-3-1/</a >
https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#features.docker-compose</a >
https://spring.io/blog/2023/06/19/spring-boot-31-connectiondetails-abstraction/</a >