개요

공지사항은 이번 작업 중 가장 연결고리가 많았던 기능이다. 등록 시 입주민 알림 전송, 일정이 있으면 이벤트 자동 등록까지 연동이 필요했다.


고정 공지 정렬

공지사항 목록 조회 시 고정 공지(isPinned: true)가 상단에 오도록 정렬했다.

orderBy: [{ isPinned: "desc" }, { createdAt: "desc" }],

Prisma에서 orderBy에 배열을 넘기면 다중 정렬이 가능하다. 첫 번째 기준이 같으면 두 번째 기준으로 정렬한다. isPinned를 내림차순으로 정렬하면 true(1)false(0)보다 앞에 오고, 같은 isPinned 값끼리는 최신순으로 정렬된다.


입주민 전체 알림 전송

공지사항이 등록되면 해당 아파트 입주민 전체에게 알림을 보내야 했다.

const residents = await prisma.user.findMany({
  where: {
    role: "USER",
    joinStatus: "APPROVED",
    managedApartment: null,
    apartmentName: { not: null },
  },
  select: { id: true },
});

for (const resident of residents) {
  const dedupeKey = `notice-created-${notice.id}-${resident.id}`;
  const existing = await notificationRepository.findNotificationByDedupeKey(dedupeKey);
  if (!existing) {
    await notificationRepository.createNotifiacationRecord({ ... });
  }
}

반복문 안에서 DB 쿼리를 날리는 건 N+1 문제를 일으킬 수 있어서 원래 좋지 않다. 하지만 이 케이스는 이미 입주민 목록을 한 번에 가져왔고, 각 입주민마다 dedupeKey가 다르기 때문에 배치 처리보다 개별 처리가 더 자연스럽다고 판단했다.

입주민이 많아지면 성능 이슈가 생길 수 있는 부분이라, 이후에 큐(Queue)를 활용한 비동기 처리로 개선할 수 있을 것 같다.


이벤트 자동 등록 (브랜치 간 의존성)

공지사항에 startDate가 있으면 이벤트 테이블에도 자동으로 등록되도록 했다.

if (body.startDate) {
  const board = await prisma.board.findUnique({ ... });
  if (board) {
    await prisma.event.create({ ... });
  }
}

문제는 이 기능이 feat/taewon-event 브랜치에서 만든 Event 모델에 의존한다는 것이었다. 공지사항 브랜치(feat/taewon-notice)를 먼저 작업하는 상황에서는 Event 테이블이 없어서 테스트가 실패했다.

해결 방법으로 해당 테스트를 임시로 it.skip()처리하고, 이벤트 브랜치가 merge된 후에 다시 활성화했다.

// 이벤트 브랜치 merge 전
it.skip("startDate가 있으면 이벤트가 자동 등록되어야 한다", ...)

// merge 후 skip 해제
it("startDate가 있으면 이벤트가 자동 등록되어야 한다", ...)