안녕하세요 토비님 배운 내용을 바탕으로 기존 프로젝트 구조를 변경하는 연습하고 있습니다. 기존 프로젝트가 멀티 모듈로 되어있어 멀티모듈 구조는 그대로 가져가고 싶은데 분리한다면 어떻게 나누어야 할까요? 모듈을 두개로 나누어 api와 core로 구성했는데 adapter, application, domain를 api 모듈에 adapter core 모듈에 application, domain 이렇게 구성하였는데 이렇게 구분해도 괜찮은지 잘 모르겠습니다
record + 클래스 레벨 @Transactional에서 에러가 발생합니다.! 질문은 record 예약어는 final 클래스라서 상속이나 프록시 생성이 불가능한거 같은데 @Transactional 어노테이션은 Spring AOP기반이라 프록시 객체 생성이 안되서 에러가 발생하는거 같은데... 강의에서는 통과가 되더라구요 제가 어떤 부분이 놓쳤을까요?
안녕하세요! 이번 강의 수강 중 서비스 인터페이스에 대해 궁금하여 글을 남기게 되었습니다. 흔히 스프링 프로젝트에서 보통 Service 인터페이스와 ServiceImpl 구현체로 나누어 개발하며 제가 참여하게 된 프로젝트 또한 이러한 구조였습니다. 이미 이전 개발자 분께서 Service 인터페이스와 ServiceImpl 구현체로 나눠놓은 상태였고, 제가 A 기능의 AServiceImpl 안에 특정 서비스 로직이 비대해짐에 따라 해당 코드가 명확하지 않다고 생각되어 메서드로 분리하게 되었습니다. A 엔티티의 상태, 계산 등의 로직으로 private 메서드를 두었습니다. 여기서 동료와 의견이 갈렸습니다: 동료 입장: 1. ServiceImpl 안의 모든 메서드는 반드시 인터페이스에 있어야 한다. 2. 인터페이스는 외부·내부 구분이 아니라, 구현체가 제공하는 기능을 보장하는 것이므로 전부 담아야 한다. 3. 내부 메서드를 둘 거라면 굳이 인터페이스로 추상화할 필요가 없고 그냥 클래스로 쓰면 된다. 제 입장: 1. 인터페이스에는 “외부 계약(=공개 API)”만 있어야 한다. 2. 구현체 내부에서만 쓰이는 로직은 private으로 감추는 게 맞고, 외부에서 호출할 필요가 전혀 없다. 3. 인터페이스를 구현체 내부 헬퍼까지 다 포함하면, 오히려 계약이 불필요하게 비대해지고 역할이 모호해진다. 핵심 쟁점 저는 public 메서드는 모두 인터페이스에 있어야 한다는 데에는 동의합니다. 또한, 단순 public, private 만 있다면, 인터페이스의 역할이 모호하다는 것또한 이해하지만, 이전 코드와의 일관성과 운영 중인 시스템에 대한 변경이 필요할정도의 중대사항이라고 생각하지 않습니다. 궁금한 점은 private/내부 헬퍼 메서드까지 인터페이스에 강제로 올리는 게 맞는지, 인터페이스를 사용하는 의의가 궁금합니다. 긴글 읽어주셔서 감사합니다.
안녕하세요. 강의 정말 잘 들었습니다. 도움이 많이 되어서 수강평은 나중에 꼼꼼히 작성해볼 생각입니다~! ====================== 바쁘시면 이 부분만 읽으셔도 됩니다. ============= 배포 시 jenkins server에서 실제 서버에 배포할때 테스트가 돌아갈텐데 jenkins server에 compose를 통해 작동 하게 될 mysql에 DB schema insert 작업을 해서 동일하게 구조를 맞추고 테스트가 돌아가게 하는 게 좋은 생각일 까요? =========================================================== [세부 사항] 일단 테스트 코드가 local mysql에서 돌아가는 걸로 이해 했습니다. 배포 시 jenkins server에서 실제 서버에 배포할때 테스트가 돌아갈텐데 jenkins server에 compose를 통해 작동 하게 될 mysql에 DB schema insert 작업을 해서 동일하게 구조를 맞추고 테스트가 돌아가게 하는 게 좋은 생각일 까요? 현재 아직 jenkins 배포를 하고 있지는 않고 소스코드 개발중이라 머리속으로만 생각하는 상태입니다. 토비님 의견은 어떠신가요.....?
안녕하세요 토비님 가의 잘 보고 있습니다. DDD는 처음이고 헥사고날방식도 처음이라 질문 수준이 낮은 점 양해 부탁드립니다! 이 강의까지 보고나서 전체코드를 봤을떄 궁금한 점들이 있어서 질문 드립니다! 1번 질문: @Entity public class Member { @Embedded private Address homeAddress; } @Embeddable public class Address { private String city; private String street; private String zipcode; } @Embeded사용할때 데이터 필드를 의미 있는 하나의 묶음 이라는 의미로 사용하는데 profile 이나 email같은 경우에는 컬럼이 하나여도 @Embeded 로 묶으셨는데 그 기준(언제 사용해야하느지)가 궁금합니다. 저라면 그냥 생각 없이 String 으로 받았을거같은데 ….! 2번 질문 : Aggregate root 로 접근해하면 메서드 체이닝 비슷하게 일어나는거 같은데 그런 점은 괜찮나요!? 3번 질문 :. Aggreagate root로 접근하는 방식은 DDD에서만 사용하는지 ?? 기존 JPA + 레이어 아키텍쳐에서도 사용이 가능한건지....? 4번 질문 : 강의에서는 1:1 이였지만 , ManyToOne 단방향 , ManyToOne OneToMany 양방향 관계에서도똑같이 Aggregate root로 접근해서 데이터를 처리해야하는지..? 미리 감사드립니다!!
안녕하세요 토비님 어플리케이션 계층의 port는 도메인과 외부의 연결을 위해 필요 하다고 알고 있는데 jpa나 마이바티스 같은 기술 또한 도메인 외부의 내용이기에 인프라 계층으로 들어간다고 알고 있습니다 그런데 의존성 역전을 위한 레파지토리 인터페이스를 어플리케이션 계층에 만들면 어플리케이션 서비스에서 도메인에 관련된 내용을 너무 깊게 관여하는 것 같아서 질문 드립니다 어플리케이션과 도메인의 경계를 어디까지로 보는게 좋을까요
안녕하세요 토비님 회원 애플리케이션 서비스 테스트 (2) -25:19 에 대해 질문 드립니다. record 에 @Transactional을 사용하면 실행에는 문제가 없으나 인텔리제이에서 Classes annotated with '@Transactional' could be implicitly subclassed and must not be final 이와같은 경고 메세지를 주는데 무시하고 지나가도 되는건지요?
토비님 안녕하세요. 39. 문서와 코드 다듬기 강의의 updateInfo 테스트 부분 질문이 있습니다. 강의 28:30 부근에서 updateInfoFail() 에 코멘트를 달게 되는데 성공케이스에 대한 코드는 updateInfo() 부분으로 옮기는 것도 좋아보이는데 토비님 생각이 궁금합니다 . updateInfoFail() 의 마지막에 member 가 member2 의 프로필 주소와 중복되는 테스트가 있으므로, member2 의 프로필 주소를 설정해주는 코드가 필요하지만, 하나만 있어도 괜찮지 않을까 생각합니다. 그래서 나머지 성공에 대한 코드는 updateInfo() 메소드로 옮겨 "상세 정보 수정이 가능한 경우"들을 나열하는게 좋다고 생각했습니다. 똑같은 준비작업이 반복되다보니 한 번 세팅해놓고 여러 케이스를 다 검증해보는 것이 낫다고 하셨는데, 현재 테스트 클래스 메소드가 성공/실패로 나뉘어져있어 성공하는 부분은 다른 메소드로 이동하는게 좋아보인다 생각했습니다. 아래 코드에서 성공 케이스와 실패 케이스로 나누어보았습니다. 코드 updateInfo() @Test void updateInfo() { Member member = registerMember(); Long memberId = member.getId(); memberRegister.activate(memberId); entityManager.flush(); entityManager.clear(); var updateRequest = createMemberInfoUpdateRequest(); member = memberRegister.updateInfo(memberId, updateRequest); assertThat(member.getDetail().getProfile().address()).isEqualTo(updateRequest.profileAddress()); // 기존 프로필 주소로 계속 변경 요청 가능 memberRegister.updateInfo(memberId, updateRequest); // 다른 프로필 주소로 변경 가능 memberRegister.updateInfo(memberId, createMemberInfoUpdateRequest("omg123")); // 프로필 주소 제거 가능 memberRegister.updateInfo(memberId, createMemberInfoUpdateRequest("")); } updateInfoFail() @Test void updateInfoFail() { Member member = registerMember(); Long memberId = member.getId(); memberRegister.activate(memberId); member = memberRegister.updateInfo(memberId, createMemberInfoUpdateRequest()); Member anotherMember = registerMember("another@email.com"); Long anotherMemberId = anotherMember.getId(); memberRegister.activate(anotherMemberId); entityManager.flush(); entityManager.clear(); // anotherMember가 member와 프로필 주소 중복 MemberInfoUpdateRequest duplicateProfileUpdateRequest = createMemberInfoUpdateRequest(member.getDetail().getProfile().address()); assertThatThrownBy(() -> { memberRegister.updateInfo(anotherMemberId, duplicateProfileUpdateRequest); }).isInstanceOf(DuplicateProfileException.class); // member와 중복되지 않는 프로필 주소로는 변경 가능 MemberInfoUpdateRequest updateRequest = createMemberInfoUpdateRequest("profile123"); memberRegister.updateInfo(anotherMemberId, updateRequest); // member가 anotherMember와 프로필 주소 중복 assertThatThrownBy(() -> { memberRegister.updateInfo(memberId, updateRequest); }).isInstanceOf(DuplicateProfileException.class); } 좋은 강의 해주셔서 감사드립니다!
안녕하세요. 토비님, 강의 잘 듣고 있습니다. 🙂 오늘은 강의 내용에서 좀 굉장히 의외인 부분을 발견해서 질문드립니다. 강의 #41. MemberApi와 웹 단위 테스트 에서 MemberRegisterRequest 가 domain 레이어에서 정의했던 클래스임에도, @RequestBody 파라미터 그대로 쓰셨는데, 이 부분이 많이 의외고 우려가 되었습니다. 저렇게 하면 MemberRegisterRequest 클래스의 코드 변경이 api 스펙 변경을 의미하는지가 코드리뷰 상에서 쉽게 보이지 않고 숨겨질 수 있다는 염려가 됩니다. 실제로 MemberRegisterRequest 에 필드를 추가해서 PR 을 올리면 코드리뷰어가 봤을 때 domain 레이어의 특정 모델에 필드가 추가됐을 뿐인 작은 변경으로 보일 것입니다. 그래서 그것이 어느어느 API 의 스펙에 영향을 주는지 알기가 너무 어려울 것 같습니다. 그래서 저는 API 의 스펙이 되는 Request, Response DTO 의 경우 반드시 클래스를 별도로 분리해야한다고 생각합니다. API 스펙은 server 마음대로 변경할 수 있는 서버만의 코드가 아니라 client 와의 계약 문서라고 보기 때문입니다. 그래서 Request/Response 같이 백앤드 엔지니어가 함부로 변경할 수 없는 영역과 맘대로 변경 가능한 영역을 분리해서, 어플리케이션과 도메인 로직의 변화가 API 스펙 변경으로 인한 장애 걱정으로 이어지지 않게 하는 것이 중요하다고 생각합니다. 이게 근데 단순히 클래스 분리만 해둬도 PR 에서 API 스펙이 어떻게 바뀌는지 쉽게 트래킹이 가능해지기 떄문에 이 부분 만큼은 번거롭더라도 실보다 득이 훨씬 많아서 꼭 분리해야한다고 생각해왔습니다. 이 부분에 대해서 어떻게 생각하시는지 궁금합니다.
안녕하세요 토비님! 강의를 보다가 궁금한 점이 생겨서 질문 드립니다. 테스트 코드 작성시 EmailSender같은 경우나 , 외부 요인(?) 같은 경우에 저는 테스트 코드가 외부요인에 의해 영향받기를 원하지 않아 @MockitoBean을 사용하는데요 그런데 강의에서는 왜 @MocktioBean을 사용하시지 않고 @TestConfiguration을 사용하셨는지 궁금합니다! 감사합니다!
Member activate 호출 시 MemberDetail의 activate 또한 호출 하게 되는데요. 이때 MemberDetail의 activate 로직에서 this.deactivatedAt = null 을 통해 비활성 일시는 초기화 해주어야 활성 -> 비활성 -> 활성 -> 비활성 시 deactivatedAt이 null임으로 문제가 없을 것 같은데 어떻게 생각하시나요?
MemberRegisterTest를 진행할 때 @SpringBootTest를 사용해서 테스트를 진행했는데요 서비스 테스트에는 @ExtendWith(MockitoExtension.class)를 사용하는 경우를 많이 봤습니다 헥사고날 아키텍처에서는 애플리케이션과 도메인이 중심이 되기 때문에 서비스에서 @SpringBootTest를 사용한걸까요?
안녕하세요. ApiControllerAdvice에서 하나의 Handler 메서드에서 아래 2개 Exeception 타입을 처리하시도록 변경하셨는데요. - DuplicateEmailException - DuplicateProfileException 이렇게 할 경우, 아래와 같이 두 Exception의 공통 타입인 RuntimeException 객체로 파라미터를 받아야 하는거 아닐까요? @ExceptionHandler({DuplicateEmailException.class, DuplicateProfileException.class}) public ProblemDetail duplicateExceptionHandler(RuntimeException e) { return getProblemDetail(HttpStatus.CONFLICT, e); } 감사합니다.