Firebase 용 Cloud Functions를 구성하여 여러 파일에서 여러 기능을 배포하려면 어떻게해야하나요?
Firebase 용 Cloud 기능을 여러 개 만들고 한 프로젝트에서 동시에 모두 배포하고 싶습니다. 또한 각 기능을 별도의 파일로 분리하고 싶습니다. 현재 index.js에 둘 다 넣으면 여러 함수를 만들 수 있습니다.
exports.foo = functions.database.ref('/foo').onWrite(event => {
...
});
exports.bar = functions.database.ref('/bar').onWrite(event => {
...
});
그러나 foo와 bar를 별도의 파일에 넣고 싶습니다. 나는 이것을 시도했다 :
/functions
|--index.js (blank)
|--foo.js
|--bar.js
|--package.json
foo.js는
exports.foo = functions.database.ref('/foo').onWrite(event => {
...
});
bar.js는
exports.bar = functions.database.ref('/bar').onWrite(event => {
...
});
index.js에 모든 기능을 넣지 않고이를 수행 할 수있는 방법이 있습니까?
Ah, Firebase로드 노드 모듈 용 Cloud Functions는 정상적으로 작동하므로
구조:
/functions
|--index.js
|--foo.js
|--bar.js
|--package.json
index.js :
const functions = require('firebase-functions');
const fooModule = require('./foo');
const barModule = require('./bar');
exports.foo = functions.database.ref('/foo').onWrite(fooModule.handler);
exports.bar = functions.database.ref('/bar').onWrite(barModule.handler);
foo.js :
exports.handler = (event) => {
...
};
bar.js :
exports.handler = (event) => {
...
};
@jasonsirota의 답변은 매우 도움이되었습니다. 그러나 특히 HTTP 트리거 함수의 경우 더 자세한 코드를 보는 것이 유용 할 수 있습니다.
@jasonsirota의 답변과 동일한 구조를 사용하여 두 개의 다른 파일에 두 개의 별도의 HTTP 트리거 기능을 원한다고 가정 해보십시오.
디렉토리 구조 :
/functions
|--index.js
|--foo.js
|--bar.js
|--package.json`
index.js :
'use strict';
const fooFunction = require('./foo');
const barFunction = require('./bar');
// Note do below initialization tasks in index.js and
// NOT in child functions:
const functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp(functions.config().firebase);
const database = admin.database();
// Pass database to child functions so they have access to it
exports.fooFunction = functions.https.onRequest((req, res) => {
fooFunction.handler(req, res, database);
});
exports.barFunction = functions.https.onRequest((req, res) => {
barFunction.handler(req, res, database);
});
foo.js :
exports.handler = function(req, res, database) {
// Use database to declare databaseRefs:
usersRef = database.ref('users');
...
res.send('foo ran successfully');
}
bar.js :
exports.handler = function(req, res, database) {
// Use database to declare databaseRefs:
usersRef = database.ref('users');
...
res.send('bar ran successfully');
}
업데이트 : 이 문서가 도움이되어야합니다 . 제 답변 이이 문서보다 깁니다.
다음은 타이프 스크립트로 개인적으로 수행 한 방법입니다.
/functions
|--src
|--index.ts
|--http-functions.ts
|--main.js
|--db.ts
|--package.json
|--tsconfig.json
이 작업을 수행하기 위해 두 가지 경고를 제공하여이 서문을 시작하겠습니다.
- index.ts 의 가져 오기 / 내보내기 순서
- db는 별도의 파일이어야합니다
포인트 2의 경우 왜 그런지 잘 모르겠습니다. Secundo 당신은 index, main 및 db의 구성을 정확히 존중해야 합니다 (적어도 시도해보십시오).
index.ts : 내보내기를 처리합니다. index.ts가 내보내기를 처리하는 것이 더 깨끗하다는 것을 알았습니다.
// main must be before functions
export * from './main';
export * from "./http-functions";
main.ts : 초기화를 다룹니다.
import { config } from 'firebase-functions';
import { initializeApp } from 'firebase-admin';
initializeApp(config().firebase);
export * from "firebase-functions";
db.ts : db를 다시 내 보내면 이름이 짧아집니다.database()
import { database } from "firebase-admin";
export const db = database();
http-functions.ts
// db must be imported like this
import { db } from './db';
// you can now import everything from index.
import { https } from './index';
// or (both work)
// import { https } from 'firebase-functions';
export let newComment = https.onRequest(createComment);
export async function createComment(req: any, res: any){
db.ref('comments').push(req.body.comment);
res.send(req.body.comment);
}
Cloud / Firebase 기능과 함께 노드 8 LTS를 사용할 수있게되면 스프레드 연산자를 사용하여 다음을 수행 할 수 있습니다.
/package.json
"engines": {
"node": "8"
},
/index.js
const functions = require("firebase-functions");
const admin = require("firebase-admin");
admin.initializeApp();
module.exports = {
...require("./lib/foo.js"),
// ...require("./lib/bar.js") // add as many as you like
};
/lib/foo.js
const functions = require("firebase-functions");
const admin = require("firebase-admin");
exports.fooHandler = functions.database
.ref("/food/{id}")
.onCreate((snap, context) => {
let id = context.params["id"];
return admin
.database()
.ref(`/bar/${id}`)
.set(true);
});
가진 경우에는 바벨 / 흐름 은 다음과 같을 것이다 :
디렉토리 레이아웃
.
├── /build/ # Compiled output for Node.js 6.x
├── /src/ # Application source files
│ ├── db.js # Cloud SQL client for Postgres
│ ├── index.js # Main export(s)
│ ├── someFuncA.js # Function A
│ ├── someFuncA.test.js # Function A unit tests
│ ├── someFuncB.js # Function B
│ ├── someFuncB.test.js # Function B unit tests
│ └── store.js # Firebase Firestore client
├── .babelrc # Babel configuration
├── firebase.json # Firebase configuration
└── package.json # List of project dependencies and NPM scripts
src/index.js
-주요 수출
export * from './someFuncA.js';
export * from './someFuncB.js';
src/db.js
-Postgres 용 Cloud SQL 클라이언트
import { Pool } from 'pg';
import { config } from 'firebase-functions';
export default new Pool({
max: 1,
user: '<username>',
database: '<database>',
password: config().db.password,
host: `/cloudsql/${process.env.GCP_PROJECT}:<region>:<instance>`,
});
src/store.js
-Firebase Firestore 클라이언트
import firebase from 'firebase-admin';
import { config } from 'firebase-functions';
firebase.initializeApp(config().firebase);
export default firebase.firestore();
src/someFuncA.js
-기능 A
import { https } from 'firebase-functions';
import db from './db';
export const someFuncA = https.onRequest(async (req, res) => {
const { rows: regions } = await db.query(`
SELECT * FROM regions WHERE country_code = $1
`, ['US']);
res.send(regions);
});
src/someFuncB.js
-기능 B
import { https } from 'firebase-functions';
import store from './store';
export const someFuncB = https.onRequest(async (req, res) => {
const { docs: regions } = await store
.collection('regions')
.where('countryCode', '==', 'US')
.get();
res.send(regions);
});
.babelrc
{
"presets": [["env", { "targets": { "node": "6.11" } }]],
}
firebase.json
{
"functions": {
"source": ".",
"ignore": [
"**/node_modules/**"
]
}
}
package.json
{
"name": "functions",
"verson": "0.0.0",
"private": true,
"main": "build/index.js",
"dependencies": {
"firebase-admin": "^5.9.0",
"firebase-functions": "^0.8.1",
"pg": "^7.4.1"
},
"devDependencies": {
"babel-cli": "^6.26.0",
"babel-core": "^6.26.0",
"babel-jest": "^22.2.2",
"babel-preset-env": "^1.6.1",
"jest": "^22.2.2"
},
"scripts": {
"test": "jest --env=node",
"predeploy": "rm -rf ./build && babel --out-dir ./build src",
"deploy": "firebase deploy --only functions"
}
}
$ yarn install # Install project dependencies
$ yarn test # Run unit tests
$ yarn deploy # Deploy to Firebase
간단하게 유지하기 위해 (하지만 작업을 수행) 개인적으로 코드를 이와 같이 구성했습니다.
나열한 것
├── /src/
│ ├── index.ts
│ ├── foo.ts
│ ├── bar.ts
└── package.json
foo.ts
export const fooFunction = functions.database()......... {
//do your function.
}
export const someOtherFunction = functions.database().......... {
// do the thing.
}
bar.ts
export const barFunction = functions.database()......... {
//do your function.
}
export const anotherFunction = functions.database().......... {
// do the thing.
}
index.ts
import * as fooFunctions from './foo';
import * as barFunctions from './bar';
module.exports = {
...fooFunctions,
...barFunctions,
};
중첩 레벨의 디렉토리에서 작동합니다. 디렉토리 내부의 패턴도 따르십시오.
간단하게 유지하기 위해 (하지만 작업을 수행) 개인적으로 코드를 이와 같이 구성했습니다.
나열한 것
├── /src/
│ ├── index.ts
│ ├── foo.ts
│ ├── bar.ts
| ├── db.ts
└── package.json
foo.ts
import * as functions from 'firebase-functions';
export const fooFunction = functions.database()......... {
//do your function.
}
export const someOtherFunction = functions.database().......... {
// do the thing.
}
bar.ts
import * as functions from 'firebase-functions';
export const barFunction = functions.database()......... {
//do your function.
}
export const anotherFunction = functions.database().......... {
// do the thing.
}
db.ts
import * as admin from 'firebase-admin';
import * as functions from 'firebase-functions';
export const firestore = admin.firestore();
export const realtimeDb = admin.database();
index.ts
import * as admin from 'firebase-admin';
import * as functions from 'firebase-functions';
admin.initializeApp(functions.config().firebase);
// above codes only needed if you use firebase admin
export * from './foo';
export * from './bar';
중첩 레벨의 디렉토리에서 작동합니다. 디렉토리 내부의 패턴도 따르십시오.
@zaidfazil 답변에 신용
이 형식을 사용하면 진입 점에서 추가 기능 파일을 찾고 각 파일 내에서 각 기능을 자동으로 내보낼 수 있습니다.
기본 진입 점 스크립트
함수 폴더 내에서 모든 .js 파일을 찾아 각 파일에서 내 보낸 각 함수를 내 보냅니다.
const fs = require('fs');
const path = require('path');
// Folder where all your individual Cloud Functions files are located.
const FUNCTIONS_FOLDER = './scFunctions';
fs.readdirSync(path.resolve(__dirname, FUNCTIONS_FOLDER)).forEach(file => { // list files in the folder.
if(file.endsWith('.js')) {
const fileBaseName = file.slice(0, -3); // Remove the '.js' extension
const thisFunction = require(`${FUNCTIONS_FOLDER}/${fileBaseName}`);
for(var i in thisFunction) {
exports[i] = thisFunction[i];
}
}
});
한 파일에서 여러 함수를 내보내는 예제
const functions = require('firebase-functions');
const query = functions.https.onRequest((req, res) => {
let query = req.query.q;
res.send({
"You Searched For": query
});
});
const searchTest = functions.https.onRequest((req, res) => {
res.send({
"searchTest": "Hi There!"
});
});
module.exports = {
query,
searchTest
}
http 액세스 가능한 엔드 포인트는 적절하게 이름이 지정됩니다
✔ functions: query: http://localhost:5001/PROJECT-NAME/us-central1/query
✔ functions: helloWorlds: http://localhost:5001/PROJECT-NAME/us-central1/helloWorlds
✔ functions: searchTest: http://localhost:5001/PROJECT-NAME/us-central1/searchTest
하나의 파일
몇 개의 추가 파일 (예 : 하나만)이있는 경우 다음을 사용할 수 있습니다.
const your_functions = require('./path_to_your_functions');
for (var i in your_functions) {
exports[i] = your_functions[i];
}
장기적으로 모든 클라우드 기능을 구성하는 좋은 방법이 있습니다. 나는 최근에 이것을했고 완벽하게 작동하고 있습니다.
내가 한 것은 트리거 엔드 포인트를 기반으로 각 클라우드 기능을 별도의 폴더로 구성하는 것이 었습니다. 모든 클라우드 함수 파일 이름은로 끝납니다 *.f.js
. 예를 들어, 가지고 onCreate
있고 onUpdate
트리거 user/{userId}/document/{documentId}
하면 두 개의 파일을 작성 onCreate.f.js
하고 onUpdate.f.js
디렉토리에 functions/user/document/
함수의 이름 userDocumentOnCreate
을 userDocumentOnUpdate
각각 지정합니다. (1)
다음은 샘플 디렉토리 구조입니다.
functions/
|----package.json
|----index.js
/----user/
|-------onCreate.f.js
|-------onWrite.f.js
/-------document/
|------------onCreate.f.js
|------------onUpdate.f.js
/----books/
|-------onCreate.f.js
|-------onUpdate.f.js
|-------onDelete.f.js
샘플 기능
const functions = require('firebase-functions');
const admin = require('firebase-admin');
const db = admin.database();
const documentsOnCreate = functions.database
.ref('user/{userId}/document/{documentId}')
.onCreate((snap, context) => {
// your code goes here
});
exports = module.exports = documentsOnCreate;
Index.js
const glob = require("glob");
const camelCase = require('camelcase');
const admin = require('firebase-admin');
const serviceAccount = require('./path/to/ServiceAccountKey.json');
try {
admin.initializeApp({ credential: admin.credential.cert(serviceAccount),
databaseURL: "Your database URL" });
} catch (e) {
console.log(e);
}
const files = glob.sync('./**/*.f.js', { cwd: __dirname });
for (let f = 0, fl = files.length; f < fl; f++) {
const file = files[f];
const functionName = camelCase(file.slice(0, -5).split('/'));
if (!process.env.FUNCTION_NAME || process.env.FUNCTION_NAME === functionName) {
exports[functionName] = require(file);
}
}
(1) : 원하는 이름을 사용할 수 있습니다. 나에게 onCreate.f.js, onUpdate.f.js 등은 트리거의 종류와 더 관련이있는 것 같습니다.
백그라운드 기능과 http 기능이있는이 프로젝트가 있습니다. 단위 테스트를위한 테스트도 있습니다. 클라우드 기능을 배포 할 때 CI / CD를 사용하면 훨씬 쉽게 생활 할 수 있습니다.
폴더 구조
|-- package.json
|-- cloudbuild.yaml
|-- functions
|-- index.js
|-- background
| |-- onCreate
| |-- index.js
|-- create.js
|
|-- http
| |-- stripe
| |-- index.js
| |-- payment.js
|-- utils
|-- firebaseHelpers.js
|-- test
|-- ...
|-- package.json
참고 : utils/
폴더는 기능 간 공유 코드입니다
함수 /index.js
여기서 필요한 모든 함수를 가져 와서 선언 할 수 있습니다. 여기에 논리가 없어도됩니다. 내 의견으로는 더 깨끗합니다.
require('module-alias/register');
const functions = require('firebase-functions');
const onCreate = require('@background/onCreate');
const onDelete = require('@background/onDelete');
const onUpdate = require('@background/onUpdate');
const tours = require('@http/tours');
const stripe = require('@http/stripe');
const docPath = 'tours/{tourId}';
module.exports.onCreate = functions.firestore.document(docPath).onCreate(onCreate);
module.exports.onDelete = functions.firestore.document(docPath).onDelete(onDelete);
module.exports.onUpdate = functions.firestore.document(docPath).onUpdate(onUpdate);
module.exports.tours = functions.https.onRequest(tours);
module.exports.stripe = functions.https.onRequest(stripe);
CI / CD
변경 사항을 리포지토리로 푸시 할 때마다 지속적인 통합 및 배포는 어떻습니까? google google cloud build를 사용하여 사용할 수 있습니다 . 특정 시점까지 무료입니다 :)이 링크를 확인하십시오 .
./cloudbuild.yaml
steps:
- name: "gcr.io/cloud-builders/npm"
args: ["run", "install:functions"]
- name: "gcr.io/cloud-builders/npm"
args: ["test"]
- name: "gcr.io/${PROJECT_ID}/firebase"
args:
[
"deploy",
"--only",
"functions",
"-P",
"${PROJECT_ID}",
"--token",
"${_FIREBASE_TOKEN}"
]
substitutions:
_FIREBASE_TOKEN: nothing
vanilla JS 부트 로더를 사용하여 사용하려는 모든 기능을 자동으로 포함시킵니다.
├── /functions
│ ├── /test/
│ │ ├── testA.js
│ │ └── testB.js
│ ├── index.js
│ └── package.json
index.js (부트 로더)
/**
* The bootloader reads all directories (single level, NOT recursively)
* to include all known functions.
*/
const functions = require('firebase-functions');
const fs = require('fs')
const path = require('path')
fs.readdirSync(process.cwd()).forEach(location => {
if (!location.startsWith('.')) {
location = path.resolve(location)
if (fs.statSync(location).isDirectory() && path.dirname(location).toLowerCase() !== 'node_modules') {
fs.readdirSync(location).forEach(filepath => {
filepath = path.join(location, filepath)
if (fs.statSync(filepath).isFile() && path.extname(filepath).toLowerCase() === '.js') {
Object.assign(exports, require(filepath))
}
})
}
}
})
이 예제 index.js 파일은 루트 내의 디렉토리 만 자동으로 포함합니다. 디렉토리를 걷고, .gitignore 등을 존중하도록 확장 될 수 있습니다. 이것은 나에게 충분했습니다.
인덱스 파일이 있으면 새 기능을 추가하는 것이 쉽지 않습니다.
/test/testA.js
const functions = require('firebase-functions');
exports.helloWorld = functions.https.onRequest((request, response) => {
response.send("Hello from Firebase!");
});
/test/testB.js
const functions = require('firebase-functions');
exports.helloWorld2 = functions.https.onRequest((request, response) => {
response.send("Hello again, from Firebase!");
});
npm run serve
수율 :
λ ~/Workspace/Ventures/Author.io/Firebase/functions/ npm run serve
> functions@ serve /Users/cbutler/Workspace/Ventures/Author.io/Firebase/functions
> firebase serve --only functions
=== Serving from '/Users/cbutler/Workspace/Ventures/Author.io/Firebase'...
i functions: Preparing to emulate functions.
Warning: You're using Node.js v9.3.0 but Google Cloud Functions only supports v6.11.5.
✔ functions: helloWorld: http://localhost:5000/authorio-ecorventures/us-central1/helloWorld
✔ functions: helloWorld2: http://localhost:5000/authorio-ecorventures/us-central1/helloWorld2
이 워크 플로우는 새로운 함수 / 파일이 추가 / 수정 / 제거 될 때마다 index.js 파일을 수정하지 않고도 "쓰기 및 실행"과 거의 비슷합니다.
bigcodenerd.org 개요는 메소드를 다른 파일로 분리 하고 index.js 파일 내에서 한 줄로 내보내도록하기 위해 더 간단한 아키텍처 패턴입니다 .
이 샘플에서 프로젝트의 아키텍처는 다음과 같습니다.
projectDirectory
- index.js
- podcast.js
- profile.js
index.js
const admin = require('firebase-admin');
const podcast = require('./podcast');
const profile = require('./profile');
admin.initializeApp();
exports.getPodcast = podcast.getPodcast();
exports.removeProfile = profile.removeProfile();
podcast.js
const functions = require('firebase-functions');
exports.getPodcast = () => functions.https.onCall(async (data, context) => {
...
return { ... }
});
프로파일 파일 의 removeProfile
메소드에 동일한 패턴이 사용 됩니다.
나는 똑같은 것을 찾는 데 많은 시간을 보냈고 그것을 달성하는 가장 좋은 방법이라고 생각합니다 (firebase@7.3.0을 사용하고 있습니다).
https://codeburst.io/organizing-your-firebase-cloud-functions-67dc17b3b0da
땀 없습니다 ;)
'Programing' 카테고리의 다른 글
상태 비 저장 프로그래밍의 장점? (0) | 2020.07.06 |
---|---|
PostgreSQL 9.2 pg_dump 버전 불일치 (0) | 2020.07.06 |
Rails CSRF Protection + Angular.js : protect_from_forgery는 POST에서 로그 아웃하도록합니다 (0) | 2020.07.05 |
IOS7 : UINavigationController의 UIScrollView 오프셋 (0) | 2020.07.05 |
PHP를 사용하여 페이지 새로 고침 (0) | 2020.07.05 |