Elaborazione in background con PHP


Molte app devono eseguire l'elaborazione in background al di fuori del contesto di una richiesta web. Questo tutorial crea un'app web che consente agli utenti di inserire il testo da tradurre e poi mostra un elenco delle traduzioni precedenti. La traduzione viene eseguita in background per evitare di bloccare la richiesta dell'utente.

Il seguente diagramma illustra il processo di richiesta di traduzione.

Diagramma dell'architettura.

Ecco la sequenza di eventi relativi al funzionamento dell'app Tutorial:

  1. Visita la pagina web per visualizzare un elenco delle traduzioni precedenti, memorizzate in Firestore.
  2. Richiedi una traduzione del testo inserendo un modulo HTML.
  3. La richiesta di traduzione viene pubblicata in Pub/Sub.
  4. Un'app Cloud Run riceve il messaggio Pub/Sub.
  5. L'app Cloud Run utilizza Cloud Translation per tradurre il testo.
  6. L'app Cloud Run archivia il risultato in Firestore.

Questo tutorial è rivolto a chiunque sia interessato a saperne di più sull'elaborazione in background con Google Cloud. Non è richiesta alcuna esperienza precedente con Pub/Sub, Firestore, App Engine o Cloud Run. Tuttavia, per comprendere tutto il codice, è utile avere una certa esperienza con PHP, JavaScript e HTML.

Obiettivi

  • Comprendi ed esegui il deployment dei servizi Cloud Run.
  • Comprendere ed eseguire il deployment di un'app App Engine.
  • Prova l'app.

Costi

In questo documento vengono utilizzati i seguenti componenti fatturabili di Google Cloud:

Per generare una stima dei costi in base all'utilizzo previsto, utilizza il Calcolatore prezzi. I nuovi utenti di Google Cloud possono essere idonei a una prova senza costi aggiuntivi.

Una volta completate le attività descritte in questo documento, puoi evitare la fatturazione continua eliminando le risorse che hai creato. Per ulteriori informazioni, consulta la pagina Pulizia.

Prima di iniziare

  1. Accedi al tuo account Google Cloud. Se non conosci Google Cloud, crea un account per valutare le prestazioni dei nostri prodotti in scenari reali. I nuovi clienti ricevono anche 300 $di crediti gratuiti per l'esecuzione, il test e il deployment dei carichi di lavoro.
  2. Nella pagina del selettore di progetti della console Google Cloud, seleziona o crea un progetto Google Cloud.

    Vai al selettore progetti

  3. Assicurati che la fatturazione sia attivata per il tuo progetto Google Cloud.

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

    Abilita le API

  5. Nella pagina del selettore di progetti della console Google Cloud, seleziona o crea un progetto Google Cloud.

    Vai al selettore progetti

  6. Assicurati che la fatturazione sia attivata per il tuo progetto Google Cloud.

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

    Abilita le API

  8. Nella console Google Cloud, apri l'app in Cloud Shell.

    Vai a Cloud Shell

    Cloud Shell fornisce l'accesso tramite riga di comando alle risorse cloud direttamente dal browser. Apri Cloud Shell nel browser e fai clic su Continua per scaricare il codice campione e modificarlo nella directory dell'app.

  9. In Cloud Shell, configura lo strumento gcloud in modo da utilizzare il tuo progetto Google Cloud:
    # Configure gcloud for your project
    gcloud config set project YOUR_PROJECT_ID

Informazioni sul backend Cloud Run

Puoi definire una singola funzione PHP translateString e configurare il tuo servizio Cloud Run per rispondere a un messaggio Pub/Sub richiamando questa funzione.

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. La funzione deve importare diverse dipendenze per connettersi a Firestore e Translation.

    use Google\Cloud\Firestore\FirestoreClient;
    use Google\Cloud\Firestore\Transaction;
    use Google\Cloud\Translate\TranslateClient;
    
  2. Cloud Run inizia con l'inizializzazione dei client Firestore e Pub/Sub. Poi analizza i dati dei messaggi Pub/Sub per ottenere il testo da tradurre e la lingua di destinazione desiderata.

    $firestore = new FirestoreClient();
    $translate = new TranslateClient();
    
    $translation = [
        'original' => $data['text'],
        'lang' => $data['language'],
    ];
  3. L'API Translation viene utilizzata per tradurre la stringa nella lingua desiderata.

    $result = $translate->translate($translation['original'], [
        'target' => $translation['lang'],
    ]);
  4. La funzione assegna un nome univoco alla richiesta di traduzione, per assicurarci di non memorizzare traduzioni duplicate. Poi, traduce in una transazione Firestore per assicurarsi che le esecuzioni simultanee non eseguano accidentalmente la stessa traduzione due volte.

    $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'],
            ]);
        }
    );

Creazione e deployment del backend Cloud Run

  • Crea l'app Cloud Run nella directory backend:

    gcloud builds submit backend/ \
      --tag gcr.io/PROJECT_ID/background-function
  • Esegui il deployment dell'app Cloud Run utilizzando il tag immagine del passaggio precedente:

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

    Dove REGION corrisponde a una regione Google Cloud.

  • Al termine del deployment, nell'output comando vedrai un URL per l'app di cui hai eseguito il deployment. Ad esempio:

    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

    Copia questo URL per il passaggio successivo.

Configurazione della sottoscrizione Pub/Sub

La tua app Cloud Run riceverà messaggi da Pub/Sub ogni volta che viene pubblicato un messaggio nell'argomento translate.

Un controllo di autenticazione integrato garantisce che il messaggio Pub/Sub contenga un token di autorizzazione valido di un account di servizio che dispone dell'autorizzazione per richiamare il backend Cloud Run.

I passaggi successivi illustrano la configurazione dell'argomento Pub/Sub, della sottoscrizione e dell'account di servizio per effettuare chiamate autenticate al backend Cloud Run. Scopri di più su questa integrazione in Autenticazione tra servizi.

  1. Crea l'argomento translate per la pubblicazione di nuove richieste di traduzione:

    gcloud pubsub topics create translate
    
  2. Abilita il tuo progetto per creare token di autenticazione 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

    Dove PROJECT_NUMBER è il numero di progetto Google Cloud, che puoi trovare eseguendo gcloud projects describe PROJECT_ID | grep projectNumber.

  3. Crea o seleziona un account di servizio per rappresentare l'identità della sottoscrizione Pub/Sub.

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

    Nota: puoi utilizzare cloud-run-pubsub-invoker o sostituirlo con un nome univoco all'interno del tuo progetto Google Cloud.

  4. Concedi all'account di servizio dell'invoker l'autorizzazione per richiamare il tuo servizio background-processing-function:

    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

    La propagazione delle modifiche a Identity and Access Management può richiedere diversi minuti. Nel frattempo, potresti visualizzare HTTP 403 errori nei log del servizio.

  5. Crea una sottoscrizione Pub/Sub con l'account di servizio:

    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

    Dove CLOUD_RUN_URL è l'URL HTTPS che hai copiato dopo la creazione e il deployment del backend.

    Il flag --push-account-service-account attiva la funzionalità push di Pub/Sub per Autenticazione e autorizzazione.

    Il tuo dominio di servizio Cloud Run viene registrato automaticamente per essere utilizzato con le sottoscrizioni Pub/Sub.

Comprendere l'app

L'app web ha due componenti principali:

  • Un server HTTP PHP per gestire le richieste web. Il server ha i seguenti due endpoint:
    • /: elenca tutte le traduzioni esistenti e mostra un modulo che gli utenti possono inviare per richiedere nuove traduzioni.
    • /request-translation: gli invii del modulo vengono inviati a questo endpoint, che pubblica la richiesta in Pub/Sub affinché venga tradotta in modo asincrono.
  • Modello HTML compilato con le traduzioni esistenti del server PHP.

Il server HTTP

  • Nella directory app, index.php inizia configurando l'app Lumen e registrando i gestori HTTP:

    $app = new Laravel\Lumen\Application(__DIR__);
    $app->router->group([
    ], function ($router) {
        require __DIR__ . '/routes/web.php';
    });
    $app->run();
  • Il gestore dell'indice (/) recupera tutte le traduzioni esistenti da Firestore e visualizza un modello con l'elenco:

    /**
     * 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]);
    });
  • Il gestore della traduzione della richiesta, registrato all'indirizzo /request-translation, analizza l'invio del modulo HTML, convalida la richiesta e pubblica un messaggio in 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 '';
    });

Il modello HTML

Il modello HTML rappresenta la base per la pagina HTML mostrata all'utente, che consente così che l'utente possa vedere le traduzioni precedenti e richiederne di nuove. Il modello viene compilato dal server HTTP con l'elenco delle traduzioni esistenti.

  • L'elemento <head> del modello HTML include metadati, fogli di stile e JavaScript per la pagina:

    La pagina recupera gli asset CSS e JavaScript di Material Design Lite (MDL). La MDL ti consente di aggiungere un aspetto Material Design ai tuoi siti web.

    La pagina utilizza JQuery per attendere il completamento del caricamento del documento e imposta un gestore di invio del modulo. Ogni volta che viene inviato il modulo della richiesta di traduzione, la pagina esegue una convalida del modulo minima per verificare che il valore non sia vuoto, quindi invia una richiesta asincrona all'endpoint /request-translation.

    Infine, viene visualizzata una snackbar MDL che indica se la richiesta è riuscita o se si è verificato un errore.

  • Il corpo HTML della pagina utilizza un layout MDL e diversi componenti MDL per visualizzare un elenco di traduzioni e un modulo per richiedere traduzioni aggiuntive:
    <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>
    

Esecuzione dell'app in Cloud Shell

Prima di provare a eseguire il deployment dell'app web, installa le dipendenze ed eseguile in locale.

  1. Innanzitutto, installa le dipendenze con Composer. L'estensione gRPC per PHP è obbligatoria ed è preinstallata in Cloud Shell.

    composer install -d app
    
  2. Quindi, esegui il server web PHP integrato per pubblicare l'app:

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

    Il flag APP_DEBUG=true mostrerà tutte le eccezioni che si verificano.

  3. In Cloud Shell, fai clic su Anteprima web , e seleziona Anteprima sulla porta 8080. Si apre una nuova finestra con la tua app in esecuzione.

Deployment dellapp web

Puoi utilizzare l'ambiente standard di App Engine per creare ed eseguire il deployment di un'app che viene eseguita in modo affidabile anche se sottoposta a un carico elevato e con grandi quantità di dati.

Questo tutorial utilizza l'ambiente standard di App Engine per eseguire il deployment del frontend HTTP.

app.yaml configura l'app App Engine:

runtime: php73

env_variables:
  APP_DEBUG: true
  LOG_CHANNEL: stderr
  APP_STORAGE: /tmp
  • Dalla stessa directory del file app.yaml, esegui il deployment della tua app nell'ambiente standard di App Engine:
    gcloud app deploy

Test dell'app

Dopo aver eseguito il deployment della Cloud Function e dell'app App Engine, prova a richiedere una traduzione.

  1. Per visualizzare l'app nel browser,inserisci il seguente URL:

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

    Sostituisci quanto segue:

    C'è una pagina con un elenco vuoto di traduzioni e un modulo per richiedere nuove traduzioni.

  2. Nel campo Testo da tradurre, inserisci il testo da tradurre, ad esempio Hello, World.
  3. Seleziona una lingua dall'elenco a discesa in cui vuoi tradurre il testo.
  4. Fai clic su Invia.
  5. Per aggiornare la pagina, fai clic su Aggiorna . È presente una nuova riga nell'elenco di traduzione. Se non vedi una traduzione, attendi qualche secondo e riprova. Se continui a non vedere una traduzione, consulta la sezione successiva sul debug dell'app.

Debug dell'app

Se non riesci a connetterti all'app App Engine o non vedi nuove traduzioni, verifica quanto segue:

  1. Verifica che i comandi di deployment di gcloud siano stati completati correttamente e non abbiano restituito alcun errore. Se si sono verificati errori (ad esempio message=Build failed), correggili, prova a creare e a eseguire il deployment dell'app Cloud Run e di eseguire di nuovo il deployment dell'app App Engine.
  2. Nella console Google Cloud, vai alla pagina Esplora log.

    Vai alla pagina Esplora log

    1. Nell'elenco a discesa Risorse selezionate di recente, fai clic su Applicazione GAE, quindi su Tutti gli ID modulo. Viene visualizzato un elenco di richieste effettuate quando hai visitato la tua app. Se non vedi un elenco di richieste, conferma di aver selezionato Tutti i moduli_id dall'elenco a discesa. Se vengono visualizzati messaggi di errore stampati nella console Google Cloud, verifica che il codice dell'app corrisponda al codice nella sezione relativa alla comprensione dell'app web.
    2. Nell'elenco a discesa Risorse selezionate di recente, fai clic su Revisione Cloud Run, quindi su Tutti i log. Dovresti visualizzare una richiesta POST inviata all'URL dell'app di cui hai eseguito il deployment. In caso contrario, verifica che l'app Cloud Run e App Engine utilizzino lo stesso argomento Pub/Sub e che esista una sottoscrizione Pub/Sub per eseguire il push al tuo endpoint Cloud Run.

Esegui la pulizia

Per evitare che al tuo Account Google Cloud vengano addebitati costi relativi alle risorse utilizzate in questo tutorial, elimina il progetto che contiene le risorse oppure mantieni il progetto ed elimina le singole risorse.

Elimina il progetto Google Cloud

  1. Nella console Google Cloud, vai alla pagina Gestisci risorse.

    Vai a Gestisci risorse

  2. Nell'elenco dei progetti, seleziona il progetto che vuoi eliminare, quindi fai clic su Elimina.
  3. Nella finestra di dialogo, digita l'ID del progetto e fai clic su Chiudi per eliminare il progetto.

Elimina le risorse del tutorial

  1. Elimina l'app App Engine creata in questo tutorial:

    1. Nella console Google Cloud, vai alla pagina Versioni di App Engine.

      Vai a Versioni

    2. Seleziona la casella di controllo relativa alla versione non predefinita dell'app che vuoi eliminare.
    3. Per eliminare la versione dell'app, fai clic su Elimina.

  2. Elimina il servizio Cloud Run di cui hai eseguito il deployment in questo tutorial:

    gcloud run services delete background-processing-function

    Puoi anche eliminare i servizi Cloud Run dalla console Google Cloud.

  3. Elimina le altre risorse Google Cloud create in questo tutorial:

Passaggi successivi