tobyilee
@tobyilee
Học viên
15,865
Đánh giá khóa học
1,035
Đánh giá khóa học
5.0
Bài viết
Hỏi & Đáp
어댑터에서 도메인에 직접 의존하는 경우에 대해
사실 실무에서는 계층간 의존 방향성이나 독립성이 그렇게 엄격하게 지켜지지 않고 개발되는 경우가 많습니다. 애플리케이션 서비스 계층은 항상 DTO를 주고 받도록 만드는 원칙을 가지고 개발하는 유명 기업 개발팀도 많이 봤습니다.그럼에도 이번 강의가 헥사고날 아키텍처가 제공하는 유익과 가치를 잘 따르려면 어떤 접근이 가능할지에 대해서 이야기하는 것이니만큼, 헥사고날 아키텍처, 또 클린 아키텍처가 추구하는 의존 관계를 잘 지키면 어떻게 될지를 계속 살펴보려고 하고, 그 기준에 따라서 어떤 모습이 나오는지 탐구해보고 싶었습니다.언제부턴가 많은 분들이 도메인 계층의 요소가 어댑터 계층(인프라, 컨트롤러 등등)에 노출되는 것을 문제라고 이야기하는 것을 잘 알고 있습니다. 엔티티를 컨트롤러에서 직접 리턴하는 초간단 방식이 결국 큰 문제를 일으키기도 하고, 뷰 역할을 하는 코드나 API 응답 생성만 해야 하는 코드에서 도메인 로직을 건드리는 문제 등등도 많이 이야기 되고 있죠. 그래서 만만하게 애플리케이션 계층은 DTO로 변환된 어댑터가 필요로 하는 정보만 노출한다는 것을 해결책으로 삼습니다. 하지만 저는 이 방식이 단순한 경우엔 별 차이가 없겠지만, 시간이 흘러서 동일 포트를 이용하는 어댑터가 여럿이 되고(웹 API, 배치, 다른 메시징 클라이언트 등등), 그때마다 DTO가 자꾸 새로 만들어지거나, 변경이 되는 것을 자주 관찰했습니다. 어댑터 앞 단의 액터의 요구 변경이나 배치 로직 등의 변경이 있을 때마다 이렇게 DTO가 바뀌고, 더 나아가서 리포지토리 로직도 따라서 바뀌는 등의 예는 정말 아주 오래 전부터, DB SQL에 UI 로직까지 반영되던 방식으로 개발하던 시절의 유물이기도 합니다. 강의에서 제가 강조했던 것처럼 그런 DTO의 과도한 사용은 결국 어댑터와 애플리케이션 서비스의 강한 의존를 만들고, 그에 따른 잦은 변경이 여러 계층에서 일어나게 만든다고 봅니다. 이게 DTO라는 아주 심플한 구조여서 그렇지, 오래전 서비스 계층에서 뷰에서 조립할 HTML 조각을 생성해서 보내주던 것과 솔직히 다르지 않다고 봅니다. 어탭터가 도메인을 이용하는 것은 사실 자연스럽습니다. 도메인 중심의 개발에서 모든 코드는 도메인의 개념과 보편언어, 도메인 로직 등에 의존하는 것이 자연스럽습니다. 한번 모델이 잘 설계되면 가장 변하지 않고 유지되는 것이 도메인이고, 그 부분이 변경되는 크리티컬한 설계 변경이 있다면 그에 따라 다른 계층의 변화도 자연스럽게 이어지는 것이 맞다고 봅니다. 물론 경우에 따라 포트 입장에서 단순한 값을 리턴하는 것으로 그 의도를 잘 나타낼 수 있다면 간단한 값이나 작은 DTO를 리턴하게 하는게 맞습니다. 이건 단지 엔티티를 읽기전용으로 만든 오브젝트에 옮겨 담는 것과는 다르고, 어댑터와 그 앞의 요구 변화에 따라 도메인이 변경되지 않았음에도 서비스계층의 DTO가 자꾸 바뀌는 일이 일어나지는 않을 것입니다. 하지만 대부분은 애그리거트나 엔티티를 리턴하고, 이를 어떻게 클라이언트에게 보여줄지는 컨트롤러 코드 내의 뷰 로직을 담당하는 응답 객체 생성 시점에 결정되는 것이 훨씬 낫다고 봅니다.도메인은 가장 소중하고 중요하고, 그리고 애플리케이션 모든 영역이 이에 의존하는 것이 너무 자연스럽습니다. 물론 요즘은 제가 주장하는 것이 현장에서 보편적으로 통용되는 주류 논리가 아닐 수 있습니다. 언제부턴가 DTO 만능 시대라, DTO는 특정 계층에 속하지 않는 모든 계층에서 사용하는 보편적인 오브젝트로 취급하기도 합니다. 요즘 AI에게 스프링 개발을 시키면 그렇게 만들더라고요. 하지만 계층형 아키테처의 원칙과 헥사고날 아키텍처가 추구하는 것을 잘 지키면서 유지보수성이 좋게 발전하는 코드가 주는 가치가 충분히 있다고 믿기에 이런 구조를 따르려고 합니다. Provided interface가 어댑터의 요구에 의존한다는 이야기도 하셨는데, 이건 반대로 접근하는 것이 헥사고날 아키텍처의 원칙인 듯 싶습니다. 기본적으로 도메인과 이 로직을 담은 헥사곤은 우선 도메인이 제공하는 기능에 충실하게 만들어지고, 결과적으로 액터, 그리고 이 중계 역할을 하는 어댑터가 이용하는 것이지, 어댑터의 요구를 충족하기 위해서 포트와 인터페이스를 만든다고 생각하지 않으면 좋겠습니다. 물론 액터가 없는 서비스는 없을 것이지만, 도메인 관점에서 설계한 작업의 결과로서 인터페이스가 정의되고 이게 서비스에서 제공되는 것이어야지, 단지 외부의 요구가 있으니까 이걸 만든다는 식으로 접근하지 않는게 도메인 모델 중심의 개발 방법일 듯합니다. 외부의 요구가 존재한다는 것을, 먼저 도메인 모델에 반영하고 안에서부터 설계해서 밖으로 나가는 것이 자연스럽습니다. 더 이야기하고 싶으시면 댓글을 남겨주세요.
- 0
- 2
- 35
Hỏi & Đáp
Member 도메인이 PasswordEncoder를 받는 구조 질문 있습니다.
흥미로운 질문을 주셨네요.저는 실무 일을 할 때 스프링 기술의 특정 인터페이스 하나가 도메인 또는 서비스 계층의 로직을 독립적으로 잘 표현한다면, 그걸 기술 구현체와 상관없이 가져다 쓰기도 합니다. 대부분은 구현 레벨을 쓰는 영역에서 스프링 기술을 쓸게 분명합니다. 그러면서 인터페이스만 보자면, 패키지 이름에 스프링이 들어가는 걸 제외하면 얼마든지 순수하게 도메인과 애플리케이션 서비스의 아이디어를 표현해 내기도 합니다. 말씀하신 PasswordEncoder가 바로 그런 예이죠. 그럼에도 도메인 코드에 스프링이 들어가는 것을 못겼뎌하는, 스프링에 의존적인 코드라고 맹비난하는 순수한(?) 열혈 개발자들이 가끔 보입니다. 그렇다면 의존이란 무엇인가요? 왜 의존이 나쁘다고 하는 것일까요. 코드 차원에서 의존이란, 내가 의존하는 대상이 변경되거나 바뀌면 내 코드도 따라서 바뀔 가능성이 높은 경우를 말합니다. 예를 들어 비밀번호 기술 A 라이브러리를 도메인과 여기저기서 쓰도록 만들었는데, 비밀번호 기술을 따로 만들어서 클래스와 인터페이스, 메소드 시그니처가 바뀌면 내 코드도 따라서 바뀐다면 의존한다고 할 수 있죠. 그럼 스프링에 의존한다는 것은, 스프링을 안쓰고(과연 그럴 수가 있을지 모르겠지만) 다른 기술을 쓴다면, 혹은 스프링 기술이 업데이트 되면서 핵심 인터페이스를 확 바꾼다면(그런 경우는 역사적으로 거의 없습니다만), 도메인 코드도 바뀌고 그래야 하니 의존한다고 할 수 있겠죠.그렇다면 스프링 시큐리티의 PasswordEncoder 인터페이스도 그렇게 의존성을 부여할 것인가? 라고 한다면 비밀번호를 다루는 코드가 가지는 정말 너무 뻔하고, 대표적인, 지극히 평범한 로직을 담은 것 이상을 그 인터페이스에서 찾을 수가 없습니다.그래서 이럴 때는 스프링 시큐리티 인터페이스를 도메인이나 서비스에서 사용해도 아무런 문제가 없습니다. 매우 희박하지만 스프링을 쓰면서 시큐리티 기술을 다른 것을 쓴다고 하더라도, 비밀번호를 다루는 로직을 가진 다른 구현 기술을 쓰더라도, 스프링의 인터페이스를 구현해서 그 기술을 사용하도록 어댑터를 하나 추가하면 나머지 코드는 전혀 손을 대지 않아도 됩니다. 그러니까 의존 문제가 발생하지 않는다는 것이죠.그럼에도 별도의 인터페이스를 정의해서 사용한 것은, 그래도 괜찮기 때문입니다. 아직 스프링 시큐리티 기술을 잘 검토하지 않은 상태에서 도메인 모델에 담긴 비밀번호 처리 로직을 가지고 얼마든지 인터페이스를 정의하고 도메인 개념을 담아 사용할 수 있죠. 그리고 어댑터에서 스프링 시큐리티의 거의 비슷한 이름의 기술을 사용하도록 만들어도 사실 문제 없습니다.이건 그다지 차이가 없는 결과에 대한, 개발팀의 취향, 경험에 의한 판단의 결과라고 봐도 됩니다. 스프링 시큐리티의 PasswordEncoder에 이미 익숙하고 모두가 잘 이해하고 있고, 하나라도 더 인터페이스를 추가하고 싶지 않다면 스프링 것을 도메인에서 사용해도, 그 코드를 모두가 잘 이해하고 도메인 개념을 잘 표현했다고 생각할 수 있을 겁니다. 반대로 도메인 관점에서 먼저 인터페이스를 설계해서 넣고, 나중에 스프링 시큐리티 어댑터를 만들어서 사용해도 아무 문제는 없겠죠. PasswordEncoder가 도메인 코드로 직접 표현되어 있으면, 아 이런 개념으로 비밀번호를 다루는 것이구나라는 기술 사용에 관한 도메인 로직을 잘 이해할 수도 있을 겁니다. 아직 스프링 시큐리티에 친숙하지 않은 주니어 개발자들이 많다면 이렇게 해도 괜찮아 보이네요. 저는 둘 다 적절하고, 별 차이는 없다고 생각합니다. 제가 혼자 개발한다면 스프링 인터페이스를 가져다 쓸 겁니다. 그러다 혹시 스프링 시큐리티를 안 쓰게 되더라도, 그 인터페이스 클래스 하나만 빼서 프로젝트에 남겨놓고 써도 되겠죠. 아니면 아주 작은 시큐리티 코어 jar 파일 하나를 덤으로 넣어서 쓰던지요.
- 0
- 2
- 40
Hỏi & Đáp
MemberService와 EmailSender 책임 분리에 대한 질문
안녕하세요. 좋은 질문을 해주셨네요. 클린 스프링 강의 시리즈는 말씀해주신 그런 의문을 가지고 문제를 풀어가는 과정을 보여드리는 것이 목적입니다. 일단 시작 단계에서는 우선 기능을 빠르게 구현하고, 테스트를 만든 뒤에 가장 단순한 방법부터 시작해서 문제를 차근차근 풀어나가는 과정에 있다고 보시면 됩니다. 매번 어떤 의문을 가지고 어떤 시도를 하는지가 중요합니다. 가입하면 환영 이메일을 보내는 것은 아주 기본적인 도메인 모델에서 발견할 수 있는 기능이지만, 서비스가 성장하다보면 꽤 많은 변화가 필요하게 됩니다. 메일 내용이 변경되는 것은 아주 단순한 변화입니다. 그보다 더 복잡한 조건이 붙을 수도 있죠. 이벤트에 의해서 가입이 됐을 경우 메일 내용이 완전히 다르게 나갈 수도 있죠. 가입을 했지만 메일이 아닌 사전 인증된 메신저로 축하 메시지를 보낼 수도 있습니다. 글로벌 서비스가 돼서 여러 언어로 다르게 메일이 작성되야 할 수도 있고, 최종 메일 발송은 시간을 두고 배치로 처리하거나, 일정 승인 과정을 통해서 나가야 할 수도 있습니다. 시간이 지나면 회원 가입 직후에 진행되어야 할 작업이 이보다 훨씬 많아지게 됩니다. 각종 자료 업데이트부터, 포인트 적립을 위한 기초 작업 진행, 모니터링을 위한 통보 기능 등등. 이렇게 되면 더 큰 문제는 회원 가입 로직보다 후처리 작업이 커지고, 이게 또 시간을 많이 잡아먹습니다. 메일 발송도 꽤나 시간 걸리는 작업이거든요. 그러는 동안 DB 트랜잭션은 묶여있어서 가입이 갑자기 폭증하는 경우 성능에도 영향을 미칩니다. 그래서 트랜잭션 밖으로 이걸 분리하는 작업도 필요하죠.중요한 건 현재 시점에서 "경제적"으로 할 수 있는 빠른 작업을 해두고, 이후 분리, 추상화 등에 투자할 타이밍을 찾는 것이죠. 이제 가입 기능을 처음 만들었는데 일 가입자 10만명을 상정해서 무리해서 기능을 만들어 둘 필요는 없으니까요. 우선은 가입 축하 메일을 보내는 코드가 회원 가입의 핵심 도메인 로직에서 분리해두는 것으로 충분할 것 같습니다. 이제 강의 시리즈 두 번째가 되면 이를 어떻게 독립적으로 만들지 구체적으로 다루게 될 겁니다.어쨌든 좋은 문제의식을 가지고 지적을 해주셔서 감사합니다. 시간이 걸리는 외부 연동 서비스인데 경우에 따라 내용이 동적으로 변경되어야 하고, 템플릿을 관리해야 하고, 성공 여부를 확인해서 재시도 해야 하는 가입 축하 메일 발송이라면 이걸 어떻게 관리하면 좋을지 고민해보시고, 여러가지 시도를 해보세요.
- 0
- 2
- 62
Hỏi & Đáp
NonNullApi를 NullMarked로 대체하라고 합니다.
안녕하세요.말씀하신 것처럼 스프링 7부터는 null 체크에 사용되는 기술이 변경되었습니다.강의에서는 당시 스프링 6까지 사용하뎐 스프링 자체 애노테이션을 사용했습니다. 그런데 7에서는 기존 스프링 자체 애노테이션을 deprecated로 바꾸고, JSpecify라는 여러 벤더와 프로젝트에서 적극 채용하고 있는 방식을 사용하는 것으로 정책을 변경했습니다. 여러 도구와 벤더들이 수용한 JSpecify가 가지는 여러 장점을 수용하고, 특히 Kotlin과 경계에서 null 안정성을 대폭 개선하게 되었습니다.클린 스프링은 두 번째 주제를 다룰 때 스프링 7로 업데이트 할 계획을 가지고 있습니다(첫번째 강의의 Part 2까지는 스프링 6를 사용합니다). 그때 null을 다루는 새로운 방법도 적용해보겠습니다.
- 0
- 2
- 66
Hỏi & Đáp
헥사고날 part2 강의 출시 예정일 문의 드립니다.
안녕하세요.죄송하게도 계속 Part 2 출시 일정이 미뤄지고 있었습니다. 제 건강 상태가 많이 안 좋아서, 녹화하기가 힘들었는데요. 좀 더 힘을 내서 최대한 빨리 마무리해보려고 하고 있습니다. 이번엔 꼭 2월까지는 마무리해보도록 하겠습니다.
- 0
- 2
- 167
Hỏi & Đáp
Repository Adapter 설계에 대해 피드백을 부탁드립니다
안녕하세요.지금 접근하시는 방법이 어댑터를 사용해서 구현 부분을 분리하기에 적절한 경우입니다. 이렇게 기술을 병합해서 사용할 경우 리포지토리 인터페이스를 구현하는 업댑터를 따로 만드시는 방법을 사용하실 수 있습니다. 다만, Spring Data JPA를 사용했을 때의 장점을 놓칠 수도 있겠죠.그런데 저라면 이렇게 해보겠습니다. 먼저 기본 Spring Data JPA Repository는 그대로 사용합니다. 복잡한 동적 쿼리나 네이티브 쿼리를 사용할 필요가 없는 Spring Data에서 만들어주는 JPQL로도 충분한 경우는 그 인터페이스의 리포지토리에서 처리합니다.그러면서 동시에 CustomRepository 인터페이스를 추가로 만듭니다. 이건 Spring Data JPA 리포지토리가 아니기 때문에 구현 클래스를 따로 구현합니다. 그리고 이걸 결합해서 최종 인터페이스를 만드는 것이죠. Repository Composition 방법을 사용하는 것입니다.이렇게 하면 QueryDSL이나 혹은 MyBatis 등의 구현 코드가 필요한 리포지토리를 편하게 조합해서 사용할 수 있습니다. Jpa Repository까지 굳이 어댑터를 태울 필요는 없죠.다음 내용을 참고해보세요.https://docs.spring.io/spring-data/jpa/reference/repositories/custom-implementations.html
- 0
- 2
- 84
Hỏi & Đáp
39. 문서와 코드 다듬기 updateInfo 테스트 질문 있습니다.
안녕하세요. 말씀하신 대로 unique 제약 조건이 있는 경우 NULL 값은 상관없지만 빈 문자열은 중복 문제가 생길 수 있습니다. 강의 때는 그 부분을 체크 못했네요. 이전에 같은 질문을 주신 분이 계셔서 해결 방법을 이야기했습니다. 빈 문자열로 세팅하더라도, 값을 NULL로 등록되도록 조정을 하는 방법을 사용해야 합니다. 해당 질문은 다시 찾아보겠습니다.
- 0
- 2
- 53
Hỏi & Đáp
수업을 잘 듣고 있습니다.
안녕하세요. 노션으로 작성한 문서는 강의자료에 pdf로 넣어뒀습니다. 강의자료에서 다운 받으실 수 있습니다.
- 0
- 2
- 79
Hỏi & Đáp
PT 문의사항
안녕하세요. 제가 사용한 도구는 애플 키노트(Apple Keynote)입니다.
- 0
- 1
- 74
Hỏi & Đáp
jackson(3.0.2 버전) ObjectMapper.readValue 에러타입
RuntimeException 타입의 예외인 경우 다음 필요가 있을 때만 catch를 하면 됩니다. 그 외에는 그냥 예외로 넘어가도록 놔두시는 것이 좋겠네요.다른 예외로 전환할 필요가 있을 때. 이건 메소드의 규약을 의도적으로 특정 예외를 반환하도록 강제할 필요가 있을 때 런타임이더라도 예외 전환을 하도록 catch해서 wrapping한 다음에 다시 throw할 수 있습니다. Checked 예외라서 계속 throw/catch가 전파되는 상황을 피하기 위해서 RuntimeException으로 바꾸는 것도 이런 예이죠.예외 복구가 가능한 경우. 정말 예외적인 상황(네트워크 예외 같은)이 예상되고 이때도 최대한 성공할 수 있게 하려면 재시도나 다른 대체방법(fallback)을 사용하도록 catch할 필요가 있기도 합니다. 그 외의 경우는 굳이 catch할 필요는 없습니다. 강의에서 try/catch를 했다면 그건 아마 사용했던 버전에서는 checked exception이었기 때문일 것 같네요. 버전이 올라가면서 런타임 예외로 바뀌는 경우는 종종 있습니다. catch를 하지 않은 상태에서 코드를 놔두면 IDE에서 체크해주니까 그런 경우에 빠르게 파악하고 넘어갈 수 있죠.
- 0
- 2
- 77




