Ash Oban
최근 작업중에 이메일 발송을 처리해야 했다.
after_action 에 이마일 발송을 고려했는데 최종적으로는 AshOban 을 사용했다.
AshOban이 뭔가?
Ash Framework와 Oban을 이어주는 라이브러리다. 백그라운드 job을 Ash 스타일로 쓸 수 있게 해준다.
멤버 초대 이메일 보내기
클럽에 새 멤버를 초대하면 이메일을 보내야 하는 상황이다. 이걸 어떻게 처리할까?
Action Hook으로 하면:
사용자가 서버에서 이메일 발송을 위한 외부요청을 처리하는 것을 기다려야 함
이메일 발송에 실패하면 멤버 생성도 실패함
AshOban으로 하면:
after_action fn _changeset, membership, context ->
# 이메일을 나중에 보내도록 예약
AshOban.schedule(SendInvitationEmail, %{membership_id: membership.id})
{:ok, membership}
end
# Job 정의
oban do
job SendInvitationEmail do
def perform(%{args: %{"membership_id" => membership_id}}, context) do
membership = Ash.get!(Membership, membership_id) |> Ash.load!([:user, :club])
Emails.send_invitation_email(membership.user, membership.club)
end
end
end
사용자가 기다릴 필요 없음 (빠름)
이메일 실패해도 멤버 생성은 성공
실패하면 자동으로 재시도
Ash context(권한, tenant 등)가 자동으로 넘어감
왜 AshOban 을 사용하나?
일반 Oban은 actor나 tenant 정보를 수동으로 넘겨야 하는데, AshOban은 알아서 해준다. Multi-tenancy 쓰면 특히 편하다. 그리고 이메일 서버가 다운되어도 알아서 나중에 다시 시도한다.
언제 써야 할까?
AshOban 써야 하는 경우:
이메일, SMS 같은 외부 서비스 호출
시간 오래 걸리는 작업
실패해도 재시도해야 하는 작업
Action Hook으로도 충분한 경우:
간단한 데이터 변환, 로깅
실패하면 전체를 롤백해야 하는 경우
주의할 점
Struct 대신 ID만 넘기자. struct는 직렬화가 안된다:
# 안됨
AshOban.schedule(MyJob, %{membership: membership})
# 됨
AshOban.schedule(MyJob, %{membership_id: membership.id})
정리
이메일 발송 같은 외부 서비스 연동은 Oban 을 통해 백그라운드로 처리하는게 좋은 것 같다. 사용자 경험도 개선할 수 있다.