"The database is a detail. The web is a detail. The frameworks are details."
— Robert C. Martin (Uncle Bob)
클린 아키텍처(Clean Architecture)는 시스템의 핵심 규칙과 비즈니스 로직을 외부로부터 분리하고 보호하는 것에 집중하는 아키텍처 설계 패턴이다.
클린 아키텍처는 다음과 같은 문제를 해결하는 데 초점을 맞춘다.
Uncle Bob이 제시한 이 아키텍처는 의존성 규칙을 중심으로, 책임의 분리(SRP)와 의존성 역전(DIP)을 실천하는 구조다.
클린 아키텍처는 중심을 향하는 동심원 구조로 표현된다. 각 계층은 안쪽 계층에만 의존 가능하고, 밖에서 안으로만 의존성이 흐른다.
"안쪽 계층은 바깥 계층을 모른다."
예:
User
,Order
,Policy
,Money
,Validator
등
public class User {
private final String email;
private final String password;
public User(String email, String rawPassword) {
this.email = email;
this.password = hash(rawPassword); // 도메인 레벨에서 비밀번호 해싱
}
public boolean isValidPassword(String rawPassword) {
return this.password.equals(hash(rawPassword));
}
private String hash(String rawPassword) {
// 해싱 로직은 단순화
return "hashed:" + rawPassword;
}
}
예: 회원 가입, 주문 생성, 결제 처리 등
public class SignUpUseCase {
private final UserRepository userRepository;
public SignUpUseCase(UserRepository userRepository) {
this.userRepository = userRepository;
}
public void execute(SignUpCommand command) {
if (userRepository.existsByEmail(command.email())) {
throw new DuplicateEmailException();
}
User user = new User(command.email(), command.password());
userRepository.save(user);
}
}
예:
UserController
,UserRequestDto
,JpaUserRepository
@RestController
public class UserController {
private final SignUpUseCase signUpUseCase;
public UserController(SignUpUseCase signUpUseCase) {
this.signUpUseCase = signUpUseCase;
}
@PostMapping("/signup")
public ResponseEntity<Void> signup(@RequestBody SignUpRequest request) {
SignUpCommand command = new SignUpCommand(request.email(), request.password());
signUpUseCase.execute(command);
return ResponseEntity.ok().build();
}
}
예:
Spring
,MySQL
,Redis
,Kafka
,SMTP
,React
등
// 실제 DB와 매핑되는 구조. 도메인 객체인 User와는 구분됨.
public interface SpringDataUserRepository extends JpaRepository<UserEntity, Long> {
boolean existsByEmail(String email);
}
이 계층에선 어떤 프레임워크를 쓰든 상관없다. 클린 아키텍처에서는 이런 기술들이 ‘디테일’이기 때문이다.
이 모든 계층 구조의 핵심을 관통하는 원칙은 바로 의존성 역전 원칙(DIP)이다.
유스케이스는 오직 Repository 인터페이스(추상화)에만 의존하며, 실제 구현은 인터페이스 어댑터 계층에서 주입된다.
이로 인해 유스케이스는 Spring이나 DB와 같은 인프라 기술의 변화에 영향을 받지 않는다.
// UseCase 계층 - 추상화만 의존
public interface UserRepository {
void save(User user);
boolean existsByEmail(String email);
}
// Interface Adapter 계층 - 세부 구현
@Repository
public class JpaUserRepository implements UserRepository {
private final SpringDataUserRepository jpaRepo;
public JpaUserRepository(SpringDataUserRepository jpaRepo) {
this.jpaRepo = jpaRepo;
}
public void save(User user) {
jpaRepo.save(UserEntity.from(user));
}
public boolean existsByEmail(String email) {
return jpaRepo.existsByEmail(email);
}
}
어떤 프레임워크도 내부 로직을 지배하지 않는다.
웹이든 앱이든 CLI든 상관없다. UI는 교체 가능한 껍데기일 뿐이다.
RDB, NoSQL, 파일 시스템, 메시징 시스템과 독립적인 도메인 로직.
외부 환경이 없어도, 순수 자바 코드로 유닛 테스트 가능.
클린 아키텍처는 단지 "겹겹이 나눈 계층"이 아니다.
비즈니스 로직을 보호하고, 기술 변화에 유연하게 대처할 수 있도록 설계하는 사고방식이다.
The Clean Code Blog - The Clean Architecture
by Robert C. Martin (Uncle Bob)
내용의 질이 논문급입니다. 대학원 가시는 걸 적극 추천드립니다.