잊을만 하면 돌아오는 정산 신병들

Feb.05.2021 김시영

Backend Culture

안녕하세요!!!

안녕하세요. 정산시스템팀 온택트 신입 개발자 김시영입니다. (입사한 지 2달이 되었지만 한 번도 출근한 적이 없습니다..)

꿈꾸던 회사에서 (방에서) 직접 글을 작성하고 있다니 믿기지가 않는데요..ㅎㅎ

properties

하 또 왔네 안 궁금한데..

이번 글은 파일럿 프로젝트에 대한 후기를 공유해 드리고자 글을 작성하게 되었습니다. 정산시스템 팀은 신규 입사자를 대상으로 항상 파일럿 프로젝트를 진행하고, 이후에 기술 블로그에 글을 작성하는 유서와 전통을 가진 팀입니다.

기술 블로그에서 다루는 깊이 있는 주제는 아니지만,
갓 입사한 신입 개발자들은 이런 것들을 고민하고 느끼는구나정도로 봐주시면 좋을 것 같습니다.

정산시스템 팀의 다른 파일럿이 궁금하신 분은 링크에서 확인하실 수 있습니다. (세희님, 태현님, 우빈님)

1~2주차 도메인 설계 및 개발

파일럿 프로젝트는 정산시스템의 매우 매우 간략한 버전을 구현해 보는 것인데요! 요구사항은 아래와 같습니다.

기능 요구사항

관리자는 기본적으로 모든 데이터를 생성/조회/수정/삭제가 가능하고, 일반 회원은 아래의 정보를 검색만 할 수 있습니다.

  • 어드민(Member)

    정산 어드민에 접근하는 우아한형제들 직원을 의미하며 일반회원/관리자로 구분됩니다.
    회원가입/로그인/권한관리 기능

  • B2B 회원(Owner)

    업주는 여러 주문을 가지며 업주는 검색될 수 있습니다.

  • 주문 관리 기능(Order)

    주문은 결제 수단 및 금액에 대한 주문상세(OrderDetail)를 갖습니다.

  • 보상 금액 기능(Reward)

    사고와 같은 특정 사유로 인해 업주님들께 보상해주는 금액을 의미합니다.

  • 지급 관리 기능(Settle)

    주문데이터와 보상금액 데이터를 바탕으로 업주님들에게 지급해야 할 금액을 생성합니다.

기술 요구사항

  • OOP (객체지향) 코드 & 클린 코드
  • 단위 테스트 & 통합 테스트
  • SQL 인잭션, 스크립팅 공격을 비롯한 기본적인 보안
  • HTTP API
  • Spring Boot 2.3.x
  • JPA
  • Gradle 6.x 이상
  • Lombok
  • Git & Gitlab
  • JIRA
  • H2
  • 모던 JS 환경

엔티티간의 연관관계는 아래와 같습니다. 아래의 내용을 이해하시는데 참고하시면 좋을 것 같습니다.

properties

가볍게 보셔도 내용 이해에 어려움은 없으실거에요!

지옥이라던 코드리뷰.. 정말 지옥이구나

properties

하…

첫 코드리뷰는 개발자뿐만 아니라 기획자분들도 참가하셔서 코드/UX/UI 부분까지 함께 리뷰해주셨습니다.

정말 말로 뺨 맞는다는 말이 무슨 말인지 알게 되는 시간이었습니다. 정말 식은땀이 나고 허리가 아파오더라구요.

그럼에도 많은 걸 느낄 수 있었고 스스로가 너무 부족하다는 걸 알게 되는 소중한 시간이었습니다!

받았던 리뷰가 너무 너무 많아서 기억에 남고 공유하고 싶은 내용 위주로 이야기해보겠습니다.

1. 요구사항조차 지키지 않았다니? 그게 무슨 말이야?!

정말 멘붕의 연속이었습니다. 운영 측면에서 당연하다고 생각되는 기능이 전혀 안 되고 있었습니다.

정확히 말하면 주어진 요구사항은 모두 지켰습니다.

다만 요구사항으로 명시되어 있지 않더라도 당연히 되어야 하는 것들이 안 되는 경우가 많았는데요.

예를 들면 아래와 같습니다.

업주 번호를 통한 주문조회

AS-IS 처럼 업주 번호를 입력하고, 업주에 소속된 주문을 조회하는 기능을 구현하였는데요.

이상한 점을 찾으셨나요?! 누가 업주 번호를 다 외우고 있어?라는 생각이 드셨다면 정상입니다…

저는 너무나 당연하게 저렇게 구현하고 업주 번호로도 검색된다 크.. 라고 생각하고 있었습니다.

AS-IS : 번호 직접 입력
properties

TO-BE : 검색/모달 추가

properties

properties
우리는 업주 번호를 외우지 않고, 검색/클릭을 통해 작성하는 경우가 일반적입니다.

그래서 업주번호를 아는 경우는 직접 입력할 수 있고, 모르는 경우 검색/클릭을 통해 입력할 수 있는 형태로 기능 추가하였습니다!

지급금을 생성할 때 배달완료/취소된 주문만 가지고 하나요?

지급금이란 완료된 주문과 보정금액을 합산하여 최종적으로 업주에게 지급해야 할 금액을 의미합니다. 주문은 주문대기/주문접수/배달중/배달완료/취소 등 다양한 상태를 가지는데요.

지급금에 포함되는 주문은 당연히 배달 중이거나, 조리 중인 주문은 포함돼선 안되며 완료된 주문만 지급금의 대상입니다 저는 당연히 모든 주문을 지급금 대상에 넣어버렸습니다.

AS-IS : 모든 주문을 조회

properties

TO-BE : 주문상태를 기록하는 Snapshot + 조건 추가 조회

properties

주문이 배달중인지/배달완료되었는지 등의 상태를 기록하는 Snapshot 엔티티를 활용해 배달완료된 주문만 조회되도록 조건을 추가하였습니다.

해당 범위가 모든 시간을 커버하나요?

오늘 발생한 주문에 대해 지급금을 생성하는 경우 시간을 어떻게 설정해야 할까요?

저는 자정~23:59:59 로 설정했는데요. 이 경우 23:59:59:01초에 들어온 주문은 어떠한 지급금 생성에도 포함되지 않습니다. 해당 피드백을 듣고 다음 날 자정 직전까지라는 형태로 변경했습니다.

AS-IS : 오늘 자정(00:00:00 ≤ 주문시각 ≤ 23:59:59)까지 해드릴게요! 나노초는 깔끔하게 무시하고~~

properties

TO-BE : 다 포함시켜 드릴게요~

properties

기존에는 between으로 파라미터를 둘 다 포함하는 관계 start ≤ statusAt ≤ end 구조로 작성했다면 현재는 start ≤ statusAt < end 형태로 리팩토링하였습니다.

기본에 대해 느낀 점

보시면서 너무나 당연한 것들 아니야? 라고 생각하셨다면 정상이시고 아니라면 우리 같이 열심히 해봐요..

유지보수/확장성/고가용성 등 다양한 개발원칙이 있지만, 그중에 가장 우선되어야 하는 것은 비즈니스 요구사항을 명확히 충족하는가? 라는 것임을 알 수 있었습니다.

더 나은 설계를 고민하기에 앞서 당연한 것이 당연한 자리에 있는가? 에 대해서 먼저 고민할 수 있는 계기가 된 소중한 리뷰입니다!

2. Controller와 Service 레이어의 강한 결합

MVC 구조로 프로젝트를 구성하는 분들은 Controller -> Service -> Repository 구조가 익숙하실텐데요! 이 경우 의존성의 방향이 Controller에서 Repository로 단방향으로 흐르는 것이 일반적입니다.

하지만 저는 아래와 같이(AS-IS) Service에서 Controller에서 받아온 Request Type을 그대로 받아서 사용하고 있었습니다.

AS-IS : Service에서 WebDto에 의존

properties

properties

위의 경우 문제가 되는 것은 다음과 같습니다.

  • Service가 받고 싶은 포맷(Parameter)이 Controller에 종속적이게 된다 : Service가 Controller 패키지에 의존하게 된다.
  • Service레이어가 모듈로 분리되는 경우 해당 Type을 사용할 수 없다.
  • 트랜잭션으로 처리되어야하는 DTO 항목이, 항상 요청으로 들어온 값과 동일하지 않을 수 있습니다. 아래의 그림을 예로 들어보겠습니다.

    properties

    사용자 요청의 파라미터를 통해 외부 API를 여러번 호출한 이후 Service 레이어를 호출하는 경우, Controller가 받은 Web DTO와 Service가 받아야 할 DTO가 달라집니다.

    외부 API 호출뿐만 아니라 Client 요청 이후 Service 레이어를 호출하기 전 다른 작업으로 인해 데이터 포맷이 달라질 수 있습니다.
    이런 경우에 Service 레이어가 Controller 레이어 DTO에 의존하고 있다면 문제가 될 수 있습니다. 따라서 Service 레이어는 자신이 원하는 포맷으로 데이터를 받을 수 있어야합니다.

    참고로 위의 예제에서 Controller에서(Controller-Service사이에 중간 레이어를 두고 하는 경우 포함) 외부 API를 조회하는 이유는 Service에서 해당 작업을 수행하는 경우 트랜잭션과 무관한 작업이 트랜잭션내에 포함되기 때문에 DB 타임아웃과 같은 이슈가 발생할 수 있기에 위와 같이 작성하였습니다.

TO-BE : 서비스의 포맷에 맞게 변환해서 전달

이러한 문제를 개선하기 위해 Service는 자신이 원하는 포맷에 맞게 데이터를 받고 Controller에서 그 포맷을 만들어주는 것이 적절하다라고 생각하여 아래와 같이 리팩토링하였습니다.

properties

properties

레이어를 분리하는 것이 습관적으로 하는 작업이 아니라 레이어별로 담당해야 하는 역할을 명확히 하고, 레이어별 의존관계를 고려해 유지보수 하기 좋은 형태를 만든다라는 점을 다시금 생각할 수 있는 리뷰였던 것 같습니다!

추가로 Service에서 엔티티를 받고 엔티티를 반환하는 형태도 좋은 방법이라고 생각합니다. 다만 아래와 같은 이유로 저는 DTO를 받고, DTO를 반환하고 있습니다.

  • 불완전한 엔티티를 Service 파리미터로 받는 부분이 적절하지 않다.
  • Service 메소드별로 원하는 포맷이 달라지는 경우 결국 DTO로 분리될 것이고, 이는 Service 파라미터가 엔티티/DTO로 받게 되어 일관성을 위배할 수 있다.
  • 반환 타입의 경우 Service를 사용하는 한 부분인 Presentation 레이어에서 도메인을 알고 있는 것 자체가 문제가 될 수 있다고 판단해서 DTO를 반환하고 있습니다.

부족한 지식으로 혼자 생각한 것이니 피드백 주세요!

3. 추가 리뷰

JPA distinct – 테스트에 유의하자

JPA에서는 distinct라는 옵션을 통해 DB에서 동일한 칼럼을 제거하는 기능 + OneToMany 관계에서 One에게 Many를 자동으로 할당해주는 기능이 존재하는데요.

주의해야 할 점은 OneToMany 관계라도 Many가 하나인 경우 즉 1:1로 매칭되는 경우, 당연히 Distinct가 없어도 정상 동작합니다. (distinct가 필요한 이유는 1:N관계를 조인했을 때 N을 기준으로 조인이 되고, 그 결과를 그대로 가져오는 것을 방지하고자 필요하기 때문입니다.)

AS-IS : 주문과 주문 상세가 하나씩 매핑되는 테스트

저는 이 경우를 간과하고, 1:N 관계에서 N을 하나로 두고 테스트를 진행하였고, 뒤늦게 버그를 발견해 distinct 옵션을 추가한 이슈가 있었습니다.

properties

TO-BE : 주문에 해당하는 주문상세가 각각 3개씩

properties

잘못된 테스트는 오히려 독이 될 수 있다 라는 점을 알게 되었습니다.

테스트란 결국 프로그램이 정상 동작함을 증명할 수 있는 수단인데, 프로그램의 오류가 존재하는데 테스트가 통과한다는 모순이 생길 수 있습니다.
테스트를 작성할 때는 최대한 예외케이스부터, 해피 케이스 순으로 다양한 경우를 커버하는 테스트가 중요하다는 생각이 들었습니다! (생각은 하고 있었는데, 지키는 건 쉽지 않네요..)

JPA distinct – 페이징과 distinct

JPA에서 distinct 옵션을 사용할 때 주의해야 할 점이 하나 더 있는데요!
아래의 코드는 주문을 조회할 때 연관된 엔티티인 업주/업주의 계좌/주문상세목록을 함께 조회하는 코드입니다.

AS-IS

properties

properties

쿼리만 봤을 때는 페이징처리를 하고 있지 않기 때문에 페이징과 관련 없는 쿼리처럼 보이지만, 배치에서 chunkSize 를 통해 offset을 걸고 있기 때문에 이 또한 페이징 쿼리로 동작합니다.

문제가 되는 것은 페이징은 DB에서 일어나는 작업인데, 1 : N 관계를 조인하는 경우 DB 칼럼의 갯수가 N을 기준으로 맞춰지게 됩니다.
주문을 페이징처리해서 조회하고 싶지만 주문상세를 기준으로 페이징된 결과를 반환하게 되는데요.

데이터베이스에서 페이징한 결과가(주문상세를 기준으로) 원하는 결과(주문을 기준으로)와 다르기 때문에 JPA에서는 아래와 같은 로그를 보이며, 모든 데이터를 메모리에 올리고 메모리에서 페이징 작업을 수행하게 됩니다.

properties

properties

TO-BE

이를 해결하기 위해선 다양한 방법이 있는데요. 저는 아래와 같이 연산을 모두 DB에 맡기는 방식으로 리팩토링 하였습니다! 이에 대한 설명은 3주차 배치 적용에 조금 더 자세히 나와있습니다.

properties

이렇게 DB연산을 하지 않는 경우는 hibernate.default_batch_fetch_size 를 활용하거나, id만 조회하여 in절로 직접 쿼리를 작성하는 방법 등의 방법으로 이를 해결 할 수 있습니다. 조금 더 구체적인 원인/해결책을 원하신다면 이 블로그를 참고해주세요!

JpaItemReader에서 쿼리를 문자열로 작성하는 경우 페이징 관련된 쿼리가 없어서(chunkSize로 조절하기 때문에) 위와 같은 문제를 간과하기 쉬운데요.
쿼리가 어떻게 날라가는지 항상 로그를 확인하는 습관을 가져야겠다고 느꼈습니다!

Utility클래스

날짜와 관련된 값들을 편하게 사용할 수 있게 Util 클래스를 작성했는데요. 해당 클래스를 설명해 드리기에 앞서 간단하게 사용한 로직의 순서에 대해 설명해 드리자면

  • 지급금을 생성할 때 일 단위/주 단위/월 단위로 기준일자를 요청으로 받습니다.
  • 예를 들어 주 단위 지급금에 기준일자는, 화요일로 들어온 경우 월요일 자정 ~ 다음 주 월요일 직전 까지가 지급금을 생성하는 날짜 범위입니다.
  • 해당 범위를 찾기 위한 클래스로 아래와 같은 유틸성 클래스를 작성했는데요!

properties

여기서 문제는 위와 같은 도메인 특화된 로직임에도 불구하고 Util 클래스라는 점입니다.

유틸 클래스란 일반적으로 프로젝트에서 범용적으로 사용되며 특정 도메인에 종속적이지 않고 다양하게 사용될 수 있는 클래스(String 관련 파싱 클래스나, 날짜 관련 파싱 클래스) 등을 의미하는데요.
저는 지급금이라는 도메인에 매우 종속적인 클래스를 작성하고도 당당하게 유틸이라는 이름으로 사용하고 있었습니다. 피드백 이후 지급금 패키지로 이동하여 지급금용 유틸클래스로 사용하고 있습니다.

유틸성 클래스를 무엇이라 정의할 것인가?와 같은 용어에 대한 정의는 팀별로 다르겠지만, 팀 의견이 없다면 가장 범용적으로 사용되는 의미에 적합한 형태로 코드를 작성하는 것이 중요하다는 걸 느낄 수 있었습니다!

여담으로 Lombok에서 @UtilityClass 라는 어노테이션을 통해 final 클래스, static 메소드로 유틸 클래스를 관리할 수 있습니다.

3주차 모듈 분리

3주차 과제는 단일모듈로 되어 있던 프로젝트를 멀티 모듈로 변환하는 과제였습니다. 모듈을 분리하는 과정에서 했던 고민과 피드백을 공유해 드리겠습니다.

현재 팀의 모든 서비스는 Gradle Multi Module 프로젝트로 사용중입니다.
단일 모듈을 어드민모듈과/도메인모듈로 분리해봅니다.

멀티모듈을 적용하면서 가장 고민했던 부분은, 확장성을 고려한 설계 였습니다. 현재 상황에선 모듈로 분리되지 않아도 될 것들을 미래에는 이렇게 되지 않을까? 라는 생각으로 최대한 작은 단위로 쪼개려고 했습니다.

모듈간의 의존성

초기 모델

도메인 모듈 내에서 회원 모듈어드민 어플리케이션에서만 사용되고 추후에 추가될 배치나 API어플리케이션회원 모듈을 사용하지 않을 것 같다고 생각하여 도메인 모듈을 세분화하여 설계하였습니다.

properties

리뷰 내용

이에 대해 다음과 같은 피드백을 받았습니다.

모듈은 최소스펙으로, 데이터의 형태는 확장가능한 형태를 지향하고 있어요.
미래를 고려한 설계는 오히려 복잡성을 증가시키는 경향이 있어요.

  • 예상 가능한 범위라면 확장성/유지 보수하기 좋은 코드를 지향해야겠지만 불확실한 형태로 모듈을 세분화하는 것은 오히려 복잡성을 증가시킬 수 있습니다. 최소스펙으로 설계하면서 확장 포인트를 모두 닫아버리는 것은 문제가 되지만 불필요한 혹은 조금은 의미가 적은 모듈 세분화는 지양한다는 팀 내 의견을 듣고 아래와 같이 변경하였습니다.
  • 추가로 파일럿프로젝트의 경우 JPA를 사용하여 엔티티간 객체참조를 사용하고 있는데요. 이 경우에 모듈을 세분화하면 서로 복잡한 형태로 각각의 모듈을 참조해야 하는 이슈도 있었습니다. (의존하고 있는 모든 도메인 모듈을 추가해야 하기 때문에)

리뷰 이후의 모델

너무 깔끔해졌나요? 이게 멀티?모듈인가.. (현재는 배치어플리케이션과 테스트모듈이 추가되었습니다…4개에요…!)

3주차를 기준으로 어드민 어플리케이션 하나만 사용되고 있고, 그렇다면 도메인 모듈이 회원/정산이라는 세부 모듈로 분리하는 것보다 실제로 분리될 일이 있을 때 분리하기 위해 하나로 합치는 형태로 리팩토링하였습니다.

properties

외부 라이브러리를 사용하는 것

멀티모듈 과제를 진행하면서 외부 라이브러리를 사용했는데요. 외부 라이브러리를 선택할 때 여러 가지 기준에 대해서 피드백 받은 부분도 함께 공유해 드리려고 합니다.

잘 아시다시피 외부 라이브러리는 직접 작성한 것이 아니기에 프로젝트에 도입할 때 고려해야 할 부분이 많습니다. 스타 많으면 장땡..

  • 제어권의 유무 직접 작성한 코드를 사용하는 것이 아니기에 외부 라이브러리를 수정하거나/수정 요청하는 등의 작업이 가능해야 합니다. 직접적으로 코드를 제어할 수 있거나 수정요청 시 빠르게 반영되는 것이 중요합니다.
  • 공식지원 유무 스프링 진영 정확하게는 스프링 부트 환경에서 공식적으로 지원하는 라이브러리인지도 중요한 기준이 될 수 있습니다. 프레임워크 차원에서 외부 라이브러리의 의존성을 관리하고 지원하기 때문에 조금 더 안정성이 올라갈 수 있습니다.
  • 커스텀 지원 외부 라이브러리를 사용하다 보면 불편한 부분들에 대해서 혹은 사용자의 입맛에 맞게 확장할 수 있는 형태인지도 중요합니다. 이러한 확장 포인트가 잘 열려있으며 사용하는 데 어려움이 없는 것도 중요합니다.
  • 안정성 많은 사람이 사용하고 검증한 라이브러리인지 등 안정적으로 운영 중인지에 대한 요인들을 검토합니다.
  • 마지막 수정/커밋 위에서 제시한 기준을 충족하기 위해서는 사용자가 요구하는 방향으로 지속해서 유지 보수되어야 하기 때문에 마지막 커밋은 중요합니다.

외부 라이브러리를 사용한다는 것은 외부에 의존성을 가진다는 의미이기에 선택이 조심스러운데요!
위와 같은 기준 + 본인의 상황에 적절한 기준을 추가해서 추가/삭제하시면 좋을 것 같습니다.

4주차 배치 적용

4주차에는 지급금 생성, 결제수단별 주문집계 두가지 로직을 배치를 사용하여 구현하는 것이 과제였습니다.

이제 정산 시스템의 핵심 기술인 Spring Batch를 사용해봅니다.
(실제 정산 시스템의 전체 코드 중 절반이 Spring Batch로 진행됩니다)

배치를 처음 접하시는 분은 이 영상이나 블로그를 참고하시면 도움이 많이 될 것 같습니다.

WAS 연산 vs 데이터베이스 연산

배치를 구현하며 처음 했던 고민은 WAS와 데이터베이스 중 어디서 연산해야 하는가? 였습니다. 여러분은 연산을 어디서 주로 처리하시나요?

저는 AS-IS와 같이 자바에서 연산을 처리하는 방식을 선택했는데요. 결제수단별 집계를 위해 작성한 로직은 아래와 같습니다.

  • 주문데이터를 조회
  • 업주별 주문상세(결제 타입 및 금액이 포함되어 있음) 집계
  • 집계된 주문 상세를 결제 타입별로 다시 집계

AS-IS : 데이터 조회 및 WAS에서 집계 및 연산

properties

properties

위와 같이 DB에서 Grouping 하지 않고 자바에서 Grouping을 하는 경우 아래와 같은 문제가 있습니다.

  • Grouping 로직을 자바에서 수행하기에 이를 관리하는 일급컬렉션/자료구조가 복잡해진다.
  • 복잡한 자료구조를 갖기 때문에 단위테스트가 불편하다.
  • Grouping 조건이 추가되면 또 다른 일급컬렉션을 만드는 등 유지보수가 어렵다.

여담으로 ItemReaderQueryDsl을 사용하시려면 이 글을 참고하시면 도움이 많이 될 것 같습니다!

TO-BE : 데이터베이스에서 집계/연산 이후 저장

위와 같은 문제를 해결하기 위해서 DB에서 Grouping과 Sum을 모두 하는 형태로 아래와 같이 변경하였습니다.

properties

위와 같이 데이터베이스에서 연산 해서 조회하는 경우 AS-IS 에서 작성한 일급컬렉션도 필요 없고, 쿼리로 집계된 결제수단별 주문데이터를 아래와 같이 저장하는 작업만 수행하면 됩니다.

properties

DB vs WAS

데이터 25,000건을 대상으로 로직을 계산할 때 아래와 같은 결과를 얻을 수 있었습니다. 성능에 문제가 되는 상황이 아니라면 유지 보수하기 용이한 방향으로 로직을 작성하는 것이 좋다고 판단하여 DB에서 연산하는 형태로 리팩토링하였습니다.

WAS : java 연산 DB연산
데이터 25,000건 25,000건
소요시간 12초 8초
사용 DB 동일환경(t2.micro) 동일환경(t2.micro)
사용 WAS 동일환경 동일환경

배치/실시간 API/캐시/사양/시스템 부하 등 다양한 요인으로 인해 DB와 WAS, 어디서 연산하는 게 좋다고 말하긴 어려운 것 같습니다. DB를 성능 좋게 사용하시고 WAS를 조금 낮은 사양으로 Scale-out 하시는 분도 있을 것이고, WAS를 좋은 사양으로 사용하시는 분도 있을 것입니다.

결국 어디서 연산하는가는 유지보수와 성능 측면에서, 성능의 기준점을 충족한다면 상황에 맞게 유지 보수하기 좋은 형태로 로직을 작성하는 것이 좋을 것 같습니다!

Chunk-Oriented Processing에서 각 단계의 역할

Spring Batch를 사용할 때 Chunk-Oriented Processing 방식으로 배치를 작성하신다면 Reader Processor Writer 에 대한 고민을 한 번쯤 해보셨을 것 같습니다. 이에 대한 피드백을 공유해보겠습니다.

AS-IS : 스텝 분리 및 임시저장소 활용

처음에는 각각의 이름이 의미하듯 읽어오는 작업은 모두 Reader에서, 가공은 Processor, 저장/수정은 모두 Writer에서 라는 생각으로 작성했습니다. 이를 위해 스텝을 여러 개로 두고, 스텝별로 읽어온 데이터를 메모리에 올려두는 형태를 선택했는데요.

해당 방식에서 문제는 아래와 같습니다.

  • 데이터가 많아지는 경우에도 메모리에 모든 데이터를 올려야 합니다.
  • 스텝별로 데이터를 공유할 수 없기에 임시저장소(메모리)를 만들어야 합니다.

properties

properties

TO-BE : 프로세서 분리

프로세서가 조회해도 된다. 프로세서를 활용하고 이를 단위테스트 하는 형태로

Step 별로 데이터를 넘겨줄 방법이 없기 때문에 Processor 를 분할하여 사용하는 것도 좋은 방법이라는 피드백을 듣고 아래와 같이 변경해보았습니다.
이렇게 되는 경우 Reader의 역할이 약간은 모호해지고 때에 따라서는 Writer의 역할도 모호해지는 느낌이 있는데요. 이 부분은 아래에서 다시 정리해보겠습니다.

전체 구조

properties

Processor

properties

Writer

properties

각각의 단계에서 어떤 역할을 담당하면 좋을지 고민해본 내용을 이야기해보겠습니다.

정리하기에 앞서 저는 각 작업의 특성 및 역할을 아래와 같이 정의했습니다.

  • Reader/Processor/Writer 모두 실행순서를 보장해주는 역할로써, 도메인의 로직이 밖으로 나오지 않도록 해야 한다.
  • Reader 트랜잭션의 시작점 + 한방쿼리가 가능하다면 조회의 모든 기능을 담당하게 되지만, 그렇지 않은 경우 트랜잭션을 시작하고, 배치를 시작하는 엔드포인트로써의 역할.
  • Processor 데이터를 가공하는 역할로 Reader 에서 읽은 데이터에서 부족한 부분을 추가로 조회할 수 있다. 또한 멀티쓰레드 사용이 용이하고 유일하게 Delegating 을 지원하는 작업이기에 작업을 세분화하는 형태로 사용
  • Writer 데이터를 수정/저장하는 역할

위와 같이 정의했을 때 애매했던 부분은 데이터를 저장/수정 하는 작업을 모두 writer 에서 수행하는 것이 맞을까 혹은 해당 배치의 핵심적인 저장/수정 내용이 아닌 부분은 processor 의 단계 중 일부로 처리할까?
라는 생각이 들었고 이에 대해 팀에서는 일반적으로는 아래와 같은 처리방식을 가진다고 피드백 받았습니다.

  1. Reader에서 읽어온 데이터 각각마다 다른 API를 요청하거나 각각의 데이터 저장/수정이 필요한 경우 processor에서 처리 (reader에서 processor로 데이터는 단건씩 넘어가기 때문에) 이후 최종 데이터만 Writer에서 저장
  2. 일괄적으로 동일하게 저장/수정해야 하는 경우 writer에서 처리

1번과 2번이 상호배타적인 방법이 아니기 때문에 상황에 적절한 형태로 선택하셔서 사용하시면 좋을 것 같습니다. 2번의 방법을 아래의 그림과 같이 추상화한 형태로 관리하는 등의 방식으로도 처리할 수 있을 것 같습니다.

properties

물론 상황에 따라 다양한 요소들을 활용하여 팀 내에선 다른 방식으로 처리하겠지만, 막연하게 배치를 접하신 분들에게는 위와 같은 기준이 도움이 될 수 있을 것 같습니다!

5주차 배포

5주차는 지금까지 만든 프로젝트를 배포하는 것이 과제였습니다. 코드 추가된 것이 없어 받았던 리뷰들만 간략하게 공유해 드리겠습니다!

선택 요구사항에 아래와 같은 내용이 작성되어 있었는데요. 무슨 의미인지 감이 오시나요?

실제 서비스 운영 환경에 맞는 OS/DB 파라미터 설정

저는 실제 사용자가 얼마나 들어오면 커넥션풀은 얼마로 설정할 것이며 트래픽별로 어느 정도의 EC2/오토스케일링을 설정해야 하는가? 와 같은 내용 같아서 못했는데요..
사실 그런 내용이 아니라 아래와 같은 내용에 대한 설정이었습니다.

  • AWS EC2를 만들면 서버 시간이 한국 기준으로 설정되어 있지 않습니다. 코드리뷰 받을 때 힌트를 주셨는데도 전 몰랐습니다…
  • DB 시간/언어 설정 등

또 한 번 기본이 안 되어있구나…를 느낄 수 있던 과제였습니다.

드디어 파일럿 끝났다…

properties

기진맥진….죽겄다..

드디어 엄청 길었지만, 또 짧게 느껴진 파일럿 프로젝트가 끝이 났는데요! 파일럿 프로젝트를 하며 개인적으로 느꼈던 점에 대해서도 한 번 공유해보겠습니다ㅎㅎ!

회고

꽤 길게 작성했지만, 작성하지 못한 리뷰와 감정들이 참 많은데요. 파일럿 프로젝트를 수행하며 얻은 것들을 크게 범주화하자면 아래와 같습니다.

기본에 충실한 개발자가 되자.

부끄럽지만 저는 비즈니스 요구사항보단, 어떤 설계가 더 나은 설계이고 어떻게 해야 더 나은 코드를 짤 수 있을까? 에 대한 고민이 늘 앞섰습니다.
물론 이런 고민이 나쁘다고 생각하진 않지만 우선시되어야 하는 것은 요구사항을 명확히, 그리고 당연한 것을 당연히 되도록 하는 것임을 느낄 수 있었습니다.

현업은 처음이라 첫 리뷰에서 많이 당황하셨을 정산시스템 팀원들께 죄송한 마음을 전하며 기본에 충실한 개발자가 되겠습니다!

스스로 해내야 한다.

배치를 구현하는 과제에서 동욱님은 아래와 같은 피드백을 주셨는데요.

시영님이 우아한테크코스를 통해 현업을 1년 먼저 배워서 잘하는 건지, 원래 잘하는 사람인지는 알 수 없다.
새로운 기술을 적용할 때 얼마나 빠르게 베스트 프렉티스에 가깝게 적용할 수 있는가가 시영님의 실력이다.

첫 주차에 받았던 충격만큼 강한 충격을 받은 메시지였습니다.
우아한테크코스에서는 늘 모두와 함께 고민하며 가장 최적의 해를 찾아가는 과정이 일반적인데요.
현업에서는 항상 누군가와 함께할 수 없으며 문제를 해결할 때 스스로의 역량을 발휘하는 것이 필요합니다.

혼자서 해내는 것이 아직은 낯설지만 새로운 기술에 대해서 그리고 베스트프렉티스를 늘 고민하며 지낸다면 언젠간 빠르게 베스트 프렉티스를 적용할 수 있는 개발자가 되지 않을까 라는 생각입니다! 많이 도와주세요..

내가 작성하는 코드는 레거시가 된다.

파일럿 프로젝트는 특성상 혼자서 개발하고 개발이 끝나면 아무도 쳐다보지 않는 프로젝트입니다.

하지만 실무에 투입되면 제가 작성하는 코드 한 줄이 정산시스템의 코드 한 줄이 됩니다. 너무 당연한 걸 왜 이렇게 엄근진이야?

너무나 당연하지만 늘 가볍게 생각하고 코드를 작성하던 저에게는 또 다른 부담이자 기대감으로 다가오는 요인인 것 같습니다.

유지보수하기 좋은코드/객체지향적인 코드 등 항상 이론으로만, 혼자 하는 프로젝트에서만 적용해보다 이제 정말 내가 작성한 코드가 누군가가 유지 보수해야 할 대상이라고 생각하니 더욱 열심히 학습하고 고민하며 코드를 작성해야겠다고 느꼈습니다.

자연스럽지 않은 팀 자랑

코로나 19로 인해 출근은 못 했지만 나름대로 많은 관심을 받으며 힘든 파일럿 기간동안 정산시스템 팀의 문화를 많이 느낄 수 있었는데요.

코드리뷰

properties

온라인임에도 불구하고 코드리뷰를 위해 모인 개발자들

온라인이라도 리뷰시간을 항상 꽉꽉 채워서 리뷰해주는 문화가 정말 좋았습니다. (물론 처음엔 지옥이였지만요..) 나름대로 고민하고 학습했던 내용을 코드리뷰를 통해 점검받고, 실무에서 코드를 작성할 때 주의점부터 제가 작성한 코드의 잠재적인 문제점까지 들을 수 있는 정말 소중한 시간이었습니다.
파일럿 이외에도 내부적으로도 코드리뷰 문화가 활발해서 참 좋은 것 같습니다.

첫 파일럿에 대해서는 기획자분들에 대한 리뷰도 포함되어, 힘들고 부족한 부분을 정말 많이 느낄 수 있었습니다.

정말 활발한 커뮤니케이션

정산시스템 팀은 정말 활발하게 커뮤니케이션하는 조직인 것 같습니다! 갑자기 정산시스템 팀 자랑을 한다고? 업무적인 대화 이외에도 팀원들끼리 항상 재밌는 농담을 주고받고 하는 문화가 너무 이상적인 팀이 아닌가 싶고, 이런 팀에서 일하게 되어서(아직 일한 적은 없습니다) 너무 좋은 것 같습니다!

properties

properties

properties

카톡 단톡방 같은 슬랙방

서로 사적인 이야기를 하는 자리가 아닌 업무적인 대화에도 동참하여 의견을 적극적으로 낼 수 있도록 더욱 열심히 해야겠습니다.

이력서 첨삭..?

갑분 이력서 첨삭..?이라고 느꼈으면 정상입니다. ㅋㅋㅋㅋㅋ

정산시스템 팀에서는 일정 기간마다 이력서를 점검하는 시간을 갖는데요!
각자가 해왔던 것을 점검하는 회고의 시간이 되기도 하고, 앞으로 어떻게 나아갈 것인지에 대한 고찰의 시간도 되는 것 같습니다!
이력서 첨삭하던 사진이 없어졌네요ㅠㅠㅠ 아 물론 자신의 이력서를 볼 때의 부끄러움은 자신의 몫입니다… 그래도 단순히 일만을 위한 관계가 아닌 서로를 챙겨주는 문화가 참 좋았던 것 같습니다.


properties

파일럿 프로젝트를 하며 느낀 점을 주저리주저리 적어봤는데요.
힘들었지만 좋은 팀 문화, 좋은 분들과 같이해서 즐거웠던 기간이었습니다.
너무나 많은 것을 느끼게 해준 정산시스템과 파일럿 프로젝트님(?) 감사합니다!

긴 글 읽어주셔서 감사합니다.