호주에 살고 있는 소프트웨어 개발자입니다. 30년간 다양한 분야의 시스템과 서비스를 개발해본 경험이 있습니다.
스프링 프레임워크와 관련 기술을 좋아하고 JVM 기반 언어를 주로 사용합니다.
한국스프링사용자모임(KSUG)을 설립하고 활동했고, 토비의 스프링이라는 책을 쓰기도 했습니다.
개발과 관련된 다양한 주제에 관해 이야기하는 것을 좋아합니다.
講義
受講レビュー
- トビーのクリーン・スプリング - ドメインモデルパターンとヘキサゴナルアーキテクチャ Part 2
- トビーのクリーン・スプリング - ドメインモデルパターンとヘキサゴナルアーキテクチャ Part 2
- トビーのクリーン・スプリング - ドメインモデルパターンとヘキサゴナルアーキテクチャ Part 2
- トビーのクリーン・スプリング - ドメインモデルパターンとヘキサゴナルアーキテクチャ Part 1
投稿
Q&A
조회 메서드 네이밍 질문
아주 여러운 질문을 주셨네요. 🙂 좋은 네이밍을 만드는 정답은 없습니다. 데이터가 없을 때 예외 발생을 하거나 null을 리턴하게 하는 것을 구분하기 위해서 오래전에 Hibernate는 get, find 같은 방식으로 이름을 주어서 구분하기도 했는데요. 사실 이 단어가 직관적으로 이해될 수 있는 것이 아니기 때문에 매번 정확히 정의를 기억하고 있지 않으면 네이밍은 아무런 도움이 되지 않는 다는 것이 사실입니다.자바에는 이제는 다음과 같은 규칙을 따르는 것이 권장됩니다.단일 데이터를 조회하는 메소드인데 데이터가 없는 경우가 있을 수 있고, 그때도 예외없이 리턴을 해야 한다면 Optional 타입을 리턴하게 합니다. 이때는 메소드 이름이 아니라 리턴 타입을 기준으로 어떻게 동작하는 메소드인지 판단할 수 있습니다. NPE 문제 등으로 인해서 Optional이 나온 뒤에는 null을 리턴하는 방식은, 리턴값을 null 여부와 상관없이 단순 저장하는 경우가 아니라면 사용하지 않는 것이 권장됩니다.데이터가 없을 수도 있는데 있을 때는 하나 또는 여러개라면 List를 사용합니다. 사이즈를 보면 데이터가 없는 경우도 쉽게 파악할 수 있기 때문에 이 방식이 많이 사용됩니다.데이터가 없을 때는 예외가 발생할 수 있다면 권장되는 방식은 예외를 선언하는 것입니다. throws NoSuchElementException을 불여주는 것이 좋습니다. 이건 메소드가 데이터를 리턴하는 것이 원칙인데, 파라미터가 바르지 않기 때문에 데이터를 가져오지 못하는 경우, 개발자에게 경고를 주기 위해서 예외를 발생시키는 것입니다. 정상 케이스에서는 항상 데이터를 리턴하도록 설계된 조회에서는 사용하지 않는 것이 좋습니다. 그렇다면 메소드 이름을 만드는 것은 어떤 규칙을 쓰는 게 좋은가를 생각해보면, 이것은 철저히 취향 또는 결정의 문제입니다. 조회하는데 add, update로 시작하는 메소드를 쓰지는 않겠죠. 아마도 get, find 등을 많이 사용할 겁니다. 어떤 것이든 사실 아무 상관이 없습니다. 지켜야할 규칙은 일관성이 있어야 한다는 것입니다. 여기저기서 다른 동사로 시작하는 메소드를 쓰면 코드를 읽을 때 혼란이 생길 수도 있겠죠.OrNull, OrElseThrow 등의 접미사를 쓰는 방식은 자바에서도 Optional 쪽에서만 사용하는, 그 기술을 만든 전문가 그룹의 취향이 반영된 방식인데요. 매번 이런 스타일을 메소드 이름에 적용하는 것은 별로 바람직하지 않습니다. 우리가 코드를 볼 때 주요 로직의 흐름을 빠르게 파악하는 것이 중요한데 이런게 많이 붙어 있으면 시선을 뺐기고 인지부하가 걸릴 뿐이죠.어떤 선택이든 상관없습니다만 메소드 설계는 가능한 자바의 최신 관례를 따르는 것이 좋고, 메소드 이름은 일관성을 지키는 선에서 간결하게 만드는 것이 바람직합니다.
- いいね数
- 0
- コメント数
- 1
- 閲覧数
- 39
Q&A
핵사고날 아키텍처 기반으로 멀티 모듈 설계 시 질문드립니다..
Part1 강의에서 설명드렸던 것처럼 헥사고날 아키텍처에서 Repository는 인터페이스만을 가리킵니다. 실제 그 인터페이스를 구현한 코드는 기술과 환경에 종속되기 때문에 어댑터에 인터페이스를 구현하는 클래스를 두고 만들어야 하죠. 다만, 스프링 데이터 JPA를 사용하는 경우 이 구현 클래스를 개발자들이 직접 만들지 않아도 스프링이 런타임에 자동으로 코드를 작성하고 컴파일해서 구현 기능을 사용하게 해줄 뿐입니다. 다시 정리하자면, Repository는 헥사고날 애플리케이션이 필요한 기능을 정의한 required interface이고요. 실제 구현은 그 외부 어댑터 영역에서 일어납니다. 스프링 데이터를 쓴다고 하더라도 QueryDSL이나 다른 데이터 기술을 사용한다면 어댑터 쪽에 실제 기능을 담당하는 리포지토리의 구현 코드가 만들어지면 됩니다.
- いいね数
- 0
- コメント数
- 2
- 閲覧数
- 59
Q&A
내부 객체 직접 접근에 관하여.
좋은 질문을 해주셨네요. 애그리거트 안에 루트 외에 내부 엔티티가 존재할 경우에 내부 엔티티에 접근하거나 기능을 수행하는 것은 최대한 제한되는 것이 바람직합니다. 애그리거트 내부 구조가 노출되고 내부 엔티티의 기능까지 직접 사용하게 되면 애그리거트 단위로 작업을 수행하려고 한 애초의 목적을 놓칠 수 있게 됩니다. 그래서 모든 내부 엔티티에 대한 조작과 접근을 루트 엔티티로 제한하는 방법을 선택할 수 있습니다. 그래서 보여주는 방법처럼 내부 detail 엔티티가 가지고 있는 정보를 조회할 때도 루트 엔티티의 메소드를 통해서 정보를 가져오는 작업을 위임할 수 있습니다. 그런데 저는 이렇게 모든 정보의 조회까지 루트에 다 구현을 해두면 루트 엔티티가 너무 복잡해진다는 느낌이 들더라고요. 그래서 저는 트레이드 오프를 통해서 단순 조회인 경우, 그리고 변경이 크게 일어날 가능성이 없다고 판단되는 경우엔 조회 로직은 내부 엔티티에 접근해서 가져오는 방식을 선택했습니다. 물론 변경이 필요한 작업은 애그리거트 단위에서 일관성을 지키는 것이 중요하기 때문에 내부 엔티티의 변경 로직은 철저하게 차단을 해두고, 루트 엔티티가 외부에 공개한 퍼블릭 메소드를 통해서만 일어나게 제한을 두는 방식을 선택한 것입니다.보여주신 방식처럼 조회 메소드도 루트 엔티티로만 제한을 하는 방법도 선택이 가능한 옵션입니다. 가벼운 트레이드 오프를 통해서 접근 방식을 결정하고 일관되게 적용하시면 됩니다. 루트 엔티티가 커지는 문제 vs 애그리거트 내부 엔티티 구조가 변경됐을 때 조회 로직도 따라서 바뀌어야 하는 문제 중에서 어느 부분에서 손해를 감수하고 다른 장점을 취할 것인가를 실제 코드를 구현해보시면서 판단에 따라 트레이드 오프 해보세요.
- いいね数
- 0
- コメント数
- 2
- 閲覧数
- 87
Q&A
InvalidCurriculumException DIP 적용 여부 문의
날카로운 지적을 해주셨네요.말씀하신 대로 지금 구현에서는 예외 클래스 때문에 양방향 의존성이 남아있게 되네요. 현재 ArchUnit에서는 애플리케이션 컴포넌트 레벨에서 순환참조 확인이 안 되고 있었습니다. 도메인과 애플리케이션 계층에서 각각 체크하기만 해서 이 부분을 놓치고 있었네요. ValidationException은 shared 모듈에 정의를 해둔 것이 있으니 그걸 인터페이스에서 던지도록 정의해두고, CurriculumValidationException이 발생하는 CurriculumModifyService 클래스 내 validate() 구현 내부에서 이를 캐치해서 ValidationException으로 한번 wrapping해서 던지게 하면 됩니다.제가 미처 체크하지 못했던 부분을 알려주셔서 감사합니다. 피드백 반영할 때 이 내용도 추가하도록 하겠습니다.
- いいね数
- 0
- コメント数
- 2
- 閲覧数
- 65
Q&A
readme에 대한 자료는 따로 받아볼수 있나요?
말씀하신 readme가 어떤 내용인지 잘 모르겠습니다. 구체적으로 알려주시면 답변을 드릴게요. 강의자료를 보시면 전체 소스코드와 문서 다운로드는 확인하실 수 있습니다.
- いいね数
- 0
- コメント数
- 2
- 閲覧数
- 77
Q&A
Part 2 듣기전 복습하던 중, 아키텍처에 관한 질문이 있습니다.
제 생각엔 스프링 프레임워크를 사용해서 애플리케이션을 개발하면서 스프링 개발자들이 권장하는 방식으로 자연스럽게 코드를 설계하고 구성한다면, 대부분 헥사고날 아키텍처를 적용하고 있다고 봅니다. 아직 복잡하지 않은 시스템이라고 하더라도, 객체지향 설계원칙과 응집도/결합도라는 기본적인 원칙을 따르면 자연스럽게 헥사고날 아키텍처가 나오는 것이 정상입니다. 이건 스프링이 처음 등장했을 때부터 제안하고 지지했던 방식입니다.도메인 모델 패턴도 마찬가지입니다. 빈약한 도메인(getter, setter 외에는 없는 엔티티만 사용하는) 모델 같은 것도 스프링의 개발 철학과 맞지 않습니다. 따라서 간단한 애플리케이션이니 스프링을 사용하지 않겠다는 결정을 하실 수는 있겠으나, 스프링으로 개발하기로 결정하셨다면 헥사고날 아키텍처를 자연스럽게 사용하실 수 있습니다.한때 인기를 끌었던 클린아키텍처 책을 통해서 헥사고날 아키텍처가 알려지면서 그 책의 예제에 나온 방식으로 코드를 작성해야 헥사고날 아키텍처라는 잘못된 인식이 널리 퍼졌는데요. 그 방식은 너무 복잡한 관례를 요구합니다. 네이밍 규칙도 자연스럽지 않고 패키지가 아주 많아지죠. 그래서 Part1에서 헥사고날 아키텍처의 본래 의도와 적용 방법을 가능한 가장 단순한 방식으로 설명드리려고 했습니다. 그 부분을 잘 이해하고 따라가시면 되는데, 그래도 전통적인 자바 백엔드 개발 스타일과는 다른 부분이 있어서 고민이 되실 수는 있습니다. 포트 인터페이스를 의도 단위로 쪼개는 것들도 익숙하지 않으실 수 있습니다. 그냥 MemberService라는 인터페이스 하나에 Member 관련 기능을 다 넣어서 개발하는 것이 단순하기 때문에 아주 복잡해지지 않는다면 이런 방법을 선택하는 경우가 많았거든요. 이런 부분은 좀 고민이 필요한데, 사실 처음부터 완벽하게 포트를 나누고 시작하지 않으셔도 됩니다. 우선 Member 엔티티를 종심으로 Service, Port 인터페이스를 하나씩 만들고 시작하다가 나중에 메소드가 많아지면 그때 이걸 어떻게 나눠볼까 하고 인터페이스를 쪼개고, 구현 클래스도 복잡해지면 구현도 분리하는 방법도 가능합니다. 어쨌든 헥사고날 아키텍처가 지향하는 원칙은 포기하지 않으시면 좋겠습니다. 외부 시스템 연동이 없다면 required interface는 사실 스프링의 Repository 인터페이스 쓰는 것이면 충분합니다. 그것만 사용하셔도 이미 헥사고날 아키텍처를 하고 계신 거죠. 그런데 DDD는 많이 다릅니다. DDD는 상당히 복잡하고 거대한 철학과 원칙, 패턴의 집합이고 진정한 DDD는 아주 복잡한 도메인과 시스템에서 가치가 드러납니다. 그래서 제 강의는 DDD가 아니라고 굳이 강조를 하는 것이고요. 다만 거기서 소개된 좋은 패턴은 얼마든지 적극 사용할 수 있습니다. 엔티티, 값 객체, 서비스, 리포지토리, 애그리거트 같은 것들이죠. 질문에 답변을 드리자면, 스프링을 쓰신다면 헥사고날 아키텍처로 개발하시면 됩니다.도메인과 관련된 로직을 담은 코드에 기술적인 코드가 혼재되지 않도록 해주시고요. 이미 리포지토리 인터페이스를 쓰면 그 부분은 90%쯤 성취된 것이죠. 반대로 UI(API 등등)쪽 기술이 도메인 로직을 담은 서비스 쪽으로 들어오지만 않게 해주시면 됩니다. ServletRequest, Cookie 같은 것들이죠. 그리고 도메인의 핵심 로직, 가장 중요한 불변식 등은 엔티티 안에 넣어주시면 됩니다. 그러면 도메인 모델 패턴까지도 적용하는 것입니다.답변이 도움이 되었으면 좋겠습니다. 더 궁금하신 게 있으시면 질문해주세요.
- いいね数
- 0
- コメント数
- 2
- 閲覧数
- 89
Q&A
JPA 엔티티로 만들었다고 기술종속이 아닌지에 대한 질문
날카로운 질문을 해주셨네요. 언급하신 제약사항이 JPA로 만든 도메인 엔티티의 상속 가능성, 생성 방식, 불변성 설계에 일부 영향을 미치는 것이 사실입니다. final을 사용하지 못해서 완전히 닫힌 타입을 만들지 못하는 것, 사용 가능성은 극히 낮지만 어쨌든 기술적으로 디폴트 생성자 호출이 가능해지는 것 등은 분명히 JPA 기술의 제약 때문에 도입되는 현상입니다. 만약 언어가 주는 특성을 모두 이용해서 강력한 캡슐화를 진심으로 원한다면 트레이드 오프라고 고민할 수도 있겠죠. 포기해야 할 것들이 있으니까요.그런데 이건 도메인 모델을 코드에도 반영해서 구현하는 도메인 모델 패턴 방식에서 제약이라고는 보지 않습니다. JPA를 사용해도 private 필드를 쓰고, setter를 최소화하고, 도메인 메소드를 이용해서 상태 변경을 사용할 수 있습니다. 도메인 로직을 구현하고 기능을 넣는 것에서 방해되는 것은 없습니다. 상속도 자유롭게 사용할 수 있고요. 그런면에서 JPA가 주는 제약이 도메인 설계를 방해하는 것은 거의 없다고 봅니다. 기술 종속도 마찬가지입니다. 이런 제약을 가지고 만들어진 JPA 엔티티가 JPA 외의 기술로 대체되거나, 아예 데이터 모델을 독립시키고 매핑을 한다고 했을 때 기존의 JPA를 사용하는 도메인 엔티티가 방해를 하나요? 기존 코드를 그대로 두고 기술을 바꿔도 무방합니다. 강의에서 처럼 애노테이션은 도메인 설계의 의미를 담은 주석 역할로 의미있는 것만 남기고, 매핑은 XML로 빼둔다면 더더욱 그렇습니다. JPA를 사용하지 않게 되면서 굳이 기본 생성자가 만들어지는 게 싫다면 Lombok 설정 한 줄 제거하면 됩니다. 그 외에는 어떤 저장 기술로 바뀐다고 하더라도 도메인 로직을 충실하게 구현한 코드가 JPA에서 다른 기술로 대체되었을 때 이를 방해하거나 어렵게 만들 이유는 전혀 없습니다. 그런면에서 저는 기술에 종속되었거나 설계에 영향을 미쳤다고는 생각되지 않습니다. 구현에 약간의 군더더기를 남긴다 정도면 충분할 듯합니다.
- いいね数
- 0
- コメント数
- 3
- 閲覧数
- 101
Q&A
설계 트레이드 오프 링크 접속 안됨
제가 다시 확인해본 강의영상이나 강의자료에는 블로그 글 링크가 바르게 들어간 듯 합니다.혹시 잘못 나온 부분이 어디였는지 다시 확인해주실 수 있을까요.
- いいね数
- 0
- コメント数
- 2
- 閲覧数
- 128
Q&A
설계 트레이드 오프 링크 접속 안됨
안녕하세요. 블로그 글 링크가 잘못 들어간 듯 합니다. 아래 주소로 들어가시면 됩니다.https://eternity-object.tistory.com/43강의 영상의 내용은 수정해서 업데이트 해두겠습니다.
- いいね数
- 0
- コメント数
- 2
- 閲覧数
- 128
Q&A
헥사고날 아키텍처와 DDD를 적용할 때, 화면에 강하게 연관된 조회 데이터를 어떻게 다루는 게 좋은지 궁금합니다.
우리가 도메인 중심의 개발을 한다고 할 때는 보통 변경이 일어나는 상황을 주로 다룹니다. 대부분의 로직은 변경 로직이고, 조회는 보안과 관련이 없다면 도메인 모델의 변경 없이도 새로운 화면이나 클라이언트 요구사항에 따라서 계속 추가될 수 있겠죠.그런데 말씀하신 경우처럼 보다 복잡한 조회 로직이 필요한 경우가 많이 존재합니다. 엔티티 구조로 커버할 수 있는 조회라면 기준이 되는 엔티티와 필요하다면 연관관계에 있는 엔티티까지 함께 페치하고, 이를 응답 데이터로 만드는 것은 웹 어댑터에서 진행해도 됩니다.그런데 리포트성 쿼리라든가, 복잡한 화면이나 테이블에서 필요한 정보 등은 DB 쿼리에 의존하는 경우가 많고, 필요하다면 네이티브 쿼리를 사용해야 합니다.헥사고날 애플리케이션 구조에서 이런 경우 내용에 따라 적절하게 분리된 조회 의도를 담은 Port를 설계하고, 구현 클래스는 필요에 따라 기본적인 조회용 query service를 같이 활용해도 되고 분리해도 좋습니다.QueryDSL은 스프링 데이터를 쓴다면 커스톰 repository 인터페이스를 구현해서 어댑터 쪽에 만들면 되니까, 헥사고날 쪽에서 볼 때는 인터페이스만 보고, 형식만 잘 맞춰주면 될 겁니다. 어떤 경우에는 가져온 데이터를 애플리케이션 내부에서 가공해서 최종 응답을 만들어야 할 수도 있습니다. 여기에도 결국 도메인 조회 로직이 만들어지는 것이겠죠. 조회가 복잡하고 많은 경우엔 엔티티 단위의 리포지토리 외에 추가로 구분된 리포지토리를 두는 것이 좋을 것 같습니다. 복잡한 분석을 다루는 경우 애그리거트/애플리케이션 서비스 경계를 넘어서게 되는데 이럴 때는 그런 조회를 담당하는 리포트 컴포넌트를 따로 추가하는 것도 좋습니다. 기술에 종속되는 코드는 어댑터에 두고, 헥사고날 애플리케이션 내부에는 도메인 정보로 해석되는 로직을 담은 코드를 두고, 의도 단위로 구분된 Port를 통해서 서비스를 제공한다는 헥사고날 아키텍처의 기본 원칙을 따른다면 크게 고민할 부분은 없을 것입니다.
- いいね数
- 0
- コメント数
- 2
- 閲覧数
- 176





