Quasar

vue.js + Quasar 헥사고날 아키텍처

DEV-Front 2024. 1. 7. 20:05
반응형

DRS Web Project에 헥사고날 아키텍처를 적용할지에 대해 고민했습니다.

제가 조사한바로는 헥사고날 아키텍처(Hexagonal Architecture)는

클린 아키텍처를 기반으로 한, 소프트웨어를 여러 계층으로 나누어 각 계층이 독립적이고 유연하게 동작할 수 있도록 하는 아키텍처 패턴입니다.

 

Quasar는 Vue.js 기반의 프레임워크로 주로 사용자 인터페이스(UI)를 구축하는 데에 특화되어 있습니다.

따라서 헥사고날 아키텍처를 적용하는 데에는 일부 제약이 있을 수 있습니다.

 

그러나 Quasar 애플리케이션에서도 비즈니스 로직을 분리하고 유지보수성을 향상시키기 위한 일반적인 아키텍처 원칙을 적용할 수 있습니다.

여러 방법으로 비즈니스 로직을 분리할 수 있으며, 그 중에서도 다음과 같은 방법을 고려할 수 있습니다

 

  1. Pinia 모듈 활용: Pinia는 Vue.js 애플리케이션의 상태 관리를 위한 라이브러리로, 상태를 중앙에서 관리하고 컴포넌트 간의 통신을 도와줍니다. 비즈니스 로직을 Pinia 모듈로 분리하여 각 모듈이 독립적으로 동작하도록 설계할 수 있습니다.
  2. Domain 계층 도입: 비즈니스 로직을 처리하는 Domain 계층을 도입하여 UI 계층과 완전히 분리할 수 있습니다. 이 Domain 계층은 특정 비즈니스 기능을 담당하며, 필요한 경우 Pinia 모듈과 연계하여 상태를 관리할 수 있습니다.
  3. 모듈화된 컴포넌트 설계: Quasar는 모듈화된 컴포넌트 설계를 통해 컴포넌트 간의 결합도를 낮출 수 있습니다. 각 컴포넌트는 특정 역할에 집중하고, 필요한 경우 Pinia나 Domain과 통신하여 데이터를 처리할 수 있습니다.

 

헥사고날 아키텍처의 원칙을 가능한 범위에서 적용하면서도 Quasar의 특성에 맞게 유연하게 구조를 조정하면 됩니다. 전체적인 아키텍처의 구조는 프로젝트의 크기, 팀의 개발 방법론, 요구 사항 등을 고려하여 결정하는 것이 중요합니다.

 

 

Quasar에서 헥사고날 아키텍처를 적용하는데는 명시적인 표준이나 규칙이 없습니다.

그러나 일반적인 웹 애플리케이션 아키텍처 원칙을 기반으로 폴더 구조를 설계할 수 있습니다.

다음은 일반적인 Quasar 애플리케이션 폴더 구조와 헥사고날 아키텍처 원칙을 적용한 예시입니다.

/src
|-- /assets
|-- /boot
|-- /components
|-- /layouts
|-- /pages
|-- /router
|-- /domains
|   |-- HexagonalService.js
|-- /stores
|-- /css
|-- /test
|-- /utils
|-- App.vue

헥사고날 아키텍처의 핵심은 외부와의 인터페이스를 중심으로 나머지 부분을 구성하는 것입니다. 위의 구조에서 /domains 폴더에는 외부 시스템이나 데이터와 상호작용하는 서비스들이 들어갈 수 있습니다.

이 서비스들은 비즈니스 로직을 담당하며, 필요한 경우 Pinia 와 통합될 수 있습니다.

 

DRS Web Project에서는 /domains 폴더외에 /api 폴더를 추가 했습니다.

그리고 /pages 폴더에는 사용자 인터페이스(UI) 관련 컴포넌트와 페이지들이 들어갑니다.

이들은 주로 표시 및 사용자 입력과 관련된 역할을 합니다.

WAS와 통신하는 API는 주로 /api 폴더에 위치하게 됩니다.

비즈니스 로직은 주로 /domains 폴더에 위치하게 됩니다.

API, Domains 사용은 Pages에서만 가능합니다.

/src
|-- /assets
|-- /boot
|-- /components
|-- /layouts
|-- /pages
|-- /router
|-- /api
| |-- /bp
| | |-- /SempleApi.ts
| |-- /even
| | |-- /SempleApi.ts
| |-- /icp
| | |-- /SempleApi.ts
|-- /services
| |-- /bp
| | |-- /ReviewService.ts
| | |-- /DefectService.ts
| | |-- /RepairService.ts
| |-- /even
| | |-- /SempleService.ts
| |-- /icp
| | |-- /SempleService.ts
|-- /css
|-- /test
|-- /utils
|-- App.vue

이는 일반적인 웹 애플리케이션 구조와 헥사고날 아키텍처를 결합한 예시이며, 프로젝트의 규모와 팀의 선호에 따라 구조를 조정할 수 있습니다.

 

여기서 주의할 점은 헥사고날 아키텍처의 목표 중 하나는 의존성 역전 원칙(Dependency Inversion Principle)을 지향하는 것입니다. 즉, 상위 수준의 모듈이 하위 수준의 모듈에 의존하지 않도록 하는 것입니다. 따라서 api domains는 상위 수준 모듈이 되고, 이들이 의존하는 것이 pages stores입니다.

 

pages는 API와 Service를 의존합니다.

Service는 API의 응답 Data를 인자값으로 받습니다.

Service는 API가 교체되어도 영향을 받지 않습니다.


quasar 프로젝트 기본 구조

/src
|-- /assets
|-- /boot
|-- /components
|-- /css
|-- /layouts
|-- /pages
|-- /router
|-- /store

Fonts 추가

/src
|-- /assets
|-- /boot
|-- /components
|-- /css
|-- /fonts
|-- /layouts
|-- /pages
|-- /router
|-- /stores

 


Dialog 추가

/src
|-- /assets
|-- /boot
|-- /components
|-- /css
|-- /dialog
|-- /fonts
|-- /layouts
|-- /pages
|-- /router
|-- /stores
  • Form Popup UI만 존재하는 폴더를 추가 했습니다.

Api 추가 (Axios 모듈화)

/src
|-- /assets
|-- /boot
|-- /components
|-- /css
|-- /dialog
|-- /fonts
|-- /layouts
|-- /pages
|-- /router
|-- /api
|-- /stores
  • Axios 모듈화를 위해 quasar 프로젝트에 api 폴더를 추가 했습니다.
  • Axios를 모듈화 것은 API 호출 코드를 조직화하고 재사용성을 높이기 위해서 입니다.
  • /api 폴더에서는 외부 서비스나 API와 상호작용하는 로직을 담당합니다.

Axios 모듈화 전

  • 전역으로 Axios 인스턴스 생성
    • 컴포넌트 내에서 Axios를 직접 생성하고 사용했습니다.
    • 이 경우 Axios의 인스턴스가 전역으로 생성되기 때문에 중복코드가 발생합니다.
  • 중복 코드
    • 여러 컴포넌트나 서비스에서 Axios를 사용할 때 동일한 API을 여러번 반복해서 작성했습니다.
<template>

...	
</template>

<script setup>
import { api } from 'boot/axios';


const endJudgeData = {
    scanIndex: workInfo.value.scanIndex,
    department: userInfo.value.department,
    userID: userInfo.value.id,
    judgeNo: judgeReasonSelect.value.judgeNo,
    reasonCode: reasonCodeValue.value,
    rootPath: workInfo.value.diskPath + '\\\\'};

// api 호출
api.post('/api/judge/end', endJudgeData)
.then(() => {

...

          });
		})
	.catch(err => {
    	console.log(err);
	});
};

</script>

<style lang="scss">
...
</style>

 

 

Axios 모듈화 후

  • 모듈로 Axios 인스턴스 생성
    • Axios를 별도의 모듈로 분리하여 인스턴스를 생성합니다.
    • 이 모듈은 프로젝트 전체에서 재사용될 수 있습니다.
  • 중복 코드 제거
    • Axios 모듈을 사용하면 설정이 담긴 파일을 하나로 관리할 수 있으므로 중복코드가 사라집니다.
  • 일관성
    • Axios의 설정을 일관되게 유지하기 용이, 향후 변경이 필요할 때 모듈 하나만 수정이 가능합니다.
 

Axios 모듈화의 예

// api폴더의 index.ts

import { api } from 'boot/axios';


function judgeStart(startJudgeData: object) {
  return api.post('/api/judge/start', startJudgeData);
}

function judgeHold(holdJudgeData: string) {
  return api.get(`/api/judge/hold/${holdJudgeData}`);
}



function judgeEnd(endJudgeData: object) {
  return api.post('/api/judge/end', endJudgeData);
}


export { judgeStart, judgeHold, judgeEnd };
  • 결과가 Promise 이기 때문에 Retrun 해줘야만 함수를 호출하고, 그 후의 비동기 동작이 수행 가능합니다.
  • Primise 객체는 비동기 작업이 맞이할 미래의 완료 또는 실패와 그 결과 값을 나타냅니다.
  • Primise는 주로 서버에서 받아온 Data를 UI에 표시할 때 사용합니다.
  • 일반적으로 웹 애플리케이션을 구현할 때 서버에서 Data를 요청하고 받아오기 위해 사용합니다.
// component



<template>
...	
</template>

<script setup>

// api 파일에서 사용할 api함수 import
import { judgeEnd } from 'api/judge/index.ts';

const judgeEnd = async () => {
try {

    const endJudgeData = {
      scanIndex: workInfo.value.scanIndex,
      department: userInfo.value.department,
      userID: userInfo.value.id,
      judgeNo: judgeReasonSelect.value.judgeNo,
      reasonCode: reasonCodeValue.value,
      rootPath: workInfo.value.diskPath + '\\\\'
};

    // api 사용
    const { data } = await judgeEnd(endJudgeData);
    console.log(data);
  } catch (error) {
    console.log(error);
  } finally {

...

  }
};
</script>

<style lang="scss">
...
</style>

Domains 폴더 추가

/src
|-- /assets
|-- /boot
|-- /components
|-- /css
|-- /dialog
|-- /fonts
|-- /layouts
|-- /pages
|-- /router
|-- /api
|-- /domains
| |-- /bp
| | |-- /ReviewService.ts
| |-- /even
| | |-- /SempleService.ts
| |-- /icp
| | |-- /SempleService.ts
| |-- /dms
| |-- /activeRos
|-- /stores
  • 변하지 않는 비지니스 로직만 주로 있는 Domain 폴더를 추가 했습니다.
반응형