토스 - 지속 성장 가능한 코드를 만들어가는 방법
본 글은 토스ㅣSLASH 22 - 지속 성장 가능한 코드를 만들어가는 방법을 정리한 글입니다.
- 토스페이먼츠는 코드에 대해 지속적으로 관심을 갖고 확장 가능한 방식으로 관리하고 있음
- 처음부터 최고의 설계나 품질을 유지하는것 보다는 최소 규칙을 기반으로 동작하는 코드를 빠르게 만들고 코드를 성장시킴
- 이를 위해서는 코드 한줄 한줄의 의미가 중요
- 소프트웨어 운영에서 가장 중요한건 코드
- 처음부터 최고의 설계나 품질을 유지하는것 보다는 최소 규칙을 기반으로 동작하는 코드를 빠르게 만들고 코드를 성장시킴
- 위 코드만 보면 어떤 느낌이 드는가?
- 구현 내용은 없지만 생성자를 통해 의존 클래스 확인이 가능하고 대략 무엇을 할지 추측 알 수 있음
- 이처럼 생성자는 무슨일을 할지 힌트를 줄 수 있음
- 추가적으로 꼭 필요한 의존인지, 너무 과한 의존이 아닌지 생각해볼수 있음
- 그렇다면 import 문을 보면?
- 오늘 발표에서는 import문을 동해 어떤 부분이 아쉽고 어떤 방향으로 개선가능한지 다음과 같은 사항을 통해 얘기해봄
Package
- 대략적으로 생성자를 통해 무엇을 할지 예상할 수 있음
- addPayment 로직도 흐름이 보이는것 같음
- import문을 함께 보면?
- Card에 대한 응집이 잘 이루어지지 않았다고 느껴짐
- CardService에서 Card관련(*Card*네이밍) 클래스 사용을 import를 통해 사용해야하는게 아쉬움
- CardPaymentProcessor, CardVaildation, CardReader, Card, CardPaymentRequest, CustomerCard
- 이유는 component, vo, service와 같이 역할별로 package가 구성되어있기 때문에
- CardService에서 Card관련(*Card*네이밍) 클래스 사용을 import를 통해 사용해야하는게 아쉬움
- 위와 package 구조를 변견하면?
- CardService가 의존하고 있는 클래스들이 CardService클래스 가까운곳에 응집되어있음
- import문이 줄어듬
- 역할이 아닌 개념끼리 더 뭉쳐진 느낌
- CardService가 의존하고 있는 클래스들이 CardService클래스 가까운곳에 응집되어있음
- 개념기준으로 package를 응집시켰을때 한 개념에 너무 많은 클래스가 포함되면 이것 또한 응집이 깨지는 결과를 초래함
- 이경우 적절한 시기에 개념을 더 세분화해야함
- 소유개념(card.owner)을 추가
- 다른 package가 아닌 카드 하위에 새 개념을 추가함
Layer
- layer속에서 코드를 관리할때 import문의 신호를 알아봄
- 위는 토스페이먼츠의 표준 layer 규칙
- 위에서 아래로 순방향만 참조 가능
- 참조 방향 역류 금지
- 건너 뛰기 금지
- presentation의 import는 별 문제 없어보임
- business layer에서 presentation layer의 request객체를 그대로 받음
- business layer -> presentation layer 참조 발생
- 역류 금지 규칙이 지켜지지 않음
- presentaiion layer에서 business layer로 전달할때 개념화된 클래스로 변환하여 전달
- business layer -> presentation layer 참조가 제거됨
- layer에 대한 잘못된 참조는 코드의 복잡도를 높이고 확장에 발복을 잡거나 문제를 만듬
- import 또한 코드로써 계속 주의를 기울여야함
Module
- module관계에서 import문의 역할
- 위는 토스페이먼츠 표준 module 구조
- 화살표는 Gradle 구성에서 의존하는 방향
- runtime시 runnable한 module을 중심으로 의존성이 주입되어 실행됨
- 회색원은 외부기능 확장에 대한 모듈
- module분리의 장점
- 기술 격리
- Module별로 테스트가능
- 역할과 경계를 뚜렷하게 정의 가능
- 위처럼 단일 Module로 작업하면?
- 비즈니스로직에 특정 라이브러리(외부에서 관리하는)의 의존이 침투
- 버전업 또는 내부 요구사항에의해 라이브러리 관련 변경이 필요하면 비즈니스로직도 같이 수정되어야함
- 토스페이먼츠 처럼 라이브러리에 대한 부분을 격리하면 의존성 침투를 막을 수 있음
- 추후 라이브러리 교체시 비즈니스로직 수정없이 가능
- Payments API는 도메인을 가지고 있고 API 서빙을 위해 Spring에 대한 의존성을 가지고 있음
- 도메인 코드가 잘 정리되어있고 외부 의존성 격리를 잘 했다면 다음과 같은 구조로 변경가능
- domain module이 payments module로 부터 분리 되었고 Spring과 멀어져 의존성이 격리
- 선택에 따라 storage module 은닉가능
- payments module -> storage module 의존시 runtime 의존
- storage는 domain에 대한 구현체 역할만 수행
- payments API는 domain module만 알고 storage module의 구성/구현체를 모름
- 결과적으로 business layer에서 presentaiion layer에 존재하는 클래스 import 불가능
- spring import 또한 불가능, 순수한 코드만 남음
마무리
- 토스페이먼츠가 코드에 관심을 두는 이유
- 통제, 제어
- 소개된 3가지를 기반으로 코드를 통제
- 이를 기반으로 소프트웨어를 제어
- 이를 통해 지속 성장 가능한 소프트웨어 개발
- 수명이 긴 소프트웨어 개발
- 통제, 제어
정리
- 역할이 아닌 개념(도메인)단위로 package구성을 하면 import문(다른 클래스의 의존성)을 보고 응집도 파악이 가능
- import문 또한 코드임, 계속해서 주의를 기울여야함
- 모듈 구성을 통해 외부의 변화로 부터 안전하고 통제가능한 비즈니스 코드 작성가능