방구석에 놔둔 개발 노트

1년차 웹 / 앱 프론트엔드 엔지니어의 좌충우돌 얼렁뚱땅 앞뒤짱구 생존기

2023-06-18: RTK와 Zustand를 통해 보는 상태 관리 라이브러리에서의 미들웨어의 역할

부트캠프에서 프로잭트를 진행했을 때, 상태 관리툴로 Redux Toolkit을 사용했다. 그러나 그 당시에는 미들웨어의 역할이 무엇이고, 왜 쓰는지. 그리고, 그냥 fetch나 axios 통해 연결하고 쓰면 되는게 아닌지.

프로젝트를 진행하는 과정 내내 그런 생각이 들었고, 이후 그 프로젝트를 마치고 나서는 미들웨어를 건드릴 일이 없었기에 그대로 기억 속에서 잊혀져가고 있었다.

그러던 중에 다시 회사에 입사하고 미들웨어의 개념을 짚고 갈 필요성을 느끼게 되었는데, 때마침 그때의 궁금증을 좀 풀고 싶기도 해서 오랜만에 이렇게 블로그로 상태 관리에서의 미들웨어 역할이 무엇인지, 그리고 어떻게 쓰는지를 RTK와 Zustand를 통해 참고해보고자 한다.

🤔 그래서 미들웨어가 뭔데?

그래서 들어가기 전에, 미들웨어라는 존재가 무엇인지부터 우선 짚고 가보자.

구글링에서 미들웨어를 검색하면 여러 글이 나오는데, 개인적으로 벨로그에 쉽게 이해할 수 있도록 쓰신 분이 있으셔서 그 분의 글을 발췌해왔다.

미들웨어는 양 쪽을 연결하여 데이터를 주고 받을 수 있도록 중간에서 매개 역할을 하는 소프트웨어, 네트워크를 통해서 연결된 여러 개의 컴퓨터에 있는 많은 프로세스들에게 어떤 서비스를 사용할 수 있도록 연결해 주는 소프트웨어를 말한다.

3/계층 클라이언트/서버 구조에서 미들웨어가 존재한다. 웹 브라우저에서 데이터베이스로부터 데이터를 저장하거나 읽어올 수 있게 중간에 미들웨어가 존재하게 된다.

여기서 미들웨어의 역할로 가리키고자 하는 것은 두 가지이다.

양 쪽을 연결’, 그리고 ‘중간에서 매개 역할’을 한다는 것인데 이 부분인 어떤 시점에서의 미들웨어냐에 관계없이 다 동일한 것을 가리킨다고 생각한다.

다만 그냥 검색창에 이렇게 미들웨어를 검색하면 내가 알고 싶어하는 상태 관리 라이브러리 등에 있는 미들웨어에 대한 설명이라기 보단, 미들웨어 역할을 하는 소프트웨어를 설명하고 있는 글들을 더 많이 발견할 수 있을 것이다.

아래의 AWS에서 소개해주는 내용처럼 말이다.

미들웨어는 서로 다른 애플리케이션이 서로 통신하는 데 사용되는 소프트웨어입니다. 미들웨어는 더욱 빠르게 혁신할 수 있도록 애플리케이션을 지능적이고 효율적으로 연결하는 기능을 제공합니다. 미들웨어는 단일 시스템에 원활하게 통합할 수 있도록 다양한 기술, 도구, 데이터베이스 간에 다리 역할을 합니다. 그런 다음 이 단일 시스템은 사용자에게 통합된 서비스를 제공합니다. 예를 들어 Windows 프런트엔드 애플리케이션은 Linux 백엔드 서버에서 데이터를 송수신하지만, 애플리케이션 사용자는 그 차이를 인식하지 못합니다.

이렇게 인프라적인 부분에서의 미들웨어 설명을 해주시는 게 검색에서는 대다수이다.

그러면 소프트웨어, 인프라적인 부분이 아닌 우리가 쓰고 싶어하는 상태 관리 라이브러리에서의 미들웨어는 대체 뭘까?

❓ 소프트웨어가 아닌 상태 관리 라이브러리에서의 미들웨어?

상태 관리 라이브러리에서의 미들웨어의 정의란 과연 무엇일까?

이에 대해서 트위터에 궁금하다는 트윗을 올렸더니, 매번 많은 것을 가르쳐주시던 탐정토끼 님께서 여러가지 말씀을 해주셨다.

그 중에서 보내주신 몇 가지 링크 중에 Redux 내의 미들웨어에 대한 설명이 조금 더 친절하게 설명됐다고 생각해 내용을 발췌해왔다.

Redux는 미들웨어라는 특수한 종류의 애드온을 사용하여 dispatch 기능을 사용자 정의할 수 있도록 합니다.

Express나 Koa와 같은 라이브러리를 사용해 본 적이 있다면 미들웨어를 추가하여 동작을 사용자 정의하는 개념에 이미 익숙할 것입니다. 이러한 프레임워크에서 미들웨어는 요청을 받는 프레임워크와 응답을 생성하는 프레임워크 사이에 넣을 수 있는 코드입니다.

예를 들어 Express 또는 Koa 미들웨어는 CORS 헤더, 로깅, 압축 등을 추가할 수 있습니다. 미들웨어의 가장 큰 특징은 체인에서 컴포저블이 가능하다는 점입니다. 단일 프로젝트에서 여러 개의 독립적인 타사 미들웨어를 사용할 수 있습니다.

Redux 미들웨어는 Express나 Koa 미들웨어와는 다른 문제를 해결하지만 개념적으로는 비슷한 방식으로 문제를 해결합니다. Redux 미들웨어는 액션을 dispatch하는 순간부터 reducer에 도달하는 순간까지 서드파티 확장 지점을 제공합니다.

사람들은 로깅, 크래시 보고, 비동기 API 와의 통신, 라우팅 등에 Redux 미들웨어를 사용합니다.

이 내용을 보면 상태 관리 라이브러리에서의 미들웨어란 다음과 같이 정의할 수 있을 것 같다.

  • action에서 reducer까지 도달하는 과정에서, dispatch 후 바로 reducer로 보내는 것이 아닌 중간에서 처리해야 할 코드들을 적용시켜주는 역할

위에서 소프트웨어의 미들웨어에 설명처럼, action에서 reducer로 도달하는 과정 속에actionreducer를 ‘연결해주는 역할’이면서, 또 그 둘의 ‘중간에서의 매개 역할’을 하는 것.

미들웨어 자체의 정의는 소프트웨어의 미들웨어 정의와 다르진 않은 것 같다.

❗ 미들웨어를 쓰는 이유, 미들웨어의 장점

그러면 무엇이 미들웨어를 처음 접했을 때 어렵게 생각하게 만든 걸까?

다시금 짚고가자니 역시 ‘미들웨어를 쓰는 이유’를 찾지 못한 점이었던 것 같다.

그럼 미들웨어를 쓰는 이유는 무엇일까? 라는 점에서 찾아보니, 이 역시 Redux 문서에서 몇 가지 사례를 정리하여 미들웨어의 사용 이유에 대해 안내해주고 있다.

크게 정리하자면 이미 위에서 언급됐지만 다음과 같다.

  • 로깅과 크래시 보고 (디버깅)
  • 비동기 API와의 통신
  • 몽키 패칭 (Monkey Patching)
    • 이게 언급되어서 궁금해서 찾아봤는데, 설명하기 쉽게 말하자면 ‘원래 소스코드를 변경하지 않고 실행 시 코드 기본 동작을 추가, 변경 또는 억제하는 기술’이라고 보면 된다.
    • 약간 axios의 intercepter 같은 느낌?
  • 라우팅 활용 등

미들웨어를 쓰는 이유이자 장점은 이런 부분으로 묶을 수 있을 것 같다.

이게 Redux Toolkit에만 해당하는 포인트일 수 있지만, 다른 상태 관리에서도 어느 정도 비슷하게 적용되는 사항이라고 생각한다.

👀 미들웨어 엿보기 1) Redux Toolkit

Redix Toolkit의 미들웨어라고 하면, 개인적으로 많이 활용했던 부분이 thunk였기 때문에, thunk를 사용했던 프로젝트 당시의 코드를 가져와봤다.

//!get 팔로우/팔로잉리스트 불러오기
export const getMyPageFollowFetch = createAsyncThunk(
  "follow/getMyPageFollowFetch",
  async (payload, thunkAPI) => {
    try {
            // 1) 비동기 API와의 통신
      const response = await instance.get(`/follows/${payload.userId}`);
            // 2) 받아온 데이터의 일부만을 보내주기 위해 몽키 페칭
      return thunkAPI.fulfillWithValue(response.data);
    } catch (error) {
      Sentry.captureException(error.response.data);
            // 2) 실패 시, 실패 케이스의 데이터를 보내주기 위해 몽키 페칭
      return thunkAPI.rejectWithValue(error.response.data);
    }
  }
);

미들웨어인 thunk를 이용하려면 createAsyncThunk를 통해 설정할 수 있다.

위의 코드에서는 두 가지의 기능을 활용하고 있는데, 첫 번째는 비동기 API와의 통신async, await을 통해 활용하는 것.

두 번째는 받아온 응답을 thunkAPI를 통해 reducer에 어떤 데이터를 보낼지 가공해서 적용하는, 즉 몽키 페칭을 하는 것.

이런 식을 통해서 thunk미들웨어로서 어떤 방식으로 사용되고 처리하는지를 볼 수있다.

👀 미들웨어 엿보기 2) Zustand

사실 이 글을 쓰게 된 목적은 이 라이브러리다. Zustand는 Redux의 방식에서 좀 더 쉽고 깔끔하게 관리되도록 형성된 상태 관리 라이브러리이다.

Zustand에서 기본적으로 제공해주는 미들웨어Persistimmer가 있으며, 서드 파티의 미들웨어 라이브러리를 결합하여 사용하는 것도 가능하다.

여기서는 Persist를 어떻게 사용하는지 예시를 살짝 살펴보고자 한다.

import { create } from 'zustand'
import { persist, createJSONStorage } from 'zustand/middleware'

export const useBearStore = create(
    // persist를 선언함으로서 아래의 내용은 브라우저의 스토리지에 값을 저장하게 된다.
  persist(
    (set, get) => ({
      bears: 0,
      addABear: () => set({ bears: get().bears + 1 }),
    }),
    {
            // 특정 스토리지에 저장될 키 이름. 중복되어서는 안된다.
      name: 'food-storage', 
            // 세션 스토리지에 값을 저장하도록 한다.
            // 아무것도 없을 경우, 기본적으로 로컬 스토리지에 저장된다.
      storage: createJSONStorage(() => sessionStorage),
    }
  )
)

Zustand의 미들웨어인 Persist는 새로고침 등을 통해 페이지가 새로 렌더링될 경우 값이 사라지는 것을 방지하기 위해서 세션, 로컬 등의 스토리지에 접근해 값을 저장, 변경할 수 있다.

이 내용 또한 클라이언트의 동작브라우저의 스토리지에 저장하도록 연결해주는 점에서 중간 매개체 역할을 하고 있다고 볼 수 있는데, 이런 식으로 미들웨어의 역할은 꼭 통신에만 국한되는 게 아니며, 단어 그대로 ‘중간에서의 역할’을 담당하고 있다.

🏳️ 맺으며

사실 이번 글은 Zustand를 공부하기 위한 것도 있지만, 그에 앞서 대체 미들웨어를 왜 쓰고 뭐가 쓰면 좋은지를 알고 싶어서 시작한 것에 기인해있다.

처음에는 위의 이미지와 같이 단지 중간에서 뭔가 하겠지..? 라는 생각으로만 여겼는데 탐정토끼 님과 다른 분들의 말씀과 여러 자료를 읽고 나서 미들웨어에서 하는 역할이 생각보다 많았으며 이를 잘 활용하면 데이터 바인딩이나 최적화에도 좋은 영향을 줄 수 있을 거라고 생각했다.

단지 내가 궁금하고 몰라서 정리했던 글이지만, 미들웨어의 쓰임에 대해서 의문이 드는 사람들에게 조금이라도 도움이 될 수 있는 내용이기를 바란다.

🔖 참고 자료

[인프라 뿌시기 #1] 미들웨어, 개념을 알아보자

미들웨어란 무엇인가요? - 미들웨어 소프트웨어 설명 - AWS

Redux Fundamentals, Part 4: Store | Redux

Middleware | Redux

리덕스(redux)에서 미들웨어(middleware)를 사용하는 이유

Middleware - Hono

Zustand Documentation

Zustand Documentation

[react] react zustand persist store 구현하기