로또 플러그인에서 MessageBus 실시간 업데이트 관련 도움 요청 (AI 보조 개발)

안녕하세요 여러분,

저는 디스코르스(Discourse)에서 사용자가 복권 주제에 답변을 달아 참여할 수 있는 복권 플러그인을 개발 중입니다. 충분한 고지를 드립니다: 저는 프로그래머가 아닙니다 - Claude Code를 사용하여 AI의 도움으로 이 프로젝트를 전적으로 구축하고 있으므로, 근본적인 개념을 놓치고 있을 수 있습니다. 기본 기능은 작동하지만, MessageBus를 사용한 실시간 업데이트에서 문제가 발생하고 있습니다. 올바른 접근법에 대한 조언을 주시면 감사하겠습니다.

제가 달성하려는 목표

  • BBCode [lottery]...[/lottery]를 사용하여 복권 게시물을 생성합니다.
  • 게시물에 참가자 수를 표시하는 복권 카드를 보여줍니다.
  • 사용자가 참여하기 위해 답변을 달면, 페이지 새로고침 없이 모든 시청자가 실시간으로 참가자 수 업데이트를 볼 수 있어야 합니다.
  • discourse-calendar 플러그인이 이벤트 정보를 업데이트하는 방식과 유사합니다.

현재 구현

백엔드 (Ruby)

Lottery 모델 (app/models/lottery.rb):

def publish_update!
  channel = "/lottery/#{self.post.topic_id}"
  message = { id: self.id }

  MessageBus.publish(channel, message)
end

LotteryParticipant 모델 (app/models/lottery_participant.rb):

after_commit :publish_lottery_update, on: [:create, :destroy]

private

def publish_lottery_update
  self.lottery.publish_update!
end

백엔드 로그를 확인한 결과 MessageBus.publish가 성공적으로 호출되고 있습니다:

[Lottery] 📡 Publishing MessageBus update:
  Channel: /lottery/27
  Message: {:id=>52}
[Lottery] ✅ MessageBus.publish completed

프론트엔드 (JavaScript)

디코레이터 (discourse-lottery-decorator.gjs):

api.decorateCookedElement((cooked, helper) => {
  const post = helper.getModel();

  if (!post?.lottery_data) return;

  // 이미 장식되었는지 확인 (AI가 data 속성을 사용하도록 제안)
  if (cooked.dataset.lotteryDecorated === "true") return;

  const lotteryNode = cooked.querySelector(".discourse-lottery");
  if (!lotteryNode) return;

  const wrapper = document.createElement("div");
  lotteryNode.before(wrapper);

  const lottery = Lottery.create(post.lottery_data);

  helper.renderGlimmer(
    wrapper,
    <template><DiscourseLottery @lotteryData={{lottery}} /></template>
  );

  lotteryNode.remove();
  cooked.dataset.lotteryDecorated = "true";
}, { id: "discourse-lottery" });

컴포넌트 (discourse-lottery/index.gjs):

export default class DiscourseLottery extends Component {
  @service messageBus;
  @service lotteryApi;

  constructor() {
    super(...arguments);

    const { lotteryData } = this.args;
    if (lotteryData?.topicId) {
      this.lotteryPath = `/lottery/${lotteryData.topicId}`;
      this.lotteryData = lotteryData;

      // AI가 콜백을 수동으로 바인딩하도록 제안
      this._boundOnLotteryUpdate = this._onLotteryUpdate.bind(this);
      this.messageBus.subscribe(this.lotteryPath, this._boundOnLotteryUpdate);

      registerDestructor(this, () => {
        this.messageBus.unsubscribe(this.lotteryPath, this._boundOnLotteryUpdate);
      });
    }
  }

  async _onLotteryUpdate(msg) {
    console.log('[Lottery Component] 🔔 MessageBus message received!');

    const updatedData = await this.lotteryApi.lottery(this.lotteryData.topicId);
    this.lotteryData.updateFromLottery(updatedData);
  }
}

모델 (models/lottery.js):

export default class Lottery {
  @tracked _stats;

  set stats(stats) {
    this._stats = LotteryStats.create(stats || {});
  }

  updateFromLottery(lottery) {
    // stats를 포함한 모든 속성 업데이트
    this.stats = lottery.stats || {};
    // ... 다른 속성들
  }
}

문제점

참가자의 페이지만 실시간으로 업데이트되고, 다른 시청자의 페이지는 새로고침 없이는 업데이트되지 않습니다.

정상 작동하는 부분:

  • :white_check_mark: 사용자가 참여하기 위해 답변을 달면, 해당 사용자의 페이지에서 참가자 수 업데이트가 실시간으로 표시됩니다.
  • :white_check_mark: 백엔드가 MessageBus 메시지를 성공적으로 게시합니다.
  • :white_check_mark: 참가자의 페이지에서 MessageBus 메시지가 수신됩니다 (로그에 “:bell: MessageBus message received!” 표시).
  • :white_check_mark: 모델이 새로운 참가자 수로 업데이트됩니다 (로그에 0에서 1로 증가하는 수치가 표시됨).

작동하지 않는 부분:

  • :cross_mark: 복권 주제를 보고 있는 다른 시청자들은 실시간으로 수치가 업데이트되는 것을 볼 수 없습니다.
  • :cross_mark: 복권 생성자의 페이지가 업데이트되지 않습니다.
  • :cross_mark: 복권 주제가 열려 있는 다른 사용자의 페이지도 업데이트되지 않습니다.
  • 모두 업데이트된 참가자 수를 보려면 페이지를 수동으로 새로고침해야 합니다.

또한 콘솔에서 컴포넌트가 여러 번 파괴되고 재생성되는 것을 확인할 수 있습니다:

[Lottery Component] ✅ MessageBus subscription established
[Lottery Component] 🧹 Cleaning up MessageBus for topic: 27
[Lottery Component] ✅ MessageBus subscription established
[Lottery Component] 🧹 Cleaning up MessageBus for topic: 27

이는 decorateCookedElement가 여러 번 트리거되어 컴포넌트가 파괴되고 재생성되며, 이로 인해 다른 시청자의 MessageBus 구독이 깨지는 것일 수 있음을 시사합니다.

질문

  1. cooked 요소에 data-decorated 속성을 사용하는 것이 중복 장식을 방지하는 올바른 방법인가요? AI가 이 접근법을 제안했지만, 이것이 권장되는 패턴임을 확인하는 공식 문서를 찾지 못했습니다.

  2. tracked 모델 속성이 변경되고 있음에도 UI가 업데이트되지 않는 이유는 무엇인가요? @tracked _statsthis.stats = lottery.stats를 통해 업데이트될 때 다시 렌더링을 트리거해야 하지만, 표시되는 참가자 수는 그대로입니다. @tracked 사용법에 문제가 있는 것인가요?

  3. 전혀 다른 접근법을 사용해야 하나요? AI에게 discourse-calendar 패턴을 따르라고 요청했지만, 디스코르스가 실시간 업데이트를 처리하는 방식에 대해 근본적으로 놓치고 있는 것이 있을 수 있습니다.

  4. 원래 lotteryNode를 제거하는 것이 문제의 원인가요? AI는 처음에는 제거하지 않는 것을 시도했지만, 그렇게 하면 CSS(.cooked > .discourse-lottery { display: none; })에 의해 복권 카드가 숨겨지는 문제가 발생했습니다. 더 나은 패턴이 있을까요?

저는 프로그래머가 아니므로 기본적인 질문을 하고 있을 수 있습니다 - 문서나 예제에 대한 힌트를 주시면 정말 감사하겠습니다! 감사합니다!

2개의 좋아요