Firebase Cloud Functions๋ž€? Firebase์—์„œ cloud functions์— ๋Œ€ํ•œ ์„ค๋ช…์„ ์ฐพ์•„๋ณด๋ฉด ์•„๋ž˜์™€ ๊ฐ™์ด ๋‚˜์˜จ๋‹ค.

Firebase์šฉย Cloud Functions๋Š” ๋ฐฑ๊ทธ๋ผ์šด๋“œ ์ด๋ฒคํŠธ, HTTPS ์š”์ฒญ,ย Admin SDKย ๋˜๋Š”ย Cloud Schedulerย ์ž‘์—…์— ์˜ํ•ด ํŠธ๋ฆฌ๊ฑฐ๋œ ์ด๋ฒคํŠธ์— ๋Œ€ํ•œ ์‘๋‹ต์œผ๋กœ ๋ฐฑ์—”๋“œ ์ฝ”๋“œ๋ฅผ ์ž๋™์œผ๋กœ ์‹คํ–‰ํ•  ์ˆ˜ ์žˆ๋Š” ์„œ๋ฒ„๋ฆฌ์Šค ํ”„๋ ˆ์ž„์›Œํฌ์ž…๋‹ˆ๋‹ค.

์‰ฝ๊ฒŒ ๋งํ•ด์„œ ์„œ๋ฒ„๊ฐ€ ๊ธฐ๋ณธ์ ์ธ api์— ๋Œ€ํ•œ ๋กœ์ง์„ ์ž‘์„ฑํ•œ ํ›„ ์ดํ›„ ํ•ด์•ผ ํ•˜๋Š” ๋ฐฐํฌ๋ถ€ํ„ฐ ๊ด€๋ฆฌ ๋ฌธ์ œ ์ „๋ฐ˜์„ firebase์ชฝ์— ์ด๊ด€ํ•˜๋Š” ๊ฒƒ์ด๋‹ค. ์œ ๋ช…ํ•œ ์„œ๋ฒ„๋ฆฌ์Šค ํ”„๋ ˆ์ž„์›Œํฌ๋Š” functions ๋ง๊ณ ๋„ aws lambda ๋“ฑ์ด ์žˆ๋‹ค. ๊ทธ๋ ‡๋‹ค๋ฉด ์ด ์„œ๋ฒ„๋ฆฌ์Šค๋ผ๋Š” ๊ฐœ๋…์€ ์ •ํ™•ํžˆ ๋ฌด์—‡์„ ์˜๋ฏธํ•˜๋Š” ๊ฑธ๊นŒ?

์„œ๋ฒ„๋ฆฌ์Šค ์•„ํ‚คํ…์ฒ˜(serverless architecture)

๋‹จ์–ด ๊ทธ๋Œ€๋กœ server + less, ์„œ๋ฒ„๋ฅผ ๊ด€๋ฆฌํ•  ํ•„์š”๊ฐ€ ์ ์–ด์ง„๋‹ค๋Š” ๋œป์ด๋‹ค. ๊ฐœ๋ฐœ์ž๊ฐ€ ์ผ์ผ์ด ์„œ๋ฒ„๋ฅผ ๊ด€๋ฆฌํ•  ํ•„์š” ์—†์ด ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ๋นŒ๋“œํ•˜๊ณ  ์‹คํ–‰ํ•  ์ˆ˜ ์žˆ๋„๋ก ํ•˜๋Š” ํด๋ผ์šฐ๋“œ ๋„ค์ดํ‹ฐ๋ธŒ ๊ฐœ๋ฐœ ๋ชจ๋ธ์ด๋‹ค. ๊ทธ๋ ‡๋‹ค๊ณ  ์„œ๋ฒ„๊ฐ€ ์—†์–ด์ง€๋Š” ๊ฒƒ์€ ์•„๋‹ˆ๋‹ค. ๊ทธ์ € ๊ฐœ๋ฐœ์ž๊ฐ€ ์„œ๋ฒ„๋ฅผ ๊ฑด๋“œ๋ฆด ํ•„์š”๊ฐ€ ์—†๋‹ค๋Š” ๋œป์ด์ง€, ์„œ๋ฒ„๋ฆฌ์Šค ๋ชจ๋ธ์—๋„ ์„œ๋ฒ„๋Š” ์กด์žฌํ•œ๋‹ค. ํ•˜์ง€๋งŒ ๊ทธ๊ฑธ ๊ด€๋ฆฌํ•˜๋Š” ์ฃผ์ฒด๊ฐ€ ํด๋ผ์šฐ๋“œ ์ œ๊ณต์—…์ฒด๋กœ ๋ฐ”๋€๋‹ค๋Š” ์ ๊ณผ ์ด ํด๋ผ์šฐ๋“œ ์ œ๊ณต์—…์ฒด์—์„œ ์„œ๋ฒ„ ์ธํ”„๋ผ์— ๋Œ€ํ•œ ํ”„๋กœ๋น„์ €๋‹, ์œ ์ง€๊ด€๋ฆฌ, ์Šค์ผ€์ผ๋ง ๋“ฑ ์„œ๋ฒ„ ๊ด€๋ฆฌ์—์„œ ํ•„์š”ํ•œ ํ•„์ˆ˜์ ์ธ ์š”์†Œ๋ฅผ ๋Œ€์‹  ํ•ด์ค€๋‹ค.

์„œ๋ฒ„๋ฆฌ์Šค ์•„ํ‚คํ…์ฒ˜์™€ IaaS(Infrastructure-as-a-Service)๋Š” ํด๋ผ์šฐ๋“œ ์ปดํ“จํŒ… ๋ชจ๋ธ์ด๋ผ๋Š” ์ ์—์„œ ๊ณตํ†ต์ ์„ ๊ฐ€์ง€์ง€๋งŒ, ์„œ๋ฒ„๋ฆฌ์Šค๋Š” ์ด๋ฒคํŠธ ๊ด€๋ฆฌ ๊ธฐ๋ฐ˜ ๋ชจ๋ธ์ด๋ผ๋Š” ์ ์—์„œ ํฐ ์ฐจ์ด์ ์„ ๊ฐ€์ง„๋‹ค. 

Iaas(Infrastructure-as-a-Service) PaaS(Platform-as-a-Service) SaaS(Software-as-a-Sercvice) ์™€ ํ•จ๊ป˜ 3๋Œ€ ํด๋ผ์šฐ๋“œ ์„œ๋น„์Šค ๋ชจ๋ธ ์ค‘ ํ•˜๋‚˜์ด๋ฉฐ, ์Šคํ† ๋ฆฌ์ง€, ๋„คํŠธ์›Œํฌ, ์„œ๋ฒ„ ๋“ฑ์„ ๋ชจ๋‘ ์ œ๊ณตํ•˜๋ฉด์„œ ์„œ๋ฒ„ ๊ด€๋ จ ์ธํ”„๋ผ๋ฅผ ๋ชจ๋‘ ์ œ์–ดํ•ด์ค€๋‹ค

Iaas๋Š” ์‚ฌ์šฉ์ž๊ฐ€ ์ธํ”„๋ผ๋ฅผ ์ง์ ‘ ๊ด€๋ฆฌํ•˜๋Š” ๊ฒƒ์ด์ง€๋งŒ ์„œ๋ฒ„ ์„ค์ •, ๋„คํŠธ์›Œํ‚น, OS ์„ค์น˜ ๋“ฑ์˜ ์„ธํŒ…๊ณผ ๊ฐ™์€ ์ผ๋ จ์˜ ๊ณผ์ •์„ ํด๋ผ์šฐ๋“œ ์ƒ์—์„œ ํ•˜๋Š” ๊ฒƒ์ด๋‹ค. ๋ฐ˜๋ฉด ์„œ๋ฒ„๋ฆฌ์Šค์˜ ๊ฒฝ์šฐ์—๋Š” ์„œ๋ฒ„ ์ธํ”„๋ผ์˜ ์ฒ˜์Œ๋ถ€ํ„ฐ ๋๊นŒ์ง€ ํด๋ผ์šฐ๋“œ๋ฅผ ํ†ตํ•ด ์„œ๋น„์Šค๋ฅผ ์ œ๊ณตํ•˜๊ธฐ ๋•Œ๋ฌธ์— ์„œ๋กœ ๊ด€์—ฌํ•˜๋Š” ์ •๋„๊ฐ€ ๋‹ค๋ฅด๋‹ค.

๋˜ํ•œ ๊ฐ€์žฅ ์ค‘์š”ํ•œ ์ ์€ Iaas๋Š” ์„œ๋ฒ„๋ฅผ ๊ณ„์† ๋Œ๋ฆฌ๊ณ  ์žˆ๋Š” ์ƒํƒœ์—ฌ์•ผ ํ•˜๊ธฐ ๋•Œ๋ฌธ์— ๊ฐ€๋™ ์‹œ๊ฐ„์ด๋‚˜ ์šฉ๋Ÿ‰ ๋“ฑ์— ๋Œ€ํ•ด์„œ ๋น„์šฉ์ด ์ฑ…์ •๋˜๋Š” ๋ฐ˜๋ฉด, ์„œ๋ฒ„๋ฆฌ์Šค๋Š” ๋‚ด๊ฐ€ ์„ค์ •ํ•ด๋†“์€ ํŠน์ • ํ•จ์ˆ˜๊ฐ€ ์‹คํ–‰๋˜์—ˆ์„ ๊ฒฝ์šฐ์— ์ด๋ฒคํŠธ๋ฅผ ๋ฐœ์ƒ์‹œ์ผœ ๋ฆฌ์†Œ์Šค๋ฅผ ๋™์ ์œผ๋กœ ํ• ๋‹นํ•œ๋‹ค(๊ทธ๋ž˜์„œ ์ด๋ฆ„์„ lambda๋‚˜ functions๋กœ ํ•˜๋Š”๋“ฏ). ๋”ฐ๋ผ์„œ ์•„๋ฌด๋ฆฌ ํŠธ๋ž˜ํ”ฝ์ด ๋ชฐ๋ฆฐ๋‹ค ํ•˜๋”๋ผ๋„ ๋™์  ํ• ๋‹น์œผ๋กœ ๊ฒฌ๋”œ ์ˆ˜ ์žˆ๋‹ค. ํ•˜์ง€๋งŒ ์“ด ๋งŒํผ ๋ˆ์€ ๋‚˜์˜จ๋‹ค.(on-demand)

์•„๋ฌดํŠผ ์ด๋Ÿฌํ•œ ์„œ๋ฒ„๋ฆฌ์Šค ์•„ํ‚คํ…์ณ๋Š” FaaS(Function-as-a-Service) ์™€ Baas(Backend-as-a-Service) ๋กœ ๋‚˜๋‰˜๋Š”๋ฐ, ์šฐ๋ฆฌ๊ฐ€ ์„œ๋ฒ„๋ฆฌ์Šค๋ฅผ ์ง€์นญํ•  ๋•Œ๋Š” ์ฃผ๋กœ FaaS๋ฅผ ๋‚˜ํƒ€๋‚ธ๋‹ค๊ณ  ๋ณด๋ฉด ๋œ๋‹ค. Faas๋Š” ํŠน์ • ํ•จ์ˆ˜ ๋‹จ์œ„๋กœ ์ฝ”๋“œ๋ฅผ ์‹คํ–‰ํ•  ์ˆ˜ ์žˆ๋Š” ์„œ๋น„์Šค๋กœ, ํด๋ผ์šฐ๋“œ ์„œ๋น„์Šค ์ œ๊ณต์ž๊ฐ€ ํŠน์ • ์š”์ฒญ์— ๋Œ€ํ•ด ๋ฐœ์ƒํ•˜๋Š” ํ•จ์ˆ˜๋ฅผ ์‹คํ–‰ํ•  ์ˆ˜ ์žˆ๋Š” ํ™˜๊ฒฝ์„ ์ œ๊ณตํ•˜๋„๋ก ํ•˜์—ฌ ์‚ฌ์šฉ์ž๋“ค์˜ ์š”์ฒญ์— ํ•จ์ˆ˜๊ฐ€ ์‹คํ–‰๋  ์ˆ˜ ์žˆ๋„๋ก ํ•œ๋‹ค.

BaaS(Backend-as-a-Service) BaaS๋Š” ํด๋ผ์šฐ๋“œ ์ œ๊ณต์ž๊ฐ€ ๋ฐฑ์—”๋“œ(์„œ๋ฒ„)์—์„œ ํ•„์š”ํ•œ ๋ชจ๋“  ๊ฒƒ๋“ค์„ ์ œ๊ณตํ•œ๋‹ค(์ธ์ฆ, ์Šคํ† ๋ฆฌ์ง€, db ๋“ฑ). Firebase๋‚˜ AWS Amplify๊ฐ€ ๋Œ€ํ‘œ์ ์ด๋ฉฐ, ์ด๋ฅผ ํ†ตํ•ด remote ํ‘ธ์‹œ ์•Œ๋ฆผ๊ณผ ๊ฐ™์€ ๊ธฐ๋Šฅ๋“ค์„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค.

ํŠน์ง•

  • Statelessํ•˜๋‹ค
    • ํ•จ์ˆ˜๊ฐ€ ์‹คํ–‰๋˜๋Š” ๋™์•ˆ๋งŒ ๊ด€๋ จ๋œ ๋ฆฌ์†Œ์Šค๋งŒ์„ ํ• ๋‹นํ•˜๊ณ  ๋๋‚˜๋ฉด ๋ฐ˜ํ™˜ํ•˜๊ธฐ ๋•Œ๋ฌธ์— ์ƒ์‹์ ์œผ๋กœ ์ƒ๊ฐํ•ด๋„ statelessํ•  ์ˆ˜๋ฐ–์— ์—†๋‹ค. ๋”ฐ๋ผ์„œ ์ง€์†๋˜๋Š” ์ƒํƒœ๋ฅผ ์œ ์ง€ํ•˜๊ธฐ ์œ„ํ•ด์„œ๋Š” DB๋ฅผ ๋”ฐ๋กœ ์ด์šฉํ•ด์•ผ ํ•  ํ•„์š”์„ฑ์ด ์žˆ๋‹ค.
  • Ephemeralํ•˜๋‹ค(์ผ์‹œ์ ์ž„)
    • stateless์™€ ๋น„์Šทํ•œ ๊ฒฐ์ด๊ธด ํ•˜๋‹ค. ํŠน์ • ์ด๋ฒคํŠธ๊ฐ€ ๋ฐœ์ƒํ–ˆ์„ ๊ฒฝ์šฐ ์ปจํ…Œ์ด๋„ˆ๋กœ์„œ ๋ฐฐํฌ๊ฐ€ ๋˜๊ณ  ํšŒ์ˆ˜๋˜๋Š” ๊ณผ์ •์ด ๋ฐ˜๋ณต๋˜๊ธฐ ๋•Œ๋ฌธ์— ์ผ์‹œ์ ์œผ๋กœ ๋ฐฐํฌ๋œ๋‹ค๊ณ  ๋ณผ ์ˆ˜ ์žˆ๋‹ค.

์žฅ๋‹จ์ 

์žฅ์ ์€ ๋ฌด์—‡๋ณด๋‹ค ์„œ๋ฒ„๋ฅผ ๊ทธ๋Œ€๋กœ ๋งก๊ธฐ๊ธฐ ๋•Œ๋ฌธ์— ์„œ๋ฒ„์— ๋Œ€ํ•œ ๋ฌธ์ œ๋ฅผ ์‹ ๊ฒฝ์“ฐ์ง€ ์•Š์„ ์ˆ˜ ์žˆ๋‹ค๋Š” ์ ์ด๋‹ค. ๋‚˜๊ฐ™์€ ํ”„๋ก ํŠธ๊ฐ€ ์ž‘์€ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ํ•˜๋‚˜ ๋งŒ๋“ค๋ ค๋ฉด ์„œ๋ฒ„๊ฐ€ ํ•„์š”ํ•˜๊ธฐ ๋•Œ๋ฌธ์— ์„œ๋ฒ„ ์ฝ”๋“œ ์“ฐ๊ณ  aws ์ธ์Šคํ„ด์Šค๋ฅผ ํŒŒ๊ณ  ๋ฐฐํฌํ•˜๋žด.. ํ”„๋ก์‹œ ์„ค์ •ํ•˜๋žด.. pm2 ์„ค์ •ํ•˜๋žด.. crontab ์„ค์ •ํ•˜๋žดโ€ฆ ํ• ๊ฒŒ ๋งŽ์€๋ฐ ์ œ๋Œ€๋กœ ํ• ์ค„๋„ ๋ชฐ๋ผ์„œ ํ‚น๋ฐ›๊ฒŒ ์‹œ๊ฐ„์„ ๋งŽ์ด ์žก์•„๋จน๋Š”๋‹ค. ํ•˜์ง€๋งŒ ์ด ๊ท€์ฐฎ์€ ์ž‘์—…๋“ค์„ ์•Œ์•„์„œ ์ฒ˜๋ฆฌํ•ด์ค€๋‹ค๊ณ  ํ•˜๋‹ˆ ์šฐ๋ฆฌ์•ผ ๋•กํ๋‹ค. ๊ฒŒ๋‹ค๊ฐ€ ๋Œ€๋ถ€๋ถ„ ํ˜ธ์ถœ 100๋งŒ๊ฑด์ •๋„๋Š” ๋ฌด๋ฃŒ๋กœ ์ œ๊ณตํ•˜๋Š” ํšŸ์ˆ˜์ด๋‹ˆ, ๋ฏธ๋‹ˆ ํ”„๋กœ์ ํŠธ์—์„œ๋Š” ์ด๋งŒํผ ์•ˆ์„ฑ๋งž์ถค์ธ ์„œ๋ฒ„๊ฐ€ ์—†๋‹ค. ๋˜ํ•œ ๊ฐ‘์ž๊ธฐ ํŠธ๋ž˜ํ”ฝ์ด ๋ชฐ๋ฆฐ๋‹ค๊ณ  ํ•ด๋„ ๋‹ค์ด๋‚˜๋ฏนํ•˜๊ฒŒ ์•Œ์•„์„œ ์Šค์ผ€์ผ์—…์„ ํ•ด์ฃผ๊ธฐ ๋•Œ๋ฌธ์— ์‹ ๊ฒฝ์“ธ ํ•„์š”๊ฐ€ ์—†๋‹ค(์‚ฌ์‹ค ์‹ ๊ฒฝ์“ฐ๊ธด ํ•ด์•ผํ•จ).

ํ•˜์ง€๋งŒ ์žฅ์ ์ด ๋šœ๋ ทํ•œ ๋งŒํผ ๊ทธ์— ๋”ฐ๋ฅธ ๋‹จ์  ๋˜ํ•œ ๋ช…ํ™•ํ•˜๋‹ค. ์„œ๋ฒ„์— ๋Œ€ํ•œ ์ƒ์„ธ ์„ค์ •์ด๋‚˜ ์ธํ”„๋ผ ์ œ์–ด ๋“ฑ์— ๋Œ€ํ•ด์„œ๋Š” ๋ชจ์กฐ๋ฆฌ ์„œ๋น„์Šค ์ œ๊ณตํ•˜๋Š” ๊ณณ์—์„œ ๊ด€๋ฆฌํ•˜๊ธฐ ๋•Œ๋ฌธ์— ์šฐ๋ฆฌ๊ฐ€ ์„ค์ •ํ•  ์ˆ˜ ์žˆ๋Š” ๋ฒ”์œ„๊ฐ€ ์ œํ•œ๋œ๋‹ค๋Š” ์ ์ด ์ž์œ ๋„๋ฅผ ๋ฌถ๋Š”๋‹ค. ๋˜ํ•œ ์„œ๋ฒ„๋ฆฌ์Šค ํ•จ์ˆ˜์˜ ๊ฒฝ์šฐ์—๋Š” ๋‚˜ ๋˜ํ•œ ์ด๋ฒˆ์— ํ•˜๋ฉด์„œ ๋А๋‚€ ํ˜„์ƒ์œผ๋กœ, ์ฝœ๋“œ ์Šคํƒ€ํŠธ(Cold Start) ๋ผ๋Š” ํ˜„์ƒ์ธ๋ฐ ์ฒ˜์Œ ํ˜ธ์ถœ ์‹œ์— ์‹œ๊ฐ„ ์ง€์—ฐ์ด ์กฐ๊ธˆ ๋ฐœ์ƒํ•œ๋‹ค.

Cold Start๋ž€?

์œ„์—์„œ ๋งํ–ˆ๋“ฏ์ด ์„œ๋ฒ„๋ฆฌ์Šค๋Š” ํ•จ์ˆ˜๊ฐ€ ์‹คํ–‰๋  ๋•Œ๋งˆ๋‹ค ๋ฆฌ์†Œ์Šค(์ž์›)์„ ๋™์ ์œผ๋กœ ํ• ๋‹นํ•˜๋Š” ๊ตฌ์กฐ์ด๊ธฐ ๋•Œ๋ฌธ์— ํ•จ์ˆ˜๊ฐ€ ์‹คํ–‰๋˜๋Š” ๋™์•ˆ๋งŒ ๊ด€๋ จ๋œ ์ž์›์ด ํ• ๋‹น๋˜๊ณ , ์‹คํ–‰์ด ๋๋‚˜๊ณ  ์‘๋‹ต์ด ๋Œ์•„์™”๋‹ค๋ฉด ํ•จ์ˆ˜๋Š” ํ• ๋‹น๋œ ๋ฆฌ์†Œ์Šค๋ฅผ ๋ฐ˜ํ™˜ํ•œ๋‹ค.

์—ฌ๋Ÿฌ ์š”์ฒญ์ด ๋™์‹œ์— ์˜ค๋Š” ๊ฒฝ์šฐ ๊ธฐ๋Šฅ ๋ณต์‚ฌ๋ณธ์„ ์ƒ์„ฑํ•˜์—ฌ ์žฌํ™œ์šฉํ•˜๊ฑฐ๋‚˜ ํ•œ๋ฒˆ ์š”์ฒญ์ด ๊ฐ”์„๋•Œ ์œ ์ง€ํ•˜๋Š” ์œ ํœด ์ƒํƒœ์—์„œ ๋ฒ—์–ด๋‚˜๊ฒŒ ๋˜๋ฉด ๋‹ค์‹œ ์œ„์™€ ๊ฐ™์€ ๋ผ์ดํ”„์‚ฌ์ดํด์„ ๋Œ์•„์•ผ ํ•˜๊ฒŒ ๋˜๋‹ˆ, ๋‹ค์‹œ๊ธˆ ์†๋„๊ฐ€ ๋А๋ ค์ง€๊ฒŒ ๋˜๋Š” ํ˜„์ƒ์ด ์žˆ๋‹ค. ์˜ˆ์ „์— IEEE์—์„œ AWS Lambda์™€ Microsoft Azure Functions์—์„œ ๋ฒค์น˜๋งˆํฌ๋ฅผ ์ˆ˜ํ–‰ํ•œ ๊ฒฐ๊ณผ๊ฐ€ ์žˆ๋Š”๋ฐ, 300ms์—์„œ 24์ดˆ๊นŒ์ง€ ์‹œ๊ฐ„์ด ๊ฑธ๋ ธ๋‹ค๊ณ  ํ•˜๋‹ˆ ์ด ์ ์ด ์‚ฌ์šฉ์ž์—๊ฒŒ ์„œ๋น„์Šค ์ œ๊ณต ๊ณผ์ •์— ์žˆ์–ด์„œ ์น˜๋ช…์ ์ธ ๋‹จ์ ์œผ๋กœ ์ž‘์šฉํ•œ๋‹ค. ๊ทธ๋ž˜์„œ firebase์˜ cloud functions์™€ ๊ฐ™์€ ๊ณณ์—์„œ๋Š” ์ตœ์†Œ ์ธ์Šคํ„ด์Šค ์ˆ˜๋ฅผ ์„ค์ •ํ•˜์—ฌ ์ปจํ…Œ์ด๋„ˆ๋ฅผ ์œ ์ง€ํ•จ์œผ๋กœ์จ ์ตœ์†Œํ•œ ๋“ค์–ด์˜ฌ ์ˆ˜ ์žˆ๋Š” ์‚ฌ์šฉ์ž์— ๋Œ€ํ•ด ๋ฐ”๋กœ ์‘๋‹ตํ•  ์ˆ˜ ์žˆ๋„๋ก ํ•˜๋Š” ๋“ฑ์˜ ๋Œ€๋น„์ฑ…์„ ํ†ตํ•ด ์†๋„๋ฅผ ์ค„์ด๋Š” ๋ฐฉ๋ฒ•์„ ์‚ฌ์šฉํ•œ๋‹ค. ์ถ”๊ฐ€์ ์œผ๋กœ ์ข…์†์„ฑ ์ค„์ด๊ธฐ, ์บ์‹ฑ ํ—ค๋” ์‚ฌ์šฉ, ์˜ฌ๋ฐ”๋ฅธ ์ง€์—ญ ์„ ํƒ ๋“ฑ์„ ํ†ตํ•ด ์†๋„๋ฅผ ์ค„์ผ ์ˆ˜ ์žˆ๋‹ค๊ณ ๋Š” ํ•˜์ง€๋งŒ ํšจ๊ณผ์ ์œผ๋กœ ์ค„์ด๊ธฐ ์œ„ํ•œ ํ™•์‹คํ•œ ๋ฐฉ๋ฒ•์€ ์ตœ์†Œ ์ธ์Šคํ„ด์Šค ์ˆ˜๋ฅผ ์œ ์ง€ํ•˜๋Š” ๊ฒƒ์ด๋‹ค. ์ด๋ฅผ ์œ„ํ•ด ์Šค์ผ€์ค„๋ง์„ ๋”ฐ๋กœ ์‹œ์ผœ ๊ณ„์†ํ•ด์„œ ์ธ์Šคํ„ด์Šค๋ฅผ ์œ ์ง€ํ•˜๋„๋ก ํ•˜๋Š” ๋“ฑ์˜ ๋ฐฉ๋ฒ•์„ ์“ฐ๋Š” ๊ฒƒ์œผ๋กœ ๋ณด์ธ๋‹ค.

firebase Functions๋ฅผ ์‚ฌ์šฉํ•ด์„œ ์„œ๋ฒ„๋ฆฌ์Šค ๋ฐฐํฌํ•˜๊ธฐ(node.js)

์„œ๋ฒ„๋ฆฌ์Šค(FaaS)์— ๋Œ€ํ•ด ์•Œ์•„๋ณด์•˜์œผ๋‹ˆ ์‚ฌ์šฉํ•˜๋ฉด์„œ ์ง์ ‘ ์•Œ์•„๊ฐ€๋ณด์ž. ๋‚˜๋Š” FaaS๋“ค ์ค‘์— firebase Functions๋ฅผ ์‚ฌ์šฉํ–ˆ๋‹ค. ์ด์œ ๋Š” ์ด ์ „์— firebase๋ฅผ ์‚ฌ์šฉํ•œ cloud messaging ๊ธฐ์ˆ ์„ ๋‚ด ํ”„๋กœ์ ํŠธ ์•ฑ์— ์‚ฌ์šฉํ–ˆ๋˜ ๊ฒฝํ—˜์ด ์žˆ๋Š”๋ฐ, firebase์— cloud messaging ๋ง๊ณ ๋„ ๋‹ค๋ฅธ ๊ธฐ๋Šฅ๋“ค์ด ์ •๋ง ๋งŽ์•„ ๊ณ„์† ํ•œ๋ฒˆ์ฏค์€ ์‚ฌ์šฉํ•ด๋ณด๊ณ  ์‹ถ๋‹ค๋Š” ์ƒ๊ฐ์ด ๋“ค์—ˆ๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค. ๋‚˜๋Š” ๊ธฐ์กด functions๋ฅผ ํ†ตํ•ด ๋Ÿฐํƒ€์ž„์— ๋ฐฐํฌํ•˜๋Š” ๊ฒƒ์„ ๋„˜์–ด db ๋˜ํ•œ ๋”ฐ๋กœ ๋ฐฐํฌํ•˜๊ธฐ ๊ท€์ฐฎ์•˜๊ธฐ ๋•Œ๋ฌธ์— firebase์—์„œ ์ œ๊ณตํ•˜๋Š” cloud db์ธ firestore๊นŒ์ง€ ์ด์šฉํ•  ๊ฒƒ์ด๋‹ค.

ํ•ด๋‹น ๊ธ€์€ node.js ๊ธฐ์ค€์œผ๋กœ ์ž‘์„ฑ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.

ํ”„๋กœ์ ํŠธ initialize

ํ”„๋กœ์ ํŠธ ์ถ”๊ฐ€ํ•˜๊ธฐ

Firebaseย Console์—์„œ ํ”„๋กœ์ ํŠธ ์ถ”๊ฐ€ํ•˜๊ธฐ ํ”„๋กœ์ ํŠธ๋ฅผ ์ถ”๊ฐ€ํ•˜๋Š” ๊ฒƒ์€ ์‚ฌ์ดํŠธ์—์„œ ์ถ”๊ฐ€ํ•˜๋ฉด ๋˜๊ธฐ ๋•Œ๋ฌธ์— ์–ด๋ ค์šด ๊ฒƒ์€ ์—†๋‹ค. ๋“ฑ๋ก์„ ๋๋‚ด๊ณ  ๋‚˜๋ฉด ์ด๋Ÿฐ ์‹์œผ๋กœ ์ฝ˜์†”์ด ๋œฌ๋‹ค.

Firebase CLI ์„ค์น˜ ๋ฐ ์ดˆ๊ธฐํ™”

npm install -g firebase-tools

firebase functions๋ฅผ ๋Ÿฐํƒ€์ž„์— ๋ฐฐํฌํ•˜๊ธฐ ์œ„ํ•ด์„œ๋Š” Firebase CLI๊ฐ€ ํ•„์š”ํ•˜๋‹ค. npm์„ ํ†ตํ•ด ๋‹ค์šด๋กœ๋“œ ๋ฐ›์•„์ฃผ์ž ๋‹ค์šด๋กœ๋“œ ๋ฐ›์œผ๋ฉด Firebase๋ฅผ ์‹คํ–‰ํ•˜์—ฌ ๋กœ๊ทธ์ธ ๋ฐ ๊ธฐํƒ€ ์ดˆ๊ธฐํ™” ์ž‘์—…์„ ํ•  ์ˆ˜ ์žˆ๋‹ค.

// ์–˜ ์“ฐ๋ฉด ๋กœ๊ทธ์ธ์ฐฝ ๋œธ
firebase login
 
// ๋‚ด๊ฐ€ ํ”„๋กœ์ ํŠธ ์ƒ์„ฑํ•  ๋””๋ ‰ํ† ๋ฆฌ๋กœ ์ด๋™
cd ๋””๋ ‰ํ† ๋ฆฌ
 
// firestore init
firebase init firestore 
 
// functions init
firebase init functions

์ด๋ ‡๊ฒŒ ์„ธํŒ…ํ•˜๋‹ค๋ณด๋ฉด ์–ด๋–ค ์–ธ์–ด ์“ธ๊ฑฐ๋ƒ๋Š” ์งˆ๋ฌธ์ด ๋‚˜์˜ฌ๊ฑด๋ฐ ๋‚˜๋Š” js๋กœ ์„ ํƒํ•ด์ฃผ์—ˆ๋‹ค

myproject
+- .firebaserc    # `firebase use` ๋ช…๋ น์–ด๋ฅผ ํ†ตํ•ด ํ”„๋กœ์ ํŠธ ๊ฐ„ ์ „ํ™˜์„ ๋น ๋ฅด๊ฒŒ ๋„์™€์ฃผ๋Š” ์ˆจ๊น€ ํŒŒ์ผ
|
+- firebase.json  # ํ”„๋กœ์ ํŠธ ์†์„ฑ์„ ์„ค๋ช…ํ•˜๋Š” ํŒŒ์ผ
|
+- functions/     # ๋ชจ๋“  ํ•จ์ˆ˜ ์ฝ”๋“œ๊ฐ€ ํฌํ•จ๋œ ๋””๋ ‰ํ„ฐ๋ฆฌ
      |
      +- .eslintrc.json  # ์ž๋ฐ”์Šคํฌ๋ฆฝํŠธ ๋ฆฐํŒ… ๊ทœ์น™์„ ํฌํ•จํ•œ ์„ ํƒ์  ํŒŒ์ผ
      |
      +- package.json  # Cloud Functions ์ฝ”๋“œ๋ฅผ ์„ค๋ช…ํ•˜๋Š” npm ํŒจํ‚ค์ง€ ํŒŒ์ผ
      |
      +- index.js      # Cloud Functions ์ฝ”๋“œ์˜ ๋ฉ”์ธ ์†Œ์Šค ํŒŒ์ผ
      |
      +- node_modules/ # package.json์— ์„ ์–ธ๋œ ์˜์กด์„ฑ๋“ค์ด ์„ค์น˜๋˜๋Š” ๋””๋ ‰ํ„ฐ๋ฆฌ

๊ทธ๋Ÿผ ์ด๋Ÿฐ ์‹์œผ๋กœ ์ฒ˜์Œ ์„ธํŒ…์ด ์™„๋ฃŒ๋œ๋‹ค

//package.json
{
  "name": "functions",
  "description": "Cloud Functions for Firebase",
  "scripts": {
    "lint": "eslint .",
    "serve": "firebase emulators:start --only functions",
    "shell": "firebase functions:shell",
    "start": "npm run shell",
    "deploy": "firebase deploy --only functions",
    "logs": "firebase functions:log"
  },
  "engines": {
    "node": "22"
  },
  "main": "index.js",
  "dependencies": {
    "cors": "^2.8.5",
    "firebase-admin": "^12.1.0",
    "firebase-functions": "^5.0.0"
  },
  "devDependencies": {
    "eslint": "^8.15.0",
    "eslint-config-google": "^0.14.0",
    "firebase-functions-test": "^3.1.0"
  },
  "private": true
}

package.json์„ ์—ด์–ด๋ณด๋ฉด ์ด๋Ÿฐ ์‹์œผ๋กœ scripts๊ฐ€ ์งœ์—ฌ ์žˆ์–ด์„œ ์“ฐ๊ธฐ ํŽธํ•˜๋‹ค ๊ธฐ์–ตํ•˜๋ฉด ์ข‹์€ ๊ฒƒ์€

  • engines : ๋‚ด๊ฐ€ ์“ธ node ๋ฒ„์ „ ์„ค์ •
  • scripts
    • serve : emulator๋ฅผ ํ†ตํ•ด์„œ ๋กœ์ปฌ์—์„œ ์„œ๋ฒ„๋ฅผ ํ‚ฌ ์ˆ˜ ์žˆ๋‹ค. ui๋„ ์ œ๊ณตํ•˜๋‹ˆ ๋ณด๊ณ  ํ…Œ์ŠคํŠธํ•˜๊ธฐ ๋Œ•๊ฟ€์ด๋‹ค
    • deploy: functions๋งŒ ๋ฐฐํฌ ์†”์งํžˆ ๋‚œ ์ด์ •๋„๋งŒ ์ผ๋‹ค
// The Cloud Functions for Firebase SDK to create Cloud Functions and triggers.
const {logger} = require("firebase-functions");
const {onRequest} = require("firebase-functions/v2/https");
const {onDocumentCreated} = require("firebase-functions/v2/firestore");
 
// The Firebase Admin SDK to access Firestore.
const {initializeApp} = require("firebase-admin/app");
const {getFirestore} = require("firebase-admin/firestore");
 
initializeApp();

์ฒ˜์Œ์— index.js๊ฐ€๋ฉด ์žก๋‹คํ•œ ํ…Œ์ŠคํŠธ์šฉ๋“ค์ด ์ฃผ์„์ฒ˜๋ฆฌ ๋˜์–ด ์žˆ๊ณ  ์ด๋Ÿฐ ์‹์œผ๋กœ ํ•ด๋ณด๋ผ๊ณ  ํ•œ๋‹ค. initializeApp()์„ ํ†ตํ•ด Firebase ์„œ๋น„์Šค์— ์ ‘๊ทผ ๊ฐ€๋Šฅํ•˜๋„๋ก ์ดˆ๊ธฐ ์„ค์ •์„ ํ•  ์ˆ˜ ์žˆ์œผ๋ฉฐ ์ธ์ฆ์ฒ˜๋ฆฌ ๋“ฑ์˜ ๊ณผ์ •์ด ์ด๋ฃจ์–ด์ง€๋ฉด์„œ ์—ฐ๊ฒฐ๋œ๋‹ค.

http ์š”์ฒญ ๋ฐ›๊ธฐ

์š”์ฒญ ํ•ธ๋“ค๋Ÿฌ๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด HTTP ์š”์ฒญ์„ ํ†ตํ•ด ํ•จ์ˆ˜๋ฅผ ํŠธ๋ฆฌ๊ฑฐ์‹œํ‚ค๊ณ , ํ•ด๋‹น ํ•จ์ˆ˜๋ฅผ ํ†ตํ•ด ์‘๋‹ต์„ ๋ฐ˜ํ™˜์‹œํ‚ฌ ์ˆ˜ ์žˆ๋‹ค.

์˜ต์…˜์„ค๋ช…
regionHTTP ํ•จ์ˆ˜๋Š” ๋‹จ์ผ ๋ฆฌ์ „๊ณผ ์—ฌ๋Ÿฌ ๋ฆฌ์ „์„ ์ง€์ •ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์—ฌ๋Ÿฌ ๋ฆฌ์ „์ด ์ง€์ •๋˜๋ฉด ๋ฆฌ์ „๋ณ„๋กœ ๋ณ„๋„์˜ ํ•จ์ˆ˜ ์ธ์Šคํ„ด์Šค๊ฐ€ ๋ฐฐํฌ๋ฉ๋‹ˆ๋‹ค.
timeoutSeconds(Python์˜ ๊ฒฝ์šฐย timeout_sec)HTTP ํ•จ์ˆ˜์—์„œ ์ตœ๋Œ€ 1์‹œ๊ฐ„์˜ ์ œํ•œ ์‹œ๊ฐ„์„ ์ง€์ •ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
corsHTTP ํ•จ์ˆ˜๊ฐ€ CORS ์ •์ฑ…์„ ์ง€์ •ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.ย true๋กœ ์„ค์ •ํ•˜๋ฉด ๋ชจ๋“  ์ถœ์ฒ˜๊ฐ€ ํ—ˆ์šฉ๋˜๊ณ ,ย string,ย regex,ย array๋ฅผ ์„ค์ •ํ•˜๋ฉด ํ—ˆ์šฉ๋œ ์ถœ์ฒ˜๋ฅผ ์ง€์ •ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๋ช…์‹œ์ ์œผ๋กœ ์„ค์ •ํ•˜์ง€ ์•Š์œผ๋ฉด ๊ธฐ๋ณธ๊ฐ’์€ false/CORS ์ •์ฑ… ์—†์Œ์ž…๋‹ˆ๋‹ค.
์š”์ฒญ์— ๋Œ€ํ•œ ์˜ต์…˜์€ ์ด๋ ‡๊ฒŒ ์ง€์ •ํ•ด์ค„ ์ˆ˜ ์žˆ๋‹ค.
const { onRequest } = require("firebase-functions/v2/https");
 
exports.sayHello = onRequest(
  { cors: [/firebase\.com$/, "flutter.com"] },
  (req, res) => {
    res.status(200).send("Hello world!");
  }
);

์š”๋Ÿฐ ์‹์œผ๋กœ cors์˜ ๊ฒฝ์šฐ ๋‚ด๊ฐ€ ๊ฐ€์ง€๊ณ  ์žˆ๋Š” ๋„๋ฉ”์ธ๋“ค์„ ์„ค์ •ํ•  ์ˆ˜ ์žˆ์œผ๋ฉฐ, ์ผ๋ฐ˜ express ์‚ฌ์šฉํ•˜๋“ฏ์ด ์š”์ฒญ๊ณผ ์‘๋‹ต์— ๋Œ€ํ•œ ์ฝœ๋ฐฑํ•จ์ˆ˜๋ฅผ onRequest๋ฅผ ํ†ตํ•ด ๋ณด๋‚ผ ์ˆ˜ ์žˆ๋‹ค. ์ฐธ๊ณ ๋กœ exports.๋’ค์˜ ์ด๋ฆ„์€ ๊ธฐ๋ณธ url ๋’ค์˜ uri๋กœ ๋” ๋ถ™๊ฒŒ๋œ๋‹ค

const functions = require("firebase-functions");
exports.api = functions.region("asia-northeast3").https.onRequest();

์ด๋ ‡๊ฒŒ region ์˜ต์…˜๋„ ์ง€์ •ํ•ด์ฃผ์–ด ๋ฆฌ์ „ ์„ค์ •ํ•ด์„œ ์†๋„๋ฅผ ์กฐ๊ธˆ์ด๋ผ๋„ ์˜ฌ๋ฆด ์ˆ˜ ์žˆ๋‹ค. ๋˜ํ•œ https ํ”„๋กœํผํ‹ฐ๋ฅผ ํ†ตํ•ด onRequest ๋ฉ”์„œ๋“œ๋ฅผ ์‹คํ–‰ํ•˜๋ฉด ๋ฐฐํฌํ•  ๋•Œ https ๋กœ ๋ฐฐํฌ๋œ๋‹ค.

express + firebase functions๋กœ ์š”์ฒญ๊ณผ ์‘๋‹ต ๊ด€๋ฆฌํ•˜๊ธฐ

๊ธฐ์กด์˜ ์›น ์„œ๋ฒ„ ํ”„๋ ˆ์ž„์›Œํฌ๋ฅผ initializeํ•œ app์„ ์š”์ฒญ ํ•ธ๋“ค๋Ÿฌ์— ์ธ์ž๋กœ ๋„ฃ์œผ๋ฉด ์ „์ฒด ์•ฑ์„ HTTP ํ•จ์ˆ˜์— ์ „๋‹ฌํ•  ์ˆ˜ ์žˆ๋‹ค. ๋”ฐ๋ผ์„œ ์š”์ฒญ๊ณผ ์‘๋‹ต์„ ์›น ์„œ๋ฒ„ ํ”„๋ ˆ์ž„์›Œํฌ๋ฅผ ํ†ตํ•ด ๊ด€๋ฆฌํ•  ์ˆ˜ ์žˆ๋Š” ๊ฒƒ์ด๋‹ค.

๋‚˜๋Š” express๋ฅผ ์‚ฌ์šฉํ•ด์„œ ์š”์ฒญ๊ณผ ์‘๋‹ต์„ ์ฒ˜๋ฆฌํ•ด๋ณด๊ณ , ํด๋ผ์ด์–ธํŠธ์™€ ์„œ๋ฒ„ ๋ชจ๋‘ ํ•ด๋‹น ์„œ๋ฒ„์—์„œ ๋Œ๋ฆด ์ƒ๊ฐ์ด์—ˆ๋‹ค.

const functions = require("firebase-functions");
const path = require("path");
const { initializeApp } = require("firebase-admin/app");
const express = require("express");
const { routeManager } = require("./controller/index.js");
const cors = require("cors");
 
initializeApp();
const app = express();
 
app.use(cors());
app.use(express.static(path.join(__dirname, "dist")));
app.use(express.json());
 
routeManager(app);
app.get("/", (req, res, next) => {
  res.send("index");
});
 
exports.api = functions.region("asia-northeast3").https.onRequest(app);

์ด๋ ‡๊ฒŒ onRequest์— initializeํ•œ app์„ ์ธ์ž๋กœ ๋„ฃ์–ด์ฃผ์—ˆ๋‹ค. ๊ทธ๋ ‡๊ฒŒ ๋˜๋ฉด ํ•ด๋‹น functions๋กœ ์˜ค๋Š” http ์š”์ฒญ๋“ค์ด ๋ชจ๋‘ app์„ ํ†ตํ•˜๊ฒŒ ๋œ๋‹ค. ๋‚˜๋Š” ๊ฐ„๋‹จํ•œ ํ”„๋กœ์ ํŠธ๋ฅผ ๋งŒ๋“ค ์˜ˆ์ •์ด์—ˆ๊ธฐ ๋•Œ๋ฌธ์— ๋ฏธ๋“ค์›จ์–ด๋กœ cors์™€ ๋ฒˆ๋“ค๋งํ•  ์ •์  ํŒŒ์ผ๋“ค์— ๋Œ€ํ•œ ์„ค์ •, json ํŒŒ์‹ฑ ๋“ฑ์— ๋Œ€ํ•œ ์„ค์ •๋งŒ ํ•ด์ฃผ์—ˆ๋‹ค.

ํด๋ผ์ด์–ธํŠธ๋Š” esbuild๋ฅผ ํ†ตํ•ด ๋ฒˆ๋“ค๋งํ•˜์—ฌ dist๋ผ๋Š” ๋””๋ ‰ํ† ๋ฆฌ์— ๋„ฃ์–ด๋‘์—ˆ๊ธฐ ๋•Œ๋ฌธ์— ์ €๋ ‡๊ฒŒ ์„ค์ •ํ•ด์ฃผ์—ˆ๊ณ , ๊ธฐ๋ณธ url์— ๋Œ€ํ•ด์„œ๋Š” index.htmlํŒŒ์ผ๋งŒ์„ ๋ณด๋‚ด๋„๋ก ํ–ˆ๋‹ค. ์–ด์ฐจํ”ผ SPA๋กœ ๋งŒ๋“  ํ”„๋กœ์ ํŠธ๊ธฐ ๋•Œ๋ฌธ์— ์ €๊ฑฐ ํ•˜๋‚˜๋งŒ ์„ค์ •ํ•ด๋‘์—ˆ๋‹ค. api uri์— ํด๋ผ์ด์–ธํŠธ๊นŒ์ง€ ๋•Œ๋ ค๋ฐ•์€ ์ด์œ ๋Š” ๊ท€์ฐฎ์•„์„œ๋‹ค ์•„๋ฌดํŠผ ๋‹ค๋ฅธ ์ถ”๊ฐ€์ ์ธ uri๊ฐ€ ๋ถ™๋Š” ๊ฒƒ์— ๋Œ€ํ•ด์„œ๋Š” ๋”ฐ๋กœ functions๋ฅผ ๋‚˜๋ˆ„์ง€ ์•Š๊ณ  express์˜ Router๋ฅผ ์ด์šฉํ•ด์„œ ๊ฐ ์š”์ฒญ๊ณผ ์‘๋‹ต์„ ์ฒ˜๋ฆฌํ•ด์ฃผ์—ˆ๋‹ค. functions๋ฅผ ๋„๋ฉ”์ธ๋ณ„๋กœ ๋‚˜๋ˆ„๋Š” ๊ฒƒ๋„ ์ข‹๊ณ  ๊น”๋”ํ•œ๊ฒŒ ์ข‹์œผ๋ฉด express์—์„œ route๋งŒ ๋ณ„๋„ ํŒŒ์ผ๋กœ๋„ ๊ด€๋ฆฌํ•  ์ˆ˜ ์žˆ์–ด์„œ ์ทจํ–ฅ์ฐจ์ด์ธ ๊ฒƒ ๊ฐ™๋‹ค. ์•”ํŠผ ์ด์ •๋„ ์„ธํŒ…์„ ๊ฐ„๋‹จํ•˜๊ฒŒ๋งŒ ํ•ด์ฃผ๊ณ  deploy โ€˜๋”ธ๊นโ€™ ํ•˜๋ฉด ๋ฐฐํฌ๋„ ์ฒ™์ฒ™ ์ž˜๋œ๋‹ค.

์ฐธ๊ณ ๋กœ ๋ฐฐํฌํ•˜๋ ค๋ฉด ๋ฌด๋ฃŒ ์š”๊ธˆ์ œ(spark) ์š”๊ธˆ์ œ์—์„œ Blaze ์š”๊ธˆ์ œ๋กœ ๋ฐ”๊ฟ”์•ผ ํ•œ๋‹ค. ๊ทธ๋ž˜๋„ ์•ต๊ฐ„ํ•œ ๋งŒํผ์€ ๊ณต์งœ๋กœ ์“ธ ์ˆ˜ ์žˆ์œผ๋‹ˆ ์•ผ๋ฌด์ง€๊ฒŒ ์จ๋จน์ž

์•„๋ฌดํŠผ โ€˜๋”ธ๊นโ€™๋งŒ์œผ๋กœ ๊ด€๋ฆฌํ•  ํ•„์š”๊ฐ€ ์—†์–ด์ง€๋Š” ์„œ๋ฒ„๋ฆฌ์Šค๋Š” ํ”„๋ก ํŠธ์ธ ๋‚˜์—๊ฒŒ ๊ทธ์ € ๋น›โ€ฆ

firestore ์„ค์ •๊ณผ ์•Œ๋ฉด ์ข‹์„ ๊ฒƒ๋“ค

์ฒ˜์Œ ์ดˆ๊ธฐ ์„ธํŒ…ํ•˜๋Š” ๊ณผ์ •์—์„œ functions ์„ธํŒ…์„ ํ•˜๊ณ  firestore ์„ธํŒ…๋„ ๋”ฐ๋กœ ํ•  ์ˆ˜ ์žˆ๋‹ค. http ์š”์ฒญ์— ๋”ฐ๋ฅธ CRUD๋„ express์—์„œ firestore์— ์ ‘๊ทผํ•˜์—ฌ ์กฐ์ž‘ํ•˜๋ฉด ๋œ๋‹ค.

const admin = require("firebase-admin");
async function getUserList() {
  try {
    const data = await admin.firestore().collection("todo").get();
    return data.docs.map((doc) => new User(doc.data());
  } catch (error) {
    throw new Error(error);
  }
}

admin์˜ firestore ๋ฉ”์„œ๋“œ๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ํ•ด๋‹น ํ”„๋กœ์ ํŠธ์— ์—ฐ๊ฒฐ๋˜์–ด ์žˆ๋Š” firestore์— ์ ‘๊ทผํ•˜์—ฌ CRUD๋ฅผ ์ˆ˜ํ–‰ํ•  ์ˆ˜ ์žˆ๋‹ค.

const cityRef = db.collection('cities').doc('SF');
const doc = await cityRef.get();
if (!doc.exists) {
  console.log('No such document!');
} else {
  console.log('Document data:', doc.data());
}

์ค‘์š”ํ•œ ์ ์€ ๊ฐœ๋ณ„ ๋ฐ์ดํ„ฐ ๊ฐ์ฒด์— ๋Œ€ํ•ด์„œ data() ๋ฉ”์„œ๋“œ๋ฅผ ์‚ฌ์šฉํ•ด์•ผ๋งŒ ๋‚ด๊ฐ€ ์›ํ•˜๋Š” ๋ฐ์ดํ„ฐ๊ฐ€ ๋‚˜์˜จ๋‹ค

functions ์—†์ด firestore๋งŒ ์‚ฌ์šฉํ•˜๊ธฐ

๋”ฐ๋กœ functions ์—†์ด firestore์—๋งŒ ์ ‘๊ทผํ•˜๊ณ  ์‹ถ๋‹ค๋ฉด firebase SDK๋ฅผ ๋”ฐ๋กœ ์„ค์ •ํ•œ ๋’ค์— ์„œ๋ฒ„๋ฅผ ํ‚ค๋ฉด์„œ ์ดˆ๊ธฐํ™” ์‹œ์ผœ์ฃผ๋ฉด ๋œ๋‹ค.

firebase ์ฝ˜์†” โ†’ ํ”„๋กœ์ ํŠธ ์„ค์ • โ†’ ์•„๋ž˜ ๋‚ด ์•ฑ โ†’ SDK ์„ค์ • ๋ฐ ๊ตฌ์„ฑ์—์„œ ๋‚ด firebase config๋ฅผ ๋ณผ ์ˆ˜ ์žˆ๋‹ค.

npm install firebase

firebase ๋”ฐ๋กœ ์„ค์น˜ํ•˜๊ณ 

// Import the functions you need from the SDKs you need
import { initializeApp } from "firebase/app";
import { getAnalytics } from "firebase/analytics";
// TODO: Add SDKs for Firebase products that you want to use
// https://firebase.google.com/docs/web/setup#available-libraries
 
// Your web app's Firebase configuration
// For Firebase JS SDK v7.20.0 and later, measurementId is optional
const firebaseConfig = {
  apiKey: "APIํ‚ค",
  authDomain: "๋„๋ฉ”์ธ",
  projectId: "ํ”„๋กœ์ ํŠธID",
  storageBucket: "์Šคํ† ๋ฆฌ์ง€๋ฒ„ํ‚ท",
  messagingSenderId: "๋ฉ”์‹œ์ง€ ์„ผ๋”ID",
  appId: "์•ฑ์•„์ด๋””",
  measurementId: "์ธก์ •ID"
};
 
// Initialize Firebase
const app = initializeApp(firebaseConfig);
const analytics = getAnalytics(app);

์ด๋ ‡๊ฒŒ InitializeAppํ•˜๊ณ  ์‚ฌ์šฉํ•˜๋ฉด ๋œ๋‹ค.

์ฟผ๋ฆฌ

where๋ฌธ์„ ์‚ฌ์šฉํ•ด์„œ ๋‹จ์ผ ํ˜น์€ ์—ฌ๋Ÿฌ๊ฐœ ๊ฐ€์ ธ์˜ค๊ธฐ

// Create a reference to the cities collection
const citiesRef = db.collection('cities');

// Create a query against the collection
const queryRef = citiesRef.where('state', '==', 'CA');
const citiesRef = db.collection('cities');
const snapshot = await citiesRef.where('capital', '==', true).get();
if (snapshot.empty) {
  console.log('No matching documents.');
  return;
}  
 
snapshot.forEach(doc => {
  console.log(doc.id, '=>', doc.data());
});

where๋ฌธ์„ ์‚ฌ์šฉํ•˜๊ฒŒ ๋˜๋ฉด ์ฟผ๋ฆฌ ๊ฐ์ฒด๋ฅผ ๊ฐ€์ ธ์˜ค๊ฒŒ ๋˜๊ณ , get()์„ ํ†ตํ•ด ๊ฒฐ๊ณผ๋ฅผ ๊ฒ€์ƒ‰ํ•œ ๊ฒฐ๊ณผ๋ฅผ ๋ฐ›์•„์˜ฌ ์ˆ˜ ์žˆ๋‹ค. ๊ฐ€์ ธ์˜จ ๊ฐ์ฒด๋Š” QueryDocumentSnapshot๊ฐ์ฒด๋กœ, ์—ฌ๋Ÿฌ ๊ฐœ์˜ ํ”„๋กœํผํ‹ฐ๋ฅผ ๊ฐ€์ง€๊ณ  ์žˆ๊ธฐ ๋•Œ๋ฌธ์— ์šฐ๋ฆฌ๊ฐ€ ํ•„์š”ํ•œ ๋ฐ์ดํ„ฐ๋งŒ ๊ฐ€์ ธ์˜ค๊ธฐ ์œ„ํ•ด์„œ๋Š” data() ๋ฉ”์„œ๋“œ๋ฅผ ํ†ตํ•ด ๋ฐ˜ํ™˜๋œ ๋ฐ์ดํ„ฐ๋ฅผ ์ด์šฉํ•ด์•ผ ํ•œ๋‹ค.

where()ย ๋ฉ”์„œ๋“œ๋Š” ํ•„ํ„ฐ๋งํ•  (ํ•„๋“œ, ๋น„๊ต ์—ฐ์‚ฐ์ž, ๊ฐ’)์˜ 3๊ฐ€์ง€ ๋งค๊ฐœ๋ณ€์ˆ˜๋ฅผ ์‚ฌ์šฉํ•œ๋‹ค. ์—ฐ์‚ฐ์ž๋Š”

  • <ย : ๋ฏธ๋งŒ
  • <=ย : ์ž‘๊ฑฐ๋‚˜ ๊ฐ™์Œ
  • ==ย : ๊ฐ™์Œ
  • > :ย ๋ณด๋‹ค ํผ
  • >= :ย ์ด์ƒ
  • !=ย : ๊ฐ™์ง€ ์•Š์Œ
  • array-contains : ํฌํ•จํ•˜๋Š” ๋ฐฐ์—ด
  • array-contains-any : ๋ฐฐ์—ด ์•ˆ์˜ ๊ฒƒ๋“ค๊ณผ ์ผ์น˜ํ•˜๋Š” ๋ชจ๋“  ๋ ˆ์ฝ”๋“œ
  • in : ์ง€์ •๋œ ํ•„๋“œ๊ฐ€ ๋น„๊ต๊ฐ’๊ณผ ์ผ์น˜ํ•˜๋Š” ๋ ˆ์ฝ”๋“œ
  • not-in : ๋™์ผํ•œ ํ•„๋“œ์—์„œ ๊ฐ™์ง€ ์•Š์€ ์—์ฝ”๋“œ
const stateQueryRes = await citiesRef.where('state', '==', 'CA').get();
const populationQueryRes = await citiesRef.where('population', '<', 1000000).get();
const nameQueryRes = await citiesRef.where('name', '>=', 'San Francisco').get();

๋ณตํ•ฉ์ฟผ๋ฆฌ(And)

==๋‚˜ array-contains๋ฅผ ์—ฐ๊ฒฐํ•˜์—ฌ ์ œ์•ฝ ์กฐ๊ฑด์„ AND์™€ ๊ฒฐํ•ฉ

citiesRef.where('state', '==', 'CO').where('name', '==', 'Denver');
citiesRef.where('state', '==', 'CA').where('population', '<', 1000000);

OR ์ฟผ๋ฆฌ ๋งŒ๋“ค๊ธฐ

์ œ์•ฝ ์กฐ๊ฑด์„ ๋…ผ๋ฆฌ์  OR๊ณผ ๊ฒฐํ•ฉ

const bigCities = await citiesRef
  .where(
    Filter.or(
      Filter.where('capital', '==', true),
      Filter.where('population', '>=', 1000000)
    )
  )
  .get();

orderBy ๋ฐ ์กด์žฌ ์—ฌ๋ถ€

db.collection("cities").whereEqualTo("country", โ€œUSAโ€).orderBy(โ€œpopulationโ€);

ํ•ด๋‹น ์ฟผ๋ฆฌ์— ๋Œ€ํ•ด์„œ ์ •๋ ฌ ๊ธฐ์ค€์„ ์žก๊ณ  ์ •๋ ฌ orderBy ์•ˆ์— ๋“ค์–ด์žˆ๋Š” ํ•„๋“œ๊ฐ€ ์—†์œผ๋ฉด ๋ฐ˜ํ™˜ํ•˜์ง€ ์•Š์Œ

Firestore CRUD

Create

function addTodo(req) {
  const id = crypto.randomUUID();
  const created_at = new Date().toISOString();
  return admin.firestore().collection("todo").add({
    id,
    detail: req.body.detail,
    created_at,
  });
}

๋„ฃ์œผ๋ ค๋Š” ๋„๋ฉ”์ธ์— ๋Œ€ํ•ด์„œ add๋ฉ”์„œ๋“œ๋ฅผ ํ†ตํ•ด ๊ฐ์ฒด๋ฅผ ๊ทธ๋Œ€๋กœ ๋„ฃ์–ด์ค„ ์ˆ˜ ์žˆ๋‹ค. NOSQL์ธ ๋งŒํผ ๋ฐ์ดํ„ฐ๋ฅผ ๋ณ€ํ˜•๋˜์ง€ ์•Š๋„๋ก ์‹ ๊ฒฝ์จ์•ผ ํ•  ๊ฒƒ ๊ฐ™๋‹ค.

Read

const citiesRef = db.collection('cities');
const snapshot = await citiesRef.where('capital', '==', true).get();
if (snapshot.empty) {
  console.log('No matching documents.');
  return;
}  
 
snapshot.forEach(doc => {
  console.log(doc.id, '=>', doc.data());
});

์•„๊นŒ ๋ดค๋˜ ์˜ˆ์‹œ์ฒ˜๋Ÿผ get()์„ ํ†ตํ•ด ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ€์ ธ์˜ค๊ณ  ๊ฐœ๋ณ„ ๋ฐ์ดํ„ฐ์— ๋Œ€ํ•ด์„œ data()๋ฅผ ํ†ตํ•ด ๋‚ด๊ฐ€ ์›ํ•˜๋Š” ๋ฐ์ดํ„ฐ๋งŒ ๋ฐ˜ํ™˜๋ฐ›์ž

Update

async function updateTodo(req) {
  try {
    console.log(req.body);
    const id = req.body.id;
    const detailToUpdate = req.body.detail;
    const todoToUpdate = await admin
      .firestore()
      .collection("todo")
      .where("id", "==", id)
      .limit(1)
      .get();
    if (todoToUpdate.empty) {
      throw new Error("updateํ•  todo๋ฅผ ์ฐพ์ง€ ๋ชปํ–ˆ์Šต๋‹ˆ๋‹ค.");
    }
    const todo = new Todo(cardToUpdate.docs[0].data());
    todo.update(detailToUpdate);
    return cardToUpdate.docs[0].ref.update(todo);
  } catch (error) {
    throw new Error("์˜ˆ์ƒ์น˜ ๋ชปํ•œ Error๊ฐ€ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค.");
  }
}

๋ฐ์ดํ„ฐ๋ฅผ ์ฐพ์•„์™€์„œ, ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐฑ์‹ ํ•œ ๋‹ค์Œ update()๋ฉ”์„œ๋“œ๋ฅผ ํ†ตํ•ด ์ƒˆ๋กญ๊ฒŒ ๊ต์ฒดํ•  ๊ฐ์ฒด๋ฅผ ๋„ฃ์–ด์ฃผ๋ฉด ํ†ต์œผ๋กœ ๋ฐ”๋€๋‹ค

Delete

async function deleteTodo(req) {
  try {
    const id = req.params.id;
    const cardToDelete = await admin
      .firestore()
      .collection("todo")
      .where("id", "==", id)
      .limit(1)
      .get();
    console.log("???", cardToDelete.docs[0]);
    if (cardToDelete.empty) {
      throw new Error("์‚ญ์ œํ•  todo๋ฅผ ์ฐพ์ง€ ๋ชปํ–ˆ์Šต๋‹ˆ๋‹ค.");
    }
    return cardToDelete.docs[0].ref.delete();
  } catch (error) {
    throw new Error(error);
  }
}

์›ํ•˜๋Š” ๋ฐ์ดํ„ฐ์— ๋Œ€ํ•ด์„œ ์ฐพ๊ณ  ref property์˜ delete() ๋ฉ”์„œ๋“œ๋ฅผ ํ†ตํ•ด ์‚ญ์ œํ•  ์ˆ˜ ์žˆ๋‹ค.