호주에 살고 있는 소프트웨어 개발자입니다. 30년간 다양한 분야의 시스템과 서비스를 개발해본 경험이 있습니다.
스프링 프레임워크와 관련 기술을 좋아하고 JVM 기반 언어를 주로 사용합니다.
한국스프링사용자모임(KSUG)을 설립하고 활동했고, 토비의 스프링이라는 책을 쓰기도 했습니다.
개발과 관련된 다양한 주제에 관해 이야기하는 것을 좋아합니다.
강의
수강평
- [밋업 Vod] 31년차 개발자가 전하는 "AI시대, 개발자로 살아가는 법"
- [밋업 Vod] 31년차 개발자가 전하는 "AI시대, 개발자로 살아가는 법"
- [밋업 Vod] 31년차 개발자가 전하는 "AI시대, 개발자로 살아가는 법"
- [밋업 Vod] 31년차 개발자가 전하는 "AI시대, 개발자로 살아가는 법"
- [밋업 Vod] 31년차 개발자가 전하는 "AI시대, 개발자로 살아가는 법"
게시글
질문&답변
28. 회원 애플리케이션... / 이메일 중복 검사를 도메인 서비스로 수행하는 방식에 대해서
말씀하신 대로 회원의 이메일은 중복되지 않는다는 도메인 지식을 인터페이스로 만들어 도메인 서비스로 정의하고, 메소드 호출 주입 방식을 써서 필요할 때 엔티티에 전달해서 생성 로직 과정중 검증 기능으 사용하게 만들 수 있습니다.이런 경우 도메인 규칙이 도메인 모델을 직접 반영한 코드에 더 명시적으로 드러나고 풍부한 모델로서 가치가 있습니다. 다만, 이번 강의에서는 제가 이메일 중복을 체크하는 것은 같은 도메인 로직을 담당하지만 도메인 계증 내부가 아닌 애플리케이션 서비스에 두는 방식을 선택했습니다. 우선 외부 자원을 이용하는 시스템에 의존해야 하고, 애플리케이션이 포트를 통해 제공하는 기능의 흐름에 절차적으로 들어가기에 적당하다고 생각을 했고요. 다른 도메인 로직에 비해 단순 조회를 이용한 검증 로직이라서 도메인 서비스까지 동원해서 작성할 필요가 있을까라고 생각을 했습니다. 물론 리포지토리를 이용해서 동작하는 기능이 내부에 들어갈 수 있다고 해서 도메인 서비스로 만들면 안 되는 것은 아닙니다. 복잡한 도메인 규칙, 예를 들어 포인트 정책이나 결제 한도 계산 등의 도메인 로직은 구현이 자주 바뀌고 다양한 외부 리소스에 의존할 가능성이 높지만, 이건 도메인 모델로 표현되는 핵심적인 기능이기 때문에 이런 경우엔 도메인 서비스로 만들어 사용할 것입니다. 이때 도메인 모델 내부에서 동작하는 코드가 런타임시에 외부 리소스까지 연결되기 때문에 발생하는 복잡성 보다 중요한 핵심 도메인 로직을 처리하는 코드이기 때문에 도메인 모델 내에 두고, 개발팀 내에서 소통하고 테스트로 검증하는 등의 작업이 더 가치를 줄 수 있기 때문에 그런 선택을 하게 되는 것이죠.제가 이번엔 그런 선택을 했지만, 만약 개발팀이 아직 도메인 서비스를 작성하는 것에 익숙하지 않고, 메소드 주입을 이용해서 도메인 서비스를 사용하면서 그 구현은 도메인 외부에 작성하는 방식의 접근 방법을 한번 손쉽게 적용하는 연습을 해본다면, 그때는 이메일 중복 체크를 위한 도메인 서비스를 따로 만들어 사용했을 것입니다. 이 접근 방법도 꽤 여러가지가 있습니다. 이어지는 강의에서 이에 대해서 여러가지 접근 방법을 한번 보여드리도록 할게요.
- 0
- 1
- 15
질문&답변
회원 애플리케이션 서비스 테스트 (1)
앗. 제가 코딩 영상을 편집하다가 getTos() 추가하는 부분을 잘못 삭제한 듯합니다. 14분 36초 부분을 보시면 getTos() 메소드가 있는 걸 보실 수 있습니다. 당연히 메소드가 있어야겠죠.(사진)
- 0
- 2
- 24
질문&답변
정적 팩토리 메서드 관련 질문드립니다!
네. 보통은 생성자를 통해서 한번에 인스턴스를 만드는 것이 완결성이 있어서 좋다고 여겨집니다. 저도 동의하고요.그런데 여기서 정적 팩토리 메소드를 사용하려고 했던 이유 중에는 생성자 파라미터가 길어지고 같은 타입의 파라미터가 연속으로 나올 때 순서가 잘못 되어서 버그가 생기는 문제를 피하기 위함도 있었습니다. 그런데 팩토리 메소드에서도 생성자를 호출하는 방식을 쓴다면 그 문제는 여전히 남아있겠죠.그래서 여기서는 마치 빌더패턴을 사용하듯이 하나씩 프로퍼티를 설정하는 방식을 사용해봤습니다. 팩토리 메소드도 일종의 생성자라고 생각한다면 그 안에서 완결성을 가지는 오브젝트를 초기화하는 코드를 넣는 것도 나쁘지 않겠다고 생각합니다. 물론 저도 익숙한 방법은 아니긴합니다. 생성자를 private으로 만들고, 팩토리 메소드를 단일 오브젝트 생성 구조로 만드는 경우라면 이 안에서 초기화와 검증까지 모두 완료할 수 있습니다. 더더욱 필드 이름과 파라미터로 전달된 오브젝트에서 꺼내는 값의 이름을 일치시키면서 코딩을 할 수 있기 때문에 나중에 생성 시점에 전달할 파라미터가 변경되거나 순서가 바뀔 때 실수할 확률이 줄어듭니다.final은 어짜피 JPA 엔티티이고 필드 액세스를 디폴트로 적용했기 때문에 쓸 수 없습니다. JPA 엔티티는 가변 객체로 선언해야 합니다. 사실 생성자를 써도 파라미터로 넘어온 값을 모두 필드에 할당하지 않을 수도 있습니다. 그러면 부분 초기화 문제가 발생하고요. 검증 누락도 마찬가지죠. 어디선가는 생성 로직을 완벽하게 구성해야 할 책임이 있습니다. 그걸 팩토리 메소드로 가져오는 것도 하나의 옵션으로 생각하시면 좋겠습니다. 물론 저도 생성자만 쓰기도 하고, 프로퍼티 갯수가 많지 않다면 생성자에 파라미터로 풀어서 전달하기도 합니다.
- 0
- 2
- 33
질문&답변
백오피스 개발에도 헥사고날 아키텍처가 유용할까요?
우선 헥사고날 아키텍처를 사용하는 것이 도메인 모델 패턴을 사용하거나 DDD 스타일의 패턴을 적용해야 하는 것은 아닙니다. 강의에서는 이 두가지를 결합해서 적용했지만, 순수하게 헥사고날 아키텍처 패턴만 따라서 개발하더라도 유익이 많습니다. 반복적인 CRUD가 많이 등장하는 데이터 처리 중심의 애플리케이션이더라도, 20%쯤에 해당하는 간단하지 않은 로직을 처리하는 코드는 테스트가 잘 작성되는 것이 중요할 겁니다. 백오피스라면 더더욱 버그 없이 작성된 코드를 만드는 것이 중요하고 그런 면에서 헥사고날 스타일로 작성해서 충실한 테스트를 만드는 것이 주는 장점이 많습니다. 그런데 요즘 스프링을 이용한 개발을 충실하게 하시면 사실 대부분 헥사고날 아키텍처에 거의 가깝게 개발하고 계실 겁니다. 스프링 데이터 JPA를 쓴다면 더더욱 그렇고요. 그렇다면 계층형 스타일과 다른 점은 크지 않습니다. 중요한 것은 서비스에 인터페이스를 반드시 정의하고, 어떤 의도로 어떤 기능을 사용하는지를 잘 담아두고요. 서비스 계층의 비즈니스 로직이 외부로 노출되지 않도록 주의하면 됩니다. 그리고 리포지토리를 비롯해 서비스에서 외부 시스템을 사용하는 코드도 인터페이스를 충실하게 작성하고 이를 서비스 모듈 안에 정의해두고, 어떤 환경에서는지 테스트하기 좋게 만들어서 중요하다고 생각되는 작업에는 반드시 테스트를 작성하시면 됩니다.헥사고날 아키텍처 자체는 그리 어려운 것이 아니고, 스프링에서는 계층형 아키텍처와 구조적으로 거의 차이가 없습니다. 강의에서 강조드린 헥사고날 아키텍처의 특징과 요구사항이 어떤 것인지만 잘 생각해보시고 그걸 따를 수 있도록 하시면 좋겠습니다.CRUD에 대해서 다 테스트를 만들어야 할지도 고민이 될 수 있는데, 매번 동일한 형식의 구조이고 별다른 로직이 없다면 처음부터 안 만드셔도 괜찮습니다. 그보다는 상태 변화와 복잡한 조회, 여러 엔티티가 한번에 바뀌는 로직 등에 대해서만 테스트를 잘 작성해보세요. 도메인 모델 패턴은 헥사고날 아키텍처가 요구하는 것은 아닙니다. CRUD 중심이라면 도메인 모델 패턴을 쓴다고 해도 아주 단순한 구조가 되겠죠. 하지만 20%에 해당하는 로직을 처리하는 코드에서 엔티티가 가진 정보를 이용하는 코드는 엔티티 내부에 작성하는 정도만 충실하게 하셔도 충분합니다. 제 생각에는 헥사고날 아키텍처가 오버엔지니어링이 될 가능성은 거의 없습니다. 시중에 잘못 알려진 복잡한 형식을 요구하는 오해를 가진 잘못된 헥사고날 아키텍처를 따르지만 않는다면요.
- 0
- 2
- 54
질문&답변
spotbug + @NonNullApi 로만 Null 방어가 될까요?
안녕하세요.@NonNullApi와 이를 체크하는 SpotBug과 같은 도구는 빌드 과정에서 명시적으로 해당 체크를 요구할 때만 검증을 해줍니다. 만약 SpotBug 체크를 빌드에 추가하지 않으면, 자바 언어의 기본 빌드에서는 이 애노테이션은 아무런 역할을 해주지 못하겠죠. 그래서 직접 null 체크하는 코드를 requireNonNull()을 넣은 것입니다.Null 값을 잘못 전달하는 상황을 막아주는 것은 런타임에 동작하는 requireNonNull() 입니다. 이게 가장 중요하죠. 자칫 null 값이 DB에 들어가게 되면 이후 여러가지 복잡한 버그가 발생하고 데이터가 망가질 겁니다. 이걸 복구하는 건 꽤나 어려운 작업입니다. 하지만 이렇게 런타임에 안전장치를 해두는 것은 실제 해당 코드가 실행되기 전에는 문제가 있다는 것을 알 수가 없습니다. 서버를 배포하고 2-3일 지나서 처음으로 requireNonNull() 코드가 실행이 된다면, 그때까지 코드의 결함이 있었음에도 확인을 못하고 방치하게 되는 거죠.그래서 100% 완벽하게 null 문제를 발견해주지는 못하지만(왜냐하면 API 등을 통해서 외부에서 전달되는 값에 의한 null 문제나, 우리가 작상한 코드 외의 라이브러리나 다른 모듈을 통해서 잘못 사용되는 경우도 있으니까요) 그래도 검토 가능한 코드 레벨에서 명백하게 null 값이 들어갈 수 있는 오류가 있다면 미리 체크할 수 있게 하기 위해서 SpotBug 같은 빌드 시점에 동작하는 정적인 체크 기능을 사용해서 결함을 일부라도 미리 발견할 수 있도록 한 것입니다. 이런 정적인 코드 분석을 통한 결함 사전 발견은 IntelliJ 같은 IDE에서도 일부 제공됩니다. 이것도 역시 100% 문제를 다 체크해주지 못하기 때문에 우리가 코드에서 항상 주어진 값을 검증하는 코드를 넣어서 실행시점에서라도 확인이 가능하도록 만드는 것이 필요합니다.
- 0
- 2
- 29
질문&답변
domain 모듈에 entity를 정의한다고 했을때
질문하신 내용에 대해서는 강의 후반부에 자세하게 설명드렸습니다.그런데 JPA와 MongoDB를 동시에 써서 개발하는 경우가 아니라면 어떤 방식으로 접근하든(도메인 엔티티와 영속 엔티티를 분리하고 매핑, 또는 단일 엔티티 유지) 코드의 변경은 필요합니다. 동시에 쓰는 경우는 별로 없다고 생각하고요.당장 JPA와 같은 RDB에서는 요즘 Long 타입의 인조키를 선호합니다. 반면 MongoDB는 String 타입의 id를 사용하죠. 그런식의 코드 변화는 도메인 엔티티에도 영향을 미칩니다. 모든 엔티티는 고유한 ID를 가지고 있어야 하니 그 값을 매핑 받아야 하기 때문이죠. 더 나아가 그런 DB의 변화가 있다면 마이그레이션 작업도 크게 일어날 것입니다. 영속 엔티티를 분리해서 작성하는 번거로운 작업을 굳이 해야할 필요가 있는 경우는, 아마도 레거시 DB를 사용하거나, 서로 영향을 주기 힘든 다른 팀과 DB를 공유해서 우리가 DB 설계와 생성 부터 컨트롤 할 수 없고, 도메인 설계를 해보니 매핑이 바로 안 돼서 매번 번거로운 전환이 필요한 경우입니다.그 외에는 도메인 엔티티를 영속 저장용 엔티티로 활용하는 것이 대체로 더 나은 선택일 것입니다.
- 0
- 2
- 29
질문&답변
서비스 단위 테스트 코드 작성
단위 테스트는 말 그대로 한 단위를 어떻게 정의하냐에 따라서 여러가지 접근 방법이 있습니다. 단위 테스트라고 무조건 클래스 하나만 테스트할 필요는 없습니다. 그리고 단위 테스트라고 하더라도 인터페이스를 타고 테스트하는 것이 좋고요.애플리케이션 서비스 계층은 외부 리소스, DB 등에 의존하는 특징이 있습니다. 이걸 단위 테스트로 하려면 의존 대상에 대한 적절한 Mock, Stub 등을 만들어서 해야 합니다. 이번 강의에서 테스트를 만드는 여러가지 방식에 대해서 설명드리면서 단위 테스트를 작성하는 방법도 설명 드렸습니다. 그 부분을 참조해보시면 좋겠습니다. 아니면 다른 구체적인 케이스가 있다면 알려주세요.
- 0
- 2
- 38
질문&답변
혹시 다음 편은 언제쯤 오픈할까요?
11월 중에 마무리하는 것을 목표로 열심히 진행하고 있습니다.
- 0
- 2
- 58
질문&답변
required 포트에 관해서
리포지토리처럼 DIP가 적용된 경우에 이 인터페이스는 이를 사용하는 쪽 모듈에 가까이 두는 것이 중요합니다. 여기서는 헥사곤 내부에 두었지요. 그런데 그 중에서 여러 모듈에서 사용하게 되는 것들이 있습니다. 외부 연동용 API도 그렇고요. 보안쪽도 그럴 수 있죠. 또 조회용 리포지토리도 많은 경우 그에 해당됩니다.이 때 두가지 접근방법을 둘 수 있습니다. 여러 모듈에서 사용되는 required interface에 해당되는 리포지토리를 shared 모듈을 별도로 만들어서 그 안에 두는 방법입니다. 이러면 어떤 모듈에서든 자유롭게 사용해도 되고, 의존 방향도 단방향으로 적절하게 구성할 수 있습니다. 다만, 도메인의 엔티티가 파라미터나 리턴 값으로 사용되고 있다는 점에서 리포지토리를 메인 엔티티가 존재하는 모듈 외부로 가져오는 것이 적절한지는 의문이기도 합니다. 그래서 두번째 방법으로 헥사고날 애플리케이션 모듈의 의존 방향이 꼬이지 않는다면, 즉 양방향 참조가 일어나지 않는다면 다른 모듈의 리포지토리는 필요에 따라서 사용하는 것을 허용하는 것입니다. 이때는 반드시 조회 기능에 제한을 두어야 합니다. 다른 모듈에서 수정까지 일으키면 의존관계가 얽혀서 나중에 이해하기 힘든 코드가 되버리겠죠.Team -> Member 의존 구조로 정의한다면 Team 애플리케이션 쪽에서는 Member의 provided interface를 태우지 않고, 바로 MemberRepository(여기서는 Finder라고 하셨네요)에 접근하는 것까지 허용하는 것입니다. 하지만 반대는 만들지 않는 것이 좋습니다. Member에서 Team에 접근해야 하는 상황이라면 도메인쪽 설계를 다시 고민해보거나, 여기서 모듈간에 DIP를 다시 쓰는 방법이 있습니다.
- 0
- 2
- 26
질문&답변
PaymentConfig에 대해 궁금한게있습니다
앗. 제가 질문 올리신 것을 확인하지 못해서 답변을 못드리고 있었네요.이건 단순하게 시작하기 위해서 직접 new로 오브젝트를 만들었고요. 빈으로 정의한 뒤에 메소드 파라미터로 주입 받아서 연결하는 것도 물론 가능합니다.
- 0
- 2
- 179




