본문 바로가기
Front-end/React.js

리액트와 계층형 설계

by img 2022. 11. 29.

계층형 설계란

계층형설계는 소프트웨어 아키텍처를 구성하는데 사용되는 패턴으로, 서로 다른 기능을 수행하는 여러 계층으로 애플리케이션을 나눕니다.

작동 원칙

계층형 설계의 원칙은 상위 계층에서는 하위계층으로만 접근할 수 있으며, 서로 마주보는 두개의 계층사이에서만 상호작용이 이루어집니다. 그리고 하위계층에서는 자체적으로 상위계층을 호출 할 수 없고, 상위계층의 호출에 응답만 하게 됩니다.

각 레이어는 그 위에 있는 레이어에 필요한 서비스를 제공하므로 가장 낮은 수준의 레이어는 시스템 전반에서 사용되는 핵심 서비스를 나타냅니다. 때문에 상위에 있는 레이어일수록 호출되는 빈도가 낮기 때문에 수정이 쉬워지고, 하위에 있는 레이어일수록 호출하고 있는 레이어가 그 위에 많기 때문에 수정이 어려워집니다.

모든 어플리케이션은 별도의 논리적 레이어로 구성될 수 있지만, 일반적으로 presentation layer, business layer, data access layer 등으로 구성이 됩니다. 계층형 설계의 목적은 애플리케이션의 모듈성, 유연성, 확장성을 높이고, 각 계층간의 의존성을 최소화 하는것입니다

Presentation Layer

리액트 관점에서, UI를 구성하는 리액트 컴포넌트를 presentation layer 에 포함할 수 있습니다. 이 계층에서는 사용자와의 인터랙션을 다루며, UI 레이아웃을 구성하고 UI 상태를 관리합니다. Presentation 계층에서는 주로 다음과 같은 역할을 하는 컴포넌트들이 포함됩니다.

  • UI 구성요소 (버튼, 폼, 라벨, 리스트 …)
  • 페이지 레이아웃 구성요소 (헤더, 푸터, 사이드바 … )
// DataService 를 주입받은 App 컴포넌트
// App.js
import React, { useState, useEffect } from "react";

const App = ({ dataService }) => {
  const [data, setData] = useState([]);

  useEffect(() => {
    const fetchData = async () => {
      const result = await dataService.getData();
      setData(result);
    };

    fetchData();
  }, [dataService]);

  return (
    <div>
      {data.map((item, index) => (
        <p key={index}>{item.name}</p>
      ))}
    </div>
  );
};

export default App;

Business Layer

business layer는 비즈니스 로직을 담당하는 계층으로, presentation layer에서 발생한 이벤트나 데이터를 가지고, 비즈니스 로직을 실행하고, 요청에 대한 결과를 반환합니다. presentation layer과 data access layer의 인터페이스 역할도 합니다.

// 비즈니스 로직 계층 dataService.js
import { fetchData } from "./api";

export class DataService {
  async getData() {
    const data = await fetchData();
    return data;
  }
}

Data Acess Layer

데이터에 접근해 데이터 관리를 담당하는 계층입니다. 보통 business layer의 요청에 따라 데이터에 접근해 요청을 처리하는 로직을 수행합니다.

// 데이터 액세스 계층 api.js
export const fetchData = async () => {
  const response = await fetch("<https://api.example.com/data>");
  const data = await response.json();
  return data;
};

DI (Dependency Injection, 의존성 주입)

DI라고도 불리는 의존성 주입은 객체지향 프로그래밍에서 사용되는 디자인 패턴 중 하나로,

  • 의존성이란, 하나의 독립적인 모듈이 다른 모듈의 기능이나 데이터를 사용할 때, 이들간의 관계를 의존성 관계라고 합니다. 즉, 하나의 모듈이 참조하고 있는 다른 모듈을 의존성이라고 합니다. 흔히 npm install 을 통해 외부 라이브러리 패키지를 설치하여 사용하는 것도 의존성의 예시 중 하나 입니다.
  • 주입이란, 의존성을 외부에서 전달하는 것을 의미합니다. 이를 통해 모듈이 의존성을 직접 생성하거나 찾지 않고, 외부에서 제공받게됩니다.

따라서 의존성 주입은 객체간의 의존성을 외부에서 주입해서 느슨한 결합을 유지하는 것입니다.

리액트에서 흔히 의존성을 주입받을 수 있는 방법은 props를 이용한 방법과 contextAPI를 이용한 방법이 있습니다.

Props를 이용한 의존성 주입

// 데이터 액세스 계층 api.js
export const fetchData = async () => {
  const response = await fetch("<https://api.example.com/data>");
  const data = await response.json();
  return data;
};
// 비즈니스 로직 계층 dataService.js
import { fetchData } from "./api";

export class DataService {
  async getData() {
    const data = await fetchData();
    return data;
  }
}
// 최상위레벨에서 DataService의 인스턴스를 생성하고 App 컴포넌트에 주입
// index.js
import { DataService } from "./dataService";

const dataService = new DataService();

ReactDOM.render(
  <React.StrictMode>
    <App dataService={dataService} />
  </React.StrictMode>,
  document.getElementById("root")
);
// DataService 를 주입받은 App 컴포넌트
// App.js
import React, { useState, useEffect } from "react";

const App = ({ dataService }) => {
  const [data, setData] = useState([]);

  useEffect(() => {
    const fetchData = async () => {
      const result = await dataService.getData();
      setData(result);
    };

    fetchData();
  }, [dataService]);

  return (
    <div>
      {data.map((item, index) => (
        <p key={index}>{item.name}</p>
      ))}
    </div>
  );
};

export default App;

→ 계층형 설계를 통해 계층을 나눈 후, 의존성 주입을 사용하여 DataSerivce를 App 컴포넌트에 주입하여 각 계층간의 결합을 줄였습니다.

ContextAPI를 이용한 의존성 주입

// 데이터 액세스 계층 api.js
export const fetchData = async () => {
  const response = await fetch("<https://api.example.com/data>");
  const data = await response.json();
  return data;
};
// 비즈니스 로직 계층 dataService.js
import { fetchData } from "./api";

export class DataService {
  async getData() {
    const data = await fetchData();
    return data;
  }
}
// 최상위레벨에서 DataService의 인스턴스를 생성하고 App 컴포넌트에 주입
// index.js
import { DataService } from "./dataService";

const dataService = new DataService();

ReactDOM.render(
  <React.StrictMode>
		<MyContext.Provider value={{dataService}}>
			<App />
		</MyContext.Provider>
	</React.StrictMode>,
  document.getElementById("root")
);
// DataService 를 주입받은 App 컴포넌트
// App.js
const App = () => {
  const [data, setData] = useState([]);
	const dataService = useContext(MyContext)

  useEffect(() => {
    const fetchData = async () => {
      const result = await dataService.getData();
      setData(result);
    };

    fetchData();
  }, [dataService]);

  return (
    <div>
      {data.map((item, index) => (
        <p key={index}>{item.name}</p>
      ))}
    </div>
  );
};

export default App;

의존성 주입의 장점은

  1. 결합도 감소: 클래스간의 결합도를 낮추어 유연한 코드 구조를 만들 수 있고, 이를 통해 기능 변경이나 확장이 용이해지게 됩니다.
  2. 모듈화 및 재사용성: 모듈과 그 의존성 간의 분리로 인해, 재사용성이 높아지고 모듈화가 쉬워집니다.
  3. 테스트용이성: 의존성을 주입하는 방식을 사용하면, 테스트시 가짜 객체(Mock) 등을 쉽게 주입할 수 있어 테스트가 용이해집니다.

계층형 설계에서 각 계층간의 의존성을 최소화하기 위해서, 의존성을 주입해 사용할 수 있는데요. 예를들어, 비즈니스 로직 계층이 데이터 액세스 계층에 의존할 때, 의존성 주입을 사용하여 데이터 액세스 계층의 구현을 외부에서 주입할 수 있습니다. 이렇게 하면 비즈니스 로직 계층은 특정 데이터 액세스 구현에 직접적으로 의존하지 않게 되어 유연성과 확장성이 높아집니다. 따라서, 계층형설계와 의존성 주입은 함께 사용되어 애플리케이션의 구조를 개선하고, 유연성과 확장성을 높이며, 모듈간의 결합을 줄일 수 있습니다.

계층형설계의 장점

추상화

계층형 설계는 복잡한 시스템을 간소화하여 이해하기 쉬운 구조를 만듭니다. 각 계층은 특정 기능에 집중하므로 구현 세부사항이 다른 계층에 노출되지 않습니다. 이를 통해 전체 시스템의 복잡성을 줄이고, 개발자가 해당 계층의 기능에만 집중할 수 있습니다.

독립성

각 계층은 독립적인 역할을 수행하므로, 한 계층의 변경이 다른 계층에 최소한의 영향만을 주게됩니다. 이를 통해 개발자가 각 계층의 변경 및 유지관리를 수월하게 할 수 있습니다.

관리성

계층형 설계는 코드의 모듈화를 촉진합니다. 이로 인해 코드의 관리가 용이해지며, 문제 발생 시 해당계층을 쉽게 식별하고 수정할 수 있습니다.

재사용성

각 계층은 잘 정의된 인터페이스와 역할을 가지므로, 특정 계층을 다른 시스템에서도 재사용할 수 있습니다. 이를 통해 개발 시간과 비용을 절약할 수 있게 되고, 코드 중복을 막을 수 있어 번들 사이즈도 최적화할 수 있게됩니다.

테스트가능성

계층형 설계는 테스트를 쉽게 수행할 수 있게합니다. 각 계층은 독립적이기 때문에, 계층 별로 단위 테스트를 수행할 수 있으며, 통합 테스트를 진행할 때에도 계층간의 의존성을 쉽게 제어할 수 있습니다.

확장성

계층형 설계는 시스템의 확장성을 높입니다. 각 계층의 독립성 때문에, 특정계층의 성능이나 기능을 개선하거나 새로운 기능을 추가하기 쉽습니다. 또한 계층간의 인터페이스를 변경하지 않고 한 계층을 다른 구현으로 교체할 수 있습니다. 이를 통해 시스템을 더욱 유연하게 확장하고 조정할 수 있습니다.

계층형설계의 단점

오버헤드

계층형 설계를 사용하면 각 계층간에 데이터를 전달하건 계층간 인터페이스를 호출하는 과정에서 오버헤드가 발생할 수 있습니다. 이로 인해 전체 시스템의 성능이 저하될 수 있습니다

복잡성

계층형 설계는 시스템의 전체 구조를 이해하는데에는 도움이 되지만, 각 계층간의 의존성이 복잡해질 수 있습니다. 이로 인해 개발자가 시스템의 구조를 파악하기 어려울 수 있게됩니다.

유연성 감소

계층형 설계는 각 계층의 역할과 책임이 명확하게 정의되어야 합니다. 이로 인해 특정 계층의 기능이 변경되거나 새로운 기능이 추가될 때 다른 계층에 영향을 주지 않도록 주의를 기울여야합니다. 이러한 제약으로 인해 시스템의 유연성이 감소할 수 있습니다.

개발속도 저하

계층형 설계는 각 계층을 독립적으로 개발하고 관리할 수 있게 하지만, 초기 개발 단계에서는 각 계층간의 인터페이스를 정의하고 협업을 위한 명확한 계획이 필요합니다. 이로 인해 개발 속도가 저하될 수 있습니다.

러닝커브

계층형 설계는 개발자들이 시스템의 구조와 각 계층의 역할을 완전히 이해해야 합니다. 이로 인해 러닝 커브가 높을 수 있으며, 특히 크고 복잡한 시스템에서는 이해하기 어려울 수 있습니다.

댓글