왜 디자인 패턴을 사용하는가?
Node.js는 소프트웨어 개발의 일반적인 문제나 과제에 대한 검증된 솔루션을 제공하기 때문에 디자인 패턴을 사용합니다. 여기 디자인 패턴을 사용해야 하는 이유 몇가지를 살펴보도록 하죠.
- 재사용성 : 디자인 패턴을 사용하면 복잡한 논리와 기능을 재사용 가능하고 모듈식으로 캡슐화할 수 있으므로 애플리케이션을 구축할 때 시간과 노력을 절약할 수 있습니다.
- 확장성 : 디자인 패턴은 애플리케이션의 성장과 발전에 따라 쉽게 확장하고 유지관리할 수 있는 구조와 조직을 코드에 제공합니다.
- 유지보수성 : 디자인 패턴은 관심사와 책임을 분리하고 코드 중복을 줄이며 일관성을 향상시킴으로써 유지보수 가능한 코드를 작성하는 데 도움이 됩니다.
- 유연성 : 디자인 패턴을 사용하면 전반적인 기능에 영향을 미치지 않고 애플리케이션의 다양한 구성요소 또는 모듈을 교환하거나 수정할 수 있으므로 코드가 변화하는 요구사항에 더 유연하고 적응할 수 있습니다.
- 코드품질 : 디자인 패턴은 추상화, 캡슐화 및 관심사 분리와 같은 우수한 코딩 관행 및 원칙을 촉진하여 더 높은 품질과 더 신뢰할 수 있는 코드로 이어질 수 있습니다.
디자인 패턴을 사용하면 재사용 가능성, 확장성, 유지보수성, 유연성 및 고품질의 더 나은 코드를 작성할 수 있으므로 궁극적으로 더 성공적이고 강력한 애플리케이션을 구축할 수 있습니다.
지금부터 "로그인" 서비스 로직 상황에서 코드가 포함된 각 디자인 패턴을 살펴보겠습니다.
Singleton Pattern
Singleton 패턴을 사용하여 인증 서비스의 인스턴스가 하나만 생성되고 애플리케이션 간에 공유되도록 할 수 있습니다. 이것은 JWT 비밀 키 또는 사용자 데이터베이스 연결과 같은 공유 리소스를 관리하는 데 유용할 수 있습니다.
class AuthenticationService {
constructor() {
this.jwtSecret = 'secret'; // initialize the JWT secret key
this.userDb = new DatabaseConnection('mongodb://localhost/users'); // initialize the user database connection
}
static getInstance() {
if (!AuthenticationService.instance) {
AuthenticationService.instance = new AuthenticationService();
}
return AuthenticationService.instance;
}
// authentication logic
async authenticate(username, password) {
const user = await this.userDb.findUserByUsername(username);
if (user && user.password === password) {
const token = jwt.sign({ username: user.username }, this.jwtSecret);
return { token };
} else {
throw new Error('Invalid username or password');
}
}
}
// Usage:
const authService = AuthenticationService.getInstance();
이 예에서는 JWT 비밀 키와 사용자 데이터베이스 연결을 관리하는 AuthenticationService라는 Singleton 클래스를 만듭니다. authenticate() 메서드는 인증 로직을 수행하고 사용자 이름과 암호가 유효한 경우 JWT 토큰을 반환합니다.
Factory Pattern
Factory 패턴을 사용하여 인증 유형에 따라 다른 유형의 인증 전략을 만들 수 있습니다. 예를 들어, 로컬 인증, 소셜 미디어 인증 또는 다중 요소 인증을 위한 다양한 전략을 만들 수 있습니다.
class AuthenticationStrategyFactory {
static createStrategy(type) {
if (type === 'local') {
return new LocalAuthenticationStrategy();
} else if (type === 'social') {
return new SocialMediaAuthenticationStrategy();
} else if (type === 'multi-factor') {
return new MultiFactorAuthenticationStrategy();
} else {
throw new Error(`Unsupported authentication type: ${type}`);
}
}
}
// Usage:
const strategy = AuthenticationStrategyFactory.createStrategy('local');
이 예에서는 Authentication Strategy Factory라는 Factory 클래스를 생성하여 유형에 따라 다양한 유형의 인증 전략을 생성할 수 있습니다. 우리는 LocalAuthenticationStrategy, SocialMediaAuthenticationStrategy, MultiFactorAuthenticationStrategy라는 세 가지 다른 전략을 만듭니다. 인증 전략을 선택하고 유형에 따라 적절한 전략을 반환합니다.
Middleware Pattern
Middleware 패턴을 사용하여 모듈식 방식으로 인증 요청을 처리하는 데 사용할 수 있는 handler 체인을 정의할 수 있습니다. 이는 Express.js와 같은 미들웨어 기반 프레임워크를 구현하는 데 유용할 수 있습니다.
class AuthenticationMiddleware extends Middleware {
constructor(strategy) {
super();
this.strategy = strategy;
}
async _handle(req, res, next) {
try {
const result = await this.strategy.authenticate(req.body.username, req.body.password);
req.user = result;
next();
} catch (error) {
res.status(401).json({ error: error.message });
}
}
}
// Usage:
const strategy = AuthenticationStrategyFactory.createStrategy('local');
const middleware = new AuthenticationMiddleware(strategy);
app.post('/login', middleware.handle.bind(middleware), (req, res) => {
res.json({ token: req.user.token });
});
이 예에서는 지정된 전략을 사용하여 인증 로직을 처리하는 Authentication Middleware라는 미들웨어 클래스를 만듭니다. handle() 메서드는 미들웨어 체인을 통해 인증 요청을 처리하는 데 사용되며, next() 함수는 요청을 체인의 다음 미들웨어로 전달하는 데 사용됩니다. 인증이 실패하면 오류 메시지가 클라이언트에 반환됩니다.
Observer Pattern
사용자가 로그인하거나 로그아웃할 때 Observer 패턴을 사용하여 다른 개체나 서비스에 알릴 수 있습니다. 예를 들어 사용자가 로그인하거나 로그아웃할 때 로깅 서비스 또는 분석 서비스에 알릴 수 있습니다.
class LoginService {
constructor() {
this.observers = [];
}
addObserver(observer) {
this.observers.push(observer);
}
removeObserver(observer) {
this.observers = this.observers.filter((o) => o !== observer);
}
async login(username, password) {
const authService = AuthenticationService.getInstance();
const result = await authService.authenticate(username, password);
// notify the observers
this.notifyObservers({ username });
return result;
}
async logout(token) {
// revoke the token
const authService = AuthenticationService.getInstance();
const user = jwt.verify(token, authService.jwtSecret);
const tokenRevoked = await this.revokeToken(user.username, token);
// notify the observers
this.notifyObservers({ username: user.username });
return tokenRevoked;
}
notifyObservers(data) {
this.observers.forEach((observer) => observer.update(data));
}
}
// Usage:
const loginService = new LoginService();
loginService.addObserver(new LoggingService());
loginService.addObserver(new AnalyticsService());
이 예에서는 다른 개체나 서비스에서 관찰할 수 있는 LoginService 클래스를 생성합니다. login() 메서드는 인증 로직을 수행하고 사용자 이름과 암호가 유효한 경우 JWT 토큰을 반환합니다. logout() 메서드는 토큰을 해지하고 사용자가 로그아웃할 때 관찰자에게 알립니다. addObserver() 및 removeObserver() 메서드는 관찰자를 관리하는 데 사용할 수 있으며 notifyObservers() 메서드는 사용자가 로그인하거나 로그아웃할 때 모든 관찰자에게 알리는 데 사용됩니다.
Strategy Pattern
Strategy 패턴을 사용하여 다양한 유형의 사용자 또는 시나리오에 대해 서로 다른 인증 전략을 정의할 수 있습니다. 예를 들어, 관리자, 프리미엄 사용자 또는 API 사용자를 위한 다양한 전략을 만들 수 있습니다.
class AdminAuthenticationStrategy extends LocalAuthenticationStrategy {
async authenticate(username, password) {
const user = await this.authService.userDb.findUserByUsername(username);
if (user && user.password === password && user.role === 'admin') {
const token = jwt.sign({ username: user.username }, this.authService.jwtSecret);
return { token };
} else {
throw new Error('Invalid username or password');
}
}
}
class PremiumAuthenticationStrategy extends LocalAuthenticationStrategy {
async authenticate(username, password) {
const user = await this.authService.userDb.findUserByUsername(username);
if (user && user.password === password && user.subscription === 'premium') {
const token = jwt.sign({ username: user.username }, this.authService.jwtSecret);
return { token };
} else {
throw new Error('Invalid username or password');
}
}
}
// Usage:
const authService = AuthenticationService.getInstance();
const localStrategy = new LocalAuthenticationStrategy(authService);
const adminStrategy = new AdminAuthenticationStrategy(authService);
const premiumStrategy = new PremiumAuthenticationStrategy(authService);
이 예에서는 로컬 인증 전략에서 상속되는 관리 사용자와 프리미엄 사용자에 대해 서로 다른 인증 전략을 만듭니다. 각 전략의 authenticate() 메서드는 JWT 토큰을 반환하기 전에 사용자의 역할 또는 구독을 확인합니다.
각 패턴들 비교
Singleton Pattern
- 요소: Singleton 클래스, 생성자, 정적 getInstance() 메서드, 공유 인스턴스 변수, 전용 생성자
- 목적: 클래스의 인스턴스가 하나만 생성되고 응용프로그램 전체에서 공유되는지 확인합니다.
- 이점: 동일한 개체의 여러 인스턴스를 생성하지 않고, 공유 리소스의 일관성과 중앙 집중식 제어를 보장합니다.
Factory Pattern
- 요소: Factory 클래스, 생성 메서드, 제품 클래스, 제품 생성 로직, 추상 제품 클래스(선택 사항)
- 목적: 클라이언트에 생성 로직을 노출하지 않고 개체를 생성할 수 있으므로 개체 생성을 유연하게 사용자 지정할 수 있습니다.
- 이점: 객체 생성 프로세스를 캡슐화하고, 클라이언트 코드와 객체 생성 로직 간의 결합을 줄이며, 객체 인스턴스화를 단순화합니다.
Middleware Pattern
- 요소: Middleware 클래스, handle() 메서드, next() 함수 또는 Middleware chain, 요청 및 응답 개체
- 목적: 요청 또는 데이터를 모듈식으로 처리하는 데 사용할 수 있는 처리기 체인을 정의합니다.
- 이점: 모듈화 및 확장성을 높이고, 코드 중복을 줄이며, 복잡한 처리 로직의 구현을 단순화하고, 처리 로직의 유연성과 사용자 정의를 지원합니다.
Observer Pattern
- 요소: Subject 클래스, observer 클래스, registerObserver() 및 removeObserver() 메서드, notifyObserver() 메서드, update() 메서드를 관찰자에서 사용할 수 있습니다.
- 목적: 개체 간의 일대일 종속성을 정의하여 한 개체가 상태를 변경할 때 모든 종속성이 자동으로 통지되고 업데이트되도록 합니다.
- 이점: 개체 간의 디커플링을 촉진하고, 확장성과 모듈성을 개선하며, 개체와 동작을 동적으로 구성할 수 있습니다.
Strategy Pattern
- 요소: Context 클래스, strategy 인터페이스 또는 클래스, 구체적인 strategy 클래스, setStrategy() 및 executeStrategy() 메서드
- 목적: 상황 또는 환경에 따라 알고리즘 제품군을 정의하고, 각 알고리즘을 캡슐화하고, 서로 교환할 수 있도록 합니다.
- 이점: 알고리즘 또는 동작 로직을 캡슐화하고, 동작의 유연성과 사용자 정의를 촉진하며, 코드 유지보수 및 테스트를 단순화합니다.
결론
각 설계 패턴에는 고유한 요소, 목적 및 이점 집합이 있습니다. 이러한 패턴을 이해하고 적용함으로써 코드의 품질, 확장성, 유지보수성 및 유연성을 개선하고 궁극적으로 더 성공적이고 강력한 애플리케이션을 만들 수 있습니다.
'Node.js' 카테고리의 다른 글
⚙️Three Tier Architecture in Node.js (0) | 2023.02.28 |
---|---|
✨Welcome to Express! (0) | 2023.02.27 |
🎉Start Node.js (0) | 2023.02.24 |