Hegwin.Me

寓形宇内复几时?曷不委心任去留?胡为乎遑遑欲何之?

谷歌开发者大会2017心得:Firebase 和 Serverless

Takeaways from Google Developer Days 2017: Firebase and Serverless

今天是谷歌开发者大会(GDG, Google Developer Days)在上海举办的第一天。由于GDG是在工作日举办,因此还不得不向公司告了假才能来参加。这届的GDG有非常多让人兴奋的议题。我打算写几篇文章介绍我在 GDG 2017 的收获(如果我偷懒了,可能也只有这一篇)。

这一篇文章的主题是 Firebase 和 Serverless。

在此之前,我对 Firebase 稍有了解,我们的 Quesbook 项目中有一个在线课堂,课堂上的实时聊天内容是使用 Firebase 存储的。这让我对 Firebase 有一个误解, 我以为它就单纯是一个实时数据库。但是,这次在 GDG 上,Firebase 让我大为惊叹 —— 它是一个全套的Serverless构解决方案,不仅有 Real-time Database,还有 Storage, Cloud Functions 和 Authentication ,让人可以不需要自己部署代码到服务器,就可以从0到1实现自己的应用。

GDG 上的例子

在下午的会议上,有两位演示者给我们介绍了用 Firebase 实现一个应用。这个应用可以将说话的人的语音录下来,然后再翻译成其他语言的文字展示给别人。这里面使用了 Firebase 的 Storage (语音文件和转录的文件)和Cloud Functions(Sound to Text 和翻译),只用了几十行代码,就实现了所有的必要功能,让人非常惊讶。

GDG-2017-Firebase.jpg

上面这幅图介绍了这个应用的架构,前端App通过SDK调用部署在 Firebase 定义的functions,比如上传语音文件,将其存储到 Storage。同时 Firebase 会监听文件的存储,当有语音文件写入后,就会调用 Cloud Functions 将其转化成文字再存储,而这又触发了另一个 function,它会将文字翻译成目标语言。

在这个架构中,我们可以看到,里面已经没有了server的角色——这便是serverless的由来。

整个代码也非常简单,以下代码都是在 functions/index.js 中:

首先,是引用需要的package:

const functions = require('firebase-functions');
const Speech = require('@google-cloud/speech');
const speech = Speech({keyFilename: "service-account-credentials.json"});
const Translate = require('@google-cloud/translate');
const translate = Translate({keyFilename: "service-account-credentials.json"});
const Encoding = Speech.v1.types.RecognitionConfig.AudioEncoding;

其次,是当语音文件写入时,它会触发下面这个 onUpload 事件,将文件读取出来,然后调用 Cloud Function: Speech 将其转化成 transcript 并写回 Storage:

exports.onUpload = functions.database
    .ref("/uploads/{uploadId}")
    .onWrite((event) => {
        let data = event.data.val();
        let language = data.language ? data.language : "en";
        let sampleRate = data.sampleRate ? data.sampleRate : 16000;
        let encoding = data.encoding == "FLAC" ? Encoding.FLAC : Encoding.AMR;

        let request = {
            config: {
                languageCode : language,
                sampleRateHertz : sampleRate,
                encoding : encoding
            },
            audio: { uri : `gs://mimming-babelfire.appspot.com/${data.fullPath}` }
        };

        return speech.recognize(request).then((response) => {
            let transcript = response[0].results[0].alternatives[0].transcript;
            return event.data.adminRef.root
                .child("transcripts").child(event.params.uploadId)
                .set({text: transcript, language: language});
        });
    });

最后,是 onTranscript 函数,当它发现有 transcripts 写入(onWrite事件)时,则去调用 Cloud Function: Translate 将其翻译成目标语言:

exports.onTranscript = functions.database
    .ref("/transcripts/{transcriptId}")
    .onWrite((event) => {
        let value = event.data.val();
        let transcriptId = event.params.transcriptId;
        let text = value.text ? value.text : value;
        let languages = ["en", "es", "pt", "de", "ja", "hi", "nl", "fr", "pl"];
        // All supported languages: https://cloud.google.com/translate/docs/languages

        let from = value.language ? getLanguageWithoutLocale(value.language) : "en";
        let promises = languages.map(to => {
            console.log(`translating from '${from}' to '${to}', text '${text}'`);
            // Call the Google Cloud Platform Translate API

            if (from == to) {
                return event.data.adminRef.root
                    .child("translations").child(transcriptId).child(to)
                    .set({text: text, language: from});
            } else {
                return translate.translate(text, {
                    from: from,
                    to: to
                }).then(result => {
                    // Write the translation to the database
                    let translation = result[0];
                    return event.data.adminRef.root
                        .child("translations").child(transcriptId).child(to)
                        .set({text: translation, language: to});
                });
            }
        });
        return Promise.all(promises);
    });

总共只有71行代码,而且这不是伪代码,而是完整地实现这个应用所需的全部后端功能。

这个项目完整的Github repo 在这里 Zero to App: Building a universal translator。不仅有上述提到的 functions 部分的代码,还有 Android/iPhone/Web 客户端的代码。

我们自己来试一试

其实 Firebase 官方的 Get Started 就已经非常全面了,里面包含了:

  1. 如何创建项目

  2. 如何启用 Firebase,以及创建 Storage

  3. 如何将文件写入Storage并触发函数调用

  4. 如何在本地安转和初始化

  5. 如何在本地测试和部署

我尝试跟着里面一步一步进行,几乎是没有遇到什么问题的。除了在本地测试这一步,它是利用了 Local Emulator Suite,它是依赖 Node >= 8 和 Java >= 11的,这一点没有在 Get Started 部分提到。如果自己尝试的话,需要先准备好环境。

其他趣闻

在 GDG 上午的 opening 中,我们见到了李飞飞。她回到中国,宣布了Google AI中国中心的成立。

此外,她还倡导:“AI没有国界,AI的福祉亦无边界”。

GDG-2017-AI-no-boundries.jpg

< Back