sql >> Base de Datos >  >> RDS >> Database

Primeros pasos con Cloud Firestore para iOS

Los programadores móviles se han aprovechado de la plataforma Firebase Realtime Database de Google Backend móvil como servicio (MBaaS) durante muchos años, lo que les ha ayudado a centrarse en la creación de funciones para sus aplicaciones sin tener que preocuparse por la base de datos ni la infraestructura de back-end. Al facilitar el almacenamiento y la persistencia de datos en la nube y cuidar la autenticación y la seguridad, Firebase permite a los codificadores centrarse en el lado del cliente.

El año pasado, Google anunció otra solución de base de datos de back-end, Cloud Firestore, creada desde cero con la promesa de una mayor escalabilidad e intuición. Sin embargo, esto generó cierta confusión en cuanto a su lugar en relación con el producto estrella ya existente de Google, Firebase Realtime Database. Este tutorial describirá las diferencias entre las dos plataformas y las distintas ventajas de cada una. Aprenderá a trabajar con referencias de documentos de Firestore, así como a leer, escribir, actualizar y eliminar datos en tiempo real, mediante la creación de una aplicación de recordatorios simple.

Objetivos de este tutorial

Este tutorial lo expondrá a Cloud Firestore. Aprenderá a aprovechar la plataforma para la persistencia y sincronización de bases de datos en tiempo real. Cubriremos los siguientes temas:

  • qué es Cloud Firestore
  • el modelo de datos de Firestore
  • configurar Cloud Firestore
  • crear y trabajar con referencias de Cloud Firestore
  • lectura de datos en tiempo real desde Cloud Firestore
  • crear, actualizar y eliminar datos
  • filtrado y consultas compuestas

Conocimiento asumido

En este tutorial, se supone que ha estado expuesto a Firebase y que tiene antecedentes de desarrollo con Swift y Xcode.

¿Qué es Cloud Firestore?

Al igual que Firebase Realtime Database, Firestore proporciona a los desarrolladores web y móviles una solución en la nube multiplataforma para conservar los datos en tiempo real, independientemente de la latencia de la red o la conectividad a Internet, así como una integración perfecta con el conjunto de productos de Google Cloud Platform. Junto con estas similitudes, existen claras ventajas y desventajas que diferencian una de la otra.

Modelo de datos

En un nivel fundamental, Realtime Database almacena datos como un árbol JSON grande, monolítico y jerárquico, mientras que Firestore organiza los datos en documentos y colecciones, así como en subcolecciones. Esto requiere menos desnormalización. El almacenamiento de datos en un árbol JSON tiene los beneficios de la simplicidad cuando se trata de trabajar con requisitos de datos simples; sin embargo, se vuelve más engorroso a escala cuando se trabaja con datos jerárquicos más complejos.

Asistencia fuera de línea

Ambos productos ofrecen soporte fuera de línea, almacenando datos en caché activamente en las colas cuando no hay conectividad de red latente o no, sincronizando los cambios locales con el back-end cuando sea posible. Firestore admite la sincronización sin conexión para aplicaciones web además de aplicaciones móviles, mientras que Realtime Database solo permite la sincronización móvil.

Consultas y Transacciones 

Realtime Database solo admite capacidades limitadas de clasificación y filtrado:solo puede ordenar o filtrar en un nivel de propiedad, pero no en ambos, en una sola consulta. Las consultas también son profundas, lo que significa que devuelven un gran subárbol de resultados. El producto solo admite operaciones simples de escritura y transacción que requieren una devolución de llamada de finalización.

Firestore, por otro lado, presenta consultas de índice con clasificación y filtrado compuestos, lo que le permite combinar acciones para crear filtros y clasificación en cadena. También puede ejecutar consultas superficiales que devuelvan subcolecciones en lugar de la colección completa que obtendría con Realtime Database. Las transacciones son de naturaleza atómica, ya sea que envíe una operación por lotes o una sola, y las transacciones se repiten automáticamente hasta que finalizan. Además, Realtime Database solo admite transacciones de escritura individuales, mientras que Firestore permite operaciones por lotes de forma atómica.

Rendimiento y escalabilidad

La base de datos en tiempo real, como era de esperar, es bastante robusta y tiene baja latencia. Sin embargo, las bases de datos están restringidas a regiones individuales, sujetas a disponibilidad zonal. Firestore, por otro lado, aloja datos horizontalmente en varias zonas y regiones para garantizar una verdadera disponibilidad, escalabilidad y confiabilidad global. De hecho, Google ha prometido que Firestore será más fiable que Realtime Database.

Otra deficiencia de Realtime Database es la limitación a 100 000 usuarios simultáneos (100 000 conexiones simultáneas y 1000 escrituras/segundo en una sola base de datos) después de lo cual tendría que fragmentar su base de datos (dividir su base de datos en varias bases de datos) para admitir más usuarios . Firestore se escala automáticamente en varias instancias sin que tengas que intervenir.

Diseñado desde cero con la escalabilidad en mente, Firestore tiene una nueva arquitectura esquemática que replica datos en múltiples regiones, se encarga de la autenticación y maneja otros asuntos relacionados con la seguridad, todo dentro de su SDK del lado del cliente. Su nuevo modelo de datos es más intuitivo que el de Firebase y se parece más a otras soluciones de bases de datos NoSQL comparables como MongoDB, al tiempo que proporciona un motor de consulta más sólido.

Seguridad 

Finalmente, Realtime Database, como sabe por nuestros tutoriales anteriores, administra la seguridad a través de reglas en cascada con disparadores de validación separados. Esto funciona con las reglas de la base de datos de Firebase, validando sus datos por separado. Firestore, por otro lado, proporciona un modelo de seguridad más simple pero más potente que aprovecha las reglas de seguridad de Cloud Firestore y la administración de identidad y acceso (IAM), con la validación de datos exceptuada automáticamente.

  • Desarrollo móvilReglas de seguridad de FirebaseChike Mgbemena

El modelo de datos de Firestore

Firestore es una base de datos basada en documentos NoSQL que consta de colecciones de documentos, cada uno de los cuales contiene datos. Como es una base de datos NoSQL, no obtendrá tablas, filas y otros elementos que encontraría en una base de datos relacional, sino conjuntos de pares clave/valor que encontraría en los documentos.

Los documentos y las colecciones se crean implícitamente mediante la asignación de datos a un documento y, si el documento o la colección no existe, se creará automáticamente para usted, ya que la colección siempre tiene que ser el nodo raíz (primero). Aquí hay un esquema de ejemplo de tareas simple del proyecto en el que trabajará en breve, que consta de la colección de tareas, así como numerosos documentos que contienen dos campos, el nombre (cadena) y una marca para saber si la tarea está completa (booleano) .

Vamos a descomponer cada uno de los elementos para que puedas entenderlos mejor.

Colecciones

Sinónimo de tablas de bases de datos en el mundo SQL, las colecciones contienen uno o más documentos. Las colecciones deben ser los elementos raíz de su esquema y solo pueden contener documentos, no otras colecciones. Sin embargo, puede hacer referencia a un documento que a su vez se refiere a colecciones (subcolecciones).

En el diagrama anterior, una tarea consta de dos campos primitivos (nombre y hecho), así como una subcolección (subtarea) que consta de dos campos primitivos propios.

Documentos

Los documentos constan de pares clave/valor, y los valores tienen uno de los siguientes tipos: 

  • campos primitivos (como cadenas, números, booleanos)
  • objetos anidados complejos (listas o matrices de primitivas)
  • subcolecciones

Los objetos anidados también se denominan mapas y se pueden representar de la siguiente manera, dentro del documento. El siguiente es un ejemplo de un objeto anidado y una matriz, respectivamente:

ID: 2422892 //primitive
name: “Remember to buy milk” 
detail: //nested object
    notes: "This is a task to buy milk from the store"
	created: 2017-04-09
	due: 2017-04-10
done: false
notify: ["2F22-89R2", "L092-G623", "H00V-T4S1"]
...

Para obtener más información sobre los tipos de datos admitidos, consulte la documentación de Tipos de datos de Google. A continuación, configurará un proyecto para trabajar con Cloud Firestore.

Configuración del proyecto

Si ha trabajado con Firebase anteriormente, mucho de esto debería resultarle familiar. De lo contrario, deberá crear una cuenta en Firebase y seguir las instrucciones de la sección "Configurar el proyecto" de nuestro tutorial anterior, Comenzar con la autenticación de Firebase para iOS .

Para seguir este tutorial, clone el repositorio del proyecto del tutorial. A continuación, incluya la biblioteca de Firestore por agregando lo siguiente a su  Podfile :

pod 'Firebase/Core' 
pod 'Firebase/Firestore'

Ingrese lo siguiente en su terminal para construir su biblioteca:

pod install

A continuación, cambie a Xcode y abra el  .xcworkspace expediente. Vaya a AppDelegate.swift  archivo e ingrese lo siguiente dentro de la application:didFinishLaunchingWithOptions: método:

FirebaseApp.configure()

En su navegador, vaya a Firebase console y seleccione la Base de datos pestaña a la izquierda.

Asegúrate de seleccionar la opción Empezar en modo de prueba para que no tenga ningún problema de seguridad mientras experimentamos, y preste atención al aviso de seguridad cuando mueva su aplicación a producción. Ahora está listo para crear una colección y algunos documentos de muestra.

Agregar una colección y un documento de muestra

Para empezar, crea una colección inicial, Tasks , seleccionando Añadir colección y nombrando la colección, como se ilustra a continuación:

Para el primer documento, dejará en blanco la ID del documento, lo que generará automáticamente una ID para usted. El documento constará simplemente de dos campos: namedone .

Guarde el documento y debería poder confirmar la colección y el documento junto con el ID generado automáticamente:

Con la base de datos configurada con un documento de muestra en la nube, está listo para comenzar a implementar el SDK de Firestore en Xcode.

Crear y trabajar con referencias de bases de datos

Abra el MasterViewController.swift archivo en Xcode y agregue las siguientes líneas para importar la biblioteca:

import Firebase

class MasterViewController: UITableViewController {
    @IBOutlet weak var addButton: UIBarButtonItem!
    
    private var documents: [DocumentSnapshot] = []
    public var tasks: [Task] = []
    private var listener : ListenerRegistration!
   ...

Aquí simplemente está creando una variable de escucha que le permitirá activar una conexión a la base de datos en tiempo real cuando haya un cambio. También está creando una DocumentSnapshot referencia que contendrá la instantánea de datos temporales.

Antes de continuar con el controlador de vista, cree otro archivo Swift, Task.swift , que representará su modelo de datos:

import Foundation

struct Task{
    var name:String
    var done: Bool
    var id: String
    
    var dictionary: [String: Any] {
        return [
            "name": name,
            "done": done
        ]
    }
}

extension Task{
    init?(dictionary: [String : Any], id: String) {
        guard   let name = dictionary["name"] as? String,
            let done = dictionary["done"] as? Bool
            else { return nil }
        
        self.init(name: name, done: done, id: id)
    }
}

El fragmento de código anterior incluye una propiedad conveniente (diccionario) y un método (init) que facilitará el llenado del objeto modelo. Vuelva al controlador de vista y declare una variable de establecimiento global que restringirá la consulta base a las 50 entradas principales en la lista de tareas. También eliminará el oyente una vez que establezca la variable de consulta, como se indica en el didSet propiedad a continuación:

fileprivate func baseQuery() -> Query {
        return Firestore.firestore().collection("Tasks").limit(to: 50)
    }
    
    fileprivate var query: Query? {
        didSet {
            if let listener = listener {
                listener.remove()
            }
        }
    }

override func viewDidLoad() {
        super.viewDidLoad()
        self.query = baseQuery()
    }

 override func viewWillDisappear(_ animated: Bool) {
        super.viewWillDisappear(animated)
        self.listener.remove()
    }

Lectura de datos en tiempo real desde Cloud Firestore

Con la referencia del documento en su lugar, en viewWillAppear(_animated: Bool) , asocie el agente de escucha que creó anteriormente con los resultados de la instantánea de la consulta y recupere una lista de documentos. Esto se hace llamando al método de Firestore query?.addSnapshotListener :

self.listener =  query?.addSnapshotListener { (documents, error) in
            guard let snapshot = documents else {
                print("Error fetching documents results: \(error!)")
                return
            }
            
            let results = snapshot.documents.map { (document) -> Task in
                if let task = Task(dictionary: document.data(), id: document.documentID) {
                    return task
                } else {
                    fatalError("Unable to initialize type \(Task.self) with dictionary \(document.data())")
                }
            }
            
            self.tasks = results
            self.documents = snapshot.documents
            self.tableView.reloadData()
            
        }

El cierre anterior asigna snapshot.documents mapeando la matriz iterativamente y envolviéndola en una nueva Task objeto de instancia de modelo para cada elemento de datos en la instantánea. Entonces, con solo unas pocas líneas, ha leído con éxito todas las tareas de la nube y las ha asignado a las tasks globales.   formación.

Para mostrar los resultados, complete lo siguiente TableView métodos delegados:

override func numberOfSections(in tableView: UITableView) -> Int {
        return 1
    }
    
    override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return tasks.count
    }
    
    
    override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath)
        
        let item = tasks[indexPath.row]
        
        cell.textLabel!.text = item.name
        cell.textLabel!.textColor = item.done == false ? UIColor.black : UIColor.lightGray
        
        return cell
    }

En esta etapa, construya y ejecute el proyecto y en el Simulador debería poder observar los datos que aparecen en tiempo real. Agregue datos a través de la consola Firebase y debería verlos aparecer instantáneamente en el simulador de la aplicación.

Creación, actualización y eliminación de datos

Después de leer con éxito el contenido del back-end, a continuación, creará, actualizará y eliminará datos. El siguiente ejemplo ilustrará cómo actualizar los datos, utilizando un ejemplo artificial en el que la aplicación solo le permitirá marcar un elemento como hecho tocando la celda. Tenga en cuenta el collection.document( item.id ).updateData(["done": !item.done]) propiedad de cierre, que simplemente hace referencia a un ID de documento específico, actualizando cada uno de los campos en el diccionario:

override func tableView(_ tableView: UITableView,
                            didSelectRowAt indexPath: IndexPath) {

        let item = tasks[indexPath.row]
        let collection = Firestore.firestore().collection("Tasks")

        collection.document(item.id).updateData([
            "done": !item.done,
            ]) { err in
                if let err = err {
                    print("Error updating document: \(err)")
                } else {
                    print("Document successfully updated")
                }
        }

        tableView.reloadRows(at: [indexPath], with: .automatic)
        
    }

Para eliminar un elemento, llame al document( item.id ).delete() método:

override func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool {
        return true
    }
override func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath) {

        if (editingStyle == .delete){
            let item = tasks[indexPath.row]
            _ = Firestore.firestore().collection("Tasks").document(item.id).delete()
        }

    }

Crear una nueva tarea implicará agregar un nuevo botón en su Storyboard y conectar su IBAction al controlador de vista, creando un addTask(_ sender:) método. Cuando un usuario presiona el botón, aparecerá una hoja de alerta donde el usuario puede agregar un nuevo nombre de tarea:

collection("Tasks").addDocument
    (data: ["name": textFieldReminder.text ?? 
        "empty task", "done": false])

Complete la parte final de la aplicación ingresando lo siguiente:

@IBAction func addTask(_ sender: Any) {
        
        let alertVC : UIAlertController = UIAlertController(title: "New Task", message: "What do you want to remember?", preferredStyle: .alert)
        
        alertVC.addTextField { (UITextField) in
            
        }
        
        let cancelAction = UIAlertAction.init(title: "Cancel", style: .destructive, handler: nil)
        
        alertVC.addAction(cancelAction)
        
        //Alert action closure
        let addAction = UIAlertAction.init(title: "Add", style: .default) { (UIAlertAction) -> Void in
            
            let textFieldReminder = (alertVC.textFields?.first)! as UITextField
            
            let db = Firestore.firestore()
            var docRef: DocumentReference? = nil
            docRef = db.collection("Tasks").addDocument(data: [
                "name": textFieldReminder.text ?? "empty task",
                "done": false
            ]) { err in
                if let err = err {
                    print("Error adding document: \(err)")
                } else {
                    print("Document added with ID: \(docRef!.documentID)")
                }
            }
            
        }
    
        alertVC.addAction(addAction)
        present(alertVC, animated: true, completion: nil)
        
    }

Cree y ejecute la aplicación una vez más y, cuando aparezca el simulador, intente agregar algunas tareas, así como marcar algunas como completadas, y finalmente pruebe la función de eliminación eliminando algunas tareas. Puede confirmar que los datos almacenados se han actualizado en tiempo real cambiando a su consola de base de datos Firebase y observando la colección y los documentos.

Filtrado y consultas compuestas

Hasta ahora, solo ha trabajado con una consulta simple, sin ninguna capacidad de filtrado específica. Para crear consultas un poco más sólidas, puede filtrar por valores específicos haciendo uso de un whereField cláusula:

docRef.whereField(“name”, isEqualTo: searchString)

Puede ordenar y limitar los datos de su consulta haciendo uso de order(by: ) y limit(to: ) métodos de la siguiente manera:

docRef.order(by: "name").limit(5)

En la aplicación FirebaseDo, ya usaste limit con la consulta base. En el fragmento anterior, también hizo uso de otra característica, consultas compuestas, donde tanto el orden como el límite están encadenados. Puede encadenar tantas consultas como desee, como en el siguiente ejemplo:

docRef
    .whereField(“name”, isEqualTo: searchString)
	.whereField(“done”, isEqualTo: false)
	.order(by: "name")
	.limit(5)