Kanban-Board

간략소개
trello와 비슷한 협업 tool, Kanban-Board-Project
팀 구성
backend: 5
개발기간
Mar 11, 2024
기술
Nest.js MySQL SSE LexoRank
관련 활동
스파르타 코딩 클럽 Node.js_3기

0️⃣ 서비스 소개

칸반보드 프로젝트는 개발 작업을 효율적으로 추적하고 관리하기 위한 도구로서, Trello와 유사한 기능을 제공합니다. 프로젝트에는 주요 기능으로 메시지 알림(SSE) 및 LexoRank를 사용한 칼럼 및 카드 정렬이 포함되어 있습니다.
notion image

1️⃣ 기술 스택

Nest.js Bootstrap MySQL SSE LexoRank

2️⃣ 핵심 기능

Video preview
Num
기능
기술
비고
1
백엔드
Nest.js
서버 개발을 위한 효율적이고 확장 가능한 구조 제공
2
프론트
HTML, CSS(Bootstrap)
사용자 인터페이스 개발
3
데이터
MySQL
데이터베이스로 사용자 정보 및 대화기록 저장
4
메시지 알림
SSE
클라이언트에게 단방향 메시지 정도만 전달하기 때문에 SSE의 단점이 부각되지 않겠다고 생각
5
칼럼 및 카드 정렬
LexoRank
정렬기준 속성값을 생성하여 사잇값을 주는 방식으로 선택

3️⃣  개발 내용

3.1 메시지 알림 (SSE)

  • 기능 설명: 클라이언트에게 단방향 메시지를 실시간으로 전달하여 업무 현황 및 알림을 제공합니다.
  • SSE 활용 이유: 프로젝트의 특성상 단방향 메시지가 주로 필요하며, 복잡한 웹소켓을 도입하지 않고도 간편하게 실시간 업데이트를 제공하기 위해 SSE를 활용했습니다.
// sse.controller.ts import { Controller, Param, Sse } from "@nestjs/common"; import { SseService } from "./sse.service"; @Controller("sse") export class SseController { constructor(private readonly sseService: SseService) {} @Sse(":userId") sendClientAlarm(@Param("userId") userId: string) { return this.sseService.sendClientAlarm(+userId); } }
// sse.service.ts import { Injectable, MessageEvent } from "@nestjs/common"; import { Observable, Subject, filter, map } from "rxjs"; @Injectable() export class SseService { private users$: Subject<any> = new Subject(); private observer = this.users$.asObservable(); emitCardChangeEvent(userId: number, message: string) { this.users$.next({ id: userId, message: message }); } sendClientAlarm(userId: number): Observable<any> { return this.observer.pipe( filter((user) => user.id === userId), map((user) => { return { data: { message: user.message, }, } as MessageEvent; }), ); } }

3.2 칼럼 및 카드 정렬 (LexoRank)

  • 기능 설명: 칸반보드의 칼럼과 카드를 정렬하기 위해 LexoRank를 활용했습니다. LexoRank는 사전식 순서를 기반으로 한 카드 정렬을 가능케 하는 알고리즘입니다.
  • LexoRank 활용 이유: 사용자들이 직관적으로 칼럼과 카드를 정렬하고 이동할 수 있도록 하기 위해 LexoRank를 도입했습니다. 이를 통해 정확한 사잇값을 생성하여 정렬 기준을 제공하고자 했습니다.
// LexoRank Test import { Injectable } from '@nestjs/common'; import { LexoRank } from 'lexorank'; @Injectable() export class AppService { insert(_data: string) { throw new Error('Method not implemented.'); } items: { data: string; lexo: LexoRank }[] = [ { data: 'item1', lexo: null }, { data: 'item2', lexo: null }, { data: 'item3', lexo: null }, { data: 'item4', lexo: null }, { data: 'item5', lexo: null }, { data: 'item6', lexo: null }, { data: 'item7', lexo: null }, ]; constructor() { let lexoRank = LexoRank.middle(); this.items.map((item) => { item.lexo = lexoRank; lexoRank = lexoRank.genPrev(); }); } //! lexo 값 출력 find() { const newItems = this.items .sort((a, b) => { return a.lexo.compareTo(b.lexo); }) .map((item) => { return { data: item.data, lexo: item.lexo.toString() }; }); return newItems; } //! 위치 이동 move(id: number, where: number) { let newLexo: LexoRank; if (where >= this.items.length) { newLexo = this.items[this.items.length - 1].lexo.genNext(); } else if (where <= 0) { newLexo = this.items[0].lexo.genPrev(); } else { newLexo = this.items[where].lexo.between(this.items[where + 1].lexo); } this.items[id].lexo = newLexo; return true; } }

4️⃣ 성장 경험

LexoRank의 한계점과 해결방법

LEXORANK는 목록이나 순서가 자주 변경되는 경우에도 효과적으로 순서를 부여하는 알고리즘 중 하나입니다. 그러나 이 알고리즘이 가끔 한계에 부딪힐 수 있습니다.
특히, 새로운 아이템을 추가하거나 순서를 변경할 때 이전의 순서 정보가 누적되어 부정확한 결과를 가져올 수 있습니다. 이 문제를 해결하기 위해 이동할 때마다 초기화하는 방법을 사용할 수 있습니다. 이것은 일종의 "갱신" 작업으로 생각할 수 있습니다.
LEXORANK를 사용할 때 자주 발생하는 문제 중 하나는 목록이 동적으로 변할 때 이전 순서 정보가 누적되어 예상치 못한 결과를 초래할 수 있다는 것입니다. 이를 방지하기 위해 주기적으로 또는 특정 이벤트가 발생할 때마다 순서를 초기화하여 새로운 아이템이나 변경된 순서에 대해 정확한 순서를 유지할 수 있습니다.
이를 위한 간단한 절차는 다음과 같습니다:
  1. 모든 순서 정보 초기화 이동할 때마다 순서를 초기화하려면 기존의 순서 정보를 완전히 초기화해야 합니다. 저장된 순서 데이터를 삭제하거나 리셋합니다.
  1. 새로운 순서 정보 생성 초기화 후, 새로운 아이템이나 변경된 순서에 대한 정보를 다시 생성합니다. LEXORANK 알고리즘을 다시 적용하거나 필요에 따라 다른 알고리즘을 사용할 수 있습니다.
  1. 순서 정보 저장: 새로운 순서 정보를 저장하여 나중에 참조할 수 있도록 합니다.
이러한 초기화 과정을 얼마나 자주 수행할지는 애플리케이션의 특성과 요구사항에 따라 다릅니다. 목록이 자주 변경되는 경우 더 자주 초기화해야 할 수 있습니다. 이 방법을 통해 목록의 정확한 순서를 유지하면서도 알고리즘의 성능을 최적화 할 수 있습니다.