토비
@tobyilee
수강생
15,769
수강평
1,018
강의 평점
5.0
호주에 살고 있는 소프트웨어 개발자입니다. 30년간 다양한 분야의 시스템과 서비스를 개발해본 경험이 있습니다.
스프링 프레임워크와 관련 기술을 좋아하고 JVM 기반 언어를 주로 사용합니다.
한국스프링사용자모임(KSUG)을 설립하고 활동했고, 토비의 스프링이라는 책을 쓰기도 했습니다.
개발과 관련된 다양한 주제에 관해 이야기하는 것을 좋아합니다.
강의
수강평
- [밋업 Vod] 31년차 개발자가 전하는 "AI시대, 개발자로 살아가는 법"
- 토비의 클린 스프링 - 도메인 모델 패턴과 헥사고날 아키텍처 Part 1
- 토비의 스프링 6 - 이해와 원리
게시글
질문&답변
MemberService와 EmailSender 책임 분리에 대한 질문
안녕하세요. 좋은 질문을 해주셨네요. 클린 스프링 강의 시리즈는 말씀해주신 그런 의문을 가지고 문제를 풀어가는 과정을 보여드리는 것이 목적입니다. 일단 시작 단계에서는 우선 기능을 빠르게 구현하고, 테스트를 만든 뒤에 가장 단순한 방법부터 시작해서 문제를 차근차근 풀어나가는 과정에 있다고 보시면 됩니다. 매번 어떤 의문을 가지고 어떤 시도를 하는지가 중요합니다. 가입하면 환영 이메일을 보내는 것은 아주 기본적인 도메인 모델에서 발견할 수 있는 기능이지만, 서비스가 성장하다보면 꽤 많은 변화가 필요하게 됩니다. 메일 내용이 변경되는 것은 아주 단순한 변화입니다. 그보다 더 복잡한 조건이 붙을 수도 있죠. 이벤트에 의해서 가입이 됐을 경우 메일 내용이 완전히 다르게 나갈 수도 있죠. 가입을 했지만 메일이 아닌 사전 인증된 메신저로 축하 메시지를 보낼 수도 있습니다. 글로벌 서비스가 돼서 여러 언어로 다르게 메일이 작성되야 할 수도 있고, 최종 메일 발송은 시간을 두고 배치로 처리하거나, 일정 승인 과정을 통해서 나가야 할 수도 있습니다. 시간이 지나면 회원 가입 직후에 진행되어야 할 작업이 이보다 훨씬 많아지게 됩니다. 각종 자료 업데이트부터, 포인트 적립을 위한 기초 작업 진행, 모니터링을 위한 통보 기능 등등. 이렇게 되면 더 큰 문제는 회원 가입 로직보다 후처리 작업이 커지고, 이게 또 시간을 많이 잡아먹습니다. 메일 발송도 꽤나 시간 걸리는 작업이거든요. 그러는 동안 DB 트랜잭션은 묶여있어서 가입이 갑자기 폭증하는 경우 성능에도 영향을 미칩니다. 그래서 트랜잭션 밖으로 이걸 분리하는 작업도 필요하죠.중요한 건 현재 시점에서 "경제적"으로 할 수 있는 빠른 작업을 해두고, 이후 분리, 추상화 등에 투자할 타이밍을 찾는 것이죠. 이제 가입 기능을 처음 만들었는데 일 가입자 10만명을 상정해서 무리해서 기능을 만들어 둘 필요는 없으니까요. 우선은 가입 축하 메일을 보내는 코드가 회원 가입의 핵심 도메인 로직에서 분리해두는 것으로 충분할 것 같습니다. 이제 강의 시리즈 두 번째가 되면 이를 어떻게 독립적으로 만들지 구체적으로 다루게 될 겁니다.어쨌든 좋은 문제의식을 가지고 지적을 해주셔서 감사합니다. 시간이 걸리는 외부 연동 서비스인데 경우에 따라 내용이 동적으로 변경되어야 하고, 템플릿을 관리해야 하고, 성공 여부를 확인해서 재시도 해야 하는 가입 축하 메일 발송이라면 이걸 어떻게 관리하면 좋을지 고민해보시고, 여러가지 시도를 해보세요.
- 0
- 2
- 22
질문&답변
NonNullApi를 NullMarked로 대체하라고 합니다.
안녕하세요.말씀하신 것처럼 스프링 7부터는 null 체크에 사용되는 기술이 변경되었습니다.강의에서는 당시 스프링 6까지 사용하뎐 스프링 자체 애노테이션을 사용했습니다. 그런데 7에서는 기존 스프링 자체 애노테이션을 deprecated로 바꾸고, JSpecify라는 여러 벤더와 프로젝트에서 적극 채용하고 있는 방식을 사용하는 것으로 정책을 변경했습니다. 여러 도구와 벤더들이 수용한 JSpecify가 가지는 여러 장점을 수용하고, 특히 Kotlin과 경계에서 null 안정성을 대폭 개선하게 되었습니다.클린 스프링은 두 번째 주제를 다룰 때 스프링 7로 업데이트 할 계획을 가지고 있습니다(첫번째 강의의 Part 2까지는 스프링 6를 사용합니다). 그때 null을 다루는 새로운 방법도 적용해보겠습니다.
- 0
- 2
- 33
질문&답변
헥사고날 part2 강의 출시 예정일 문의 드립니다.
안녕하세요.죄송하게도 계속 Part 2 출시 일정이 미뤄지고 있었습니다. 제 건강 상태가 많이 안 좋아서, 녹화하기가 힘들었는데요. 좀 더 힘을 내서 최대한 빨리 마무리해보려고 하고 있습니다. 이번엔 꼭 2월까지는 마무리해보도록 하겠습니다.
- 0
- 2
- 119
질문&답변
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
- 66
질문&답변
39. 문서와 코드 다듬기 updateInfo 테스트 질문 있습니다.
안녕하세요. 말씀하신 대로 unique 제약 조건이 있는 경우 NULL 값은 상관없지만 빈 문자열은 중복 문제가 생길 수 있습니다. 강의 때는 그 부분을 체크 못했네요. 이전에 같은 질문을 주신 분이 계셔서 해결 방법을 이야기했습니다. 빈 문자열로 세팅하더라도, 값을 NULL로 등록되도록 조정을 하는 방법을 사용해야 합니다. 해당 질문은 다시 찾아보겠습니다.
- 0
- 2
- 38
질문&답변
수업을 잘 듣고 있습니다.
안녕하세요. 노션으로 작성한 문서는 강의자료에 pdf로 넣어뒀습니다. 강의자료에서 다운 받으실 수 있습니다.
- 0
- 2
- 67
질문&답변
PT 문의사항
안녕하세요. 제가 사용한 도구는 애플 키노트(Apple Keynote)입니다.
- 0
- 1
- 65
질문&답변
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
- 65
질문&답변
애플리케이션의 JPA 리턴과 도메인 모델
도메인 모델에 적용된 기술의 변경은 꽤 큰 변화입니다. MyBatis에서 JPA로의 변화는 단순히 저장 기술의 변화 이상이죠. 이전엔 DB 테이블의 구조에 종속적인 바인딩 모델을 썼다면, JPA는 그 부분을 더 유연하게 매핑할 수 있고(테이블과 엔티티가 1:1이 아니어도 가능하니까요), 도메인 모델에 충실하게 엔티티를 설계할 수 있는 기회를 제공해줬습니다. 이런 변화는 이후에 저장 기술이 다시 변경되더라도 JPA 시절에 만든 도메인 엔티티(이게 JPA용으로 현재 쓰이지만, 애노테이션만 무시한다면 이후 다른 기술을 쓰더라도 그대로 사용 가능합니다. JPA 엔티티는 실행 기능과는 무관한 애노테이션을 제외하고 코드만 보자면 JPA 기술에 종속적이지 않고, 오히려 도메인 설계에 충실하게 작성 가능하니까요.애플리케이션에서 엔티티를 리턴하는 것이 더 나은 선택이라는 제 의견은, 반대로 웹 계층에 종속된 DTO를 애플리케이션이 매번 알고 의존하지 않는 게 더 낫기 때문입니다. 엔티티 대신, 엔티티와 동일한 구조를 가졌지만 읽기 전용으로 만들어진 불변 DTO에 매번 매핑해서 리턴해도 같은 효과를 볼 수 있습니다. 하지만 이 방법이 주는 이익에 비해서 수고롭고, 버그가 들어갈 가능성이 높아지고, 매우 번거롭죠. 그래서 엔티티를 애플리케이션에서 리턴할 때는 정보를 전달하는 오브젝트로 사용하는 선택을 한 것입니다. 도메인 모델을 중심에 두고 개발하는 방식을 포기하지 않는다면, 저장 기술이 변경된다 하더라도 JPA 엔티티를 리턴하는 구조로 만들었던 코드는 거의 변경이 필요없을 겁니다.
- 0
- 2
- 92
질문&답변
초기 어플리케이션 구동 시 compose.yml 파싱 오류
자바 25/스프링 7/부트 4가 나오고 메이저 버전 마이그레이션이 큰 이슈가 되고 있네요. 저도 관심있게 살펴보고 있습니다. 알려주신 내용을 포함해서 기존 예제를 최신 버전에 적용하려면 어떤 부분을 살펴봐야할지 차근차근 확인해보고, 조만간 부트 4 브랜치를 하나 만들어서 변경점을 반영해두겠습니다.강의에서는 지금 준비중인 Part2까지는 기존 부트 3버전으로 진행하고, 두 번째 주제를 다루는 강의에서 최신 버전으로 마이그레이션하는 내용을 넣을까 합니다. 메이저 버전 업데이트라서 당분간은 버그와 이슈들이 많이 등장할 듯 보여서 안정되는 시기를 기다리는 게 좋을 것 같네요. 지금 제가 휴가 기간인데 마치도 돌아가면 알려주신 오류부터 확인해보겠습니다. 감사합니다.
- 0
- 2
- 102






