5. 클라이언트 배포 회고 (feat. hot-reload)
지금까지 한일
- 핵심 기능 (BE, 아이템 지급 / 닉네임 변경) 개발
- 웹페이지(front-end) 개발
- 인증(Authentication / Authorization) 기능 개발
- 테스트 데이터베이스 환경에서 서비스 테스트
- 서버 배포
조금 문제가 있긴 했지만, 임시로 서버 배포까지 마쳤습니다. 이제 클라이언트 프로그램을 배포하여 사용자가 클라이언트 서비스를 사용할 수 있게 해야 합니다. 클라이언트 서비스를 배포하는 방법은 여러개가 있을 수 있습니다.
- 커멘드 라인 인터페이스
- 데스트톱 애플리케이션
- 웹 페이지
- 등등등...
최초 개발시부터 클라이언트 프로그램은 데스크탑 애플리케이션으로 개발할 계획이었습니다.
데스크톱 애플리케이션으로 개발하려던 가장 큰 이유는 보안 문제였습니다. 데이터베이스의 주요 내용들 수정, 추가를 할 수 있는 서비스이므로 정해진 소수의 사용자만이 서비스에 접근이 가능하도록 해야합니다. 그런데 웹사이트는 모두에게 공개되므로 데스크톱 애플리케이션 대비 보안에 취약할 수 있다고 판단하였습니다.
물론 추가적인 보안 설정을 하긴 하지만, 그럼에도 모두에게 공개되는 웹사이트는 부담이 크다고 판단했습니다.
Electron
일렉트론(electron)은 오픈소스 프레임워크로 웹 애플리케이션을 개발하기위한 프런트엔드, 벡엔드 구성요소를 사용하여 데스크탑 그래픽 인터페이스 애플리케이션의 개발을 가능케한다.
일렉트론을 사용하여, vite 기반의 vue 프로젝트를 데스크탑 애플리케이션으로 빌드하기로 하였습니다. 일단 일렉트론을 설치해줍니다.
npm install --save-dev electron
그리고 일렉트론으로 빌드하기 위해서는 크게 3가지를 신경쒀줘야 합니다.
- main.js : 일렉트론 윈도우(창)의 여러 정보를 설정합니다.
- package.json : script에 일렉트론 빌드/실행 명령어를 정의합니다.
- index.html : index.html은 실제 빌드할 파일입니다. vue 프로젝트를 구현하면서 이미 개발이 완료되었습니다.
package.json
{
...
"scripts": {
"dev": "vite",
"build": "vite build",
"preview": "vite preview",
"start": "electron ."
},
...
}
다른 부분을 생략하고 스크립트(scripts) 부분만 살펴보겠습니다. electron을 빌드하기 위한 명령어 electron . 을 start로 설정해주었습니다.
main.js
const { app, BrowserWindow } = require('electron');
const path = require('path');
require('electron-reload')(__dirname, {
electron: path.join(__dirname, 'node_modules', '.bin', 'electron'),
});
function createWindow () {
const mainWindow = new BrowserWindow({
width: 1400,
height: 800,
webPreferences: {
preload: path.join(__dirname, 'preload.js')
}
});
if (process.env.NODE_ENV === 'production') {
mainWindow.loadFile('./dist/index.html');
} else {
mainWindow.loadURL('http://localhost:5174');
}
}
app.whenReady().then(() => {
createWindow();
app.on('activate', function () {
if (BrowserWindow.getAllWindows().length === 0) createWindow();
});
});
app.on('window-all-closed', function() {
if (process.platform !== 'darwin') app.quit();
});
createWindow() 메서드는 로드될 페이지의 크기나 로드할 데이터 등을 설정합니다. 특히, 핫-리로드(hot-reload)를 위해 개발(dev)모드에서는 특정 파일이 아닌 url을 설정하게 하여, 프로젝트가 수정되면 서버를 자동으로 껏다 키는 방식을 통해 수정 사항이 바로바로 적용되게 하였습니다. 프로덕션(production) 모드에서는 파일을 지정하게 하여 빌드된 파일을 페이지로 로드합니다. 이때 개발모드와 프로덕션모드를 설정하는 것은 환경변수를 사용하였습니다.
31-38번 줄을 일렉트론이 시작되는 시점 뒤에 실행할 콜백함수입니다. 일단 페이지를 로드하고, 만약 activate 시점에 로드된 페이지가 없을 경우, 다시 페이지를 로드합니다.
이제 프로젝트가 일렉트론 프로젝트로 실행되는 시점에 크게 3가지를 해야합니다.
- 환경 변수를 현재 모드에 맞게 설정(개발 vs 프로덕션)
- vite 명령어를 통해 프로젝트 빌드
- electron 명령어를 통해 빌드된 파일을 로드하여 일렉트론 애플리케션으로 실행
환경 변수를 커멘드 라인에서 설정하기 위해 추가적인 라이브러리를 설치해줍니다.
npm install --save-dev cross-env
이제 커멘드 라인을 아래와 같이 설정해줍니다. 실행해야할 커멘드가 여러 개라면 '&&'로 묶어주면 됩니다.
cross-env NODE_ENV=dev && vite && electron .
이제 npm run start를 실행해주면... 실행이 안됩니다...
vite명령어를 실행 후 멈춰있는 것을 볼 수 있습니다. 왜냐하면 &&로 묶어주게 되면 하나의 터미널에서 명령어를 순서대로 하나씩 실행하기 때문입니다. vite 명령어는 자동으로 종료되지 않습니다. 그러므로 그 다음 실행해야할 electron . 명령어는 실행하지 않습니다.
이 문제를 해결하기 위해서는 세개의 명령어를 동시에 실행해야 합니다. 그러기위해 추가적인 라이브러리를 설치해줍니다.
npm install --save-dev concurrently
그리고 명령어 앞에 concurrently를 명시후 동시에 실행할 명령어 들을 적어줍니다.
concurrently -k \"cross-env NODE_ENV=development && vite\" \"electron .\"
package.json의 scripts 파트를 위 명령어로 수정해줍니다.
{
...
"scripts": {
"dev": "vite",
"build": "vite build",
"preview": "vite preview",
"start": "concurrently -k \"cross-env NODE_ENV=development && vite\" \"electron .\""
},
...
}
그러면 이제 npm run start 명령어를 통해 프로젝트를 시작해주면 프로젝트가 정상적으로 실행되는 것을 볼 수 있습니다.
실행 프로그램 만들기
위 과정을 통해 npm run start 명령어로 일렉트론 데스크탑 애플리케이션을 실행할 수 있게 하였습니다. 이제 이 명령어를 자동으로 실행해주는 실행 프로그램을 제작하여야 합니다.
실행 프로그램 제작과정은 서버 배포때와 크게 다르지 않습니다.
참고 : https://ohreallystore.tistory.com/84
4. 서버 배포
지금까지 한일 핵심 기능 (BE, 아이템 지급 / 닉네임 변경) 개발 웹페이지(front-end) 개발 인증(Authentication / Authorization) 기능 개발 테스트 데이터베이스 환경에서 서비스 테스트 기본적인 앱 개발이
ohreallystore.tistory.com
일단 java 프로젝트를 생성하여 코드를 작성해주었다.
public class Main {
public static void main(String[] args) {
String cmd = "cd gam-dbadmin-app;npm install;npm run start";
Main.execute(cmd);
}
public static void execute(String cmd) {
Process process = null;
Runtime runtime = Runtime.getRuntime();
StringBuffer successOutput = new StringBuffer();
StringBuffer errorOutput = new StringBuffer();
BufferedReader successBufferReader = null;
BufferedReader errorBufferReader = null;
String msg = null;
ArrayList<String> cmdList = new ArrayList<>();
if (System.getProperty("os.name").indexOf("Windows") > -1) {
cmdList.add("cmd");
cmdList.add("/c");
} else {
cmdList.add("/bin/sh");
cmdList.add("-c");
}
cmdList.add(cmd);
String[] array = cmdList.toArray(new String[cmdList.size()]);
try {
process = runtime.exec(array);
successBufferReader = new BufferedReader(new InputStreamReader(process.getInputStream(), "EUC-KR"));
while ((msg = successBufferReader.readLine()) != null) {
successOutput.append(msg + System.getProperty("line.separator"));
System.out.println(msg);
}
errorBufferReader = new BufferedReader(new InputStreamReader(process.getErrorStream(), "EUC-KR"));
while ((msg = errorBufferReader.readLine()) != null) {
errorOutput.append(msg + System.getProperty("line.separator"));
System.out.println(msg);
}
if (process.exitValue() == 0) {
System.out.println("성공");
System.out.println(successOutput.toString());
} else {
System.out.println("비정상 종료");
System.out.println(successOutput.toString());
}
if (!errorOutput.toString().isEmpty()) {
System.out.println("오류");
System.out.println(successOutput.toString());
}
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
process.destroy();
if (successBufferReader != null) successBufferReader.close();
if (errorBufferReader != null) errorBufferReader.close();
} catch (IOException e1) {
e1.printStackTrace();
}
}
}
}
코드는 서버 실행 프로그램과 거의 동일합니다.
이제 jar 파일로 빌드후 응용 프로그램(exe) 파일로 변환해줍니다.
하나의 폴더에 프로젝트 폴더와 실행 프로그램을 넣어주면 완성입니당!