Book
애그리거트 트랜잭션 관리
애그리거트와 트랜잭션 한 주문 애그리거트에 대해 운영자는 뱅송 상태로 변경할 때 사용자는 배송지 주소를 변경할 때를 생각해보자. 한 애그리거트를 두 사용자가 동시에 변경할 때 트랜잭션이 필요하다. 트랜잭션마다 리포지터리는 새로운 애그리거트 객체를 생성하므로 운영자 스레드와 고객 스레드는 같은 주문 애그리거트를 나타내는 다른 객체를 구하게 된다. 때문에 운영자 스레드가 주문 애그리거트 객체를 배송 상태로 변경하더라도 고객 스레드가 사용하는 주문 애그리거트 객체에는 영향을 주지 않는다. 고객 스레드 입장에서 주문 애그리거트 객체는 아직 배송 상태 전이므로 배송지 정보를 변경할 수 있다. 이 상황에서 두 스레드는 가각 트랜잭션을 커밋할 때 수정한 내용을 DB에 반영한다. 이 시점에 배송 상태로 바뀌고 배송지 ..
도메인 서비스
여러 애그리거트가 필요한 기능 한 애그리거트에 넣기 애매한 도메인 기능을 억지로 특정 애그리거트에 구현하면 안된다. 억지로 구현하면 애그리거트는 자신의 책임 범위를 넘어서는 기능을 구현하기 때문에 코드가 길어지고 외부에 대한 의존이 높아지게 되며 코드를 복잡하게 만들어 수정을 어렵게 만드는 요인이 된다. 위의 문제점을 해소하는 가장 쉬운 방법은 도메인 기능을 별도 서비스로 구현하는 것이다. 도메인 서비스 도메인 영역에 위치한 도메인 로직을 표현할 때 도메인 서비스를 사용한다. 계산로직 여러 애그리거트가 필요한 계산 로직이나, 한 애그리거트에 넣기에는 다소 복잡한 계산 로직 외부 시스템 연동이 필요한 도메인 로직 구현하기 위해 타 시스템을 사용해야 하는 도메인 로직
스프링 데이터 JPA를 이용한 조회 기능
CQRS 명령 (command) 모델과 조회 (Query) 도델을 분리하는 패턴이다. 명령 모델은 상태를 변경하는 기능을 구현할 때 사용 도메인 모델은 명령 모델로 주로 사용된다. 조회 모델은 데이터를 조회하는 기능을 구현할 때 사용 정렬, 페이징, 검색 조건 지정과 같은 기능은 주문 목록, 상품 상세와 같은 조회 기능에 사용된다. 예를 들어 회원 가입, 암호 변경, 주문 취소 처럼 상태를 변경하는 기능을 구현할 때 명령 모델을 사용 주문 목록, 주문 상세처럼 데이터를 보여주는 기능을 구현할 때는 조회 모델을 사용한다. 스펙 애그리거트가 특정 조건을 충족하는지를 검사할 때 사용 메모 애그리거트에 포함되어 있는 객체를 모두 불러와 데이터 정제 작업을 하려면 메모리에 저장되는 객체 정보들이 무수히 많아진다...
응용 서비스와 표현 영역
표현 영역과 응용 영역 응용 영역과 표현 영역이 사용자와 도메인을 연결해 주는 매개체 역할을 한다. Controller 사용자와 상호 작용하는 영역은 표현 영역의 역할 표현 영역은 사용자의 요청을 해석한다. 웹 브라우저에서 폼에 입력한 데이터를 전송하면 요청 파라미터를 포함한 HTTP 요청을 표현 영역에 전달한다. Service URL, 요청 파라미터, 쿠키, 헤더 등을 이용해서 사용자가 실행하고 싶은 기능을 판별하고 그기능을 제공하는 응용 서비스를 실행한다. 표현 영역에서 파라미터로 넘어온 데이터로 어떤 서비스를 제공을 하는지는 응용 서비스의 역할이다. 웹 개발을 진행 하면서 많이 보이는 구조다. RestController 의 코드라던지 Controller 코드라던지 이러한 영역이 표현 영역이다. 응용..
리포지터리와 모델 구현
도메인 모델과 리포지터리를 구현할 때 선호하는 기술은 JPA다. 데이터 보관소로 RDBMS를 사용할 때, 객체 기반의 도멘 모델과 관계형 데이터 모델 간의 매핑을 처리하는 기술로 ORM 만한 것이 없다. 4.1 JPA를 이용한 리포지터리 구현 모듈 위치 리포지터리의 인터페이스는 애그리거트와 같이 도메인 영역에 속한다. 리포지터리를 구현한 클래스는 인프라스트럭처 영역에 속한다. 리포지터리 구현 클래스를 인프라스트럭처 영역에 위치시켜 인프라스트럭처에 대한 의존을 낮춰야 한다. 리포지터리 기본 기능 구현 ID로 애그리거트 조회하기 애그리거트 저장하기 조회 시 해당 애그리거트가 존재 하지 않으면 Null 을 반환한다. null을 반환하고 싶지 않으면 Optional을 사용해도 좋다. 애그리거트를 수정한 결과를 ..
애그리거트
3.1 애그리거트란? 연관된 엔티티와 밸류 객체를 개념적으로 하나로 묶은 것이다. 복잡한 도메인을 이해하고 관리하기 쉬운 단위로 만드는 방법이 애그리거트 묶은 단위로 표현하는 것이다. 상위 수준의 개념을 이용해서 전체 모델을 정리하면 전반적인 관계를 이해 하는데 도움이 된다. 반대로 상위 수준에서의 개념을 파악하려면 오랜 시간이 걸린다. 도메인의 객체 모델이 복잡해지면 개별 구성요소 위주로 모델을 이해하게 되고 전반적인 구조나 큰수준에서 도메인 간의 관계를 파악하기 어려워진다. 즉, 코드를 변경하고 확장하는 것이 어려워진다. 애그리거트의 장점 모델의 이해도가 높아진다. 일관성 있게 관리하게 한다. 복잡도가 낮아져 확장하는데 도움을 준다. 3.2 애그리거트 루트 애그리거트에 속한 모든 객체가 일관성을 가지..
아키텍처 개요
2.1 네개의 영역 스프링 MVC 프레임워크가 표현 영역을 위한 기술에 해당한다. 표현 영역은 사용자의 요청을 해석해서 응용 서비스에 전달하고 응용 서비스의 실행 결과를 사용자가 이해할수 있는 형식으로 변환하여 응답한다. 응용 서비스는 로직을 직접 수행하기보다는 도메인 모델에 로직 수행을 위임한다. 핵심 로직은 도메인 모델에서 구현 2.2 계층 구조 아키텍처 계층 구조의 특성은 상위 계층에서 하위 계층으로의 의존만 존재하고 하위 계층은 상위 계층에 의존하지 않는다. 단방향 의존 인프라스트럭처에 의존하면 테스트 어려움과 기능 확장의 어려움이라는 2가지 문제가 발생한다. 이러한 문제점은 DIP로 해결한다. DIP 저수준 모듈이 고수준 모듈에 의존하게 되는 것 2.4 도메인 영역의 주요 구성요소 요소 설명 엔..
도메인 모델 시작하기
패턴이 중요한 것이 아니고 비즈니스 문제에 맞게 코드를 구성하는 것이 중요하다. 1.1 도메인 소프트웨어로 해결하고자 하는 문제 영역 여러 하위 도메인으로 구성된다. 1.2 도메인 전문가와 개발자 간 지식 공유 각 영역에는 전문가가 있다. 이들 전문가는 해당 도메인에 대한 지식과 경험을 바탕으로 본인들이 원하는 기능 개발을 요구한다. 개발자는 이러한 요구사항을 개발과 테스트과정을 거쳐 배포를 하게 된다. 이 과정이 첫 단추와 같다. 요구사항을 올바르게 이해해야 올바른 소프트웨어가 나온다. 그렇다면 요구사항을 올바르게 이해하려면 어떻게 해야 하나? 개발자와 전문가가 직접 대화하는 것이다. 다음으로 개발자도 도메인 지식을 갖춰야 한다. 알아두면 좋은 문장 Garbage in, Garbage out 잘못된 값..
[오브젝트] 결합도를 줄여보자
개발을 하다보면 기능 동작을 구현하고 나만 알기 쉬운 코드(암호문?) 가 된다. 각 객체에 의존성이 높아지며 스파게티 코드가 된다. 이번 우테코 5기 프리코스를 지원하여 미션을 진행하다 많이 부족한 부분들을 정리하고 오브젝트 책을 참고하며 글을 써봅니다. 먼저 소프트웨어의 모듈이 가져야하는 기능은 아래 3가지와 같다. 모듈이 가져야 하는 3가지 기능 제대로 동작해야 한다. 변경이 자유로워야 한다. 가독성이 좋아야 한다. 예제 코드를 보면서 이해해보자. 관람객이 가지고 있는 가방에 초대장이 있으면 티켓을 주고 없으면 티켓 금액을 지불하여 티켓을 구매하는 코드다. public void enter(Audience audience) { if (audience.getBag().hasInvitation()) { T..
[Item 34] int 상수 대신 열거 타입을 사용하라
자바에서 열거 타입을을 제공하기 전에 정수 상수를 한 묶을 선언해서 사용했다. public static final int A = 1; public static final int B = 1; public static final int C = 1; 위와 같은 열거 패턴의 단점 타입 안전을 보장하는 방법이 없으며 표현력도 좋지 않다. A 라는 상수를 사용해야하는 메서드에 B 라는 상수를 사용해도 컴파일러는 해줄 수 있는게 없다. 동등성을 검증할 때도 경고 메시지를 출력하지 않는다. 상수의 값이 바뀌면 클라이언트도 반드시 다시 컴파일 해야한다. 상수의 갯수를 파악하기 어렵다. 문자열을 상수로 만들 경우 하드 코딩하게 만들기 때문에 안정적이지 못하다. 단점을 보완하기 위해 열거타입이 제공된다. public enu..
[Item 33] 타입 안전 이종 컨테이너를 고려하라
class 리터럴의 타입은 Class가 아닌 Class 다. String.class 는 Class Integer.class 는 Class 컴파일타임 타입 정보와 런타임 타입 정보를 알아내기 위해 메서드들이 주고 받는 class 리터럴을 타입 토큰(type token) 이라 한다. 타입 안전 이종 컨테이너 패턴(type safe heterogeneous container pattern) 컨테이너 대신 키를 매개변수화한 다음, 컨테이너에 값을 넣거나 뺄 때 매개변수화한 키를 함계 제공 하는 패턴 이종 컨테이너 패턴 예제 코드 public class Favorites { public void putFavorites(Class type, T instance); public T getFavorites(Class ..
[Item 32] 제네릭과 가변인수를 함께 쓸 때는 신중하라
가변인수 메서드를 호출하면 가변인수를 담기 위해 배열이 자동으로 하나 만들어진다. 내부에서 사용하는 배열이 클라이언트에게 노출되는 문제점이 있다. 제네릭이나 매개변수화 타입을 포함한 가변 인수 메서드를 호출하면 컴파일러가 경고를 보낼것이다. 매개 변수화된 Vararg 유형으로 인한 힙 오염 가능성으로 경고가 발생한다고 확인 할 수 있다. 제네릭과 가변인수를 같이 사용하면 타입 안정성이 깨진다. public static void test1(List...stringLists){ List intList = List.of(42); Object[] objects = stringLists; objects[0] = intList; // 힙오염 발생 String s = stringLists[0].get(0); // C..
[Item 31] 한정적 와일드카드를 사용해 API 유연성을 높이라
제네릭 타입 즉, 매개변수화 타입은 불공변(invariant)이다. 아이템 28에서 살펴봤을 것이다. 하지만 불공변 방식보다 유연한 무언가가 필요하다. 아래와 같은 스택 구조가 있다고 가정해보자. public class Stack { private Object[] objects; private int index; private int top = -1; public Stack(int size) { this.objects = new Object[size]; } public void push(E e) { this.objects[index] = e; index++; } @SuppressWarnings("unchecked") public E pop() { E element = (E) this.objects[ind..
[Item 30] 이왕이면 제네릭 메서드로 만들라
클래스를 제네릭 클래스를 만들수 있고 메서드도 제네릭 메서드로 만들수 있다. 메개변수화 타입을 받는 정적 유틸리티 메서드는 보통 제네릭이다. 예를 들어 Collections 클래스의 sort를 보면 제네릭으로 구성 되어있다. 제네릭 메서드 작성법 로타입 사용 - 수용 불가 → 경고 발생 public static Set union(Set s1, Set s2) { Set result = new HashSet(s1); result.addAll(s2); return result; } 경고를 없애려면 타입 안전하게 만들어야 한다. 타입 매개변수 목록은 메서드의 제한자와 반환 타입 사이에 온다. 제네릭 메서드로 수정 public static Set union(Set s1, Set s2) { Set result = ..
[Item 29] 이왕이면 제네릭 타입으로 만들라
책에는 Stack 클래스를 예로 들었는데 이 글은 stack클래스를 모방한 Cage 클래스로 예를 들겠습니다. public class Cage { private Object[] cages; private int size; private static intDEFAULT_SIZE= 10; public Cage() { this.cages = new Object[DEFAULT_SIZE]; } public void push(Object o) { checkCapacity(); this.cages[size++] = o; } public Object pop() { if (size == 0) { throw new EmptyStackException(); } Object result = cages[--size]; cage..