Replies: 2 comments
-
|
저는 이론적인 개념에 프로젝트에서의 경험을 담아 작성해보았습니다!
계층별 역할에 따라 나눈다고 생각합니다.
동의합니다!
해당 문제에 명확한 해답이 없을 뿐 아니라, 핵사고날아키텍처 등 다양한 구현 방법론들이 있어, 현실적으로는 기존 코드와의 일관성을 맞추는 것이 가장 좋은 것 같습니다. |
Beta Was this translation helpful? Give feedback.
-
|
답변 정말 감사합니다. 🙇🏻♂️ 제가 이해하고 정리한 내용과는 조금 다르게 보는 지점들이 있어서, 1. Controller에 대해서
이 정의는 Spring Web MVC라는 특정 환경에서의 Controller 모습이지 Controller라는 계층의 본질은 아니라고 봐요. 본질은 외부 세계와 도메인 사이의 어댑터(adapter) 라고 생각해요. Hexagonal Architecture에서 말하는 "Primary Adapter" / "Driving Adapter"가 가장 또렷한 표현 같아요. 당장 이번 미션을 콘솔로 진행한다고 생각하면 차이가 보여요. 콘솔 Controller에는 while 루프와 Scanner 처리가 들어가야 해요. 사용자가 종료할 때까지 입력을 계속 받아야 하니까요. 반면 웹 Controller에는 그런 루프가 없죠 - 요청-응답 한 사이클이 끝이에요. 같은 유스케이스를 다루는데도 환경에 따라 Controller가 해야 할 일의 모양이 완전히 달라져요. 그러면 환경이 바뀌어도 변하지 않는 Controller의 본질은 무엇이냐 하면
이 어댑터 역할이에요. "HTTP 통신 담당"은 그중 웹 환경에서의 한 가지 구현일 뿐이고요. 그래서 Controller를 정의할 때 "HTTP" 같은 환경 단어를 빼고도 정의할 수 있어야 한다고 생각해요. 또 한 가지, Spring Controller가 보통 얇은 건 사실인데, 그게 Controller가 원래 얇아야 하는 게 아니라 웹 환경에서는 (1) 입력이 HTTP라는 표준 형식으로 정형화되어 있고 (2) View 렌더링은 템플릿이나 프론트엔드가 따로 담당하고 (3) Service 계층이 이미 분리되어 있어서 얇게 만들 수 있는 결과예요. 콘솔 환경에서는 흐름 제어 루프가 Controller에 남아있어야 하므로 자연스럽게 더 두꺼워지고요. "Controller는 얇아야 한다"는 명제는 본질이 아니라 환경의 산물이라고 봐요. 2. Service에 대해서
이 정의가 가장 토론이 필요한 부분이라고 생각해요. "비즈니스 로직"을 한 덩어리로 묶으면 모든 게 Service로 들어가버리는 함정이 있어서요. 저는 두 가지를 나눠서 보는 편이에요.
Service의 본질은 "여러 도메인 객체와 Repository를 엮어 유스케이스를 진행하는 조립자" 라고 생각해요. 조립자이지 판단자가 아니에요. 실제 규칙 판단은 도메인 객체에게 메시지를 보내서 처리하는 거죠. // ❌ Service가 판단자가 되면 — 트랜잭션 스크립트
public void editPost(Long postId, String content, Long editorId) {
Post post = postRepo.findById(postId);
User editor = userRepo.findById(editorId);
if (!post.getAuthorId().equals(editor.getId())) { //← 규칙이 Service에 샘
throw new NotAuthorException();
}
post.setContent(content); // ← setter로 상태 직접 조작
postRepo.save(post);
}
// ✅ Service가 조립자에 머무르면 — 도메인 모델
public void editPost(Long postId, String content, Long editorId) {
Post post = postRepo.findById(postId);
User editor = userRepo.findById(editorId);
post.edit(content, editor); // ← 규칙은 Post가 안다
postRepo.save(post);
}Service가 직접 if문으로 규칙을 판단하기 시작하면 트랜잭션 스크립트로 퇴화하고, 그러면 비즈니스 규칙이 Service에 흩어져요. 같은 규칙이 여러 Service 메서드에 중복되고, 규칙이 바뀌면 여러 곳을 같이 고쳐야 해요. 도메인 객체에 규칙을 응집시키면 변경 범위가 좁아지고 단위 테스트가 가능해져요 - 외부 의존 없이 Post 객체 하나만 테스트하면 되니까요.
DDD에서 이걸 위한 자리가 따로 있습니다 - Domain Service. 우리가 보통 "Service"라고 부르는 건 사실 Application Service예요. 둘이 다른 거예요.
"DB를 조회해야 판단할 수 있는 규칙"은 보통 이렇게 처리해요 - Application Service가 Repository로 필요한 도메인 객체를 모아온 뒤, 그 객체들(또는 Domain Service)에게 메시지를 보낸다. Service는 여전히 조립자에 머물고, 판단은 도메인이 해요.
이 부분도 다르게 봐요. Service가 응답 DTO를 조립하면 다음 문제들이 생긴다고 생각해요. (a) 환경 의존성이 생김. (b) Service 시그니처가 표현 형식을 드러냄. (c) 같은 유스케이스의 결과를 환경마다 다르게 보여줘야 할 때 변환 로직이 Service에 들어가버림. 웹은 JSON 한 모양, 관리자 페이지는 다른 모양, 배치 로그는 또 다른 모양 - 이게 다 Service로 들어오면 Service가 표현 계층 사정을 다 알아야 해요. 올바른 모양은 이거라고 생각해요:
Spring 실무에서 "Controller를 얇게"라는 욕구 때문에 변환 책임을 Service로 미는 관습이 있는데, 그건 Controller가 얇아야 한다는 명제를 잘못 적용한 결과라고 봐요. Controller는 어댑터로서 변환 책임을 정당하게 가져야 해요. 얇은 건 결과일 뿐 목표가 아니고요. 3. Domain에 대해서
이 정의가 가장 동의하기 어려운 부분이에요. 세 가지 문제가 있다고 봐요. (a) DB 스키마가 도메인을 정의한다는 사고가 깔려 있음. 의존 방향이 거꾸로예요. 도메인이 먼저 있고, DB는 그걸 영속화하는 수단이어야 해요. 비즈니스 규칙("왕이 잡히면 게임 종료")은 수년 동안 거의 안 바뀌는데, DB 스키마와 ORM 기술은 자주 바뀌어요. 자주 바뀌는 것이 안 바뀌는 것에 의존해야 연쇄 수정이 안 일어나요. 도메인이 DB 칼럼에 맞춰 정의되면 그 방향이 뒤집혀요. (b) JPA Entity와 Domain Object를 같은 것으로 봄. 둘이 보통 한 클래스로 구현되니까 혼동하기 쉬운데, 개념적으로는 다른 거라고 생각해요. (c) "검증 책임"으로 도메인의 역할을 축소함. 검증은 도메인이 하는 일의 일부지 전부가 아니에요. 도메인의 본질은 비즈니스 규칙을 가진 객체가 메시지에 응답하며 협력하는 것이라고 생각해요. 저는 Domain을 이렇게 정의하는 게 맞다고 봐요: 비즈니스 규칙이 사는 자리. 객체에게 메시지를 보내면 그 객체가 자기 규칙대로 응답하는 곳. 솔직히 지금 진행 중인 미션 범위에서는 규칙이 많지 않아서 도메인 객체가 데이터 컨테이너에 가깝게 볼수(오해) 있다고 생각해요. 하지만 그건 현재 상태가 빈약한 것이지, 그 자리의 본질이 그렇다는 뜻은 아니에요. 규칙이 추가되면 Service나 Controller가 아니라 여기로 들어와야 하는 자리라는 인식이 중요하다고 생각해요. 4. "명확한 해답이 없다, 일관성이 중요하다"에 대해서
이 부분은 절반 동의, 절반 비동의예요. 동의하는 부분: Clean Architecture, Hexagonal, DDD, 전통 3계층 등 방법론 수준에서 여러 가지가 있는 건 사실이에요. 그리고 팀으로 일할 때 일관성이 중요한 것도 사실이고요. 비동의하는 부분: 방법론이 여러 개라는 게 "본질적 원리도 여러 개" 라는 뜻은 아니라고 봐요. 모든 방법론이 공통으로 깔고 있는 원리가 있어요:
이건 SRP의 계층 버전이고, Clean Architecture든 Hexagonal이든 DDD든 공통으로 깔고 있는 원리예요. 방법론은 이 원리를 어떻게 구체적으로 구현할지에서 갈리는 거지, 원리 자체가 다른 게 아니에요. 그래서 "명확한 해답이 없다"는 말은 두 가지로 나눠서 봐야 한다고 생각해요.
그래서 결국 따져야 하는 건 원리의 적용 방식이지 원리 자체는 아니라고 봐요. 답이 없는 게 아니라, 그 답을 상황마다 원리로부터 도출해야 하는 거고요. 그리고 "기존 코드와의 일관성" 도 그 일관성이 어떤 근거에 기반한 일관성인지가 더 중요한 것 같아요. 기존 코드가 변화의 이유를 잘 분리한 코드라서 그 결을 따라가는 거라면 좋은 일관성이지만, 기존 코드가 그냥 관습대로 짜여 있고 책임 분리가 모호한데 "일관성"이라는 이유로 그걸 답습하면, 잘못된 구조가 프로젝트 전체로 굳어져요. 일관성은 좋은 원칙을 일관되게 적용할 때 가치 있는 거지, 원칙 없는 관성을 정당화하는 도구는 아니라고 생각해요. 물론 협업 상황에서는 "지금 당장 다 뜯어고치자"가 답이 아닐 수 있고, 점진적으로 개선하는 게 현실적이라는 건 동의해요. 다만 그 점진적 개선의 방향성을 잃지 않으려면 원리가 머릿속에 또렷이 있어야 한다고 봐요. 정리제 입장을 한 줄로 요약하면 이래요:
그리고 답변자분의 의견 중에서도 부분적으로 동의하는 지점들이 분명히 있어요 - 특히 협업과 일관성에 대한 현실 감각, 그리고 명확한 해답이 코드 수준에서 항상 자동으로 떨어지지는 않는다는 인식. 다만 그게 원리가 모호하다는 결론으로 가면 곤란하다는 게 제 입장이에요. 원리는 또렷이 두고, 적용에서 신중해야 한다고 봐요. 긴 답변 죄송하고, 이렇게 의견 나눌 수 있어서 좋네요. 다른 분들 생각도 궁금합니다 🙂 |
Beta Was this translation helpful? Give feedback.
Uh oh!
There was an error while loading. Please reload this page.
-
계층은 무엇을 기준으로 나눠야 할까요?
대부분의 프레임워크의 공식문서 Example에는
Controller, Service, Domain, Repository로 나뉜 레이어드 아키텍처를 관습처럼 사용합니다.
그런데 미션을 진행하면서 계층을 나누는 기준은
단순한 폴더 분리가 아니라 변화의 이유가 다른 것들을 분리하는 것에 가깝다고 느꼈습니다.
예를 들어 Controller는 HTTP 요청, 콘솔 입력, 화면 응답처럼 외부 환경의 변화에 영향을 받습니다.
Service는 유스케이스 흐름과 트랜잭션 경계에 영향을 받습니다.
Domain은 비즈니스 규칙의 변화에 영향을 받습니다.
Repository는 DB 스키마, SQL, 영속성 기술의 변화에 영향을 받습니다.
즉, 한 계층에 서로 다른 변화의 이유가 섞이면 변경이 퍼지고,
그래서 계층을 나누는 것이 아닐까 생각했습니다.
질문입니다.
Beta Was this translation helpful? Give feedback.
All reactions