--- title: "[데브인턴십]헬로우봇 프론트앤드 개발 인턴십 후기" excerpt: "---" author: seongyeon.lim categories: - tech tags: - frontend - angular last_modified_at: 2022-08-23T18:09:00-05:00 --- 7, 8월 2달간의 인턴이 끝났습니다. 인턴기간동안 많은 것을 배웠고 개발하였습니다. 저는 인턴기간동안의 겪은 일들을 헬로우봇이란 프로젝트의 구조, 제가 작업한 스프린트, 그리고 별도로 진행했던 리펙토링의 3가지 파트로 이야기 해보려합니다. # 헬로우봇 프로젝트의 구조 ## 스킬스토어와 스튜디오 헬로우봇 프로젝트는 2개의 웹서비스를 가지고 있는데 **헬로우봇 스킬스토어**(이하 스킬스토어)와 **헬로우봇 스튜디오**(이하 스튜디오)입니다. 스킬스토어는 헬로우봇의 주력 상품인 **스킬**을 판매하는 웹 스토어이고 스튜디오는 그러한 **스킬**을 개발할 수 있는 GUI 툴 서비스입니다. 두 프로젝트의 프론트는 모두 Angular로 개발되어있고 travisCI와 AWS를 통해 배포되고 있습니다. 다만 스튜디오는 특정 에디터를 대상으로 한 만큼 범용성 고려 중요도가 낮아 SPA로 일반 배포되어있지만 스킬스토어는 일반 유저들을 대상으로 하므로 검색(SEO), 접근성등을 고려한 ssr로 배포되어 있고 GA(google analytics), hackle(A/B test tool)와 같은 분석도구와도 연결되어 있습니다. ## 두 프로젝트의 코드 구조 먼저 프론트개발자일지라도 생소할 Angular를 간단히 설명하겠습니다. Angular는 typescript class 문법을 기반으로 작성된 통합 프레임워크입니다. 통합 프레임워크란 react, vue와 달리 http통신에 대한 모듈 등 외부라이브러리에 의존해야하던 부분을 내부에 포함시킨 프레임워크란 의미입니다. Angular의 요소들은 앵귤러모듈(NgModule)로 묶여서 외부로 제공됩니다. 이는 기존 js의 module패턴과 동일한 형태를 띕니다. 코드 구조는 Angular를 기반으로 한 만큼 Angular의 골조를 따릅니다. 큰 디렉토리 구성은 shared, module, core 입니다. ```bash ├── app │ ├── app.module.ts │ ├── app.component.ts │ ├── app.component.html │ ├── core │ ├── modules │ └── shared ├── assets -> 정적파일 ├── environment -> 배포환경에 따른 설정 파일 ├── main.ts -> build 진입점 ├── index.html -> 진입 템플릿 ├── ... ``` **shared**: 프로젝트에서 공통적으로 사용되는 요소를 가지는 앵귤러 모듈(NgModule)입니다. `directive`, `pipe`와 같이 템플레이트에 적용될 요소와 공통적으로 쓰이는 `컴포넌트`(ex> 경고창)들로 구성되어 있습니다. **module**: 프로젝트에서 특정 기능을 가지는, 혹은 특정 페이지를 구성하는 컴포넌트와 서비스를 하나의 모듈로 묶어둔 앵귤러 모듈(NgModule)입니다. 예를 들면 대부분의 페이지에서 쓰이는 skill과 관련된 모듈은 `skill.module`에서 통합적으로 제공됩니다. **core**: module이 페이지의 구성을 담당한다면 core는 로직의 구성을 담당하는 요소들로 구성되어 있습니다. 예를 들면 http통신의 앞뒤에 붙어 http통신을 제어하는 `interceptor`라던가, 페이지 접근을 제어하는 `guard`나 모든 페이지에서 공통으로 쓰이는 loading화면을 제어하는 `서비스`등을 포함합니다. ## 서비스 로직의 흐름 웹 서비스인 만큼 routing과 api로직이 핵심적인 요소로 그 흐름을 간단히 살펴보려합니다. ![guard](https://user-images.githubusercontent.com/73334068/186595553-85d4917d-2ad7-4aca-b532-3b0a779879b8.png) Angular는 내장된 routing 시스템과 라우팅에대한 권한관리를 할 수 있는 Guard를 함께 제공해줍니다. Angular Router는 브라우저의 url을 읽어 랜더링되는 요소를 제어하게끔 도와주고 Guard는 canActivate에 설정해둔 조건에 따라 페이지 접근을 제어할 수 있습니다. 아래 코드를 살펴보죠. ```typescript export class SomeGuard implements CanActivate, CanActivateChild, Resolve<Admin> { constructor( authService: AuthService ) {} canActivate(next: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<boolean> | Promise<boolean> | boolean { return this.checkLogin(); } private checkLogin(): boolean { if (this.authService.isLogin) { return true; } this.router.navigate(['/login']); return false; } } ``` canActivate는 라우팅 서비스에 등록되어 해당 패스로 이동할때 동작하여 return값(true, false)에 따라 페이지의 접속 여부를 판단합니다. 다음은 api로직입니다. angular는 Http 통신을 지원하는 httpClient를 제공하고 HttpInterceptor를 통해 http request의 진입점과 response의 진입점에 특정 로직을 집어넣을 수 있습니다. 컴포넌트는 rxjs패턴을 통해 HttpClient의 동작을 구독하고 데이터를 비동기적으로 처리합니다. 흐름은 아래와 같습니다. ![interceptor](https://user-images.githubusercontent.com/73334068/186595556-5c8a73be-6b73-47c7-9d5b-56a60c5411e2.png) interceptor의 역할은 다양하지만 주로 500번 error에 따른 리트라이, 권한(token)과 관련된 처리, os나 브라우저와 관련되어 달리 처리해야하는 부분을 관리합니다. 컴포넌트는 아래 코드와 같이 httpClient의 동작을 구독하여 데이터를 비동기 처리할 수 있습니다 ```typescript @Component({ selector: 'some-component', // 비동기적으로 data$의 값을 받음 template: ` <ng-container *ngIf="data$ | async"> ... </ng-container> `, styleUrls: ['./friend-detail.component.scss'] }) export class SomeComponent implements OnInit { public data$: Observable<Data>; constructor( private httpClient: HttpClient, ) { } ngOnInit(): void { this.data$ = this.HttpClient.get<Data>(url).pipe( map(process...) ); } } ``` 이외에도 form에대한 처리방식등 다양한 로직 흐름이 존재하지만 크게 저 2가지 패턴으로 유저의 동작을 제어하고 있습니다. # 작업했던 스프린트 2개월동안 2개의 스프린트가 진행되었고 하나는 스튜디오와, 하나는 스킬스토어 작업이었습니다. 앞서말했듯이 두개의 구조는 유사하면서도 다르고, 작업또한 스튜디오는 기존의 피쳐를 수정하는 작업이었고 스킬스토어는 새로운 피쳐를 만드는 작업이었기에 각각의 작업에서 느낀점이 달랐습니다. ## 스프린트 1. 헬로우봇 스튜디오 기존 페이지 변경 ### 배경 스튜디오는 원래 에디터분들을 대상으로 개발된 UI툴입니다. 따라서 기존 권한은 2가지만 동작하였고 권한 관리가 크게 필요하지 않았습니다. 하지만 스튜디오가 일반 사용자 대상 공개 목적으로 스프린트가 진행되었고 유저들의 권한을 세분화함(슈퍼에디터, 3단계 에디터 -> 슈퍼에디터, 띵스플로우 에디터, 일반에디터)에따라 권한에 따른 기능 노출을 제한할 필요가 생겼습니다. 이에따라 기존에 어드민(관리 페이지)가 노출관리로 변경되고 권한에 따른 테이블 필드노출과 수정페이지의 수정가능한 필드가 변경되어야하는 작업을 요하게 되었습니다. ### 작업 **1.새로운 가드의 구현** 권한이 분리되었고 권한에 따른 페이지의 접속 권한이 달라졌으니 새로운 가드를 작성할 필요가 생겼습니다. 기존의 노출관리에선 총 (TODO)개의 탭이 존재하나 변경된 사항에선 띵스플로우 에디터는 아예 해당 탭에 접근이 불가하고 일반 에디터는 3개의 탭만이 접근 가능하게 변경되었습니다. ![editor](https://user-images.githubusercontent.com/73334068/186595516-2b7561a2-9b2d-4403-ab01-56c8135e6829.png) 따라서 새로운 가드의 구축이 요구되었고 `admin-editor.guard`를 통해 띵스플로우 에디터가 노출관리에 접속할 수 없도록 하였고 `admin-menu.guard`를 통해 일반에디터가 노출관리에서 접근할 수 있는 탭을 제한하였습니다. **2.서버에서 내려주는 데이터에 따른 업무 관리** 언제라도 바뀔 수 있는 노출 필드의 제어를 아예 프론트단이 아닌 서버에서 내려주는 데이터를 통해 관리하게 되었습니다. 유저 정보에 `adminMenu` 필드가 추가되었고 `editableFeilds`가 추가되어 이 정보를 토대로 노출되는 테이블의 컬럼이 변경되는 로직을 구축하고 템플릿에 추가하였습니다. 아래 adminMenu Guard는 2개의 로직이 적용된 코드입니다. ```typescript export class AdminMenuGuard implements CanActivate, CanActivateChild { ... canActivate( next: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<boolean | UrlTree> | Promise<boolean | UrlTree> | boolean | UrlTree { ... return this.checkType(state.url); } ... private checkType(url: string): Observable<boolean> { //auth 서비스는 서버로부터 내려오는 유저의 정보를 담고 있습니다. return this.authService.loggedInEditor$.pipe( ... // 서버로부터 내려온 정도를 받아 현재 접속하려 하는 url이 허용되는 필드인지 확인합니다. switchMap(userInfo => { return of( // `check user info access in url` ); }), ) } } ``` 이후 코드리뷰, QA, 운영배포를 통해 작업이 마무리되었습니다. ### 작업 후기 첫 작업을 진행하면서 다양한 부분을 깨닫게 되었습니다. 특히 이 과정에는 리펙토링을 포함한 작업을 고민하며 진행하면서 기존 작업된 코드를 변경하는 작업이 어떻게 어려운가, 리펙토링이 왜 어려운가를 직접적으로 깨닿게 되었습니다. 기존 코드가 방대하고 Angular라는 프레임워크, bootstrap style의 스타일링, 생소한 분야 사이에서 제 코드는 방향성을 잃기 일쑤였습니다. 특히 리펙토링에 대해 생각하고 작업한 부분이 오히려 기존의 작업을 방해하고 스타일을 깨는 일을 초래하였죠. 너무 성급하게 작업을 들어갔고 좀 더 상세한 부분을 생각하고 오랜 기획 후 작업하면 어떠하였을까라는 아쉬움이 남는 작업이었습니다. ## 스킬스토어 마이봇 프로필 페이지 개발 ### 배경 기존에 헬로우봇 앱을 플랫폼화 진행하면서 웹 서비스인 스킬스토어에 링크로 **마이봇**을 접속하게 하는 기능이 기획에 추가됨에 따라 해당 페이지를 직접 개발해야하는 업무가 추가되었습니다. 스튜디오와 달리 아예 새로운 페이지를 개발하는 작업이었습니다. ### 작업 **1.신규 페이지 구현** 마이봇화면의 구성은 상단의 프로필 화면, 링크의 리스트, 스킬의 리스트, 그리고 링크리스트로만 구현된 별도의 화면이 존재하였고, 앱 연결을 위한 팝업창으로 구성되었습니다. 스킬스토어는 design system을 부트스트랩(bootstrap)과 동일한 시스템을 사용해 css 클래스를 통해 디자인을 구축하였고 design guide에 기록된 만큼 구성을 어렵지 않게 진행할 수 있었습니다. ![linklist](https://user-images.githubusercontent.com/73334068/186595559-39893054-cb48-48a2-9a0c-48154468cd99.png) **2.딥링크** 모바일 환경에 놓인 유저가 웹사이트 혹은 다른 앱에서 URL을 클릭했을 때, 앱을 실행시키고 특정 페이지로 이동하도록 돕는 기술입니다. 스킬스토어의 딥링크의 동작은 urlScheme과 airbridge란 외부 서비스로 이루어져 있으며 앱으로 연결할 버튼의 id를 이동할 딥링크와 함께 airbridge에 등록하여 동작합니다. **3.Google Analytics** Google Analytics(이하 GA)란 웹사이트 방문자의 데이터를 수집해서 분석함으로써 온라인 비즈니스의 성과를 측정하고 개선하는 데 사용하는 웹로그분석 도구입니다. 웹사이트에서 사용자의 등록된 동작들은 Google Tag manager를 통해 이벤트를 수집해 GA로 이벤트를 쏴주는 방식으로 진행됩니다. 스킬스토어에는 analytic.service에 GA에 특정 이벤트를 날릴 수 있는 함수가 개발되어 있습니다.따라서 유저의 동작에 따라 기획된 이벤트를 GA로 날릴 수 있도록 eventListener를 등록하는 작업을 진행하였습니다. ```typescript // 마이봇 화면의 link를 클릭했을 시 GA로 이벤트를 보내는 코드 triggerTouchAccessLink(...eventFlag) { if (typeof gtag !== 'undefined') { this.gaEvent('이벤트 플래그', { ...eventFlag }); } } ``` 이후 테스트 서버에 배포, QA를 진행하고 작업이 마무리되었습니다. ### 작업 후기 GA, deeplink등 기존에 경험해보지 못한 코드 외적 시스템을 직접 활용해보는 작업은 흥미로우면서도 막막한 작업이었습니다. 아예 새로운 화면을 개발하는 만큼, 커뮤니케이션이 많이 필요하였고, 업무 프로세스를 잘 이해하지 못한 시점에서 개발 보다는 개발 외적인 프로세스에서 힘들었던 작업이었습니다. 좀더 어떻게 일을 진행하면 좋을지 구체화 시킨 후 작업을 들어가면 좋았을 것 같습니다. # 리펙토링 처음에 스튜디오, 스킬스토어의 코드를 보면서 느낀점은 "왜 이렇게 되어있을까?" 였습니다. 제가 프론트엔드 작업에 대해 배운 점과 많이 달랐던 부분들이 존재해서 였지요. 재사용성, 공통모듈화, 합성 컴포넌트, props를 통한 컴포넌트의 상태관리, 버저닝 문제 등등, 개선할 수 있어보이는 부분들이 손쉽게 보였죠. 따라서 스튜디오의 리펙토링을 목표로 잡고 작업 기획을 해보았습니다. ## 작업 과정 먼저 리펙토링의 목적은 **개발을 효율적으로 할 수 있게끔** 하는 것이었습니다. 그에 대한 세부 목표로 **폼 입력 컴포넌트 공용 모듈화**를 우선적으로 내세웠습니다. 테크스펙을 작성하고 예시 작업을 진행해보았죠. ### 입력 컴포넌트 공용 모듈화 스튜디오의 대부분의 페이지는 데이터를 보여주는 **테이블**과 수정이나 생성을 **폼**으로 구성되어 있습니다. 테이블은 `table.component.ts`에공통 컴포넌트로 구성되어 있지만 폼은 그러하지 못하였습니다. 예를 들면 시간을 입력하는 폼은 아래와 같이 몇몇 패턴을 가지고 존재하였고 이를 공통화 한 컴포넌트를 개발/적용해보았습니다. <figure class="half"> <img width="379" alt="time1" src="https://user-images.githubusercontent.com/73334068/186595562-6302e242-cce0-44ef-be85-b3f1c36f905a.png"> <img width="375" alt="time2" src="https://user-images.githubusercontent.com/73334068/186595564-e97e9b39-4611-42ed-957a-ac5bc23a6c79.png"> </figure> <img width="765" alt="time3" src="https://user-images.githubusercontent.com/73334068/186595568-f1244242-3f05-457f-a23b-dd64833ff76a.png"> 시간 입력 폼은 **1.기간을 선택하거나 2.단일시간만 선택하거나 로 나뉘고** **1.시간만 선택하거나 2.날짜를 포함해서 선택하거나로 나뉘며** **1.팁이 하단에 적히는가 2.팁이 하단에 적히지 않는가로 나뉘며** **1.특정요소의 선택 가능여부로 나뉘고** **1.기간 선택을 돕는 버튼이 있는가 없는가로** 나뉘었습니다. 이를 type과 해당 타입에 해당하는 property를 받아 실행하는 범용 컴포넌트를 개발해보았고 이에따라 아래와 같은 코드 절감효과를 얻었습니다. <img width="379" alt="time1" src="https://user-images.githubusercontent.com/73334068/186595562-6302e242-cce0-44ef-be85-b3f1c36f905a.png"> ![effect2](https://user-images.githubusercontent.com/73334068/186595545-a2a42a8c-07fe-44a5-995f-bcca78af9f04.png) <img width="375" alt="time2" src="https://user-images.githubusercontent.com/73334068/186595564-e97e9b39-4611-42ed-957a-ac5bc23a6c79.png"> <img width="1290" alt="effect3" src="https://user-images.githubusercontent.com/73334068/186595551-85e4fddc-0493-441f-b931-29a9b3de9b8b.png"> </figure> <img width="765" alt="time3" src="https://user-images.githubusercontent.com/73334068/186595568-f1244242-3f05-457f-a23b-dd64833ff76a.png"> ![effect1](https://user-images.githubusercontent.com/73334068/186595534-e57480ca-25cb-40cb-bf4f-37e3beb1778d.png) 이외에 텍스트 인풋 폼, 라디오 버튼 인풋 폼 등을 개발해서 적용 예정이었습니다. ### 작업 후기 리펙토링 작업은 최종적으로 진행하지 않았으며 그 이유는 아래와 같습니다. **모든 컴포넌트에 대응하는 형태로의 개발 어려움** - 위의 시간 입력 폼은 가장 잘 된 예시를 가져온 것이지만 스튜디오의 컴포넌트들은 기능, 디자인에서 변주가 많은편이었고 또한 확실하게 정해진 디자인 시스템이 없었기에 기능이 추가되거나 변할때 이를 대응할 수 있는 컴포넌트로 설계되었어야하는데, 제 역량으론 이 기획을 하기위해 많은 공수가 들 것이 분명했고 이에 따라 개발을 중단하였습니다. 리펙토링을 진행하지 못한것은 아쉽지만 작업을 고안해보면서 컴포넌트 공용화를 위해 어떤 부분을 중점시 해야하는지, 어떤 부분이 장애로 다가올 수 있는지를 파악할 수 있었습니다. # 인턴십을 마치며 두달간의 작업이 언제 지나간지 모를만큼 빠르게 지나갔습니다. 기간동안 좋았던 부분도 아쉬운 부분도 있지만 어느쪽이든 개발자로써 한단계 성장하는 계기가 되었습니다. 두서없는 글이지만 제가 경험한 부분이 잘 전달되었으면 좋겠습니다.