Skip to content

마이크로프론트엔드 배포 마무리와 전략

선택적 배포의 중요성을 종합 정리하고, 컴포넌트 인터페이스를 단순하게 유지해야 하는 설계 원칙과, 배포 시나리오별 전략 및 롤백 전략을 체계적으로 학습한다.

학습 목표

  • 마이크로프론트엔드에서 선택적 배포가 왜 핵심인지 종합적으로 이해한다
  • 공유 컴포넌트 인터페이스를 단순하게 유지해야 하는 구체적 이유를 파악한다
  • 배포 시나리오별 전략을 정리하고 적절한 전략을 선택할 수 있다
  • 롤백 전략을 수립하여 배포 실패 시 빠르게 복구할 수 있다

1. 선택적 배포의 중요성

마이크로프론트엔드의 핵심 가치는 독립 배포다. 하나의 팀이 자신의 서비스를 다른 팀의 배포 일정과 무관하게 배포할 수 있어야 한다. 이것이 불가능하면 마이크로프론트엔드 아키텍처를 도입한 의미가 퇴색된다.

전체 배포 vs 선택적 배포의 조직 영향

측면전체 배포선택적 배포
배포 주기모든 팀이 합의한 일정에 종속각 팀 독립적으로 배포
배포 위험도모든 서비스가 동시에 변경변경 범위가 좁아 위험 감소
장애 격리하나의 장애가 전체에 영향문제된 서비스만 롤백
릴리스 속도가장 느린 팀에 맞춤각 팀 자체 속도로 릴리스
조직 자율성낮음 (조율 비용 높음)높음 (독립적 의사결정)

현실에서의 트레이드오프

실무에서는 공유 패키지(UIKit 등) 변경 시 전체 배포가 필요한 상황을 완전히 피할 수 없다. 핵심은 그런 상황의 빈도를 최소화하는 것이다.


2. 컴포넌트 인터페이스를 단순하게 유지하는 이유

공유 컴포넌트의 인터페이스(Props)가 복잡해지면, 변경 시 영향 범위가 기하급수적으로 커진다. 인터페이스를 단순하게 유지하는 것은 배포 전략의 근간이다.

인터페이스 복잡도와 배포 영향의 관계

인터페이스 설계 원칙

1. 옵셔널 확장 우선 원칙

기존 인터페이스에 새 기능을 추가할 때, 필수(required) Props 대신 옵셔널(optional) Props로 추가한다.

typescript
// 나쁜 예: 필수 Props 추가 → 모든 소비자 코드 수정 필요
interface ProfileProps {
  user: UserType;
  showBadge: boolean;    // 필수 → 기존 소비자 빌드 에러
  badgeType: string;     // 필수 → 기존 소비자 빌드 에러
}

// 좋은 예: 옵셔널 Props 추가 → 기존 소비자 영향 없음
interface ProfileProps {
  user: UserType;
  showBadge?: boolean;   // 옵셔널 → 기존 소비자 그대로 동작
  badgeType?: string;    // 옵셔널 → 기존 소비자 그대로 동작
}

2. 합성(Composition) 패턴 활용

단일 컴포넌트에 모든 기능을 넣지 말고, 작은 컴포넌트를 합성하는 구조로 설계한다.

typescript
// 나쁜 예: 하나의 거대한 컴포넌트
<UserCard
  showProfile showStats showBadge showActions
  onFollow={...} onMessage={...} onBlock={...}
  statsLayout="horizontal" badgePosition="top-right"
/>

// 좋은 예: 합성 가능한 작은 컴포넌트
<UserCard>
  <UserCard.Profile user={user} />
  <UserCard.Stats layout="horizontal" />
  <UserCard.Badge position="top-right" />
  <UserCard.Actions>
    <FollowButton onClick={...} />
  </UserCard.Actions>
</UserCard>

3. 프래그먼트 컨테이너 분리

UI 컴포넌트와 비즈니스 로직을 분리하여, UI 변경이 로직에 영향을 주지 않도록 한다.

typescript
// UIKit: 순수 UI 컴포넌트 (인터페이스 안정)
export const Profile: React.FC<{ user: UserType }> = ({ user }) => (
  <div className={styles.wrapper}>
    <img src={user.profileImageUrl} />
    <span>{user.name}</span>
  </div>
);

// 각 마이크로앱: 컨테이너에서 데이터 페칭
const ProfileContainer: React.FC = () => {
  const [user, setUser] = useState<UserType | null>(null);
  useEffect(() => { getUser().then(setUser); }, []);
  return user ? <Profile user={user} /> : <Skeleton />;
};

3. 배포 시나리오별 전략

시나리오 종합 매트릭스

시나리오변경 대상배포 전략다운타임롤백 난이도
마이크로앱 버그 수정단일 앱 내부해당 앱만 배포없음낮음
프래그먼트 기능 추가단일 프래그먼트해당 프래그먼트만 배포없음낮음
UIKit 컴포넌트 추가패키지 + 소비 앱ShareScope 분리 + 점진 배포없음중간
UIKit 인터페이스 변경패키지 + 모든 소비 앱전체 빌드 + 순차 배포짧음높음
Shell 라우팅 변경Shell + 새 앱Shell 배포 후 앱 배포짧음중간
인프라 엔드포인트 변경환경 변수환경별 .env 교체 + 전체 빌드없음낮음

점진적 배포 (Incremental Deployment)

UIKit 같은 공유 패키지가 변경되었을 때, 모든 앱을 한 번에 배포하지 않고 순차적으로 배포한다.

이 전략의 핵심은 각 단계에서 문제가 발생하면 해당 앱만 롤백하고, 나머지 앱은 영향을 받지 않는다는 것이다.


4. 롤백 전략

배포 후 문제가 발견되면 빠르게 이전 상태로 복구해야 한다. 마이크로프론트엔드에서 롤백은 기존 모놀리식보다 세밀하게 수행할 수 있다.

롤백 방식 비교

롤백 방식동작소요 시간적용 대상
CDN 아티팩트 교체이전 버전 dist/를 CDN에 재배포수 초단일 앱/프래그먼트
CDN 캐시 무효화CloudFront Invalidation 실행수 분CDN 전체
Git revert + 재빌드코드 되돌리고 재빌드/재배포수십 분코드 수준 롤백
환경 변수 URL 전환리모트 엔트리 URL을 이전 버전으로즉시 (빌드 불필요)Shell에서 로딩하는 리모트

CDN 기반 롤백 전략

정적 파일 서버(S3, GCS 등)에 버전별로 디렉토리를 분리하면, 롤백은 단순히 심볼릭 링크 또는 라우팅 규칙을 변경하는 것으로 완료된다.

bash
# CDN 디렉토리 구조
s3://cdn-bucket/
  posting/
    v1.2.3/          # 이전 안정 버전
      remoteEntry.js
      chunk-*.js
    v1.3.0/          # 현재 배포 버전 (문제 발생)
      remoteEntry.js
      chunk-*.js
    latest -> v1.2.3  # 심볼릭 링크를 이전 버전으로 변경

# 롤백 명령 (수 초 내 완료)
aws s3 cp s3://cdn-bucket/posting/v1.2.3/remoteEntry.js \
         s3://cdn-bucket/posting/latest/remoteEntry.js

# CDN 캐시 무효화
aws cloudfront create-invalidation \
  --distribution-id EXXXXX \
  --paths "/posting/latest/*"

런타임 URL 전환을 활용한 즉시 롤백

Shell이 importRemote로 마이크로앱을 로딩하는 경우, 환경 변수의 URL만 변경하면 빌드 없이 즉시 롤백이 가능하다.

typescript
// Shell: 환경 변수로 버전 제어
importRemote({
  url: process.env.REACT_APP_MICRO_POSTING!,
  // url: "https://cdn.example.com/posting/v1.2.3"  ← 롤백 시 이전 버전 URL
  scope: "posting",
  module: "./injector"
});

다만 이 방식은 Shell 자체의 재빌드가 필요하다. 완전히 빌드 없이 롤백하려면 Config Server 패턴을 도입한다.

typescript
// Shell: 런타임에 Config 서버에서 URL 조회
const config = await fetch("https://config.example.com/remote-urls.json");
const urls = await config.json();

importRemote({
  url: urls.posting,  // Config 서버에서 반환된 URL
  scope: "posting",
  module: "./injector"
});

Config Server 패턴을 사용하면 remote-urls.json만 수정하여 빌드 없이 즉시 롤백할 수 있다.


5. 배포 자동화 체크리스트

프로덕션 배포 전 반드시 확인해야 할 항목을 정리한다.

배포 전 체크리스트

단계체크 항목확인 방법
빌드공유 패키지 선행 빌드 완료pnpm build:packages 성공
빌드변경된 앱 빌드 성공pnpm --filter <app> build 성공
테스트유닛 테스트 통과pnpm --filter <app> test 성공
테스트E2E 테스트 통과 (있는 경우)Cypress/Playwright 통과
환경프로덕션 환경 변수 확인.env.production 엔드포인트 정확성
공유SharedScope 충돌 없음스테이징 환경에서 검증
공유Shared 의존성 버전 명시webpack shared 설정 확인
배포이전 버전 아티팩트 보존S3/GCS에 버전별 디렉토리 유지
배포CDN 캐시 무효화 실행CloudFront Invalidation 확인
확인remoteEntry.js 접근 가능HTTP 200 응답 확인
확인콘솔 에러 없음브라우저 DevTools 확인

6. 장기적 배포 전략 로드맵

마이크로프론트엔드의 배포 성숙도는 단계적으로 발전시킨다.

각 레벨은 조직의 규모와 마이크로프론트엔드의 복잡도에 따라 선택한다. 처음부터 Level 4를 목표로 하기보다는, Level 2에서 시작하여 필요에 따라 점진적으로 발전시키는 것을 권장한다.


7. 프로젝트 마무리 정리

커리어업 프로젝트를 통해 학습한 마이크로프론트엔드 배포의 핵심 교훈을 정리한다.

실습 내용변경 대상배포 전략학습 포인트
CI/CD 파이프라인 구축빌드 스크립트Turborepo --filter + 선행 빌드변경 감지 기반 자동화
UIKit 컴포넌트 이동ui-kit + edu + postingShareScope 분리 + 점진 배포빌드타임 의존성 관리
프래그먼트 SWR 도입fragment 단독프래그먼트만 재배포런타임 의존성의 독립성
환경 변수 엔드포인트모든 앱의 .env환경별 .env 교체환경 분리 전략

핵심 정리

  1. 마이크로프론트엔드의 궁극적 목표는 독립 배포다. 변경된 부분만 배포하는 선택적 배포가 기본 원칙이며, 전체 배포는 최후의 수단이어야 한다
  2. 공유 컴포넌트 인터페이스를 단순하게 유지하면 연쇄 배포 빈도가 줄어든다. 옵셔널 확장을 기본으로 하고, 필수 Props 추가는 메이저 버전 업으로 관리한다
  3. 점진적 배포 전략(ShareScope 분리 + 앱별 순차 배포)을 사용하면, UIKit 같은 공유 패키지 변경 시에도 안전하게 배포할 수 있다
  4. 롤백은 CDN 아티팩트 버전 교체가 가장 빠르다. Config Server 패턴을 도입하면 빌드 없이 즉시 롤백이 가능하다
  5. 프래그먼트는 인터페이스를 최소화하고 기능 단위로 응집시키면, 가장 높은 수준의 독립 배포를 달성할 수 있다

다음 단계