한 장의 행동 쪽지로
모든 분석 도구에 알리기
화면마다 GTM, Airbridge, 검색 로그, EAPP Tracker를 따로 부르지 않습니다. 화면은 “무슨 일이 있었는지”만 한 번 말하고, 중앙 트래킹 모듈이 각 시스템의 언어로 번역합니다.
1. 전체 구조
track()
의미·정책 검사
TrackingEvent
각 언어로 번역
Search · EAPP
2. TrackingEvent의 각 열은 왜 필요한가?
domain
어느 큰 가게의 일인가?
orderpick · wine · search
이벤트를 소유하고 유지하는 bounded context.
area
가게 안 어느 코너인가?
commerce · discovery · promotion
분석 목적. domain과 값이 겹치지 않습니다.
objectType / objectId
무엇에 한 행동인가?
order / ORDER-123
type은 대상 종류, id는 실제 식별자가 있을 때만 사용.
action
무엇을 했는가?
view · cancel · complete
UI click이 아니라 의미 중심 행동. 입력 방식은 interaction으로 분리.
scope
얼마나 넓게 적용됐나?
single · partial · full
상품 하나, 주문 일부, 주문 전체를 구분.
surface / placement
어디서 일어났나?
order_detail / order_actions
surface는 화면, placement는 화면 내부 구좌.
interaction
어떤 경로로 조작했나?
control + confirm
버튼, 뒤로가기, 닫기, gesture, system을 구분.
outcome
실제로 어떻게 끝났나?
attempted · succeeded · rejected
클릭과 성공을 섞지 않게 해 줍니다.
3. 가장 중요한 결정: 클릭·의도·결과를 섞지 않는다
| 상황 | action | interaction | outcome |
|---|---|---|---|
| 주문 취소 버튼 실행 | cancel | control / primary | attempted |
| 로그인이 필요해 차단 | cancel | control | rejected / authentication_required |
| 서버에서 취소 완료 | cancel | 생략 가능 | succeeded |
| 취소 화면에서 뒤로가기 | exit | back_navigation | abandoned |
구매 버튼을 눌렀다는 사실만으로 checkout succeeded를 만들지 않습니다. 미로그인·재고 부족·옵션 미선택일 수 있기 때문입니다.
4. Catalog는 이벤트 사전이자 교통경찰
'orderpick.commerce.order.complete': {
confirmedBy: 'backend',
outcomePolicy: 'required',
defaultScope: 'single',
destinations: ['gtm', 'airbridge'],
owner: 'orderpick',
lifecycle: 'active',
schemaVersion: 1
}
confirmedBy
누구의 확인을 믿을지 결정합니다. 버튼 클릭은 interaction, viewport는 client, 주문 완료는 backend가 확인합니다.
destinations
화면은 전송 대상을 모릅니다. Catalog가 GTM·Airbridge·Search·EAPP 중 어디로 보낼지 결정합니다.
lifecycle
이벤트를 바로 삭제하지 않고 draft → active → deprecated → retired로 관리합니다.
5. 여러 웹뷰를 건너는 Flow
startFlow
resumeFlow
sequence +1
flow.end
localStorage에는 flowId, eventKey, sequence, 시작·만료 시각, 버전만 저장합니다. 주문번호·상품·검색어·사용자 식별자는 저장하지 않습니다. TTL은 30분이며, 완료하면 즉시 삭제합니다.
6. Adapter 활용 예시
dataLayer.push({
event: 'orderpick_commerce_order_complete',
surface: 'order_complete',
outcome: 'succeeded'
})airbridge.ecommerce.order.completed
// products, transactionID, paymentName으로 변환{
cust_id: 'NO_USER',
action: 'rec-impression',
object_ids: [...],
info: { channel: 'product-search' }
}{
event_source: 'app-dg-apple-recomm-form-select',
event_type: 'click',
event: { screen_id, check_list },
// user_id 없음
}7. 개인정보와 안전 규칙
APP_CID 금지
canonical event와 EAPP adapter payload에 넣지 않습니다. 키와 값 모두 검사합니다.
원본 응답 금지
API response, stack trace, 서버 메시지, 사용자가 직접 쓴 문장을 통째로 attributes에 넣지 않습니다.
제품 기능 비차단
분석 시스템이 실패해도 결제·이동·취소가 멈추면 안 됩니다. Adapter는 독립 비동기 실행합니다.
Clean cutover
canonical 호출을 추가하면 같은 변경에서 기존 직접 호출을 제거합니다. 이중 집계를 허용하지 않습니다.
8. 잘 쓰는 예와 피해야 할 예
좋은 예
tracking.track(
'orderpick.commerce.order.cancel',
{
objectId: orderId,
scope: 'full',
outcome: {
status: 'succeeded',
nextState: 'cancelled'
}
}
)피해야 할 예
tracking.track('cancel-click', {
objectId: 'purchase',
attributes: {
APP_CID,
rawResponse,
errorMessage
}
})9. 운영 체크리스트
| 새 이벤트 추가 | key, 입력 타입, catalog 정책, adapter mapping, 테스트를 함께 추가 |
|---|---|
| 성공 이벤트 | backend 응답으로 실제 상태 전환을 확인한 지점에서만 발행 |
| 노출 이벤트 | 렌더링이 아니라 viewport 기준과 중복 정책을 명확히 정의 |
| 페이지뷰 | Router에서 한 번 발행하고 GTM 자동 page view와 중복 금지 |
| 검증 | lint, 신규 vue-tsc 오류 0, 관련 Jest, build, 실제 payload smoke |
핵심 한 문장: 화면은 “무슨 일이 있었는지”만 말하고, Catalog와 Adapter가 “누구에게 어떤 모양으로 알릴지” 책임집니다.