이전 글을 통해서 사라 마라 서비스의 문제점과 이를 해결할 수 있는, 아토믹 디자인에 대해 공부할 수 있었습니다. 이번에는 직접 사라마라 서비스의 전체적인 코드를 리팩터링 하면서, 아토믹 디자인 패턴을 도입하였습니다.
1. 컴포넌트를 다섯단계로 구분하기
아토믹컴포넌트를 도입하면서, 가장 먼저 한 것은 저만의 컴포넌트 기준을 가지는 것이었습니다. 앞서 아토믹 디자인 패턴을 도입하면서 대부분에 사람들이 기준 그대로 따랐을 때, molecules와 organisms의 모호성 때문의 고민하는 것을 보았기 때문에, 저는 이 부분을 명확히 하고자 했습니다.
저의 기준은 다음과 같습니다.
1. atoms - 더 이상 분해가 되지 않는 컴포넌트 (ex. Text, Input, Button, Label)
atoms는 아토믹 디자인 패턴의 정의를 그대로 따랐습니다. 더 이상 분해가 되지 않는 컴포넌트를 의미합니다. 그렇기 때문에, 다른 컴포넌트를 만드는데 기본이 되며, 변형이 많이 생기기 때문에 props의 종류가 많이 생기는 컴포넌트입니다.
atoms/Input
/**
*
* @param {string} value Input Value
* @param {func} onChange Onchange func
* @param {string} ph placeholder value
* @param {string} w Input width
* @param {string} h Input Height
* @param {string} id Input Id
* @param {css} styles Input Custom Styles
* @returns 인풋을 리턴하는 함수입니다
*/
export default function Input({ ph, ...rest }) {
return <StyledInput placeholder={ph} {...rest} />;
}
2. molecules - atoms가 합쳐져서 하나의 기능을 수행할 수 있는 컴포넌트 (ex. Form, Select...)
moleculse는 atoms과 molecules들이 합쳐져 하나의 기능을 하는 컴포넌트입니다. Form의 경우 atoms인 label, input, button들이 합쳐져 어디론가 보낼 수 있는 함수입니다. 여기까지도, 아토믹디자인 패턴의 기본 정의와 유사합니다. 하지만, 제가 정의한 molecules는 높은 재사용성이 보장되어야 합니다. 그리고, 그 키워드는 비즈니스로직과의 분리입니다. 비즈니스 로직 분리에 대해서는 뒤에 조금 더 자세히 정리하였습니다.
정리하자면, 하나의 기능을 하는 컴포넌트이지만, 비즈니스 로직과 분리되어 재사용이 수월한 컴포넌트가 molecules입니다.
위 컴포넌트는 저의 molecules인 Select 컴포넌트입니다. 저의 Select컴포넌트는 트리거 컴포넌트와 option들을 props로 받아, 트리거가 작동됐을 때, options들을 보여주는 역할을 합니다. option들을 보여주는 하나의 역할만을 수행하고, 재사용을 통해 다양한 비즈니스 로직과 합쳐질 수 있는 컴포넌트입니다.
3.organisms - 비즈니스로직이 합쳐진 컴포넌트
앞서, molecules를 정의하니, organisms의 정의가 명확해졌습니다. organisms는 하나의 비즈니스 로직을 처리하는 함수입니다.
molecules인 Form과 비즈니스로직(폼에있는 내용을 서버에 전송한다)이라는 기능이 합쳐져 있는 컴포넌트입니다. 저의 molecules는 비즈니스로직을 허용했기 때문에, 사이드 이펙트로 인한 의존성 문제가 생길 수 있었습니다. 이러한 문제를 해결하기 위해 Hook을 통해서 로직과 디자인을 구분하기 위해 노력했습니다
4. templates - 데이터 결합이 없이, 각 컴포넌트를 배치하는 컴포넌트
template 컴포넌트는 정의하는데 어려움은 없었습니다. page의 이전 단계이며, page에서 데이터 결합을 뺀 컴포넌트입니다. 전체적으로 layout과 비슷한 역할을 합니다. 하지만 사용하는 데에 고민은 있었습니다. 보통 layout은 의존성이 적고, 각각의 컴포넌트를 배치하는 역할을 하기 때문에, 재사용이 높은 컴포넌트입니다. 하지만 저의 프로젝트는 아직 페이지가 두 개밖에 없으며, 그 두 개의 페이지 레이아웃조차 달라 재사용을 기대하기 힘들었기 때문입니다. 또한, template이 중첩되는 경우에 template안에 template이 들어가야 하나?라는 고민도 하게 만들었습니다.
이러한 고민에 의해 templates를 제외하고 프로젝트를 진행할까도 생각했지만, 프로젝트가 확장하고, templates에 재사용이 증가할 것을 기대하고 각각의 템플릿으로 페이지를 개발했습니다. 추가로, template중첩문제는 후술할 Compound Component(결합 컴포넌트)로 page로 해결했습니다. 결과적으로 프로젝트가 커졌을 때, template들을 조합하여 새로운 페이지를 쉽게 생성할 수 있다는 장점을 활용하기 위해 page에서 이러한 중첩문제를 해결하도록 했습니다.
5. page - template와 데이터를 결합한 컴포넌트
page는 template과 데이터를 결합하는 컴포넌트입니다. 하지만, 이러할 경우 많은 사이드이펙트를 페이지에서 담당하게 되고, page컴포넌트는 너무많은 책임을 맡게 됩니다. 저는 이를 앞서 설명한 organisms와 책임을 나누었습니다. 특정 컴포넌트에서 발생하는 비즈니스로직과 그 사이드 이펙트를 organisms와 Hook에 위임하였습니다. 또한, 비즈니스로직과 관련 없이 전체 컴포넌트에서 사용하는 데이터는 ContextAPI를 활용하여 Props Drilling을 사용하였습니다.
아토믹 디자인 패턴 도입이 어려웠던 점
이러한 정의를 바탕으로 약 2주동안 프로젝트에 몰입하여 코드리팩터링을 마칠 수 있었습니다. 코드 리팩토링을 하는 과정에서 제가 생각한 정의대로 되는 경우도 있었지만, 그렇지 않았던 상황도 있었습니다. 아래는 제가 겪었던 고민에 대해 정리한 내용입니다.
1. 재사용을 어떻게 늘릴 수 있을까?
아토믹 디자인 패턴을 도입한 이유 중 하나는 컴포넌트설계를 바탕으로 재사용성의 증가입니다. 하지만 단순히 컴포넌트를 5단계로 나눈다고 재사용성이 증가하는 것이 아니었습니다. 저는 아토믹 디자인 패턴의 장점을 적극적으로 활용하기 위해 재사용성을 증가시키기 위한 고민을 했습니다.
1. 비즈니스 로직을 구분하자
그 첫번째로, 비즈니스 로직을 구분하는 것이었습니다. 아래는 Select 컴포넌트와 Select 된 결과에 따라, 쿠팡 카드를 렌더링 하는 컴포넌트입니다. 현 컴포넌트는 카테고리들을 서버에서 받아, Options들을 보여주는 컴포넌트처럼 보입니다. 하지만, 비즈니스로직이 결합이 된 상태라면 아래 Select컴포넌트는 쿠팡컴포넌트를 렌더링 할 때에만 렌더링 할 수 있을 것입니다.
위 Select 컴포넌트의 재사용성을 증가시키기 위해서 저는 비즈니스 로직을 제거했습니다.
function Select({ trigger, options, setValue }) {
const [isOpen, setIsOpen] = useState(false);
const changeValue = (value) => {
setValue(value);
setIsModal(false);
};
return (
<StyledSelect>
{cloneElement(trigger, { onClick: () => setIsOpen((prev) => !prev) })}
{IsOpen && (
<div className="selects">
{options?.map(({ value }) => {
return (
<option onClick={() => changeValue(value)} className="option" value={value}>
<Text label={value} color={Theme.color.darkGray} size="md" />
</option>
);
})}
</div>
)}
</StyledSelect>
);
}
Select 컴포넌트는 options와 trigger를 받습니다. 쿠팡 카테고리라는 비즈니스 로직을 제거하여, trigger 컴포넌트를 받는다면, 셀렉트 모달이 열리게 되며, props로 전달받은 option들을 제공하게 됩니다.
이제 Select컴포넌트는 쿠팡컴포넌트뿐만아니라, 다른 컴포넌트에도 바로 사용할 수 있는 컴포넌트가 됐습니다.
2. Compound Component
두 번째로 고민한 방법은 Compound Component 입니다. Form은 사용자로부터 값을 받아 서버에 전송하는 함수입니다. 중요한 것은 서버에 전송하는 하나의 기능이지 어떤 입력을 몇 개 받는지는 중요하지 않습니다. 만약 두 개의 인풋을 받는 Form을 재사용하기 위해 개발한다면, Input 3개를 받는 Form, Textarea가 필요한 Form모두 새로 개발해야 할 것입니다. 저는 이러한 문제를 해결하기 위해 Compound Component를 활용했습니다.
<Form onSubmit={SubmitQuestion}>
<Form.Label>
<Label m="0 0 8px 0px" htmlFor="item" text={<Text bold="lg" label="어떤걸 사고싶어?" />} />
</Form.Label>
<Form.Input>
<Input
id="item"
h="56px"
ph="고민하는 물건을 적어주세요"
value={QuestionFormData.ItemValue}
onChange={QuestionFormData.ItemChange}
/>
</Form.Input>
</Form>
Form 컴포넌트는 onSubmit 함수를 받아 Input값들을 제출하는 역할을 합니다. 그 과정에서 Label과 Input은 Compound Component를 활용해서 일관된 UI를 제공하면서도, 유기적으로 대응할수 있도록 했습니다.
2. 사이드 이펙트를 줄이자
그 다음으로 고민한 내용은 사이드 이펙트를 줄이기 위한 노력입니다. page단에서 모든 데이터를 담당하기에는 앞서 말한 것처럼 너무 많은 책임이 전가됐으며, props drilling문제가 발생했습니다.
1. Hook을 활용한 Logic 모듈화
저는 이러한 문제를 사이드 이펙트가 발생할 수 있는 로직을 Hook을 통해 모듈화하여 관리했으며, React Query를 활용해요 서버상태와 클라이언트 상태를 일치시키기 위해 노력했습니다.
const useQuestion = () => {
...
return {stage}
}
useQuestion 훅은 자신의 질문 상태에 따라 컴포넌트를 변경시키는 사이드 이펙트를 발생시키는 함수입니다. 이때, React-Query의 실패, 성공로직을 정리하여 컴포넌트의 단계를 표시하는 stage를 서버 상태와 일치시키기 위해 노력했습니다.
앞으로의 숙제
이러한 고민과 노력을 통해 프로젝트 전체 코드를 아토믹 디자인 패턴을 기반으로 리팩토링 할 수 있었습니다. 그 결과로 코드 리팩토링 전보다 로직이 추가됐음에도 코드 라인이 30,000줄 줄일 수 있었습니다. 또한, 다섯 단계의 체계적인 컴포넌트 구조를 바탕으로 코드 가독성이 눈에 띄게 향상됐습니다. 디자인 패턴도입으로 앞으로 프로젝트 유지 보수 시에 높은 생산성을 기대할 수 있게 됐습니다.
아래에는 사라마라 프로젝트와 아토믹 디자인 패턴을 통한 조금 고민하거나 도전해 볼 내용을 정리하였습니다.
1. 폴더 구조를 더 깔끔하게 할 수 없을까?
이 내용은 1편에 정리했던, 테오님의 고민과 유사합니다. 차이점은 테오 님은 2안을 선택했지만, 저는 1안을 선택했다는 점입니다. 그 이유는 1안이 기존 저의 프로젝트 구조와 유사했으며, 한 feature에서 재사용되는 컴포넌트는 유사할 거라는 생각으로 feature기반으로 나누었습니다. 하지만, 현재 페이지수가 적을 때는 덜하지만, 페이지가 늘어날수록, 다른 feature에 있는 atom을 재사용하는 경우가 생길 것이기 때문에 좀 더 나은 구조를 고민할 필요가 있습니다.
2. StoryBook 적극 활용해보기
두 번째는 하나의 목표로, StoryBook을 적극적으로 활용하는 것입니다. 아토믹디자인패턴으로 리팩터링 하며 뷰와 데이터를 분리하였고, 이는 StoryBook을 사용할 수 있는 하나의 조건을 달성한 샘입니다. 현재 저희 프로젝트는 staging서버가 없기 때문에 프런트 테스트가 어렵습니다. StoryBook을 통해 알파테스트뿐만 아니라, 디자인 QA, 그리고 jest 등을 활용한 자동화 테스트까지 도입한다면 프로젝트 전체적인 생산성과 협업에 도움이 될 거라 생각합니다.
'개발' 카테고리의 다른 글
코드 리팩토링 (액션, 데이터, 계산 나누기) (0) | 2023.12.08 |
---|---|
CORS (0) | 2023.12.06 |
[SaraMara] 아토믹 디자인 패턴 도입하기 (0) | 2023.11.20 |
Github Action + NCP + Nginx로 간단 CI / CD 구성하기 (0) | 2023.07.11 |
[Nginx] 비-www URL을 www URL로 redirect (0) | 2023.07.08 |