Factory 패턴으로 Zustand 스토어 생성
in Design_patterns / Zustand on Factory pattern, Zustand, Typescript, Auth, State management
이 글에서는 Zustand 스토어를 Factory 패턴을 통해 생성하는 방법을 배웁니다.
요약
- Factory 패턴을 사용하여 인증 로직을 여러 프로젝트에서 공유하는 방법을 배웁니다.
- Zustand 스토어의 구조와 사용법을 이해합니다.
- 공통 로직과 개별 로직을 분리하여 유지보수성을 높이는 방법에 대해 논의합니다.
배경/문제
3개의 프론트엔드 프로젝트(lms.web, lms-admin.web, lms-integration-admin.web)가 동일한 인증 로직을 사용하지만, 각 프로젝트마다 초기화해야 할 스토어와 로그인 후 처리의 차이점이 있습니다. 또한, 스키마 검증 실패 시의 처리도 다릅니다. 이러한 상황에서 Factory 패턴을 활용하여 공통 로직과 각 프로젝트에 특화된 로직을 분리할 수 있습니다.
접근/해결 전략
Factory 패턴을 통해 공통된 인증 로직을 정의하고, 각 프로젝트별로 필요한 설정을 주입하여 유연하게 사용할 수 있는 구조를 만듭니다. 이를 통해 코드의 재사용성을 높이고, 유지보수를 용이하게 합니다.
구현 포인트
1. 타입 정의
AuthStoreConfig 및 AuthState 인터페이스를 정의합니다. 아래 코드는 shared/auth/types.ts 파일에서 정의된 내용입니다.
// shared/auth/types.ts
export interface AuthStoreConfig {
storesToReset?: Array<{ getState: () => { reset?: () => void } }>
onLogin?: () => void
onLogoutFallback?: () => void
onSchemaValidationFail?: () => void
}
export interface AuthState {
user: User | null
isAuthenticated: boolean
isHydrated: boolean
login: (userData: User) => void
logout: () => Promise<void>
validateToken: () => Promise<void>
setHydrated: () => void
}
2. Factory 함수
createAuthStore 함수를 정의하여 인증 스토어를 생성합니다. 이 함수는 프로젝트별로 전달된 설정을 바탕으로 동작합니다. 아래 코드는 shared/auth/authStoreFactory.ts 파일에서 정의된 내용입니다.
// shared/auth/authStoreFactory.ts
export function createAuthStore(config: AuthStoreConfig = {}) {
const {
storesToReset = [],
onLogin,
onLogoutFallback,
onSchemaValidationFail,
} = config;
return create<AuthState>()(
persist(
(set, get) => ({
user: null,
isAuthenticated: false,
isHydrated: false,
login: (userData) => {
const currentUser = get().user;
// 다른 사용자로 로그인 시 스토어 초기화
if (currentUser && currentUser.id !== userData.id) {
localStorage.clear();
sessionStorage.clear();
storesToReset.forEach((store) =>
store.getState().reset?.()
);
}
set({ user: userData, isAuthenticated: true });
onLogin?.();
},
logout: async () => {
// 공통 로그아웃 로직
localStorage.setItem('isExplicitLogout', 'true');
// ...
},
// ...
}),
{
name: 'auth-storage',
onRehydrateStorage: () => (state) => {
if (state) {
state.isHydrated = true;
}
},
}
)
);
}
3. 프로젝트별 사용
각 프로젝트에서 인증 스토어를 사용하는 방법입니다. 아래 코드는 각 프로젝트별로 스토어를 생성하는 예시입니다.
// lms.web/src/stores/authStore.ts
import { createAuthStore } from '@/shared/auth';
import { useCartStore } from './cartStore';
import { useTermStore } from './termStore';
import { useTenantStore } from './tenantStore';
export const useAuthStore = createAuthStore({
storesToReset: [useCartStore, useTermStore, useTenantStore],
onLogin: () => {
console.log('LMS Web: 로그인 완료');
},
onSchemaValidationFail: () => {
useCartStore.getState().reset();
},
});
// lms-admin.web/src/stores/authStore.ts
import { createAuthStore } from '@/shared/auth';
import { useTenantStore } from './tenantStore';
export const useAuthStore = createAuthStore({
storesToReset: [useTenantStore],
});
// lms-integration-admin.web/src/stores/authStore.ts
import { createAuthStore } from '@/shared/auth';
export const useAuthStore = createAuthStore({
// 초기화할 스토어 없음
});
주의사항/트레이드오프
- 성능: Factory 패턴을 사용하면 공통 로직이 한 곳에서 정의되어 성능 저하 없이 관리할 수 있습니다.
- 보안: 사용자 인증 로직을 재사용하는 만큼, 공통 로직의 보안성을 철저히 검토해야 합니다.
- 유지보수: 공통 로직에서 발견된 버그는 모든 프로젝트에 반영되므로, 수정 후 모든 프로젝트에서 테스트를 수행해야 합니다.
마무리
- Factory 패턴을 사용하여 인증 로직을 구현하는 방법을 배웠습니다.
- 각 프로젝트에서 필요에 따라 스토어 초기화 및 사용자 처리 로직을 간편하게 커스터마이징할 수 있습니다.
- 체크리스트:
- 공통 로직 구현 여부 확인
- 각 프로젝트별 특정 로직 주입
- 테스트 수행 및 배포 준비
관련 링크
- [[Zustand Persist와 Hydration]]
- [[의존성 주입 패턴]]
- [[SOLID 원칙 - 개방폐쇄 원칙]]