호주에 살고 있는 소프트웨어 개발자입니다. 30년간 다양한 분야의 시스템과 서비스를 개발해본 경험이 있습니다.
스프링 프레임워크와 관련 기술을 좋아하고 JVM 기반 언어를 주로 사용합니다.
한국스프링사용자모임(KSUG)을 설립하고 활동했고, 토비의 스프링이라는 책을 쓰기도 했습니다.
개발과 관련된 다양한 주제에 관해 이야기하는 것을 좋아합니다.
강의
수강평
- 토비의 스프링 6 - 이해와 원리
- [밋업 Vod] 31년차 개발자가 전하는 "AI시대, 개발자로 살아가는 법"
- 토비의 클린 스프링 - 도메인 모델 패턴과 헥사고날 아키텍처 Part 1
- 토비의 클린 스프링 - 도메인 모델 패턴과 헥사고날 아키텍처 Part 1
- 토비의 클린 스프링 - 도메인 모델 패턴과 헥사고날 아키텍처 Part 1
게시글
질문&답변
PaymentConfig에 대해 궁금한게있습니다
앗. 제가 질문 올리신 것을 확인하지 못해서 답변을 못드리고 있었네요.이건 단순하게 시작하기 위해서 직접 new로 오브젝트를 만들었고요. 빈으로 정의한 뒤에 메소드 파라미터로 주입 받아서 연결하는 것도 물론 가능합니다.
- 0
- 2
- 173
질문&답변
Serverproperties 객체 생성 후 @Impor 어노테이션 사용 이유 용도
제가 질문 올리신 것 확인을 못했네요. 답변이 늦었습니다. @Import는 @Configuration 과 같은 구성정보를 가진 클래스에서 이와 직접 연관된 다른 @Configuration 혹은 빈 컴포넌트를 가져올 때 사용합니다. 이를 다른 데서 @Bean 등으로 선언해도 되지만 명시적으로 @Import를 사용하는 것은 가져오는 구성정보와의 연관성 때문에 그렇습니다. 역사적으로 XML로 스프링의 구성정보를 작성할 때 태그로 구성정보를 가져오는 것을 애노테이션 방식에 그대로 도입한 것입니다. @Import를 뺀다면 그 빈에 대한 정의를 어디서든 직접 해야 합니다. 그렇지 않으면 위와 같은 에러를 만나게 될 겁니다.
- 0
- 2
- 129
질문&답변
상태 변경 API 질문
컬렉션을 애그리거트에 포함시키는 경우 단순한 변경은 매번 새로 등록하는 것도 가능하겠만, 컬렉션 원소 엔티티가 복잡하거나 양이 많거나, 한번에 업데이트하는 작업이 크다면 변경 트랜잭션을 처리하는 작업은 고민이 필요할 겁니다.먼저, 애그리거트에서 서브 엔티티는 id를 노출하지 않는다는 것은 직접 서브 엔티티에 접근하려고 id 값을 사용하지 않는다는 것입니다. DDD 책에서는 서브 엔티티는 애그리거트 내에서 유의미한 id를 가진다라고 되어있습니다. 예를 들면 인덱스 같은 것이겠죠. 하지만 필요하면 id 값을 공유해도 아무 문제 없습니다. list 내의 원소 하나에 직접 접근하기 위해서 그 id를 쓰는 것이 아니라면 애그리거트 설계면에서 아무 문제가 없습니다. 이후에 그 원소를 한번에 업데이트 할 때, 변경이나 삭제가 필요한지를 따져볼 때 사용할 수 있죠. 그런면에서 굳이 id를 제거할 이유는 없습니다.다만 이렇게 컬렉션을 한번에 업데이트 하는 방식은 어쩔 수 없이 복잡해질 겁니다. 근본적으로 컬렉션 관계의 엔티티가 매번 상위 트랜잭션에 묶여서 한번에 변경되는가라는 것도 따져봐야 합니다. 애그리거트 단위가 너무 크다고 판단되면 이를 분리해서 더 작게 쪼개는 것은 실제로 많이 경험하는 일이기도 합니다. One to many가 2단계 연결된 경우를 애그리거트 하나로 잡으면 한번에 수백개씩 조회하고 변경하는 일이 일어나기도 합니다. 저라면, 이 구조를 애그리거트로 묶었다고 하더라도 list 내부 원소를 변경하는 작업을 하나의 트랜잭션으로 묶어서 처리할 겁니다. 그러면 변경이 일어날 때 애그리거트를 로딩하긴 하지만, 모든 컬렉션을 매번 가져올 필요는 없을 것이고(lazy), 추가인지 변경인지, 삭제인지 구분된 요청이 오기 때문에 루트 엔티티를 통해서 요구가 들어오더라도 변경에 큰 무리가 없습니다.대량 데이터의 변경과 한방 업데이트라고 하면, 일단 가공된 정보를 최대한 상세하게 받아서 처리를 해야겠죠. 여러가지 선택지와 트레이드 오프를 고려할 점이 많은 케이스 같습니다.
- 0
- 2
- 68
질문&답변
WebApiExRateProvider 템플릿 콜백 패턴을 적용하면서 테스트 코드를 만들어보았습니다.
API가 사용하는 URL을 검증하는 테스트로군요. 좋은데요.URL은 외부 설정에 의해서, 또는 하드코딩된 코드에 의해서 결정되는데 이걸 검증한다면, 아무래도 코드에 고정된 URL인 경우에 유효할 것입니다. URL을 다른 걸 사용하지 않도록 강제하는 경우겠죠.반면에 빈 설정이나 프로퍼티 등을 통해서 URL을 넣는 경우라면 프로퍼티 값과 실제 사용되는 URL이 일치하는지를 테스트 하면 되겠네요.어쨌든 Mock을 이용해서 사용 URL을 캡처하는 고급 기법을 잘 사용하셨습니다. 저는 실제로 이렇게까지는 테스트를 하지 않습니다. 아마 사용하는 API 서비스가 하나 새로 선택된다면 수동적으로 확인 가능한 테스트를 해서 API 사용 코드가 잘 동작하는지 확인해보고, 이후에는 그걸 믿고 사용할 겁니다.외부 서비스를 이용하는 코드에 대해서 테스트하는 게 사실 쉽지는 않거든요. 환율이 어떻게 와야 성공인지 결정할 수 없기 때문이기도 하고, 테스트 하는 동안에 외부 서비스가 다운 되거나 네트워크 문제가 생기면 테스트가 실패하기 때문이죠. 그래서 API 호출하는 인터페이스까지 잘 넘어가지는지 정도만 고정된 테스트로 만드는 경우가 많습니다. 물론 아주 중요한 API라면 mock server를 이용한 테스트를 만들기도 합니다.
- 0
- 2
- 28
질문&답변
빈약한 도메인 모델을 보완하기
단순한 상태 변경을 JPA의 UPDATE 문을 이용해서 한다면 굳이 다르게 접근할 필요는 없습니다.그런데 그 엔티티의 상태 변경이 여러가지 중요한 로직과 연결이 될 수 있다면 그때는 엔티티로 받아서 상태 변경을 하고 다시 업데이트 하는 구조로 만들어야겠죠. 대체로 상태 변경은 중요한 로직이 될 가능성이 있긴 하거든요. 실제 코드를 보기 전에는 딱히 어떻다고 말씀 드리긴 어렵겠네요.
- 0
- 2
- 16
질문&답변
인메모리 DB, 테스트 컨테이너 선택 기준이 있으신지 궁금합니다
메모리DB 테스트는 실행 속도가 빠릅니다. JPA는 DB가 달라져도 동일한 결과를 어느 수준까지 보장을 해줍니다. 그래서 메모리DB를 우선 선택하는 것을 선호합니다.반면 실제 DB를 컨테이너든, 로컬에 설치한 테스트용 DB를 쓰든 사용하는 경우엔 운영환경과 100% 동일한 조건으로 실행할 수 있고, 또 native sql과 같은 JPA가 지원하지 않는 조회 방식을 쓰는 코드도 테스트가 가능합니다.실무에선 이 두가지를 동시에 사용하는 경우가 많습니다. 특별한 조회 로직이 있지 않은 기본적인, JPA로 커버되는 DB조작은 메모리 DB로, 실제 사용 DB로만 테스트가 가능한 것은 컨테이너 등을 이용해서 실 DB에 대해서 테스트를 수행합니다. 물론 운영 환경에 배포되기 전에는 h2로 테스트 했던 모든 테스트를 MySQL과 같은 실제 DB를 사용해서 테스트 되도록 만드는 것이 좋습니다. 만에 하나 DB 차이에 의해서, JPA를 쓰더라도 차이가 발생하는 문제를 발견할 수 있기 때문입니다.물론 처음부터 운영용 DB로만 테스트 할 거야라는 분들도 있습니다. 그래도 상관없습니다. 여기서부터는 좀 취향의 영역이기도 합니다. 대신 테스트 수행 속도에서 차이가 있습니다.
- 0
- 2
- 18
질문&답변
entity 내부에 passwordEncoder 를 넣는다면 결합도를 높게 만들게 되는 것 아닌가요?
결합도가 높으면 어떤 변경이 다른 곳에 전파가 계속 됩니다.그런데 PasswordEncoder는 인터페이스이고, 여기에는 비밀번호를 암호화한다라는 우리가 도메인을 분석했을 때 나온 로직만 표현되어 있습니다. 그걸 구현한 코드는 완전히 분리되어 있어서 암호화 기술을 변경하든 다른 방식으로 하든 그게 엔티티(Member)에 영향을 주지 않습니다. 그래서 결합도가 높지 않습니다.비밀번호를 암호화한다라는 규칙을 바꾼다면, 그때는 변경이 필요한데 그건 어짜피 Member에 대한 로직이 변경되는 것이니 그때 PasswordEncoder가 빠지거나 달라지는 것은 아무 문제가 없습니다. 오히려 응집도가 높아지는 것이죠.
- 0
- 2
- 28
질문&답변
테스트의 기준을 어떤식으로 설정하는 것이 바람직한 테스트일까요 ?
테스트는 많으면 좋다고 생각하기 쉽지만, 사실은 테스트도 관리해야 할 코드이기 때문에 무조건 많다고, 혹은 커버리지가 높다고 좋은 건 아닙니다.테스트는 중요한 것, 이 로직이 틀리면 버그가 되는 것, 내부 구현 방식이 변경될 가능성이 높은 것을 우선적으로 집중적으로 만드는 것이 가장 중요합니다. 그리고 테스트 실행을 빠르게 자주 할 수 있는 방식으로 하면 좋죠. 테스트를 하려면 서버에 배포하고 준비하고 사람이 손으로 뭔가 작업을 해서 10분 걸린다, 이러면 테스트를 잘 못하겠죠. 대체로 로직이라고 생각되는 코드, 보통 데이터를 변경하는 것, 조건이 붙는 것(if, switch) 등이 테스트를 잘 만들어야 할 코드입니다. 자칫 실수하면 심각한 버그가 될 수 있거든요. 테스트를 통해서 요구사항을 개발자가 잘 정리하기도 좋습니다.테스트 범위나 단위는 절대적으로 어떻게 좋다고 정할 수 없습니다. 중요한 건 개발자가 테스트의 크기를 스스로 조율할 수 있어야 합니다. 이번 것은 테스트를 좀 작게 만들고, 작은 메소드 하나이지만 중요하니 이건 집중해서 하자, 이럴 수도 있고. 또 이건 너무 뻔한 패턴이고 전형적인 구조니까, 그냥 API 테스트 하나로 만들자, 이건 비슷한 코드가 너무 많으니 굳이 테스트 만들지 말자 하고 넘어갈 수도 있어야 합니다.그걸 정하는 기준을 개발 스스로가 빠르게 판단하고, 필요하면 그 크기를 바꾸면서 진행하는 능력이 개발자에게 반드시 필요합니다. 코드를 많이 만들어보시면서, 작성된 코드와 테스트를 종종 리뷰해보세요. 그리고 어떻게 테스트를 만들었다면 더 좋았을까에 대해서 팀원들과 이야기도 많이 나눠보세요.
- 0
- 2
- 17
질문&답변
헥사고날 아키텍처와 DDD
헥사고날 아키텍처를 그 원칙대로 잘 적용해보시면 좋겠습니다. 사실 스프링을 써서 개발하시는 분들이라면 어느 정도 헥사고날 아키텍처에 가까운 구조로 개발을 이미 하고 계실 겁니다. 거기에 기능 제공 인터페이스만 애플리케이션쪽으로 가져오시면 됩니다. 헥사고날 아키텍처 뿐 아니라 도메인 모델 패턴을 활용하시면 좋겠습니다. 데이터 분량과 상관없이 비즈니스 로직이 분명하게 들어가고, 변경이 일어날 가능성이 있다면, 데이터에 적용하는 로직을 도메인 모델로 반영하시면 이후 개발에 도움이 되실 겁니다.그 외에 대량의 데이터를 처리하기 위한 아키텍처나 설계는 워낙 여러가지 고려할 점이 있을텐데 이건 실제 케이스에 따라서 워낙 다양한 접근 방법이 있을 겁니다. 그런데 이걸 초기에 아키텍처는 이래야해라고 다 결정해서 적용하기 보다는 우선은 가장 단순하지만 명확한 것으로 시작하고, 어떤 문제가 있을 거라는 점이 분명하게 드러날 때 단계적으로 발전시키는 방향으로 나가는 것이 좋습니다.우리는 나중에 엄청나게 큰 서비스가 될 거니까 처음부터 MSA를 쓸 거야, 이런 식으로 시작하면 정말 처음에 중요하게 접근해야 할 도메인의 핵심을 잘 분석하고 이해하는 것이 뒷전이 되기 쉽습니다. MSA가 무슨 도움이 될지도 알 수 없는 채로 적용하면서, 수많은 기술적인 문제를 만나게 되면 개발팀은 지치게 되죠.아키텍처에 대해서 너무 부담 가지지 마시고, 우선은 단순하게 시작하시되 강의에서 설명드리는 도메인 모델을 잘 반영하도록 코드를 작성하는 것, 환경과 기술 변화에 영향을 덜 받고 테스트를 잘 만들 수 있도록 헥사고날 아키텍처를 쓰는 것 정도면 충분하지 않을까 싶습니다.
- 0
- 2
- 26
질문&답변
Seprate Interface 패턴에 대한 궁금증
제가 관찰해본 경험으로는 생각보다 DIP를 잘 쓰지 못하는 경우가 많습니다.DIP를 단지 인터페이스를 만드는 것으로 오해하는 분들도 많습니다. 이런 경우엔 만들어진 인터페이스를 하위 모듈에 두는데, 리포지토리 인터페이스처럼 구현이 하나 뿐인 경우엔 별로 문제라고 느껴지지 않을 수도 있긴 합니다.하지만 시스템이 커지고 모듈 구조가 복잡하게 얽히기 시작하면 DIP를 하지 않은 경우는 변경에 취약하고, 코드를 이해하기도 힘들어질 수 있습니다. 그래도 최근엔 DIP를 잘 이해하고 쓰시는 분들이 늘어나고 있는 것 같습니다. SpringMVC는 최상위 모듈이고 거기서 DIP를 직접 적용할 케이스는 별로 없을 듯합니다. 보통 서비스에서 사용하는 리포지토리나, 외부 API 호출, 또는 다른 기능 모듈에 대한 접근 등에서 DIP가 주요하게 사용됩니다.
- 0
- 2
- 16