Pemrosesan latar belakang dengan PHP


Banyak aplikasi perlu melakukan pemrosesan latar belakang di luar konteks permintaan web. Tutorial ini membuat aplikasi web yang memungkinkan pengguna memasukkan teks untuk diterjemahkan, lalu menampilkan daftar terjemahan sebelumnya. Terjemahan dilakukan di proses latar belakang untuk menghindari pemblokiran permintaan pengguna.

Diagram berikut mengilustrasikan proses permintaan terjemahan.

Diagram arsitektur.

Berikut adalah urutan peristiwa untuk cara kerja aplikasi tutorial:

  1. Kunjungi halaman web untuk melihat daftar terjemahan sebelumnya, yang disimpan di Firestore.
  2. Minta terjemahan teks dengan memasukkan formulir HTML.
  3. Permintaan terjemahan dipublikasikan ke Pub/Sub.
  4. Aplikasi Cloud Run menerima pesan Pub/Sub.
  5. Aplikasi Cloud Run menggunakan Cloud Translation untuk menerjemahkan teks.
  6. Aplikasi Cloud Run menyimpan hasilnya di Firestore.

Tutorial ini ditujukan bagi siapa saja yang tertarik untuk mempelajari pemrosesan latar belakang dengan Google Cloud. Anda tidak memerlukan pengalaman terkait Pub/Sub, Firestore, App Engine, atau Cloud Run. Namun, untuk memahami semua kode, pengalaman dengan PHP, JavaScript, dan HTML akan sangat membantu.

Tujuan

  • Memahami dan men-deploy layanan Cloud Run.
  • Pahami dan deploy aplikasi App Engine.
  • Coba aplikasi.

Biaya

Dalam dokumen ini, Anda menggunakan komponen Google Cloud yang dapat ditagih berikut:

Untuk membuat perkiraan biaya berdasarkan proyeksi penggunaan Anda, gunakan kalkulator harga. Pengguna baru Google Cloud mungkin memenuhi syarat untuk mendapatkan uji coba gratis.

Setelah menyelesaikan tugas yang dijelaskan dalam dokumen ini, Anda dapat menghindari penagihan berkelanjutan dengan menghapus resource yang Anda buat. Untuk mengetahui informasi selengkapnya, lihat Pembersihan.

Sebelum memulai

  1. Login ke akun Google Cloud Anda. Jika Anda baru menggunakan Google Cloud, buat akun untuk mengevaluasi performa produk kami dalam skenario dunia nyata. Pelanggan baru juga mendapatkan kredit gratis senilai $300 untuk menjalankan, menguji, dan men-deploy workload.
  2. Di konsol Google Cloud, pada halaman pemilih project, pilih atau buat project Google Cloud.

    Buka pemilih project

  3. Pastikan penagihan telah diaktifkan untuk project Google Cloud Anda.

  4. Aktifkan API Firestore, Cloud Run, Pub/Sub, and Cloud Translation.

    Mengaktifkan API

  5. Di konsol Google Cloud, pada halaman pemilih project, pilih atau buat project Google Cloud.

    Buka pemilih project

  6. Pastikan penagihan telah diaktifkan untuk project Google Cloud Anda.

  7. Aktifkan API Firestore, Cloud Run, Pub/Sub, and Cloud Translation.

    Mengaktifkan API

  8. Di Konsol Google Cloud, buka aplikasi di Cloud Shell.

    Buka Cloud Shell

    Cloud Shell menyediakan akses command line ke resource cloud langsung dari browser. Buka Cloud Shell di browser Anda, lalu klik Lanjutkan untuk mendownload kode contoh dan berpindah ke direktori aplikasi.

  9. Di Cloud Shell, konfigurasikan alat gcloud untuk menggunakan project Google Cloud Anda:
    # Configure gcloud for your project
    gcloud config set project YOUR_PROJECT_ID

Memahami backend Cloud Run

Anda dapat menentukan satu fungsi PHP translateString, dan mengonfigurasi layanan Cloud Run untuk merespons pesan Pub/Sub dengan memanggil fungsi ini.

use Google\Cloud\Firestore\FirestoreClient;
use Google\Cloud\Firestore\Transaction;
use Google\Cloud\Translate\TranslateClient;

/**
 * @param array $data {
 *     The PubSub message data containing text and target language.
 *
 *     @type string $text
 *           The full text to translate.
 *     @type string $language
 *           The target language for the translation.
 * }
 */
function translateString(array $data)
{
    if (empty($data['language']) || empty($data['text'])) {
        throw new Exception('Error parsing translation data');
    }

    $firestore = new FirestoreClient();
    $translate = new TranslateClient();

    $translation = [
        'original' => $data['text'],
        'lang' => $data['language'],
    ];

    $docId = sprintf('%s:%s', $data['language'], base64_encode($data['text']));
    $docRef = $firestore->collection('translations')->document($docId);

    $firestore->runTransaction(
        function (Transaction $transaction) use ($translate, $translation, $docRef) {
            $snapshot = $transaction->snapshot($docRef);
            if ($snapshot->exists()) {
                return; // Do nothing if the document already exists
            }

            $result = $translate->translate($translation['original'], [
                'target' => $translation['lang'],
            ]);
            $transaction->set($docRef, $translation + [
                'translated' => $result['text'],
                'originalLang' => $result['source'],
            ]);
        }
    );

    echo "Done.";
}
  1. Fungsi ini harus mengimpor beberapa dependensi agar dapat terhubung dengan Firestore dan Translation.

    use Google\Cloud\Firestore\FirestoreClient;
    use Google\Cloud\Firestore\Transaction;
    use Google\Cloud\Translate\TranslateClient;
    
  2. Cloud Run dimulai dengan melakukan inisialisasi klien Firestore dan Pub/Sub. Kemudian, Analytics menguraikan data pesan Pub/Sub untuk mendapatkan teks yang akan diterjemahkan dan bahasa target yang diinginkan.

    $firestore = new FirestoreClient();
    $translate = new TranslateClient();
    
    $translation = [
        'original' => $data['text'],
        'lang' => $data['language'],
    ];
  3. Translation API digunakan untuk menerjemahkan string ke bahasa yang diinginkan.

    $result = $translate->translate($translation['original'], [
        'target' => $translation['lang'],
    ]);
  4. Fungsi ini memberikan nama unik untuk permintaan terjemahan guna memastikan kami tidak menyimpan terjemahan duplikat. Kemudian, pemroses akan diterjemahkan dalam transaksi Firestore untuk memastikan eksekusi serentak tidak menjalankan terjemahan yang sama secara tidak sengaja dua kali.

    $docId = sprintf('%s:%s', $data['language'], base64_encode($data['text']));
    $docRef = $firestore->collection('translations')->document($docId);
    
    $firestore->runTransaction(
        function (Transaction $transaction) use ($translate, $translation, $docRef) {
            $snapshot = $transaction->snapshot($docRef);
            if ($snapshot->exists()) {
                return; // Do nothing if the document already exists
            }
    
            $result = $translate->translate($translation['original'], [
                'target' => $translation['lang'],
            ]);
            $transaction->set($docRef, $translation + [
                'translated' => $result['text'],
                'originalLang' => $result['source'],
            ]);
        }
    );

Membangun dan Men-deploy backend Cloud Run

  • Build aplikasi Cloud Run di direktori backend:

    gcloud builds submit backend/ \
      --tag gcr.io/PROJECT_ID/background-function
  • Deploy aplikasi Cloud Run menggunakan tag image dari langkah sebelumnya:

    gcloud run deploy background-processing-function --platform managed \
      --image gcr.io/PROJECT_ID/background-function --region REGION

    Dengan REGION sebagai region Google Cloud.

  • Setelah deployment selesai, Anda akan melihat URL untuk aplikasi yang di-deploy di output perintah. Contoh:

    Service [background-processing-function] revision [default-00002-vav] has been deployed and is serving 100 percent of traffic at https://default-c457u4v2ma-uc.a.run.app

    Salin URL ini untuk langkah berikutnya.

Menyiapkan langganan Pub/Sub

Aplikasi Cloud Run Anda akan menerima pesan dari Pub/Sub setiap kali pesan dipublikasikan ke topik translate.

Pemeriksaan autentikasi bawaan memastikan pesan Pub/Sub berisi token otorisasi yang valid dari akun layanan yang memiliki izin untuk memanggil backend Cloud Run Anda.

Langkah selanjutnya akan memandu Anda menyiapkan topik, langganan, dan akun layanan Pub/Sub untuk melakukan panggilan terautentikasi ke backend Cloud Run. Baca lebih lanjut integrasi ini di artikel Mengautentikasi layanan ke layanan.

  1. Buat topik translate untuk memublikasikan permintaan terjemahan baru:

    gcloud pubsub topics create translate
    
  2. Aktifkan project Anda untuk membuat token autentikasi Pub/Sub:

    gcloud projects add-iam-policy-binding PROJECT_ID \
         --member=serviceAccount:service-PROJECT_NUMBER@gcp-sa-pubsub.iam.gserviceaccount.com \
         --role=roles/iam.serviceAccountTokenCreator

    Dengan PROJECT_NUMBER adalah nomor project Google Cloud Anda, yang dapat ditemukan dengan menjalankan gcloud projects describe PROJECT_ID | grep projectNumber.

  3. Buat atau pilih akun layanan untuk mewakili identitas langganan Pub/Sub.

    gcloud iam service-accounts create cloud-run-pubsub-invoker \
         --display-name "Cloud Run Pub/Sub Invoker"

    Catatan: Anda dapat menggunakan cloud-run-pubsub-invoker atau menggantinya dengan nama unik dalam project Google Cloud Anda.

  4. Berikan izin pada akun layanan invoker untuk memanggil layanan background-processing-function Anda:

    gcloud run services add-iam-policy-binding background-processing-function \
       --member=serviceAccount:cloud-run-pubsub-invoker@PROJECT_ID.iam.gserviceaccount.com \
       --role=roles/run.invoker  --platform managed --region REGION

    Diperlukan waktu beberapa menit agar perubahan Identity and Access Management diterapkan. Sementara itu, Anda mungkin melihat error HTTP 403 dalam log layanan.

  5. Buat langganan Pub/Sub dengan akun layanan:

    gcloud pubsub subscriptions create run-translate-string --topic translate \
       --push-endpoint=CLOUD_RUN_URL \
       --push-auth-service-account=cloud-run-pubsub-invoker@PROJECT_ID.iam.gserviceaccount.com

    Dengan CLOUD_RUN_URL adalah URL HTTPS yang Anda salin setelah mem-build dan men-deploy backend.

    Flag --push-account-service-account mengaktifkan fungsi push Pub/Sub untuk Autentikasi dan otorisasi.

    Domain layanan Cloud Run Anda secara otomatis didaftarkan untuk digunakan dengan langganan Pub/Sub.

Memahami aplikasi

Ada dua komponen utama untuk aplikasi web:

  • Server HTTP PHP untuk menangani permintaan web. Server memiliki dua endpoint berikut:
    • /: Mencantumkan semua terjemahan yang ada dan menampilkan formulir yang dapat dikirim pengguna untuk meminta terjemahan baru.
    • /request-translation: Pengiriman formulir dikirim ke endpoint ini, yang memublikasikan permintaan ke Pub/Sub agar diterjemahkan secara asinkron.
  • Template HTML yang diisi dengan terjemahan yang sudah ada oleh server PHP.

Server HTTP

  • Dalam direktori app, index.php dimulai dengan menyiapkan aplikasi Lumen dan mendaftarkan pengendali HTTP:

    $app = new Laravel\Lumen\Application(__DIR__);
    $app->router->group([
    ], function ($router) {
        require __DIR__ . '/routes/web.php';
    });
    $app->run();
  • Pengendali indeks (/) mendapatkan semua terjemahan yang ada dari Firestore dan merender template dengan daftar:

    /**
     * Homepage listing all requested translations and their results.
     */
    $router->get('/', function (Request $request) use ($projectId) {
        $firestore = new FirestoreClient([
            'projectId' => $projectId,
        ]);
        $translations = $firestore->collection('translations')->documents();
        return view('home', ['translations' => $translations]);
    });
  • Pengendali terjemahan permintaan, yang terdaftar di /request-translation, mengurai pengiriman formulir HTML, memvalidasi permintaan, dan memublikasikan pesan ke Pub/Sub:

    /**
     * Endpoint which publishes a PubSub request for a new translation.
     */
    $router->post('/request-translation', function (Request $request) use ($projectId) {
        $acceptableLanguages = ['de', 'en', 'es', 'fr', 'ja', 'sw'];
        if (!in_array($lang = $request->get('lang'), $acceptableLanguages)) {
            throw new Exception('Unsupported Language: ' . $lang);
        }
        if (!$text = $request->get('v')) {
            throw new Exception('No text to translate');
        }
        $pubsub = new PubSubClient([
            'projectId' => $projectId,
        ]);
        $topic = $pubsub->topic('translate');
        $topic->publish(['data' => json_encode([
            'language' => $lang,
            'text' => $text,
        ])]);
    
        return '';
    });

Template HTML

Template HTML adalah dasar untuk halaman HTML yang ditampilkan kepada pengguna, sehingga mereka dapat melihat terjemahan sebelumnya dan meminta terjemahan baru. Template diisi oleh server HTTP dengan daftar terjemahan yang ada.

  • Elemen <head> template HTML menyertakan metadata, lembar gaya, dan JavaScript untuk halaman:

    Halaman ini mengambil aset CSS dan JavaScript Material Design Lite (MDL). MDL memungkinkan Anda menambahkan tampilan dan nuansa Desain Material ke situs Anda.

    Halaman ini menggunakan JQuery untuk menunggu dokumen selesai dimuat dan menetapkan pengendali pengiriman formulir. Setiap kali formulir permintaan terjemahan dikirimkan, halaman melakukan validasi formulir minimal untuk memastikan nilainya tidak kosong, lalu mengirimkan permintaan asinkron ke endpoint /request-translation.

    Terakhir, snackbar MDL muncul untuk menunjukkan apakah permintaan berhasil atau mengalami error.

  • Isi HTML halaman menggunakan tata letak MDL dan beberapa komponen MDL untuk menampilkan daftar terjemahan dan formulir untuk meminta terjemahan tambahan:
    <body>
      <div class="mdl-layout mdl-js-layout mdl-layout--fixed-header">
        <header class="mdl-layout__header">
          <div class="mdl-layout__header-row">
            <!-- Title -->
            <span class="mdl-layout-title">Translate with Background Processing</span>
          </div>
        </header>
        <main class="mdl-layout__content">
          <div class="page-content">
            <div class="mdl-grid">
              <div class="mdl-cell mdl-cell--1-col"></div>
              <div class="mdl-cell mdl-cell--3-col">
                <form id="translate-form" class="translate-form">
                  <div class="mdl-textfield mdl-js-textfield mdl-textfield--floating-label">
                    <input class="mdl-textfield__input" type="text" id="v" name="v">
                    <label class="mdl-textfield__label" for="v">Text to translate...</label>
                  </div>
                  <select class="mdl-textfield__input lang" name="lang">
                    <option value="de">de</option>
                    <option value="en">en</option>
                    <option value="es">es</option>
                    <option value="fr">fr</option>
                    <option value="ja">ja</option>
                    <option value="sw">sw</option>
                  </select>
                  <button class="mdl-button mdl-js-button mdl-button--raised mdl-button--accent" type="submit"
                      name="submit">Submit</button>
                </form>
              </div>
              <div class="mdl-cell mdl-cell--8-col">
                <table class="mdl-data-table mdl-js-data-table mdl-shadow--2dp">
                  <thead>
                    <tr>
                      <th class="mdl-data-table__cell--non-numeric"><strong>Original</strong></th>
                      <th class="mdl-data-table__cell--non-numeric"><strong>Translation</strong></th>
                    </tr>
                  </thead>
                  <tbody>
                  <?php foreach ($translations as $translation): ?>
                    <tr>
                      <td class="mdl-data-table__cell--non-numeric">
                        <span class="mdl-chip mdl-color--primary">
                          <span class="mdl-chip__text mdl-color-text--white"><?= $translation['originalLang'] ?></span>
                        </span>
                      <?= $translation['original'] ?>
                      </td>
                      <td class="mdl-data-table__cell--non-numeric">
                        <span class="mdl-chip mdl-color--accent">
                          <span class="mdl-chip__text mdl-color-text--white"><?= $translation['lang'] ?></span>
                        </span>
                        <?= $translation['translated'] ?>
                      </td>
                    </tr>
                  <?php endforeach ?>
                  </tbody>
                </table>
                <br/>
                <button class="mdl-button mdl-js-button mdl-button--raised" type="button" onClick="window.location.reload();">Refresh</button>
              </div>
            </div>
          </div>
          <div aria-live="assertive" aria-atomic="true" aria-relevant="text" class="mdl-snackbar mdl-js-snackbar" id="snackbar">
            <div class="mdl-snackbar__text mdl-color-text--black"></div>
            <button type="button" class="mdl-snackbar__action"></button>
          </div>
        </main>
      </div>
    </body>
    </html>
    

Menjalankan aplikasi di Cloud Shell

Sebelum mencoba men-deploy aplikasi web, instal dependensi dan jalankan secara lokal.

  1. Pertama, instal dependensi dengan Composer. Ekstensi gRPC untuk PHP diperlukan, dan sudah diinstal sebelumnya di Cloud Shell.

    composer install -d app
    
  2. Selanjutnya, jalankan server web bawaan PHP untuk menyalurkan aplikasi Anda:

    APP_DEBUG=true php -S localhost:8080 -t app
    

    Flag APP_DEBUG=true akan menampilkan pengecualian apa pun yang terjadi.

  3. Di Cloud Shell, klik Web preview , lalu pilih Preview on port 8080. Tindakan ini akan membuka jendela baru berisi aplikasi yang sedang berjalan.

Men-deploy aplikasi web

Anda dapat menggunakan lingkungan standar App Engine untuk membangun dan men-deploy aplikasi yang berjalan dengan andal dalam beban berat dan data dalam jumlah besar.

Tutorial ini menggunakan lingkungan standar App Engine untuk men-deploy frontend HTTP.

app.yaml mengonfigurasi aplikasi App Engine:

runtime: php73

env_variables:
  APP_DEBUG: true
  LOG_CHANNEL: stderr
  APP_STORAGE: /tmp
  • Dari direktori yang sama dengan file app.yaml, deploy aplikasi Anda ke lingkungan standar App Engine:
    gcloud app deploy

Menguji aplikasi

Setelah Anda men-deploy aplikasi Cloud Function dan App Engine, coba minta terjemahan.

  1. Untuk melihat aplikasi di browser Anda,masukkan URL berikut:

    https://PROJECT_ID.REGION_ID.r.appspot.com

    Ganti kode berikut:

    Ada halaman dengan daftar terjemahan kosong dan formulir untuk meminta terjemahan baru.

  2. Di kolom Text to translate, masukkan teks untuk diterjemahkan, misalnya, Hello, World.
  3. Pilih bahasa dari menu drop-down yang teksnya ingin Anda terjemahkan.
  4. Klik Submit.
  5. Untuk memuat ulang halaman, klik Refresh . Ada baris baru di daftar terjemahan. Jika terjemahan tidak muncul, tunggu beberapa detik dan coba lagi. Jika masih tidak melihat terjemahan, lihat bagian berikutnya tentang proses debug aplikasi.

Men-debug aplikasi

Jika Anda tidak dapat terhubung ke aplikasi App Engine atau tidak melihat terjemahan baru, periksa hal berikut:

  1. Pastikan perintah deployment gcloud berhasil diselesaikan dan tidak menghasilkan error apa pun. Jika ada error (misalnya, message=Build failed), perbaiki, lalu coba mem-build dan men-deploy aplikasi Cloud Run dan men-deploy aplikasi App Engine lagi.
  2. Di konsol Google Cloud, buka halaman Logs Explorer.

    Buka halaman Logs Explorer

    1. Di menu drop-down Recent selected resources, klik GAE Application, lalu klik All module_id. Anda akan melihat daftar permintaan saat mengunjungi aplikasi. Jika Anda tidak melihat daftar permintaan, konfirmasi bahwa Anda telah memilih All module_id dari menu drop-down. Jika Anda melihat pesan error yang dicetak ke Google Cloud Console, pastikan kode aplikasi Anda cocok dengan kode di bagian tentang memahami aplikasi web.
    2. Dalam menu drop-down Recent selected resources, klik Cloud Run Revision, lalu klik All logs. Anda akan melihat permintaan POST yang dikirim ke URL aplikasi yang di-deploy. Jika belum, pastikan bahwa aplikasi Cloud Run dan App Engine menggunakan topik Pub/Sub yang sama, dan ada langganan Pub/Sub untuk dikirim ke endpoint Cloud Run Anda.

Pembersihan

Agar tidak perlu membayar biaya pada akun Google Cloud Anda untuk resource yang digunakan dalam tutorial ini, hapus project yang berisi resource tersebut, atau simpan project dan hapus setiap resource.

Menghapus project Google Cloud

  1. Di konsol Google Cloud, buka halaman Manage resource.

    Buka Manage resource

  2. Pada daftar project, pilih project yang ingin Anda hapus, lalu klik Delete.
  3. Pada dialog, ketik project ID, lalu klik Shut down untuk menghapus project.

Menghapus resource tutorial

  1. Hapus aplikasi App Engine yang Anda buat dalam tutorial ini:

    1. Di konsol Google Cloud, buka halaman Versi untuk App Engine.

      Buka Versi

    2. Pilih kotak centang untuk versi aplikasi non-default yang ingin Anda hapus.
    3. Untuk menghapus versi aplikasi, klik Hapus.

  2. Hapus layanan Cloud Run yang Anda deploy dalam tutorial ini:

    gcloud run services delete background-processing-function

    Anda juga dapat menghapus layanan Cloud Run dari Konsol Google Cloud.

  3. Hapus resource Google Cloud lain yang dibuat dalam tutorial ini:

Langkah selanjutnya