묻고 답해요
169만명의 커뮤니티!! 함께 토론해봐요.
인프런 TOP Writers
-
미해결토비의 클린 스프링 - 도메인 모델 패턴과 헥사고날 아키텍처 Part 1
도메인의 응집도와 모델 복잡도간 밸런스 고민
강의 너무 재미있게 듣고 있습니다.도메인이라는 개념에 대해서 고민하다가 궁금한 사항이 생겨서 질문드립니다. "도메인 모델이 비즈니스 규칙을 모두 내포하면 응집도는 높아지지만, 복잡도도 함께 커집니다. 이때 어디까지를 도메인 모델에 포함시키는 게 적절할까요?"
-
미해결토비의 클린 스프링 - 도메인 모델 패턴과 헥사고날 아키텍처 Part 1
Application Service와 Domain Service를 명확하게 이해한 건지 궁금합니다.
Application Service에서는 흐름을 관리하고 (예를 들면 DB에서 데이터를 가져오는 등) Domain Service는 복잡한 비즈니스 로직을 처리하는 역할로 이해를 했는데 이해한 것이 맞을까요?
-
미해결토비의 클린 스프링 - 도메인 모델 패턴과 헥사고날 아키텍처 Part 1
빈약한 도메인 모델을 보완하기
안녕하세요. 빈약한 도메인 모델에 관하여 질문이 있습니다.현재 개인적으로 진행하는 프로젝트에서 데이터 홀더 역할정도만 하는 빈약한 도메인 모델이 있습니다.repository에는 테이블의 상태 컬럼을 업데이트하는 메소드가 존재하는데 이를 도메인 모델 내부에 메소드를 만들어 업데이트하고 repository의 save를 통해 엔티티의 상태를 update하는 것이 강의에서 의도한 내용으로 이해했는데 맞을까요?추가로 이런 경우(비즈니스 로직이 복잡하지 않은)에 꼭 도메인 모델이 없어도 될지 궁금합니다.
-
미해결토비의 클린 스프링 - 도메인 모델 패턴과 헥사고날 아키텍처 Part 1
JPA entity와 도메인 모델을 분리하는 케이스에 대한 질문입니다.
JPA entity와 도메인 모델을 분리하는 케이스에서 데이터 저장 기술이 바뀌는 경우 Spring Data를 사용하면 해당되지 않는다고 하셨는데 JPA에서 MyBatis로 변경하는 경우도 Spring Data로 커버가 가능한가요? 회사에서 JPA로 개발을 진행중인데 MyBatis로 마이그레이션을 해야할수도 있어서 질문드립니다.
-
해결됨토비의 클린 스프링 - 도메인 모델 패턴과 헥사고날 아키텍처 Part 1
아키텍쳐 선택의 순수한 질문드립니다.
안녕하세요 토비님! 순수한 궁금증이 생겨서 질문 드립니다!아키텍처마다 장단점이 있다고 생각합니다. 프로젝트의 규모나 확장성 여부에 따라 어울리는 아키텍처가 다를 것 같은데요, 토비님은 현업에서 어떤 기준을 가장 중점적으로 두고 아키텍처를 선택하시는지 궁금합니다!
-
미해결토비의 클린 스프링 - 도메인 모델 패턴과 헥사고날 아키텍처 Part 1
JPA 모델과 도메인 모델을 분리했을 때 식별자는 어디에 두는 게 맞을까요❓
안녕하세요 토비님 🙃여러 데이터 접근 기술을 병행해야 하는 상황에서, 도메인 모델과 JPA 모델을 분리해서 관리하고 있습니다.이때 한 가지 궁금증이 생겼습니다.RDB 외의 데이터 접근 기술(예: Redis, MongoDB 등)을 고려하면 도메인 모델에서도 식별자(ID) 개념이 필요할 것 같은데, 이런 경우 도메인 객체가 ID를 직접 가지는 것이 괜찮을까요?만약 괜찮다면, 이는 RDB의 책임(시퀀스, AUTO_INCREMENT 등)에 위임하지 않고, 별도의 UUID나 Snowflake ID 등 도메인 차원의 식별자 생성 전략을 두어야 할 것 같은데 이런 방향성에 대해 어떻게 생각하시는지 궁금합니다.
-
미해결토비의 클린 스프링 - 도메인 모델 패턴과 헥사고날 아키텍처 Part 1
아키텍처 테스트에 대해 질문있습니다.
강의를 NestJS로 적용해 따라가고 있습니다. 마지막 “ArchUnit을 이용한 아키텍처 테스트” 파트에서 Node/NestJS 환경에서는 어떤 방식으로 아키텍처 규칙을 테스트/검증하는 것이 좋은지 궁금합니다.자바에서는 ArchUnit으로 레이어 규칙, 패키지 의존성, 순환 참조 등을 명시적으로 검사할 수 있는데, Node 진영에서는 유사한 도구로 무엇을 추천하실까요? 제가 찾은 것은 “ts-arch”였고, 폴더/슬라이스 의존성, 사이클 검사를 지원하는 것으로 보입니다. 적용을 하다가 실패를 했는데, 다른 방법이 있다면 조언 부탁드립니다. 😭😭😭
-
미해결토비의 클린 스프링 - 도메인 모델 패턴과 헥사고날 아키텍처 Part 1
Part 2 강의는 언제쯤 나오는지 궁금해요
안녕하세요, 강의 정말 잘보았습니다!!Part1을 전부 보고 나니까 part2가 언제쯤 나올지 궁금해서 질문남깁니다!
-
미해결Microservice 설계(with EventStorming,DDD)
현재에도 강의와 동일한 방식을 사용하고 계실지 궁금합니다.
우선 좋은 강의 정말 잘 들었습니다. 감사합니다. 그동안 파편화되어 있던 "어떻게 도메인을 분리하는가"에 대한 질문이 강의를 토대로 많이 깔끔해질 수 있었습니다.말씀 주신 방식을 토대로 현재 신규 도입하고 있는 서비스를 이벤트 스토밍을 토대로 구체화해 보고 있는데요.아무래도 강의가 제작되고 나서 시간이 좀 흐른 만큼, 현재에도 해당 방식을 토대로 설계를 해 가시고 계신지, 뭔가 더 좋은 방식이 추가되셨을지 궁금합니다!
-
미해결토비의 클린 스프링 - 도메인 모델 패턴과 헥사고날 아키텍처 Part 1
코틀린에서 value class 적용 시 문제
안녕하세요 코틀린으로 현재 강의를 수강하고 있는 수강생입니다. 현재 자바로 작성된 코드를 보고 설명과 함께 어떤 이유로 이런 코드를 작성한 것인지 생각하며, 코틀린으로 이 개념을 적용하면 어떻게 작성할 수 있을지 DDD와 클린 아키텍처를 코틀린 문법 활용하여 구상하는 연습 중입니다. 현재 Member 도메인 코드 개선 강의에서 value class 적용하여 필드의 값이 바뀌는 문제(email자리에 nickname이 오더라도 같은 String이라 컴파일 에러가 안 남)를 해결하려 시도했습니다 package org.example.splearn.domain @JvmInline value class Email( val value: String, ) @JvmInline value class Nickname( val value: String, ) @JvmInline value class PasswordHash( val value: String, ) class Member private constructor( val email: Email, var nickname: Nickname, var passwordHash: PasswordHash, var status: MemberStatus, ) { fun activate() { check(status == MemberStatus.PENDING) { "회원이 PENDING 상태가 아닙니다" } this.status = MemberStatus.ACTIVATE } fun deactivate() { check(status == MemberStatus.ACTIVATE) { "회원이 ACTIVE 상태가 아닙니다" } this.status = MemberStatus.DEACTIVATED } fun verifyPassword( password: String, passwordEncoder: PasswordEncoder, ): Boolean = passwordEncoder.matches(password, this.passwordHash.value) fun changeNickname(nickname: String) { this.nickname = Nickname(nickname) } fun changePassword( password: String, passwordEncoder: PasswordEncoder, ) { this.passwordHash = PasswordHash(passwordEncoder.encode(password)) } fun isActive(): Boolean = this.status == MemberStatus.ACTIVATE companion object { fun create( memberCreateRequest: MemberCreateRequest, passwordEncoder: PasswordEncoder, ): Member = Member( email = memberCreateRequest.email, nickname = memberCreateRequest.nickname, passwordHash = PasswordHash( passwordEncoder.encode(memberCreateRequest.password.value), ), status = MemberStatus.PENDING, ) } } 강의대에서는 static 메소드인 of에서 MemberCreateRequest를 파라미터로 사옹하고 있습니다. 코틀린이라 companion object를 사용했구요 그러던 중 "헥사고날 아키텍처의 특성을 고려하면 의존성 외부 로직인 dto가 내부로 향해야 하고 따라서 도메인이 dto에 의존하는 것이 괜찮을까" 하는 의문이 들었습니다. companion object { fun create( email: Email, nickname: Nickname, password: String, passwordEncoder: PasswordEncoder, ): Member = Member( email = email, nickname = nickname, passwordHash = PasswordHash( passwordEncoder.encode(password), ), status = MemberStatus.PENDING, ) }그래서 코드를 수정해 보면 이런 식으로 수정해 볼 수 있을 것 같습니다. 이에 대해서 토비님 의견이 어떠신지 여쭙고 싶습니다
-
미해결토비의 클린 스프링 - 도메인 모델 패턴과 헥사고날 아키텍처 Part 1
안녕하세요 코틀린으로 강의 수강 시 도메인 코드 질문드립니다
좋은 추석 보내고 계신가요?현재 코틀린으로 강의를 따라해 보고 있습니다. class Member private constructor( val email: String, var nickname: String, var passwordHash: String, var status: MemberStatus, ) { fun activate() { check(status == MemberStatus.PENDING) { "회원이 PENDING 상태가 아닙니다" } this.status = MemberStatus.ACTIVATE } fun deactivate() { check(status == MemberStatus.ACTIVATE) { "회원이 ACTIVE 상태가 아닙니다" } this.status = MemberStatus.DEACTIVATED } fun verifyPassword( password: String, passwordEncoder: PasswordEncoder, ): Boolean = passwordEncoder.matches(password, this.passwordHash) fun changeNickname(nickname: String) { this.nickname = nickname } fun changePassword(password: String) { this.passwordHash = password } companion object { fun create( email: String, nickname: String, password: String, passwordEncoder: PasswordEncoder, ): Member = Member( email, nickname, passwordEncoder.encode(password), MemberStatus.PENDING, ) } }이러한 식으로 작성하였는데 자바에서는 const로 선언한 객체나 변수가 아닌 이상 기본적으로 가변입니다. 그런데 코틀린에서는 val, var 키워드에 따라서 var로 선언해야 가변 타입이 됩니다. Member 도메인 모델 확장 챕터 수강하고 있는데 이 경우는 도메인에서 가변 속성을 미리 정의하고 해당 속성들을 var로 선언하는 것이 맞을지, 혹은 val을 통해 불변성을 확보하고 새 객체를 생성하여 변경을 처리하는 것이 적합할지 궁금합니다.코틀린에서 도메인 코드를 작성할 때 자바와 다른 문법&개념과 도메인 중심 설계가 종종 난해할 때가 있네요.
-
미해결토비의 클린 스프링 - 도메인 모델 패턴과 헥사고날 아키텍처 Part 1
마지막 헥사고날아키텍쳐 테스트
마지막 강의에서 진행한 헥사고날 아키텍처 테스트에서 저는 아래와 같이 에러가 발생하고 있는데 어떻게 수정을 해야 할까요? 소스 코드는 동일한 것 같은데... 무엇이 차이인지 모르겠어요 13:09:39.840 [Test worker] INFO com.tngtech.archunit.core.PluginLoader -- Detected Java version 17.0.12Architecture Violation [Priority: MEDIUM] - Rule 'Layered architecture considering all dependencies, consisting oflayer 'domain' ('com.inflearn.splearn.domain..')layer 'application' ('com.inflearn.splearn.application..')layer 'adapter' ('com.inflearn.splearn.adapter..')where layer 'domain' may only be accessed by layers ['application', 'adapter']where layer 'application' may only be accessed by layers ['adapter']where layer 'adapter' may not be accessed by any layer' was violated (1 times):Method <com.inflearn.splearn.SplearnTestConfiguration.passwordEncoder()> calls method <com.inflearn.splearn.domain.member.MemberFixture.createPasswordEncoder()> in (SplearnTestConfiguration.java:19)java.lang.AssertionError: Architecture Violation [Priority: MEDIUM] - Rule 'Layered architecture considering all dependencies, consisting oflayer 'domain' ('com.inflearn.splearn.domain..')layer 'application' ('com.inflearn.splearn.application..')layer 'adapter' ('com.inflearn.splearn.adapter..')where layer 'domain' may only be accessed by layers ['application', 'adapter']where layer 'application' may only be accessed by layers ['adapter']where layer 'adapter' may not be accessed by any layer' was violated (1 times):Method <com.inflearn.splearn.SplearnTestConfiguration.passwordEncoder()> calls method <com.inflearn.splearn.domain.member.MemberFixture.createPasswordEncoder()> in (SplearnTestConfiguration.java:19) at com.tngtech.archunit.lang.ArchRule$Assertions.assertNoViolation(ArchRule.java:94) at com.tngtech.archunit.lang.ArchRule$Assertions.check(ArchRule.java:86) at com.tngtech.archunit.library.Architectures$LayeredArchitecture.check(Architectures.java:347) at com.inflearn.splearn.HexagonalArchitectureTest.hexagonalArchitecture(HexagonalArchitectureTest.java:22) at java.base/java.lang.reflect.Method.invoke(Method.java:569) at com.tngtech.archunit.junit.internal.ReflectionUtils.invoke(ReflectionUtils.java:111) at com.tngtech.archunit.junit.internal.ReflectionUtils.invokeMethod(ReflectionUtils.java:103) at com.tngtech.archunit.junit.internal.ArchUnitTestDescriptor$ArchUnitMethodDescriptor.execute(ArchUnitTestDescriptor.java:203) at com.tngtech.archunit.junit.internal.ArchUnitTestDescriptor$ArchUnitMethodDescriptor.execute(ArchUnitTestDescriptor.java:173) at java.base/java.util.ArrayList.forEach(ArrayList.java:1511) at java.base/java.util.ArrayList.forEach(ArrayList.java:1511)
-
미해결토비의 클린 스프링 - 도메인 모델 패턴과 헥사고날 아키텍처 Part 1
email과 패스워드 VO 질문이 있습니다.
안녕하세요 !! 도메인 모델의 값 객체 도입편에서 궁금한게 있습니다. 패스워드는 passwordEncoder를 의존하여 암호화와 매치 여부를 확인합니다. 이메일 vo는 검증 패턴이 member만 사용하는 것이 아니라 다른 곳에서도 사용할 수 있고 중복된 코드를 줄이기 위해서 변경하셨습니다. password도 이메일 vo처럼 매번 passwordEncoder를 주입받는 게 아니라 password VO를 만들어서 관리할 수 있을거같은데 패스워드는 의존성 주입으로 해결하게 되신 이유가 궁금합니다
-
미해결Microservice 구현 (with EDA,Hexagonal, DDD)
강의보다 바로 여쭤봅니다.
안녕하세요. 강의를 보다가 패키지 구조 패턴이 궁금해졌습니다. 강의에서는 JPA 매핑을 위해 entity 객체를 framework → jpaadapter → entity 패키지 아래에 두어야 한다고 말씀하셨는데, 실제로는 model에 작업하신 것처럼 보였습니다.실무에서는 보통 jpaadapter 패키지에 entity 패키지를 따로 만들어서 강의에서 model에 하셨던 작업을 진행하시는 건가요?또한 만약 framework → jpaadapter → entity 패키지 구조를 따른다면, domain → model 아래 객체들과의 관계나 역할 구분 그리고 소스 구성은 어떻게 되는지도 궁금합니다.
-
미해결토비의 클린 스프링 - 도메인 모델 패턴과 헥사고날 아키텍처 Part 1
UseCase 메서드 단위에 대한 Best Practice
안녕하세요! 토비님.헷갈리는 개념이 하나 있어서 여쭤보고 싶습니다.바로 헥사고날 아키텍처에서 UseCase의 책임 범위인데요.우선 정답은 없다는 것은 알고 있습니다. 다만 Best Practice나 권장되는 방법이 있는지, 그리고 토비님의 고견이 궁금하여 질문드리게 되었습니다. 기능 단위로 UseCase 인터페이스 분리하기 vs 연관된 기능은 UseCase 인터페이스에 묶음으로 제공하기(메서드별로)입니다. 전자는 SRP가 매우 엄격하게 준수되고, 테스트 용이성, 개별 인터페이스별로 정책을 다르게 적용할 수 있다는 장점들이 있지만 과도하게 인터페이스화를 하다 보니 관리할 포인트가 많아져 복잡해진다는 게 단점인 것 같습니다. 후자는 SRP가 엄격하게 준수되지 않더라도, 관련된 기능을 응집도 있게 관리하기 때문에 테스트 용이성이 조금 떨어지고, 일관된 정책을 관리하거나 인터페이스가 비대해질 수도 있다는 단점이 있지만, 응집도 있게 관리하여 유지보수에는 편한 장점이 있는 것 같습니다. 코드를 예시로 보면 아래처럼 콘서트를 조회한다고 했을 때, 일반적으로 PK를 기반으로 조회하지만, 아래와 같이 콘서트명도 unique하고, 가수도 1개의 진행 중인 콘서트만 가지고 있을 수 있을 때 조회 조건이 Id, Name, ArtistName으로 분류될 수 있다고 예시를 들어보겠습니다.public interface GetConcertUseCase { ConcertResult findById(Long concertId); ConcertResult findByName(String name); ConcertResult findByArtistName(String ArtistName); ConcertResult findByIdWithSchedules(Long concertId); // Aggregate Member인 ConcertSchedule 목록 정보도 포함하여 조회 }위에처럼 구성하는 게 후자 방식이고 응집도가 높다고 생각합니다. 그런데 해당 방식은 유스케이스가 비대해질 수 있고, 단일 책임 원칙에서 벗어날 수 있다는 의견 때문에 조회 목적별로 유스케이스 분리하는 것을 권장하는 의견도 있습니다. (전자 방식)public class GetConcertByIdUseCase { ... } public class GetConcertByNameUseCase { ... } public class GetConcertByArtistUseCase { ... } public class GetConcertByIdWithSchedulesUseCase { ... } 정답은 없어서 프로젝트 규모나, 각자의 스타일, 기능 분석에 의해 정해지겠다만, 보편적으로 이런 경우 어떻게 접근하는 게 Best Practice인지 감이 잡히질 않아 질문드리게 되었습니다.
-
미해결토비의 클린 스프링 - 도메인 모델 패턴과 헥사고날 아키텍처 Part 1
repository에 직접 접근
repository에 직접 접근하는 기준이 어떤건지 궁금합니다.강의에서 예를 들면 @Override public Member updateInfo(Long memberId, MemberInfoUpdateRequest memberInfoUpdateRequest) { Member member = memberFinder.find(memberId); checkDuplicateProfile(member, memberInfoUpdateRequest.profileAddress()); member.updateInfo(memberInfoUpdateRequest); return memberRepository.save(member); } 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); } }이 부분에서 member를 찾아올 때는 memberFinder를 통해서 가져왔는데 아래 중복 체크에서는 repository를 바로 사용했습니다. 물론 강의에서 profile을 찾는거기에 memberFinder에 넣기에는 책임이 난잡해질 것 같다는 느낌은 듭니다만 여기서 구분한 기준이 있는지 궁금합니다.다른 예를 들면 public LoginResponse login(LoginRequest request) { Member member = memberFinder.findByEmail(request.email()); if(!member.verifyPassword(request.password(), passwordEncoder)){ //일치하지 않으면 에러 } //이 부분은 직접 접근하는게 좋은지 member.updateLastLoginAt(); memberRepository.save(member); //아니면 memberUpdater라는 포트를 통해 접근하는게 좋은지 memberUpdater.updateLastLoginAt(); return new LoginResponse(member.getId(), member.getStatus()); }이런 경우 둘중 어떤 방법이 더 구조적으로 좋은지 궁금합니다.
-
미해결토비의 클린 스프링 - 도메인 모델 패턴과 헥사고날 아키텍처 Part 1
멀티모듈
안녕하세요 토비님배운 내용을 바탕으로 기존 프로젝트 구조를 변경하는 연습하고 있습니다.기존 프로젝트가 멀티 모듈로 되어있어 멀티모듈 구조는 그대로 가져가고 싶은데 분리한다면어떻게 나누어야 할까요?모듈을 두개로 나누어 api와 core로 구성했는데 adapter, application, domain를api 모듈에 adapter core 모듈에 application, domain 이렇게 구성하였는데 이렇게 구분해도 괜찮은지 잘 모르겠습니다
-
미해결토비의 클린 스프링 - 도메인 모델 패턴과 헥사고날 아키텍처 Part 1
DTO를 서비스 레이어에서 사용할 수 밖에 없다면
엔티티를 쓰지 못하는 상황JDBC Template나 nativeQuery처럼 직접 조회가 필요한 경우에는 엔티티를 사용하기 힘들 것 같은데이런 경우에 DTO를 사용하게 되면 서비스 레이어에 해당 부분이 생길거 같은데 이 정도는 괜찮은 걸까요?
-
미해결토비의 클린 스프링 - 도메인 모델 패턴과 헥사고날 아키텍처 Part 1
강의 업데이트 내역 질문
안녕하세요.강의 업데이트가 된 거 같은데 혹시 업데이트 된 내역을 확인 할 수 있는 곳이 있을까요? 어떤 부분이 바뀌었는지 궁금합니다~
-
미해결토비의 클린 스프링 - 도메인 모델 패턴과 헥사고날 아키텍처 Part 1
"MemberFinderTest, MemberRegisterTest" record관련
record + 클래스 레벨 @Transactional에서 에러가 발생합니다.!질문은 record 예약어는 final 클래스라서 상속이나 프록시 생성이 불가능한거 같은데@Transactional 어노테이션은 Spring AOP기반이라 프록시 객체 생성이 안되서 에러가 발생하는거 같은데... 강의에서는 통과가 되더라구요 제가 어떤 부분이 놓쳤을까요?