tobyilee
@tobyilee
受講生
15,890
受講レビュー
1,044
講義評価
5.0
호주에 살고 있는 소프트웨어 개발자입니다. 30년간 다양한 분야의 시스템과 서비스를 개발해본 경험이 있습니다.
스프링 프레임워크와 관련 기술을 좋아하고 JVM 기반 언어를 주로 사용합니다.
한국스프링사용자모임(KSUG)을 설립하고 활동했고, 토비의 스프링이라는 책을 쓰기도 했습니다.
개발과 관련된 다양한 주제에 관해 이야기하는 것을 좋아합니다.
講義
受講レビュー
- トビーのクリーン・スプリング - ドメインモデルパターンとヘキサゴナルアーキテクチャ Part 1
- トビーのクリーン・スプリング - ドメインモデルパターンとヘキサゴナルアーキテクチャ Part 1
- [ミートアップ VOD] 31年目の開発者が語る「AI時代、開発者として生きる方法」
- トビーのクリーン・スプリング - ドメインモデルパターンとヘキサゴナルアーキテクチャ Part 1
- トビーのクリーン・スプリング - ドメインモデルパターンとヘキサゴナルアーキテクチャ Part 1
投稿
Q&A
헥사고날 아키텍처에서의 배치, 시큐리티, 비동기 이벤트 처리는 어떻게 하나요?
안녕하세요. 많은 질문을 주셨네요. 🙂배치 프로세스 자체는 헥사고날을 이용하는 어댑터로 구현하면 됩니다. 배치에서 필요로 하는 애플리케이션 서비스 기능과 관련 도메인은 내부에서 구현하겠죠. 일반 애플리케이션에서 보안, 인증 등은 기본적으로 헥사고날 밖에서 처리하게 되겠죠. 헥사고날 내부에는 보안 검증이 완료된 요청이 전달이 되는게 일반적입니다. 그런데 도메인 레벨에서도 일종의 보안이 필요한 경우도 있습니다. 도메인 모델을 설계하다 보면, 특히 권한과 관련해서 로직이 수립될 수가 있고 이건 도메인의 관심사입니다. 예를 들어 게시글은 작성자와 관리자만 수정할 수 있다 같은 것들은 애플리케이션 내에서 처리되는 것이 자연스럽습니다. 헥사곤 외부의 액터와 어댑터는 항상 완벽하지 않고, 버그가 있을 수도 있고, 악의적인 요청이 침투할 수도 있습니다. 도메인의 정합성과 규칙을 깨뜨릴 수 있는 행위는 헥사곤 내부에서 다루어야 합니다. 이벤트 자체는 아주 기초적인 개념이기 때문에 어떤 것으로 분류한다고 정해져있지는 않습니다. 다양한 종류의 이벤트가 있기 때문이죠. 그중에서 도메인 이벤트를 사용하기로 했다면, 그건 당연히 도메인 내부에 등록하고 가능하면 도메인 계층에서 발행을 합니다. 주문이 접수됐다는 이벤트가 있다면 주문 접수로 상태를 변환하는 로직에서 이벤트가 던져지면 되겠죠.도메인 이벤트를 받아서 처리하는 곳은 다양합니다. 일차적으로 애플리케이션 내부에서 처리할 수 있습니다. 이벤트 스토밍을 해보면 이런 케이스를 많이 발견할 수 있습니다. 이걸 이벤트로 처리할지, 아니면 직접 호출로 처리할지 등은 고민해볼 주제입니다. 어떤 경우엔 도메인 이벤트를 애플리케이션 밖에서 처리해야 합니다. 어댑터 내부에서 이벤트를 접수해야 하는 경우도 많습니다. 외부 시스템과 연동, 또 트랜잭션 밖에서 처리해야 하는 경우에 많이 사용합니다. 도메인 이벤트 외에도 스프링의 이벤트 시스템을 활용하는 다양한 방법이 있습니다. 또, 이벤트를 다른 시스템으로 전송해야 하는 필요도 있습니다.이와 관련해서는 클린 스프링의 두 번째 주제에서 설명드릴 계획입니다.
- 0
- 2
- 21
Q&A
어댑터에서 도메인에 직접 의존하는 경우에 대해
사실 실무에서는 계층간 의존 방향성이나 독립성이 그렇게 엄격하게 지켜지지 않고 개발되는 경우가 많습니다. 애플리케이션 서비스 계층은 항상 DTO를 주고 받도록 만드는 원칙을 가지고 개발하는 유명 기업 개발팀도 많이 봤습니다.그럼에도 이번 강의가 헥사고날 아키텍처가 제공하는 유익과 가치를 잘 따르려면 어떤 접근이 가능할지에 대해서 이야기하는 것이니만큼, 헥사고날 아키텍처, 또 클린 아키텍처가 추구하는 의존 관계를 잘 지키면 어떻게 될지를 계속 살펴보려고 하고, 그 기준에 따라서 어떤 모습이 나오는지 탐구해보고 싶었습니다.언제부턴가 많은 분들이 도메인 계층의 요소가 어댑터 계층(인프라, 컨트롤러 등등)에 노출되는 것을 문제라고 이야기하는 것을 잘 알고 있습니다. 엔티티를 컨트롤러에서 직접 리턴하는 초간단 방식이 결국 큰 문제를 일으키기도 하고, 뷰 역할을 하는 코드나 API 응답 생성만 해야 하는 코드에서 도메인 로직을 건드리는 문제 등등도 많이 이야기 되고 있죠. 그래서 만만하게 애플리케이션 계층은 DTO로 변환된 어댑터가 필요로 하는 정보만 노출한다는 것을 해결책으로 삼습니다. 하지만 저는 이 방식이 단순한 경우엔 별 차이가 없겠지만, 시간이 흘러서 동일 포트를 이용하는 어댑터가 여럿이 되고(웹 API, 배치, 다른 메시징 클라이언트 등등), 그때마다 DTO가 자꾸 새로 만들어지거나, 변경이 되는 것을 자주 관찰했습니다. 어댑터 앞 단의 액터의 요구 변경이나 배치 로직 등의 변경이 있을 때마다 이렇게 DTO가 바뀌고, 더 나아가서 리포지토리 로직도 따라서 바뀌는 등의 예는 정말 아주 오래 전부터, DB SQL에 UI 로직까지 반영되던 방식으로 개발하던 시절의 유물이기도 합니다. 강의에서 제가 강조했던 것처럼 그런 DTO의 과도한 사용은 결국 어댑터와 애플리케이션 서비스의 강한 의존를 만들고, 그에 따른 잦은 변경이 여러 계층에서 일어나게 만든다고 봅니다. 이게 DTO라는 아주 심플한 구조여서 그렇지, 오래전 서비스 계층에서 뷰에서 조립할 HTML 조각을 생성해서 보내주던 것과 솔직히 다르지 않다고 봅니다. 어탭터가 도메인을 이용하는 것은 사실 자연스럽습니다. 도메인 중심의 개발에서 모든 코드는 도메인의 개념과 보편언어, 도메인 로직 등에 의존하는 것이 자연스럽습니다. 한번 모델이 잘 설계되면 가장 변하지 않고 유지되는 것이 도메인이고, 그 부분이 변경되는 크리티컬한 설계 변경이 있다면 그에 따라 다른 계층의 변화도 자연스럽게 이어지는 것이 맞다고 봅니다. 물론 경우에 따라 포트 입장에서 단순한 값을 리턴하는 것으로 그 의도를 잘 나타낼 수 있다면 간단한 값이나 작은 DTO를 리턴하게 하는게 맞습니다. 이건 단지 엔티티를 읽기전용으로 만든 오브젝트에 옮겨 담는 것과는 다르고, 어댑터와 그 앞의 요구 변화에 따라 도메인이 변경되지 않았음에도 서비스계층의 DTO가 자꾸 바뀌는 일이 일어나지는 않을 것입니다. 하지만 대부분은 애그리거트나 엔티티를 리턴하고, 이를 어떻게 클라이언트에게 보여줄지는 컨트롤러 코드 내의 뷰 로직을 담당하는 응답 객체 생성 시점에 결정되는 것이 훨씬 낫다고 봅니다.도메인은 가장 소중하고 중요하고, 그리고 애플리케이션 모든 영역이 이에 의존하는 것이 너무 자연스럽습니다. 물론 요즘은 제가 주장하는 것이 현장에서 보편적으로 통용되는 주류 논리가 아닐 수 있습니다. 언제부턴가 DTO 만능 시대라, DTO는 특정 계층에 속하지 않는 모든 계층에서 사용하는 보편적인 오브젝트로 취급하기도 합니다. 요즘 AI에게 스프링 개발을 시키면 그렇게 만들더라고요. 하지만 계층형 아키테처의 원칙과 헥사고날 아키텍처가 추구하는 것을 잘 지키면서 유지보수성이 좋게 발전하는 코드가 주는 가치가 충분히 있다고 믿기에 이런 구조를 따르려고 합니다. Provided interface가 어댑터의 요구에 의존한다는 이야기도 하셨는데, 이건 반대로 접근하는 것이 헥사고날 아키텍처의 원칙인 듯 싶습니다. 기본적으로 도메인과 이 로직을 담은 헥사곤은 우선 도메인이 제공하는 기능에 충실하게 만들어지고, 결과적으로 액터, 그리고 이 중계 역할을 하는 어댑터가 이용하는 것이지, 어댑터의 요구를 충족하기 위해서 포트와 인터페이스를 만든다고 생각하지 않으면 좋겠습니다. 물론 액터가 없는 서비스는 없을 것이지만, 도메인 관점에서 설계한 작업의 결과로서 인터페이스가 정의되고 이게 서비스에서 제공되는 것이어야지, 단지 외부의 요구가 있으니까 이걸 만든다는 식으로 접근하지 않는게 도메인 모델 중심의 개발 방법일 듯합니다. 외부의 요구가 존재한다는 것을, 먼저 도메인 모델에 반영하고 안에서부터 설계해서 밖으로 나가는 것이 자연스럽습니다. 더 이야기하고 싶으시면 댓글을 남겨주세요.
- 0
- 2
- 71
Q&A
Member 도메인이 PasswordEncoder를 받는 구조 질문 있습니다.
흥미로운 질문을 주셨네요.저는 실무 일을 할 때 스프링 기술의 특정 인터페이스 하나가 도메인 또는 서비스 계층의 로직을 독립적으로 잘 표현한다면, 그걸 기술 구현체와 상관없이 가져다 쓰기도 합니다. 대부분은 구현 레벨을 쓰는 영역에서 스프링 기술을 쓸게 분명합니다. 그러면서 인터페이스만 보자면, 패키지 이름에 스프링이 들어가는 걸 제외하면 얼마든지 순수하게 도메인과 애플리케이션 서비스의 아이디어를 표현해 내기도 합니다. 말씀하신 PasswordEncoder가 바로 그런 예이죠. 그럼에도 도메인 코드에 스프링이 들어가는 것을 못겼뎌하는, 스프링에 의존적인 코드라고 맹비난하는 순수한(?) 열혈 개발자들이 가끔 보입니다. 그렇다면 의존이란 무엇인가요? 왜 의존이 나쁘다고 하는 것일까요. 코드 차원에서 의존이란, 내가 의존하는 대상이 변경되거나 바뀌면 내 코드도 따라서 바뀔 가능성이 높은 경우를 말합니다. 예를 들어 비밀번호 기술 A 라이브러리를 도메인과 여기저기서 쓰도록 만들었는데, 비밀번호 기술을 따로 만들어서 클래스와 인터페이스, 메소드 시그니처가 바뀌면 내 코드도 따라서 바뀐다면 의존한다고 할 수 있죠. 그럼 스프링에 의존한다는 것은, 스프링을 안쓰고(과연 그럴 수가 있을지 모르겠지만) 다른 기술을 쓴다면, 혹은 스프링 기술이 업데이트 되면서 핵심 인터페이스를 확 바꾼다면(그런 경우는 역사적으로 거의 없습니다만), 도메인 코드도 바뀌고 그래야 하니 의존한다고 할 수 있겠죠.그렇다면 스프링 시큐리티의 PasswordEncoder 인터페이스도 그렇게 의존성을 부여할 것인가? 라고 한다면 비밀번호를 다루는 코드가 가지는 정말 너무 뻔하고, 대표적인, 지극히 평범한 로직을 담은 것 이상을 그 인터페이스에서 찾을 수가 없습니다.그래서 이럴 때는 스프링 시큐리티 인터페이스를 도메인이나 서비스에서 사용해도 아무런 문제가 없습니다. 매우 희박하지만 스프링을 쓰면서 시큐리티 기술을 다른 것을 쓴다고 하더라도, 비밀번호를 다루는 로직을 가진 다른 구현 기술을 쓰더라도, 스프링의 인터페이스를 구현해서 그 기술을 사용하도록 어댑터를 하나 추가하면 나머지 코드는 전혀 손을 대지 않아도 됩니다. 그러니까 의존 문제가 발생하지 않는다는 것이죠.그럼에도 별도의 인터페이스를 정의해서 사용한 것은, 그래도 괜찮기 때문입니다. 아직 스프링 시큐리티 기술을 잘 검토하지 않은 상태에서 도메인 모델에 담긴 비밀번호 처리 로직을 가지고 얼마든지 인터페이스를 정의하고 도메인 개념을 담아 사용할 수 있죠. 그리고 어댑터에서 스프링 시큐리티의 거의 비슷한 이름의 기술을 사용하도록 만들어도 사실 문제 없습니다.이건 그다지 차이가 없는 결과에 대한, 개발팀의 취향, 경험에 의한 판단의 결과라고 봐도 됩니다. 스프링 시큐리티의 PasswordEncoder에 이미 익숙하고 모두가 잘 이해하고 있고, 하나라도 더 인터페이스를 추가하고 싶지 않다면 스프링 것을 도메인에서 사용해도, 그 코드를 모두가 잘 이해하고 도메인 개념을 잘 표현했다고 생각할 수 있을 겁니다. 반대로 도메인 관점에서 먼저 인터페이스를 설계해서 넣고, 나중에 스프링 시큐리티 어댑터를 만들어서 사용해도 아무 문제는 없겠죠. PasswordEncoder가 도메인 코드로 직접 표현되어 있으면, 아 이런 개념으로 비밀번호를 다루는 것이구나라는 기술 사용에 관한 도메인 로직을 잘 이해할 수도 있을 겁니다. 아직 스프링 시큐리티에 친숙하지 않은 주니어 개발자들이 많다면 이렇게 해도 괜찮아 보이네요. 저는 둘 다 적절하고, 별 차이는 없다고 생각합니다. 제가 혼자 개발한다면 스프링 인터페이스를 가져다 쓸 겁니다. 그러다 혹시 스프링 시큐리티를 안 쓰게 되더라도, 그 인터페이스 클래스 하나만 빼서 프로젝트에 남겨놓고 써도 되겠죠. 아니면 아주 작은 시큐리티 코어 jar 파일 하나를 덤으로 넣어서 쓰던지요.
- 0
- 2
- 56
Q&A
MemberService와 EmailSender 책임 분리에 대한 질문
안녕하세요. 좋은 질문을 해주셨네요. 클린 스프링 강의 시리즈는 말씀해주신 그런 의문을 가지고 문제를 풀어가는 과정을 보여드리는 것이 목적입니다. 일단 시작 단계에서는 우선 기능을 빠르게 구현하고, 테스트를 만든 뒤에 가장 단순한 방법부터 시작해서 문제를 차근차근 풀어나가는 과정에 있다고 보시면 됩니다. 매번 어떤 의문을 가지고 어떤 시도를 하는지가 중요합니다. 가입하면 환영 이메일을 보내는 것은 아주 기본적인 도메인 모델에서 발견할 수 있는 기능이지만, 서비스가 성장하다보면 꽤 많은 변화가 필요하게 됩니다. 메일 내용이 변경되는 것은 아주 단순한 변화입니다. 그보다 더 복잡한 조건이 붙을 수도 있죠. 이벤트에 의해서 가입이 됐을 경우 메일 내용이 완전히 다르게 나갈 수도 있죠. 가입을 했지만 메일이 아닌 사전 인증된 메신저로 축하 메시지를 보낼 수도 있습니다. 글로벌 서비스가 돼서 여러 언어로 다르게 메일이 작성되야 할 수도 있고, 최종 메일 발송은 시간을 두고 배치로 처리하거나, 일정 승인 과정을 통해서 나가야 할 수도 있습니다. 시간이 지나면 회원 가입 직후에 진행되어야 할 작업이 이보다 훨씬 많아지게 됩니다. 각종 자료 업데이트부터, 포인트 적립을 위한 기초 작업 진행, 모니터링을 위한 통보 기능 등등. 이렇게 되면 더 큰 문제는 회원 가입 로직보다 후처리 작업이 커지고, 이게 또 시간을 많이 잡아먹습니다. 메일 발송도 꽤나 시간 걸리는 작업이거든요. 그러는 동안 DB 트랜잭션은 묶여있어서 가입이 갑자기 폭증하는 경우 성능에도 영향을 미칩니다. 그래서 트랜잭션 밖으로 이걸 분리하는 작업도 필요하죠.중요한 건 현재 시점에서 "경제적"으로 할 수 있는 빠른 작업을 해두고, 이후 분리, 추상화 등에 투자할 타이밍을 찾는 것이죠. 이제 가입 기능을 처음 만들었는데 일 가입자 10만명을 상정해서 무리해서 기능을 만들어 둘 필요는 없으니까요. 우선은 가입 축하 메일을 보내는 코드가 회원 가입의 핵심 도메인 로직에서 분리해두는 것으로 충분할 것 같습니다. 이제 강의 시리즈 두 번째가 되면 이를 어떻게 독립적으로 만들지 구체적으로 다루게 될 겁니다.어쨌든 좋은 문제의식을 가지고 지적을 해주셔서 감사합니다. 시간이 걸리는 외부 연동 서비스인데 경우에 따라 내용이 동적으로 변경되어야 하고, 템플릿을 관리해야 하고, 성공 여부를 확인해서 재시도 해야 하는 가입 축하 메일 발송이라면 이걸 어떻게 관리하면 좋을지 고민해보시고, 여러가지 시도를 해보세요.
- 0
- 2
- 75
Q&A
NonNullApi를 NullMarked로 대체하라고 합니다.
안녕하세요.말씀하신 것처럼 스프링 7부터는 null 체크에 사용되는 기술이 변경되었습니다.강의에서는 당시 스프링 6까지 사용하뎐 스프링 자체 애노테이션을 사용했습니다. 그런데 7에서는 기존 스프링 자체 애노테이션을 deprecated로 바꾸고, JSpecify라는 여러 벤더와 프로젝트에서 적극 채용하고 있는 방식을 사용하는 것으로 정책을 변경했습니다. 여러 도구와 벤더들이 수용한 JSpecify가 가지는 여러 장점을 수용하고, 특히 Kotlin과 경계에서 null 안정성을 대폭 개선하게 되었습니다.클린 스프링은 두 번째 주제를 다룰 때 스프링 7로 업데이트 할 계획을 가지고 있습니다(첫번째 강의의 Part 2까지는 스프링 6를 사용합니다). 그때 null을 다루는 새로운 방법도 적용해보겠습니다.
- 0
- 2
- 89
Q&A
헥사고날 part2 강의 출시 예정일 문의 드립니다.
안녕하세요.죄송하게도 계속 Part 2 출시 일정이 미뤄지고 있었습니다. 제 건강 상태가 많이 안 좋아서, 녹화하기가 힘들었는데요. 좀 더 힘을 내서 최대한 빨리 마무리해보려고 하고 있습니다. 이번엔 꼭 2월까지는 마무리해보도록 하겠습니다.
- 0
- 2
- 190
Q&A
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
- 91
Q&A
39. 문서와 코드 다듬기 updateInfo 테스트 질문 있습니다.
안녕하세요. 말씀하신 대로 unique 제약 조건이 있는 경우 NULL 값은 상관없지만 빈 문자열은 중복 문제가 생길 수 있습니다. 강의 때는 그 부분을 체크 못했네요. 이전에 같은 질문을 주신 분이 계셔서 해결 방법을 이야기했습니다. 빈 문자열로 세팅하더라도, 값을 NULL로 등록되도록 조정을 하는 방법을 사용해야 합니다. 해당 질문은 다시 찾아보겠습니다.
- 0
- 2
- 61
Q&A
수업을 잘 듣고 있습니다.
안녕하세요. 노션으로 작성한 문서는 강의자료에 pdf로 넣어뒀습니다. 강의자료에서 다운 받으실 수 있습니다.
- 0
- 2
- 86
Q&A
PT 문의사항
안녕하세요. 제가 사용한 도구는 애플 키노트(Apple Keynote)입니다.
- 0
- 1
- 80




