디자인 패턴을 사용하여 TypeScript로 확장 가능한 사용자 관리 시스템 구축하기

TypeScript와 Mongoose를 사용하여 확장 가능한 사용자 관리 시스템을 구축하는 과정을 안내합니다. 몇 가지 주요 디자인 패턴을 구현하고 논의할 것입니다.

디자인 패턴 개요

  • Model-View-Controller(MVC) 패턴: 애플리케이션을 상호 연결된 세 가지 구성 요소로 분리합니다.
  • Repository 패턴: 데이터 액세스 로직을 추상화합니다.
  • Service 패턴: 비즈니스 로직을 캡슐화합니다.
  • Factory 패턴: 체 생성 로직을 캡슐화합니다.
  • Dependency Injection(DI) 패턴: 종속성을 주입하여 느슨한 결합을 촉진합니다.
  • Single Responsibility Principle(SRP): 각 클래스가 단일 책임을 갖도록 합니다.

단계별 구현

다음과 같은 구조의 사용자 관리 시스템을 만들어 보겠습니다.

  • UserModel: 데이터 구조를 나타냅니다.
  • UserRepository: 데이터 액세스를 처리합니다.
  • UserService: 비즈니스 로직을 포함합니다.
  • UserFactory: 서비스 인스턴스를 만듭니다.
  • UserController: HTTP 요청을 관리합니다.

1단계: UserModel 정의

먼저 User 엔티티에 대한 Mongoose 모델을 정의해 보겠습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import mongoose, { Schema, Document, Model } from 'mongoose';

// Mongoose Document를 확장하는 User 인터페이스를 정의합니다.
interface IUser extends Document {
username: string;
email: string;
password: string;
}

// 사용자 스키마 정의
const UserSchema: Schema<IUser> = new Schema({
username: { type: String, required: true },
email: { type: String, required: true },
password: { type: String, required: true },
});

// 사용자 모델 생성
const UserModel: Model<IUser> = mongoose.model<IUser>('User', UserSchema);
export { IUser, UserModel };

2단계: UserRepository 만들기

다음으로 데이터 액세스 작업을 처리할 Repository 클래스를 생성합니다.

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
import { Model } from 'mongoose';
import { IUser, UserModel } from './UserModel';

class UserRepository {
private userModel: Model<IUser>;

constructor() {
this.userModel = UserModel;
}

async findOneByUsername(username: string): Promise<IUser | null> {
return this.userModel.findOne({ username }).exec();
}

async findAll(): Promise<IUser[]> {
return this.userModel.find().exec();
}

async create(user: Partial<IUser>): Promise<IUser> {
return this.userModel.create(user);
}

async updateById(id: string, update: Partial<IUser>): Promise<void> {
await this.userModel.findByIdAndUpdate(id, update).exec();
}

async deleteById(id: string): Promise<void> {
await this.userModel.findByIdAndDelete(id).exec();
}
}
export default UserRepository;

3단계: UserService 생성

서비스 계층은 비즈니스 로직을 포함하고 Repository와 상호 작용합니다.

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
import UserRepository from './UserRepository';
import { IUser } from './UserModel';

class UserService {
private userRepository: UserRepository;

constructor(userRepository: UserRepository) {
this.userRepository = userRepository;
}

async registerUser(username: string, email: string, password: string): Promise<IUser> {
const user = { username, email, password };
return this.userRepository.create(user);
}

async getUserByUsername(username: string): Promise<IUser | null> {
return this.userRepository.findOneByUsername(username);
}

async updateUser(id: string, update: Partial<IUser>): Promise<void> {
await this.userRepository.updateById(id, update);
}

async deleteUser(id: string): Promise<void> {
await this.userRepository.deleteById(id);
}
}
export default UserService;

4단계: UserFactory 생성

팩토리 패턴은 서비스의 인스턴스를 생성하는 데 사용됩니다.

1
2
3
4
5
6
7
8
9
10
import UserRepository from './UserRepository';
import UserService from './UserService';

class UserFactory {
static createUserService(): UserService {
const userRepository = new UserRepository();
return new UserService(userRepository);
}
}
export default UserFactory;

5단계: UserController 생성

컨트롤러는 HTTP 요청을 처리하고 서비스를 사용하여 작업을 수행합니다.

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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
import { Request, Response } from 'express';
import UserFactory from './UserFactory';

class UserController {
private userService = UserFactory.createUserService();

async registerUser(req: Request, res: Response): Promise<void> {
const { username, email, password } = req.body;
try {
const user = await this.userService.registerUser(username, email, password);
res.status(201).json(user);
} catch (error) {
res.status(400).json({ message: error.message });
}
}

async getUser(req: Request, res: Response): Promise<void> {
const { username } = req.params;
try {
const user = await this.userService.getUserByUsername(username);
if (user) {
res.status(200).json(user);
} else {
res.status(404).json({ message: 'User not found' });
}
} catch (error) {
res.status(400).json({ message: error.message });
}
}

async updateUser(req: Request, res: Response): Promise<void> {
const { id } = req.params;
const update = req.body;
try {
await this.userService.updateUser(id, update);
res.status(200).json({ message: 'User updated' });
} catch (error) {
res.status(400).json({ message: error.message });
}
}

async deleteUser(req: Request, res: Response): Promise<void> {
const { id } = req.params;
try {
await this.userService.deleteUser(id);
res.status(200).json({ message: 'User deleted' });
} catch (error) {
res.status(400).json({ message: error.message });
}
}
}
export default UserController;

6단계: Express 경로 설정

마지막으로 HTTP 요청을 처리할 Express 경로를 설정합니다.

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
import express from 'express';
import mongoose from 'mongoose';
import UserController from './UserController';

const app = express();
const userController = new UserController();

// Middleware
app.use(express.json());
// Routes
app.post('/users', userController.registerUser.bind(userController));
app.get('/users/:username', userController.getUser.bind(userController));
app.put('/users/:id', userController.updateUser.bind(userController));
app.delete('/users/:id', userController.deleteUser.bind(userController));

// Connect to MongoDB and start the server
mongoose
.connect('mongodb://localhost:27017/mydatabase', {
useNewUrlParser: true,
useUnifiedTopology: true,
})
.then(() => {
app.listen(3000, () => {
console.log('Server is running on port 3000');
});
})
.catch(console.error);

관련된 디자인 패턴 요약

1. Model-View-Controller (MVC) 패턴

UserModel은 MVC의 Model 부분과 일치합니다.
UserController는 MVC의 Controller 부분을 관리합니다.
이 예시에서는 View 부분이 직접적으로 표현되지는 않았지만 프론트엔드 컴포넌트와 관련이 있습니다.

2. Repository 패턴

UserRepository는 사용자 데이터에 액세스하기 위한 컬렉션과 유사한 인터페이스를 제공합니다.

3. Service 패턴

UserService는 사용자 작업과 관련된 비즈니스 로직을 캡슐화합니다.

4. Factory 패턴

UserFactory는 UserService의 생성 로직을 캡슐화합니다.

5. Dependency Injection(DI) 패턴

UserFactory는 UserRepository를 UserService에 주입합니다.

6. Single Responsibility Principle(SRP)

각 클래스에는 단일 책임이 있어 유지보수성과 명확성을 높입니다.

결론

이러한 디자인 패턴을 구현하면 애플리케이션을 위한 견고하고 유지 관리 가능하며 확장 가능한 아키텍처를 만들 수 있습니다. 이 접근 방식은 우려 사항을 분리하고 재사용성을 촉진하며 코드를 테스트하고 유지 관리하기 쉽게 만듭니다.

Share