
소프트웨어 아키텍처란 무엇인가?
소프트웨어 아키텍처는 시스템의 전체적인 구조와 설계 원칙을 의미합니다. 건축물을 짓기 전에 설계도가 필요하듯이, 소프트웨어도 체계적인 설계가 필요합니다.
왜 아키텍처가 중요한가?
나쁜 아키텍처의 결과:
- 코드 수정이 어려움 (한 부분을 고치면 다른 부분이 망가짐)
- 새로운 기능 추가가 불가능
- 버그가 끊임없이 발생
- 팀원 간 협업이 어려움
좋은 아키텍처의 이점:
- 유지보수가 쉬움
- 확장 가능 (Scale)
- 테스트가 용이
- 팀 협업이 원활
기본 개념: 관심사의 분리 (Separation of Concerns)
가장 중요한 원칙은 각 부분이 하나의 역할만 담당하도록 나누는 것입니다.
// 나쁜 예: 모든 것이 한 곳에
function processOrder(orderId) {
// 1. 데이터베이스 조회
const order = db.query("SELECT * FROM orders WHERE id = ?", orderId);
// 2. 비즈니스 로직
const total = order.items.reduce((sum, item) => sum + item.price, 0);
// 3. UI 업데이트
document.getElementById("total").innerText = total;
// 4. 이메일 발송
sendEmail(order.customer.email, "주문 확인");
}
// 좋은 예: 역할별로 분리
class OrderRepository {
getOrder(orderId) {
return db.query("SELECT * FROM orders WHERE id = ?", orderId);
}
}
class OrderService {
calculateTotal(order) {
return order.items.reduce((sum, item) => sum + item.price, 0);
}
}
class OrderUI {
displayTotal(total) {
document.getElementById("total").innerText = total;
}
}
class NotificationService {
sendOrderConfirmation(email) {
sendEmail(email, "주문 확인");
}
}
계층형 아키텍처 (Layered Architecture)
가장 기본적이고 널리 사용되는 패턴입니다.
3계층 아키텍처
1. Presentation Layer (표현 계층)
- 사용자 인터페이스 (UI)
- 사용자 입력 처리
- 데이터 표시
// React 컴포넌트
function ProductList() {
const [products, setProducts] = useState([]);
useEffect(() => {
// 비즈니스 로직 계층 호출
productService.getAll().then(setProducts);
}, []);
return (
<div>
{products.map(p => <ProductCard key={p.id} product={p} />)}
</div>
);
}
2. Business Logic Layer (비즈니스 로직 계층)
- 핵심 비즈니스 규칙
- 데이터 처리 및 변환
- 유효성 검증
class ProductService {
async getAll() {
const products = await productRepository.findAll();
// 비즈니스 로직: 재고 있는 상품만 반환
return products.filter(p => p.stock > 0);
}
async purchase(productId, quantity) {
const product = await productRepository.findById(productId);
// 비즈니스 규칙 검증
if (product.stock < quantity) {
throw new Error("재고 부족");
}
// 재고 차감
product.stock -= quantity;
await productRepository.update(product);
return product;
}
}
3. Data Layer (데이터 계층)
- 데이터베이스 접근
- 외부 API 호출
- 파일 시스템 접근
class ProductRepository {
async findAll() {
return await db.query("SELECT * FROM products");
}
async findById(id) {
return await db.query("SELECT * FROM products WHERE id = ?", [id]);
}
async update(product) {
return await db.query(
"UPDATE products SET stock = ? WHERE id = ?",
[product.stock, product.id]
);
}
}
MVC 패턴 (Model-View-Controller)
웹 애플리케이션에서 가장 많이 사용되는 패턴입니다.
사용자 → View → Controller → Model → Database
↑ ↓
└──────────────────────┘
Model (모델): 데이터와 비즈니스 로직
class User {
constructor(name, email) {
this.name = name;
this.email = email;
}
validate() {
if (!this.email.includes('@')) {
throw new Error("유효하지 않은 이메일");
}
}
}
View (뷰): 사용자 인터페이스
function UserProfile({ user }) {
return (
<div>
<h1>{user.name}</h1>
<p>{user.email}</p>
</div>
);
}
Controller (컨트롤러): 요청 처리 및 흐름 제어
app.post('/users', async (req, res) => {
try {
const user = new User(req.body.name, req.body.email);
user.validate();
await userRepository.save(user);
res.json({ success: true });
} catch (error) {
res.status(400).json({ error: error.message });
}
});
마이크로서비스 아키텍처
대규모 애플리케이션을 작은 독립적인 서비스들로 분리하는 패턴입니다.
모놀리식 vs 마이크로서비스
모놀리식 (Monolithic)
┌─────────────────────────────┐
│ 하나의 큰 애플리케이션 │
│ ┌─────┬─────┬─────┬─────┐ │
│ │사용자│상품 │주문 │결제 │ │
│ └─────┴─────┴─────┴─────┘ │
│ 하나의 데이터베이스 │
└─────────────────────────────┘
마이크로서비스
┌────────┐ ┌────────┐ ┌────────┐ ┌────────┐
│사용자 │ │상품 │ │주문 │ │결제 │
│서비스 │ │서비스 │ │서비스 │ │서비스 │
│ DB │ │ DB │ │ DB │ │ DB │
└────────┘ └────────┘ └────────┘ └────────┘
마이크로서비스의 장점
- 독립 배포: 한 서비스만 업데이트 가능
- 기술 다양성: 서비스마다 다른 언어/프레임워크 사용 가능
- 확장성: 필요한 서비스만 확장
- 장애 격리: 한 서비스 장애가 전체에 영향 안 줌
마이크로서비스의 단점
- 복잡성 증가: 서비스 간 통신 관리
- 데이터 일관성: 분산 트랜잭션 처리 어려움
- 운영 부담: 여러 서비스 모니터링 필요
디자인 패턴 (Design Patterns)
자주 발생하는 문제에 대한 검증된 해결책입니다.
1. Singleton 패턴
class Database {
static instance = null;
static getInstance() {
if (!Database.instance) {
Database.instance = new Database();
}
return Database.instance;
}
}
// 어디서든 같은 인스턴스 사용
const db1 = Database.getInstance();
const db2 = Database.getInstance();
console.log(db1 === db2); // true
2. Factory 패턴
class UserFactory {
createUser(type) {
switch(type) {
case 'admin':
return new AdminUser();
case 'customer':
return new CustomerUser();
default:
return new GuestUser();
}
}
}
3. Observer 패턴
class EventEmitter {
listeners = {};
on(event, callback) {
if (!this.listeners[event]) {
this.listeners[event] = [];
}
this.listeners[event].push(callback);
}
emit(event, data) {
if (this.listeners[event]) {
this.listeners[event].forEach(cb => cb(data));
}
}
}
const emitter = new EventEmitter();
emitter.on('userLogin', (user) => {
console.log(`${user.name}님이 로그인했습니다`);
});
초보자를 위한 학습 로드맵
1단계: 기본 개념 이해 (1-2개월)
- 관심사의 분리
- 계층형 아키텍처
- MVC 패턴
2단계: 실전 적용 (3-4개월)
- 작은 프로젝트에 MVC 적용
- Repository 패턴 사용
- 의존성 주입(Dependency Injection) 학습
3단계: 고급 패턴 (5-6개월)
- 디자인 패턴 학습 및 적용
- Clean Architecture 이해
- SOLID 원칙 학습
4단계: 대규모 시스템 (6개월 이상)
- 마이크로서비스 아키텍처
- 이벤트 기반 아키텍처
- CQRS, Event Sourcing
실전 프로젝트 구조 예시
my-app/
├── src/
│ ├── presentation/ # UI 계층
│ │ ├── components/
│ │ ├── pages/
│ │ └── hooks/
│ ├── application/ # 비즈니스 로직 계층
│ │ ├── services/
│ │ └── use-cases/
│ ├── domain/ # 도메인 모델
│ │ ├── entities/
│ │ └── value-objects/
│ └── infrastructure/ # 데이터 계층
│ ├── repositories/
│ ├── api/
│ └── database/
└── tests/
아키텍처 선택 가이드
| 프로젝트 규모 | 추천 아키텍처 |
|---|---|
| 소규모 (1-2명) | 단순 계층형 또는 MVC |
| 중규모 (3-10명) | Clean Architecture |
| 대규모 (10명 이상) | 마이크로서비스 고려 |
소프트웨어 아키텍처는 한 번에 완벽하게 설계할 수 없습니다. 프로젝트가 성장하면서 지속적으로 개선해야 합니다.
핵심 원칙:
- 관심사를 분리하라
- 의존성을 관리하라
- 테스트 가능하게 설계하라
- 단순함을 유지하라
초보자라면 먼저 계층형 아키텍처와 MVC 패턴부터 시작하세요. 작은 프로젝트에 적용하면서 경험을 쌓다 보면, 자연스럽게 더 복잡한 패턴을 이해하고 적용할 수 있게 됩니다.
