출처 : 제로초 - ES2015(ES6) Map, Set, WeakMap, WeakSet
내가 map을 쓰는이유? C++의 경우는
문자열을 인덱스로 쓰고 싶을때~ 썻었다.
key, value 쌍으로 값을 저장하는 데이터 창고이다.
map["문자열도인덱스가 될 수 있다."] = value;
사실 자바스크립트에서는 Object가 있기 때문에..크게 의미가 있지는 않다.
Map 생성 + 초기화까지
var map = new Map([['zero', 'ZeroCho']]);
Map에 값을 넣기
map.set('hero', 'Hero');
Map에서 값을 받기
map.get('zero'); // 'ZeroCho'
Map에 저장된 데이터량
map.size; // 2
Map에 데이터가 있냐? 없냐? c++경우 map.count(key) 썼었다.
map.has('hero'); // true
map.has('nero'); // false
값을 순회할때도 쓸 수 있다.
map.entries(); // {['zero', 'ZeroCho'], ['hero', 'Hero']}
map.keys(); // {'zero', 'hero'}
map.values(); // {'ZeroCho', 'Hero'}
//예를 들어 key가 string, value가 callback의 배열인 경우
for ( const fn of map.get(key).values()) {
fn.apply(null, args);
}
Map에서 데이터 제거하기 (key로 제거한다.)
map.delete('hero');
Map안에 데이터 싹다 지우기
map.clear();
저는 set를 중복없이 데이터를 저장할때 사용해요.
중복이 없다는건 균형 이진 트리로 저장한다고 생각을 할 수 있겠죠.
중복이 없다는 걸 결국 Set도 logN의 시간복잡도로 찾아낼 수 있고,
물론 지식이 짧기 때문에 ㅎㅎ 더 신박한 자료구조와 알고리즘으로 상수 시간복잡도로 할 수 있을 수도 있을 것 같다.
다행히 ㅎㅎ koa앱을 다시 serverless 아키텍쳐로 일일이 바꾸지 않아도 돼요.
npm i serverless-http
출처 : y0c - serverless-koa-bilerplate
위 레퍼런스를 통해 serverless.yml 생성해서 정보 넣고,
serverless.ts 파일 만들어서 app을 handler로 빼고
npm i serverless-plugin-typescript
serverless.yml에 플러그인아래 추가해주고
npm install serverless-offline --save-dev
serverless.yml에 플러그인아래 추가해주고
serverless.yml 설정 뜻을 몰라..
여기보면 다 나옴
serverless 공홈
npm run sls-offline
에러 발생!
TypeError: Cannot read property 'getLineAndCharacterOfPosition' of undefined
구글링!
에러 코드 구글링
stackoverflow 발견
tsconfig.json 이 문제라네요.
다시 우리 에러메시지 보면, tsconfig에 대한 이야기가 있어요. incremental은 파일 하나로 묶을때만 사용가능하대요.
제거 해줬어요.
serverless-offline 동작...
요약
TypeError: Cannot read property 'getLineAndCharacterOfPosition' of undefined
-> tsconfig.json 에서 "incremental": true,
를 제거해주면 됍니다.
Not Found... 요청을 보냈는데~ 해당 router가 없었다.
그럼 예측을 해볼 수 있죠.
라우팅이 안됐다..
라우팅은 어디서 건들죠?
serverless.yml에 등록을 다음과 같이 해놨는데
이렇게 하면 안되나봐요. 구체적으로 바꿔볼게요.
구글링 해서 따라했어요.
serverless.yml for http 으로 구글링~
출처: dougmoscrop - serverless-http
functions:
app:
handler: src/serverless.handler
events:
- http:
path: /fileMetadataList
method: get
- http:
path: /uploadFile
method: post
- http:
path: /downloadFile
method: get
cors:
origin: '*'
또 에러 발생
다른 에러가 발생했어요.
라우터는 찾아가는데 /.build/src/uploads 저런게 뜨네용...
.build로 디폴트로 저장되는 위치가 있나봐요. 저거 없애주고 싶어요.
구글링
Serverless: POST /uploadFile (λ: app)
server is listening to port 4002
Error --------------------------------------------------
Error: ENOENT: no such file or directory, open '/Volumes/Samsung_T5/codestates/CreativeStorage/server/.build/src/uploads/upload_a57fe6e537cd317ad37993ff88d27298'
sls offline 이랑 serverless offline 하고 똑같아요.
다음과 같이 .build가 생성이 돼요.
그리고... src 폴더에 uploads 폴더를 생성해주니 에러가 해결됐어요.
또 다른 에러 발생
server is listening to port 4002
Serverless: GET /fileMetadataList (λ: app)
Error --------------------------------------------------
Error: listen EADDRINUSE: address already in use :::4002
바보였었음..
in index.ts
app.listen(PORT, () => {
console.log(`server is listening to port ${PORT}`);
});
export default app;
listen을 하고 export하다니...
listen()을 server.ts 생성해서 다음과 같이 만들어 놨음.
import app from "./index";
const PORT: number = process.env.NODE_ENV === "production" ? 4001 : 4002;
app.listen(PORT, () => {
console.log(`server is listening to port ${PORT}`);
});
serverless 아키텍쳐로 잘 동작하게 되었다.
에러가 또 발생함...
기존 CreativeStorage V0.1 koa app에서
파일을 업로드, 다운로드, 리스트를 반환하는 API를 제공해줬습니다.
그래서 파일을 formData로 파일을 업로드했었는데,
파일을 업로드하고, 다운로드 했는데 이 둘이 같은지 체크하는 테스트 코드인데
in FileApiRouter.test.ts
describe(`upload and download and listFileMetadata test as FileApiRouter`, () => {
it(`should upload file and get FileMetadatalist from server and check it in downloads files`, async () => {
const imageInBase64 = "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg==";
const imageBuffer = Buffer.from(imageInBase64, "base64");
const filename = uuid();
await uploadFile(filename, imageBuffer);
const fileMetadataList = await getFileMetadataList();
const filenameFromApi = fileMetadataList.map((fileMetadata) => {
return fileMetadata.filename;
});
const downloadFiles: string[] = await Promise.all(filenameFromApi.map(async (filenameToBeDownloaded: string) => {
const downloadedFile = await downloadFile(filenameToBeDownloaded);
const downloadedFileInBase64 = downloadedFile.toString("base64");
return downloadedFileInBase64;
}));
expect(downloadFiles).toEqual(expect.arrayContaining([imageInBase64]));
});
});
테스트 코드도 http 요청을 하고 응답을 받는 형식으로 바꿔야겠다.
serverless.yml은 들여쓰기로 필드를 구분합니다.
indentation이 공식홈페이지나 레퍼런스에 나오듯이 써야합니다.
aws region list는? region은 한국으로...
in serverless.yml
provider:
name: aws
runtime: nodejs10.x
region : ap-northeast-2
stage: ${opt:stage, 'dev'}
${opt:stage}는 뭐지?
레퍼런스에 나오듯이 cli(터미널)에서 옵션을 줄 수 있다.
예를 들어 다음과 같이 사용한다면
serverless offline --stage prod
serverless.yml에서 이렇게 사용된다.
${self:provider.stage} <- prod 값이 들어가게 된다.
${self:provider.stage} 이게 뭔말인지? 어디서 봐야하나?
출처 : serverless 공홈
in serverless.yml
service: new-service
provider:
name: aws
stage: dev
custom:
myStage: ${opt:stage, self:provider.stage}
myRegion: ${opt:region, 'us-west-1'}
functions:
hello:
handler: handler.hello
myStage: ${opt:stage, self:provider.stage}
이 의미는 stage 옵션을 두었다면 그 스테이지가 myStage로 들어간다.
stage 옵션을 두지 않았다면 provider.stage인 dev가 들어간다.
기존 serverless.yml
provider:
name: aws
runtime: nodejs10.x
region : ap-northeast-2
stage: ${opt:stage, 'dev'}
custom:
serverless-offline:
port: ${file(./src/config/${self:provider.stage}.json):PORT}
custom변수를 이용한 serverless.yml
provider:
name: aws
runtime: nodejs10.x
region : ap-northeast-2
stage: dev
custom:
myStage: ${opt:stage, self:provider.stage}
myRegion: ${opt:region, self:provider.region}
serverless-offline:
port: ${file(./src/config/${self:custom.myStage}.json):PORT}
레퍼런스
붙이는데 실패했음.
https://niradler.com/serverless-integration-tests-with-offline-plugin/
에러 발생
timeout이 발생한다... timeout 전에 비동키 콜백이 돌지 않는다..
레퍼런스
https://dev.to/didil/serverless-testing-strategies-4g92
테스트 시작할때 serverless offline 실행했다가 테스트 끝날때 serverless offline 중지시키기
레퍼런스
실패했지만
https://codeburst.io/how-i-do-integration-test-for-service-powered-by-serverless-dynamodb-using-jest-e94f0710d28f
spawn을 통해 npm run script를 쓸 수 있다는 정보를 얻음.
shell을 쓰는데.. 난 못했음..
아직 해결못함.
해결함. 아래에 정리해놓음.
cross-env? nodejs process 환경변수로 이용해서 프로그램을 하고싶을때 써요.
process.env.NODE_ENV=development 이렇게 변수를 나눠서 개발 할 수 있는데요.
저는 맥을 쓰는데요. 윈도우는 방식이 달라요.
그래서 운영체제 상관없이 NODE_ENV를 바꿀 때 cross-env를 쓴대요.
https://thebook.io/006982/ch15/01/03-01/
npm scripts에서 윈도우에서도 NODE_ENV를 세팅하고 싶다면~
"start": "cross-env NODE_ENV=production node app",
node.js 로 자식 프로세스를 만들어서 프로그램을 돌릴 수 있다고?
출처 : www.freecodecamp.org
방법도 4개나 됌. 위에 레퍼런스 자세히 나와있음.
[node.js] 외부 프로세스를 생성하고 제어하기
출처 : 몽상가
node.js node.js 에서 spawn 과 exec 의 차이
출처 : 꿀벌개발일지
현재 프로젝트 디렉토리
- server
- src
- serverlessUtil.ts
- test
- serverless.test.ts
serverless offline을 자식프로세스로 실행시킬 util 프로그램
in serverlessUtil.ts
import { spawn, ChildProcessWithoutNullStreams } from "child_process";
const isWindows: boolean = /^win/.test(process.platform);
const npmCommand: string = isWindows ? "npm.cmd" : "npm";
let slsProcess: ChildProcessWithoutNullStreams;
export function start(): Promise<string | Error> {
return new Promise((resolve, reject) => {
slsProcess = spawn(npmCommand, ["run", "sls-offline", "--", "--noTimeout"]);
slsProcess.stdout.on("data", (data) => {
const log: string = (data as Buffer).toString("utf-8");
if (log.includes("listening on")) {
resolve(log);
}
});
slsProcess.stderr.on("data", (data) => {
const log: string = (data as Buffer).toString("utf-8");
reject(log);
});
slsProcess.on("error", (data) => {
reject(data);
});
});
}
export function stop(): void {
slsProcess.kill();
}
in serverless.test.ts
import { start, stop } from "../ServerlessUtil";
import fetch from "node-fetch";
export async function getTest(testUrl: string): Promise<boolean> {
const response = await fetch(testUrl);
if (!response.ok) {
throw new Error(response.statusText);
}
return response.ok;
}
async function healthCheck(): Promise<boolean> {
const url = "http://localhost:4002/";
const response = await fetch(url);
let status: boolean = false;
if (!response.ok) {
throw new Error(response.statusText);
}
status = true;
return status;
}
describe("serverelss", () => {
beforeEach(async () => {
jest.setTimeout(30000);
await start();
});
afterEach(() => {
stop();
});
it("/filemetadatalist api get request response test", async () => {
const serverStatus = await healthCheck();
if (serverStatus) {
const status: boolean = await getTest("http://localhost:4002/filemetadatalist");
expect(status).toEqual(true);
} else {
expect(true).toEqual(false);
}
});
it("/uploadfileurl api get request response test", async () => {
const serverStatus = await healthCheck();
if (serverStatus) {
const status: boolean = await getTest("http://localhost:4002/uploadfileurl");
expect(status).toEqual(true);
} else {
expect(true).toEqual(false);
}
});
it("/downloadfileurl api get request response test", async () => {
const serverStatus = await healthCheck();
if (serverStatus) {
const status: boolean = await getTest("http://localhost:4002/downloadfileurl");
expect(status).toEqual(true);
} else {
expect(true).toEqual(false);
}
});
it("/deletefileurl api get request response test", async () => {
const serverStatus = await healthCheck();
if (serverStatus) {
const status: boolean = await getTest("http://localhost:4002/deletefileurl");
expect(status).toEqual(true);
} else {
expect(true).toEqual(false);
}
});
});
위와 같이 serverlessConfig.json 파일을 하나 만들었습니다.
그 안에는 미리 stage에 따라 다르게 나눌 프로퍼티들을 설정해둡니다.
serverless.yml 에서는
${file(경로):stage.port} 이런 형식으로 써줘야 합니다. stage앞에는 : 를 붙여줘야 합니다.
아래는 참조한 레퍼런스들입니다.
레퍼런스 1
환경에 따라 다르게 serverless.yml 설정을 다르게 하고싶다.
env.yml을 만들어서 사용한 레퍼런스
레퍼런스 2
환경에 따라 다르게 serverless.yml 설정을 다르게 하고싶다.
serverless-dotenv-plugin 사용
레퍼런스 3
환경에 따라 다르게 serverless.yml 설정을 다르게 하고싶다.
Recursively reference properties
여기보면 --stage 옵션을 통해 여러 환경을 미리 세팅해놓고 사용할 수 있음.
레퍼런스 4
serverless.yml에 나오는 키워드?필드?프로퍼티? 이게 무슨뜻인지 모르겟다 ㅠ
구체적으로 serverless.yml에 대한 내용이 나옴.
공식홈페이지에 나온 방법을 좀 더 자세히 설명해준 레퍼런스
json 파일을 읽어서 사용하는 방식
serverless.yml
custom:
STAGE: ${self:provider.stage} # 현재 스테이지 별로 데이터베이스 접속 정보를 달리하기 위함
DB_CONFIG: ${file(./config/config.js):DB_CONFIG} # config.js 에서 가져올 데이터 베이스 접속정보
config.js 은 다음과 같이 쓸 수 있다.
module.exports.DATABASE_CONFIG = (serverless) => ({
dev: {
DB_HOST: 'localhost',
DB_USER: 'scott',
DB_PASSWORD: 'tiger'
},
prod: {
DB_HOST: 'fake.database.com',
DB_USER: 'scott2',
DB_PASSWORD: 'tiger2'
}
});
다른 Nuxt 프로젝트를 배포하는 것이긴 하지만
현재 serverless deploy 작업만 남아있기 때문에 참조했음.
특히 serverless.yml 에서 package 부분을 참조했음.