2부 스프링과 객체지향 설계)
스프링 프레임워크는 자바 기반의 오픈소스 애플리케이션이다. 특히 스프링 부트의 등장은 스프링의 사용성을 끌어올렸다.
- 트랜잭션 스크립트란?
- 레이어드 아키텍처란?
- 서비스는 왜 서비스라고 부를까?
- 서비스는 왜 생성자 주입을 사용해야 할까?
- 모듈이란 뭘까?
- 패키지는 어떻게 구분하는 것이 좋을까? 레이어, 도메인 순서로 구성하는 것이 좋을까, 도메인,레이어 순서로 구성하는 것이 좋을까?
- 애플리케이션의 핵심은 무엇일까, 스프링? JPA?
Chapter 6. 안티 패턴
스프링 개발자가 많이 저지르는 '구조적 실수'가 있다. 개발엔 정답이 없지만 유지보수나 확장성 관점에서 좋지 못하다 알려진 안티패턴은 존재한다.
6.1 스마트 UI
스마트 UI 패턴은 유명한 안티패턴이다. 스마트 UI는 다음과 같은 코드를 말한다.
- 스마트 UI는 데이터 입출력을 UI 레벨에서 처리한다.
- 스마트 UI는 비즈니스 로직도 UI 레벨에서 처리한다.
- 스마트 UI는 데이터베이스와 통신하는 코드도 UI 레벨에서 처리한다.
즉, 스마트 UI란 시스템의 UI 레벨에서 너무 많은 업부를 처리하고 있는 경우를 말한다.
백엔드 개발자도 '백엔드 개발자의 UI'를 신경 써야 한다. 이는 곧 GUI 같은 것이 아닌 백엔드 API를 뜻한다. 물론 API와 UI는 다른 용어이다. API는 Application Programming Interface의 약자로 애플리케이션 프로그래밍을 위해 마련된 인터페이스를 뜻한다. 반면 UI는 User Interface의 약자로 사용자를 위해 마련된 인터페이스를 뜻한다. 그래서 이 둘은 누군가에게 소프트웨어를 사용하는 방법을 알려주기 위해 만들어진 인터페이스라는 점에서 같지만 대상이 다르다.
하지만 이를 백엔드 개발자 관점에서 바라본다면, 맥락상 같은 의미로 해석할 수 있다. 왜냐하면 백엔드 서버의 사용자는 프론트엔드 개발자나 협력 서비스의 개발자이고, 이들과 소통하기 위해 사용되는 인터페이스가 API이기 때문이다.
백엔드 개발자에게 API는 UI이고, 컨트롤러는 API를 만드는 컴포넌트이다. 그렇다면, 컨트롤러는 스프링에서 UI를 만드는 도구라고 볼 수 있다. 따라서 스마트 UI는 컨트롤러의 핸들러 메서드에 지나치게 많은 로직이 들어있는 경우를 의미할 것이다. 따라서 이러한 맥락에서 본다면 스마트 UI는 스마트 컨트롤러로 부를 수도 있다.
일반적으로 UI는 사용자의 입출력을 위한 창구로만 사용돼야 한다. 입력을 받고 이를 뒷단으로 넘겨 비즈니스 로직을 실행하는 역할 정도여야 한다. 하지만 스마트 UI 패턴을 따르는 코드에서는 그렇지 않다. 컨트롤러 같은 UI코드에 과한 책임이 할당돼 있다. 즉, 스마트 UI는 비즈니스 로직을 UI 수준에서 가지고 있는 경우를 뜻한다.
우리는 스마트 UI 패턴 사례를 통해 '컨트롤러'의 역할은 무엇이 되어야 하는지 한 번 생각해 볼 수 있다.
- API 호출 방식을 정의한다.
- 어떤 비즈니스 로직을 실행할 것인지 결정한다.
- API 호출 결과를 어떤 포맷으로 응답할지 정의한다.
컨트롤러의 가장 큰 역할은 엔드포인트를 정의하고 API 사용자의 요청을 받아 그 결과 응답 포맷에 맞춰 반환하는 것이다. 컨트롤러에 비즈니스 로직이 있어서는 안되고, 데이터베이스 관련 로직이 있어서도 안된다. 왜냐하면 이러한 로직을 컨트롤러에 두는 것이 컨트롤러의 목적과 다르기 때문이다. 이는 명백히 책임을 과하게 부여하는 행동이다.
물론 스마트 UI에 단점만 있는 것은 아니다. 빠르게 개발할 수 있고, 그래서 생산성이 높다. 모든 코드가 하나의 메서드에 집중돼 있기 떄문에 이해하기도 쉽고 작성하기도 쉽다. 따라서 이 방식은 흔히 얘기하는 MVP(최소 기능 제품)을 만들 때 유용하다.
하지만 실무에서 클라이언트가 요청하는 요구사항들은 이런 방법을 써서 해결해도 지속될 수 있을 만큼 단순하지 않다. 처음에는 빠르게 개발할 수 있어 좋을지 몰라도 요구사항이 많아지고 복잡해짐에 있어서 나중에는 유지보수가 불가능한 코드가 된다.
더불어 스마트 UI는 객체지향보다는 절차지향에 가까운 사례이다. 객체들은 협력하지 않고, 애초에 객체라고 부를 것이 존재하지 않는다. (ex. User, Cafe, Board와 같은 객체들은 단지 데이터 덩어리일 뿐이다.) 따라서 데이터를 보고, 판단하고, 저장해서, 응답을 반환하기까지 객체들은 능동적으로 협력하지 않는다.
따라서 스마트 UI는 분명한 안티패턴이다. 이 패턴을 반면교사 삼아 그동안 컨트롤러를 되돌아봐야한다. 컨트롤러의 역할은 무엇인지 고민해 보고, 실제로 그 역할만 하고 있는지 돌이켜봐야 한다. 기능 개발을 빨리 해야 하는 조직에서 이 패턴은 해결책이 될 수 있겠지만 유지보수 가능한 코드와 객체지향을 목표로 한다면 이런 상황을 반드시 피해야 한다.
6.2 양방향 레이어드 아키텍처
두 번째 안티패턴은 양방향 레이어드 아키텍처(bidirectional layered architecture)이다. 이는 레이터드 아키텍처를 지향하는 프로젝트에서 많이 발생하는 안티패턴이며, 레이어드 아키텍처에서 정의한 레이어들의 의존 관계에서 양방향 의존이 발생하는 경우를 칭한다.
레이어드 아키텍처는 소프트웨어 시스템을 설계하는 방식 중 하나로, 이름에서도 알 수 있듯이 '레이어'라고 불리는 분류 체계를 사용한다. 그리고 이 아키텍처를 따르는 애플리케이션은 개발하기 전에 레이어를 먼저 정의하는데, 보편적으로 다음과 같은 3개의 레이어를 사용한다.
- 프리젠테이션 레이어(Presentation Layer): 이 레이어에서는 사용자와 상호작용을 처리하고 결과를 표시하는 역할을 담당한다. 그리고 이 역할을 처리하는 대표적인 스프링 컴포넌트가 하나가 있는데, 바로 컨트롤러 컴포넌트이다. 따라서 이 레이어는 쉽게 설명해서 컨트롤러 같은 컴포넌트가 모이는 곳이라 볼 수 있다.
- 비즈니스 레이어(Business Layer): 이 레이어에서는 애플리케이션의 비즈니스 로직을 처리하는 역할을 한다. 그래서 데이터의 유효성 검사, 데이터 가공, 비즈니스 규칙 적용 등의 일이 이 레이어에서 이뤄진다. 이러한 성격 때문에 스프링에서는 주로 서비스 컴포넌트가 이곳으로 모인다.
- 인프라스트럭처 레이어(Infrastructure Layer): 이 레이어에서는 외부 시스템과의 상호작용을 담당한다. 예를 들어 대표적인 외부 시스템으로 데이터베이스가 있다. 그래서 스프링에서 데이터에 접근하는 기술인 JDBC나 ORM 프레임워크인 JPA, 하이버네이트 관련 코드들이 이 레이어에 배치된다. 따라서 이 레이어에 주로 들어가는 코드는 데이터를 저장하거나 조회하는 등의 일을 수행하는데, 그러한 이유로 이 레이어는 좁은 의미에서 영속성 레이어(persistence layer)라고 불리기도 한다.
여기서 설명한 레어이드 아키텍처와 각 레이어는 보편적으로 알려진 내용을 기반으로 한다. 그러나 이는 일부 다른 학습 자료와 구분하는 방법이나 설명하는 내용이 다를 수 있다. 레이어드 아키텍처는 다수의 아키텍처들의 경험에 의해 만들어진 설계 방식이기 때문에 각 개발자마다 이 아키텍처를 받아들이고 이해나는 수준이 모두 다르다. 이러한 이유로 레이어드를 사용하고 있는 프로젝트들도 자세히 보면 형태가 모두 제각각이다. 어떤 조직에서는 레이어드를 4개, 5개로 분류해서 사용하기도 한다. 레이어드 아키텍처에서 레이터를 얼마나 세분화하고 통합할 것이냐는 답이 정해져 있지 않은 온전히 조직의 선택이다.
우선 presentation, business, infrastructure 처럼 큰 패키지를 나누고 그 안에는 이게 각각 Controller, Service, Repository관련 코드가 들어가도록 배치했다고 해보자. 이렇게 구성할 경우 단순하고 직관적인 구조라는 점이 가장 큰 장점이다.
양방향 레이어드 아키텍처는 레이터드 아키텍처를 지향해 개발했지만, 레이어드 아키텍처가 반드시 지켜야 할 가장 기초적인 제약을 위반할 때 지칭하는 말이다. 그리고 여기서 가장 기초적인 제약이란 레이어 간 의존 방향은 단방향을 유지해야 한다라는 것이다. 예를 들어 설명하면, 간혹 레이어드 아키텍처를 사용하는 조직에서는 편의상 하위 레이어에 있는 컴포넌트가 상위 레이어에 존재하는 모델을 이용하는 경우가 발생한다.
Controller 패키지에 있는 DTO를 Controller와 Service에서 같이 쓰는 경우가 바로 하위 레이어인 서비스 계층에서 상위 레이어인 API 레이어의 모델에 접근하는 대표적 사례이다. 그럼 Presentation(ReqeustDto) -> Business -> Infrastructure의 과정에서 사실상 Business -> Presentation(ReqeustDto)이 되기 때문에 양방향으로 의존 관계가 생겼다. 양방향 의존성이 생겼다는 말은 실은 아키텍처 수준에서 순환 참조가 생겼다는 말이다. 5장을 돌이켜 보면 순환 참조가 발생했다는 것은 사실상 '해당 컴포넌트들이 사실상 하나의 컴포넌트가 됐다는 선언이다'라고 했다. 따라서 그 논리를 그대로 사용하면, 양방향 의존성이 생겼다는 말은 분리된 레이어가 하나로 통합됐다는 선언과 같다. 그러므로 양방향 레이어드 아키텍처는 레이어드 아키텍처에서 제일 중요한 계층 관계가 사라진 상황이다.
양방향 레이어드 아키텍처에서 레이어는 더 이상 레이어라 부를 수 없다. 레이어가 컴포넌트를 구분하는 역할밖에 하지 못하기 때문이다. 그렇다면 어떻게 이를 해결해야 할까?
6.2.1 레이어별 모델 구성
각 레이어 별로 모델을 따로 만드는 방법이다. 기존에 PostCreateRequest 모델을 프레젠테이션 레이어에서 사용했다면, 비즈니스 레이어에서 사용할 PostCreateCommand 모델을 추가로 만들어 주는 것이다. 여기서는 다음과 같은 명명 규칙을 사용했다.
1. ~Request 클래스는 API 요청을 처리하는 모델이다.
2. ~Command 클래스는 서비스에 어떤 생성, 수정, 삭제 요청을 보낼 때 사용하는 DTO이다.
이처럼 Request와 Command 클래스를 구분하고, 컨트롤러에서 서비스에 요청을 보낼 때는 Request -> Command로 변경해서 서비스의 메서드를 호출한다. 따라서 의존 방향이 단방향이 되고 순환 참조가 사라진다. 더불에 Request에서는 없었던 작성자를 뜻하는 writeId 같은 멤버 변수를 Command에 추가해서 넣을 수도 있다.
하지만 이런 방식에도 단점은 있다. 코드의 양이 늘어난다. Post관련해서 Post, PostCreateRequest, PostCreateCommand 현재는 3개이지만 앞으로 점점 더 클래스가 늘어나게 될 것이다. 작성해야 하는 코드가 늘어나는 것은 조직 관점에서 비용이 증가한다는 의미이다. 따라서 모델은 적당히 세분화되고, 적당히 통합되어야 한다.
코드 중복과 코드 유사성
일각에서는 이런 식으로 작업하면 데이터 중복이 많아지니 피해야 하는 것이 아니냐는 의견이 있다. 맞는 말이다. 이러한 DTO는 도메인 모델과 데이터의 형태가 유사할 것이다. 하지만 이 주장은 모호하다. 왜냐하면 코드 중복과 코드 유사성은 다르기 때문이다.
필요에 따라 만들어진 코드는 말 그대로 필요하니까 존재하는 것이 당연하다. 중복은 역할과 책임, 구현 등이 비슷할 때를 보고 중복이라 부른다. 그래서 데이터 형태가 유사하다고 해서 중복이라 보기는 어렵다. 목적도 같고 해결 방법이 같을 때 중복이라 부른다.
유사한 데이터를 여러 개 만드는 것을 두려워하지 않아도 된다. 몇몇 멤버 변수가 겹친다고 데이터 모델을 어정쩡하게 공유하는 것보다 역할과 책임에 따라 확실하게 모델을 구분하는 편이 훨씬 낫다.
6.2.2 공통 모듈 구성
기존에 순환 참조를 해결하는 방법과 같다. 공통으로 참조하는 코드를 별도의 모듈로 분리하는 것이다. 모든 레이어가 단방향으로 참조하는 공통 모듈을 만들고, PostCreateRequest 클래스 같은 모델을 그 곳에 배치한다. 따라서 Presentation -> Business -> Infrastructure와 별도로 core라는 모듈을 Presentation -> core, Business-> core, Infrastructure -> core가 되어 이 문제를 해결할 수 있다. 공통 모듈로 분리하는 전략은 범욕적으로 사용할 수 있는 유틸성 클래스들을 한 곳에 모아둘 때 유용하다.
하지만 이것은 문제를 해결한 것이 아니라 회피한 것처럼 보여져 부정적으로 볼 수도 있다.
1. core 모듈은 레이어라고 봐야하는가?
2. core 모듈이 레이어라면 모든 레이어가 바라보는 하나의 레이어를 두는 것은 괜찮은가?
질문에 명확히 대답하기 어렵다. 이는 core 모듈이 현재로선 모호하기 때문이다. 그리고 레이어드 아키텍처에서 말하는 레이어의 정의 또한 모호하기 때문이다. 이는 나중에 8,9장에서 알아볼 예정이다. 지금은 순환 참조가 발생했을 때 공통 모듈로 분리할 수 있다는 아이디어만 기억하자.
6.3 완화된 레이어드 아키텍처
컨트롤러가 리포지토리를 사용하는 것은 괜찮을까?
Presentation -> Business -> Infrastructure 이 구조에서 Presentation -> Infrastructure 이런 구조로 코드를 작성할 수 있게 허용해도 괜찮을지 묻는 것이다. 컨트롤러가 리포지터리를 사용할 수 있게 하는 것은 좋지 못하다. 왜냐하면 일반적으로 이처럼 2개 이상의 레이어를 건너뛰어 통신하는 구조도 안티패턴으로 분류하기 때문이다.
그래서 이처럼 상위 레이어에 모든 하위 레이어에 접근할 수 있는 권한을 주는 구조를 가리켜 완화된 레이어드 아키텍처라고 부른다. 완화된 레이어드 아키텍처란 완화됐다는 표현 그대로 '레이어드 아키텍처이기는 한데 제약을 조금 완화했다'라는 의미이다. 그리고 여기서 말하는 제약은 레이어 간 통신은 인접한 레이어에서만 이뤄져야 한다이다.
Controller에서 JpaRepository를 멤버 변수로 가진다면 Presentation 레이어가 Infrastructure 레이어에 의존한다. 하지만 이는 좀 더 생각해 본다면 6.1의 스마트 UI 같은 코드가 만들어지기 때문에 안티패턴으로 규정된다는 것을 알 수 있다.
컨트롤러에 비즈니스 로직이 들어가며, 서비스에 비즈니스 로직이 들어가기도 할 것이다. 결국 비즈니스 로직이 역할과 책임에 따라 유의미한 객체 한 곳으로 모이는 것이 아닌 중구난방으로 위치하게 된다. 따라서 완화된 레이어드 아키텍처는 안티패턴이다.
그래서 레이어드 아키텍처에는 레이어 간 통신은 인접한 레이어끼리 이뤄져야 한다 같은 제약이 있다. 따라서 컨트롤러가 리포지터리를 사용해서는 안 된다. 다시 말해 Presentation 레이어가 Infrastructure 레이어에 의존하면 안된다.
공통 모듈은 완화된 레이어드 아키텍처가 아닌가?
위에 공통 모듈을 두어 양방향 의존성을 제거한 구조는 Presentation -> Business -> Infrastructure -> core처럼 세로로 재배치해서 완화된 레이어드 아키텍처처럼 보인다. core 모듈이 Presentation, Business, Infrastructure가 참조하는 공통 레이어인 것처럼 보이고, Presentation, Business는 두 개 이상의 레이어를 건너뛰어 core 레이어에 의존하는 것처럼 보인다.
하지만 core 모듈은 레이어를 의도한 것은 아니라는 점을 유의해야 한다. 이는 우리가 늘 사용하는 Spring, Lombok과 같이 공통으로 사용하는 모듈일 뿐이다. 레이어드 아키텍처에서 말하는 '레이어 간 통신은 인접한 레이어끼리 이뤄져야 한다'라는 제약은 레이어에서 발생하는 제약이다. 레이어와 모듈은 다르다. 그런 의미에서 이는 완화된 레이어드 아키텍처가 아니라고 볼 수 있다.
6.4 트랜잭션 스크립트
스마트 UI에서 스마트 UI를 사용하는 애플리케이션은 '모든 API는 어떤 스크립트를 실행하고 응답하는 수준의 역할만 한다'라고 했다. 트랜잭션 스크립트는 그 내용의 연장이다. 트랜잭션 스크립트는 비즈니스 레이어에 위치하는 서비스 컴포넌트에서 발생하는 안티패턴이다. 트랜잭션 스크립트는 서비스 컴포넌트의 구현이 사실상 어떤 '트랜잭션이 걸려있는 스크립트'를 실행하는 것처럼 보일 때를 말한다.
@RequiredArgsConstructor
@Service
public class PostService {
private final CafeMemberJpaRepository cafeMemberJpaRepository;
private final BoardJpaRepository boardJpaRepository;
private final PostJpaRepository postJpaRepository;
@Transactional
public Post create(long cafeId, long boardId, PostCreateCommand req) {
long currentTime = Instant.now().toEpochMillis();
CafeMember cafeMember = cafeMemberJpaRepository.findBy.....
User writer = userJpaRepository.findById()...
Cafe cate = cafeMember.getCafe();
Board board = boardJpaRepository.find...
Post post = new Post();
post.set ...
post.set ...
...
post = postJpaRepository.save(post);
cafe.set ...
cafe = cafeMemberJpaRepository(cafe);
return post;
}
이건 6.3 양방향 레이어드 아키텍처 문제를 레이어별 모델 구성을 이용해 해결한 것이다. 그렇다면 이 코드는 괜찮은 건가?
아니다. 왜냐하면 여전히 코드가 어떤 스크립트를 실행하는 수준이기 때문이다. 또한 스마트 UI의 코드와 큰 차이가 없다. 비즈니스 로직을 컨트롤러에서 서비스 컴포넌트로 옮겼을 뿐 여전히 절차지향적이고 객체라고 부를 만한 것이 없다. 그나마 유의미하게 변경된 점이라면 스크립트 코드에 @Transactional 에너테이션이 추가된 정도이다.
그래서 이를 트랜잭션 스크립트라고 한다. 서비스 컴포넌트의 동작이 사실상 트랜잭션이 걸려있는 거대한 스크립트를 실행하는 것처럼 보이기 때문이다. 어떻게 보면 스마트 UI와 비슷한 맥락으로 스마트 서비스라고 부를 수도 있다. 트랜잭션 스크립트 코드는 객체지향보다 절차지향에 가까운 사례이기 때문에 절차지향의 문제점을 그대로 가진다. 변경에 취약하고 확장에 취약하며 업무가 병렬 처리되기 어렵다.
이런 패턴을 피하려면 서비스의 역할이 무엇인지 재고해야 한다. 서비스란 무엇이고 서비스의 역할이 어떤 것인지 이해해야 이 패턴을 피할 수 있다. 이는 다음 장에서 다룰 것이다. 그전에 알고 가야 할 것이 있다.
비즈니스 로직은 어디에 위치해야 할까?
단순하게 생각하면 비즈니스 로직은 서비스 컴포넌트에 있는 것이 맞는 것처럼 보인다. 왜냐하면 서비스 컴포넌트는 비즈니스 레이어에 위치하는 컴포넌트이고, 비즈니스 레이어는 비즈니스 로직이 있는 공간이기 때문이다. 그래서 비즈니스 로직은 서비스 컴포넌트에 위치하는 것이 자연스러워 보인다. 하지만 이 답은 반은 맞고 반은 틀렸다.
'비즈니스 로직은 도메인 모델에 위치해야 한다.' 도메인 모델이라는 말이 아직 익숙하지 않다면 Post, Cafe, User과 같은 객체들을 떠올리면 된다. 비즈니스 로직은 이러한 객체들이 갖고 있어야 한다. 능동적인 객체가 협력하는 것을 강조했던 객체지향을 떠올리면 이것은 당연하다.
비즈니스 로직이 처리되는 '주(main)' 영역은 도메인 모델이어야 한다. 서비스 컴포넌트가 아니다. 서비스는 도메인을 불러와서 도메인에 일을 시키는 정도의 역할만 해야 한다. 트랜잭션 스크립트 같은 코드가 발생하는 이유는 간단하다. 개발자가 '서비스는 비즈니스 로직을 처리하는 곳'이라 생각하기 때문이다. 이러한 생각을 갖고 있는 개발자는 애플리케이션의 모든 비즈니스 로직을 서비스에 작성한다. 그리고 객체 모델을 서비스의 비즈니스 로직을 실행하기 위한 데이터 저장 공간 수준으로 인식한다. 그래서 절차지향적인 코드가 만들어진다.
객체를 그저 데이터베이스에 있는 데이터와 매핑하기 위한 존재라 생각하면 도메인이라 부를 수 없다. 이 애플리케이션은 객체지향적이라 말할 수 없으며, 이를 활용하기 어렵다. 서비스의 역할이 무엇인지와 애플리케이션의 본질이 무엇인지 모른다면 이런 현상은 반복될 것이다. 애플리케이션의 본질은 도메인이다. 서비스가 아니다. 서비스는 도메인을 위한 무대일 뿐이기 때문에 도메인이 협력할 무대만 제공하고 그 이상의 역할을 하지 않는 것이 좋다. 이 같은 내용을 이해하고 있다면, 열심히 배운 객체지향을 도메인 수준에서 적용하면 된다.
비즈니스 로직을 도메인이 처리하게 해라
개발자가 비즈니스 로직을 처리하는 공간은 도메인이다. 서비스는 도메인을 실행하는 역할만 한다. 인지 모델에서 리포지토리의 역할은 단순히 데이터를 불러오는 곳이 아니라 도메인 객체를 불러오는 곳이 된다. 그래서 서비스 코드는 주로 다음과 같은 형태로 동작한다.
1. 리포지터리에서 도메인 객체를 불러온다.
2. 도메인 객체에 일을 시킨다.
3. 리포지터리에 도메인 객체를 저장한다.
4. 컨트롤러에 응답한다.
앞선 트랜잭션 스크립트를 개발하는 방식(위의 방식에서 도메인 대신 데이터라고 생각한다.)과 크게 다를 것이 없는 것처럼 보이지만 미묘한 생각의 차이가 애플리케이션 전체 품질에 큰 차이를 만든다. 절차지향이었던 코드를 객체지향으로 만들며, 서비스가 비즈니스의 역할을 하게 만들며, 도메인 모델이 서로 협력하게 만든다.
서비스는 도메인 객체나 도메인 서비스라고 불리는 도메인에 일을 위임하는 공간이어야 한다.
이를 잘 기억하고 서비스란 무엇이고 서비스의 역할은 왜 위와 같이 정의하는지 7장에서 알아볼 예정이다..
Chapter6을 마치며
이 챕터를 통해 스마트 UI, 트랜잭션 스크립트, 양방향 레이어드 아키텍처가 왜 안티패턴으로 불리는지 조금 더 명확히 이해하게 된 것 같다. 사실 이들 패턴이 좋지 않다는 건 알고 있었지만, 실무에서 빠르게 개발하려다 보니 은연중에 사용하고 있었던 것 같다.
아직 내가 작성하는 코드에도 스마트한 컨트롤러, 스마트한 서비스의 흔적이 많이 남아 있다. 도메인을 단순한 데이터 구조로만 생각한 적도 있었던 것 같다. 하지만 이번 챕터를 읽으며, 서비스는 조율자일 뿐 진짜 비즈니스 로직은 도메인이 주도해야 한다는 점을 확실히 알게 되어 좋았다. 결국 객체지향의 핵심은 '어디서 어떤 책임을 지게 할 것인가'에 대한 고민이라는 점에서, 챕터 1에서 느꼈던 인사이트와도 맞닿아 있다. 조금씩이라도 의식적으로 이런 안티패턴을 피하려는 구조를 고민하고, 도메인이 주체가 되는 객체지향적인 설계를 연습해 봐야겠다. 앞으로 나올 챕터들에서 이 이론들도 잘 정리해 봐야겠다..ㅎㅎ
'책책책 책을 읽어요 > 자바,스프링 개발자를 위한 실용주의 프로그래밍' 카테고리의 다른 글
[자바/스프링 개발자를 위한 실용주의 프로그래밍] Chapter 5. 순환 참조 (1) | 2025.04.15 |
---|---|
[자바/스프링 개발자를 위한 실용주의 프로그래밍] Chapter 4. SOLID (0) | 2025.04.08 |
[자바/스프링 개발자를 위한 실용주의 프로그래밍] Chapter 3. 행동 (2) | 2025.04.06 |
[자바/스프링 개발자를 위한 실용주의 프로그래밍] Chapter 2. 객체의 종류 (0) | 2025.03.16 |
[자바/스프링 개발자를 위한 실용주의 프로그래밍] Chapter 1. 절차지향과 비교하기 (2) | 2025.03.15 |