우아한 객체지향

✨ 설계에 대한 핵심은 의존성이다.

설계란 코드를 어떻게 배치할 것 인가에 대한 의사 결정이다.

  • 어떤 클래스에 어떤 코드를 넣을 것인가

  • 어떤 패키지에 어떤 코드를 넣을 것인가

  • 어떤 프로젝트에 어떤 코드를 넣을 것인가

핵심은 변경에 초점을 맞춰야 한다. 같이 변경되는 코드는 같이 넣고, 같이 변경 되지 않는 코드는 따로 넣어야 된다.

변경의 핵심은 의존성(Dependency)이다. 의존성이 있다는 것은, B가 변경할 때 A도 같이 변경될 수 있는 가능성을 갖고있다는 것을 말한다. 즉 변경에 의해서 영향을 받을 수 있는 가능성을 의미한다.

의존성 종류

클래스 의존성의 종류

1 . 연관 관계 (Association) - 영구적으로 갈 수 있는 경로가 있다.

class A {
    private B b;
}

2 . 의존 관계 (Dependency) - 일시적으로 협력을 하는 시점에 관계를 맺고 헤어진다.

  • 파라미터에 타입이 나오거나

  • 리턴 타입에 그 타입이 나오거나

  • 메서드 안에서 그 타입의 인스턴스를 생성할 때

class A {
    public B method(B b) {
        return new B();
    }
}

3 . 상속 관계 (Inheritance) - B의 구현이 바뀌면 영향 받을 수 있다.

class A extends B {
}

4 . 실체화 관계 (Realization) - 인테페이스의 시그니처가 바꼈을 때만 영향을 받는다.

class A implements B {
}

패키지 의존성

  • 패키지에 포함된 클래스 사이의 의존성

좋은 의존성을 관리하기 위한 설계 Guide

1 . 양방향 의존성을 피하라

👎🏻

  • 성능 이슈

  • 싱크 맞추기도 힘듬

// Bi-Direction : 양방향 
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;
    }
}

👍🏻

// Uni-Direction : 단방향 
class A {
    private B b;
    
    public void setA(B b) {
        this.b = b;
    }
}

class B {
}

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

👎🏻

  • 리스크, 컬렉션, 셋 인스턴스 변수로 가지면 다양한 이슈 발생 가능성 존재한다.

    • 성능 이슈

    • 관계를 유지하기 위한 노력

// One-To-Many : 일대다 
class A {
    private Collection<B> bs;
}

class B {
}

👍🏻

// Many-To-One : 다대일
class A {
}

class B {
    private A a;
}

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

👎🏻

// Uni-Directoin : 단방향 
class A {
    private B b ;
}

class B {
}

👍🏻

// None
class A {
}

class B {
}

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

Example

주문플로우

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

Domain Objects - 가게 & 메뉴 (런타임 )

Domain Objects - 주문 (런타임 )

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

<1>

🙀 문제점

메뉴 불일치

  • 장바구니 내용을 서버에 내용을 저장하는 것이 아니라 핸드폰 로컬에 데이터를 저장한다.

  • 장바구니에 들어있는 메뉴 주문과 현재 사장님이 등록한 메뉴 사이에 불일치가 발생할 수 있다.

  • 따라서 주문이 발생할 때 마다 주문이 전송되는 데이터랑 사장님이 등록한 데이터가 일치하는지 확인을 해야 한다.

주문 Validation

<2>

클래스 다이어그램

<3>

  • 개발은 메모리 상에서 돌아가는 동적인 구조(객체들이 메시지를 주고 받고, 객체가 생성되고, 객체가 소멸 되는 실제로 실행되는 시간이 들어가는 구조)를 정적인 코드로 담아야 한다.

  • 관계, 메소드, 로직 등 변할 수 있는 것들을 다 정적으로 만들어 주어야 한다.

정적인 무언가 중 하나가 관계이다. 즉, 의존성을 말한다. 클래스(객체)들 사이에 관계가 있다는 것은 런타임에 그 클래스의 인스턴스가 다른 클래스의 인스턴스와 협력하는 경우를 의미한다.

관계에는 방향성이 필요하다. 관계를 결정하는 것은 객체가 런타임에 어떤 식으로, 어떤 방향으로 협력하는지를 바탕으로 잡아야 한다.

🖇 관계의 방향 = 협력의 방향 = 의존성의 방향

관계의 종류 결정하기

1 . 연관관계 - 협력을 위해 필요한 영구적인 탐색 구조

  • 두 객체 사이의 협력이 필요하고, 두 객체의 관계가 영구적이라면 연관관계를 이용해 탐색 경로 구현

  • 객체 참조(구현)를 이용한 연관관계(개념) 구현

2 . 의존관계 - 협력을 위해 일시적으로 필요한 의존성

  • 파라미터, 리턴타입, 지역변수를 이용해 구현

  • Order에서 OrderLineItem으로 탐색가능하다.

구현 시작하기

어떤 객체가 어떤 메시지를 받는다는 것은 그 객체에 퍼블릭 메서드로 구현이 된다. (메시지 -> 메서드)

객체 협력

<4>

레이어 아키텍처 상에서 도메인 계층에 객체 간의 관계를 설정함. 실제로 비즈니스 영역에 존재한다.

<5>

설계 개선하기

일단 짜고 개선하다 보면 원하는 구조로 가는 경우가 많다.

설계를 진화 시키기 위한 출발점은 코드 작성 후 의존성 관점에서 설계 검토하는 것.

💡 디펜던시를 직접 그려보자.

두 가지 문제

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

  • 패키지 의존성 사이클

패키지 의존성 끊기

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

  • 의존성 역전 원리 - 클래스들이 구체적인거에 의존하지 말고 추상화에 의존하는 것

  • 추상화 -> 꼭 추상 클래스나 인터페이스여야 하나 ?

    • 개발적인 부분에서 추상화란 잘 안변하는 거를 의미한다. 어떤거에 비해서 어떤게 잘 변하지 않다 그럼 추상적인 거다. (클래스로 구현했지 클래스 보다는 추상적인거)

✔️ 연관관계 다시 보기 - 객체 참조로 연관 관계 조회 하면 생기는 문제

  • 성능 문제 - 어디까지 조회할 것인가 ?

  • LazyLoading 이슈

  • 수정 시 도메인 규칙을 함께 적용할 경계는 ?

    • 객체 참조를 해서 수정하고 있는 모든 것들이 하나의 트랜잭션인데, 이러면 롱 트랜잭션으로 물려버린다.

  • 객체가 변경되는 주기가 다르다. 트랙잭션 경합으로 인한 성능이 저하 될 수 있다.

✔️ 객체참조가 꼭 필요할까 ..?

객체 참조는 모든 객체를 다 연결 시켜버린다. 객체 참조는 결합도가 가장 높은 의존성이다.

필요한 경우는 객체 참조를 다 끊어야 한다.

Repository를 통한 탐색(약한 결합도)

  • Respository에는 있는 인터페이스는 연관관계를 구현할 수 있는 operation들이 들어가 줘야 한다. 파라미터로 들어온 타입을 가지고 객체를 찾을 수 있도록 해야한다.

  • 비즈니스 로직 보다는 어드민이나 조회 로직에서 양방향 관계가 덕지덕지 들어가게 된다.

  • 비즈니스 로직 관점에서는 리포지터리에 들어가는 오퍼레이션은 연관관계를 구현할 수 있는 걸로 제한해야 한다.

✔️ 어떤 객체들을 묶고 어떤 객체들을 분리할 것인가 ? (도메인 관점이 중요)

  • 간단한 규칙

    • 함께 생성되고 함께 삭제되는 객체들을 함께 묶어라

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

    • 가능하면 분리하라

  • 💡 경계 안의 객체는 참조를 이용해 접근하고(편하다), 경계 밖의 객체는 ID를 이용해 접근하자(Repository 사용) !

  • 그룹은 트랜잭션/조회/비즈니스 제약의 단위

💥 1st 컴파일 에러 ...

객체를 직접 참조하는 로직 다른 객체로 옮기자

  • Validation Logic(컴파일 에러 나는거)을 OrderValidator로 모은다. OrderService에서는 Order Validator를 Injection 해서 사용한다.

  • 객체지향은 여러 객체를 오가며 로직 파악해야 한다. Validation이 다 찢어져 있다. OrderValidator로 모아두 전체 Validation Logic을 한 눈에 볼 수 있다.

  • 낮은 응집도의 객체가 (validation + 주문 처리) 서로 떨어지며 응집도가 높아진다.

  • 때로는 절차지향이 객체지향보다 좋다.

💥 1nd 컴파일 에러 - 배달 완

OrderDeliveredService 만들기

  • 만들어보면 이게 정말 잘 된 설계인지 의존성을 그려봐야 함

  • 의존성이 사이클을 돈다.

2 . 인터페이스를 사용해서 의존성을 역전시키자

도메인 이벤트(Domain Event)를 이용한 의존성 제거

3 . 패키지를 분리

의존성과 시스템 분리

  • 도메인 단위 모듈화

  • 도메인 이벤트를 통한 협력

  • 도메인 단위 모듈 = 시스템 분리의 기반

  • System Event를 통한 시스템 통합

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

Last updated