오픈소스 CATS로 간단히 퍼즈(Fuzz) 테스트

퍼징이란 퍼즈(Fuzz) 또는 퍼징(Fuzzing)이란 블랙박스 테스트의 한 방법으로 정상/비정상 데이터를 자동으로 생성하고 함수나 API에 전달한다. 어떻게 보면 암호 해독(crypto anaylysis) 과정과 비슷하게 보이지만 퍼징은 프로토콜이나 입력 데이터 타입에 의존적이다. 그래서 실패한 테스트 결과들은 암호 해독과는 다르게 실제로 유의미하다. 여담으로 몇년전 대기업에서는 유사한 테스트를 수행하기 위해서 전문 인력까지 두었었다. 그때도 도구를 사용하긴 했지만 테스트 설정부터 수행 후 보고서 … Read more

클린 (리액티브) 코드

RxJS merge operator

클린코드(Clean Code)는 소프트웨어 엔지니어링 분야에서 굉장히 의미가 깊은 책이다. 클린코드로 많은 개발자들이 자기가 작성한 코드의 정량적인(Quantitative) 부분 뿐만 아니라 정성적인(Qualitative) 부분도 신경쓰게 되었다. 책을 관통하는 주제는 일관되다. 사람이 읽기 쉬운 코드가 유지보수가 더 쉽기 때문에 높은 품질을 가진다는 것이다. 나는 3~4년에 걸쳐 스프링 WebFlux를 사용해 증권앱의 서버 그리고 GraphQL의 게이트웨이 프로젝트를 진행했다. WebFlux를 사용한 결과에 충분히 만족했고 Graphql … Read more

아치유닛(ArchUnit) 테스트

아치유닛(ArchUnit)을 사용하는 이유

“두 패키지 사이에 순환 참조(Circular Dependency)가 존재합니다. 변경이 필요해요.”

“@SpringBootTest 어노테이션을 사용하는 통합 테스트 코드는 test 폴더가 아니라 integration-test 폴더에 위치해야합니다.”

“Service 레이어는 Controller와 Model 패키지 에서만 접근해야 합니다”

팀내 경력이 오래된 시니어 개발자는 위와 같은 커멘트를 작성할 때가 많다. 그러면서 왜 그래야 하는지 코드나 패키지 단위로 다이어그램을 그려서 어떻게 컴포넌트들이 서로 작동을 해야하는지 설명한 경험이 있지 않은가?

새롭게 팀에 들어왔거나 해당 지식을 가지지 않은 사람들은 제일 경험이 많은 사람이 친절하게 알려주는 것이 팀내 지식 공유 차원에서는 제일 바람직할 것이다. 하지만 확장 가능하지 않고 팀내 고급인력의 끊임 없는 관심을 요구한다. 갑자기 사람이 늘어나거나 담당자가 휴가를 가버리거나 퇴사하면 잘 작동하지 않는 모델인 것이다.

우리는 이미 유사한 상황들에 많이 대처해봤다. 아치유닛을 사용하면 아키텍처상의 결정사항이나 결함들도 통합 빌드의 한 부분으로 테스트를 작성하고 자동화 하는 것이 가능하다.

홈페이지에서도 언급하고 있듯이 아치유닛이 아니어도 AspectJ나 CheckStyle, Findbugs를 사용해서 유사한 테스트를 수행할 수 있다. 하지만 해당 도구들은 조금 더 범용적인 성격을 가지고 있기 때문에 코드의 구조를 분석해서 읽기 쉬운 테스트를 작성하기 위해서는 아치유닛을 사용하는 것을 것을 추천한다.

아치유닛 구성

아치유닛은 Core 레이어, Lang 레이어, Library 레이어가 존재한다. Core API를 통해 대상을 특정하고 Lang API를 사용해 규칙을 정의한다. Library 레이어는 미리 정의된 규칙들, 예를들어 3 Tier 아키텍처, 6각형 아키텍처(Hexagonal Architecture) 등을 위한 규칙들을 제공한다. 아직은 실험적인 상태로 보이며 추후 확장의 여지가 있는 부분이다.

Core 레이어의 ClassImporter

리플렉션과 유사한 기능을 제공하는 Core레이어에서는 ClassImporter가 가장 중요한 API들을 제공한다. ClassImporters는 컴파일된 클래스들을 불러오기 위해 사용한다. 다음 코드는 com.book 패키지내의 클래스중에 Jar나 테스트 폴더등을 빼놓고프로덕션 코드만 대상으로 지정하는 설정이다.

ClassFileImporter()
        .withImportOption(ImportOption.Predefined.DO_NOT_INCLUDE_ARCHIVES)
      .withImportOption(ImportOption.Predefined.DO_NOT_INCLUDE_JARS)
        .withImportOption(ImportOption.Predefined.DO_NOT_INCLUDE_TESTS)
        .importPackages("com.book")

Lang 레이어는 아키텍처 규칙을 정의하는 API들을 제공한다. 규칙을 정하고 나면 다음과 같이 실제 검증을 수행한다.

JavaClasses importedClasses = new ClassFileImporter().importPackage("com.myapp");
ArchRule rule = // 아래 예와 같이 다양한 룰 생성
rule.check(importedClasses);

패키지 의존성 확인

service 패키지는 controller와 resource에서만 접근 가능하다.

classes()
   .that()
   .resideInAPackage("..service..")
   .should()
   .onlyHaveDependentClassesThat()
   .resideInAnyPackage("..controller..", "..resource..")

클래스 의존성 확인

*Service 클래스는 Controller 클래스 에서만 접근 가능하다.

classes()
  .that()
  .haveNameMatching(".*Service")
  .should()
  .onlyBeAccessed()
  .byClassesThat()
  .haveSimpleName("Controller")

클래스와 패키지 관계 확인

Book으로 시작하는 클래스는 com.book 패키지에 위치해야 한다

classes()
  .that()
  .haveSimpleNameStartingWith("Book")
  .should()
  .resideInAPackage("com.book")

상속 관계 확인

Connection 인터페이스를 구현하는 클래스는 이름이 Connectiond으로 끝나야한다.

classes()
  .that()
  .implement(Connection.class)
  .should()
  .haveSimpleNameEndingWith("Connection")

EntityManger클래스로 할당 가능한 클래스들은 persistence 패키지에 위치해야 한다.

classes()
  .that()
  .areAssignableTo(EntityManager.class)   
  .should()
  .onlyBeAccessed()
  .byAnyPackage("..persistence..")

주석 테스트

com.book 패키지 중에서도 “build/classes/kotlin/test” 폴더에 위치한 테스트들은 SpringBootTest를 사용해서는 안된다.

 classes()
    .that()
    .resideInAPackage("com.book")
    .should()
    .notBeAnnotatedWith(SpringBootTest::class.java)
    .check(ClassFileImporter()
         .importPath("build/classes/kotlin/test"))

레이어 테스트

논리적인 레이어를 구성해서 그 관계를 검증한다. 패키지로 구분된 controller, service, persistence 레이어를 각각 정의하고 각 레이어 별로 접근가능한 레이어들을 정의한다.

layeredArchitecture()
    .layer("Controller").definedBy("..controller..")
    .layer("Service").definedBy("..service..")
    .layer("Persistence").definedBy("..persistence..")
    .whereLayer("Controller")
    .mayNotBeAccessedByAnyLayer()
    .whereLayer("Service")
    .mayOnlyBeAccessedByLayers("Controller")
    .whereLayer("Persistence")
    .mayOnlyBeAccessedByLayers("Service")

순환 참조 테스트

패키지 com.book 의 하위 패키지들을 slice로 구성해서 각 slice들이 순환 참조 하지 않는 지 검사한다.

slices()
 .matching("com.book.(**)")
 .should().beFreeOfCycles()
 .check(javaClasses)

유용한 애자일 의식들 (Agile Rituals)

이전 아틀리시안 취업 후기에서 짧게 언급했지만 아틀리시안에서는 애자일 관련된 미팅을 할 때 사용하는 여러가지 템플릿들이 존재한다. 보통 애자일 의식(ritual, ceremony)이라고 하면 스프린트 플래닝, 데일리 스크럼, 회고 등을 떠올리는데 그 외에도 팀이 업무를 하면서 마주치는 여러가지 상황에 맞게 활용할 수 있는 다양한 의식들이 존재한다.

애자일 의식으로 얻는 것

정확한 의사전달

이렇게 미리 정의된 도구들을 사용할 때 얻는 가장 큰 장점은 구성원간 의미전달이 아주 명확해 진다는 점이다. 예를 들어 “이번주 금요일에 회의 있어요”. “오늘은 일하는 날이에요” 라고 말하는 것 보다 “이번주 금요일에 스파링 있습니다”, “오늘은 GSD날이에요”. 라고 말하는게 세세한 차이까지 전부 전달할 수 있다.

생산성 증가

선진국을 중심으로 근로자의 월 평균 근무시간은 계속 줄어들고 있다. 어떻게 적게 일하면서 더 높은 생산성을 유지할 수 있을까? 회사 전체의 생산성을 위해서는 위해선 여러 사람이 모이는 회의나 의사결정의 생산성이 무엇보다 중요하다. 팀원이 5명인 경우 1시간 걸릴 의사 결정을 3시간 걸려서 끝낼 경우 10시간의 추가 근무가 필요하다.

결과물 명확화

각 의식 별로 단계별 결과물이 확실히 정해져 있기 때문에 회의가 중간에 다른 길로 샐 우려가 적다. 각 참가자들은 회의에 앞서 어떤 내용을 준비해야 하는지 진행되면서 어떤 행동을 해야하는지 미리 알 수 있게 된다. 이는 여러 사람이 모였을 때 쓸데 없는 시간 낭비를 줄일 수 있다.

10년전에 한국 회사에 근무할 때 목적을 알 수 없는 미팅이 참 많았다. 2-3시간을 내리 미팅을 하지만 회의록만 늘어날 뿐 결정된것은 하나 없고 미팅이 끝나도 구성원들이 무엇을 해야할지 감을 잡을 수 없는 그런 상황, 참 많이 겪어봤다. 그런상황에서는 단순히 지칭하는 용어를 미팅,회의에서 회고, 데일리 스크럼 바꾸는 것 자체가 큰 효과를 가진다. 기술회사에서 근무하는 대부분의 사람들은 해당 용어를 접했을 때 회의의 목적이 무엇인지, 참가자로서 어떻게 행동해야 하는지 대부분 바로 이해할 수 있다. 데일리 스크럼이나 회고 같이 업계 수준에서 사용하는 의식들은 훨씬 도입하기 쉽다.

아틀라시안의 팀플레이 북은 아틀라시안 내부에서 자주 사용되는 여러 의식들을 플레이(Play) 형태로 제공한다. 여기서는 플레이 북의 의식들을 위주로 어떤 상황에서 활용할 수 있는지 알아 본다. (위키삽입)

애자일 리츄얼

DACI

컨플루언스 DACI 템플릿

의사 결정을 위한 애자일 의식은 다씨(DACI)라고 부르는데 팀단위나 그 상위 조직의 의사결정을 효율적이고 효과적으로 할 수 있게 도와준다. 아틀라시안에서는 보통 개발자가 아키텍처의 변경이나 여러 팀에게 영향을 미치는 의사 결정을 할때 사용한다. 다씨는 Driver(드라이버), Approver(승인자, 주로 엔지니어링 매니져 또는 아키텍트 ), Contributors(기여자, 동료 개발자들), Informed(결정 사항을 알아두면 좋은 사람들)로 구성 된다.

1단계는 의사 결정을 추적하기 위한 컨플루언스 페이지를 만든다.

2단계에서는 10분 정도 시간을 할애해 역할을 할당한다. 보통 다씨를 여러번 수행 했고 역할이 대략적으로 이미 정해져있다면 이 단계는 생략 한다.

3단계에서는 드라이버가 각 항목 들을 입력 한다. 모든 항목 들을 반드시 채워넣어야 하는 것은 아니다. 템플릿은 템플릿일뿐 상황에 맞게 응용한다.

  • 의사 결정 데드라인
  • 결정이 필요한 배후사정, 컨텍스트.
  • 현재 상태, 결정중, 결정됨, 연기 등
  • 의사 결정을 위해 실행한 관련 조사
  • 선택 사항들. 각 항목들의 장단점, 예상 비용 또는 필요한 노력등을 표시한 테이블
  • 추천, 기여자들의 추천 사항
  • FAQ

이렇게 작성한 페이지를 팀원들에게 공유하고 커멘트를 통해 의견을 받는다. 보통 이와 같은 방식으로 특별한 회의 없이 컨플루언스 페이지 만으로 여러 다양한 의사 결정이 가능하다. 다시는 의사 결정을 효율적이고 신속하게 만들어 줄 뿐만 아니라 기록을 확실히 남김 수 있는 장점도 존재한다. 이는 Github에서 AWR을 작성하는 이유와 비슷하다.

참조

의사 결정 템플릿

ADR(Architecture Decision Records)을 써야하는 이유

프로젝트 포스터(Project Poster)

프로젝트 포스터 템플릿
컨플루언스 프로젝트 포스터 템플릿

서비스를 개발하면서 여러가지 새로운 프로젝트들을 진행하게 되는데 그때 관련 정보의 시작점이 되는 의식이 프로젝트 포스터이다. PO(Project Owner)와 구성원들을 나열한뒤 프로젝트가 풀고자 하는 문제 영역에 대해서 서술한다. 검증(Validation) 영역에서는 현재 알고 있는 것과 모르는 것을 구분한다.

이 문서는 주로 PM이나 PO들이 주로 작성을하는 문서다. 개발자로서 프로젝트 포스터는 사내에 진행되는 프로젝트 들의 개요를 파악하는데 아주 유용한 문서가 된다.

참조

프로젝트 포스터 템플릿

스파링

애자일 스파링은 동료들에게 피드백을 구할 때 사용하는 의식이다. 스파링의 주제는 여러가지가 될 수 있다. 개발시 진행 방법에 대해서도 스파링을 할 수 있지만 주로 스파링의 대상이 되는건 제품이나 디자인 관련된 주제들이 많다. 스파링에 앞서서 참가자들에게 관련 자료 링크를 첨부한다. Garbage In, Garbage out , 입력이 좋아야 출력이 유의미하다. 회의 시간은 30분으로서 다음과 같은 절차를 거친다

1단계는 참가자들에게 원하는 피드백의 수준과 비평받길 원하는 영역에 대해서 언급한다. 예를 들어 너무 세세한 디테일에 신경쓰지 않아도 된다는 언급을 해주면 참석자들의 시간을 아낄 수 있을 것이다. (5분)

2단계는 다루는 주제에 대해 설명한다. 시간이 없으므로 내용은 모두 간략하게. 큰 방향을 잡기 위한 정보만 제공하고 주장을 정당화 하기 위해서 사용하지 않는다. (5분)

3단계는 참가자 들에게 피드백을 받는 시간이다. 참가자들은 직접적인 피드백이나 질문을 포스트잇을 통해 제출한다. 진행은 조용하게 필요한 질문만 한다. 그렇지 않으면 또 시간이 길어진다. (10분)

4단계는 3단계에서 나온 내용들을 가지고 토론하는 시간이다.

5단계는 유의미한 질문이나 피드백을 정리하고 필요한 경우 다음 스파링 세션을 정한다.

스파링에서 다루기 적합한 주제로는 성공에 대한 정의, 문제 영역에 대한 정의, 우선순위등이 될 수 있다. 문제는 발견했지만 어떻게 성공을 정의할 것인가. 수치는 있지만 목표를 어떻게 정해야 할지 모를 때. 스파링을 통해 자신이 보지 못한 답을 얻을수 있을 것이다. 컴포트 존에서 나올 수 있도록 도와주는 것이 애자일 스파링이다.

참조

https://dzone.com/articles/sparring-how-to-get-peer-feedback-you-can-actually

https://www.atlassian.com/team-playbook/plays/sparring

GSD(Get Shit Done)

GSD는 실제 문제 해결에 집중하는 의식이다. Get Stuff Done, Get Things Done등과 같이도 부른다. 내 경험상으로는 GSD를 위해 별도의 문서작성이 필요 하진 않지만 다른 팀원 들을 위해서 GSD가 필요한 이유와 원하는 커뮤니케이션 방법등을 따로 남겨두면 더 좋을 것이다. 현재 팀에서는 주로 월요일을 GSD 하는 날로 정하고 있다. 주말에서 돌아오자마자 플래닝 미팅을 한시간 하게 되면 팀원 들은 보통 괴로워 한다. 그래서 월요일을 GSD로 정해 스프린트를 마무리하고 보통 화요일날 플래닝 미팅과 회고를 진행한다.

참조

https://www.atlassian.com/team-playbook/plays/gsd-day

팀 상태 모니터

팀 상태 모니터 템플릿

프로젝트 팀의 전반적인 상태를 알아보기 위해 사용하는 의식이다. 팀 멤버들은 각자 주어진 팀 상태 평가에 대해 답을하게 되고 개선이 필요한 영역을 도출하게 된다. 미진한 부분을 개선 시키는 것이 목적이며 해당 영역에서 실행 가능한 방법이 무엇인지 도출할 수 있게 된다.

컨플루언스 상태 모니터 템플릿

https://www.atlassian.com/team-playbook/health-monitor

엘리베이터 피치

진행하는 프로젝트의 일관되고 간단히 설명하기 위해 진행하는 의식. 각 팀원들이 포맷에 맞춰서 입에 달라 붙는 문구를 나열한다. 마지막에 투표를 통해 하나의 통일된 표어를 결정한다.

참고

https://www.atlassian.com/ko/team-playbook/plays/elevator-pitch

https://ko.wikipedia.org/wiki/엘리베이터_피치

5 Why 분석

5 Why 분석은 장애 원인 분석이나 회고(Postmortem)시에 많이 사용하는 의식이다. 이 의식은 실제 문제가 무엇인지 명확히 드러나 있지 않았을 때 5 Why 방법을 사용해 문제 도출을 위해 사용한다.

참고

https://www.atlassian.com/team-playbook/plays/5-whys

업무 능력 계획(Capacity Planning)

업무 능력 계획 템플릿
컨플루언스 업무 능력 계획 템플릿

팀의 처리 능력을 확인하기 위해서 수행하는 의식. 첫번째로 팀원 들에게 각자 1주일 동안 수행하는 업무를 나열하게 한다. 해당 작업에 얼마 만큼의 시간을 소모하는지 예상치를 적는다. 슬랙, 컨플루언스, 이메일, 지라 이슈등을 사용해 최대한 정확히 밑그림을 파악한다.

다음 단계에서는 팀이 1주일에 얼마 만큼의 업무를 수행하는지 테이블로 표현한다. 마지막으로 작성한 테이블을 기반으로 미래나 현재의 프로젝트에 어떻게 사람을 할당해야 할지 판단한다.

참고

컨플루언스 업무 능력 계획 템플릿

https://www.atlassian.com/ko/team-playbook/plays/capacity-planning

교전 규칙(Rule of Engagement)

팀의 규칙들을 명문화 시키는 의식이다. 얼마나 세세하게 어디까지 정할 지는 팀별로 다르다. 먼저 원하는 팀의 방향에 대해서 간단히 토론을 한다. 다음과 같은 내용들을 공유 문서에 정리한다. 이어서 소개하는 ‘나의 사용 매뉴얼’과 함께 사용하면 더욱 효과가 있다.

  • 월요일은 회의 금지..
  • 코드 리뷰는 어떻게 진행해야 할지
  • 어떤 방식으로 피드백을 주고 받을지.
  • 팀내 공유 활성화 하기

참고

https://www.atlassian.com/team-playbook/plays/rules-of-engagement

나의 사용 매뉴얼

새로운 팀원이 팀에 들어오거나 하면 나의 사용 매뉴얼을 작성해 공유하는 동료들을 꽤 보았다. 자신의 업무 스타일과 과거 프로젝트 배경들을 아주 가벼운 분위기에서 동료들에게 설명하는 것이다. 이 설명서는 보통 새로 들어오는 팀원별로 아주 각양각생 이기 때문에 동료들에게 조금 더 허물없이 다가갈 수 있도록 도와준다.

첫 만남을 위해서만 사용하는 것은 아니고 매뉴얼을 작성해 블로그 형태로 게시해 두면 다른 팀 사람들도 더욱 쉽게 커뮤니케이션할 수 있을 것이다.

참고

컨틀루언스 나의 사용 매뉴얼 템플릿

블릿츠 (Blitz)

블릿츠(Blitz, 전격전) 는 말그대로 신속하게 문제를 찾아내기 위해서 진행하는 세션이다. 프로젝트나 기능 완성 후에 QA를 특별히 거치지 않는 상황에서 조금 더 자신감을 가지고 기능을 런칭하기 위해 여러 인원들이 모여서 동시에 사용자 시나리오를 재현하는 의식이다. 보통 드라이버가 블리츠 전에 여러가지 예상 가능한 시나리오들을 정리해 놓는데 참가자들은 해당 시나리오들을 따라가면서 이전에는 보지 못한 버그나 유의미한 피드백등을 발견하게 된다.

참고

https://www.mindsettlers.com/guide/5qzvTQqYh2eSYYAEmeOuEw

워게임 (Wargame)

워게임은 애자일의 의식은 아니지만 장애 시나리오를 미리 예상해보고 팀이 미리 대책을 세울 수 있도록 도와준다. 보통 워게임에 앞서서 1-2시간 정도의 브레인 스토밍 세션을 가진다. 세션에서는 시스템의 다이어그램을 크게 그려 놓고 예상되는 실패 모델과 실험들을 나열한다. 일반적으로 FEMA(Failure Model and Effect Analysis)를 사용한다. 가장 발생할 것 같은 우선순위가 높은 시나리오들을 먼저 실험하게 된다.

Renovate로 의존성 관리

MSA에서 의존성 관리

MSA에서 라이브러리 업데이트는 꼭 해야 하지만 잊기 쉬운 특성을 가진다. 이는 손씻기나 양치질등과 닮아 있다. 열심히 해도 티가 안난다. 문제가 생기기 전까진!

백엔드 개발자의 백미는 자동화를 통해 적은 리소스로 많은 사용자들을 대상으로 한 서비스에서 발생하는 문제를 해결하는데 있다. Renovate는 버전업 프로세스를 사용자가 원하는 만큼 자동화 시켜준다.

주기적인 버전업은 잠재적인 기술부채를 줄이고 서비스를 더욱 안정적으로 쓸 수 있게 해준다. 최근에는 보안 관련 패치가 버전 업그레이드를 통해 자주 일어난다.

때문에 주기적으로 라이브러리를 업데이트 하지 않으면 기술 부채를 조금씩 저축하는 것과 같다. 지금 팀에서는 매 빌드시마다 SourceClear 를 사용해 소스코드와 라이브러리의 취약점을 분석한다.

작성한 코드에서 취약점이 발견 되는 경우 직접 수정을 하면 되지만 Spring이나 Jackson과 같이 사용중인 라이브러리 취약점이 발생하면 라이브러이 버전 업그레이드로 필요하다. 문제는 이 취약점이 꽤나 빈번하게 발견된다는 점이다. SourceClear와 연동하고 나서 버전 업그레이드가 팀내의 잡일처럼 되어버렸다.

그와중에 Renovate는 가뭄에 단비같은 존재, 사용자 설정한 내용대로 버전 업그레이드를 위한 풀리퀘스트를 생성해준다. 자동으로 master머지도 가능하며 시간당 생성하는 풀리퀘스트의 수 등 아주 다양한 설정이 가능하다.

팀에서는 kotlin + spring + gradle + docker 플러그인을 사용하고 있는데 이외에도 아주 다양한 플러그인을 지원한다. 아래 스크린샷에서도 확인할 수 있듯이 change log까지 전부 첨부해준다. 이건 개발자가 PR을 생성할떄 잘 안해주는 부분인데, Renovate는 세심하다.

Github에서는 App으로 제공되고 있으며 Gitlab이나 기타 설치형 저장소에서는 Renovate Bot을 직접 호스팅 하는 것도 가능하다. 아래는 팀의 일부 저장소에서 사용중인 renovate.json 설정 파일이다. 원하는 만큼 세세하게 설정이 가능하다. https://docs.renovatebot.com/self-hosted-configuration/

</p>
{
  "$schema": "https://docs.renovatebot.com/renovate-schema.json",
  "schedule": [
    "after 10am and before 5pm every weekday"
  ],
  "timezone": "Australia/Sydney",
  "prConcurrentLimit": 20,
  "prHourlyLimit": 1,
  "automerge": true,
  "dockerfile": {
    "enabled": true
  },
  "maven": {
    "enabled": true
  },
  "terraform": {
    "enabled": true
  },
  "extends": [
    "config:base"
  ]
}

<p>

Bitbucket에서 혼자서 열심히 버전 업중인 Renovate

Pull Request 템플릿

문제점

하지만 소프트웨어 문제가 언제나 그렇듯이, 항상 좋기만 한 건 없다. 그렇지 않아도 몇일전에 관련된 장애가 한 건 있었다. reactor-netty 에서 중대한 메모리 누수가 발견되서 부랴부랴 전부 롤백해야했다.

사실 Renovate의 문제라기 보다 시스템적으로 버전업시의 충격을 흡수 할 수 있는 쿠션이나 버퍼가 존재했어야 한다. 배포가 잦지 않다면 스테이징 서버가 이런 역할을 할 수 도 있겠지만 문제가 일어난 서비스는 배포가 아주 빈번하게 일어나서 버전 업그레이드가 문제인지 개발자가 머지한 PR이 문제인지 판단하기 힘들었다. 

그래서 우리는 모든 스프링기반 프로젝트의 베이스가 되는 프로젝트를  만들고 해당 프로젝트는 자동적으로 의존성을 갱신하고 배포되도록 했다. 

이글을 읽는 분들도 저장소에 Renovate를 적용해 보고 어느정도 자동화가 가능할지 테스트 해보길 권한다.

onErrorDropped explained

RxJS merge operator

웹플럭스나 리액티브로 서비스를 개발하고 있다면 로그에서 onErrorDropped 메시지를 보게될 확률이 높다.  리액티브 애플리케이션을 사용하는 주된 이유는 높은 동시성을 달성하기 위한 것인데 동시성이 높아져 비동기로 여러 장소에서 데이터를 수신하는 경우 해당 에러가 자주 발생한다.

onErrorDropped가 발생 이유

이 에러 메시지는  정상적인 경우라면 오류가 발생했을 때 다운스트림으로 onError 에러를 전달해야 하지만 이미 다른 스레드에서 onError가 발생해 전체 스트림이 terminated된 상황임을 알려주는 것이다. 이미 종료된 스트림에 onError를 전달하는 행동은 리액티브 사양을 위반한다. 이 사양 링크 7번째 항목을 참고하자.

모든 것이 동기화된 세상에서는 이런 비정상적인 에러를 일어나지 않는다. Operators.onErrorDropped가 사용된 곳을 라이브러리에서 검색해보면 flatMap, merge와 같이 순서가 없이 실행되는 연산자들에서 주로 사용되고 업스트림의 순서를 보장하는 map이나 concat에서는 사용되지 않는다.

스프링 부트 애플리케이션이 WebClient를 통해 여러 데이터 세트를 동시에 가져온다면 발생한 에러는 이미 진행중인 다른 요청들에 영향을 줄 수 있다.

대처방법

onErrorDropped 로그가 너무 많다면 진짜 중요한 에러를 숨기고 있을 가느성이 높다. onErroDropped의 로그 레벨을 낮추는 몇가지 방법이 존재한다. (Project Reactor 코어에서 버전 업그레이드를 통해 조정될 가능성도 있다)

Hook을 사용

onErrorDropped 의 Hook을 설정하면 onErrorDropped가 발생했을 때 Hook에서 지정한 람다 함수가 실행된다. 프로젝트 리액터의 스케쥴러와 마찬가지로 Hook은 글로벌로 등록된다.

Hooks.onErrorDropped { log.info("onError Dropped.", it) }

onErrorContinue사용

동시성이 발생할 수 있는 기존 연산자를 확장해 각 Source 퍼블리셔들에 onErrorContinue를 추가해준다. 이 방법은 위 방법보다는 영향이 덜하며(less intrusive) 원하는 곳에만 적용이 가능하다. 다만 onErrorContinue의 사용법이 다소 복잡해 특정 연산만을 대상으로 작동함으로 메뉴얼을 잘 읽어봐야 한다. 아래는 merge연산자를 확장해

fun merge(sources: Iterable<Mono<T>>): Flux<T> = Flux.merge( 
    sources.map { 
        it.onErrorContinue { throwable, any -> log.info("Suppressed error in merging flux, {}", any, throwable)} 
}
)

이제는 패스워드 매니져와 보안 토큰을 사용할 때

점점 많은 사람들의 생활의 축이 인터넷 으로 옮겨 가면서 패스워드 관리의 중요성이 부각되고 있다. 특히 개발자들은 웹으로 제공되는 다양한 도구들도 사용하게 되기 때문에 패스워드 관리의 중요성은 두말할 필요가 없다.  다음 방법을 통해 패스워드 입력 시간은 줄어들고 기억해야할 패스워드의 수도 적어지며  혹시 모를 패스워드 노출 사고에도 더욱 안전해 진다.  보안 토큰에 관련 해서는 별도의 Wiki 링크에  … Read more

엘라스틱서치에 넣은 데이터가 키바나 에서 표시되지 않을 때

관련현상

Index Pattern에 Time Filter 를 지정했음에도 Discover에 아무것도 보이지 않는 현상이 있었다.  엘라스틱서치 포럼이나 구글링을 해봐도 시간 간격을 잘 조정하라는 이야기뿐.

해결방법

인덱싱 생성시 timestamp에 OFFSET 정보 (e.g. +9:00) 을 추가해준다. Kibana에서 시간을 제대로 해석하지 못해서 발생한 문제였다.

</p>
//As is
XContentFactory
    .jsonBuilder()
    .startObject()
    .field("keyword", keyword)
    .field("hits", totalHits)
    .field("seconds", tookSeconds) 
    .timeField("@timestamp", LocalDateTime.now()) 
    .endObject())
<p>
</p>
//To be
XContentFactory 
  .jsonBuilder() 
  .startObject() 
  .field("keyword", keyword) 
  .field("hits", totalHits) 
  .field("seconds", tookSeconds) 
  .timeField("@timestamp",ZonedDateTime.now(ZoneId.of("Asia/Tokyo")).format(DateTimeFormatter.ISO_OFFSET_DATE_TIME)) 
  .endObject())
<p>

코딩 교육이 필요한 이유

Code

최근 일자리 시장이 개발자 위주로 공급되면서 일반인들, 즉 비전공자들도 코딩 교육을 받는 경우가 늘어나고 있다. 코딩 열풍, 명과 암 취업을 하기 위한 것이 아니라면 왜 코딩을 배워야 할까? 여기서는 단순히 ‘코딩’만을 의미한다. 실제 많은 개발자들은 숲을 보지 못하고 나무만을 보는 개발자들을 ‘코더’라고 부르며 하대한다. 기반이 되는 공학적인 지식에 대한 배움이나 깨우침 없이 단순히 코딩 교육 … Read more

REST 아키텍쳐 레벨 3단계, HATEOAS 를 꼭 적용해야 할까?

HATEOAS가 이루고자 하는 이상과 현실의 차이가 존재한다. 개발과 의사결정 속도가 중요한 조직에서는 쓰지 않아도 좋다.

API 디자인을 시작하면서 뭔가 정말 제대로 REST 아키텍쳐를 만들고 싶어서 마틴 파울러가 쓴 REST성숙도 모델도 읽어보고 여러가지 자료 조사를해 보았다.

하나의 엔드포인트를 여러개의 리소스에 할당하기 보다 각 리소스를 그에 맞는 엔드포인트에 맵핑하고 API의 동작은 HTTP의 method를 동사로서 사용한다. 여기까지가 레벨2,대부분의 개발자들(백엔드,클라이언트모두 포함)이 이부분은 쉽게 이해하고 따라할 수 있지만 문제는 레벨 3부터 시작되었다.

하이퍼 미디어 컨트롤, 뭔가 멋진 단어들을 많이 모아 놓았지만 이 레벨은 HATEOAS가 적용되었냐 아니냐가 그 판단 기준이다.

REST API의 창시자인 로이필딩 (Roy Fielding)은 REST API는 반드시 하이퍼 미디어 드리븐이어야 하며 그렇지 않다면 그것은 REST가 아니라 RPC라고 주장한다. , 2008년의 글이지만 논문의 원저자이기 때문에 지금까지 미치는 파장이 적지 않은 것 같다.

HATEOAS의 장점

HATEOAS 를 적용하면 얻게 되는 장점이 무었일까, 당장 생각나는 것은 애플리케이션의 리소스가 상태머신(State Machine)으로서 해석될 수 있다는 것이다. 즉 상태머신이 가지는 장점을 API인터페이스에도 그대로 적용할 수 있다. 두번째는 API와 컨슈머의 결합이 느슨해진다는 점이다.

무슨 말이냐 하면 아래와 같은 응답이 있다고 가정하자. API응답을 봐도 상품 목록에서 이동할 수 있는 상태는 어떤것인지 짐작이 간다. 상품목록 API응답에서는 detail 과 order로 이동할 수 있다. 개발자의 입장에서도 API를 보면 다음 상태가 어떻게 변화할 수 있는지 예측이 가능하다.

두번째 느슨한 결합은, API 컨슈머 쪽에서 detail을 응답 받기위한 URL을 저장하지 않고 href 값을 얻어와 호출한다는 뜻이다. 그렇게 구현함으로서 컨슈머는 API 엔드포인트의 변화로부터 자유로워진다. (장점을 이렇게 적다보니 미래의 API에 적용하고 싶은 맘이 든다 위험하다. 하지만 이 포스트는 분명히 적용하지 않아도 좋다는 주장을 하기위함이다.)

&lt;/pre&gt;<br />
&lt;pre&gt;{<br />
  &quot;links&quot;: [<br />
    {<br />
      &quot;rel&quot;: &quot;detail&quot;,<br />
      &quot;href&quot;: &quot;http://server/api/items/12345&quot;<br />
    },<br />
    {<br />
      &quot;rel&quot;: &quot;order&quot;,<br />
      &quot;method&quot;: &quot;post&quot;,<br />
      &quot;href&quot;: &quot;http://server/api/items/order&quot;<br />
    }<br />
  ]<br />
}&lt;/pre&gt;<br />
&lt;pre&gt;

하지만 그럼에도 불구하고 REST API의 사용 주체가 빠른 속도의 의사결정과 개발을 중시하는 웹 서비스 업체라면, 너무 아카데미컬한 주제에 파뭍혀 실제 문제를 해결하는데 집중하지 못한다는 비난을 들을 수도 있을 것 같다. 오히려 이런 문제들 때문에 GraphQL같은 대안들이 나오게 된것이 아닐까?

물론 이런 REST가 추구하는 이상은 소프트웨어 엔지니어로서 성취하고 싶은 것이나, 회사원으로서 서비스의 전개 속도도 빠트릴 수 없는 부분이다. 그래서 슬며서 이슈를 던져보고 대부분 사람들이 쉽게 이해하지 못한다면 HATEOAS는 적용하지 않는게 좋겠다는 결론을 얻었다.

Restful API를 문서화를 도와주는 swagger API같은 도구들도 오히려 HATEOAS의 도입을 저하시키는 이유가 된다. 개발자가 손쉽게 전체 API엔드 포인트를 파악할 수 있으니 어떤 가정을 가지고 API 를 탐색하게 되고 이것은 강한 결합으로 이루어진다. (예를들어 swagger 페이지를 보고 상품 목록은 GET /items, 상품 상세 정보는 GET /items/1 과 같은 유추가 가능하다. HATEOAS 개념을 이용하면 상품 목록 리소스에서 전환 가능한 변화들이 link에 나타나야 한다. ).

물론 swagger를 사용해도 HATEOAS정보인 link 를 참조하는 것이 가능하나, link 사용을 클라이언트에게 강조하는 것이 원천적으로 불가능하기 때문에, API 컨슈머가 특정 가정을 가지고 (= 결합) API를 사용해도 막을 방법이 없다.

로이필딩이 인정하지 않는 REST API 면 어떠한가, 실제 HATEOAS나 REST성숙도 레벨 3까지에 대한 이해가 존재하지 않는 조직이라면 원작자의 의도를 무시하고 RPC스타일로 사용할것이 확실하다.

관련 링크들

https://martinfowler.com/articles/richardsonMaturityModel.html

https://stackoverflow.com/questions/1164154/is-that-rest-api-really-rpc-roy-fielding-seems-to-think-so

https://stackoverflow.com/questions/1139095/actual-examples-for-hateoas-rest-architecture

https://www.infoq.com/news/2009/04/hateoas-restful-api-advantages

REST APIs must be hypertext-driven

https://opencredo.com/designing-rest-api-fine-grained-resources-hateoas-hal/

https://jeffknupp.com/blog/2014/06/03/why-i-hate-hateoas/

https://softwareengineering.stackexchange.com/questions/348054/is-rest-and-hateoas-a-good-architecture-for-web-services/348167

https://www.infoq.com/news/2011/11/web-api-versioning-options%3bjsessionid=326B76743A247A4FDF42738061443BFE