[질문 템플릿] 1. 강의 내용과 관련된 질문인가요? (예) 2. 인프런의 질문 게시판과 자주 하는 질문에 없는 내용인가요? (예) 3. 질문 잘하기 메뉴얼을 읽어보셨나요? (예) [질문 내용] 안녕하세요. 강의 자료에 orderItems.stream().forEach(o -> o.getItem()).getName()); // LAZY 강제 초기화 라고 적혀있어서 그 위의 코드인 List<OrderItem> orderItems = order.getItems()에서는 DB에 SELECT 쿼리를 보내지 않는구나라고 이해했습니다. 근데 확인해보니 아래 코드를 PostMan으로 테스트를 해보았더니 orderItems : null 이지만 SELECT절을 통해 데이터를 조회한 결과를 보였습니다. 또한, order.getOrderItems()를 하지 않아도 orderItem을 조회하는 SELECT절이 나가더라구요 .. @GetMapping("/api/v1/simple-orders") public List<Order> ordersV1() { List<Order> all = orderRepository.findAllByString(new OrderSearch()); for (Order order : all) { order.getMember().getName(); // LAZY 강제 초기화 => 예외 발생 안함 ! why? Open Session in View(OSIV) 때문에 order.getDelivery().getAddress(); // LAZY 강제 초기화 => 예외 발생 안함 ! why? Open Session in View(OSIV) 때문에 order.getOrderItems(); // ? } // 원래는 LazyInitializationException이 발생하는게 정상이지만 OSIV로 인해 발생하지 않음 return all; } 다른 질문을 참고하였을 때 David님께서 아래와 같은 답변을 해주셨습니다. 안녕하세요. Jeongmin Lee님:), 공식 서포터즈 Taewon David Hwang입니다. 지연로딩(Lazy Loading)은 엔티티의 애트리뷰트에 접근할 때 데이터를 가져옵니다. 그러나 하이버네이트에서 지정한 기본타입에 해당되는 것들은 기본적으로 지연로딩을 허용하지 않습니다. 따라서 OrderItem의 애트리뷰트인 name, price, stockQuantty 중 하나라도 접근하게 되면 기본타입에 해당하는 name, price, stockQuantity의 데이터를 모두 불러오게 됩니다. David 선생님 의견에 따르면 엔티티의 에트리뷰트에 접근해야 데이터를 가져오는거 같은데 왜 위 사진같이 DB에 SELECT절을 호출 해 데이터를 가져오는건가요?
안녕하세요. 강의 잘 듣고 있습니다. 섹션2. 8번 강의 11분 13초 쯤에 하시는 말씀에 의문이 들어 질문글 남깁니다. 이 부분 설명하실때 ' 컨슈밍을 할때 파라미터로 받은 초를 슬립을 준 뒤에 소진을 하겠다' 라고 하셨는데, 메시지를 받은 순간 이미 소진이 시작 된 것이고 각각 2초 4초 5초뒤에 소진이 끝나는 것 아닌가요? 조금 헷갈려서 질문 드립니다. 좋은 강의 감사드립니다.
안녕하세요, 토비님. 도메인 모델의 정의 범위에 대해 토비님의 생각이 궁금하여 질문드립니다. 현재 저는 MSA 환경에서 여러 시스템이 나뉘어 있는 구조에서 일하고 있습니다. 보통 팀에서는 “우리가 생성·저장하는 데이터 구조” 를 도메인 모델로 이해하는 경우가 많은데요, 저는 도메인 모델의 범위가 그보다 더 넓다고 생각하고 있습니다. 예를 들어, 주문시스템이 있다고 할 때, 주문시스템은 주문데이터를 생성/관리하는 책임을 지겠지만 이를 위해서 여러 시스템(상품, 프로모션, 결제 등)에서 데이터를 조회해 조합하여 업무 규칙을 수행하는 책임을 가질 수 있습니다 . 이 경우, 다른 시스템이 생성·관리하는 개념이라 하더라도, 주문시스템 내부에서의 목적과 규칙에 따라 주문에 맞는 방식으로 추상화된 모델을 정의하는 것 이 바로 도메인 모델이라고 생각하는데요. 즉, 외부 개념이라도 주문시스템의 책임 하에 있는 로직과 규칙이 있다면, 그것은 주문의 도메인이다 라는 관점입니다. 이와 같은 관점에 대해 토비님은 어떻게 생각하시는지, 도메인 모델 정의의 기준에 대해 의견을 듣고 싶습니다. 감사합니다.
학습하는 분들께 도움이 되고, 더 좋은 답변을 드릴 수 있도록 질문전에 다음을 꼭 확인해주세요. 1. 강의 내용과 관련된 질문을 남겨주세요. 2. 인프런의 질문 게시판과 자주 하는 질문(링크)을 먼저 확인해주세요. (자주 하는 질문 링크: https://bit.ly/3fX6ygx) 3. 질문 잘하기 메뉴얼(링크)을 먼저 읽어주세요. (질문 잘하기 메뉴얼 링크: https://bit.ly/2UfeqCG) 질문 시에는 위 내용은 삭제하고 다음 내용을 남겨주세요. ========================================= [질문 템플릿] 1. 강의 내용과 관련된 질문인가요? (예/아니오) 2. 인프런의 질문 게시판과 자주 하는 질문에 없는 내용인가요? (예/아니오) 3. 질문 잘하기 메뉴얼을 읽어보셨나요? (예/아니오) [질문 내용] localhost:8080/hello를 가면 이렇게 뜹니다ㅜㅜ
@Test void find() { Member member = memberRegister.register(MemberFixture.createMemberRegisterRequest()); entityManager.flush(); entityManager.clear(); Member found = memberFinder.find(member.getId()); assertThat(member.getId()).isEqualTo(found.getId()); } 현재 코드에서 위와 같이 테스트에서 회원 저장을 위해 memberRegister.register를 호출하고 있습니다. 그런데 memberRegister.register에는 단순히 회원을 저장하는 것 외에도 이메일 전송 같은 부가적인 로직이 포함되어 있습니다. 이러한 부가적인 로직때문에 테스트 속도가 느려진다던가, 테스트가 실패하는 원인이 될 수 있다고 생각이 들었습니다. 이처럼 테스트 준비에 필요하지 않은 부가 로직까지 수행되는 상황에서, memberRegister.register를 테스트 준비 용도로 사용하는 것이 적절한지 토비님의 생각이 궁금합니다.
도메인 모델에서의 검증과 애플리케이션 레벨 검증의 경계에 대해 질문드립니다. 현재 도메인 모델에서는 이메일 형식이 올바른지, 닉네임이나 비밀번호가 null이 아닌지 같은 최소한의 조건만 검증하고 있습니다. 반면, 비밀번호가 8자 이상인지, 닉네임이 5자 이상인지 같은 검증은 애플리케이션 레이어에서 처리하고 있습니다. 그런데 닉네임이 5자 이상이어야 한다 같은 규칙도 도메인 규칙으로 볼 수 있지 않을까 라고 생각이 들어 해당 검증 역시 도메인 모델에서 처리하는게 맞지 않나 라는 생각이 드는데, 도메인 모델에서의 검증과 애플리케이션 레벨 검증의 경계는 어디까지 두는 게 좋은지 토비님 의견이 궁금합니다. 말씀하신것을 토대로 예측해 봤을때 형식이나 정책적 요구사항(변경 가능성이 있는 규칙)은 애플리케이션 레이어에서, 도메인의 본질적 불변 조건은 도메인에서 검증하는 걸까요?
강의한번 들었다고 모든 내용이 기억나는게 당연한 것이 아닌 것은 압니다. 그래서 토이프로젝트도 만들어보면서 학습 내용을 이해하려고 합니다. 근데 문제는 기간이 어느정도 지나면, 실질적으로 쓰이는 기술 빼고는 그 원리가 어떻게 발전해서 내가 지금 편리하게 사용하는 기술로 발전되었는지 그 과정이 잘 기억이 나지 않습니다. 예를들어서 aop라고 한다면, aop를 결국 어노테이션을 사용해서 하는 방법들은 기억에 남고 편리하게 사용가능하지만, 프록시를 만들고 프록시 팩토리를 만들고, 어떤식으로 스프링 빈에서 프록시로 대체되는건지 그러한 발전과정들이 잘 기억이 나지 않습니다. 트랜잭션도 결국 트랜잭션 어노테이션을 이용하는것은 기억에 남는데, 그 트랜잭션 어노테이션을 사용할 수 있기까지의 과정이 자세하게는 기억이 안납니다. 지금도 기억나는건 트랜잭션을 aop형식으로 빼내는 이유가 서비스단에서 db기술을 이용하지 않기위함, 온전한 로직을 사용하기위함 그정도밖에 기억이 안나네요... 토이프로젝트를 할 때에 결국 최종 발전방식만 사용하고 옛방식을 사용하지 않는 편이고 개념만 보고 이해하고 넘어만 가기에 기억에 오래남지 않아서 그런건지... 이런 발전과정들도 완벽하게 알고있어야하는지 궁금합니다. 다시보면 이해하는데까지 오래걸리진 않지만 하루만에 다시 다 볼수있는 정도는 아니기도 하고, 다른것들도 공부해야할 것이 남아있어서 고민이되네요. jpa 강의듣기 전에 토이프로젝트 만들고있는것도 우선 완성해두고 db sql도 공부한번 하고 jpa강의를 들어야하지 않나 생각중인데 생각보다 토이프로젝트도 오래걸리고... 취업준비같은거 하면 공부에 집중이 안되어서 그동안에 또 까먹게 되고 참 어렵네요..
Member#create() 메서드를 register로 공통언어를 바꾸셨는데, 뜻이 모호해진 것 같아서 질문드립니다! 제가 생각하기에 register(등록)이라는 단어는 생성과 영속화라는 두가지의 행위를 함축한 단어로 느껴집니다. 따라서 MemberService#register()는 너무 자연스럽습니다. 실제로 Member를 생성한 후에 MemberRepository를 통하여 영속화까지 하는 내용으로 구현되어있습니다. 하지만 Member#register()는 이와 다르게 Java 객체를 생성하기만 하는 것이라, 메서드명과 실제 동작이 불일치한다고 느껴집니다. 하지만 또 동시에 도메인 모델을 글로 작성하는 과정에서 '멤버를 등록한다.' 라는 말을 쓰는 것은 자연스럽습니다. 그 구현이 코드적으로 Member에 있는 것만이 부자연스럽습니다. 이런 경우에는 Member라는 객체만으로 도메인 모델을 온전히 표현해내기가 어려운 것일까요? 해당 모델을 표현하기 위해서는 MemberService 같은 코드가 꼭 필요한 걸까요?
멤버를 저장하는 테스트에서 EntityManager#flush()를 호출해서 플러시되어 DB까지 쿼리문이 제대로 가는지 검증하시는 것을 보았습니다. (저는 플러시까지 테스트해볼 생각을 못했어서 아주 인상깊었습니다 ㅎㅎ) 하지만 해당 부분이 검증문 없이 홀로 호출되고 있습니다. 좀더 의도를 드러내기 위해서는 assertThatCode(() -> em.flush()).doesNotThrowAnyException() 처럼 코드를 작성하는게 더 좋지 않을까 싶어서 질문드립니다! 적용 예시 @Test void 멤버를_저장한다() { var member = MemberFixture.createAny(); memberRepository.save(member); assertThat(member.id()).isNotNull(); assertThatCode(() -> em.flush()) .doesNotThrowAnyException(); }
MemberRepositoryTest는 EntityManager 나 @DataJpaTest 등 Jpa와 깊게 관련되어있는 테스트로 이해됐습니다. EntityManager#flush() 가 잘 호출되는지 확인하는 부분도 그런 이유에서 이해됩니다. 하지만 테스트명이 MemberRepositoryTest인 점에서 헷갈리는 부분이 발생합니다. MemberRepository 인터페이스의 메서드를 테스트하는 것이 목적이라면 좀 더 추상화된 테스트를 작성하는 것이 옳지 않았나? 라는 것이 주된 궁금증입니다. 물론 Spring Data JPA 특성상 런타임에 구현체가 만들어지기 때문에, 테스트 대상은 인터페이스를 사용해야 한다는 것은 이해합니다만, 최소한 클래스명은 좀 더 구체적으로 작성해주는 것이 의도에 부합하지 않나? 해서 질문드립니다! 어떻게 생각하시나요?
해당 코드에서 자기 자신의 프로필 address와 비교하는 로직을 도메인에게 맡기는 건 어떨까요? 토비님! 이렇게 해도 되는건지 아니면 따로 빼신 이유가 있으신 지 궁금합니다. private void checkDuplicateProfile(Member member, String profileAddress) { if (profileAddress.isEmpty()) return; Profile currentProfile = member.getDetail().getProfile(); if (currentProfile != null && currentProfile.address().equals(profileAddress)) return; if (memberRepository.findByProfile(new Profile(profileAddress)).isPresent()) { throw new DuplicateProfileException("이미 존재하는 프로필 주소입니다: " + profileAddress); } } 이런 식으로요! private void checkDuplicateProfile(Member member, String profileAddress) { if (profileAddress.isEmpty()) return; if (!member.isProfileNull() && member.isProfileEquals(profileAddress)) return; if (memberRepository.findByProfile(new Profile(profileAddress)).isPresent()) { throw new DuplicateProfileException("이미 존재하는 프로필 주소입니다: " + profileAddress); } }
안녕하세요 먼저 좋은 강의 정말 감사드립니다. 섹션2 인가코드(프론트에서 발급) 강의와 강의 자료에서 get 요청을 통해 인가코드 전달을 한다 고 설명하셨는데, 프론트엔드 코드에서는 post로 요청하고 있고, http body로 jwt 토큰을 받으려면 post로 요청해야하는게 맞는 것 같은데, 제가 잘못 이해하고 있는건지 잘못 설명하신건지 헷갈려서 질문글 남깁니다. const response = await axios.post("http://localhost:8080/member/google/doLogin", {code});
안녕하십니까 강사님. 강의를 잘 듣고있던 중에 질문이 생겨서 드립니다. AsyncExcutor의 설정값을 설정하는 부분에서 서비스 앞단에서 대고객 트래픽을 받을때 maxPoolSize를 무한으로 설정하고, 큐의 크기를 0으로 두어서 빠르게 스레드를 생성하여 요청을 처리한다고 하셨습니다. 또한 그 설정을 한 스레드 풀의 작업들은 대체로 레디스 캐싱된 값을 뿌려주는것과 같이 응답시간이 빠르게 보장된 요청의 경우에 위와같은 설정값을 설정한다고 이해했습니다. 실제 실무에서 위와같이 설정해도 무한대까지 스레드가 생기지는 않으니 문제가 없다?고 하신것 같은데 그 이유가 잘 이해가 안되는 것 같습니다. 제 나름대로 추론을 해봤는데요. 우선 redis에 캐싱된 결과를 뿌리는 요청을 예로 들어서 보면, I/O 바운드 작업이므로 스레드가 빠르게 증가하여 요청을 처리하는게 중요하고, 스레드가 증가하더라고 CPU 사용량의 증가가 CPU 바운드 작업처럼 크게 증가하지 않을것이다. 따라서 스파이크 트래픽이 발생해도 서버가 죽거나 문제가 생기지는 않을것이다. 이렇게 생각을 해봤는데 좀 더 정확한 답변을 듣고싶어서 질문드립니다!
안녕하세요, 토비님. 주니어로서 아직 부족한 점이 많지만, 궁금한 점이 있어 또 이렇게 질문드립니다. Section 3에서 소개된 splearn 도메인 강의를 듣기 전에, 스스로 먼저 도메인 모델링을 해보는 시간을 가졌습니다. 그 과정에서 회원 , 수강 , 강의 등의 개념 외에 수강신청 이라는 도메인을 도출했는데요, 그 이유는 “수강은 수강신청 후 결제가 완료되어야 성립된다” 는 제 안의 암묵적인 경험에 의한 도메인 해석 때문이었습니다. 그런데 사실 강의에서 제시된 사용자 스토리에는 결제 라는 개념이 존재하지 않았습니다. 그럼에도 불구하고, 과거 경험에서 비롯된 도메인이 자연스럽게 설계에 스며들었고, 결과적으로 오버엔지니어링으로 이어졌다 는 생각이 들었습니다. 이런 식의 ‘의도되지 않았지만 그럴듯한 도메인’ 을 설계에 넣고 싶은 유혹은 실제 회사에서 설계할 때도 자주 느끼는 부분입니다. 그래서 토비님께서는 이런 상황에서 어떤 기준으로 도메인을 포함하거나 배제하시는지, 그리고 본인의 경험을 어떤 방식으로 설계에 녹이시는지 가 궁금해졌습니다. 바쁘시겠지만, 짧은 의견이라도 들려주시면 큰 도움이 될 것 같습니다. 감사합니다!
안녕하세요 토비님, 강의 너무 재미있게 듣고 있습니다. 인터페이스를 도메인 계층에 둘지, required 포트에 둘지 결정하는 기준이 있을까요? Splearn의 코드를 예로 들어보면, 도메인 서비스 인터페이스인 passwordEncoder 는 어댑터 계층에서 구체적인 기술로 구현된다는 점에서 EmailSender , MemberRepository 같은 required 포트의 인터페이스와 유사하게 느껴집니다. 이들 모두 외부 세계(또는 특정 기술)와 애플리케이션을 분리하는 역할을 하기 때문입니다. 만약 애플리케이션 서비스에서 passwordEncoder 를 사용해 비밀번호를 암호화한 후, 암호화된 비밀번호( passwordHash )를 Member.register 메서드에 인자로 직접 넘겨준다면, passwordEncoder 역시 required 포트에 위치할 수 있지 않을까요? 도메인 계층도 애플리케이션 서비스에 의존하지 않게 되고요. 그런데도 불구하고 passwordEncoder 를 도메인 계층의 인터페이스로 두신 이유가 궁금합니다. 감사합니다.