우아한 객체지향

객체지향에서 설계에 대해서 굉장히 중요한 것은 의존성이다.

설계라는 것은 -> 코드를 어떻게 배치할 것에 대한 의사결정.
어떤 패키지에 어떤 코드를 넣을거고, 어떤 프로젝트에 어떤 코드를 넣을거고.. 이런 것들.

초점은 변경에 맞춰야한다. 같이 변경되는 코드는 함께두어야하고 그렇지 않으면 따로둬야한다.
이 변경이라는 것이 결국은 의존성.

A ———-> B
A가 B에 의존되고 있다 = B가 변경되면 A도 변경될 가능성이 있다.

의존성은 변경과 관련된 것.

Class 사이의 Dependancy

Package 사이의 Dependancy

두가지로 나뉜다.

클래스 의존성 종류.

연관관계 (Association) -> A에서 B로 영구적으로 갈 수 있는 것

1
2
3
class A {
private B b;
}

의존관계 (Dependency) -> 일시적으로 관계를 맺는 것.

1
2
3
4
5
class A {
public B method(B b) {
return new B();
}
}

상속관계 (Inheritance) -> 구현이 바뀌더라도 영향을 받는다.

1
2
class A extends B {
}

실체화 관계 -> 인터페이스가 구현된것

1
2
class A implements B {
}

설계를 할때 좋은 의존성을 관리하는 규칙

  1. 양방향 의존성을 피하라 (순환참조 관계)
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    /** BAD */
    class A {
    private B b;

    public void setA(B b) {
    this.b = b;
    this.b.setA(this);
    }
    }

    class B {
    private A a;

    public void setA(A a) {
    this. a = a;
    }
    }
1
2
3
4
5
6
7
8
9
10
11
/** GOOD */ 
class A {
private B b;

public void setA(B b) {
this.b = b;
}
}

class B {
}

만약 정말 이렇게 구현된 객체가 있다면 이것은 한 클래스로도 만들 수 있음에도 억지로 찢어 놓은 걸 수도 있다.

  1. 다중성이 적은 방향을 선택하라.
1
2
3
4
5
6
7
/** BAD */
class A {
private Collection<B> bs;
}

class B {
}
1
2
3
4
5
6
7
/** GOOD */
class A {
}

class B {
private A a;
}
  1. 의존성이 필요없다면 제거하라.

패키지 사이의 의존성 사이클을 제거하라.

설계에서 가장 중요한건 변경.
의존성이 사이클이 돈다는 이야기는 -> 원래 하나의 패키지로 볼수도 있음을 고려해야함.

시스템은 엄청 동적인것이지만 이걸 구현하는 코드는 정적인 것.

정적인 이 코드로 동적인 시스템을 구현하기 위해선 시스템이 가진 정적인 특징들을 잘 잡아쟈한다.

첫번째 클래스 다이어그램ㅇ르 그리고 이 클래스 끼리의 관계라고 하는것. 의존성이라고 하는 걸 잘 고려하자.
의존성은 방향성이 존재.

디비의 릴레이션은 외래키 잡으면 방향성이 없음

관계의 방향 -> 협력의 방향 -> 의존성의 방향
시스템상에서 실제로 플로우가 어떻게 흐르느냐에 따라서 그걸 따라서 클래스 다이어그램을 만들고, 관계를 그려야한다. 즉 의존성을 만들어내야 한다.

클래스 다이어그램을 그리라 -> 협력의 방향을 상상해라 -> 의존성을 잡아라.

이 의존성을 표현하는 방법은 4가지 아까 위에서 언급함.

(1) 연관관계 (객체참조)
협력을 위해 필요한 영구적 탐색 구조.
실제적으로는 데이터에 영향을 받지만 우선은 잡는다.

(2) 의존관계 (파라미터, 리턴타입, 지연변수)
협력을 위해 일시적으로 필요한 의존성.

협력을 해야하는 이유, 의존성을 가져야 하는 이유를 항상 생각하라.

연관관계는 개념적인것. 이를 구현하기 위해 객체참조. 이 개념을 구현하기 위한 방법이 여러가지가 있음.
대표적인 방법은 객체참조.

메세지를 받는다 = 메서드를 호출한다.
메세지라는 개념적인 것을 먼저 생각하고 그다음에 메서드라는 구현의 관점을 떠올려라.

** Layered Archutecture **

Presentation Layer

Service Layer

Domain Layer -> 이 계층을 내가 맨날 생각했었는데 한번도 이게 언급된적이 없길래 내가 잘못생각하나 싶었는데. 이게 진짜 중요하네. 와 리얼 명강의다. 꿀정보를 얻었다.
비지니스 로직을 구현하는 레벨이 여기다ㅏ.
Jpa를 쓴다면 이 엔티티라는 녀석이 도메인 레이어애 해당하는 거지. 근데 Sql Mapper를 쓰게 되면 이게 자동으로 매핑이 안되니까.
도메인을 먼저 클래스 다이어그램으로 잡고 시작을 해야해 그거 기준으로 매퍼들을 만들어야해.

Infrastructure Layer

와 서비스쪽 코드보니까 정적 팩토리 메서드로 매핑한다. 와 진짜 내가 쓰던 코딩 스타일이 다 여기나오네.. 진짜 헛으로 고민하고 짠 시간들이 아니였다. ㅠㅠ

설계를 개선하기.

클래스 다이어그램을 그리고, 의존성을 다 그려봐라. 그러면 좀 잘 보인다.

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

패키지의 양방향 의존성 관계.

의존성 역전 원리. -> 클래스들의 추상화들에 의존하게 하라. 인터페이스를 바라보게하라.
인터페이스이거나 추상 클래스여야 한다는 고정관념이 있다.

해결방법 첫번쨰.

(1) 중간 객체를 이용한 의존성 사이클 끊기.

(2) 연관관계를 다시 살펴보기. = 어떤 객체에서 어떤 객체로 탐색할수 있도록 하는 것.
성능 문제 - 어디까지 조회할 것인가.
연관관계가 끝까지 가있기 때문에.
데이터들이 메모리에 올라가있으면 상관 없으나 Persistance Layer 까지 내려가게 되면 조인 문제 등으로 성능이슈가 발생한다.

어디까지 읽어야 하는가 말아야하는가 Lazy Loading을 어디까지 해야하는가.

근본적인 문제는 모두가 연결되어 있어서 그렇다.

객체 참조가 길어지면 = 트랜잭션도 길어진다.

클래스 다이어그램만 고려하고 객체 참조를 하게 되면 -> 인프라적인 이슈들을 생각하지 않게 한다.
너무 이상론이라는 거지.

롱 트랜잭션으로 묶여있는 곳을 냄새를 맡을떄

트랜잭션의 발생 빈도, 주기가 서로 맞는지를 봐야한다.

클래스 다이어그램 기준 객체 참조를 하면서 객체를 만들게 되면. 롱 트랜잭션이 발생하고 트랜잭션 경합으로 인한 성능 저하.

객체 참조의 문제점 -> EVERYTHING IS CONNECTED 모든게 연결되는게 문제점.
모든 객체가 연결되어 있기 때문에 어디라도 접근이 가능할 것 같지만 어디라도 함꼐 수정할 것 같지만 성능저하, 트랜잭션 문제, 이상론이고. 인프라적으로는 최대의 적.

객체 참조는 결합도가 가장 높은 의존성. -> 필요하다면 객체 참조를 끊어야 함.

연관관계 = A를 알면 B를 알 수 있어.
연관관계를 표현할 수 있는 방법 중 하나가 객체 참조. (강한 결합도)

Repository를 통한 탐색(약한 결합도)
연관관계를 A 라는 파라미터를 받으면 B를 얻을 수 있어요 이거를 보여줘야 함.

어디까지 객체 참조를 해야하는가.
본질적으로 결합도가 굉장히 높은 애들. vs
굳이 연결하지 않아도 Repository로 접근해서 해결 가능한 것들.

함께 생성되고 함께 삭제되는 객체들을 함께 묶어라 -> 트랜잭션의 주기가 같아야 하기 떄문에.

도메인 제약사항을 공유하는 객체들을 함께 묶어라.

그러나 가능하면 분리하라. 약한 결합성을 가지게 하라.

A가 B의 객체 참조 대신에 B의 PK를 가지게 해서 repository를 통해서 객체를 가져오도록 구현하자. 이게 보다 더 약한 결합성이다.

ex) 장바구니와 장바구니 안에 들어가는 아이템들은 라이프 사이클이 다르다 (생성되는 시점, 지워지는 시점) 등등
서로 연결되는 데이터가 별로 없다. 이런 경우에는 그냥 약한 결합성을 유지하도록 하는게 좋다.

객체를 묶을 때 라이프 사이클, 도메인 제약사항의 공통점을 찾아보자.

묶인 객체들 내부끼리는 객체 참조로 통신.
경계 밖의 객체는 ID를 이용해 접근.

객체참조라는 개념이 메세지를 주고받는 것에 대해서 객체지향을 설명하기가 이론적으로 좋은데. 이상적이다.
즉 실무적으로 자원을 생각하고, 성능을 고려해야 한다면 이것만 믿을 수 없다.

객체가 묶인 단위를 기준으로
트랜잭션 단위 관리의 기준
조회 경계 단위 관리의 기준.
영속화 의 기준.
이 될수 있다.

몽고DB 같은 NoSQL를 사용하게 되면 이 강한결합도를 가진 객체의 묶음 단위로 컬렉션을 구축하면 된다.

응집도를 다르게 표현하면 같이 변경되야하는 객체들이 같이 있는게 응집도가 높다는 것.

도메인들의 흐름을 표현하는 방법

첫번째 절차지향적인 OrderDeleveredService 코드를 작성하자. -> 로직간의 결합도가 높다.

두번째 도메인 이벤트 중심 퍼블리싱. -> 로직간의 결합도를 느슨하게 하지만 특정 코드가 호출되면 다음 코드가 호출되었으면 좋겠어 이런느낌.

객체 참조는 로직이 아닌 객체 끼리의 결합도였음.

도메인 단위로 패키지를 나누면 의존성 사이클이 엄청나게 생긴다.

그래서 레이어드 기준으로 보통 패키지를 만들어서 관리한다.

근데 도메인을 위에 언급한 강의들을 가지고 리팩토링을 한다면 도메인 단위로 패키지를 구분할 수 있다.
도메인 단위로 패키지를 가지게 되면 시스템을 분리하기가 쉬워진다. 그말은 즉은 보다 더 훨씬 모듈화가 되어있다는 의미.

도메인 단위로 시스템을 분리할 수가 있다. 이게 진짜 개쩌는 팁이네.

도메인 관리를 잘해서 최종적으로는 도메인 단위로 객체를 관리.

의존성을 따라서 시스템을 진화시켜라.

Share