이 문서를 통해 아래 질문들에 답할 수 있게 됩니다.

  • audit log와 domain event는 각각 어떤 목적을 가진 로그인가?
  • audit log를 integration event처럼 사용하면 어떤 문제가 생기는가?
  • Java/Spring 서비스에서 감사 기록과 비즈니스 사건 발행을 어떻게 분리하는가?
  • 보관 기간, 개인정보, consumer 계약은 두 로그에서 어떻게 달라지는가?

개요

audit log는 누가 언제 무엇을 했는지 추적하기 위한 기록이다.

domain event는 비즈니스적으로 의미 있는 사건을 표현한다.

두 로그는 모두 “무언가 일어났다”를 기록하지만 목적이 다르다.

audit log는 조사, 보안, 규정, 운영 추적에 가깝다.

domain event는 downstream projection, 알림, warehouse 적재, 다른 서비스 연동에 가깝다.

원리

audit log는 행위자와 변경 흔적을 중시한다.

domain event는 비즈니스 사건과 consumer 계약을 중시한다.

예를 들어 관리자가 주문 상태를 수정했다면 audit log에는 adminId, ip, before, after, reason이 중요하다.

domain event에는 OrderCanceled, OrderPaidAdjusted처럼 downstream이 처리할 비즈니스 의미가 중요하다.

두 목적을 한 table이나 한 topic으로 해결하려 하면 보관 정책, 개인정보, schema 안정성이 충돌한다.

비교

구분Audit LogDomain Event
목적추적, 감사, 조사비즈니스 사건 전달
소비자운영자, 보안, 감사서비스, consumer, data pipeline
핵심 필드actor, action, before/after, reasoneventId, aggregateId, eventType, occurredAt
보관규정과 감사 기준replay와 consumer 요구
개인정보포함 가능성이 높음최소화해야 함
변경내부 정책 영향외부 consumer 계약 영향

같은 주문 취소라도 두 기록은 다르게 생긴다.

Audit Log 예시

{
  "auditId": "aud-991",
  "actorType": "ADMIN",
  "actorId": "admin-17",
  "action": "ORDER_STATUS_CHANGE",
  "resourceType": "Order",
  "resourceId": "10042",
  "before": {"status": "PAID"},
  "after": {"status": "CANCELED"},
  "reason": "customer request",
  "ipAddress": "10.10.1.5",
  "createdAt": "2026-06-30T11:00:00+09:00"
}

이 기록은 누가 왜 바꿨는지를 설명한다.

consumer가 검색 색인에서 주문을 제거하기 위해 읽기에는 불필요한 정보가 많고 개인정보 위험도 있다.

Domain Event 예시

{
  "eventId": "evt-10042-003",
  "eventType": "OrderCanceled",
  "eventVersion": 1,
  "aggregateId": "10042",
  "occurredAt": "2026-06-30T11:00:00+09:00",
  "payload": {
    "orderId": 10042,
    "cancelReasonCode": "CUSTOMER_REQUEST",
    "refundRequired": true
  }
}

이 event는 downstream이 주문 취소를 처리하는 데 필요한 계약이다.

검색 색인은 문서를 제거하고, warehouse는 취소 fact를 반영하고, 알림 consumer는 취소 알림을 보낼 수 있다.

Spring 코드 분리

@Transactional
public void cancelOrder(Long orderId, AdminActor actor, String reason) {
    Order order = orderRepository.getById(orderId);
    OrderStatus before = order.getStatus();
    order.cancel(reason);
 
    auditLogRepository.save(AuditLog.orderStatusChanged(actor, orderId, before, order.getStatus(), reason));
    outboxRepository.save(OutboxEvent.from(order.canceledEvent()));
}

같은 transaction 안에서 두 기록을 모두 남길 수 있다.

하지만 목적과 schema는 분리한다.

판단 축

  • 사람이 한 조작을 추적하려면 audit log로 남긴다.
  • downstream이 처리해야 할 비즈니스 변화는 domain event로 남긴다.
  • audit log의 before/after를 consumer 계약으로 사용하지 않는다.
  • domain event에는 필요한 business key만 넣고 개인정보를 줄인다.
  • 보관 기간과 삭제 정책을 두 로그에 따로 둔다.

실패 모델

  • audit log topic을 그대로 Kafka consumer가 읽어 projection을 만든다.
  • audit log에 개인정보가 많아 replay와 장기 보관이 어려워진다.
  • domain event에 actor IP와 내부 관리자 사유가 과하게 들어간다.
  • audit log는 남았지만 domain event가 없어 downstream이 갱신되지 않는다.
  • domain event만 남기고 누가 수동 보정했는지 추적할 수 없다.

운영 지표

  • audit_write_failure_count: 감사 기록 누락 위험이다.
  • domain_event_missing_count: state 변경 대비 domain event 누락 수다.
  • audit_payload_sensitive_field_count: 감사 로그 개인정보 검토 지표다.
  • domain_event_consumer_count: event 계약 영향 범위다.
  • manual_change_event_gap_count: 수동 변경과 downstream event 사이 gap이다.

보관과 개인정보

audit log는 법적 요구나 보안 정책에 따라 오래 보관할 수 있다.

동시에 개인정보나 내부자 정보가 포함될 가능성이 높다.

domain event는 replay를 위해 오래 보관할 수 있지만, consumer에게 불필요한 개인정보를 담으면 안 된다.

따라서 audit log를 그대로 event stream으로 쓰면 개인정보와 보관 정책이 충돌한다.

개인 프로젝트 기준

개인 프로젝트에서는 두 table을 꼭 나누지 않아도 된다.

하지만 field와 목적은 나눠서 설계한다.

예를 들어 audit_logoutbox_event를 별도로 두고, 같은 transaction에서 함께 저장하면 충분하다.

기업 운영 기준

기업에서는 audit log와 domain event의 접근 권한이 달라야 한다.

보안팀이나 운영팀은 audit log를 볼 수 있지만, 모든 consumer가 audit log를 읽어서는 안 된다.

domain event 변경은 consumer owner와 함께 리뷰하고, audit log 변경은 보안과 규정 요구도 함께 본다.

실전 팁

  • event 이름을 정하기 전에 이 기록의 소비자가 누구인지 묻는다.
  • 감사 목적이면 actor와 reason을, domain event면 aggregate와 business payload를 먼저 본다.
  • 수동 관리자 기능은 audit log와 domain event를 둘 다 검토한다.
  • audit log의 before/after JSON을 downstream 계약으로 삼지 않는다.
  • 개인정보가 들어가는 event는 보관 기간과 삭제 요구를 먼저 확인한다.

위험 신호!

  • “이미 audit log가 있으니 event는 필요 없다”고 판단한다.
  • consumer가 audit log의 내부 action 이름에 의존한다.
  • domain event payload에 관리자 IP와 내부 메모가 들어간다.
  • 수동 보정은 audit만 남고 mart와 search index가 갱신되지 않는다.
  • audit log schema 변경이 downstream consumer 장애로 이어진다.

확인 질문

확인 질문

  • audit log와 domain event의 핵심 차이는 무엇인가?
    • audit log는 행위 추적이고, domain event는 비즈니스 사건을 downstream에 전달하는 계약이다.
  • audit log를 integration event처럼 쓰면 왜 위험한가?
    • 개인정보와 내부 필드가 노출되고, schema 변경이 consumer 계약을 깨뜨리며, 보관 정책이 충돌할 수 있다.
  • 관리자 수동 취소 기능에서는 무엇을 남겨야 하는가?
    • 누가 왜 바꿨는지에 대한 audit log와, downstream이 처리할 OrderCanceled 같은 domain event를 함께 남겨야 한다.

참고 문서