2015년 5월 31일 일요일

수학을 포기한 직업 프로그래머가 머신러닝 학습을 시작하기위한 학습법 소개

오늘은 일본의 유명 개발자용 지식공유 서비스인 Qiita에서 큰 인기를 끌고 있는 “수학을 포기한 직업 프로그래머가 기계학습을 시작하기위한 최단경로数学を避けてきた社会人プログラマが機械学習の勉強を始める際の最短経路“를 번역하여 소개해 보고자 합니다. 번역을 흔쾨히 허락해 주신 단노 류이치だんの りゅういち씨에게 감사의 말씀을 드립니다.
전체적으로는 선형대수에 대해서 쉽게 배우는 방법과 무엇 보다 머신러닝의 필수 과목으로 꼽히는 Andrew Ng교수의 머신러닝 강의에 대한 공략법/내용설명이 주를 이룹니다.
빅데이터나 머신러닝에 대해서 한번 공부는 해 보고 싶었지만 수학에서 좌절하여 문턱을 서성거리는 프로그래머들이 꼭 한번 읽어보면 좋은 내용이라 생각됩니다.



요즘 항간에서는 딥 러닝 같은 단어가 여기저기서 들려는 오는 바람에, 나도 머신러닝인지 뭔지 한번 해 볼까 하고 생각해 오다 용기를 내어 두꺼운 알록달록한 책을 사긴 했는데 좀체 손이 가질 않고 나오는 수식만 봐도 머리가 아파오는분. 그리하여, 그래 어차피 나따위가 머신러닝같은거 할 수 있을리가 없어 라는 한숨 섞인 자조속에 눈이 저절로 감겨오는 분 이라면 잠깐 시간을 내어 이 글을 끝까지 읽어 보시기 바랍니다.

대상

  • 공부에 많은 시간을 투자하기 어려운 직업 프로그래머
  • 슬슬 윗사람이나 고객 에게서 “이런건 머신러닝 이용하면 간단하지 않어?”라는 말을 듣게될것 같은 분
  • 이과에서 수학을 공부하긴 했지만, 미분이라던지 행렬같은거 누가 물어보면 난처해 지는분

이 글에서 다루게 될 것

  • 수학의 기본지식에 익숙해지기 위한, 수학이 처음부터 나오지 않는 프로그래머를 위한 수학입문서의 소개
  • 머신러닝의 초보자에게 알맞은 머신러닝애 대한 온라인강좌(MOOC) 소개

환경

  • 윈도우나 맥,리눅스에서 사용 가능한 MATLAB/Ocatave라는 툴을 사용합니다.
  • 종이와 팬을 준비해두면 이해를 돕는데 편리합니다.

용어와 약어소개

  • PRML = 웹에서 머신러닝으로 검색하면 반드시 등장하는 약어입니다. Pattern Recognition and Machine Learning의 약자로 머신러닝에서는 바이블이라 할 수 있는 책 입니다. 이 글의 서문에서 소개한 알록달록한 책이 바로 이 책입니다.
  • MOOC/MOOCs = Massie Open Online Course(s)의 약자. 인터넷상에서 무료로 수강 가능한 오픈 강좌를 말합니다. 이번에 여러모로 많은 도움을 받고 있습니다.

초보자의 머신러닝학습 흐름

“전혀 머신러닝은 공부한적이 없고, 책을 좀 읽을라치면 수학공식이 나오는 바람에 어디서부터 시작해야 할 지 모르겠습니다” 라는 프로그래머에게 제 자신의 실제 경험을 바탕으로 권해드리는 방법은 다음과 같습니다.
  1. 행렬이라던지 백터를 모른다면 동영상을 봐도 중간에 무슨 소리인지 알 수가 없습니다. 따라서 프로그래머를 위한 수학책을 먼저 읽을 필요가 있습니다.
  2. Coursera라는 온라인 강좌의 회원이 되고, 스텐퍼드 대학의 Andrew NG(앤드류 응)교수의 Machine Learning강의(무료)를 듣습니다.
그럼 이제부터 자세히 살펴보겠습니다.

프로그래머를 위한 수학 책

프로그래머 여러분들은 게이밍라던지 소프트웨어라던지 설명서를 읽기 보다는 어떻게든 일단 프로그램을 짜서 움직이는것을 보면서 생각하자는 식의 사고 회로가 형성되어 있다고 생각합니다.
하지만 이 머신러닝에 대한 공부라는 것은 수학적인 기초가 없는 상태에서 진행하려고 하는것은 게임으로 비교하자면 변변한 무기도 없이 어려운 던전에 들어가는것과 마찬가지라서 금방 벽에 부딧히게 됩니다.
머신러닝에 대한 책은 미분 적분과 선형대수를 알고있다는것을 전제하고 있기 때문에, 갑자기 모르는 수식이 튀어나오는것은 바로 그 부분에 대한 지식이 없는것이 원인입니다.
위에 설명한 코세라의 머신러닝 과정은 그러한 수학 지식이 없어도 들을 수 있게 배려하고 있으며, 중간중간 해설도 해 주는 프로그래머 친화적인 교육 코스입니다.
그래도 최소한 행렬과 백터의 취급에 익숙하지 않으면 중간중간 튀어나오는 행렬조작에 대한 내용들을 이해 못하고 이게 뭔가 라는 상태가 되기 쉽습니다. 그래서 사전에 준비운동 정도로 익혀 두는것이 좋습니다.
프로그래머를 대상으로 추천할만 선형 대수학 강의/책은 여기 입니다.(역자주:원문에서는 일본어로 된 서적:프로그래밍을 위한 선형대수학(히라 카즈유키저)을 소개하고 있어 한국에서 접할 수 있는 선형대수 관련 강의/책으로 대체하여 소개해 봅니다)
  • 칸아카데미 선형대수 강의(한글자막)
    뭐 설명이 필요 없습니다. 마치 어딘가의 광고에 나오는 문구 처럼 씹을 필요도 없이 보기만 하면 머리속에 쏙쏙 들어오는 명 강의입니다.
  • 코딩 더 메트릭스
    파이선을 이용행 여러가지 선영대수의 문제를 풀는 법에 대하여 설명하고 있습니다. 머신러닝에 있어서 많은 예제들이 파이선으로 작성되어 있으므로 파이선이 익숙치 않다면 이번 기회에 다뤄보는것도 좋을듯 합니다. Coursera에서 하는 동명의 강의도 같은 내용을 다루고 있으며 프로그래머를 위한 선형대수를 자세히 다루고 있으므로 추천할만 합니다.
개인적으로는 바로 다음에 소개할 머신러닝 과정을 이해하는데 필요한 최소의 지식이 행렬의 곱이라고 생각합니다. 중요한것은 머리속에서 어떻게 이미지를 그려나가야 하는지를 알아야 하는것 같습니다.
저는 행렬 수식의 어디에 어떻게 주목하지 않으면 안되는 것인지 전혀 이해할 수 없었습니다만 행렬의 진정한 가치는 사이즈의 변환에 있다는것을 알게되었습니다. ( 역자주 : 칸아카데미의 Scaling vector에 나오는 예제들을 충분히 다뤄보시기 바랍니다)

머신러닝 강의를 수강한다

머신러닝 초보자에게 추천하는 것이 지금부터 소개하는 온라인 강좌입니다. 등록 방법은 여기에서 설명하지 않겠습니다만, 여렵지 않게 계정을 만들고 수강신청을 할 수 있습니다.
이 강의를 추천하는 이유는 다음과 같습니다.
  • 공짜다
  • 머신러닝 에서 초심자에게 필요한 지식을 개념을 통해 설명해 줍니다.
  • 어렵지 않은 수준의 영어로 강의가 진행됩니다. (역자주: 2015년 5월 현재 이 강의의 모든 동영상은 일본어 자막 지원하지만 한국어 자막은 소개부분의 일부분만 지원되고 있습니다)
  • 영상 강의 뿐만 아니라 시험도 봅니다. 실제로 프로그램 결과를 제출해야만 합니다. 그래서 자신이 이해하고 있는지 여부를 알기 쉽습니다.
  • 시험은 여러 번 제출을 할 수 있기 때문에 혹시 잘 못하면 어쩌나 라는 생각을 하지 않아도 좋습니다.
  • 기한이 따로 없습니다. 자신의 페이스에 맞춰서 학습을 진행 할 수 있습니다.
  • 동영상 다운로드가 있기 때문에 열차안에서도 보기 쉽습니다.
장점이 많은 강의이지만, 온라인 강의의 특성상 통제 불가능한 부분도 있습니다.
  • 기한도 없고 돈도 내지 않기 때문에 중간에 그만둬 버리기 쉽다
사실, 강좌를 시작하는 사람수에 비해 끝까지 마치는 사람의 비율은 매우 낮다는 데이터가 있다고 합니다. 그래서 마지막으로 필요한 것은 ‘강철의 의지’ 입니다. 혼자 하기 어렵다면 친구나 동료를 모아 함께 배워나가는 것이 좋을지도 모릅니다.
역자주: 그렇습니다. 이 강의는 혼자 듣기가 참 어렵습니다. 일단 18주(무려 4달 반에 해당한다!)에 이르는 양도 양 이지만 연습문제풀이등에서 막히면 혼자 풀어나가기가 참 어렵습니다. 주변의 널널한 프로그래머, 혹은 수학이나 통계학 전공자 들을 꼬득여 온라인 스터디 그룹을 만들고 1주일에 한번씩 서로의 진도를 체크해 주며 격려해 나가는 것도 한가지 좋은 방법이 되리라 생각합니다.

머신러닝 과정의 내용

  • 여러가지 WEB페이지에 같은 내용의 소개가 있습니다만, 시기에 따라 코스수나 조건이 달라지는것 같습니다. 이 글의 내용은 2015년 5월 현재 오픈된 코스를 기준으로 소개합니다.

1. Introduction(소개)

머신러닝이라는게 데체 뭐야? 어디서 쓰는 것인지에 대해서 설명하고 있습니다. 아울러 쓰게되는 툴의 설치 방법을 설명하고 있습니다.
동영상은 물론 자막이 있으며 아직 한국어 자막은 소개 이외엔 제공되고 있지 않습니다만 영어 자막을 켜거나 일본어가 가능하다면 일본어 자막을 선택 할 수 있습니다.
설명은 윈도우즈라면 MATLAB를 설치하는것 부터 시작합니다. (이 과정을 수강하면 무료로 사용할 수 있습니다.) Linux / Mac OS X라면 Octave를 인스톨 합니다.
각 장에서 토론을 할 수 있는 게시판 같은 곳이 있는데, 아무것도 쓰지 않아도 과정을 끝까지 수료하는데엔 아무 문제가 없으므로 인스톨 이외엔 무시하고 넘어갑시다.

2. Linear Regression with One Variable(변수의 선형 회귀)

여기서부터 실전으로 들어갑니다. 머신러닝의 출발은 선형 회귀라는 것을 알 수 있습니다.
또한 이 장에서는 앞 장에서는 없었던 Review라는 이름의 테스트가 있습니다.
테스트는 5 문항 중 4 문항이상을 맞춰야 통과가 되는데, 단순 객관식이 아니라 복수 선택이라던지 계산하여 숫자를 써 넣는식으로 시험이 진행됩니다. 게다가, 어려운것은 매번 출제되는 순서나 내용이 바뀌므로 한번 통과했다 하더라도 다시 시험을 보았을때 떨어지느 경우도 있습니다.
게다가 이 테스트는 꼼수를 막기위해서 3회동안 패스(4문제 이상 정답 제출)하지 못하면 8시간 이내에 시험을 볼 수 없는 패널티가 주어지게 됩니다. (패널티는 이것 뿐입니다.)
패널티를 받게되면 아래 사진처럼 몇 분 내기라는 표시가 뜨게 됩니다.
테스트를 통과하지 못한다 하더라도 다음 진행을 못하는 것은 아니므로 혹시나 패널티를 받게 된다면 그냥 다음 진도를 일단 나가는 것이 좋습니다.

3. Linear Algebra Review(선형 대수학 복습)

선형 대수학을 모르면 앞으로 진행 할수 없다는 것을 잘 알려주는 장 입니다. 기초에 대해 많은 공을 들여 친절하게 시간을 투자하고 있습니다. 머신러닝에 사용되는 행렬이나 벡터에 대한 개념을 이 장에서 잡으셔야 합니다.
이 장의 Review는 좀 달라서, 이 장에서 한두번 치루게 되는 확인시험과 동일한 평가를 Review형식으로 취합니다. 따라서 클리어 한다고 해도 전체 성적에 반영되지는 않습니다. 혼동하지 않도록 주의합시다. 이 후로도 이러한 형태의 시험은 여기 뿐입니다.

4. Linear Regression with Multiple Variables(다변량 선형 회귀)

2장에서는 선형 회귀를 살펴보았으므로, 이번에는 다변량 선형 회귀에 대한 강의입니다. 다변량을 취급하는데 있어서 규모를 맞추거나 매개 변수의 조정에 대한 논의가 있습니다.
또한 이 장에서 프로그램을 직접 짜는 테스트가 추가됩니다. MATLAB/Octave에 익숙하지 않으면 좀 푸는 것이 괴롭습니다. 하지만, MATLAB/Octave에 대해서는 다음장에서 설명을 시작하므로 먼저 5장을 듣고 나서 돌아오는것이 좋습니다.
덧붙여서, 프로그램은 MATLAB/Octave에서 직접 업로드 할 수 있습니다. 업로드 하면 WEB에서 성적을 볼 수 있습니다. 업로드는 몇번이고 가능하므로 부담 갖지 말고 들어보시기 바랍니다.

5. Octave Tutorial(Octave의 설명)

Octave라고 써 있습니다만, MATLAB도 마찬가지입니다. 행렬 처리에 익숙하지 않은 사람은 이 장의 “Vectorization”동영상의 내용을 마스터 하면 for문이 불필요한 계산을 구축 할 수 있게 됩니다. 개인적으로 이 장의 강좌를 들으면서 좋았다고 생각한 순간이었습니다.
역자주: 이 강의에서는 MATLAB/Octabe를 위주로 설명하고 있지만 기업환경에서 머신러닝을 이끌고 있는 대중적인 언어는 파이선/R입니다. 파이선이나 R에 대한 튜토리얼은 아래 링크에서 확인해 보세요.

6. Logistic Regression(로지스틱 회귀)

동영상 내에서도 선생님이 혼자서 마구 달라는 느낌입니다만, 이름은 ‘회귀(Regression)’ 인데 ‘분류(Classification)’에 에 사용되는 로지스틱 회귀를 배우는 장 입니다. 선형 회귀와 비슷한데, 다른 곳을 확인하는것으로 머신러닝에 대한 깊이가 드러나게 됩니다.
또한 여기서도 프로그램의 게시물 테스트가 있는데, 곤란하게도 다음장에 배울 지식이 없으면 풀리지 않는 문제(Regularization:정규화)가 들어 있습니다. 제 경우 결국 풀지 못하고 다음장에 갔다가 다시 돌아와서 풀어야만 했습니다. 설명하지 않아도 풀 수 있을거라고 생각했는지, 암튼 여러가지로 깊이가 있는 챕터입니다.

7. Regularization(정규화)

점점 머신러닝 다워 진다고나 할까, “XX를 향상시키기 위해 이를 수식에 추가”라는 요소 중 하나인 정규화에 대해 배울 수 있는 장 입니다. 장으로는 짧지만, 이 장을 대충 넘기게 되면 나중에 다시보지 않으면 안되는 것이 많아지므로 제대로 이해하는것이 중요합니다.

8. Neural Networks : Representation(신경망:표현)

이번 장은 전반부 최대의 고비인 신경망에 대한 내용입니다. 비교적 복잡하기 때문에 2개의 장으로 나눠져 있습니다. 이 장에서는 먼저 입출력에서 출력까지 어떻게 계산하고 진행해야 하는지를 설명하고 있습니다.

9. Neural Networks: Learning(신경망:학습)

여기가 신경망을 머신러닝에 적용시키는데 중요한 Backpropagation(오차역전파방법)에 대해서 설명하는 장 입니다. 저는 지금까지 다른 책 이라던지 웹에 적힌 내용을 봐도 도통 뉴럴네트워크가 어떻게 학습을 해 나가는지 이해 할 수 없었습니다만, 동영상 및 구현을 보고 마침내 어떤 계산에서 학습하고 있는지를 알겠다는 생각이 들었습니다.

10. Advice for Applying Machine Learning(머신러닝을 적용하기 위한 조언)

머신러닝 이론을 안다고 해도 실제 적용하기 위해서 무엇을 조심하지 않으면 안되는가에 대해서 다루는 대단히 중요한 장 입니다.
언더핏, 오버핏, 학습곡선이 어떻게 되는가 등 머신러닝을 실무에 적용하려 할때 지침이 되는 장 입니다.

11. Machine Learning System Design(머신러닝 시스템 디자인)

이전 장 처럼 이번 장도 머신러닝을 실무에 도입하려고 할 때 발생하는 문제점을 알아보고 해결책을 제시하는 장 입니다.
구체적으로는 99%일어나지 않지만 1%일어날 가능성이 있는 현상에 대해서 머신러닝의 지표는 어떻게 설정해야 하는것인가에 대해서 이야기 하고 있습니다.

12. Support Vector Machines(서포트 벡터 머신(SVM))

분류 알고리즘의 하나인 SVM에 대한 장 입니다. SVM은 인기있는 알고리즘 이므로 잘 배워둬야 한다고 선생님은 말씀하시고 있었습니다.
또한 마지막 동영상은 SVM을 포함해 여러 알고리즘들이 어떤때 사용되는지 이야기 하고 있으므로 잘 기억해 둬야 하는 포인트 입니다.

13. Unsupervised Learning(무감독 학습)

이번장부터 15장 까지가 무감독 학습의 장 입니다. 이 장 에서는 K-Means알고리즘이라 불리우는 인기있는 다운클러스터링 알고리즘에 대해 배웁니다.

14. Dimensionality Reduction(차원감소)

이 장에서는 차원감소에 대해서 배웁니다. 차원감소라는 개념에서 실제로 배우는 알고리즘은 PCA(주성분 분석)라 불리우는 알고리즘 입니다.
저는 100차원이든 1000차원이든 2차원 또는 3차원까지 줄여버리면 그래프 그리기가 가능하다는 것으로 듣고 학습 동기가 생겼습니다.
또한 차원을 감소 시켜 극단적으로 적은 수가 된다면 어떻게 될까 생각했습니다만, 그에 대해서는 제대로 대답이 준비되어 있어 원래의 상태 대비 압축 정도에 따라 어느정도 정보량이 손실되는지를 파악하는 것이 중요하다고 생각했습니다.

15. Anomaly Detection(이상 검출)

이상을 검출하는 알고리즘을 학습하는 장 입니다.
여기서 처음으로 정규분포(상당히 복잡한 수식)이 나오는 것 입니다만, 그 이전에 여러 수식에 익숙해 진 터라, 그렇게까지 흉악해 보이지는 않았습니다.
흥미로왔던 것은 감독 학습과 비정상 검출을 비교하여, 데이터 상태에 따라 어느쪽을 사용해야 할 지와 같은 주제가 앞으로 도움이 될 것이라 합니다.

16. Recommender Systems(추천시스템)

사용자가 평가 한 영화의 평점을 어떻게 처리할 것 인가라는 굉장히 실용적인 주제에 대한 해설을 하는 장 입니다.
처음에는 제한적인 작은 데이터부터 시작하여 마지막에는 비어있는 부분이 있다 하여도 모든 파라메터를 단번에 계산 해 버릴 듯한 이야기의 흐름이 무척 좋았습니다.

17. Large Scale Machine Learning(대규모 머신러닝)

대규모 데이터에대해서 매번 전부다 처리를 하게되면 속도가 느려지는 것에 대해서 어떻게 할 지를 생각해 보는 장 입니다.
여기에서 처음 Map-Reduce개념이 등장 합니다만, 분할해서 처리하니 빠르군요 정도의 뻔한 수준의 이야기 이므로 좀 더 자세하게 알아보고 싶으신 분들은 다른 문헌을 찾아보시는것을 추천합니다.

18. Application Example : Photo OCR(응용 예: 사진에서의 텍스트 추출)

마지막 장은 응용에 대한 예로서 파이프라인을 이용한 머신러닝 로직의 조합으로, 사진에서 텍스트를 추출하는 작업을 수행합니다.
실제 프로그램을 짤 것이라고 생각했는데, 이 장에서는 프로그램을 제출하지 않았습니다.

모든 테스트를 통과하면 무엇이 일어나는가?

기간제 코스의 경우 유료로 수료증 같은 것을 발행해 주는듯 합니다만, 완전 오픈코스인 이 과정은 결과가 코스 상단에 표시됩니다.
역자주: 코세라는 코스 자체는 무료로 제공하고 있으며, 수료증을 유료로 제공하는것을 비즈니스 모델로 하고 있습니다.
실제 전 과정을 패스하자 아래와 같이 표시되어 “Course Passed”부분에 뭔가 링크가 있을까 기대해 봤지만… 아니었습니다.

머신러닝 수강이 끝났다! 이제 뭘 하지?

프로그래머라면 이제 손을 움직여 다양한 것을 구현해 나갑시다. 샘플 코드의 의미도 알게 되었고 어디를 수정하면 어떻게 될까 어쨌든 머리속에 그려 볼 수 있게 된 듯 합니다.
스터디 그룹같은걸 열어서 아직 머신러닝에 경험이 없는 사람을 끌여들여 동료로 늘려 나가는 것도 좋은 방법입니다.
참여자가 늘지 않으면 이 분야는 발전이 없기 때문에 꼭 주변에 퍼트리고 다닙시다.

역자 추가: 머신 러닝 관련 읽을거리들

쉽게 풀어쓴 딥 러닝의 모든것
PredictionIO:오픈소스 머신러닝서버
Getting Started with Microsoft Azure Machine Learning :간편한 인터페이스임에도 강력한 기능을 제공하는 AzureML에 대한 온라인 강좌 입니다.
DL4J: Word2Vec를 비롯 각종 머신러닝 알고리즘을 자바에서 쓸 수 있게 해 주는 오픈소스 프로젝트입니다.
​

2015년 1월 25일 일요일

어떻게 하면 사람들이 즐겁게 테스트를 작성하도록 할 수 있을까?

프로젝트에서 프로덕트로


 제임스 루이스와 마틴 파울러는 마이크로서비스에 대한 논문에서 마이크로서비스의 특징의 하나로 프로젝트에서 프로덕트로의 패러다임 전환을 꼽고 있다.

 하지만 이는 마이크로서비스에 대한 것 만은 아니다. 소프트웨어의 품질과 기능 그 모든면에서 프로젝트에서 프로더트로의 관점 전환은 대단히 중요한 요소이다. 오늘 소개할 InfoQ의 기사는 바로 그러한 관점의 전환이 소프트웨어 개발과 테스팅을 어떻게 바꿔 놓을 수 있는지를 보여준다.

 자동화 테스트는 메트릭스의 모피어스가 건네주는 알약과도 같아서 일단 처음 어느 정도 고통스러운 시기를 거치게 되지만 한번 제대로 동작하기 시작하면 자동화 테스트가 없는 개발로는 두 번 다시 돌아가지 못한다. 하지만 그 첫 걸음을 어떻게 하면 고통 없이, 또는 적은 고통으로 성공시킬 수 있을까?

 자신의 개발 조직에 자동화 테스트를 도입하려고 하고 있거나 이미 도입하였지만 제대로 정착 시키는 데에 어려움을 겪고 있다면 이 기사가 큰 도움이 되리라 생각한다.

www.workyouenjoy.com


어떻게 하면 사람들이 즐겁게 테스트를 작성하도록 할 수 있을까?


원문 링크 : Leading a Culture of Effective Testing


 저는 제가 개발에서 운영까지 책임을 맞았던 최초의 시스템을 지금도 잘 기억하고 있습니다. 다행히도 설계에서 지원까지 모두 맡았습니다. 몇 년 후, 그 시스템 중 하나의 하위 시스템이 자주 사용되게 되었습니다. 지속적인 기능 추가 요청이 오기 시작했고 복잡성은 증가했습니다. 변경을 할 때마다 나는 주의 깊게 기존의 기능을 확인 했습니다 만 수작업으로 하는 검사는 시간이 걸리고 기능 추가에 따른 복잡성의 증가는 폭발적으로 늘어났습니다.

 다른 사람들과 마찬가지로 나는 자동 테스트에 대해 들어 본 적이 있었지만, 그 방법을 배울 시간이 없었습니다. 그러다가 시간적 여유가 생겼을 때, 나는 몇 가지 책을 읽고 그 방법을 배웠습니다. 그 방법을 현실의 일에 적용 한 결과, 성장함에 따라 변경이 어려워 졌던 서브 시스템의 문제를 해결하기 위해 자동화 된 테스트는 완전한 방법이라고 느꼈습니다. 어느 날 오후, 나는 자동차 드라이브 나가고 있었습니다. 시간에 여유가 생겼던 겁니다. 인터넷에 연결되지는 않았지만 테스트를 작성하는 데 필요한 모든 도구를 가지고 있었습니다. 며칠 후 수동으로 실시하고 있던 검증의 세세한 부분 대다수를 자동화했습니다. 시간 절약 효과는 엄청난 것 이었습니다. 몇 시간 이나 필요했던 테스트가 불과 몇 초 만에 끝나버렸습니다.

 그러나 시간 절약은 두 번째 효과였습니다. 내 머릿속의 모든 시나리오가 크게 변화했습니다. 만든 것에 대한 신뢰의 감각이 되살아 난 것입니다. 아직 복잡성이 증가하지 않는 새로운 시스템에 대한 신뢰와 비슷합니다. 새 릴리스를 할 때 마다 불안감이 머리속을 떠나지 않았습니다. 여러 번 확인 했음에도 불구하고 불안은 사라지지 않았습니다. 응용 프로그램의 릴리스에 따른 지원은 만든 사람이 책임을 져야 했기 때문에 본능적으로 릴리스를 꺼리게 되었습니다. 이러한 상황에서 자동 테스트의 도입은 불안감을 안심으로 바꿔주었습니다.

 테스트에 누락이 있으면, 간단하게 테스트를 추가하고 다시 누락 되지 않도록 했습니다. 자신감이 생기자 위험을 감수하고 새로운 기능을 추가 할 수 있게 되었습니다. 또한 더 이상 문제를 무서워하지 않고 가치를 제공하는 데 주력 할 수 있었습니다.

 수동 테스트의 경우 테스트 시나리오는 수동으로 주의 깊게 설계하고 수동으로 실행하며 수동으로 확인 해야 합니다. 게다가 머리 속에 남아 있는 시간은 길지 않아 쉽게 잊혀집니다.

 자동 테스트의 경우, 시나리오, 실행, 검증은 모두 프로그램에 의해 문서화 되고 자동화됩니다. 유일한 수동 절차는 실행 버튼의 클릭 뿐입니다. 기억에 의존 할 필요도 없으며, 유통 기한이 지난 문서를 의지하여 시나리오를 쫓아 수동으로 수행 할 필요가 없습니다.

 대부분의 개발자가 테스트 자동화의 가치를 인정하면서도 그 혜택을 향유 하는 데 에는 실패했습니다만, 나는 다행히도 장점을 누리기 위한 올바른 방법을 찾을 수 있었습니다.

책임


 만약 제가 테스트에 대해 책임을 지지 않았다면, 테스트를 자동화하는 시간은 없었을 것입니다. 이것은 매우 간단한 것입니다. 자신이 개발을 지원하는 시스템의 사후 지원을 담당하는 것으로, 나는 소프트웨어가 제대로 작동하는지 보장하는 데 큰 관심을 갖게 되었습니다.  오전 5시 전화 받고 일어나 시스템 장애를 해결하는 사태를 막기 위해 나는 문제점이 이월 되지 않도록 꾸준히 노력해 왔습니다.

 개발자야 말로 만든 것을 어떻게 확인 해야 할지 가장 잘 알고 있습니다. 어느 부분에 자신이 없거나 어디에 복잡성이 존재하는지 어디에 문제가 포함될 여지가 있는지 잘 알고 있지요. 개발자 이외에 이처럼 포괄적인 관점을 가지고 있는 사람은 없습니다. 개발자는 작업 자동화 전문가이며, 검증 작업 자동화의 적임자입니다.

 많은 개발자들이 개발 한 시스템의 사후 지원이 가능하며, 그 책임을 다른 사람에게 전달하고 싶어하지 않습니다. 뭔가를 만든 사람은 그것이 성공하는 것을 희망합니다. 우리가 할 수 있는 것은 개개인이 어떠한 것에 대하여 공헌하고 싶어 하는지 묻는 것입니다. 책임을 공유하고 개발자가 시스템의 사후 지원에 기여할 수 있도록 하는 것이 효율적인 테스트의 이점을 누리는 방법입니다.

제각각인 팀을 융합하다


 만약 조직이 소프트웨어 개발의 책임을 여러 팀에 분산 시키게 되면 테스트를 하는 것은 힘든 작업이 됩니다. 불행히도, 고객의 납품이 완료된 이후의 시점부터는 이러한 어려움이 일상화되어 버리게 됩니다.

 테스트가 다른 팀에 맡겨진 경우 대부분의 경우 수동 테스트입니다. 자동화되어있는 경우에도 유지 보수하기 어려운 엔드 - 투 - 엔드 테스트입니다. 엔드 투 엔드 테스트는 운영 환경과 동일한 환경을 설정 해 줘야 합니다. 또한 개별 부분을 잘라낼 수 없습니다. 또한 뭔가 잘못되면 많은 테스트에 영향을 주고, 의미있는 피드백을 실패로 부터 얻을 수 없게 됩니다. 응용 프로그램 업데이트를 비활성화 버리는 취성 엔드 투 엔드 테스트 코드를 정기적으로 업데이트한다면, 수동 테스트에 회귀하는 것은 자연스러운 것입니다.

 분리 된 테스터가 응용 프로그램의 낮은 수준을 테스트 하기 위한 능력과 지식을 가지고있는 것은 드문 경우입니다. 특히 효과적인 검증 될 필요가 있는 단위 테스트 수준의 것은 알 수가 없습니다. 단위 테스트는 시스템을 작은 단위로 분리하고 확인합니다. 이 수준에서는 코드와 애플리케이션에 대한 깊은 이해가 필요 합니다 만, 개발자 만이 이에 대한 지식을 가지고 있습니다. 만약 개발에서 분리 된 테스터가 이러한 단위 테스트에 대한 지식을 지니고 있다면, 그 테스터가 개발팀이 아닌 것은 이상한 일입니다.

 또한, 테스트의 책임이 전가되어 버리면 개발자에게 소프트웨어를 성공적으로 움직이는 것에 대한 책임이 없다 라고도 할 수 있습니다. 하지만 그러면 개발자에게 신뢰하지 않는다는 메시지를 보내 버리게됩니다. 이것은 최악의 마이크로 관리입니다. 우리는 확인 사항이 줄을 설 정도로 많은 새로운 기능을 개발하고 있습니다. 실제로 테스트를 하고 피드백을 하는 데 몇 일, 몇 주 가 소요될 것입니다. 작은 문제가 숨어 있던 것 만으로도 새로운 기능의 구현에 장애가 될 수 있습니다. 그리고 기능 구현이 지연하면 급격하게 진행이 악화되고 마는 것입니다.

 이렇게 개발과 테스트를 분리하는 것은 자연스러울 지도 모릅니다 만, 결과적으로는 테스터의 레이어가 두꺼워지고, 몇 번이나 확인을 하게 되어 버립니다. 나는 3 개 이상의 분리 된 테스트 단계를 가지고 조직을 알고 있습니다. 이 경우 전체 프로세스가 멈추었습니다. 하나의 경쟁 상대가 책임을 직접 개발자와 공유하는 방식의 장점을 발견 하게 되면, 상대적으로 다른 기업에 불리한 위치에 서고, 결국 경쟁에서 질지도 모릅니다.

 책임과 권한의 명확화는 효율적인 테스트를 하는 데 매우 중요합니다. 하나의 팀이 테스트 지원에 책임을 갖게 합니다. 책임 여러 팀에 분산 되어 있다면, 하나의 팀으로 통합 해야 합니다. 테스터가 개발자의 옆에서 일을 하게 하는 겁니다. 또는 각각의 멤버들이 테스터와 개발자 모두의 역할을 서로 확인하고 편견을 제거합니다. 개발자가 일부 단위 테스트를 작성하고 다른 테스트 팀에 전달하는 것이 아니라, 단위 테스트와 엔드 - 투 - 엔드 자동화 된 테스트를 할 것인지, 아니면 수동 테스트를 할 것 인지를 상황에 따라 전체적인 의견을 통해 결정 하도록 합니다.

 새로운 팀의 업무 초첨은 단일 기능 또는 관련된 몇 가지 기능에 한정 시키는 것이 좋습니다. 이렇게 하면, 전원이 함께 일하고 하나의 기능을 개발하고 테스트 할 수 있습니다. 서로 상관 없는 변경 요소를 한 팀에 처리 시킨 다던지, 대규모 변경의 대응을 개발 팀 테스트 팀, 지원팀 사이에서 몇 달에 걸쳐 이동하는 것이 아니라 하나의 팀으로 작은 릴리스에 필요한 모든 것을 수행 하고 1 에서 2 주 기간으로 작업합니다. 이 제한적인 포커싱에 의한 개발 방법은 실제로 생산성을 향상 시키고 있습니다.

 작은 변경을 실시하는 것으로, 고객의 의견을 신속하게 받을 수 있게 됩니다. 변화가 작기 때문에 테스트 환경이나 스테이징 환경도 쉽게 준비 할 수 있고, 외부 고객의 리뷰에 더욱 포커싱 할 수 있게 됩니다. 고객이 확인할 내용에 대해서도 모호함이 줄어듭니다. 피드백이 오는 것도 빠르게 되어, 피드백에 대한 대응도 늦지 않게 됩니다. 큰 덩어리보다 작은 변화가 더 테스트하기 쉽기 때문입니다. 빠르게 피드백을 받고 빠르게 변경하고 출시하여 다음의 작은 기능 세트로 이동합니다.


자동 테스트의 가치를 보여주다


 팀 개개인이 개발, 테스트 및 시스템 지원의 책임을 가지게 되면 두 가지 선택이 있습니다. 하나는 팀이 수동으로 테스트가 효율적이고 않는다는 것을 깨달을 때 까지 기다리는 것. 다른 하나는 도박을 피하고 팀을 교육하는 방법 입니다. 적당한 지침 없이 자동 테스트를 실시하는 데 몇 년이 걸립니다. 개개인이 일상적인 업무량에 치이고 있는 경우에는 특히 그렇습니다. 자동 테스트를 도입한다고 해도 학습하는 데 몇 년이 걸립니다. 그러나 전문가의 도움을 받아 테스트 자동화의 가치를 알리는 것으로,이 기간을 단축 할 수 있습니다.

 자동 테스트에 완전히 매료되어 충분한 성과를 올린 후, 나는 다른 사람들도 동참 시키고 싶다고 생각 하게 되었습니다. 먼저 데모를 실시하여 자동화 된 테스트 도구의 활용 방법을 설명했습니다. 하지만 이 방식은 실패 였다고 생각합니다. 직접 실제 프로젝트에서 자동 테스트를 실시 할 수 있도록 지원하는 것이 아니라 시험 방법을 가르쳐 버린 것입니다. 가르치는 것 만으로는 부족합니다. 테스트의 가치를 증명하는 유일한 방법은 팀이 실제로 책임 지고 있는 일에 대해 테스트를 통해 자신감을 키우는 것을 도와주는 방법 뿐 입니다.

 가장 효율적인 것은 자동화 된 테스트 기법을 실천할 수 있는 기회를 만들고, 복잡하고 오류 나기 쉬운 응용 프로그램에 적용해 보는 것입니다. 개발자는 모두 까다롭고 다루기 귀찮은 시스템을 최소한 하나는 가지고 있습니다. 경험을 통해 불안감을 자신감으로 바꾸는 체험만이 자동화 테스트가 개발의 일부로서 받아지도록 하는 유일한 방법입니다.

개인 개발자가 다음과 같은 것을 경험하면 도입은 더 수월하게 진행 되겠지요.


  • 복잡성을 자동으로 테스트하는 것에 대한 신뢰.
  • 테스트 주도 개발의 경우 코드를 작성하기 전에 테스트를 작성하여 소프트웨어 개발을 진행해 나가는 것.
  • 의미 없는 테스트에 대해서 테스트를 자동화하는 것은 역효과. 이 트레이드 오프를 깨닫기 위해 몇 년을 낭비한다. 많은 자동화 된 테스트를 포기하게 되는 큰 원인.
  • 도구가 자동 테스트의 부하를 획기적으로 낮출 것.
  • 테스트는 소프트웨어의 변경과 기능 추가가 쉬워 기존 기능도 심플하게된다는 것.
  • 기존 문제의 근본 원인 분석 수정에 테스트가 도움이된다는 것.
  • 외부의 실시간 시스템과의 통합 등 수동 테스트가 불가능한 영역에서 자동 테스트가 효과를 발휘한다는 것.

개발자 툴 킷 중에서도 자동 테스트에 큰 가치가 있음을 증명하는 것은 간단합니다. 개발자가 자동​​ 테스트가 일을 개선해주는 것을 한번 경험하면 그 이후로는 단번에 가치에 대해 깨닫게 됩니다.

강제로 요구 하지 않기


 데모를 실시하고 자동화 테스트의 전파에 실패한 후, 나는 적어도 팀이 나를 따라 올 수 있는 상태를 만들고 싶다고 생각했습니다. 여기서 저는 또 다른 실패를 저지릅니다. 그것은 응용 프로그램의 일부에 대한 테스트를 쓰는 것을 의무화 한 것입니다. 이것이 실패한 것은 다음의 두 가지 이유에서 입니다.


  •  제 자신이 너무 많은 책임을 지고 말아 버렸습니다. 뭔가 좋지 않은 일이 일어 났을 때, 늦게까지 남아있는 역할 이었는데, 그에 따라 많은 테스트가 내 부담이 되었습니다. 나는 잘 작동하지 않을 것 같은 시스템의 일부에 대한 테스트를 선택했습니다. 하지만 테스트 케이스를 전달 하는 과정에서 본래의 의미는 사라져 버렸습니다.
  •  의미가 희석 되어 버렸으므로, 가치도 느껴지지 않습니다. 그리고 성급하게 테스트 케이스를 만들었습니다. 그 결과, 테스트 케이스의 질이 희생되었습니다. 이해하기 어렵고, 대충 적당한 , 때로는 부정확 한 사례가 되어 버렸습니다. 룰을 강제 하여 많은 테스트가 만들어 졌지만, 그 테스트가 쓸모 없다는 사실을 깨달았습니다. 목적을 잃어버린 테스트 작성은 벌칙 게임 같은 것입니다. 쓸모없는 테스트는 프로젝트에 유해한 요소가 되어 유지 보수 해야 할 코드의 양만 늘려버립니다. 또한 사용할 수 없는 시험은 장기적으로는 혼란의 씨앗입니다. 시간이 흐르면 의미 없는 테스트가 본래 시스템이 가져야 할 모습을 묘사하고 있는 것으로 둔갑하게 되기 때문입니다.

 책임을 공유하고 테스트 케이스의 장점을 근본부터 이해하게 된다면 그 이후로 프로그래머는 가치가 있는 테스트를 작성하기 시작합니다. 교훈은 매우 간단합니다. 즉, 강제하는 것은 피하라.

그 밖에도 몇 가지 피해야 할 사항들이 있습니다.


  • 코드 커버리지 수준을 강제한다.
    • 테스트 코드 커버리지는 테스트 케이스가 커버 하는 코드의 양을 측정합니다. 범위가 80 %라면 20 %의 코드는 테스트를 실행하여도 호출되지 않습니다.
    • 코드 커버리지는 테스트하지 않은 영역을 손쉽게 찾을 수 있는 긍정적인 면이 있기도 합니다. 그러나 테스트의 품질을 보장​​하는 것은 아닙니다. 테스트 케이스의 효과는 수치로 측정 할 수 있는 게 아닙니다. 또한 응용 프로그램은 테스트 할 가치가없는 영역도 있습니다. 따라서 100 %의 커버리지를 제공하도록 지시를하는 것은 현명한 생각이 아닙니다.
    • 코드 커버리지 수준을 강제하는 것은 쓸데없는 테스트를 많이 낳아 버립니다.
  • 코드를 작성하기 전에 테스트를 작성하도록 지시한다. 즉 테스트 주도 개발 (TDD).
    • TDD는 매우 중요한 기술입니다. 그러나 강제하는 방식으로는 진행하지 말아야 합니다. 응용 프로그램에는 TDD가 유효하지 않은 부분이 존재 하게 마련입니다. TDD의 가치를 나타내는 것은 좋은 것입니다. 그러나 개개인이 자신의 판단에 따라 실천 해야 합니다.
  • 테스트 수를 강제한다.
    • 이것은 코드의 라인 수를 지시하는 것과 같습니다. 의미가 없습니다.
    • 시스템에 불필요한 테스트가 있다면, 과감히 제거해 나갑시다. 테스트 코드는 유지 보수 해야 하는 코드의 양을 늘립니다.
 이러한 강제에 의해 개발자는 난처한 상황에 빠지고, 쓸데없는 테스트가 태어나 시스템의 유지 보수 용이성을 저하 시킵니다.



장애물을 제거


 개발자가 한 번 자동 테스트의 가치를 실감 한 후에는 실천에 들어가는데 걸림돌이 되는 장애물을 제거하는 것이 중요합니다. 장애를 제거하여 수동 테스트의 단점을 제거 할 수 있습니다. 테스트를 자동화하는 것을 어렵게 하고 있는 원인은 어떤 것이 든, 자동 테스트 구축을 실시하지 않는 이유 중 하나에 지나지 않습니다.

장벽을 제거하기위한 도구는 많이 있습니다.


  • 테스트 러너
    • 테스트 러너를 사용하면 신속하게 테스트 할 수 있습니다. 개발자가 사용하는 개발 환경과 통합하면 좋을 것입니다. 개발자가 TDD를 실천하고 있다면, 코드를 작성하는 만큼 테스트를 쉽게 하고 싶어할 것 입니다.
  • 코드 커버리지 측정
    • 코드 검사는 개발자가 응용 프로그램을 얼마나 테스트하고 있는지 알려줍니다. 한층 더 테스트를 추진하기에 훌륭한 도구입니다.
  • 지속적인 통합 (CI) 서버
    • 개개인이 테스트 케이스를 실행하는 이외에, CI 서버를 사용하여 변경이 발생할 때마다 자동으로 테스트를 실시합니다.
    • 테스트 전체를 실시 하는 것을 잊어서는 안됩니다. 안전망을 구축하는 것이 테스트의 가치를 높이는 것입니다. 안전망이 없어서 테스트가 무시 된 환경을 여러 번 본 적이 있습니다. 테스트가 작동 하고 있는지 않는지 조차 인식하지 못하는 경우도 있습니다.
    • 테스트가 실패하면 즉시 알려주기 때문에 문제를 빠른 시간 안에 바로 잡을 수 있습니다.
    • 개발자가 CI 서버를 신뢰하여 만든 테스트에 더 큰 가치를 인정하게 된다면 그 가치를 만끽 하기 위해 테스트를 빨리 만들고 싶어지게 됩니다.

 이러한 도구의 구입 비용을 가지고 고민하는 기업들을 여럿 보아왔습니다. 아이러니하게도 많은 도구의 비용은 개발자의 몇 시간 분 시급보다 낮을 것입니다. 효율적인 테스트를 실현하는 중요한 역할을 하는 도구의 이점에 대해 논하는 것은 시간 낭비입니다.

 지금 바로 실천을 통해 도구를 사용하여 오류를 제거합시다. 시작이 늦으면 늦을수록 손에 들어오는 가치도 작아 져 버립니다.


여유


 기법을 배우고 실천하는 데 필요한 여유도 테스트에 대한 장애 중 하나 입니다. 개발자가 여가 시간에 테스트를 테마로하고 배워주는 기대해야할까요? 아니면 일 동안 시간을​​ 확보하고, 이러한 학습을​​ 지원하는 편이 좋은 것일까 요.

 나는 오랫동안 다양한 테스트 기법의 학습과 적용에 종사하여 "자동 테스트 도구"어떤 때 유용 어떤 때 쓸모 있는지 본능적으로 알게 되었습니다.

개인에게 여유를 주고, 자동 테스트 기술의 연마를 지원합시다.


  • 고 부하 일정을 피합니다. 바쁜 와중에 학습을 동시에 진행 하기는 쉽지 않기 때문입니다.
  • 불필요한 작업은 중단합니다. 예를 들어, 수동으로 실시하는 테스트 케이스를 문서화하는 것입니다. 이렇게 하면 자동화 하는 인센티브도 생깁니다.
  • 자동 테스트를 낭비라고 느끼는 고객 및 이해 관계자도 있을지도 모릅니다. 미신은 추방합시다. 이런 이야기에 의욕을 잃어서는 안됩니다.
  • 무엇이 성공하고 무엇이 실패 했는 지를 자주 되돌아 봅시다. 모두가 배운 것을 공유하고 성과를 서로 치하 할 수 있도록 합시다.



성과에 주목하자


 내가 참가한 가운데 가장 효과적인 테스트가 행해진 프로젝트는 모두가 소프트웨어의 성과에 주목하고 있던 프로젝트였습니다. 어떤 보고서를 작성해야 할지 지시를 받는 것이 아니라 내가 원하는 비즈니스 성과를 이해하고 어떠한 보고서가 필요한 지를 결정하는 데 도움이 될 수 있었습니다.

 시스템의 어느 부분이 가장 비즈니스 성공에 기여하는지 이해하고 있었기 때문에 그 부분에 주력했습니다. 비즈니스의 바람직한 성과를 바탕으로 테스트 우선 순위를 정하고있었습니다. 또한, 성과의 관점에서 테스트를 설계하고 있었으므로, 테스트의 기대 값에 대해 고객이나 사용자에게 직접 설명 할 수있게되어있었습니다.

 이것은 테스트에서 최대한의 가치를 끌어낸 예입니다. 이것을 목표로 해야만 합니다. 비즈니스에 가치를 추가하지 않는 코드와 테스트를 억지로 만들어야 하는 것 처럼 우울한 것도 없습니다.


신뢰


 복통을 일으켰다고 합시다. 병원에 도착했을 때, 당신은 접수 계원에게 증상을 설명합니다. 접수 계원은 맹장 상단의 복부를 절개하는 의사에게 진찰을 요구합니다. 그리고 맹장을 절제하는 것을 전문으로 하는 의사도 부릅니다. 또한 절개 한 복부를 봉합 의사도 부릅니다. 당신은 회복실로 옮겨진 이후 의사로 부터 연락이 끊깁니다. 아직 복통이 있습니다. 다시 진찰 해 보니 하면 통증은 담낭이 원인이었습니다. 맹장은 문제 없었습니다.

 다행히도 우리는 이러한 세계와는 무관합니다. 의사의 업무는 내장을 제거하는 것이 아닙니다. 무엇보다 이렇게 하는 전문 능력은 가지고 있지만. 그들은 진찰 부탁 받아 주의 깊게 분석 한 후 해결책을 처방합니다. 수술의 경우 절개를 하고 배운 것을 바탕으로 진단을 확인합니다. 어떤 경우에도 올바른 길을 확보합니다. 수술 후에도 수술이 완벽하다는 것을 알 때까지 환자를 돌봅니다. 그리하여 안전하다는 확신이 들면 회복실로 보내집니다. 회복시에도 상황을 확인하고 회복을 지원하기 위한 조언을 준비합니다. 의사가 아닌 사람도 환자의 용태를 확인하고 있지만 궁극적으로는 의사가 환자의 회복을 담당합니다.

 만약 의사에게 자신의 생명을 맡길 수 있다면, 마찬가지로 자신이 사용하는 소프트웨어 개발자를 신뢰 하는 것도 가능하지 않을까요?



저자에 관하여


 Wes McClure 씨는 기술과 소프트웨어로 기업이 눈부신 성과를 달성 하는 것에 열정을 가지고 지원하고 있습니다. 소프트웨어 개발의 폭 넓은 경험을 바탕으로 비즈니스 목표에 부합하는 소프트웨어를 개발하는 방법의 개선을 실시하고 있습니다.  Full City Tech를 시작해 전문성을 활용하여 고품질의 소프트웨어를 신속하게 제공하고자하는 기업을 지원하고 있습니다.


2014년 12월 31일 수요일

게임을 예로 설명한 알기 쉬운 도메인 주도 설계 - 1.문제 영역에 대한 올바른 이해




왜 DDD인가?

도메인 주도 설계(Domain Driven Development . 이하DDD)는 말 그대로 도메인 패턴을 중심에 놓고 프로그램을 설계/개발해 나가는 방식을 말한다. 사실 DDD자체는 하나도 새로울 것이 없는 개발 방법으로 객체 지향 프로그래밍(OOP)과도 사실상 동의어라 할 수 있다.

 OOP가 도입 되기 이전에 대부분의 프로그램에서 사용되는 문제 해결 방식인 트렌젝션스트립트Transaction Script나 SMART UI의 경우 문제 해결을 위해 일반적으로 처리를 순서 별로 나열하여 처리하므로 직관적이고 대부분의 문제 영역에서 큰 문제 없이 작동한다. 하지만 문제 영역이 복잡해지면 이러한 방식은 한계를 드러내게 되며, 이를 극복하기 위한 시도가 OOP이고 이를 구현하기 위한 실천적인 어드바이스를 모아 놓은 것이 이 DDD이다.

 도메인 모델링에 대한 자세한 설명은 예전에 작성한 "도메인 주도 설계와 애자일 개발"을 참고하기 바란다.

 오늘은 일본의 유명 모바일 게임 회사인 GREE의 블로그에 게재된 도메인 주도 설계에 대한 기사를 소개해 보고자 한다.

 저자인 카토 준이치씨는 GREE(2014년 9월경 ChatWark사로 이직)에서 선임 엔지니어로 활동하면서 사내에 Scala와 DDD를 도입 시킨 전도사로 유명하며 InfoQ Tokyo 2014에서 동일한 주제로 강연을 하기도 하였다.

 이 글은 비교적 난해한 도메인 주도 설계에 대해 게임을 예로 들어 알기 쉽게 설명하고 있으며, 예제로 등장하는 코드는 Scala이지만 그다지 어려운 코드는 사용하지 않고 있으므로 Scala를 잘 모른다 하더라도 이해하는데 큰 무리는 없을 것 이라 생각한다. (Scala의 용어에 대해서는 중간 중간 주석을 첨부하였다.)


원문 : Scalaコードでわかった気になるDDD
참고로 이 번역 기사는 저자의 허락을 받았음을 밝혀둔다.
(사실은 QCon에서 만난 카토씨 에게 기사를 번역해서 한국에 소개한다고 일방적으로 통보한 거지만...)

본 기사는 내용이 많아 다음과 같이 두 번에 나눠서 게재 하도록 하겠다. 절대 블로그 방문자 수를 올려보기 위한 수작이 아님을 믿어 달라.


1. 문제 영역에 대한 올바른 이해
2. 라이프사이클의 관리



문제영역에 대한 올바른 이해


DDD란?

DDD는 이름 그대로 도메인을 중심으로 생각한 설계 방법 입니다. 조금 어려운 표현이지만, 도메인이라고 하는 것은 소프트웨어가 취급하는 '어떤 활동이나 관심과 관계가 있는 지식' 으로서, 바로 이 도메인을 중심으로 설계를 해 나가는 방법입니다. 흐음... 뭔가 잘 와 닿지 않는 표현이군요. ㅋ

좀 더 구체적으로 예를 들자면, 롤플레잉게임(이하 RPG)에서는 '플레이어','몬스터','아이템','무기/방어구','도시','던전'과 같은 여러가지 개념이 일반적으로 포함되어 있습니다. 이러한 개념을, RPG의 세계관의 '언어'를 사용하여 '도메인모델'이라고 하는 객체를 표현 하고 있습니다. 플레이어는 무언인가? 어떤 특징이 있고, 무엇이 가능하며 무엇이 가능하지 않은가 등. 그러한 언어를 DDD에서는 유비쿼터스 언어Ubiquitous language라고 부르며, 도메인모델과 대응하게 됩니다. 이러한 도메인 모델dms '구현'과 연결 시킴으로서 소프트웨어의 최종 목적을 달성하게 됩니다. RPG의 경우 "최종 보스를 물리치고 세계에 평화를 가져온다"라고 하는 것이 목적이 됩니다.(당연하지만 최종 목적을 달성하기 위해서 크고 작은 목적들이 먼저 달성해야 합니다.)

 그만큼 중요한 요소이지만, 실제 개발 현장 에서는 여러가지 이유로 그러한 지식이 구현 코드 속에 매몰되어 버리기 십상 입니다. 코드를 읽어도 설계의 의미가 좀체 와 닿지 않는 것도 무리가 아니라는 생각이 듭니다. 그렇다면 도메인 지식을 반영한 소프트웨어 제작을 하기 위해서는 어떻게 해야 될까요? 이를 위해 제시된 한 가지 방법론이 DDD입니다. (이렇게 말해 놓고 보니 DDD가 만능처럼 들리지만, DDD는 복잡한 문제를 도메인 모델을 통해 해결하기 때문에 간단한 문제에는 적합하지 않습니다. 우선은 문제에 적합한 해결 방법을 선택 하는 것이 중요합니다.)

왜 DDD를 사용해야만 하는가?

저희 팀에서 DDD를 사용하는 이유는 다음과 같습니다.


  • 어느 정도 복잡한 문제를 다루는 기반 시스템 이었기 때문에, 비용 대비 효과에 대한 기대가 가능하다고 생각했다.


프로토타입이라던지, 그다지 공을 들이고 싶지 않은 시스템에는 적합하지 않은 것이 사실이지만, 제대로 만들어서 지속적으로 성장 시켜 나가는 시스템에는 적용 가능하다고 판단했습니다.


  • 엔지니어 뿐만이 아니라 디렉터나 기획등 비엔지니어가 포함된 팀 멤버가 유비쿼터스 언어를 이용해 같은 눈높이로 시스템을 만들어 나가고 싶었다.


개발측과 기획측이 대립하지 않도록 유비쿼터스언어를 정해, 팀 멤버 전원이 도메인 위에서 문제해결이라는 최종 목적을 공유하고 싶었습니다.


  • 내가 팀 멤버가 되어버려서 반 강제적으로 DDD로 개발하는게 되어버렸다...라는게 사실 가장 큰 이유임. (죄송합니다...)


지금부터 가상의 프로젝트를 예로 실제 모델이 만들어지는 과정을 설명하겠습니다.

시나리오로부터 모델을 만들어낸다

DDD를 시작하려면 어디서 부터 손을 대야 할까요?

DDD에는 Model Exploration Whirlpool이라고 하는 모델을 정제하기 위한 프로세스가 정의되어 있습니다. (이 프로세스는 도메인주도설계(에릭 에반스)에는 실려있지 않습니다.)

그림을 클릭하면 확대해서 보실 수 있습니다

역자주 : Model Exploration Whirlpool에 대하여
문제 해결을 위한 도메인 모델은 우리가 만드는 것 이 아니라 발견 하는 것 이다. 마치 문제 그 자체는 우리의 인지와 상관없이 처음부터 그 자리에 있었던 것처럼 말이다. 그래서 DDD의 저자인 Eric Evans는 자신이 제안한 DDD의 표준 프로세스에 Design(설계)가 아니라 Exploration(탐구)라는 단어를 선택했다. 즉, 도메인 모델 자체는 우리의 인식의 경계선에서 관찰하고 인지하여 그것을 언어로 표현하는 것 이므로 우리는 이러한 반복 적인 탐구 과정을 통해 모델을 정제해 나가는 것 으로서 보다 정확한 모델을 얻는 것이 가능해 진다. 이러한 이유로 처음부터 완벽한 모델을 얻기는 쉽지 않다. 여기서 발상의 전환이 필요해진다. 처음 얻어지는 모델은 틀린 것일 수 있다는 가정 하에 일을 진행 시키는 것 이다. 내가 틀릴 수 있다는 가정하에 반복적으로 검토하며 개선 시켜 나가는 작업은 피터드러커나 조지소러스도 비슷한 사고의 프레임워크를 제시해 나가고 있으며 기본적으로 반복형 개발 프로세스에 기반한 애자일 개발 프로세스의 사상과도 일치한다.
이 그림에 의하면, DDD는 시나리오를 정의 내리는 것 으로 부터 출발하고 있습니다. 시나리오는 모델의 사용 방법을 표현한 것 입니다.

예를 들어, "헌터가 몬스터를 사냥한다"는 게임의 도메인을 생각해 본다면 다음과 같은 시나리오가 생각되어집니다.


  • 헌터는 무기를 사용하여 몬스터를 공격 하는 것이 가능하다.
  • 헌터는 아이템을 사용하는 것이 가능하다.
  • 헌터는 다른 헌터 에게 아이템을 건네는 것이 가능하다.


정말 일부분이긴 하지만 도메인의 세계가 보입니다. 적어도 '헌터', '몬스터', '아이템'은 모델의 후보가 될듯 합니다.


유비쿼터스언어를 찾는 방법

시나리오라고 말한다면 확 와 닿지 않을지도 모릅니다. 처음에는 저도 그랬습니다. 게임의 경우 어느정도 세계관이 정해져 있어 모델을 상상하기가 쉽지만, 업무 시스템의 경우에는 전문지식을 가진 사람(DDD에서는 도메인 전문가라고 부릅니다)이라던지, 그 분야의 전문서적 으로 부터 힌트를 얻는 편이 좋을 듯 합니다. 또, 새로운 분야의 도메인에는 도메인 전문가 자체가 존재하지 않는 경우도 있습니다. 그러한 경우, 스스로가 소프트웨어 전문가 이면서 도메인 전문가라는 마음가짐이 필요합니다.

시나리오에 사용되는 언어는  실제로는 애매한 표현인 경우도 있으므로, 그것도 팀 내에서 의논하여 언어를 통일해 나갑니다. "헌터란 무엇인가?"라는 질문에 대한 대답을 만듭니다. 즉, 유비쿼터스 언어의 정의입니다. 설계라는것은 국어의 문제로군요! DDD에서 가장 어려운것이 바로 이 언어의 정의 이기도 합니다. 유비쿼터스 언어를 정해가면서 화이트보드같은것을 사용해 모델의 이미지를 공유해 나갑니다. 이런 이미지 입니다.


이러한 이미지의 공유가 가능해지면, 바로 코드작성이 가능합니다. 코드의 예는 잠시후에 소개하겠습니다.

역자주 : 모델을 표현하는데에는 여러가지 표기법이 있어 왔지만 현재는 UML로 거의 통일된 상태이다. 표준언어인 UML로 모델을 작성하는 경우 적은 학습비용으로 오해없이 모델에 대한 이미지를 공유하는것이 가능해 지므로 개발자가 아아니더라도 디렉터나 기획자등 프로젝트 이해관계자 전원이 UML의 사용법을 숙지하는 약간의 노력만으로 커뮤니케이션에 소요되는 비용을 크게 줄일 수 있다. UML을 익히는 데에는 숙련자에 의한 일일 워크샵이 가장 효과적으로 각각의 UML요소들에 대해 잘게 나누어 설명, 실습, 쪽지시험을 한 사이클로 묶어 시행하는것이 참가자들의 집중도와 이해도를 높이는데 크게 도움이 될 것이다.   
역자주 : 유비쿼터스언어를 만드는 가장 좋은방법
당연히 워크샵이다. 교외로 놀러가서 술마시라는 이야기가 아니다. 프로젝트 관계자를 모아놓고 만들어 가고자 하는것에 대해 정의해 나가자. 의외로 많은 부분들에 대해서 다른 생각을 가지고 있음을 알게 될 것이다. 프로젝트 사용 언어에 대해 정의내리고 정리해 나가자. 기획자나 개발책임자는 의논의 토대가 될 기본적인 모델이나 용어 일람을 미리 준비하자. 너무 완벽을 추구하지는 말되 각각의 워크샾은 작던 크던 의미있는 성과를 낼 수 있어야 한다. 처음부터 문서화에 공들이지는 말자. 우선은 화이트보드에 적힌 내용을 휴대폰 카메라로 찍고 이것을 공유하는것으로 충분하다.

모델을 코드에 반영한다


서론이 길어졌습니다만, 이제부터 여러분이 좋아하는 구현에 대한 이야기 입니다. 여기서부터는 유비쿼터스언어로 표현한 모델을 실제로 반영하기 위한 방법인 '모델주도설계'를 간단하게 설명하겠습니다. 구현에 대한 이야기를 꺼내면, 프레임웍 이라던지 DB억세스 같은 것이 떠오르시겠지만 여기서는 일단 잊어주시기 바랍니다. (덧붙이자면, 저희들은 DDD를 효과적으로 구현하기 위한 라이브러리인 scala-dddbase를 이용하고 있습니다. 이제부터 등장하는 'Entity','Identity'등은 scala-dddbase의 것을 참조하고 있습니다.)

식별을 위한 엔티티Entity


우선은 엔티티부터 설명하겠습니다. 엔티티는 (동일성의) 식별을 목적으로 하는 모델입니다. 예를 들어 은행 계좌로의 '송금'은 일시, 계좌번호, 금액이라는 속성만 가지고는 '송금'의 식별이 완벽하게 보장되지는 않습니다. 이러한 '송금'의 식별이 불가능 하다는 것은 상식적으로 있을 수 없는 일이기 때문에 '송금'이라고 하는 개념은 식별의 대상이 됩니다. 즉, '송금'은 엔티티 입니다.

 엔티티의 임무는 동일성을 보증하는것 입니다. 소속하는 속성이 변화하였다고 해도 동일성을 보증합니다. 예를 들면, '사람'이라는 엔티티의 속성인 주소, 신장, 체중 등은 때에 따라 변화합니다. 이름이 변경될 가능성도 있습니다.  어린 시절의 속성은 어른이 되면 변화할지도 모릅니다. 하지만, 그 '사람'자체는 동일 인물로서 보는 모델이 엔티티인 것입니다. 표현을 달리하자면, 엔티티는 아이덴티티를 가지고 있다고 말할 수 있습니다. 구현에 대한 이야기를 하자면, 이 아이덴티티는 불변의 식별자(ID)로서 표현하는것이 일반적입니다.

컬럼 : 아이덴티티의 재이용에 대해서
아이덴티티에 대한 이야기는 상당히 심오합니다. 사람을 예로 들자면 태어나는 순간부터 아이덴티티가 발생하여 죽은 후 고인이 되어도 그 아이덴티티는 쭉 남습니다.  그러니까 제가 스티브잡스로 변하는것은 불가능하다는 이야기 입니다. 당연하지요.  즉, 엔티티의 라이프사이클이 끝났다 하더라도 아이덴티티는 재 이용 불가 하다는 것을 의미 합니다. 예를 들어, 재이용되어지는 "전화번호"로 사람을 구분하는 식별자로 사용한다고 하는 경우 큰 혼란을 가져 올 수 있습니다. 사용하던 사용하지 않던, 이러한 리스크에 대한 고려 위에서 손익 계산이 이루어져야 합니다.

이 게임의 "헌터"는, 케릭터의 이름은 동일하더라도, 각각 개별적으로 구분되어야 할 필요가 있기 때문에 엔티티 입니다. 아마 몬스터도 사냥중 같은 종류의 몬스터가 두마리 이상 등장할 경우 이를 구분해야 되기 때문에 엔티티가 된다고 생각합니다.

Hunter엔티티의 구현예는 다음과 같습니다. Hunter엔티티는 Entity를 상속합니다. 실별자에는  Entity트레이트의 identity필드가 이용되어집니다. 기본적으로는 식벌자를 보면 구분이 가능합니다. 하지만, 컬렉션프레임웍의 엔티티 등록에 대한 이용성을 생각하여 equals,hashCode는 엔티티가 가진 속성에 기초하여 구현 하는 것이 아닌, 식별자(identity)가 동일한지 아닌지 만을 구현 하는 것이 바람직하다고 생각합니다.

trait Hunter extends Entity[HunterId] {
  // val identity: HunterId
  val name: HunterName
  // 몬스터를 공격한다
  def attack(methodType: MethodType.Value, monster: Monster): Try[(Hunter, Monster)]
}

엔티티를 발견하는 방법


팀맴버와 시나리오에 기초하여 (아니면 유저스토리를 힌트로 하여) 엔티티에서 찾아나갑니다. 화이트보드로 가서, 팀 맴버와 의논하며 대략적인 이미지가 그려지면 바로 토레이트를 작성합니다. 처음에는 화이트보드에 모델을 그린 후에 코드를 작성하였지만 이후에 코드와의 동기화가 귀찮게 되어 버려 바로 트레이트 코드가 그림을 대신하게 되었습니다. 또, 테스트시에 Mock화 하는 경우에 구현이 번거로워 지는 경우가 있으므로 트레이트를 먼저 작성하는것을 우선하고 있습니다.

예를 들자면 다음과 같습니다.

// 헌터객체
object Hunter {
  def apply(identity: HunterId, name: HunterName): Hunter =
    new HunterImpl(identity, name)
}
private class HunterImpl(val identity: HunterId, val name: HunterName)
  extends Hunter {
  def attack(methodType: MethodType.Value, monster: Monster): Try[(Hunter, Monster)] = {
    // ...
  }
}


역자주 : 트레이트란?
스칼라(Scala)에서 사용하는 오브젝트의 한 종류. 한마디로 정의하자면 자바8에서도 가능하게 된 실행코드를 지니는것이 가능한 인터페이스라고 할 수 있다. 다만 자바8의 인터페이스는 맴버변수의 정의가 불가능한 반면, 스칼라의 트레이트는 가능하다.  자바에서는 인터페이스를 구현(implemnets)한다고 표현하는 것에 대하여 트레이트는 믹스인(mixin)한다고 표현한다.

값을 설명하기위한 값 객체Value Object

다음은 값 객체 입니다. 이것은 엔티티와는 다르게 동일성은 신경 쓰지 않고 값 자체를 설명 하는 것을 목적으로 하는 모델입니다.

헌터가 사용하는 아이템으로 설명하자면, 회복약 등의 아이템은 어떠한 종류의 아이템인가, 그 효과는? 몇 개 있는가? 에 대한 것 밖에 관심이 없습니다. 이러한 특징을 가진 모델이 값 객체에 속하게 됩니다.

아이템을 구현하는 코드로 표현하면 다음과 같은 이미지 입니다. 동일성을 나타내는 식별자는 없습니다. 그 대신, 아이템이 가지고 있는 속성이나 효능에 대한 기술이 있습니다.

// 값 객체
sealed trait Item {
  val name: String
  def beUsedBy(hunter: Hunter): Try[Hunter]
}
case class Analepticum() extends Item {
  val name = "analepticum"
  def beUsedBy(hunter: Hunter): Try[Hunter] = {
    // 헌터를 회복시킨다
  }
}
case class Antidote() extends Item {
  val name = "antidote"
  def beUsedBy(hunter: Hunter): Try[Hunter] = {
    // 헌터를 해독시킨다
  }
}
class HunterImpl(
  val identity: HunterId,
  val name: HunterName,
  val items: Set[Item]
) extends Hunter {
  // 헌터가 아이템을 사용
  def use(item: Item): Try[Hunter] = {
     require(items.exists(_ == item))
     // 아이템의 작용을 헌터에 기술하고싶은경우는
     // Visitor패턴을 사용하면 된다
     item.beUsedBy(hunter)
  }
}

다른 예를 하나 살펴봅시다. 헌터의 이름을 나타내는 Hunter엔티티의 name속성은 HunterName형 입니다. 이름 자체로는 동일성이 보장되지 않으며 값 만이 표현됩니다. 예를들면, 어떤 헌터가 이름이 kato라고 했을 때 그것이 같은 이름인지 다른 이름 인지 하나하나 신경쓰지 않습니다. 아이템과 마찬가지로 이름 또한 값 객체로서 표현 가능합니다.

스칼라로 구현을하는경우에는 간단히 case class라고 하는 선언을 하는 것 만으로 충분합니다. case class의 equals, hasCode는 엔티티와는 다르게 생성자 인자값이 같은 값 인가에 의해 구현됩니다. 즉, 가지고 있는 속성이 같은 것 인가 아닌 가로 판단합니다.

trait HunterName {
  val firstName: String
  val lastName: String
}
object HunterName {
  def apply(firstName: String, lastName: String): UserName =
    new HunterNameImpl(firstName, lastName)
}
private case class HunterNameImpl(firstName: String, lastName: String) extends Hunter



값 객체를 발견하는 방법


엔티티를 찾아냈다면, 그것이 가지고 있는 속성을 열거해나가게됩니다만, 최초의 헌터는 다음과 같은 이미지일지도 모릅니다. 헌터의 성별이나 랭크를 각가 보유하고 있는 형태입니다.

trait Hunter extends Entity[HunterId] {
  val firstHunterName: String
  val lastHunterName: String
  val hunterRank: Int
  val hunterRankPoint: Int
  val nextHunterRankPoint: Int
  /// …
}

여기서, 다음과 같이 유비쿼터스언어를 정의한경우의 코드예를 설명하겠습니다.


  • 헌터의 이름은, 성과이름이 모두 포함됩니다.
  • 헌터의 랭크에는 현재 랭크와 현재의 랭크포인트 다음랭크에 올라가기위한 포인트가 포함됩니다.

유비쿼터스언어에는, 성과이름이 포함되므로, 다음과 같이 통합해서 하나의 값 객체 HunterName을 만들면 좋을것 같습니다(이러한 개념이 아직 없다면, 팀맴버와 의논하여 유비쿼터스 언어를 만들어 둡시다).

헌터의 랭크에 대해서도 마찬가지입니다. 그렇게하면 Hunter 엔티티도 심플하게 되어 보다 응집도가 높은 값 객체가 구현됩니다.

// 이름에 대한 값 객체
trait HunterName {
  val firstName: String
  val lastName: String
}
// 랭크에대한 값 객체
trait HunterRank {
  val rank: Int
  val point: Int
  val nextRankPoint: Int
}
// 응집도가 높은 값객체를 지닌 엔티티
trait Hunter extends Entity[HunterId] {
  val name: HunterName
  val rank: HunterRank
  //
}


행위을 표현하는 서비스Service


마지막은 서비스입니다. 서비스는 서비스는 어떤 모델인가 하면, 행위(DDD번역서에서는 "연산"이라고 표현함)을 표현하는 모델입니다. 행위중에는 엔티티나 값 객체에 속하기엔 부자연스러운 표현이 되버리는 것이 있습니다. 그러한경우에는 서비스로서 표현합니다.

예를들어 헌터가 다른 헌터에게 아이템을 건네는경우를 생각 해 봅시다. 헌터가 자신으로부터 다른 헌터에게 아이템을 넘기는경우는 다음과 같은 모될이 되지 않을까요?

class Hunter {
  // 성공한경우에는, 각자 상태가 업데이트 되면서
  // Success(fromHunter, toHunter)를 반환한다.
  def transferItems(items: Seq[Item], to: Hunter): Try[(Hunter, Hunter)] = {
    // this의items로부터 인수items를 감소시킨다.
    // to.items에 인수items를 늘린다.
    // 이것은 주는쪽에서 하는것인가?
  }
  // ...
}

그리고, 넘겨주는 쪽에서는 받는 쪽에서 해야 할 동작도 함께 넘겨줍니다. 하지만, 주는 쪽이 받는 쪽의 아이템 주머니에 추가하는 행위는 자연스러운 것일까요? 이러한 행위가 헌터에게는 어울리지 않는다고 판단하는 경우에는, 아직 발견하지 못한 다른 모델은 없는지 찾아봅시다. 그렇게 해도 적당한 주인을 찾을 수 없는 경우 어떻게 하는 게 맞는 걸까요? DDD에서는 또다른 서비스를 정의하고 그곳에 행위를 소속시킵니다. 서비스는 행위 만을 표현하는 모델로서, 의외로 인위적인 이미지가 강합니다.

아이템을 넘겨주는 서비스 코드의 예는 다음과 같습니다.

object ItemService {
  def transferItems(items: Seq[Item], from: Hunter, to: Hunter):
    Try[(Hunter, Hunter)] = Try {
    require(from.has(items))
    (from.withoutItems(items), to.withItems(items))
  }
}

지금까지의 모델은 "식별(구분)", "값의 설명"을 위한 모델이었기 때문에 명사적인 표현을 했습니다만, 서비스는 행위를 나타내는 모델이므로 동사로 표현하는 모델입니다. 또, 행위의 이름은 유비쿼터스 언어와 연결 시킬 필요가 있어, 입출력을 위해 취득하는 모델은 엔티티나 값 객체에 있어야 합니다. 이들 객체가 상태를 가지기 때문에, 원칙적으로 서비스는 Stateless 이어야만 한다고 말해집니다.

컬럼 : 서비스로 할 것인가 말 것인가
 적당히 갈 곳이 없어 공중에 떠버린 "행위"를 엔티티나 값 객체에 위치 시킬 것 인가, 서비스에 포함 시킬 것 인가는 상당히 어려운 부분입니다.
 서비스를 활용하면 도메인 모델로부터 행위를 뺏어와 도메인모델 빈혈증을 일으킬 가능성이 있습니다.  하지만 억지로 가공의 모델을 만든다던지, 기존 모델에 붇여버린다던지 하는것도 문제가 있습니다. "이렇게 하면 절대로 된다"라는 만능 해결책은 잘 없기때문에 제 자신또한 늘 고민하는 부분입니다. 어느쪽인가의 설계원칙에 위배된다 하더라도, 유비쿼터스언어와 연결되는 모델링을 해 나가는것이 현실적이라고 생각합니다.
 그렇다고는 해도, 인위적인 절차로서의 서비스가 아닌 엔티티나 값 객체등의 오브젝트로서 표현 할 수 없는 것이 대상 이라고 생각되어집니다. 부연 설명을 하자면, DCI라고 하는 프로그래밍 패러다임이 하나의 수단이 될 수도 있다고 생각합니다. DCI에는 유스케이스에 대응하는 역할과 행위를 도메인모델에 투영 하는것 으로서 유저의 멘탈모델에 접근시키는 방식이 있는 듯 합니다. 예를들면, 아이템을 건네는 장면을 생각해 봤을 때, "아이템을 건넨다"라고 하는 장면(유스케스)에 대해서 헌터(도메인모델)각각에게 "건네 주는 자(Sender)"나"넘겨 받는 자(Receiver)"라고 하는 행위를 지닌 역할(롤)이 부여된다 라고 하는 사고방식입니다. 관심있으신분께는 DCI아키텍쳐 - Trygve Reenskaug and James O. Coplien 을 읽어보실것을 추천합니다.


도메인모델을 그룹화하는 모듈


 모듈은 도메인 모델을 그룹화 하기 위한 것 입니다. 이것도 모델의 한종류입니다. 객체지향언어에서 모듈을 구현하기위해서는 패키지가 주로 사용됩니다(패키지가 없는 언어라도 클래스명의 일부로서 모듈명을 넣는것에의해 구현이 가능합니다).

 모듈을 어떤 단위로 그룹화 할 것 인가에 대해서는 여러가지 패턴이 있습니다만, 가장 유명한 것은 다음과 같이 엔티티, 값 객체, 서비스등의 오브젝트 종류에 의해 모듈을 분리하는 패턴입니다(이 외에도 XXXLgoci, XXXBean과 같은 패턴으로 분리하는 경우도 있는데 이러한 분류 방법을 기술(技術)주도 패키지라고 말합니다).


  • net.gree.xxx.domain.enitity
    • Hunter, Monster
  • net.gree.xxx.domain.valueobject
    • HunterName, HunterRank, Item, ...
  • net.gree.xxx.domain.service
    • ItemService,...


DDD의모듈에서는 다음과 같이 분류합니다. 도메인오브젝트는 유비쿼터스언어에 관련된 세계입니다. 모듈의 이름도 예외는 아닙니다. 위의 예에서는 모쥴명이 entity, valueobject, service인것처럼 말입니다. 그러한 언어는 유비쿼터스언어에는 없기 때문에 명명할 수가 없습니다. 여기서 헌터관련 모듈에는 hunter모듈, 몬스터 관련 모듈에는  monster모듈이라고 부릅니다(도메인모델군의 상위개념이 발견된다면, 그 이름을 쓰는편이 좋을지도 모릅니다.)


  • net.gree.xxx.domain.hunter
    • Hunter, HunterName, HunterRank
  • net.gree.xxx.domain.monster
    • Monster
  • net.gree.xxx.domain.item
    • Item,ItemService


도메인모델은 유비쿼터스언어와 매핑된다.
저또한 익숙해지기 전에는 오브젝트의 종류에 따라서 그룹핑을 했었습니다. 정확히는 언떤게 불편한것인지를 몰랐었다고 하는게 정확한 표현이겠지요. 하지만, 잘 생각해보면 개념적으로 같은 그룹에 속한 도메인모델을 놓고봤을때 복수의 패키지를 오가면서 모델의 이미지를 연결하고 있다는사실을 깨달았습니다.  게다가 더더욱 나쁜것은, 패키지간의 의존관계가 단단하게 결합되어지고 말았다는 사실입니다. 이러한 결과, 테스트 코드 또한 작성하기가 어려운 상황이 되어버렸습니다. 본래 구현레벨의 흐름상에는 표현되지 않는, 도메인모델을 억지로 그룹핑을 하게되면, 이러한 폐해가 발생할 가능성이 잇습니다. 도메인모델은 유비쿼터스언어로 그루핑을 하도록 합시다.


모델구현에 있어서의 주의점


모델에 대한 설명의 마지막으로서, 주의해야할 사항이 한가지 있습니다. 도메인모델의 클래스명, 속성명,행위명은 모두 유비쿼서스언어와 대응해야한다는점을 잊지 말아주세요. 반대로 말하자면, 프로그래머들만 알아듣는 구현상의 언어가 등장하게 되면 어떻게 해서든지 저항해 나가야 합니다. 도메인모델이라는것은 그러한 모델입니다. 이것을 잊어버리면 기껏 팀 내부에서 정의해 놓은 유비쿼터스언어가 쓸모없게 되어버리고 맙니다.