# 의존성을 배제한 개발

## 개발 의존성&#x20;

> 의존성이 커질수록 제품은 더 복잡해지고 문제가 발생하기 쉬워진다.
>
> 제품 개발의 의존성을 줄이는 아키텍처 설계에 대해 신경을 쓰고 도입해보자.

### 의존성으로 인한 문제

#### 직군 간의 의존성

* BE <--> FE,  PM <--> FE, 상호간 커뮤니케이션 문제로 인한 개발 시간 증가
* 기획 정의서가 준비될 때 까지 기다린다거나 BE API spec을 기다리는 등의 대기시간 낭비

#### 모듈 간의 의존성

* 코드 레벨에서 모듈간의 의존성이 커질수록 복잡도가 커지게 된다.
* 코드 리팩토링 또는 기능이 수정 & 추가되어야 하는 상황에서 개발 난이도가 증가하게 된다.&#x20;

#### 일정 상의 의존성

* 일정 지연으로 인한 개발 시간 준수가 어렵게 된다.&#x20;
* 다른 테스크들을 병행하면서 개발할 때 컨텍스트 스위칭 비용이 발생하여 집중도  하락 및 개발 시간 증가&#x20;

## BE 의존성 줄이기

### Mocking이 필요한 상황

* 개발 과정에서 준비되지 않은 서버 API 응답 처리를 위해 사용
  * API Mocking Layer 구현으로 Mocking 여부에 대한 차이를 파악할 필요없도록 구성
* API Mocking을 통해 정확한 API Interaction 모델링과 성공/실패 케이스를 모의하여 디버깅 필요
  * Client 이슈는 대부분 Data 문제로 잘못된 API 호출, 오류처리 누락, 예상치 못한 response 반환같은 상황 등

### MSW (Mock Service Worker)

* Service Worker API를 이용해 HTTP 요청 가로챔
* API 요청 발생 시 가로채서 준비한 Mock Data로 응답
* 별도의 Mock Server 없이 Mocking 가능

{% hint style="info" %}
Service Worker

* 웹 어플리케이션의 백그라운드 스레드에서 실행됨
* UI Block 없이 연산 가능
* Network 요청 가로채서 응답 모킹 가능
  {% endhint %}

#### 동작 원리

1. 브라우저 request 발송
2. worker는 해당 요청을 받고 클론한다음 MSW로 전달
3. 전달받은 요청과 일치하는 목 데이터가 존재하는지 조회
4. 존재한다면 ServiceWorker에 Mock Response 전달
5. ServiceWorker는 전달받은 응답을 브라우저에게 전달

### MSW 설정

#### Install

```bash
npm install msw -D
// crate mockServiceWorker file in public folder
npx msw init public/ --save
```

#### Request Handler 설정

* response 값 설정 (code, data)
* URL/RegExp/특정 조건의 request 캡처
* HTTP method 별 조건 적용

```typescript
// src/mocks/products/handler.ts
import { rest } from 'msw'
import productsData from './products.json'

import type { RestRequest, ResponseComposition, DefaultBodyType, RestContext } from 'msw'
import { ProductResponse } from '@/api/products'

const getProducts = (
    req: RestRequest,
    res: ResponseComposition<ProductResponse>,
    ctx: RestContext
) => {
    return res(ctx.status(200), ctx.json({ products: productsData }))
}

const getProductsHandler = [rest.get(`${process.env.NEXT_PUBLIC_URL}/api/products`, getProducts)];

export default getProductsHandler
```

#### Worker Setup

* 구현한 handler들을 worker에 삽입

```typescript
// src/mocks/browser.ts
import { setupWorker } from 'msw'
import productHandler from './products/handler'

// This configures a Service Worker with the given request handlers.
export const worker = setupWorker(...productHandler)
```

* worker 실행 (production 빌드는 수행 안되도록 env 조건 설정)

```typescript
import { worker } from '@/mocks/browser';

if (process.env.NODE_ENV === 'development') {
  worker.start();
}

export default function App() {//...}
```

#### Next.js 에서 MSW 이슈

1. Next.js는 서버사이드에서도 API 호출할 수 있기 때문에 브라우저 모킹뿐만아니라 서버모킹(node)가 필요
2. MSW init 이전 API 호출 발생될 수 있음
   1. MSW를 init 후 Component load 필요 -> HOC 구성
   2. initMock 함수를 dynamic import 후 실행, loaded 상태 설정을 통해 children 로드 진행
   3. 만약 MSW를 사용하지 않는 경우 최초 상태값 설정을 통해 바로 Component를 로드하도록 구성

```typescript
import { setupServer } from 'msw/node'
import productHandler from './products/handler'

export const server = setupServer(...productHandler)
```

```typescript
 // src/mocks/index.ts
export async function initMocks() {
    console.log('init mocks')
    if (typeof window === 'undefined') {
        const { server } = await import('./server')
        server.listen()
    } else {
        const { worker } = await import('./browser')
        worker.start()
    }
}
```

```typescript
 // src/mocks/MSWProvider
 import { PropsWithChildren, useState, useEffect } from 'react';

const isMockingEnabled = Boolean(process.env.NEXT_PUBLIC_API_MOCKING) 
    && process.env.NODE_ENV !== 'production';

export const MSWProvider = ({ children }: PropsWithChildren) => {
    const [isLoaaded, setIsLoaded] = useState(!isMockingEnabled)

    useEffect(() => {
        const init = async () => {
            if (isMockingEnabled) {
                const fn = await import('./index').then((res) => res.initMocks)
                await fn()
                setIsLoaded(true)
            }
        }
        !isLoaaded && init()
    }, [isLoaaded])

    if (!isLoaaded) {
        return null
    }

    return (
        <>
            {children}
        </>
    )
}
```

## API Layer DI

### 의존성(Dependency)

> 의존성이란 두 모듈간의 연결,  서로 간 관계가 있는 경우 의존성이 있다고 본다.

* 한 모듈이 다른 모듈을 사용할 때
  * 다른 모듈을 생성할 때
  * 다른 모듈의 함수를 호출할 때
  * 다른 모듈을 인자로 받아 사용할 때

#### 의존성이 위험한 이유

* 한 모듈의 변경이 다른 모듈에 영향을 끼침
* 이러한 영향이 모듈간 전파되어 알 수 없는 Side Effect 발생

### 의존성 주입(DI, Dependency Injection)

* Inversion of Control&#x20;
* 모듈의 의존성이 내부가 아닌 외부로부터 주입 받는 형태
* Component의 props로 의존성 주입

### 의존성 분리 장점

* 모듈간 의존성 감소
* 재사용성 증가
* 유닛테스트 용이
* 가독성 증가

## Tree shaking

> 나무를 흔들어 죽은 나뭇잎을 떨어트리는 것처럼, 사용하지 않는 코드를 제거하는 것을 의미
>
> 즉, 사용하는 코드들만 사용해서 빌드하는 것

### Next.js Tree shaking 적용

#### v13.5 이상 버전만 사용 가능한 방법

```javascript
 // next.config.js
 module.exports = {
   experimental: {
     optimizePackageImports: ['package-name'],
   },
 }
```

* `experimental.optimizePackageImports` 에 패키지를 추가하면 자동으로 tree shaking 적용
* 많이 사용되는 대부분의 라이브러리들(ex: lodash 등)은 기본적으로 추가 되어있어 따로 적용하지 않아도됨

#### v13.1 < version < v13.5 사용 가능한 방법

```javascript
 module.exports = {
   swcMinify: true,
    experimental: {
     modularizeImports: {
       antd: {
         transform: 'antd/lib/{{member}}',
       },
       lodash: {
         transform: 'lodash/{{member}}',         
       }
     }
   },
 }
```

* `experimental.modularizeImports` 에 패키지와 transform할 형태 정의

{% hint style="info" %}
빌드시`--verborse` 옵션을 추가하여 build시 실행시간을 측정할 수 있다.

```bash
npm run build --verborse
```

{% endhint %}

### 결론

Barrel < Barrel with \[modularizeImports | optimizePackageImports] < default import

{% hint style="info" %}
**Barrel**&#x20;

* Barrel 파일은 여러 모듈이나 파일을 하나의 집합으로 내보내기 위해 사용되는 파일을 가리킨다.
* Barrel export pattern -> 여러 모듈이나 파일을 묶어서 간결하게 내보내는 디자인 패턴
  {% endhint %}
