본문 바로가기

BackEnd

[우아한테크세미나] 190620 우아한객체지향 by 우아한형제들 개발실장 조영호님 요약

반응형

[우아한테크세미나] 190620 우아한객체지향 by 우아한형제들 개발실장 조영호님 - YouTube

[수정본] 우아한 객체지향 (slideshare.net)

 

[수정본] 우아한 객체지향

2019년 6월 20일 우아한 테크 세미나 "우아한 객체지향" 발표자료 수정본

www.slideshare.net

설계의 핵심은 의존성의 관리다!

역할, 책임, 협력은 의존성 관리를 위해 존재한다.

  • DDD를 배우자.
    • 도메인 모델 기준으로 패키지를 분리하여 객체 참조 범위를 나눈다.
    • 애그리거트로 참조경계, 트랜잭션경계, 도메인규칙 경계를 설정한다.
    • 다른 애그리거트객체 참조를 레포지토리 패턴으로 구현한다.
    • 패키지로 레이어를 나눈다.

설계는 코드 배치 의사결정.

클래스, 패키지(모듈), 프로젝트 어디에 코드를 둘 것인가

변경에 초점을 맞춰야 한다.

의존성은 같이 변경될 수 있는 가능성이다.

(설계를 잘하면 B가 변경되어도 A가 변경되지 않을 수 있다.)

같이 변경되는 코드를 같이 둬야 한다. (colocation)

DDD를 잘쓰자

객체참조조회경계, 도메인규칙, 트랜잭션 경계

의존성 : B가 변경될 때 A도 같이 바뀔 가능성이 있다면 A는 B에 의존한다.

Part 1 : 의존성


의존성의 종류는 두 가지가 있음

1. 클래스 의존성

연관관계(Association) : 영구적 객체 참조

A에서 B로 이동할 수 있어요. 객체 참조 (영구)

의존관계(Dependency) : 일시적 관계 (파라미터 타입, 리턴 타입, 지역변수, 메소드 안 인스턴스 생성)

의존 관계는 파라미터나 리턴 타입에 해당 타입이 나올 경우(인스턴스 생성) (일시적 관계)

상속 관계 : B의 구현을 A가 계승

구현이 바뀔 시 영향 받을 가능성 큼

구현 변경에 영향을 받음

실체화 관계 :  인터페이스의 시그니처가 바뀌었을 때만 영향을 받음

구현 변경에는 자유롭지만 시그니처 변경에 영향 받음.

패키지 의존성(가장 중요!!)

한 패키지의 클래스 변화가 다른 패키지의 클래스 변화를 유발함

패키지 의존성

양방향 의존성을 피하라

두 클래스의 싱크를 맞춰줘야 함. (성능 이슈 및 버그 많이 발생)

원래 하나인 것을 두개로 찢어버림

단방향 의존성으로 바꿔리

양방향 의존성은 하나의 클래스인걸 어거지로 찢어두었기에 존재

다중성이 적은 방향을 선택하라

many쪽에서 one을 참조하라

많은 쪽이 컬랙션으로 갖는 것보단, 적은 쪽이 많은 쪽을 참조하도록 한다.

의존성이 필요없다면 제거하라

가장 좋은 것은 의존성이 없는 것

패키지 사이의 의존성 사이클을 제거하라. (필수)

의존성이란 함께 바뀔 가능성.

사이클이 생긴다는건 원래 하나라는 뜻. (통합 고려)

 

설계의 핵심은 어떻게 바뀔지를 추적하는것.

디펜던시 싸이클을 추적하라.

요약 : 

  • 양방향 의존성을 피해라
  • 다중성이 적은 방향을 선택하라
  • 의존성이 필요없다면 제거하라
  • 패키지 사이의 의존성 사이클을 제거하라

Part 2 : 예제 살펴보기


업무 흐름

가게선텍 후 메뉴선택 후 장바구니담기 후 주문완료

도메인 모델 : 가게 & 메뉴

정보 전문가를 찾아 책임을 할당하라

도메인 객체 : 가게 & 메뉴 (런타임)

핵심은 업주가 운영하는 가게의 메뉴에는 여러개의 옵션 그룹이 있을 수 있다.

각 옵션 그룹은 여러개의 옵션을 가질 수 있다.

실제 객체를 펼쳐좀

 Domain Concept - 주문

Domain Object - 메뉴

실제 객체를 펼쳐보자

Domain Object - 메뉴 & 주문 (런타임)

주문은 어떤 가게에 대해 주문을 함.

메뉴도 어떤 가게에 속해있는 정보임.

주문 항목과 메뉴는 동기화 시켜야 한다.

주문.주문()으로 협력이 시작된다.

문제점 

사용자가 장바구니 담고있는데, 사장님이 메뉴 정보를 바꿔버리면?

주문 정보는 로컬 시스템에 저장된다. (핸드폰)

가게 정보는 서버에 저장된다. (서버)

주문 정보와 사장님이 가게 정보와의 동기화가 필요하다.

메뉴와 옵션을 선택하고 장바구니에 담음
사장님이 메뉴 정보를 바꿔버림
서버와 로컬 정보를 계속 검증해야 함.

실제로 배민은 엄청나게 많은 정보를 검증한다 함.

이름을 검증하는 이유 - 짜장면과 짬봉은 굉장히 다름

밸리데이션 협력 설계하기

주문 객체에서부터 협력이 시작
가게가 판단할 정보 확인

검증 대상 데이터는 서버(가게정보)에 있기 때문에 로컬에서 서버로 정보를 전달해야 함.

주문은 주문항목에 메뉴와 이름 일치 여부 검중 요청. 주문항목은 메뉴에 자신의 정보를 전달해 검증 요청

옵션그룹(서버)는 주문항목(로컬-stale data)에서 데이터(주문옵션그룹)를 가져와 검증 (진짜정보전문가)

주문옵션그룹은 이름을 가져와 만족여부 판단.

옵션그룹(서버)은 주문옵션그룹의 주문옵션(로컬-stale)을 이용해 옵션(서버)에게 만족여부 확인을 요청한다. (

옵션의 이름, 가격과의 비교 통해)

옵션은 주문옵션(로컬-stale)의 이름/가격을 가져와 자신과 검증 후 검증결과를 리턴한다.

정보 전문가에게 물어라. 주문옵션그룹에서 주문옵션은 참조를 의미.

해당 플로우를 계속 바꿔보자

개발자는 변화(시간)을 예측해서 정적인 코드에 담아야함.

관계란 런타임에 해당 타입의 인스턴스들이 협력함을 의미한다.

관계란 방향성이 필요하다.

즉, 의존성이라는 것은 소스와 타겟이 필요

누군가가 누구에게 의존해

객체는 단방향 참조해야 한다.

DB는 FK만 있으면 양방향 참조가 가능함.

관계란 런타임에 해당 타입의 인스턴스들이 협력함을 의미한다.
관계에는 방향성이 필요하다.

메세지를 보내는 방향에 따라 협력(관계)가 형성된다

참조에는 항상 이유가 있어야 함.

런타임 협력 기반

  • 관계는 딱 4개임
    • 연관관계
      • 탐색가능성 (협력 경로의 영구적 유지)
      • 협력을 위해 필요한 영구적인 탐색 구조
      • 라이프싸이클을 함께 함.
        • 대표적인 구현방법
          • 객체 필드로 구현
    • 의존관계
    • 상속 
    • 실체화

런타임에 객체들이 어떤 방향으로 협력(메세지를 보내는 방향)
객체참조
파라미터, 리턴타입, 지역변수
연관관계 = 탐색가능성

연관관계는 개념, 객체참조는 구현

일반적인 연관관계 구현은 객체 참조.
(public 메소드) 메세지가 필요해서 (private 메소드) 메소드를 만든다. place가 필요해서 validate와 ordered가 존재한다.

나중에 코드를 찢고 분리할 예정

메세지를 결정하고

메소드를 구현한다.

메세지를 생각하고 메소드를 구현하라!!
검증과 주문이 필요하다.

Shop(가게)와 OrderLineItem(주문항목 연결)

주문 시마다 반드시 가게가 영업중인지 판단해야한다.

주문에는 반드시 주문항목이 있어야 한다.

따라서 참조 관계로 구현한다.

참조로 구현한 이유 : 객체 사이에 경로는 영구적인 관계. 주문은 항상 주문항목이 있고, 가게가 필요하다.
생명주기를 같이하는 관계 (반드시 필요)를 참조로 구현하였다.
사장님이 중간에 바꿨는지를 검증하기 위한 로직을 구현한다.
1.Shop의 구현. 가게 오픈 중인지, 최소주문금액 맞는지 확인
OrderLineItem쪽을 보자

주문항목에서는 메뉴항목과 정보가 맞는지 검증 협력을 시작한다.

검증 협력 구현 시작하기.

주문은 주문항목만 알고있다. 따라서 주문항목에게 검증을 요청한다.

주문항목과 메뉴가 일치하는지 검증 - tihs를 쪼개서 보냄. 두가지를 검증하라는 의미가 전달되는 듯 함.

this에서 name, orderOptionGroups로 파라미터를 나눠서 두 개를 검증해야 함을 의미적으로 나타냄.

oli가 아니라 groups를 넘김.

메뉴는 자신의 이름과 주문항목의 이름을 검증함.

메뉴의 옵션그룹에게 주문항목의 주문옵션그룹의 검증을 위임함.

메뉴는 전달된 주문의 이름과, 주문의 주문옵션그룹을 검증. 주문옵션그룹은 메뉴의 옵션그룹에게 검증 요청
메뉴의 옵션그룹에게 주문항목의 주문옵션그룹의 검증을 위임함.
메뉴의 옵션그룹에게 주문항목의 주문옵션그룹의 검증을 위임함.

옵션그룹은 옵션에게 주문옵션그룹의 주문옵션이 일치하는지 판단을 위임함.

옵션그룹의 옵션들 각각을 주문옵션들의 주문옵션과 비교함

옵션그룹의 옵션에 검증로직이 있고, 인자로 주문옵션의 옵션을 넘겨줌.

최종 전체 플로우

도메인 레이어를 지금까지 도메인 모델 패턴으로 구현한 것임

클라이언트에서 카트 객체를 받아서 카트를 주문이라는 도메인 모델로 변경함.

카트를 오더로 매핑하여 로직 구현

Part -3 설계 개선하기


설계를 개선해야 한다.
보통 코드 개선이라 하면 코드레벨을 많이 보여주는데,

클린 코드보다는 구조적인 개선이 먼저 중요함. (이름, 메서드 정도밖에 이야기가 안됨)

구조개선이 먼저 되어야 한다. (객체 협력 우선)

이 코드가 해당 패키지, 클래스, 프로젝트에 있는것이 맞는가?

디펜던시가 어떻게 되어있는지 보세요.

의존성을 항상 그림으로 그려보자

클래스 다이어그램, 시퀀스 다이어그램을 여러번 보자.

변수 이름, 메소드 사이즈가 문제가 아니라 협력, 메서드, 데이터 위치에 대한 이야기를 해야 함

현재 설계의 문제점

객체 참조로 인한 결합도 상승

패키지 의존성 싸이클도

도메인 개념 기준으로 패키지를 쪼개야 한다

앞에 본 두 가지 문제점

레이어는 개념, 패키지로 구현함

서비스 레이어를 패키지로 나눔.

도메인 레이어도 패키지로 눔

도메인도 패키지가 됨

자바는 패키지를 레이어로 구현함. 레이어는 개념.
Order와 Shop 사이에 사이클이 돌고 있다. 
패키지 간 양방향 연관관계. 양쪽 패키지를 같이 고쳐야 함.

해결방법 1 : 중간객체로 변환. 의존관계가 단방향이 됨.

의존성 역전 법칙

추상화는 추상 클래스가 인터페이스가 아님.

추상화는 잘 안변하는거임.

딱 shop에서 order에서 가져오고 싶은 것들만 정의하면 잘 안변함.

클래스로 구현되어 있지만, 꼭 필요한 데이터만 있어서 잘 변하지 않는다.
Mapper 객체 사용.

의존성 역전의 법칙을 적용하여 중간객체 사용
연관관계란 탐색경로를 제공하는 것이다.

객체 참조로 구현한 연관관계의 문제점

객체 탐색 그래프. (jpa의 Lazy Loading 문제)

근본적인 문제는 객체가 다 연결되어 있음

어디까지 읽어야 하는가

어디까지 수정해야 할까?

모든 객체가 하나의 트랜잭션으로 묶여버림.

지금까지 살펴본 것 : 조회경계, 도메인규칙, 트랜잭션 경계

이 세 가지는 하나의 트랜잭션으로 묶여있음
배달 완료 트랜잭션
세가지 도메인이 트랜잭션으로 묶여버림

라이프싸이클, 변경 빈도가 다름!

어드민 부하가 트랜잭션 경합을 발생시킴!
ㅠㅠ

객체 참조가 꼭 필요할까?

객체 참조가 꼭 필요할까?
객체 참조의 문제 : 모든것을 연결시켜버림
모든 객체가 연결돼 있기 떄문에
어떤 객체라도 참조로 접근 가능

객체 참조는 결합도가 가장 높은 의존성이다

모든 객체가 접근 가능하다

모든 객체라도 함께 수정 가능하다

연결된 객체는 항상 같이 있어야 한다.

연관관계와 탐색가능성 다시보기

객체 참조는 연관관계의 구현 방법 중 하나임

객체 참조는 연관관계의 구현 방법 중 하나임
리포지토리에서 연관관계 리포지토리를 구현해야 함

문제 : 비즈니스 로직은 단방향 관계로 깔끔하게 만들 수 있음 (도메인 모델 기준 분리)

조회로직, 어드민 로직을 넣다 보면 복잡해짐.

어떤 객체들을 묶고 어떤 객체들을 분리할 것인가?

함께 생성되는 객체 함께 묶어라.

도메인 제약사약 공유 항목들을 함께 묶어라.

가능하면 분리하라

가능하면 분리하라

장바구니와 장바구니 항목은 묶어야 하나?

장바구니가 생성되는 시점과 장바구니 항목이 생성되는 시점은 다름.

라이프사이클이 다름.

변경의 시점이 다름.

분리한다? (여기까진 가능)

장바구니와 장바구니 항목에 constraint가 존재함.

(같은 업소 것만 넣을 수 있음.)

하나로 묶는다.

 

객체 묶기

샵과

메뉴와

주문과

배송의

라이프싸이클, 제약사항은 다르다.

경계 안의 객체는 참조로 접근한다.

경계 안의 객체는 참조를 이용해 접근한다.

경계 밖의 객체는 ID를 통해 접근

모든 것을 분리해서도 안되고

모든 것을 연결해도 안됨

비즈니스를 잘 이해해야 함.

객체지향 책에서 이론적으로 연관관계는 참조로 많이 구현함

실무에서는 ID참조를 많이 씀.

객체 간 연관 관계를 ID조회를 통해 구현

각 단위가 트랜잭션 단위, 조회 경계가 되어줌

일단 참조 없는 객체 그룹으로 나누고 나면

다른 기술을 사용할 수 있음

몽고디비는 트랜잭션이 없어서 실제로 한번에 써야 함

조인을 하든 단순 쿼리로 가져오든 한방에 처리 가능
객체 참조를 ID조회로 바꾸니 오류가 남

 

참조 로직을 없애야 함

컴파일 에러 해결 : 새 객체 OrderValidation을 만들고 검증 로직 한곳으로 모음

OrderValidator : 트랜잭션 스크립트 패턴

객체 지향은 비즈니스 제약사항을 여러 객체를 오가며 파악해야 한다.

전체 검증 로직이 하나의 파일로 모인다.

절차지향으로 바꾸니 응집도가 높아졌다.

객체의 결합도는 높아짐. 하지만 다른 객체들의 응집도가 높아짐.

Validator에 전부 몰아넣으면 한눈에 비즈니스 플로우가 좋다.

테스트가 어렵긴 함.

그래서  validation 객체도 쪼개기도 한다.

 

사가 오케스트레이션 VS 코레오그래피

 

OrderDeliveredService 하나에 비즈니스 플로우를 몰아넣음

비즈니스 플로우가 한눈에 보임
의존성을 주입함.

ID참조, 중간객체 사용, 인터페이스

인터페이스, 추상클래스를 이용한 추상화

운영에서는 도메인 이벤트 기반으로 이야기 많이 함.

정말 잘게 찢어서 느슨하게 하고 싶을 때 사용.

커맨드를 받으면, 이벤트를 발행하고, 이벤트를 받으면 핸들러를 통해 처리함.

DB 커밋되면 이벤트를 발행함.

도메인 이벤트 발행 로직은 직접 만들면 좋지만,

아래처럼 쉽게 구현 가능

커밋하면 해당 이벤트를 발행해줌

이벤트를 추가했더니 싸이클이 돈다.

해결방법 : 패키지 분리

정산을 위한 커미션 계산 로직과 실제 정산 로직을 떼어냄

패키지를 찢으면 도메인 개념이 명확해짐

가게에는 가게를 열고 닫고 메뉴 등록하고 등 가게주인 가게 운영 로직이 들어가야 함.

정산은 완전 다른 도메인임.

 

 

정리 : 패키지 의존성을 제거하는 3가지 방법

도메인 이벤트 기준으로 찢으면 패키지 분리가 쉬움

의존성 컨트롤 가능

도메인 이벤트 기반 협력

도메인 인터널 이벤트 (시스템 내부)-> 참조 가능하지만 큐 쓰는게 좋음.

도메인 익스터널 이벤트 (시스템 외부)

비동기적 메세지를 통한 커뮤니케이션

반응형