diff --git a/.run/AiServiceApplication.run.xml b/.run/AiServiceApplication.run.xml
new file mode 100644
index 0000000..d03ed94
--- /dev/null
+++ b/.run/AiServiceApplication.run.xml
@@ -0,0 +1,31 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/.run/AnalyticsServiceApplication.run.xml b/.run/AnalyticsServiceApplication.run.xml
new file mode 100644
index 0000000..bf57744
--- /dev/null
+++ b/.run/AnalyticsServiceApplication.run.xml
@@ -0,0 +1,31 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/.run/ContentServiceApplication.run.xml b/.run/ContentServiceApplication.run.xml
new file mode 100644
index 0000000..85d4235
--- /dev/null
+++ b/.run/ContentServiceApplication.run.xml
@@ -0,0 +1,29 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/.run/DistributionServiceApplication.run.xml b/.run/DistributionServiceApplication.run.xml
new file mode 100644
index 0000000..b023d5b
--- /dev/null
+++ b/.run/DistributionServiceApplication.run.xml
@@ -0,0 +1,31 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/.run/EventServiceApplication.run.xml b/.run/EventServiceApplication.run.xml
new file mode 100644
index 0000000..46ef667
--- /dev/null
+++ b/.run/EventServiceApplication.run.xml
@@ -0,0 +1,31 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/.run/UserServiceApplication.run.xml b/.run/UserServiceApplication.run.xml
new file mode 100644
index 0000000..23d83db
--- /dev/null
+++ b/.run/UserServiceApplication.run.xml
@@ -0,0 +1,29 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/design/backend/api/distribution-service-api.yaml b/design/backend/api/distribution-service-api.yaml
index 938b3a8..d47ecb3 100644
--- a/design/backend/api/distribution-service-api.yaml
+++ b/design/backend/api/distribution-service-api.yaml
@@ -11,10 +11,10 @@ info:
- Retry 패턴 및 Fallback 처리
## 배포 채널
- - **우리동네TV**: 영상 콘텐츠 업로드
- - **링고비즈**: 연결음 업데이트
- - **지니TV**: 광고 등록
- - **SNS**: Instagram, Naver Blog, Kakao Channel
+ - **우리동네TV** (URIDONGNETV): 영상 콘텐츠 업로드
+ - **링고비즈** (RINGOBIZ): 연결음 업데이트
+ - **지니TV** (GINITV): 광고 등록
+ - **SNS**: Instagram (INSTAGRAM), Naver Blog (NAVER), Kakao Channel (KAKAO)
## Resilience 패턴
- Circuit Breaker: 채널별 독립적 장애 격리
@@ -79,23 +79,21 @@ paths:
summary: 다중 채널 배포 예시
value:
eventId: "evt-12345"
+ title: "신규 고객 환영 이벤트"
+ description: "신규 고객님을 위한 특별 할인 이벤트"
+ imageUrl: "https://cdn.example.com/images/event-main.jpg"
channels:
- - type: "WOORIDONGNE_TV"
- config:
- radius: "1km"
- timeSlots:
- - "weekday_evening"
- - "weekend_lunch"
- - type: "INSTAGRAM"
- config:
- scheduledTime: "2025-11-01T10:00:00Z"
- - type: "NAVER_BLOG"
- config:
- scheduledTime: "2025-11-01T10:30:00Z"
- contentUrls:
- instagram: "https://cdn.example.com/images/event-instagram.jpg"
- naverBlog: "https://cdn.example.com/images/event-naver.jpg"
- kakaoChannel: "https://cdn.example.com/images/event-kakao.jpg"
+ - "URIDONGNETV"
+ - "INSTAGRAM"
+ - "NAVER"
+ channelSettings:
+ URIDONGNETV:
+ radius: "1km"
+ timeSlot: "evening"
+ INSTAGRAM:
+ scheduledTime: "2025-11-01T10:00:00"
+ NAVER:
+ scheduledTime: "2025-11-01T10:30:00"
responses:
'200':
description: 배포 완료
@@ -107,25 +105,29 @@ paths:
allSuccess:
summary: 모든 채널 배포 성공
value:
- distributionId: "dist-12345"
eventId: "evt-12345"
- status: "COMPLETED"
- completedAt: "2025-11-01T09:00:00Z"
- results:
- - channel: "WOORIDONGNE_TV"
- status: "SUCCESS"
+ success: true
+ channelResults:
+ - channel: "URIDONGNETV"
+ success: true
distributionId: "wtv-uuid-12345"
- estimatedViews: 1000
- message: "배포 완료"
+ estimatedReach: 1000
+ executionTimeMs: 234
- channel: "INSTAGRAM"
- status: "SUCCESS"
- postUrl: "https://instagram.com/p/generated-post-id"
- postId: "ig-post-12345"
- message: "게시 완료"
- - channel: "NAVER_BLOG"
- status: "SUCCESS"
- postUrl: "https://blog.naver.com/store123/generated-post"
- message: "게시 완료"
+ success: true
+ distributionId: "ig-uuid-12345"
+ estimatedReach: 500
+ executionTimeMs: 456
+ - channel: "NAVER"
+ success: true
+ distributionId: "naver-uuid-12345"
+ estimatedReach: 300
+ executionTimeMs: 123
+ successCount: 3
+ failureCount: 0
+ completedAt: "2025-11-01T09:00:00"
+ totalExecutionTimeMs: 1234
+ message: "배포가 성공적으로 완료되었습니다"
'400':
description: 잘못된 요청
content:
@@ -217,67 +219,77 @@ paths:
value:
eventId: "evt-12345"
overallStatus: "COMPLETED"
- completedAt: "2025-11-01T09:00:00Z"
+ startedAt: "2025-11-01T08:58:00"
+ completedAt: "2025-11-01T09:00:00"
channels:
- - channel: "WOORIDONGNE_TV"
+ - channel: "URIDONGNETV"
status: "COMPLETED"
distributionId: "wtv-uuid-12345"
estimatedViews: 1500
- completedAt: "2025-11-01T09:00:00Z"
- - channel: "RINGO_BIZ"
+ completedAt: "2025-11-01T09:00:00"
+ - channel: "RINGOBIZ"
status: "COMPLETED"
- updateTimestamp: "2025-11-01T09:00:00Z"
- - channel: "GENIE_TV"
+ updateTimestamp: "2025-11-01T09:00:00"
+ completedAt: "2025-11-01T09:00:00"
+ - channel: "GINITV"
status: "COMPLETED"
adId: "gtv-uuid-12345"
impressionSchedule:
- "2025-11-01 18:00-20:00"
- "2025-11-02 12:00-14:00"
+ completedAt: "2025-11-01T09:00:00"
- channel: "INSTAGRAM"
status: "COMPLETED"
postUrl: "https://instagram.com/p/generated-post-id"
postId: "ig-post-12345"
- - channel: "NAVER_BLOG"
+ completedAt: "2025-11-01T09:00:00"
+ - channel: "NAVER"
status: "COMPLETED"
postUrl: "https://blog.naver.com/store123/generated-post"
- - channel: "KAKAO_CHANNEL"
+ completedAt: "2025-11-01T09:00:00"
+ - channel: "KAKAO"
status: "COMPLETED"
messageId: "kakao-msg-12345"
+ completedAt: "2025-11-01T09:00:00"
inProgress:
summary: 배포 진행중 상태
value:
eventId: "evt-12345"
overallStatus: "IN_PROGRESS"
- startedAt: "2025-11-01T08:58:00Z"
+ startedAt: "2025-11-01T08:58:00"
channels:
- - channel: "WOORIDONGNE_TV"
+ - channel: "URIDONGNETV"
status: "COMPLETED"
distributionId: "wtv-uuid-12345"
estimatedViews: 1500
+ completedAt: "2025-11-01T08:59:00"
- channel: "INSTAGRAM"
status: "IN_PROGRESS"
progress: 50
- - channel: "NAVER_BLOG"
+ - channel: "NAVER"
status: "PENDING"
partialFailure:
summary: 일부 채널 실패 상태
value:
eventId: "evt-12345"
overallStatus: "PARTIAL_FAILURE"
- completedAt: "2025-11-01T09:00:00Z"
+ startedAt: "2025-11-01T08:58:00"
+ completedAt: "2025-11-01T09:00:00"
channels:
- - channel: "WOORIDONGNE_TV"
+ - channel: "URIDONGNETV"
status: "COMPLETED"
distributionId: "wtv-uuid-12345"
estimatedViews: 1500
+ completedAt: "2025-11-01T08:59:00"
- channel: "INSTAGRAM"
status: "FAILED"
errorMessage: "Instagram API 타임아웃"
retries: 3
- lastRetryAt: "2025-11-01T08:59:30Z"
- - channel: "NAVER_BLOG"
+ lastRetryAt: "2025-11-01T08:59:30"
+ - channel: "NAVER"
status: "COMPLETED"
postUrl: "https://blog.naver.com/store123/generated-post"
+ completedAt: "2025-11-01T09:00:00"
'404':
description: 배포 이력을 찾을 수 없음
content:
@@ -305,196 +317,133 @@ components:
required:
- eventId
- channels
- - contentUrls
properties:
eventId:
type: string
description: 이벤트 ID
example: "evt-12345"
+ title:
+ type: string
+ description: 이벤트 제목
+ example: "신규 고객 환영 이벤트"
+ description:
+ type: string
+ description: 이벤트 설명
+ example: "신규 고객님을 위한 특별 할인 이벤트"
+ imageUrl:
+ type: string
+ description: 이미지 URL (CDN)
+ example: "https://cdn.example.com/images/event-main.jpg"
channels:
type: array
description: 배포할 채널 목록
minItems: 1
items:
- $ref: '#/components/schemas/ChannelConfig'
- contentUrls:
+ type: string
+ enum:
+ - URIDONGNETV
+ - RINGOBIZ
+ - GINITV
+ - INSTAGRAM
+ - NAVER
+ - KAKAO
+ example: ["URIDONGNETV", "INSTAGRAM", "NAVER"]
+ channelSettings:
type: object
- description: 플랫폼별 콘텐츠 URL
- properties:
- wooridongneTV:
- type: string
- description: 우리동네TV 영상 URL (15초)
- example: "https://cdn.example.com/videos/event-15s.mp4"
- ringoBiz:
- type: string
- description: 링고비즈 연결음 파일 URL
- example: "https://cdn.example.com/audio/ringtone.mp3"
- genieTV:
- type: string
- description: 지니TV 광고 영상 URL
- example: "https://cdn.example.com/videos/event-ad.mp4"
- instagram:
- type: string
- description: Instagram 이미지 URL (1080x1080)
- example: "https://cdn.example.com/images/event-instagram.jpg"
- naverBlog:
- type: string
- description: Naver Blog 이미지 URL (800x600)
- example: "https://cdn.example.com/images/event-naver.jpg"
- kakaoChannel:
- type: string
- description: Kakao Channel 이미지 URL (800x800)
- example: "https://cdn.example.com/images/event-kakao.jpg"
-
- ChannelConfig:
- type: object
- required:
- - type
- properties:
- type:
- type: string
- description: 채널 타입
- enum:
- - WOORIDONGNE_TV
- - RINGO_BIZ
- - GENIE_TV
- - INSTAGRAM
- - NAVER_BLOG
- - KAKAO_CHANNEL
- example: "INSTAGRAM"
- config:
- type: object
- description: 채널별 설정 (채널에 따라 다름)
- additionalProperties: true
+ description: 채널별 추가 설정 (Optional)
+ additionalProperties:
+ type: object
+ additionalProperties: true
example:
- scheduledTime: "2025-11-01T10:00:00Z"
- caption: "이벤트 안내"
- hashtags:
- - "이벤트"
- - "할인"
+ URIDONGNETV:
+ radius: "1km"
+ timeSlot: "evening"
+ INSTAGRAM:
+ scheduledTime: "2025-11-01T10:00:00"
DistributionResponse:
type: object
required:
- - distributionId
- eventId
- - status
- - results
+ - success
+ - channelResults
+ - successCount
+ - failureCount
properties:
- distributionId:
- type: string
- description: 배포 ID
- example: "dist-12345"
eventId:
type: string
description: 이벤트 ID
example: "evt-12345"
- status:
- type: string
- description: 전체 배포 상태
- enum:
- - PENDING
- - IN_PROGRESS
- - COMPLETED
- - PARTIAL_FAILURE
- - FAILED
- example: "COMPLETED"
- startedAt:
- type: string
- format: date-time
- description: 배포 시작 시각
- example: "2025-11-01T08:59:00Z"
+ success:
+ type: boolean
+ description: 배포 성공 여부 (모든 채널 또는 일부 채널 성공)
+ example: true
+ channelResults:
+ type: array
+ description: 채널별 배포 결과
+ items:
+ $ref: '#/components/schemas/ChannelDistributionResult'
+ successCount:
+ type: integer
+ description: 성공한 채널 수
+ example: 3
+ failureCount:
+ type: integer
+ description: 실패한 채널 수
+ example: 0
completedAt:
type: string
format: date-time
description: 배포 완료 시각
- example: "2025-11-01T09:00:00Z"
- results:
- type: array
- description: 채널별 배포 결과
- items:
- $ref: '#/components/schemas/ChannelResult'
+ example: "2025-11-01T09:00:00"
+ totalExecutionTimeMs:
+ type: integer
+ format: int64
+ description: 전체 배포 소요 시간 (ms)
+ example: 1234
+ message:
+ type: string
+ description: 메시지
+ example: "배포가 성공적으로 완료되었습니다"
- ChannelResult:
+ ChannelDistributionResult:
type: object
required:
- channel
- - status
+ - success
properties:
channel:
type: string
description: 채널 타입
enum:
- - WOORIDONGNE_TV
- - RINGO_BIZ
- - GENIE_TV
+ - URIDONGNETV
+ - RINGOBIZ
+ - GINITV
- INSTAGRAM
- - NAVER_BLOG
- - KAKAO_CHANNEL
+ - NAVER
+ - KAKAO
example: "INSTAGRAM"
- status:
- type: string
- description: 채널별 배포 상태
- enum:
- - PENDING
- - IN_PROGRESS
- - SUCCESS
- - FAILED
- example: "SUCCESS"
+ success:
+ type: boolean
+ description: 배포 성공 여부
+ example: true
distributionId:
type: string
- description: 채널별 배포 ID (우리동네TV, 지니TV)
- example: "wtv-uuid-12345"
- estimatedViews:
+ description: 배포 ID (성공 시)
+ example: "dist-uuid-12345"
+ estimatedReach:
type: integer
- description: 예상 노출 수 (우리동네TV, 지니TV)
+ description: 예상 노출 수 (성공 시)
example: 1500
- updateTimestamp:
- type: string
- format: date-time
- description: 업데이트 완료 시각 (링고비즈)
- example: "2025-11-01T09:00:00Z"
- adId:
- type: string
- description: 광고 ID (지니TV)
- example: "gtv-uuid-12345"
- impressionSchedule:
- type: array
- description: 노출 스케줄 (지니TV)
- items:
- type: string
- example:
- - "2025-11-01 18:00-20:00"
- - "2025-11-02 12:00-14:00"
- postUrl:
- type: string
- description: 게시물 URL (Instagram, Naver Blog)
- example: "https://instagram.com/p/generated-post-id"
- postId:
- type: string
- description: 게시물 ID (Instagram)
- example: "ig-post-12345"
- messageId:
- type: string
- description: 메시지 ID (Kakao Channel)
- example: "kakao-msg-12345"
- message:
- type: string
- description: 결과 메시지
- example: "배포 완료"
errorMessage:
type: string
- description: 오류 메시지 (실패 시)
+ description: 에러 메시지 (실패 시)
example: "Instagram API 타임아웃"
- retries:
+ executionTimeMs:
type: integer
- description: 재시도 횟수
- example: 0
- lastRetryAt:
- type: string
- format: date-time
- description: 마지막 재시도 시각
- example: "2025-11-01T08:59:30Z"
+ format: int64
+ description: 배포 소요 시간 (ms)
+ example: 234
DistributionStatusResponse:
type: object
@@ -544,12 +493,12 @@ components:
type: string
description: 채널 타입
enum:
- - WOORIDONGNE_TV
- - RINGO_BIZ
- - GENIE_TV
+ - URIDONGNETV
+ - RINGOBIZ
+ - GINITV
- INSTAGRAM
- - NAVER_BLOG
- - KAKAO_CHANNEL
+ - NAVER
+ - KAKAO
example: "INSTAGRAM"
status:
type: string
@@ -569,7 +518,7 @@ components:
distributionId:
type: string
description: 채널별 배포 ID
- example: "wtv-uuid-12345"
+ example: "dist-uuid-12345"
estimatedViews:
type: integer
description: 예상 노출 수
@@ -578,35 +527,35 @@ components:
type: string
format: date-time
description: 업데이트 완료 시각
- example: "2025-11-01T09:00:00Z"
+ example: "2025-11-01T09:00:00"
adId:
type: string
- description: 광고 ID
+ description: 광고 ID (지니TV)
example: "gtv-uuid-12345"
impressionSchedule:
type: array
- description: 노출 스케줄
+ description: 노출 스케줄 (지니TV)
items:
type: string
example:
- "2025-11-01 18:00-20:00"
postUrl:
type: string
- description: 게시물 URL
+ description: 게시물 URL (Instagram, Naver Blog)
example: "https://instagram.com/p/generated-post-id"
postId:
type: string
- description: 게시물 ID
+ description: 게시물 ID (Instagram)
example: "ig-post-12345"
messageId:
type: string
- description: 메시지 ID
+ description: 메시지 ID (Kakao Channel)
example: "kakao-msg-12345"
completedAt:
type: string
format: date-time
description: 완료 시각
- example: "2025-11-01T09:00:00Z"
+ example: "2025-11-01T09:00:00"
errorMessage:
type: string
description: 오류 메시지
@@ -619,7 +568,7 @@ components:
type: string
format: date-time
description: 마지막 재시도 시각
- example: "2025-11-01T08:59:30Z"
+ example: "2025-11-01T08:59:30"
ErrorResponse:
type: object
diff --git a/distribution-service/.run/distribution-service.run.xml b/distribution-service/.run/distribution-service.run.xml
new file mode 100644
index 0000000..2736380
--- /dev/null
+++ b/distribution-service/.run/distribution-service.run.xml
@@ -0,0 +1,51 @@
+
+
+
+
+
+
+
+
+
+
+
+
+ true
+ true
+
+
+
+
+
+
+
+
+
+
+
+
+ false
+ false
+
+
+
\ No newline at end of file
diff --git a/distribution-service/src/main/java/com/kt/distribution/DistributionApplication.java b/distribution-service/src/main/java/com/kt/distribution/DistributionApplication.java
new file mode 100644
index 0000000..2534d29
--- /dev/null
+++ b/distribution-service/src/main/java/com/kt/distribution/DistributionApplication.java
@@ -0,0 +1,23 @@
+package com.kt.distribution;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.cloud.openfeign.EnableFeignClients;
+import org.springframework.kafka.annotation.EnableKafka;
+
+/**
+ * Distribution Service Application
+ * 다중 채널 배포 관리 서비스
+ *
+ * @author System Architect
+ * @since 2025-10-23
+ */
+@SpringBootApplication
+@EnableKafka
+@EnableFeignClients
+public class DistributionApplication {
+
+ public static void main(String[] args) {
+ SpringApplication.run(DistributionApplication.class, args);
+ }
+}
diff --git a/distribution-service/src/main/java/com/kt/distribution/adapter/AbstractChannelAdapter.java b/distribution-service/src/main/java/com/kt/distribution/adapter/AbstractChannelAdapter.java
new file mode 100644
index 0000000..c0bebce
--- /dev/null
+++ b/distribution-service/src/main/java/com/kt/distribution/adapter/AbstractChannelAdapter.java
@@ -0,0 +1,86 @@
+package com.kt.distribution.adapter;
+
+import com.kt.distribution.dto.ChannelDistributionResult;
+import com.kt.distribution.dto.DistributionRequest;
+import io.github.resilience4j.bulkhead.annotation.Bulkhead;
+import io.github.resilience4j.circuitbreaker.annotation.CircuitBreaker;
+import io.github.resilience4j.retry.annotation.Retry;
+import lombok.extern.slf4j.Slf4j;
+
+/**
+ * Abstract Channel Adapter
+ * 공통 로직 및 Resilience4j 적용
+ *
+ * @author System Architect
+ * @since 2025-10-23
+ */
+@Slf4j
+public abstract class AbstractChannelAdapter implements ChannelAdapter {
+
+ /**
+ * 채널로 배포 실행 (Resilience4j 적용)
+ *
+ * @param request DistributionRequest
+ * @return ChannelDistributionResult
+ */
+ @Override
+ @CircuitBreaker(name = "channelApi", fallbackMethod = "fallback")
+ @Retry(name = "channelApi")
+ @Bulkhead(name = "channelApi")
+ public ChannelDistributionResult distribute(DistributionRequest request) {
+ long startTime = System.currentTimeMillis();
+
+ try {
+ log.info("Starting distribution to channel: {}, eventId: {}",
+ getChannelType(), request.getEventId());
+
+ // 실제 외부 API 호출 (구현체에서 구현)
+ ChannelDistributionResult result = executeDistribution(request);
+ result.setExecutionTimeMs(System.currentTimeMillis() - startTime);
+
+ log.info("Distribution completed successfully: channel={}, eventId={}, executionTime={}ms",
+ getChannelType(), request.getEventId(), result.getExecutionTimeMs());
+
+ return result;
+
+ } catch (Exception e) {
+ long executionTime = System.currentTimeMillis() - startTime;
+ log.error("Distribution failed: channel={}, eventId={}, error={}",
+ getChannelType(), request.getEventId(), e.getMessage(), e);
+
+ return ChannelDistributionResult.builder()
+ .channel(getChannelType())
+ .success(false)
+ .errorMessage(e.getMessage())
+ .executionTimeMs(executionTime)
+ .build();
+ }
+ }
+
+ /**
+ * 실제 외부 API 호출 로직 (구현체에서 구현)
+ *
+ * @param request DistributionRequest
+ * @return ChannelDistributionResult
+ */
+ protected abstract ChannelDistributionResult executeDistribution(DistributionRequest request);
+
+ /**
+ * Fallback 메서드 (Circuit Breaker Open 시)
+ *
+ * @param request DistributionRequest
+ * @param throwable Throwable
+ * @return ChannelDistributionResult
+ */
+ protected ChannelDistributionResult fallback(DistributionRequest request, Throwable throwable) {
+ log.warn("Fallback triggered for channel: {}, eventId: {}, reason: {}",
+ getChannelType(), request.getEventId(), throwable.getMessage());
+
+ return ChannelDistributionResult.builder()
+ .channel(getChannelType())
+ .success(false)
+ .errorMessage("Circuit Breaker Open: " + throwable.getMessage())
+ .executionTimeMs(0)
+ .build();
+ }
+}
diff --git a/distribution-service/src/main/java/com/kt/distribution/adapter/ChannelAdapter.java b/distribution-service/src/main/java/com/kt/distribution/adapter/ChannelAdapter.java
new file mode 100644
index 0000000..bfedfc7
--- /dev/null
+++ b/distribution-service/src/main/java/com/kt/distribution/adapter/ChannelAdapter.java
@@ -0,0 +1,30 @@
+package com.kt.distribution.adapter;
+
+import com.kt.distribution.dto.ChannelDistributionResult;
+import com.kt.distribution.dto.ChannelType;
+import com.kt.distribution.dto.DistributionRequest;
+
+/**
+ * Channel Adapter Interface
+ * 각 채널별 배포 API를 호출하는 인터페이스
+ *
+ * @author System Architect
+ * @since 2025-10-23
+ */
+public interface ChannelAdapter {
+
+ /**
+ * 지원하는 채널 타입
+ *
+ * @return ChannelType
+ */
+ ChannelType getChannelType();
+
+ /**
+ * 채널로 배포 실행
+ *
+ * @param request DistributionRequest
+ * @return ChannelDistributionResult
+ */
+ ChannelDistributionResult distribute(DistributionRequest request);
+}
diff --git a/distribution-service/src/main/java/com/kt/distribution/adapter/GiniTvAdapter.java b/distribution-service/src/main/java/com/kt/distribution/adapter/GiniTvAdapter.java
new file mode 100644
index 0000000..655d9a6
--- /dev/null
+++ b/distribution-service/src/main/java/com/kt/distribution/adapter/GiniTvAdapter.java
@@ -0,0 +1,45 @@
+package com.kt.distribution.adapter;
+
+import com.kt.distribution.dto.ChannelDistributionResult;
+import com.kt.distribution.dto.ChannelType;
+import com.kt.distribution.dto.DistributionRequest;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.stereotype.Component;
+
+import java.util.UUID;
+
+/**
+ * 지니TV Adapter
+ * 지니TV 광고 등록 API 호출
+ *
+ * @author System Architect
+ * @since 2025-10-23
+ */
+@Slf4j
+@Component
+public class GiniTvAdapter extends AbstractChannelAdapter {
+
+ @Value("${channel.apis.ginitv.url}")
+ private String apiUrl;
+
+ @Override
+ public ChannelType getChannelType() {
+ return ChannelType.GINITV;
+ }
+
+ @Override
+ protected ChannelDistributionResult executeDistribution(DistributionRequest request) {
+ log.debug("Calling GiniTV API: url={}, eventId={}", apiUrl, request.getEventId());
+
+ // TODO: 실제 API 호출 (현재는 Mock)
+ String distributionId = "GTIV-" + UUID.randomUUID().toString();
+
+ return ChannelDistributionResult.builder()
+ .channel(ChannelType.GINITV)
+ .success(true)
+ .distributionId(distributionId)
+ .estimatedReach(10000) // TV 광고 노출 수
+ .build();
+ }
+}
diff --git a/distribution-service/src/main/java/com/kt/distribution/adapter/InstagramAdapter.java b/distribution-service/src/main/java/com/kt/distribution/adapter/InstagramAdapter.java
new file mode 100644
index 0000000..3b98443
--- /dev/null
+++ b/distribution-service/src/main/java/com/kt/distribution/adapter/InstagramAdapter.java
@@ -0,0 +1,45 @@
+package com.kt.distribution.adapter;
+
+import com.kt.distribution.dto.ChannelDistributionResult;
+import com.kt.distribution.dto.ChannelType;
+import com.kt.distribution.dto.DistributionRequest;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.stereotype.Component;
+
+import java.util.UUID;
+
+/**
+ * Instagram Adapter
+ * Instagram 포스팅 API 호출
+ *
+ * @author System Architect
+ * @since 2025-10-23
+ */
+@Slf4j
+@Component
+public class InstagramAdapter extends AbstractChannelAdapter {
+
+ @Value("${channel.apis.instagram.url}")
+ private String apiUrl;
+
+ @Override
+ public ChannelType getChannelType() {
+ return ChannelType.INSTAGRAM;
+ }
+
+ @Override
+ protected ChannelDistributionResult executeDistribution(DistributionRequest request) {
+ log.debug("Calling Instagram API: url={}, eventId={}", apiUrl, request.getEventId());
+
+ // TODO: 실제 API 호출 (현재는 Mock)
+ String distributionId = "INSTA-" + UUID.randomUUID().toString();
+
+ return ChannelDistributionResult.builder()
+ .channel(ChannelType.INSTAGRAM)
+ .success(true)
+ .distributionId(distributionId)
+ .estimatedReach(3000) // 팔로워 수 기반 예상 노출
+ .build();
+ }
+}
diff --git a/distribution-service/src/main/java/com/kt/distribution/adapter/KakaoAdapter.java b/distribution-service/src/main/java/com/kt/distribution/adapter/KakaoAdapter.java
new file mode 100644
index 0000000..68c7e06
--- /dev/null
+++ b/distribution-service/src/main/java/com/kt/distribution/adapter/KakaoAdapter.java
@@ -0,0 +1,45 @@
+package com.kt.distribution.adapter;
+
+import com.kt.distribution.dto.ChannelDistributionResult;
+import com.kt.distribution.dto.ChannelType;
+import com.kt.distribution.dto.DistributionRequest;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.stereotype.Component;
+
+import java.util.UUID;
+
+/**
+ * Kakao Channel Adapter
+ * Kakao Channel 포스팅 API 호출
+ *
+ * @author System Architect
+ * @since 2025-10-23
+ */
+@Slf4j
+@Component
+public class KakaoAdapter extends AbstractChannelAdapter {
+
+ @Value("${channel.apis.kakao.url}")
+ private String apiUrl;
+
+ @Override
+ public ChannelType getChannelType() {
+ return ChannelType.KAKAO;
+ }
+
+ @Override
+ protected ChannelDistributionResult executeDistribution(DistributionRequest request) {
+ log.debug("Calling Kakao API: url={}, eventId={}", apiUrl, request.getEventId());
+
+ // TODO: 실제 API 호출 (현재는 Mock)
+ String distributionId = "KAKAO-" + UUID.randomUUID().toString();
+
+ return ChannelDistributionResult.builder()
+ .channel(ChannelType.KAKAO)
+ .success(true)
+ .distributionId(distributionId)
+ .estimatedReach(4000) // 채널 친구 수 기반
+ .build();
+ }
+}
diff --git a/distribution-service/src/main/java/com/kt/distribution/adapter/NaverAdapter.java b/distribution-service/src/main/java/com/kt/distribution/adapter/NaverAdapter.java
new file mode 100644
index 0000000..0d7f44e
--- /dev/null
+++ b/distribution-service/src/main/java/com/kt/distribution/adapter/NaverAdapter.java
@@ -0,0 +1,45 @@
+package com.kt.distribution.adapter;
+
+import com.kt.distribution.dto.ChannelDistributionResult;
+import com.kt.distribution.dto.ChannelType;
+import com.kt.distribution.dto.DistributionRequest;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.stereotype.Component;
+
+import java.util.UUID;
+
+/**
+ * Naver Blog Adapter
+ * Naver Blog 포스팅 API 호출
+ *
+ * @author System Architect
+ * @since 2025-10-23
+ */
+@Slf4j
+@Component
+public class NaverAdapter extends AbstractChannelAdapter {
+
+ @Value("${channel.apis.naver.url}")
+ private String apiUrl;
+
+ @Override
+ public ChannelType getChannelType() {
+ return ChannelType.NAVER;
+ }
+
+ @Override
+ protected ChannelDistributionResult executeDistribution(DistributionRequest request) {
+ log.debug("Calling Naver API: url={}, eventId={}", apiUrl, request.getEventId());
+
+ // TODO: 실제 API 호출 (현재는 Mock)
+ String distributionId = "NAVER-" + UUID.randomUUID().toString();
+
+ return ChannelDistributionResult.builder()
+ .channel(ChannelType.NAVER)
+ .success(true)
+ .distributionId(distributionId)
+ .estimatedReach(2000) // 블로그 방문자 수 기반
+ .build();
+ }
+}
diff --git a/distribution-service/src/main/java/com/kt/distribution/adapter/RingoBizAdapter.java b/distribution-service/src/main/java/com/kt/distribution/adapter/RingoBizAdapter.java
new file mode 100644
index 0000000..8ec0634
--- /dev/null
+++ b/distribution-service/src/main/java/com/kt/distribution/adapter/RingoBizAdapter.java
@@ -0,0 +1,45 @@
+package com.kt.distribution.adapter;
+
+import com.kt.distribution.dto.ChannelDistributionResult;
+import com.kt.distribution.dto.ChannelType;
+import com.kt.distribution.dto.DistributionRequest;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.stereotype.Component;
+
+import java.util.UUID;
+
+/**
+ * 링고비즈 Adapter
+ * 링고비즈 연결음 업데이트 API 호출
+ *
+ * @author System Architect
+ * @since 2025-10-23
+ */
+@Slf4j
+@Component
+public class RingoBizAdapter extends AbstractChannelAdapter {
+
+ @Value("${channel.apis.ringobiz.url}")
+ private String apiUrl;
+
+ @Override
+ public ChannelType getChannelType() {
+ return ChannelType.RINGOBIZ;
+ }
+
+ @Override
+ protected ChannelDistributionResult executeDistribution(DistributionRequest request) {
+ log.debug("Calling RingoBiz API: url={}, eventId={}", apiUrl, request.getEventId());
+
+ // TODO: 실제 API 호출 (현재는 Mock)
+ String distributionId = "RBIZ-" + UUID.randomUUID().toString();
+
+ return ChannelDistributionResult.builder()
+ .channel(ChannelType.RINGOBIZ)
+ .success(true)
+ .distributionId(distributionId)
+ .estimatedReach(1000) // 연결음 사용자 수
+ .build();
+ }
+}
diff --git a/distribution-service/src/main/java/com/kt/distribution/adapter/UriDongNeTvAdapter.java b/distribution-service/src/main/java/com/kt/distribution/adapter/UriDongNeTvAdapter.java
new file mode 100644
index 0000000..41fa264
--- /dev/null
+++ b/distribution-service/src/main/java/com/kt/distribution/adapter/UriDongNeTvAdapter.java
@@ -0,0 +1,72 @@
+package com.kt.distribution.adapter;
+
+import com.kt.distribution.dto.ChannelDistributionResult;
+import com.kt.distribution.dto.ChannelType;
+import com.kt.distribution.dto.DistributionRequest;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.stereotype.Component;
+import org.springframework.web.client.RestTemplate;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.UUID;
+
+/**
+ * 우리동네TV Adapter
+ * 우리동네TV API 호출
+ *
+ * @author System Architect
+ * @since 2025-10-23
+ */
+@Slf4j
+@Component
+public class UriDongNeTvAdapter extends AbstractChannelAdapter {
+
+ @Value("${channel.apis.uridongnetv.url}")
+ private String apiUrl;
+
+ private final RestTemplate restTemplate = new RestTemplate();
+
+ @Override
+ public ChannelType getChannelType() {
+ return ChannelType.URIDONGNETV;
+ }
+
+ @Override
+ protected ChannelDistributionResult executeDistribution(DistributionRequest request) {
+ log.debug("Calling UriDongNeTV API: url={}, eventId={}", apiUrl, request.getEventId());
+
+ // 외부 API 호출 준비
+ Map payload = new HashMap<>();
+ payload.put("eventId", request.getEventId());
+ payload.put("title", request.getTitle());
+ payload.put("videoUrl", request.getImageUrl()); // 이미지를 영상으로 변환한 URL
+ payload.put("radius", getChannelSetting(request, "radius", "500m"));
+ payload.put("timeSlot", getChannelSetting(request, "timeSlot", "evening"));
+
+ // TODO: 실제 API 호출 (현재는 Mock)
+ // ResponseEntity