[ 2024.11.20 TIL ] 프로젝트 초기 세팅

박지영·어제
0

Today I Learned

목록 보기
83/84

turbo 레포 초기 세팅 수정 사항 정리

  • 각 서비스 build.js
    import { build } from "@repo/build";
    
    await build({
      entryPoints: ["./src/server.js"],
      outdir: "dist",
    });
  • 각 서비스 server.js
    import { loadProtos } from "@repo/common/load.protos"; // 제거
    loadProtos // 제거
    
    await server.start(); // 서버 시작 시 await 추가
  • 각 서비스 package.json
    {
      "name": "서버명",
      "private": true,
      "main": "src/server.js",
      "license": "MIT",
      "type": "module",
      "dependencies": {
        "@repo/common": "*",
        "@repo/build": "*"
      },
      "scripts": {
        "build": "yarn && node build.js",
        "start:서버명": "node dist/server.js"
      }
    }
  • 각 서비스 클래스 파일
    // import 부분만 변경
    //import TcpServer from "@repo/common/classes/models/server.class.js"; 제거
    //import { config } from "@repo/common/config/config.js"; 제거
    //import { deserialize } from "@repo/common/utils/deserialize/deserialize.js"; 제거
    import { TcpServer } from "@repo/common/classes";
    import { config } from "@repo/common/config";
    import { createServerInfoNotification, deserialize } from "@repo/common/utils";
  • 루트 package.json
    {
      "name": "super-convergence-msa-server",
      "private": true,
      "scripts": {
        "build": "turbo build",
        "start": "turbo run start:ice --no-daemon", // turbo.json 설정에 따라 실행
        "dev": "turbo dev",
        "lint": "turbo lint",
        "format": "prettier --write \"**/*.{ts,tsx,md}\""
      },
      "devDependencies": {
        "turbo": "^2.3.0"
      },
      "packageManager": "yarn@1.22.22",
      "workspaces": [
        "apps/*",
        "packages/*"
      ]
    }
    
  • 루트 turbo.json
    {
      "$schema": "https://turbo.build/schema.json",
      "ui": "tui",
      "tasks": { // 빌드 -> distributor 시작 -> gate 시작 -> ice 시작 순서로 실행
        "build": {
          "dependsOn": ["^build"],
          "outputs": ["dist/**"]
        },
        "start:distributor": {
          "dependsOn": ["build"],
          "cache": false
        },
        "start:gate": {
          "dependsOn": ["start:distributor"],
          "cache": false
        },
        "start:ice": {
          "dependsOn": ["start:gate"],
          "cache": false
        }
      }
    }
    
  • packages/common 설정 common 내부의 각 폴더에서 필요한 부분을 index.js에 모아서 모듈화
    // common/utils/index.js
    export { deserialize } from "./deserialize/deserialize.js";
    export { createServerInfoNotification } from "./notifications/distributor.notification.js";
    export { packetParser } from "./packets/packet.parser.js";
    export { serialize } from "./serialize/serialize.js";
    
    // common/config/index.js
    export { config } from "./config.js";
    export { logger } from "./logger/winston.config.js";
    
    // common/classes/index.js
    export { default as TcpClient } from "./models/client.class.js";
    export { default as TcpServer } from "./models/server.class.js";
    export { default as IntervalManager } from "./managers/interval.manager.js";
    
    // 사용예: import { TcpClient, TcpServer } from "@repo/common/classes";
    단일 파일은 그 파일 통째로 내보냄
    {
      "name": "@repo/common",
      "version": "0.0.0",
      "type": "module",
      "private": true,
      "license": "MIT",
      "publishConfig": {
        "access": "public"
      },
      "scripts": {
        "build": "yarn"
      },
      "dependencies": {
        "protobufjs": "^7.4.0",
        "winston": "^3.11.0",
        "winston-daily-rotate-file": "^5.0.0"
      },
      "exports": {
        "./utils": "./utils/index.js",
        "./classes": "./classes/index.js",
        "./config": "./config/index.js",
        "./header": "./constants/header.js",
        "./handlers": "./handlers/index.js",
        "./load.protos": "./init/load.protos.js",
        "./protobuf": "./protobuf"
      },
      "files": [
        "utils/**/*",
        "classes/**/*",
        "config/**/*",
        "constants/**/*",
        "handlers/**/*",
        "init/**/*",
        "protobuf/**/*.proto"
      ]
    }
    
  • packages/build 설정
    import * as esbuild from "esbuild";
    
    export const build = async (options) => {
      const defaultOptions = {
        bundle: true,
        platform: "node",
        format: "esm",
        minify: true,
        sourcemap: false,
        target: "node20",
        external: [
          "util",
          "net",
          "fs",
          "path",
          "url",
          "protobufjs",
          "winston",
          "winston-daily-rotate-file",
          "@repo/common", // common도 번들링 제외
        ],
        define: {
          "process.env.NODE_ENV": '"production"',
        },
        loader: {
          ".proto": "file", // .proto도 파일로 처리
        },
      };
    
      await esbuild.build({
        ...defaultOptions,
        ...options,
      });
    };
    
    // package.json
    {
      "name": "@repo/build",
      "version": "0.0.0",
      "main": "./build.js",
      "type": "module",
      "private": true,
      "license": "MIT",
      "publishConfig": {
        "access": "public"
      },
      "devDependencies": {
        "esbuild": "^0.24.0"
      },
      "exports": {
        ".": "./build.js"
      },
      "files": [
        "*.js"
      ]
    }
  • server.class 변경 사항
    // 모든 import 경로 @repo/common을 참조하도록 변경
    import { TcpClient } from "@repo/common/classes";
    import { getProtoMessages, loadProtos } from "@repo/common/load.protos";
    import {
      createServerInfoNotification,
      packetParser,
      deserialize,
    } from "@repo/common/utils";
    import { config } from "@repo/common/config";
      
      _protoMessages = getProtoMessages(); ->  _protoMessages = null;
      
      // 메소드 추가
      async initialize() {  
        await loadProtos();
        this._protoMessages = getProtoMessages();
      }
      
      // start 메소드 내부에 추가 + async
        await this.initialize();
        
      //  connectToDistributor 메소드에 추가 + async
      if (!this._protoMessages) {
          await this.initialize();
        }
      
      const packet...
    

트러블 슈팅

빌드된 파일로 서비스 실행 시 protobuf를 찾지 못하는 문제

  1. 문제 정의

    • 각 서비스 별로 빌드된 파일을 실행하면 loadProto 함수가 proto 파일들을 읽어오지 못하는 문제
  2. 사실 수집

    • 오류 내용
    Protobuf 파일 로드 중 오류가 발생했습니다: Error: ENOENT: no such file or directory, scandir 'D:\github\super-convergenc
    │ e-msa-server\apps\ice\protobuf'
    │     at Object.readdirSync (node:fs:1506:26)
    │     at O (file:///D:/github/super-convergence-msa-server/apps/ice/dist/server.js:1:820)
    │     at L (file:///D:/github/super-convergence-msa-server/apps/ice/dist/server.js:1:967)
    │     at C.initialize (file:///D:/github/super-convergence-msa-server/apps/ice/dist/server.js:1:6020)
    │     at C.start (file:///D:/github/super-convergence-msa-server/apps/ice/dist/server.js:1:6075)
    │     at file:///D:/github/super-convergence-msa-server/apps/ice/dist/server.js:8:436
    │     at ModuleJob.run (node:internal/modules/esm/module_job:234:25)
    │     at async ModuleLoader.import (node:internal/modules/esm/loader:473:24)
    │     at async asyncRunEntryPointWithESMLoader (node:internal/modules/run_main:123:5) {
    │   errno: -4058,
    │   code: 'ENOENT',
    │   syscall: 'scandir',
    │   path: 'D:\\github\\super-convergence-msa-server\\apps\\ice\\protobuf'}
  3. 원인 추론

    • proto 파일은 빌드되고 있지 않은데 각 서비스에서 자신의 경로에서 proto 파일들을 읽어오려고 함
  4. 해결 방안

    a. proto 파일도 같이 빌드 시킨다.
    → 컨테이너로 배포할 때 결국 포함시켜야 함. → 시도

    b. 상위에 있는 proto 파일을 읽어오게 경로를 수정한다.
    → 컨테이너로 배포할 때 결국 수정해야 함. → 기각

  5. 해결 시도

    • protobuf 폴더도 dist에 같이 빌드
    // 빌드 때 protobuf로 복사해서 생성시킨다.
    export const buildServiceWithProto = async (options) => {
      const srcProtoDir = path.resolve(
        process.cwd(),
        "../../packages/common/protobuf"
      );
      const destProtoDir = path.resolve(process.cwd(), "./dist/protobuf");
    
      try {
        await fs.mkdir(destProtoDir, { recursive: true });
        await fs.cp(srcProtoDir, destProtoDir, { recursive: true });
    
        await buildService(options);
      } catch (error) {
        console.error("Build failed:", error);
        process.exit(1);
      }
    };
     $ node dist/server.js
    │ Protobuf initialized : 14
    │ distributor server listening on port 9000[ _onConnection ] distributor server : =>  ::ffff:127.0.0.1 : 5591[ _onCreate ]  ::ffff:127.0.0.1 5591[ createServerInfoNotification ] payload ===>>  {
    │   params: [{
    │       name: 'distributor',
    │       number: 1,
    │       host: 'localhost',
    │       port: '9000',
    │       types: []}]}[ serialize ] payload ===>>>  {
    │   serverInfoNotification: S2S_ServerInfoNotification { params: [ [Object] ] }}

    문제는 해결되었으나 배포 때 결국 수정해야 함 → 상위 경로를 참조하기 때문

    • common을 모듈화할 때 protobuf 폴더도 패키지의 일부로 만들어 이것을 참조하도록 수정
    {
      "name": "@repo/common",
      "version": "0.0.0",
      "type": "module",
      "private": true,
      "license": "MIT",
      "exports": {
        "./utils": "./utils/index.js",
        "./classes": "./classes/index.js",
        "./config": "./config/index.js",
        "./header": "./constants/header.js",
        "./handlers": "./handlers/index.js",
        "./load.protos": "./init/load.protos.js",
        "./protobuf": "./protobuf" // protobuf 명으로 사용
      },
      "files": [
        "utils/**/*",
        "classes/**/*",
        "config/**/*",
        "constants/**/*",
        "handlers/**/*",
        "init/**/*",
        "protobuf/**/*.proto"  // proto 파일들을 포함
      ]
    }
  6. 해결

    • protobuf 폴더도 패키지의 일부로서 node_modules와 함께 하위 서비스로 내보내서 해결
      // nodemodules 트리 구조
      node_modules/@repo/common/
      ├── init/
      │   └── load.protos.js  # __dirname은 이 파일의 위치
      ├── protobuf/           # "../protobuf"는 이 디렉토리를 가리킴
      │   ├── distributor/
      │   │   └── packet.proto
      │   └── games/
      │       └── ice/
      │           └── ice.proto
      └── package.json

    컨테이너로 배포 시에도 같은 node_modules 폴더의 protobuf를 참조하기 때문에 문제 발생 x

profile
신입 개발자

0개의 댓글