tobyilee
@tobyilee
Học viên
16,394
Đánh giá khóa học
1,114
Đánh giá khóa học
5.0
호주에 살고 있는 소프트웨어 개발자입니다. 30년간 다양한 분야의 시스템과 서비스를 개발해본 경험이 있습니다.
스프링 프레임워크와 관련 기술을 좋아하고 JVM 기반 언어를 주로 사용합니다.
한국스프링사용자모임(KSUG)을 설립하고 활동했고, 토비의 스프링이라는 책을 쓰기도 했습니다.
개발과 관련된 다양한 주제에 관해 이야기하는 것을 좋아합니다.
Khóa học
Đánh giá khóa học
- Toby's Clean Spring - Domain Model Pattern và Hexagonal Architecture Phần 2
- Toby's Clean Spring - Domain Model Pattern và Hexagonal Architecture Phần 2
- Toby's Spring 6 - Hiểu biết và Nguyên lý
- Clean Spring của Toby - Mô hình Domain Model và Kiến trúc Hexagonal Phần 1
- Clean Spring của Toby - Mô hình Domain Model và Kiến trúc Hexagonal Phần 1
Bài viết
Hỏi & Đáp
헥사고날 아키텍처와 DDD를 적용할 때, 화면에 강하게 연관된 조회 데이터를 어떻게 다루는 게 좋은지 궁금합니다.
우리가 도메인 중심의 개발을 한다고 할 때는 보통 변경이 일어나는 상황을 주로 다룹니다. 대부분의 로직은 변경 로직이고, 조회는 보안과 관련이 없다면 도메인 모델의 변경 없이도 새로운 화면이나 클라이언트 요구사항에 따라서 계속 추가될 수 있겠죠.그런데 말씀하신 경우처럼 보다 복잡한 조회 로직이 필요한 경우가 많이 존재합니다. 엔티티 구조로 커버할 수 있는 조회라면 기준이 되는 엔티티와 필요하다면 연관관계에 있는 엔티티까지 함께 페치하고, 이를 응답 데이터로 만드는 것은 웹 어댑터에서 진행해도 됩니다.그런데 리포트성 쿼리라든가, 복잡한 화면이나 테이블에서 필요한 정보 등은 DB 쿼리에 의존하는 경우가 많고, 필요하다면 네이티브 쿼리를 사용해야 합니다.헥사고날 애플리케이션 구조에서 이런 경우 내용에 따라 적절하게 분리된 조회 의도를 담은 Port를 설계하고, 구현 클래스는 필요에 따라 기본적인 조회용 query service를 같이 활용해도 되고 분리해도 좋습니다.QueryDSL은 스프링 데이터를 쓴다면 커스톰 repository 인터페이스를 구현해서 어댑터 쪽에 만들면 되니까, 헥사고날 쪽에서 볼 때는 인터페이스만 보고, 형식만 잘 맞춰주면 될 겁니다. 어떤 경우에는 가져온 데이터를 애플리케이션 내부에서 가공해서 최종 응답을 만들어야 할 수도 있습니다. 여기에도 결국 도메인 조회 로직이 만들어지는 것이겠죠. 조회가 복잡하고 많은 경우엔 엔티티 단위의 리포지토리 외에 추가로 구분된 리포지토리를 두는 것이 좋을 것 같습니다. 복잡한 분석을 다루는 경우 애그리거트/애플리케이션 서비스 경계를 넘어서게 되는데 이럴 때는 그런 조회를 담당하는 리포트 컴포넌트를 따로 추가하는 것도 좋습니다. 기술에 종속되는 코드는 어댑터에 두고, 헥사고날 애플리케이션 내부에는 도메인 정보로 해석되는 로직을 담은 코드를 두고, 의도 단위로 구분된 Port를 통해서 서비스를 제공한다는 헥사고날 아키텍처의 기본 원칙을 따른다면 크게 고민할 부분은 없을 것입니다.
- Lượt thích
- 0
- Số bình luận
- 2
- Lượt xem
- 15
Hỏi & Đáp
Request DTO에서 Entity를 생성할 때 의존성 방향을 반대로 하면 어떨까요?
좋은 질문을 해주셨네요.말씀하신 방법도 실무에서 많이 사용하는 것이긴 합니다. 저도 그런 스타일로 개발한 적도 있고요.그럼에도 이번 강의 시리즈에선 도메인 계층에 있는 Member 엔티티의 생성자에 동일한 타입의 파라미터가 여러개 연속으로 등장하는 근본적인 문제를 해결하는 것이 좋다고 생각을 했습니다. Part 1에서 말씀드렸던 것처럼 타입이 달라서 보호되지 못하는 String과 같은 타입이 연속으로 나오면, 이후 코드 변경에 취약해지기 쉽습니다. 그래서 parameter object 패턴 같은 것을 활용하려고 했던 것이지요.그런데 말씀해주신 것처럼 DTO에서 바로 복잡한 생성자/생성 메소드를 가진 엔티티를 생성하는 것을 개발 규칙으로 두는 경우 다음 두 가지 문제가 있다고 생각을 합니다.근본적으로 Member 엔티티를 다른 곳에서 DTO 없이 직접 생성하는 것을 막지는 못합니다. 웹 API를 사용하는 경우 한가지 경우만 있다면 문제가 되지 않을 수 있지만, 이후에 데이터 마이그레이션, 배치 등에서 다른 경로로 Member의 생성자(생성메소드)를 사용하는 경우가 나오면 동일 타입의 긴 파라미터를 가진 메소드 사용의 위험은 그대로 가지고 있게 됩니다.Part 2에서 리팩터링을 결정하면서 설명했듯이 서비스로 전달되는 Request DTO는 이후 Member 생성에 필요한 정보 외에 다른 데이터도 추가될 수 있습니다. 이런 경우 DTO가 단순히 Member 생성 정보 이상의 역할을 하게 됩니다. 그래서 이런 DTO에서 Entity를 바로 생성하는 방식으로 코드를 작성하면 직접적으로 생성 로직을 바로 이해하기가 여러울 수 있습니다. 도메인 엔티티가 어떻게 만들어지는가는 애플리케이션 서비스 코드에 잘 드러나면 좋겠고, 그 정보는 도메인 계층으로 오브젝트로만 구성되어 있는 것이 응집도가 높은 코드가 됩니다. DTO에 엔티티 생성 정보 이상의 정보를 넣게 되는 경우 DTO가 사용되는 방식이 아닌 다른 경로로 엔티티를 생성할 필요가 있을 때도 필요하지 않은 정보까지 넣어서 DTO를 만들어 쓰도록 강제하는 것도 어렵겠지요. 이런 이유로 말씀하신 방식을 이번 강의에서는 선택하지 않았습니다. 강의 제목에서 볼 수 있듯이 이번엔 도메인 계층을 가능한 도메인 지식을 그대로 드러내도록 순수하게 구성해보는 것이 강의 목적이어서 그런 것이기도 합니다. 물론 현실에서는 얼마든지 말씀하신 방법을 선택하실 수 있습니다. 더 이득이 있다고 판단되면 트레이드오프를 할 수 있겠죠.개인적으로는 코틀린처럼 named parameter가 지원된다면 긴 파라미터를 가진 메소드를 그대로 사용해도 괜찮았습니다. 파라미터 이름과 전달되는 변수나 프로퍼티 이름이 직접 매칭이 되기 때문에 이후 파라미터 변경에 취약해지지 않습니다. 하지만 자바는 아직 지원되지 않는 방식이라 아쉽긴 합니다.Lombok을 이용해서 생성자 빌더를 사용하는 것도 써본적이 있습니다. 코드는 간결해지지만 결국 setter를 사용하는 것과 같은 한계를 가지고 있기 때문에 역시 아쉬움이 있었습니다.제가 선택한 방식이 어떤 경우든 최선이라고 생각하지는 않습니다. 다만, 그 결정 과정에서 어떤 이유를 고민했고, 왜 선택을 했는지, 그 방식이 도메인 코드 개발에 주는 유익은 무엇인지 정도만 한번 생각해주시면 좋겠습니다.여러 선택지 중에서 어떤 것을 고르셔도 분명한 개발 규칙을 정의하고 일관성 있게 사용되도록 하시면 좋겠습니다.
- Lượt thích
- 0
- Số bình luận
- 2
- Lượt xem
- 38
Hỏi & Đáp
RestTemplate과 webClient, RestClient에 대해서 질문드립니다.
WebClient는 스프링 초기부터 있던 RestTemplate을 대체하는 프로그래밍 API로 등장한 것인데요. 소개 당시 주목을 받았던 Reactive 방식의 비동기 API 호출을 지원하기 위해서 동기/블록킹 방식인 RestTemplate과 다른 접근 방법과 사용법을 가지고 있습니다. 비동기 방식 API 호출 외에도 스프링 개발진이 Modern API 스타일이라고 부르는, 메소드를 체이닝하면서 호출 방법에 대한 스펙을 쌓아두고 이용하게 한 것도 특징입니다. 복잡한 세팅이 필요한 API 호출인 경우에도 직관적으로 이해될 수 있는 표현력이 뛰어난 메소드의 조합으로 코드가 작성이 되어서 편리하죠.동기/블록킹 방식의 API 호출에도 WebClient와 같은 스타일의 모던 API를 적용한 RestClient도 있습니다. RestTemplate처럼 템플릿/콜백 패턴의 활용이 없어진 것은 아닙니다. 자바 8에서 람다식이 등장하면서 콜백은 예전처럼 익명 클래스를 매번 사용할 필요가 없어지고 훨씬 간결하게 언어레벨에서 지원하는 스타일로 개발이 가능해졌죠. 이런 람다식의 활용은 이제 템플릿/콜백 패턴이라는 이름을 굳이 사용하지 않더라도 WebClient, RestClient 등의 모던한 스프링 API에서 적극 사용됩니다. 그 동작 원리는 기존 템플릿 방식과 동일합니다.
- Lượt thích
- 0
- Số bình luận
- 2
- Lượt xem
- 55
Hỏi & Đáp
안녕하세요 토비님 혹시 완성된 코드의 repository 주소는 없을까요?
강의 예제 프로젝트 리포지토리는 강의자료에서 알려드렸는데요. 다음 주소입니다.https://github.com/tobyspringboot/splearn
- Lượt thích
- 0
- Số bình luận
- 2
- Lượt xem
- 74
Hỏi & Đáp
테스트 관련 질문!
안녕하세요. 강의 진행하는 중간에 테스트가 실행이 안 되는 상황이 나오고 해당 수업 후반부에 이를 해결하는 내용을 설명합니다. 혹시 그 부분이 아닌가 싶은데요.그래도 해결이 안 되시면 전체 테스트를 실행할 때 문제가 되는 프로젝트 작성하신 걸 github으로 공유해주시면 제가 받아서 확인을 해볼게요.
- Lượt thích
- 0
- Số bình luận
- 2
- Lượt xem
- 76
Hỏi & Đáp
N+1 관련해서 질문있습니다.
안녕하세요.질문의 내용을 보니 두 가지로 구분해서 답변을 드리는게 좋겠습니다. 우선은 도메인 오브젝트 관점에서 설계에 대해서 먼저 생각해봐야 합니다. 이 경우엔 N+1은 관심사가 아니죠. 여기서 중요한 것은 각 오브젝트가 자신이 가진 정보를 가지고 필요한 기능을 제공하면서 다른 오브젝트와 적절하게 협력하는가의 문제입니다. 객체 설계 관점에서는 대체로 잘 작성된 코드로 보입니다. Journal 안에 Entry를 모두 가지고 있다면 이를 이용해서 필요한 Entry를 가져와 로직을 수행하도록 되어있네요. 그런데 질문하신 EntryLine의 getAccountType() 부분은 로직은 바르게 작성되어 있지만 꽤 여러 단계를 거쳐서 타입을 가져오는 구조로 짜여있네요. 이런 경우 모델 설계가 변경이 되는 경우 영향을 받을 수 있을 겁니다. 그래서 이렇게 여러 단계로 네비게이션해서 정보를 가져오는 것이 EntryLine 뿐 아니라 여기저기 나타날 가능성도 있겠죠. 그러면 중간에 한번 이 작업을 위임하는 코드를 넣는 것이 좋지 않을까 싶네요. 예를 들어 Account 내에 getType()을 만들고, 여기서 카테고리를 거쳐서 타입을 가져오게 만들면, Account를 사용하는 여러 로직에서 간단하게 Account의 타입 정보가 필요하니 요청해서 사용하면 되겠다라고 쉽게 이해하고 사용할 수 있을 겁니다. 나중에 타입 정보를 저장하는 방식이 바뀌더라도 Account.getType()을 쓰는 코드에는 문제가 없겠죠. EntryLine의 getAccountType()도 지금 구조를 그대로 가지고 있으면 될 듯합니다. 그런데 질문에서 N+1 얘기를 꺼내신 것은 이걸 JPA 엔티티로 사용할 경우 조회 성능의 문제가 발생할 여지가 있다고 보는 것이죠. N+1의 N의 크기가 얼마냐가 문제입니다. N이 만약 100만개라면, EntryLIne 하나를 찾기 위해 100만개를 로딩해야겠죠. 이게 N+1 형태로 일어나면 더 큰 문제일 것이겠네요. 만약 Journal 안에 EntryLine이 꽤 많다면, 그리고 매번 그 전부를 다 사용할 것이 아니라면 OneToMany를 lazy로 썼다고 하더라도 이걸 매번 전체를 로딩하는 것이 시스템 성능에 미치는 영향이 있지 않을까도 고민이 필요할 듯합니다. 물론 양이 작거나 매번 전체 entry를 다 써야 한다면 fetch join을 사용해서 한번에 조회해두고, 필요한 부분을 사용하면 될 것입니다. 그게 아니라면 필요한 EntryLine을 가져오는 기능을 Repository에 만들어두고 도메인 서비스 또는 애프리케이션 서비스에서 EntryLine을 가져와서 로직을 수행하는 코드로 만드는 방법을 트레이드 오프해야할 것입니다. 한번에 전체 컬렉션을 메모리로 다 로딩하는 문제를 제외하고 단지 N+1이 문제라면, 현재 lazy loading 구조로 관계가 연결되어 있으니 리포지토리에 Journal을 단독으로 가져오고 필요할 때 lazy하게 연관 엔티티를 다시 조회하는 방식과 Journal부터 EntryLine, Account, Category까지 한번에 fetch join으로 가져오는 두 가지 메소드를 만들고, 이후 데이터를 사용하는 방식에 따라서 선택하도록 하면 될 것입니다. N+1을 해결한다고 매번 매번 fetch join으로 Journal에 연결된 나머지 엔티티 전체를 로딩하는 것도 바람직하지 않기 때문에 선택적으로 데이터를 가져오는 리포지토리 구성을 해두는 것이 실전에서는 현실적입니다. 더 궁금하신 것이 있으면 댓글을 남겨주세요.
- Lượt thích
- 0
- Số bình luận
- 3
- Lượt xem
- 128
Hỏi & Đáp
N+1 관련해서 질문있습니다.
안녕하세요.질문의 내용을 보니 두 가지로 구분해서 답변을 드리는게 좋겠습니다. 우선은 도메인 오브젝트 관점에서 설계에 대해서 먼저 생각해봐야 합니다. 이 경우엔 N+1은 관심사가 아니죠. 여기서 중요한 것은 각 오브젝트가 자신이 가진 정보를 가지고 필요한 기능을 제공하면서 다른 오브젝트와 적절하게 협력하는가의 문제입니다. 객체 설계 관점에서는 대체로 잘 작성된 코드로 보입니다. Journal 안에 Entry를 모두 가지고 있다면 이를 이용해서 필요한 Entry를 가져와 로직을 수행하도록 되어있네요. 그런데 질문하신 EntryLine의 getAccountType() 부분은 로직은 바르게 작성되어 있지만 꽤 여러 단계를 거쳐서 타입을 가져오는 구조로 짜여있네요. 이런 경우 모델 설계가 변경이 되는 경우 영향을 받을 수 있을 겁니다. 그래서 이렇게 여러 단계로 네비게이션해서 정보를 가져오는 것이 EntryLine 뿐 아니라 여기저기 나타날 가능성도 있겠죠. 그러면 중간에 한번 이 작업을 위임하는 코드를 넣는 것이 좋지 않을까 싶네요. 예를 들어 Account 내에 getType()을 만들고, 여기서 카테고리를 거쳐서 타입을 가져오게 만들면, Account를 사용하는 여러 로직에서 간단하게 Account의 타입 정보가 필요하니 요청해서 사용하면 되겠다라고 쉽게 이해하고 사용할 수 있을 겁니다. 나중에 타입 정보를 저장하는 방식이 바뀌더라도 Account.getType()을 쓰는 코드에는 문제가 없겠죠. EntryLine의 getAccountType()도 지금 구조를 그대로 가지고 있으면 될 듯합니다. 그런데 질문에서 N+1 얘기를 꺼내신 것은 이걸 JPA 엔티티로 사용할 경우 조회 성능의 문제가 발생할 여지가 있다고 보는 것이죠. N+1의 N의 크기가 얼마냐가 문제입니다. N이 만약 100만개라면, EntryLIne 하나를 찾기 위해 100만개를 로딩해야겠죠. 이게 N+1 형태로 일어나면 더 큰 문제일 것이겠네요. 만약 Journal 안에 EntryLine이 꽤 많다면, 그리고 매번 그 전부를 다 사용할 것이 아니라면 OneToMany를 lazy로 썼다고 하더라도 이걸 매번 전체를 로딩하는 것이 시스템 성능에 미치는 영향이 있지 않을까도 고민이 필요할 듯합니다. 물론 양이 작거나 매번 전체 entry를 다 써야 한다면 fetch join을 사용해서 한번에 조회해두고, 필요한 부분을 사용하면 될 것입니다. 그게 아니라면 필요한 EntryLine을 가져오는 기능을 Repository에 만들어두고 도메인 서비스 또는 애프리케이션 서비스에서 EntryLine을 가져와서 로직을 수행하는 코드로 만드는 방법을 트레이드 오프해야할 것입니다. 한번에 전체 컬렉션을 메모리로 다 로딩하는 문제를 제외하고 단지 N+1이 문제라면, 현재 lazy loading 구조로 관계가 연결되어 있으니 리포지토리에 Journal을 단독으로 가져오고 필요할 때 lazy하게 연관 엔티티를 다시 조회하는 방식과 Journal부터 EntryLine, Account, Category까지 한번에 fetch join으로 가져오는 두 가지 메소드를 만들고, 이후 데이터를 사용하는 방식에 따라서 선택하도록 하면 될 것입니다. N+1을 해결한다고 매번 매번 fetch join으로 Journal에 연결된 나머지 엔티티 전체를 로딩하는 것도 바람직하지 않기 때문에 선택적으로 데이터를 가져오는 리포지토리 구성을 해두는 것이 실전에서는 현실적입니다. 더 궁금하신 것이 있으면 댓글을 남겨주세요.
- Lượt thích
- 0
- Số bình luận
- 3
- Lượt xem
- 128
Hỏi & Đáp
도메인 모델에서 관계와 규칙을 구분하는 방법
흥미로운 질문이네요.제 생각에는 관계도 규칙의 일부라고 생각합니다. 도메인에서 엔티티가 만들어지고 다른 엔티티와 관계를 맺게 될 때 어떤 규칙을 따라서 진행이 되어야 할 겁니다. 도메인의 규칙과 로직은 대부분 변경에 관한 것입니다. 등록이나 수정, 삭제와 같은 것들이죠. 조회는 사실 애플리케이션 레벨의 규칙일 수는 있지만, 대체로 도메인의 로직에 포함되지는 않을 겁니다. 조회가 규칙에 들어가는 건 결국 변경과 관련된 것이겠죠.관계는 엔티티를 식별하고 정의하면서 자연스럽게 만들어질 겁니다. 그런데 그런 관계는 결국 엔티티의 속성이나 변경 로직과 연결되어서 쓰이겠죠.
- Lượt thích
- 0
- Số bình luận
- 2
- Lượt xem
- 114
Hỏi & Đáp
헥사고날 아키텍처에서의 배치, 시큐리티, 비동기 이벤트 처리는 어떻게 하나요?
안녕하세요. 많은 질문을 주셨네요. 🙂배치 프로세스 자체는 헥사고날을 이용하는 어댑터로 구현하면 됩니다. 배치에서 필요로 하는 애플리케이션 서비스 기능과 관련 도메인은 내부에서 구현하겠죠. 일반 애플리케이션에서 보안, 인증 등은 기본적으로 헥사고날 밖에서 처리하게 되겠죠. 헥사고날 내부에는 보안 검증이 완료된 요청이 전달이 되는게 일반적입니다. 그런데 도메인 레벨에서도 일종의 보안이 필요한 경우도 있습니다. 도메인 모델을 설계하다 보면, 특히 권한과 관련해서 로직이 수립될 수가 있고 이건 도메인의 관심사입니다. 예를 들어 게시글은 작성자와 관리자만 수정할 수 있다 같은 것들은 애플리케이션 내에서 처리되는 것이 자연스럽습니다. 헥사곤 외부의 액터와 어댑터는 항상 완벽하지 않고, 버그가 있을 수도 있고, 악의적인 요청이 침투할 수도 있습니다. 도메인의 정합성과 규칙을 깨뜨릴 수 있는 행위는 헥사곤 내부에서 다루어야 합니다. 이벤트 자체는 아주 기초적인 개념이기 때문에 어떤 것으로 분류한다고 정해져있지는 않습니다. 다양한 종류의 이벤트가 있기 때문이죠. 그중에서 도메인 이벤트를 사용하기로 했다면, 그건 당연히 도메인 내부에 등록하고 가능하면 도메인 계층에서 발행을 합니다. 주문이 접수됐다는 이벤트가 있다면 주문 접수로 상태를 변환하는 로직에서 이벤트가 던져지면 되겠죠.도메인 이벤트를 받아서 처리하는 곳은 다양합니다. 일차적으로 애플리케이션 내부에서 처리할 수 있습니다. 이벤트 스토밍을 해보면 이런 케이스를 많이 발견할 수 있습니다. 이걸 이벤트로 처리할지, 아니면 직접 호출로 처리할지 등은 고민해볼 주제입니다. 어떤 경우엔 도메인 이벤트를 애플리케이션 밖에서 처리해야 합니다. 어댑터 내부에서 이벤트를 접수해야 하는 경우도 많습니다. 외부 시스템과 연동, 또 트랜잭션 밖에서 처리해야 하는 경우에 많이 사용합니다. 도메인 이벤트 외에도 스프링의 이벤트 시스템을 활용하는 다양한 방법이 있습니다. 또, 이벤트를 다른 시스템으로 전송해야 하는 필요도 있습니다.이와 관련해서는 클린 스프링의 두 번째 주제에서 설명드릴 계획입니다.
- Lượt thích
- 0
- Số bình luận
- 2
- Lượt xem
- 192
Hỏi & Đáp
어댑터에서 도메인에 직접 의존하는 경우에 대해
사실 실무에서는 계층간 의존 방향성이나 독립성이 그렇게 엄격하게 지켜지지 않고 개발되는 경우가 많습니다. 애플리케이션 서비스 계층은 항상 DTO를 주고 받도록 만드는 원칙을 가지고 개발하는 유명 기업 개발팀도 많이 봤습니다.그럼에도 이번 강의가 헥사고날 아키텍처가 제공하는 유익과 가치를 잘 따르려면 어떤 접근이 가능할지에 대해서 이야기하는 것이니만큼, 헥사고날 아키텍처, 또 클린 아키텍처가 추구하는 의존 관계를 잘 지키면 어떻게 될지를 계속 살펴보려고 하고, 그 기준에 따라서 어떤 모습이 나오는지 탐구해보고 싶었습니다.언제부턴가 많은 분들이 도메인 계층의 요소가 어댑터 계층(인프라, 컨트롤러 등등)에 노출되는 것을 문제라고 이야기하는 것을 잘 알고 있습니다. 엔티티를 컨트롤러에서 직접 리턴하는 초간단 방식이 결국 큰 문제를 일으키기도 하고, 뷰 역할을 하는 코드나 API 응답 생성만 해야 하는 코드에서 도메인 로직을 건드리는 문제 등등도 많이 이야기 되고 있죠. 그래서 만만하게 애플리케이션 계층은 DTO로 변환된 어댑터가 필요로 하는 정보만 노출한다는 것을 해결책으로 삼습니다. 하지만 저는 이 방식이 단순한 경우엔 별 차이가 없겠지만, 시간이 흘러서 동일 포트를 이용하는 어댑터가 여럿이 되고(웹 API, 배치, 다른 메시징 클라이언트 등등), 그때마다 DTO가 자꾸 새로 만들어지거나, 변경이 되는 것을 자주 관찰했습니다. 어댑터 앞 단의 액터의 요구 변경이나 배치 로직 등의 변경이 있을 때마다 이렇게 DTO가 바뀌고, 더 나아가서 리포지토리 로직도 따라서 바뀌는 등의 예는 정말 아주 오래 전부터, DB SQL에 UI 로직까지 반영되던 방식으로 개발하던 시절의 유물이기도 합니다. 강의에서 제가 강조했던 것처럼 그런 DTO의 과도한 사용은 결국 어댑터와 애플리케이션 서비스의 강한 의존를 만들고, 그에 따른 잦은 변경이 여러 계층에서 일어나게 만든다고 봅니다. 이게 DTO라는 아주 심플한 구조여서 그렇지, 오래전 서비스 계층에서 뷰에서 조립할 HTML 조각을 생성해서 보내주던 것과 솔직히 다르지 않다고 봅니다. 어탭터가 도메인을 이용하는 것은 사실 자연스럽습니다. 도메인 중심의 개발에서 모든 코드는 도메인의 개념과 보편언어, 도메인 로직 등에 의존하는 것이 자연스럽습니다. 한번 모델이 잘 설계되면 가장 변하지 않고 유지되는 것이 도메인이고, 그 부분이 변경되는 크리티컬한 설계 변경이 있다면 그에 따라 다른 계층의 변화도 자연스럽게 이어지는 것이 맞다고 봅니다. 물론 경우에 따라 포트 입장에서 단순한 값을 리턴하는 것으로 그 의도를 잘 나타낼 수 있다면 간단한 값이나 작은 DTO를 리턴하게 하는게 맞습니다. 이건 단지 엔티티를 읽기전용으로 만든 오브젝트에 옮겨 담는 것과는 다르고, 어댑터와 그 앞의 요구 변화에 따라 도메인이 변경되지 않았음에도 서비스계층의 DTO가 자꾸 바뀌는 일이 일어나지는 않을 것입니다. 하지만 대부분은 애그리거트나 엔티티를 리턴하고, 이를 어떻게 클라이언트에게 보여줄지는 컨트롤러 코드 내의 뷰 로직을 담당하는 응답 객체 생성 시점에 결정되는 것이 훨씬 낫다고 봅니다.도메인은 가장 소중하고 중요하고, 그리고 애플리케이션 모든 영역이 이에 의존하는 것이 너무 자연스럽습니다. 물론 요즘은 제가 주장하는 것이 현장에서 보편적으로 통용되는 주류 논리가 아닐 수 있습니다. 언제부턴가 DTO 만능 시대라, DTO는 특정 계층에 속하지 않는 모든 계층에서 사용하는 보편적인 오브젝트로 취급하기도 합니다. 요즘 AI에게 스프링 개발을 시키면 그렇게 만들더라고요. 하지만 계층형 아키테처의 원칙과 헥사고날 아키텍처가 추구하는 것을 잘 지키면서 유지보수성이 좋게 발전하는 코드가 주는 가치가 충분히 있다고 믿기에 이런 구조를 따르려고 합니다. Provided interface가 어댑터의 요구에 의존한다는 이야기도 하셨는데, 이건 반대로 접근하는 것이 헥사고날 아키텍처의 원칙인 듯 싶습니다. 기본적으로 도메인과 이 로직을 담은 헥사곤은 우선 도메인이 제공하는 기능에 충실하게 만들어지고, 결과적으로 액터, 그리고 이 중계 역할을 하는 어댑터가 이용하는 것이지, 어댑터의 요구를 충족하기 위해서 포트와 인터페이스를 만든다고 생각하지 않으면 좋겠습니다. 물론 액터가 없는 서비스는 없을 것이지만, 도메인 관점에서 설계한 작업의 결과로서 인터페이스가 정의되고 이게 서비스에서 제공되는 것이어야지, 단지 외부의 요구가 있으니까 이걸 만든다는 식으로 접근하지 않는게 도메인 모델 중심의 개발 방법일 듯합니다. 외부의 요구가 존재한다는 것을, 먼저 도메인 모델에 반영하고 안에서부터 설계해서 밖으로 나가는 것이 자연스럽습니다. 더 이야기하고 싶으시면 댓글을 남겨주세요.
- Lượt thích
- 0
- Số bình luận
- 2
- Lượt xem
- 187





