[우아한테크세미나] 190620 우아한객체지향 by 우아한형제들 개발실장 조영호님 - YouTube
[수정본] 우아한 객체지향 (slideshare.net)
설계의 핵심은 의존성의 관리다!
역할, 책임, 협력은 의존성 관리를 위해 존재한다.
- DDD를 배우자.
- 도메인 모델 기준으로 패키지를 분리하여 객체 참조 범위를 나눈다.
- 애그리거트로 참조경계, 트랜잭션경계, 도메인규칙 경계를 설정한다.
- 다른 애그리거트객체 참조를 레포지토리 패턴으로 구현한다.
- 패키지로 레이어를 나눈다.
설계는 코드 배치 의사결정.
클래스, 패키지(모듈), 프로젝트 어디에 코드를 둘 것인가
변경에 초점을 맞춰야 한다.
의존성은 같이 변경될 수 있는 가능성이다.
(설계를 잘하면 B가 변경되어도 A가 변경되지 않을 수 있다.)
같이 변경되는 코드를 같이 둬야 한다. (colocation)
DDD를 잘쓰자
객체참조조회경계, 도메인규칙, 트랜잭션 경계
Part 1 : 의존성
의존성의 종류는 두 가지가 있음
1. 클래스 의존성
연관관계(Association) : 영구적 객체 참조
의존관계(Dependency) : 일시적 관계 (파라미터 타입, 리턴 타입, 지역변수, 메소드 안 인스턴스 생성)
상속 관계 : B의 구현을 A가 계승
구현이 바뀔 시 영향 받을 가능성 큼
실체화 관계 : 인터페이스의 시그니처가 바뀌었을 때만 영향을 받음
패키지 의존성(가장 중요!!)
한 패키지의 클래스 변화가 다른 패키지의 클래스 변화를 유발함
양방향 의존성을 피하라
두 클래스의 싱크를 맞춰줘야 함. (성능 이슈 및 버그 많이 발생)
원래 하나인 것을 두개로 찢어버림
단방향 의존성으로 바꿔리
다중성이 적은 방향을 선택하라
many쪽에서 one을 참조하라
의존성이 필요없다면 제거하라
패키지 사이의 의존성 사이클을 제거하라. (필수)
의존성이란 함께 바뀔 가능성.
사이클이 생긴다는건 원래 하나라는 뜻. (통합 고려)
설계의 핵심은 어떻게 바뀔지를 추적하는것.
디펜던시 싸이클을 추적하라.
요약 :
- 양방향 의존성을 피해라
- 다중성이 적은 방향을 선택하라
- 의존성이 필요없다면 제거하라
- 패키지 사이의 의존성 사이클을 제거하라
Part 2 : 예제 살펴보기
업무 흐름
도메인 모델 : 가게 & 메뉴
도메인 객체 : 가게 & 메뉴 (런타임)
핵심은 업주가 운영하는 가게의 메뉴에는 여러개의 옵션 그룹이 있을 수 있다.
각 옵션 그룹은 여러개의 옵션을 가질 수 있다.
Domain Concept - 주문
Domain Object - 메뉴
Domain Object - 메뉴 & 주문 (런타임)
주문은 어떤 가게에 대해 주문을 함.
메뉴도 어떤 가게에 속해있는 정보임.
주문 항목과 메뉴는 동기화 시켜야 한다.
문제점
사용자가 장바구니 담고있는데, 사장님이 메뉴 정보를 바꿔버리면?
주문 정보는 로컬 시스템에 저장된다. (핸드폰)
가게 정보는 서버에 저장된다. (서버)
주문 정보와 사장님이 가게 정보와의 동기화가 필요하다.
실제로 배민은 엄청나게 많은 정보를 검증한다 함.
밸리데이션 협력 설계하기
검증 대상 데이터는 서버(가게정보)에 있기 때문에 로컬에서 서버로 정보를 전달해야 함.
옵션그룹(서버)는 주문항목(로컬-stale data)에서 데이터(주문옵션그룹)를 가져와 검증 (진짜정보전문가)
옵션그룹(서버)은 주문옵션그룹의 주문옵션(로컬-stale)을 이용해 옵션(서버)에게 만족여부 확인을 요청한다. (
옵션의 이름, 가격과의 비교 통해)
옵션은 주문옵션(로컬-stale)의 이름/가격을 가져와 자신과 검증 후 검증결과를 리턴한다.
해당 플로우를 계속 바꿔보자
개발자는 변화(시간)을 예측해서 정적인 코드에 담아야함.
관계란 런타임에 해당 타입의 인스턴스들이 협력함을 의미한다.
관계란 방향성이 필요하다.
즉, 의존성이라는 것은 소스와 타겟이 필요
누군가가 누구에게 의존해
객체는 단방향 참조해야 한다.
DB는 FK만 있으면 양방향 참조가 가능함.
메세지를 보내는 방향에 따라 협력(관계)가 형성된다
참조에는 항상 이유가 있어야 함.
런타임 협력 기반
- 관계는 딱 4개임
- 연관관계
- 탐색가능성 (협력 경로의 영구적 유지)
- 협력을 위해 필요한 영구적인 탐색 구조
- 라이프싸이클을 함께 함.
- 대표적인 구현방법
- 객체 필드로 구현
- 대표적인 구현방법
- 의존관계
- 상속
- 실체화
- 연관관계
연관관계는 개념, 객체참조는 구현
나중에 코드를 찢고 분리할 예정
메세지를 결정하고
메소드를 구현한다.
Shop(가게)와 OrderLineItem(주문항목 연결)
주문 시마다 반드시 가게가 영업중인지 판단해야한다.
주문에는 반드시 주문항목이 있어야 한다.
따라서 참조 관계로 구현한다.
주문항목에서는 메뉴항목과 정보가 맞는지 검증 협력을 시작한다.
검증 협력 구현 시작하기.
주문은 주문항목만 알고있다. 따라서 주문항목에게 검증을 요청한다.
this에서 name, orderOptionGroups로 파라미터를 나눠서 두 개를 검증해야 함을 의미적으로 나타냄.
메뉴는 자신의 이름과 주문항목의 이름을 검증함.
메뉴의 옵션그룹에게 주문항목의 주문옵션그룹의 검증을 위임함.
옵션그룹은 옵션에게 주문옵션그룹의 주문옵션이 일치하는지 판단을 위임함.
옵션그룹의 옵션들 각각을 주문옵션들의 주문옵션과 비교함
옵션그룹의 옵션에 검증로직이 있고, 인자로 주문옵션의 옵션을 넘겨줌.
최종 전체 플로우
클라이언트에서 카트 객체를 받아서 카트를 주문이라는 도메인 모델로 변경함.
Part -3 설계 개선하기
설계를 개선해야 한다.
보통 코드 개선이라 하면 코드레벨을 많이 보여주는데,
클린 코드보다는 구조적인 개선이 먼저 중요함. (이름, 메서드 정도밖에 이야기가 안됨)
구조개선이 먼저 되어야 한다. (객체 협력 우선)
이 코드가 해당 패키지, 클래스, 프로젝트에 있는것이 맞는가?
디펜던시가 어떻게 되어있는지 보세요.
의존성을 항상 그림으로 그려보자
클래스 다이어그램, 시퀀스 다이어그램을 여러번 보자.
현재 설계의 문제점
객체 참조로 인한 결합도 상승
패키지 의존성 싸이클도
도메인 개념 기준으로 패키지를 쪼개야 한다
레이어는 개념, 패키지로 구현함
서비스 레이어를 패키지로 나눔.
도메인 레이어도 패키지로 눔
도메인도 패키지가 됨
해결방법 1 : 중간객체로 변환. 의존관계가 단방향이 됨.
의존성 역전 법칙
추상화는 추상 클래스가 인터페이스가 아님.
추상화는 잘 안변하는거임.
딱 shop에서 order에서 가져오고 싶은 것들만 정의하면 잘 안변함.
클래스로 구현되어 있지만, 꼭 필요한 데이터만 있어서 잘 변하지 않는다.
Mapper 객체 사용.
객체 참조로 구현한 연관관계의 문제점
객체 탐색 그래프. (jpa의 Lazy Loading 문제)
근본적인 문제는 객체가 다 연결되어 있음
어디까지 수정해야 할까?
모든 객체가 하나의 트랜잭션으로 묶여버림.
지금까지 살펴본 것 : 조회경계, 도메인규칙, 트랜잭션 경계
라이프싸이클, 변경 빈도가 다름!
객체 참조가 꼭 필요할까?
객체 참조는 결합도가 가장 높은 의존성이다
모든 객체가 접근 가능하다
모든 객체라도 함께 수정 가능하다
연결된 객체는 항상 같이 있어야 한다.
연관관계와 탐색가능성 다시보기
객체 참조는 연관관계의 구현 방법 중 하나임
문제 : 비즈니스 로직은 단방향 관계로 깔끔하게 만들 수 있음 (도메인 모델 기준 분리)
조회로직, 어드민 로직을 넣다 보면 복잡해짐.
어떤 객체들을 묶고 어떤 객체들을 분리할 것인가?
함께 생성되는 객체 함께 묶어라.
도메인 제약사약 공유 항목들을 함께 묶어라.
가능하면 분리하라
장바구니와 장바구니 항목은 묶어야 하나?
장바구니가 생성되는 시점과 장바구니 항목이 생성되는 시점은 다름.
라이프사이클이 다름.
변경의 시점이 다름.
분리한다? (여기까진 가능)
장바구니와 장바구니 항목에 constraint가 존재함.
(같은 업소 것만 넣을 수 있음.)
하나로 묶는다.
객체 묶기
샵과
메뉴와
주문과
배송의
라이프싸이클, 제약사항은 다르다.
경계 안의 객체는 참조로 접근한다.
경계 밖의 객체는 ID를 통해 접근
모든 것을 분리해서도 안되고
모든 것을 연결해도 안됨
비즈니스를 잘 이해해야 함.
객체지향 책에서 이론적으로 연관관계는 참조로 많이 구현함
실무에서는 ID참조를 많이 씀.
객체 간 연관 관계를 ID조회를 통해 구현
각 단위가 트랜잭션 단위, 조회 경계가 되어줌
일단 참조 없는 객체 그룹으로 나누고 나면
몽고디비는 트랜잭션이 없어서 실제로 한번에 써야 함
참조 로직을 없애야 함
컴파일 에러 해결 : 새 객체 OrderValidation을 만들고 검증 로직 한곳으로 모음
OrderValidator : 트랜잭션 스크립트 패턴
객체 지향은 비즈니스 제약사항을 여러 객체를 오가며 파악해야 한다.
전체 검증 로직이 하나의 파일로 모인다.
절차지향으로 바꾸니 응집도가 높아졌다.
객체의 결합도는 높아짐. 하지만 다른 객체들의 응집도가 높아짐.
Validator에 전부 몰아넣으면 한눈에 비즈니스 플로우가 좋다.
테스트가 어렵긴 함.
그래서 validation 객체도 쪼개기도 한다.
사가 오케스트레이션 VS 코레오그래피
OrderDeliveredService 하나에 비즈니스 플로우를 몰아넣음
ID참조, 중간객체 사용, 인터페이스
인터페이스, 추상클래스를 이용한 추상화
운영에서는 도메인 이벤트 기반으로 이야기 많이 함.
정말 잘게 찢어서 느슨하게 하고 싶을 때 사용.
커맨드를 받으면, 이벤트를 발행하고, 이벤트를 받으면 핸들러를 통해 처리함.
DB 커밋되면 이벤트를 발행함.
도메인 이벤트 발행 로직은 직접 만들면 좋지만,
아래처럼 쉽게 구현 가능
커밋하면 해당 이벤트를 발행해줌
이벤트를 추가했더니 싸이클이 돈다.
해결방법 : 패키지 분리
정산을 위한 커미션 계산 로직과 실제 정산 로직을 떼어냄
패키지를 찢으면 도메인 개념이 명확해짐
가게에는 가게를 열고 닫고 메뉴 등록하고 등 가게주인 가게 운영 로직이 들어가야 함.
정산은 완전 다른 도메인임.
정리 : 패키지 의존성을 제거하는 3가지 방법
도메인 이벤트 기준으로 찢으면 패키지 분리가 쉬움
의존성 컨트롤 가능
도메인 이벤트 기반 협력
도메인 인터널 이벤트 (시스템 내부)-> 참조 가능하지만 큐 쓰는게 좋음.
도메인 익스터널 이벤트 (시스템 외부)
비동기적 메세지를 통한 커뮤니케이션
'BackEnd' 카테고리의 다른 글
JS OOP 시리즈 2 : 프록시를 이용한 vue3 반응형 동작 원리 살펴보며 AOP 이해하기. (0) | 2022.01.17 |
---|---|
JS OOP 시리즈 1 : 메타 프로그래밍과 Proxy, Reflect 간단하게 알아보기 (0) | 2022.01.17 |
[KSUG Seminar] Growing Application - 2nd. 애플리케이션 아키텍처와 객체지향 (0) | 2021.12.27 |
7장 객체 분해 [WIP] (0) | 2021.12.19 |
오브젝트 5장. 책임 할당하기 (0) | 2021.12.17 |