이때까진 행복했지...

본선 들어가서 진짜 열심히 5일간 달리고 연습용으로 만든 AWS 계정에서 과금도 발생하고 ^^ 우여곡절이 많았는데 우리 서비스는 아직 부족했나보다.

 

오배송 관련 문제를 처리해주는 앱 2개를 개발해서 제출했는데 월요일 오후에 온다던 결과가 아침 9시에 와서 아침부터 눈물의 런닝을 하고 왔다. 

 

그치.. 감사하다는 멘트로 시작하면 떨어진거지.... 메일 제목만 보고 직감했다 엉엉

 

개발자... 꼭 되어야지 

npm stop같은 멋진 기능은 안 만들어둔 과거의 나...

sudo lsof -nPi -sTCP:LISTEN

기능 실행해서 몇번 pid에서 돌고 있나 보고 kill해주자

7213번에서 돌고있다 ㅡㅡ

kill -9 7213

 

해주고 다시 npm start 해주니까 잘 돌아간다~

매번 putty를 켜놓고 살 순 없다. ec2에 pm2 라는 패키지를 설치하여 계속 돌아갈 수 있도록 셋팅해주었다.

 

sudo npm i pm2 -g

 

빌드 된 최종 파일 위치에 맞춰서 실행해주자.

 

pm2 start build/init.js

 

종료할 때는 pm2 stop id를 통해 종료할 수 있다. 나는 하나만 돌릴 거니까 id는 0이다

db에 연결하는 부분을 매번 코드로 다 작성해줄 수 없으니 기본적으로 연결해주는 부분과 비동기처리해주는 부분을 나눠서 모듈화해야 했다. 

 

mysql2/promise 라이브러리를 사용하였고 createConnection보다 더 효율적이라는 createPool 메서드를 사용하여 연결을 해주었다. 연결이 끊기는 문제가 있을 수 있어 이 부분은 구글링을 통해 enableKeepAlive: true 옵션을 주었다. 

 

연결해주는 부분은 다음과 같다.

import mysql from "mysql2/promise";
import dotenv from "dotenv";

dotenv.config();

export const pool = mysql.createPool({
  host: process.env.AWS_DB_HOST,
  user: process.env.AWS_DB_USER,
  password: process.env.AWS_DB_PASSWORD,
  port: process.env.AWS_DB_PORT,
  database: "kurylyDB",
  connectionLimit: 30,
  enableKeepAlive: true,
});

 

비동기 처리 문법 복기를 위해 적어두기! (비동기처리 메서드는 promise 객체를 반환해야 await가 제대로 작동한다.)

async function 함수명() {
  await 비동기처리_메서드명();
}

 

 

그리고 꼬박 10시간 정도를 머리 싸매고 고민했던 부분이다. 이렇게 db.js에서 실행하면 잘되는 코드가 export만 하면 난리가 나는 것이다. query문의 결과 rows 변수를 리턴해도 promise객체라서 갖다 쓰려면 복잡했다. 

const getOrderItemQuery = async () => {
  try {
    const connection = await pool.getConnection(async (conn) => conn);
    try {
      let [rows] = await connection.query("SELECT * FROM `order`");
      connection.release();
      console.log(rows);
      return rows;
    } catch (err) {
      console.log("Query Error", err);
      connection.release();
      return false;
    }
  } catch (err) {
    console.log("DB error");
    return false;
  }
};

 

위 코드 적고 실행해봤을 때 무사히 db에서 값이 콘솔에는 찍히는데 이제 저 함수를 export 해서 컨트롤러에서 가져다 쓰면 난리법석이 나는 것이다. 그래서 계속 방법을 찾아봤더니 connect된 promise 객체를 callback 함수로 넘겨줘서 사용해야 한다고 한다.

 

원래는 getConnection함수를 await해주기만 했는데 해당 요소를 변수에 넣어서 그 변수 자체를 콜백함수에 넘겨주니까 제대로 db에 쿼리문을 쏠 수 있게 되었다ㅠㅠ

export async function getConnectionPool(callback) {
  const connection = await pool.getConnection(async (err, conn) => {
    if (!err) {
      return conn;
    } else {
      console.log("db connection err", err);
      throw err;
    }
  });
  console.log("✅ Connected to DB 🎉");
  callback(connection);
}

 

실제로 사용해본 컨트롤러 코드를 하나 가져오면 다음과 같다. order 데이터 베이스에 들어있는 모든 컬럼을 다 건네줘야한다. 

 

import { getConnectionPool } from "../db";

export const getOrderItems = (req, res) => {
  getConnectionPool(async (connection) => {
    try {
      let [rows] = await connection.query("SELECT * FROM `order`");
      connection.release();
      return res.send(rows);
    } catch (err) {
      console.log("Query Error", err);
      connection.release();
      return res.send(err);
    }
  });
};

 

catch(err) 했을 때 는 어떻게 해야할지 고민이다 사실. 

ubuntu 18 환경으로 인스턴스를 만들어준 뒤 나는 윈도우 환경이니까 putty를 쓰기 위해 키 페어를 ppk 형식으로 받았다. ec2 환경에서 돌리고자 하는 서버 port가 4000번이어 인바운드 규칙에 해당 부분도 추가해주었다.

 

putty에서 인스턴스 id를 눌러 퍼블릭 IPv4 주소를 복사하여 session에 넣어주었다. auth 탭에서 아까 받은 ppk 키 페어도 넣어준 뒤, login as 창에 'ubuntu'를 입력해주니 다음과 같이 잘 연결됬다.

 

고맙다 welcome이라니...

 

물론 express 서버 역시 babel로 최신 js 문법 편하게 쓰고 있었기에  npm install babel cli 해주고 안전한 js 문법으로 빌드해주는 script를 추가했다.

  "scripts": {
    "start": "node build/init.js",
    "build:server": "babel src -d build",
    "dev:server": "nodemon --exec babel-node src/init.js"
  },

 

src 폴더 안에 있는 모든 요소를 build라는 디렉토리 안에 babel이 잘 변환해서 넣어줄 것이다. 그리고 서버를 다시 돌릴 수 있도록 start 명령어 역시 넣어주었다. 

빌드한 코드는 일단 돌아가는 것을 확인했다!

 

 

ec2에 접속하여 기본적으로 필요한 요소들을 셋팅해주자.

sudo apt-get update
sudo apt-get install nodejs
sudo apt-get install npm
sudo apt-get install git

 

이제 올려두었던 git 주소를 clone해와서 해보자.

와우 오류 장난아니게 났다 node랑 npm 버전이 다르다고 한다... 하하 왜 노드가 버전8로 설치되었을까..

검색해보니 다음 코드를 실행하면 된다고 한다. 내 node와 맞춰서 16,x로 해줬다.

curl -sL https://deb.nodesource.com/setup_16.x | sudo -E bash -
sudo apt-get install -y nodejs
sudo apt-get install nodejs
node -v

 

후... 그러고 나서 git에는 올라가지 않았으나 우리의 서버를 돌릴 떄 필요한 환경변수가 담겨있는 파일을 만들어주었다. export ~~ 를 통해서 해주면 된다는데 너무 많아서 ec2 리눅스 환경에서 .env파일을 만들어주었다. 만약 내 깃헙 ,env 파일이 바뀐다면 잊지 말고 업데이트를 해줘야겠지!

 

vim .env파일을 최상단 디렉토리에서 해준 뒤 기존 파일과 동일하게 작성한 뒤 저장해주었다,

ls -a 명령어를 통해 확인해보니 잘 있다.

 

npm install 실행해서 npm run build:server npm start 해주니까 제대로 돌아간다...!

인스턴스의 퍼블릭 IPv4 DNS 혹은 퍼블릭 IPv4 + 포트번호 연결해주니까 제대로 된다 엉엉

밖에 노출되면 안되는 API 키나 비밀번호 등을 저장하기 위해 .env 파일을 사용하는데 제대로 못 불러와서 애먹어서 기록을 위해 저장한다.

 

프로젝트 루트 디렉토리에 .env 파일을 생성한뒤

키=값

의 형태로 값을 넣어주면 된다. 문자열이어도 "" 은 쓰지 말 것! (나는 놀랍게도 콜론을 쓰고 계속 값을 불러와서 몇시간을 삽질했다 ... )

 

.env 파일을 보다 편하게 쓰기 위해 dotenv npm module을 설치했다. 그나저나 import 쓰려니까 package.json에 type: "module" 을 추가하라고 에러 메세지가 떠서 추가했더니 갑자기 잘 import해오던 js파일 뒤에 모두 확장자를 붙여줘야 찾아오는... 이상한 상황이 발생했다. 일단은 돌아가게 하는게 목적이니까 .js 모두 적어주고 type: "module"까지 잘 추가해주었다,

 

aws rds에 연결해야되므로 엔드포인트, 아이디, 비밀번호, 포트 번호를 env 파일에 저장하여 로드했다. dotenv.config()를 최상단에 호출하여 .env 파일을 불러올 수 있게 설정한 뒤 process.env._____ 의 형식으로 가져다가 쓰면 된다!!

 

aws rds에 mysql 을 셋팅한 뒤 workbench에 연결하는 건 쉬웠는데 저 이상한 삽질 때문에 시간을 너무 많이 날렸다... 

 

import mysql from "mysql";
import dotenv from "dotenv";

dotenv.config();

const connection = mysql.createConnection({
  host: process.env.AWS_DB_HOST,
  user: process.env.AWS_DB_USER,
  password: process.env.AWS_DB_PASSWORD,
  port: process.env.AWS_DB_PORT,
  database: "test",
});

connection.connect(function (err) {
  if (err) {
    throw err;
  } 
  console.log("✅ Connected to DB");
  connection.end();
});

 

그나저나 나는 aws rds 연결하려면 무조건 aws-sdk 써야되는줄 알았는데 그것도 아니었음

https://durian9s-coding-tree.tistory.com/15

 

Workbench 를 통해, AWS RDS에 접속환경 설정하는 방법

Workbench 를 통해, AWS RDS에 접속환경 설정하는 방법입니다. MySQL Workbench를 사용하여 MySQL을 실행하는 Amazon Relational Database Service(Amazon RDS) DB 인스턴스에 연결하려면 어떻게 해야 합니까? 해..

durian9s-coding-tree.tistory.com

 

현재 이 블로그 보고 설정중이고 localhost 에서 서버 개발한 뒤 ec2나 s3로 배포해서 제대로 할 예정이다. 참고할만한 블로그라서 티스토리에 박제

프론트엔드 웹 개발만 주구장창하다가 갑자기 안드로이드 RN 백업 + 백엔드 포지션을 맡게 되어서 엄청나게 정신이 없다. 뒤돌면 까먹으니까 지금 하나하나 작성해둬야겠다. 앱 내에서 특정 상황에 push 알림을 보내야 하는 상황이 있고 그 부분을 구현해야 한다. 일단 서버를 직접 구현하여 알림을 보내려면 앱<-> 서버 연결이 지속적으로 유지가 되어야 하는데, FCM을 사용하면 앱에서 서버에 연결하지 않아도 기기와의 내부 연결을 통해 메시지를 보낼 수 있다.

 

(https://maejing.tistory.com/entry/Android-FCM%EC%9D%84-%EC%9D%B4%EC%9A%A9%ED%95%B4-Push-%EA%B5%AC%ED%98%84%ED%95%98%EA%B8%B0 이 블로그가 도움이 많이 되었다)

 

참고한 공식 문서들

https://firebase.google.com/docs/cloud-messaging

 

Firebase 클라우드 메시징  |  Firebase 문서

Firebase 클라우드 메시징(FCM)은 무료로 메시지를 안정적으로 전송할 수 있는 크로스 플랫폼 메시징 솔루션입니다.

firebase.google.com

https://rnfirebase.io/messaging/usage

 

Cloud Messaging | React Native Firebase

Copyright © 2017-2020 Invertase Limited. Except as otherwise noted, the content of this page is licensed under the Creative Commons Attribution 3.0 License, and code samples are licensed under the Apache 2.0 License. Some partial documentation, under the

rnfirebase.io

 


FCM (firebase cloud messaging)

메시지를 안정적으로 무료 전송할 수 있는 크로스 플랫폼 메시징 솔루션

  • 알림 메시지 / 데이터 메시지 전송
  • 다양한 메시지 타켓팅 (단일 기기, 기기 그룹, 주제를 구독한 기기 등 3가지 방법)
  • 클라이언트 앱에서 메시지 전송

기능 들이 존재한다.

FCM 아키텍쳐 개요 (출처 : firebase fcm 공식문서)

 

 

notification에는 크게 2가지 종류가 있는데 1) foreground notification 2) background notification이 있다.

foreground notification

현재 앱이 열려있고 돌아가고 있는 상태에서 알림을 받을 때에 해당한다

 

background notification

앱이 현재 열려있지 않을 때 전송되는 알림이다. 

 

 

현재 프로젝트에서는 앱이 켜져 있을 때에도 알림이 와야 한다. (맞나?) 따라서 포그라운드, 백그라운드 경우 모두 처리가 되어야 한다.

 


 

FCM에서 보낼 수 있는 메시지는 알림 / 데이터 메시지 2종류다.

알림 메시지의 경우 클라이언트 앱을 대신하여 FCM이 알림 표시 작업을 처리해준다. 이번 프로젝트에서는 이걸 사용하려고 한다. 

 

전송 방법의 경우 공식문서에 다음과 같이 나와있다.

  1. Cloud Functions 또는 앱 서버와 같은 신뢰할 수 있는 환경에서 Admin SDK 또는 FCM 서버 프로토콜을 사용하여 notification 키를 설정합니다. 선택사항으로 데이터 페이로드를 추가할 수 있습니다. 항상 축소형입니다.
  2. 디스플레이 알림의 예시를 보고 요청 페이로드를 전송하세요.
  3. 알림 작성기 사용: 메시지 본문, 제목 등을 입력하고 전송합니다. 커스텀 데이터를 제공하여 선택사항인 데이터 페이로드를 추가합니다.

 

디스플레이 알림의 예시 화면으로 가보면 꽤 다양한 종류가 존재한다!

 

+ Recent posts