소프트웨어 디자인, 설계
아키텍쳐 원리
아키텍쳐 원리를 설명드리겠습니다.
소프트웨어 아키텍처를 설계할 때 중요한 원칙을 Component Principles, Policy vs. Detail, Coupling and Cohesion, Boundaries 기준으로 설명합니다.
소프트웨어 설계가 가장 중요하다고 봐요. 왜 중요하냐면 사실 기능추가나 수정 같은 경우 기존에 최초에 설계되어 있는 소스코드를 기반으로 응용하는 경우가 대부분이기 때문입니다. 아닌 경우도 있지만요. 무튼, 그래서 초창기에 잘못 만들어 놓으면 규모가 커질수록 성능, 속도, 유지보수 비용, 확장성 등등 속도가 더뎌지는 경우가 생기겠죠.
근데 누군가 만들어 놓은 소스코드를 응용해서 사용하는게 마냥 나쁜건 아니라고 봐요. 굉장히 생산적인 행동이에요. 세탁기 냅두고 손빨래 할 필요는 없는 거든요.
하지만 지금 만들어진 소스코드가 제대로 된건지 아닌지는 본인이 많이 알고 있어야 제대로 사용할 수 있는 거에요. 그리고 제대로 알아야 라이브러리를 사용해도 되는 상황인지 직접 만들거나 커스텀을 해야 하는건지 판단 할 수 있는거라고 봐요.
이러한 능력은 사실 AI가 도와주기 힘들죠. 본인 뇌에서 판단을 하니깐요. 또한 기술적인 부분은 AI가 많이 알수도 있겠지만 현재 회사 상황은 AI보다 본인이 잘 알테니깐요.
무튼 결론적으로 공부해봅시다.
1. Component Principles (컴포넌트 원칙)
컴포넌트를 설계할 때 고려해야 하는 세 가지 원칙이 있다.
1.1 REP (Reuse/Release Equivalence Principle, 재사용/릴리스 등가 원칙)
- 컴포넌트는 독립적으로 배포 가능해야 하며, 배포 단위와 재사용 단위가 일치해야 합니다.
- 예를 들어,
utils.js
모듈을 배포할 때는 안정적인 API를 제공해야 합니다.
// utils.js (독립적으로 배포할 수 있는 모듈)
export function formatDate(date) {
return date.toISOString().split('T')[0]
}
1.2 CCP (Common Closure Principle, 공통 폐쇄 원칙)
- 같은 이유로 변경되는 코드들은 같은 모듈에 위치해야 합니다.
// orderService.js (Order 관련 로직을 한 곳에 모음)
export function validateOrder(order) {
return order.items.length > 0
}
export function processOrder(order) {
if (!validateOrder(order)) throw new Error('Invalid order')
console.log('Processing order...')
}
1.3 CRP (Common Reuse Principle, 공통 재사용 원칙)
- 필요하지 않은 코드가 포함되지 않도록 주의해야 합니다.
- 관련이 없는 기능을 한 모듈에 포함하지 맙시다.
// 잘못된 예시 (불필요한 결합)
export function validateUser(user) {
return user.email.includes('@')
}
export function processOrder(order) {
console.log('Processing order...')
}
올바른 분리: userValidation.js
와 orderService.js
로 나누어야 합니다.
2. Policy vs. Detail
비즈니스 로직(Policy)과 세부 구현(Detail)을 분리해야 합니다.
2.1 Policy (정책, 비즈니스 로직)
// policy/orderPolicy.js (비즈니스 로직)
export function applyDiscount(order) {
if (order.customerType === 'VIP') {
order.totalPrice *= 0.9
}
}
2.2 Detail (세부 구현)
// services/orderService.js (구현 세부 사항)
import { applyDiscount } from '../policy/orderPolicy.js'
export function checkout(order) {
applyDiscount(order)
console.log('Order checked out with total:', order.totalPrice)
}
비즈니스 로직을 정책 파일로 분리하면 구현을 쉽게 변경할 수 있다.
3. Coupling and Cohesion (결합도와 응집도)
3.1 낮은 결합도 (Loose Coupling)
// 낮은 결합도: 인터페이스(추상화)를 활용한 의존성 감소
class PaymentProcessor {
process(order) {
throw new Error('Not implemented')
}
}
class PayPalProcessor extends PaymentProcessor {
process(order) {
console.log('Processing payment via PayPal...')
}
}
class OrderService {
constructor(paymentProcessor) {
this.paymentProcessor = paymentProcessor
}
checkout(order) {
this.paymentProcessor.process(order)
}
}
const orderService = new OrderService(new PayPalProcessor())
orderService.checkout({ total: 100 })
인터페이스를 활용하면, PayPalProcessor
를 다른 결제 방식으로 쉽게 변경할 수 있다.
3.2 높은 응집도 (High Cohesion)
// 낮은 응집도 (Bad Example)
class OrderManager {
validateOrder(order) {
/* 검증 로직 */
}
processPayment(order) {
/* 결제 처리 */
}
sendEmail(order) {
/* 이메일 전송 */
}
}
// 높은 응집도 (Good Example)
class OrderValidator {
validate(order) {
return order.items.length > 0
}
}
class PaymentProcessor {
process(order) {
console.log('Processing payment...')
}
}
class EmailService {
sendEmail(order) {
console.log('Sending confirmation email...')
}
}
각 클래스가 하나의 책임만 가지도록 분리하면 유지보수가 쉬워진다.
4. Boundaries (경계)
4.1 시스템 내 경계를 명확히 설정하기
- 마이크로서비스 아키텍처, DDD (Domain-Driven Design)에서 중요한 개념
- 모듈을 명확히 나누고, 인터페이스를 통해 통신하도록 합니다.
// orderService.js (Order 도메인)
export function placeOrder(order) {
console.log('Placing order...')
}
// shippingService.js (배송 도메인)
export function shipOrder(order) {
console.log('Shipping order...')
}
주문(Order)과 배송(Shipping)을 분리하여 유지보수성을 높인다.
정리
원칙 | 설명 | 핵심 포인트 |
---|---|---|
Component Principles | 모듈화의 기본 원칙 (REP, CCP, CRP) | 재사용성과 유지보수성을 고려하여 컴포넌트 설계 |
Policy vs. Detail | 비즈니스 로직(Policy)과 세부 구현(Detail) 분리 | 인터페이스를 활용하여 정책과 구현을 분리 |
Coupling & Cohesion | 결합도 낮추고, 응집도 높이기 | 인터페이스와 책임 분리를 통한 유연한 설계 |
Boundaries | 시스템 내 경계를 명확하게 정의 | 모듈화와 인터페이스 활용 |
이 원칙들을 잘 활용하면 유지보수하기 쉽고, 확장 가능한 소프트웨어 아키텍처를 설계할 수 있어요.