하마
7k
2017-11-27 12:36:03 작성 2017-12-08 09:54:39 수정됨
6
4183

DDD(domain driven design) 관점에서 객체지향 vs 함수형 코드 비교






실제 도메인 관점에서의 예를 통해 객체지향과 함수형 코드를 비교해보면, 어떤 분들에게는 좀 더 도움이 될 수 있을 거 같아서. 함수형& 반응형 도메인 모델링이라는 책에 있는 내용중 일부를 짧게 소개해 보려고 합니다.

-장,단점을 논하는 글이 아니며, 더 자세한 내용이 필요하다면 책을 참고하세요. 
-DDD 하면 전 아직도 장거리 직통전화 와 가수 김혜림씨가 먼저 생각나네요 :-) 




개발자는 코드로 말하기 때문에 바로 코드 보겠습니다. 스칼라 코드이긴 하나 매우 간단하기 때문에 자바개발자라면 무난히 이해 하실 겁니다만 약간의 설명은 추가 하겠습니다.

1. 객체지향 


은행등에서 계좌의 돈을 입금하고 출금하는 상황(도메인) 에 대한 코드입니다.

Account(계좌) 라는 클래스 내부에는
balance 라는 멤버변수와   
출금(debit), 입금(credit) 메소드가 있습니다.

여기서 중요하게 볼 장면은 Account 에 돈을 입금하거나, 출금하는 행위(behavior)가 있을 때마다, 
Account 라는 객체의 내부 상태(state) 는 바뀐다는 점인데요.  (이것이 단점이다라고 말하는 것은 아니며, 일반적인 객체지향 모델링의 특징이라는 겁니다) 풀어서 말하면
 내부의 balance 라는 멤버변수의 상태가 변하기 때문에, 객체 자체의 내부상태가 바뀐다고 말합니다.


2. 함수형 -1 

이제 함수형 예제를 보기전에 잠깐 이야기 하나 하자면요.

스타크래프트를 예로 들어보면, 
상대편의 드라군이 던진 공에 나의 마린이 맞으면, 마린의 상태는 변경되게 됩니다.
Marine이라는 객체가 있다고 가정하면 내부에 life 라는 멤버변수가 있으며, 타격을 받으면 life 는 줄어 들겁니다. 즉 내부의 상태가 바뀌게 되는데요.

하지만 함수형 모델링에서는 건강한 마린 -> 피가 20%정도 깍인 마린으로 "대체" 됩니다.
다시 반복해서 예를 들면 슈퍼맨이 조드장군한테 쳐 맞아서 상태가 안좋아지게되면, 그 슈퍼맨이 상태가 안좋아진게 아니라, 상태가 안좋아진 슈퍼맨을 새로 만들어서 대체해 버립니다. 이런 '짓' 에 거부감을 가져서 함수형을 싫어하는 사람들도 많다고 하네요.  대충 감이 잡히시죠?  (뭐가 더 좋고 나쁘다라고 짤라 이야기 할 수 없습니다.)

이제 내부를 변경하는 대신 새로운 녀석(계좌)을 만드는 예제를 보겠습니다. 


여전히 Account 클래스 내부에 debit 와 credit 메소드가 있습니다.
변한것은 내부 멤버변수였던 balance 가 없어졌으며, 
debit 과 credit 가 반환하는 리턴 값에 큰 변화가 생겼습니다.

즉 입,출금 후에는 새로운 계좌(Account) 객체를 만들어서 반환하고 있네요. 네 상태가 바뀐 새로운 마린(계좌) 을 만들어서 반환하는 겁니다. 이전 마린(계좌)는 버리구요.


3. 함수형 -2

마지막으로 살펴 볼 것은 위의 예제를 함수형 도메인 모델링 방식으로 리팩토링 할 것인데요.
함수형 도메인 모델링은 보통 "행위" 와 "상태" 를 책임을 분리 한다고 합니다. 

import java.util.{ Date, Calendar }
import scala.util. { Try, Success, Failure }

def today = Calendar.getInstance.getTime
type Amount = BigDecimal

case class Balance ( amount: Amount = 0) 
//도메인 모델 : 상태를 책임 (멤버변수는 모두 immutable 하다)
case class Account(no : String, name: String, dateOfOpening: Date, balance: Balance = Balance())

------------- 상태와 행위를 디커플링 한다 ----------------------------------------

//도메인 서비스: 행위를 책임 (내부 행위에서 부수작용을 일으키지 않는다) 
trait AccountService {
  def debit(a: Account, amount: Amount): Try[Account] = {
    if (a.balance.amount < amount)
      Failure(new Exception("Insufficient balance in account"))
    else
      Success(a.copy(balance = Balance(a.balance.amount + amount)))
  }

  def credit(a: Account, amount: Amount): Try[Account] =
    Success(a.copy(balance = Balance(a.balance.amount + amount)))
}

object AccountService extends AccountService

import AccountService._

val a = Account("a1", "John", today)
val b = credit(a, 10)


도메인 모델(상태)과 도메인 서비스(행위)로 나뉘어 졌습니다.
상태를 책임지는 immutable 한 Account 라는 클래스와  행위를 책임지는 AccountService. 

val b = credit(a, 1000) 을 보시면 , a 라는 계좌 객체에 10억을 입금했더니, a 라는 계좌객체가 부자가 된 것이 아니라, 부자가 된 b 객체를 리턴해주고 있습니다.

* 어쩌면 상태라는 것은 Discrete 된 것이라고 생각하고, 그것들을 관리하는데 있어서는 이게 더 옳을 수도 있고, continuous 나 seamless 라고 생각하면 말도 안되죠 ^^

trait 는 자바8의 인터페이스라고 생각하시면 됩니다. 즉 has-a 이며 내부 구현이 가능하고, 그 자체로 실질적 객체생성은 못함.

* Try 는 스칼라에서 예외처리를 대신하는데 사용되는 놈으로, Try 내에는 성공객체 or 실패객체 둘 중 하나가 들어갑니다.  따라서 그것을 리턴 받는 쪽은 Try 가 성공인지, 실패인지를 확인해서 처리를 하게됩니다. 확인하는 과정 또한 행사코드 범벅이 되는 문제도 있는데 해결 방안도 공존합니다.


4. 함수형 -3  

마지막으로 설명할 것은 함수형 도메인을 가지고 요리(행위추가)하는 방법에 대한 것인데요. 
자바, 파이썬, 자바스크립트 등에서 사용되는 함수형 스타일에 대한 글을 한번도 읽어보지 않았다면 이해하기 힘드실 거에요 (적어도 map, reduce 가 뭔지 아시는분 대상)

val generateAuditLog: (Account, Amount) => Try[Strng] = //..
val write: String => Unit

debit (source, amount)
.flatMap( b => generateAuditLog(b, amount))
.foreach(write)

debit 은 출금된 이 후의 상태를 가진 새로운 Account 를 돌려준다고 말했잖아요? 슈퍼맨의 예로 기억을 다시 살려보세요. debit (source, amount) 를 호출해서 새로운 Account 를 리턴 받은후에 flatMap 을 적용하게 됩니다.  

아시다시피 Map 이나 flatMap 은 주로 List [A] 와 같이 어떤 타입을 감싸고 있는 타입에 대해 행동을 하는 놈인데요. 위에서는 Try[Account] , 즉 Account이라는 타입을 감싸고 있는 Try에 대해서 행위를 합니다. 

flatMap은 만약 debit 가 Try[Failure] 를 리턴한다면 시퀀스는 진행하지 않고 멈추며,  그렇지 않고 정상적으로  Try[Account] 리턴한다면 그것의 타입을 한단계 벗겨서 , 내부의 Account 를 b => generatedAuditLog 함수리터럴에 적용시킵니다. 물론 generatedAuditLog 는 순수함수입니다.

마지막으로 그 결과를 .foreach 로 돌리는데, 보통 foreach 는 부수효과를 가져옵니다.
val write: String => Unit 를 사용하는것만 봐서도 감이 오는데요. 스칼라(함수형 파라다임)에서는 보통 Unit 을 리턴하는 함수라면 즉 리턴하는게 없는 함수는 대게 부수효과가 있는 함수입니다.

여기서는 foreach(write) 를 통해서 데이터베이스나 파일에 로그를 출력합니다.


여기까지 대략적으로 간단히 살펴보았구요. 나중에 기회가 되면 다른 예제들 및 반응형에 대한 도메인 모델링에 대한 글도 작성하겠습니다.

감사합니다.


하마블로그

6
  • 댓글 6

  • 빠다
    514
    2017-11-27 16:01:40

    항상 잘 읽고 있습니다.

    좋은 글 감사합니다.

  • 초코쪼꼬
    6k
    2017-11-27 17:09:12

    잘 읽을게요. 감사합니다.

  • 하니
    566
    2017-11-29 13:00:07

    감사합니다.

  • 무명소졸
    6k
    2017-11-29 13:23:48

    늘 잘보고 있습니다.

    하마님 블로그 가보니까 회사명이 나와서 검색 해봤는데 검색이 안되더라구요^^;

    이런분(내공심후) 은 어디에 계시나 궁굼해서 찾아 봤습니다.

  • 하마
    7k
    2017-11-29 14:20:23 작성 2017-11-30 13:06:23 수정됨

    저도 함 검색해보니 "아무고토" 없군요 ㅎㅎ;;

    에너지(전력,전기차등) 아이디어 기반 극소수의 인원으로 이루어진 스탓업 회사입니다.
    현재는 대기업POC 및 투자유치중이라 내세울만한게 없네요.  


  • solid
    74
    2017-12-08 09:42:09

    아... DDD 김혜림을 보고 바로 이해한 내가 미워진다...

  • 로그인을 하시면 댓글을 등록할 수 있습니다.