배달의민족 주문접수 채널에 Flutter를 도입하며 고민한 것 | 우아한형제들 기술블로그
배달의민족 주문접수 채널에 Flutter를 도입하며 고민한 것 | 우아한형제들 기술블로그
들어가며 배달의민족 주문접수 채널은 파트너의 주문 관리를 돕기 위해 매일 수백만 건의 주문을 실시간으로 처리하며, 다양한 디바이스를 지원하고 있습니다. 점차 다양해지는 디바이스 생태
techblog.woowahan.com
배달의민족 주문접수 채널은 파트너의 주문 관리를 돕기 위해 매일 수백만 건의 주문을 실시간으로 처리하며, 다양한 디바이스를 지원하고 있다.
점차 다양해지는 디바이스 생태계에 대응하고자, 신규 제품에 Flutter+Clean Architecture를 선택했고, 비즈니스 요구사항에 더 빠르게 대응하기 위해 웹뷰 기반 컨테이너 앱(App Shell)으로 전환까지 진행 중이다.
[왜 Flutter를 사용?]
늘어나는 플랫폼 요구사항
최근에는 Windows, Android 모바일, iOS 3가지 플랫폼을 지원하고 있었지만, 다양한 디바이스에서 주문을 관리하고 싶다는 요구가 커지고 있는 추세이다.
- macOS 데스크톱: Mac 디바이스로 주문을 관리하고 싶은 요청
- Android 테블릿/POS: Windows POS에서 Android 기반 디바이스로 전환 추세
- 향후 확장성: 신규 디바이스 지원 및 접수 채널 통합 대비
개발 생산성과 비용
기존에는 Windows, Andorid 모바일, iOS 각 플랫폼별로 주문접수 채널을 개발해야 했음. 하나의 기능을 추가하려면 각 플랫폼에 맞춰 반복 구현해야 했음.
지금은, Android 태블릿/POS와 macOS 버전을 Flutter로 운영중
Windows와 모바일 플랫폼(Android 모바일, iOS)은 점진적으로 전환하고 있다.
Flutter 도입으로 확인된 효과
- 인력 절감: Android와 macOS 각각 별도의 개발자가 필요했지만, Flutter 개발자가 두 플랫폼을 담당
- 개발 속도 향상: 1번의 구현으로 멀티 플랫폼에 동시 배포 가능하게 되었음
유지보수 효율성
버그 수정, 기능 변경 시에도 1번의 수정으로 모든 플랫폼 반영 가능
플랫폼 간 동기화 이슈가 줄어들고, 동일한 기능과 UX 제공 가능
일관된 사용자 경험
파트너분들에게 동일한 UX 제공 가능
학습 비용을 줄이면서, OS별 다른 UI로 인한 혼란을 줄일 수 있음
선택
장기적으로 Windows, Android, iOS, macOS 4개 플랫폼을 모두 지원해야 하는 상황에서 생산성을 확보하려면 단일 코드베이스가 필수적이었음. 이 조건을 만족하면서 성숙한 생태계를 갖춘 프레임워크는 Flutter가 유일했음
[Flutter 한계와 해결]: Write Once, Adapt Everwhere
플랫폼 차이의 현실
처음에는 코드 하나로 모든 플랫폼을 동일하게 지원할 수 있을 것이라 기대.
실제로는 플랫폼마다 주변기기 제어, 권한 시스템, 알림 방식, 업데이트 등 메커니즘이 다른 부분들이 존재.
각 플랫폼의 업데트 방식이 완전히 다르지만, 비즈니스 로직에서는 "업데이트 확인 및 실행"이라는 하나의 인터페이스로 통일 가능
해결방법
공통 인터페이스를 정의하고, 플랫폼별로 구현을 분리
Clean Architecture의 계층 분리 덕분에 플랫폼 의존성을 격리하고 비즈니스 로직을 독립적으로 유지 가능
[실용주의적 추상화: 변경 지점에만]
모든 것을 추상화하면 인터페이스 파일과 구현체 파일들로 코드량이 증가하고 간단한 로직도 여러 파일을 오가며 추적해야 한다.
"변경 지점에만 추상화한다."
추상화 기준
다음 조건 중 하나라도 해당하면 추상화함.
1. 플랫폼별로 구현이 다른 경우
2. 외부 라이브러리에 의존하며 교체 가능성이 있는 경우
3. 테스트를 위해 Mock이 필요한 경우
사례: 실시간 통신 라이브러리 전환
최근 실시간 메시지 수신 방식을 MQTT에서 SSE(Server-Sent Events)로 변경함.
ServerEventReceiver 인터페이스로 추상화해두었기 때문에 아래와 같이 효율적으로 변경 가능
- 구현체만 교체(MqttEventReceiver→SseEventReceiver)
- 비즈니스 로직은 변경 없음
- 전환 작업 시간 최소화
추상화가 없었다면 MQTT 코드가 전체 코드베이스에 퍼져 대규모 수정이 필요했을 것
[아키텍처 선택: Clean Architecture+BLoC]
Clean Architecture란?
Robert C. Martin이 제안한 소프트웨어 설계 원칙.
관심사를 계층별로 분리하여 비즈니스 로직을 UI, 프레임워크, 외부 의존성의로부터 독립시키는 아키텍처
Flutter 프로젝트에서는 주로 BLoC 패턴이나 MVVM+Provider를 사용.
여기서는, 플랫폼 확장과 큰 변경을 대비해 계층 분리가 명확한 Clean Architecture+BLoC를 선택.
Clean Architecture를 선택한 이유
- 계층 분리로 충분한 관심사 분리
- UI 프레임워크 교체 가능성 확보
- 인터페이스 기반 계층 분리로 테스트 가능성과 플랫폼 독립성 확보
- 팀의 학습 곡선이 완만함
프로젝트 구조:
Feature 단위로 모듈을 분리,
각 Feature 내부에는 Data, Domain, Presentation 계층으로 구성.
추가로 Infrastructure 계층에서 순수 기술 구현(프린터, 오디오, 실시간 통신 등)을 제공
[BLoC 선택: 명시적 상태 관리의 중요성]
BLoC(Business Logic Component)란?
Flutter에서 UI와 비즈니스 로직을 분리하기 위한 상태 관리 패턴.
Event 입력받아 State를 출력하는 스트림 기반 아키텍처로, 명시적인 상태 변화 추적 가능
코드량보다 유지보수성을 선택한 이유:
BLoC은 코드가 많음.
하지만 구조 덕분에 상태 변화 흐름이 명확해지고, 대규모 프로젝트에서도 유지보수성을 유지할 수 있음
- 버그 디버깅: 이벤트 로그 추적으로 빠른 원인 파악
- 신규 기능: Event/State/Handler 패턴 반복
- 팀 온보딩: 일관된 구조로 학습 곡선 완만
- 코드 리뷰: 명확한 기준(역할 분리)
[BLoC의 올바른 역할: Presenter]
BLoC=Business Logic Component
BLoC을 Presenter로 정의.
Presentation Logic만 처리하고, 핵심 비즈니스 로직은 Domain 계층의 UseCase에 분리함.
왜 분리?
테스트 용이성: UseCase는 Flutter 의존성 없이 순수 Dart로 테스트 가능
재사용성: 같은 UseCase를 다른 화면, 다른 BLoC에서 재사용
책임 명확화: BLoC은 "어떻게 보여줄까", UseCase는 "무엇을 할까"
[계층별 데이터 모델: DTO→ Entity→ UI State]
프론트엔드에는 Domain 계층을 둠.
타입안정성, 플랫폼 독립성, 변경의 격리, 클라이언트 비즈니스 로직 관리 측면에서 이점이 있다고 판단
데이터는 3단계를 거쳐 반환됨
- DTO(Data Transfer Object): 서버 응답 그대로 매핑. nullable 하고 원시 타입(String, int, bool 등) 기반임.
- Entity: 비즈니스 도메인 개념으로 변환, 타입 안전성과 불변성을 보장하고 Flutter에 의존하지 않는 순수 Dart 객체
- UI State: UI 표현에 필요한 정보로 변환. 화면에 표시할 텍스트, 색상, 버튼 상태 등 화면 렌더링에 특화된 데이터.
각 계층은 명확히 분리된 책임을 가짐.
서버 변경은 Data 계층에서, UI 변경은 Presentation 계층에서만 처리됨
장점
1. 타입 안정성: nullable 지옥 탈출, Enum으로 타입 안전
2. 플랫폼 독립성: Entity는 Flutter에 의존하지 않는 순수 Dart(테스트 시 유리)
3. 변경의 격리: 서버 API 변경 시 Mapper만 수정, 나머지 계층 불변
4. 비즈니스 로직 캡슐화: 클라이언트에 처리해야만 하는 비즈니스 로직을 Domain에서 관리
5. UI 로직 분리: isVisible, ButtonStatus 같은 UI 로직이 Entity에 없음
[웹뷰 기반 앱으로 전환 배경]
현재는 macOS와 Android 태블릿/POS 버전만 Flutter로 운영하고 있음
주문접수 채널은 실시간성이 중요한 서비스.
긴급 버그 수정이 필요할 때 네이티브 앱은 특성 플랫폼에서 빌드, 심사, 배포까지 며칠이 소요될 수 있어, 플랫폼이 늘어나면 이 시간 동안이 비즈니스 영향도 커짐.
웹뷰 기반 아키텍처 장점
- 앱 심사 없이 즉시 배포 가능
- 모든 플랫폼에 동시 반영
- 긴급 버그 수정 시간 단축
[결정: 비즈니스 로직으로 웹으로]
주요 이점
- 긴급 버그→즉시 수정→즉시 배포
- 주문 손실 최소화
- 플랫폼 수와 무관하게 동일한 배포 속도
전환 내용:
비즈니스 로직(Domain, Data)은 웹으로 이동.
Presentation 계층은 WebView+Bridge로 새롭게 구현.
Infrastructure 계층(프린터, 권한, 알림 등)은 그대로 재사용
[유지보수하기 쉬운 코드 만들기]
이름으로 계층과 역할을 구분할 수 있어야 한다!
각 계층은 자신만의 책임을 수행.
클래스명과 메서드명에 일관된 규칙을 적용, 코드를 읽는 사람이 즉시 "어느 계층의 어떤 역할"인지 파악할 수 있도록 함.
특히, 같은 이름이 여러 계층에 반복되면 각 메서드의 역할이 불명확해짐.
계층 간 이름 중복을 지양하며, 불가피하게 같은 이름을 쓸 때는 반환 타입으로 구분하거나,
검색 시 의미 파악이 가능하도록 네이밍
<프로젝트 주요 성과와 교훈>
1. 플랫폼 차이 대응: Flutter는 크로스플랫폼이지만, 플랫폼 차이는 존재함. 공통 인터페이스를 정의하고 플랫폼별 구현을 분리하여 해결.
2. 보일러플레이트 코드의 장단점: BLoC는 코드량이 증가하지만, 경험상 유지보수 시간이 크게 단축됨. 명시적 상태 관리가 장기적으로 유리.
3. 필요한 곳에만 추상화: 모든 것을 추상화하면서 복잡도만 증가할 수 있음. 플랫폼별 구현이 다른 곳, 외부 의존성 격리, 테스트 Mock이 필요한 곳만 추상화
4. 일관된 네이밍 컨벤션: 메서드 이름만 봐도 어느 계층인지 알 수 있도록 일관된 네이밍 컨벤션을 유지
5. BLoC의 역할: 비즈니스 로직은 UseCase에, BLoC은 UI State 관리에 집중
'4학년 > 기술블로그' 카테고리의 다른 글
| [kakao enterprise|Tech&] 자율 AI 에이전트, ChatGPT 다음의 메가트렌드? (1) | 2026.05.09 |
|---|---|
| [260406] [엔키화이트햇] 공시가 '보안 성적표'가 되는 시대: 2027 정보보호 공시 확대의 시사점과 대응 (0) | 2026.04.08 |
| [260405] [우아한 기술블로그] 5년 동안 못 푼 배민 다국어 숙제, AI와 함께 한 달 만에 끝내기 (0) | 2026.04.03 |
| [260329][Theori] Xint로 구축한 안전한헬스케어 보안 (0) | 2026.03.29 |
| [260130] [Theori] 2025 하반기 Hot🔥보안 사건 사고 (0) | 2026.01.30 |