게시글
질문&답변
2024.09.08
인터페이스는 사용하는 클래스에 가장 가까이 두는 걸로 이해했습니다. 하지만 그 인터페이스를 사용하는 클라이언트가 많다면 어떻게 해야할까요?
인터페이스를 구현과 분리하는 방식을 쓸 때, 기본적으로는 인터페이스의 클라이언트와 인터페이스를 가까이 둡니다. 그런데 인터페이스를 사용하는 클라이언트가 여러개라면? 이 때는 별도의 위치로 분리하는 수 밖에 없죠. 중요한 건 모듈 레벨에서, 혹은 계층 레벨에서 클라이언트와 같은 레벨에 두는 겁니다. 구현과 함께 또는 가까운 쪽이 아니고요. 패키지는 분리될 수 있겠지만요.
- 0
- 2
- 45
질문&답변
2024.09.08
멀티 스레드를 사용하는 테스트에서 트랜잭션 사용에 대해서..
API 레벨에서 DB 데이터의 동시성 문제를 다루고 싶다면 이건 DB 트랜잭션과 락을 사용하는 방식으로 해결해야 합니다. 테스트를 만들 더라도 모든 작업이 완료된 뒤에(즉 여러개의 트랜잭션 처리가 다 정상적으로 실행완료된 뒤에) 최종 값을 검증해야 하기 때문에 @Transactional 테스트는 어떤 식으로든 사용할 수 없습니다. @Transactional 테스트는 단일 트랜잭션을 롤백하는 것인데, API 수준의 동시성이라면 동시에 API를 실행하는 순간 이미 트랜잭션이 API마다 하나씩 따로 생기는 시나리오니 이건 단일 트랜잭션 롤백이라는 방식을 사용하는 테스트 기법의 대상에서 완전히 벗아난 것입니다. ExecutorService를 사용할 때는 트랜잭션 시작 이전, 즉 Controller에 대해서 테스트 하시는 것이 좋습니다. 개별적으로 새로운 트랜잭션이 만들어질 것이고요. 혹은 mockMvc를 이용한 MVC테스트를 이용하셔도 됩니다. 지금 준비하는 강의에서 데이터 처리 동시성 관련 문제를 한번쯤 다룰텐데 그때 구체적인 예를 소개해드리겠습니다. 그 전에라도 검색해보시면 동시성 문제를 다루는 좋은 블로그 글을 찾으실 수도 있을 겁니다.
- 0
- 4
- 54
질문&답변
2024.09.08
멀티 스레드를 사용하는 테스트에서 트랜잭션 사용에 대해서..
그런데 예로 드신 코드는 문제가 있습니다. ExecutorService에서 새로운 쓰레드를 만들고, 이 쓰레드에서 db 액세스를 하면, repository에서 새로운 트랜잭션이 만들어지는데, 이러면 고립도 설정에 따라서 같은 데이터를 두 개의 쓰레드에서 액세스할 때 어떤 결과가 나올지 모릅니다. 트랜잭션이 완료되지 않(을 수도 있)는 상태에서 두 개가 어떤 순서대로 진행될지 모르기 때문이죠. 현실에서는 절대 이런 스타일로 코드를 작성하시면 안 됩니다. 해결할 수 있는 문제도 없고, 멀티 쓰레드에서 어떤 타이밍에 어떤 코드가 실행될지 예측도 불가능합니다. 멀티쓰레드에서 서로 동기화를 잘 하지 않으면 안 되는데, 굳이 이렇게 코드를 작성할 필요도 없는데 이렇게 만드는 건 불필요할 뿐입니다.
- 0
- 4
- 54
질문&답변
2024.09.08
멀티 스레드를 사용하는 테스트에서 트랜잭션 사용에 대해서..
@Transactional 롤백 테스트를 사용하는 데 두 가지 제약사항이 있습니다. 이건 공식 문서에도 구체적으로 업급됩니다. 첫번째는 지금 처럼 멀티 쓰레드를 사용하는 것이고, 두 번째는 @Transactional의 propagation을 REQUIRES_NEW로 지정해서 별개의 트랜잭션을 만드는 것입니다. 이 두가지 케이스에선 테스트에서 @Transactional을 사용하시면 안 됩니다. @Transactional은 테스트에서 트랜잭션을 시작한 뒤에 ThreadLocal에 바인딩하고 해당 쓰레드와 해당 트랜잭션만 롤백시키기 때문입니다. 이런 특별한 케이스에선 트랜잭션을 다 커밋시키고 테스트에서 검증한 뒤에 해당 데이터를 삭제해주는 방법을 쓰셔야 합니다. 특히 @Async라든가 멀티 쓰레드를 만들어서 그 안에서 DB 조작이 일어나는 경우, 모든 쓰레드 작업이 완료된 것을 확인하고 테스트에서 검증을 해야 합니다. 그렇지 않으면 테스트 결과가 항상 동일하게 나오지 않을 수도 있습니다.
- 0
- 4
- 54
질문&답변
2024.09.02
MyAutoConfigImportSelector 에서 생성자로 ClassLoader를 주입받을 수 있는 점
MyAutoConfigImportSelector는 EnableMyAutoConfiguration 애노테이션 정의에서 @Import로 지정되어있습니다. 여기서 @Import에 다음과 갈이 지정되어있어서 MyAutoConfigImportSelector를 스프링 컨테이너를 초기화하는 과정에서 마치 빈 처럼 취급해서 필요한 오브젝트를 주입 받고, 빈 등록 기능을 수행할 수 있도록 해줍니다. (사진) ImportSelector는 좀 특별하게 취급되는 스프링 구성정보를 동적으로 만드는 컨테이너의 고유한 기능이라고 생각하시면 됩니다. @Import와 ImportSelector 인터페이스의 javadoc을 자세히 읽어보시면 도움이 될 겁니다. ImportSelector 인터페이스의 문서를 읽어보면 다음과 같이 BeanClassLoaderAware 인터페이스를 추가해서 클래스로더를 주입 받도록 만들 수 있다고 되어있고요. ImportSelector 구현 클래스가 만들어져서 동작할 때 필요한 오브젝트를 제공해주는 것이지요. 예전엔 setter 메소드를 가진 인터페이스를 추가로 구현해줬지만, 스프링의 생성자 주입 방식을 따르면 생성자 파라미터로 정의하는 것만으로, 스프링 컨테이너가 만든 클래스로더가 적용이 됩니다. (사진)
- 0
- 1
- 27
질문&답변
2024.08.29
BeanFactory 의존관계
@Configuration 클래스 안에 @Bean이 붙은 메소드만 스프링 컨테이너가 관리하는 오브젝트, 즉 빈이 됩니다. @Bean 이 없으면 스프링의 빈으로는 생성이 안 됩니다. 그런데, 스프링이 아니라 그냥 단순 오브젝트 팩토리라고 보고 실행을 하면 코드는 동작할테니 @Bean이 없어도 해당 오브젝트를 만들 수는 있죠. 이걸 스프링과 같은 프레임워크가 인식해서 자동으로 만들고 관리해주도록 하는 것이 이런 애노테이션의 활용 방법입니다.
- 0
- 1
- 59
질문&답변
2024.08.29
예외 처리에 대한 질문
DuplicateKeyException는 특정 데이터 액세스 기술이나 구현, DB에 종속되지 않도록 만들어진 비즈니스적인 의미가 담긴 예외입니다. 중복 값을 허용하지 않으므로 등록에 실패했다는 것만 담겨져있죠. 이걸 등록 처리라는 비즈니스 로직 안에서 "복구"할 수 있는 예외로 취급하고 재시도라는 로직에 사용한다면 아무 문제가 없습니다. 서비스는 리포지토리를 이용해서 데이터를 저장하거나 가져온다는 건 자신의 로직을 처리하는 기본 전제라서 문제는 없습니다. 올려주신 예제는 비밀번호를 암호화해서 이를 저장한다는 기본 로직의 구현에는 아무 문제는 없습니다. 그런데 DataAccessException을 캐치해서 이걸 boolean으로 리턴하는 건, 특별한 이유가 있는 게 아니라면 별로 좋아보이지 않네요. 재시도 등을 통해서 예외를 복구할 수 없다면 그냥 예외가 앞으로 던져지도록 아무 것도 하지 않는게 가장 좋습니다. 이걸 캐치해서 다른 시도를 하거나 특별한 처리하는 게 필요하다면 더 앞쪽에서 하는 게 맞고요, 데이터 저장에서 예외가 발생하는 경우엔 보통 특별한 대응이 없으니 어디서든 굳이 catch할 필요도 없는게 일반적이긴 합니다. 물론 이 메소드를 호출하는 쪽에서 저장 실패인 경우 꼭 boolean으로 받아야 한다고 요구를 했다면 어쩔 수 없겠지만요.
- 0
- 2
- 94
질문&답변
2024.08.25
IntelliJ project jenerator spring initailizr
최근 IntelliJ 업데이트에서 Spring Initializr가 Spring Boot라고 메뉴 이름이 변경됐습니다. Sring Boot를 선택하시면 됩니다. 실제로 Spring Boot 선택 후에 나오는 모는 기능과 내용은 Spring Initializr인데 갑자기 이름을 변경해서 혼란이 발생한 듯합니다. 강의 노트나 안내에 관련 내용을 추가해두겠습니다.
- 0
- 1
- 28
질문&답변
2024.08.20
변하지 않는 코드 분리하기 - 메소드 추출
메소드 추출을 하면 static 메소드로 만들어지는 걸 눈치채셨군요. 사실 저도 이건 생각을 못하고 있다나 강의 리뷰하면서 보고 살짝 놀랐던 기억이 있습니다. 언제부터인지 모르겠지만 IntelliJ가 자바 코드에서 메소드 추출 리팩터링을 하면 static 메소드로 만드는 게 기본 옵션으로 되어있더라고요. 인스턴스 변수를 사용하제 않는 코드는 static으로 만드는 것 같은데, 정확한 의도는 잘 모르겠습니다. 그렇게 해도 동작에는 문제가 없긴 하지만 굳이 그럴 필요는 없을 듯해서 저도 좀 의아했습니다. 옵션을 조정하면 static이 아닌 메소드로 추출하는 것도 물론 가능한데 이게 디폴트가 아니네요. (사진) 검색해봤지만 IntelliJ 개발자들의 의도가 무엇인지는 찾을 수는 없었네요. 아직도 바뀌지 않은 것으로 봐서는 버그는 아닌 듯하고, 최적화 관련 의도가 있는 게 아닌가 싶긴 합니다. 어쨌든 저도 생각 못했던 부분이긴한데 일단 기능으로는 문제는 없고, 이후에 더 나는 방법으로 확장해 가면서 그때 static을 제거하기도 했습니다. 이건 저도 좀 더 생각해보고 싶은 내용이네요. 일단 IntelliJ에서 메소드 추출 기능을 사용하시면 단축키는 메뉴에서 사용하든 static일 수도 있고, 옵션 아이콘을 눌러서 변경할 수도 있다는 것을 기억해두시면 좋겠습니다. 개인적으로는 static 메소드에서 추출이 아니라면 굳이 static으로 변경하지 않는게 더 낫지 않을까 싶습니다.
- 0
- 1
- 70
질문&답변
2024.08.17
도메인 오브젝트 테스트 강의 개선 과제
ExRateProvider를 이용해서 환율 정보를 가져오는 것도 Payment의 createPrepared()의 파라미터로 전달해서 내부에서 환율 정보를 가져오는 작업을 직접 하도록 만드셨네요. 이건 제가 의도했던 대로 잘하셨습니다. Payment 오브젝트 밖에서는 이게 API를 사용할 수도 있고, DB 액세스를 이용할 수도 있지만 Payment는 그런 구현에 의존하지 않는 인터페이스만 가지고 있기 때문에 이를 통해서 필요한 정보를 요청해서 사용할 수 있게 됩니다. 메소드 주입 방식을 잘 활용한 경우입니다. 이러면 도메인 오브젝트 테스트를 작성하기도 더 편리해집니다. 그런데 createPrepared() 내부에 Clock.fixed()를 넣으신 것은 개선이 필요합니다. 제가 의도했던 방법은 Clock 타입의 파라미터를 createPrepared()에 추가하는 것입니다. 내부에선 LocalDateTime.now (clock) 이렇게만 사용하면 되겠죠. 어떤 Clock을 전달하는지는 Payment 오브젝트 외부에서 결정해줘야 합니다. 이 부분도 개선해서 다시 코드를 보여주시면 제가 다시 의견 드릴게요.
- 0
- 2
- 112