Clean Architecture Part 3: Principles

TedLim·2021년 7월 28일
0

Single Responsibility Principle

하나의 모듈은 하나의, 오직 하나의 Actor 에 대해서만 책임을 져야 한다. The module here refers to a cohesive set of codes.

중복과 병합의 문제

중복

예를 들어 급여 애플리케이션의 Employee Class 가 있다고 해보자. SRP 를 위반하는 이 클래스는 다음과 같이 서로 성격이 다른 method 를 가지게 된다.

class Employee():
	def __init__(self):
		self.employees = []

	def getWorkingHours(self):
		pass

	def calculatePay(self):
		"""
		finance team needs this api
		"""
		hrs = self.getWorkingHours()
		pay = hrs*10
		return pay

	def reportHours(self):
		"""
		HR team needs this api
		"""
		_hrs = self.getWorkingHours()
		hrs = _hrs * 1.3
		return hrs

	def save(self):
		pass

여기서 문제는 만약 getWorkingHours 함수를 바꾸게 되면 서로 다른 actor (finance and HR team) 에게 영향을 준다는 것이다.

이러한 문제는 서로 다른 actor 가 의존하는 코드를 너무 가까이 배치했기 때문에 발생한다. 해결책은 이를 분리하는 것이다.

병합

위 사례에서 finance, HR 각각의 개발자가 Employee 클래스를 수정하게 되면 conflict 가 발생하게 되고 merge 가 불가피하다.

이 역시 서로 다른 액터가 동일한 소스 파일을 접근하려고 하기 때문에 발생하게 된다.

Open-Closed Principle

소프트웨어 개체는 확장에는 열려 있어야 하고, 변경에는 닫혀 있어야 한다.

즉, 아키텍처가 훌륭하다면 변경대신 코드를 추가하는 것만으로 새로운 기능을 추가할 수 있어야 한다는 말이다.

소스 코드 의존성의 조직화

  • 화살표의 시작 모듈과 끝 모듈은 시작 모듈이 끝 모듈의 함수(데이터)를 사용하고 있지만, 반대로 끝 모듈은 시작 모듈의 아무것도 사용하지 않음을 의미한다.
  • 이중선으로 닫힌 상위 레벨의 컴포넌트는 모두 단방향으로 종속 관계를 이루고 있다. 이들의 화살표는 변경으로부터 보호하려는 컴포넌트를 향하도록 그려진다.
  • Interactor 컴포넌트의 경우 다른 모든 것에서 발생한 변경으로부터 보호되는데, 이는 이 컴포넌트가 가장 높은 수준의 정책을 포함하기 때문이다.
  • 마찬가지로 상위 레벨의 중요도를 담당할 수록 다른 컴포넌트로 부터 보호된다. 즉, 보호의 계층구조가 수준이라는 개념을 바탕으로 생성된다.

방향성 제어

Financial Data Gateway 인터페이스의 경우 별 역할은 없지만 interactor 와 database 간의 의존성을 역전시키기 위해 존재한다.

이게 없었다면 interactor 는 Financial Data Mapper 에 직접 접근하게 되어 interactor 를 최상위로 보호하고자 하는 목적이 깨지게 된다.

Liskov Substitution Principle

S 타입의 객체 o1 각각에 대응하는 T 타입 객체 o2 가 있고, T 타입을 이용해서 정의한 모든프로그램 P에서 o2의 자리에 o1을 치환하더라도 P의 행위가 변하지 않는다면, S는 T의 하위 타입이다.

정사각형 & 직사각형 문제

LSP 를 위반하는 유명한 문제가 있다.

class User():
	def __init__(self):
		self.rect = Rectangle()

	def validate(self):
		if self.H == self.W:
			raise Exception("It is a square!")

class Rectangle():
	def setH():
		pass

	def setW():
		pass

class Square(Rectangle):
	def setSide():
		pass

위 코드에서 SquareRectangle 을 치환할 수 없다.

Interface Segregation Principle

system S → framework F → database D 의 관계대로 의존도가 성립되어 있을 때, F에서는 불필요한 기능인 S와는 전혀 관계없는 기능이 D에 포함된다고 가정하자. 이 기능 때문에 D 내부가 변경된다면 F를 재배포해야 할 수도 있고, 따라서 S까지 재배포해야 할지 모른다.

이럴땐 operation 단위로 D의 기능을 나누어 F가 각각의 Ops 를 의존하도록 분리할 수 있다.

Dependency Inversion Principle

의존성이 추상 abstraction 에 의존하며 구체 concretion 에는 의존하지 않는 시스템을 말한다.

우리가 의존하지 않도록 최대한 피하고자 하는 것은 변동성이 큰 구체적인 요소다.

안정된 추상화

안정된 소프트웨어 아키텍처란 변동성이 큰 구현체에 의존하는 일은 피하고, 안정된 추상 인터페이스를 선호하는 아키텍처 라는 뜻이다.

  1. 변동성이 큰 구체 클래스를 참조하지 말 것
  2. 변동성이 큰 구체 클래스로부터 파생하지 말 것
  3. 구체 함수를 오버라이드 하지 말 것

DIP 위배를 모두 없앨 수는 없다. 하지만 DIP 를 위배하는 클래스들은 적은 수의 구체 컴포넌트 내부로 모을 수 있고, 이를 통해 시스템의 나머지 부분과는 분리할 수 있다.

profile
인공지능과 금융생활에 관심이 많은 개발자입니다 ;)

0개의 댓글