타입스크립트와 Docker: 개발 환경 구축 🚀

콘텐츠 대표 이미지 - 타입스크립트와 Docker: 개발 환경 구축 🚀

 

 

안녕하세요, 개발자 여러분! 오늘은 현대 웹 개발의 두 가지 핵심 기술인 타입스크립트와 Docker에 대해 깊이 있게 알아보겠습니다. 이 글을 통해 여러분은 안정적이고 효율적인 개발 환경을 구축하는 방법을 배우게 될 것입니다. 🛠️

타입스크립트는 자바스크립트의 슈퍼셋으로, 정적 타입 검사와 최신 ECMAScript 기능을 제공합니다. Docker는 애플리케이션을 빠르게 구축, 테스트 및 배포할 수 있는 소프트웨어 플랫폼입니다. 이 두 기술을 결합하면, 개발부터 배포까지의 전체 과정을 더욱 원활하게 만들 수 있습니다.

 

이 글에서는 타입스크립트의 기본 개념부터 고급 기능까지, 그리고 Docker의 설치부터 실제 프로젝트에 적용하는 방법까지 상세히 다룰 예정입니다. 또한, 두 기술을 함께 사용하여 개발 환경을 최적화하는 방법에 대해서도 알아볼 것입니다.

재능넷(https://www.jaenung.net)과 같은 플랫폼에서 활동하는 개발자들에게 특히 유용한 내용이 될 것입니다. 이제 본격적으로 타입스크립트와 Docker의 세계로 뛰어들어볼까요? 🏊‍♂️

1. 타입스크립트 기초 🏗️

타입스크립트는 마이크로소프트에서 개발한 오픈 소스 프로그래밍 언어로, 자바스크립트의 상위 집합(superset)입니다. 즉, 모든 자바스크립트 코드는 유효한 타입스크립트 코드이지만, 타입스크립트는 추가적인 기능을 제공합니다.

 

타입스크립트의 주요 특징은 다음과 같습니다:

  • 정적 타입 검사: 코드 실행 전에 타입 오류를 잡아낼 수 있습니다.
  • 객체 지향 프로그래밍 지원: 클래스, 인터페이스, 모듈 등을 제공합니다.
  • 최신 ECMAScript 기능: 최신 자바스크립트 기능을 사용할 수 있습니다.
  • 강력한 개발 도구 지원: IDE에서의 자동 완성, 리팩토링 등이 가능합니다.

 

이제 타입스크립트의 기본 문법에 대해 알아보겠습니다.

1.1 타입 주석(Type Annotations) 📝

타입스크립트의 가장 기본적인 특징은 변수, 함수 매개변수, 반환 값 등에 타입을 명시할 수 있다는 것입니다.


let name: string = "Alice";
let age: number = 30;
let isStudent: boolean = false;

function greet(person: string): string {
    return `Hello, ${person}!`;
}

위 코드에서 : string, : number, : boolean 등이 타입 주석입니다. 이를 통해 각 변수와 함수의 타입을 명확히 할 수 있습니다.

1.2 인터페이스(Interfaces) 🔗

인터페이스는 객체의 구조를 정의하는 데 사용됩니다. 이를 통해 코드의 일관성을 유지하고 타입 안정성을 높일 수 있습니다.


interface Person {
    name: string;
    age: number;
    greet(): void;
}

let user: Person = {
    name: "Bob",
    age: 25,
    greet() {
        console.log(`Hello, I'm ${this.name}`);
    }
};

이 예제에서 Person 인터페이스는 name, age, greet 속성을 가진 객체의 구조를 정의합니다.

1.3 클래스(Classes) 🏫

타입스크립트는 객체 지향 프로그래밍을 완벽하게 지원합니다. 클래스를 사용하여 객체의 구조와 행동을 정의할 수 있습니다.


class Student {
    private name: string;
    
    constructor(name: string) {
        this.name = name;
    }
    
    public introduce(): void {
        console.log(`Hi, I'm ${this.name}`);
    }
}

let student = new Student("Charlie");
student.introduce(); // 출력: Hi, I'm Charlie

이 예제에서 Student 클래스는 private 속성 name과 public 메서드 introduce를 가집니다.

1.4 제네릭(Generics) 🧬

제네릭을 사용하면 다양한 타입에 대해 재사용 가능한 컴포넌트를 만들 수 있습니다.


function identity<T>(arg: T): T {
    return arg;
}

let output1 = identity<string>("myString");
let output2 = identity<number>(100);

identity 함수는 어떤 타입의 인자도 받아 그대로 반환할 수 있습니다.

1.5 열거형(Enums) 🔢

열거형은 관련된 상수들의 집합을 정의할 때 사용됩니다.


enum Color {
    Red,
    Green,
    Blue
}

let c: Color = Color.Green;

이 예제에서 Color는 세 가지 상수를 가진 열거형입니다.

 

이러한 기본적인 개념들을 이해하면, 타입스크립트를 사용하여 더 안전하고 유지보수가 쉬운 코드를 작성할 수 있습니다. 다음 섹션에서는 이러한 개념들을 실제 프로젝트에 어떻게 적용할 수 있는지 살펴보겠습니다. 🚀

2. 타입스크립트 고급 기능 🎓

타입스크립트의 기본을 이해했다면, 이제 더 깊이 있는 기능들을 살펴볼 차례입니다. 이 섹션에서는 타입스크립트의 고급 기능들을 소개하고, 이를 통해 어떻게 더 강력하고 유연한 코드를 작성할 수 있는지 알아보겠습니다.

2.1 유니온 타입과 인터섹션 타입 🔀

유니온 타입은 여러 타입 중 하나일 수 있는 값을 나타냅니다. 인터섹션 타입은 여러 타입을 결합합니다.


// 유니온 타입
type StringOrNumber = string | number;

let value: StringOrNumber = "Hello";
value = 42; // 유효

// 인터섹션 타입
interface Name {
    name: string;
}

interface Age {
    age: number;
}

type Person = Name & Age;

let person: Person = {
    name: "Alice",
    age: 30
};

유니온 타입은 OR 연산자(|)를 사용하여 표현하며, 인터섹션 타입은 AND 연산자(&)를 사용합니다.

2.2 타입 가드와 타입 단언 🛡️

타입 가드는 특정 스코프 내에서 변수의 타입을 보장하는 방법입니다. 타입 단언은 컴파일러에게 "이 값의 타입은 이것이다"라고 알려주는 방법입니다.


// 타입 가드
function printLength(value: string | number) {
    if (typeof value === "string") {
        console.log(value.length); // value는 여기서 string 타입
    } else {
        console.log(value.toFixed(2)); // value는 여기서 number 타입
    }
}

// 타입 단언
let someValue: any = "this is a string";
let strLength: number = (someValue as string).length;

타입 가드를 사용하면 런타임에 타입을 체크할 수 있으며, 타입 단언을 통해 컴파일러에게 타입에 대한 추가 정보를 제공할 수 있습니다.

2.3 고급 타입 기능 🧠

타입스크립트는 더 복잡한 타입 관계를 표현할 수 있는 고급 타입 기능을 제공합니다.


// 조건부 타입
type IsString<T> = T extends string ? true : false;
type A = IsString<string>; // true
type B = IsString<number>; // false

// 매핑된 타입
type Readonly<T> = {
    readonly [P in keyof T]: T[P];
};

interface Point {
    x: number;
    y: number;
}

type ReadonlyPoint = Readonly<Point>;
// { readonly x: number; readonly y: number; }

// 인덱스 타입
function pluck<T, K extends keyof T>(o: T, names: K[]): T[K][] {
    return names.map(n => o[n]);
}

let person = {
    name: "Jarid",
    age: 35
};

let strings: string[] = pluck(person, ["name"]); // ["Jarid"]

이러한 고급 타입 기능들을 사용하면 더 유연하고 재사용 가능한 타입을 정의할 수 있습니다.

2.4 데코레이터 🎀

데코레이터는 클래스 선언, 메서드, 접근자, 프로퍼티 또는 매개변수에 첨부할 수 있는 특별한 종류의 선언입니다.


function logged(target: any, key: string, descriptor: PropertyDescriptor) {
    let originalMethod = descriptor.value;
    descriptor.value = function (...args: any[]) {
        console.log(`Calling ${key} with`, args);
        return originalMethod.apply(this, args);
    };
    return descriptor;
}

class Calculator {
    @logged
    add(x: number, y: number) {
        return x + y;
    }
}

let calc = new Calculator();
calc.add(1, 2); // 콘솔에 "Calling add with [1, 2]" 출력 후 3 반환

데코레이터를 사용하면 메타프로그래밍이 가능해지며, 코드를 더 선언적으로 만들 수 있습니다.

2.5 모듈과 네임스페이스 📦

타입스크립트는 모듈과 네임스페이스를 통해 코드를 구조화하고 캡슐화할 수 있습니다.


// math.ts
export function add(x: number, y: number): number {
    return x + y;
}

export function subtract(x: number, y: number): number {
    return x - y;
}

// main.ts
import { add, subtract } from './math';

console.log(add(5, 3)); // 8
console.log(subtract(5, 3)); // 2

// 네임스페이스 사용
namespace Validation {
    export interface StringValidator {
        isAcceptable(s: string): boolean;
    }

    export class LettersOnlyValidator implements StringValidator {
        isAcceptable(s: string) {
            return /^[A-Za-z]+$/.test(s);
        }
    }
}

let validator = new Validation.LettersOnlyValidator();
console.log(validator.isAcceptable("Hello")); // true

모듈을 사용하면 코드를 여러 파일로 분할하여 관리할 수 있으며, 네임스페이스를 통해 관련된 기능을 그룹화할 수 있습니다.

 

이러한 고급 기능들을 마스터하면, 타입스크립트를 사용하여 더 복잡하고 강력한 애플리케이션을 개발할 수 있습니다. 다음 섹션에서는 이러한 개념들을 실제 프로젝트에 적용하는 방법에 대해 알아보겠습니다. 💪

3. Docker 기초 🐳

Docker는 애플리케이션을 빠르게 구축, 테스트 및 배포할 수 있게 해주는 소프트웨어 플랫폼입니다. 컨테이너라는 표준화된 유닛으로 애플리케이션을 "패키징"하여, 이 컨테이너에는 라이브러리, 시스템 도구, 코드, 런타임 등 소프트웨어를 실행하는 데 필요한 모든 것이 포함됩니다.

3.1 Docker의 주요 개념 🗝️

  • 컨테이너(Container): 애플리케이션과 그 종속성을 포함하는 실행 환경
  • 이미지(Image): 컨테이너를 생성하는 데 사용되는 읽기 전용 템플릿
  • Dockerfile: 이미지를 생성하는 데 사용되는 스크립트
  • Docker Hub: Docker 이미지를 공유하고 관리하는 클라우드 기반 레지스트리 서비스

3.2 Docker 설치하기 🛠️

Docker를 사용하기 위해서는 먼저 시스템에 Docker를 설치해야 합니다. Docker는 Windows, macOS, Linux 등 다양한 운영 체제를 지원합니다.

각 운영 체제별 설치 방법은 다음과 같습니다:

  • Windows: Docker Desktop for Windows를 다운로드하여 설치
  • macOS: Docker Desktop for Mac을 다운로드하여 설치
  • Linux: 패키지 관리자를 통해 설치 (예: Ubuntu의 경우 sudo apt-get install docker-ce)

설치가 완료되면 터미널에서 다음 명령어를 실행하여 Docker가 제대로 설치되었는지 확인할 수 있습니다:

docker --version
docker run hello-world

3.3 Docker 기본 명령어 📝

Docker를 사용하기 위한 기본적인 명령어들을 알아보겠습니다:

  • docker pull [이미지 이름]: Docker Hub에서 이미지를 다운로드
  • docker images: 로컬에 저장된 Docker 이미지 목록 확인
  • docker run [옵션] [이미지 이름]: 컨테이너 생성 및 시작
  • docker ps: 실행 중인 컨테이너 목록 확인
  • docker stop [컨테이너 ID]: 실행 중인 컨테이너 중지
  • docker rm [컨테이너 ID]: 컨테이너 삭제
  • docker rmi [이미지 ID]: 이미지 삭제

3.4 Dockerfile 작성하기 📄

Dockerfile은 Docker 이미지를 생성하기 위한 설정 파일입니다. 다음은 간단한 Node.js 애플리케이션을 위한 Dockerfile 예시입니다:


# Node.js 공식 이미지를 기반으로 함
FROM node:14

# 앱 디렉토리 생성
WORKDIR /usr/src/app

# 앱 종속성 설치
COPY package*.json ./
RUN npm install

# 앱 소스 추가
COPY . .

# 포트 설정
EXPOSE 8080

# 앱 실행
CMD [ "node", "server.js" ]

이 Dockerfile은 Node.js 애플리케이션을 위한 환경을 설정하고, 필요한 파일들을 복사하며, 애플리케이션을 실행하는 명령을 정의합니다.

3.5 Docker Compose 🎼

Docker Compose는 여러 컨테이너로 구성된 애플리케이션을 정의하고 실행하기 위한 도구입니다. docker-compose.yml 파일을 사용하여 애플리케이션의 서비스, 네트워크, 볼륨 등을 정의할 수 있습니다.

다음은 간단한 docker-compose.yml 파일의 예시입니다:


version: '3'
services:
  web:
    build: .
    ports:
      - "5000:5000"
  redis:
    image: "redis:alpine"

이 설정은 두 개의 서비스(web과 redis)를 정의하고 있습니다. web 서비스는 현재 디렉토리의 Dockerfile을 기반으로 빌드되며, redis 서비스는 공식 Redis 이미지를 사용합니다.

 

Docker를 사용하면 개발 환경을 일관성 있게 유지하고, 애플리케이션을 쉽게 배포할 수 있습니다. 특히 재능넷(https://www.jaenung.net)과 같은 플랫폼에서 다양한 프로젝트를 진행할 때, Docker를 활용하면 환경 설정에 드는 시간과 노력을 크게 줄일 수 있습니다. 다음 섹션에서는 타입스크립트 프로젝트에 Docker를 적용하는 방법에 대해 자세히 알아보겠습니다. 🚢

4. 타입스크립트 프로젝트에 Docker 적용하기 🔧

이제 타입스크립트와 Docker의 기본을 이해했으니, 두 기술을 결합하여 실제 프로젝트에 적용하는 방법을 알아보겠습니다. 이 과정을 통해 개발부터 배포까지의 전체 워크플로우를 최적화할 수 있습니다.

4.1 프로젝트 구조 설정 🏗️

먼저, 간단한 타입스크립트 프로젝트를 생성해 보겠습니다. 프로젝트 구조는 다음과 같습니다:


my-ts-project/
├── src/
│   └── index.ts
├── package.json
├── tsconfig.json
├── Dockerfile
└── .dockerignore

각 파일의 내용은 다음과 같습니다:

src/index.ts


const greeting: string = "Hello, Docker and TypeScript!";
console.log(greeting);

package.json


{
  "name": "my-ts-project",
  "version": "1.0.0",
  "scripts": {
    "build": "tsc",
    "start": "node dist/index.js"
  },
  "devDependencies": {
    "typescript": "^4.5.4"
  }
}

tsconfig.json


{
  "compilerOptions": {
    "target": "es6",
    "module": "commonjs",
    "outDir": "./dist",
    "strict": true,
    "esModuleInterop": true
  },
  "include": ["src/**/*"]
}

4.2 Dockerfile 작성하기 📝

이제 프로젝트를 Docker 컨테이너로 실행할 수 있도록 Dockerfile을 작성해 보겠습니다:


# Node.js 이미지를 기반으로 함
FROM node:14

# 작업 디렉토리 설정
WORKDIR /usr/src/app

# 패키지 파일 복사 및 의존성 설치
COPY package*.json ./
RUN npm install

# TypeScript 설치
RUN npm install -g typescript

# 소스 코드 복사
COPY . .

# TypeScript 컴파일
RUN npm run build

# 애플리케이션 실행
CMD [ "npm", "start" ]

4.3 .dockerignore 파일 생성 🚫

.dockerignore 파일을 사용하여 Docker 이미지에 불필요한 파일이 포함되지 않도록 합니다:


node_modules
npm-debug.log
Dockerfile
.dockerignore

4.4 Docker 이미지 빌드 및 실행 🏭

이제 Docker 이미지를 빌드하고 실행할 수 있습니다:


# 이미지 빌드
docker build -t my-ts-app .

# 컨테이너 실행
docker run my-ts-app

이 명령어를 실행하면 "Hello, Docker and TypeScript!" 메시지가 콘솔에 출력됩니다.

4.5 개발 환경 최적화 🔧

개발 중에는 코드 변경사항을 즉시 반영하고 싶을 것입니다. 이를 위해 볼륨 마운트와 Nodemon을 사용할 수 있습니다.

먼저, package.json에 Nodemon을 추가합니다:


{
  "name": "my-ts-project",
  "version": "1.0.0",
  "scripts": {
    "build": "tsc",
    "start": "node dist/index.js",
    "dev": "nodemon --watch 'src/**/*.ts' --exec 'ts-node' src/index.ts"
  },
  "devDependencies": {
    "typescript": "^4.5.4",
    "nodemon": "^2.0.7",
    "ts-node": "^10.0.0"
  }
}

그리고 개발용 Dockerfile을 생성합니다 (Dockerfile.dev):


FROM node:14

WORKDIR /usr/src/app

COPY package*.json ./
RUN npm install

COPY . .

CMD [ "npm", "run", "dev" ]

이제 다음 명령어로 개발 환경을 실행할 수 있습니다:


docker build -t my-ts-app-dev -f Dockerfile.dev .
docker run -v $(pwd):/usr/src/app -v /usr/src/app/node_modules -p 3000:3000 my-ts-app-dev

이렇게 하면 로컬 소스 코드의 변경사항이 즉시 컨테이너에 반영됩니다.

4.6 멀티 스테이지 빌드 🏗️

프로덕션 환경을 위해 멀티 스테이지 빌드를 사용하여 최종 이미지 크기를 줄일 수 있습니다:


# 빌드 스테이지
FROM node:14 AS builder
WORKDIR /usr/src/app
COPY package*.json ./
RUN npm install
COPY .  RUN npm run build

# 프로덕션 스테이지
FROM node:14-alpine
WORKDIR /usr/src/app
COPY --from=builder /usr/src/app/dist ./dist
COPY package*.json ./
RUN npm install --only=production
CMD [ "npm", "start" ]

이 Dockerfile은 두 단계로 나뉩니다. 첫 번째 단계에서는 TypeScript를 JavaScript로 컴파일하고, 두 번째 단계에서는 컴파일된 코드만을 가볍고 보안성 높은 Alpine 기반 이미지로 복사합니다.

4.7 Docker Compose 사용하기 🎼

여러 서비스를 포함하는 더 복잡한 애플리케이션의 경우, Docker Compose를 사용하여 전체 애플리케이션 스택을 정의하고 실행할 수 있습니다. 예를 들어, TypeScript 애플리케이션과 데이터베이스를 함께 실행하는 docker-compose.yml 파일은 다음과 같습니다:


version: '3'
services:
  app:
    build: .
    ports:
      - "3000:3000"
    environment:
      - NODE_ENV=production
      - DB_HOST=db
    depends_on:
      - db
  db:
    image: postgres:13
    environment:
      - POSTGRES_DB=myapp
      - POSTGRES_USER=user
      - POSTGRES_PASSWORD=password
    volumes:
      - postgres_data:/var/lib/postgresql/data

volumes:
  postgres_data:

이 설정은 TypeScript 애플리케이션과 PostgreSQL 데이터베이스를 함께 실행합니다. docker-compose up 명령어로 전체 스택을 한 번에 시작할 수 있습니다.

4.8 CI/CD 파이프라인 통합 🔄

Docker를 사용하면 CI/CD(지속적 통합/지속적 배포) 파이프라인을 쉽게 구축할 수 있습니다. 예를 들어, GitHub Actions를 사용한 간단한 CI/CD 워크플로우는 다음과 같습니다:


name: CI/CD

on:
  push:
    branches: [ main ]

jobs:
  build-and-deploy:
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@v2
    - name: Build Docker image
      run: docker build -t my-ts-app .
    - name: Run tests
      run: docker run my-ts-app npm test
    - name: Deploy to production
      if: success()
      run: |
        # 여기에 배포 스크립트 추가
        echo "Deploying to production server"

이 워크플로우는 코드가 main 브랜치에 푸시될 때마다 Docker 이미지를 빌드하고, 테스트를 실행한 후, 성공하면 프로덕션 서버에 배포합니다.

4.9 모니터링 및 로깅 📊

Docker 컨테이너의 모니터링과 로깅은 애플리케이션 운영에 중요합니다. 다음과 같은 도구들을 사용할 수 있습니다:

  • Prometheus: 메트릭 수집 및 저장
  • Grafana: 메트릭 시각화
  • ELK Stack (Elasticsearch, Logstash, Kibana): 로그 수집, 처리, 시각화

예를 들어, Prometheus를 사용하여 Node.js 애플리케이션의 메트릭을 수집하려면, 먼저 prom-client 라이브러리를 설치하고 애플리케이션에 통합해야 합니다:


npm install prom-client

그리고 애플리케이션 코드에 다음과 같이 추가합니다:


import express from 'express';
import client from 'prom-client';

const app = express();
const collectDefaultMetrics = client.collectDefaultMetrics;
collectDefaultMetrics();

app.get('/metrics', async (req, res) => {
  res.set('Content-Type', client.register.contentType);
  res.end(await client.register.metrics());
});

app.listen(3000, () => console.log('Server is running on port 3000'));

이렇게 하면 '/metrics' 엔드포인트에서 Prometheus 형식의 메트릭을 확인할 수 있습니다.

4.10 보안 고려사항 🔒

Docker 컨테이너를 사용할 때는 보안에 특별히 주의를 기울여야 합니다:

  • 최소 권한 원칙을 따라 컨테이너를 실행합니다.
  • 이미지를 정기적으로 스캔하여 취약점을 확인합니다.
  • 프로덕션 환경에서는 루트가 아닌 사용자로 애플리케이션을 실행합니다.
  • 민감한 정보는 환경 변수나 Docker secrets를 통해 안전하게 관리합니다.

예를 들어, 루트가 아닌 사용자로 애플리케이션을 실행하려면 Dockerfile을 다음과 같이 수정할 수 있습니다:


FROM node:14-alpine

# 비루트 사용자 생성
RUN addgroup -S appgroup && adduser -S appuser -G appgroup

WORKDIR /usr/src/app

COPY package*.json ./
RUN npm install

COPY . .
RUN npm run build

# 비루트 사용자로 전환
USER appuser

CMD [ "npm", "start" ]

이렇게 구성하면 애플리케이션이 제한된 권한으로 실행되어 보안이 강화됩니다.

 

이러한 방식으로 타입스크립트 프로젝트에 Docker를 적용하면, 개발부터 배포, 운영까지의 전체 라이프사이클을 효율적으로 관리할 수 있습니다. 특히 재능넷(https://www.jaenung.net)과 같은 플랫폼에서 다양한 프로젝트를 진행할 때, 이러한 접근 방식은 일관성 있는 개발 환경을 제공하고 배포 프로세스를 간소화하는 데 큰 도움이 될 것입니다. 🚀

5. 결론 및 추가 리소스 📚

지금까지 타입스크립트와 Docker를 결합하여 강력하고 효율적인 개발 환경을 구축하는 방법에 대해 알아보았습니다. 이 두 기술을 함께 사용함으로써 얻을 수 있는 주요 이점은 다음과 같습니다:

  • 타입 안정성과 코드 품질 향상
  • 일관된 개발 및 운영 환경
  • 쉬운 배포 및 확장성
  • 효율적인 협업 및 프로젝트 관리

특히 재능넷(https://www.jaenung.net)과 같은 플랫폼에서 활동하는 개발자들에게 이러한 접근 방식은 매우 유용할 것입니다. 다양한 프로젝트를 효율적으로 관리하고, 클라이언트에게 높은 품질의 결과물을 제공하는 데 도움이 될 것입니다.

앞으로 더 발전된 개발자가 되기 위해 다음과 같은 주제들을 추가로 학습해 보는 것이 좋습니다:

  • Kubernetes를 이용한 컨테이너 오케스트레이션
  • 서버리스 아키텍처와 타입스크립트
  • 마이크로서비스 아키텍처 설계 및 구현
  • CI/CD 파이프라인 고도화
  • 클라우드 네이티브 애플리케이션 개발

추가 학습을 위한 유용한 리소스:

타입스크립트와 Docker는 현대 웹 개발에서 핵심적인 기술들입니다. 이 두 기술을 마스터하고 효과적으로 결합하면, 더 나은 개발자로 성장하고 더 가치 있는 서비스를 제공할 수 있을 것입니다. 계속해서 학습하고 실험하며, 새로운 기술과 방법론을 탐구해 나가시기 바랍니다. 여러분의 개발 여정에 행운이 함께하기를 바랍니다! 🌟