В предыдущей части мы увидели, как написать приложение NodeJS с помощью Kotlin. Теперь давайте применим эти знания для создания наших первых облачных функций с помощью Kotlin 👌

Создать проект Firebase Cloud Functions

Во-первых, рекомендую вам ознакомиться с документацией по firebase.

Возобновлено двумя простыми командами:

npm install -g firebase-tools
firebase init

Переход на Котлин

У нас есть проект с папкой functions, в которой содержится код, выполняемый Firebase Cloud Functions. Добавим поддержку Kotlin.

Создайте build.gradle в корне проекта

buildscript {
    ext.kotlin_version = '1.2.30'

    repositories {
        mavenCentral()
    }
    dependencies {
        classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
    }
}

apply plugin: 'kotlin2js'

repositories {
    mavenCentral()
}

dependencies {
    compile "org.jetbrains.kotlin:kotlin-stdlib-js:$kotlin_version"
    testCompile "org.jetbrains.kotlin:kotlin-test-js:$kotlin_version"
}

compileKotlin2Js.kotlinOptions {
    moduleKind = "commonjs"
    outputFile = "functions/index.js"
    sourceMap = true
}

Примечание: outputFile должен указывать на functions/index.js

Инициализация оболочки Gradle:

gradle wrapper

Эта команда создает сценарий gradlew с папкой gradle, которая содержит оболочку gradle, чтобы избежать конфликтов.

Создайте исходную папку в корне проекта.

src/main/kotlin

Используйте обертки

Я начинаю с создания набора оболочек для ExpressJS и Firebase. Я использую документацию и определение файлов TypeScript. Эти обертки стараются быть максимально точными.

Оболочки ExpressJS доступны на моем гитхабе здесь.

Оболочки Firebase доступны на моем гитхабе здесь.

Скопируйте все обертки в src/main/kotlin, мы будем использовать их для создания нашей первой облачной функции.

Первая облачная функция

Давайте создадим бэкэнд ToDo. В приложении ToDo будет 3 API:

  • PUT /v1/task: Создать новую задачу
  • GET /v1/task/{id}?: получить задачу или список задач
  • DELETE /v1/task/{id}: Удалить задачу

Создать Index.kt файл в src/main/kotlin

external val exports: dynamic

fun main(args: Array<String>) {

    val app = Express()
    Admin.initializeApp(Functions.config().firebase)
    val db = Admin.firestore()

    // PUT method here
    // GET method here
    // DELETE method here
    exports.v1 = Functions.https.onRequest(app)
}
data class Task(val id: String? = undefined, val label: String, val time: Double) : DocumentData

Используя оболочки, мы создали приложение ExpressJS и объявили его облачным функциям. У нас есть только один динамический тип для экспорта. Весь код строго проверяется компилятором Kotlin 💪

Создать новую задачу

app.put("/task", { req, res ->
    val input = req.body.unsafeCast<TaskInput>()
    val inputTask = Task(label = input.label, time = Date().getTime())

    // Hack because firestore checks the object prototype and KotlinJS has his own
    db.collection("task").add(jsObject {
        label = inputTask.label
        time = inputTask.time
    }).then({ ref ->
        res.status(201).json(Task(ref.id, inputTodo.label, inputTodo.time))
    }).catch({ error ->
        res.status(500).json(error)
    })
})

Чтобы добавить новую задачу, мы создали запрос на размещение. В теле req содержится json, уже проанализированный Firebase. Мы небезопасно помещаем тело в класс данных Kotlin. Параметры доступны через TaskInput class. Создаем новый документ в коллекции задача. В случае успеха мы возвращаем статус 201 (СОЗДАНО) с содержимым задачи как json.

data class TaskInput(val label: String? = null)
inline fun jsObject(init: dynamic.() -> Unit): dynamic {
    val o = js("{}")
    init(o)
    return o
}

jsObject - это хитрость для динамического создания объекта JavaScript. Firebase требует простой объект JavaScript (проверьте прототип), но Kotlin создает объекты с конкретным прототипом…

Получите одну или несколько задач

app.get("/task/:id?", { req, res ->
    val params = req.params.unsafeCast<Params>()
    if (params.id != undefined) {
        db.collection("task").doc(params.id).get().then({ doc ->
            if (!doc.exists) {
                res.status(404).json(Message("Task not found!"))
            } else {
                val data = doc.data().unsafeCast<Task>()
                res.status(200).json(Task(doc.id, data.label, data.time))
            }
        }).catch({ err ->
            res.status(500).json(err)
        })
    } else {
        db.collection("task").get().then({ snapshot ->
            val result = snapshot.docs.toList().map {
                Task(it.id, it.data()!!["label"] as String, it.data()!!["time"] as Double)
            }
            res.status(200).json(result)
        }).catch({ err ->
            res.status(500).json(err)
        })
    }
})

Мы создали метод get с необязательным параметром id. Если указан id, мы получим связанный документ. В противном случае мы получили бы все документы. Как видите, мы используем .toList().map{}, специфичный для Kotlin, и он отлично работает 👍

data class Params(val id: String? = undefined)
data class Message(val msg: String)

Удалить задачу

app.delete("/task/:id", { req, res ->
    val params = req.params.unsafeCast<Params>()
    db.collection("task").doc(params.id).delete().then({
        res.status(200).json(Message("Task has been deleted"))
    })
})

Мы создали метод удаления, который удаляет документ с предоставленным идентификатором. Мы не проверяли, было ли удаление успешным, здесь (вы можете улучшить его, если хотите 😉).

Попробуй это

Последний шаг, но не в последнюю очередь! Мы хотим протестировать наши новые облачные функции, и хорошие новости заключаются в том, что это очень просто!

Сгенерируйте код JavaScript:

./gradlew build

Запускаем локальный сервер (из папки functions)

npm run serve

И протестируйте любые методы, используя одну из этих примеров команд curl:

Создать новую задачу

curl -X PUT \
  http://localhost:5000/todomedium/us-central1/v1/task \
  -d 'label=New%20task!'

Получить список задач

curl -X GET \
  http://localhost:5000/todomedium/us-central1/v1/task

Получите задание с TASK_ID

curl -X GET \
  http://localhost:5000/todomedium/us-central1/v1/task/TASK_ID

Удалить задачу с TASK_ID

curl -X DELETE \
  http://localhost:5000/todomedium/us-central1/v1/task/TASK_ID

Исходный код доступен в этом проекте на github.

и вуаля! теперь вы можете создавать красивые облачные функции с помощью Kotlin 🎉

Бонус: Kotlinify it!

Надеюсь, вам понравилось, и вы можете поделиться им со своими друзьями и коллегами 😉

… А если есть вопросы, задавайте их в комментариях!