[데브인턴십]헬로우봇 프론트앤드 개발 인턴십 후기

임성연

업데이트:

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 입니다.

├── 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로직이 핵심적인 요소로 그 흐름을 간단히 살펴보려합니다.

img

Angular는 내장된 routing 시스템과 라우팅에대한 권한관리를 할 수 있는 Guard를 함께 제공해줍니다. Angular Router는 브라우저의 url을 읽어 랜더링되는 요소를 제어하게끔 도와주고 Guard는 canActivate에 설정해둔 조건에 따라 페이지 접근을 제어할 수 있습니다. 아래 코드를 살펴보죠.

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의 동작을 구독하고 데이터를 비동기적으로 처리합니다. 흐름은 아래와 같습니다.

img

interceptor의 역할은 다양하지만 주로 500번 error에 따른 리트라이, 권한(token)과 관련된 처리, os나 브라우저와 관련되어 달리 처리해야하는 부분을 관리합니다.

컴포넌트는 아래 코드와 같이 httpClient의 동작을 구독하여 데이터를 비동기 처리할 수 있습니다

@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개의 탭만이 접근 가능하게 변경되었습니다.

image

따라서 새로운 가드의 구축이 요구되었고 admin-editor.guard를 통해 띵스플로우 에디터가 노출관리에 접속할 수 없도록 하였고 admin-menu.guard를 통해 일반에디터가 노출관리에서 접근할 수 있는 탭을 제한하였습니다.

2.서버에서 내려주는 데이터에 따른 업무 관리

언제라도 바뀔 수 있는 노출 필드의 제어를 아예 프론트단이 아닌 서버에서 내려주는 데이터를 통해 관리하게 되었습니다. 유저 정보에 adminMenu 필드가 추가되었고 editableFeilds가 추가되어 이 정보를 토대로 노출되는 테이블의 컬럼이 변경되는 로직을 구축하고 템플릿에 추가하였습니다.

아래 adminMenu Guard는 2개의 로직이 적용된 코드입니다.

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에 기록된 만큼 구성을 어렵지 않게 진행할 수 있었습니다.

image

2.딥링크

모바일 환경에 놓인 유저가 웹사이트 혹은 다른 앱에서 URL을 클릭했을 때, 앱을 실행시키고 특정 페이지로 이동하도록 돕는 기술입니다. 스킬스토어의 딥링크의 동작은 urlScheme과 airbridge란 외부 서비스로 이루어져 있으며 앱으로 연결할 버튼의 id를 이동할 딥링크와 함께 airbridge에 등록하여 동작합니다.

3.Google Analytics

Google Analytics(이하 GA)란 웹사이트 방문자의 데이터를 수집해서 분석함으로써 온라인 비즈니스의 성과를 측정하고 개선하는 데 사용하는 웹로그분석 도구입니다. 웹사이트에서 사용자의 등록된 동작들은 Google Tag manager를 통해 이벤트를 수집해 GA로 이벤트를 쏴주는 방식으로 진행됩니다.

스킬스토어에는 analytic.service에 GA에 특정 이벤트를 날릴 수 있는 함수가 개발되어 있습니다.따라서 유저의 동작에 따라 기획된 이벤트를 GA로 날릴 수 있도록 eventListener를 등록하는 작업을 진행하였습니다.

// 마이봇 화면의 link를 클릭했을 시 GA로 이벤트를 보내는 코드
triggerTouchAccessLink(...eventFlag) {
  if (typeof gtag !== 'undefined') {
    this.gaEvent('이벤트 플래그', {
      ...eventFlag
    });
  }
}

이후 테스트 서버에 배포, QA를 진행하고 작업이 마무리되었습니다.

작업 후기

GA, deeplink등 기존에 경험해보지 못한 코드 외적 시스템을 직접 활용해보는 작업은 흥미로우면서도 막막한 작업이었습니다. 아예 새로운 화면을 개발하는 만큼, 커뮤니케이션이 많이 필요하였고, 업무 프로세스를 잘 이해하지 못한 시점에서 개발 보다는 개발 외적인 프로세스에서 힘들었던 작업이었습니다.

좀더 어떻게 일을 진행하면 좋을지 구체화 시킨 후 작업을 들어가면 좋았을 것 같습니다.

리펙토링

처음에 스튜디오, 스킬스토어의 코드를 보면서 느낀점은 “왜 이렇게 되어있을까?” 였습니다. 제가 프론트엔드 작업에 대해 배운 점과 많이 달랐던 부분들이 존재해서 였지요. 재사용성, 공통모듈화, 합성 컴포넌트, props를 통한 컴포넌트의 상태관리, 버저닝 문제 등등, 개선할 수 있어보이는 부분들이 손쉽게 보였죠. 따라서 스튜디오의 리펙토링을 목표로 잡고 작업 기획을 해보았습니다.

작업 과정

먼저 리펙토링의 목적은 개발을 효율적으로 할 수 있게끔 하는 것이었습니다. 그에 대한 세부 목표로 폼 입력 컴포넌트 공용 모듈화를 우선적으로 내세웠습니다. 테크스펙을 작성하고 예시 작업을 진행해보았죠.

입력 컴포넌트 공용 모듈화

스튜디오의 대부분의 페이지는 데이터를 보여주는 테이블과 수정이나 생성을 으로 구성되어 있습니다. 테이블은 table.component.ts에공통 컴포넌트로 구성되어 있지만 폼은 그러하지 못하였습니다. 예를 들면 시간을 입력하는 폼은 아래와 같이 몇몇 패턴을 가지고 존재하였고 이를 공통화 한 컴포넌트를 개발/적용해보았습니다.

시간 입력 폼은

1.기간을 선택하거나 2.단일시간만 선택하거나 로 나뉘고

1.시간만 선택하거나 2.날짜를 포함해서 선택하거나로 나뉘며

1.팁이 하단에 적히는가 2.팁이 하단에 적히지 않는가로 나뉘며

1.특정요소의 선택 가능여부로 나뉘고

1.기간 선택을 돕는 버튼이 있는가 없는가로

나뉘었습니다. 이를 type과 해당 타입에 해당하는 property를 받아 실행하는 범용 컴포넌트를 개발해보았고 이에따라 아래와 같은 코드 절감효과를 얻었습니다.

이외에 텍스트 인풋 폼, 라디오 버튼 인풋 폼 등을 개발해서 적용 예정이었습니다.

리펙토링의 문제

결국 이 프로젝트는 실패하였습니다. 여러가지 원인이 있겠지만 주 원인은 “현재 진행중인 작업(스프린트)과 어우러질 수 있는가”“이 부분이 생산성 향상에 도움이 되는가?”라고 생각합니다. 헬로우봇 팀은 프로젝트 스프린트를 빠르게 쳐내고 있고 계속해서 새로운 스프린트가 들어옵니다. 그 과정에서 리펙토링이 작업과 어우러질 수 있을만큼 충분히 고민했는가에 대해 그러지 못했다고 생각합니다.

또한 두 스프린트를 진행하면서 많은 부분이 바뀌었고 이에 대응하는 개발이 요구되었는데, 이런 부분이 리펙토링에서 기획한 컴포넌트시스템에서 가능한가에 대해 ‘NO’라고 답할 수 있었습니다. 제가 개발한 공통 컴포넌트는 그런 부분에 쉽게 대응하지 못하였고 생산성의 향상은 커녕 오히려 기존의 개발 가이드를 해치는 결과를 가져오게 되었죠.

그외에도 앵귤러 버전 업, 정적 코드 별도 파일화 등 몇몇 기획이 더 있었지만 모두 적용되기 어렵다는 결론에 다다랐습니다. 결국 이 리펙토링이 저의 경험에서 시작하여 “이렇게 하면 더 낫지 않을까?”라는 생각에서 구체화되지 못하고 현재 프로젝트와 어우러지도록 하지 못한 부분이 가장 아쉬운 점이었습니다.

작업 후기

앞선 2개의 스프린트도 좋은 경험이었지만 리펙토링을 고안해보고 실패한 경험은 단순히 “개발”이 아닌 “상황에 맞는 개발”이 얼마나 중요한지 알게끔 하였고 리펙토링이라는 작업이 책에서 보던것 만큼 단순한 작업이 아님을 알게하였습니다. 언젠가 리펙토링이라는 작업을 꼭 다시 해보고 싶을 정도로요.

인턴십을 마치며

두달간의 작업이 언제 지나간지 모를만큼 빠르게 지나갔습니다. 기간동안 좋았던 부분도 아쉬운 부분도 있지만 어느쪽이든 개발자로써 한단계 성장하는 계기가 되었습니다. 두서없는 글이지만 제가 경험한 부분이 잘 전달되었으면 좋겠습니다.

띵스플로우 팀은 자기의 일을 좋아하고 잘하는 사람들 입니다. 사용자와 서비스를 중심으로 빠르게 실행하고 학습하며, 다양한 직무의 사람들이 협업을 통해 시너지를 내고 있습니다. 다양한 콘텐츠 혁신을 이루고 있는 띵스플로우 팀에 함께할 분을 찾습니다! 언제든 people@thingsflow.com로 이메일을 주시기 바랍니다!

태그: ,

카테고리:

업데이트:

댓글남기기