Skip to content

Layered frontend application

sunub edited this page Nov 7, 2024 · 3 revisions

Layered frontend application

2024-11-07_20-17-28

2024-11-07_20-18-00

저희는 Presentation Domain Data Layering 의 이론을 기반으로 위의 그림과 같이 한 개의 컴포넌트를 여러개의 계층으로 분리하여 모듈로 관리하는 아키텍쳐를 적용해보고자 했습니다.

Presentational Component

UI와 컴포넌트와 상태를 관리하고 Hook을 통해서 처리된 이벤트에 대한 결과를 표시하고 스타일링 역할을 수행한다.

React Hook

Component 내부에서 외부에서 데이터를 불러오기 위해 Fetcher 를 사용하여 데이터를 불러오거나 상태의 변경을 통해서 UI에 변화를 주어야 하는 작업들을 소유한 계층에 해당한다.

Infrastructure

// Infrastructure Layer의 주요 구성요소들
src/infrastructure/
  ├── http/              // HTTP 클라이언트 관련
  │   ├── fetcher.ts     // API 요청 처리
  │   └── interceptors/  // 요청/응답 인터셉터
  ├── storage/           // 저장소 관련
  │   ├── localStorage.ts
  │   └── sessionStorage.ts
  ├── logger/            // 로깅 시스템
  └── security/          // 보안 관련

Infrastructure 는 외부 시스템과의 통신을 담당하는 모듈들이 위치하는 계층입니다. 비즈니스 로직을 기술적 구현으로부터 분리하여 시스템의 유지보수성과 테스트를 용이하게 하기 위한 것을 목표로 하고 있습니다.

Domain Object

export class CountryPayment {
    private readonly _currencySign: string;
    private readonly algorithm: RoundUpStrategy;

    public constructor(currencySign: string, roundUpAlgorithm: RoundUpStrategy) {
      this._currencySign = currencySign;
      this.algorithm = roundUpAlgorithm;
    }

    get currencySign(): string {
      return this._currencySign;
    }

    getRoundUpAmount(amount: number): number {
      return this.algorithm(amount);
    }

    getTip(amount: number): number {
      return calculateTipFor(this.getRoundUpAmount.bind(this))(amount);
    }
  }

Domain Object 위와 같이 정 기술이나 프레임워크에 의존하지 않으며, 오직 비즈니스 로직과 규칙만을 포함합니다. 이러한 특징 때문에 도메인 로직의 순수성을 유지할 수 있으며 Domain Object 를 중심으로 기능을 확장하는 방식을 이용하기 기능 추가나 변경에 용이하다는 장점이 있습니다.

The shotgun surgery problem

export const Payment = ({ amount }: { amount: number }) => {
    const { paymentMethods } = usePaymentMethods();
  
    const { total, tip, agreeToDonate, updateAgreeToDonate } = useRoundUp(amount);
  
    return (
      <div>
        <h3>Payment</h3>
        <PaymentMethods options={paymentMethods} />
        <DonationCheckbox
          onChange={updateAgreeToDonate}
          checked={agreeToDonate}
          content={formatCheckboxLabel(agreeToDonate, tip)}
        />
        <button>${total}</button>
      </div>
    );
  };

위의 userRoundup 메서드는 달러 환율을 기본적인 메서드로 작동 되고 있었습니다. 하지만 여기서 Payment 컴포넌트에 대한 요구 사항이 추가 되었다고 가정해보겠습니다.

<Payment amount={3312} countryCode="JP" />;

기존에는 달러 환율 만 계산하고 있었는데 달러 환율만이 아니라 일본에 대한 환율 이외에도 여러 나라의 환율을 적용하여 Payment를 계산 하게 끔 기능을 추가해 달라는 요구가 추가 되었습니다.

const useRoundUp = (amount: number, countryCode: string) => {
  //...

  const { total, tip } = useMemo(
    () => ({
      total: agreeToDonate
        ? countryCode === "JP"
          ? Math.floor(amount / 100 + 1) * 100
          : Math.floor(amount + 1)
        : amount,
      //...
    }),
    [amount, agreeToDonate, countryCode]
  );
  //...
};

그래서 기존의 환율을 계산하고 UI에 렌더하는 것을 담당하는 로직인 useRoundUp 내부에서 환율을 담당하고 있는 부분을 수정해야 하는 문제를 맞닥뜨렸습니다. 여기서 끝난다면 비교적 간단한 문제라고 할 수 있지만 문제는 다른 부분들 또한 수정을 해주어야 하는 문제가 남아 있습니다.

// 나라의 환율에 맞춰 문구를 수정하는 로직
const formatCheckboxLabel = (
  agreeToDonate: boolean,
  tip: number,
  countryCode: string
) => {
  const currencySign = countryCode === "JP" ? "¥" : "$";

  return agreeToDonate
    ? "Thanks for your donation."
    : `I would like to donate ${currencySign}${tip} to charity.`;
};

// 버튼 UI
<button>
  {countryCode === "JP" ? "¥" : "$"}
  {total}
</button>;

새로운 기능이 추가되니 단순히 하나의 기능을 손 보는 것이 아니라 여러 부분을 수정해야 하는 문제로 문제가 확산되었습니다.

2024-11-07_22-28-19

위의 그림은 여러 컴포넌트간의 관계 샷건처럼 흩어져서 관계를 갖고 있어서 새로운 기능을 추가할 경우 문제가 확산되는 것을 표현한 그림입니다. 이럴 경우 관련된 코드들을 모두 다 읽어보고 수정해야 하는 문제가 있어서 이 문제를 계층 아키텍쳐를 활용하여서 한 개의 모듈에 의존성을 부여해서 여러 곳에서 수정해서 문제를 해결하던 방식을 한 개의 모듈을 수정해서 개선한다는게 재가 보여드렸던 글의 핵심인거 같아요

저희는 이와 같이 새 기능이 추가되거나 버그를 수정해야 하는 경우 여러 개의 모듈을 다시 읽어보고 해당 기능이 있는 지 파악하고 수정을 진행해줘야 하는 shotgun surgery 과정에서의 문제가 발생합니다.

그래서 위에서 제시한 계층 아키텍쳐를 활용하여 한 개의 모듈에 의존성을 부여하여 특정 기능이 추가되거나 버그가 발생하였을 경우 여러 코드를 고쳐야 하는 문제를 한 개의 모듈을 수정하여 개선할 수 있게끔 구조를 개선하여 shotgun surgery 를 해결하는 과정에서 생기는 문제를 해결하는데 유용하지 않을까? 라는 생각을 했습니다.

또한 위의 아키텍쳐를 적용함으로써 각각의 개별 모듈에서 복잡성이 감소함으로써, 전체 시스템에 영향을 주지 않고 새로운 기능을 추가하거나 변경하기가 더 쉬워질 수 있지 않을까? 생각했습니다.

또한 팀으로써 작업을 수행하면서 어떻게 하면 조금 더 효율적으로 같이 작업을 수행할 수 있을 지 고민 했을 경우 계층 단위로 세분화된 모듈 코드가 있을 경우 다른 팀원 분이 작성한 코드를 재 사용하는 방식으로도 활용할 수 있을 것이라 생각하여 어려운 도전이겠지만 최대한 위의 아키텍처를 고려하여 코드를 작성해보고자 합니다.

🏠 𝐇𝐨𝐦𝐞

🙌 𝐈𝐧𝐭𝐫𝐨𝐝𝐮𝐜𝐭𝐢𝐨𝐧

📄 𝐏𝐫𝐨𝐣𝐞𝐜𝐭 𝐃𝐨𝐜𝐮𝐦𝐞𝐧𝐭𝐚𝐭𝐢𝐨𝐧

🤝 𝐓𝐞𝐚𝐦 𝐑𝐮𝐥𝐞𝐬

🎁 𝐓𝐞𝐜𝐡𝐧𝐢𝐜𝐚𝐥 𝐒𝐡𝐚𝐫𝐢𝐧𝐠

프로젝트 초기 세팅

메인 페이지

로그인/회원가입/게스트 로그인

베팅페이지

BE

FE

🔫 𝐓𝐫𝐨𝐮𝐛𝐥𝐞𝐬𝐡𝐨𝐨𝐭𝐢𝐧𝐠

📎 𝐑𝐞𝐟𝐞𝐫𝐞𝐧𝐜𝐞 𝐋𝐢𝐧𝐤𝐬

Clone this wiki locally