-
Notifications
You must be signed in to change notification settings - Fork 5
소프트웨어 아키텍처 The Hard Parts sprint 4 - 김도경 #616
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,39 @@ | ||
| # Chapter 08: 재사용 패턴 | ||
|
|
||
| 2부 "다시 합치기"의 시작이다. 1부에서는 분해인/통합인 분석하고, 컴포넌트 쪼개고, DB 나누고, 세분도 결정하고... 계속 쪼개는 얘기만 했는데, 2부부터는 관점이 확 바뀐다. 쪼개진 서비스들이 혼자서는 의미가 없으니까, 이걸 어떻게 다시 엮어서 하나의 시스템으로 동작하게 만들 건지에 대한 이야기가 나온다. 1부가 "어디를 자를까"였다면 2부는 "자른 것들을 어떻게 이어붙일까"인 셈이다. 읽으면서 느낀 건데, 1부에서 쪼개는 게 어렵다고 생각했는데 다시 합치는 게 더 어려운 것 같다. | ||
|
|
||
| 솔직히 이번 챕터는 읽으면서 좀 찔렸다. 나도 프로젝트 할 때 공통 코드 보이면 반사적으로 "이거 공통으로 빼자"부터 생각했던 것 같은데, 책에서 "재사용은 남용이다"라는 표현이 나온다. 분산 환경에서는 무조건 중복을 없애는 게 답이 아니라는 건데, 중복 제거 원칙을 지키자니 서비스 간 결합이 생기고, 중복을 허용하자니 일관성이 깨지고. 결국 상황에 맞는 판단이 필요하다는 거다. | ||
|
|
||
| 책에서 코드 재사용 방법을 네 가지로 나눠서 설명하는데, 하나씩 따라가면서 읽었다. | ||
|
|
||
| 코드 복제는 가장 단순한 방법이다. 그냥 코드를 각 서비스에 복사하는 건데, 처음에는 "이게 패턴이라고?" 싶었다. 근데 경계 컨텍스트를 완벽하게 보존할 수 있다는 장점이 있고, 어노테이션이나 마커 인터페이스처럼 한번 만들고 거의 안 바뀌는 코드에는 오히려 합리적이라고 한다. 물론 버그 나면 서비스마다 일일이 고쳐야 하고 버저닝도 안 되지만, 변경이 거의 없는 코드를 굳이 공유로 묶어서 결합을 만들 필요는 없겠다 싶었다. | ||
|
|
||
| 공유 라이브러리는 가장 흔하게 쓰는 방식일 것 같다. 근데 여기서 디펜던시 관리 vs 변경 관리라는 트레이드오프가 나오는데, 이 부분이 좀 헷갈려서 천천히 읽었다. 하나의 큰 라이브러리로 묶으면 관리는 편한데 클래스 하나 바꿨을 뿐인데 모든 서비스가 영향받고, 기능별로 잘게 쪼개면 변경 범위는 줄어드는데 디펜던시 관리가 지옥이 된다. 읽다 보니까 7장에서 서비스 세분도 고민했던 거랑 비슷한 문제가 라이브러리에서도 나오는 거였다. 어디까지 묶고 어디서 끊을지가 결국 똑같은 고민이라는 게 좀 신기했다. 그리고 버저닝 관련해서 최신 버전 지정은 피하라는 조언이 나오는데, 핫픽스 배포할 때 최신 버전이 갑자기 바뀌면서 서비스가 깨질 수 있기 때문이라고 한다. | ||
|
|
||
| 공유 서비스는 공유 코드를 별도 서비스로 띄우는 방식인데, 공유 라이브러리랑 비교하면 컴파일 타임 vs 런타임의 차이다. 공유 라이브러리는 빌드할 때 문제가 드러나지만, 공유 서비스는 배포하고 나서야 터진다. 이 차이가 꽤 크다고 느꼈는데, 빌드할 때 터지면 배포 전에 잡을 수 있지만 런타임에 터지면 이미 늦은 거니까. 여기에 성능, 확장성, 내고장성 문제까지 따라온다. 공유 서비스 하나 죽으면 의존하는 서비스 전부 영향받는 건 꽤 무섭다고 느꼈다. | ||
|
|
||
| 사이드카와 서비스 메시 부분은 처음에 개념이 잘 안 잡혀서 몇 번 다시 읽었다. 마이크로서비스에서 도메인 코드는 중복을 허용하면서도 모니터링/로깅/인증 같은 운영 관심사는 일관성이 필요한데, 이 모순을 어떻게 해결하지? 하는 질문에 대한 답이 사이드카였다. 핵심은 도메인 커플링과 운영 커플링을 분리하는 거다. 사이드카에는 운영 관심사만 넣고 도메인 클래스는 절대 넣지 않는다. 찾아보니까 이걸 직교 커플링이라고 부르는데, 도메인은 도메인대로 독립적이고 운영은 운영대로 일관성을 갖는 구조라고 이해했다. | ||
|
|
||
| 챕터 마지막에 나오는 사례가 인상 깊었는데, 여러 사업부가 같은 개념을 다루니까 하나의 서비스로 통합하면 좋겠다는 발상이 나온다. 근데 실제로 그렇게 하면 모든 도메인을 수용해야 하니까 서비스가 복잡해지고, 한 쪽이 뭔가 바꾸면 다른 쪽까지 영향을 받는다. 결론은 재사용은 추상화만으로는 부족하고 변경 속도가 느려야 가치가 있다는 것이다. 운영체제나 프레임워크가 재사용이 잘 되는 이유가 바로 변경이 느리기 때문이라는 설명이 납득됐다. | ||
|
|
||
| # Chapter 09: 데이터 오너쉽과 분산 트랜젝션 | ||
|
|
||
| 데이터를 쪼개고 나면 "이 테이블 누구 거야?"랑 "트랜젝션은 어떻게 보장해?"라는 문제가 바로 나온다. 이번 챕터 읽으면서 6~7장에서 서비스 분해하는 과정이 떠올랐는데, 결국 서비스를 나누면 데이터도 나눠야 하고 그러면 트랜잭션 문제가 따라온다는 걸 다시 한번 느꼈다. | ||
|
|
||
| 데이터 오너쉽의 기본 원칙은 단순하다. 테이블에 쓰기 작업을 하는 서비스가 그 테이블의 오너다. 근데 현실은 그렇게 깔끔하지 않다. 6장에서 DB 분리할 때 "이 테이블 어디에 넣지?" 하는 얘기가 나왔었는데, 그때는 도메인 기준이었고 여기서는 "누가 쓰느냐"라는 좀 더 구체적인 기준이 나온다. 결국 둘 다 맞물려야 제대로 된 분리가 되는 거라고 이해했다. | ||
|
|
||
| 단독 오너쉽은 하나의 서비스만 쓰는 테이블이니까 그냥 경계 컨텍스트에 넣으면 끝이라 쉽다. 공통 오너쉽은 모든 서비스가 쓰는 테이블인데, 이건 전용 서비스를 만들어서 단독 오너로 바꾸는 방식으로 해결한다. 여기까지는 따라가기 어렵지 않았다. | ||
|
|
||
| 문제는 공동 오너쉽이다. 같은 도메인 내에서 소수의 서비스가 하나의 테이블에 쓰는 경우인데, 이걸 해결하는 기법이 네 가지 나온다. 솔직히 이 네 가지가 처음에 한번에 안 들어와서 하나씩 정리하면서 읽었다. 테이블 분할 기법은 테이블 자체를 쪼개서 각자 소유하게 하는 건데 동기화 문제가 생긴다. 데이터 도메인 기법은 공유 스키마에 같이 두는 건데 스키마 바꿀 때 전부 영향 받는다. 대리자 기법은 한 서비스를 오너로 지정하고 나머지는 그 서비스를 통해 접근하는 건데, 오너가 아닌 쪽은 성능이랑 내고장성에서 불리하다. 서비스 통합 기법은 그냥 합치는 건데 서비스가 커져버린다. 네 가지를 다 읽고 나니까, 7장에서 통합인 얘기할 때 "데이터베이스 트랜잭션"이 왜 합쳐야 하는 이유가 되는지 그때는 좀 추상적으로 느꼈는데, 여기서 공동 오너쉽 문제를 보니까 "아 이래서 통합인이었구나" 하고 이해가 됐다. 결국 네 가지 기법 다 트레이드오프가 있어서 "정답"은 없고, 상황에 맞는 걸 골라야 한다. | ||
|
|
||
| 분산 트랜잭션 부분에서는 원자성, 일관성, 격리성, 영속성을 설명하는데, 핵심은 서비스가 나뉘면 이게 더 이상 보장이 안 된다는 거다. 단일 서비스에서는 하나의 DB 트랜잭션으로 끝나는 게 서비스가 나뉘면 각자 자기 DB에만 커밋하니까 전체 원자성이 깨진다. 이걸 보면서 1부에서 쪼개는 게 끝이 아니라 여기서부터가 진짜 대가구나 싶었다. 나눌 때는 깔끔해지는 것만 보이는데, 트랜잭션을 어떻게 할 건지는 나눈 다음에야 체감되는 문제인 것 같다. | ||
|
|
||
| 최종 일관성 패턴이 세 가지 나오는데, 이것도 하나씩 따라가면서 읽었다. 백그라운드 동기화 패턴은 외부 프로세스가 주기적으로 데이터를 맞춰주는 방식이다. 제약이 많아서 특정 상황에서만 쓸 수 있다고 한다. 오케스트레이티드 요청 기반 패턴은 오케스트레이터가 순차적으로 서비스에 요청을 보내면서 관리하는 건데, 서비스가 커지면 오케스트레이터 로직도 같이 복잡해지고 단일 장애점이 될 수 있다. 이벤트 기반 패턴은 서비스들이 이벤트를 발행/구독하면서 비동기로 처리하는 방식인데, 에러 처리가 어렵다. 수신 서비스가 죽어 있으면 메시지가 유실될 수 있어서 지속 가능 구독자랑 데드 레터 큐가 필요하다. 솔직히 세 가지 다 읽으면서 "그래서 뭘 쓰라는 거지?" 싶었는데, 결국 어떤 패턴을 쓰든 완벽하지 않다는 게 이 챕터의 메시지인 것 같다. | ||
|
|
||
| 세 가지 패턴 모두 에러가 나면 보상 트랜잭션을 실행해야 하는데, 이것마저 실패하면 결국 사람이 직접 개입해야 한다는 부분이 인상 깊었다. 아무리 자동화를 해도 마지막에는 사람이 필요하다는 게 좀 아이러니하다고 느꼈다. | ||
|
|
||
| # 논의 주제 | ||
|
|
||
| - 코드 재사용 방식(코드 복제, 공유 라이브러리, 공유 서비스, 사이드카) 중에서 현재 프로젝트나 회사에서 가장 많이 쓰는 방식은 무엇인가요? 저는 아직 이 네 가지를 의식적으로 구분해서 적용해본 적이 없는 것 같아서, 경험이 있으신 분들은 어떤 기준으로 방식을 선택하는지 궁금합니다. | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 저는 코드 복제에서 공유 라이브러리 까지는 써봤습니다. 각자 상황에 맞는 프로젝트 규모와 비즈니스 복잡도에 따라 달라지지 않을까 생각합니다.
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 위에 말씀하신대로 회사나 팀마다 다르겠지만, 저의 회사 같은 경우에는 현재 공유 라이브러리의 비중이 큰 것 같습니다.
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 사이드카는 아예 듣도보도 못했고 (...) FE팀 기준 복제가 1순위 라이브러리가 2순위인 것 같습니다 라이브러리 개발용 노드 버전이랑 제품 개발용 노드 버전이 달라서 라이브러리는 가급적 안 건드리게 되네요 :/ 제가 작업할 땐 코드 복붙 후 특정 서비스용 예외처리만 별도 수정하는 일이 제일 많았습니다 .. |
||
|
|
||
| - 분산 트랜잭션에서 에러가 나서 결국 사람이 개입했던 경험이 있으신 분이 있다면, 그때 어떻게 대응하셨는지 이야기 나눠보면 좋겠습니다. | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 평시에는 비즈니스로직에 문제가 없지 않는 이상은 사람이 개입해서 처리해야할 정도의 일은 없는거 같습니다 다만, 장애 상황이 발생한 경우에 상태를 맞추기 위해서, 사람이 개입해서 작업하는 것은 많이 했습니다 python의 경우는 interpreter 로 바로 서버에 DB query 혹은 API 호출 등을 할 수 있어서, 보상 트랜잭션을 위한 python script를 만들어두고, 활용하는 경우도 있었고, 슬랙봇을 통해서 하는 경우도 있었네요
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 직접 에러를 확인해서 추적을 하고 롤백을 별도로 실행하거나 DB에 직접 접근해 롤백 하는 쿼리를 실행하거나 한 것 같습니다.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 전면 장애로 인해 분산 트랜잭션의 보상이 제대로 이뤄지지 않은 경험이 있습니다(어제 4시간짜리 장애도 그랬습니다 ㅎㅎ). 그 때 저희가 할 수 있는게 없어서 타 팀의 장애가 해소되길 기다렸고, 장애 해소 후, 기존 데이터 비교를 통해 후처리 했습니다. |
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
각 회사와 팀의 상황에 따라 다른 것 같습니다 저는 4가지 모두 다 경험해보기도 했는데, 각각 장단이 있는거 같습니다