반응형 아키텍처가 필요한 문제 상황
프론트엔드 애플리케이션을 개발할 때, 상태 관리와 데이터 흐름이 복잡해지면 유지보수성이 떨어지고, 성능 저하로 이어질 수 있다. 특히 다음과 같은 문제가 발생할 수 있다.
문제 상황 예시
이벤트 기반 UI에서 비효율적인 상태 관리
React 애플리케이션에서 사용자의 입력에 따라 여러 컴포넌트가 영향을 받는 경우, 이벤트 핸들러를 여기저기 추가하다 보면 다음과 같은 문제가 생긴다.
•
상태가 여러 곳에서 관리됨: 특정 컴포넌트에서 상태를 변경하면, 그 영향을 받는 다른 컴포넌트도 일일이 업데이트해야 한다.
•
이벤트 흐름이 불명확함: 한 곳에서 발생한 상태 변화가 여러 컴포넌트에 영향을 미치므로, 디버깅이 어렵다.
•
성능 저하: 불필요한 리렌더링이 발생할 수 있다.
예를 들어, 사용자가 입력 폼에서 검색어를 입력할 때마다 API 호출이 발생하고, 이 결과가 여러 컴포넌트에서 사용되는 상황을 가정해 보자.
function SearchComponent() {
const [query, setQuery] = useState('');
const [results, setResults] = useState([]);
useEffect(() => {
fetch(`/api/search?q=${query}`)
.then(response => response.json())
.then(data => setResults(data));
}, [query]);
return (
<input
type="text"
value={query}
onChange={(e) => setQuery(e.target.value)}
/>
);
}
TypeScript
복사
위 코드에서는 검색어가 변경될 때마다 API 호출이 발생한다. 하지만 검색 결과가 여러 다른 컴포넌트에서도 필요하다면 어떻게 될까?
각 컴포넌트가 query 상태를 관리하고 API를 호출하게 되면, 불필요한 네트워크 요청과 리렌더링이 발생한다.
반응형 아키텍처 소개
반응형 아키텍처(reactive architecture)는 비동기 이벤트 스트림을 중심으로 상태를 관리하는 방식이다. 이를 통해 여러 컴포넌트가 독립적으로 상태 변화를 구독(subscribe)하고, 필요할 때만 업데이트되도록 설계한다.
반응형 아키텍처의 핵심 개념은 다음과 같다.
•
이벤트 기반(event-driven): UI의 변화가 이벤트 형태로 전달됨.
•
비동기 스트림(asynchronous streams): 상태 변경이 스트림을 통해 전달됨.
•
구독 기반(subscription-based): 필요한 컴포넌트만 상태 변화를 구독하고 업데이트함.
반응형 아키텍처로 리팩터링
위의 검색 예제를 rxjs를 통해 반응형 아키텍처로 리팩터링해 보자.
개선된 코드
import { BehaviorSubject } from 'rxjs';
import { debounceTime, switchMap } from 'rxjs/operators';
const searchQuery$ = new BehaviorSubject('');
const searchResults$ = searchQuery$.pipe(
debounceTime(300),
switchMap(query => fetch(`/api/search?q=${query}`).then(res => res.json()))
);
function SearchComponent() {
const [query, setQuery] = useState('');
const [results, setResults] = useState([]);
useEffect(() => {
const subscription = searchResults$.subscribe(setResults);
return () => subscription.unsubscribe();
}, []);
return (
<input
type="text"
value={query}
onChange={(e) => {
setQuery(e.target.value);
searchQuery$.next(e.target.value);
}}
/>
);
}
JavaScript
복사
리팩터링의 장점
•
불필요한 API 호출 감소: debounceTime(300)을 사용해 연속적인 입력을 줄여 API 호출을 최소화한다.
•
상태를 중앙에서 관리: BehaviorSubject를 사용해 검색 상태를 한 곳에서 관리할 수 있다.
•
다른 컴포넌트에서 쉽게 구독 가능: searchResults$를 다른 컴포넌트에서도 구독할 수 있어, 코드 중복이 줄어든다.
어니언 아키텍처 소개
어니언 아키텍처(Onion Architecture)는 도메인 중심의 계층 구조를 갖는 아키텍처 패턴으로, 애플리케이션을 여러 계층으로 분리하여 의존성을 줄이고 확장성을 높이는 방식이다. 핵심 원칙은 다음과 같다.
•
비즈니스 로직을 중심으로 계층을 구축: UI, 데이터 접근, 인프라 등의 외부 요소는 도메인 계층을 감싼다.
•
의존성 역전 원칙(DIP, Dependency Inversion Principle) 준수: 내부 계층이 외부 계층에 의존하지 않도록 한다.
•
테스트 용이성 향상: 도메인 로직이 독립적이므로 단위 테스트가 쉬워진다.
어니언 아키텍처 계층
1.
도메인 계층 (Domain Layer): 비즈니스 로직과 핵심 모델이 위치.
2.
애플리케이션 계층 (Application Layer): 도메인 계층을 호출하는 서비스 로직이 포함.
3.
인터페이스 계층 (Interface Layer): UI, API 핸들러 등이 포함.
4.
인프라스트럭처 계층 (Infrastructure Layer): 데이터베이스, 외부 API 호출 등이 포함.
결론
반응형 아키텍처는 이벤트 기반의 상태 관리로 비동기 데이터를 효율적으로 처리하는 데 강력한 도구이다. 특히 RxJS 같은 라이브러리를 활용하면 UI의 복잡한 상태 흐름을 효과적으로 관리할 수 있다. 어니언 아키텍처는 애플리케이션을 계층화하여 유지보수성과 확장성을 높이는 데 유용하다. 프론트엔드 개발자는 이러한 아키텍처를 적절히 조합하여 보다 견고한 애플리케이션을 개발할 수 있다.