블로그

.NET 레거시를 건드리지 않고 외부 VOD 솔루션을 연동한 방법

동영상 광고 타입을 고려하지 않은 레거시 .NET 광고 시스템에 Shoplive VOD 솔루션을 연동하면서, 레거시 안정성에 영향 없이 수십억 원 규모의 신규 광고 상품을 런칭한 과정을 공유합니다.

Node.js.NETWebhookGmarket레거시

참고: 이 글의 코드는 실제 업무에서 경험한 내용을 바탕으로 개념적으로 재작성한 것입니다. 실제 회사 코드와는 무관합니다.

연동 흐름

광고주동영상 소재 업로드 .NET 광고 어드민 Shoplive API동영상 콘텐츠 생성 Shoplive인코딩 처리 인코딩 완료Webhook 소재 상태: READY광고 집행 가능

들어가며

레거시 시스템에 새 기능을 추가하는 작업은 항상 긴장됩니다. 특히 수천 개의 광고 상품이 실시간으로 노출되고 있는 프로덕션 .NET 시스템에서는 더욱 그렇습니다.

이 글은 이미지 광고만을 위해 설계된 레거시 광고 어드민 시스템에, 동영상(VOD) 광고 상품을 최소한의 영향으로 추가한 경험을 공유합니다. 결과적으로 이 작업으로 Gmarket에 새로운 동영상 광고 상품이 출시됐고 최소 수십억 원 규모의 신규 매출에 기여했습니다.


문제 정의: 무엇이 어려웠나

기존 시스템의 한계

기존 .NET 광고 어드민 시스템은 이미지 배너 광고를 전제로 설계되어 있었습니다. 데이터 모델부터 UI까지 모든 것이 이미지 소재(Creative)를 기준으로 만들어졌습니다.

동영상 광고는 근본적으로 다릅니다:

  • 파일 크기: 이미지는 수백 KB, 동영상은 수십~수백 MB
  • 처리 방식: 이미지는 바로 쓸 수 있지만, 동영상은 인코딩/트랜스코딩 과정이 필요
  • 메타데이터: 재생 시간, 해상도, 코덱 정보 등 이미지에는 없는 속성들
  • 상태 관리: 동영상 처리가 완료됐는지, 실패했는지 외부 시스템에서 확인 필요

왜 레거시를 완전히 바꾸지 않았나

가장 간단한 해법은 새로운 동영상 광고 시스템을 별도로 만드는 것이겠지만, 현실적으로 불가능했습니다. 광고주 관리, 상품 등록, 심사 프로세스 등 이미 잘 동작하는 수십 개의 기능을 다시 만들어야 하는 공수가 너무 컸습니다.

결론은 기존 레거시를 최소한으로 수정하고, 외부 VOD 솔루션(Shoplive)과 연동하는 방향이었습니다.


설계: 웹훅 기반 단방향 동기화

핵심 설계 결정은 웹훅(Webhook) 기반 단방향 동기화였습니다.

[광고주] → [.NET 어드민] → [Shoplive API] → [동영상 업로드/인코딩]
                                    ↓
                              [인코딩 완료 웹훅]
                                    ↓
                            [.NET 어드민 상태 업데이트]

레거시 .NET 시스템이 Shoplive의 상태를 주기적으로 폴링(Polling)하는 대신, Shoplive가 처리 완료 후 웹훅으로 알려주는 방식을 선택했습니다. 이 방식의 장점은:

  1. 레거시 시스템의 부하 최소화: 폴링 없이 이벤트 기반으로 동작
  2. 실시간에 가까운 상태 동기화: 인코딩 완료 즉시 반영
  3. 결합도 최소화: 레거시가 외부 시스템에 의존하지 않음

구현 과정

1. .NET 어드민 최소 수정

레거시 .NET 시스템에는 최소한의 변경만 가했습니다:

  • 광고 소재(Creative) 타입 필드에 VIDEO 추가
  • 동영상 파일 업로드 처리 로직 추가
  • Shoplive API 호출 모듈 추가 (콘텐츠 생성, 정보 동기화)
  • 동영상 처리 상태 필드 추가 (PENDING / PROCESSING / READY / FAILED)
// .NET 레거시에 추가한 Creative 타입 확장
public enum CreativeType 
{
    IMAGE = 1,
    VIDEO = 2  // 신규 추가
}

// Shoplive 연동 서비스 추가
public class ShopliveService 
{
    public async Task<string> CreateContent(VideoCreativeDto dto)
    {
        var response = await _httpClient.PostAsync(
            "/api/v1/contents",
            JsonContent.Create(dto)
        );
        // 외부 콘텐츠 ID 반환
        return (await response.Content.ReadFromJsonAsync<ShopliveResponse>()).ContentId;
    }
}

2. 연동 안정화: 실패 처리가 핵심

외부 API 연동에서 가장 중요한 것은 실패 처리입니다. 네트워크 오류, Shoplive 서버 일시 장애, 타임아웃 등 다양한 이유로 연동이 실패할 수 있습니다.

public async Task SyncVideoStatus(string creativeId, string shopliveContentId)
{
    const int maxRetries = 3;
    for (int attempt = 0; attempt < maxRetries; attempt++)
    {
        try
        {
            var status = await _shopliveService.GetContentStatus(shopliveContentId);
            await UpdateCreativeStatus(creativeId, status);
            return;
        }
        catch (HttpRequestException ex) when (attempt < maxRetries - 1)
        {
            _logger.LogWarning("Shoplive 연동 실패, 재시도 {attempt}: {message}", 
                attempt + 1, ex.Message);
            await Task.Delay(TimeSpan.FromSeconds(Math.Pow(2, attempt))); // Exponential backoff
        }
    }
    // 최종 실패 시 알람
    await _alertService.SendAlert($"Shoplive 동기화 최종 실패: {creativeId}");
}

3. 광고 상세 조회 시 실시간 상태 반영

광고주가 등록한 동영상 광고를 조회할 때, DB의 상태와 실제 Shoplive의 처리 상태가 다를 수 있습니다. 이를 방지하기 위해 상세 조회 시 Shoplive의 실제 상태를 확인하고 내부 상태를 최신화했습니다.

public async Task<CreativeDetailDto> GetCreativeDetail(string creativeId)
{
    var creative = await _repository.GetById(creativeId);
    
    if (creative.Type == CreativeType.VIDEO && 
        creative.Status != VideoStatus.READY)
    {
        // 실시간 상태 확인 및 업데이트
        var latestStatus = await _shopliveService.GetContentStatus(creative.ShopliveContentId);
        if (latestStatus != creative.Status)
        {
            await UpdateCreativeStatus(creativeId, latestStatus);
            creative.Status = latestStatus;
        }
    }
    
    return creative.ToDetailDto();
}

결과

레거시 시스템 안정성을 유지하면서:

  • 동영상 광고 상품 신규 런칭 성공
  • 런칭 이후 최소 수십억 원 규모의 신규 광고 매출 기여
  • 외부 VOD 솔루션 연동 과정에서 발생한 실패율 0% 유지 (재시도 로직 덕분)

마치며

레거시 시스템은 "고치기 무서운 것"이 아니라 "영향 범위를 정확히 파악하고 최소한으로 개입해야 하는 것"입니다.

이 프로젝트에서 배운 가장 큰 교훈은 영향 범위를 철저히 정의하는 것이었습니다. 어디까지 건드리고 어디서부터는 새로 만드는지의 경계를 명확히 하고, 그 경계를 철저히 지켰기 때문에 레거시 안정성을 해치지 않고 신규 기능을 추가할 수 있었습니다.