100명 가까이 참여하는 프로젝트를 하다 보면, 작은 사이드 프로젝트와는 다르게 역할이 참 많다.
역할에 따라 번역을 할 때도 있고, 개발을 할 때도 있는 등 여러가지 일을 해 볼수록 재미가 더해지는데, Git Conflict를 한번 겪어보면 머리가 뜨거워짐과 함께 성장통이 반드시 오게 마련이다. 해결하면 재미가 2배지만, 포기하고 최후의 방법인 다시 fork뜨기, repository 삭제 등을 하다보면 git이라는 이름만 들어도 러다이트 운동을 일으키고 싶을 만큼의 분노가 발생한다.

언제 Conflict가 발생할까?

나는 3주동안 다음과 같은 팀에 참여했다.

1주차 : 번역 및 검수
2주차 : 번역 및 검수, 개발 1팀
3주차 : 번역 및 검수, 개발 2팀, 개발 3팀

번역 및 검수팀과 개발팀은 서로 역할이 다른데, 먼저 PR 프로세스와 Git 전략을 보고 가자.

PR Process

PR Process는 어렵지 않았다. 사실 어렵게 하면 안 되는게, 비개발자들도 많은 참여를 하고 있는 소스라 최대한 단순하게 가는 것이 좋다. 한 파일의 사이클은 아래와 같이 돌린다.

1주 : 개발팀 개발 -> Peer Review 1 / 2 -> Merge
2주 : 번역검수팀 Review 1 / 2 -> 최종 Merge

Git 전략

Git을 모르는 사람들이 많았어서, 깃 전략 또한 Git Flow 또는 Github Flow같은 정형화된 프로세스를 가져가지 않고, trunk-based보다는 "Fork and Pull"(사실 나도 이름모름) 전략을 따른다.

 PC            github          github organization
local ----->   origin  ------ > upstream
main            main            main

Fork and Pull(또는 Fork-based Workflow) 전략은 간단하다. Upstream(원본)에서 Fork를 해서 각 기여자가 본인의 Repository에 Origin을 구성한 후, local에서 작업해서 origin에 push을 하고, origin에서 upstream으로 PR(Pull Request)을 날려서 관리자가 Merge를 하는 방식이다. 이 방법은 단순하면서도 git 초보자가 하기에 정말 좋은 전략이다.

스멀스멀 올라오는 Conflict의 기운

Git 전략은 좋았지만, git branch에 대해서는 따로 정해진 것이 없었다. 깃 초보자가 있는 프로젝트에서 " 'feature branch'를 이용해보세요 ^^ checkout만 해주면 된답니다~" 하면 경험상 무조건 '파일이 날라갔는데요? 제 파일 어디갔죠?'라는 답변이 오기 마련이다. 나 또한 같이 해본 프로젝트가 5인 미만 프로젝트만 있거나, 기능을 혼자 가져가거나 했기 때문에 local에서는 따로 브랜치를 쓸 필요가 없어서 main 이외의 브랜치를 가져간다는 생각조차 하지 못했다.

문제는 여기서 발생했다.

보통 두가지 경우에서 문제가 일어나는데, 다음과 같다.

검수팀

검수팀의 경우, 어떤 부분에서 문제가 발생했는지 쉽게 알려주기 위해 다음과 같은 프로세스를 따른다.

local  -----> origin -----> upstream
main    push   main
작업           작업물

Upstream까지 가지 않고, Origin에 본인의 작업물을 push하고, upstream에는 issue를 발행하며 본인의 작업물을 그대로 올려서 개발자들이 어떤 부분이 바뀌어야 하는지 쉽게 알려준다. 그러면 개발팀은 2주차에 본인의 작업물을 PR해서 반영하게 된다. 그러면 local, origin, upstream은 다음과 같이 구성된다.

사람 local origin upstream
개발 A' A'  
검수 A'' A'' A''

이 때, 검수는 origin을 upstream과 맞추기 위해 sync fork를 하는데, 여기서 origin 저장소는 A'를 받아들여야 하는지, A''를 받아들여야 하는지 헷갈려서 conflict를 내거나 discard A'(A'의 커밋을 버리고 A''로 동기화하기)를 지시한다.
잔디에 관심이 없거나 커밋에 쿨한 상남자 바이킹들은 '어 커밋 버려~' 할 수 있겠지만, 깃헙 잔디 수호자나 내 '귀엽고소중하고깜찍한' 커밋이 버려지는 것을 싫어하는 사람들은 쉽게 discard 할 수 없다. 이때부터 '내 커밋을 보호하면서 upstream을 받아오는 일'에 대해 검색하거나, 내 리포지토리를 세련된 닥스훈트로 만드려 수술을 집도하지만 merge 과정에서 수없이 많은 resolve conflict를 하다 케르베로스가 되고 눈물을 흘리며 저장소를 지우고 새로 fork를 따게 된다.

개발팀

개발팀의 경우, 문제가 조금 더 복잡해진다. 위에서 설명했듯, 한 주마다 신규 개발건을 맡기 때문에 타임라인이 다음과 같아진다.

1주차 : 1주차 내용 개발 -> peer review 1 / 2 -> PR Merge
2주차 : 2주차 내용 개발 -> peer review 1 / 2 -> PR Merge
        + 1주차 내용 검수 -> 번역검수팀 review 1 / 2 -> PR Merge

1주차는 문제가 없지만, 2주차에 돌입하면 2주차의 내용을 개발함과 동시에 1주차의 검수 교정까지 들어가야 한다. 그러면 local, origin, upstream은 다음과 같이 구성된다.

 

주차 local origin upstream
1주차 A A A
2주차 A'
B
A'
B
???

의도(개발자) : "나는 A' 따로, B 따로 PR 날려야지"
현실(github) : "제가 최신꺼 다 같은 PR에 묶었어요 ㅎㅎ 잘했죠?"

이때부터 github 공포증이 돌면서 러다이트운동이 마려워지는 일이 생긴다. 내가 원하는 커밋만 보내고 싶은데, 보낼줄 모르기 때문이다.

어떻게 해결해야 할까?

사실 본인의 의도에 맞춰서 branch를 따로 따면 된다.

주의 : 초보자라면 항상 파일 백업을 따 두고 시작하자.

브랜치 생성

검수팀이라면, proofread branch를 따로 따서

1주차
           local  -----> origin  -----> upstream
main         A             A               A
proofread    A'            A'

개발팀의 검수 이후 최종 PR 결과(A'')을 local에 sync

2주차
           local  -----> origin  -----> upstream
main         A''           A''             A''
proof(1주)   A'            A'              A'
proof(2주)  A''+B'         A''+B'          

이렇게 관리하면 절대 conflict가 나지 않는다.


본인의 repository(origin)에 가서, branch를 보면 'view all branches'가 보인다.


여기 브랜치 관리에서 'New branch'를 누르게 되면


upstream / origin의 어떤 브랜치를 source root로 브랜치를 딸 지 결정할 수 있다.
위의 예시를 기반으로 위를 선택하면 A''가, 아래를 선택하면 A' 소스부터 시작하는 branch가 새로 파진다.
그러면 local에서는 해당 소스를 받아와서 새로 작업하면 된다.

git fetch origin # 내 origin의 정보를 가져오고 싶어.
git checkout -b proofread origin/tutorial # 내 local에 proofread라는 branch를 만드는데, 내 github 저장소의 tutorial 브랜치에 있는 내용을 가져오고 싶어. 그리고 branch를 proofread 브랜치로 바꿔줘.

원래 브랜치에서 개발하고 있는 내용이 있다면, 잠깐 stash라는 무적의 명령어를 사용해서 각 브랜치에 임시보관할 수 있다. 물론 개발하고 있는 내용을 현재 branch에 커밋하는게 가장 안전하지만, 혹시나 secret key같은 것이 같이 커밋되면 안되는 경우에는 stash를 사용한다.

git stash
git fetch origin
git checkout -b proofread origin/tutorial
# 작업 시작
git add .
git commit -m "작업 다함 ㅋㅋ 개꿀"
git push origin/proofread
# 작업 끝
git checkout main
git stash pop

그리고 github에 가 보면, PR을 날릴 수 있다.
나오지 않는 경우는 직접 눌러서 compare across forks 버튼을 눌러 서로 다른 저장소의 서로 다른 브랜치끼리의 PR이 가능하다. 이렇게 하면 원하는대로 특정 커밋만 보낼 수 있다.

이런 식으로 작업하면 된다. 혹시나 수술 집도가 들어갔을 경우는 아래에서 다루겠다.

이미 수술 집도가 들어간 경우

커밋이 진행됐으면 진행됐을수록, 작업을 중첩으로 했으면 했을수록 더더욱 손을 대기가 어렵다.
conflict가 나지 않으면, github(origin)에서 sync fork를 눌렀을 때 다음과 같은 화면이 뜬다.

이럴때는 무소유 정신으로 Discard 9 commit을 해서 아예 upstream과 똑같이 동기화를 해주거나, Update branch로 최신 upstream 위에 내 commit을 얹어주면 될 일이다. 그러나 문제는 다음과 같다.


이 경우에는 충돌이 나서 Sync fork가 되지 않는다. 그러면 로컬에서 충돌 해결을 한 후 동기화해줘야 하는데, 그 과정이 쉽지 않다.

나는 급하다

지금 동기화가 문제가 아니라 작업을 제출해야 하는 상황이면, 파일을 백업해두고, 브랜치를 위의 방법처럼 새로 파서 파일을 넣어서 먼저 제출한다. 추후 cherry-pick 기능을 이용해 main branch에서 해당 커밋을 가져올 수 있다.

난 너보다 더 급해서 이미 main 브랜치에 커밋을 해버렸다

이럴 때에는 체리픽( cherry-pick ) 기능을 이용해 원하는 커밋을 가지고 올 수 있다.
체리픽의 어원은 체리 피킹에서 나왔는데, 체리 피킹(cherry picking)은 '케이크 위에 얹어져 있는 체리만 집어먹는 행위'라는 뜻으로, 어떤 회사의 제품이나 서비스 중 인기 있는 특정 요소만을 골라 경제적으로 취하려는 소비 현상을 비유하는 용어였다. 깃에서는 이 단어를 가지고 내가 원하는 커밋만 가져올 수 있게끔 지원한다.
내가 원하는 이럴 때에는 다음의 방법대로 할 수 있다.

git log
---
commit 2ef0c22671ab9b72534c2f57966a4e97494f188c (HEAD -> main, origin/main, origin/HEAD)
Merge: 6f97cad 4d92521
Author: 
Date:   Mon Jan 13 22:26:25 2025 +0900

    Merge branch 'LangChain-OpenTutorial:main' into main

commit 4d92521a5378a37225b5336f470f5a09a0561fb5
Merge: 5a125c5 4731410
Author: 
Date:   Mon Jan 13 06:27:12 2025 +0900

    [GitBook] Update: Uploaded README.md file (#332)

commit 47314103c61e9e9f1e92a0d0fa2ef1101601c820
Author: 
Date:   Mon Jan 13 06:25:40 2025 +0900

    [GitBook] Update: Uploaded README.md file

    [GitBook] Update: Uploaded README.md file

git log 명령어를 입력하면, 커밋에 대한 해시번호가 나온다. 이 해시번호를 사용해서, 내가 있는 브랜치에서 명령어를 입력하면 된다.

git cherry-pick 2ef0c22671ab9b72534c2f57966a4e97494f188c 4d92521a5378a37225b5336f470f5a09a0561fb5
git add . # 또는 git add 파일명
git cherry-pick --continue
git commit -m "원하는 것만 가지고 오기"
git push origin/proofread

만약, cherry-pick 중 문제가 터졌다면 충돌을 해결하고 다시 add 후 --continue로 마무리하면 된다.
답이 나오지 않는다면

git cherry-pick --abort

로 체리픽을 중단하고 원래 상태로 돌아가면 된다.

Sync 맞추기

이제, 남은 Sync를 맞추는 일만 남았다. 도식화하면 아래의 작업을 할 것이다.

                1. fetch
   ↙─────────────────────────────────┐
 local ------->  origin  ------->  upstream
   2.   push
resolve conflict

local에서 upstream의 작업 과정을 가지고 와서, local에서 충돌을 해결한 뒤에 다시 origin에 변경 사항을 작업해 줄 것이다.

git fetch upstream
git merge upstream/main

# 충돌 해결 : IDE 또는 VIM 등...

git add file # 충돌 해결한 파일 커밋
git commit -m "resolve conflict"

git push origin main

결론

현실이 차가워도 내 깃은 더 차갑다. 위에서 설명한 것은 이상적인 시나리오고, 현재 작업하고 있는 상황에 따라 충돌이 날 확률이 높고, abort나 rebase등을 섞어 사용하면서 해결하다보면 어느새 내 완성품이 아래처럼 보인다.


이걸 설명하고 있는 나도 리포지토리를 수없이 날려봤고, 아직도 깃 명령어도 잘 모른다. branch를 새로 파는것도 2년 전쯤 한번 해보고 말았다. cherry-pick이 있는 것도 알았지만, 왜 쓰는지도 잘 몰랐고 git 전략도 불필요하다고 생각했으나 실제 상황에 직면해보니 너무나 좋은 기능이고 또 잘 알게 되었다. 이런 기회가 아니었다면 아마 더 나중에 까마득할 때 써볼 것 같아 이런 상황이 너무 즐겁고 재미있게 공부하고 있다.

'GIT' 카테고리의 다른 글

TIL - 아무것도 못 본척 해야 돼! "REVERT"  (0) 2023.02.06
TIL - 협업시 Repository는 FE/BE로 나눈다.  (0) 2023.02.06

오늘 참여하고 있는 랭체인 글로벌 튜토리얼을 제작하는 프로젝트에서 일괄적으로

!pip install package

구문을 아래와 같이 바꾸었다.

%pip install package

!pip 명령어가 돌아가지 않는것도 아니었는데, 왜 그렇게 바꿔야 했을까?

!pip install package
%pip install package 

둘의 차이는 무엇일까?
바로 (!)는 시스템 명령어, (%)는 매직 명령어라는 것이다.
OS에서 실행하는 시스템 명령어와는 달리, Jupyter Notebook이나 IPython에서 제공하는 매직 명령어는 다음 부분에서 시스템 명령어와 차이를 보인다.

  시스템 명령어(!) 매직 명령어(%)
역할 시스템 쉘 명령어 실행 IPython 및 Jupyter에서 제공하는 명령어
문법 !<쉘 명령어> %<매직 명령어>(라인 단위) 또는 %%<매직 명령어>(셀 단위)
예시 !ls %time
Python 변수 지원 Python 변수 삽입 가능 {} 대부분 직접 사용 가능
실행 환경 OS의 shell Jupyter Notebook / IPython

시스템 명령어가 있는데 매직 명령어는 왜 쓸까?

!pip install package를 ipynb파일의 code 셀에서 돌려 보면, 잘 돌아가는것을 알 수 있다. 그런데도 왜 매직 명령어를 쓰는 걸까? 이를 이해하기 위해서는, Jupyter Notebook의 동작 원리부터 이해해야 한다.

Jupyter Notebook의 동작 원리


주피터 노트북은 위와 같은 구조를 가지고 있다. (참조 : https://sawyer-jo.tistory.com/72)
주피터 노트북이 설치된 웹 브라우저(jupyter lab)나, VSC에서 제공하는 jupyter notebook extension을 설치하면, ipynb 파일 내부에 코드를 실행할 수 있는 셀이 제공된다.
이 셀을 위의 사진의 클라이언트(Client)라고 이해하면 쉽다. 이 셀에서 코드를 작성하고 run을 하면, 웹에서는 제공되는 앱 서버, 로컬에서는 로컬 앱 서버에서 '커널'이라는 핵심 컴포넌트에서 코드를 실행하게 된다.

커널

커널은 위에서 설명했다시피, 실제적으로 코드를 실행시키는 파이썬 인터프리터(엔진) 위에 존재한다. 사용자가 작성한 코드를 받아서 파이썬 인터프리터에 코드를 가져다 주고, 그 결과를 반환받아 Jupyter Notebook이라는 사용자 인터페이스에 포매팅하여 제공한다. 다음의 비유로 이해하면 쉽다.
Python Restaruant

  • 손님 : ipynb 셀
  • 웨이터 : 커널
  • 요리사 : 파이썬 인터프리터
  • 요리사마다 다른 주방 : 가상 환경
    Jupyter에서는 프로그램 언어별 여러가지 커널을 지원하지만, 가장 많이 사용되는 커널은 Python용 ipykernel이다.
    통합 환경에서 커널을 설치하였다면 다음과 같은 명령어로 확인할 수 있다.
    pip show ipykernel

그리고, python이 여러 개 설치되어 있거나, 가상 환경을 이용 중이라면, 프로젝트 내부에서 명령어로 확인할 수 있다.

PS C:\Users\User\Documents\langchain-kr> pip show ipykernel

Name: ipykernel
Version: 6.29.5
Summary: IPython Kernel for Jupyter
Home-page: https://ipython.org
Author:
Author-email: IPython Development Team <ipython-dev@scipy.org>
License: BSD 3-Clause License

...

환경 통합

따라서, 프로젝트마다 다른 커널을 사용할 수 있기 때문에 패키지의 분리가 중요하다. pip install package는 위의 예를 기반으로 하면, 요리사가 새로운 재료를 시장에서 사 오는 행위라고 생각하면 된다.

%pip install을 하는 경우, 현재 Jupyter Notebook의 kernel의 가상 환경 위에 패키지를 설치한다. 각각의 요리사가 레스토랑 규정에 따라 재료를 요청하고, 그 재료를 자신의 주방에 넣어놓고 쓰는 행위라고 볼 수 있다. 매직 명령어는 현재 활성화된 Jupyter kernel의 환경에만 패키지를 설치하는 것을 원칙으로 하기 때문이다. 커널이 가상 환경을 사용 중이라면, 가상 환경에서의 설치를 보장한다.

반면 !pip install은 기본 환경에 설치할 수 있어 의도하지 않은 위치에 패키지가 설치될 수 있다.
요리사가 레스토랑 규정을 무시하고 시장에서 재료를 사 와서 공용 주방이나 레스토랑이 대충 관리하는 주방(전역 python 환경 또는 기본 설정된 pip와 연결된 환경)에 설치하는 행위라고 볼 수 있다.

!pip install은 잘못된 방식은 아니다. 그러나 Jupyter 커널이 가상 환경을 사용중인 경우, !pip install은 가상 환경이 아닌 시스템 Python 환경에 패키지를 설치해버릴 수도 있기 때문에, 잘못하다가 패키지가 꼬여버릴 수가 있어 %pip install을 권장한다.
패키지가 충돌하거나, Jupyter kernel이 패키지를 찾지 못하면 그 때부터는 문제가 더 어려워지기 때문이다.

추가 기능성

매직 명령어는 IPython 커널에 의해 해석되기 때문에, 추가 기능을 제공한다.

%lsmagic

으로 다양한 매직 명령어를 확인할 수 있다.
jupyter lab 또는 본인 ipynb 파일에서 %lsmagic 명령어로 확인해보자.

 가끔 커피챗 등으로 개발자들을 만나거나, 개발 업무에 대해 상대방에 설명해야 할 때가 있다. 그럴때면 가끔 듣는 말이 있다.

개발에 정말 열정적이시네요!

 

그런데, 나는 생각보다 개발에 열정이 있지는 않다. 오히려 요즘은 퇴근 후 집에 누워서 유튜브 보면서 드러누워있는게 일이라 오히려 반성하고 있을 정도다. 그런 내가 정말 열정적으로 개발에 임할 때가 있다. 보통 둘 중 하나인데, 다음과 같다.

  • 아무것도 모를 때
  • 분노했을 때

 아무것도 모를 때는 열심히 하는게 당연하다. 예를 들어 회사에서 JSP에서 React.js로 코드 마이그레이션 업무를 받았을 때, 나는 Java만 알고 Javascript에 대해서는 무지했으므로, 알지 못하면 일을 할수가 없어서 살아남기 위해 진짜 열심히 개발공부를 했다.

 그리고 오늘 얘기할 부분, '분노했을 때'이다. 부트캠프에 다니면서 정말 하루에 잠자는 시간 외에는 정말 공부만 했었다. 물론 개발이 재밌어서도 있고, 좋은 동료를 만나서도 있지만 그 이전에는 기저가 있었다.

 

 내가 수료한 부트캠프를 다니기 이전에, 다른 부트캠프를 다녔었다. 어떤 백엔드 언어의 바이블같은 책이 있었는데, 그 책의 저자가 직접 만든 부트캠프를 먼저 갔었다. 가격도 정말 비쌌다. 조금만 과장을 더하면, 지금 수료한 부트캠프의 전체 수강료가 그 부트캠프의 1달 수강료와 맞먹었다. 내가 모아둔 돈을 다 털어서 갔기에 정말 열심히 하려고 했고, 간절한 마음으로 갔다. 질문도 많이 하고, 매주 보는 시험에서 항상 최상위권의 성적을 유지했다.

 

 문제는, 다닌 지 2주일만에 학원에서 잘리게 되었다. 사유는 간단하고도, 어이가 없었다. '질문을 너무 많이 해서.' 처음에는 내가 다른 이유로 잘못했나 싶었다. 왜냐면 다닌지 1주일만에 학원에서 잘렸던 사람이 있었는데, 잘린 사유가 '질문을 너무 안 해서'였으니까. 이 글을 보는 사람들도 내가 잘못된 질문을 했겠거니 할 수도 있겠지만, 나는 맹세코 개발에 관련된 쓸데없는 질문을 하지도 않았고, 또 무슨 수다맨마냥 너무 여러번 질문을 하지도 않았다. 잘리던 날 내가 마지막으로 했던 질문은 '선생님, 컴퓨터는 1초를 어떻게 세나요?' 였다. 지피티도 없을 때라, 질문에 대한 해답을 개인적으로 찾아보기도 어려웠고, 블로그에 정말 많은 잘못된 정보를 봤던 때라 가장 빠르고 확실하게 정답을 알아낼 수 있는 방법은 질문 뿐이었다.

 

  당연히 잘리던 날 거의 울면서 '앞으로는 질문을 절대 안 하겠습니다' 라는 말을 해도, 그 외곬수의 마음에는 이미 내가 없었다. 무슨 짓을 해도 안 된다고 했고, 다음날 학원에서 만난 동기들은 너무나 의아한 표정으로 나에게 물어봤다. '니가 잘렸다고? 도대체 왜?' 그렇지만 나는 '질문을 너무 많이 했대'라는 말 밖에는 할 수가 없었다. 문제는 그 다음이었다. 환불 처리에서도 문제가 있었고, 음식점에 컴플레인 한번 걸어본적 없던 내가 국민신문고에까지 글을 남겼었다. 그제서야 일부 환불을 받을 수 있었다. 자기가 잘라 놓고. 그 후로는 머릿속에 단 한가지 생각밖에 안들었던 것 같다. '내가 정말 어마어마한 사람이 되어 복수해 주겠다'고. 그렇게 정말 한동안 미친듯이 공부했었던것 같다. 물론, 새로운 부트캠프에서 만난, 내 새로운 개발 인생을 열어준 매니저님이 '질문하시면 저도 답변해드리면서 공부할 수 있어서 좋으니, 질문을 꼭 해주세요'라고 말씀해주셨던 부분도 컸지만.

 

 그렇게 열심히 부트캠프를 다녀 최상위 수료생으로 마친 뒤, 몇 달간을 쉬고 첫 회사에 들어갔다. 쉬운 결정은 아니었다. 기존의 사업을 할 때보다 연봉이 1/3토막이 되었으니까. 그래도 원하던 연봉이 있어서(사규 신입 연봉테이블과 크지 않았다) 되지 않을까 하고 여쭈어봤는데, 답변이 충격적이었다. '보여준 일이 없으니 그만큼은 주지 못 할것 같아.' 그 날부터 내가 어떤 사람인지 보여주겠노라고 회사 입사 이후 정말 궂은일 가리지 않고 이 일, 저 일을 다 했다. 물론 불만은 있지만, 그래도 회사에서 백엔드, 프론트엔드, 또 AI 트랙까지 모두 경험해볼수 있었던것이 너무 감사하고 좋았다. 

 

 돌아보니, 그냥 열심히 살았던 것 같은데, 어떻게 보면 분노가 이끄는 삶을 살았던 것 같다. 나는 분노를 부정적 스트레스로 풀지 않고, 긍정적 스트레스로 받아들여 더 열심히 살고자 하는 경향이 강하지만, 그래도 분노가 주 원동력이 되는 것 보다는 앞으로는 재미를 원동력 삼아 나아가고자 한다. 세상에 '노력하는 자는 즐기는 자를 이길 수 없다'는 속담은 있지만, '즐기는 자는 분노하는 자를 이길 수 없다'는 속담은 없으니까.

'잡설 > 회고' 카테고리의 다른 글

24년도 4월 회고  (0) 2024.05.05

 

 나는 예전부터 우리집에서 '마이너스의 손' 또는 '파괴왕'으로 유명했다. 집에서 물건을 고친다고 고쳐보면 항상 물건이 박살나기 일쑤였고, 뭘 만들때도 항상 손이 떨려 제대로 만들 수 없었다. 이 특성은 아마 어머니로부터 유전받은것이 분명한데, 아버지는 옛날부터 항상 물건을 잘 고치셨고 내 동생들 또한 그랬기 때문이다.

 

 이게 무슨 상관이야? 하는 사람들도 있겠지만 이 이유로 인해 개발자가 되기 전까지는 하드웨어 부분에서 아예 손을 떼고 동생들에게 모든것을 일임했다. 개발자가 되어서야 비로소 필요에 의해 조금씩 하드웨어를 손대다보니, 조금씩 흥미가 짙어져 이제는 조금씩 장비들을 사서 연결해보고, 조립해보고 있다.

 

 이사를 조금 더 큰 집으로 간 후, 기존과는 달리 집에 네트워크가 잘 통하지 않았다. 집의 구조도 구조라, 작은 방에 모뎀을 넣어야 하길래 넣었는데, 내 방까지 인터넷 신호가 전달되지 않았다. 부랴부랴 저 네트워크를 끌어올 수 있는 방법을 생각했고, 메쉬로 집안에 와이파이를 채울 것인지, 아니면 증폭기로 무선 신호를 연결할 것인지 선택해야 했다.

 

  선택 : Mesh vs Extender

먼저 방법을 확인하니, Mesh 시스템으로 집안에 균등하게 와이파이를 구축하거나, Extender로 기존의 무선 신호를 확장할 수 있다.

Mesh와 Extender의 차이는 다음과 같다.

  Mesh Extender
(상대적)가격 비쌈 저렴함
초기 세팅 복잡함 비교적 쉬움
성능 데이터 손실이 거의 없음 디바이스가 많이 연결될수록 성능 저하 있음

 

외에 다른 차이도 있지만, 위 3가지 조건을 보고 결정했는데, 처음 이사할 때만 해도 회사 일도 스프린트를 몰아서 하던 시기였고 이래저래 돈이 나갈데가 있어 나는 Extender로 결정했다.

지금 생각해보니 그냥 Mesh 할걸, Extender의 가격이 훨씬 저렴해졌다. 지금 찾아보니 3달만에 25% 할인이라니 말도안돼...!

 문제는 하나 더 있었는데, 아까 설명했던 귀차니즘과 더불어 iptime을 치니 나오던 수많은 모델들이 가격도 천차만별이고 모델명도 이상해서 대체 뭔지 몰랐기에 그나마 쉬워보이는 무선확장기를 구매했었다.

그리고는 동생을 불러 설치를 시키고 동생이 이렇게 하면 돼~ 라는 말만 듣고 몇달이 지났다. (생각해보니 아직도 '자발적' 하드웨어 실험으로의 태도는 좀 먼것 같다.)

이번에는 데스크탑을 내 방에 설치할 일이 있었는데, 데스크탑에는 항상 유선 랜만 꽂아두고 사용했어서 무선 랜카드를 사야했었다.

 집에는 2.5GHz를 지원하는 와이파이와 5G를 지원하는 와이파이가 동시에 출력이 되어 일단 랜카드를 사러 다이소에 갔는데, 5G는 지원하지 않지만 2.5GHz를 지원하는 저렴한 최대 300ms짜리 무선 랜카드를 팔길래, 사온 뒤 pc에 꽂고 오랜만의 휴가에 게임을 잠깐 했었는데, 연결이 너무 잘 안되길래 도대체 이게 속도가 얼마야 하고 보니 10ms가 찍히는 것이었다.

 믿을 수 없는 속도에 쿠팡으로 와이파이 속도가 문젠가 싶어서 보니 현재 wi-fi6까지 찍히는 무선 랜카드를 팔길래 구매했다. 그런데 무선 랜카드를 꽂아도 속도가 너무 안 나오길래 그제서야 부랴부랴 문제점이 뭔지 찾기 시작했다.

 

 문제 1. 세팅을 해두지 않은 와이파이

 첫 번째 발견은, 결론부터 말하자면 무선 확장기에 와이파이 세팅이 되지 않았었다. 저 확장기는 2.5GHz와 5GHz를 모두 수신해서 신호를 송신할 수 있는 모델인데, 5GHz 수신기에 불이 들어오지 않았다. 분명히 와이파이 이름은 iptime_extender_5G였는데, 그것만 믿고 착각을 한 것이다. 

사용한 모델의 무선 확장방식은 여러개인데, 입력으로 들어온 2.4Ghz와 5Ghz를 이용해 여러가지 노선으로 신호를 확장시킬 수 있었다.

찬찬히 설명서를 읽고 따라해보니, 기존에 있던 2.4Ghz를 받아와서 위 사진의 빨간 박스처럼 2.4Ghz와 5Ghz로 뿌리고 있었던 것이다. 어쩐지 속도가 너무 안나오더라니... 세팅만 해 두니 바로 속도가 10배 올라갔다. 

 

 문제 2. 국제 규약

 속도는 해결했고, 집의 모뎀에서 나오는 와이파이가 Wi-Fi 6으로 나오는것도 인지했는데 어째선지 5G로 연결해봐도 Wi-Fi 6이 세팅되지 않았다. 문제는 국제 규격에 있었다. 찾아보니 국제 규약은 다음과 같다.

규약명 (구) 규약명 주파수 특징
Wi-Fi 1 802.11b 2.4Ghz  
Wi-Fi 2 802.11a 5Ghz  
Wi-Fi 3 802.11g 2.4Ghz  
Wi-Fi 4 802.11n 2.4Ghz & 5Ghz Dual-Band 지원
Wi-Fi 5 802.11ac 5Ghz Giga-Wifi 지원 시작
Wi-Fi 6 802.11ax 2.4Ghz & 5Ghz + 1~6Ghz ISM  

쿠팡에서 구매할 때 모델들이 전부 다 AC-1000M AX3000U 이런식으로 되어 있길래 그냥 모델 명이 저런건줄 알았는데, 알고 보니 국제 규약이었다. 내가 구매한 모델을 보면 iptime "AC1200" "Giga-Extender" 이므로 Wi-Fi 5에 맞춘 모델이었던 것이다. 그래서 아무리 모뎀이 Wi-Fi 6을 지원해도, 무선 확장기가 Wi-Fi 5를 이용하기에 Wi-Fi 6이 되지 않았던 것이다.

  추가)

 윈도우를 기준으로, CMD에서 다음의 명령어로 인터페이스 및 드라이버를 확인할 수 있다.

$ netsh wlan show interfaces

시스템에 1 인터페이스가 있습니다

이름          : Wi-Fi 2
설명          : Realtek 8832CU Wireless LAN WiFi 6 USB NIC
...
SSID         : U+Net9443_5G
...
송수신 장치 종류 : 802.11ac
인증          : WPA2-개인


$ netsh wlan show drivers

인터페이스 이름 : Wi-Fi 2

드라이버 : Realtek 8832CU Wireless LAN WiFi 6 USB NIC
...
지원되는 주파수 형식 : 802.11b 802.11g 802.11ac 802.11ax 802.11a
...

이제 내 와이파이 규약명이 무엇인지, 또 내 랜카드는 어느 Wi-Fi를 지원하는지 이해가 됐다.

 

문제 3. SSID

 다시 연결하려고 보니, 분명히 세팅 전에는 iptime_giga_extender5 와이파이가 있었는데 세팅을 하고 나니 없어졌길래 잘못 세팅한 줄 알고 한참을 찾았는데, 찾고 보니 세팅이 완료되면 기존의 SSID를 따라가고, 한 주파수에서 다른 주파수를 분리하면 그제서야 SSID가 따로 세팅된다고 되어 있었다. SSID가 무엇인지는 문제 2번의 cmd에서 확인해 보니 기존 사용하던 와이파이의 이름이었다.

 

 결론

 생각보다 찾으면 금방 나오는 정보들임에도 불구하고, 귀찮거나 바쁘다고 또 습관처럼 미뤄놨던 나를 반성한다. 알고 나니 집에 쌓여있던 랜카드나 공유기 모델명이 이제 무슨 모델을 지원하는지, 또 네트워크가 어떻게 돌아가고 있는지 잘 확인이 된다. 앞으로는 네트워크 말고도 조금 더 하드웨어 레이어에 있는 것들을 만져보면서 탐험해야겠다. 또 글을 쓰면서 다시 한번 제품설명을 보니 iptime Easy Mesh Agent를 지원한다는데, 다음에 시간될때는 동일 기기를 Extender로 쓰느냐, Mesh Agent로 쓰느냐에 대해 포스팅하겠다.

Ubuntu와 같은 Debian 계열의 리눅스에서는 여러 버전의 프로그램들이 설치되어 있을 때 기본으로 사용할 프로그램을 선택하고 관리하는 데 사용되는 명령어입니다. 이 명령어는 주로 다양한 버전의 자바, 에디터, 컴파일러 등 여러 버전이 설치된 프로그램들 사이에서 하나의 기본 프로그램을 설정하는 데 사용됩니다.

Update-Alternatives의 사용법

선택

자바로 예를 들자면, 간단하게 사용할 수 있습니다.

sudo update-alternatives --config <name>
sudo update-alternatives --config java
# 예시 결과
ubuntu@ip-:~/$ sudo update-alternatives --config java
There are 2 choices for the alternative java (providing /usr/bin/java).

  Selection    Path                                         Priority   Status
------------------------------------------------------------
  0            /usr/lib/jvm/java-11-openjdk-amd64/bin/java   1111      auto mode
  1            /usr/lib/jvm/java-11-openjdk-amd64/bin/java   1111      manual mode
* 2            /usr/lib/jvm/jdk-17.0.12/bin/java             1         manual mode

이렇게 openJDK 11과 Oracle JDK 17이 깔려있을 때, '시스템 전반적으로' 사용할 자바 버전을 지정해 줄 수 있습니다. 물론 자바 컴파일러도 같습니다. 위의 shell 명령어에서, --config java 대신 --config javac를 입력해주면 컴파일러도 지정이 가능합니다.

등록

Selection에 새로운 자바 버전을 등록하고 싶다면, 다음과 같이 할 수 있습니다.

sudo update-alternatives --install <link> <name> <path> <priority>
sudo update-alternatives --install /usr/bin/java java /usr/lib/jvm/~

이 외에도 display로 특정 목록을 보여준다든지, remove로 Selection에서 제거한다든지, --auto로 Priority가 가장 높은 대체 프로그램을 선택한다든지 등을 할 수 있습니다.

주의할 점

update-alternatives와 SDK 관리 도구(예 : SDKMAN)는 서로 독립적으로 작동합니다. 따라서 update-alternatives로 설정된 자바 버전이 SDK 관리 도구에서 설정된 자바 버전보다 우선적으로 작용할지는 상황에 따라 다릅니다.

SDK를 통해 활성화된 자바 버전은 현재 세션이나 쉘에서만 적용됩니다. 예를 들어 터미널 세션에서 sdk use java <version> 명령어로 설정한 자바 버전은, 그 세션에서만 유효합니다.

그러나 update-alternatives에서 사용되는 자바는 시스템 전반에서 사용되는 기본 자바 버전을 설정합니다.

따라서 현재 터미널 세션에서는 SDK 관리 도구에서 설정해준 자바가 우선되지만, 시스템 전체에서는 update-alternatives가 우선됩니다.

예를 들어, 터미널 세션을 열고 들어가서 자바 파일을 실행하는 경우에는 SDK 관리 도구에서 설정해준, --version으로 확인할 수 있는 자바 버전이 우선이지만, 직접 터미널을 통하지 않고 CICD 툴들을 통해 .class파일로 만든 파일들을 컴파일하는것은 update-alternatives에 설정되어 있는 자바가 합니다.

추가 : '세션'이란?
여기서 말하는 세션이란 사용자가 터미널 창을 열고 그 안에서 수행하는 작업의 기간을 의미합니다. 세션은 터미널 창이 열려있는 동안 지속되며, 터미널 창을 닫으면 세션이 종료됩니다.
터미널 세션은 사용자가 터미널을 열고 명령어를 입력하는 기간이며, 세션은 터미널 창이 열려 있는 동안 계속됩니다. 사용자가 터미널 창을 닫으면 그 세션도 종료됩니다.
터미널 세션 동안 사용자가 설정한 환경 변수나 경로는 그 세션에만 영향을 미칩니다. 예를 들어, export PATH = ~ 명령어로 PATH 환경 변수를 수정하였다면, 그 변경 사항은 현재 세션에만 적용되고, 터미널을 닫게 되면 초기화됩니다.

'CS' 카테고리의 다른 글

도커 이미지를 구워주세요? - 굽는다는 것은 무엇일까?  (0) 2024.07.22

Docker에서나 다른 툴에서 이미지를 생성할 때에 많은 사람들이 이미지를 생성한다고도 하고, 굽는다고도 한다.
어릴 때, Daemon tools 등으로 CD 안에 들어있는 내용물을 .lcd 또는 .iso로 변환해서 데이터로 만드는 과정을 '씨디를 굽는다'라고 했는데, 관련이 있을까?

CD의 구조


CD-ROM의 단면을 기준으로 보호층, 반사층, 기판, 인쇄층으로 구성된다.

  1. 보호층(Protective Layer) : 폴리카보네이트 기판 위의 가장 상단에는 레커(lacquer)로 만들어진 얇은 보호층이 있어 물리적 손상이나 산화로부터 CD를 보호한다.
  2. 반사층(Reflective Layer) : 기판과 보호층 사이에는 반사층이 존재한다. 알루미늄(또는 구리나 금)으로 만들어진 이 반사층은 레이저 빛을 반사하여 데이터를 읽을 수 있도록 한다.
  3. 기판(Polycarbonate Substrate) : 이름부터 알 수 있듯 폴리카보네이트 플라스틱으로 만들어져 있고, 레이저가 데이터를 읽을 때 통과하는 역할을 하며 디스크의 물리적 강도를 제공한다.
  4. 라벨(Label) : CD의 상단 표면에는 레이블이나 인쇄된 정보를 자외선으로 건조시킨다.

CD는 데이터를 어떻게 기록할까?

하드 디스크나 플로피 디스크가 디스크 표면에 자성물질이 발라져 있어서, 액추에이터의 헤드에서 자기장을 형성해 자기장의 방향에 따라 0과 1이 기록된다는 사실은 많이 알려져 있다. 그러나 CD는 디스크의 표면의 홈에 레이저 빔을 이용해 발사하고, 반사 굴절효과를 이용해 디지털 정보를 기록한다.
이 때 CD의 기판과 반사층 사이의 염료층에 레이저를 발사해 염료를 태우는 과정에서 열이 많이 발생해 이를 보고 burn(굽는다)는 표현이 나왔다.

추가 : CD의 데이터 읽기




CD에 저장된 데이터를 읽으려면 CD-ROM 드라이브에서는 레이저 빔을 쏴서 반사층의 표면에서 굴절된 레이저 빔을 렌즈를 통해 빛의 길이나 파동 등을 측정한다. CD에는 디스크의 표면에 데이터를 저장하기 위해 반사층과 기판 사이에 미세한 홈(피트)와 평평한 표면(랜드)가 있다.

  • 피트(pit) : 실제적으로 데이터를 저장하는 부분으로, 레이저 빔이 디스크 표면을 태워 만들어진 홈이다. 레이저가 피트에 도달하면 빛이 산란돼서 반사되는 양이 감소한다.
  • 랜드(land) : 레이저 빔이 발사되는 부분으로, 레이저가 랜드와 피트의 반사 신호 차이를 감지해서 데이터를 읽어낸다.
    위에서처럼, 레이저를 쏴서 피트에서 랜드로 또는 랜드에서 피트로 변환되는 부분은 1로, 변화가 없는 부분은 0으로 데이터가 해석된다.

'CS' 카테고리의 다른 글

Update-Alternatives  (0) 2024.07.29

4월 회고록

 

 4월은 정말 바빴다. 부트캠프 튜터와 함께 회사에서 첫 메이저 개발업무에 투입되었다.

부트캠프 튜터는 내가 배웠던 대로, 가르침을 받았던 대로 치어리딩과 함께 기술지원 업무만 있는 줄 알았는데 커리큘럼이 바뀌어서 그런지 먼저 알고리즘을 3주동안 가르치게 되었다. 다행히도 알고리즘은 백준 골드1을 쌓아놔서 이해하는 데에는 어렵지 않았지만, 하루에 매일매일 알고리즘을 최대 7문제씩 풀어야 하니 3시에 취침 ~ 7시에 기상하는 날이 많았다.

정말 고된 나날이었지만, 한편으로는 정말 즐거웠다.

 처음 개발공부를 하러 부트캠프에 갔을 때 누가 시키지 않았어도 항상 공부하느라 새벽 5시에 취침하고 오전 9시에 기상하고, 너무 바빠서 밖에 나가지도 못했었던 생활을 지속하다가 몸이 아파서 취직 전까지는 매일매일 멍하니 하루를 보내는데 허송세월을 보냈다. 취직을 해서도 물론 매일매일 공부했지만, ‘잠이 세상에서 제일 달다’는 다시는 못 느껴봤었다.

 

 우테코 입학 설명회 영상을 본 적이 있었는데, 포비님께서 ‘몰입 경험’에 대한 강조를 하셨었다. 그 때에는 나에게 있어서 ‘몰입’이라는 것이 얼마나 크게 다가왔는지 못 느꼈는데, 이번에 정말 바쁜 삶을 살면서 예전 부트캠프 때가 생각나 몸은 힘들었지만 새삼 행복함을 느꼈고 동시에 나의 몰입 경험이 바로 그 시절이었다는 것을 떠올렸다.

 알고리즘 주간이 끝나고는 MSA에 대해 튜터를 해야 했다. 멀티 모듈 프로젝트를 해 본적은 있었지만 MSA에 대해서는 만져본적이 없어서 당황했지만 어찌됐든 수강생들을 이끌고 프로젝트에 대한 도움을 줘야 했다. 그래서 그때부터 MSA에 대해 정말 많이 공부를 했다. 주말에는 MSA와 관련된 DDD에 대해 세션까지 들었다. DDD 세션을 듣고 나니 MSA도 MSA였지만, ‘개발이란 무엇인가’와 함께 회사에서 작성 중인 기고문에 대한 블루프린트가 명확해져 좋았다.

 회사에서는 Agile PMS 개발을 진행했다. PMS는 예전에 부트캠프에서 최종 프로젝트로 B2B SaaS 형태의 PMS를 개발해봐서 플로우는 대충 이해했지만, 회사 자체의 도메인이 정말 어려워서 개발하는데 힘이 들었다. 다만 Agile PMS가 아닌 다른 프로젝트가 이미 개발되어 있어서 참고를 많이 했다.

 개발하면서 힘들었던 점은 Query에 대한 이해도가 낮아서 문제가 되었다는 것이다. 나는 QueryDsl+Spring Data JPA를 배우고 회사에 들어왔는데, 회사에서 구버전으로 개발 시Mybatis+native query를 이용하기도 하고, 기존 쿼리들 중 recursive query가 많아서 이런것들을 처음 봐서 매우 어려웠다. 그래서 원래 공부 계획은 Kafka + Lang chain을 이용한 LLM 프로젝트를 혼자 해보고 싶었는데, 5월부터 신버전에 들어가는 jsp → react migration 작업만 하고 kafka보다는 java와 db에 대한 이해도를 높이는 것이 우선이라는 생각이 들었다.

 회사의 도메인이 법때문에 굉장히 핫해져서 그런지, 회사의 크기도 엄청 커지고 이에 따라 신입분들도 많이 들어오고 계신다. 다음달에만 해도 신입 사원 10명을 추가 충원한다고 하니, 곧 100명 사원을 보유한 회사가 되는게 머지 않은 것 같다. 그래서 지금보다 더 노력해야 어떤 사람이 들어온다 해도 나의 identity와 irreplaceable함을 증명할 수 있을 것 같다.

 5월에는 신규 프로젝트로 기존 jsp로 되어있던 프론트엔드 소스들을 react로 migration해야 한다. 나는 프론트엔드를 해본 적이 없기에 정말 기대가 되고 열심히 공부해야 할 것 같다는것을 온몸으로 느끼고 있다. 항상 겸손의 자세로 빠르게 배워서 회사에 도움이 되는 좋은 소스코드를 만들어내고 싶다.

화이팅!

'잡설 > 회고' 카테고리의 다른 글

분노는 나의 힘  (2) 2024.12.22

NEXTSTEP에서 24/04/20~21 진행했던 '도메인 주도 설계의 사실과 오해' 수업을 들었다.

멘토했던 부트캠프의 학생들이 프로젝트 주제를 MSA로 잡았는데, 내가 MSA에 대해서 알고 있는것도 없기도 하거니와

무언가 알려주고 싶은데, MSA와 DDD는 항상 붙어다니는 사이기도 하고 옛날 김영한 강사님의 인강을 들을 때 항상

조영호 강사님의 '객체지향의 사실과 오해' 시리즈를 추천해주셔서, 어떤 생각을 가지고 계신지 궁금하기도 했다.

 

결론적으로 이틀 수업하기에 나름 비싼 가격이었지만, 짧은 시간동안 정말 많은 것을 깨달았다.

아래의 내용들은 추후 코드로 예제를 만들어 블로그에 기고할 생각이다.

 

많이 와닿았던 부분은 다음과 같다.

1. 설계에 정답은 없다.

나름 큰 충격이었던게, 마음 속으로만 아키텍쳐 설계란 대체 무엇일까? 라고 생각만 하고 있었는데, 나름 무엇인가 해답을 찾으러 간 수업에서 설계에 정답은 없다는 이야기를 들으니 새삼 충격으로 다가왔다.

 

2. DDD는 방법론, 프레임워크가 아닌 '사고방식'에 가깝다.

그러나 설계에 정답은 없다는 말이, 설명을 들으면 들을수록 점점 와닿게 되었다. DDD는 강제되거나 '그래도 이 방법이 맞다'고 하는 것이 아니라, 베스트 프랙티스를 제시해 놓고 본인이 마주하는 도메인에 따라 테일러링을 해서 사용해야 하기 때문이다.

3. 도메인이란?

'도메인이란 무엇이다'라고 확실히 정의할 수는 없지만, 정리하자면 도메인은 '현실 세계'이다. 개발자들의 직업병인지 모르지만, 은행 거래처럼 가용성과 확장성을 포기해서라도 같은 트랜잭션 안에 도메인에서 정답이라고 생각하는 플로우들을 모조리 때려박거나, 트랜잭션 일관성(Transactional Consistency : 모든 노드의 데이터를 같게 한다)이나 각 DB의 Sync를 실시간으로 맞추기 위해 정말 많은 노력을 기울인다. 그러나 현실은 그렇지 않다. 유저들은 때로는 본인이 중요하지 않다고 생각하는 것에서는 많은 주의를 기울이지 않을 수도 있고, 시장에서 물건을 거래하면서 계획에 없던 흥정을 만들어내기도 한다. DDD는 이 현실적인 도메인 문제를 도메인 전문가와 개발자가 서로 합치된 개념을 유비쿼터스 언어로 녹여 코드에 최대한 비슷하게 반영하는것에 가깝다. 마치 우리가 알고리즘을 보면서 한 문장 한 문장 중요하다고 생각하는 부분을 머릿속에 그려내 그것을 코드에 녹여내는 것처럼. 또한, 도메인은 쉴새없이 빠르게 바뀌기 때문에 애자일 스타일의 개발 방법론과 정말 잘 맞다. 

4. 불변식(Invariants)

수업을 듣고 DDD에서 가장 중요한 한 가지를 꼽으라면, 바로 '불변식'을 꼽겠다.

객체의 State와는 상관없이 만족해야 하는 도메인의 Rule이자, 객체의 State 변화는 바로 이 '불변식' 아래에서 움직여야 한다. 불변식이 중요한 이유는, 불변식에 맞춰서 Aggregate를 짜고, 트랜잭션을 묶고, 식별자로 추적해야 하는 Entity와 값만 같으면 추적할 필요가 없는 Value Object를 설계하고, 리포지토리 단위를 두어야 하기 때문이다. 이 불변식의 개념을 몰랐기 때문에 예전 프로젝트를 진행할 때 모든 엔터티에 대해 리포지토리를 만들어줬고, 어느 값 객체를 Collection으로 담아주어야 하는지 몰랐다. 

5. 애그리거트(Aggregate)

애그리게이트, 또는 애그리거트라고도 불리는 이 개념은 DDD에서 핵심이 된다. DDD도 결국엔 라이프사이클이 존재하고, 이 라이프사이클을 관리하기 위한 빌딩 블록(베스트 프랙티스)중 하나인 애그리거트는 불변식을 만족하는 최소한의 그룹이다. 따라서 불변식이 깨지지 않게 하기 위해서는 Root Aggregate를 설정하고, 이 Root Aggregate가 불변식의 만족을 담당하는 '책임'을 져야 한다. 애그리거트를 계획할 때에는 주의해야 하는 것이, 애그리거트 단위로 트랜잭션을 돌리고 그 외에는 Event로 돌려야 하기 때문에 이 애그리거트 단위로 불변식을 이행하는지를 잘 살펴야 한다.

Command와 Event의 차이는 '취소가 가능한가?'이다. Event는 이미 일어난 일을 기준으로 발행되기 때문에 취소를 할 수가 없다. 

6. 왜 DDD는 MSA / 헥사고날 아키텍쳐와 연관되는가?

결국 이 질문에 대한 답을 듣기 위해 수업을 들은 것과 마찬가지다. 아마 모놀리식 아키텍쳐에서 멀티모듈 아키텍쳐로, 그곳에서 더 진화해서 마이크로서비스 아키텍쳐로의 진화를 꾀하는 프로젝트의 가장 큰 물음은 다음과 같을 것이다:

도대체 어떤 기준으로 서비스를 찢어야 하는가?

 

그래서 때로는 Layer 단위(Service, Database, ... )로, 때로는 Vertical하게(주문, 결제 등의 서비스 위주) 나눈다. 그러나 그 원칙은 Bounded Context( 쉽게 생각하면, "니가 이 도메인을 가지고 팀을 나눈다면 어떤 팀으로 나눌 건데?"로 생각하면 쉽다.) 가 적용되는 단위가 된다. 바운디드 컨텍스트란 특정한 도메인 모델이 적용되는 범위로, 같은 바운디드 컨텍스트 안에서는 도메인 모델의 통합성을 유지하고, 서로 다른 바운디드 컨텍스트 사이에서는 통합성(Integration)을 신경쓰지 않는다.

다만, 컨텍스트 맵(Context Map)을 통해 서로 다른 바운디드 컨텍스트 사이의 관계와 모델 변환 방식을 잘 정의해놓아야 한다.

 

그래서 MSA에서는 중복을 허용한다. 중복이라는 것은, 단순히 이름이 같다고 중복이 아니라 A가 바뀔 때 B가 바뀌면 중복이라고 정의한다. 중복을 포함해서 설계란 항상 트레이드오프로 구성되어있기 때문에 이 방법을 택할 때와 저 방법을 택할 때에 항상 장단점이 존재한다. MSA로 구성하게 되면 필연적으로 코드의 전체 소스는 늘어나게 된다.

 

OOP를 동반할 때에는 중복되는 로직을 최대한 지양했는데, DDD에서는 '중복'이라는 의미가 OOP와는 다르게 적용되기 때문에 이것을 이해하는 것이 힘들어서 계속 DDD를 찾게 되는 것 같다.

+ Recent posts