에드워드 스노든 그리고 샌드웜

연말에 시간 가는줄 모르고 읽었던 보안관련을 소개한다. 이 두책은 서로 얽혀 있는 부분이 있지만 스노든의 책은 정부의 민간인 감청에 대해서, 샌드웜은 정보기관 사이의 사이버전쟁에 대해서 다룬다. 결론은 IT분야 종사자라면 반드시 한번 읽어 보기를 추천한다. 안타까운건 두책 다 원서인데 스노든의 책은 아마도 번역이 될 것 같지만 샌드웜은 저자가 2014년에 출간한 책도 한국에 없는 걸 보면 번역 될 것 같지 않다.

영구기록(Permanent Record) – 에느워드 스노든

이 책은 스노든의 유년시절부터 시작해서 어떻게 미국 정부의 불법 적인 대중 감시를 폭로하게 되었는지 까지 과정을 설명한 회고록이다. 실제 스파이 업무나 사용한 기술에 대해 깊게 다루진 않아서 누구나 읽을 수 있다.

유년시절

스노든의 유년시절은 많은 IT 종사자들과 닮아있다. 게임을 좋아하고 그게 컴퓨터에 대한 관심으로 커졌으며 학교에서는 크게 인기 있는 유형은 아닌 전형적인 컴퓨터 너드였다. 당시에 해커로 활동하기보단 인터넷 커뮤니티에서 활발한 활동을 한 것 같다. 그리고 이 시절의 경험이 후에 스노든이 미국 정부의 불법에 큰 위기 의식을 느끼도록 만든다. 아마 비슷한 시기에 하이텔,나우누리 같은 PC통신을 해본 사람들은 크게 공감할 이야기 일듯. 현재와 비교해서 가격도 비싸고 접속하기 불편하며 서로 이야기를 나누는 것 외에는 크게 할 것없는 그런 장소였지만 커뮤니티는 지금보다 더 따뜻했으며 익명뒤에서 느끼는 안전함도 더 컸기에 스노든은 포함한 우리들은 그 시절의 인터넷에 낭만을 느낀다.

9/11

그런 스노든의 인생이 확 바뀌게 된 시점은 9/11이다. 그 사건이후 그린베레에 지원했지만 훈련 도중 부상을 입고 전역하게 된다. 그 뒤에 NSA 시설의 경비원으로 1년정도 일한다. 어떻게 보면 스노든은 소위 말하는 고스펙을 가진 인물은 아니다. 부모님이 모두 정부기관에서 일했고 군에 자원한 경험도 있기 때문에 높은 보안 등급을 받을 수 있었고 9/11 이후의 정보기관의 확장 시기와 맞물려서 CIA에서 일하게 된다. CIA라고 해도 막상 컴퓨터에 정통한 사람은 얼마 없었기에 스노든은 곧 두각을 나타낸다.

내부고발까지

그 뒤에 동경으로 옮겨 NSA에서 IT 시스템을 구축하는 업무를 맏게 되는데 거기서 부터 미국 정부의 불법을 알게 된다. 정보기관내의 파편화가 굉장히 심해서 실제 폭로의 대상인 감시 도구 프리즘을개발한 것은 아니었지만 운영을 잠시 도와주며 충격적인 사실을 발견한다. NSA는 우리가 상상하는 지휘통제실 같은 곳에서 구글에 검색하듯이 원하는 사람을 입력하기만 하면 그사람의 메일, 문자등을 전부 보고 있던 것이다. 실제 요원들 중에는 개인 용도로 자기 부인이나 여자친구를 대상으로 사용하는 사람들도 많았고 무작위로 사람들이 주고받는 사진들을 돌려보기도 했다니, 스노든의 폭로후에 제발 변화가 있었길 바란다..참고로 이 시스템은 아직도 운영중이다. 다만 많은 사이트가 HTTPS로 넘어가는 등 서비스 제공자들도 보안을 강화했기 때문에 아직도 그 수준으로 감시가 가능한지는 의문이다.

그렇게 언론에 제보할 결심을 한뒤에는 시스템 내부에서 문서를 크롤링하는 Heartbeat를 개발한뒤 기자에 접근하고 결국 홍콩에서 기사를 작성하기 까지의 상황을 설명한다. 당연하겠지만 정말 많은 고민을 한 것 같으며 미국 정부와 의회가 서로 견제하는데 실패 했기 때문에 이런 방법을 택하게 됐다고 한다. 스노든이 그 고민 속에서 다음과 같은 비유를 했는데 마음에 와 닿았다.

Ultimately, saying that you don’t care about privacy because you have nothing to hide is no different from saying you don’t care about freedom of speech because you have nothing to say. Or that you don’t care about freedom of the press because you don’t like to read. Or that you don’t care about freedom of religion because you don’t believe in God. Or that you don’t care about the freedom to peaceably assemble because you’re a lazy, antisocial agoraphobe.

대충 번역하면 자기는 당당하기 때문에 프라이버시에 별로 신경쓰지 않는다고 말하는 것은 읽는 것을 좋아하지 않기 때문에 출판의 자유도 신경쓰지 않으며 신을 믿지 않기 때문에 종교의 자유도 신경쓰지 않는다고 말하는 것과 별반 다르지 않다는 내용.

스노든의 미래는?

스노든은 대의를 위해 저질러버렸지만 정말 힘든 결정이었음이 책의 후반부에 그대로 드러난다. 스노든은 수사가 시작 됐을때 피해가 갈 수 있기 때문에 다른 가족들과 동거중인 여자친구에게도 단 한마디도 하지 않았다. 하지만 실제 스노든이 홍콩에서 뉴스에 등장했을 때 그들이 겪었을 고충은 상상하기가 힘들다.. 다행히도 여자친구는 스노든이 망명중인 러시아로 건너가 그와 결혼했으며 미국내에서는 스 노든을 사면하기 위한 목소리도 존재한다. 하지만 비슷한 사례를 참고해봤을 때 스노든은 엄청난 중형을 받게될 가능성이 높고 공화당은 물론 민주당에서도 사면을 허용해서는 안된다는 의견도 많다.. 그리고 최근 미국 법원은 책의 인세와 강연료등으로 번 50억 달러를 몰수하는 결정을 내렸다..(기사)

이사건을 계기로 미국 뿐만 아니라 전세계의 많은 사람들이 각자 정부기관의 잘못된 행태에 관심을 가지도록 만들었다. 나도 책을 읽으면서 정부의 잘못된 관행에 견제의 시그널을 – 원래라면 국회가 해야할 – 보내준 스노든에게 크게 감사함을 느꼈다.

관련기사

스노든도 사용한 메신저 ‘시그널’ 보안성 최강

에드워드 스노든 “K방역 정보수집, 효과 확실치 않다”

샌드웜(Sandworm) – 앤디 그린버그

Sandworm

샌드웜은 러시아 GRU소속의 해킹 전담 부대의 코드명이다. 이 책은 최근에 벌어진 일련의 해킹 사건들의 배후를 다루는 논픽션으로 책에서 언급된 사건을 정리한 아래 목록은 기승전러 대부분 러시아가 그 배후에 있다. 많은 사람들이 아래 사건들중 한두가지는 단신 뉴스로 봤을 것이다.

저자는 놀라운 취재력으로 일련의 사건들 사이의 단서를 차근차근 맞춰나간다. 미국과 우크라이나를 포함한 세계 각국의 멀웨어 분석가, 보안전문가와의 심도깊은 취재를 통해 실제 멀웨어의 분석 방법은 물론이고 어떤 원리로 해킹이 작동하는지도 설명하고 있다. 특히나 초반의 흡입력이 대단한데, 실제 사건을 다룸에도 왠만한 소설보다 더 긴장감이 느껴진다. 사이버 워에 관해서 인터넷 기사나 위키피디아의 글로서는 파악하기가 힘든 전체 맥락을 한번에 다 이해할 수 있게된다.

미미카츠

책에서 언급된 대부분의 제로데이 취약점은 윈도우 기반이다. 프랑스 정부기관의 IT매니져로 일하던 델피는 메모리에 사용자의 크레덴셜을 저장하는 윈도우 WDigest 기능의 취약점을 발견하고 이를 MS에 즉각 보고하지만 MS는 해당 취약점은 컴퓨터가 이미 탈취당한 상태에서만 문제가 되기 때문에 당장 큰 문제가 아니다 라는 식으로 대응을한다. 델피는 경각심을 일깨우기위해 POC개념으로 Mimikatz을 깃헙에 공개한다. 그렇게 MS의 무책임한 대응으로 네덜란드의 인증서 발급 업체였던 DigiNotar는 인증서를 부정 발급하게 되고 파산에 이르게 된다.

피해액만 해도 조단위가 넘어가는 페트야는 NSA가 나중에 써먹기 위해 꽁꽁 숨겨뒀던 이터널블루 취약점을 사용해 공격을 사용한다. 이렇게 의도치않게 정보기관끼리 취약점들을 공유해서 더욱 더 발전된 멀웨어가 나오게 한다.

러시아의 사이버 워

러시아는 왜이렇게 해킹을 하는 걸까? 냉전 이후에 러시아의 세계 경쟁력은 내리막길을 걸었고 남은건 막대한 군사력뿐 그마저도 미국 국방비의 1/10에 그치기 때문에 예전같은 영향력을 되찾고 싶어하는 시도라는 분석이다. 2014년에 일어난 러시아 우크라이나 전쟁에서 러시아는 승리했지만 여러가지 판단 착오로 인해 첩보 기관인 GRU는 신뢰도에 큰 타격을 입는다. 이를 복구하기 위해 적은 비용으로 큰 혼란을 일으킬 수 있는 사이버 전쟁에 적극적으로 뛰어들고 있다는 분석이다.

작가는 2015년에 일어난 우크라이나 정전사건때 미국이 조금 더 강력한 메시지를 보냈어야 한다고 말한다. 오바마 정부는 미국내에서 일어난 일이 아니라서 이를 단순히 무시했고 러시아는 우크라이나를 미래의 공격을 위한 놀이터로 적극 활용한다. 책에서는 다루지 못했지만 2020년 다시 한번 러시아발 해킹사건이 발생한다. 그리고 이게 마지막도 아닐 것이다.

배후(attribution)문제

러시아 보안업체인 카스퍼스키의 보안 전문가는 저자와 인터뷰중에 평창 올림픽 해킹은 북한 소행이 아니며 누군가 일부러 그렇게 보이게 설계했다고 말한다. 저자는 그래서 북한이아니라면 누구 소행이라고 생각하냐? 라고 답정남 질문을 하지만 해당 전문가는 가방에서 attribution dice(배후? 주사위)를 꺼내서 보여주는데 이부분은 조금 웃겼다:) 이는 그만큼 해킹의 책임자를 가려내기가 힘들다는 사실을 뜻한다.

배후를 밝혀내가 힘든 사이버워의 특성상 앞으로의 세상은 예전 전쟁처럼 개전과 종전으로 명확하게 구분되지 않고 항시 존재할 것이라는 분석이다.

복원력

그리고 저자는 사이버워에 대한 대응으로 댄 지어가 주장하는 복원력(Resilence)를 언급하며 책을 마무리한다. 댄지어는 미국 정보기관의 벤처캐피탈 역할을 하는 비영리기관인 In-Q-Tel 의 최고보안 책임자이다. 많은 보안 전문가들이 해킹 공격에 대해서 더 많은 보안 패치, 머신러닝에 의한 모니터링등 강력한 조치를 이야기 하지만 그는 방지보다는 피해를 최소화하고 복구시간을 줄이는데 더욱 집중해야 한다고 한다. 그 핵심 기념이 복원력으로 독립성을 의미한다. 각 구성요소들이 네트워크에 의존하지 않고 아날로그로도 독집적으로 수행될 수 있어야 한다는 의미이다. 현대 사회는 아주 복잡한 시스템들이 단계적으로 엮여 있어서 그 기반이 무너지면 전체가 다 무너기 쉽다. 그래서 이메일 시스템이 멈추면 우편시스템이 작동해야 하고 이동전화가 작동하지 않으면 유선전화를 사용할 수 있는 사회가 되어야 한다. 그 예로 우크라이나에서 해킹으로 대규모 정전 일어났어도 생각보다 빠르게 복구가 가능했다고 말한다. 우크라이나의 기반 시설들은 선진국보다 평소에도 더 자주 멈추고 많은 부분들이 사람손으로 아직 움직이고 있었기에 해킹하기는 쉬웠지만 피해는 적었다. 하지만 선진국의 시설들은 해킹하기는 더욱 어렵지만 해킹 당해서 멈추게 되면 그 피해는 훨씬 비극적일 것이다.

에드워드 스노든 그리고 샌드웜

더 라스트 오브 어스 파트2 리뷰

이 리뷰는 강력한 스포일러를 담고 있습니다. 

“영화 같은 게임”으로 유명한 더 라스트 오브 어스 파트2(이하 라오어2)가 올해 6월에 출시되었다.  나중에 플레이하려고 맘먹었기 때문에 출시 직후 최대한 리뷰는 읽지 않으려고 했지만 너무나 실망이라는 소식과 게임 관련 웹진의 후한 리뷰에 속았다는  사람들의 반응만 전해 들었다.  미루다가 아마존에서 29달러 세일을 하기에 빠르게 구매해서 플레이해 보았는데, 웬걸. 개인적으로는 정말 집중해서 재미있게 플레이 했고 각종 웹진에서 출시 직후 했던 10년에 한 번 나오는 타이틀이라는 평가에도 공감한다.

사람들이 분노하는 이유, 이야기 때문

인터넷에서 본 박한 평가들은 주로 전작의 팬들을 고려하지 않은 개연성 없는 전개, 공감할 수 없는 메시지 등 주로 캐릭터와 스토리에 관한 평가가 주를 이룬다. 라오어1 에서 플레이어들이 그토록 감정이입을 했던 조엘이  PC설정으로 추가된 새로운 캐릭터에게 골프공 취급을 당하고 친딸보다 더 정성스럽게 지켜낸 엘리가 레즈비언이라는 설정, 그리고 플레이어에게 그녀를 쥐 잡듯이 두드려 패도록 하는 진행까지..정확히 그 시점부터 엔딩까지 이어지는 이야기는 나도 모르게 얼굴이 찌푸리면서 “아 나는 이렇게 하기 싫어”라고 외치게 만든다. 이 게임은 플레이어들이 주말 드라마의 상황에 완전히 몰입한 주부처럼 만들어 버렸다.

스토리 위주의 게임 만들기 쉽지 않다

덧붙여서 요즘 같은 시기에 이야기 위주의 게임을 만드는 일은 절대 쉬운 일이 아니다. 근 10년간 출시된 게임을 보면 스토리는 대부분 부수적인 역할을 한다. 8, 90년 대에는 이야기 중심의 게임이 나왔지만, 오히려 컴퓨터 그래픽 기술이 월등하게 발전한 최근에는 스토리 위주의 게임보다는 화면의 화려함이나 참신한 플레이 등이 주로 AA타이틀로 등장한다. 화려한 그래픽과 스토리를 다 잡으려면 헤비레인이나 디트로이트 비컴 휴먼같은 장르 말고는 선택지가 없었다.

그렇게 어려웠던 것을 라오어는 해냈다. 플레이어 앞에 펼쳐지는 스토리에 대한 선호는 사람마다 다를 수 있지만, 이야기에 몰입하게 만든 그 기술과 실행 능력에 100점을 주고 싶다. 이 게임의 메뉴부터 게임 모드까지 거의 모든 요소가 스토리에 집중하도록 의도적 의로 배치되어있다. 최소한의 UI, 게임 내 오브젝트와 캐릭터 간의 상호작용, 실제에 가까운 음향 효과까지. 아마 플레이어는 게임 내에서 존재하는지도 몰랐을 사소한 섬세함이 모이고 모여서 그 몰입감을 만들어 낸다. 최근에 갓 오브 워도 비슷한 시도를 했고 결과는 매우 성공적이었지만 라오어는 그것을 뛰어넘는다.

내가 개발자라면 만든 맵이 아까워서 온라인 모드나 여러 가지 더 오락적인 내용들을 추가 했을 것이다. 하지만 너티독은 플레이어가 감독이 정해놓은 스토리만을 따라가도록 모든 걸 다 버리고 디테일에 집중했다. 이러한 게임의 작가주의 성격 때문에 평가가 더욱더 양극화된 것 같다.

라오어2 디테일에 관한 유튜브 비디오 1
라오어2 디테일에 관한 유튜브 비디오 2

개인적으로 만족했던 이야기

많은 라오어1 팬들의 치를 떨게 만들었던 스토리도 사람별로 여러 가지 해석이 가능하다는 점에서 나는 맘에 들었다. 1의 이야기도 좋았지만 여태까지 접해봤던 이야기 형태라면 라오어2의 이야기과 주제는 참신하게 다가왔다.

많은 사람이 가족 같은 조엘과 엘리를 갑자기 튀어나온 애비와 레브가 파괴한 것에 분노하고 (특히나 플레이어의 손에 직접) 복수는 복수를 부를 뿐이라는 메시지에 공감하지 못하겠다고 하지만 나는 세상에는 수많은 조엘이나 엘리가 존재할 수 있고 철저한 악인도 누구에게는 생명의 은인이 될 수 있다는 메시지를 받았다. 그리고 마지막에 엘리가 그 고리를 끊어 내려는 모습에 큰 안도와 감동을 했다.

PC설정의 순기능

엘리를 레즈비언으로 설정한 것도 단순히 PC설정이라기 보다는, 조엘이 소중한 사람의 생명이 인류의 미래보다 중요하다고 판단했던 것처럼 유사하게 엘리도 인류의 미래 보다 자신의 인생을 살아가기로 한 것 아닐까? 공공의 이익을 보면 엘리는 이성과 연애해서 자신의 면역력을 널리 퍼트리거나 적어도 보존해야만 한다.  이런 자유주의적 메시지를 위해서 엘리를 레즈비언으로 설정한 것은 아닐까 생각해본다.

그리고 설령 PC 설정이라고 해도 단순히 성 대결로 몰고 갔다기보다는 다양한 인종을 어우르는 시도로 보이며 이는 게임 전체 설계에도 반영되어 있다. 이전까지는 많은 장애인이 게임을 플레이하는데 많은 장애물이 존재했지만 라오어2는 이러한 장벽을 없애주는 게임 접근성 부분에서 역대 최고라는 평가를 받고 있다. 비쥬얼 모드나 조작에 관한 세세한 설정까지 왜 존재하는지 알 수 없었던 많은 부분이 의도된 것임을 알고 다시 한번 놀랐다. 아래 유튜브 링크는 시각 장애인 게이머가 라어오2를 극찬하는 영상이다. 이걸 보고도 라오어2에서 PC 설정을 택한 것을 가지고 비난하긴 힘들 것이다.

끝으로

여려 면에서 많은 생각을 하게 하고 영감을 주는 게임이었다. 닐 드럭만이 트위터에서 그를 비난하는 사람들을 향해 공격적인 대응으로 욕을 먹고 있지만 라어오2로 인해 이제 그 누구도 닐 드럭만이 게임 업계에서 최고의 Bigshot이 되었음을 부정할 수는 없을 것이다.

더 라스트 오브 어스 파트2 리뷰

클린 (리액티브) 코드

클린코드(Clean Code)는 소프트웨어 엔지니어링 분야에서 굉장히 의미가 깊은 책이다. 클린코드로 많은 개발자들이 자기가 작성한 코드의 정량적인(Quantitative) 부분 뿐만 아니라 정성적인(Qualitative) 부분도 신경쓰게 되었다. 책을 관통하는 주제는 일관되다. 사람이 읽기 쉬운 코드가 유지보수가 더 쉽기 때문에 높은 품질을 가진다는 것이다.

나는 3~4년에 걸쳐 스프링 WebFlux를 사용해 증권앱의 서버 그리고 GraphQL의 게이트웨이 프로젝트를 진행했다. WebFlux를 사용한 결과에 충분히 만족했고 Graphql 게이트웨이는 안정화 단계에 접어들어서 최근에는 여러번의 코드리뷰를 진행했다. 다양한 사람들의 코드를 리뷰하면서 많은 사람들이 자신들만의 방식으로 코드를 작성하고 있음을 깨달았다. 공식 매뉴얼을 읽고 작동하는 코드를 만들긴 했지만 어떻게 해야 다른 사람이 더 쉽게 이해할 수 있을지에 대한 배려는 부족했다.

이는 아직 리액티브 프로그래밍이 본격적으로 사용 된지 얼마 지나지 않았기 때문에 클린코드나 이펙티브 자바와 같이 많은 엔지니어들이 이정표로 삼을 수 있는 자료들이 부족한 것이 주된 이유라고 생각한다. 엔지니어들 사이에 합의된 관습등이 부족해서 리액티브 코드는 작성자 별로 중구난방이 되기 쉬우며 코드 리뷰시에도 더 많은 시간을 필요로 한다.

여기서는 내가 개인적으로 리액티브 코드 리뷰시에 자주 언급하는 항목들을 정리해 본다. 여기서 말하는 내용들이 반드시 옳다는 이야기는 아니며 프로젝트 상황마다 다르겠지만 일반적으로 리뷰어의 입장에서 읽기 편안함에 중점을 두고 나열해본다.

모든 예제는 코틀린코드로 작성되었다.

1. 연산자를 중심으로 코드를 작성

연산자(Operator)는 리액티브 프로그래밍에서 사용 가능한 연산의 기본 단위이다. 그래서 코드를 작성할 때는 연산자를 중심으로 왼쪽에서 오른쪽, 위에서 아래 방향으로 마치 책을 읽는 것처럼 작성하는 것이 다른 사람들이 제일 이해하기 편하다.

//Bad
userService.getFavorites(userId).map(Favorite:toRequestModel)
           .flatMap(favoriteService::getDetails) 

// Good
userService.getFavorites(userId) 
           .map(Favorite:toRequestModel)
           .flatMap(favoriteService::getDetails) 

2. map, flatMap의 함수는 최대한 간결하게

1번 에서 언급한 것처럼 모든 코드는 연산자 중심으로 읽혀져야 한다. 많은 사람들이 제일 자주 사용하는 map과 flatMap의 함수 내부에 장황한 비즈니스 로직을 작성하곤 한다. 이것은 코드의 흐름을 끊기게 해 가독성에 좋지 않다. 중요한 큰 흐름은 연산자의 체인과 그 인자만으로 읽어낼 수 있어야 하기 때문에 장황한 로직은 외부 함수로 빼내도록 하자.

//Bad
userService.getFavorites(userId)
           .map { 
               val (favorites, user) = it
               val userRequest = user.toUserRequest()
               GetFavoriteDetailRequest(
                   favorites = favorites,
                   user = userRequest
               )
            }
           .flatMap(favoriteService::getDetails) 

//Good
userService.getFavorites(userId)
           .map(this:toRequestModel)
           .flatMap(favoriteService::getDetails) 

fun toRequestModel(input: Tuple2<Favorites, Users>) {
     val (favorites, user) = input
     userRequest = user.toUserRequest()
     GetFavoriteDetailRequest(
        favorites = favorites,
        user = userRequest
     )
} 

3. 연산자는 목적과 그 이름에 걸맞게 사용

리액티브 연산자의 이름은 실제 작동 방식만큼이나 중요하다. 예를들어 map은 값을 다른 값으로 매핑할때, flatMap은 값을 Publisher 타입으로 매핑할 때 사용한다. 여기서 Publihser타입은 값의 계산 자체를 추상화한 타입이다. doOnNext 연산자는 전체 연산의 결과에 영향을 미치지 못하는 부수 효과(Side effect)를 주고 싶을 때 사용한다. 이름에 맞는 연산자를 사용하면 코드리뷰시에 인지부하를 줄여 준다.

//Bad
userService.getFavorites(userId)
           .map { 
               log.info("Received favoirtes, $it")
               it.toRequestModel()
            }
           .flatMap(favoriteService::getDetails) 

//Good
userService.getFavorites(userId)
           .doOnNext { log.info("Received favoirtes, $it") }
           .map(this:toRequestModel)

           .flatMap(favoriteService::getDetails) 

4. Mono, Flux 사용이 필요한지 확인

3번에서 언급했듯이 Mon, Flux의 상위 타입인 Publisher 타입은 “계산” 자체를 추상화 한다. 이 타입은 리액티브의 프로그래밍의 장점인 비동기 연산을 제어하는데 사용되어야 하기 때문에 중구난방으로 사용되어서는 안된다. 되도록 데이터 클래스의 멤버는 모두 Non-Publihser타입으로 선언한다.

//Bad
userService.getFavorites(userId)
           .map { 
               val (favorites, user) = it
               GetRequestModel(
                   Mono.just(favorites),
                   Mono.just(user)
               )
            }
           .flatMap(favoriteService::getDetails) 

data class GetRequestModel(
     val favorites: Mono<Favorites>,
     val user: Mono<User>
)

//Good
userService.getFavorites(userId)
           .map { 
               val (favorites, user) = it
               GetRequestModel(favorites, user)
           }
           .flatMap(favoriteService::getDetails) 

data class GetRequestModel(
     val favorites: Favorites,
     val user: User
)

5. Publihser의 Null 타입은 Mono.empty

다시 한번 이야기 하지만 Publisher 타입은 미래의 계산에 대한 추상화이다. 그렇기 때문에 Publihser타입을 반환해야 하는 장소에서 Null을 반환하면 에러가 발생한다. 코틀린에서는 non-nullible 타입을 제공해서 한결 처리하기 수월하지만 자바는 Mono를 반환하는 함수가 Null을 반환하는지 확인하고 Mono.empty 타입을 반환하도록 한다. 반대로 값에서 값으로의 맵핑을 수행하는 map은 null을 반환해도 에러가 발생하지 않고 Mono.empty 타입과 동일하게 처리된다.

// Bad
Mono
   .just("test")
   .flatMap { testFunc(it) }

// Good
Mono
  .just("test")
  .flatMap { 
      testFunc(it) ?: Mono.empty()
   }

private fun testFunc(seed: String): Mono<String>? =
    if (seed  == "test") {
        null
    } else {
        Mono.just("Mono - test")
    }

6. Mono나 Flux가 중첩되는 경우에는 메소드 참조를 활용

1번에서 언급한 것처럼 연산자를 중심으로 메소드 체인 형식으로 작성하는 것이 최선이나 어쩔 수 없이 연산자들을 중첩해서 사용할 때가 있다. 그럴 때는 반드시 메서드 참조를 사용할 수 있도록 한다. 특히 람다에서 묵시적 타입 “it” 을 허용하는 코틀린에서 모든 람다의 매개 변수로 it을 사용하면 변수 스코프가 가려져서 (Variable shadowing) 가독성에 심각한 문제가 발생한다. 자바에서도 메서드 참조가 유리한 이유는 일반적으로 개발자가 한 클래스 내에서 여러 번 변수명을 지정해야 할 경우 특별한 의미 없이 비슷한 이름을 연속해서 사용하기 쉽기 때문이다.

// Bad
userService.getUser(userId)
           .map { it.toFavoriteReq() }
           .flatMap { 
               favoriteService
                       .getFavorites(it)
                       .flatMap { 
                          favoriteService.getDetails(it.toDetailRequest) 
                       }

           }

// Good 
userService.getUser(userId)
           .map(User::toFavoriteReq)
           .flatMap { favoriteReq ->
               favoriteService
                       .getFavorites(favoriteReq)
                       .map(Favorite:toDetailRequest)
                       .flatMap(favoriteService::getDetail) 
                          
           }

7. Collection API와 겹치지 않게 사용

Mono 타입의 가장 대표적인 연산자인 map과 flatMap, filter 등은 자바, 코틀린의 컬렉션 API뿐만 아니라 여러 곳에서 사용된다. 동일한 범위에서 사용되면 소스 타입이 Publisher인지 Iterable인지 바로 알기가 힘들다. 두 가지가 서로 겹치게 되면 분리할 수 있는 방법을 찾자.

// val books: List<Book>
// Bad
Flux.merge(
  books.map { book ->
     if  (book.id == null) {
       Mono.just(card.copy(id = UUID.randomUUID()))
     } else {
       Mono.just(book)
     }
  }
)

//Good
Flux.merge(
  books.collectionMap { book ->
     if  (book.id == null) {
       Mono.just(card.copy(id = UUID.randomUUID()))
     } else {
       Mono.just(book)
     }
  }
)

private fun <T, R> Iterable<T>.collectionMap(transform: (T) -> R): List<R> = this.map(transform)

8. Mon, Flux의 구분이 필요한 곳은 변수명에 명시하기

일반적으로 모든 Publisher타입에 mono나 flux를 변수 명에 넣어줄 필요는 없다. 하지만 해당 변수가 Publisher임을 명시적으로 하고 싶거나 Flux와 Mono 사이에 연산을 수행할 때는 변수명에 flux나  mono를 포함 시키도록 한다. 특히 flux 와 mono 간의 연산이 필요할 때는 변수명을 mono나 flux를 붙여서 서로 다른 타입과 연산을 수행함을 알 수 있게 한다. 다음 예제에서는 Flux 와 Mono를 zip하는데 그 결과는 Mono타입이 된다.

// Bad
val numberKor = Mono.just("hana")
val numberEng = Flux.just("one", "two")
numberEng
      .zipWith(numberKor)
      .doOnNext { zipped ->
         val (eng, kr) = zipped
         log.info("English:$eng, Korean:$kr")
       }

// Good
val numberKorMono = Mono.just("hana")
val numberEngFlux = Flux.just("one", "two")

numberEngFlux
     .zipWith(numberKorMono)
     .doOnNext { zipped ->
        val (eng, kr) = zipped
        log.info("English:$eng, Korean:$kr")
     }

9. 명시적인 subscribe() 호출은 신중하게

리액티브 체인 내부에서 subscibe를 호출하는 것은 되도록 피하자. 이는 엔지니어가 인식하지 못하는 사이 장기간 돌아가는 스레드를 만들 가능성이 있다. 리액티브 프로그래밍은 subscribe가 반환하는 Disposable 타입을 사용해 처리량을 조절할 수 있어야 한다.

// Bad
userService
         .getUser(req)
         .flatMap(userService::changePassword)
         .doOnNext {
              auditLogger
                      .auditLog(it)
                      .subscribe()
         }

10. 가장 중요한 연산자는 flatMap

리액티브에서 이야기 하는 성능의 향상, 즉 높은 동시성은 거의 대부분은 flatMap을 중심으로 이루어진다. 그래서 데이터 흐름을 조절하는 delayElement등을 flatMap 주변에서 사용할 때는 유의하고 자매 연산자인 flatMapSequential, concatMap 등과의 차이점을 확실히 파악하도록 하자.

클린 (리액티브) 코드