Skip to content

3.3.1 · Repository map + รวบรวม dead code

บทนี้วาดแผนผังของ centralize-api/ ทั้ง repository พร้อมระบุ dead code และไฟล์ที่ตกค้างจาก NestJS starter template เพื่อให้อ่านและค้นหาโค้ดได้ง่ายขึ้น และทราบว่าไฟล์ไหนไม่ควรแก้เพราะเป็นขยะ


ภาพรวมขนาด

หมวดจำนวนไฟล์รวม LOC
Controllers4 (app, employee, hrPaySlip, hrTimeTemp)218
Services71,019
Entities7 (+ 1 index)200
Modules699
DTOs5 (+ 1 index)276
Repositories380
Root + libs + config + docs12523
รวม TypeScript44~2,243

เปรียบเทียบขนาดกับ service อื่นในระบบ — vtrc-api ใหญ่ที่สุดที่ประมาณ 25,800 LOC, cu-central-api อยู่ในระดับกลาง, ส่วน centralize-api ตัวนี้เล็กที่สุด (ประมาณ 2,243 LOC) — เล็กพอที่จะอ่านได้ทั้ง repo ในรอบเดียว แต่มีรายละเอียดซ่อนอยู่มากตามที่บทถัดไปจะอธิบาย


แผนผังไดเรกทอรี (depth 3 ของ src/)

centralize-api/
├── .env                              ← committed มี prod/UAT creds (3.3.2 + 3.3.8)
├── .eslintrc.js
├── .gitignore                        ← มี `.env*` แต่ `.env` ถูก force-add อยู่แล้ว
├── .prettierrc
├── Dockerfile                        ← ใช้ yarn install ทั้งที่เป็น npm repo (bug — ดู §"Dockerfile")
├── docker-compose.yml
├── nest-cli.json                     ← Swagger plugin config
├── package.json                      ← NestJS 9, TypeORM 0.3, mssql 9
├── tsconfig.json                     ← strictNullChecks: false
├── tsconfig.build.json
├── README.md                         ← default NestJS starter (ไม่มีเนื้อหา-specific)
├── config/
│   └── configuration.ts              ← env var loader 13 LOC
├── docs/
│   └── document.config.ts            ← Swagger DocumentBuilder 10 LOC
├── libs/
│   ├── common/
│   │   └── validator-date.ts         ← IsOnlyDate ไม่ได้ใช้ (dead)
│   ├── exception/
│   │   └── http-exception.filter.ts  ← จับแค่ HttpException
│   ├── jwt/
│   │   ├── jwt-auth.guard.ts         ← global guard ที่ถูก bypass ทุก route (3.3.5)
│   │   └── jwt.strategy.ts           ← secretOrKey ผิดประเภท (3.3.5)
│   └── log/
│       └── logs.middleware.ts        ← custom logger I3GatewayLogger
├── src/
│   ├── main.ts                       ← bootstrap + globals (3.3.2)
│   ├── app.module.ts                 ← root module + 2 TypeORM connections (3.3.2)
│   ├── app.controller.ts             ← root controller 1 endpoint
│   ├── app.controller.spec.ts        ← empty test (dead)
│   ├── app.service.ts                ← org hierarchy CTE ~130 LOC
│   ├── dto/
│   │   ├── employee.dto.ts           ← DTOs ครอบคลุม EmployeeController (14 GET)
│   │   ├── hrTimeTemp.dto.ts         ← time attendance DTOs
│   │   ├── hrpayslip.dto.ts          ← EmpCodeListDto 1 class
│   │   ├── test.dto.ts               ← CreateCatDto (dead — โค้ดค้างจาก NestJS starter)
│   │   └── index.ts
│   ├── entities/
│   │   ├── employeeGroup.entity.ts        ← center connection
│   │   ├── employeeGroup.nbc.entity.ts    ← NBC connection (inherit)
│   │   ├── employeeLevel.entity.ts        ← center
│   │   ├── employeeLevel.nbc.entity.ts    ← NBC
│   │   ├── technicalPosition.entity.ts    ← center
│   │   ├── technicalPosition.nbc.entity.ts← NBC
│   │   ├── user.entity.ts                 ← DEAD — not registered (3.3.6)
│   │   └── index.ts
│   └── modules/
│       ├── employee/                 ← 1 controller 14 GET endpoints + 1 service 605 LOC
│       │   ├── employee.controller.ts
│       │   ├── employee.service.ts
│       │   └── employee.module.ts
│       ├── employeeGroup/            ← sub-module ไม่มี controller
│       ├── employeeLevel/            ← sub-module ไม่มี controller
│       ├── hrPaySlip/                ← 1 controller 1 POST endpoint
│       ├── hrTimeTemp/               ← 1 controller 1 POST endpoint
│       └── technicalPosition/        ← sub-module ไม่มี controller
└── test/
    ├── app.e2e-spec.ts               ← broken — expects 'Hello World!' แต่จริงคือ SQL rows
    └── jest-e2e.json

จุดสังเกต

  • src/modules/employeeGroup/, employeeLevel/, technicalPosition/ เป็น sub-module ที่มี service + module + repository แต่ ไม่มี controller ของตัวเอง — service ของทั้งสามถูก inject เข้าไปใน EmployeeController แทน (บท 3.3.3) ทำให้ EmployeeModule import ทั้งสาม sub-module เข้ามา (centralize-api/src/modules/employee/employee.module.ts:18-19)
  • libs/ ใช้สำหรับโค้ด cross-cutting (auth, logging, exception, validator) ไม่ใช่ module ของ domain
  • ไม่มี migrations/ directory — TypeORM ใช้แค่ connection ไม่ได้ทำ schema migration (บท 3.3.2)
  • ไม่มี health/ directory หรือ HealthController — root /api ที่ดูเหมือน health check จริง ๆ รัน SQL 130 บรรทัด (บท 3.3.2)

package.json — version ที่ใช้

centralize-api/package.json:26-45

json
"dependencies": {
  "@nestjs/common": "^9.0.0",
  "@nestjs/config": "^2.2.0",
  "@nestjs/core": "^9.0.0",
  "@nestjs/passport": "^9.0.0",
  "@nestjs/platform-express": "^9.0.0",
  "@nestjs/swagger": "^6.1.3",
  "@nestjs/typeorm": "^9.0.1",
  "class-transformer": "^0.5.1",
  "class-validator": "^0.13.2",
  "moment": "^2.29.4",
  "mssql": "^9.1.1",
  "passport": "^0.6.0",
  "passport-jwt": "^4.0.0",
  "passport-local": "^1.0.0",
  "reflect-metadata": "^0.1.13",
  "rimraf": "^3.0.2",
  "rxjs": "^7.2.0",
  "typeorm": "^0.3.10"
}

สิ่งที่ควรทราบ

  • @nestjs/* 9.x — NestJS 9 release เดือน พฤษภาคม 2022 (ปัจจุบันเวอร์ชั่น 11.x ออกแล้ว ตามหลัง 2 major)
  • typeorm 0.3.10 — เวอร์ชั่น 0.3 ล่าสุดรองรับทะเบียนวันที่ 2026 อยู่ที่ 0.3.20+
  • mssql 9.1.1 — Microsoft driver ตามหลัง 2 major (ปัจจุบันเวอร์ชั่น 11.x) มี known issue เรื่อง tedious driver
  • moment 2.29.4 — deprecated อย่างเป็นทางการ เปลี่ยนเป็น date-fns หรือ dayjs
  • passport-local อยู่ใน deps แต่ไม่ถูก import ที่ไหน — dead dependency (บท 3.3.5)
  • ไม่มี axios, got, node-fetch → service ไม่ได้เรียก HTTP external ใด ๆ
  • ไม่มี @nestjs/schedule → ไม่มี cron
  • ไม่มี helmet, compression, cookie-parser → ขาดการเตรียมพร้อมสำหรับ production

centralize-api/package.json:8-25

json
"scripts": {
  "nest": "nest",
  "dev:docker": "docker-compose up dev",
  "prod:docker": "docker-compose up prod",
  "prebuild": "rimraf dist",
  "build": "nest build",
  "format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"",
  "start": "nest start --watch",
  "start:dev": "rm -rf ./dist && nest start --watch",
  "start:debug": "nest start --debug --watch",
  "start:prod": "node dist/src/main",
  "lint": "eslint \"{src,docs,config,libs,test}/**/*.ts\" --fix",
  "test": "jest",
  "test:watch": "jest --watch",
  "test:cov": "jest --coverage",
  "test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand",
  "test:e2e": "jest --config ./test/jest-e2e.json"
}

จุดสำคัญ

  • start:prod = node dist/src/main ไม่มี NODE_ENV injection ใน script → env var ต้องตั้งภายนอก (docker-compose หรือ systemd)
  • lint ใช้ --fix เป็น default → อันตรายถ้ารันใน CI เพราะจะ modify file แทนที่จะแค่ตรวจ
  • มี test, test:watch, test:cov, test:debug, test:e2e ครบ แต่ไฟล์ test ที่มีคือ empty spec + broken e2e (ดู §"Dead code")

tsconfig.json — type safety ปิดเกือบหมด

centralize-api/tsconfig.json:1-21

json
{
  "compilerOptions": {
    "module": "commonjs",
    "declaration": true,
    "removeComments": true,
    "emitDecoratorMetadata": true,
    "experimentalDecorators": true,
    "allowSyntheticDefaultImports": true,
    "target": "es2017",
    "sourceMap": true,
    "outDir": "./dist",
    "baseUrl": "./",
    "incremental": true,
    "skipLibCheck": true,
    "strictNullChecks": false,
    "noImplicitAny": false,
    "strictBindCallApply": false,
    "forceConsistentCasingInFileNames": false,
    "noFallthroughCasesInSwitch": false
  }
}

การตั้งค่าที่ทำให้ type safety อ่อนลง

ตัวเลือกค่าผลกระทบ
strictNullChecksfalseTypeScript ไม่ตรวจ null/undefined safety — obj.field ที่อาจเป็น null จะ compile ผ่าน
noImplicitAnyfalseอนุญาต implicit any — function parameter ที่ไม่มี type จะเป็น any โดยอัตโนมัติ
strictBindCallApplyfalseไม่ตรวจ bind/call/apply argument type
forceConsistentCasingInFileNamesfalseimport path ตัวเล็ก-ใหญ่ผิดได้ (เสี่ยงกับ filesystem case-sensitive เช่น Linux)
ไม่มี strict: trueumbrella strict mode ไม่เปิด

experimentalDecorators + emitDecoratorMetadata จำเป็นสำหรับ NestJS เพราะ decorator ต้องการ metadata ในขณะรัน (runtime)

.eslintrc.js ก็ปิด @typescript-eslint/no-explicit-any: 'off' เสริม → any ใช้ได้อย่างเสรี พบจริงใน centralize-api/libs/jwt/jwt.strategy.ts:9,18, repositories ทั้ง 3 (centralize-api/src/modules/employeeGroup/employeeGroup.repository.ts:9, centralize-api/src/modules/employeeLevel/employeeLevel.repository.ts:7, centralize-api/src/modules/technicalPosition/technicalPosition.repository.ts:9)


nest-cli.json — Swagger plugin

centralize-api/nest-cli.json:1-17

json
{
  "$schema": "https://json.schemastore.org/nest-cli",
  "collection": "@nestjs/schematics",
  "sourceRoot": "src",
  "compilerOptions": {
    "plugins": [
      {
        "name": "@nestjs/swagger",
        "options": {
          "dtoFileNameSuffix": [".dto.ts"],
          "classValidatorShim": true,
          "introspectComments": true
        }
      }
    ]
  }
}

plugin นี้อ่าน DTO class ที่ลงท้ายด้วย .dto.ts แล้วสร้าง @ApiProperty() ให้อัตโนมัติจาก TypeScript type — เป็นเหตุผลที่ DTOs ใน src/dto/ ไม่ต้องเขียน decorator เองทุกบรรทัด (บท 3.3.3)

introspectComments: true → JSDoc comment เหนือ class/method จะถูกดึงไปเป็น Swagger description ด้วย แต่ในทางปฏิบัติ DTOs ส่วนใหญ่ใน repo ไม่มี JSDoc ทำให้ description ว่าง


Dockerfile — yarn ใน npm repo + ขน .env เข้า production image

centralize-api/Dockerfile:1-27

dockerfile
FROM node:16 AS develop

WORKDIR /usr/src/app

ENV TZ="Asia/Bangkok"

COPY package.json ./

COPY . .

RUN yarn install --ignore-engines

RUN yarn build

FROM node:16 AS production

WORKDIR /usr/src/app

COPY --from=develop /usr/src/app/.env .
COPY --from=develop /usr/src/app/dist ./dist
COPY --from=develop /usr/src/app/package.json ./package.json

RUN yarn install --ignore-engines

EXPOSE 3000

CMD [ "yarn", "start:prod"]

ปัญหา

  1. ทั้งสอง stage ใช้ yarn install --ignore-engines ทั้งที่ repo เป็น npm (มีแค่ package.json ไม่มี yarn.lock) → yarn จะ resolve dependency tree เองใหม่ตาม semver range ผลลัพธ์ที่ได้ใน container อาจต่างจากที่ dev ติดตั้ง local ผ่าน npm เพราะไม่มี lockfile เป็นหลักฐาน → dependency drift อย่างเงียบ
  2. COPY --from=develop /usr/src/app/.env . — บรรทัดนี้เป็นหลักฐานชัดเจนว่า build pipeline พึ่งพาไฟล์ .env ที่ committed โดยตรง ไม่ได้รับ env var จาก orchestration (Kubernetes Secret, docker-compose env_file) → แม้จะแก้ CRIT-01 (ลบ .env ออกจาก git) แต่ถ้าไม่แก้ Dockerfile ไปด้วย จะ build ไม่ผ่าน
  3. --ignore-engines ใน yarn stage ปิดเสียงเตือน engine check — อาจซ่อนปัญหาความเข้ากันได้ของ dependency กับ Node เวอร์ชั่นปัจจุบัน (Node 16 เป็น EOL ตั้งแต่ กันยายน 2023)
  4. ไม่มี multi-stage แยก build deps ออกdevelop stage เอาทั้ง devDependencies ไปด้วย ทำให้ image โตเกินจำเป็น
  5. ไม่มี USER directive — container รันเป็น root โดย default

ผลกระทบ — build ระหว่าง stage ไม่สามารถทำซ้ำได้ผลลัพธ์เดิม (reproducible) อย่างสมบูรณ์ และถ้ามี dependency ที่ติดตั้งไม่เหมือนกันระหว่าง dev กับ production จะเจอ bug ที่หายาก

การแก้ — เปลี่ยนทุก stage เป็น npm ci (clean install จาก package-lock.json) ลบ --ignore-engines ใช้ --omit=dev แยก stage build ออกจาก runtime, เพิ่ม USER node, ดึง .env ออกแล้วใช้ env_file ใน compose หรือ Secret ใน Kubernetes แทน


รวบรวม dead code

ไฟล์และโค้ดต่อไปนี้อยู่ใน repo แต่ ไม่ได้ใช้งานจริง — ลบได้โดยไม่กระทบ runtime แต่ทีมยังไม่ได้ลบเพราะอาจมีการคืนมาใช้ในอนาคต

รายการไฟล์หลักฐาน
CreateCatDto (class cat/breed/age)centralize-api/src/dto/test.dto.ts:1-17ค้างจาก NestJS starter template — ไม่ถูก import
IsOnlyDate validatorcentralize-api/libs/common/validator-date.ts:8-22grep ทั้ง repo ไม่พบการใช้งาน
UserEntity (table auth_user)centralize-api/src/entities/user.entity.ts:1-47ไม่ได้ export จาก entities/index.ts, ไม่ได้ register ใน module, table ไม่มีใน HRMI
getEmployeesTempPOakByEmployeeCode (commented)centralize-api/src/modules/employee/employee.service.ts:588-604โค้ดทั้ง method อยู่ใน /* */
EmployeeResponseDtocentralize-api/src/dto/employee.dto.ts:72-102define แต่ไม่ได้เป็น return type ของ controller
passport-local dependencycentralize-api/package.json:40import ไม่ได้เกิดขึ้นที่ไหน
app.controller.spec.ts empty testcentralize-api/src/app.controller.spec.ts:18-20expect() ทั้งหมด comment ไว้
test/app.e2e-spec.ts broken e2ecentralize-api/test/app.e2e-spec.tsทดสอบ return string 'Hello World!' แต่ service จริงคืน SQL rows → fail ทุกครั้ง
RootObject interface (unused)centralize-api/src/app.controller.ts:7-16define shape 9 ฟิลด์แต่ไม่ได้ใช้เป็น type ของอะไร

UserEntity — ตัวอย่าง dead code ที่น่าสนใจ

centralize-api/src/entities/user.entity.ts:1-48

typescript
import { Column, Entity, PrimaryGeneratedColumn, Unique } from 'typeorm';

@Entity('auth_user')
@Unique('my_unique_auth_user', ['userName', 'email'])
export class UserEntity {
  @PrimaryGeneratedColumn('uuid')
  userId: string;
  // ... userName, password, firstName, lastName, email, userLevel, ...
}

จุดสังเกตที่บ่งบอกว่าเป็นโค้ดที่ค้างจาก starter template

  • ใช้ PrimaryGeneratedColumn('uuid') แบบ auto-generate — HRMI tables ใช้ string PK (เช่น EmplGrupID length 36) ไม่ใช่ auto-gen UUID
  • table name auth_user ไม่มีใน HRMI MSSQL เลย
  • decorator @Unique('my_unique_auth_user', ['userName', 'email']) มี prefix my_ บ่งบอก tutorial code
  • ไม่มี @ManyToOne/@OneToMany ใด ๆ ขาด FK ทั้งหมด
  • ไม่ได้ export จาก centralize-api/src/entities/index.ts:1-6 → ไม่ถูก TypeORM load เข้า connection
  • comment // type: "timestamp with time zone" ที่ centralize-api/src/entities/user.entity.ts:28,37 เป็น Postgres type ไม่ใช่ MSSQL → เดาได้ว่าคนเขียน copy มาจาก Postgres starter

สิ่งที่ขาด — ไฟล์ที่คาดว่าจะมีแต่ไม่มี

เมื่อเทียบกับ production NestJS service มาตรฐาน ไฟล์ต่อไปนี้ขาดหายไป

ไฟล์ / directoryปกติมีcentralize-api
src/migrations/TypeORM migration filesไม่มี — schema sync อาศัย HRMI เป็น source of truth
src/health/ หรือ HealthController/health endpointไม่มี — root /api รัน SQL หนักไม่เหมาะเป็น probe
.env.exampletemplate สำหรับ devไม่มี — มีแค่ .env จริงที่ committed (บท 3.3.8)
helm/ หรือ k8s/Kubernetes manifestไม่มี — deploy ผ่าน docker-compose
package-lock.json (committed)การติดตั้งที่ทำซ้ำได้ผลลัพธ์เดิม (reproducible)ไม่มีใน repo — Dockerfile ใช้ yarn install แทน
tests/ ที่มี unit จริงcoverageมีแค่ empty spec + broken e2e
LICENSEopen source licenseไม่มี

สรุปประเด็นสำคัญ

  • centralize-api เป็น TypeScript service ตัวเดียวในระบบ แต่ type safety ปิดเกือบหมด (strictNullChecks: false, noImplicitAny: false)
  • Dockerfile มี bug ร้าย — yarn install ใน npm repo ทำให้ build ไม่สามารถทำซ้ำได้ผลลัพธ์เดิม (reproducible) และมีการ COPY --from=develop /usr/src/app/.env . ที่ bind กับ .env committed โดยตรง
  • Dead code 9 รายการค้างอยู่ รวมถึง UserEntity ทั้งไฟล์และ CreateCatDto ที่เป็นโค้ดค้างจาก NestJS starter template
  • README.md เป็น default NestJS starter ไม่มีเนื้อหาเกี่ยวกับ project จริง — ถ้าเจอช่องว่างเอกสาร ให้เริ่มเขียนใหม่ทั้งหมด อย่าแก้ไขจากของเดิม