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를 다뤘던 점이 좋았고, 웹기술의 변천사의 흐름을 이해할 수 있었음