📚 Study

[Toss SLASH22] Day 1 - 지속 성장 가능한 코드를 만들어가는 방법

date
Jun 22, 2023
slug
toss-slash22-day1-wlthrrksmd
author
status
Public
category
📚 Study
tags
Toss
Conference
클린코드
summary
import를 중심으로 코드 품질 개선하기
type
Post
thumbnail
스크린샷 2023-07-05 오후 4.09.15.png
 
notion image

지속 성장 가능한 코드를 만들어가는 방법

토스페이먼츠 Server Developer 김재민 님
우리가 만드는 코드는 처음부터 완벽할 수는 없습니다. 하지만 서비스가 폭발적으로 성장하려고 할 때 발목을 잡아선 안 되죠. 하루 이틀 운영되고 끝인 게 아닌, 오래 살아 있을 수 있는 좋은 소프트웨어는 어떤 모습일까요?

코드에 대해 ‘왜?’라고 질문하기

토스페이먼츠는 코드의 품질에 대해 지속해서 관심을 갖고 확장 가능한 방식으로 관리하고 있음
처음부터 최고의 설계나 품질을 유지하려는 것보다는 최소 규칙을 지켜 동작하는 소프르웨어를 빠르게 만들고 이를 성장시키는 과정을 거침
왜? 라고 질문하는 것이 가장 중요하다
 

생성자를 통해 의존하는 클래스 확인 및 기능 상상이 가능함

notion image
생성자는 클래스 의존도와 해당 클래스가 무슨 기능을 하는지 힌트를 준다
꼭 필요한 의존인지, 너무 과한 의존은 아닌지 판단할 수 있다
 

하지만 import 문을 열면?

notion image
… 코드가 대략 무엇을 할지 상상이 되는가? No
 

import 문을 중심으로 다음 3가지 관점에서 코드 품질을 개선해보자

  1. Package 구조
  1. Layer 구조
  1. Module 구조
 

1. Package 구조와 import

패키지 정책은 유연해야 한다

현재 상황을 계속 점검하면서 전략에 따라 유연하게 지켜내야 하는 가치
Card에 대한 부분에서 응집이 잘 이루어지지 않아 보임
특히, Card, CardPaymentRequest, CustomerCard 와 같이 같은 개념에 속하는 클래스들을 각각 import를 통해 사용해야 하는 점이 아쉬움
생성자에 있는 CardReader, CardPaymentProcessor, CardValidator 또한 import문까지 필요한가? 하는 고민이 들게 됨
notion image
 

응집에 대해 고민하면서 수정한 Package

Card 관련 클래스들의 import문이 사라짐
CardReader, CardPaymentProcessor, CardValidator 들은 생성자에 존재하면서 import까지는 필요없음
기존 패키지가 역할에 따라 나누어져 있었다면, 개념끼리 더 뭉쳐진 느낌
notion image
 

개념 기준으로 Package를 응집시켰을 때 한 개념에 너무 많다면?

import문을 줄이기 위해 한 패키지에 너무 많은 클래스를 넣게 되면 이 또한 응집이 깨지는 결과를 초래함
따라서 적절한 시기에 개념을 더 세분화하여야 한다
카드 소유에 대한 새로운 개념이 탄생하여 새로운 Package를 구성함
→ 따라서 새로운 import문이 추가되기는 하지만, 그 전처럼 다른 결의 패키지로 나뉘는 게 아니라 하위에 새 개념이 생기는 것으로 인식됨
notion image
 

2. Layer 구조와 import

토스페이먼츠 표준 Layer 구조

  • Layer는 위에서 아래로 순방향으로만 참조되어야 한다.
  • Layer의 참조 방향이 역류되지 않아야 한다.
  • Layer를 건너뛰지 않는다.
    • 경우에 따라 Layer를 제거할 수 있으나 Layer가 있는 것으로 정의한다면 건너뛰지 않는다.
notion image
notion image
notion image

Layer의 참조 방향은 역류되지 않아야 한다(윗단계의 클래스를 참조하면 안됨)

Presentation Layer
Presentation Layer
Business Layer
Business Layer
Presentation Layer의 import문은 깔끔해 보이지만 Business Layer의 Import문은 뭔가 잘못된 느낌이 든다!
레이어는 역류하지 않아야 한다는 규칙을 위반하고 있음
 
 

역류하지 않고 순방향으로 참조하도록 개선한다면 어떻게 될까?

Presentation Layer
Presentation Layer
Business Layer
Business Layer
전달할 때 개념화된 클래스로 변환하여 전달하여 역류가 일어나지 않음
모바일 서비스가 어떤 다른 개념에 의존적인지도 import문을 통해 쉽게 유추할 수 있게 됨
 

3. Module 구조와 import

토스페이먼츠 표준 Module 구조

화살표 방향은 Gradle 구성에서 의존하는 방향을 의미
Runtime시 Runnable한 모듈 중심으로 의존성이 주입되어 실행되는 구조
회색 원 : 외부 기능 확장 시 새로운 Module을 만드는 의미
모듈 분리의 이유?
  • 기술 격리 가능
  • 각 모듈별로 테스트 가능
  • 역할과 경계를 뚜렷하게 정의할 수 있게 됨
notion image
 

단일 Module로 작업 시 발생하는 의존성 문제

Business Logic 안에 특정 라이브러리에 대한 의존이 들어가게 됨
라이브러리의 경우 버전 업데이트 또는 외부 요구사항에 의해 변경될 수 있음
외부 의존을 줄이려면 Module을 분리하고 격리하여 의존성 침투를 막을 수 있음
라이브러리 부분을 격리하여 사용한다면 비즈니스 로직에서 특정 라이브러리에 대한 import를 할 수 없음
추후 라이브러리 교체 시 Business Logic에 영향을 주지 않고 작업할 수 있음
Module 분리 이전 - 외부 라이브러리에 의존적임
Module 분리 이전 - 외부 라이브러리에 의존적임
Module 분리 이후 - 특정 라이브러리 import 불가능해짐
Module 분리 이후 - 특정 라이브러리 import 불가능해짐
 

격리된 Module 환경의 예시

도메인을 가지고 있고, 또한 API 서빙을 위해 Spring에 대한 의존성을 가짐
도메인 코드가 잘 정리되어 있고 외부 의존성 격리를 잘 해두었다면 어렵지 않게 Module 구조를 변경할 수 있음
변경 전
변경 전
변경 후
변경 후
도메인 모듈이 Payments API에서 분리되었고, Spring과 멀어져 의존성이 격리됨
또한 선택에 따라 스토리지 모듈 자체를 은닉할 수 있음
Payments API가 스토리지 모듈을 Runtime으로 의존하게 하고, 스토리지 모듈은 도메인 모듈의 명세를 따라 구현체의 역할만 하는 구조로 수정되었음
이 구조에서 Payments API는 오직 도메인 모듈만 알고, 스토리지 모듈이 어떻게 구성되어 있는지, 어떤 구현체를 쓰는지 아예 모르는 형태가 됨
그러므로 HTTP 응답으로 JPA Entity를 사용한다거나 도메인이 HTTP 스펙을 알고 있는 문제를 방지할 수 있음
 
코드를 보면,
notion image
Business Layer에서 Presentation Layer에 존재하는 클래스에 import 불가능해짐
또한 Spring에 대한 import도 불가능해지고 순수한 코드만 남음
 

토스페이먼츠가 생각하는 지속 성장 가능한 코드란?

import문을 포함한 코드 한 줄 한 줄을 소중히 여기고 관리해야 한다!
모든 코드는 각자 신호를 보내고 있고, 우리는 그 속에서 적절한 trade-off를 하면서, 다음 수준의 설계를 고민해야 한다.
지속 가능한 소프트웨어는 지속 가능한 코드 기반에서 나온다.
토스페이먼츠가 생각하는 소프트웨어 개발의 2가지 핵심 키워드 : 통제와 제어

통제

코드에 지속적인 관심을 두며 소프트웨어를 ‘통제’해야 한다.
올바른 응집으로 코드를 관리하고, Layer간의 규칙을 지키며, 적절한 Module화로 기술을 격리하여 다양한 변화에 대응할 수 있도록 코드를 통제한다.

제어

이 통제를 기반으로 하여 소프트웨어를 자유자재로 제어할 수 있어야 한다.
토스페이먼츠는 수명이 매우 긴 소프트웨어이므로 코드 또한 지속적인 관리와 성장이 필요하다.

토스페이먼츠는 통제와 제어를 기반으로 지속 성장하는 소프트웨어를 추구한다.

통제를 기반으로 코드의 제어권을 얻어 지속 성장 가능한 소프트웨어를 효과적이고 안정적으로 만들어내어, 단순히 사업을 유지하는 것이 아니라 산업을 이끌어가는 시스템을 구축하려 한다.
페이테크 기업으로서 비즈니스와 기술의 변화에도 유연하게 대응하고 진화할 수 있는 소프트웨어를 만들어 결제산업을 계속 혁신시킬 것이다!