ํ‹ฐ์Šคํ† ๋ฆฌ ๋ทฐ

๋ฐ˜์‘ํ˜•

๐Ÿ“Œ NestJS + Kubernetes & ๋งˆ์ดํฌ๋กœ์„œ๋น„์Šค ๋ฐฐํฌ: ์‹ค์ „ ๊ฐ€์ด๋“œ

23. NestJS + DDD๋กœ ๋„๋ฉ”์ธ ์ค‘์‹ฌ ๋ฐฑ์—”๋“œ ๊ตฌ์ถ•ํ•˜๊ธฐ


๋Œ€๊ทœ๋ชจ SaaS ํ”„๋กœ์ ํŠธ๋ฅผ ๊ด€๋ฆฌํ•  ๋•Œ, ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง์ด ์ ์  ๋ณต์žกํ•ด์ง€๋ฉด์„œ ์ฝ”๋“œ ๋ณต์žก๋„์™€ ๊ด€๋ฆฌ ๋ถ€๋‹ด์ด ์ปค์ง‘๋‹ˆ๋‹ค.
์ด๋•Œ **DDD(Domain-Driven Design)**์„ ์ ์šฉํ•˜์—ฌ ๋„๋ฉ”์ธ ์ค‘์‹ฌ์œผ๋กœ ๊ตฌ์กฐํ™”ํ•˜๋ฉด ์ฝ”๋“œ์˜ ๊ฐ€๋…์„ฑ, ์œ ์ง€๋ณด์ˆ˜์„ฑ, ํ™•์žฅ์„ฑ์ด ํฌ๊ฒŒ ํ–ฅ์ƒ๋ฉ๋‹ˆ๋‹ค.
์ด๋ฒˆ ๊ธ€์—์„œ๋Š” NestJS์™€ DDD๋ฅผ ๊ฒฐํ•ฉํ•˜์—ฌ ๋ชจ๋“ˆํ™”๋œ ๋„๋ฉ”์ธ ์ค‘์‹ฌ ๋ฐฑ์—”๋“œ ์„ค๊ณ„ ๋ฐฉ๋ฒ•์„ ์†Œ๊ฐœํ•ฉ๋‹ˆ๋‹ค. ๐Ÿง 


โœ… 1. DDD(Domain-Driven Design)๋ž€?

๋น„์ฆˆ๋‹ˆ์Šค ๋„๋ฉ”์ธ์„ ์ค‘์‹ฌ์œผ๋กœ ์†Œํ”„ํŠธ์›จ์–ด๋ฅผ ์„ค๊ณ„ํ•˜์—ฌ ๋ณต์žก์„ฑ์„ ๊ด€๋ฆฌํ•˜๋Š” ๋ฐฉ๋ฒ•๋ก ์ž…๋‹ˆ๋‹ค.

ํ•ต์‹ฌ ๊ฐœ๋… ์„ค๋ช…

๋„๋ฉ”์ธ ํŠน์ • ๋น„์ฆˆ๋‹ˆ์Šค ๋งฅ๋ฝ์—์„œ ๋‹ค๋ฃจ๋Š” ๋ฌธ์ œ ์˜์—ญ
์• ๊ทธ๋ฆฌ๊ฒŒ์ดํŠธ ์—ฌ๋Ÿฌ ์—”ํ‹ฐํ‹ฐ๋ฅผ ๊ทธ๋ฃนํ™”ํ•˜์—ฌ ์ผ๊ด€์„ฑ์„ ์œ ์ง€ํ•˜๋Š” ๋‹จ์œ„
๋ฆฌํฌ์ง€ํ† ๋ฆฌ ๋„๋ฉ”์ธ ๊ฐ์ฒด๋ฅผ ์˜์†ํ™”ํ•˜๊ฑฐ๋‚˜ ๋ถˆ๋Ÿฌ์˜ค๋Š” ์—ญํ• 
์„œ๋น„์Šค ๋„๋ฉ”์ธ ๋กœ์ง์„ ์บก์Аํ™”ํ•˜์—ฌ ์ œ๊ณต
๊ฐ’ ๊ฐ์ฒด ๋ถˆ๋ณ€ํ•˜๋ฉฐ ๋™์ผ์„ฑ(identity)๋ณด๋‹ค ๋‚ด์šฉ์— ์ค‘์ ์„ ๋‘” ๊ฐ์ฒด

โœ… 2. DDD ๊ธฐ๋ฐ˜ ํ”„๋กœ์ ํŠธ ๊ตฌ์กฐ

src/
โ”œโ”€โ”€ modules/
โ”‚   โ”œโ”€โ”€ user/
โ”‚   โ”‚   โ”œโ”€โ”€ domain/      # ์—”ํ‹ฐํ‹ฐ, ๊ฐ’ ๊ฐ์ฒด, ์• ๊ทธ๋ฆฌ๊ฒŒ์ดํŠธ
โ”‚   โ”‚   โ”œโ”€โ”€ application/ # ์„œ๋น„์Šค, ์œ ์ฆˆ์ผ€์ด์Šค
โ”‚   โ”‚   โ”œโ”€โ”€ infrastructure/ # DB, ์™ธ๋ถ€ API ์—ฐ๋™
โ”‚   โ”‚   โ””โ”€โ”€ presentation/ # ์ปจํŠธ๋กค๋Ÿฌ, DTO
โ”œโ”€โ”€ shared/ # ๊ณตํ†ต ๋ชจ๋“ˆ, ์œ ํ‹ธ๋ฆฌํ‹ฐ
โ”œโ”€โ”€ config/
โ””โ”€โ”€ main.ts

โœ… 3. ๋„๋ฉ”์ธ ๋ชจ๋“ˆ ๊ตฌ์„ฑ ์˜ˆ์‹œ (User)

๐Ÿ“ฆ 1) ๋„๋ฉ”์ธ ์—”ํ‹ฐํ‹ฐ

๐Ÿ“‚ domain/user.entity.ts

export class User {
  constructor(
    public readonly id: string,
    private _name: string,
    private _email: string,
  ) {}

  get name(): string {
    return this._name;
  }

  changeName(newName: string) {
    if (!newName) throw new Error('์ด๋ฆ„์ด ์œ ํšจํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.');
    this._name = newName;
  }
}

๐Ÿ“ฆ 2) ๊ฐ’ ๊ฐ์ฒด

๐Ÿ“‚ domain/email.vo.ts

export class Email {
  constructor(private readonly value: string) {
    if (!this.isValid(value)) throw new Error('์œ ํšจํ•˜์ง€ ์•Š์€ ์ด๋ฉ”์ผ');
  }

  private isValid(email: string): boolean {
    return /\S+@\S+\.\S+/.test(email);
  }

  get email(): string {
    return this.value;
  }
}

๐Ÿ“ฆ 3) ๋ฆฌํฌ์ง€ํ† ๋ฆฌ ์ธํ„ฐํŽ˜์ด์Šค

๐Ÿ“‚ application/user.repository.ts

export interface UserRepository {
  save(user: User): Promise<void>;
  findById(id: string): Promise<User | null>;
}

๐Ÿ“ฆ 4) ์„œ๋น„์Šค ๊ณ„์ธต

๐Ÿ“‚ application/user.service.ts

@Injectable()
export class UserService {
  constructor(private readonly userRepository: UserRepository) {}

  async createUser(name: string, email: string): Promise<User> {
    const user = new User(uuid(), name, new Email(email).email);
    await this.userRepository.save(user);
    return user;
  }

  async changeUserName(id: string, newName: string): Promise<void> {
    const user = await this.userRepository.findById(id);
    if (!user) throw new NotFoundException('User not found');
    user.changeName(newName);
    await this.userRepository.save(user);
  }
}

๐Ÿ“ฆ 5) ์ธํ”„๋ผ ๊ณ„์ธต (TypeORM ๊ธฐ๋ฐ˜)

๐Ÿ“‚ infrastructure/typeorm-user.repository.ts

@Injectable()
export class TypeOrmUserRepository implements UserRepository {
  constructor(@InjectRepository(User) private repo: Repository<User>) {}

  async save(user: User): Promise<void> {
    await this.repo.save(user);
  }

  async findById(id: string): Promise<User | null> {
    return this.repo.findOne({ where: { id } });
  }
}

๐Ÿ“ฆ 6) ํ”„๋ ˆ์  ํ…Œ์ด์…˜ ๊ณ„์ธต (์ปจํŠธ๋กค๋Ÿฌ)

๐Ÿ“‚ presentation/user.controller.ts

@Controller('users')
export class UserController {
  constructor(private readonly userService: UserService) {}

  @Post()
  async create(@Body() dto: CreateUserDto) {
    return this.userService.createUser(dto.name, dto.email);
  }

  @Patch(':id/name')
  async updateName(@Param('id') id: string, @Body() dto: UpdateNameDto) {
    await this.userService.changeUserName(id, dto.newName);
    return { message: 'Name updated' };
  }
}

โœ… 4. ๋ชจ๋“ˆ ๋‹จ์œ„ DI ๊ตฌ์„ฑ

๐Ÿ“‚ user.module.ts

@Module({
  imports: [TypeOrmModule.forFeature([User])],
  providers: [
    UserService,
    { provide: 'UserRepository', useClass: TypeOrmUserRepository },
  ],
  controllers: [UserController],
})
export class UserModule {}

โœ… 5. DDD๋ฅผ ํ™œ์šฉํ•œ ์œ ์ง€๋ณด์ˆ˜ ์ „๋žต

๋ฌธ์ œ์  DDD๋กœ ํ•ด๊ฒฐ ๋ฐฉ๋ฒ•

๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง์ด ๋ณต์žก ๋„๋ฉ”์ธ ์ค‘์‹ฌ ๋ชจ๋“ˆ๋กœ ๋กœ์ง ์บก์Аํ™”
๋น„๋™๊ธฐ ์ด๋ฒคํŠธ ๊ด€๋ฆฌ Event Handler๋กœ ๋น„๋™๊ธฐ ์ฒ˜๋ฆฌ ๋ถ„๋ฆฌ
๋ฐ์ดํ„ฐ ๋ฌด๊ฒฐ์„ฑ ๊ด€๋ฆฌ ๊ฐ’ ๊ฐ์ฒด(VO)๋กœ ๊ธฐ๋ณธ ๊ฒ€์ฆ ์ฒ˜๋ฆฌ
์„œ๋น„์Šค ๊ฐ„ ์ค‘๋ณต ๋กœ์ง ๊ณตํ†ต ๋ชจ๋“ˆ์„ shared ๋””๋ ‰ํ† ๋ฆฌ๋กœ ๋ถ„๋ฆฌ

โœ… 6. DDD๋ฅผ ์ ์šฉํ•  ๋•Œ ์ฃผ์˜์ 

  • ๋„๋ฉ”์ธ ์ง€์‹์„ ์ œ๋Œ€๋กœ ๋ฐ˜์˜ํ•ด์•ผ ํ•จ (๊ธฐํš์ž์™€ ํ˜‘์—… ํ•„์ˆ˜)
  • ๋ณต์žก๋„๋ฅผ ์ค„์ด๊ธฐ ์œ„ํ•ด ํ•„์š”ํ•œ ๊ณณ์—๋งŒ ์ ์šฉ (๋ชจ๋“  ๋ชจ๋“ˆ์— ๊ฐ•์ œ ์ ์šฉ X)
  • ๊ธฐ๋ณธ CRUD ๊ฐ™์€ ๋‹จ์ˆœ ๋กœ์ง์€ DDD๋ฅผ ๊ตณ์ด ์‚ฌ์šฉํ•˜์ง€ ์•Š์•„๋„ ๋จ

โœ… ๊ฒฐ๋ก : NestJS์™€ DDD๋กœ ๋ณต์žกํ•œ ๋„๋ฉ”์ธ ๊ตฌ์กฐํ™”

โœ… ๋„๋ฉ”์ธ ์ค‘์‹ฌ์œผ๋กœ ์„ค๊ณ„ํ•˜์—ฌ ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง์„ ๊น”๋”ํ•˜๊ฒŒ ์œ ์ง€
โœ… ๋„๋ฉ”์ธ ๋กœ์ง, ์ธํ”„๋ผ, ํ”„๋ ˆ์  ํ…Œ์ด์…˜ ๊ณ„์ธต์„ ๋ช…ํ™•ํžˆ ๊ตฌ๋ถ„
โœ… ์ฝ”๋“œ ๋ณต์žก๋„ ๊ฐ์†Œ ๋ฐ ์œ ์ง€๋ณด์ˆ˜์„ฑ ํ–ฅ์ƒ
โœ… ๋Œ€๊ทœ๋ชจ SaaS ๋˜๋Š” ๋ณต์žกํ•œ ์„œ๋น„์Šค์— ๋งค์šฐ ์ ํ•ฉ

๋‹ค์Œ ๊ธ€์—์„œ๋Š” NestJS ํ”„๋กœ์ ํŠธ์—์„œ CI/CD ์ž๋™ํ™” ํŒŒ์ดํ”„๋ผ์ธ์„ ArgoCD์™€ GitHub Actions๋กœ ๊ตฌ์ถ•ํ•˜๋Š” ์‹ค์ „ ๊ฐ€์ด๋“œ๋ฅผ ์†Œ๊ฐœํ•ฉ๋‹ˆ๋‹ค! ๐Ÿš€


๐Ÿ” ๋‹ค์Œ ๊ธ€ ์˜ˆ๊ณ : NestJS CI/CD ์ž๋™ํ™” – ArgoCD + GitHub Actions

๐Ÿ“Œ ๋‹ค์Œ ํŽธ: 24. NestJS CI/CD ์ž๋™ํ™”๋กœ ์šด์˜ ํšจ์œจ ๊ทน๋Œ€ํ™”


 

NestJS DDD,NestJS ๋„๋ฉ”์ธ ์ค‘์‹ฌ ์„ค๊ณ„,NestJS ๋„๋ฉ”์ธ ๋ชจ๋“ˆ,NestJS CQRS DDD,NestJS ๋ชจ๋“ˆํ™”,NestJS ๋ณต์žก์„ฑ ๊ด€๋ฆฌ,NestJS ๋Œ€๊ทœ๋ชจ ํ”„๋กœ์ ํŠธ,NestJS ๋„๋ฉ”์ธ ๋ชจ๋ธ๋ง,NestJS ์• ๊ทธ๋ฆฌ๊ฒŒ์ดํŠธ,NestJS ๊ฐ’ ๊ฐ์ฒด ์„ค๊ณ„

โ€ป ์ด ํฌ์ŠคํŒ…์€ ์ฟ ํŒก ํŒŒํŠธ๋„ˆ์Šค ํ™œ๋™์˜ ์ผํ™˜์œผ๋กœ, ์ด์— ๋”ฐ๋ฅธ ์ผ์ •์•ก์˜ ์ˆ˜์ˆ˜๋ฃŒ๋ฅผ ์ œ๊ณต๋ฐ›์Šต๋‹ˆ๋‹ค.
๊ณต์ง€์‚ฌํ•ญ
์ตœ๊ทผ์— ์˜ฌ๋ผ์˜จ ๊ธ€
์ตœ๊ทผ์— ๋‹ฌ๋ฆฐ ๋Œ“๊ธ€
Total
Today
Yesterday
๋งํฌ
ยซ   2025/05   ยป
์ผ ์›” ํ™” ์ˆ˜ ๋ชฉ ๊ธˆ ํ† 
1 2 3
4 5 6 7 8 9 10
11 12 13 14 15 16 17
18 19 20 21 22 23 24
25 26 27 28 29 30 31
๊ธ€ ๋ณด๊ด€ํ•จ
๋ฐ˜์‘ํ˜•