Computer >> 컴퓨터 >  >> 프로그래밍 >> Ruby

Noticed와 CableReady로 구현하는 Rails 강력한 시스템 알림 완벽 가이드

이 글은 필자의 『Advanced CableReady』 책의 다가오는 한 챕터에서 영감을 받아, AppSignal 게스트 포스트에 맞춰 다듬은 내용입니다.

알림 기능은 수많은 웹 애플리케이션이 공유하는 대표적인 횡단 관심사(cross-cutting concern)입니다.

Noticed 젬(gem)은 데이터베이스 기반 모델과 플러그인 방식의 전달 메서드를 제공하여 Ruby on Rails 애플리케이션에서 알림 개발을 놀랍도록 쉽게 만들어 줍니다. 메일러, 웹소켓 등 여러 전달 방식이 기본으로 내장되어 있습니다.

이번 글에서는 CableReady 젬을 활용해 Rails 애플리케이션에서 시스템 알림을 발생시키는 방법과 그 장점도 함께 살펴보겠습니다.

그럼 시작해 보겠습니다!

사전 준비 사항 및 요구 조건

앱을 개발하다 보면 종종 애플리케이션 자체에서 시스템 알림을 발생시켜야 할 때가 있습니다. 이는 Notifications API를 통해 구현할 수 있습니다.

예를 들어, 사용자가 트랜스코딩에 오랜 시간이 걸리는 대용량 파일을 업로드할 수 있는 애플리케이션이 있다고 가정해 봅시다. 비디오 업로드가 완료되면 사용자에게 알림을 보내고 싶을 것입니다. 그러면 사용자는 그동안 다른 작업으로 전환할 수 있고, 몇 분 동안 애플리케이션을 열어둘 필요가 없습니다.

다행히도 다음 두 가지 도구만 있으면 시스템 알림을 쉽게 구현하고 확장할 수 있습니다:

  • Noticed는 커스텀 전달 메서드를 지원합니다(Noticed는 Discord 서버로 전송하는 것처럼 원하는 어떤 전송 방식이든 구현할 수 있는 간단한 API를 제공합니다).
  • CableReady는 알림(notification) 연산을 기본 기능으로 갖추고 있습니다.

CableReady와 Noticed의 요구 사항 목록은 짧습니다. 필요한 것은 다음과 같습니다:

  • 실행 중인 ActionCable 서버
  • ActiveJob 백엔드(주로 Noticed에서 사용)

참고: 이 프로젝트의 전체 샘플 코드는 GitHub에서 확인할 수 있습니다. 아래 단계별 가이드를 따라 하셔도 좋습니다.

Rails 애플리케이션을 위한 CableReady 기초

먼저 CableReady에 익숙해져 보겠습니다. 2017년에 출시된 CableReady는 '누락된 ActionCable 표준 라이브러리'라고 생각할 수 있습니다.

Turbo가 등장하기 전에는 Ruby on Rails에서 실시간 애플리케이션을 만드는 유일한 방법이 ActionCable이었습니다. ActionCable은 웹소켓을 감싸는 Rails의 네이티브 래퍼로, 지속적인 연결을 통해 양방향으로 언제든 메시지를 주고받을 수 있는 서버 측 및 클라이언트 측 API를 제공합니다.

하지만 이 방식의 단점은, 실제로 동작하게 만들려면 상당한 양의 보일러플레이트 코드를 작성해야 한다는 것이었습니다(그리고 지금도 마찬가지입니다).

바로 이 지점에서 CableReady가 힘을 발휘합니다. 서버에서 트리거할 수 있는 다양한 DOM 연산들을 추상화 레이어로 감싸 주기 때문입니다. 몇 가지 예는 다음과 같습니다:

  • DOM 변경 (inner_html, insert_adjacent_html, morph 등)
  • DOM 요소 속성 변경 (add_css_class, remove_css_class, set_dataset_property 등)
  • 임의의 DOM 이벤트 디스패치
  • 브라우저 히스토리 조작
  • 알림(notifications) (이번 글에서 활용할 기능)

CableReady의 서버와 클라이언트

CableReady는 어떻게 마법 같은 일을 해낼까요? 핵심은 서버 측과 클라이언트 측 두 부분으로 구성되어 있다는 점입니다.

서버 측에서는 CableReady::Broadcaster 모듈을 애플리케이션의 필요한 어느 곳에든 포함시킬 수 있습니다. 잡(job), 모델 콜백, 또는 일반 컨트롤러가 될 수 있죠. 다만 먼저 ActionCable 채널이 준비되어 있어야 합니다. CableReady 공식 문서를 인용하면 다음과 같습니다:

여기서 visitors스트림 식별자(stream identifier)라고 불립니다. 이를 통해 광범위한 대상 또는 채널을 구독 중인 특정 클라이언트만을 대상으로 지정할 수 있습니다. 예시를 마무리하면, 모델에 브로드캐스터 모듈을 포함시키고 회원가입 후 클라이언트로 console.log를 전송할 수 있습니다.

클라이언트 측 로직은 간단합니다. 앞서 언급한 채널에 대한 구독을 생성하고, 해당 구독의 received 훅에서 네트워크를 통해 전달된 모든 연산에 대해 CableReady.perform을 호출하면 됩니다.

Rails에서 CableReady vs. Turbo

정리하자면, 언제 CableReady를 사용하고 언제 피해야 할까요?

Turbo의 등장과 함께 웹 개발 커뮤니티는 서버 렌더링 기반의 반응형 애플리케이션을 만들 수 있는 강력한 도구 상자를 갖게 되었습니다. 본질적으로 Rails를 위한 강력한 서버 측 바인딩을 갖춘 프런트엔드 기술인 Turbo는 표준 MVC(Model-View-Controller) 스택에 잘 들어맞으며, 일반적인 앱의 대부분 요구 사항을 충족할 수 있습니다.

반면 CableReady는 실시간 Rails 개발의 '맥가이버 칼'에 가깝으므로 신중하게 사용해야 합니다. 매우 강력한 추상화이기 때문에 광범위하게 사용하고 싶은 유혹을 느낄 수 있습니다. 하지만 DOM의 모든 부분이 앱의 어느 위치에서든 변경될 수 있다고 상상해 보면, 이것이 경쟁 상태(race condition)와 추적하기 어려운 버그로 이어질 수 있다는 것을 알 수 있습니다.

그럼에도 불구하고 지금과 같은 경우에는 DOM을 더 세밀하게 제어할 수 있어 CableReady가 완벽하게 적합합니다.

간단한 TL;DR을 요청한다면 이렇게 답하겠습니다. Turbo는 애플리케이션 개발자를 위한 것이고, CableReady는 라이브러리 빌더를 위한 것입니다. 하지만 앞으로 보겠지만, 두 기술 사이에는 회색 지대도 존재합니다.

Noticed — Rails 애플리케이션을 위한 간편한 알림

시스템 알림을 전달하기 위해 적용할 두 번째 라이브러리는 Chris Oliver가 만든 Noticed 젬입니다. Noticed의 핵심은 수신자에게 전달되는 단일 알림을 모델링하는 ActiveRecord 모델 위에 구축되어 있다는 점입니다. 이 모델은 다음과 같은 공통 메타데이터를 저장합니다:

  • 알림이 누구에게 전송되었는지(수신자)
  • 알림을 언제 읽었는지
  • 알림과 연관된 파라미터(일반적으로 다른 모델에 대한 참조)

ActiveStorage나 ActionText의 메타 테이블 동작 방식에 익숙하다면, 이와 매우 유사하다는 것을 알 수 있습니다.

이와 함께 Noticed는 PORO(Plain Old Ruby Object, 즉 Rails나 다른 프레임워크와 아무런 연결이 없는 순수 객체)를 활용합니다. 이 객체들은 실제 알림의 청사진 역할을 하며, 다소 혼동스럽게도 역시 Notification이라고 불립니다. 이들은 알림을 렌더링하고 배포하는 방법에 대한 로직을 담고 있습니다. README의 예시는 다음과 같습니다:

곧 이것이 실제로 작동하는 모습을 보게 될 것입니다. 특히 주목할 부분은 deliver_by 호출입니다. 이는 해당 알림이 어떤 전달 메서드를 사용할지 결정합니다:

  • deliver_by :database — 나중에 조회할 수 있도록 Notification 레코드(앞서 언급한 모델)를 저장합니다.
  • deliver_by :action_cable — 정의된 ActionCable 채널과 스트림(기본값 Noticed::NotificationChannel)을 통해 전송합니다.
  • deliver_by :email — 알림 전송에 사용할 메일러를 지정합니다. 예시에서는 수신자가 설정한 환경설정을 반영하는 방법도 보여줍니다.

이 글의 나머지 부분에서 우리의 목표는 시스템 알림을 전송하는 커스텀 전달 메서드를 구현하는 것입니다.

커스텀 전달 메서드: Notifications API를 통한 시스템 알림

본격적으로 시작하기 전에, 새 Rails 애플리케이션을 생성하겠습니다. 이 방식이 CableReady 통합이 더 쉽기 때문에 importmaps 대신 esbuild JavaScript 옵션을 선택했습니다:

참고: 이 글을 작성하는 시점의 Rails 버전은 7.0.4입니다. 이미 존재하는 Rails 애플리케이션이 있다면 다음 단계는 건너뛰어도 되지만, 알림 수신자(recipient) 역할을 할 User 모델 또는 유사한 개념이 있는지 확인하세요.

1. 수신자 준비하기

Noticed는 수신자 역할을 할 User 모델이 필요합니다. 간결하게 진행하기 위해 Devise를 추가하고 User 모델을 생성하겠습니다.

그런 다음 Rails 콘솔을 열어 샘플 사용자를 생성합니다:

2. Noticed 추가하기

다음으로 번들에 Noticed를 추가하고 데이터베이스 모델을 생성합니다.

안내에 따라 db:migrate를 실행하고 User 모델에 폴리모픽(polymorphic) has_many 연관관계를 추가합니다:

테스트를 진행하기 전 마지막 조각은 청사진 역할을 할 PORO를 생성하는 것입니다:

여기서 약간 수정하여 당장은 데이터베이스 전달 메서드와 임시 메시지만 사용하도록 하겠습니다. 일반적으로는 메시지와 링크할 URL을 구성하기 위해 모델 ID 같은 필수 파라미터를 여기에 추가하게 됩니다. 자세한 내용은 Noticed README를 참고하세요.

Rails 콘솔만 사용해서도 이 PORO 기반의 알림을 전달하는 방법을 시연할 수 있습니다:

보시다시피 Noticed는 데이터베이스 insert를 수행하므로, 이제 특정 사용자의 모든 Notification을 조회할 수 있습니다:

이 정도면 현재 사용자에게 전달된 모든 알림을 나열하는 간단한 index 뷰를 구성하기에 충분합니다:

bin/dev로 앱을 실행한 후 로그인하고 https://localhost:3000/notifications 에 접속하면 다음 화면을 볼 수 있습니다:

Noticed와 CableReady로 구현하는 Rails 강력한 시스템 알림 완벽 가이드

3. CableReady 설치하기

CableReady를 사용하려면 설치가 필요합니다. 다행히 설치는 빠르게 끝납니다:

메시지를 전달할 NotificationChannel을 생성해 보겠습니다:

이 명령은 ActionCable(JavaScript) 의존성을 추가하고 해당 채널 파일들, 특히 app/channels/notification_channel.rbapp/javascript/channels/notification_channel.js를 스캐폴딩합니다.

서버 측 채널은 Noticed::NotificationChannel을 상속합니다:

계속 진행하기 전에, Devise 사용자에 대해 ActionCable 인증이 설정되어 있는지 확인해야 합니다. 여기서 자세한 내용은 다루지 않겠지만, 필요한 보일러플레이트 코드는 대략 다음과 같습니다:

다른 옵션들은 StimulusReflex 문서를 참고하세요.

클라이언트 측에서는 앞서 언급한 간단한 설정 코드를 추가하면 됩니다:

참고: ActionCable이 동작하려면 Redis가 필요하므로, 이 글의 나머지 부분에서는 로컬에서 Redis 서버가 실행 중이라고 가정합니다.

4. 전달 메서드 구현하기

시스템 알림을 브로드캐스트하기 위해 새로운 전달 메서드를 생성해 보겠습니다:

생성된 클래스의 초기 모습은 다음과 같습니다:

이제 deliver 메서드가 어떻게 동작하기를 원하는지 구상해 보겠습니다:

channel 메서드는 내장된 ActionCable 전달 메서드에서 일부 차용한 것입니다. 클래스 메서드 옵션을 통해 채널을 전달할 수 있으며, 그렇지 않으면 기본값인 Noticed::NotificationsChannel로 폴백(fallback)됩니다.

그런 다음 CableReady의 notification 메서드를 사용하여 해당 알림 인스턴스를 수신자에게 브로드캐스트합니다.

실제로 동작하게 하려면 이를 알림 PORO에 연결해야 합니다:

5. 직접 실행해 보기

이제 남은 일은 직접 시도해 보는 것뿐입니다. Rails 콘솔에서 다시 실행해 보겠습니다:

아직 로그인 상태라면, 브라우저가 먼저 앱을 대신해 알림을 수신할 수 있는 권한을 요청할 것입니다:

Noticed와 CableReady로 구현하는 Rails 강력한 시스템 알림 완벽 가이드

권한을 승인하면 브라우저에서 이렇게 멋진 팝업 알림을 받게 됩니다:

Noticed와 CableReady로 구현하는 Rails 강력한 시스템 알림 완벽 가이드

마무리

CableReady와 Noticed를 빠르게 둘러보며, 브라우저 네이티브 API를 여러분의 앱에 통합하는 방법을 시연했습니다. 그 결과로 사용자에게 시스템 알림을 전달하는 간단하고 일관된 방식을 얻을 수 있었습니다.

이 사용 사례는 CableReady를 여러분의 상황에 얼마나 쉽게 녹여낼 수 있는지 보여주기 위한 것이기도 합니다. 한 걸음 더 나아가 생각해 보면, 이렇게 작성한 전달 메서드를 라이브러리로 분리하는 것도 어렵지 않습니다.

이 글이 여러분의 앱에서 세로형 반응형 문제 영역을 찾아보고 CableReady를 시도해 보는 계기가 되기를 바랍니다.

더 깊이 배우고 싶다면 필자의 신간 『Advanced CableReady』를 확인해 보세요. 쿠폰 코드 APPSIGNAL-PROMO를 사용하면 $10을 절약할 수 있습니다!

즐거운 코딩 되세요!

P.S. Ruby Magic 포스트를 발행 즉시 읽고 싶다면 Ruby Magic 뉴스레터를 구독하고 어떤 글도 놓치지 마세요!