Notice
Recent Posts
Recent Comments
Link
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 |
Tags
- 내일배움카드
- UXUI챌린지
- 오픈챌린지
- 오블완
- 백엔드
- Spring
- 백준
- UXUI기초정복
- 부트캠프
- UXUIPrimary
- 내일배움캠프
- 백엔드개발자
- 환급챌린지
- 티스토리챌린지
- OPENPATH
- baekjoon
- Java
- 디자인강의
- 디자인챌린지
- 국비지원
- 객체지향
- 오픈패스
- Be
- 백엔드 부트캠프
- mysql
- 패스트캠퍼스
- 디자인교육
- KDT
- 국비지원취업
- 국비지원교육
Archives
- Today
- Total
군만두의 IT 공부 일지
[스터디2] 04. 안티패턴 및 서비스 본문
목차
이 책의 2부에서는 저처럼 스프링에 대해서 배운지 얼마 안 된 개발자들이 놓치는 부분을 알려줍니다. 책을 공부하면서, '내가 지금까지 트랜잭션 스크립트 같은 안티패턴을 사용하고 있었구나'하고 반성하게 되는 것 같습니다. 각종 컴포넌트와 DTO 구현에 대한 것도 많이 배워가는 것 같습니다.
6. 안티패턴
6.1 스마트 UI
- 스마트 UI(User Interface) 패턴: 시스템의 UI 레벨에서 너무 많은 업무를 처리하고 있는 경우
- 스마트 UI는 데이터 입출력을 UI 레벨에서 처리함.
- 스마트 UI는 비즈니스 로직도 UI 레벨에서 처리함.
- 스마트 UI는 데이터베이스와 통신하는 코드도 UI 레벨에서 처리함.
- → 백엔드 개발자도 백엔드 개발자의 UI(백엔드 API)를 신경써야 함.
- 컨트롤러(Controller)는 API를 만드는 컴포넌트임. 따라서 스마트 UI는 '스마트 컨트롤러'라고 볼 수 있고, 다음과 같은 경우를 의미함.
- 컨트롤러의 핸들러 메서드에 지나치게 많은 로직이 들어있는 경우
- 비즈니스 로직을 컨트롤러 수준에서 갖고 있는 경우
- 컨트롤러 같은 UI 코드는 사용자의 입출력을 받고 어떤 비즈니스 로직을 실행할지 결정하는 역할만 해야 함.
- 컨트롤러의 역할 재정의
- API 호출 방식을 정의함.
- 어떤 비니스 로직을 실행할 것인지 결정함.
- API 호출 결과를 어떤 포맷으로 응답할지 정의함.
- 스마트 UI 장단점
- 장점: 빠르게 개발할 수 있어 생산성이 높고, 이해하기 쉬우며 작성하기 쉬움. → MVP(최소 기능 제품)를 만들 때 유용함.
- 단점: 확장성과 유지보수성이 떨어짐.
- 컨트롤러(Controller)는 API를 만드는 컴포넌트임. 따라서 스마트 UI는 '스마트 컨트롤러'라고 볼 수 있고, 다음과 같은 경우를 의미함.
6.2 양방향 레이어드 아키텍처
- 양방향 레이어드 아키텍처(bidirectional layered architecture): 레이어드 아키텍처를 지향하는 프로젝트에서 많이 발생하는 안티패턴. 레이어드 아키텍처에서 정의한 레이어들의 의존 관계에 양방향 의존이 발생하는 경우.
- 레이어드 아키텍처: 레이어라고 불리는 분류 체계를 사용함. 레이어드 아키텍처의 형태는 프로젝트마다 다름.
- 프레젠테이션 레이어(presentation layer): 사용자와의 상호작용을 처리하고 결과를 표시하는 역할을 담당함. 스프링에서 컨트롤러와 같은 컴포넌트가 이곳으로 모임.
- 비즈니스 레이어(business layer): 애플리케이션의 비즈니스 로직을 처리하는 역할을 함. 스프링에서 주로 서비스 컴포넌트가 이곳으로 모임.
- 인프라스트럭처 레이어(infrastucture layer): 외부 시스템과의 상호작용을 담당함. 스프링에서 JDBC나 JPA, 하이버네이트 관련 코드들이 이곳에 배치됨.
- 양방향 레이어드 아키텍처: 레이어드 아키텍처가 반드시 지켜야 할 가장 기초젝인 제약(레이어 간 의존 방향은 단방향을 유지해야 함)을 위반할 때
- 하위 레이어에 있는 컴포넌트가 상위 레이어에 존재하는 모델을 이용하는 경우 → 양방향 의존 관계. 즉, 순환 참조가 생김.
- 레이어드 아키텍처: 레이어라고 불리는 분류 체계를 사용함. 레이어드 아키텍처의 형태는 프로젝트마다 다름.
/* 레이어드 아티켁처 패키지 구조(책 예제 요약) */
com.example.cafeapp
│
├── presentation
│ └── Controller // 웹 요청을 처리하는 컨트롤러
│
├── business
│ └── Service // 비즈니스 로직을 수행하는 서비스
│
└── infrastructure
│ └── JpaRepository // 데이터베이스 접근을 위한 리포지토리 구현체
│
└── core
├── User.jav // 사용자 도메인 모델
├── Cafe.java // 카페 도메인 모델
├── Board.java // 게시판 도메인 모델
└── Post.java // 게시글 도메인 모델
레이어 간에 양방향 참조가 생겼을 때 해결하는 방법으로 아래 2가지가 있음.
6.2.1 레이어별 모델 구성
- 레이어별로 모델을 따로 만드는 것
- 예) 하위 레이어인 서비스 계층에서 상위 레이어인 API 레이어의 모델에 접근하는 경우, 비즈니스 레이어에서 사용할 모델을 추가로 만드는 것임. 클라이언트 요청을 받는 모델과 실제로 객체를 생성하는 DTO 모델을 분리하면 해결할 수 있음.
- ~Request 클래스는 API 요청을 처리하는 모델임.
- ~Command 클래스는 서비스에 어떤 생성, 수정, 삭제 요청을 보낼 때 사용하는 DTO임.
- 장점: 클라이언트가 API 요청을 보내는 시점의 요청 본문(request body)과 서비스 컴포넌트에서 사용하는 DTO를 분리할 수 있음.
- 단점: 작성해야 하는 코드의 양이 늘어남. 즉, 비용이 증가함.
- 따라서 모델을 적당히 세분화하고 통합해야 함. 하지만 몇몇 멤버 변수가 겹친다고 데이터 모델을 애매하게 공유하는 것보다 역할과 책임에 따라 확실하게 모델을 구분하는 편이 나음.
6.2.2 공통 모듈 구성
- 공통으로 참조하는 코드를 별도의 모듈로 분리하는 것
- 예) 모든 레이어가 단방향으로 참조하는 공통 모듈을 만들고, PostCreateRequest 클래스 같은 모델을 거기에 배치하는 것. core는 모듈이며 레이어가 아님.
6.3 완화된 레이어드 아키텍처
Q. 컨트롤러가 리포지터리를 사용하는 것은 괜찮을까?
- A. 컨트롤러가 리포지터리를 사용해서는 안 됨.
- 2개 이상의 레이어를 건너뛰어 통신하는 구조도 안티패턴으로 분류함.
- 완화된 레이어드 아키텍처(relaxed layered architecture): 상위 레이어에 모든 하위 레이어에 접근할 수 있는 권한을 주는 구조. 레이어드 아키텍처에서 제약(레어어드 간 통신은 인접한 레이어에서만 이뤄저야 함)을 완화함.
- 예) 프레젠테이션 레이어에 위치한 컨트롤러가 인프라스트럭처 레이어에 위치한 JpaRepository를 멤버 변수로 가지고 있음.
- 단점: 기능 개발을 위한 코드가 어디에 들어가는지 한눈에 파악하기 힘듦.
6.4 트랜잭션 스크립트
- 트랜잭션 스크립트(transaction script): 비즈니스 레이어에 위치하는 서비스 컴포넌트에서 발생하는 안티패턴. 서비스 컴포넌트의 구현이 사실상 어떤 트랜잭션이 걸려있는 스크립트를 실행하는 것처럼 보일 때. 스마트 UI와 비슷하게 '스마트 서비스'라고 볼 수 있음.
- 단점: 객체지향보다 절차지향에 가깝기 때문에 절차지향의 문제점을 가짐. 변경 및 확장에 취약하며, 업무가 병렬 처리되기 어려움.
Q. 비지니스 로직은 어디에 위치해야 할까요?
- A1. 비즈니스 로직은 서비스 컴포넌트에 있어야 함.(△)
- A2. 비즈니스 로직은 도메인 모델에 위치해야 함.(○)
- 비즈니스 로직이 처리되는 주(main) 영역은 서비스 컴포넌트가 아닌 도메인 모델이어야 함. 서비스는 도메인을 불러와서 도메인에 일을 시키는 정도의 역할만 해야 함.
- 서비스는 도메인 객체나 도메인 서비스라고 불리는 도메인에 일을 위임하는 공간이어야 함.
7. 서비스
- 서비스의 역할
- 도메인 객체를 불러옴.
- 도메인 객체나 도메인 서비스에 일을 위임함.
- 도메인 객체의 변경 사항을 저장함.
7.1 Manager
- 스프링에서 컨트롤러는 제어부이고, 리포지터리는 저장소이며, 컴포넌트는 구성 요소임. 그리고 서비스는 DDD(Domain-Driven Design, 도메인 주도 설계)에서 영감을 받아 파생된 개념임.
- DDD: 도메인을 중심에 놓고 소프트웨어를 설계하는 개발 방법론
- 도메인(domain): 비즈니스 영역이자 우리가 해결하고 싶은 문제 영역
- 예) 은행 시스템의 도메인은 은행임. DDD에서 개발자는 도메인(은행)에 대해서도 잘 알고 있어야 함. 하지만 이는 어렵기 때문에 도메인 탐색 과정이 도메인 전문가(은행원)과 소통하면서 이루어짐.
- 서비스는 도메인 객체가 처리하기 애매한 연산 자체를 표현하기 위한 컴포넌트임.
- 예) 물건을 파는 사이트의 도메인에 상품(Product), 쿠폰(Coupon), 사용자(User)의 마일리지(Mileage)가 있음. 물건의 가격을 계산하는 로직을 표현하기 위해 PriceManager 클래스를 만듦.
- 가격 계산 로직 같은 모든 도메인 객체가 처리하기 애매한 연산이나 행동을 매니저 클래스를 만들어 해결함. 이렇게 만든 매니저(Manager) 클래스가 서비스임.(7.2에서 이어짐)
- ~Manager 클래스는 접두어에 있는 모델을 관리하는 클래스임.
- 스프링의 서비스 컴포넌트의 역할
- 저장소에서 데이터를 불러옴.
- 네트워크 호출 결과를 정리해서 객체에 넘겨줌.
- 저장소에 데이터를 저장함.
- 도메인 서비스: 도메인 개발에 필요하지만 객체로 표현하기 애매한 로직을 처리하는 서비스
- 애플리케이션 서비스: 애플리케이션 개발에 필요하지만 객체로 표현하기 애매한 로직을 처리하는 서비스
- 예) PriceManager은 도메인에 필요한 비즈니스 업무 규칙을 가진 도메인에 가까운 로직(도메인 서비스)이지만, 스프링에서 @Service 애너테이션으로 만든 ProductService는 애플리케이션이 돌아가는 데 필요한 연산을 갖고 있는 서비스(애플리케이션 서비스)임.
분류 | 역할 | 주요 행동 | 예시 |
도메인 | 비즈니스 로직을 처리 | - 도메인 역할을 수행함. - 다른 도메인과 협력함. |
User, Product, Coupon |
도메인 서비스 | 비즈니스 연산 로직을 처리 | - 도메인 협력을 중재함. - 도메인 객체에 기술할 수 없는 연산 로직을 처리함. |
PriceManager |
애플리케이션 서비스 | 애플리케이션 연산 로직을 처리 | - 도메인을 저장소에서 불러옴. - 도메인 서비스를 실행함. - 도메인을 실행함. |
ProductManager |
7.2 서비스보다 도메인 모델
- 앞에서 설명한 가격 계산 로직은 새로운 도메인 객체를 만들어 도메인 객체로 표현할 수 있음.
- 예) PriceManager를 Cashier(점원)으로 표현하여, 도메인 모델을 만들어 가격을 계산하는 로직을 만들었음.
- 객체지향으로 보는 서비스
- 서비스는 가능한 한 적게 만들고, 얇게 유지해야 함.
- 서비스보다 풍부한 도메인 모델을 만들어야 함.
- 개발 우선순위: 도메인 모델 > 도메인 서비스 > 애플리케이션 서비스
7.3 작은 기계
- 서비스는 항번 생성하면 여러 번 사용하지만 그 자신은 바꿀 수 없음. → 불변성
- 서비스를 필드 주입이나 수정자 주입을 이용해서 초기화하지 말고 생성자 주입을 사용해야 함.
- 생성자 주입을 사용하면 명시적으로 의존성을 표현할 수 있음.
- 생성자 주입을 사용하면 테스트하기가 쉬워짐.
- 생성자 주입을 사용하면 순환 의존성을 방지할 수 있음.
- 필드 주입을 사용하는 이유 중 하나는 생성자가 존재하는 것이 미관상 깔끔하지 않다고 함.@RequiredArgsConstructor 애너테이션과 final 키워드를 이용하면 코드를 미관상 좋게 만들 수 있음.
- 서비스를 필드 주입이나 수정자 주입을 이용해서 초기화하지 말고 생성자 주입을 사용해야 함.
- 서비스는 작은 기계처럼 영원히 실행할 수 있음.
7.4 조언
- 서비스와 관련된 행동 조언
- 서비스의 멤버 변수는 모두 final로 만듦.
- 서비스에 세터가 존재한다면 지움.
- 서비스는 반드시 생성자 주입으로 바꿈.
- 서비스의 비즈니스 로직을 도메인에 양보함.
- 서비스를 얇게 유지함.
스마트 서비스처럼 서비스가 비즈니스 로직을 다 처리하게 코드를 짰었는데, 도메인에서 비즈니스 로직을 처리할 수 있도록 개선해야 할 것 같습니다. 책을 학습할 때마다 책에서 말하는 개발자의 실수를 제가 다 저지르고 있었다는 걸 알게 되네요.
이 글은 『자바/스프링 개발자를 위한 실용주의 프로그래밍』 책을 학습한 내용을 정리한 것입니다.
'프로그래밍 > 객체지향' 카테고리의 다른 글
[스터디2] 06. 모듈 (0) | 2025.01.15 |
---|---|
[스터디2] 05. 레이어드 아키텍처 (0) | 2025.01.13 |
[스터디2] 03. SOLID 및 순환 참조 (0) | 2024.12.20 |
[스터디2] 02. 행동 및 SOLID (1) | 2024.12.15 |
[스터디2] 01. 객체의 종류 (2) | 2024.12.07 |
Comments