Dynamo DB en un proyecto Django: ¿Vale la pena?
Las migraciones y modelos de Django son herramientas que permiten el desarrollo y entrega continua rápida en proyectos ágiles. Además gracias a su ORM (Object Relational Mapping), un proyecto puede migrar de una base de datos a otra, haciendo que la lógica de negocio no se vea afectada por el lugar de almacenamiento. Y aunque el ORM de Django no soporta todas las bases de datos existentes, se pueden utilizar dependencias externas.
Por otro lado, una base de datos que está siendo utilizada bastantes es Dynamo DB porque permite ajustar la capacidad de cada tabla automáticamente bajo demanda. Esta flexibilidad y la integración con otros servicios de Amazon Web Services (AWS) son características atractivas para querer utilizarla en una aplicación.
Boto3 y Pynamo DB (interfaz de Dynamo con Python) son las alternativas mejor desarrolladas para conectar Django con Dynamo DB. Sin embargo, si se quiere integrar Dynamo DB al inicio o en el proceso de desarrollo de un proyecto Django, existen algunos puntos que se deben tomar en cuenta. Y a continuación muestro las ventajas y desventajas de realizar dicha integración con Pynamo DB, Boto3 y Django.db.models.
Tablas y atributos en DynamoDB
Crear una tabla es elemental para trabajar con Dynamo DB. Para hacerlo, se pueden utilizar 3 enfoques:
Pynamo DB
- Se puede escribir código más rápido y mantenible a través de su clase Model. En el siguiente código se muestra que basta con definir los atributos de la tabla y las claves hash y range.
- Al tener una estructura similar a django.db.models.Model, la migración desde otra base de datos es sencilla, porque permite reutilizar la lógica y solo cambiar la firma de algunos métodos o inicializaciones.
- Está alineado con el patrón de diseño de Django. Existe un equivalente de clase para cada campo de django.db.models e incluso una clase llamada JSONAttribute que permite guardar contenido JSON.
Boto3
- Es una librería de más bajo nivel con la cual la definición de un modelo o clase que pueda ser usado en nuestra aplicación resulta más tedioso. Por tal motivo, debemos implementar una clase similar a pynamodb.models.Model.
- Estas definiciones dependen de las necesidades del proyecto, aunque algunos métodos que permitan definir y modificar los atributos de una tabla deberían incluirse. A pesar de que la clase ModelBoto3 sea reutilizable, esto demandaría bastante esfuerzo de desarrollo y testing, lo cual es valioso al comenzar un proyecto.
Django.db.models.Model
Otro posible enfoque para definir una tabla y sus atributos recae en la utilización del modelo django.db.models.Model, reescribiendo los métodos create, from_db, __init__ y save, entre otros.
La ventaja es el ahorro de tiempo y esfuerzo al inicio del proyecto, pero esto puede variar de acuerdo con las necesidades del proyecto, por ejemplo _save_table a la hora de actualizar las claves hash y range. Además, se debe agregar que los atributos de las tablas deben ser creados desde cero, similarmente al caso de Boto3.
Actualización de atributos
Si bien es cierto que las migraciones en Django permiten realizar actualizaciones a las tablas, objetos o documentos en las bases de datos a través de los modelos, estas operaciones tienen una diferenciación más marcada para cada enfoque que se mencionó en la sección anterior.
Pynamo DB
- Agregar o quitar un atributo simple es relativamente rápido. Por ejemplo, si se agrega regimen_laboral como campo opcional, basta con escribir lo siguiente:
En Dynamo DB, este atributo tendrá, automáticamente, un valor nulo para los registros ya existentes, al tiempo que los nuevos registros pueden tener un valor.
- En caso de tener un valor por defecto, se debe realizar un data migration en Django y escribir el código para obtener todos los registros anteriores y actualizar el nuevo valor para regimen_laboral.
Para esto, debes crear una migración vacía:
Recuerda que app debe ser reemplazado por el nombre de la aplicación en la que trabajas.
El comando ejecutado te creará un archivo y deberás reemplazarlo con el siguiente contenido:
WorkerBoto3Table debe ser el nombre de la tabla en los datos Meta de la tabla WorkerBoto3. Puedes hacer un scan o un query para obtener los registros en los que quieres agregar un valor por defecto para el nuevo campo regimen_laboral.
Recuerda que, en caso de que tengas gran cantidad de registros en DynamoDB, puedes mejorar el tiempo de ejecución de este data migration utilizando algún método de actualización por lotes como batch_update_table_rows.
Hasta ahora todo parece ser sencillo, pero esto se complica cuando se quiere cambiar, agregar o quitar un hash key o range key.
Actualización de claves de una tabla
Dynamo DB permite trabajar con dos claves primarias en una tabla: hash key y range key. La primera clave es un identificador único que lo diferencia de otros registros y su idea es más cercana a un primary key de una base de datos.
Por otro lado, un range key puede representar a dos atributos del registro que permitan identificar de forma única un ítem (por ejemplo, los registros de viajeros con determinada aerolínea pueden tener un identificador, id o clave primaria, pero también pueden identificarse por el documento de identidad del viajero junto con el identificador del vuelo).
Estos conceptos son importantes porque en caso de que se quiera modificar la clave primaria en una tabla relacional, uno podría realizar la siguiente secuencia de pasos:
- Copiar la tabla en otra temporal con otro identificador temporal y un nuevo campo con el nuevo identificador (con otro nombre y como un campo común).
- Borrar la primera tabla u original.
- Crear nuevamente la primera tabla con el esquema actualizado.
- Copiar los datos de la tabla temporal a la nueva tabla.
Pynamo DB
- Con Pynamo DB, este proceso es largo porque se aplica de la misma forma que se realizaría con otra base de datos relacional como Postgres o MySQL.
Primero, el proceso de copiado de una tabla a otra puedes realizarlo de esta forma:
- Si tienes acceso para eliminar una tabla de Dynamo DB desde la interfaz con la que te comunicas desde tu proyecto en Django, no se presentarán dificultades. Puedes utilizar la siguiente sentencia, reemplazando WorkerPynamoDB por el nombre de tu modelo original.
En caso que no tengas permisos desde tu interfaz, deberías hacer una copia local o en otro ambiente, solicitar la eliminación y creación de la tabla y hacer la copia con el nuevo esquema.
Asimismo, puedes volver a crear una tabla con WorkerPynamoDB.create_table() en Pynamo DB con:
Boto3
- Si utilizas Boto3, el proceso es largo porque también se debe realizar la misma secuencia de pasos, aunque debes recurrir a un cliente o recurso de Boto3. Por ejemplo:
- Por otro lado, el proceso de la re-creación de la tabla original es sencilla y solo basta agregar unas líneas de código y crear un comando shell para que se ejecute en el pipeline del proyecto o realizarlo manualmente.
Si piensas que con esto se acaba la parte difícil te equivocas porque hay dos escenarios más:
- Modificar un Global Secondary Index (GSI) o un Local Secondary Index (LSI).
- Cambiar el tipo de dato de un GSI o LSI.
Modificar un GSI o LSI
Es importante saber la diferencia de los índices. Un GSI es un índice que tiene una clave de partición y una clave de clasificación opcional diferentes de la clave principal de la tabla base. Y un LSI es un índice que debe tener la misma clave de partición pero una clave de clasificación diferente de la tabla base.
Por otra parte, un GSI no cuenta con restricción de tamaño mientras que un LSI sí. Para cada valor de clave de partición el tamaño máximo es de 10 GB. En la creación y eliminación un GSI se puede realizar en cualquier momento, mientras en LSI solo cuando la tabla DDB se elimina.
Pynamo DB
Agregar o quitar un GSI es una tarea que no resulta tan fácil utilizando Pynamo DB y, en la práctica, muchas veces es necesario sobre todo cuando se quiere realizar un nuevo filtro para un modelo.
Se puede utilizar un scan pero esta operación es muy costosa en recursos y en tiempo, por lo que utilizar una query es más conveniente.
Boto3
- En este caso, boto3 tiene métodos que permiten interactuar a más bajo nivel y, por lo tanto, modificar los índices en un modelo ya existente es posible y más intuitivo.
- Permite realzar integraciones con otras librerías de forma más limpia. En el código se realizan varias llamadas a otros métodos, porque se hace uso de las tablas modelas con Pynamo DB. El método _hash_key_attribute devolverá la clave hash de la tabla, en caso que el índice tenga un range key, también se debe invocarlo y almacenarlo en otra variable.
El código que está dentro de update_table crece de acuerdo con la cantidad de atributos que tiene la tabla y debe tener en cuenta que al menos todos los atributos obligatorios (null=False) se deben incluir en AttributeDefinitions y se debe respetar el tipo de dato con el que es guardado en Dynamo DB.
- Otra cualidad importante de Boto3 es que se pueden realizar múltiples actualizaciones de índices en una actualización o update_table. En el ejemplo se actualiza un GSI, el cual es agregado en GlobalSecondaryIndexUpdates, pero si se quiere modificar un LSI, también se puede enviar en el parámetro LocalSecondaryIndexUpdates.
Modificar tipo de dato GSI o LSI
Ahora, realizar el cambio de tipo de dato de una clave (hash key o range key) de un índice es un proceso más largo, porque conlleva realizar una copia de datos ya existentes (como en la primera parte de esta sección) y luego una actualización con Boto3 como acaba de ser explicado.
Pueden existir casos más estructurados de actualizaciones sobre tablas. Sin embargo, estos casos representan un enfoque que puede ser aplicado en situaciones más complejas.
Conexión con la base de datos
Pynamo DB
- La configuración de la conexión del proyecto es transparente ya que se puede abstraer a través de las variables de entorno y la configuración principal del proyecto (usualmente en settings.py).
Los valores deben estar definidos para cada ambiente y las clases de Pynamo DB automáticamente utilizan estas variables para utilizar la base de datos en la nube.
Si deseas trabajar localmente, recuerda que 'AWS_REGION_NAME' debe tener el valor de local y los valores de 'AWS_ACCESS_KEY_ID' y 'AWS_SECRET_ACCESS_KEY' son por defecto nulos.
Boto3
- Aunque uno mismo debe integrar en el pipeline esta inicialización, el esfuerzo requerido es poco y se puede realizar escribiendo el siguiente código:
Alternativamente se puede reemplazar el uso de un client por un resource de la siguiente manera:
Las instrucciones anteriores son muy parecidas, pero la diferencia entre usar un cliente y un recurso en Boto3 es que el recurso permite realizar operaciones a más alto nivel, como Pynamo DB, es ideal cuando se quieren leer registros o actualizarlos en lotes, como al realizar un backup o copia de una tabla.
Por otro lado, utilizar un cliente permite trabajar a más bajo nivel como realizar actualización del esquema de una tabla o índice.
Conclusión
En resumen el mantenimiento de código en producción a la hora de trabajar con Dynamo DB y Django dependerá de las necesidades del proyecto que se lleve a cabo, muchas veces elegir una solución no cubrirá todas esas necesidades, por lo cual puedes combinar estas librerías entre sí, especialmente para las migraciones o actualizaciones del esquema.
Espero que este artículo te haya dado una idea de los retos a los que te enfrentarás con estas tecnologías y tengas un mejor panorama a la hora de elegir la tecnología o tecnologías que usarás.
Si tienes dudas más puntuales o quieres conversar acerca de este tema, puedes escribirme a milapacompiamachaca@gmail.com o buscarme en LinkedIn.
Listopro Community da la bienvenida a todas las razas, etnias, nacionalidades, credos, géneros, orientaciones, puntos de vista e ideologías, siempre y cuando promuevan la diversidad, la equidad, la inclusión y el crecimiento profesional de los profesionales en tecnología.