백엔드 개발/API · 아키텍처 설계
Procedural 구조에서 Use Case 구조로, 내가 구조를 다시 짠 이유
JeongPark
2025. 5. 8. 19:17
728x90
1. 서론
많은 Node.js 백엔드 프로젝트들은 처음에는 빠르게 기능 구현을 목표로 시작되며, 자연스럽게 "절차적 구조"를 가지게 됩니다. 하지만 프로젝트가 커지고 유지보수와 테스트가 복잡해질수록, 구조적인 설계에 대한 고민이 시작됩니다.
이 글에서는 Use Case 지향 서비스 구조가 무엇이며, 기존 절차적 구조와 어떤 차이가 있고, 실제 프로젝트에 어떻게 리팩토링해나갈 수 있는지 정리합니다.
2. 절차형(Procedural) 서비스 구조란?
절차형 구조는 함수 중심으로 코드를 구성하며, 하나의 함수가 여러 역할(DB 접근, 로직 처리, 응답 구성 등)을 동시에 수행하는 구조입니다.
예시 구조
export const registerUser = async (data) => {
const user = await userRepository.findOne(...);
const hashed = await bcrypt.hash(data.password);
const saved = await userRepository.save({...});
const token = jwt.sign(...);
return token;
};
문제점
- 단일 책임 원칙 위반: 하나의 함수에서 너무 많은 역할을 수행함
- 유지보수 어려움: 로직이 길어질수록 변경에 민감해짐
- 재사용성 낮음: 함수가 특정 흐름에 종속됨
- 테스트 어려움: 외부 의존성(DB, Redis 등)을 끊기 힘듦
3. Use Case 지향 구조란?
Use Case 지향 구조는 사용자 행위 단위로 비즈니스 로직을 구분하여, 각 단위가 오직 하나의 책임만을 가지게 만드는 구조입니다.
핵심 철학
- 핵심 비즈니스 흐름(Use Case)를 명확하게 구분
- 모든 외부 의존성은 주입받아 테스트와 재사용이 쉬운 구조로 설계
- Service 로직은 Domain 규칙을 다루고, 인프라와는 분리함
예시 구조
📦 use-cases
┣ 📜 RegisterUser.ts
┗ 📜 LoginUser.ts
📦 services
┗ 📜 TokenService.ts
📦 infra
┣ 📜 BcryptAdapter.ts
┗ 📜 RedisTokenStore.ts
export class RegisterUserUseCase {
constructor(
private readonly userRepo: UserRepository,
private readonly tokenService: TokenService,
private readonly passwordHasher: PasswordHasher
) {}
async execute(dto: RegisterUserDto) {
const user = await this.userRepo.findByEmail(dto.email);
const hashed = await this.passwordHasher.hash(dto.password);
const savedUser = await this.userRepo.save({ ...dto, password: hashed });
return this.tokenService.issue(savedUser);
}
}
장점
- 역할 명확화: 유저 행동마다 하나의 유즈케이스로 표현됨
- 테스트 용이: 각 클래스가 외부 의존성을 받기 때문에 mocking 쉬움
- 확장성 높음: 새로운 시나리오 추가에 유리함 (e.g. OAuth, 관리자 계정 등)
4. 리팩토링 전략
절차적 구조에서 Use Case 구조로 전환하기 위해선 다음과 같은 단계를 거칩니다.
1) 서비스 계층 명확히 구분
- 기존 loginUser, registerUser 등을 LoginUserUseCase, RegisterUserUseCase 클래스로 분리
2) 유틸리티와 인프라 분리
- bcrypt, redis, email 등은 infra/ 또는 adapters/로 옮김
3) DI 컨테이너 도입 고려
- 테스트와 확장성을 위해 의존성 주입 구조 고려 (e.g. tsyringe, awilix)
4) Express 컨트롤러는 유즈케이스만 호출
const registerController = async (req, res) => {
const result = await registerUserUseCase.execute(req.body);
return res.json(result);
};
5. 정리하며
당신의 서비스가 성장하고 있다면, 구조 또한 성장해야 합니다. 절차형 서비스 구조는 빠른 개발에는 좋지만, 테스트와 확장에 큰 제약을 주곤 합니다.
이번 글에서 소개한 Use Case 지향 구조는 그 해답이 될 수 있으며, 특히 인증, 결제, 주문 처리 같은 도메인 중심 로직에서 큰 장점을 발휘합니다.
반응형