-FHBDZ"SDIJUFDUVSF - .. 1SFTFOUBUJPO %BUB %PNBJO &OUJUZ Layer Layer use case Layer Layer -BZFSܻ࠙৬ઓࢿੑী'PDVT 전체 구조를 이렇게 4가지 레이어로 분리했습니다. 이 구조는 Layer의 분리와 의존성주입에 Focus를 맞추어 설계했습니다.
-FHBDZ"SDIJUFDUVSF - .. 얼마전 발표했던 CleanArchitecture in Banksalad의 마지막 장표인데요, 그 때 이 구조를 소개할 때 장점으로 ‘View는 Service만 알면 되고, 그 행동은 주입받은 리소스에 의존하기 때문에 이 규칙만 이해하면 빠르고 규칙적인 개발이 가능하다고 말씀드렸었습니다.
-FHBDZ"SDIJUFDUVSF - .. 이 구조를 한장에 모아보면 이렇게 정리할 수 있습니다. 보라색의 Presentation Layer는 오직 context의 service만 알게되는 형태이고 갈색의 services에서 각 서비스는 Repository들을 주입받습니다. context의 services안에 있는 Repositories는 파란색의 data영역의 Repository들을 모아둔 것입니다. Repository는 core에서 interface영역을 정의하고 data에 실제 구현체 부분이 있습니다 사실 이렇게만 설명드리면, 기존 구조가 어떠한 흐름이었는지 설명드리기에 충분하지 않을 것 같습니다.
new Context를 통해 생성한 Application의 service호출 -FHBDZ"SDIJUFDUVSF - .. 아까 보았던 Context를 new Context를 이용해서 Application 이라는 이름으로 생성했고, 이 구문을 통해 생성한 Application의 service를 호출합니다.
interface로 정의된 Repository를 주입받는다. catch৬ call ࢎਊ use case۽ ࠅ ࣻ णפ.
ਃೠ ੋ ৻ࠗ ܻࣗझח Ӓ ޖب աఋա ঋणפ.
ࠗ࠙ ,߸ೡ ࣻ ח ਃࣗ۽ࠗఠ ܻ࠙दఃӝ ਤೣੑפ. -FHBDZ"SDIJUFDUVSF - .. view가 부르게 되는 각 서비스는 이렇게 interface형태로 된 Repository를 주입받습니다. 이 코드를 보면 Service가 주입받는 repo는 하단에 interface형태로 된, 실제 구현체가 아닙니다. catch와 call은 사용자의 useCase, 즉 행동으로 볼 수 있고 이 단계에서는 필요한 직접적인 외부 리소스가 명시되어 있지 않습니다.
৻ࠗઓࢿ݅߸҃оמ ਬ೯ز VTF$BTF ߸ೞঋҊ 외부 의존성에 해당하는 storgae나 api 설정만 변경할 수 있습니다. 만약 storage나 api를 다른것을 사용하고 싶다면 interface영역은 수정하지 않고 data layer의 Repository생성자만 수정하면 됩니다.
https://speakerdeck.com/soyoung210/clean-architecture-in-banksalad -FHBDZ"SDIJUFDUVSF - .. 사실 이 설명만으로는 기존 구조에 대해서 충분히 말씀드리지 못했고, 아마 이해가 잘 안되실것 같다고도 생각됩니다. 하지만 오늘 중점적으로 다룰 부분은 이 구조에 대해서가 아니라, 이사 과정에 대한 이야기 입니다. 그럼에도 불구하고, 혹시 이 구조에 대해서 더 알고싶은 분이 계시다면 위 발표자료를 참고하시면 좋을것 같습니다.
1. core ف ࠗ࠙ਵ۽ ա 정의부 구현부 ѐࢶ೧ঠೡѪ 첫번째는,Repository를 Interface와 실제 구현부로 나누어 정의하는 것에 효용을 느끼지 못했습니다. 사실, 이 당시에는 TestCode 작성이 고도화 되어 있지 않아서 일수도 있지만, 이 때의 상황에서는 개발을 진행함에 있어 아무런 이득을 느끼지 못한다는 결론을 내렸습니다.
3. View৬ Dataо ೠҔী ѐࢶ೧ঠೡѪ https://notefolio.net/virgintruth/69097 api response api fetchState localState view helper render VIEW 마지막으로, view에 render외에 너무 많은 정보가 관리되고 있었습니다. api response, fetchState 그리고 렌더링에 필요한 local state, 데이터를 가공하기 위한 helper function등이 제대로 분리되어 있지 않았습니다.
1. core ف ࠗ࠙ਵ۽ ա 2. ٜ݅ٸח જওחؘ… 3. View৬ Dataо ೠ Ҕী ѐࢶ೧ঠೡѪ 정리하자면, 기존 구조에서는 이렇게 3가지 불편함이 있었습니다. 이는 기존에 사용했던 구조의 문제점이라기 보다는, 그 사용에 있어서 면밀히 고려하지 못했기 때문이라고 생각합니다.
Repo CMS 0. ࢲ࠺झ ܻ࠙ service 앞서 말씀드렸듯, 구조에 대한 변경을 결정하기 전에 저희팀은 서비스별로 저장소를 분리하자 라는 결정을 했습니다. 이런 상황이다보니 구조를 개선하는 작업은 기존 레거시를 모두 고치는 것이 아니라 비교적 작은 단위인 하나의 서비스부터 적용하는것이 가능했습니다.
ࢎೡٸח؊જਵ۽ Dependencies repo 1. ৬ ҳഅਸ э 이를 하나로 합쳐, api provider라는 의존성을 바로 주입받고 RepositoryDependencies라고 하는, 모든 Repository를 모아두는 쪽에 바로 주입하는 형태로 작성했습니다. 코드량이 반으로 줄어드니까, 개발 단계에서 행복감은 두배로 상승했습니다.
Entity core/Respository core/service data/http/mapper data/Repository view Before After 두번째 개선은 첫번째 내용과 겹치는 부분도 있습니다. 수정사항이 생겼을 때 변경해야하는 파일의 양이 너무 많았었습니다. 구조를 간단하게하고, 역할을 명확히 함으로서 수정이 두렵지 않도록 했습니다.
ࢎೡٸח؊જਵ۽ 3. View৬ Data ܻ࠙ store는 server에서 받아오는 데이터를 명시하는 역할로 사용했습니다. api호출과 관련된 처리는 미들웨어에 모두 위임하였습니다. view에서는 직접 api 호출과 관련된 로직을 관리하지 않고 오직 dispatch와 Store의 state만 바라보면 되는 구조로 변경하였습니다. Dispatch를 담당하는 곳을 한 레벨로 지정하는것까지 적용하여 각 요소의 역할을 나누었습니다.
ޖਸ֬ଢ଼ਸө Store , dispatch ? mapStateToProps . mapDispatchToProps Route View . dispatch : dispatch ! ( : entry/view , : entry/container) ( hook react-redux 6.x) 중반까지라고 말씀드린 이유는, 끝날쯤에는 놓친부분들이 많이 보였기 때문입니다. 당시에는 react-redux 6버전을 사용하고 있었는데요, 해당 버전을 쓰신 분들이라면 아실 mapStateToProps와 mapDispatchToProps 즉 store와 connect되는 부분을 어디로 잡을지에 대한 고민이 있었습니다. 그리고, 이 고민을 썩 잘 해결하지 못했습니다. dispatch는 상태를 변화시킬 수 있으니까 가급적 한 곳에서만 이루어져야 한다고 생각했고 이 위치가 route의 바로 하위 레벨인 entry의 view폴더에서 이루어져야 한다고 생각했습니다.
ޖਸ֬ଢ଼ਸө Store , dispatch ? ( hook react-redux 6.x) mapStateToProps . mapDispatchToProps Route View . dispatch Drilling ⚓, Component : dispatch ! ( : entry/view , : entry/container) ❓ 이렇게 생각했던 것은 끊임없는 Driling과 재사용 불가능한 Component를 만들었습니다. redux를 사용했던 목적이 Driliing해결은 아니었지만 실제 필요한 컴포넌트에게 전달되기까지 몇단계를 거치는 구조는 다소 고통스러웠습니다. 그리고 dispatch의 기준이 기본은 entry이고 필요하면 container라는 러프한 룰을 적용하다보니 더이상 기준이 아니게 되었고, props가 강제된 컴포넌트는 재사용이 불가능해졌습니다.
ޖਸ֬ଢ଼ਸө action, reducer, middleware .. 그리고 이건 주위에서 ‘redux를 사용하지 않는 이유’ 중 하나로 많이 들었던 이야기 인데요, redux를 사용하기 위해 반복적으로 적어줘야 하는 코드가 많았습니다. 복사-붙여넣기 하면 손쉽게 작성할 수는 있지만, 중복코드가 늘어나는 작업은 꽤 귀찮은 일이었습니다.
ޖਸ֬ଢ଼ਸө Data ? 마지막으로 놓친점은, 제가 너무 Data만 신경썼다는 점입니다. 이전 구조에서 view와 data가 너무 심하게 바인딩 되어 있었다는 점에 포커스를 맞추다 보니 다른 부분들을 많이 놓쳤습니다. 하나의 프로젝트는 data와 view만으로 구성되지 않습니다. view, data외에도 api, util, view를 위한 helper function등이 프로젝트의 구성요소가 될 수 있습니다.
ޖਸ֬ଢ଼ਸө Data ? 놓쳤던 문제를 조금더 자세히 설명드리자면 view에 필요한 helper의 위치를 어디로 둘것인지 정하지 않았습니다. helper라고 해서 뭔가 좀 작아보이긴 하지만 사실 일종의 ‘로직처리’로 볼 수 있는 부분인데, 이를 명확히 정하지 않아서 로직이 곳곳에 흩뿌려진 상태가 되었습니다.
ޖਸ֬ଢ଼ਸө Data ? 그리고, helper와 유틸의 차이에 대한 기준을 명확히 하지 않았습니다. 명확한 목적을 구분하지 않아 비슷한 역할을 하는 함수가 util에 있거나, helper function이 정의된 곳에 있거나.. 이렇게 케이스가 나뉘면서 다른사람이 보았을때 혹은 미래의 내가 보았을때 코드가 쉽게 파악되지 않는 문제가 있었습니다.
۞ೠਬ۽ ( ! ) 하지만 역시 다 만들고 나서야 아까같은 스파게티 상태가 눈에밟혔습니다. 이를 개선하기 위해 다 부수고 다시 만드는 것은 일정상 불가능한 일이었으므로 이 프로젝트는 이대로 유지보수 하는것으로 하고, 묻고 다음 프로젝트에서 더블로 잘해보기로 했습니다.
components / container / routes / view domain Primitive Type Props . interface Props { name: string; value: string; } component는 무조건 primitive 타입의 props만 가집니다. 개발자가 따로 정의한 entity를 props로 가질 수 없는 부분입니다. entity를 가지게 되면 도메인을 알게되고, 이렇게 되면 다른곳에서 재사용을 하지 못할 확률이 높아지기 때문입니다.
components / container / routes / view domain Props . , container는 domain props를 가질수도 있는 부분으로 정의하였습니다. 이 부분은 재사용을 할수도, 하지 않을 수도 있습니다. 재사용성은 컨테이너에게 주요한 관심사가 아닙니다.
components / container / routes / view domain Props . , Entity Type Props . , dispatch + ) tmi: react-redux 7.1 도메인을 알게되는 부분이기 때문에 자연스럽게 dispatch는 컨테이너 레벨에서 이루어지게 됩니다. 그리고, eact-redux 7.1 버전의 useSelector와 useDispatch를 사용하게 되면서 코드량이 확 줄었습니다. connect와 mapdispatchToProps등을 정의할 필요가 없어져서 훨씬 간결하게 사용할 수 있으니 이 문법을 사용해보시는 것을 추천드립니다.
"OE , 모호했던 helper function에 대해서는 이렇게 정리했습니다. helper의 위치는 component혹은 container 하위의 controller라는 폴더에 위치해야 한다는 규 칙을 세웠습니다. util과는 다르게 view를 위한 함수이며 1회성에 가까운 역할을 한다고 정의했습니다.
࢜ૐറҵృܻ؋झಞ action, reducer, middleware . redux . . 아까 액션, 리듀서, 미들웨어를 정의하는데있어 비슷한 코드를 매번 적어주어야 한다는 불편함이 있었는데요, 생각해보니 redux는 잘못이 없고 제가 조금 잘못사용하고 있지 않나.. 라는 생각이 들었습니다. 오른쪽 사진이 리덕스 새집증후군을 해결하기 위한 actionUtil 함수인데요,
이를 자세히 보자면 이런 구조입니다. createAsynAction이라는 유틸성 함수를 정의하고 이 함수는 3개의 프리픽스와 액션 함수를 리턴 합니다. 사용하는 쪽에서는 함수 하나만 호출하면 바로 액션정의가 끝나게 됩니다. 미들웨어 또한 이런식으로 작성해서 중복코드를 줄였습니다.
. sample project sca olding 이렇게 샘플프로젝트를 만들면 새로운 구조에 대해서 누구나 쉽고 간단하게 검증해볼 수 있습니다. 저희 팀에서 관리하는 대부분의 프로젝트는 api response를 이용하여 이를 가공하는 형태의 프로젝트 이기 때문이죠. 덤으로, 스캐폴딩도구도 만들 수있으니 서비스별로 저장소를 분리한 저희팀에 유용한 방식이 아닌가.. 하고 생각하고 있습니다.