Beatween을 설계하면서, 단순한 데이터 저장을 넘어서 서비스 목표를 달성할 수 있게 ‘합주서비스를 이용하는 사용자 흐름에 맞는 구조’를 만드는 것이 중요하다고 느꼈습니다. 초기에는 단순하고 빠르게 동작하는 구조를 선택했지만, 실제 기능을 구체화하는 과정에서 사용자 시나리오가 다양해지면서 구조적인 재정비가 필요해졌고, 몇 가지 핵심적인 설계 결정을 내리게 되었습니다. 이 글에서는 그중 가장 중요한 세 가지 구조 변경 과정을 정리하고자 합니다.

 

 

1. personal_space와 team_space의 분리 vs 통합

분리 구조

 

초기에는 개인 스페이스를 따로 테이블로 분리하지 않고, user 테이블이 곧 개인 스페이스의 역할을 하도록 설계했었습니다. 반면 팀 스페이스는 teams 테이블을 별도로 두고 관리했기 때문에, 개인과 팀 스페이스가 서로 다른 방식으로 구성되어 있었습니다. 이처럼 두 스페이스가 이질적인 구조로 운영되다 보니, 스페이스를 기준으로 데이터를 일관되게 참조하거나 처리하기가 구조적으로 어렵다는 한계가 명확히 드러났습니다. 유저가 곡을 생성했을 때 알림을 보내는 처리 흐름에서, 이 구조적 복잡성이 본격적으로 나타났습니다. 알림 테이블에는 스페이스의 종류와 관계없이 하나의 row만 생성되지만, 알림을 실제로 전달하는 방식은 스페이스 유형에 따라 달랐습니다. 개인 스페이스에서는 생성자 본인에게만 알림을 보내면 되지만, 팀 스페이스에서는 해당 팀의 전체 구성원에게 알림을 전송해야 했기 때문에, user_notifications 테이블에는 팀원 수만큼의 row를 추가로 생성해야 했습니다. 따라서 알림 생성 로직에서는 "이 곡이 개인 스페이스에서 생성된 곡인지, 팀 스페이스에서 생성된 곡인지"를 정확히 판단할 수 있어야 했고, 그 판단을 위해 songs 테이블에도 space_type과 같은 보조 필드를 추가하게 되었습니다. 이처럼 판단 로직이 알림을 넘어서 곡 데이터까지 번지게 되면서, 결국 각 테이블마다 스페이스 유형을 구분하기 위한 보조 필드가 하나씩 늘어나기 시작했고, 전반적인 구조는 점점 더 복잡해졌습니다.

 

이 문제를 해결하기 위해, 개인과 팀 스페이스를 하나의 spaces 테이블로 통합하고, space_type 필드를 통해 PERSONAL과 TEAM을 구분하는 방식으로 구조를 변경하였습니다. 이제는 space_id 하나만 있으면 어떤 스페이스에서 발생한 작업인지 명확하게 알 수 있고, 알림, 곡 생성, 접근 권한 등 다양한 기능에서 중복된 조건 분기 없이 일관된 처리 흐름을 만들 수 있게 되었습니다.

 

통합 구조

 

 

 

 

2. 곡 저장 방식: 단일 테이블 vs 원본/복사 테이블 분리

초기에는 곡과 악보를 각각 songs와 sheets라는 하나의 테이블에서 통합 관리하고 있었습니다. 유저가 곡을 생성하면 songs 테이블에 저장되고, 세션 수만큼 악보가 sheets 테이블에 함께 등록되는 구조였습니다. 이때 songs 테이블에는 해당 곡이 DB에 처음 등록된 곡인지, 아니면 기존에 존재하는 곡을 유저가 복사한 것인지를 구분하기 위해 is_original 필드를 사용하였습니다. 예를 들어, 유저 A가 ‘천국’이라는 곡을 생성하면 songs 테이블에 is_original = true로 저장되고, 세션 수만큼의 악보가 AI를 통해 생성되어 sheets 테이블에 삽입됩니다. 이후 유저 B가 동일한 곡명을 생성하면, 유저 A가 생성한 기존 악보들을 조회하여 동일한 구조로 복사한 뒤, sheets 테이블에 새로운 row로 insert하고, 이를 참조하는 형태로 songs 테이블에 is_original = false로 저장합니다. 이 과정에서는 AI 기반 악보 생성을 반복하지 않으며, 기존 데이터를 복제하여 그대로 재사용합니다. 유저가 악보를 편집할 경우에는 해당 세션의 악보만 새로운 row로 추가되며, 편집되지 않은 세션의 악보는 그대로 유지되었습니다. 즉, 복사된 악보는 수정이 발생하는 경우에만 개별적으로 누적 저장되고, 그 외에는 원본 데이터를 공유하는 형태였습니다.

 

이 구조는 당시 서비스 요구사항을 충족하는 데에는 문제 없다고 판단하였지만, 설계 단계에서 곡 복사, 악보 복사, 편집 여부 등을 다양한 상황별로 분기 처리해야 하는 흐름이 과도하게 복잡하다고 느꼈습니다. 특히 테이블 구조만으로는, 유저가 각자의 복사본을 기준으로 악보를 편집하고, 그 이력이 누적되는 구조를 데이터 모델 차원에서 명확하고 직관적으로 표현하기 어려운 부분이 있었습니다.  그래서 현재는 original_songs, copy_songs, original_sheets, copy_sheets로 구조를 명확히 분리하여, 원본은 보존하고, 유저별 복사본은 독립적으로 관리하는 방식으로 전환하였습니다. 유저가 곡을 생성할 경우, 해당 곡이 원본 DB에 없다면 original_songs에 저장되고, 동시에 copy_songs에도 복사본이 자동 등록됩니다. 악보 또한 original_sheets를 기반으로 복사되어 copy_sheets에 저장되며, 유저는 자신의 복사본 악보만 수정할 수 있도록 구조화하였습니다. 이제는 원본 곡과 복사본이 물리적으로 분리되어 있기 때문에, 조회 시 is_original 여부를 조건으로 판단하거나 불필요한 분기 처리를 하지 않아도 되며, 합주 서비스 상의 사용자 협업 흐름도 훨씬 깔끔하게 유지할 수 있는 구조가 되었다고 판단하고 있습니다.

 

 

 

3. copy_songs의 정규화 설계

초기 설계에서는 copy_songs 테이블이 space_id와 category_id를 모두 직접 참조하는 구조였습니다. 또한 spaces와 categories 역시 1:N 관계로 연결되어 있었기 때문에, 하나의 곡이 스페이스를 직접 참조하는 동시에, 자신이 속한 카테고리를 통해서도 간접적으로 동일한 스페이스를 참조하는 구조가 되었습니다. 이 방식은 copy_songs 테이블에서 스페이스 기준으로 곡을 바로 조회할 수 있기 때문에, 조회 측면에서의 단순성과 효율성이라는 분명한 장점이 있었습니다. 그러나 구조적으로는 동일한 정보를 두 경로로 중복 참조하고 있어, 데이터 흐름을 복잡하게 만들고 관리 포인트를 불필요하게 증가시키는 문제가 있었습니다. 특히 copy_songs.space_id와 categories.space_id는 항상 동일하게 설정되도록 삽입 로직을 관리하면 정합성에 문제가 발생하지는 않지만, 의미상 중복이 존재하고, 이를 동기화하는 책임이 애플리케이션 로직에 전가된다는 점에서 바람직하지 않은 구조였습니다.

무엇보다 저희 서비스에서는 모든 곡은 하나의 카테고리에 반드시 속해야 하며, 각 카테고리는 특정 스페이스에 종속되고, 스페이스가 생성될 때는 해당 스페이스에 귀속된 "기본" 카테고리가 자동으로 함께 생성되도록 설계되어 있습니다. 따라서 copy_songs는 category_id만 가지고 있어도 해당 곡이 어떤 스페이스에 속해 있는지를 카테고리를 통해 추론하는 데 전혀 문제가 없었습니다. 이러한 이유로 최종적으로는 copy_songs에서 space_id를 제거하고, category_id만을 통해 간접적으로 스페이스와 연결되도록 정규화된 구조를 확정하였습니다. 조회 시에는 JOIN이 필요하지만, 데이터 흐름을 단일 경로로 유지하고 정합성을 구조 차원에서 보장할 수 있다는 점에서 장기적인 유지보수와 서비스 확장 측면에서 더 안정적이고 일관된 선택이었다고 판단하였습니다.

 

 

 

 

 

 

KBO 중계 실시간 상황에 맞춰 생성된 경기 문제를 클라이언트에 push하기 위해 WebSocket과 SSE 중 어떤 방식을 사용할지 고민했었습니다. 이미 다른 실시간 기능을 위해 WebSocket 로직이 세팅되있어, 편의상 WebSocket을 사용하게 되었지만, SSE와 WebSocket의 정확한 차이를 이해하기 위해 추가로 공부해보려 합니다.

 

 

1. WebSocket

WebSocket은 클라이언트와 서버 간 지속적인 연결을 유지하여 양방향 통신을 가능하게 하는 프로토콜입니다.
초기에 HTTP 프로토콜을 통해 연결(handshake)을 맺은 이후에는, 별도의 추가 요청 없이 양쪽이 자유롭게 메시지를 주고받을 수 있습니다.

특징

  • 양방향 통신이 가능합니다. 서버와 클라이언트 모두 자유롭게 데이터를 전송할 수 있습니다.
  • 연결이 유지되는 동안 실시간 데이터 교환이 가능합니다.
  • 초기 핸드셰이크 이후에는 HTTP 헤더 오버헤드가 줄어들어 통신 효율이 높습니다.

사용 예시

  • 실시간 채팅 애플리케이션
  • 실시간 주식, 암호화폐 가격 알림
  • 스포츠 경기 실시간 중계

 

 

 

 

2. SSE (Server-Sent Events)

SSE는 서버가 클라이언트로 데이터를 일방향으로 지속적으로 전송할 수 있도록 하는 표준 기술입니다.
HTTP 연결을 기반으로 하며, 클라이언트가 서버에 연결을 열어두면 서버는 필요한 때마다 데이터를 전송할 수 있습니다.

특징

  • 서버 → 클라이언트 방향으로만 데이터가 전송됩니다. (단방향 통신)
  • HTTP 프로토콜을 그대로 사용하기 때문에 설정과 구현이 상대적으로 간단합니다.
  • 연결이 끊어졌을 때 자동으로 재연결하는 기능이 기본 제공됩니다.

주로 사용되는 예시

  • 뉴스 속보 전송
  • 주식 시세 알림
  • 실시간 알림 서비스

 

 

3. WebSocket과 SSE의 차이

 

출처 : https://velog.io/@coastby/네트워크-SSE로-구현하는-채팅

 

구분 WebSocket SSE
통신 방향 양방향 (클라이언트 ↔ 서버) 단방향 (서버 → 클라이언트)
프로토콜 WebSocket 전용 프로토콜 HTTP 기반
브라우저 지원 대부분 지원 (구형 브라우저까지 고려 필요) 최신 브라우저는 대부분 지원 (Internet Explorer 미지원)
연결 관리 핸드셰이크 후 별도 연결 유지 HTTP 연결 유지, 자동 재연결 기능 포함
서버 구현 난이도 상대적으로 복잡 상대적으로 단순
주 사용 사례 실시간 채팅, 게임, 실시간 협업 뉴스 알림, 단순 푸시 알림, 실시간 스트리밍
  • 복잡한 양방향 통신이 필요한 경우 → WebSocket이 적합
  • 서버에서 클라이언트로 일방적으로 데이터를 보내는 경우 → SSE가 가볍고 효율적

 

 

 

4. 개발 당시의 WebSocket 선택 회고

1) WebSocket

import lombok.RequiredArgsConstructor;
import org.springframework.messaging.simp.SimpMessagingTemplate;
import org.springframework.stereotype.Service;

@Service
@RequiredArgsConstructor
public class GameProblemSocketServiceImpl implements GameProblemSocketService {

    private final SimpMessagingTemplate messagingTemplate;

    @Override
    public void sendProblem(String gameId, RedisGameProblem problem) {
        String destination = "/topic/problem/" + gameId;
        messagingTemplate.convertAndSend(destination, problem);
    }
}

 

 

2. SSE

import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;

import java.io.IOException;
import java.util.concurrent.ConcurrentHashMap;
import java.util.Map;

@Service
@RequiredArgsConstructor
public class GameProblemSseServiceImpl implements GameProblemSseService {

    private final Map<String, SseEmitter> emitters = new ConcurrentHashMap<>();

    @Override
    public SseEmitter subscribe(String gameId) {
        SseEmitter emitter = new SseEmitter(60L * 1000L * 60L); 
        emitters.put(gameId, emitter);
        emitter.onCompletion(() -> emitters.remove(gameId));
        emitter.onTimeout(() -> emitters.remove(gameId));
        return emitter;
    }

    @Override
    public void sendProblem(String gameId, RedisGameProblem problem) {
        SseEmitter emitter = emitters.get(gameId);
        if (emitter != null) {
            try {
                emitter.send(SseEmitter.event()
                        .name("problem")
                        .data(problem));
            } catch (IOException e) {
                emitters.remove(gameId);
            }
        }
    }
}

 

 

SSE는 단순한 Push 알림 기능을 구현하는 데 충분한 성능을 제공하기 때문에, 기술적으로 사용해도 큰 문제는 없었습니다. 다만, 프로젝트 초기에 WebSocket 인프라가 이미 구축되어 있었기 때문에, 실시간 통신 방식을 WebSocket과 SSE로 이중화하는 것은 오히려 시스템 복잡도를 증가시키는 요소였을 거라고 생각됩니다. 이에 따라 실시간 문제 푸시 기능 역시 WebSocket 기반으로 통합하여 구현하는 것이 아키텍처의 일관성을 유지하고, 유지보수 측면에서도 보다 효율적이라고 정리할 수 있을 것 같습니다.

 

 

 

 

KBO 경기 수는 평균 하루 5경기. 각 경기에 대해 5초 간격으로 실시간 중계 데이터를 수집해야 했습니다.

단순한 크롤링이 아닌,

  • 여러 경기를 동시에
  • 끊김 없이 실시간으로
  • 성능을 고려한 설계

구조

[GameScheduler] → [RelayAsyncExecutor] → [LiveRelayScraper] → [driverMap(gameId → WebDriver)] → [5초 간격 중계 파싱]

 


SPRING WEBFLUX와 SPRING BOOT사이의 고민

처음에는 Spring WebFlux라는 기술을 사용할까 고민했습니다. WebFlux는 적은 컴퓨터 자원으로도 많은 작업을 동시에 처리할 수 있는 장점이 있었습니다. 하지만 크롤링에 사용하려한 Selenium(웹 크롤링 도구)가 WebFlux와 잘 맞지 않는다는 문제가 있었고, 코드를 작성하고 오류를 찾기도 더 어려웠습니다.

 

Selenium은 기본적으로 블로킹 방식으로 작동하는데, 이는 WebFlux의 논블로킹 아키텍처와 충돌합니다. Selenium이 브라우저를 제어하고 웹 페이지와 상호작용할 때 각 명령이 완료될 때까지 실행 스레드를 차단하게 됩니다. 반면 WebFlux는 스레드를 차단하지 않고 이벤트 루프와 비동기 처리를 통해 작동합니다. 이로 인해 Selenium이 WebFlux의 이벤트 루프 스레드를 차단하면 전체 애플리케이션 성능이 저하될 수 있고, 비동기 처리 과정에서 코드가 복잡해지며 예외 처리도 어려워집니다.

 

그래서 Spring Boot와 @Async라는 기능을 사용하기로 했습니다. 이 방식은 각 경기마다 별도의 작업 스레드를 할당해주는 방식인데, 더 잘 알고 있는 기술이라 개발 속도도 빠를 것 같았고 문제가 생겼을 때 해결하기도 더 쉬웠습니다. 하루에 5경기 정도를 처리하는 데는 이 방식으로도 충분하다고 판단했습니다.

 

성능을 좋게 하기 위해 스레드풀이라는 작업 관리 도구를 세밀하게 조정하고, 각 경기마다 전용 처리 공간을 주어 안정성을 높였습니다. 그리고 메모리 사용량도 계속 확인하며 최적화했습니다.

 

 

Selenium 멀티 인스턴스 구조

1. 경기별 WebDriver 분리

경기마다 중계 페이지가 다르므로 WebDriver를 gameId별로 분리했습니다.

private final Map<String, WebDriver> driverMap = new ConcurrentHashMap<>();

public void initDriver(String gameId, int index) {
    WebDriver driver = createDriver(index); // Remote URL로부터 driver 생성
    driverMap.put(gameId, driver);
}
 

 

 

2. Dockerized Selenium Grid 사용

  • selenium-chrome-1 ~ selenium-chrome-5까지 Docker 컨테이너로 구성
selenium.remote.urls=http://selenium-chrome-1:4444/wd/hub,...
 
 

3. WebDriver 할당 최적화

  • 경기당 하나의 드라이버만 사용
  • driverMap에 없으면 initDriver로 생성, 있으면 재사용

 


비동기 & 병렬 실행 구조

1. 스케줄링 기반 병렬 실행

taskScheduler.schedule(
    () -> relayAsyncExecutor.executeRelay(gameId),
    startTime
);

 

 

2. @Async 비동기 처리

@Async("relayExecutor")
public void executeRelay(String gameId) {
    while (!gameEnded(gameId)) {
        RedisGameRelay relay = liveRelayScraper.scrapeRelay(gameId);
        gameCacheService.saveRelay(gameId, relay);
        Thread.sleep(5000);
    }
}

 

  • 각 경기는 @Async로 분리된 쓰레드에서 독립 실행
  • 최대 5경기 동시 실행 처리

 


개발 중 겪었던 이슈 & 해결 과정

1. Selenium 세션 초기화 실패 (SessionNotCreatedException)

  이슈

  • SessionNotCreatedException 발생
  • RemoteWebDriver에서 Chrome 인스턴스 생성 실패
  • 로그에 "invalid address of the remote server" 경고

  원인

  • 로컬 환경에서는 Selenium Remote URL이 localhost, Docker 배포에서는 selenium-chrome-1 등으로 구성되어야 함
  • .properties 설정값을 환경에 맞게 다르게 적용하지 않아서 발생

  해결

  • 로컬 개발용 .env.local에 http://localhost:4444/wd/hub 추가
  • 크롬 드라이버 생성 메서드에서 예외 처리

 

 

2. WebDriver 세션 종료 관리

  이슈

  • 실시간 중계는 경기당 평균 3시간 이상 진행됨
  • WebDriver가 오래 켜져 있으면 메모리 누수나 세션 불안정 문제가 발생할 가능성이 있었음

  원인

  • 처음에는 일정 시간 후 자동 종료(TaskScheduler로 3시간 뒤 종료)를 고려했

  해결

  • 실시간 중계 데이터에서 “경기 종료” 텍스트가 중계 화면에 표시되는 시점을 파싱
  • 해당 멘트를 감지하면 driver.quit()으로 WebDriver 세션을 안전하게 종료

 

 

3. 경기 동시 실행 시 드라이버 중복 문제

  이슈

  • 한 인스턴스에서 2개 경기 크롤링을 시도할 때 WebDriver가 충돌하는 현상

  원인

  • Selenium Grid URL을 index로 나눴지만, 실행 타이밍이 겹치면 같은 인덱스로 두 경기 실행됨

  해결

  • gameId → index를 매핑한 Map<String, Integer> 생성해서 한 번 배정된 index는 고정
  • 드라이버 부하 분산을 위해 추후에는 남는 컨테이너 탐색 + 자동 할당 로직 도입 고려 중

 

 

 

 

'특화프로젝트' 카테고리의 다른 글

WebSocket과 SSE  (0) 2025.04.26
블록체인과 머클 트리  (0) 2025.03.12

블록체인의 보안 개념

블록체인은 기존의 보안 방식과는 다른 접근 방식을 취한다. 기존의 보안 시스템은 정보를 최소한의 사람만 알고 조작할 수 있도록 폐쇄적인 환경을 조성했다. 예를 들어, 은행 시스템에서 데이터에 접근할 수 있는 사람은 극히 제한적이며, 내부자가 정보를 조작할 경우 이를 감지하기 어려울 수 있다.

반면, 블록체인은 가능한 많은 사람이 정보를 공유함으로써 보안을 유지하는 구조를 가진다. 블록체인의 모든 참여자는 동일한 데이터를 가지고 있으며, 특정 정보를 조작하려면 네트워크에 참여한 모든 사용자의 정보를 동시에 변경해야 한다. 이는 사실상 불가능에 가까운 작업이며, 블록체인의 신뢰성을 보장하는 중요한 요소가 된다. 이러한 신뢰성의 기반은 암호학이며, 블록체인은 암호학적 구조를 통해 데이터를 보호하고 있다.

 


암호학과 머클 트리

1) 머클 트리란?

머클 트리(Merkle Tree)는 랄프 머클(Ralph Merkle)이 고안한 트리 구조로, 해시 함수를 이용해 데이터의 무결성을 검증하는 역할을 한다. 블록체인에서 머클 트리는 라이트 노드와 거래 검증에 핵심적인 요소로 활용된다.

머클 트리는 이진 트리 구조를 가지며, 트랜잭션 데이터를 해시값으로 변환한 후 이를 결합하여 상위 해시 값을 생성하는 방식으로 구성된다. 최종적으로 하나의 머클 루트(Merkle Root) 값이 생성되며, 이 값은 해당 블록의 모든 트랜잭션을 대표하는 해시값이 된다.

만약 블록 내 트랜잭션이 변경될 경우, 머클 트리의 구조상 머클 루트 값이 달라지게 된다. 따라서 블록체인은 머클 루트 값을 비교하는 것만으로도 데이터의 무결성을 쉽게 확인할 수 있다.

 

2) 라이트 노드란?

블록체인 네트워크에는 풀 노드(Full Node)와 라이트 노드(Light Node)가 존재한다.

  • 풀 노드(Full Node): 블록체인의 모든 데이터를 저장하고 검증하는 노드로, 전체 블록 데이터를 가지고 있어 완전한 검증이 가능하다.
  • 라이트 노드(Light Node): 모든 블록 데이터를 저장하지 않고, 머클 루트 값과 블록 헤더만 저장하는 노드이다.

라이트 노드는 블록체인의 전체 트랜잭션을 저장하지 않고도 특정 트랜잭션이 블록에 포함되어 있는지 확인할 수 있다. 이는 머클 증명(Merkle Proof)이라는 방식을 통해 이루어진다. 라이트 노드는 머클 루트 값을 가지고 있으며, 특정 트랜잭션의 포함 여부를 확인하기 위해 필요한 최소한의 해시값만 요청하여 검증할 수 있다.

이러한 방식 덕분에 라이트 노드는 저장 공간이 적고, 연산 능력이 제한적인 환경에서도 블록체인 네트워크에 참여할 수 있다. 예를 들어, 모바일 기기나 IoT 기기에서도 블록체인을 활용할 수 있도록 해준다.

 


머클 트리의 블록체인 적용

머클 트리는 한 블록 내의 트랜잭션을 대상으로 만들어지며, 다음과 같은 과정으로 구성된다.

  1. 블록 안에 포함된 트랜잭션 각각의 해시값을 계산한다.
  2. 인접한 해시값을 두 개씩 묶어 새로운 해시를 생성하고 상위 노드를 구성한다.
  3. 이 과정을 반복하여 최종적으로 하나의 머클 루트에 도달한다.
  4. 생성된 머클 루트 값은 블록 헤더에 포함된다.

각 블록은 독립적인 머클 트리를 가지며, 이전 블록이나 다음 블록의 트랜잭션과는 관계없이 해당 블록 내의 트랜잭션만을 기반으로 한다. 이를 통해 데이터 무결성을 효과적으로 검증할 수 있다.

 


이더리움에서의 머클 트리 활용

이더리움은 단순한 머클 트리가 아니라 머클-패트리시아 트리(Merkle-Patricia Trie, MPT)라는 변형된 구조를 사용한다. 이는 블록체인 상태를 보다 효율적으로 저장하고 업데이트하기 위해 도입되었다.

이더리움에서는 총 3개의 머클-패트리시아 트리를 사용한다.

  1. 상태 트리(State Trie): 계정 상태(잔액, 스마트 컨트랙트 정보 등)를 저장한다.
  2. 트랜잭션 트리(Transaction Trie): 블록에 포함된 모든 트랜잭션을 저장한다.
  3. 영수증 트리(Receipt Trie): 트랜잭션 실행 결과(가스 사용량, 이벤트 로그 등)를 저장한다.

머클-패트리시아 트리는 단순한 머클 트리와 달리 키-값 저장이 가능하며, 데이터 추가 및 수정이 가능하다. 이를 통해 빠른 조회 및 업데이트가 가능해지며, 이더리움의 스마트 컨트랙트 실행을 효율적으로 관리할 수 있다.

 


결론

머클 트리는 블록체인의 데이터 무결성을 보장하는 핵심적인 요소이다. 특히 라이트 노드를 통해 블록체인의 확장성과 접근성을 높이는 역할을 한다. 이더리움에서는 머클-패트리시아 트리를 활용하여 블록체인의 상태를 보다 효율적으로 관리하고 있다. 블록체인의 신뢰성과 보안성을 유지하는 데 있어 암호학적 구조는 필수적인 요소이며, 머클 트리는 그 중심에 있는 기술이라 할 수 있다.

'특화프로젝트' 카테고리의 다른 글

WebSocket과 SSE  (0) 2025.04.26
Selenium으로 구현한 KBO 경기 실시간 크롤링 시스템  (0) 2025.04.20

개발 단계에서는 확장 프로그램 관리 페이지(chrome://extensions)에서 수동으로 설치하여 쉽게 테스트할 수 있습니다. 매번 다시 로드할 필요 없이, 로컬에서 빌드 파일을 업데이트하면 자동으로 반영됩니다. 추가적으로, 확장프로그램을 개발한 Vue.js 환경에서 코드 저장 시 빌드가 자동으로 실행되게 설정해 번거로운 절차를 줄였습니다.

개발자모드 확장 프로그램 설치

// package.json
// "dev:extension": "vite build --watch"  => 저장 및 빌드 동시

"scripts": {
    "dev": "vite",
    "dev:extension": "vite build --watch",
    "build": "vite build",
    "build:extension": "zip -r Byeoldam.zip dist/",
    "preview": "vite preview"
  }

 

 

 

 

프로젝트를 진행하며 실사용자를 만들어 사용자 중심 리팩토링을 진행하기 위해 확장프로그램을 웹스토어에 등록했습니다. 공식문서에 따르면 3일안에는 모든 업데이트가 반영된다 했지만 저처럼 처음 개발자 등록을 하고 첫 확장 프로그램을 등록하는 경우 3주까지도 걸릴 수 있다고 안내되있었습니다.

 

 

1. 개발자 등록하기

https://chrome.google.com/webstore/devconsole/4c1cfdaf-1e25-4576-89f6-c0ee5eeb0923

 

Chrome Web Store

로그인 Chrome 웹 스토어로 이동

accounts.google.com

개발자 등록 절차는 어렵지 않았습니다. 체크할 사항은 2가지 입니다.

1) 개발자 등록으로 5$ 한 번만 결제하면 이후 추가로 청구되는 비용이 없습니다.

2) 결제 등록 창에서 국가/지역에 한국이 없는데, 저같은 경우 일본이라하고 우편번호도 양식맞춰서 임의로 등록했습니다.

 

 

완료하면, 대시보드 창으로 이동합니다.

 

위 사진은 현재 게시된 상태일 때 보이는 화면입니다. 우측 상단 새 항목을 클릭하면 프로그램 등록을 진행할 수 있습니다.

 

 

 

 

2. 확장 프로그램 등록하기 - 파일 선택

 

 

 

이 때, 파일은 수동으로 업로드할 때와 달리 dist 폴더 자체가 아니라, dist 폴더 안의 파일들을 압축해야 합니다. 

 

 

 

 

3. 확장 프로그램 등록하기 - 스토어 등록정보

파일 로드 성공 후 등록 화면

 

 

다음으로, 스토어 등록정보와, 개인정보 보호, 배포 탭을 하나씩 선택하며 필수록 입력해야하는 내용들을 성실히 작성해야합니다. 스토어 등록정보 탭에서는 그래픽 저작물에 이미지들이 요구한 픽셀에 딱 맞아야 업로드가 되니, 미리 잘 준비하는 것을 추천드립니다.

 

스토어 아이콘(필수) 128x128 픽셀

캡쳐 화면(최소 1개 필수) 1280x800 또는 640x400 JPEG 또는 24bit PNG

마키 프로모션 타일 1400X560

 

 

 

 

 

4. 확장 프로그램 등록하기 - 개인 정보 보호

 

개인 정보 보호탭에서 입력해야할 정보로는 크게 두가지가 있습니다. 첫번째로, manifest.json 파일에서 설정한 permissions와 content_scripts에 설정한 권한들에 대한 설명을 기재해야 합니다. 크롬 웹스토어에서 확장 프로그램을 등록할 때, 사용자가 어떤 데이터에 접근하는지 투명하게 공개해야 하기 때문입니다. (입력해야하는 권한 들은 본인이 로드한 파일 기준으로 자동으로 생성됩니다.)

 

 

처음에 위와 같이 storage, activeTab, tabs 3가지를 permissions로 설정했었는데, 아래와 같은 이유로 tabs 권한을 삭제해 달라는 요청이 왔었습니다. 브라우저 탭 이동 동작을 위해 tabs API를 사용해서 권한으로 설정했었는데, 기본적인 동작이라 권한 요청까지는 필요없다는 내용이었습니다.(공식 문서를 이런 부분때문에 꼼꼼히 읽어봐야함을 느꼈습니다.)

 

 

 

 

두번째로, 사용자 데이터 사용을 체크해줘야하는데, 북마크할 때 사용자가 접근하는 여러 웹페이지의 정보들을 수집하기 때문에 해당 하는 사항들을 다 체크해줘야 했습니다. 또한 어떻게 이 데이터를 서비스에서 활용하는지 사용자에게 안내할 개인정보처리방침 문서의 경우 노션으로 작성해서 아래와 같이 링크를 첨부했습니다. 개인정보처리방침 작성의 경우 GPT의 도움을 받아서 금방 작성할 수 있었습니다.

 

 

 

 

5. 확장 프로그램 게시 완료

이렇게 나머지 필수 사항들을 다 체크해서 완료하면, 3일 뒤 정도 아래와 같은 메일이 왔습니다. 그리고 몇 시간 뒤에 바로 웹스토어에서 조회도 가능했습니다.(다양한 등록후기들을 봤을때 저처럼 사용자 데이터를 요구하는 프로그램이 아니면 3일 이내에 빠르게 처리되는 경우가 많은 것 같습니다. 하지만 저처럼 사용자 데이터를 요구하는 순간 좀 더 확인 절차가 필요해져서 3일 이상 걸릴 확률이 높습니다.) 

 

 

 

 

 

 

'공통프로젝트' 카테고리의 다른 글

Chrome Extension 개발하기 - (3)  (0) 2025.02.23
Chrome Extension 개발하기 - (2)  (4) 2025.01.28
Chrome Extension 개발하기 - (1)  (6) 2025.01.24
FastAPI  (2) 2025.01.17

1. 파일 디렉토리 트리

프로젝트는 크게 확장 프로그램의 핵심 기능을 담당하는 public 디렉토리와 Vue.js 애플리케이션의 src 디렉토리로 명확히 분리해서, 각 영역의 독립적인 개발과 유지보수가 용이하도록 했습니다.

 

📂 프로젝트 루트
├── 📂 public                 # 정적 파일이 위치
│   ├── 📄 background.js      # 백그라운드 스크립트 (크롬 익스텐션의 핵심 로직)
│   ├── 📄 contentScript.js   # 컨텐트 스크립트 (웹페이지 DOM 접근)
│   ├── 📄 favicon.ico        # 아이콘 파일
│   └── 📄 manifest.json      # 크롬 익스텐션 설정 파일
├── 📂 src                    # Vue 프로젝트의 핵심 코드
│   ├── 📂 assets             # 정적 리소스 (이미지, CSS 등)
│   │   ├── 📂 css            # CSS 파일
│   │   │   ├── 📄 index.css  # Tailwind CSS 메인 파일
│   │   │   └── 📄 main.css   # 프리텐다드 폰트 등 커스텀 CSS
│   ├── 📂 components         # Vue 컴포넌트
│   ├── 📂 router             # Vue Router 설정
│   │   └── 📄 index.js       # 라우터 설정 파일
│   ├── 📂 store              # 상태 관리 (Pinia)
│   │   └── 📄 bookmarkStore.js  # 북마크 관련 상태 저장소
│   ├── 📂 views              # Vue 페이지 컴포넌트
│   │   ├── 📄 StorageView.vue   # 저장소 페이지
│   │   ├── 📄 AlarmView.vue  # 알람 페이지
│   │   └── 📄 FeedView.vue   # 피드 페이지
│   ├── 📄 App.vue            # Vue 루트 컴포넌트
│   └── 📄 main.js            # Vue 앱 초기화 (엔트리 포인트)
├── 📄 index.html             # HTML 템플릿 (Vue 앱 마운트 위치)
├── 📄 jsconfig.json          # JS 프로젝트 설정
├── 📄 package-lock.json      # 패키지 버전 잠금 파일
├── 📄 package.json           # 프로젝트 종속성 및 설정
├── 📄 postcss.config.js      # PostCSS 설정 (Tailwind CSS)
├── 📄 README.md              # 프로젝트 문서
├── 📄 tailwind.config.js     # Tailwind CSS 설정
└── 📄 vite.config.js         # Vite 빌드 설정

 

1) 확장프로그램 핵심 기능

  • background.js: 브라우저 이벤트 처리, 상태 관리
  • contentScript.js: 웹페이지 정보 수집(url, title), 읽기 시간 계산
  • manifest.json: 확장프로그램 설정 및 권한 관리

2) 사용자 인터페이스 (Vue.js)

  • App.vue:
    • 루트 컴포넌트
    • RouterLink를 통한 페이지 네비게이션
  • views/: 각 페이지 뷰 컴포넌트
    • StorageView.vue: 북마크 저장 페이지
    • AlarmView.vue: 알림 페이지(북마크 N일 경과 및 공유 컬렉션 초대 알림)
    • FeedView.vue: 피드 리스트 페이지
  • assets/: 스타일시트와 정적 리소스

3) 라우팅 및 상태 관리

  • router/index.js: SPA 라우팅 설정
  • bookmarkStore.js: 북마크 관련 상태 관리

 

 


2. 아키텍처

 

1) Content Script

  • 웹 페이지에 직접 삽입되어 실행되는 JavaScript
  • DOM 파싱 및 필요한 정보 추출
  • 웹 페이지 콘텐츠 정제 및 가공
  • Vue Application에 데이터 전달하기 위해서는 Background Script(Service Worker)를 통한 메시지 패싱이 필요

2) Background.js

  • 크롬 익스텐션의 백그라운드 프로세스
  • Content Script와 Vue 앱 사이의 중계자 역할
  • 이벤트 핸들링(탭 변경, 페이지 업데이트 등 브라우저 이벤트 감지 및 처리)
  • 메시지 라우팅 및 상태 관리 담당

3) Vue Application

  • 사용자 인터페이스 제공
  • 북마크 데이터 표시 및 관리
  • Background.js와의 메시지 통신

4) 데이터 흐름

  1. Content Script가 웹 페이지 DOM을 파싱하고 필요한 데이터 추출
  2. 추출된 데이터를 Background.js로 전송
  3. Background.js에서 데이터 처리 및 저장
  4. Vue 애플리케이션에서 처리된 데이터를 표시

 

 


3. 기능





1. 데이터 로드
1)RSS 구독 가능여부
2)유저 컬렉션 리스트
3) 웹페이지 내용 기반 추천 키워드
4) 알림리스트 
5) 피드리스트 
2. 북마크 저장
3. RSS 구독

1. 공유 컬렉션 수락 
2. 공유 컬렉션 거절 및 경과 알림 삭제
3. 알림 전체 삭제
 1. 별담 사이트 RSS 페이지 바로가기
(구독 피드 목록 클릭시)

 

 

 


4. ChatGPT를 활용한 URL 기반 키워드 추출 개선 과정

초기 접근 방식과 한계

처음에는 ChatGPT 웹 인터페이스를 통해 URL 기반 키워드 추출을 테스트했습니다. 테스트 결과 충분한 정확도를 보여 ChatGPT API 도입을 결정했으나, 실제 구현 과정에서 중요한 문제점을 발견했습니다. ChatGPT API는 웹 브라우징 기능을 지원하지 않아, URL을 단순 텍스트로만 인식한다는 한계가 있었습니다. 이로 인해 실제 웹사이트의 컨텍스트를 파악하지 못하고 키워드 추출의 품질이 현저히 저하되었습니다.

 

DOM 파싱 접근의 한계

첫 번째 대안으로 고려했던 것은 DOM 파싱을 통한 컨텐츠 추출이었습니다. 하지만 북마크 저장 대상이 되는 웹사이트들의 DOM 구조가 표준화되어 있지 않아, querySelector를 통한 범용적인 컨텐츠 추출은 현실적으로 어려웠습니다.

 

개선된 솔루션

예시를 기반한 상세한 프롬프트 엔지니어링을 하고, ChatGPT에게 북마크할 웹페이지의 URL과 제목을 제공하면서, 별담 서비스 유저들이 실제로 사용하는 태그 리스트 중에서 우선적으로 선택하도록 했습니다.

 

결과 및 이점

 

  • 키워드 추출 정확도 향상 : 무제한적인 키워드 생성이 아닌, 검증된 태그 풀 내에서의 선택
  • 추천 시스템과의 시너지 : 사용자들이 비슷한 콘텐츠에 대해 일관된 태그를 사용하게 되어 추천 시스템의 정확도 향상으로 이어짐

 

 

프롬프트 엔지니어링

SystemMessage systemMessage = new SystemMessage("""
                    당신은 웹 콘텐츠 태깅 및 키워드 추출 전문가입니다.
                
                    역할:
                    사용자가 북마크하는 웹페이지의 내용을 나중에 쉽게 찾을 수 있도록
                    가장 적절한 태그나 키워드를 추천합니다.
                
                    분석 대상:
                    1. URL 분석 (도메인 및 특정 패턴 참고)
                       - 개발 관련: stackoverflow.com, docs.spring.io
                       - 학습 관련: inflearn.com, nomadcoders.co, champ.hackers.com
                       - 취업 관련: jobkorea.co.kr, wanted.co.kr, saramin.co.kr
                
                
                    2. 제목 분석 (다음 도메인 또는 유사한 도메인일 경우 수행)
                       - 블로그 (velog.io, tistory.com, blog.naver.com 등)
                       - 동영상 (youtube.com, tv.naver.com 등)
                       - 커뮤니티 (stackoverflow.com 등)
                       - 뉴스 및 기타 콘텐츠
                
                    주어진 URL과 제목을 분석하여 가장 연관성 높은 태그를 아래 목록에서 선택하거나,
                    적절한 태그가 없는 경우 핵심 키워드를 추출해주세요.
                
                    현재 사용 가능한 태그 목록: %s
                
                    태그 선택 규칙:
                    1. 태그 유사도 판단 (URL + 제목 종합 분석)
                       - 강한 연관성: 제공된 태그 목록에서 선택
                       - 부분 연관성: 제공된 태그와 추출 키워드 조합
                       - 약한 연관성: 제목에서 직접 키워드 추출
                
                    2. 태그 선택 우선순위
                       1순위: 콘텐츠의 핵심을 가장 정확하게 표현하는 태그
                         - 구체적인 기술/도구
                         - 명확한 주제/활동
                         - 특정 분야/장소
                       2순위: 콘텐츠 이해나 분류에 도움이 되는 보조 태그
                         - 관련 분야
                         - 상위 카테고리
                         - 연관 주제
                
                    3. URL 분석 참고사항:
                       - 공식 문서/레퍼런스:
                         docs.spring.io, developer.mozilla.org 등 도메인에서 주제 파악
                       - URL 기반 분석의 구체화:
                         특정 URL 패턴을 참고하여 더 정교한 분석 수행
                         (예: stackoverflow.com/questions/ → 개발 질문)
                       - 제목 중심 분석:
                         • 블로그 (velog.io, tistory.com, blog.naver.com 등)
                         • 동영상 (youtube.com, tv.naver.com 등)
                         • 커뮤니티 (stackoverflow.com 등)
                
                    4. 태그 선택 예시:
                       블로그/개인 콘텐츠:
                       - URL: "velog.io/@user/post-123"
                         제목: "스프링 시큐리티 기초"
                         -> [SPRING, 시큐리티, 개발]
                
                       동영상:
                       - URL: "https://www.youtube.com/watch?v=V1PI7KTCcWo"
                         제목: "30분 전신 홈트레이닝"
                         -> [홈트레이닝, 전신, 운동]
                
                       기사:
                       - URL: "https://sgsg.hankyung.com/article/2025020700311"
                         제목: "시사이슈 찬반토론 상장 폐지 요건 완화해야 할"
                         -> [상장폐지, 시사이슈, 학습]
                
                    5. 추천 개수:
                       - 정확도 높은 태그/키워드 3개
                
                    6. 응답 형식:
                       - 응답은 키워드들이 쉼표로 구분되고, 각 키워드 뒤에는 공백 한 칸이 포함됩니다.
                       - 예시: [키워드1, 키워드2, 키워드3]
                """.formatted(tagList.toString()));

        UserMessage userMessage = new UserMessage("""
                다음 웹페이지의 URL과 제목을 분석하여 적절한 태그나 키워드를 추출해주세요:
                URL: %s
                제목: %s
                """.formatted(request.getSiteUrl(), request.getTitle()));

 

 

 

 

'공통프로젝트' 카테고리의 다른 글

Chrome Extension 개발자 등록 후기  (0) 2025.03.02
Chrome Extension 개발하기 - (2)  (4) 2025.01.28
Chrome Extension 개발하기 - (1)  (6) 2025.01.24
FastAPI  (2) 2025.01.17

1. permissions 설정하기

Chrome 확장 프로그램의 manifest.json에는 확장 프로그램이 필요로 하는 권한(permissions)을 설정하는 항목이 있습니다. chrome for developers에 따르면 아래와 같이 설명되있습니다.

Permissions API를 사용하면 개발자가 권한 요청에 대한 설명을 제공하고, 새로운 기능을 *점진적으로 도입하여 사용자에게 확장 프로그램을 위험 없이 소개할 수 있습니다. 이렇게 하면 사용자가 부여할 엑세스 수준과 사용 설정할 기능을 지정할 수 있습니다.

*점진적 도입이란 확장 프로그램이 처음 설치될 때 최소한의 권한만 요청하고, 추가적인 기능이 필요할 때만 선택으로 권한을 요청하는 방식을 의미

 

 

 

1) 필수 권한과 선택적 권한

확장 프로그램의 권한은 크게 "필수 권환"과 "선택적 권한"으로 나뉩니다.

필수 권한 : 확장 프로그램의 핵심 기능을 수행하는 데 반드시 필요한 권한 입니다.

  • tabs, storage, identity 등
  • manifest.json의 "permissions" 항목에 선언합니다.

선택적 권한 : 특정 기능을 사용할 때만 필요한 권한으로, 사용자 동의 후 활성화됩니다.

  • downloads, 특정 도메인 접근 권한 등
  • manifest.json의 "optional_permissions"항목에 선언하며, chrome.permissions.request()를 통해 동적으로 요청할 수 있습니다.

두 권한의 이점은 아래와 같기 때문에 서비스 요구사항에 따라 개발해야합니다.

[ 필수 권한의 이점 ]
1. 메시지 감소: 확장 프로그램은 사용자에게 한 번만 메시지를 표시하여 모든 권한을 수락하도록 요청할 수 있습니다.
2. 더 간단한 개발: 필수 권한이 있는지 확인할 필요가 없습니다.

[ 선택사항 권한의 이점 ]
1. 보안 강화 : 사용자가 필요한 권한만 사용 설정하므로 확장 프로그램이 더 적은 권한으로 실행됩니다.사용자에게 2. 더 나은 정보 제공: 사용자가 관련 기능을 사용 설정할 때 확장 프로그램에 특정 권한이 필요한 이유를 설명할 수 있습니다.
3. 더 쉬운 업그레이드: 확장 프로그램을 업그레이드할 때 업그레이드에서 필수 권한이 아닌 선택적 권한을 추가하는 경우 Chrome에서 사용자를 위해 확장 프로그램을 사용 중지하지 않습니다.

 

https://developer.chrome.com/docs/extensions/reference/api/permissions?hl=ko

 

chrome.permissions  |  API  |  Chrome for Developers

이 페이지는 Cloud Translation API를 통해 번역되었습니다. chrome.permissions 컬렉션을 사용해 정리하기 내 환경설정을 기준으로 콘텐츠를 저장하고 분류하세요. 설명 chrome.permissions API를 사용하여 설치

developer.chrome.com

 

 

2) 권한 설정과 API 사용 예시

✅ 브라우저 탭의 URL 가져오기

  1. manifest.json 파일에 "tabs"권한을 추가 : 권한 없이 API 호출시 오류 발생, 필요한 권한만 최소한으로 설정
{
  "name": "My Extension",
  "version": "1.0",
  "manifest_version": 3,
  "permissions": ["tabs"]
}

  

   2. chrome.tabs.query() API 사용

chrome.tabs.query({ active: true, currentWindow: true }, function (tabs) {
  console.log("현재 활성 탭의 URL:", tabs[0].url);
});

 

4️⃣ 선택적 권한의 경우 권한을 요청한 후 사용자가 거부할 수도 있으므로 권한이 있는지 확인 후 API를 호출하는 로직으로 설계해야 합니다.

 


2. background.js

background.js는 크롬 확장 프로그램의 백그라운트 스크립트로, 브라우저가 실행되는 동안 계곧 동작하면서 이벤트를 처리하는 역할을 합니다. 위에서 작성한 API호출 같은 작업을 해당 파일에서 진행합니다.

 

이번 크롬 익스텐션 개발에 vue.js 프레임워크를 사용하면서, background.js를 manifest.json과 같은 public/ 폴더에 배치했습니다. 그 이유는 src/ 폴더에 두면 Vite 빌드 과정에서 코드 변환이 발생하는데, 크롬 확장 프로그램에서는 원본 코드 그대로 실행되어야 하기 때문입니다. 따라서 빌드 영향을 받지 않는 public/ 폴더 아래에 두었습니다.

 

 

dist/ 폴더는 배포용 빌드 파일이 생성되는 폴더입니다. src/폴더의 개발용 코드가 변환 및 압축되어 최적화된 상태로 dist에 저장되고, public/ 폴더의 파일들은 변환없이 그대로 복사됩니다.

 

background.js에서는 API호출뿐만 아니라 다양한 기능을 처리합니다.

  1. 이벤트 리스너 처리 : 브라우저에서 특정 이벤트가 발생했을 때 확장 프로그램이 반응하도록 설정(브라우저 상 확장 프로그램 아이콘 클릭 이벤트 감지 등)
  2. 메시징 시스템 처리 : 팝업 script와 background script, 콘텐츠 script 간 데이터를 주고 받기 위한 기능
  3. 알림 생성 : 특정 조건이 충족되면 사용자에게 알림을 보내는 기능 구현
  4. 주기적 작업 수행 : 일정 간격마다 실행되는 기능(사용자 저장 목록 업데이트 등)
  5. 자동 로그인, 쿠키 관리 : 쿠키 정보를 가져오거나 설정하여 사용자 자동 로그인 지원

 

 

'공통프로젝트' 카테고리의 다른 글

Chrome Extension 개발자 등록 후기  (0) 2025.03.02
Chrome Extension 개발하기 - (3)  (0) 2025.02.23
Chrome Extension 개발하기 - (1)  (6) 2025.01.24
FastAPI  (2) 2025.01.17

1. Vue.js를 활용한 크롬 익스텐션 개발 세팅

크롬 확장 프로그램은 Javascript, CSS, HTML 3가지로 구성된 단일 폴더로 이루어져있습니다. 때문에 익숙한 Vue.js의 컴포넌트 기반 개발 방식을 활용하여 UI 요소를 재사용 가능하게 설계하고, 유지보수성을 높이는 방향으로 효율적인 개발을 진행하고자 합니다.

 

1) manifest 파일 작성하기

크롬 뿐만아니라 웨일, 엣지 등 Chromium 기반 브라우저(=오픈소스 웹 브라우저 프로젝트)의 확장 프로그램 개발을 위해선 manifest.json 파일이 필요합니다. manifest.json 파일은 확장 프로그램의 구조와 동작 방식을 정의하고, 브라우저와 상호작용하는 방법을 설정합니다.

 

크롬 웹스토어에 등록할 예정이라 manifest_version을 3으로 설정(크롬에서 유일하게 지원되는 값은 3)했습니다. 실사용자 유치를 목표로 한다면, 브라우저별 지원 버전을 확인하여 적절히 설정하는 것이 중요합니다.

{
  "name": "서비스명",
  "description": "설명",
  "version": "0.0.1",
  "manifest_version": 3,
  "action": {
    "default_popup": "index.html"
  }
}

 

name :  확장 프로그램 식별 문자, 배포 후 웹 스토어에 검색되는 명

description : 확장 프로그램에 대한 설명

version : 확장 프로그램의 버전

action.default_popup : 확장 프로그램을 눌렀을 때, 뜨게 될 팝업

 

이 외에도 개발 공식 문서를 참고해 추가해갈 예정입니다.

https://developer.chrome.com/docs/extensions/reference/manifest?hl=ko

 

매니페스트 파일 형식  |  Manifest  |  Chrome for Developers

Chrome 확장 프로그램의 manifest.json 속성 개요입니다.

developer.chrome.com

 

 

 

2) vue.js 배포 파일 생성하기

Vue 프로젝트 빌드 실행방법

npm run build

 

빌드가 완료되면, Vue 프로젝트 폴더안에 dist라는 폴더(default 설정)가 생성되고, dist 폴던안에 배포할 수 있는 최적화된 정적 파일들이 포함됩니다.

 

테스트를 위해 chrome://extensions/페이지로 이동 후 우측 상단 개발자 모드를 활성화 시킵니다. 3개의 활성화 된 버튼 중

압축해제된 확장 프로그램을 로드합니다.를 선택해 빌드한 프로젝트를 업로드합니다. 그러면, 아래와 같이 확장 프로그램 버튼이 생기며 바로 확인할 수 있습니다.

 

프로그램 아이콘을 지정하지 않았는데, 임의로 지정한 서비스명 첫 글자로 아이콘이 만들어진 것 같습니다.

 

 

 

이 외에도 개발을 위해 공식 문서를 꼼꼼히 참고하여 진행해야겠습니다. 크롬 웹스토어에 게시하려면 5달러만 결제하면 자격이 부여되지만, 개인 정보 처리 방침 등 주의 깊게 작성해야 할 내용들이 몇 가지 있습니다. 이 중 하나라도 방침에 맞지 않으면 재게시 요청을 해야 하므로, 한정된 프로젝트 기한 내에 개발을 완료하기 위해서는 해당 개발 부분을 우선순위로 두고 진행해야 합니다.

https://developer.chrome.com/docs/extensions?hl=ko

 

Chrome 확장 프로그램  |  Chrome Extensions  |  Chrome for Developers

Chrome 확장 프로그램 개발 방법을 알아보세요.

developer.chrome.com

 

 


2. 기술적 고민

크롬 익스텐션의 기능 특성 상 빠른 데이터 로딩과 응답성이 중요하다고 생각했습니다. 그래서 이와 관련해 개발 시 고려해야할 부분을 정리해봤습니다.

 

 

1) API 호출 최적화

크롬 익스텐션 팝업을 3개의 탭으로 구성하면서, 각 탭에서 API를 호출하는 방식을 고민했습니다. 탭1은 사용자들이 가장 자주 사용하는 기본 페이지로, GPT API를 통해 데이터를 처리하는 과정을 포함하고 있습니다. 그래서 팝업 초기 로딩 시에는 탭1과 관련된 API만 호출하는 방향으로 결정했습니다.

 

탭2와 탭3은 사용 빈도가 상대적으로 낮고, 최신 데이터가 중요한 영역이므로 탭 이동 시점에서 API를 호출하는 방식을 채택했습니다. 이 방식은 불필요한 API 호출을 방지해 초기 팝업 로딩 속도를 개선하고, 사용자가 탭을 이동할 때마다 최신 데이터를 실시간으로 제공할 수 있는 이점이 있습니다.

 

2) 탭 이동 간 캐싱 고려

동일한 탭으로 재방문할 때마다 API를 호출하지 않도록, 탭 데이터 캐싱을 활용하는 방식을 고려하였습니다. 예를 들어, 탭1에서 다른 탭으로 이동한 후 다시 탭1으로 돌아올 경우, 캐싱된 데이터를 우선적으로 표시하도록 설계하는 것입니다. 탭1의 경우 최신 데이터보다는 빠른 접근성과 사용자 경험이 더 중요하기 때문에 이러한 방식이 적합하다고 판단했습니다. 이렇게 불필요한 네트워크 요청을 줄여 성능을 최적화할 수 있으므로, 클라이언트 측(localStorage, SessionStorage) 캐싱을 사용해 적용해보려합니다.

 

 

 

'공통프로젝트' 카테고리의 다른 글

Chrome Extension 개발자 등록 후기  (0) 2025.03.02
Chrome Extension 개발하기 - (3)  (0) 2025.02.23
Chrome Extension 개발하기 - (2)  (4) 2025.01.28
FastAPI  (2) 2025.01.17

+ Recent posts