Layered Architecture
계층형 아키텍처 (Layered Architecture) 는 각 역할, 책임, 처리 흐름을 명확히 구분하여 복잡한 소프트웨어 시스템을 논리적 계층으로 나누는 구조적 설계 패턴이다.
전통적인 계층 구조는 단방향 흐름과 명확한 책임 분리를 중심으로 단순성과 빠른 개발 속도를 중시했던 아키텍처다. 하지만 시간이 지나며 계층 간 강한 결합, 테스트의 어려움, 확장성 제약 등의 한계가 드러났고, 특히 계층 간 통신 비용 증가, 변경 시 여러 계층에 걸친 리팩토링 요구, 단단한 결합, 병렬 처리의 어려움 등도 함께 고려해야 할 구조적 문제로 지적되고 있다.
이러한 한계를 보완하기 위해 등장한 현대적 계층 구조는 동적 재구성, 레이어 간 의존성 최소화, 서비스 기반 접근법, DevOps 자동화 등 최신 개발 환경과 요구의 변화에 유연하게 적응하며, 유지보수성과 확장성을 극대화한다. 특히 도메인 중심의 Hexagonal Architecture, Onion Architecture, Clean Architecture 와 같은 계층화 모델은 테스트 용이성과 변경 유연성을 확보하는 데 효과적이며, 마이크로서비스, DDD, CI/CD 기반 DevOps 환경과도 높은 시너지를 발휘한다.
등장 배경 및 발전 과정
메인프레임 시대와 계층형 사고의 출현 (1950 년대~1970 년대)
초기 컴퓨팅 환경의 특징
- 메인프레임 중심의 컴퓨팅:
- IBM System/360 (1964): 최초의 계층적 시스템 구조 도입
- 배치 처리 시스템: 펀치카드와 자기 테이프를 통한 입력 처리
- 중앙집중식 아키텍처: 모든 처리가 메인프레임에서 수행
- 터미널 시스템: 더미 터미널을 통한 사용자 인터페이스
메인프레임 중심의 컴퓨팅 (Mainframe-Centric Computing)
메인프레임 (Mainframe) 은 대량의 데이터 처리, 고가용성, 보안성, 안정성이 요구되는 기업 또는 기관의 핵심 업무 시스템을 수행하기 위해 설계된 대형 중앙 집중식 컴퓨터 시스템이다. 메인프레임 중심 컴퓨팅은 이러한 메인프레임을 업무 처리의 핵심 허브로 삼아 모든 처리를 중앙화된 방식으로 수행하는 컴퓨팅 모델이다.
1950 년대 초기 컴퓨터 시스템의 문제점:
- 하드웨어 직접 제어: 프로그래머가 CPU 명령어, 메모리 주소, I/O 포트를 직접 다뤄야 함
- 중복 개발: 모든 프로그램이 기본적인 I/O 처리를 반복 구현
- 하드웨어 의존성: 다른 컴퓨터로 프로그램 이식 시 전체 재작성 필요
- 복잡성 폭발: 시스템이 커질수록 관리 불가능한 복잡도
계층화를 통한 문제 해결:
|
|
- 추상화 (Abstraction):
계층화 도입 이전에는 프로그래머가 직접 하드웨어 명령어를 작성지만 이후에는 프로그래머는 운영체제 함수만 작성하면 운영체제가 하드웨어 명령어로 반환이 가능하다. - 관심사의 분리 (Separation of Concerns):
각 계층마다 전문적으로 담당하는 영역이 다르다. - 재사용성과 이식성:
하드웨어 변경시, 계층화 이전에는 전체를 재작성해야 했지만 이후에는 하드웨어 추상화 계층만 수정하면 된다.
현대 소프트웨어 계층화의 뿌리: 메인프레임 계층화 → 현대 아키텍처 연결
graph TD subgraph "1960s 메인프레임" A1[User Terminal] A2[Job Control] A3[Application] A4[Operating System] A5[Hardware] end subgraph "현대 웹 애플리케이션" B1[Web Browser] B2[Web Server] B3[Application Server] B4[Database Server] B5[Infrastructure] end A1 -.->|진화| B1 A2 -.->|진화| B2 A3 -.->|진화| B3 A4 -.->|진화| B4 A5 -.->|진화| B5
전통적 Layered Architecture 의 확립 (1980 년대~1990 년대)
클라이언트 - 서버 모델의 등장
2-Tier Architecture (1980 년대):
- PC 의 보급: 개인용 컴퓨터가 더미 터미널을 대체
- 분산 처리: 처리 능력이 클라이언트와 서버로 분산
- 네트워크 컴퓨팅: LAN 기술의 발전으로 가능해진 분산 아키텍처
기술적 동력:
- 관계형 데이터베이스: Oracle, IBM DB2, SQL Server 등의 상용화
- 네트워킹 기술: TCP/IP 프로토콜 표준화
- GUI 기술: Windows, Mac OS 의 그래픽 사용자 인터페이스
3-Tier Architecture 의 표준화 (1990 년대)
웹 기술의 영향:
- World Wide Web (1991): 브라우저 - 웹서버 - 데이터베이스 구조
- HTTP 프로토콜: 무상태 (stateless) 통신 방식
- 인터넷 확산: 전 세계적인 네트워크 연결
표준 3 계층 구조:
graph TD A[Presentation Layer<br/>웹 브라우저, GUI] --> B[Application Layer<br/>웹 서버, 비즈니스 로직] B --> C[Data Layer<br/>데이터베이스, 파일 시스템] A1[사용자 인터페이스] --> B1[비즈니스 규칙 처리] B1 --> C1[데이터 저장 및 관리]
엔터프라이즈 표준의 확립 (2000 년대 초기)
J2EE (Java 2 Platform, Enterprise Edition) 영향:
- 컨테이너 기반: EJB 컨테이너, 서블릿 컨테이너
- 선언적 서비스: 트랜잭션, 보안, 네이밍 서비스
- 표준화: 업계 전반의 아키텍처 표준 확립
.NET Framework 의 기여:
- ASP.NET: 웹 애플리케이션 개발 표준화
- Windows DNA: Distributed interNet Architecture
- COM+: 컴포넌트 기반 서비스
전통적 아키텍처의 한계와 문제점 인식 (2000 년대)
구조적 문제점들
긴밀한 결합 (Tight Coupling):
|
|
주요 문제점들:
- 의존성 전파: 하위 계층 변경이 상위 계층에 영향
Database Schema 변경 → DAO 변경 → Service 변경 → Controller 변경 → View 변경
- 테스트 어려움: 데이터베이스 없이 비즈니스 로직 테스트 불가
- 테스트를 위해 실제 DB 설정, 테스트 데이터 준비 등이 필요
- 기술 종속성: 특정 데이터베이스나 프레임워크에 강하게 결합
- 단일 책임 원칙 위반: 계층이 여러 책임을 동시에 가짐
비즈니스 요구사항의 변화
복잡성 증가:
- 비즈니스 로직 복잡화: 단순 CRUD 를 넘어선 복잡한 업무 규칙
- 다중 채널: 웹, 모바일, API 등 다양한 인터페이스 요구
- 실시간 처리: 배치 처리에서 실시간 처리로 변화
- 확장성 요구: 대용량 트래픽과 데이터 처리 필요
Domain-Driven Design 의 등장과 패러다임 전환 (2003 년)
Eric Evans 의 혁신적 접근
DDD 의 핵심 통찰:
" 소프트웨어의 핵심은 사용자를 위해 도메인 관련 문제를 해결하는 능력이다. 다른 모든 것들은 부차적이다."
전통적 접근법과의 차이점:
구분 | 전통적 접근법 | DDD 접근법 |
---|---|---|
중심 요소 | 데이터베이스, 기술 스택 | 비즈니스 도메인 |
설계 출발점 | 데이터 모델링 | 도메인 모델링 |
의존성 방향 | 상위 → 하위 (UI → DB) | 외부 → 내부 (Infrastructure → Domain) |
언어 | 기술적 용어 중심 | 유비쿼터스 언어 |
4 계층 구조 제안:
graph TD subgraph "DDD 4-Layer Architecture" A[User Interface Layer<br/>사용자 인터페이스] B[Application Layer<br/>애플리케이션 서비스] C[Domain Layer<br/>도메인 모델, 비즈니스 로직] D[Infrastructure Layer<br/>데이터 접근, 외부 서비스] end A --> B B --> C D -.-> C D -.-> B D -.-> A
- 기본 원칙:
- 의존성 규칙: 각 계층은 자신보다 아래 계층에만 의존 가능
- Domain Layer 격리: 도메인 계층은 완전히 격리되어 다른 계층에 의존하지 않음
- Infrastructure Layer 특성: 모든 계층을 지원하되, 계층들이 Infrastructure 에 의존하지 않도록 역전된 의존성 적용
- 핵심 설계 원칙
- Domain Layer 보호: 가장 중요한 비즈니스 로직이 외부 변화로부터 격리
- 의존성 역전: Infrastructure 가 Domain 인터페이스를 구현
- 관심사의 분리: 각 계층이 명확한 단일 책임을 가짐
- 테스트 용이성: 각 계층을 독립적으로 테스트 가능
이 4 계층 구조는 전통적인 3 계층 구조의 문제점 (비즈니스 로직 분산, 기술 종속성) 을 해결하고, 도메인 중심의 설계를 가능하게 하는 Eric Evans 의 핵심 기여이다.
계층 간 통신 흐름:
sequenceDiagram participant UI as User Interface participant App as Application participant Dom as Domain participant Infra as Infrastructure UI->>App: createOrder(request) App->>Dom: Customer.placeOrder(items) Dom->>Dom: validate business rules Dom-->>App: return Order App->>Infra: orderRepository.save(order) Infra-->>App: success App->>Infra: eventPublisher.publish(event) App-->>UI: return OrderDto
계층별 정리:
계층 | 핵심 기능 | 주요 역할 | 포함 요소 | 의존성 | 주의사항 |
---|---|---|---|---|---|
User Interface Layer (사용자 인터페이스 계층) | • 사용자와의 상호작용 • 데이터 표시 및 명령 수집 • 사용자 명령 해석 | • 사용자 요청 수신 • 응답 데이터 렌더링 • 입력 검증 (기본적) • 세션 관리 | • 웹 컨트롤러 • REST API 엔드포인트 • 웹 페이지/템플릿 • 모바일 앱 화면 • CLI 인터페이스 | Application Layer 에만 의존 | • 비즈니스 로직 포함 금지 • 도메인 객체 직접 노출 금지 • DTO 사용 권장 |
Application Layer (애플리케이션 계층) | • 유스케이스 조정 • 워크플로우 관리 • 도메인 객체 협업 조정 | • 비즈니스 프로세스 오케스트레이션 • 트랜잭션 관리 • 보안 및 권한 검사 • 외부 시스템 통합 | • Application Service • Use Case 구현체 • Command/Query Handler • DTO (Data Transfer Object) • Application Event | Domain Layer 에만 의존 | • 비즈니스 규칙 구현 금지 • 얇은 (Thin) 계층 유지 • 상태를 가지지 않음 (Stateless) |
Domain Layer (도메인 계층) | • 핵심 비즈니스 로직 • 도메인 규칙 구현 • 비즈니스 상태 관리 | • 비즈니스 규칙 강제 • 도메인 지식 캡슐화 • 불변성 보장 • 비즈니스 이벤트 발생 | • Entity (엔티티) • Value Object (값 객체) • Aggregate (애그리게이트) • Domain Service • Domain Event • Repository Interface | 다른 계층에 의존하지 않음 (완전 격리) | • 기술적 세부사항 배제 • 퍼시스턴스 무시 (Persistence Ignorance) • 인프라 무시 (Infrastructure Ignorance) • 유비쿼터스 언어 사용 |
Infrastructure Layer (인프라스트럭처 계층) | • 기술적 서비스 제공 • 외부 시스템 연동 • 데이터 영속성 | • 데이터베이스 접근 • 외부 API 통신 • 메시징/이벤트 처리 • 파일 시스템 접근 • 네트워크 통신 | • Repository 구현체 • Database 설정 • External API Client • Message Queue • Email Service • Cache Provider | 모든 계층을 지원하되 역전된 의존성 적용 | • 도메인 인터페이스 구현 • 구체적 기술에 특화 • 교체 가능하도록 설계 |
아키텍처 철학의 변화
도메인 중심 사고:
- 비즈니스 가치 우선: 기술보다 비즈니스 문제 해결에 집중
- 도메인 전문가와의 협업: 개발자와 비즈니스 사이의 언어 통일
- 점진적 학습: 도메인 이해가 깊어질수록 모델을 개선
Service-Oriented Architecture (SOA) 의 발전 (2000 년대 중반)
SOA 의 등장 배경
기업 환경의 변화:
- 레거시 시스템 통합: 다양한 시스템들의 상호 운용성 필요
- 비즈니스 민첩성: 빠른 비즈니스 변화에 대응
- 재사용성 요구: 중복 개발 비용 절감
SOA 핵심 원칙:
서비스 지향 (Service Orientation)
느슨한 결합 (Loose Coupling)
서비스 재사용성 (Service Reusability)
서비스 계약 (Service Contract)
서비스 추상화 (Service Abstraction)
서비스 조합성 (Service Composability)
ESB (Enterprise Service Bus) 의 도입
중앙집중식 통합:
- 메시지 라우팅: 서비스 간 통신 중재
- 프로토콜 변환: 다양한 통신 프로토콜 지원
- 서비스 검색: 동적 서비스 발견 및 바인딩
SOA 의 한계:
- 복잡성 증가: ESB 가 단일 장애점 (SPOF) 이 됨
- 성능 오버헤드: 중앙 집중식 처리로 인한 지연
- 거버넌스 어려움: 서비스 간 의존성 관리 복잡
현대적 아키텍처 패턴의 등장 (2005 년~2012 년)
Hexagonal Architecture (2005 년)
Alistair Cockburn 의 혁신:
- 포트와 어댑터: 외부 세계와의 인터페이스 추상화
- 비즈니스 로직 격리: 핵심 로직을 외부 의존성으로부터 분리
- 테스트 용이성: 실제 외부 시스템 없이도 테스트 가능
graph TD subgraph "Hexagonal Architecture" subgraph "Adapters (외부)" A1[Web UI] A2[REST API] A3[Database] A4[Message Queue] end subgraph "Ports (인터페이스)" P1[Inbound Ports] P2[Outbound Ports] end subgraph "Core (비즈니스 로직)" C[Domain Logic] end A1 --> P1 A2 --> P1 P1 --> C C --> P2 P2 --> A3 P2 --> A4 end
Onion Architecture (2008 년)
Jeffrey Palermo 의 기여:
- 동심원 구조: 의존성이 중심을 향해 흐름
- 계층의 명확한 정의: 각 계층의 역할과 책임 명시
- 인프라스트럭처 분리: 외부 관심사를 가장 바깥 계층으로
Clean Architecture (2012 년)
Robert Martin (Uncle Bob) 의 통합:
- 의존성 규칙: " 소스 코드 의존성은 안쪽으로만 향해야 한다 "
- 경계의 명확화: 각 계층 간 명확한 경계와 계약 정의
- 플러그인 아키텍처: 프레임워크와 도구를 플러그인으로 취급
graph TD subgraph "Clean Architecture" subgraph "Frameworks & Drivers" F1[Web Frameworks] F2[Database] F3[External Interfaces] end subgraph "Interface Adapters" I1[Controllers] I2[Gateways] I3[Presenters] end subgraph "Application Business Rules" A[Use Cases] end subgraph "Enterprise Business Rules" E[Entities] end F1 --> I1 F2 --> I2 F3 --> I3 I1 --> A I2 --> A I3 --> A A --> E end
마이크로서비스 아키텍처의 부상 (2010 년대)
클라우드 컴퓨팅의 영향
기술적 기반:
- 컨테이너화: Docker (2013), Kubernetes (2014)
- 클라우드 플랫폼: AWS, Azure, Google Cloud Platform
- DevOps 문화: 지속적 통합/배포 (CI/CD)
MSA 의 핵심 특징
항목 | 세부 특징 | 주요 목적 |
---|---|---|
서비스 자율성 (Service Autonomy) | - 독립적 개발, 배포, 확장 - 팀 단위 책임 분리 | 민첩성, 빠른 릴리스, 팀 생산성 |
비즈니스 중심 분해 (Business Capability Focused) | - 도메인 중심 설계 - 바운디드 컨텍스트 기반 경계 설정 - 단일 책임 원칙 적용 | 도메인 응집성, 유지보수성, 변화 대응력 |
분산 데이터 관리 (Decentralized Data Management) | - 서비스별 DB 소유 - 폴리글랏 퍼시스턴스 - 이벤트 기반 최종 일관성 | 독립성, 확장성, 데이터 주권 |
기술 다양성 (Technology Diversity) | - 언어 및 프레임워크 자유 - 각 서비스별 최적화된 스택 선택 가능 | 기술 진화 대응, 유연한 선택 |
느슨한 결합 (Loose Coupling) | - API 기반 통신 - 구현 은닉 - 서비스 간 의존성 최소화 | 변경 유연성, 인터페이스 안정성 |
장애 격리 및 복원력 (Fault Isolation & Resilience) | - 장애 격리, 회로 차단기, 중복 인스턴스 - 빠른 장애 복구 | 시스템 전체 안정성, 가용성 유지 |
API 우선 설계 (API-First Design) | - 계약 기반 설계 - REST/gRPC 등 표준 프로토콜 - API 게이트웨이 연동 | 개발 병렬화, 외부 연동 일관성 |
클라우드 네이티브 특성 (Cloud-Native) | - 컨테이너화, 오케스트레이션, 무상태성, 임시성 | 유연한 배포, 수평 확장성, 클라우드 최적화 |
분산 거버넌스 (Decentralized Governance) | - 팀 주도 기술 선택 - 최소한의 중앙 표준 - DevOps 문화 및 CI/CD | 유연한 조직 운영, 책임 중심 운영 |
관찰가능성 (Observability) | - 분산 추적, 중앙 로그, 메트릭 수집, 헬스체크 | 문제 탐지, 성능 분석, 운영 효율성 |
SOA 와의 차이점:
특성 | SOA | MSA |
---|---|---|
서비스 크기 | 큰 비즈니스 기능 단위 | 작은 단일 책임 단위 |
데이터 관리 | 공유 데이터베이스 | 서비스별 독립 데이터베이스 |
통신 방식 | ESB, SOAP | HTTP/REST, 메시징 |
배포 방식 | 모노리틱 배포 | 독립적 배포 |
거버넌스 | 중앙집중식 | 분산형 |
현대적 계층형 아키텍처의 특징 (2010 년대~현재)
의존성 역전 원칙 (DIP) 적용
전통적 방식 vs 현대적 방식:
|
|
이벤트 기반 아키텍처 통합
비동기 처리와 이벤트 소싱:
|
|
클라우드 네이티브 특성
현대적 배포와 확장:
- 컨테이너 오케스트레이션: Kubernetes 기반 자동 확장
- 서비스 메시: Istio, Linkerd 를 통한 서비스 간 통신 관리
- 관찰가능성: 분산 추적, 메트릭, 로깅 통합
- 복원력: Circuit Breaker, Bulkhead 패턴 적용
진화의 핵심 동력과 개선사항
기술적 동력
1960 년대 → 현재까지의 기술 발전:
주요 개선사항
전통적 → 현대적 전환:
측면 | 전통적 접근법 | 현대적 접근법 | 개선 효과 |
---|---|---|---|
중심 요소 | 데이터베이스 중심 | 도메인 중심 | 비즈니스 가치 극대화 |
의존성 | 하향식 (UI→DB) | 상향식 (외부→도메인) | 변경 영향도 최소화 |
결합도 | 긴밀한 결합 | 느슨한 결합 | 유연성 및 테스트 용이성 |
배포 | 모노리틱 배포 | 독립적 배포 | 빠른 출시 및 확장성 |
테스트 | 통합 테스트 의존 | 단위 테스트 중심 | 개발 생산성 향상 |
확장 | 수직적 확장 | 수평적 확장 | 비용 효율성 |
미래 방향성
2020 년대 이후 트렌드:
서버리스 아키텍처 (Serverless Architecture) 와 계층 구조의 융합
서버리스 컴퓨팅 (Serverless Computing) 기반 환경에서는 계층형 아키텍처의 일부 계층 (예: 프리젠테이션, 비즈니스 로직 계층 등) 을 함수 (Function) 단위로 더 유연하게 분리하고 확장함. 이로 인해 기존 계층 구조의 고정성과 배포 단위를 혁신적으로 변화시키며, 이벤트 중심 처리도 손쉽게 확장할 수 있게 됨.멀티테넌트 (Multi-Tenant) 환경에 최적화된 계층 설계
SaaS(Software as a Service) 서비스 등 멀티테넌시 (Multi-Tenancy) 가 필요한 시스템에서는 데이터 액세스 계층과 도메인 계층에 개별 테넌트 분리를 적용하여 확장성과 보안성을 강화함.AI 자동화 연계
계층별 AI 모듈 (예: 추천, 예측, 자동 모니터링) 이 삽입됨으로써, 특정 계층 (예: 서비스 계층, 프리젠테이션 계층) 에서 사용자의 행동 또는 상황에 적응하는 자동화 로직을 손쉽게 통합할 수 있음.유연한 계층 전환 및 조합:
시스템 요구에 따라 레이어의 수와 역할을 동적으로 조절할 수 있는 패턴과 도구의 필요성이 높아짐.자동화 도구 및 오케스트레이션 연계:
IaC(Infrastructure as Code, 코드 기반 인프라 자동화), 컨테이너 오케스트레이션 (Kubernetes 등) 과의 통합이 필수화.신속한 장애 복구 및 모니터링:
계층별 장애 감지 및 롤백 전략 (A/B 테스트, Canary 배포 등) 정교화 필요.
계층형 아키텍처와 다른 아키텍처 패턴과의 비교
구분 | 계층형 (Layered) | 육각형 (Hexagonal) | 클린 (Clean) | 마이크로서비스 (Microservices) |
---|---|---|---|---|
구조 | 수직/단방향 | 입·출력 포트 중심 | 도메인 중심, 경계 명확 | 서비스 단위 독립 |
확장성 | 제한적 | 유연 | 매우 유연 | 극대화 가능 |
적용 | 단순, 전통적 시스템 | 복잡 도메인 | 대형 시스템, 변화 많은 환경 | 클라우드/대규모 시스템 |
대표 기술 | 웹서비스 초기, 비즈니스 App | 복잡 API/이벤트 기반 | 모노리스 - 마이크로서비스 중간 | SaaS, API 게이트웨이 등 |
목적 및 필요성
Layered Architecture 는 시스템의 복잡성을 기능적 책임에 따라 계층화함으로써 구조적 명확성과 유지보수성을 극대화하는 설계 전략이다. 하지만 시대적 변화와 비즈니스 요구사항의 진화에 따라 그 목적과 접근법이 크게 달라졌다.
공통 목적
목적 | 설명 |
---|---|
복잡성 분해 | 전체 시스템을 계층 또는 모듈 단위로 나누어 구조화된 설계 가능 |
책임 분리 | 역할에 따라 컴포넌트 구분 → 관심사 분리 (SoC, Separation of Concerns) |
유지보수성 향상 | 기능별 변경이 국지적으로 이루어질 수 있도록 구성 |
재사용성 확보 | 특정 계층 (서비스, DAO 등) 은 다른 계층에서 반복적으로 활용 가능 |
개발 효율화 | 역할별 팀 분산 개발, 인터페이스 기반 협업 촉진 |
전통적 접근법의 목적 및 필요성 (1990 년대~2000 년대)
등장 배경:
- 클라이언트 - 서버 시대의 도래: 단일 머신 환경에서 분산 환경으로의 전환
- 데이터베이스 중심 사고: RDBMS 의 보편화로 데이터 저장소가 시스템의 핵심으로 인식
- 기술적 복잡성 관리: UI, 비즈니스 로직, 데이터 접근의 기술적 분리 필요성
핵심 목적:
목적 구분 | 설명 | 해결하는 문제 |
---|---|---|
기술적 관심사 분리 | 프레젠테이션, 비즈니스, 데이터 계층을 기술적 역할에 따라 분리 | UI 코드와 비즈니스 로직, 데이터 접근 코드의 혼재 |
재사용성 확보 | 하위 계층 (특히 데이터 계층) 을 여러 상위 계층에서 공유 | 중복 코드 작성과 데이터 접근 로직의 분산 |
유지보수성 향상 | 계층별 독립적 수정을 통한 영향 범위 최소화 | 시스템 전체에 파급되는 변경 사항 |
팀별 전문화 | 프론트엔드, 백엔드, DBA 팀의 역할 분담 | 개발자들 간의 책임 영역 불분명 |
필요성의 한계:
- 데이터베이스 의존성: 비즈니스 로직이 데이터베이스 스키마에 강하게 결합
- 하향식 의존성: 상위 계층 변경이 하위 계층까지 영향을 미치는 구조적 문제
- 모놀리식 제약: 전체 시스템이 하나의 배포 단위로 관리되어 확장성 제한
현대적 접근법의 목적 및 필요성 (2000 년대~현재)
등장 배경:
- 비즈니스 복잡성 증가: 단순한 CRUD 를 넘어선 복잡한 도메인 로직 처리 필요
- 클라우드 네이티브 환경: 마이크로서비스, 컨테이너화, DevOps 문화의 확산
- 지속적 변화 대응: 애자일 개발과 빠른 비즈니스 요구사항 변화에 대한 대응
- 테스트 중심 개발: TDD, BDD 등 테스트 가능한 아키텍처의 중요성 증대
핵심 목적:
목적 구분 | 설명 | 해결하는 문제 |
---|---|---|
비즈니스 중심 설계 | 도메인 로직을 아키텍처의 중심에 배치하여 비즈니스 가치 극대화 | 기술적 세부사항에 매몰된 비즈니스 로직 |
의존성 역전 | 외부 시스템 (DB, UI) 이 도메인에 의존하도록 하여 비즈니스 로직의 독립성 확보 | 인프라스트럭처 변경이 비즈니스 로직에 미치는 영향 |
테스트 용이성 | 도메인 로직을 외부 의존성 없이 독립적으로 테스트 가능 | 통합 테스트에만 의존하는 테스트 전략의 한계 |
진화 가능성 | 변화하는 비즈니스 요구사항에 대한 빠르고 안전한 대응 | 레거시 시스템의 경직성과 변경 비용 |
기술적 독립성 | 특정 프레임워크나 데이터베이스에 종속되지 않는 설계 | 기술 스택 변경 시 전체 시스템 재작성 필요 |
현대적 필요성:
필요성 구분 | 설명 | 현대적 해결 방안 |
---|---|---|
클라우드 네이티브 대응 | 마이크로서비스, 컨테이너, 서버리스 환경에서의 유연한 배포 | 도메인별 독립적 서비스 분리 및 API 기반 통신 |
DevOps 통합 | CI/CD 파이프라인과 자동화된 테스트 체계 지원 | 계층별 독립적 테스트와 배포 가능한 구조 |
비즈니스 민첩성 | 빠른 요구사항 변화와 실험적 기능 개발 지원 | 도메인 중심의 모듈화로 기능별 독립적 개발 |
데이터 다양성 | NoSQL, Event Store, 분산 데이터베이스 등 다양한 저장소 활용 | Repository 패턴과 의존성 주입을 통한 저장소 추상화 |
접근법별 비교 분석
비교 항목 | 전통적 접근법 | 현대적 접근법 |
---|---|---|
중심 개념 | 데이터베이스 (Database-Centric) | 도메인 모델 (Domain-Centric) |
의존성 방향 | UI → Business → Data → Database | Infrastructure → Application → Domain |
변경 전파 | 하위 변경이 상위에 영향 | 외부 변경이 도메인에 영향 없음 |
테스트 전략 | 통합 테스트 중심 | 단위 테스트 중심 |
배포 단위 | 모놀리식 전체 배포 | 도메인/기능별 독립 배포 |
전통적 Layered Architecture 는 기술적 복잡성 관리에 중점을 둔 반면, 현대적 접근법은 비즈니스 복잡성 관리와 변화 대응성에 중점을 둔다. 이러한 패러다임 전환은 단순한 기술적 진화가 아니라, 소프트웨어가 비즈니스에 미치는 영향력 증대와 시장 환경의 급격한 변화에 대한 필연적 대응이라 할 수 있다.
따라서 현대의 소프트웨어 아키텍트는 프로젝트의 복잡성, 변화 빈도, 팀 구성, 비즈니스 목표 등을 종합적으로 고려하여 적절한 접근법을 선택해야 한다.
핵심 개념
Layered Architecture 는 관심사의 분리 (Separation of Concerns) 를 통해 시스템 복잡성을 체계적으로 관리하는 아키텍처 설계 철학이다. 시대적 변화에 따라 기술 중심에서 도메인 중심으로 패러다임이 전환되면서 핵심 개념과 실무 적용 방식이 크게 진화했다.
계층 (Layer) vs. 티어 (Tier) 개념 정립
구분 | Layer (논리적 계층) | Tier (물리적 티어) |
---|---|---|
정의 | 코드 구조와 기능 단위의 논리적 분리 | 서버나 인프라 단위의 물리적 분리 |
목적 | 관심사 분리 (Separation of Concerns), 유지보수성 | 스케일링, 배포 최적화, 성능 향상 |
예시 | Presentation, Application, Domain, Infrastructure | Web Server, App Server, Database Server |
의존성 | 논리적 인터페이스 기반 의존 | 네트워크 통신 기반 의존 |
변경 영향 | 컴파일 타임 의존성 관리 | 런타임 배포 및 네트워크 고려 |
핵심: Layer 는 설계상의 추상화, Tier 는 배포 관점의 구분이며, 하나의 Tier 에 여러 Layer 가 존재할 수 있다.
전통적 Layered Architecture 핵심 개념
항목 | 설명 |
---|---|
정의 | 기술적 관심사를 기준으로 시스템을 수평 계층으로 분리하여 구성하는 아키텍처 스타일 |
핵심 원칙 | 각 계층은 기술적 책임만 수행하고, 상위 계층이 하위 계층에만 의존하며, 계층 간 의존은 하향식 |
계층 간 통신 규칙 | 인접 계층만 접근 허용 (strict layering ), 경우에 따라 생략 가능 (relaxed layering ) |
전통적 핵심 개념 상세
계층별 기술적 책임 분리 (Technical Layer Responsibility)
1 2 3 4 5 6 7 8 9
┌─────────────────────────────────────┐ │ Presentation Layer (UI/Controller) │ ← 사용자 인터페이스 처리 ├─────────────────────────────────────┤ │ Business Layer (Service/Logic) │ ← 비즈니스 규칙 실행 ├─────────────────────────────────────┤ │ Persistence Layer (Repository/DAO) │ ← 데이터 접근 추상화 ├─────────────────────────────────────┤ │ Database Layer (RDBMS) │ ← 데이터 저장소 └─────────────────────────────────────┘
폐쇄 계층 (Closed Layer) 원칙
- 요청은 모든 계층을 순차적으로 통과해야 함
- 계층 건너뛰기 방지로 격리성 보장
- 각 계층의 책임 명확화
개방 계층 (Open Layer) 예외
- 아키텍처 싱크홀 안티패턴 방지
- 부가가치 없는 계층 우회 허용
- 성능 최적화를 위한 직접 접근
현대적 Domain-Centric Architecture 핵심 개념
항목 | 설명 |
---|---|
정의 | 비즈니스 도메인을 중심으로 시스템을 동심원 구조로 분리하여 구성하는 아키텍처 스타일 |
핵심 원칙 | 도메인이 비즈니스 책임의 중심이며, 외부 계층이 내부 계층에 의존하고, 의존성은 중심 지향 |
계층 간 통신 규칙 | 포트와 어댑터를 통한 인터페이스 기반 통신, 의존성 역전 원칙 적용 |
현대적 핵심 개념 상세
도메인 중심성 (Domain Centricity)
1 2 3 4 5 6 7 8 9 10
┌─────────────────────────────────┐ │ Infrastructure Layer │ │ ┌─────────────────────────┐ │ │ │ Application Layer │ │ │ │ ┌─────────────────┐ │ │ │ │ │ Domain Layer │ │ │ ← 비즈니스 로직 중심 │ │ │ (Entities, VOs) │ │ │ │ │ └─────────────────┘ │ │ │ └─────────────────────────┘ │ └─────────────────────────────────┘
의존성 역전 원칙 (Dependency Inversion Principle)
- 전통적: UI → Business → Data → Database
- 현대적: Infrastructure → Application → Domain (중심)
- 고수준 모듈이 저수준 모듈의 추상화에 의존
포트와 어댑터 (Ports and Adapters)
- 포트 (Ports): 도메인이 정의하는 인터페이스
- 어댑터 (Adapters): 외부 시스템과의 구체적 연결
- 플러그인 가능한 아키텍처 구현
실무 연관성 및 구현 전략
전통적 접근법의 실무 연관성
실무 요소 | 연관성 및 효과 | 한계점 |
---|---|---|
팀 분업 | 계층별 전문팀 구성 (FE/BE/DBA) | 사일로 현상, 소통 비용 증가 |
기술 스택 관리 | 계층별 기술 선택의 명확성 | 기술 종속성, 교체 어려움 |
배포 및 운영 | 모놀리식 단위 배포의 단순성 | 부분 배포 불가, 확장성 제한 |
테스트 전략 | 계층별 단위 테스트 가능 | 통합 테스트 의존성 높음 |
현대적 접근법의 실무 연관성
실무 요소 | 연관성 및 효과 | 장점 |
---|---|---|
도메인 전문성 | 비즈니스 전문가와 개발자 협업 강화 | 요구사항 - 구현 간 일치성 향상 |
마이크로서비스 | 도메인별 독립 서비스 분리 | 개별 확장 및 배포 가능 |
클라우드 네이티브 | 컨테이너화된 도메인 서비스 | 인프라 독립성, 탄력적 확장 |
DevOps 통합 | 도메인별 CI/CD 파이프라인 | 빠른 배포, 롤백 용이성 |
현대적 아키텍처 패턴과의 연관성
Clean Architecture 와의 관계
- 의존성 규칙: 외부 계층이 내부 계층에 의존
- 엔터프라이즈 비즈니스 규칙: 도메인 엔티티
- 애플리케이션 비즈니스 규칙: 유스케이스
Hexagonal Architecture 와의 관계
- 헥사곤 (애플리케이션 코어): 비즈니스 로직
- 포트: 애플리케이션과 외부 세계 간의 인터페이스
- 어댑터: 특정 기술에 대한 구현체
Onion Architecture 와의 관계
- 도메인 모델: 최내부 계층
- 도메인 서비스: 도메인 로직 포함
- 애플리케이션 서비스: 유스케이스 조정
- 인프라스트럭처: 외부 계층
핵심 개념 비교 분석
설계 철학 차이
비교 항목 | 전통적 접근법 | 현대적 접근법 |
---|---|---|
중심 관심사 | 기술적 계층 분리 | 비즈니스 도메인 보호 |
의존성 방향 | 하향식 (UI→DB) | 중심 지향 (Infrastructure→Domain) |
변경 전파 | 하위 변경이 상위 영향 | 외부 변경이 도메인에 무영향 |
테스트 전략 | 통합 테스트 중심 | 도메인 단위 테스트 중심 |
배포 단위 | 모놀리식 전체 | 도메인별 독립 |
실무에서의 혼합 접근법
전통적인 Layered Architecture 의 구조적 명확성과 현대적인 Domain-Centric 아키텍처의 유연성과 테스트 용이성을 결합하여 사용하는 것이 효과적이다.
이 방식은 **도메인 중심 설계 (DDD)**를 중심 코어로 두고, 그 외부는 전통적 계층처럼 Presentation, Persistence, Integration 계층으로 나누는 구조를 취한다. 이로써 비즈니스 로직의 독립성과 재사용성을 확보하면서도, 개발자와 인프라 팀에게 익숙한 레이어드 구조를 유지할 수 있다.
하이브리드 아키텍처 구조
Core (도메인 중심 구조)
Domain Layer
: Entity, Value Object, Domain LogicApplication Layer
: Use Case, Application Service
Infrastructure (전통적 구조 적용)
Presentation Layer
: REST Controller, GraphQL Resolver 등Persistence Layer
: Repository Implementation (e.g., JPA, ORM)Integration Layer
: 외부 API, 메시지 브로커 어댑터
graph TD subgraph "Presentation Layer" UI[REST Controller / UI Adapter] end subgraph "Application Layer (Use Cases)" UC[Use Case / Application Service] end subgraph "Domain Layer" E[Entities] V[Value Objects] S[Domain Services] end subgraph "Infrastructure Layer" DB["Repository Implementation (JPA/ORM)"] EXT[External API Client] MSG[Message Queue Adapter] end UI --> UC UC --> S UC --> E UC --> V S --> E S --> V DB -.-> UC EXT -.-> UC MSG -.-> UC DB -->|implements| RepoPort[Persistence Port] EXT -->|implements| APIClientPort[API Port] MSG -->|implements| MessagePort[Event Port] RepoPort --> UC APIClientPort --> UC MessagePort --> UC
- **Use Case 계층 (Application Layer)**는 도메인 계층을 호출하면서 외부 시스템 (Infra) 을 직접 의존하지 않고 Port 인터페이스를 통해 접근한다.
- Infrastructure Layer는 전통적인 방식대로 기술 기반 컴포넌트를 구현하지만, 인터페이스를 구현하는 방식으로 DIP(Dependency Inversion Principle) 를 만족한다.
- Domain Layer는 외부 의존이 없는 순수한 비즈니스 로직만 포함한다.
주요 기능 및 역할
전통적 vs. 현대적 Layered Architecture 비교
전통적 계층별 기능 및 역할
계층 | 주요 기능 (Functions) | 주요 역할 (Roles) | 핵심 특징 |
---|---|---|---|
Presentation Layer | • UI 렌더링 및 표시 • 사용자 입력 수집 • 요청 라우팅 | • 사용자와 시스템 간 인터페이스 • 비즈니스 계층 접근의 전단 게이트웨이 | 기술 중심적 분리 |
Business Logic Layer | • 비즈니스 규칙 실행 • 데이터 가공 및 검증 • 워크플로우 제어 | • 핵심 업무 로직 구현 • 데이터 계층과 UI 계층 중재 | 절차적 로직 처리 |
Persistence Layer | • ORM/데이터 매핑 • CRUD 연산 수행 • 트랜잭션 관리 | • 데이터 접근 추상화 • 비즈니스 계층과 DB 격리 | 데이터 접근 중심 |
Database Layer | • 데이터 저장 및 검색 • 무결성 제약 관리 • 인덱싱 및 최적화 | • 최종 데이터 저장소 • 영속성 보장 | 물리적 저장소 |
의존성 방향: Presentation → Business → Persistence → Database (하향식)
현대적 계층별 기능 및 역할
계층 | 주요 기능 (Functions) | 주요 역할 (Roles) | 핵심 특징 |
---|---|---|---|
Presentation Layer | • 외부 인터페이스 제공 • 요청/응답 어댑팅 • 인증 및 권한 검사 | • 어댑터 패턴 구현 • 외부 시스템과의 경계 관리 | 인터페이스 어댑터 |
Application Layer | • 유스케이스 조정 • 도메인 객체 협업 관리 • 트랜잭션 경계 설정 | • 오케스트레이터 역할 • 도메인 로직과 외부 세계 연결 | 워크플로우 조정 |
Domain Layer | • 핵심 비즈니스 로직 • 도메인 규칙 구현 • 엔티티 및 값 객체 관리 | • 비즈니스 지식의 중심 • 도메인 전문가 언어 반영 | 도메인 중심성 |
Infrastructure Layer | • 기술적 세부사항 처리 • 외부 시스템 연동 • 프레임워크 통합 | • 기술적 구현 담당 • 도메인을 외부 세계에 연결 | 구현 세부사항 |
의존성 방향: Infrastructure → Application → Domain (중심 지향)
각 계층의 구체적 책임
계층 | 접근 방식 | 주요 책임 (Responsibilities) |
---|---|---|
Presentation Layer (프레젠테이션 계층) | 전통적 | - 사용자 입력 처리 및 검증 - UI 렌더링, 화면 출력 - 세션 및 인증 상태 관리 |
현대적 | - HTTP 요청을 도메인 언어로 변환 - 도메인 객체 → JSON/XML 직렬화 - API 버전 관리 및 콘텐츠 협상 | |
Application Layer (애플리케이션 계층) | 공통 (현대 중심) | - 유스케이스 조정 및 실행 흐름 제어 - 트랜잭션 경계 설정 - 보안/권한 검사 수행 - 도메인 계층 호출 조율 - 이벤트 발행 (도메인 이벤트 → 메시지 브로커 등) |
Domain Layer (도메인 계층) | 공통 (현대 중심) | - 비즈니스 불변식 유지 - 핵심 도메인 규칙 및 정책 구현 - 엔티티 및 값 객체 설계 - 도메인 서비스 구성 및 캡슐화 - 상태 기반 의사결정 로직 처리 |
Infrastructure Layer (인프라스트럭처 계층) | 공통 | - DB, 메시지 브로커, 외부 API 등과 연동 - 기술 세부사항 구현 (JPA, Kafka Adapter 등) - 도메인 Port 구현 - 로그, 캐시, 파일 IO 등 기술 기능 제공 - SMTP, 인증, 외부 시스템 접근 등 기술 통합 |
- Presentation Layer: 시스템 외부와의 접점 → 사용자 및 클라이언트와 통신
- Application Layer: Use Case 중심 흐름 → 도메인 모델을 조정, 외부 인터페이스를 추상화
- Domain Layer: 시스템의 핵심 → 순수한 비즈니스 로직과 규칙을 담당
- Infrastructure Layer: 외부 세계와 통신 → 기술 세부사항 및 외부 리소스 연동
계층 간 상호작용 패턴
전통적 상호작용
sequenceDiagram participant UI as Presentation participant BL as Business Logic participant DAL as Data Access participant DB as Database UI->>BL: processRequest(data) BL->>BL: validateBusinessRules(data) BL->>DAL: saveData(processedData) DAL->>DB: INSERT/UPDATE DB-->>DAL: result DAL-->>BL: savedData BL-->>UI: response
현대적 상호작용
sequenceDiagram participant P as Presentation participant A as Application participant D as Domain participant I as Infrastructure P->>A: executeUseCase(request) A->>D: createDomainObject(data) D->>D: applyBusinessRules() A->>I: persist(domainObject) I->>I: mapToDatabase() I-->>A: persistedObject A-->>P: response
공통 횡단 관심사 (Cross-cutting Concerns)
관심사 | 전통적 접근법 | 현대적 접근법 |
---|---|---|
보안 | 각 계층에서 개별적으로 처리 | 인프라에서 일관된 정책 적용 |
로깅 | 각 계층에 로깅 코드 산재 | AOP 나 데코레이터로 횡단 처리 |
트랜잭션 | 비즈니스 계층에서 관리 | 애플리케이션 계층에서 경계 설정 |
예외 처리 | 각 계층에서 try-catch | 글로벌 핸들러와 도메인 예외 분리 |
캐싱 | 데이터 계층 위주 | 각 계층 특성에 맞는 캐싱 전략 |
특징
Layered Architecture 는 구조적 명확성과 운영 효율성을 동시에 추구하는 아키텍처 패턴이다. 수평적 계층 구분, 단방향 의존성, 관심사의 분리를 통해 유지보수성과 확장성을 확보하고, 각 계층의 역할과 책임을 인터페이스 기반으로 독립화함으로써 테스트 용이성, 배포 독립성, 팀별 협업, 모듈화의 장점을 극대화한다.
구조적 특징
항목 | 전통적 Layered Architecture | 현대적 Domain‑Centric Layered Architecture |
---|---|---|
의존성 방향 | 상위 → 하위 (Top‑Down), 하위 구현 직접 참조 | 외부 → 내부 (Bottom‑Up), 인터페이스 중심 의존성 역전 (DIP) |
계층 구분 기준 | 기술 중심 (UI, Service, Persistence, DB 등) | 도메인 중심 (Use Case, Entity, Port, Adapter 등) |
의존성 규칙 | 인접 계층만 호출 가능 (strict layering ) | 필요 시 인터페이스만 사용 (relaxed layering ) |
계층 응집도 | 기능적 책임 중심, 다소 기술 중심 분할 | 유스케이스/도메인 중심 응집도 높은 설계 |
동작적 특징
항목 | 전통적 Layered Architecture | 현대적 Layered Architecture |
---|---|---|
요청 흐름 (Flow) | Presentation → Business → Persistence 순차 흐름 | Controller → Use Case → Domain → Adapter 구조 |
계층 간 호출 | 계층 간 다이렉트 호출 가능 (가끔 비인접도 허용) | 오직 포트 (인터페이스) 를 통해 호출, 구현 감춤 |
트랜잭션 경계 설정 | Service Layer 중심 트랜잭션 처리 (AOP 방식) | Application / Use Case 레벨에서 명확하게 트랜잭션 경계 처리 |
처리 패턴 | 순차적 처리 (Sequential) | 이벤트 중심, 수평적 확장, 병렬 처리 구조 지원 가능 |
전통적 Layered Architecture
graph TD A[Presentation Layer] --> B[Business Layer] B --> C[Persistence Layer] C --> D[Database Layer] A1[HTTP Request] --> B1[Service Logic] B1 --> C1[Data Access] C1 --> D1[SQL Database]
특징:
- 순차적 처리: 요청이 모든 계층을 순차적으로 통과
- 폐쇄 계층: 각 계층을 반드시 거쳐야 함
- 동기적 통신: 계층 간 동기식 호출 방식
현대적 Layered Architecture
graph TD subgraph "Infrastructure Layer" A[REST Controllers] B[Database Adapters] C[Message Brokers] end subgraph "Application Layer" D[Use Cases] E[Application Services] end subgraph "Domain Layer" F[Domain Entities] G[Domain Services] H[Business Rules] end A --> D B -.-> D C -.-> D D --> F E --> G F --> H
특징:
- 비동기 처리: 계층 간 비동기 통신 지원
- 개방/폐쇄 계층: 상황에 따른 계층 우회 허용
- 이벤트 기반: 도메인 이벤트를 통한 느슨한 결합
설계 및 운영 관점 특징
항목 | 설명 |
---|---|
모듈화 (Modularity) | 계층별 책임 구분으로 코드 응집성 확보 및 재사용성 증대 |
테스트 용이성 | 각 계층 단위의 단위 테스트 및 통합 테스트 수행 가능 |
관심사 분리 (SoC) | UI, 비즈니스, 데이터 접근, 인프라 등을 명확히 분리 → 코드 이해도와 유지보수성 향상 |
유연성과 확장성 | 전통적 구조는 기술 중심 고정적 변화, 현대적 구조는 인터페이스 기반 플러그인 방식의 유연성 확보 |
표준화 및 협업 | 널리 채택된 계층 패턴으로 설계, 문서화, 팀 간 커뮤니케이션 효율 향상 |
안정성/복원력 | 장애 격리 및 영향 통제 가능한 구조적 설계; 인프라 계층 장애 시 도메인 중심 코어 영향 최소화 |
계층별 역할 비교
계층 | 전통적 역할 | 현대적 역할 | 진화 포인트 |
---|---|---|---|
Presentation | UI 렌더링, 입력 처리 | 어댑터 역할, 다중 채널 지원 | RESTful API, GraphQL, WebSocket |
Business | 비즈니스 로직 처리 | 유스케이스 조정, 도메인 오케스트레이션 | Use Case 패턴, CQRS |
Domain | 데이터 모델링 | 핵심 비즈니스 규칙, 도메인 전문가 언어 | DDD Entities, Value Objects |
Infrastructure | 데이터 접근 | 외부 시스템 연동, 기술적 세부사항 | 클라우드 네이티브, 마이크로서비스 |
핵심 원칙
전통적 Layered Architecture 원칙 (1980 년대~2000 년대)
철학적 기반: 기술적 관심사 중심의 분리 (Technology-Centric Separation)
설계 원칙 (Design Principles)
원칙 | 정의 | 특징 | 제약사항 |
---|---|---|---|
Separation of Concerns (SoC) | 기술적 관심사별 계층 분리 | UI, 비즈니스, 데이터 접근 분리 | 기술 중심적 분류 |
Layered Isolation | 계층 간 격리를 통한 독립성 | 각 계층의 독립적 변경 가능 | 완전한 격리는 현실적으로 어려움 |
Dependency Flow | 상위에서 하위로의 일방향 의존성 | UI → Business → Data → Database | 데이터베이스가 최하위 계층 |
구현 원칙 (Implementation Principles)
원칙 | 설명 | 구현 방식 |
---|---|---|
Strict Layering | 바로 인접한 하위 계층에만 접근 | 순차적 호출만 허용 |
Closed Layers | 모든 계층을 순차적으로 통과 | 계층 건너뛰기 금지 |
Top-Down Control | 상위 계층이 하위 계층 제어 | 제어 흐름의 일방향성 |
현대적 Layered Architecture 원칙 (2000 년대~현재)
철학적 기반: 도메인 중심의 분리 (Domain-Centric Separation)
설계 원칙 (Architectural Design Principles)
원칙 | 정의 | 현대적 특징 | SOLID 연관성 |
---|---|---|---|
Domain Independence | 도메인의 기술적 독립성 | 비즈니스 로직이 중심에 위치 | SRP + OCP |
Dependency Inversion (DIP) | 의존성 방향의 역전 | 추상화에 의존, 구체화 금지 | DIP 직접 적용 |
Interface Segregation | 인터페이스 분리 원칙 | 최소한의 인터페이스만 노출 | ISP 적용 |
Single Responsibility | 단일 책임 원칙 | 각 계층의 명확한 책임 | SRP 적용 |
구현 원칙 (Implementation & Operational Principles)
원칙 | 설명 | 구현 방식 | 도구/기법 |
---|---|---|---|
Inversion of Control (IoC) | 제어 역전을 통한 느슨한 결합 | 의존성 주입 컨테이너 활용 | Spring IoC,.NET DI |
Contract-First Design | 인터페이스 우선 설계 | 구현 전 계약 정의 | API-First 개발 |
Abstraction Layers | 추상화 계층을 통한 격리 | 포트와 어댑터 패턴 | Hexagonal Architecture |
Dependency Injection | 런타임 의존성 주입 | 설정 기반 의존성 해결 | DI Frameworks |
패러다임의 전환
graph LR subgraph "전통적 접근법" A[UI Layer] --> B[Business Layer] B --> C[Data Layer] C --> D[Database] end subgraph "현대적 접근법" E[Infrastructure] --> F[Application] F --> G[Domain] H[UI] --> F I[Database] --> F end A -.evolution.-> H B -.evolution.-> F C -.evolution.-> I D -.evolution.-> I
주요 변화점
측면 | 전통적 접근법 | 현대적 접근법 | 변화 동력 |
---|---|---|---|
중심 요소 | 데이터베이스 | 도메인 모델 | DDD 의 영향 |
의존성 방향 | 하향식 (Top-Down) | 중심 지향 (Inside-Out) | 의존성 역전 원칙 |
테스트 용이성 | 통합 테스트 의존적 | 도메인 독립 테스트 | 테스트 주도 개발 |
변경 영향도 | 계층 간 전파 | 도메인 격리 | 애자일 개발 방법론 |
통합된 핵심 원칙
아키텍처 설계 원칙 (Architecture Design Principles)
원칙 | 설명 | 적용 방법 | 검증 기준 |
---|---|---|---|
단일 책임 원칙 (SRP) | 각 계층은 하나의 명확한 책임만 담당 | 변경 이유가 단 하나여야 함 | 응집도 측정 |
개방 - 폐쇄 원칙 (OCP) | 확장에는 열려있고 수정에는 닫혀있음 | 인터페이스 기반 확장 | 새 기능 추가 시 기존 코드 변경 없음 |
의존성 역전 원칙 (DIP) | 상위 모듈이 하위 구현에 의존하지 않음 | 추상화에 의존하는 구조 | 인터페이스 의존성 분석 |
인터페이스 분리 원칙 (ISP) | 필요한 최소한의 인터페이스만 노출 | 역할별 인터페이스 분리 | 인터페이스 응집도 측정 |
관심사의 분리 (SoC) | 서로 다른 책임을 구분하여 설계 | 도메인 개념 기반 분리 | 기능별 모듈화 정도 |
추상화 원칙 (Abstraction) | 구현 세부사항을 숨기고 계약에 집중 | 인터페이스 중심 설계 | 구현체 교체 가능성 |
모듈화 원칙 (Modularity) | 독립된 모듈로 구성, 높은 응집도 | 패키지/네임스페이스 분리 | 결합도/응집도 측정 |
구현 운영 원칙 (Implementation & Operational Principles)
원칙 | 설명 | 구현 기법 | 측정 지표 |
---|---|---|---|
계층 격리 원칙 (Layer Isolation) | 독립적 개발·배포 가능한 구조 | 마이크로서비스, 컨테이너화 | 배포 독립성 |
단방향 의존성 (Unidirectional Dependency) | 의존성 방향의 일관성 유지 | 의존성 주입, 인터페이스 | 순환 의존성 검사 |
느슨한 결합 (Low Coupling) | 계층 간 최소한의 상호작용 | 이벤트 기반 통신 | 결합도 메트릭 |
높은 응집도 (High Cohesion) | 계층 내부의 유기적 동작 | 도메인 모델링 | 응집도 메트릭 |
계층 간 인터페이스 명확화 | 명확한 API/Interface 정의 | OpenAPI, Contract Testing | API 호환성 테스트 |
적응적 계층화 (Adaptive Layering) | 필요에 따른 계층 우회 허용 | Open Layer Pattern | 성능 최적화 측정 |
주요 원리 및 작동 원리
전통적 Layered Architecture 작동 원리
- 와이어링 방식: 프레젠테이션 → 비즈니스 → 데이터 액세스 층 순서로 호출 흐름이 내려감. 단순하지만 BLL 이 DAL 구현에 직접 노출됨
- 표준 구성: UI / BLL (Business Logic Layer) / DAL (Data Access Layer) 구조가 일반적
- 테스트 어려움: BLL 테스트 시 실제 DB 없이는 정상적인 검증 어려움, 테스트 환경 구성 복잡
graph TD A[Presentation Layer] --> B[Business Layer] B --> C[Persistence Layer] C --> D[Database Layer] A1[Controller] --> B1[Service] B1 --> C1[Repository] C1 --> D1[Database]
작동 방식:
- 사용자 요청이 Presentation Layer 로 진입
- Business Layer 에서 비즈니스 로직 처리
- Persistence Layer 를 통한 데이터 접근
- Database Layer 에서 데이터 영속성 처리
현대적 Layered Architecture 작동 원리
- 포트 & 어댑터 (Ports & Adapters) 기반 설계: 애플리케이션이 외부와 소통하는 포트는 인터페이스로 정의되고, 이를 구현하는 어댑터를 통해 실제 시스템에 연결됨
- 의존성 반전 (Dependency Inversion Principle): 핵심 비즈니스 로직 (도메인) 은 외부 구현에 의존하지 않으며, 의존은 내부로 향하는 방향만 허용
- 내부 코어 보호: 핵심 도메인 엔티티와 Use Case 는 인프라와 철저히 분리되어 변동에 무관한 안정성 제공
graph TD subgraph "Infrastructure Layer" A[Web Controllers] E[Database Adapters] F[External APIs] end subgraph "Application Layer" B[Use Cases] G[Application Services] end subgraph "Domain Layer" C[Entities] H[Value Objects] I[Domain Services] end A --> B B --> C B --> H B --> I E -.-> B F -.-> B
작동 방식:
- 외부 요청이 Infrastructure Layer 의 어댑터로 진입
- Application Layer 에서 유스케이스 조정
- Domain Layer 의 비즈니스 객체들이 협업하여 처리
- Infrastructure Layer 를 통한 외부 시스템 연동
구조 및 아키텍처
전통적 구조
구분 | 계층 (Layer) | 주요 구성요소 | 설명 |
---|---|---|---|
필수 | Presentation Layer | - MVC Controller - View Template | 사용자 입력 처리, 화면 렌더링, UI 흐름 제어 |
Business Layer | - Service Class - Business Logic | 도메인 로직 실행, 유스케이스 조정 | |
Data Access Layer | - Repository - DAO (Data Access Object) | DB 접근 추상화, 데이터 조회/저장 처리 | |
Database Layer | - RDBMS (MySQL, PostgreSQL 등) | 실제 데이터 저장소, 영속성 관리 | |
선택적 | Service Layer (중첩적 계층) | - Transaction Manager - Auth Service 등 | 트랜잭션 경계 처리, 보안/권한 처리 |
Integration Layer | - API Client - Messaging Adapter | 외부 API, 메시징 시스템 (Kafka 등) 과의 연동 관리 |
graph TD A["Presentation Layer<br/>(Controller, View)"] --> B["Business Layer<br/>(Service, Logic)"] B --> C["Data Access Layer<br/>(Repository, DAO)"] C --> D["Database Layer<br/>(RDBMS)"] B --> E["Integration Layer<br/>(API, Messaging)"]:::optional classDef optional fill:#fdf6e3,stroke:#ccc,stroke-dasharray: 5 5;
현대적 구조
구분 | 계층 (Layer) | 주요 구성요소 | 설명 |
---|---|---|---|
필수 | Domain Layer | - Entity - Value Object - Domain Service | 핵심 비즈니스 규칙과 모델을 표현하는 계층. 불변성과 도메인 로직을 책임짐 |
Application Layer | - Use Case - Application Service | 도메인 모델을 orchestration 하는 계층. 트랜잭션 경계 설정 및 흐름 조정 | |
Infrastructure Layer | - Adapter (DB, API) - Repository Impl. | 기술 세부사항을 담당하는 계층. 외부 시스템과의 연결 및 구현 포함 | |
선택적 | Anti-Corruption Layer | - ACL Adapter - Translator | 레거시 시스템과의 연동 시 도메인 오염 방지를 위한 계층 |
Event Layer | - Domain Event - Event Publisher/Handler | 도메인 내 이벤트 기반 처리 및 외부 통합을 위한 이벤트 발행/구독 구조 |
graph TD subgraph Infrastructure Layer INFRA_DB[DB Adapter] INFRA_API[External API Adapter] INFRA_REPO[Repository Implementation] ACL[Anti-Corruption Layer]:::optional EVENTS["Event Layer (Publisher/Handler)"]:::optional end subgraph Application Layer USECASE[Use Case] APP_SERVICE[Application Service] end subgraph Domain Layer ENTITY[Entities / Value Objects] DOMAIN_SERVICE[Domain Services] end INFRA_DB --> USECASE INFRA_API --> USECASE INFRA_REPO --> USECASE ACL --> USECASE EVENTS --> USECASE USECASE --> ENTITY USECASE --> DOMAIN_SERVICE classDef optional fill:#fdf6e3,stroke:#aaa,stroke-dasharray: 5 5;
전통 vs. 현대 Layered Architecture 비교
구분 | 전통적 Layered Architecture | 현대적 Layered Architecture (Hexagonal/Clean/Onion) |
---|---|---|
구조 및 구성 요소 | Presentation → Business → Data Access 계층 | Domain(Core) → Application → Ports → Adapters → Infrastructure |
의존성 방향 | 위→아래 단방향, BLL 이 DAL 에 직접 의존 | 의존성 반전 원칙 적용: 외부가 내부에 의존, 핵심은 외부 구현 모름 |
계층 간 관계 | 일방향, 단방향 | 동적, 양방향/서비스지향 |
결합도 | 높음 (잘못 구현시) | 최소화 (의존성 감소) |
테스트 편의성 | BLL 테스트 시 실제 DB 필요하거나 모킹 복잡 | 내부 도메인을 외부와 격리해 단일 계층 유닛 테스트 용이 |
인프라 독립성 | DAL 구현에 따라 전체 연쇄 영향 | 포트와 어댑터로 인프라 교체 가능, 핵심 로직에 영향 없음 |
확장 및 유지보수 | 계층 경계를 넘어선 통합이 어려움, 무거운 변경 | 새로운 어댑터 추가, 기술 변화 대응 쉽고 모듈화 선명 |
복잡도 수준 | 초기 설계 단순, 작은 프로젝트에 적합 | 구조 복잡, 설정 부담, 팀 수준 맞춤 필요 |
도전과제 | 복잡성 증가시 관리 어려움 | 분산, 오케스트레이션 |
구현 기법 및 방법
Layered Architecture 의 구현 기법은 시대적 변화와 함께 전통적 접근법에서 현대적 접근법으로 진화해왔다. 이 변화의 핵심은 기술 중심적 설계에서 도메인 중심적 설계로의 패러다임 전환이다.
구분 | 전통적 구현 기법 | 현대적 구현 기법 |
---|---|---|
구조 | 기술 중심 계층화 | 유스케이스/도메인 중심 계층화 |
대표 설계 패턴 | MVC, 3-Tier | Clean, Hexagonal, Onion |
계층간 의존성 | 단방향 (상위 → 하위) | DIP 기반 인터페이스 역전 |
트랜잭션 처리 | Service 계층에서 처리 | Application 계층에서 UseCase 단위로 처리 |
외부 시스템 연동 | Controller 또는 Service 에서 직접 호출 | Adapter 를 통해 분리 |
테스트 전략 | 통합 테스트 위주 | 단위/통합 테스트 분리, Mocking 기반 |
배포 구조 | 모놀리식 또는 수직 계층화된 서버 | 각 계층 또는 유스케이스 기반의 마이크로서비스화 가능 |
전통적 Layered Architecture 구현 기법
- 기술적 관심사 중심: 데이터베이스, UI, 비즈니스 로직 등 기술적 요소로 분리
- 하향식 의존성: 상위 계층이 하위 계층에 의존 (UI → Business → Data → Database)
- 데이터베이스 중심 설계: 데이터 모델이 아키텍처의 출발점
구조적 구현 패턴
기본 3-Tier 구조
|
|
MVC 패턴 구현
|
|
의존성 관리 방식
직접 의존성 생성
Factory 패턴 활용
|
|
데이터 처리 방식
직접 데이터 전달
Active Record 패턴
|
|
현대적 Layered Architecture 구현 기법
- 도메인 중심 설계: 비즈니스 도메인이 아키텍처의 중심
- 의존성 역전: 외부 계층이 내부 (도메인) 계층에 의존
- 관심사의 명확한 분리: DDD 원칙 적용
현대적 Layered Architecture 는 전통적 접근법의 한계를 극복하고 다음과 같은 이점을 제공한다:
- 비즈니스 중심성: 도메인이 기술적 세부사항으로부터 독립
- 테스트 용이성: 의존성 주입을 통한 격리된 단위 테스트
- 유연성: 인터페이스 기반 설계로 구현체 교체 가능
- 확장성: 마이크로서비스로의 자연스러운 전환 지원
- 유지보수성: 명확한 관심사 분리와 단일 책임 원칙 준수
이러한 변화는 단순한 기술적 개선이 아닌, 소프트웨어 개발 패러다임의 근본적 전환을 의미한다.
Domain-Centric 구조적 구현
Clean Architecture 기반 구조
|
|
Rich Domain Model 구현
|
|
의존성 주입 및 제어 역전
Interface 기반 설계
|
|
현대적 DI 프레임워크 활용
FastAPI 의존성 주입
|
|
NestJS 의존성 주입
|
|
Use Case 기반 애플리케이션 계층
Use Case 패턴 구현
|
|
CQRS 패턴 적용
|
|
도메인 이벤트 및 Event-Driven Architecture
|
|
주요 차이점 비교
구분 | 전통적 Layered Architecture | 현대적 Layered Architecture |
---|---|---|
설계 철학 | 기술 중심 (Database-Centric) | 도메인 중심 (Domain-Centric) |
의존성 방향 | 하향식 (UI→DB) | 중심 지향 (외부→도메인) |
계층 구성 | Presentation→Business→Data→DB | Infrastructure→Application→Domain |
의존성 관리 | 직접 생성, Factory 패턴 | DI Container, Interface 기반 |
비즈니스 로직 | Service 계층에 절차적 코드 | Rich Domain Model, Use Case |
데이터 전달 | 도메인 객체 직접 노출 | DTO/Request-Response 패턴 |
테스트 전략 | 통합 테스트 중심 | 단위 테스트 우선, Mock 활용 |
변경 전파 | 하위→상위로 전파 | 외부 변경이 도메인에 영향 없음 |
프레임워크 예시 | Spring MVC, Django, Rails | Clean Architecture, DDD |
실제 구현 예시 비교
전통적 방식 - 사용자 생성
|
|
현대적 방식 - 사용자 생성
|
|
마이크로서비스와의 연계
현대적 접근법에서의 마이크로서비스 지원
|
|
장점
전통적 Layered Architecture 의 장점
카테고리 | 항목 | 설명 | 구현 메커니즘 |
---|---|---|---|
구조적 이점 | 계층별 관심사 분리 | 기술적 책임에 따른 명확한 역할 구분 (UI, 비즈니스, 데이터) | 수평적 계층화, 하향식 의존성 |
단방향 의존성 | 상위 계층이 하위 계층에만 의존하는 예측 가능한 구조 | 컴파일 타임 의존성 제어 | |
계층 격리 | 각 계층의 변경이 인접 계층에만 영향을 미침 | 인터페이스 기반 계층 간 통신 | |
개발 효율성 | 팀 기반 병렬 개발 | 계층별로 전문 팀이 동시 개발 가능 | 기술 스택별 팀 구성 |
표준화된 구조 | 업계 표준 패턴으로 학습 곡선 최소화 | MVC, 3-Tier 등 확립된 패턴 | |
재사용성 | 계층별 컴포넌트를 다른 프로젝트에서 활용 | 공통 라이브러리, 프레임워크 | |
품질 보증 | 계층별 테스트 | 각 계층을 독립적으로 테스트 가능 | 모킹, 스텁을 활용한 단위 테스트 |
유지보수성 | 특정 계층의 수정이 다른 계층에 미치는 영향 제한 | 캡슐화, 정보 은닉 | |
운영 측면 | 배포 단순성 | 단일 단위로 배포하여 운영 복잡성 최소화 | 모노리스 배포 모델 |
성능 예측성 | 순차적 처리로 성능 특성 예측 가능 | 동기적 계층 간 호출 |
현대적 Domain-Centric Layered Architecture 의 장점
카테고리 | 항목 | 설명 | 구현 메커니즘 |
---|---|---|---|
비즈니스 중심성 | 도메인 독립성 | 비즈니스 로직이 기술적 세부사항으로부터 완전 격리 | 의존성 역전 원칙 (DIP) 적용 |
비즈니스 표현력 | 코드 구조가 실제 비즈니스 프로세스를 반영 | 유비쿼터스 언어, DDD 전술 패턴 | |
도메인 중심 진화 | 비즈니스 요구사항 변화에 민첩하게 대응 | 도메인 이벤트, 애그리게이트 | |
아키텍처 품질 | 강화된 테스트성 | 도메인 로직을 인프라 없이 독립 테스트 | 순수 도메인 객체 단위 테스트 |
기술적 유연성 | 프레임워크, 데이터베이스 등 기술 스택 교체 용이 | 포트와 어댑터 패턴 | |
확장성 | 개별 계층의 독립적 확장 및 성능 튜닝 | 마이크로서비스 아키텍처 대비 | |
개발 생산성 | 명확한 경계 | 바운디드 컨텍스트를 통한 명확한 책임 경계 | Clean Architecture, Hexagonal Architecture |
컴포넌트 재사용 | 도메인 로직의 다양한 인터페이스에서 재활용 | 멀티 채널 아키텍처 지원 | |
품질 속성 | 보안 강화 | 계층별 보안 정책 적용으로 다층 방어 | Zero Trust, 계층별 인증/권한 |
관찰가능성 | 계층별 메트릭 수집 및 모니터링 | 분산 추적, APM 도구 연동 | |
현대적 요구사항 | 클라우드 네이티브 | 컨테이너화, 마이크로서비스 전환 준비 | Kubernetes, 서비스 메시 지원 |
DevOps 통합 | CI/CD 파이프라인과 자연스러운 통합 | 계층별 독립 배포 파이프라인 |
전통적 vs. 현대적 접근법의 핵심 차이점
설계 철학의 차이
graph TD subgraph "Traditional Layered" A[Presentation Layer] --> B[Business Layer] B --> C[Data Access Layer] C --> D[Database Layer] end subgraph "Modern Domain-Centric" E[Infrastructure] --> F[Application] G[Infrastructure] --> F H[Infrastructure] --> F F --> I[Domain Core] end
측면 | 전통적 접근법 | 현대적 접근법 |
---|---|---|
중심 요소 | 데이터베이스/기술 스택 | 비즈니스 도메인 |
의존성 방향 | 하향식 (UI → DB) | 중심 지향 (외부 → 도메인) |
변경 전파 | 계단식 영향 | 격리된 영향 |
테스트 전략 | 통합 테스트 중심 | 도메인 단위 테스트 중심 |
장점별 상세 분석
관심사 분리 (Separation of Concerns)
전통적 접근법:
- 메커니즘: 기술적 책임에 따른 수평 분리
- 장점: 개발자가 특정 기술 영역에 집중 가능
- 한계: 비즈니스 로직이 여러 계층에 산재
현대적 접근법:
- 메커니즘: 비즈니스 개념 중심의 수직 분리
- 장점: 비즈니스 로직의 응집도 극대화
- 추가 가치: 도메인 전문가와의 의사소통 개선
테스트 용이성
전통적 접근법:
현대적 접근법:
확장성
전통적 접근법:
- 수직 확장: 전체 애플리케이션 단위 확장
- 제한: 특정 계층 병목 시 전체 확장 필요
현대적 접근법:
- 선택적 확장: 도메인별, 유스케이스별 독립 확장
- 마이크로서비스 준비: 바운디드 컨텍스트 기반 분할 가능
현대적 요구사항에 대한 대응
클라우드 네이티브 환경
전통적 방식의 한계:
- 모노리식 배포로 인한 확장성 제약
- 기술 스택 종속성으로 인한 클라우드 마이그레이션 어려움
현대적 방식의 장점:
- 컨테이너화 친화적 구조
- 서비스 메시와의 자연스러운 통합
- 클라우드 서비스 (서버리스, 관리형 서비스) 활용 용이
DevSecOps 와 보안
계층별 보안 강화:
|
|
단점과 문제점 그리고 해결방안
전통적인 Layered Architecture 단점 및 문제점
카테고리 | 항목 | 설명 | 해결 방안 |
---|---|---|---|
의존성/결합도 | 강한 계층 의존성 | BLL (비즈니스 로직) 이 DAL (데이터 접근) 에 직접 의존 | 인터페이스 추상화, 의존성 주입 (DI), DIP 적용 |
테스트 | 테스트 복잡성 | 실제 DB 의존으로 단위 테스트 어렵고, mocking 대상이 복잡함 | In-memory DB, 모킹 전략, 테스트 전용 리포지토리 도입 |
구조 경직성 | 구조의 고정성 | 모든 요청이 모든 계층을 거쳐야 하므로 유연한 변경이 어려움 | 이벤트 기반 처리, 일부 계층 우회, 계층 간 약결합 유도 |
성능/과잉 호출 | 계층 과도 통과 | 단순한 기능도 모든 계층을 거치면서 불필요한 호출/변환 발생 | DTO 단순화, 계층 축소 또는 통합, Facade 패턴 도입 |
개발/운영 효율성 | 계층 간 역할 모호 | Controller → Service → DAO 로만 구성되어 도메인 로직이 분산됨 | 도메인 중심 설계 도입, 서비스 계층 책임 명확화 |
전통적인 구조는 직관적이고 프레임워크 친화적이지만, 비즈니스 로직이 분산되기 쉽고, 계층 간 결합도가 높아 변경이나 테스트에 취약하다. 의존성 주입, 인터페이스 분리, 단순화된 계층 구조가 주요 해결책이다.
현대적인 Layered Architecture–단점 및 문제점
카테고리 | 항목 | 설명 | 해결 방안 |
---|---|---|---|
설계 복잡도 | 구조 복잡성 | 포트/어댑터/유스케이스/엔티티 등 계층이 많아 설계 진입장벽이 존재 | 단계적 도입, 문서화 강화, 팀 수준에 맞춘 구조 설계 적용 |
오버엔지니어링 | 과도한 추상화 | 단순 로직에도 인터페이스/UseCase 분리로 개발 생산성 저하 | 구조 간소화, Vertical Slice Architecture 도입 |
학습 곡선 | 개념적 이해 난이도 | DDD, DIP, SRP 등의 설계 원칙에 대한 높은 이해도 요구 | 교육 및 문서 제공, 파일/패키지 기반 시각적 구조 구성 |
성능/구현 오버헤드 | 이벤트 기반 확산 | Event 기반 설계 시 오류 추적 및 실행 흐름 파악이 어려워질 수 있음 | 이벤트 흐름 로깅 도입, Trace 기반 모니터링 |
테스트 전략 | 계층별 테스트 부담 증가 | 각 계층이 인터페이스 기반으로 분리되어 mocking 구성 많아짐 | 테스트 템플릿화, 테스트 헬퍼 도입, 도메인 중심 테스트 전략 적용 |
현대적 구조는 도메인 주도 설계 (DDD) 에 기반하여 유연성과 유지보수성을 높이지만, 구조가 복잡하고 추상화가 과도할 수 있어 개발 생산성에 악영향을 줄 수 있다.
Vertical Slice, 단계적 도입, 테스트 템플릿화로 운영 효율성을 확보하는 것이 중요하다.
공통 해결 전략 요약
- 계층 수를 목적에 맞게 설계: 작은 프로젝트는 3-Layer, 도메인 중심은 4~5 Layer 구성.
- 인터페이스 기반 통신 (DIP): 추상화 도입으로 유연성과 교체 용이성 확보.
- 테스트 전략 세분화: Domain, Application, Infra 계층별로 독립 테스트 전략 구성.
- 코드 자동 분석 도구: 정적 분석, 의존성 시각화, 순환 참조 탐지 도구 활용.
- 계층 통합/간소화 시도: 반복적이고 의미 없는 계층 (Sinkhole) 은 통합하거나 생략.
도전 과제
카테고리 | 도전 과제 | 원인 또는 트리거 | 영향 | 해결 전략 및 기법 |
---|---|---|---|---|
구조적 문제 | 계층 간 결합도 증가 | 인터페이스 부족, 하위 구현 직접 호출 | 유지보수 어려움, 변경 전파 | DIP 적용, 인터페이스 기반 설계, 의존성 주입 |
계층 침범 및 규칙 위반 | 코드 규율 미흡, 계층 우회 호출 | 설계 무력화, 모듈 결합도 증가 | 코드 리뷰, 정적 분석, AOP, 접근 제어 | |
과도한 계층화 | 책임 없는 추상화, 불필요 계층 남발 | 복잡성 증가, 성능 저하 | 계층 최소화, 도메인 중심 계층 정리, 필요 계층만 유지 | |
운영 및 테스트 | 테스트 유지 어려움 | 계층 수 증가, Mock 구성 복잡 | 테스트 커버리지 저하, 자동화 부담 증가 | 계층별 테스트 전략, Mock 프레임워크, Contract 기반 테스트 분리 |
실시간 처리 한계 | 동기 구조 기반, 계층 지연 | 응답 시간 증가, UX 저하 | 비동기 메시지 처리, 이벤트 기반 구조, Redis 등 캐싱 적용 | |
트랜잭션 경계 모호 | 트랜잭션 책임 분산 | 데이터 불일치, 예외 복잡성 | Application Layer 중심 트랜잭션 관리, @Transactional, AOP 사용 | |
현대화 이슈 | 마이크로서비스 전환의 어려움 | 모놀리식 강한 결합 | 도메인 분할 및 독립 배포 불가 | Strangler Fig, 도메인 별 API 분리, 이벤트 기반 분해 |
클라우드 네이티브 부적합 | 상태 보존, 동기 구조, 고정 계층 | 확장성 저하, 클라우드 리소스 활용 저하 | 서버리스 구조, 컨테이너화, API Gateway + 메시징 기반 아키텍처 | |
외부 기술 변화 대응 | 다양한 Adapter, DB, 프레임워크 변화 주기 | 유지보수 비용 증가, 적시 대응 어려움 | Adapter/Port 설계, Open/Closed 원칙 적용, 테스트 자동화 | |
아키텍처 유연성 | 도메인 로직 누수 | 비즈니스 로직이 Service/Infra 계층에 위치 | 테스트 어려움, 변경 유연성 저하 | 도메인 계층 순수화, DDD 적용, 유스케이스 분리 |
공통 처리 분산 | 인증, 로깅 등이 각 계층에 중복됨 | 코드 중복, 유지보수 어려움 | AOP, 미들웨어 추출, Cross-cutting 모듈화 | |
계층 오버헤드/네트워크 비용 증가 | 마이크로서비스 내 계층 구조 동일 적용 | 호출 지연, 분산 장애 확산 | BFF, API Composition, GraphQL, 경량 계층 전략 적용 |
구조적 문제:
Layered Architecture 는 명확한 경계를 기반으로 설계되지만, 계층 간 결합도 증가나 과도한 계층화로 인해 복잡성이 급증할 수 있다. 특히 DIP(Dependency Inversion Principle) 를 적용하지 않으면 구조가 경직되고 변경 비용이 높아진다.운영 및 테스트 측면:
많은 계층은 테스트 환경 구성에 복잡성을 더하고, 계층별 책임이 분산되면 트랜잭션 경계가 모호해진다. 실시간 처리나 CI/CD 환경에서는 각 계층의 상태를 mocking 하거나 병렬로 테스트하기 어려운 문제가 생긴다.현대화 이슈:
마이크로서비스나 클라우드 네이티브 아키텍처로 전환 시, 전통적인 계층 구조는 배포 단위, 확장성, 네트워크 처리에 대한 제약이 발생한다. 이벤트 중심 아키텍처, 서버리스, API Gateway 등을 활용한 리디자인이 필요하다.아키텍처 유연성 부족:
비즈니스 로직이 도메인 외부 계층에 분산되면 핵심 도메인의 테스트 및 유지보수가 어려워지고, 로깅·인증 등 공통 관심사가 중복 처리되면 코드 일관성과 재사용성이 저하된다. 이는 미들웨어 또는 AOP 기반 공통 처리로 해결할 수 있다.
분류 기준에 따른 종류 및 유형
분류 기준 | 유형 | 설명 | 적용 또는 특징 |
---|---|---|---|
계층 수 기준 | 2-Tier | 클라이언트와 데이터베이스 간 직접 통신 | 데스크탑, 단순 CRUD 시스템 등 |
3-Tier | Presentation / Business / Data 계층 분리 | 가장 일반적인 웹 애플리케이션 구조 | |
4-Tier 이상 (N-Tier) | Application, Domain, Integration 등 세분화 계층 추가 | 대규모 시스템, 모듈화·확장성 강화 | |
아키텍처 스타일 | Traditional Layered | 하향식 의존, UI → Logic → DB 순서 | 빠른 구현과 표준화에 유리, 구조 단순 |
Clean Architecture | 도메인 중심 + Use Case 계층, 의존성 역전 (DIP) | 테스트 용이, 변화에 유연한 아키텍처 | |
Onion Architecture | 도메인 모델을 중심으로 동심원 구조 구성 | 도메인 보호, 로직 중심 구조 | |
Hexagonal Architecture | 내부 (도메인) 와 외부 (인터페이스) 를 포트/어댑터로 명확히 분리 | 테스트 용이, 기술 독립성 확보 | |
계층 간 통신 방식 | Closed Layering | 인접 계층만 호출 가능 | DIP, SRP 준수, 테스트성과 일관성 확보 |
Open Layering | 상위 계층이 모든 하위 계층 직접 접근 가능 | 성능 우선 고려 시 유리, 구조 일관성 희생 | |
Relaxed (Hybrid) Layering | 기본은 Closed, 필요시 Open 허용 | 유연성과 테스트 균형, 실무에서 가장 현실적인 구성 | |
배포 구조 | Logical Layered | 물리적 서버는 같고, 코드 상에서만 계층 구분 | 소규모 시스템, 단일 인스턴스 환경 |
Physical Tiered | 계층마다 별도 서버 또는 컨테이너로 분산 구성 | 클라우드/엔터프라이즈 시스템, 보안/확장성 강화 | |
의존성 방향 | Bottom-Up | 하위 계층에서 상위 계층 호출 | 전통적인 Layered 구조에서 흔함 |
Top-Down | 상위 계층이 하위 인터페이스에만 의존 (의존성 역전) | Clean/Onion/Hexagonal 아키텍처에서 적용 | |
통신 방식 | Synchronous | 계층 간 직접 요청 - 응답 방식 (REST, RPC) | 순차적 처리 흐름, 응답시간 중요할 때 적합 |
Asynchronous | 이벤트/메시지 기반 비동기 통신 | 실시간성/확장성 필요 시 사용 (Kafka, RabbitMQ 등) | |
복잡도/적용 범위 | 단순 구조 | 기본 CRUD, 단일 서비스 구성 | 전통 Layered 구조, 빠른 구축/교육 목적 |
복합 구조 | 도메인 중심, MSA (Microservice Architecture) 환경 | Clean/Onion/Hexagonal 사용, 유연성과 테스트, 유지보수성 중시 |
계층 수 기준:
시스템의 크기와 복잡도에 따라 2N 계층으로 나뉜다. 단순한 CRUD 위주의 시스템은 23 계층이 일반적이며, 대규모 시스템에서는 도메인, 서비스, 통합 계층 등을 추가한 4 계층 이상 구조가 요구된다.아키텍처 스타일:
전통적인 Layered Architecture 는 구현이 쉽고 명확하나, 변화에 취약하다. Clean, Onion, Hexagonal 같은 현대적 아키텍처는 도메인 중심, DIP (의존성 역전 원칙) 적용, 테스트 용이성을 보장하며, 복잡성은 증가하지만 장기적인 유지보수에 강하다.계층 간 통신 방식:
계층 간 호출 제한을 두는 방식 (Closed) 은 안정성과 테스트 용이성이 높고, Open 방식은 개발 속도와 성능 유리하지만 유지보수 리스크가 존재한다. 실무에서는 Closed 기반에 예외적으로 우회 호출을 허용하는 Hybrid 방식이 많이 사용된다.배포 구조:
논리적 계층 분리는 동일 서버 내의 모듈화에 가깝고, 물리적 분리는 각 계층을 별도 인프라 (컨테이너, VM 등) 로 분리하여 클라우드, 보안, 확장성 측면에서 유리하다.의존성 방향:
전통적인 방식은 하위 계층 (데이터) 에 의존하나, 현대적인 방식은 상위 계층 (도메인) 이 하위 구현에 의존하지 않도록 추상화하여 설계한다. 이는 유연성과 테스트성을 크게 향상시킨다.통신 방식:
동기식 통신은 간단하고 직관적이지만, 응답 지연이나 장애 전파 위험이 있다. 반면 비동기식 통신은 복잡하지만 확장성과 복원력 확보에 강점을 가지며, 이벤트 기반 아키텍처와 연계된다.복잡도/적용 범위:
간단한 Layered 구조는 개발이 빠르고 직관적이며 소규모 프로젝트에 적합하다. 복잡한 도메인 요구가 있는 경우엔 도메인 주도 아키텍처로 전환이 필요하며, 이때는 책임 분리와 의존성 통제가 중요하다.
3 계층 vs. N-Tier Architecture 비교
항목 | 3 계층 아키텍처 (Three-Tier Architecture) | N 계층 아키텍처 (N-Tier Architecture) |
---|---|---|
정의 | 가장 기본적인 계층 구조 (Presentation / Logic / Data) | 필요한 기능별로 계층을 세분화하여 설계한 구조 |
계층 수 | 일반적으로 3 개 계층 | 4 개 이상 계층으로 확장 가능 |
구조 예시 | UI → 서비스 → DB | UI → 서비스 → 도메인 → 인프라 → DB 등 다계층 구성 |
사용 목적 | 단순한 웹 애플리케이션, API 중심 설계 | 대규모 엔터프라이즈 시스템, 보안 및 확장성 고려한 설계 |
유지보수 | 상대적으로 간단하며 빠르게 유지보수 가능 | 각 계층이 독립적이라 유지보수 시 영향도 적으나 초기 설계 복잡 |
테스트 | 테스트 환경이 간단함 | 계층이 많아져 Mock 또는 Stub 사용 필수 |
단점 | 계층 확장 어려움, 유연성 부족 | 계층 증가 시 과도한 복잡도 유발 가능 |
- 3 계층은 단순성과 빠른 개발을 추구할 때 적합.
- N 계층은 보안, 확장성, 역할 분리가 중요한 복잡한 시스템에 적합.
Layered Architecture vs. Clean Architecture
항목 | 레이어드 아키텍처 (Layered Architecture) | 클린 아키텍처 (Clean Architecture) |
---|---|---|
핵심 개념 | 기능별로 계층 분리 (UI, Logic, DB 등) | 도메인 중심 계층 구조, 의존성은 내부를 향함 |
계층 구성 | 프레젠테이션 → 비즈니스 로직 → 데이터 액세스 | Entity → UseCase → Interface Adapter → Framework |
의존성 방향 | 상위 계층 → 하위 계층으로만 이동 | 외부 계층 → 내부 계층으로만 이동 (역전된 의존성) |
주요 원칙 | SRP, OCP, DIP 일부 적용 | SOLID 원칙 전면 적용 (특히 DIP 중심) |
관심사 분리 | 기술 중심 (기능 단위 분리) | 도메인 중심 (비즈니스 룰에 집중) |
유연성 | 낮은 유연성: UI 와 DB 구조에 종속성 있음 | 높은 유연성: UI, DB 등 외부 요소 독립적 변경 가능 |
학습 곡선 | 낮음 (대부분의 프레임워크에서 기본 제공) | 높음 (개념적 구조와 DI 설계 학습 필요) |
예시 프레임워크 | Spring MVC,.NET MVC 등 | Hexagonal, Onion, Clean Architecture 등 |
- Clean Architecture 는 Layered Architecture 를 인터페이스 기반으로 개선하여 결합도를 낮추고 핵심 도메인을 외부 변화로부터 보호하는 설계 철학이다.
- Clean vs Layered: Clean Architecture 는 Layered 구조의 추상화와 의존관리 강화 버전이며, 핵심 도메인의 독립성과 유연성을 강조
Strict Layered vs. Loose Layered
구분 | 엄격 레이어드 (Strict Layered) | 유연 레이어드 (Loose Layered) |
---|---|---|
정의 | 요청이 반드시 인접한 하위 계층을 통해서만 전달되는 구조 | 요청이 중간 계층을 건너뛰고 하위 계층에 직접 접근 가능한 구조 |
계층 간 접근 | 상위 계층은 반드시 바로 아래 계층만 호출 가능 | 상위 계층이 하위 여러 계층 (2 단계 이상) 에 직접 접근 가능 |
예시 호출 흐름 | Presentation → Application → Domain → Persistence | Presentation → Domain 또는 Presentation → Persistence 가능 |
결합도 | 낮음 (명확한 경계 유지) | 상대적으로 높음 (우회 경로로 인해 경계가 약해질 수 있음) |
유지보수성 | 높음 (예측 가능한 흐름, 디버깅 용이) | 중간 계층 우회 시 추적 어려움 발생 가능 |
성능 최적화 | 병목 가능성 존재 (모든 요청이 모든 계층을 통과해야 함) | 성능 향상을 위한 계층 우회 가능 |
유연성 | 낮음 (구조적 제약이 많음) | 높음 (상황에 따라 계층 스킵 가능) |
테스트 용이성 | 용이 (단계별 독립적 테스트 가능) | 우회 경로 존재 시 특정 흐름 테스트 복잡도 증가 |
도입 난이도 | 높음 (명확한 규칙 설계 필요, 추상화 많음) | 낮음 (개발 유연성 확보 가능) |
적합한 시나리오 | 보안/안정성 우선 시스템, 공공 시스템, 규제가 강한 도메인 | 성능/속도 우선, MVP, 데이터 분석 시스템 등 민첩성이 중요한 도메인 |
엄격 레이어드 아키텍처는 계층 간 의존성을 철저히 제한하여 모듈성과 예측 가능성이 매우 뛰어남. 테스트와 디버깅이 용이하며, 시스템이 커질수록 구조적 강점이 발휘됨. 단, 성능 병목과 유연성 부족이 단점.
유연 레이어드 아키텍처는 특정 상황에서 계층을 스킵하여 성능을 향상시킬 수 있는 장점이 있으나, 결합도 증가와 유지보수 복잡성의 리스크가 존재함. 실무에서는 대부분 이 모델을 사용하며, 정책적으로 우회 가능한 계층만 허용하는 절충 모델도 존재함.
실무에서 효과적으로 적용하기 위한 고려사항 및 주의할 점
카테고리 | 고려 항목 | 설명 | 권장 전략 또는 실천 방법 |
---|---|---|---|
설계 | 계층 수 결정 | 과도한 계층은 오히려 복잡도를 증가시킴 | 3~5 개 계층을 기본으로, 도메인 복잡도 기반 확장 |
책임과 경계 명확화 | 계층 간 역할이 모호할 경우 유지보수/협업이 어려움 | SRP(단일 책임 원칙) 적용, 명세 기반 설계, 역할 기준 모듈화 | |
인터페이스/계약 설계 | 불명확한 계약은 통합 및 테스트에 혼선을 줌 | API/DTO 명세 문서화, Contract-First 개발, 계약 테스트 적용 | |
구현/코드 구조 | 의존성 역전 | 구현 계층에 대한 직접 의존은 변경에 취약 | DIP(Dependency Inversion Principle) 적용, Interface + DI 활용 |
순환 참조 방지 | 계층 간 상호 참조 발생 시 구조 무너짐 | 정적 분석 도구로 의존성 확인, 계층 참조 방향 제한 | |
불필요한 계층 제거 | 로직 없이 단순 전달하는 계층은 성능 저하의 원인이 됨 | Sinkhole 패턴 제거, 계층 통합 및 경량화 | |
성능 최적화 | 호출 비용 및 응답 지연 | 다단계 계층 호출은 처리 비용과 지연 시간 증가 | 캐싱 전략, DTO 최적화, 비동기/병렬 처리 도입 |
통신 방식 선택 | 실시간성과 시스템 구조에 따라 통신 방식을 달리해야 함 | 동기 (REST) ↔ 비동기 (Message Queue, Event) 선택 | |
테스트 전략 | 계층별 단위 테스트 | 통합 테스트 중심은 디버깅과 유지보수에 비효율 | Mock, Stub 활용, 유닛 테스트 기반 구조 구성 |
테스트 피라미드 적용 | 계층별 테스트 비율이 균형 잡히지 않으면 품질 저하 | 단위: 통합:E2E = 70:20:10 비율 유지, 자동화 테스트 도입 | |
배포/운영 관리 | 독립 배포 가능성 확보 | 계층 간 의존이 강하면 일부 기능 변경 시 전체 재배포 필요 | 모듈 단위 배포 전략, CI/CD, 컨테이너 기반 계층 구성 |
모니터링/문제 추적 | 이슈 발생 시 병목 계층 파악 어려움 | APM, 로그 통합, 분산 추적 시스템 (Jaeger 등) 도입 | |
보안/인증 | 접근 통제 및 인증 위치 명확화 | 인증, 권한 처리가 분산되면 취약점 증가 | 계층별 보안 책임 고정, 인증 게이트웨이 또는 보안 필터 계층 구성 |
횡단 관심사 처리 | 로깅, 트랜잭션, 보안 중복 처리 | 여러 계층에서 중복 로직 발생 | AOP(관점지향 프로그래밍), 미들웨어/공통 컴포넌트로 추출 |
문서화/규칙 관리 | 설계 문서 및 계층 가이드 정비 | 아키텍처 규칙 미준수 시 구조 무너짐 | ADR (Architecture Decision Record), 설계 표준 문서화 및 리뷰 문화 정착 |
설계
Layered Architecture 는 구조 명확성과 유지보수성을 위해 설계 초기에 계층 수를 정리하고, 각 계층의 책임과 역할을 분명히 구분해야 한다. 인터페이스 계약 기반 설계 (Contract-first) 는 계층 간 통신의 일관성과 테스트 품질 확보에 기여한다.구현/코드 구조
계층 간 의존성은 반드시 인터페이스를 통해 제어해야 하며, 순환 참조나 구현 의존은 피해야 한다. 불필요한 중간 계층은 제거하거나 간소화하여 성능과 유지보수성을 동시에 개선할 수 있다.성능 최적화
다계층 호출 구조는 호출 지연, 오버헤드를 초래하므로 DTO 최적화, 캐싱, 병렬 처리, 비동기 메시징 등으로 대응해야 한다. 특히 실시간성이 중요한 서비스는 통신 방식 자체를 유연하게 선택할 수 있어야 한다.테스트 전략
계층 기반 구조는 테스트 전략 수립이 필수이다. 단위 테스트와 통합 테스트는 역할을 분리하고, 테스트 피라미드 원칙에 따라 계층마다 알맞은 수준의 테스트 커버리지를 유지해야 한다.배포 및 운영
모든 계층이 단일 배포 단위에 포함되어 있다면, 작은 변경도 전체 시스템 재배포로 이어질 수 있다. 따라서 컨테이너 기반 배포, 모듈화된 빌드 전략, APM 및 로깅 도구로 안정적 운영이 필요하다.보안과 횡단 관심사
인증, 권한 부여, 로깅, 트랜잭션 등은 중복되지 않도록 공통 계층으로 위임하여 관리 효율성과 보안성을 동시에 확보한다. AOP 또는 미들웨어 기반 설계가 효과적이다.문서화 및 규칙 관리
Layered Architecture 는 계층 간 계약과 구조가 명확해야 하므로, 아키텍처 결정사항 (ADR), 계약 문서, 계층 규칙 등의 문서화는 장기적인 유지보수와 팀 커뮤니케이션에 핵심 역할을 한다.
Layered Architecture 설계·구현 단계 권장 전략
항목 | 설명 | 실무 적용 포인트 |
---|---|---|
표준 인터페이스 정의 | 각 계층 간 의존성을 줄이기 위해 계약 기반 인터페이스 설계 (ex: Presentation ↔ Application ↔ Domain ↔ Infra) | 인터페이스는 명세 수준에서 먼저 정의, 테스트 대상 분리에 효과적 |
의존성 주입 (DI/IoC) | 상위 계층이 하위 구현에 직접 의존하지 않도록 제어 역전 (Inversion of Control) 원칙 적용 | 프레임워크 (Spring, NestJS 등) 기반 DI Container 적극 활용 |
폴더 및 네임스페이스 분리 | 코드 구조를 계층별로 구분하여 역할을 명확히 하고 관리 효율화 | ex: /controller , /service , /repository , /domain |
단위 테스트 및 Mock 설계 | 각 계층을 독립적으로 테스트할 수 있도록 Mock/Stub 을 활용하여 테스트 커버리지와 리팩토링 신뢰성 확보 | 테스트 피라미드 구조 활용 (Unit > Integration > E2E) |
아키텍처 설계 전략
전략 항목 | 설명 | 권장 기준 |
---|---|---|
계층 정의 기준 | 기술 기반 또는 유스케이스 (Use Case) 기반 계층 구성 | 도메인 주도 설계 (DDD) 환경에서는 Use Case 중심 계층화 권장 |
인터페이스 추상화 | Domain ↔ Infrastructure 간 DIP(의존성 역전 원칙) 구현 | 테스트 유연성 확보 및 기술 교체 대응력 향상 |
DTO ↔ Domain Mapper | 계층 간 데이터 독립성을 위한 Mapper 또는 Adapter 계층 도입 | MapStruct, ModelMapper, 또는 수동 Mapper 구성 |
공통 관심사 분리 (AOP) | 인증, 로깅, 예외처리 등 공통 로직은 AOP 기반 또는 Interceptor 등으로 분리 관리 | Spring AOP, Middleware 구성으로 코드 중복 제거 |
운영·배포 단계 권장 전략
항목 | 설명 | 실무 적용 포인트 |
---|---|---|
계층 단위 롤백/배포 | 장애 발생 시 전체가 아닌 계층 또는 모듈 단위로 배포/롤백 가능하도록 구성 | CI/CD 파이프라인 내 계층 단위 배포 Job 구성 |
관찰가능성 계층 (Observability) | 로그, 모니터링, 트레이싱 등 운영 정보를 수집하는 미들웨어 또는 전용 계층 구성 | OpenTelemetry, Prometheus, ELK 도입 |
자동화된 빌드/배포 (CI/CD) | 계층별 테스트, 빌드, 배포를 자동화하여 배포 안정성과 속도를 향상 | GitHub Actions, Jenkins, GitLab CI 등 단계별 분리 구성 |
CI/CD 와 계층별 배포 전략 (CI/CD & Tiered Deployment)
Layered Architecture 계층별로 독립적인 CI/CD 파이프라인 설계를 통해 빨리, 안정적으로, 격리된 배포가 가능하다. Presentation, Application, Domain, Persistence 계층을 개별로 테스트하고 배포하며, 변경 범위를 최소화한다.
핵심 구성 요소와 흐름:
- 단위 테스트 & 통합 테스트: 각 계층별 독립 단위 테스트 후, 연계 테스트 수행
- CI 단계: 코드 커밋 → 계층별 빌드 및 테스트 병렬 실행 → 전체 통합 테스트
- CD 단계:
- Presentation Layer: 프론트엔드 또는 API Gateway
- Application/Domain Layer: 서비스 코드
- Persistence Layer: 데이터 접근 서비스
- 계층별로 Blue-Green 또는 Canary 배포 적용 가능
효과:
- 계층별 변경 시 전체 배포가 아닌 해당 계층만 재배포 → 롤백/MR 영향 최소화
- 모듈별 테스트 자동화로 품질 보장
- 전체 실패 리스크 감소 및 배포 빈도 증가
보안 강화 포인트:
- CI/CD 환경에서 각 계층 별로 비밀 관리 (secrets), 정적 코드 분석 (SAST), 동적 분석 (DAST) 적용, 권한 제어 및 접근 분리 필요
성능 최적화 전략
항목 | 문제 | 최적화 기법 |
---|---|---|
계층 호출 오버헤드 | Service → Domain → Infra → DB | 캐시 도입 (예: Redis), CQRS 도입 |
병목 I/O | 모든 계층이 동기 처리 | 비동기 처리 (Reactor, Kafka 등) |
계층간 중복 로직 | 동일한 로직이 여러 계층 존재 | 공통 유틸 모듈 또는 전략 패턴으로 통합 |
로깅 부하 | 계층별 로그 중복 | Structured Logging 도입 및 필터링 전략 적용 |
테스트 전략
계층 | 테스트 대상 | 방식 | 도구 |
---|---|---|---|
Presentation | Controller | WebMvcTest, MockMvc | SpringBootTest, RestAssured |
Application | Service, UseCase | 단위 테스트, Mock Repository | JUnit, Mockito |
Domain | Entity, DomainService | 유닛 테스트 중심 | JUnit |
Infrastructure | Repository, Adapter | DB 연동 테스트 | Testcontainers, H2DB |
리팩토링 및 구조 개선 전략
구조적 개선 대상:
- Application 계층이 DB 에 직접 접근
- Domain 계층이 외부 시스템과 직접 연동
- Controller 가 UseCase 를 넘어서 로직을 수행
개선 전략
대상 | 개선 방식 |
---|---|
Application → DB 직접 접근 | Repository Interface 를 통해 간접 접근으로 전환 |
Domain ↔ 외부 연동 | Adapter 를 통한 외부 연동 구조 설계 |
Controller 로직 중첩 | 흐름 제어만 담당하도록 축소, Service 에 로직 이관 |
Cross‑cutting Concerns 처리
Cross‑cutting concerns 는 여러 계층에 걸쳐 공통적으로 적용되는 기능들 (로그, 인증, 트랜잭션, 캐싱 등) 이다. Layered Architecture 에서 비즈니스 핵심 로직에 영향을 주지 않도록 분리된 모듈로 관리해야 한다.
처리 전략:
- Layered Architecture: 인터셉터 또는 미들웨어 계층을 Presentation 또는 Application Layer 상단에 두어 공통 로직 적용
- Clean Architecture에서는 Infrastructure 계층 또는 외부 툴 파이프라인 (middleware, 데코레이터 등) 을 활용
적용 방식:
- Logging: Middleware 또는 DI 기반 Decorator 로 요청 흐름 추적
- Validation: Controller 전에 입력 DTO 검증, Domain 규칙 검증 분리
- Caching: 캐시 조회/적용은 Application 또는 Infrastructure 계층에 적용 (Cache Aside 패턴)
- Transaction 관리: Application/Service 계층에서 트랜잭션 범위 지정
최적화하기 위한 고려사항 및 주의할 점
카테고리 | 항목 | 설명 | 권장 사항 |
---|---|---|---|
구조 단순화 | 계층 수 최적화 | 과도한 계층 분리는 복잡성과 호출 오버헤드 유발 | 3~5 개 핵심 계층 중심으로 시작하고 도메인 중심 확장 고려 |
인터페이스 단순화 | 포트/어댑터 등 추상화 계층 과도 시 오히려 구조 혼란 | Use Case 중심 인터페이스 설계, DTO 기반 데이터 교환 | |
성능 최적화 | 계층 간 통신 최소화 | 중복 호출, Sinkhole 계층, 과도한 직렬화 등은 성능 저하 원인 | DTO 최적화, 캐시 적용, 비동기/이벤트 기반 처리 도입 |
병목 제거 및 비동기화 | 응답 지연 제거, 비효율적 동기식 처리 제거 | 메시지 큐 (RabbitMQ, Kafka), async/await, AOP 병렬화 적용 | |
캐싱 전략 | 반복 데이터 호출/연산에 의한 부하 해소 | Redis, 메모리 캐시, TTL 정책 및 캐시 무효화 정책 | |
데이터 최적화 | DB 부하 분산 | 읽기/쓰기 병목 발생 시 확장 어려움 | CQRS, Read Replica, 샤딩 전략, 배치 트리거 활용 |
대용량 처리 대응 | 대규모 요청 처리에서 메모리·속도 병목 유발 가능 | 커서 기반 페이징, 스트리밍 처리 파이프라인 적용 | |
확장성 설계 | 수평 확장 구조 | 확장성 없는 계층은 병목 지점이 됨 | Stateless 구조, 세션 외부화, 로드밸런서, 오토스케일링 적용 |
독립 배포 및 모듈화 | 계층 간 강한 결합 시 전체 배포 필요, 유지보수 비용 증가 | 각 계층별 API 계약 정의, 모듈 단위 배포 전략 수립 | |
모니터링/관측성 | 계층별 메트릭 수집 | 지표 수집 없을 시 병목 진단·장애 대응 어려움 | Prometheus, OpenTelemetry, APM 도구 (New Relic 등) 사용 |
분산 추적 및 장애 복구 | 요청 흐름 추적·장애 원인 파악이 어려움 | Trace ID 기반 추적, Circuit Breaker, 재시도 정책 적용 | |
유지보수성 향상 | 계층 경계 및 역할 명확화 | 책임 중복 또는 누락 발생 가능 | SRP(단일 책임 원칙), 정적 분석 도구 (SonarQube), 인터페이스 기반 의존성 설계 |
문서화 및 아키텍처 기록 | 구조 누락 시 팀 내 혼란, 구조 침식 발생 | Swagger, ADR(Architecture Decision Record), 코드 리뷰 문화 구축 | |
테스트 전략 | 계층별 테스트 구조 | 통합 테스트 의존 시 디버깅 어려움 | Mock 기반 단위 테스트, 계층별 독립 테스트 환경 구축 |
팀 및 운영 전략 | 팀 역량 기반 점진적 도입 | 과도한 추상화나 계층화는 도입 실패 가능성 존재 | 기능 단위 (Vertical Slice) 부터 Clean Architecture 점진 적용 |
자동화 및 배포 일관성 | 수동 배포 및 환경 불일치로 인한 장애 증가 | CI/CD 파이프라인, 템플릿 기반 배포 구성, 환경별 설정 관리 | |
비용 효율성 | 리소스 최적화 및 클라우드 운영 | 불필요 인스턴스/컨테이너 사용 증가 시 비용 상승 | 태깅, 자동 스케일, 서버리스 고려, 비용 시뮬레이터 사용 |
구조 단순화:
계층은 많을수록 복잡도와 호출 비용이 증가하므로, 프로젝트 규모나 도메인 복잡도에 맞게 최소화하여 유지한다. Port/Adapter 추상화도 필요한 수준까지만 도입한다.성능 최적화:
DTO 최적화, 불필요 계층 제거, 캐시 전략, 비동기 흐름 등은 계층 간 통신 오버헤드를 줄이는 핵심 요소다. 병목이 생기기 쉬운 위치에 프로파일링 도구를 적용해 지속 개선이 필요하다.데이터 최적화:
CQRS 및 스트리밍 기반 데이터 처리는 대규모 트래픽 상황에서도 안정적이고 빠른 데이터 흐름을 유지하는 데 중요하다.확장성과 배포 유연성:
계층 간 결합을 줄이면 마이크로서비스 전환 또는 독립 배포에 유리하다. 세션 외부화 및 무상태 처리 전략은 수평 확장의 필수 조건이다.모니터링과 장애 대응:
계층이 많을수록 분산 추적 및 장애 지점 파악이 어렵기 때문에, Trace 기반 로깅과 Circuit Breaker 등 장애 복원 전략을 함께 구축해야 한다.유지보수성 확보:
아키텍처 침식을 방지하려면 명확한 역할 분리, 문서화, 코드 리뷰 문화가 필요하며, 정적 분석 도구를 통한 지속적인 품질 유지가 중요하다.테스트 전략:
모든 계층을 커버하는 테스트 체계는 유지보수성을 극대화한다. 단위 테스트와 통합 테스트를 분리하고 테스트 더블 (Mock/Stub) 을 적극 활용한다.팀 기반 적용 전략:
설계 경험이 부족한 팀에게는 점진적인 도입 전략이 필수다. 전체 구조보다 Vertical Slice 중심 리팩토링을 통해 점진적 이행을 유도해야 한다.비용 효율화:
클라우드 기반 운영 시 비용 최적화를 위해 사용량 기반 모니터링, 리소스 태깅, 오토스케일링 등을 도입하고, 정기 리사이징 및 서버리스 아키텍처도 고려한다.
클라우드 네이티브 환경에서의 현대적 Layered Architecture 진화
클라우드 네이티브 특성과 계층형 아키텍처의 적응
컨테이너화와 계층 구조:
- 마이크로서비스 기반 계층: 각 계층이 독립적인 컨테이너로 배포
- 서비스 메시 (Service Mesh): Istio, Linkerd 를 통한 계층 간 통신 관리
- 서버리스 통합: AWS Lambda, Azure Functions 를 활용한 계층별 서버리스 배포
Kubernetes 기반 계층 오케스트레이션:
|
|
MACH 아키텍처와의 연계
MACH (Microservices, API-first, Cloud-native, Headless) 원칙:
- 모듈성: 각 계층의 독립 배포 및 확장
- API 우선: 계층 간 RESTful/GraphQL API 통신
- 클라우드 네이티브: 클라우드 서비스와의 자연스러운 통합
- 헤드리스 접근법: 프레젠테이션 계층의 다중 채널 지원
실무 사용 예시
카테고리 | 시스템/도메인 | 사용 기술 스택 | 계층 구성 및 역할 | 주요 효과 및 특징 |
---|---|---|---|---|
웹 애플리케이션 | 전자상거래, B2B 플랫폼 | React, Spring Boot, MyBatis, MySQL | UI / 서비스 / 도메인 / 인프라 계층 분리 | 유지보수성 향상, 기능 단위 분리, 유연한 확장 |
모바일 백엔드 | 모바일 API 서버 | Node.js, Express, MongoDB, React Native | RESTful API → 서비스 로직 → 영속 계층 | 빠른 개발, 확장성 및 모바일 연동 최적화 |
엔터프라이즈 시스템 | ERP, 금융, 보험, 공공기관 | Java EE, SAP, Oracle,.NET Framework, Angular | 프레젠테이션 → 도메인 서비스 → 인프라 계층 | 보안성, 트랜잭션 정합성 확보, 복잡한 로직 계층화 |
콘텐츠 관리 | CMS, 블로그 플랫폼 | Laravel, WordPress, PostgreSQL, PHP | View / 콘텐츠 로직 / 데이터 계층 | 콘텐츠 재사용성 강화, 역할 분리 통한 관리 효율 향상 |
마이크로서비스 | 주문/결제/배송 MSA 플랫폼 | .NET Core, Spring Cloud, Kafka, gRPC, API Gateway | Gateway → Application → Domain → Infrastructure | 각 서비스 독립 배포, 메시징 기반 통신, 책임 중심 분산 처리 |
헬스케어/규제 산업 | 의료정보시스템, 법률/공공 시스템 | Angular,.NET Core, SQL Server, JWT, OAuth2 | 환자 UI → 진료 서비스 로직 → 데이터 저장 계층 | 보안/법적 요구사항 대응, 민감정보 보호, 인증 계층 강화 |
레거시 시스템 현대화 | Java EE 기반 레거시 | Java EE, Hibernate, Oracle, JSP | Controller → Service → Repository 구조에서 점진적 계층 분리 | 레거시 구조에서 점진적 Clean 구조로 이전 가능 |
클라우드 기반 시스템 | SaaS, BFF (Backend for Frontend) | AWS Lambda, Serverless Framework, GraphQL, Redis, SQS | 프론트 최적화 BFF → 서비스 로직 → 비동기 메시징 → 영속 계층 | 서버리스 최적화, 비동기 처리, 계층별 자원 격리 |
웹 애플리케이션:
전통적인 3-Tier 또는 4-Tier 구조를 사용하여, 사용자 인터페이스, 도메인 로직, 영속 계층을 명확히 분리함. 특히 전자상거래에서는 유연한 확장성과 기능별 유지보수가 중요.모바일 백엔드:
RESTful API 를 중심으로 비즈니스 로직과 DB 계층을 분리하여 다양한 클라이언트 (모바일, IoT 등) 대응에 용이. Node.js 기반 경량 계층이 자주 사용됨.엔터프라이즈 시스템:
대규모 시스템에서 Layered Architecture 는 모듈화, 보안, 트랜잭션 처리에 강점을 보이며, 조직 내 여러 팀이 계층별로 협업할 수 있는 구조 제공.콘텐츠 관리 시스템:
콘텐츠 CRUD 처리에 적합한 전통적 계층 구조를 사용. 콘텐츠 재사용성과 보안적 템플릿 처리에 유리하며, 중소형 시스템에 적합.마이크로서비스 플랫폼:
서비스마다 독립된 계층 구조를 가지며, 도메인 주도 설계 (DDD) 와 Hexagonal 구조를 채택해 유연한 API, 메시지 통신, 독립 배포를 실현.헬스케어 및 규제 산업:
보안, 인증, 민감 정보 보호가 중요한 도메인으로, Layered 구조는 보안계층과 서비스 계층의 명확한 분리를 통해 법적 요구사항을 충족함.레거시 시스템 현대화:
기존 계층 구조를 활용하여 점진적으로 Onion/Clean 아키텍처로 이동하는 방식. Hibernate, Repository 추상화 등을 통해 도메인 중심 설계로 이행 가능.클라우드 네이티브 시스템:
BFF 또는 서버리스 환경에서는 전통 계층 구조를 경량화하거나 메시지 중심 구조와 결합해 비동기 확장성과 민첩한 개발을 지원함.
활용 사례
사례 1: 전자상거래 플랫폼의 주문 관리 시스템
시스템 구성:
- Presentation Layer: REST API Controller
- Application Layer: Order Use Cases
- Domain Layer: Order Aggregate, Product Entity
- Infrastructure Layer: Database Repository, Payment Gateway
시스템 구성 다이어그램:
graph TB subgraph "Infrastructure Layer" A[REST Controller] B[Database Repository] C[Payment Gateway] D[Email Service] end subgraph "Application Layer" E[Create Order Use Case] F[Process Payment Use Case] G[Send Notification Use Case] end subgraph "Domain Layer" H[Order Aggregate] I[Product Entity] J[Customer Entity] K[Payment Value Object] end A --> E E --> H E --> I E --> J B -.-> E C -.-> F D -.-> G F --> K
Workflow:
- 고객이 주문 생성 요청
- REST Controller 가 요청 수신
- Create Order Use Case 실행
- Order Aggregate 가 비즈니스 규칙 검증
- Database Repository 를 통한 데이터 저장
- Payment Gateway 를 통한 결제 처리
역할:
- Domain Layer: 주문 생성 규칙, 재고 확인 로직
- Application Layer: 유스케이스 조정, 외부 서비스 호출
- Infrastructure Layer: 데이터 영속성, 외부 API 연동
유무에 따른 차이점:
- 있는 경우: 명확한 책임 분리, 테스트 용이성, 유지보수성 향상
- 없는 경우: 스파게티 코드, 비즈니스 로직 분산, 테스트 어려움
구현 예시:
|
|
사례 2: 대형 전자상거래 시스템
시나리오: 대형 전자상거래 시스템의 주문 프로세스를 계층형 아키텍처로 구축
시스템 구성:
- 프런트엔드 (프리젠테이션 계층)
- 주문 서비스 (비즈니스 계층)
- 주문 저장소 (데이터 접근 계층)
- 기타 배송/결제 연동 (서비스/통합 계층)
flowchart RL Customer -- 주문요청 --> UI[웹/모바일 프런트엔드] UI --API호출--> BL["OrderService(비즈니스 계층)"] BL --DB조작--> DAL["OrderRepository(데이터 접근 계층)"] BL --서비스연동--> Integration["배송/결제 API(통합 계층)"]
Workflow:
- 사용자가 주문을 입력 → 프런트엔드가 주문 서비스에 API 호출 → 주문 내역 저장 및 검증 → 결제/배송 시스템 연동 → 응답
역할:
- 각 계층별로 입력값 검증, 비즈니스 처리, 데이터 저장, 외부 연동이 분리됨
유무에 따른 차이점:
- 계층형 적용: 각 업무별 독립적 배포 및 변경 가능, 장애/이슈 국소화 가능
- 계층형 미적용: 전체 서비스가 단일 동작, 변경 시 전체 영향, 문제점 진단 난이도↑
구현 예시:
|
|
사례 3: Clean Architecture 기반 사용자 등록 기능
시나리오: 유저 이메일/비밀번호로 신규 가입 처리 및 검증 로직
시스템 구성:
- Domain Layer (User, EmailValueObject 등 핵심 엔티티)
- Application Layer (RegisterUseCase)
- Interface Adapters (input DTO, output DTO, presenter)
- Infrastructure Layer (DB repository 구현, 암호화 서비스 어댑터)
graph TD UI -->|RegisterRequest| UseCase UseCase --> DomainEntity UseCase -->|IUserRepository| Interface Interface --> RepoImpl RepoImpl --> DB
Workflow:
- UI 또는 API 가 RegisterRequest 전송
- Application Layer 의 RegisterUseCase 가 호출
- Domain Entity 이메일 중복 검증, 비밀번호 해시 처리
- IUserRepository 인터페이스 호출 → Infrastructure Layer RepoImpl 수행
- 결과가 DTO 로 반환
역할:
- Domain: 이메일 규칙·유효성, 유저 생성 로직
- Use Case: 유스케이스 관리, 흐름 제어, 인터페이스 호출
- Adapter (Interface): Use Case 와 Infrastructure 연계 인터페이스
- Infrastructure: 실제 DB 저장, 해시 서비스 구현
유무에 따른 차이점:
- 기존 전통적 Layered 에서는 BLL 이 직접 DB 연결, 테스트 어려움
- Clean Architecture 는 IUserRepository 추상화로 인프라 교체 가능, 유닛 테스트용 모킹 제공
구현 예시:
|
|
사례 4: 온라인 쇼핑몰 시스템
시나리오: 온라인 쇼핑몰 시스템
시스템 구성:
- 프레젠테이션 계층: React.js 기반 웹 인터페이스
- 비즈니스 계층: Node.js/Express 기반 API 서버
- 데이터 접근 계층: Sequelize ORM
- 데이터베이스 계층: PostgreSQL
graph TB subgraph "프레젠테이션 계층" UI[웹 브라우저 UI<br/>React.js] Mobile[모바일 앱<br/>React Native] end subgraph "비즈니스 계층" API[API 서버<br/>Node.js/Express] Auth[인증 서비스] Order[주문 처리 서비스] Payment[결제 서비스] end subgraph "데이터 접근 계층" ORM[Sequelize ORM] Cache[Redis 캐시] end subgraph "데이터베이스 계층" DB[(PostgreSQL)] Files[(파일 저장소)] end UI --> API Mobile --> API API --> Auth API --> Order API --> Payment API --> ORM ORM --> Cache ORM --> DB API --> Files
Workflow:
- 사용자가 상품 검색 요청을 웹 인터페이스에서 입력
- 프레젠테이션 계층에서 입력 검증 후 API 서버로 요청 전달
- 비즈니스 계층에서 검색 로직 처리 및 인증 확인
- 데이터 접근 계층에서 캐시 확인 후 필요시 데이터베이스 조회
- 결과를 역순으로 전달하여 사용자 인터페이스에 표시
역할:
- 프레젠테이션 계층: 사용자 경험 최적화와 입력 검증
- 비즈니스 계층: 쇼핑몰 비즈니스 규칙과 워크플로 관리
- 데이터 접근 계층: 성능 최적화와 데이터 매핑
- 데이터베이스 계층: 데이터 영속성과 무결성 보장
유무에 따른 차이점:
- 계층형 아키텍처 적용 시: 모듈별 독립 개발, 확장성, 유지보수 용이성
- 미적용 시: 개발 속도는 빠르나 장기적 유지보수 어려움, 확장성 제한
구현 예시:
|
|
사례 5: 전자상거래 플랫폼 사례
시스템 구성:
- 프레젠테이션: React 기반 웹 프론트엔드
- 비즈니스: Spring Boot 기반 REST API
- 퍼시스턴스: Spring Data JPA + Redis 캐시
- 데이터베이스: MySQL (주문/상품) + MongoDB (리뷰/로그)
graph TB subgraph "프레젠테이션 계층" A1[React Web App] A2[Mobile App] A3[Admin Panel] end subgraph "비즈니스 계층" B1[상품 서비스] B2[주문 서비스] B3[사용자 서비스] B4[결제 서비스] end subgraph "퍼시스턴스 계층" C1[상품 Repository] C2[주문 Repository] C3[사용자 Repository] C4[Redis Cache] end subgraph "데이터베이스 계층" D1[MySQL] D2[MongoDB] D3[외부 결제 API] end A1 --> B1 A1 --> B2 A2 --> B1 A3 --> B3 B1 --> C1 B2 --> C2 B3 --> C3 C1 --> D1 C2 --> D1 C3 --> D2 B4 --> D3
Workflow:
- 사용자가 상품 주문 요청
- 프레젠테이션 계층에서 요청 검증 및 인증
- 비즈니스 계층에서 재고 확인 및 주문 생성
- 퍼시스턴스 계층에서 데이터베이스 트랜잭션 처리
- 결제 서비스 연동 및 주문 확정
- 응답 데이터 반환
계층화의 역할:
- 프레젠테이션: 다중 채널 지원 (웹, 모바일, 관리자)
- 비즈니스: 복잡한 주문 로직, 재고 관리, 할인 계산
- 퍼시스턴스: 다중 데이터베이스 추상화, 캐싱 전략
- 데이터베이스: 트랜잭션 보장, 성능 최적화
기존 아키텍처와의 차이점:
- 전통적 모놀리식 대비: 계층별 독립 배포 가능
- 마이크로서비스 대비: 단순한 구조로 초기 개발 속도 빠름
- MVC 패턴 대비: 명확한 비즈니스 로직 분리
구현 예시:
프레젠테이션 계층 (Controller)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
@RestController @RequestMapping("/api/orders") public class OrderController { private final OrderService orderService; public OrderController(OrderService orderService) { this.orderService = orderService; } // 주문 생성 API @PostMapping public ResponseEntity<OrderResponseDto> createOrder( @Valid @RequestBody OrderRequestDto request) { // 입력 검증 및 DTO 변환 OrderResponseDto response = orderService.createOrder(request); return ResponseEntity.ok(response); } // 주문 조회 API @GetMapping("/{orderId}") public ResponseEntity<OrderResponseDto> getOrder(@PathVariable Long orderId) { OrderResponseDto response = orderService.getOrder(orderId); return ResponseEntity.ok(response); } }
비즈니스 계층 (Service)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64
@Service @Transactional public class OrderService { private final OrderRepository orderRepository; private final ProductRepository productRepository; private final PaymentService paymentService; public OrderService(OrderRepository orderRepository, ProductRepository productRepository, PaymentService paymentService) { this.orderRepository = orderRepository; this.productRepository = productRepository; this.paymentService = paymentService; } public OrderResponseDto createOrder(OrderRequestDto request) { // 1. 비즈니스 로직: 상품 재고 확인 Product product = productRepository.findById(request.getProductId()) .orElseThrow(() -> new ProductNotFoundException("상품을 찾을 수 없습니다")); if (product.getStock() < request.getQuantity()) { throw new InsufficientStockException("재고가 부족합니다"); } // 2. 비즈니스 로직: 주문 총액 계산 BigDecimal totalAmount = product.getPrice() .multiply(BigDecimal.valueOf(request.getQuantity())); // 3. 주문 엔티티 생성 Order order = Order.builder() .productId(request.getProductId()) .quantity(request.getQuantity()) .totalAmount(totalAmount) .status(OrderStatus.PENDING) .build(); // 4. 주문 저장 Order savedOrder = orderRepository.save(order); // 5. 결제 처리 PaymentResult paymentResult = paymentService.processPayment( savedOrder.getId(), totalAmount); if (paymentResult.isSuccess()) { savedOrder.confirm(); product.decreaseStock(request.getQuantity()); } else { savedOrder.cancel(); throw new PaymentFailedException("결제에 실패했습니다"); } // 6. DTO 변환하여 반환 return OrderResponseDto.from(savedOrder); } @Transactional(readOnly = true) public OrderResponseDto getOrder(Long orderId) { Order order = orderRepository.findById(orderId) .orElseThrow(() -> new OrderNotFoundException("주문을 찾을 수 없습니다")); return OrderResponseDto.from(order); } }
퍼시스턴스 계층 (Repository)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41
// Repository 인터페이스 public interface OrderRepository extends JpaRepository<Order, Long> { // 커스텀 쿼리 메서드 List<Order> findByUserIdAndStatus(Long userId, OrderStatus status); // 복잡한 쿼리를 위한 JPQL @Query("SELECT o FROM Order o WHERE o.createdAt BETWEEN :startDate AND :endDate") List<Order> findOrdersByDateRange( @Param("startDate") LocalDateTime startDate, @Param("endDate") LocalDateTime endDate); } // 구체적인 Repository 구현 (필요시) @Repository public class OrderRepositoryImpl { @PersistenceContext private EntityManager entityManager; public List<Order> findComplexOrders(OrderSearchCriteria criteria) { // 복잡한 동적 쿼리 구현 CriteriaBuilder cb = entityManager.getCriteriaBuilder(); CriteriaQuery<Order> query = cb.createQuery(Order.class); Root<Order> root = query.from(Order.class); List<Predicate> predicates = new ArrayList<>(); if (criteria.getUserId() != null) { predicates.add(cb.equal(root.get("userId"), criteria.getUserId())); } if (criteria.getStatus() != null) { predicates.add(cb.equal(root.get("status"), criteria.getStatus())); } query.where(predicates.toArray(new Predicate[0])); return entityManager.createQuery(query).getResultList(); } }
데이터베이스 계층 (Entity)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78
@Entity @Table(name = "orders") public class Order { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; @Column(name = "product_id", nullable = false) private Long productId; @Column(name = "user_id", nullable = false) private Long userId; @Column(name = "quantity", nullable = false) private Integer quantity; @Column(name = "total_amount", nullable = false, precision = 10, scale = 2) private BigDecimal totalAmount; @Enumerated(EnumType.STRING) @Column(name = "status", nullable = false) private OrderStatus status; @CreationTimestamp @Column(name = "created_at") private LocalDateTime createdAt; @UpdateTimestamp @Column(name = "updated_at") private LocalDateTime updatedAt; // 생성자, getter, setter, 비즈니스 메서드 protected Order() {} @Builder public Order(Long productId, Long userId, Integer quantity, BigDecimal totalAmount, OrderStatus status) { this.productId = productId; this.userId = userId; this.quantity = quantity; this.totalAmount = totalAmount; this.status = status; } // 비즈니스 메서드 public void confirm() { if (this.status != OrderStatus.PENDING) { throw new IllegalStateException("대기 중인 주문만 확정할 수 있습니다"); } this.status = OrderStatus.CONFIRMED; } public void cancel() { if (this.status == OrderStatus.CONFIRMED) { throw new IllegalStateException("확정된 주문은 취소할 수 없습니다"); } this.status = OrderStatus.CANCELLED; } } // 주문 상태 Enum public enum OrderStatus { PENDING("대기"), CONFIRMED("확정"), CANCELLED("취소"), COMPLETED("완료"); private final String description; OrderStatus(String description) { this.description = description; } public String getDescription() { return description; } }
주제와 관련하여 주목할 내용
카테고리 | 주제 | 핵심 항목 | 설명 |
---|---|---|---|
구조 원칙 | 계층화 구조 | 관심사의 분리 (Separation of Concerns) | 기능/책임에 따라 명확하게 계층 분리 |
책임 분리 | 단일 책임 원칙 (SRP) | 각 계층이 하나의 목적/역할만 수행 | |
인터페이스 기반 설계 | 계약 기반 통신 (계약 명세, API 스펙) | 계층 간 통신을 인터페이스로 추상화 | |
의존성 관리 | 의존성 역전 원칙 (DIP) | 상위 계층이 추상에 의존하고 하위 계층은 구현에 집중 | |
설계 패턴 | 프레젠테이션 패턴 | MVC, MVVM, MVP | 표현 계층의 책임 분리 및 테스트 용이성 향상 |
퍼시스턴스 패턴 | Repository, Unit of Work | DB 접근 로직 추상화 및 트랜잭션 관리 일관화 | |
비즈니스 로직 패턴 | Service Layer, Use Case Pattern | 도메인과 유스케이스 단위의 명확한 로직 구현 | |
현대 아키텍처 | 도메인 중심 설계 | DDD, Use Case 중심 | 도메인에 초점 맞춘 구조로 계층 배치 |
구조 확장 패턴 | Hexagonal, Onion, Clean Architecture | 계층 간 의존 방향을 제어하고 도메인 독립성 확보 | |
이벤트 중심 패턴 | Event Sourcing, CQRS | 읽기/쓰기 분리 및 변경 이력 추적 | |
운영 전략 | 테스트 자동화 | 단위/통합/E2E 테스트 전략 | 계층별 테스트 전략 명확화 및 자동화 |
배포 전략 | CI/CD, Blue-Green, 독립 배포 | 각 계층의 독립 릴리즈 및 배포 안정성 확보 | |
모니터링 및 품질 | APM, 로깅, 트레이싱 | 계층별 성능 병목 식별 및 운영 품질 확보 | |
보안 설계 | 계층별 인증/인가, 최소 권한 원칙 | 보안 경계를 계층별로 정의하여 리스크 최소화 | |
기술 전략 | API 관리 | API Gateway, API-First | 통합 진입점 구성 및 API 중심 설계 우선 |
캐싱 구조 | 다계층 캐싱 (프론트, 백엔드, DB) | 요청 처리 최적화 및 응답 시간 개선 | |
확장성 설계 | 클라우드 네이티브, 마이크로서비스화 | Layered 구조에서 유연한 확장성과 서비스 전환 지원 | |
진화 경로 | 아키텍처 발전 | 3-Layer → Modular → Hexagonal → Clean | 계층 구조의 책임 분리 방식 진화 및 도메인 중심화 |
조직 연계 설계 | Conway’s Law 기반 조직 - 구조 정렬 | 조직 구조와 아키텍처 구조를 일치시켜 협업 효율성 향상 |
구조 원칙
Layered Architecture 의 핵심은 관심사의 분리와 책임 명확화다. 각 계층은 명확한 역할을 가지며, 인터페이스 기반으로만 통신하도록 하여 의존성과 복잡도를 낮춘다. DIP 의 적용은 도메인 보호와 유연한 계층 교체에 필수적이다.설계 패턴
표현 계층에서는 MVC, MVVM 등으로 UI 와 로직을 분리하고, 퍼시스턴스 계층에서는 Repository/UoW 로 DB 접근을 캡슐화한다. 비즈니스 로직은 Service Layer 혹은 Use Case 로 도메인 로직을 정제하여 처리 흐름을 명확하게 한다.현대 아키텍처
전통적인 Layered 구조를 발전시켜 도메인을 중심에 두는 Hexagonal, Clean, Onion 아키텍처로 진화해왔으며, 이들은 계층의 방향성과 독립성을 강조한다. CQRS, Event Sourcing 등은 이벤트 기반 시스템과 확장성에 대응하는 데 적합하다.운영 전략
Layered 구조는 테스트 자동화와 계층별 CI/CD 파이프라인 구축에 유리하다. APM, 분산 트레이싱 등을 통해 병목을 추적하고, 보안 경계 설정을 통해 각 계층마다 인증·인가 체계를 분리 적용할 수 있다.기술 전략
API Gateway 를 통한 통신 집약, API-First 를 통한 설계 주도 접근, 계층별 캐싱 구조는 성능과 유지보수 측면에서 중요한 전략이다. Layered 구조는 클라우드 및 마이크로서비스로의 점진적 전환 기반으로도 적합하다.진화 경로
3 계층 아키텍처에서 시작해 모듈화된 구조, Hexagonal/Onion/Clean 아키텍처로 발전하는 과정은 책임 분리의 명확화와 의존성 관리의 엄격화 방향으로 진화해왔음을 보여준다. Conway’s Law 기반의 조직 - 아키텍처 정렬은 실무에 필수적이다.
반드시 학습해야 할 내용
카테고리 | 학습 주제 | 핵심 항목 | 설명 |
---|---|---|---|
아키텍처 개념 | 계층 아키텍처의 원리 | SoC, DIP, 계층 최소화 원칙 | 관심사의 분리 (SoC), 의존성 역전 (DIP) 및 불필요 계층 제거로 단순하고 테스트 가능한 구조 설계 |
설계 원칙 | 객체지향 설계 원칙 | SOLID, SRP, OCP, ISP | 계층 간 유연성·독립성을 유지하기 위한 객체지향 기반 원칙 |
설계 전략 | 도메인 기반 설계 | DDD, Bounded Context, Aggregate, Use Case | 도메인 중심으로 계층을 나누고 비즈니스 규칙과 기술 구현을 분리 |
아키텍처 스타일 | 현대적 계층화 패턴 | Clean, Onion, Hexagonal, Modular Monolith | 전통 Layered 구조를 개선한 구조로, 도메인 중심성과 테스트 용이성 확보 |
계층 간 통신 | API 및 데이터 전달 방식 | REST, gRPC, GraphQL, DTO | 계층 간 경계 명확화 및 표준화된 통신 방식을 통해 통합 용이성 향상 |
구현 기술 | 의존성 제어 및 추상화 | DI (Dependency Injection), IoC, 인터페이스 분리 | 느슨한 결합 및 테스트 가능성 확보를 위한 핵심 기술 |
ORM 기반 DB 접근 | JPA, Hibernate, EF Core | DB 계층의 추상화를 통해 비즈니스 로직과 인프라 분리 | |
웹 및 백엔드 프레임워크 | Spring Boot, Express.js, ASP.NET | 프레임워크별 계층 구성 특성을 이해하고 선택적으로 활용 | |
테스트 전략 | 테스트 계층화 | 단위 테스트, 통합 테스트, Mock/Stub | 계층별 독립 테스트 및 계약 기반 검증 전략 구축 |
성능 최적화 | 병목 해소 전략 | 캐싱 (Redis), CQRS, 병렬 처리, 비동기 메시징 | 계층 간 성능 저하를 방지하기 위한 기술 기반 최적화 전략 적용 |
보안 설계 | 보안 계층화 및 인증 관리 | JWT, OAuth2, TLS, 입력 검증, RBAC | 보안은 각 계층에서 책임을 분산하고 일관된 방식으로 처리되어야 함 |
운영 및 배포 | 배포 전략 및 자동화 | CI/CD (GitHub Actions, GitLab CI), 컨테이너, Kubernetes | 계층별 독립 배포 및 운영 효율성을 위한 클라우드 기반 배포 전략 설계 |
관측 및 유지보수 | 관측 가능성 및 운영 인프라 | APM, 분산 트레이싱, ELK, OpenTelemetry | 계층 간 병목 분석, 장애 추적, 서비스 가시성 확보를 위한 필수 도구 |
횡단 관심사 | 공통 기능 분리 | 로깅, 트랜잭션, 인증/인가 처리 (AOP, Middleware) | 코드 중복 최소화 및 관심사의 분리 적용으로 코드 품질과 재사용성 향상 |
조직 및 협업 | 아키텍처 - 팀 연계 | 팀 구조 ↔ 아키텍처 정렬 (Conway’s Law), 협업 구조 설계 | 기술 구조와 조직 구조를 일치시켜 생산성과 품질을 높임 |
아키텍처 개념과 설계 원칙:
계층형 구조는 관심사 분리와 의존성 방향 제어가 핵심이다. SOLID 원칙과 함께 책임의 경계를 분명히 하고, 과도한 계층 분리를 지양해야 한다.도메인 기반 전략:
DDD (Domain-Driven Design) 를 활용하면 복잡한 비즈니스 로직을 도메인 중심으로 효과적으로 분리할 수 있으며, 이는 Clean/Onion 아키텍처와 밀접하게 연관된다.현대적 아키텍처 스타일:
Hexagonal, Onion, Modular Monolith 등은 전통 Layered 아키텍처의 한계를 보완하며, 테스트 용이성과 기술 독립성을 강화하는 방향으로 발전해왔다.계층 간 통신 및 구현 기술:
API 는 계층 간 경계를 유지하기 위한 핵심 수단이며, 의존성 주입 (DI) 과 인터페이스 분리는 구현 복잡도를 줄이고 테스트 유연성을 확보하는 데 중요하다.테스트 및 최적화:
테스트는 계층마다 독립적으로 이루어져야 하며, 병목은 캐시, CQRS, 비동기 처리 등으로 최소화되어야 한다.보안 및 운영:
계층별 보안은 입력 검증, 인증/인가, 암호화 등으로 강화되어야 하고, 배포는 자동화된 CI/CD 파이프라인과 클라우드 기반 인프라를 활용해 민첩성을 확보한다.유지보수와 관측성:
운영 단계에서는 로그 수집, 트레이싱, APM 도구를 통해 시스템 내부 상태를 가시화하고 장애에 신속히 대응해야 한다.Cross-cutting Concern:
로깅, 인증, 트랜잭션 등 공통 기능은 AOP 나 미들웨어로 분리해 계층의 순수성과 코드 일관성을 유지해야 한다.조직과 아키텍처의 일치:
아키텍처 설계는 팀 구조와도 밀접히 연결되며, 조직의 커뮤니케이션 방식과 아키텍처 방향성이 일치해야 운영과 개발의 효율성이 극대화된다.
용어 정리
카테고리 | 용어 (한글/영문) | 설명 |
---|---|---|
아키텍처 스타일 | Layered Architecture | 기능과 책임을 논리적 계층으로 나눈 전통적 아키텍처 스타일 |
N-Tier Architecture | 논리 계층을 물리적으로 분리하여 독립 배포 가능한 다층 구조 | |
Clean Architecture | 도메인을 중심으로 설계된 동심원 아키텍처 스타일 | |
Hexagonal Architecture | Port & Adapter 기반, 외부 의존성과 도메인 분리 | |
Onion Architecture | 도메인을 중심으로 여러 계층이 감싸는 구조 (Clean 과 유사) | |
Serverless Architecture | 함수 단위 컴퓨팅으로 운영하며 인프라를 숨기는 클라우드 아키텍처 | |
계층 구성 요소 | Presentation Layer | 사용자 인터페이스 및 입력 처리 계층 |
Application Layer | 유스케이스 조정, 서비스 흐름 제어 | |
Domain Layer | 비즈니스 규칙과 도메인 모델 구현 계층 | |
Data Access Layer / Persistence Layer | DB 접근 및 저장 책임을 담당하는 계층 | |
설계 원칙 | Separation of Concerns (SoC) | 관심사 분리를 통해 책임 분리 및 응집도 향상 |
Single Responsibility Principle (SRP) | 각 모듈/계층은 하나의 책임만 가져야 함 | |
Dependency Inversion Principle (DIP) | 고수준 모듈이 저수준 구현이 아닌 추상에 의존 | |
Open-Closed Principle (OCP) | 확장에는 열려 있고, 수정에는 닫혀야 함 | |
Encapsulation / Abstraction | 계층 내부 구현을 외부로부터 은닉, 추상화된 인터페이스 제공 | |
설계/구현 패턴 | Service Layer | 유즈케이스 단위 비즈니스 로직 담당 |
DTO (Data Transfer Object) | 계층 간 데이터 전달을 위한 단순 구조체 | |
DAO (Data Access Object) | DB 와의 연결을 추상화하는 객체 | |
Repository Pattern | 도메인 객체 중심의 데이터 접근 추상화 | |
Dependency Injection (DI) | 의존성을 외부에서 주입하는 구현 방식 | |
Inversion of Control (IoC) | 제어 흐름을 프레임워크나 외부로 위임하는 설계 방식 | |
Facade Pattern | 복잡한 내부 로직을 단순 인터페이스로 감싸는 패턴 | |
운영 전략 및 DevOps | API Gateway | 인증, 라우팅, 보안 정책을 적용하는 중앙 진입점 |
CI/CD | 지속적 통합 및 배포 자동화를 통해 개발 주기 단축 | |
Infrastructure as Code (IaC) | 인프라를 코드로 정의하여 버전 관리 및 자동화 가능 | |
Container Orchestration | 컨테이너 배포 및 확장, 복구 등을 자동화 (예: Kubernetes) | |
Blue-Green Deployment | 무중단 배포 전략으로 새/이전 버전 병행 운영 후 전환 | |
성능 최적화 전략 | Caching | 반복되는 데이터를 임시 저장하여 응답 시간 개선 |
Batch Processing | 대량 데이터를 일괄 처리하여 시스템 효율성 확보 | |
Autoscaling | 트래픽에 따라 자원을 자동으로 증가/감소 | |
현대 아키텍처 기법 | Event Sourcing | 상태 변화를 이벤트로 기록하고 재구성하는 패턴 |
CQRS | 읽기/쓰기 모델을 분리하여 성능과 유지보수성 향상 | |
보안 및 품질 | Authentication / Authorization | 사용자 신원 확인 및 권한 검증 |
Input Validation | 외부 입력에 대한 유효성 및 보안 검증 | |
Contract Testing | API 간 계약 명세 기반의 호환성 테스트 | |
Static Analysis | 코드 실행 없이 문법 및 품질 분석 | |
테스트 기법 | Unit Test | 가장 작은 코드 단위를 테스트 |
Mock | 외부 의존성을 대체하여 독립적인 테스트 환경 구성 | |
운영/모니터링 | Observability | 메트릭, 로그, 트레이스 기반의 상태 관측 및 분석 |
APM | 애플리케이션 성능을 실시간으로 모니터링하는 시스템 | |
Distributed Tracing | 마이크로서비스 간 요청 흐름을 추적하는 기술 | |
DDD 구성 요소 | Aggregate | 일관성 경계를 가진 도메인 객체 묶음 |
Entity | 고유 식별자가 있는 도메인 객체 | |
Value Object | 식별자 없이 값만으로 의미를 가지는 객체 | |
Domain Event | 도메인에서 발생한 의미 있는 상태 변화 | |
계층 인터페이스 | Port | 외부 세계와 내부 도메인을 연결하는 추상화된 인터페이스 |
Adapter | 실제 기술 스택과 포트를 연결하는 구현체 | |
안티패턴 | Architecture Sinkhole | 처리 없이 계층을 단순 통과하는 설계 오류 |
God Object | 너무 많은 책임을 가진 클래스/객체 | |
Tight Coupling | 계층 간 강한 종속으로 인한 유지보수 어려움 | |
기타 개념 | Conway’s Law | 조직의 커뮤니케이션 구조가 시스템 구조에 반영된다는 법칙 |
Multi-Tenancy | 하나의 인스턴스로 여러 고객을 동시에 서비스하는 구조 | |
Edge Server | 사용자와 가까운 위치에서 처리하는 서버 (지연 감소 목적) |
참고 및 출처
- Martin Fowler – Layered Architecture Pattern
- Microsoft – The n-Tier Architecture and Layered Architecture Guide
- Strangler Fig Pattern
- Hexagonal Architecture (Ports & Adapters)
- Service Layer Pattern – Martin Fowler
- Systematic Review of Layered Architecture Principles
- Red Hat – Layered Architecture Overview
- Backend Evolution: N-Layered → DDD → Hexagonal
- Clean/Hexagonal vs Traditional Layered Architectures