Angular2에서 Typescript를 사용하는 이유

Angular2 코어 멤버인 victorsavkin의 글 “Angular2: Why TypeScript“을 허락을 받고 번역 하였습니다. 번역에 대한 피드백이나 오류는 주저없이 트윗이나 메일로 알려주세요.

This post is the translation version of the original post “Angular2: WhyTypeScript” with allowed to translate by the author victorsavkin.

Angular2, TypeScript 로고

Angular2는 TypeScript로 작성된 코드이다. 이 글에서는 왜 Angular2에서 TypeScript를 사용하기로 했는지 설명할 것이다. 더불어 TypeScript를 사용하면서 코드 작성과 리팩토링시 어떤 영향을 받았는지 느꼈던 경험들을 함께 나눌 것이다.

 

나는 TypeScript를 좋아하지만 여러분들도 그럴 필요는 없다.

Angular2는 TypeScript로 작성되었지만 여러분도 Angular2 애플리케이션을 TypeScript로 작성할 의무는 없다. Angular2 프레임워크는 ES5, ES6와 Dart 언어로도 훌륭하게 작업할 수 있다.

 

TypeScript는 훌륭한 도구가 있다.

TypeScript의 가장 큰 장점은 도구의 지원이다. TypeScript는 높은 수준의 코드 자동생성, 코드 탐색과 리팩토링을 제공한다. 이러한 도구들은 일정 규모 이상의 프로젝트에서는 거의 필수적인 요소이다. 도구의 지원이 없다는 것은 거의 수정하기 어려운 상태의 코드 베이스에 코드를 수정해야만 하는 두려움을 심는 일이고, 대규모의 리팩토링 시 큰 위험성과 비용을 감수해야한다는 것을 의미한다.

TypeScript만이 Javascript로 컴파일할 수 있는 유일한 타입언어는 아니다. 이론적으로 완벽하게 들어맞는 도구를 제공하는 강력한 타입 시스템을 갖춘 다른 언어들도 있다. 그러나 이러한 언어들은 실용적인 관점에서 대부분 컴파일러를 제외하면 아무 것도 가지고 있지 않다. 이러한 차이는 TypeScript팀은 시작부터 풍성한 개발도구를 만드는 것을 명확한 목표로 갖고 있었기 때문이다. 그래서 TypeScript팀이 에디터에서 타입체크 기능과 코드완성을 제공할 수 있도록 언어를 만든 이유이다. TypeScript 개발을 돕는 훌륭한 도구들이 여러 에디터에 즐비한 이유가 궁금했다면, 그 답은 바로 TypeScript 언어의 지원때문이다.

Intellisens, Rename과 같은 기본적인 리팩토링을 안정적으로 지원한다는 사실은 코드를 작성하는 과정과 리팩토링 할 때 매우 큰 영향을 미친다. 측정하기 어렵지만, 이전에 며칠씩 걸렸을 법한 리팩토링 작업이 이제는 하루도 안되는 시간안에 끝나는 것 같다.

TypeScript는 코드 수정의 경험을 극대화해주는 반면, 개발환경 구축은 특히 간단하게 페이지에 ES5스크립트를 떨어뜨리는 기존 방식과 비교할 때 좀 더 복잡해졌다는 측면이 있다. 게다가 Javascript 소스코드를 분석하는 도구류(예를 들면 JSHint)를 사용할 수 없다. 그러나 적절한 대안이 있다.

 

TypeScript는 JavaScript를 포함한 확장된 언어이다.

TypeScript는 JavaScript를 포함한 확장된 언어이기 때문에, 여러분은 TypeScript로 코드를 전환하기 위해 크게 코드를 재작성할 필요가 없다. 여러분은 시간을 고려하여 하나의 모듈씩 점진적으로 전환해도 된다.

하나의 모듈을 가볍게 골라 js 파일을 ts로 바꿔보자. 그리고 점차 하나씩 타입 정보를 추가하자. 작업이 끝나면 순차적으로 다른 모듈을 골라 똑같은 작업을 하면 된다. 일단 전체 코드베이스가 타입정보를 갖추게 되면, 그때 여러분은 TypeScript의 컴파일러 설정을 좀 더 엄격하게 조정할 수도 있다.

이러한 과정은 시간이 좀 필요할 수도 있지만 Angular2를 TypeScript로 전환했던 과정을 보면 그리 큰 문제가 아니었다. 점진적으로 이러환 과정을 진행하면서도 우리는 새로운 기능을 개발하면서 버그를 수정하는 일을 지속할 수 있었다.

 

TypeScript는 추상화를 명시적으로 지원한다.

좋은 설계는 거의 잘 정의된 인터페이스와 등치된다. 그리고 언어가 지원한다면, 인터페이스를 의도한대로 표현하는 것이 훨씬 더 쉬워진다.

예를 들어, 도서 판매 애플리케이션을 가정하고 UI나 API와 같은 류의 외부 시스템을 통해 등록된 사용자가 구매할 수 있다고 해보자.

Purchaser UI diagram

여러분이 보는 것과 같이, User, ExternalSystem 두 클래스는 모두 구매자의 역할을 한다. 구매자의 개념이 이 애플리케이션에서 매우 중요한 부분임에도 불구하고 코드로 명확하게 표현되지 않는다. purchase.js라는 이름의 파일도 없다. 결과적으로 누군가가 코드를 수정할 때 이러한 역할이 있다는 사실을 놓칠 가능성을 내포하게 된다.

위의 코드만 들여다 봐서는 어떤 객체가 구매자의 역할을 하고, 이 역할을 포함하는 메서드가 무엇인지 논하기가 쉽지 않다. 우리는 확실하게 알 수도 없고 도구의 지원을 받아도 많은 도움을 얻을 길이 없다. 우리는 이러한 정보를 매우 느리고 오류를 일으키기 쉽지만 수동으로 추론해야만 한다.

자, 이번에는 Purchaser라는 인터페이스를 명시적으로 정의한 버전과 이전 버전의 코드를 비교해 보자.

타입정보가 포함된 버전은 명확하게 Purchaser인터페이스를 갖고 있고 UserExternalSystem 클래스가 이를 구현하고 있음을 알 수 있다. 따라서 TypeScript의 인터페이스는 추상화, 프로토콜, 역할의 정의를 가능하게 한다.

TypeScript가 추상화 정보를 추가로 포함하도록 강제하는 것이 아니라는 사실을 인지하는 것이 중요하다. Purchaser의 추상화된 정보는 명시적으로 정의되지 않았을 뿐 Javascript버전의 코드에서도 포함되어 있다.

정적타입 언어에서는 하위시스템 간의 경계가 인터페이스를 통해 정의된다. Javascript는 인터페이스가 없으므로, 순수한 Javascript로는 경계가 잘 표현되지 않는다. 명확하게 경계를 확인하기 어려운 경우에 개발자들은 추상화된 인터페이스가 아닌 구상타입에 의존하기 시작하고 이는 강력한 커플링으로 귀결된다.

나는 TypeSciprt를 적용하기 전과 전환된 후의 Angular2를 모두 사용하면서 이러한 믿음이 더 강화되었다. 인터페이스를 정의하는 작업은 내게 API의 경계를 고민하도록 강제하고, 하위시스템의 퍼블릭 인터페이스를 정의하도록 도우며 부수적으로 일어나는 커플링을 드러나게 한다.

 

TypeScript는 코드를 읽기 쉽게하며 이해를 돕는다.

그렇다. 나는 이 명제가 직관적이지 않다는 것을 알고 있다. 내 의견을 예제를 통해서 좀 더 상세하게 설명해 보겠다. jQuery.ajax() 함수를 살펴보자. 다음의 시그니쳐에서 우리는 어떤 종류의 정보를 얻을 수 있는가?

우리가 확실하게 말 할 수 있는 사실은 이 함수가 두가지 인수를 취한다는 것이다. 우리는 타입을 추론해야한다. 아마도, 첫번째 인수가 string이고 두번째는 설정을 위한 객체라고 가정일 것이다. 그러나 이는 단지 추론에 불과하며 틀릴 수도 있다. 우리는 어떤 옵션을 설정객체(설정정보의 이름이나 타입)에 넣어야 하는지, 이 함수가 무엇을 반환하는지 아무런 정보를 가지고 있지 않다.

소스코드를 확인하거나 문서를 보지 않는 이상 우리는 이 함수를 호출할 방법이 없다. 소스코드를 확인하는 것은 좋은 선택은 아니다. 왜냐하면 함수와 클래스를 사용한다는 것의 핵심은 어떻게 구현되었는지를 알지 않고도 이를 사용할 수 있어야 한다는 점이다. 다시 말하면, 우리는 구현체가 아닌 인터페이스를 활용해야 한다는 점이다. 우리는 문서를 확인할 수도 있지만 이 역시 개발자에게 유익한 경험은 아니다. 왜냐하면 이는 추가적인 시간을 소모하게 되고 때론 문서 자체가 옛 버전일 수 있기 때문이다.

jQuery.ajax(url, settings)이 읽는데 어려움이 없다고 하더라도, 정말로 이 함수를 어떻게 호출되는지 이해하려면 우리는 구현된 소스를 읽거나 문서를 봐야만 한다.

이제, 타입정보를 포함한 코드와 대조해 보자.

이 코드는 좀 더 많은 정보를 제공해 준다.

  • 이 함수의 첫번째 인수는 string이다.
  • 설정객체 인수는 선택사항이다. 우리는 이 함수에 전달된 옵션을 설정이름 뿐 아니라 타입까지 모두 확인할 수 있다.
  • 함수는 JQueryXHR객체를 반환하고, 우리는 이 객체의 속성과 함수도 알 수 있다.

타입 정보가 더해진 코드는 분명히 타입이 없을 때 보다 길어졌지만, :string, :JQueryAjaxSettings, JQueryXHR는 결코 혼란을 주는 불필요한 정보가 아니다. 이는 코드의 이해도를 높여주는 중요한 문서이다. 우리는 코드의 구현체나 문서를 읽지 않아도 좀 더 깊은 수준으로 코드를 이해할 수 있다. 내 개인적인 경험으로 비추어 보면 타입정보가 기술된 코드가 좀 더 많은 맥락을 제공하기 때문에 코드를 읽는 속도가 더 빠르다. 누구라도 타입정보가 코드의 가독성에 어떤 영향을 주는지에 대한 연구를 찾았다는 댓글로 알려주기를 부탁한다.

TypeScript가 Javascript로 컴파일되는 다른 여러 언어와 비교할 때의 한가지 차이점은 TypeScript에서 타입정보는 선택사항이라는 점이다. 따라서 jQuery.ajax(url, settings)는TypeScript에서 여전히 유효한 코드이다. 그러므로 TypeScript에서 타입정보는 온오프 스위치라기 보다는 다이얼에 더 가깝다. (역주: 필요에 따라 선택할 수 있는 버튼과 같다는 의미) 여러분이 봤을 때 타입정보가 읽고 이해하는데 진부하게 느껴지면 타입을 기술하지 않아도 되며, 타입이 필요한 때 타입 정보를 포함하면 된다.

 

TypeScript가 언어의 표현성을 제한하는가?

동적 타입 언어는 도구에 있어서는 부족함이 있지만, 좀 더 유연하고 표현력이 좋다. 내 생각에는 TypeScript는 여러분의 코드를 좀 더 엄격하게 만들 수 있지만 그러나 사람들이 생각하는 것보다는 그 정도가 그리 크지 않다. 정말로 그런지 살펴보자. 예제에서 나는 Person 레코드를 정의하기 위해서 ImmutableJS를 사용하였다.

우리가 어떻게 위 레코드에 타입 정보를 표기할 수 있을까? Person이라는 인터페이스를 정의하면서 시작하자:

만약 우리가 아래와 같이 코드를 작성했다고 한다면:

PersonRecord는 입력된 인수를 반영하여 생성되었기 때문에 실제로 Person과 호환되는지 알 수 없어 TypeScript 컴파일러는 경고를 여러분에게 보여줄 것이다. 여러분 중에 함수형 프로그래밍 경험이 있는 분이라면 다음과 같이 이야기 할수도 있다. “만약 TypeScript가 의존타입(Dependent Type)이라도 있었으면…” 그러나 TypeScript의 타입시스템은 가장 진보한 형태를 갖고 있는 것은 아니다. TypeScript의 목표는 좀 다르다. TypeScript는 프로그램의 100% 정확도를 보장하기 위한 것이 아니라 여러분들에게 좀 더 많은 정보와 훌륭한 도구의 사용을 제공하는 것이다. 따라서 타입 시스템이 충분히 유연하지 않을 때 손쉬운 방법을 써도 괜찮다. 따라서 우리는 생성된 레코드의 타입을 다음과 같이 캐스팅 할수 있다.:

타입정보가 포함된 예제를 보자:

이 코드가 동작하는 이유는 TypeScript의 타입시스템이 구조적이기 때문이다. 생성된 객체가 정확한 필드를 소유하고 있는 것으로 (이 예제에서는 nameage) 충분하다.

TypeScript를 사용할 때 여러분에게 필요한 것은 위와 같이 간단한 방식을 취해도 된다는 태도이며 여러분은 즐겁게 언어를 사용할 수 있다는 점만 발견하게 된다. 예를 들면, 분명히 여러분이 정적으로 코드를 표현할 방법이 없는 어떤 애매한 메타프로그래밍 코드에 타입정보를 추가하려고 하지 마라. 다른 코드에 모두 타입을 추가하고 타입체커에게 애매한 부분을 무시하게 하면 된다. 이러한 경우에 여러분은 표현의 풍부함은 잃지 않으면서도 여러분의 코드의 일부는 여전히 도구의 지원을 받을 수 있고, 분석할 수 있게 된다.

이것은 100% 단위 테스트 커버리지를 이루려는 것과 유사하다. 95%의 커버리지를 달성하는 것은 그리 어렵지 않으나, 100%를 이루려는 것은 상당한 노력이 들고 어쩌면 여러분의 애플리케이션 아키텍쳐에 부정적인 영향을 끼칠 수 있다.

선택적으로 타입 시스템을 사용할 수 있다는 것의 의미는 기존의 Javascript 개발 워크플로우도 유지한다는 것을 말한다. 코드 베이스의 상당한 부분이 손상될수도 있지만, 여전히 애플리케이션을 실행할 수 있다. 그 이유는 TypeScript는 타입 체커가 에러나 경고를 보여주더라도 Javascript를 생성하는 작업은 유지하기 때문이다. 이러한 점은 개발 시에 매우 편리한 부분이다.

 

왜 TypeScript인가?

오늘날 프론트엔드 개발에는 ES5, ES6 (Babel), TypeScript, Dart, PureScript, Elm등 선택 가능한 옵션들이 너무나 많이 있다. 그럼에도 불구하고 왜 TypeScript인가?

ES5부터 살펴보자. ES5는TypeScript 대비 한가지 명백한 장점이 있는데 그것은 ES5는 transpiler가 필요 없다는 점이다. 이러한 점은 애플리케이션의 개발환경 셋팅을 단순화 시켜준다. 여러분은 파일 변화를 감지할 watcher, transpiler, source map 파일의 생성 등을 셋팅할 필요가 없다. 바로 개발하면 될 뿐이다.

ES6는 transpiler가 필요하기 때문에 개발환경 셋팅이 필요하다는 점에서 TypeScript와 크게 다르지 않지만 ES6는 Javascript 표준이고 이는 일반적인 모든 에디터와 빌드 툴에서 ES6를 지원하거나 지원하게 될것 임을 의미한다. 그렇지만 이러한 점은 현재 대부분의 에디터에서 TypeScript를 훌륭하게 지원하고 있다는 사실을 볼 때 약점이 되기도 한다.

Elm과 PureScript는 TypeScript보다 여러분의 프로그램의 관해서 더 많이 점검할 수 있는 강력한 타입시스템을 갖춘 우아한 언어들이다. Elm과 PureScript로 작성된 코드는 ES5로 작성된 코드보다 좀 더 간결하게 작성 가능하다.

위에서 열거한 언어들은 저마다 장단점을 가지고 있다. 그러나 나는 TypeScript가 대부분의 프로젝트에서 적절한 선택이 될 수 있는 상당히 매력적인 지점에 서 있다고 생각한다. TypeScript는 정적 타입 언어로부터 95% 가량의 특징을 취하여 이를 Javascript 생태계로 유입시켰다. 이는 TypeScript 개발이 마치 ES6로 코드를 짜는 것과 같은 느낌을 가져다 준다. 여러분은 변함없 이 ES6와 동일한 표준 라이브러리를 사용하며 동일한 서드파티 라이브러리를 사용할 수 있고, 관용적인 패턴과 (크롬 개발자도구와 같은) 동일한 도구도 그대로 사용할 수 있다. 이러한 사실은 TypeScript가 여러분을 Javascript 생태계로 부터 벗어나도록 강제하지 않고 기존의 특징을 그대로 안겨다 준다.

저자의 Angular2 Medium: https://vsavkin.com/

[Angular2] 지시자(Directive)

참고: angular2에 대한 연재는 여기를 참조해 주세요. 틀린 부분이나 의견, 피드백은 언제든지 환영합니다.

지시자에 대하여

오늘 다룰 개념은 Angularjs를 사용해 보신 분들은 적어도 한 번쯤은 들어보았을 지시자(Directive)입니다. 지시자에 대한 설명은 먼저 용어의 의미에서 출발해 보기로 합니다. 지시자 본래의 단어 Directive를 네이버에서 검색할 때 제일 처음 나오는 사전적 정의는 다음과 같습니다.

(공식적인) 지시[명령]

A directive is an official instruction that is given by someone in authority.

마찬가지로 네이버 한국어 사전에서 지시의 의미를 찾아보면 “1. 가리켜 보임, 2. 일러서 시킴. 또는 그 내용” 등으로 나오네요. 여기서 우리가 관심 갖는 의미는 2번째에 해당된다고 볼 수 있습니다.

정리하면, 어휘의 의미에서 접근했을 때 지시자라는 개념은 “어떠한 대상에게 일련의 명령을 수행하게 함”으로 다듬어 정의할 수 있습니다. 그리고 이 의미는 Angular 프레임워크 맥락에서도 동일합니다. Angular 프레임워크에서 의미하는 지시자는 “DOM을 다루기 위한 모든 것”이라 정의할 수 있습니다.

그럼 다음의 예를 통해서 조금 더 익숙한 곳에서부터 Angular가 수행하는 지시자의 역할을 살펴보기로 합니다.

지시자가 아닌 간단한 형태의 Component를 보여드렸습니다. Component 소스를 다시 꺼내 든 이유는 Component 역시 지시자이기 때문입니다. 위에서 정의한 지시자의 정의와 Component의 역할을 생각하면 Component가 왜 지시자인지 이해할 수 있습니다. Component도 화면의 렌더링과 사용자의 이벤트 처리 등 DOM을 다루기 때문에 지시자입니다.

Component는 Angular에서 중요한 컨셉 중 하나입니다. 그러므로 지시자의 일부로 다루기 보다는 독립적으로 구분해서 보는 이해하는 것이 좋습니다. 따라서 앞으로 지시자를 이야기 할 때 넓은 의미로는 Component를 포함하지만 협소한 의미로 이야기할 때는 Component를 제외하고 이야기 하도록 합니다.

그렇다면 좁은 의미로 볼 때, 지시자에는 어떤 것들이 있을까요? 구조 지시자(Structural directive)와 속성 지시자(Attribute directive)가 있습니다. 이 지시자들은 HTML의 태그 안에 속성에 선언하여 사용합니다. 세부적으로 각각 어떤 역할을 하는지 확인해 봅시다.

 

구조 지시자(Structural Directive)

구조 지시자는 DOM 요소를 추가하거나 삭제하는 등 화면의 구조를 변경할 때 사용하는 지시자입니다. 대표적인 구조 지시자에는 ngIf, ngFor가 있습니다. ngIf 지시자는 불리언 값을 입력 받아 true일 경우 ngIf가 선언된 DOM을 보여주고 false이면 제거해주는 용도의 지시자입니다. 반면 ngFor는 배열 형태의 모델을 반복해서 DOM에 표현할 때 사용하는 지시자입니다. 아래 공식매뉴얼의 간단한 예제를 살펴봅시다.

설명한 정의와 같이 구조 지시자는 DOM을 직접 조작하는데 사용하는 지시작입니다. angular에서 제공하는 기본 구조 지시자로도 어플리케이션 개발이 충분하지만, 필요에 따라 자신만의 구조지시자를 만들수도 있습니다. 이에 대해서는 나중에 다시 다루어 보도록 합니다.

angularjs에서는 지시자는 태그명, 속성, 클래스명, 주석까지 총 4가지로 표현되어 사용할 수 있었습니다. angularjs의 태그명을 사용한 지시자가 angular에서는 Component에 해당하고, angularjs의 속성 지시자가 angular에서는 협의의 의미의 지시자에 대응한다고 볼 수 있습니다.

 

속성 지시자(Attribute Directive)

속성지시자는 지시자가 선언된 DOM의 모습이나 동작을 조작하는데 사용하는 지시자입니다. 조작할 DOM에 미리 정의한 속성 지시자를 마킹하듯이 태그 안에 속성으로 선언하면 해당 DOM이 지시작에서 정의한 대로 동작하게 됩니다. 다음의 예를 한 번 봅시다.

이 예제는 DOM요소의 class에 ‘my-new-class’를 추가하는 ‘my-selector’라는 속성지시자를 선언한 것입니다. 따라서 필요에 따라 ‘my-new-class’란 클래스를 추가할 DOM에 다음과 같이 ‘my-directive’를 속성 중 하나로 선언해 주면 됩니다.

속성 지시자는 명칭에서 알 수 있듯이 DOM의 속성과도 직접 연관된 지시자입니다. 장황한 설명보다 다음의 예제가 그 역할을 충분히 설명할 수 있습니다.

예제를 보면 []를 사용하여 단방향 바인딩으로 우변의 모델 값을 직접 반영하고 있다는 것을 알 수 있습니다. 예제와 같이 기존 DOM의 속성에 접근하기 위한 용도에도 속성지시자가 사용되며 지난 시간에 설명한 바인딩도 속성 지시자의 일부가 됩니다.

 

Component와 지시자의 관계

오늘 소개했던 지시자와 Component는 Angular 프레임워크의 기둥과도 같은 중요한 개념입니다. 위에서 설명한 바와 같이 사실 Component도 지시자의 이야기 했습니다. 실제로 @Component 데코레이터는 @Directivetemplate이 포함된 확장된 형태로 구현되어 있습니다. 넓은 의미의 지시자는 DOM을 다루는 데 필요한 모든 것이기 때문에 우리가 만들 어플리케이션은 지시자들을 조합하여 구성하게 됩니다. HTML template을 포함한 Component와 구조/속성 지시자들을 활용하여 새로운 Component를 만들고, 또 이렇게 만들어진 Component 들을 조합하여 하나의 완성된 어플리케이션을 구축하는 것입니다. 따라서 지난 시간에 설명한 Component의 트리의 개념을 지시자를 포함해서 생각하면 다음과 같이 표현할 수 있습니다.

컴포넌트 트리 참조

위 그림을 보면 여러 지시자와 Component를 조합하여 새로운 Component를 부모로 가질 수 있지만 지시자는 부모가 될 수 없다는 점을 알 수 있습니다.

종합예제

그럼 한 번 지금까지 간단하게 살펴 보았던 컴포넌트, 데이터바인딩과 오늘 배운 내용들을 종합한 간단한 예제를 작성해 보기로 합니다. 예제는 초간단 Todo 리스트입니다. 먼저 작동 예제를 살펴봅시다.

예제의 시작점은 app.component.ts의 AppComponent 입니다.

AppComponent를 구성하기 위해서 TodoInputComponent와 TodoListComponent가 필요합니다. 따라서 13번 라인과 같이 metadata의 directives 에 해당 자식 컴포넌트들을 선언해야 합니다. 만약 자식 Component나 지시자를 사용하지만 directives안에 선언해 두지 않으면 어플리케이션 로딩 시 스크립트 오류가 발생합니다.

이번엔 한 번  TodoListComponent를 봅시다.

  • TodoListComponet도 metadata를 보면 TodoItemComponent를 자식 컴포넌트로 갖고 있음을 directives를 통해서 알 수 있습니다.
  • 10번 라인에서는 todos 배열의 값을 구조지시자를 활용하여 반복해서 todo-item을 표현하고 있습니다.

TodoItemComponent의 소스를 읽어 봅시다.

  • 8번 라인을 보면 PriorityDirective를 자식으로 포함하고 있음을 알 수 있습니다.
  • 7번 라인의 template  을 보면 todo-priority 라는 속성지시자를 추가했음을 확인할 수 있습니다.
  • 또한 7번 라인에서 todo 모델의 값을 사용자에게 보여주기 위해서 getTodoItemContent라는 함수를 사용했는데 다음에 “파이프”를 설명하면서 이를 좀 더 깔끔하게 처리하는 법을 소개하겠습니다.

본 예제의 PriorityDirective의 역할은 아주 간단합니다. 해당 속성지시자가 선언된 DOM의 요소에 ‘priority-grp’이라는 클래스를 추가하는 일을 합니다. 소스에서 확인해 볼까요?

소스를 보면 설명하지 않은 내용들이 많긴 하지만 일단은 무시하고 여기서는 9번라인이 todo-priority 속성 지시자가 선언된 DOM 요소에 ‘priority-grp’ 클래스를 넣는 부분이라고만 확인합시다.  지시자에 대한 자세한 활용은 다음 시간에 이야기 하기로 합니다.  (또한 constructor 안에 선언된 두 파라미터와 관련된 의존성 주입입니다. 역이 다음에 다루기로 합니다.)

 

정리하며

오늘 설명한 지시지에 대한 개념은 지난 시간의 Component와 함께 Angular 기반 어플리케이션의 뼈대를 세우는 중요한 역할을 합니다. 컴포넌트 관점에서 template이 있는 Component가 지시자와 다른 Componen를 자식으로 포함할 수 있지만 지시자는 다른 자식을 가질 수 없다는 것을 설명했습니다.

Angular2 글 목록보기

 

반응형 프로그래밍 분류

Angular2 코어 멤버인 victorsavkin의 글 “THE TAXONOMY OF REACTIVE PROGRAMMING“을 허락을 받고 번역 하였습니다. 번역에 대한 피드백이나 오류는 주저없이 트윗이나 메일로 알려주세요.

This post is the translation version of the original post “THE TAXONOMY OF REACTIVE PROGRAMMING” with allowed to translate by the author victorsavkin.]
우리는 모두 반응형 프로그램의 일부 요소를 활용하여 사용자 인터페이스를 구축합니다. 하나의 Todo 항목이 늘었났다고 해봅시다. 화면에 새 항목을 그려줘야 합니다. 이번엔 누군가가 Todo 항목의 제목을 변경했다면 어떨까요? 우리는 DOM의 텍스트 요소를 갱신해줘야 합니다. 이와 같은 일들을 도와줄 수십 종류의 라이브러리들이 이미 존재합니다. 이러한 라이브러리들은 비슷한 점도 있으면서도 각기 다른 점도 가지고 있습니다.

이 글에서 저는 반응형 프로그래밍의 각기 독립적인 4가지 측면을 설명할 것입니다.

  1. 이벤트와 상태 (events and state)
  2. 도출과 실행 (deriving and executing)
  3. 구체적 방식과 투명한 방식(reified and transparent)
  4. 자기 관찰과 외부 관찰 (self observation and external observation)

위 주제들에 대한 개념과 함께 4가지 측면이 각각 어떻게 쓰이는지를 설명할 예정입니다. 이를 통해 여러 라이브러리들을 좀 더 객관적이고 명확하게 바라보고 건설적인 토론을 이끌어 낼 수 있을 것입니다.

이 글의 예제는 Angular2와 RxJS를 사용하여 작성하였습니다. 여기에 쓰인 예제를 포함하여 재사용 가능한 라이브러리들과 대부분의 기능을 제공하고 있는 여러 프레임워크를 비교하는 것이 좀더 명확해 질 것입니다. 동일한 개념을 사용하여 이 글에서 논의를 이어나갈 예정입니다. 따라서 이러한 비교논의가 의미 있을 것입니다.

그럼 이제 한번 들어가 봅시다.

이벤트와 상태 (Events and State)

우리는 반응성을 논할 때 이벤트와 이벤트 스트림을 이야기 하곤 합니다. 이벤트 스트림은 반응형 객체에서 중요한 부분을 차지합니다. 하지만 두번째로 중요한 상태에 대해서도 간과해서는 안됩니다. 각각의 속성을 한번 비교해 봅시다.

이벤트는 독립적인 것으로 결코 지나칠 수 없습니다. 모든 단일 이벤트는 발생된 순서를 바탕으로 신경써야 할 대상입니다. 그러므로 “가장 최근의 이벤트” 같은 것이 우리가 관심 가져야 하는 특별한 것이 아닙니다. 따라서 사용자에게 직접적으로 보여지는 이벤트는 거의 없다고 볼 수 있습니다.

반면에 상태는 연속적인 속성을 지니고 있기에 시간을 기준으로 특정한 때의 값을 정의할 수 있습니다. 우리는 보통 얼마나 많이 상태가 갱신되었는지에 관심을 갖지 않고 가장 최신의 값에만 신경을 씁니다. 상태는 종종 화면에 노출되거나 일련의 의미있는 형태로 있기도 합니다.

쇼핑카트를 예로 들어봅시다. 우리는 쇼핑카트의 내용을 변경하기 위해서 마우스 클릭 이벤트를 사용할 수 있습니다. 더하기 버튼을 클릭하면 제품의 수를 증가시켜줄 것이고, 마이너스 버튼을 클릭하면 제품의 수를 감소 시킬 것입니다. 결국, 클릭의 수가 중요하기 때문에 쇼핑카트의 내용을 유지하기 위해서 단 하나의 이벤트도 누락해서는 안됩니다. 그러나 우리는 마지막 이벤트를 반드시 점검해야 하거나 이를 사용자에게 노출시킬 일도 없습니다.

반면에 쇼핑카트의 전체 항목수는 상태에 해당합니다. 우리는 전체 항목수가 얼마나 갱신되었나를 보지 않고 쇼핑카트의 내용을 나타내는 가장 최신의 값만을 고려합니다.

정의

이벤트 스트림 은 특정한 시간 주기 동안 생성된 값들의 순차적인 나열입니다. 그리고 상태 는 시간에 따라 바뀔 수 있는 단일 값입니다.

한 번 예제를 살펴봅시다.

위 예제에서는 하나의 이벤트 흐름인 moves라는 observable을 가지고 있습니다. 사용자가 매번 마우스를 움직일 때마다, observable은 이벤트를 발산(emit)시킵니다. 우리는 상태값인 position subject를 생성하여 moves observable을 사용합니다. position값의 속성은 마우스의 현재 위치를 반환합니다. 이 예제는 이벤트와 상태가 무엇인지 묘사해줄 뿐 아니라 상태는 종종 이벤트의 순차적인 나열에서 생성된다는 것을 보여줍니다.

역자 주: RxJS가 익숙하지 않은 분들은 observable이 계속해서 발생하는 이벤트 흐름의 발생출처 라고 생각하시고 읽으시면 됩니다. 그래서 이름과 같이 관찰가능한 대상이기에 observable이라고 합니다. RxJS에 대해 자세히 알고 싶으신 분들은 rxmarble의 그림을 참조해 보세요.

이벤트와 상태라는 이분법이 단지 RxJS만의 산물이 아니라는 것을 보여주기 위해서 이번에는 Angular의 예제를 봅시다.

이 예제에서 employee 컴포넌트의 name 입력은 company 컴포넌트의 employees 프로퍼티에서 끄집어 낸 상태입니다. 주의할 점은 우리는 오로지 가장 최신의 이름 값에만 신경을 쓴다는 것입니다. 예를 들어 중도에 갖고 있던 이름 값을 지나쳐도 어떤 것에도 영향을 미치지 않습니다. 모든 단일한 값을 다루면서 발생된 순서정보까지 내포한 selected 이벤트의 순차적 나열과 비교해 보세요.

정리

반응형 프로그램의 세계 안에 이벤트 스트림과 상태라는 두 개의 카테고리가 있습니다. 이벤트 스트림은 사용자에게 노출되지 않고 시간에 따라 흐르는 독립적인 값의 순차적인 나열입니다. 상태는 사용자게에 종종 노출되기도 하고 의미있는 형태를 갖는 시간에 따라 변할 수 있는 값입니다.

도출과 실행 (deriving and executing)

먼저 마우스 클릭에 대한 RxJS observable이 다음과 같이 있다고 해봅시다.

이 observable로 무얼 할 수 있을까요? 당장 드는 생각은 좌표를 표시하는 것이네요.

나름 유용하지만, 콜백을 사용하는 것과 큰 차이가 없습니다.

마우스 클릭 observable의 진짜 가치는 새로운 observable을 도출할 수 있다는 점입니다.

map 연산이 제공한 것은 오로지 새로운 observable을 제공한다는 점입니다. 이로 인해 프로그램에 영향을 주는 것은 전혀 없기에, 이 프로그램은 부수효과를 갖지 않습니다.

역자 주: 이 번역에서 side effect는 부수효과로 통일합니다. 부수효과는 함수가 자신의 결과값 이에외 다른 상태에 영향을 주는 것을 말합니다. 예를 들면 console.log를 사용한 화면 출력도 마찬가지입니다.

이제, 조금 더 깊이 들어가서 몇가지 RxJS 연산을 사용하여 새로운 observable을 생성해 봅시다. 이번에 생성할 observable은 모든 요소가 tofrom의 좌표를 갖고 있습니다.

다시 한번, observable 사이의 관계만 집중해 봅시다. 도출이란 어떻게 서로 다른 존재(entity)가 어떻게 연관되어 있는지에 대한 모든 것입니다. 결론적으로는 여러분의 프로그램이 수행하게 될 연산을 표현해주는 하나의 observable 또는 여러 observable이 있게 됩니다. 이는 연산(computation) 그 자체가 아니라 연산의 표현입니다. 사용자에게 실제로 노출되는 것은 아무것도 없습니다.

사용자에게 무엇인가를 보여주기 위해서 (다른 말로 표현하자면, 부수효과를 실행하기 위해서는) 우리는 forEach함수의 호출이 필요하다.

RxJS만이 도출과 부수효과의 수행을 별개의 연산으로 분류하는 유일한 라이브러리가 아닙니다. Angular 역시 가능합니다.

name속성은 ceo 속성에 도출되어 ngOnChanges가 부수효과를 수행하는데 사용됩니다.

역자 주: Angular2에서는 컴포넌트가 뷰의 일부 요소가 되어 트리형태를 만듭니다. 따라서 부모 컴포넌트에서 자식 컴포넌트로 상태 값을 전달할 수 있고, 이 때 자식 컴포넌트에서는 값이 변결될 때 ngOnChanges 함수가 자동으로 실행됩니다. 위 예제는 간략하게 부모 컴포넌트의 ceo 상태가 변경되어 ceo 안에 name을 받는 자식 컴포넌트의 ngOnchange가 console.log로 부수효과를 일으키는 것을 보여줍니다.

Hot과 Cold. 그리고 프라미스에 대해서

흥미롭게도 RxJS의 observable은 차갑기 때문에, 라이브러리가 두 종류의 연산으로 구분되어 있어야만 합니다. forEach와 구독 개념과 같은 일련의 연산들을 제공해야 합니다. 그렇지 않으면 어떠한 부수효과도 실행되지 않을 것입니다.

반면에 프라미스는 뜨겁습니다. 뜨겁다는 의미는 프라미스를 제공하는 쪽에서 값이 정해지면, 모든 도출된 프라미스에게 전달이 됩니다. 왜냐하면 프라미스는 도출과 부수효과를 실행하는데 모두 사용될 수 있는 then 연산을 통해서 이를 해낼 수 있기 때문입니다.

hot/cold 주제는 미묘하지만 단순히 부수효과나 구독과 관련해서 영향을 주는 것이 아니라 취소와도 관련되어 있습니다. 더 자세한 내용은 Ben Lesh의 훌륭한 글을 참조하세요.

역자 주: Hot과 Cold Observable에 대한 차이는 Rx 방식의 개념에서 나온 것으로 자세한 내용은 위의 글을 참조해 주시기 바랍니다. 간단하게 Hot은 변경된 데이터가 있으면 Push 형태로 바로 전달해 주는 것이고, Cold는 변경된 데이터를 구독하는 리스너가 있기 전까지 실행되지 않는 Pull 형태라고 보시면 됩니다.

정리

시간에 따라 변하는 변수를 다루는 방법은 두 가지가 있습니다. 기존 변수로 부터 새로운 변수를 유도하거나 값이 변화할 때마다 부수효과를 실행하는 것입니다. 이 두 방법을 분리하는 것은 코드를 좀 더 구성 가능하고 리팩토링하기 쉽게 만들어 줍니다.

구체적 방식(REIFIED)과 투명한 방식(TRANSPARENT)

먼저, 예제를 살펴봅시다.

예제에서 우리는 두 개의 subjectceoname을 가지고 있습니다. 이 두 subject는 값의 변경이 일어나는 것을 관찰할 수 있도록 도와줍니다. 반응형 프로그램에서 이러한 타입을 우리는 구체적 방식(reified)이라고 호명합니다. 그 이유는 관찰행위를 표현하는 구체적인 객체에 접근할 수 있기 때문입니다. 또한 이러한 구체적인 객체의 소유는 이를 수정하고 다른 곳에 전달하고, 새로운 것을 구성할 수 있기에 강력한 특징을 갖고 있습니다.

name.valuename의 차이는 처음에 봤을때 미묘할 수도 있지만 핵심적인 부분입니다. name.valuename상태의 현재 값을 의미하지만 name은 그 자체로 다른 것입니다. name 시간에 따라 변화하는 값을 나타냅니다.

유사한 Angular 방식의 예제와 대조해 봅시다.

Angular는 시간에 따라 변하는 name을 표현하는 어떠한 형태의 객체도 제공하지 않습니다. 우리는 오로지 현재 값만을 갖고 있습니다. 반응형 프로그래밍에서 이러한 타입을 투명한 방식(transparent)라고 호명합니다. 왜냐하면 개발자들은 오로지 가장 최신의 값만을 다루기 때문입니다. 관찰행위는 프레임워크 안에서 이뤄지기에 알 수 없습니다.

구성

투명한 방식과 구체적 방식을 활용한 반응형 프로그래밍은 상태를 구성할 때 매우 다르게 작동합니다.

구체적 방식의 반응형 프로그램을 사용하면 우리는 다음과 같이 기존의 것에서 새로운 변수를 생성하는 특수한 연산을 사용해야만 합니다.

투명한 방식의 반응형 프로그래밍을 활용 시 우리는 일반적인 자바스크립트 문법을 사용합니다.

상태를 다룰 때에, 우리는 위 두 타입의 반응형 프로그래밍을 효율적으로 사용할 수 있습니다. 투명한 반응형 프로그래밍이 좀 더 간단하고 성능에 우위가 있는 반면, 구체화한 반응형 프로그래밍은 좀 더 유연합니다.

이벤트를 다루는 경우에는 두 방식을 모두 사용할 수 없습니다. 왜 두 타입의 반응형 프로그래밍을 살펴볼 필요가 있는지 이해하려면 시간의 개념을 다뤄야 합니다.

암묵적 시간과 명시적 시간

투명한 방식의 반응형 프로그램을 사용 시, 우리는 시간을 명시적으로 다룰 수 없습니다. 상세히 설명하면 시간의 개념 자체를 실제로 정의하지 않습니다. 물론 name 속성이 시간의 흐름 속에 변할 수 있다는 것을 인지하고 있지만, 시간에 흐름에 기반하여 흥미로운 결정을 도출할 만한 어떠한 수단도 없습니다.

그래서 구체적 방식의 반응형 프로그램이 뜨기 시작한 겁니다. 구체적 방식의 반응형 프로그램은 시간의 흐름을 명시적으로 만들어 이를 활용하는 특수한 연산들을 제공하므로 새로운 것을 구성할 수 있게 도와줍니다. 예를 들면, 우리는 다음과 같이 name subject를 0.5초 지연시켜 갱신할 수 있습니다.

상태값을 활용할 때 시간을 직접 이용하는 것은 그다지 실용적이지 않겠지만 이벤트를 다룰 때는 흔한 방식입니다. (예를 들면 debouncing 같은 것이 있습니다.) 이러한 이유로, 지금까지 익숙했던 투명한 방식의 패러다임으로서 가장 최신의 값에 접근하는 것은 이벤트 처리에 있어서는 종종 충분하지 않을 때가 있습니다.

정리

투명한 방식의 반응형 프로그램은 시간에 따라 변하는 여러 변수들을 구성하는데 있어서 기존의 자바스크립트 문법을 사용하기 때문에 더 간단합니다. 그러나 이 방법은 오로지 변수의 가장 최신의 값만을 제공하며 명시적으로 시간이 흐름을 다룰 방법을 제공하지 않습니다. 결과적으로 상태 값을 도출하는 데는 유용하지만 이벤트를 조작하는데는 그렇지 않습니다.

자기관찰과 외부 관찰

정의

외부관찰(External observation)은 관찰 대상인 객체를 스스로 관찰해서 알아낼 수 없고 대신 어떤 외부 존재 (예를 들면, Angular 프레임워크)가 객체의 변화를 알아내는 방식입니다. 자기 관찰(Self observation)은 스스로 객체의 변화를 직접 관리하는 방식입니다.

다시 한번 예제를 보도록 합시다.

이 예제에서는 employee 컴포넌트의 fullName 속성을 company 컴포넌트의 ceo 속성에서 도출하고 있습니다. 다시 말하면, Angular에게 ceo 객체를 관찰하여 성이나 이름이바뀔 때마다 employee 컴포넌트를 갱신하라고 명령합니다. 여기서 흥미로운 점은 ceo 객체는 관찰에 필요한 어떠한 정보도 소유하고 있지 않고, 일련의 특수한 방식으로 표현되거나 생성된 것도 아닙니다.

이는 Angular가 대신 외부관찰을 하고 있기 때문입니다. Angular는 template1 안에 표현식을 계산하고 결과를 기억하고 있습니다. 다음 번에도 동일한 계산을 수행하면서 이전의 값과 새로운 값을 비교하게 됩니다. 이 때 만약 값이 변경되었을 경우, Angular 프레임워크에서 employee 컴포넌트를 갱신해 줍니다.

외부 관찰은 많은 이점을 갖고 있습니다. 먼저, 상당한 유연함을 가져다 줍니다. 여러분은 예제와 같이 {firstName: string, lastName: string} 인터페이스를 구현한 어떤 형태의 객체를 사용해도 됩니다. 서버로부터 전송된 json 일수도 있고 ImmutableJS 객체도 가능합니다. 이에 더하여, 외부 관찰은 강력하게 관찰의 순서를 보장하여 연속적인 갱신의 문제를 피할 수 있도록 실시합니다. 물론 불리한 측면은 실행할 코드가 프레임워크의 컨텍스트 안에서 관찰되어야 합니다. 그래서 Angular1이 $scope.apply를 API를 가지고 있으며, Angular2 에서 zone.js가 필요한 이유이기도 하니다.

객체의 변화를 스스로 인지하고 이를 구독하고 있는 대상 에게 알려주는 자기관찰과 대조해 보시기 바랍니다. MobX 또는 Knockout이 이러한 접근방식을 취하고 있는 라이브러리 입니다.

총정리

본 글에서, 반응형 프로그래밍에 대한 논의에 필수적인 4가지 측면들을 소개하고자 정리해 보았습니다. 제 바람은 이 글을 통해서 좀 더 명확하고 객관적으로 여러 라이브러리나 접근 방식을 비교하는 것입니다.

이제 여러분이 새로운 라이브러리를 접할 때 스스로에게 다음과 같은 4가지 질문을 던져 보시길 바랍니다.

  • 라이브러리가 이벤트를 다루는가? 상태를 다루는가?
  • 라이브러리가 값을 도출하는가? 부수효과를 실행하는가?
  • 라이브러리가 투명한 방식의 반응형 프로그램인가? 구체적 방식의 반응형 프로그램인가?
  • 라이브러리가 외부관찰인가? 자기관찰인가?

예를 들면, Angular에 대해서 논의할 때 우리는 상태를 도출하기 위하여 외부관찰을 통해 투명한 방식의 반응형 프로그램을 사용하거나 혹은 이벤트를 다루기 위해서 자기관찰을 통해 구체적 방식의 반응형 프로그램을 사용한다고 말할 수 있습니다. 그리고 더 바라기는 여러분은 Angular 개발팀이 이러한 선택지를 만든 이유를 알았으면 합니다. 다른 예로 자기관찰을 통해 투명한 방식의 반응형 프로그램을 사용하며 상태를 다루는 MobX가 있습니다. 또는 자기 관찰을 통해 구체적 방식의 반응형 프로그램을 지원하는 Cycle.js를 고려해 볼수도 있습니다.
모든 형태의 반응형 프로그램을 이 글을 통해서 알았을까요? 물론 아닙니다! 이 하나의 글을 통해서 반응형 프로그램에 대한 전체 분야를 다뤘다는 것은 순진한 생각입니다. 예를 들면, 저는 푸쉬나 풀 방식, 일급 및 고계함수의 FRP(Functional Reactive Programming), continus FRP, Observable/Iterable duality, back pressure, sampling 등 여러 다른 흥미롭고 중요한 주제를 다루지 않았습니다. 어쩌면 다음 블로그 포스팅의 주제가 될지도 모르겠습니다. 그러니 계속해서 주목해 주세요.

저자의 블로그: http://victorsavkin.com/


  1. http://www.notforme.kr/archives/1627#template 

[Angular2] 데이터 바인딩

참고: angular2에 대한 연재는 여기를 참조해 주세요. 틀린 부분이나 의견, 피드백은 언제든지 환영합니다.

 

들어가기

오늘은 지난 시간에 설명한 컴포넌트의 상태를 어떻게 다룰지와 연관된 주제인 “데이터 바인딩“을 소개합니다. Angular 1을 사용해 보신 분들은 양방향 데이터 바인딩(2-way data binding)을 통해서 이미 “데이터 바인딩”이란 개념을 들어 보셨을 겁니다. 이번 글을 통해서 Angular2에서는 Angular1과 달리 어떻게 “데이터 바인딩” 기능을 제공하는지 알 수 있습니다.

 

데이터 바인딩?

먼저, 데이터 바인딩은 Angular에만 있는 특별한 개념이 아닙니다. 데스크톱 기반 앱의 UI 개발시에도 있었던 개념입니다. Java Swing을 예로 들어 볼까요? 사용자의 입력이 필요한 곳에 일반적으로 JTextField를 윈도우 창(Panel)에 배치 합니다. 그리고 버튼과 같은 요소에 리스너 메서드를 등록하여 사용자가 입력한 정보를 조회하여 비즈니스로직을 구현하는데요. 이 때 Swing에서는 사용자가 정보를 입력할 때 이를 JTextField 객체 내 상태에 바인딩합니다. 그리고 getText() API를 통해서 입력한 정보를 얻을 수 있습니다. 이처럼 기존 데스크톱 기반의 UI 라이브러리들은 사용자의 입력을 받을 수 있는 컴포넌트 안에 상태를 가지고 있고, 입력이벤트에 따른 상태 변경이 암묵적으로 자연스럽게 이루어집니다.

UI 프로그램의 트렌드가 데스크톱에서 웹으로 바뀌면서 기존에 데스크톱 기반 어플리케이션에서는 편리했던 입력 컴포넌트의 상태 정보 조회가 웹 기반 어플리케이션에서는 간단하지 않게 되었습니다. 이는 웹의 태생 자체가 사용자와의 양방향 상호작용이 많은 것을 가정한 것이 아니기 때문에 발생하는 불일치 입니다.

웹 어플리케이션에 어떻게 상태 정보를 조회하고 갱신하는지 확인해 봅시다. 보통 웹 어플리케이션에서는 DOM의 요소 안에 value나 checked 프로퍼티 등에 필요한 상태정보를 담고 있는 경우가 많습니다. 사용자가 입력한 정보를 얻기 위해서 웹에서는 DOM의 API나 jQuery를 사용해서 필요한 요소를 찾아내는 코드가 필수적으로 따라 붙게 됩니다. 아래는 jQuery의 val() API 문서에 있는 예제 소스 중 일부입니다.

script 태그 안에 있는 소스를 보면 input 태그의 “keyup” 이벤트가 발생할 때마다, 해당 input 요소(8번 줄의 this)에서 val() 메서드를 통해서 사용자가 입력한 값을 읽어서 다시 이를 p 요소를 조회한 후에 text 프로퍼티에 값으로 넣고 있습니다.

이러한 환경에서 웹기반 환경에서 사용자의 입력을 자동으로 반영해주는 것을 프레임워크 레벨에서 지원하기 시작했고, Angular 역시 이를 지원하는 프레임워크 중에 하나일 뿐입니다.

 

Angular2의 데이터 바인딩

Angular2에서는 화면에 UI역할을 하는 DOM과 Component 사이에서 총 4가지 방식의 데이터 바인딩 방법을 제공하고 있습니다. 아래는 Angular.io 매뉴얼에서 가져온 4가지 데이터 바인딩을 설명하는 그림입니다. 그림의 그려진 4개의 화살표가 데이터가 반영되는 방향을 의미하고 화살표 위에 내용이 실제 문법적 표현을 나타냅니다.

Angular2 데이터 바인딩 타입

첫 번째: 보간(interpolation)

Component의 상태를 그대로 화면에 노출 시킬 때 사용하는 방법입니다. 예를 들어 아래와 같이 MyComponent라는 Component에 myData라는 멤버 변수를 상태로 갖고 있다고 해봅시다.

이러한 경우 MyComponent를 표현할 template 에서 “{{ myData }}” 와 같이 MyComponent의 멤버변수를 중괄호 2개를 중첩하여 감싸면 myData안의 값이 자연스럽게 화면에 반영됩니다. 위 예제에서는 div태그 안에 myData 변수를 보간하였으므로 실제 html 소스로는 “<div>날 화면에 그려줘!</div>”로 바뀌게 됩니다. 따라서 그림의 첫 번째 화살표와 같이 Component 안의 데이터가 DOM에 반영된다고 볼 수 있습니다.

 

두 번째: 프로퍼티 바인딩

프로퍼티 바인딩은 Component의 상태 값을 DOM에 프로퍼티에 직접 반영하는데 쓰입니다. 문법은 해당 DOM의 프로퍼티를 대괄호([])로 감싸고 우측에 바인딩할 대상을 넣어주면 됩니다. 예를 들어, HTML의 input 태그는 메모리 상에 HTMLInputElement로 DOM에 반영되는데요. input 태그의 프로퍼티 중 하나인 disabled에 다음과 같은 방식으로 직접 Component의 상태를 반영하거나, 간단한 expression을 사용할 수 있습니다.

 

세 번째: 이벤트 바인딩

DOM의 요소들은 모두 특정 이벤트에 대한 리스너를 등록할 수 있습니다. 버튼에 onClick으로 이벤트를 직접 지정하기도 하지만 보통은  jQuery를 사용해서 이벤트를 등록합니다. Angular2에서는 DOM의 요소에 이벤트 리스너를 Component에 정의한 함수로 정의할 수 있게 해주면 이를 이벤트 바인딩이라고 합니다. 이벤트 바인딩하는 방법은 바인딩할 이벤트명을 소괄호( () )로 감싸고 우측에 메서드명을 적으면 됩니다.

아래는 button 요소에 “click”이벤트를, div 요소에 “mousemove” 이벤트를 바인딩한 예제입니다.

예제의 소스를 보면 다음과 같습니다.

소스의 내용을 살펴봅시다. 24번 라인에 먼저 button 요소에 소괄호를 사용하여 click 이벤트에 AppComponent의 “clickedButton” 메서드를 바인딩했습니다. 25번 라인에서는 div 요소의 “mousemove” 이벤트를 “printMousePosition” 메서드에 바인딩했습니다. 이벤트 바인딩으로 선언한 메서드의 파라미터로 $event는 DOM의 이벤트 객체가 자동으로 들어갑니다.

따라서 화면 상의 버튼을 클릭하면 clickedButton 메서드가 실행되면서 alert창이 뜨게 되고, div요소 위에 마우스를 움직일 때마다 printMousePosition메서드가 실행되면서, 해당 이벤트 발생 시점의 마우스 포지션을 positions 배열에 넣고 화면에 자동으로 출력합니다.

 

네 번째: 양방향 바인딩

마지막으로 양방향(two way) 바인딩입니다. 양방향 바인딩은 프로퍼티 바인딩과 이벤트 바인딩을 동시에 적용하는 방법입니다. 문법은 [(ngModel)]= “바인딩할 대상” 으로 해당 요소의 속성으로 넣어주면 됩니다. input, select 박스 등에 사용하면 바인딩한 Component에 상태가 화면의 View에도 반영이 되고, 반대로 사용자가 View에서 입력할 때 Component의 상태에도 자동으로 데이터가 반영이 됩니다. 아래 예제 input에 한번 입력해 보시기 바랍니다.

아래 소스를 살펴볼까요. 8번 라인에 ngModel로 AppComponent의 value를 양방향 바인딩하고, 이를 바로 p 태그 안에 보간하여 사용자가 입력한 값을 바로 보여줄 수 있게 처리 했습니다. 양방향 바인딩을 이용할 경우 손 쉽게 서버에서 보내는 데이터를Component의 모델에 저장하면 뷰에 반영이 되고 동시에 사용자가 뷰에서 수정한 값을 다시 Component의 모델에 반영할 수도 있습니다.

양방향 바인딩의 동작원리는 내부적으로 Change Detector에 의해서 이루어지는데요. 브라우저에서 발생 가능한 비동기 이벤트(XHR, setTimeout, Event)에 Change Detector가 루트 컴포넌트부터 Depth First Search 방식으로 데이터의 변경사항을 감지하여 양방향 바인딩을 가능하게 한다는 점만 가볍게 설명하고 넘어가겠습니다. 자세한 내용은 나중에 다시 다루도록 하겠습니다.

 

정리하며

오늘은 데이터 바인딩과 Angular2가 지원하는 4가지 방식의 데이터 바인딩에 대해서 살펴 보았습니다. 실상 Angular1에서는 양방향 바인딩이 기본이고 다른 바인딩은 아예 지원하지 않았습니다. 양방향 바인딩을 구현하기 위해서 Angular가 (내부적으로 $digest라는 사이클을 돌 때마다) 모든 변경사항을 체크해야 해서 퍼포먼스의 결함이 있었습니다.

Angular2에서는 양방향 바인딩 자체에도 완전히 새로운 알고리즘으로 퍼포먼스의 향상이 있었을 뿐 아니라 다른 바인딩 옵션을 제공하여 조금더 최적화딘 어플리케이션 개발을 돕습니다. 데이터 바인딩의 활용으로 불필요하게 늘어나는 스크립트 코드들이 줄어드니 사용하지 않을 이유가 없겠지요? 오늘은 여기서 이만 글을 마무리 합니다.

Angular2 글 목록보기

jQuery vs Angular 2 코드 비교

참고: angular2에 대한 연재는 여기를 참조해 주세요. 틀린 부분이나 의견, 피드백은 언제든지 환영합니다. 

들어가기 전에

이 포스팅은 지난 2015 나는 프로그래머다 컨퍼런스 중 React 라이브 코딩에서 보고 알게 된 jQuery versus React.js thinking 글에서 출발하여 (원저자의 허락을 받아) Angular2 버전으로 쓴 것입니다.

This post is the result affected by original post “jQuery versus React.js thinking” that I found on React.js live coding session in “I am programmer” conference. I wrote this post with the permission of original blog post’s author.

이 글에서는 아주 간단한 예제를 jQuery와 Angular2로 각각 구현하여 어떤 차이점이 있는지 확인해 보려고 합니다. jQuery와 Angular2의 직접적인 비교라기 보다 최근의 프론트엔드 프레임워크가 어떤 방식으로 어플리케이션을 구성하는지 확인하기 위한 목적의 글입니다.

 

예제 설명

예제는 jQuery versus React.js thinking과 동일하며 아래와 같은 기능으로 이루어져 있습니다.

  • 아이템의 목록이 있으며 아이템은 detail 정보를 기본적으로 드러내지 않음
  • 사용자가 아이템 클릭 시에 해당 아이템의 detail 정보가 펼쳐짐
  • 이 때 클릭 안 한 아이템들은 grey로 색 변경
  • 클릭한 아이템을 재클릭 시, detail 정보는 다시 감추고 원래 상태로 돌아감
  • 클릭해서 펼쳤던 아이템 외에 다른 아이템 클릭 시 펼쳤던 아이템은 닫히고 클릭한 아이템을 펼침

 

jQuery 방식

jQuery 방식의 소스는 원 글의 소스와 동일합니다.

라이브 예제
JS Bin on jsbin.com

jQuery 방식의 구현은 먼저 아이템 정보와 상태가 그대로 html 파일 내 DOM에 분산되어 있습니다. 또한 데이터와 프레젠테이션 로직이 한 곳에 뭉쳐 있습니다. 각 상태를 변경하기 위해서는 “li”, “:hidden”과 같은 셀렉터를 사용해서 접근해야 합니다. 기능이 복 잡해 질수록 DOM에 접근하는 코드로 인해 리팩토링과 개선이 쉽지 않습니다.

Angular 2 방식

Angular2는 컴포넌트 기반으로 앱을 구성하게 되어 있습니다. 예제가 간단하기 때문에 하나의 파일 안에서 모든 기능을 구현해 넣을 수 있습니다. 하지만 가장 Angular 2 스타일 다운 특징을 보여주고자 각 요소를 최대한 기능에 맞게 나누었습니다. 또한 예제는 Javascript에 타입 등의 syntax가 추가된 Typescript로 구현되어 있습니다.

라이브 예제

product.component

Component 클래스 안에 컴포넌트를 구성하는 핵심 정보들이 다 있습니다. 먼저 6번 라인의 templateUrl을 통해서 해당 컴포넌트의 뷰를 “products.component .html”에 정의하고, 7번 라인에  styleUrls에서 해당 컴포넌트를 위한 스타일 파일을 선언해 두었습니다.

아이템 데이터는 ProductService라는 클래스에 있습니다. Angular2에서는 실제 비즈니스 로직은 Service 클래스에 담도록 안내하고 있습니다. 그리고 Service를 Component에서 가져다 쓸 때에는 간단한 선언을 통해서 자동으로 Service객체를 주입받습니다. 8번 라인이 Component에서 사용할 의존성을 선언한 것입니다.  8번 라인과 같이 providers에 의존성 선언 후에는 construtor 메서드 안에 파라미터로 해당 의존성을 선언해 주면 의존성 주입이 자동으로 이루어집니다.

product.service

서비스 클래스는 아이템 데이터를 반환하는 getProducts 메서드 하나 만을 갖고 있습니다. ES6에 추가된 const 키워드를 사용하여 상수로 mock 데이터를 선언하여 메서드에서 반환합니다. 실제 상황에서는 getProducts 메서드 안에서 ajax 호출을 하도록 구현합니다.

2번 라인에서 import한 Product와 12번 라인에 메서드의 반환타입으로 Product[]로 되어 있는 것은 아이템 데이터의 타입을 명시한 Product 클래스 정보입니다. Typescript는 반드시 타입을 필요로 하기에 다음과 같이 모델 클래스를 선언하여 사용합니다.(자세한 내용은 연재 중인 Angular.io 글 목록을 참고해 주세요.)

 

Angular2 방식에서 눈에 띄는 점은 jQuery방식과 달리 명시적으로 파일단위 관심사의 분리가 이루어진다는 점입니다. Angular2는 다음과 같은 요소들로 관심사를 나눕니다.

  • template: 뷰 역할
  • component: 뷰와 모델, 상태 관리
  • service: 비즈니스 로직

jQuery 방식에 비해 파일 개수도 늘어나고, 코드의 양도 늘어나지만 Angular2 방식이 기능 개선 및 유지보수에 훨씬 더 효율적인 방식입니다. 또한 Angular2는 독립적인 컴포넌트 방식으로 개발하기에 재사용에도 용이하며, 컴포넌트별 독립적인 테스트까지 가능하여 jQuery 방식에 비해 이점을 갖는 부분이 많습니다.

결론

웹 어플리케이션의 복잡도가 증가하면서 지속적인 개선 및 유지보수를 고려할 때, 일정 규모 이상의 웹 어플리케이션 개발 시 프레임워크 사용은 더 이상 선택사항이 아니라고 생각합니다.

이 글은 jQuery와 Angular2와 모습을 직접적으로 비교한 것은 아닙니다. 그보다 구현하려는 대상과 목적에 따라 적절한 선택이 필요함을 보여주려고 한 것입니다. 여러 프론트엔드 프레임워크 중에서 Angular2 역시 앞으로 주목하고 관심가질 만한 좋은 기술이라는 것을 알려드리면서 글을 마칩니다.

참고: jquery-versus-react-thinking

Angular2 글 목록보기

 

[Angular2] 상태 다루기

참고: angular2에 대한 연재는 여기를 참조해 주세요. 틀린 부분이나 의견, 피드백은 언제든지 환영합니다. 

들어가기

이번 글에서부터 지난 시간에 설명한 Component에 살을 붙이기 시작합니다. 오늘은 컴포넌트의 상태값의 개념을 이해해보고 Angular2에서는 어떻게 상태를 관리하는지 간략하게 설명합니다. 이를 위해 Component 클래스에 첫 변수와 메서드를 정의해 봅니다.

 

상태(State)에 대해서

웹사이트에서 상품을 구매할 때 우리는 그림에서 보는 바와 같이 “… 동의”라는 체크박스를 흔하게 접하게 됩니다. 이 때 체크박스는 사용자의 클릭 행위에 따라서 상기의 내용에 동의하는지 여부를 확인하는 용도로 쓰입니다.  그리고 체크박스는 다음의 두가지 의미 중 하나를 반드시 갖게 됩니다.

# 체크박스 상태 의미
1 체크함 내용에 동의
2 체크 안 함 내용을 안 읽었거나 동의하지 않음

따라서 HTML 요소 들이 사용자의 행위에 따른 상태를 가질 수 있습니다. 보통은 예제의 체크박스를 포함하여 Form을 구성하는 input 요소나 셀렉트박스, 라디오박스도 상태를 가집니다.

 

상태정보를 다루는 방식

사용자의 상태 정보들이 결국 우리가 개발하는 웹 어플리케이션의 주요한 관심사이면서 또한 비즈니스 로직에 깊은 연관을 갖게 됩니다. 그렇다면 Angular2가 어떻게 상태정보를 다루는지와 비교하기 위해서 기존에는 HTML요소의 상태 정보를 어떻게 접근해서 활용해 왔는지를 확인해 봅시다.

위의 예제를 그대로 이어서 사용자가 상품 구매를 진행하기 위해서 모든 “…동의” 항목에 체크 했다고 생각해 봅시다. 보통 우리는 사용자가 상품의 구매를 위해서 반드시 “…동의”체크박스를 눌러야 한다는 입력정보의 검증로직을 작성합니다. 이 때 일반적인 웹 어플리케이션의 접근 방식은 체크박스의 “checked” property 상태 정보를 얻기 위해서 DOM API나 jQuery를 사용합니다. 아래 예제를 통해서 확인해 봅시다.

결국 DOM API나 jQuery 둘 중 어느 방식이든지 상태 정보가 필요할 때에 1. 해당 DOM에 접근한 후, 2. DOM의 상태정보를 읽어서, 3. 상태정보에 따라 해당 로직을 수행하는 절차를 거칩니다.

Angular2에서는 위 방식과는 달리 “선언적인” 방식으로 상태정보를 다룰 수 있게 해줍니다. 먼저 실제 라이브 예제를 살펴 봅시다.

이 예제는 왼쪽의 체크박스의 체크 상태 여부를 오른쪽의 버튼을 클릭할 때 alert창을 통해서 확인하는 것입니다. 체크박스의 상태를 변경해 가면서 오른쪽의 버튼을 눌러 정상적으로 체크박스의 상태를 가져오는지 확인해 보세요. 그리고나서 예제의 컴포넌트의 소스를 살펴봅시다.

먼저 위 예제는 javascript가 아닌 Typescript로 작성되어 있고 앞으로도 Typescript를 사용합니다. Typescript에서는 변수에 타입을 선언해 줘야 하는데 타입의 선언은 변수명 뒤에 콜론과 함께 선언해야 합니다. (앞으로 Typescript 문법설명이 필요한 부분이 있을 때 마다 설명합니다. 자세한 설명은 “Typescript 문법 소개(미정)”나, Typescript 매뉴얼을 참고해 주세요)

각 코드에 대한 의미는 아래와 같습니다.

  • 첫 번째 줄: 클래스에 angular에게 Component라는 것을 알려주기 위한 Decorator를 사용하기 위해 import 한 것입니다.
  • 5~8번째 줄:은 Component의 뷰를 구성하는 마크업 정보입니다.
  • 11번째 줄: 클래스에 멤버 변수로 isChecked라는 변수를 선언했습니다.
  • 12~14번째 줄: 클래스의 constructor 메서드 안에 isChecked를 true로 초기화 했습니다.
  • 16~18번째 줄: 버튼의  click 이벤트 시 호출할 메서드를 선언하였습니다.

Component에 template에 익숙하지 않은  [(ngModel)], (click)이 눈에 거슬릴 텐데요. angular1을 경험해 보신 분들은 two-way binding을 알고 계셔서 눈치를 채셨겠지만, 데이터 바인딩에 대해서는 다음 글(데이터 바인딩)에서 설명 하기로 합니다.

이 예제가 보여주는 핵심은 Angular2에서는 상태정보를 Component의 멤버변수로 선언하여 이를 체크박스의 체크 상태와 자동으로 연결해 준다는 것입니다. 이로 인해 우리는 DOM API나, jQuery와 같이 직접 DOM에 접근해서 상태정보를 읽는 노력을 할 필요가 없습니다. Component안에서 상태정보를 직접 관리할 수 있습니다.

이 방식의 목적은 단순히 angular2가 사용자의 행위에 의해 변경된 HTML요소의 상태를 자동으로 Component의 멤버변수에 반영해 주어 편의를 제공해 주는 것에 있는 것이 아닙니다. 비즈니스 로직에 필요한 상태정보를 얻기 위해 비본질적인 DOM의 접근하는 로직은 프레임워크에게 위임하고 Component 안에서 뷰의 비즈니스로직 처리에만 집중하게 해주는 것이 좀 더 근본적인 목표입니다.

물론 “[(ngModel)]”과 같이 자동으로 데이터를 반영해 주기 위한 최소한의 정보를 마크업에 담아야 하기 때문에 완벽하게 뷰와 Component간의 종속성을 끊을 수는 없습니다. 또한 실제 어플리케이션을 개발하다 보면 직접 DOM에 접근해야할 일도 있고 jQuery를 쓰게 될 수도 있습니다.

하지만, 최대한 뷰에 종속되는 로직을 줄이거나 한군데에 모으고 Component의 역할을 비즈니스로직 처리와 이벤트 핸들링에 초점을 맞추는 것이 중요한 접근 방식입니다. (참고: 추후에 Service와 의존성 주입을 설명하면서 비즈니스로직은 Service로 빠지게 됩니다.)

 

정리

웹어플리케이션에서 HTML요소들 중에 사용자의 행위에 따라 상태정보를 가지는 것들이 있습니다. 이 상태정보는 기존 방식에서는 DOM에 접근해야만 얻을 수 있었습니다. Angular2에서는 Component는 순수하게 상태정보를 다루고 사용하는 역할에만 집중하고 DOM에 접근하는 역할은 angular 프레임워크 내부의 동작에 위임합니다. 다음 글에서 angular가 자동으로 DOM의 상태를 Component 내부의 상태에 반영해 주는 원리인 “데이터 바인딩”에 대해서 살펴보기로 합니다.

Angular2 글 목록보기

[Angular2] 예제 프로그램 및 프로젝트 셋팅

참고: angular2에 대한 연재는 여기를 참조해 주세요. 틀린 부분이나 의견, 피드백은 언제든지 환영합니다.  

회원관리 프로그램

예제로 사용할 웹 어플리케이션은 CRUD 기능이 있는 간단한 회원관리 앱입니다.  간단하게 아래와 같이 기능을 나열해 볼 수 있습니다.

  • 로그인/로그아웃 기능: 등록한 사용자로 로그인
  • 회원 등록 기능: id, 비번, 이름, 사진, 소속, 성별, 나이 등의 정보 입력 가능
  • 회원정보 수정 기능: 로그인한 회원 본인의 정보만 수정 및 삭제
  • 관리자 기능: 전체 회원의 목록 조회, 회원 상세 정보조회 및 수정 가능

위의 기능을 포함해서 검색 등의 추가적인 기능들을 점진적으로 만들어 볼 예정입니다.

이제 프로젝트 셋팅을 하기로 합니다. 현재 프로젝트의 기본 템플릿은 quickstart 예제의 구조와 큰 차이는 없습니다. 이미 생성해 놓은 프로젝트 템플릿 압축 파일을 받아서 작업할 디렉토리에 풀면 됩니다.

압축을 풀고 에디터로 열어서 왼쪽에 다음과 같은 파일 목록이 나오면 정상입니다.

프로젝트 폴더가 위치한 곳에서 터미널창을 열고 다음과 같은 명령어를 칩니다.

[NPM]에서 설명한 것과 같이 package.json에 이미 lite-server  실행 스크립트를 두었기 때문에 위 명령어를 실행하면 바로 localhost:3000으로 웹서버가 실행됩니다. 웹서버 실행 후 브라우저 창에 “회원정보 관리”가 뜨면 정상적으로 프로젝트 초기 환경 셋팅이 끝난 것입니다.

Angular2 글 목록보기

D 프로젝트 1차 회고

프로젝트 개요

  • 일정: 2016년 1월 중순부터 5월 중순까지 약 4개월 진행, 6월부터 2차 진행 중
  • 주요 사용 기술:
    • Backend는  Spring mvc 4 기반 사내 프레임워크. Java8의 Feature들을 적극 사용하려고 노력, 리파지토리는 jOOQ를 사용
    • Frontend는 Angular2 베타에서부터 시작해서 rc.1에 이르면서 1차 마무리. Angular2와 함께 rxjs, 일부 immutablejs 활용 및 라이브러리로 lodash 사용, Angular2 개발언어로 자바스크립트가 아닌 Typescript 사용, 패키징에 Webpack 적용
  • 첫 프로젝트
  • 주로 작업한 내역:
    • 프로젝트 초기 환경셋팅
    • front 앱 템플릿 코드 셋 작성 및 패키징 환경 설정 구성 (Angular2-webpack-starter를 우리 상황에 맞게 변형)
    • D 상세 등록 서버-프론트 부분 작업
    • 프로젝트 내 이벤트 비동기 호출 처리 서비스
    • Angular2를 열심히 파 본 덕에 angular.io 매뉴얼 소소한 2건의 commit 기여

 

느낀 점1: DDD 맛보기

  • 설계 시간이 거의 없어 충분히 고민할 시간이 부족했었음
  • 그래도 욕심을 좀 부려 DDD를 공부하면서 프로젝트에 이를 접목해 보고 싶었음
  • 자료 검색 중 DDD를 맛 보게 해준 자료: 토비님의 “스프링 프레임워크와 DDD
  • 글을 읽고 찾은 적용 포인트 하나! 가능하면 빈약한 도메인 모델은 만들지 말자.
  • 내가 맡은 코드의 서비스 레이어는 애플리케이션의 공통적이고 부가적인 지원성 성격의 기능(로깅, 트랜잭션, 에러처리 등)만 다루고 비즈니스 로직을 도메인에 담아보기로 결정
  • 비즈니스 로직의 8~90%가 CRUD와 Validation이었는데 CRUD 때문에 도메인 클래스 안에 리파지토리를 넣는 코드로 구현하게 됨
  • 하나의 메서드에 여러 개의 리파지토리 구현체를 파라미터로 넘기는 것이 맞는지 의문이 들었음
  • 차라리 도메인 클래스 안에 리파지토리를 멤버로 갖고 있어 복수의 리파지토리를 파라미터로 받지 않는 것이 더 낫지 않을까 생각
  • 그러나 이건 함께 하는 팀원과 상의가 안된 부분이고 뭔가 아닌 것 같아 완전 롤백.
  • 결국 전통적인 방식으로 서비스 레이어가 커지는 스타일로 CRUD 처리.
    • 나중에 알게 되었지만 위와 같은 스타일이 Active Record(활성레코드) 패턴

 

  • 원점에서 DDD는 도대체 어떤식으로 코드를 짜는 건지 샘플이 보고 싶어서 여러 차례 구글링 해봤지만 마땅한 예제 소스를 발견 못함
  • 그러다가 참고할 자료로”엔터프라이즈 애플리케이션 아키텍처 패턴” 책 읽기 시작
  • 트랜잭션 스크립트, 도메인 모델, 활성 레코드 개념을 알게 되고 차이를 이해하게 됨
  • 이어서 도메인 주도 설계 책을 읽기 시작 했으나 개념이 쉽게 이해되지 않고 코드 예가 부족해서 머리에 잘 안들어왔음
  • 다시 원점에서 자료 조사를 하다 발견한 자료: 조영호님의 블로그
  • 그리고 작년에 있었던 KSUG의 세미나 영상
  • 두 개의 영상을 보고 플젝에 DDD를 어떻게 적용할지 정리를 함
  • 결론적으로 트랜잭션 스크립트, 활성레코드 스타일 대신에 가능하면 Validation, 객체 조합 및 생성 등의 비즈니스 로직을 도메인 객체로 이관
  • DDD를 살짝 맛보면서 좋았던 점
    • 서비스레이어에서 비즈니스 로직을 테스트 했으면 스프링 컨텍스트 안에서 돌려야 하니 무거운데 도메인 객체는 POJO로서 테스트 케이스가 가벼워짐
    • 서비스 레이어의 메서드의 길이가 줄고 비즈니스 로직이 메서드로 추출되면서 가독성이 향상 됨

 

느낀점 2: Java8

  • 플젝 이전의 경험은 간단한 형태의 filter나 predicate 후 collect하는 수준
  • 플젝하면서 좀 더 적극적으로 Java8의 기능을 적용해 보려고 했음

 

  • 첫 욕심: Optional을 쓰자, 쥬니어다운 “예외가 없는 프로그램이 완벽하지!”  설익은 발상으로 리파지토리의 인터페이스 반환 값을 모두 Optional로 감싸자고 제안
  • 무식하게 모델 단 건 뿐아니라  Collection까지 다 Optional로 묶음
  • Optional 변수 네이밍을 고민하다 구글링 끝에 가장 마음에 드는 것으로 maybeXX를 결정하고 네이밍
  • Optional을 쓰고 보니 모든 코드에 다음과 같은 스타일의 코드가 붙음

  • 리파지토리 조회 코드에 모두 저런 식의 코드가 붙다보니 나중에 귀찮아서 그냥 maybeXXX.get()을 바로 해서 사용하는 경우가 생겨 Optional 사용의미가 떨어짐
  • 불편함을 느낀 와중에 Collection 타입의 반환은 빈 Collection을 주면 될 것을 뒤늦게 깨닫고 Collection 반환시에는 Optional을 걷어 냄
  • 이런 경험을 하고 나서 그럼 Optional은 어떨때 사용하면 좋은지 찾아보니 주로 라이브러리 개발 등 타인에게 API 제공 시 활용하면 좋다고 함
  • jOOQ와 함께 쓰면 단 건 모델의 경우 다음과 같이 코드를 짤 수가 있어서 Optional활용이 편해짐

  • 두번 째 욕심: Java8의 꽃은 역시 스트림이지 하면서 가능한 for문에 스트림 적용
  • 다만 일부 케이스에서 오히려 스트림이 로직을 한번에 이해하기 어려웠음
  • 한 예로 복수의 데이터를 사용자로부터 입력 받아 DB에 있는 데이터와 비교해서 입력된 데이터에서 빠진 것은 삭제하고 신규로 추가된 것은 insert하는 코드
  • 여러 번의 코드 리팩토링과 스트림 내의 다른 연산을 통해서 조금 더 가독성 확보

 

  • 스트림을 쓰면서 좋은 점 중 하나인 parallelStream!
  • 독립된 복수 연산이나 API 콜 및 db insert/update에 적용
  • 혹시나 하는 마음에 parallelStream과 관련해서 조사 중 다음과 같은 글들 발견
  • 내용 골자는 parallelStream이 공통의 스레드 풀을 사용한다는 것. 그래서 Hang이 걸릴 수 있는 부분에는 별도의 스레드 풀로 돌려야 함.
  • 그래서 API 콜 및 db insert/update 부분에서 parallelStream을 쓴 경우에는 ForkJoin으로 감싸서 처리

 

느낀점 3:  jOOQ

  • 리파지토리에 JPA를 적용해 볼 수도 있으나, 기존 db 스키마를 고려했을 때 JPA보단 직접 쿼리를 짜는 것에 대한 오너십이 필요하다고 생각
  • 그렇다고 if~ else 분기처리를 포함한 쿼리 템플릿을 xml에 담아서 마이바티스를 쓰는 것은 지양하고 싶다는 것이 팀원간의 공통된 의견
  • 그래서 jOOQ를 쓰기로 함.
  • jOOQ는 queryDSL과 유사하지만 좀 더 쿼리 키워드와 가까운 메서드로 쿼리를 읽는 것 같은 코드를 작성하게 해주는 라이브러리임
  • 예를 들면, insert나 select 코드는 다음과 같은 모양을 갖고 있음

  • 위의 코드와 같이 메서드 자체가 쿼리 키워드와 동일하고, 실제 생성되는 쿼리도 메서드와 동일함
  • 사전에 테이블 스키마를 클래스로 자동 생성해 주어 insert 하거나 select 할 때 바인딩할 값들은 반드시 테이블의 컬럼 타입과 동일해야 함.
  • 즉 컴파일 타임에 db에 넣는 값의 타입 안정성 보장
  • 단점이라고 한다면 쿼리를 코드로 짜다보니 큰 실수를 하기 쉬움.
  • where 절을 빼서 전체 테이블을 업데이트 했던 아찔한 순간이 있었음
  • 나중에 파트장님이  jOOQ에서 제공하는 쿼리 실행 전후 리스너에 where절이 없을 경우 쿼리 실행을 막도로 추가해 줬음

 

느낀점 4: 예외처리는 힘들어

  • 첫 프로젝트이다 보니 RunTimeException을 어떻게 처리할 것인가 고민이 많았음
  • 기본적으로 Controller를 통해서 유저뷰까지 예외를 내리고, UI에서 해당 예외를 적절하게 처리하는 것이 옳다고 생각함
  • 다만 비즈니스 로직 상 예측 가능한  NPE 같은 것들까지 일일이 UI애서 처리하기 보다 에러코드로 추상화해서 UI로 내려주는 것으로 결론을 지음
  • 사용자가 쓰는 서비스이다 보니 에러 발생 시 원인 파악을 위해서 에러코드를 관리하는 것이 더 좋을 것 같다고 판단했음
  • 그럼에도 불구하고 예외를 어떻게 관리하고 처리할 것인가는 고민이 필요한 이슈
  • 다행히 사내 기술 공유 메일에서 이와 관련된 논의가 있어서 참고하고 통찰을 얻을 수 있었음
    • 간단한 형태의 비즈니스 로직을 예외로 던지면 스택 트레이스 오버헤드 등 있을 수 있음
    • 비즈니스 로직 중 간단하게 처리하기 어려운 경우에 한해서 예외로 던지는 것은 의미가 있음 등

 

느낀점 5: UI 개발은 어려워

  • 어드민 성격이지만 사용자가 쓰는 서비스이기에 UI쪽 공수가 많은 프로젝트
  • UI에 어떤 기술을 쓸지 상당히 중요한 요소 였음
  • 종래와 같이 jQuery로 할 경우 코드가 장황하게 나열될 위험이 높아 향후 유지보수의 어려움이 따를 것 같아 프레임워크를 쓰기로 했고 다소 과감하지만 Angular2를 사용하기로 함
  • 사용자가 쓰는 서비스다보니 다소 강도높은 QA를 하면서 실제 개발 시간의 70%가 UI개발에 쓰인 것 같음
  • Angular2 학습 비용도 있었지만, Typescript, ECMA6, rxjs등 다른 내용에 대한 적응도 필요 했음

 

느낀점 6: 애플리케이션과 SPA

  • Angluar2를 쓰면서 SPA(Single Page Application)에 대해서 다시 생각해 보게 됨
  • SPA에 대한 기존의 이해도는 하나의 페이지 안에서 원하는 모든 것을 다 할 수 있는 것 정도.
  • gmail을 이미 익숙하게 써왔으니 이정도로만 생각
  • 하지만 이번 플젝하면서 SPA는 일반적인 웹페이지와 철학 자체가 다르고 따라서 개발도 기존의 웹앱을 개발하는 방식의 관점으로 접근하면 안된다는 것을 깨달음.
  • 먼저, 이해한 바를 바탕으로 개념을 분리할 필요를 느낌
    • 홈페이지, 웹사이트 등은 하나의 큰 주제를 공유하는 웹페이지들이 고정된 URL을 가지고 있는 것
    • SPA은 하나의 서비스 또는 일련의 목적을 달성하기 위한 기능들의 묶음으로 이루어진 애플리케이션이고 다만 구동되는 호스트가 브라우저 임
    • 웹 애플리케이션은 SPA나 홈페이지, 웹사이트등 브라우저를 호스트로 해서 구동하는 애플리케이션을 말함
  • 위와 같은 개념으로 볼 때 이번 플젝의 결과물은 이론의 여지 없이 SPA임
  • 따라서 UI개발을 웹페이지 개발방식으로 접근하면 안되고 델파이나  C++(MFC)로 만드는 기존의 데스크톱 애플리케이션 관점으로 접근하는 것이 더 적합하다고 생각
  • 정작 데스크톱 앱은 Java(Swing)을 이용해 본 경험이 전부였음
  • 그래도 컴포넌트 기반 Angular2의 아키텍쳐를 보면서 컴포넌트 기반 개발 방법론에 대한 이해도를 얻을 수 있었음
  • 부트스트랩이나  angular material등의 css 프레임워크가 뜨고 있는 것도 위와 같은 맥락 안에서 웹용 UI컴포넌트로서 인기를 얻는 것이라고 볼 수도 있음
  • 즉, 현재 애플리케이션의 흐름이 기존의 OS에서 직접 구동되는 네이티브 애플리케이션에서 브라우저 위에서 동작하는 웹 애플리케이션으로 변화되었고 이러한 배경하에 SPA가 나온 것임
  • 물론 SPA가 나오기 위해서 ajax 기술 부터 Shadow dom, Web Component까지 html의 스펙도 진화한 것임
  • 거기다 최근에는 오히려 웹앱개발의 언어가 네이티브와 모바일에서까지 동작하게 만드는 추세로 발전한 것임(Electron, React Native)

매듭 짓기

  • Backend 개발을 하면서는 모델링의 중요성과 DDD를 어떻게 하면 잘 적용할 수 잇을지에 대한 경험을 쌓을 수 있었음
  • Frontend 개발을 하면서는 Typescript 덕분에 ECMA6를 다뤘던 점이 좋았고, 웹기술의 변천사의 흐름을 이해할 수 있었음

[Tip] Fork한 Github 소스 원래 소스와 싱크 하기

관심갖고 Fork 한 저장소(Repository)의 커밋 내역을 원래 저장소의 최신 커밋 내역으로 바꾸는 방법은 간단합니다.

먼저 Fork한 자신의 로컬 저장소에 remote로 원래 저장소를 등록해야 합니다. 등록하기 전에 현재 원격 저장소가 무엇이 있는지 확인해 봅니다.

현재는 Github에 있는 내 저장소만 등록되어 있을 겁니다.  이제 아래와 같이 원격 저장소에 “upstream”이란 이름을 주고 원래 소스 저장소를 추가합니다.  (이름은 사실 상관 없습니다.)

이제 다시 등록된 원격 저장소를 확인합니다.

정상적으로 “upstream”이란 명칭으로 원격 저장소가 추가 되었습니다.

다음은 추가한 “upstream” 저장소를 “fetch”합니다. fetch와 pull의 차이는 가져와서 머지까지 자동을 해주느냐 안 해주느냐의 차이이고 “fetch”는 머지는 안 해 줍니다.

대략 위와 같은 결과를 확인할 수 있으면 정상적으로 “upstream”이란 저장소의 “master” 브랜치를 가져온 것을 확인할 수 있습니다.

이제 그럼 원래 자신의 로컬 저장소의 master를 checkout합니다.

fetch 해두었던 “upstream/master”를 체크아웃했던 “master” 브랜치로 머지합니다.

머지하고 Github에도 반영하려면 git push 하시면 됩니다.

이 내용은 Github에 있던 아래 두 링크 내용입니다.

  • https://help.github.com/articles/configuring-a-remote-for-a-fork/
  • https://help.github.com/articles/syncing-a-fork/