Compila una app para iOS mediante Firebase y el entorno flexible de App Engine

En este instructivo, se demuestra cómo escribir una app para iOS con almacenamiento de datos de backend, sincronización en tiempo real y registro de eventos del usuario mediante Firebase. Los servlets de Java que se ejecutan en el entorno flexible de App Engine detectan los registros de usuarios nuevos almacenados en Firebase y los procesan.

En las instrucciones, se muestra cómo hacerlo mediante Firebase y el entorno flexible de App Engine.

Si deseas que la app procese datos del usuario o que organice eventos, puedes extender Firebase mediante el entorno flexible de App Engine para realizar una sincronización automática de datos en tiempo real.

Playchat, la app de muestra, almacena mensajes de chat en Firebase Realtime Database, que sincroniza esos datos entre todos los dispositivos de forma automática. Playchat también escribe los registros de eventos del usuario en Firebase. Para obtener más información sobre cómo la base de datos sincroniza datos, consulta ¿Cómo funciona? en la documentación de Firebase.

En el siguiente diagrama, se muestra la arquitectura del cliente de Playchat.

Arquitectura del cliente de Playchat

Un conjunto de servlets de Java que se ejecutan en el entorno flexible de App Engine se registran como objetos de escucha mediante Firebase. Los servlets responden a los nuevos registros de eventos del usuario y procesan los datos de registro. Usan transacciones para garantizar que solo un servlet controle cada registro de eventos del usuario.

En el siguiente diagrama, se muestra la arquitectura del servidor de Playchat.

Arquitectura de servidor de Playchat

La comunicación entre la app y el servlet ocurre en tres partes:

  • Cuando un usuario nuevo se conecta a Playchat, la app solicita un servlet de registro para ese usuario y agrega una entrada en /inbox/ en Firebase Realtime Database.

  • Uno de los servlets acepta la asignación mediante la actualización del valor de la entrada a su identificador de servlet. El servlet usa una transacción de Firebase para garantizar que sea el único servlet que pueda actualizar el valor. Una vez que se actualiza el valor, todos los demás servlets ignoran la solicitud.

  • Cuando el usuario accede, cierra sesión o cambia a un nuevo canal, Playchat registra la acción en /inbox/[SERVLET_ID]/[USER_ID]/; en el que [SERVLET_ID] es el identificador de la instancia del servlet, y [USER_ID] es un valor de hash que representa al usuario.

  • El servlet detecta las nuevas entradas en la bandeja de entrada y recopila los datos de registro.

En esta app de muestra, los servlets copian los datos de registro de forma local y los muestran en una página web. En una versión de producción de esta app, los servlets podrían procesar los datos de registro o copiarlos en Cloud Storage, Cloud Bigtable o BigQuery para almacenarlos y analizarlos.

Objetivos

En este instructivo, se muestra cómo realizar las siguientes acciones:

  • Compilar una app para iOS, Playchat, que almacena datos en Firebase Realtime Database

  • Ejecutar un servlet de Java en el entorno flexible de App Engine que se conecte a Firebase y reciba notificaciones cuando se modifiquen los datos almacenados en Firebase

  • Usar estos dos componentes para compilar un servicio de backend distribuido de transmisión a fin de compilar y procesar datos de registro

Costos

Firebase tiene un nivel de uso gratuito. Si el uso de estos servicios es menor que los límites especificados en el plan gratuito de Firebase, no se aplican cargos por usar Firebase.

Las instancias dentro del entorno flexible de App Engine se cobran según el costo de las máquinas virtuales subyacentes de Compute Engine.

Antes de comenzar

Instala el siguiente software:

Ejecuta el siguiente comando en una ventana de la terminal para instalar el componente de Java de App Engine del SDK de Cloud.

gcloud components install app-engine-java
    

Clona el código de muestra

  1. Clona el código de la app cliente.

    git clone https://github.com/GoogleCloudPlatform/firebase-ios-samples
        
  2. Clona el código del servlet de backend.

    git clone https://github.com/GoogleCloudPlatform/firebase-appengine-backend
        

Crea un proyecto de Firebase

  1. Crea una cuenta de Firebase o inicia sesión en una cuenta existente.

  2. Haz clic en Agregar proyecto.

  3. En Nombre del proyecto, ingresa: Playchat. Anota el ID del proyecto que se asignó a tu proyecto, ya que se usa en varios pasos de este instructivo.

  4. Sigue los pasos de configuración restantes y haz clic en Crear proyecto.

  5. Después de que el asistente aprovisione el proyecto, haz clic en Continuar.

  6. En la página Descripción general del proyecto, haz clic en el ícono de Configuración y, luego, en Configuración del proyecto.

  7. Haz clic en Agregar Firebase a la app para iOS.

  8. En ID del paquete de iOS, ingresa: com.google.cloud.solutions.flexenv.PlayChat.

  9. Haz clic en Registrar app.

  10. Sigue los pasos en la sección Descargar archivo de configuración para agregar el archivo GoogleService-Info.plist a la carpeta PlayChat en el proyecto.

  11. Haz clic en Siguiente en la sección Descargar archivo de configuración.

  12. Toma nota de las instrucciones a fin de usar CocoaPods para instalar y administrar las dependencias del proyecto. El administrador de dependencias de CocoaPods ya está configurado en el código de muestra.

  13. Ejecuta el siguiente comando para instalar las dependencias. Es posible que este comando lleve mucho tiempo en completarse:

    pod install
        

    Es posible que debas ejecutar pod repo update si durante la instalación de CocoaPods no se puede encontrar la dependencia de Firebase.

    Después de este paso, usa el archivo .xcworkspace recién creado en lugar del archivo .xcodeproj en todos los desarrollos futuros en la App para iOS.

  14. Haz clic en Siguiente en la sección Agregar el SDK de Firebase.

  15. Toma nota del código necesario para inicializar Firebase en el proyecto.

  16. Haz clic en Siguiente en la sección Agregar código de inicialización.

  17. Haz clic en Omitir este paso en la sección Ejecutar la app para verificar la instalación.

Crea una base de datos de Realtime Database

  1. En el menú de la izquierda de Firebase console, selecciona Base de datos en el grupo Desarrollar.

  2. En la página Base de datos, ve a la sección Realtime Database y haz clic en Crear base de datos.

  3. En el cuadro de diálogo Reglas de seguridad para Realtime Database, selecciona Iniciar en modo de prueba y haz clic en Habilitar.

    En este paso, se muestran los datos que almacenaste en Firebase. En los pasos posteriores de este instructivo, puedes repasar esta página web para ver los datos que la app cliente y el servlet de backend agregaron y actualizaron.

  4. Toma nota de la URL de Firebase del proyecto, que tiene el formato https://[FIREBASE_PROJECT_ID].firebaseio.com/ y aparece junto al ícono de vínculo.

Habilita la autenticación de Google para el proyecto de Firebase

Hay una variedad de proveedores de acceso que puedes configurar para conectarte al proyecto de Firebase. En este instructivo, aprenderás a configurar la autenticación para que los usuarios puedan acceder mediante una Cuenta de Google.

  1. En el menú de la izquierda de Firebase console, haz clic en Autenticación en el grupo Desarrollar.

  2. Haz clic en Configurar el método de acceso.

  3. Selecciona Google, activa la opción Habilitar y haz clic en Guardar.

Agrega una cuenta de servicio al proyecto de Firebase

El servlet de backend no usa una Cuenta de Google para acceder. En su lugar, se conecta a Firebase mediante una cuenta de servicio. Sigue estos pasos para crear una cuenta de servicio que pueda conectarse a Firebase y agregar las credenciales de la cuenta de servicio al código del servlet.

  1. En el menú de la izquierda de Firebase console, junto a la página principal del proyecto de Playchat, selecciona el ícono de Configuración y, luego, Configuración del proyecto.

  2. Selecciona Cuentas de servicio y, luego, Administrar todas las cuentas de servicio.

  3. Haz clic en CREAR CUENTA DE SERVICIO.

  4. Establece la siguiente configuración:

    1. En Nombre de la cuenta de servicio, ingresa playchat-servlet.
    2. En Función, selecciona Proyecto > Propietario.

    3. Marca la opción Proporcionar una nueva clave privada.

    4. Selecciona JSON para Tipo de clave.

  5. Haz clic en Crear.

  6. Descarga el archivo de claves JSON para la cuenta de servicio y guarda el proyecto de servicio de backend, firebase-appengine-backend, en el directorio src/main/webapp/WEB-INF/. El nombre del archivo tiene el siguiente formato: Playchat-[UNIQUE_ID].json.

  7. Edita src/main/webapp/WEB-INF/web.xml y los parámetros de inicialización de la siguiente manera:

    • Reemplaza JSON_FILE_NAME por el nombre del archivo de claves JSON que descargaste.

    • Reemplaza FIREBASE_URL por la URL de Firebase que anotaste antes.

      <init-param>
            <param-name>credential</param-name>
            <param-value>/WEB-INF/JSON_FILE_NAME</param-value>
          </init-param>
          <init-param>
            <param-name>databaseUrl</param-name>
            <param-value>FIREBASE_URL</param-value>
          </init-param>
          

Habilita la facturación y las API para el proyecto de Google Cloud

Para que el servicio de backend se ejecute en GCP, debes habilitar la facturación y las API del proyecto. El proyecto de Cloud es el mismo que creaste en Crea un proyecto de Firebase y tiene el mismo identificador de proyecto.

  1. En Google Cloud Console, selecciona el proyecto de Playchat.

    Ir a la página Proyectos

  2. Asegúrate de que la facturación esté habilitada para tu proyecto de Google Cloud. Obtén información sobre cómo confirmar que tienes habilitada la facturación para tu proyecto.

  3. Habilita las API de App Engine Admin and Compute Engine.

    Habilita las API

Compila e implementa el servicio de backend

El servicio de backend de este ejemplo usa una configuración de Docker para especificar su entorno de hosting. Debido a esta personalización, debes usar el entorno flexible de App Engine en lugar del entorno estándar de App Engine.

Para compilar el servlet del backend y, luego, implementarlo en el entorno flexible de App Engine, puedes usar el complemento de Maven de Google App Engine. Este complemento ya está especificado en el archivo de compilación de Maven incluido en este ejemplo.

Configura el proyecto

Para que Maven compile el servlet del backend de forma correcta, debes proporcionarle el proyecto de Google Cloud Platform (GCP) a fin de que pueda iniciar los recursos del servlet en él. El identificador del proyecto de GCP y el de Firebase son los mismos.

  1. Proporciona las credenciales que usa la herramienta de gcloud para acceder a GCP.

    gcloud auth login
        
  2. Configura el proyecto como el proyecto de Firebase con el siguiente comando y reemplaza [FIREBASE_PROJECT_ID] por el nombre del ID del proyecto de Firebase que anotaste antes.

    gcloud config set project [FIREBASE_PROJECT_ID]
        
  3. Verifica que el proyecto se haya configurado mediante una lista de las opciones de configuración.

    gcloud config list
        

Ejecuta el servicio en el servidor local (opcional)

Cuando desarrolles un servicio de backend nuevo, ejecuta el servicio a nivel local antes de implementarlo en App Engine para iterar con rapidez los cambios sin la sobrecarga que puede generar una implementación completa en App Engine.

Si ejecutas el servidor a nivel local, este no usa una configuración de Docker ni se ejecuta en un entorno de App Engine. En su lugar, Maven garantiza que todas las bibliotecas dependientes estén instaladas a nivel local y la app se ejecute en el servidor web de Jetty.

  1. En el directorio firebase-appengine-backend, crea y ejecuta el módulo de backend de manera local mediante el siguiente comando:

    mvn clean package appengine:run
        

    Si instalaste la herramienta de línea de comandos de gcloud en un directorio que no sea ~/google-cloud-sdk, agrega la ruta de instalación al comando como se muestra a continuación y reemplaza [PATH_TO_TOOL] por la ruta personalizada.

    mvn clean package appengine:run -Dgcloud.gcloud_directory=[PATH_TO_TOOL]
        
  2. Si se te pregunta ¿Deseas que la aplicación “Python.app” acepte conexiones de red entrantes?, selecciona Permitir.

Cuando finalice la implementación, abre http://localhost:8080/printLogs para verificar que el servicio de backend esté en ejecución. En la página web, se muestra Bandeja de entrada: seguido de un identificador de 16 dígitos. Este es el identificador de la bandeja de entrada para el servlet que se ejecuta en la máquina local.

Este identificador no cambia aunque actualices la página. El servidor local inicia una sola instancia del servlet. Esto es útil para realizar pruebas, porque solo hay un identificador de servlet almacenado en Firebase Realtime Database.

Para cerrar el servidor local, presiona Ctrl+C.

Implementa el servicio en el entorno flexible de App Engine

Cuando ejecutas el servicio de backend en el entorno flexible de App Engine, esta plataforma usa la configuración en /firebase-appengine-backend/src/main/webapp/Dockerfiles para compilar el entorno de hosting en el que se ejecuta el servicio. El entorno flexible inicia varias instancias del servlet y ajusta su escala para satisfacer la demanda.

  • En el directorio firebase-appengine-backend, crea y ejecuta el módulo de backend a nivel local mediante el siguiente comando:

    mvn clean package appengine:deploy
        
    mvn clean package appengine:deploy -Dgcloud.gcloud_directory=[PATH_TO_GCLOUD]
        

A medida que se ejecute la compilación, verás el mensaje “Enviando contexto de compilación al daemon de Docker…”. Con el comando anterior, se sube la configuración de Docker y se la implementa en el entorno flexible de App Engine.

Cuando finalice la implementación, abre https://[FIREBASE_PROJECT_ID].appspot.com/printLogs; en este caso, [FIREBASE_PROJECT_ID] es el identificador de Crea un proyecto de Firebase. En la página web, se muestra Bandeja de entrada: seguido de un identificador de 16 dígitos. Este es el identificador de la bandeja de entrada para un servlet que se ejecuta en el entorno flexible de App Engine.

Cuando actualizas la página, este identificador cambia de manera periódica a medida que App Engine inicia múltiples instancias del servlet a fin de manejar las solicitudes entrantes de los clientes.

Actualiza el esquema de URL en la muestra de iOS

  1. En Xcode, con el espacio de trabajo de PlayChat abierto, abre la carpeta PlayChat.

  2. Abre GoogleService-Info.plist y copia el valor de REVERSED_CLIENT_ID.

  3. Abre Info.plist y navega a Tipos de URL clave > Elemento 0 (editor) > Esquemas de URL > Elemento 0.

  4. Reemplaza el valor del marcador de posición, [REVERSED_CLIENT_ID], por el valor que copiaste de GoogleService-Info.plist.

Ejecuta y prueba la app para iOS

  1. En Xcode, con el lugar de trabajo de PlayChat abierto, selecciona Producto > Ejecutar.

  2. Cuando la app esté cargada en el simulador, accede con tu Cuenta de Google.

    Accede a Playchat

  3. Selecciona el canal de libros.

  4. Ingresa un mensaje.

    Envía un mensaje

Cuando lo hagas, la app de Playchat almacenará el mensaje en Firebase Realtime Database. Firebase sincroniza los datos almacenados en la base de datos de los dispositivos. Los dispositivos que ejecuten Playchat mostrarán el nuevo mensaje cuando un usuario seleccione el canal de libros.

Envía un mensaje

Verifica los datos

Luego de usar la app Playchat para generar algunos eventos del usuario, puedes verificar que los servlets se registren como objetos de escucha y recopilen registros de eventos del usuario.

Abre Firebase Realtime Database para la app; en este caso [FIREBASE_PROJECT_ID] es el identificador de Crea un proyecto de Firebase.

https://console.firebase.google.com/project/[FIREBASE_PROJECT_ID]/database/data
    

En la parte inferior de Firebase Realtime Database, en la ubicación de datos /inbox/, hay un grupo de nodos con el prefijo client- seguidos por una clave generada de forma aleatoria que representa el acceso de la cuenta del usuario. A la última entrada de este ejemplo, client-1240563753, le sigue un identificador de 16 dígitos del servlet que se encuentra a la escucha de los eventos de registro de ese usuario, en este ejemplo, 0035806813827987.

Datos almacenados en Firebase Realtime Database

Justo arriba, en la ubicación de datos /inbox/, se encuentran los identificadores de servlet de todos los servlets asignados por el momento. En este ejemplo, solo un servlet recopila registros. En /inbox/[SERVLET_IDENTIFIER], están los registros de usuario que la app escribió en ese servlet.

Abre la página de App Engine del servicio de backend en https://[FIREBASE_PROJECT_ID].appspot.com/printLogs; en el que [FIREBASE_PROJECT_ID] es el identificador de Crea un proyecto de Firebase. En la página, se muestra el identificador del servlet que registró los eventos del usuario que generaste. También puedes ver las entradas de registro de esos eventos debajo del identificador de la bandeja de entrada del servlet.

Explora el código

Playchat, la app para iOS, define una clase, FirebaseLogger, que usa a fin de escribir los registros de eventos del usuario en Firebase Realtime Database.

import Firebase

    class FirebaseLogger {
      var logRef: DatabaseReference!

      init(ref: DatabaseReference!, path: String!) {
        logRef = ref.child(path)
      }

      func log(_ tag: String!, message: String!) {
        let entry: LogEntry = LogEntry(tag: tag, log: message)
        logRef.childByAutoId().setValue(entry.toDictionary())
      }
    }

Cuando un usuario nuevo accede, Playchat llama a la función requestLogger a fin de agregar una entrada nueva a la ubicación /requestLogger/ en Firebase Realtime Database y establece un objeto de escucha para poder responder cuando un servlet actualiza el valor de esa entrada y acepta la asignación.

Cuando un servlet actualiza el valor, Playchat quita el objeto de escucha y escribe el registro “Acceso” en la bandeja de entrada del servlet.

func requestLogger() {
      ref.child(IBX + "/" + inbox!).removeValue()
      ref.child(IBX + "/" + inbox!)
        .observe(.value, with: { snapshot in
          print(self.inbox!)
          if snapshot.exists() {
            self.fbLog = FirebaseLogger(ref: self.ref, path: self.IBX + "/"
              + String(describing: snapshot.value!) + "/logs")
            self.ref.child(self.IBX + "/" + self.inbox!).removeAllObservers()
            self.msgViewController!.fbLog = self.fbLog
            self.fbLog!.log(self.inbox, message: "Signed in")
          }
        })
      ref.child(REQLOG).childByAutoId().setValue(inbox)
    }

En el servicio de backend, cuando se inicia una instancia del servlet, la función init(ServletConfig config) en MessageProcessorServlet.java se conecta a Firebase Realtime Database y agrega un objeto de escucha a la ubicación de datos /inbox/.

Cuando se agrega una nueva entrada a la ubicación de datos /inbox/, el servlet actualiza el valor mediante su identificador, una señal para la app Playchat de que el servlet acepta la asignación a fin de procesar registros para ese usuario. El servlet usa las transacciones de Firebase para garantizar que solo un servlet pueda actualizar el valor y aceptar la asignación.

/*
     * Receive a request from a client and reply back its inbox ID.
     * Using a transaction ensures that only a single servlet instance replies
     * to the client. This lets the client know to which servlet instance
     * send consecutive user event logs.
     */
    firebase.child(REQLOG).addChildEventListener(new ChildEventListener() {
      public void onChildAdded(DataSnapshot snapshot, String prevKey) {
        firebase.child(IBX + "/" + snapshot.getValue()).runTransaction(new Transaction.Handler() {
          public Transaction.Result doTransaction(MutableData currentData) {
            // Only the first servlet instance writes its ID to the client inbox.
            if (currentData.getValue() == null) {
              currentData.setValue(inbox);
            }
            return Transaction.success(currentData);
          }

          public void onComplete(DatabaseError error, boolean committed, DataSnapshot snapshot) {}
        });
        firebase.child(REQLOG).removeValue();
      }
      // ...
    });

Después de que un servlet acepta una asignación para procesar los registros de eventos del usuario, agrega un objeto de escucha que detecta cuándo Playchat escribe un nuevo archivo de registro en la bandeja de entrada del servlet. El servlet responde mediante la recuperación de los datos de registro nuevos de Firebase Realtime Database.

/*
     * Initialize user event logger. This is just a sample implementation to
     * demonstrate receiving updates. A production version of this app should
     * transform, filter, or load to another data store such as Google BigQuery.
     */
    private void initLogger() {
      String loggerKey = IBX + "/" + inbox + "/logs";
      purger.registerBranch(loggerKey);
      firebase.child(loggerKey).addChildEventListener(new ChildEventListener() {
        public void onChildAdded(DataSnapshot snapshot, String prevKey) {
          if (snapshot.exists()) {
            LogEntry entry = snapshot.getValue(LogEntry.class);
            logs.add(entry);
          }
        }

        public void onCancelled(DatabaseError error) {
          localLog.warning(error.getDetails());
        }

        public void onChildChanged(DataSnapshot arg0, String arg1) {}

        public void onChildMoved(DataSnapshot arg0, String arg1) {}

        public void onChildRemoved(DataSnapshot arg0) {}
      });
    }

Limpieza

Sigue estos pasos a fin de evitar que se apliquen cargos a tu cuenta de Google Cloud Platform para los recursos que se usaron en este instructivo:

Borra el proyecto de Google Cloud Platform y Firebase

La forma más sencilla de detener los cargos de facturación es borrar el proyecto que creaste para este instructivo. Aunque creaste el proyecto en Firebase console, también puedes borrarlo en Cloud Console, ya que los proyectos de Firebase y Cloud son los mismos.

  1. En Cloud Console, ve a la página Administrar recursos.

    Ir a la página Administrar recursos

  2. En la lista de proyectos, selecciona el proyecto que deseas borrar y haz clic en Borrar .
  3. En el cuadro de diálogo, escribe el ID del proyecto y, luego, haz clic en Cerrar para borrar el proyecto.

Borra las versiones no predeterminadas de la app de App Engine

Si no quieres borrar el proyecto de GCP y Firebase, puedes reducir los costos si borras las versiones no predeterminadas de la app del entorno flexible de App Engine.

  1. En Cloud Console, ve a la página Versiones de App Engine.

    Ir a la página Versiones

  2. Selecciona la casilla de verificación de la versión no predeterminada de la app que deseas borrar.
  3. Haz clic en Borrar para borrar la versión de la app.

Próximos pasos

  • Analiza y archiva datos: En este ejemplo, los servlets almacenan datos de registro solo en la memoria. Para ampliar esta muestra, puedes hacer que los servlets archiven, transformen y analicen los datos mediante servicios como Cloud Storage, Cloud Bigtable, Google Cloud Dataflow y BigQuery.

  • Distribuye de manera uniforme la carga de trabajo entre los servlets: App Engine proporciona ajuste de escala automático y manual. Con el ajuste de escala automático, el entorno flexible detecta cambios en la carga de trabajo y, luego, agrega o quita instancias de VM en el clúster. Con el ajuste manual, debes especificar un número estático de instancias para manejar el tráfico. Para obtener más información sobre cómo configurar el escalamiento, consulta Configuración de escalamiento de servicios en la documentación de App Engine.

    Debido a que los registros de actividad del usuario se asignan a los servlets mediante el acceso a Firebase Realtime Database, puede que la carga de trabajo no se distribuya de manera uniforme. Por ejemplo, un servlet podría procesar más registros de eventos del usuario que otros servlets.

    Puedes mejorar la eficiencia si implementas un administrador de cargas de trabajo que controle la carga de trabajo de manera independiente en cada VM. Este balanceo de la carga de trabajo podría basarse en métricas como las solicitudes de registro por segundo o la cantidad de clientes en simultáneo.

  • Recupera registros de eventos del usuario no procesados: En esta implementación de muestra, si una instancia de servlet falla, la app cliente asociada con esa instancia continúa enviando eventos de registro a la bandeja de entrada del servlet en Firebase Realtime Database. En una versión de producción de esta app, el servicio de backend debe detectar esta situación para recuperar los registros de eventos del usuario no procesados.

  • Implementa funciones adicionales mediante productos de IA de Cloud: Aprende a proporcionar funciones basadas en AA con productos y servicios de IA de Cloud. Por ejemplo, puedes ampliar esta implementación de muestra a fin de proporcionar una función de traducción de voz mediante una combinación de las API de Speech-to-Text, Translation y Text-to-Speech. Si deseas obtener más información, consulta Agrega traducción de voz a la app para Android.