묻고 답해요
158만명의 커뮤니티!! 함께 토론해봐요.
인프런 TOP Writers
-
해결됨토비의 클린 스프링 - 도메인 모델 패턴과 헥사고날 아키텍처 Part 1
도메인 모델의 화살표는 특별한 의미가 있을까요?
draw.io에서 도메인 모델을 설명해주시는 부분에 대해서 질문드립니다.도표의 다른 선들은 모두 화살표가 없는데, 수강은 회원과 강의 모델로 화살표가 있어서 어떤 의미인지 궁금합니다.
-
미해결실전! 스프링 부트와 JPA 활용1 - 웹 애플리케이션 개발
JUnit5 테스트시 DB 구동
[질문 템플릿]1. 강의 내용과 관련된 질문인가요? (예)2. 인프런의 질문 게시판과 자주 하는 질문에 없는 내용인가요? (예)3. 질문 잘하기 메뉴얼을 읽어보셨나요? (예)[질문 내용]현재 주문 기능 테스트에서 JUnit5를 이용하여 테스트 중입니다. 앞선 강의(회원 기능 테스트)에서 JUnit으로 테스트할때 메모리 DB 사용한다고 application.yml 을 test 쪽에 복사해서 사용할때도 url: jdbc:h2:mem:testdb 부분을 주석해도 돌아간다고 했었던 것으로 기억이 납니다. 그래서 TEST 시에는 DB를 연결 안해도 무방하다고 생각하고 있었는데요.오늘 주문 기능 테스트시 DB 끄고 했더니 에러가 나서 DB 연결했더니 정상적으로 동작합니다. TEST에서도 DB를 연결해주어야 하는 걸까요?
-
미해결자바 ORM 표준 JPA 프로그래밍 - 기본편
기본 키 매핑
[질문 템플릿]1. 강의 내용과 관련된 질문인가요? (예)2. 인프런의 질문 게시판과 자주 하는 질문에 없는 내용인가요? (예)3. 질문 잘하기 메뉴얼을 읽어보셨나요? (예)[질문 내용]jpa 를 사용할때 id 값으로 영속성 컨텍스트를 분별하는데 사용하기 떄문에 persist 할때 미리 insert 쿼리를 사용하는걸로 알고 있는데 그럼 다른 트랜잭션 단위에서 동일한 행위를 하면 이미 auto_increment로 값이 올라가 있는 상태에서 다른 트랜잭션 작업 단위가 rollback 하게 되면 그럼 auto_increment 가 중간에 값이 비게 되는건가요? 비게 된다면 데이터가 삭제되거나 문제가 있다고 인식할수 있을것 같은데? 이럴때는 어떻게 하나요?
-
미해결스프링부트를 이용한 웹 프로그래밍: 웹사이트 이렇게 만드는 거예요!
인터셉터
스프링 시큐리티를 적용하고 나서 modify가 계속 /member/signin으로 리다이렉트 되는 현상이 발견되었는데, 원인을 파악해보니 인터셉터 클래스 문제였습니다.(기존 인터셉터 클래스에선 session을 기반으로 로그인 상태여부를 확인하는데, Spring Security를 사용하고 나선, session이 아닌 SecurityContext에 로그인 정보를 보관하니 서로 충돌을 일으켜 계쏙 signin으로 리다이렉트 되는거 같습니다.) 혹시 이 부분이 맞는지 확인 부탁드리며, 제가 쓴 내용이 맞다면, 강의 내용중에 이부분을 짚어주셨으면 합니다.
-
미해결실전! 스프링 부트와 JPA 활용1 - 웹 애플리케이션 개발
커리큘럼 방향에 대해서...
[질문 템플릿]1. 강의 내용과 관련된 질문인가요? (예/아니오) 예2. 인프런의 질문 게시판과 자주 하는 질문에 없는 내용인가요? (예/아니오) 예3. 질문 잘하기 메뉴얼을 읽어보셨나요? (예/아니오) 예[질문 내용]스프링핵심원리HTTP웹기본지식MVC1편스프링DB 1편수강완료 했고ORM표준JPA프로그래밍 완강 목표로 수강중입니다.혼자 프로젝트를 하나 만들어보고 싶은데JPA활용1편까지는 수강을 하고 시작하는게 좋을까요?아니면 실전 스프링 데이터 JPA를 수강하는게 좋을까요?
-
미해결Spring Cloud로 개발하는 마이크로서비스 애플리케이션(MSA)
안녕하세요 라우트 과정에서 http프로토콜 -> lb 프로토콜 질문 있습니다.
기존에는 http://localhost:8081 과같이 보내줬는데, 이걸 lb:/MY-FIRST-SERVICE로 바꿨습니다.그렇다면, 원래 localhost:8081로 직접 http 프로토콜로 보내줬던건, 유레카를 거치지 않았고, 하드코딩으로 직접 보내줬던 것이고, 그리고 lb:/를 이용하는 것이 유레카에 저장되어 있는, 정보들을 통해 읽어와서 동적으로 보내주는 것으로 이해하였는데, 제가 이해한게 맞을까요?
-
미해결실전! 스프링 부트와 JPA 활용1 - 웹 애플리케이션 개발
h2 database에 member 테이블이 생성되지 않아요
[질문 템플릿]1. 강의 내용과 관련된 질문인가요? (예/아니오)네, jpa와 db 설정, 동작 확인 동영상 입니다.2. 인프런의 질문 게시판과 자주 하는 질문에 없는 내용인가요? (예/아니오)아니요. 있는 내용인데 찾아보고 해결하지 못했습니다.3. 질문 잘하기 메뉴얼을 읽어보셨나요? (예/아니오)네[질문 내용]여기에 질문 내용을 남겨주세요.테스트 코드 실행 시 member 테이블이 생성되지 않습니다. ddl 설정은 create이고, 관련 설정 확인해도 못찾아서 문의 드려요.테스크 코드 맨 마지막에 로그를 찍어놨는데, 해당 로그가 찍히고 이 뒤에 테이블이 왜 drop될까요?도저히 모르겠습니다...ㅎ https://drive.google.com/file/d/1WT3dZ7HexApj0xAYZsFJSkKVpaSmGEFM/view?usp=sharing구글 드라이브 프로젝트 첨부 드립니다.
-
미해결스프링부트를 이용한 웹 프로그래밍: 웹사이트 이렇게 만드는 거예요!
DTO 관련
강의 열심히 듣고 있습니다. 다름이 아니라, DTO 관련 의구심이 들어서 질문 올립니다. 선생님께선 modifyConfirm 일때dto의 pw 값을 encode된 값으로 변경하셔서 entity로 넣으셨는데,String encodedPW = passwordEncoder.encode(memberDTO.getPw()); memberDTO.setPw(encodedPW) findedMemberEntity.setMemPw(memberDTO.getPw()); 위의 방법대로 하면, dto값이 변경이 되어 "단순 전달 객체"를 위배할 수 있으며, 간결성과 가독성이 떨어지는게 아닌가 싶어서요 아래와 같이 리펙토링 하면 조금 더 간결하게 되지 않을까요?if (optionalMember.isPresent()) { MemberEntity findedMemberEntity = optionalMember.get(); findedMemberEntity.setMemPw(passwordEncoder.encode(memberDTO.getPw())); ~~ return MODIFY_SUCCESS; }
-
미해결코드로 배우는 React 19 with 스프링부트 API서버
수업에사용되는 ppt
강의중에서 설명하시는 ppt는 어디서 확인할 수 있을까요?
-
해결됨RabbitMQ를 이용한 비동기 아키텍처 한방에 해결하기
Producer/Consumer 단위
학습중 궁금한 것은 언제든 문의 하세요.질문을 최대한 자세히 남겨주시면 반드시 답변 드리도록 하겠습니다.추가로 알고 싶은 내용도 요청해주시면 강의 자료를 업데이트 해서 제공할 예정입니다.저만 궁금한 걸 수 있는데, 예를들어 Producer가 두개일 때 이 경우는스프링 애플리케이션의 publish하는 두개의 스레드를 의미하는 건지...애플리케이션 두 개를 의미하는 건지...아니면 커넥션 두개를 의미하는 건지...무엇을 기준으로 Producer/Consumer 단위를 세는건지 아시나요 ?
-
미해결자바 ORM 표준 JPA 프로그래밍 - 기본편
mapprd by질문이요
학습하는 분들께 도움이 되고, 더 좋은 답변을 드릴 수 있도록 질문전에 다음을 꼭 확인해주세요.1. 강의 내용과 관련된 질문을 남겨주세요.2. 인프런의 질문 게시판과 자주 하는 질문(링크)을 먼저 확인해주세요.(자주 하는 질문 링크: https://bit.ly/3fX6ygx)3. 질문 잘하기 메뉴얼(링크)을 먼저 읽어주세요.(질문 잘하기 메뉴얼 링크: https://bit.ly/2UfeqCG)질문 시에는 위 내용은 삭제하고 다음 내용을 남겨주세요.=========================================[질문 템플릿]1. 강의 내용과 관련된 질문인가요? (예/아니오)2. 인프런의 질문 게시판과 자주 하는 질문에 없는 내용인가요? (예/아니오)3. 질문 잘하기 메뉴얼을 읽어보셨나요? (예/아니오)[질문 내용]여기에 질문 내용을 남겨주세요. 1대1매핑에서 주인과 종속 테이블의 개념이 모호해질수도있는데 mapprd by는 어느쪽에 적어도 상관없나요?
-
해결됨토비의 클린 스프링 - 도메인 모델 패턴과 헥사고날 아키텍처 Part 1
Kotlin 에서 JPA Entity 생성시 질문
안녕하세요. 토비님!현재 저는 강의 내용을 Kotlin springboot 로 따라가고 있습니다.JPA Entity 클래스를 생성할 때, 자바에선 롬복까지 이용해서 Getter 만 만들어놓고 setter 는 닫아놓는 게 쉽게 되는데, 이걸 코틀린에서는 롬복을 사용하지 않다보니 코틀린스러우면서도 깔끔하게 사용하는 방법에 대해서 애를 먹고 있습니다. 찾아보니 3가지 방법 있는 것 같습니다.방법1. 자바랑 가장 비슷하게, 필드를 모두 private 으로 생성하고 getter 는 롬복 대신 직접 선언.@Entity open class Member( @Column(name = "email", unique = true, nullable = false) private var email: String, @Column(name = "nickname", nullable = false) private var nickname: String, @Column(name = "passwordHash", nullable = false) private var passwordHash: String, @Column(name = "status", nullable = false) @Enumerated(EnumType.STRING) private var status: MemberStatus = MemberStatus.PENDING, ) { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) val id: Long = 0L fun getEmail(): String = email fun getNickname(): String = nickname fun getPasswordHash(): String = passwordHash fun getStatus(): MemberStatus = status } 방법2. getter 를 좀 더 코틀린스럽게 사용하기 위해 내부 필드를 _를 붙여서 선언 @Entity class Member2( @Column(name = "email", unique = true, nullable = false) private var _email: String, @Column(name = "nickname", nullable = false) private var _nickname: String, @Column(name = "passwordHash", nullable = false) private var _passwordHash: String, @Enumerated(EnumType.STRING) @Column(name = "status", nullable = false) private var _status: MemberStatus = MemberStatus.PENDING, ) { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) val id: Long = 0L val email: String get() = _email val nickname: String get() = _nickname val passwordHash: String get() = _passwordHash val status: MemberStatus get() = _status }방법3. protected set 사용@Entity open class Member3( email: String, nickname: String, passwordHash: String, status: MemberStatus = MemberStatus.PENDING, ) { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) val id: Long = 0L @Column(name = "email", unique = true, nullable = false) var email: String = email protected set @Column(name = "nickname", nullable = false) var nickname: String = nickname protected set @Column(name = "passwordHash", nullable = false) var passwordHash: String = passwordHash protected set @Column(name = "status", nullable = false) @Enumerated(EnumType.STRING) var status: MemberStatus = status protected set } 방법4(?). 전부 public val 로 선언하고, 변경시 새로운 객체 생성@Entity @Table(name = "members") class Member4( @Id @GeneratedValue(strategy = GenerationType.IDENTITY) val id: Long = 0L, @Column(name = "email", unique = true, nullable = false) val email: String, @Column(name = "nickname", nullable = false) val nickname: String, @Column(name = "passwordHash", nullable = false) val passwordHash: String, @Column(name = "status", nullable = false) @Enumerated(EnumType.STRING) val status: MemberStatus = MemberStatus.PENDING, ) { fun updateNickname(newNickname: String): Member4 { require(newNickname.isNotBlank()) { "Nickname cannot be blank" } return Member4( id = this.id, email = this.email, nickname = newNickname, passwordHash = this.passwordHash, status = this.status, ) } } 어느 방식을 선택하는게 현명할까요? 토비님은 평소에 어떻게 하시는지 궁금합니다.
-
해결됨자바와 스프링 부트로 생애 최초 서버 만들기, 누구나 쉽게 개발부터 배포까지! [서버 개발 올인원 패키지]
application.yml driver-class-name 연결오류
spring: datasource: # 데이터의 원천 spring이 어떤 데이터를 가르키냐 url: "jdbc:mysql://localhost/library" #스프링이 붙을(연결할) 데이터베이스의 주소 #jdbc- java database conenncter 말그대로 자바가 데이터베이스에 연결할수있게해주는 프로그램 #이때 데이터종류는 mysql이고 데이터베이스는 우리 컴퓨터 localhost에있다. 그리고 사용할데이터베이스는 library이다. username: "root" password: "비밀번호" driver-class-name: com.mysql.cj.jdbc.Driver #데이터베이스에 접근할 때 사용할 프로그램위와 같이 코드를 작성했는데 인텔리제이 얼티메이트를 사용하고 있는데도 com.mysql.cj.jdbc.Driver클릭이 안돼서 뭔가 이상하다 싶었는데 다른 질문들 답변을 보니 잘될수도 있다해서 일단 인강 코드대로 따라 갔습니다. 그런데 api를 전부 수정하고 서버를 동작하려고하니 mysql연결이 안된다는 오류와 함꼐 서버 실행이안돼서 질문 남깁니다..! plugins { id 'org.springframework.boot' version '2.7.6' id 'io.spring.dependency-management' version '1.0.12.RELEASE' id 'java' } group = 'com.example' version = '0.0.1-SNAPSHOT' sourceCompatibility = '11' repositories { mavenCentral() } dependencies { implementation 'org.springframework.boot:spring-boot-starter-data-jpa' implementation 'org.springframework.boot:spring-boot-starter-web' runtimeOnly 'com.h2database:h2' runtimeOnly 'mysql:mysql-connector-java' testImplementation 'org.springframework.boot:spring-boot-starter-test' } tasks.named('test') { useJUnitPlatform() } 위처럼 그래들파일에도 runtimeOnly 'mysql:mysql-connector-java:8.0.42'이코드가 잘 있는데 안되는것같습니다. 다른답변들 참고해서 위처럼 자바 버전을 붙이고 캐시지워서 리스타트한다음 다시 서버 켜봐도 안돼서 질문남깁니다..!
-
해결됨토비의 클린 스프링 - 도메인 모델 패턴과 헥사고날 아키텍처 Part 1
테스트 간헐적 실패에서 대해서 다루나요?
안녕하세요섹션5 코드 다듬기까지 들은 시점에서 테스트 코드를 여러번 실행했을때 간헐적으로 실패를 경험합니다혹시 해당 내용 수정하는 부분이 끝까지 들으면 나오나요아래 에러내용 삽입했습니다. Failed to resolve parameter [com.clean.splearn.application.provided.MemberFinder memberFinder] in constructor [com.clean.splearn.application.provided.MemberFinderTest(com.clean.splearn.application.provided.MemberFinder,com.clean.splearn.application.provided.MemberRegister,jakarta.persistence.EntityManager)]: Failed to load ApplicationContext for [WebMergedContextConfiguration@350ec690 testClass = com.clean.splearn.application.provided.MemberFinderTest, locations = [], classes = [com.clean.splearn.SplearnApplication], contextInitializerClasses = [], activeProfiles = [], propertySourceDescriptors = [], propertySourceProperties = ["org.springframework.boot.test.context.SpringBootTestContextBootstrapper=true"], contextCustomizers = [org.springframework.boot.test.autoconfigure.OnFailureConditionReportContextCustomizerFactory$OnFailureConditionReportContextCustomizer@476fe690, org.springframework.boot.test.autoconfigure.actuate.observability.ObservabilityContextCustomizerFactory$DisableObservabilityContextCustomizer@1f, org.springframework.boot.test.autoconfigure.properties.PropertyMappingContextCustomizer@0, org.springframework.boot.test.autoconfigure.web.servlet.WebDriverContextCustomizer@6dd1c3ed, [ImportsContextCustomizer@49cb1baf key = [com.clean.splearn.SplearnTestConfiguration]], org.springframework.boot.test.context.filter.ExcludeFilterContextCustomizer@7fdab70c, org.springframework.boot.test.json.DuplicateJsonObjectContextCustomizerFactory$DuplicateJsonObjectContextCustomizer@a451491, org.springframework.boot.test.mock.mockito.MockitoContextCustomizer@0, org.springframework.boot.test.web.client.TestRestTemplateContextCustomizer@2aff9dff, org.springframework.boot.test.web.reactor.netty.DisableReactorResourceFactoryGlobalResourcesContextCustomizerFactory$DisableReactorResourceFactoryGlobalResourcesContextCustomizerCustomizer@15639440, org.springframework.test.context.support.DynamicPropertiesContextCustomizer@0, org.springframework.boot.test.context.SpringBootTestAnnotation@cbfbc3c], resourceBasePath = "src/main/webapp", contextLoader = org.springframework.boot.test.context.SpringBootContextLoader, parent = null] org.junit.jupiter.api.extension.ParameterResolutionException: Failed to resolve parameter [com.clean.splearn.application.provided.MemberFinder memberFinder] in constructor [com.clean.splearn.application.provided.MemberFinderTest(com.clean.splearn.application.provided.MemberFinder,com.clean.splearn.application.provided.MemberRegister,jakarta.persistence.EntityManager)]: Failed to load ApplicationContext for [WebMergedContextConfiguration@350ec690 testClass = com.clean.splearn.application.provided.MemberFinderTest, locations = [], classes = [com.clean.splearn.SplearnApplication], contextInitializerClasses = [], activeProfiles = [], propertySourceDescriptors = [], propertySourceProperties = ["org.springframework.boot.test.context.SpringBootTestContextBootstrapper=true"], contextCustomizers = [org.springframework.boot.test.autoconfigure.OnFailureConditionReportContextCustomizerFactory$OnFailureConditionReportContextCustomizer@476fe690, org.springframework.boot.test.autoconfigure.actuate.observability.ObservabilityContextCustomizerFactory$DisableObservabilityContextCustomizer@1f, org.springframework.boot.test.autoconfigure.properties.PropertyMappingContextCustomizer@0, org.springframework.boot.test.autoconfigure.web.servlet.WebDriverContextCustomizer@6dd1c3ed, [ImportsContextCustomizer@49cb1baf key = [com.clean.splearn.SplearnTestConfiguration]], org.springframework.boot.test.context.filter.ExcludeFilterContextCustomizer@7fdab70c, org.springframework.boot.test.json.DuplicateJsonObjectContextCustomizerFactory$DuplicateJsonObjectContextCustomizer@a451491, org.springframework.boot.test.mock.mockito.MockitoContextCustomizer@0, org.springframework.boot.test.web.client.TestRestTemplateContextCustomizer@2aff9dff, org.springframework.boot.test.web.reactor.netty.DisableReactorResourceFactoryGlobalResourcesContextCustomizerFactory$DisableReactorResourceFactoryGlobalResourcesContextCustomizerCustomizer@15639440, org.springframework.test.context.support.DynamicPropertiesContextCustomizer@0, org.springframework.boot.test.context.SpringBootTestAnnotation@cbfbc3c], resourceBasePath = "src/main/webapp", contextLoader = org.springframework.boot.test.context.SpringBootContextLoader, parent = null] at java.base/java.util.Optional.orElseGet(Optional.java:364) at java.base/java.util.ArrayList.forEach(ArrayList.java:1596) at java.base/java.util.ArrayList.forEach(ArrayList.java:1596) Caused by: java.lang.IllegalStateException: Failed to load ApplicationContext for [WebMergedContextConfiguration@350ec690 testClass = com.clean.splearn.application.provided.MemberFinderTest, locations = [], classes = [com.clean.splearn.SplearnApplication], contextInitializerClasses = [], activeProfiles = [], propertySourceDescriptors = [], propertySourceProperties = ["org.springframework.boot.test.context.SpringBootTestContextBootstrapper=true"], contextCustomizers = [org.springframework.boot.test.autoconfigure.OnFailureConditionReportContextCustomizerFactory$OnFailureConditionReportContextCustomizer@476fe690, org.springframework.boot.test.autoconfigure.actuate.observability.ObservabilityContextCustomizerFactory$DisableObservabilityContextCustomizer@1f, org.springframework.boot.test.autoconfigure.properties.PropertyMappingContextCustomizer@0, org.springframework.boot.test.autoconfigure.web.servlet.WebDriverContextCustomizer@6dd1c3ed, [ImportsContextCustomizer@49cb1baf key = [com.clean.splearn.SplearnTestConfiguration]], org.springframework.boot.test.context.filter.ExcludeFilterContextCustomizer@7fdab70c, org.springframework.boot.test.json.DuplicateJsonObjectContextCustomizerFactory$DuplicateJsonObjectContextCustomizer@a451491, org.springframework.boot.test.mock.mockito.MockitoContextCustomizer@0, org.springframework.boot.test.web.client.TestRestTemplateContextCustomizer@2aff9dff, org.springframework.boot.test.web.reactor.netty.DisableReactorResourceFactoryGlobalResourcesContextCustomizerFactory$DisableReactorResourceFactoryGlobalResourcesContextCustomizerCustomizer@15639440, org.springframework.test.context.support.DynamicPropertiesContextCustomizer@0, org.springframework.boot.test.context.SpringBootTestAnnotation@cbfbc3c], resourceBasePath = "src/main/webapp", contextLoader = org.springframework.boot.test.context.SpringBootContextLoader, parent = null] at org.springframework.test.context.cache.DefaultCacheAwareContextLoaderDelegate.loadContext(DefaultCacheAwareContextLoaderDelegate.java:180) at org.springframework.test.context.support.DefaultTestContext.getApplicationContext(DefaultTestContext.java:130) at org.springframework.test.context.junit.jupiter.SpringExtension.getApplicationContext(SpringExtension.java:351) at org.springframework.test.context.junit.jupiter.SpringExtension.resolveParameter(SpringExtension.java:337) ... 3 more
-
해결됨토비의 클린 스프링 - 도메인 모델 패턴과 헥사고날 아키텍처 Part 1
JPA Entity Class 와 Domain Model Entity Class 를 분리해야 하는가? 에 대한 추가 질문
안녕하세요. 토비님!먼저 제가 정말 오랜 기간 고민해온 주제에 대해서 이렇게 강의를 내주셔서 너무 감사드립니다!!특히 강의에서 여러 차례에 걸쳐서 깊게 다룬, "JPA Entity Class 와 Domain Model Entity Class 를 분리해야 하는가?" 에 대한 토비님의 의견이 너무 공감되고 유익했습니다! 감사합니다!그리고 이에 대해서 두 가지 질문이 있습니다.첫 번째는,저는 사실 지난 3년 간 스타트업에서 Domain Entity 와 JPA Entity 를 분리해서 사용해왔습니다.그런데 강의에서 다뤄주신 내용이 제가 겪으며 갖게 된 생각과는 결이 조금 다르다고 느껴서 이렇게 질문을 드리게 되었습니다.제가 느꼈던 "Domain Entity 와 JPA Entity 를 분리해서 사용했을 때의 장단점"은 이랬습니다.장점 1. 도메인 모델의 수정이 DB 데이터 마이그레이션을 꼭 강제하는 게 아니라서, 도메인 모델 수정이 매우 자유롭습니다.상황에 따라 의도적으로 JPA Entity 클래스와 도메인 모델 클래스를 다른 모양으로 만들어두고 유지할 수 있습니다.(ex. User, UserDetail 이 쪼개져 있었는데, 어떠한 의사결정으로 인해 User 하나로 합쳐서 관리하는게 맞다는 판단이 든 경우, UserEntity, UserDetailEntity 는 놔둔 채로 User 만 합치는 게 가능.)장점 2. 도메인 모델들을 완전히 정규화된 구조로 가져갈 수 있습니다. 회사에서 비즈니스가 발전하다보면 종종 어쩔 수 없이 테이블에 성능을 위한 반정규화 필드나 soft delete 용 deleted_at 필드 등을 넣게 됩니다. 하지만 사실 이런 필드들은 순수 비즈니스 로직을 기술하는데에는 방해가 될 뿐인, 너무 Technical 한 부분들입니다. 이러한 반정규화 or 기능 필드들을 도메인 모델에는 넣지 않고 JPA Entity 에만 넣어서 어댑터에서 처리하면, 도메인 모델 내에서는 항상 순수 비즈니스 로직만을 기술할 수 있게됩니다.단점 1. JPA 의 lazy loading 을 활용할 수 없습니다. 항상 도메인 모델 객체는 완전한 상태로만 존재합니다. 그래서 성능 문제로 CQRS 패턴이 강제됩니다. 특히 저는 GraphQL 을 사용중이라 더욱 더 CQRS 가 필요했습니다.단점 2. Repository 의 save(= upsert) 로직을 직접 구현해야 합니다. 특히 복잡한 비즈니스의 핵심 도메인 모델들은 관계된 테이블도 많아가지고 이 save 구현이 엄청나게 길어집니다. 저는 이 save 로직을 직접 구현하고 관리할 때 회의감이 가장 많이 들었습니다. Spring Data JPA 가 그 동안 얼마나 압도적인 생산성을 제공해주고 있었는지도 느끼게 됐습니다.저는 스타트업에서 일하고 있다보니 특히 생산성을 중요하게 생각합니다.그런 점에서도 위의 장점들과 단점들이 모두 너무 치명적으로 느껴졌습니다.그래서 이 관점들에 대한 토비님의 의견과 토비님이 제 상황이시라면 어떤 선택을 하셨을지가 너무너무 궁금합니다. (참고로 저는 Kotlin Springboot + Spring Data JPA + Kotlin JDSL + GraphQL-Kotlin(code first)을 사용하고 있습니다.)두 번째는,사실 저는 Kotlin Springboot 를 사용중인데, 코틀린 언어와 JPA 가 너무 안 어울린다는 생각을 종종 합니다.예를 들면, DB table 에 정의된 column default 값을 쓰려면 JPA Entity 의 해당 필드에 null 을 넣어서 보내야하는데, 보통 테이블 컬럼에 default 를 쓰는 경우는 대부분 해당 컬럼이 not null 타입입니다. 그래서 당연히 JPA Entity 에도 필드 타입은 not null 로 하고 싶어집니다.물론 @Column(insertable=false)를 사용하긴 하지만, 결국 그럼 이 JPA Entity 객체를 생성할 때 해당 필드에 실제로 저장되지도 않을 값을 거짓으로 넣어야 하는 상황이 생깁니다.그래서 조금 더 Kotlin 에 잘 맞는 Exposed 를 고려하자니, 도메인 모델 클래스와 JPA Entity 클래스 분리가 강제되는 느낌이고, 이게 과연 JPA 의 생산성을 따라올 수 있을까? 올바른 선택이 맞을까? 하는 의문이 듭니다.그래서 궁금한 점은,혹시 토비님이 추천하시는 좀 더 Kotlin 언어에 잘 맞고 Kotlin Springboot 를 생산적으로 사용할 수 있는 방법이 있을까요?
-
미해결자바와 스프링 부트로 생애 최초 서버 만들기, 누구나 쉽게 개발부터 배포까지! [서버 개발 올인원 패키지]
user-loanHistory 폴더 구조 질문
안녕하세요!@OneToMany(mappedBy = "user", cascade = CascadeType.ALL, orphanRemoval = true) private List<UserLoanHistory> userLoanHistories = new ArrayList<>(); 이 코드를 봤을 때 User와 UserLoanHistory는 강한 연결관계를 가지고 있는 것 같습니다(이를 같은 aggregate라고 하는 것 맞나요?). 그래서 폴더 구조를 user 밑에 loanHistory를 넣으신 것 같은데, 일반적으로 이런식으로 폴더 구조를 잡나요? 아니면 domain 폴더 아래에 user와 같은 레벨로 loanHistory를 만들고, 대출 기능도 BookService에서 구현하는 게 아니라 UserLoanHistory용 컨트롤러/서비스/레포지토리를 따로 만들어 구현해도 되나요?
-
해결됨토비의 클린 스프링 - 도메인 모델 패턴과 헥사고날 아키텍처 Part 1
동일 계층의 애플리케이션 서비스가 서로의 인터페이스를 사용하여 의존하는 경우에 대해서
안녕하세요? 존경하는 토비님 작은 질문하나 드리겠습니다. 회원 애플리케이션 기능 추가의 25:00~26:00 에서는 MemberFinder 인터페이스를 정의하고 기존 MemberService에 혼재된 멤버 조회로직을 CQRS에 따라 구현클래스를 분리했습니다. 그리고 이를 MemberModifyService에서 사용하고 있습니다.말씀 주신 것처럼 단순히 조회이고 변경이 없다는 가정하에 이처럼 인터페이스를 정의하고 이를 통해 사용을 하는 것은 잘 이해가 됩니다!다만 저는 같은 계층에서 있는 서비스 빈이 서로를 의존하지 않는 방향으로 개발을 해오고 있었는데요. 그래서 토비님의 방식에 대해 공부하면서 같은 계층에서 인터페이스 포트를 통해서 호출하는 것은 괜찮을까 고민이 들었습니다.만약 그게 설계적으로도 문제가 없다면 앞으로도 토비님의 방식으로 개발하고 싶은데요. 분명 토비님께서 오랫동안 고민하신 개발 원칙/기준이 있을 것 같아서 여쭤봅니다.같은 계층에 있는 애플리케이션 서비스 빈이 서로를 의존, 또는 인터페이스를 통해 사용되는 경우 지켜야할 기준이나 원칙이 있을까요?*추신저는 토스의 김재님의 [블로그](https://geminikims.medium.com/%EC%A7%80%EC%86%8D-%EC%84%B1%EC%9E%A5-%EA%B0%80%EB%8A%A5%ED%95%9C-%EC%86%8C%ED%94%84%ED%8A%B8%EC%9B%A8%EC%96%B4%EB%A5%BC-%EB%A7%8C%EB%93%A4%EC%96%B4%EA%B0%80%EB%8A%94-%EB%B0%A9%EB%B2%95-97844c5dab63)를 접해서 위와 같이 개발해오고 있었습니다. 저는 계층을 하나 두고 유틸성 빈을 만들고 이를 사용하게끔 했습니다.(블로그내용 아래 일부 발췌) > 네 번째 규칙동일 레이어 간에는 서로 참조하지 않아야 한다.(다만, Implement Layer는 예외적으로 서로 참조가 가능합니다.)
-
미해결실전! 스프링 부트와 JPA 활용2 - API 개발과 성능 최적화
OSIV ON 상태일 때
[질문 템플릿]1. 강의 내용과 관련된 질문인가요? (예)2. 인프런의 질문 게시판과 자주 하는 질문에 없는 내용인가요? (예)3. 질문 잘하기 메뉴얼을 읽어보셨나요? (예)[질문 내용]안녕하세요. 항상 강의 잘 듣고 있는 학생입니다. 다름이 아니라 강의자료에 "OSIV ON 사진"을 보시면 Filter Interceptor, View, Controller는 영속 상태임에도 불구하고 수정 불가능이라고 적혀 있습니다. 제가 지금까지 알고 있는 바로는 영속성 컨텍스트가 존재한다는 것은 데이터베이스 커넥션을 갖고 있다는 뜻이라 위 3개의 파트에서 데이터 수정이 발생하면 Flush를 통해 데이터 수정이 가능한거 아닌가요 ?혹시 제가 사진의 내용을 잘 못 이해한건지 아니면 위 내용을 잘 못 이해한건지 궁금해서 질문 드립니다. 감사합니다. 무더위 조심하세요 !
-
미해결실전! 스프링 데이터 JPA
bulk insert 질문입니다.
안녕하세요.bulk insert 관련해서 궁금한점이 있습니다.사실상 데이터베이스를 다룰 때 IDENTITY를 거의 사용할 것이라고 생각합니다.이럴 경우 bulk insert를 어떻게 처리해야 될 지 궁금합니다.다른 질문에 답변을 봤을때 책에 나온 쓰기 지연같은 경우도 IDENTITY에서는 안되는 것이라고 알고 새로운 질문을 드립니다.IDENTITY 키를 가진 엔티티들 bulk insert를 하기 위해 JdbcTemplate가 최선이다 까지 이해했습니다. 예를 들어 상품을 등록할때 상품과 카테고리들 이미지들을 저장한다고 생각해보겠습니다.(각각 다른 테이블 및 1:N)이런 경우 상품에 새로운 엔티티들을 만들고 save를 호출할 경우 IDENTITY를 가진 엔티티들이니 각 쿼리들이 따로 다 날라갑니다.그래서 JdbcTemplate를 사용해 카테고리들과 이미지들을 batch insert할 경우기존 엔티티에 정보를 어떻게 관리하는지 궁금합니다. jdbcTemplate로 bulk insert한 경우 id를 받아오지 못하는 것으로 알고 있는데, 이런 경우 jdbcTemplate으로 값을 넣은 후에 기존 상품 엔티티에서 카테고리 혹은 이미지들에 접근할 경우 lazy 기능이 동작하지 않을 것이라 생각되는데 실무에서는 bulkInsert하는 경우 lazy하는 값에 접근하지 않는다. 이런 약속을 정해놓고 개발하시는건가요?아니면 batchInsert로 넣는 값들은 내부에서 가지고 있지 않고 추후에 조회할때 조인도 사용하지 않으시나요? 질문 의도는 결국 IDENTITY인 경우 batch insert를 어떻게 해야 좋은 것이고, 만약 jpa에서 처리가 안될 경우(JdbcTemplate 등을 사용) 영속성 컨텍스트와 어떻게 문제가 생기지 않고 처리하시는지 궁금해 질문드렸습니다.제 짧은 생각으로는 어떻게든 영속성 컨텍스트 문제가 생길 것 같은데, 실무에서는 그럼 이런 엔티티는 드무니까 save 코드 주석으로 특정 lazy 값에는 접근하지 말라는 내용을 써놓고 컨벤션 느낌으로 개발하시는지도 궁금합니다.jpa, data jpa와 querydsl 강의를 모두 수강하였기 때문에 jpa에서만 국한되어 답변해주시지 않고 data jpa, querydsl, JdbcTemplate 혹은 다른 방법 어떤 내용을 포함해주셔도 공부하고 이해할 수 있습니다. 항상 좋은 강의 감사드립니다.
-
미해결실전! 스프링 데이터 JPA
교만했던 것 같아요.
항상 만족합니다.