해당 게시글은 "개발자"의 관점에서 작성된 글입니다!
그렇기 때문에 개발자 혹은 개발자의 생각이 궁금한 디자이너에게 특히 도움이 될 것이라 예상됩니다 
문제의 발단
우리 팀에서는 Material-UI를 사용한다.
기술을 선택하고 처음 프로젝트가 시작할 당시에는 API 개발이 주 업무여서 이 기술에 대해 잘 알지 못했다.
그리고 나중에 첫 클라이언트 작업에서도 여전히 3~4개월차 신입이였기 때문에 작성된 다른 코드를 보고 의심없이 작성했었다.
이러한 방식은 UI를 개발하는데 아무런 문제가 없었기 때문에 불편하지만, 익숙해져갔다.
그리고 시간이 지나 최근에서야 내가 무슨 짓을 저지르고 있었는지 깨닫게 되었다.
Material UI는 Material UI의 디자인 시스템을 기준으로 UI를 제공한다.
사용자는 Material UI의 디자인 시스템을 따르거나, 사용자의 서비스의 디자인 시스템을 Theme를 통해 커스터마이징 할 수 있다.
여기서의 핵심은 UI는 디자인 시스템을 따라야 한다는 규칙은 변함이 없다는 것이다.
그럼에도 불구하고 우리는 아래처럼 오랜 시간 디자인 시스템이라는 개념에서 완전히 벗어나서 작업했다.
const tabTheme = createMuiTheme({
overrides: {
MuiTabs: {
flexContainer: {},
},
MuiTab: {
...
'&:first-child:before': {
display: 'none',
},
...
})
혹은
const StyledTabs = withStyles({
root: {
width: '100%',
height: 80,
margin: '0 auto',
padding: '0 87px',
backgroundColor: '#f1f3f5',
},
indicator: {
backgroundColor: 'transparent',
},
})(Tabs)
TypeScript
매번 새로운 탭을 만들어 냈고, 그 때마다 기존의 Material UI의 Theme를 Override하는 식으로 문제를 해결해왔다.
문제를 인식하고 나니 매번 스타일을 Override해서 작성할 것이였으면 차라리 UI Framework를 쓰지 않는 것이 더 좋았진 않았을까? 라는 생각도 들었다.
< 얼른 벗어버리고 싶은 모래주머니가 되버린 Framework >
다행히도 디자인 시스템을 만들기로 디자인팀과 잘 이야기되서, Material UI와 좀 더 공생하게 되었다.
빠른 시일내에 디자인팀과 회의를 했고 디자인 시스템에 대한 많은 대화를 나누었다.
그래서 디자인 시스템이란
디자인 시스템은 서비스에 맞는 재사용이 가능한 UI 컴포넌트들을 만드는 일이다.
UI 컴포넌트들은 Product로 취급하여 생성된다.
이렇게 생성된 컴포넌트는 회사의 자산으로써 관리하고 문서화하여 관리한다.
UI들이 재사용 되기 때문에 유지보수 비용이 적어지고 그 만큼 높은 퀄리티의 UI를 생성해야 한다.
이 때, 개발자들은 디자인 시스템을 위해서 StoryBook을 비롯한 서비스를 활용하여 UI Component Documentation을 만들고 이를 소속 팀 및 다른 팀들과 공유할 수 있게 한다.
디자이너와 개발자 모두 UI 컴포넌트를 다루기 때문에, 디자인 시스템은 두 분야를 연결하는 다리 역할을 한다.
이는 조직의 공용 컴포넌트에 대한 "진실의 근원(source of truth)"이다.
우리 팀에 정말 필요한 것일까
현재 존재하는 레거시 코드 중 중복된 Button의 수를 세어 보았다.
classname이 중복되지 않거나, 혹은 class가 아예 없는 button들을 하나의 버튼으로 기준을 삼았다.
그 결과 총 646개의 각기 다른 버튼을 사용하고 있음을 확인 할 수 있었다.
그리고 앞으로도 늘어 날 것이다.
지금까지 관리 불가능한 수의 레거시 UI들을 운영이슈로써 처리해 왔다.
하지만 하나를 고친다고 해도 여전히 645개의 버튼은 잠재적인 오류를 가지고 있었고, 앞으로 더 늘어난다면 더욱 자원관리가 힘들어 지는건 틀림없었다.
뿐만 아니라 물리적으로 먼 거리에 있는 디자인 팀, 충분하지 않은 인적 자원임에도 불구하고 매번 비슷한 버튼을 개발하며 발생하는 리소스들을 개선하길 팀은 원했다.
정리 결과, 우리팀에 도입 시에 디자인 시스템은 다음과 같은 장점과 해결해야 할 문제를 가지고 있었다.
장점
1.
디자인팀과 물리적으로 떨어진 개발팀
⇒ 불필요한 커뮤니케이션 감소 및 Context공유
2.
한번 만든 컴포넌트를 재사용하면서 리소스 절약
3.
관리 가능한 수준의 UI의 갯수 유지
해결해야 할 문제
1.
개발팀 내에선 UI Modules을 개발하기에 부담이 있음
2.
디자인팀도 기존에 어떤 자원도 없었기 때문에 처음부터 시작해야함
⇒ 기존의 업무와도 병행해야하기 때문에 투자 가능한 리소스가 적음
3.
물리적으로 디자인팀과 멀리 있기 때문에 디자인 시스템을 위한 커뮤니케이션 역시 어려움
장점도 해결해야 할 문제도 서로 한치의 양보가 없었기 때문에, 더욱 도입에 망설였다.
하지만 결국 디자인/기획팀의 동의가 이루어져야 했고, 회의를 통해 더 좋은 방법을 찾을 수 있지 않을까 싶어서 개발팀에서 먼저 디자인 시스템 도입에 대한 제안을 하게 되었다.
여담으로 입사 후에 디자인팀을 이번 회의를 통해 처음 뵀다.
회사 사업 특성 때문에 이런 일이 발생했는데 조사해보니 비슷한 사업의 다른 회사들도 마찬가지 인거 같아 보였다.
디자이너와 프론트엔드 개발자가 입사 후 한번도 만난 적 없음에도 디자인적 이슈가 없었다는 것은 그 당시 굉장히 신기하게 느껴졌다.
사실 지금도 신기하다.
출처 : 페이스북 그래도 그림님
회의를 하며 디자인 팀도 문제를 인식하고 있었다는 것을 알게 되었고, 도입에 적극적으로 동의하셨다.
다만 디자인 팀도 적은 리소스에 많은 업무들이 있었기 때문에, 우리는 Form을 구성하는 Typo Button Color등을 올해 상반기까지 정리하는 것을 목표로 정하게 되었다.
참고한 국내 디자인 시스템 사례
이 외에도 많은 글들을 참고 했는데, 그 중에서도 특히 참고가 많이 된 글들이다.
전달받은 디자인 시스템 개발팀에 녹이기
디자인팀이 디자인시스템을 전달 해주면 우리는 그에 따른 개발 환경을 구축해야 한다.
위의 기업들은 덩치가 어느정도 있는 회사이기 때문에, 우리의 상황에 맞는 방법을 찾아야 했다.
1. Material-UI - ThemeProvider 활용하기
별개의 디자인 시스템을 적용한 모듈을 개발하면 물론 좋겠지만 이를 위해선 개발 및 유지보수에 막대한 리소스가 필요하다.
현재 우리의 상황에서는 생산성 향상보다 더 많은 비용을 차지할 것이라 생각했기 때문에 이 방법은 선택할 수 없었다.
대신 Material UI의 ThemeProvider를 이용하여 프로젝트의 디자인을 우리 서비스에 맞게 Override하여 적용 할 수 있었다.
// _app.tsx
class MyApp extends App<AppProps> {
public render(): JSX.Element {
const { Component, pageProps, router } = this.props
const service = getServiceFromRouter(router)
const theme = generateThemeByService(service as ServiceType)
return (
<ThemeProvider theme={theme}>
<Component {...pageProps} />
</ThemeProvider>
)
}
}
export default MyApp
// theme.ts
const theme = createMuiTheme({
props: {
MuiButtonBase: {
disableRipple: true,
},
},
typography: {
fontFamily: ['Noto Sans KR', 'sans-serif'].join(','),
},
palette,
overrides: {
MuiCssBaseline: {
'@global': {
body: {
lineHeight: 1.4,
...
},
'div, span, iframe, h1, h2, h3, h4, h5, h6, p, a, img, dl, dt, dd, ol, ul, li, form, label, legend, table, caption, tbody, tfoot, thead, tr, th, td, footer, header ': {
margin: 0,
....
},
},
},
MuiButton: {
containedSecondary: {
'&:hover': {
boxShadow: 'none',
backgroundColor: serviceTheme.secondaryColor,
},
},
},
},
custom: {
...(serviceTheme && {
service: serviceTheme,
}),
TypeScript
Next JS를 쓰는 관계로 _app.js에서 Theme를 전달한다.
이 때 route에 따라 service 를 얻을 수 있고 얻은 service 통해서 theme 를 생성해는 것을 볼 수 있다.
이는 우리 서비스의 특징 때문인데 하나의 도메인에 여러가지 다른 서비스를 제공하기 때문이다.
각각의 서비스는 색깔, 로고 등이 다르기 때문에 도메인 Path를 통해서 서비스 테마를 달리 제공해준다.
** 선배 개발자분의 마지막 유작 중 하나이다 


또한 theme 에는 color 객체를 담는 palette, 폰트 정보를 담는 typography 그 외에 Componenet들을 override 하고 있다. 또한 custom 에 각각의 service 가 가지고 있는 asset등의 변수들 역시 theme로 전달 했다.
2. Storybook을 통해 컴포넌트 문서화하기
이렇게 Theme와 Theme를 만든 UI들은 반드시 문서화되어야 된다고 생각한다.
만약 나와 일부 팀원만 사용방법을 알고 있다면, 이것을 시스템으로는 보기 어려울 것이라고 생각하기 때문이다.
또한 문서화는 새로 만들어야 할 컴포넌트인지 이미 있는 컴포넌트인지도 한 눈에 커뮤니케이션이 가능하다.
새로 들어오는 개발자들에게 히스토리를 공유할 수도 있게 된다.
개발 리소스들이 충분한 회사에서는 별도의 문서 시스템을 직접 개발 하기도 하던데, 그런 상황이 아니라면 Storybook을 통해서 쉽게 문서를 작성할 수 있다.
Storybook을 설명하는 글은 아니지만, 그래도 그 파워풀함을 잠시 알아보자.
애드온
스토리북은 인터렉션 및 스트레스 테스트등을 돕기위해 강력한 애드온 기능을 지원한다.
큰 커뮤니티 덕분에 스토리북은 별에 별 애드온들이 있고 잘 살펴본다면 정말 많은 기능을 사용할 수 있다.
인터렉션을 위한 액션 애드온
// src/Button.stories.js
import React from 'react';
import styled from 'styled-components';
import { action } from '@storybook/addon-actions';
// 버튼을 클릭하면, `action()`이 실행되고,
// 스토리북의 애드온 패널에 나타난다.
function ButtonWrapper(props) {
return <CustomButton {...props} />;
}
export const buttonWrapper = (args) => (
return <CustomButton {...props}/>;
// … etc ..
)
TypeScript
버튼이나 링크 같은 인터렉티브한 엘리먼트를 실행했을 때, 스토리북에서 액션 애드온은 UI 피드백을 줄 수 있다.
액션 애드온은 스토리북을 설치할 때 기본으로 같이 설치된다.
'액션'을 콜백 prop으로 컴포넌트에 전달하여 사용 가능하다.
영상에서 Link 컴포넌트를 클릭시 어떤 이벤트가 발생하고 어디로 이동하는지에 대한 Action 정보를 제공한다.
컴포넌트 스트레스 테스트를 위한 Controls
//src/Avatar.stories.js
import React from 'react';
// Controls을 사용하는 새로운 스토리
const Template = args => <Avatar {...args} />;
export const Controls = Template.bind({});
Controls.args = {
loading: false,
size: 'tiny',
username: 'Dominic Nguyen',
src: 'https://avatars2.githubusercontent.com/u/263385',
};
TypeScript
Controls Addon는 props를 조정하기 위해 자동으로 그래픽 UI를 생성한다.
예를 들어, 사이즈를 선택하는 엘리먼트("size")를 이용해서 아바타 사이즈를 동적으로 tiny, small, medium, large 중에서 골라 바꿀 수 있다.
이 방식은 컴포넌트의 나머지 props에도 ("loading", "username", "src")에도 동일하게 적용된다.
이렇게 사용자 친화적인 방법으로 컴포넌트 스트레스 테스트를 만드는 것이 가능해진다.
이는 개발팀 뿐만 아니라 디자인/기획팀도 쉽게 컴포넌트에 잠재된 이슈들을 발견할 수 있게 도와줄 수 있다.
Docs
문서화는 쉽지 않은 작업이다.
내가 작성한 코드를 다시 일일이 정리해야 하고, 어디까지 어떻게 설명해야 할지 사람마다 다 다르기 때문에 팀에서 문서화작업을 각자 한다면 통일성을 유지하기도 쉽지 않다.
각자 작업하는 공간이 다 다르거나 누구는 쓰고 누구는 쓰지 않는다면 사실상 죽은 문서나 다름 없게 되어 버린다.
스토리북을 이용하면 문서를 너무나도 쉽게 얻을 수 있다.
특히 타입스크립트를 이용한다면 정말 뛰어난 문서를 너무나도 쉽게 만들 수 있다.
import React from 'react'
import MaterialButton from '@material-ui/core/Button'
export interface ButtonProps {
/**
* 커스텀 색상을 지정합니다
*/
customColor?: string
/**
* 아래에서 버튼의 타입을 지정합니다.
*/
varient?: 'text' | 'outlined' | 'contained'
/**
* 테마 색상을 선택합니다.
*/
color?: 'primary' | 'secondary' | 'default'
/**
* 사이즈의 크기를 선택합니다
*/
size?: 'small' | 'medium' | 'large'
/**
* 클릭하면 발생하는 콜백 함수 입니다.
*/
onClick?: () => void
children: any
}
/**
* Primary UI component for user interaction
*/
export const Button: React.FC<ButtonProps> = ({
varient = 'contained',
size = 'medium',
customColor,
children,
...props
}) => {
return (
<MaterialButton
variant={varient}
color="primary"
style={{ backgroundColor: customColor }}
size={size}
{...props}
>
{children}
</MaterialButton>
)
}
TypeScript
위의 예시처럼 단지 interface를 정의하고 각각의 property 위에 주석을 달아두면 이 모든것이 아래와 같이 문서화된다.
각각의 Property의 Description와 Required Optional Default Value 값들의 여부 property 들의 Type 까지 확인 할 수 있다.
그리고 Docs역시 Control 컬럼을 제공하여 값들 임의로 바꿔볼 수 있다.
이러한 것들이 약간의 주석과 당연히 작성해야할 interface 를 통해 모두 제공된다.
스토리북을 활용하면 팀원들은 일관된 양식의 문서를 본인도 모르게 작성하게 되는 것이다.
마지막으로
여전히 갈 길이 멀다. 또 어렵다.
기술적으로는 크게 문제 될 것이 없으나 디자인 팀과의 소통, 넉넉하지 않은 시간에 대한 어려움.
또한 이제 부터 준비해 나가야 하는 단계이기 때문에 어디서부터 시작해야 할 지 막막할 때도 있다.
하지만 첫 포문을 잘 띄웠다고 생각한다. 모두가 문제 해결을 공감하고 어떻게 해결해나가야 할 지 생각을 나누었기에 가능한 일이 아니었을까 싶다.
앞으로도 지치지 않고 꾸준히 진행되어 디자인 가이드 적용 사례 라는 포스트로 다시 글을 쓰는 날이 왔으면 좋겠다.
이번 준비를 통해 느낀점은 "어떤 일이든 가장 중요한 건 커뮤니케이션이다."
복잡해 보이는 문제로 보여도 여러 시각에서 아이디어를 제시하고 업무를 분담하면
생각보다 별거 아닌 문제였음을 알게 된다. 

반대로 커뮤니케이션이 없다면 간단한 문제도 오해와 불신으로 문제를 어렵게 만든다. 