https://www.youtube.com/watch?v=dJ5C4qRqAgA

우아한 형제들에서 진행한 우아한 객체지향 세미나에 가고 싶었는데 아쉽게도 가지 못했다. 발표해주시는분이 객체지향의 사실과 오해를 쓰신 조영호분이라서 더 가보고 싶었는데 아쉽다. 책에 내용이 좋아서 동영상으로라도 보고 싶었는데 유튜브에 동영상이 올라와서 보고 정리해본다.

 

개념

- 설계는 코드에 어디에 놓을건지를 정하는 것.

- 의존성 문제의 핵심은 코드 변경시 영향을 주는지이다.

- 의존성 문제는 디커플링이 되어야 한다.

 

관계설명

연관관계 (Association)

A 클래스에 B클래스로 갈수 있는 영구적인 방법이 있는 경우

A → B

class A { 
	private B b;
}

 

의존관계 (Dependency)

A ---> B (파라미터, 리턴타입, 지역변수 등등)

일시적으로 관계를 맺고 사라지는 관계

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

 

상속관계 (Inheritance)

A → B

부모에 구현이 바뀌면 영향을 받을 수 있음.

public class A extends B

 

실체화 관계 (Realization)

인터페이스에 오퍼레이션 시그니처가 변경되었을때만 영향을 받음

A ---> B

class A implements B

 

효율적인 객체 설계법

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

  • 순환참조를 피하라. 오직 단방향 참조를 하라.
  • 다중성이 적은 방향을 선택하라.
  • 1대다 보다 다대1로 설계하라. (아래 예시)
class A { 
	private Collection<B> bs;
}
보다

class B { 
	private A a
}
로 사용하라.

 

2. 제일 좋은건 의존성을 줄여라.

  • 관계가 있다는 것은 파라미터, 또는 인스턴스 변수등에서 전달되는 사이가 있다.
  • 관계에는 방향성이 있어야 한다.
  • 협력의 방향, 의존성의 방향
  • 상속관계랑 실체화 관계의 경우 명확하고 대부분이 연관, 의존 관계가 대부분
  • 데이터 흐름의 의존적일 수 있다.
  • 영구적인지, 일시적인지 여부에 따라서 의존관계 연관관계를 지정
  • 관계는 런타임에 어떤 연관을 가지는지에 따라 달라진다.

 

3.  연관관계 정의

  • 이 객체를 알면 다른 객체를 찾아갈수 있어요가 연관관계의 정의
  • 설계를 할때 의존관계를 손으로 그리고 코드를 작성한다. => 잘못된 패키지 참조가 발생되나?
  • 도메인에 나눠서 패키지를 구성하라.
  • 레이어 구현은 패키지
  • 패키지내부에 어떤 레이어로 나눌건지 패키지 구성
  • 의존성 역전 원칙
  • 패키지 싸이클은 절대 돌면 안된다.

 

대표적인 연관성 문제 해결방안

1. 객체 참조

  • 객체 참조로 List<B> b로 데이터를 가지고 있으면 메모리에 있을때 큰 이슈는 없으나 orm을 통해 데이터를 읽을 때 문제의 소지 발생 가능 (N+1 문제 등등)
  • 객체의 연관성이 너무 얽혀있음.
  • 객체 참조로 많은걸 수정하면 롱 트랜잭션이 생겨서 트랜잭션의 경계가 모호해짐.
  • 객체의 연관성이 높아지면 트랜잭션이 포인트가 커져서 트랜잭션의 문제가 발생
  • 객체 참조는 연관성이 너무 크기 때문에 필요하다면 다 끊어야한다.

  • 모든 객체 참조가 불 필요한 건 아니다. 함께 생성되고 함꼐 삭제되는 객체는 묵어라. 다시 말해 도메인 제약 사항을 공유하는 객체들은 묶어라. (도메인 관점으로 묶어라). 가능하면 분리하라 
  • 아래 같은 경우처럼 같은일은 하는 Order, OrderLineItem, OrderOptionGroup, OrderOption의 연관관계는 유지하고 Shop은 연관관계를 끊고 shopId를 보유하고 그 Id를 이용해서 데이터를 탐색하라.

  • 같은 객체와 연관관계가 있는 객체는 같은 트랜잭션 레벨에서 관리하면 된다. (같은 객체에 정보는 한번에 쿼리로 가져온면 된다.)

 

2. 객체지향보다 절차지향이 더 좋을때도 있다.

만약 연관관계가 있어서 사용하다가 연관관계를 끊으면서 문제가 생기면 그 부분을 별도의 로직으로 분리한다. 예를 들어 Order에서 validation을 밖으로 빼어 OrderValidatiuon을 만들면 Order에서 Shop, Menu등에 대한 연관관계가 필요없어진다. 그리고 Order에 별도의 validation 코드가 있다보면 응집도가 높은 코드들이 아닌 코드들이 같이 있기 때문에 통일성이 부족해진다. 꼭 객체안에 validation을 체크하는 로직이 같이 있을 필요는 없다.

 

3. 문제상황

의존성을 없애기 위해서 Shop객체가 사라지고 shopId가 생기면서 배달완료되고 배달료에 부여하는 코드에서 컴파일 오류가 발생된다.

이 부분을 validation처리 처럼 절차지향으로 변경하거나 도메인 이벤트 퍼블리싱으로 해결할 수 있다.

 

1) 절차지향 방식 사용

OrderDeliveredService를 사용해서 orderId를 받아서 Shop에 비용을 부과함으로써 Order에서 Shop의 연관성을 제거할 수 있다. 

하지만 DorderDeliveredService에 Order, Shop, Delivery를 사용하기 때문에 의존성이 다시 생긴다.

그래서 Interface를 이용해서 의존성을 해결해주면 된다. 

 

2) Domain Event를 이용한 의존성 제거

스프링에서 제공하는 Domain Event를 이용하여 특정 이벤트가 발생했을때 기능을 발생시키도록 하면 의존성없이 해결할 수 있다. 간단하게 AbstractAggregateRoot를 상속받아서 registerEvent를 통해 이벤트를 등록하면 핸들러가 처리한다. 실제로 배달의 민족의 대부분의 서비스가 다음과 같은 방식으로 이루어져있다고 한다.

이벤트가 발생하면 handler에서 이벤트를 받아서 비동기로 데이터를 처리하면 끝.!

 

직접 듣는게 아니라 녹화된 방송으로 들어서 정리하고 이해하면서 들을 수 있어서 더 좋았다. 다음에는 실제로 참석해서 들어보고 싶다. 개발하면서 지켜가면서 개발을 해봐야겠다.

+ Recent posts