[세미나 리뷰] 우아한 객체지향(1)

Doccimann·2022년 5월 3일
0

세미나 리뷰

목록 보기
1/3
post-thumbnail

본격적으로 글을 작성하기 이전에

저는 현재 팀 박카스에서 YU Market 프로젝트의 백엔드 개발 및 백엔드 시스템 개발 및 운영을 맡고있습니다.

따라서 저는 YU Market의 요구사항에 맞춰서 백엔드 어플리케이션을 설계해야하는데요, 이 과정에서 저는 고민을 많이 하게되었습니다.

어떻게 도메인을 설계하는 것이 유지보수에 유리할까?
어떻게 도메인을 설계해야만 쿼리가 효율적으로 날아갈까?

이러한 등등의 고민을 한 끝에, 저는 이것에 대해서 많이 알고있는 사람들 혹은 영상들을 찾아보기로 결정을 하였습니다. 그 과정에서 우아한 형제들에서 진행중인 우아한 테크세미나에 눈길을 들이게 되었는데요! 그 중에서 이번에는 우아한 테크세미나의 우아한 객체지향 에 대해서 리뷰를 해보고자 합니다.


우선 소프트웨어 설계 시간에 들었을법한, 클래스간 혹은 패키지간의 관계에 대해서 설명을 해볼게요

여러분은 소프트웨어 설계 시간 혹은 다른 수업에서라도 클래스(패키지)간의 관계에 대해서 다뤄봤거나, 혹은 들어본적이 있으실겁니다. 수업이 아니더라도 starUML을 다뤄보신 분이라면 아래의 내용들을 알고계실겁니다.

👉 연관관계 (Associating Relationship)

class A {
	private val b: B
}

위와 같이 A라는 클래스가 B의 인스턴스를 직접 참조하고 있는 경우를 A와 B는 연관관계를 가지고있다 라고 부릅니다.

그리고 제가 설명할 4가지의 관계들 중에서 제일 강한 객체결합의 형태이기도합니다.

👉 의존관계 (Dependent Relationship)

class A {
	private fun getName(b: B): String = b.name
}

위와 같이 A라는 클래스 내부에서 B의 인스턴스를 직접 참조하는 방식이 아니라 B를 파라미터로 받거나, 혹은 리턴타입으로 가지는 등의 간접 참조를 하는 경우를 A는 B와 의존관계를 가진다 라고 부릅니다.

그리고 의존관계의 경우도 꽤 강한 객체결합의 형태를 가지지만, 그래도 Association에 비해서는 약한 결합도를 가집니다.

👉 일반화관계(상속관계) (Generalization Relationship)

class A {

}

class B: A() {
	
}

위와 같이 B가 A를 상속받고있는 관계를 일반화 관계 라고 부릅니다.

이러한 상속관계의 경우에는 개발자가 개발하는 과정에서 명시적으로 신경을 쓰기 때문에 이러한 관계는 크게 신경을 쓰지 않으셔도 괜찮습니다.

👉 실체화관계(Realization Relationship)

interface A {
	fun validate(b: B)
}

class B: A {
	override fun validate(b: B) {
    	/* Write code what you want to do */
    }
}

위와 같이 A라는 인터페이스를 B가 implement하고 있는 관계의 경우를 실체화 관계라고 부릅니다.

👉 패키지 간의 의존관계

import com.example.b

class A {
	private val b: B
}

위와 같이 패키지 소속의 클래스끼리 관계를 가지고있는 경우를 패키지 간에 의존관계가 있다 라고 정의합니다.


의존성 관리에 대한 규칙

이러한 의존성들에는 관리 규칙이 정해져있습니다.

🔨 Bi-Direction(양방향) 관계보다는 Uni-Direction(단방향) 관계를 선택해라

클래스 간에 양방향 관계를 가지게 되는 경우에는 한 쪽이 수정이 되는 경우 양쪽 다 수정을 해야할 경우가 많이 발생하기 때문에 양방향은 최대한 지양하는것이 좋습니다.

그러나 진짜로 필요한 경우에는 양방향을 써야할 수도 있습니다. 위의 규칙은 무조건 지켜져야하는 것은 아닙니다.

🔨 의존성은 다중성이 적은 방향으로 진행되어야한다

예를 들어서 아래와 같은 클래스가 있다고 가정해봅시다.

@Entity
data class Shop(
	@Id
    @GeneratedValue(strategy=GenerationType.IDENTITY)
    @Column(name="shop_id")
	val id: Long, 
    val name: String
)
@Entity
data class Menu(
	@Id
    @GeneratedValue(strategy=GenerationType.IDENTITY)
    @Column(name="menu_id")
	val id: Long, 
    val name: String, 
    val price: Int
}

그리고 Shop과 Menu 사이에는 1:N의 연관관계가 있다고 가정해봅시다.

위의 규칙에 따르면 다중성이 최대한 적어야하기 때문에 Menu에다가 ManyToOne 관계를 붙여주면 될것입니다.

@Entity
data class Menu(
	@Id
    @GeneratedValue(strategy=GenerationType.IDENTITY)
    @Column(name="menu_id")
	val id: Long, 
    val name: String, 
    val price: Int, 
    @ManyToOne
    @JoinColumn(name="shop_id")
    val shop: Shop
}

🔨 불필요한 의존성은 모두 제거하여라

말 그대로, 불필요한 의존성을 제거해야한다는 규칙입니다.

🔨 패키지 간에 Dependency Cycle은 제거하여라

예를 들어서, package A가 package B를 의존하고, package B가 package C에 의존하고, 다시 package C는 package A를 의존하는 관계가 있다고 가정합시다.

그렇게 되면 세개의 패키지 간에 Dependency Cycle이 발생하기 때문에 유지보수에 코스트가 발생하게 될겁니다. 그리고 애초에 저렇게 3개의 패키지가 사이클을 형성할 경우에는 처음부터 하나의 패키지로 묶어야하는 경우일수도 있습니다.

따라서 저런 Dependency Cycle, 혹은 Bi-Direction의 패키지간 싸이클은 모두 제거를 해주는것이 바람직합니다.


Dependency Cycle이 발생한 경우에는 어떻게 해결을 해야할까요?

1️⃣ First Case

아래의 의존성 다이어그램을 확인해봅시다.

아래의 의존성 다이어그램을 패키지 단위의 의존성 다이어그램으로 옮기면 아래와 같습니다.

여기서 잘 보시면, domain layer 내부에서 shop과 order 패키지 사이에서 dependency cycle이 발생하고 있습니다.

이렇게 된 원인은 아래로 정리가 가능합니다.

  • order -> shop (package)
    1) Order entity가 Shop entity와 Association에 놓여있다.
    2) OrderLineItem entity가 Menu entity와 Association에 놓여있다.
  • shop -> order (package)
    1) OptionGroupSpecification entity와 OrderOptionGroup이 Dependency에 놓여있다.
    2) OptionSpecification entity와 OrderOption이 Dependency에 놓여있다.

위의 관계로 인해서 Cycle이 발생했으니, 저희는 어떻게든 이런 cycle을 풀어내야합니다. 우선 첫번째 방법인, 중간 객체를 삽입하여 의존성 cycle을 박살낸다 를 사용해보겠습니다.


🔑 첫번째, 중간객체를 삽입해서 의존성 cycle을 끊어냅니다.

우선 아래의 그림부터 확인해보고 설명을 드리겠습니다.

위의 그림에서는 Specification entity들 사이에다가 중간 객체를 삽입하여서, Dependency를 유발하던 메소드들의 파라미터를 전부 중간객체의 타입으로 전환해버린 모습을 확인할 수 있습니다.

위의 방법을 통해서 Dependency를 order -> shop의 방향으로 Uni-Direction을 가지게 만들 수 있습니다.


2️⃣ Second Case

두번째 케이스를 살펴보겠습니다.

아래의 경우에는 OrderDeliveredService가 DeliveryRepository를 의존하면서 order package와 delivery package 사이에 dependency cycle이 발생하고 있습니다.

여기서는 delivery package -> order package 사이의 의존성을 해결하는 것 보다는 order package -> delivery package 방향의 의존성을 해결하는 것이 쉽습니다. 아래와 같이 하면 됩니다.

🔑 의존성 역전 원리(Dependency Inversion Principle)을 이용합니다.

의존성 역전 원리란, 특정 객체를 추상화 시켜서 다른 위치에다가 추상화된 클래스를 구현시키는 방식으로 의존성을 역전시키는 방법입니다.

이를 이용하면, 아래와 같이 의존성 역전을 시켜서 단뱡향의 의존성을 갖게 만드는 것이 가능합니다.


위의 두가지 사례를 통해서 의존성 cycle을 해결하는 방법을 알아보았습니다.

다음 포스트에서는, 의존성 cycle을 넘어서 Association Relationship이 불러올 수 있는 문제들, 그리고 해결 방법을 다뤄보겠습니다. 감사합니다!


🌲 Reference

우아한 객체지향 세미나

profile
Hi There 🤗! I'm college student majoring Mathematics, and double majoring CSE. I'm just enjoying studying about good architectures of back-end system(applications) and how to operate the servers efficiently! 🔥

0개의 댓글