Skip to content
Miguel Romero Arjona edited this page Jan 24, 2024 · 21 revisions

Microservicio de Clases y Materiales (Learning)

Introducción

El Microservicio de Clases y Materiales (Learning) es una herramienta integral que facilita la gestión de contenidos educativos asociados a cursos específicos. Este servicio permite a los usuarios subir, visualizar y actualizar tanto materiales como clases, brindando un control detallado sobre el contenido educativo. Con funcionalidades que van desde la obtención de información detallada sobre clases y materiales hasta la posibilidad de asociarlos a cursos existentes, este microservicio proporciona flexibilidad y eficiencia en la administración de recursos educativos, contribuyendo así a una experiencia de aprendizaje más efectiva.

Nivel de acabado al que se presenta

Nivel 10

Descripción de la aplicación

Note

Puede encontrar más información de la aplicación a la aquí descrita en el documento adjuntado en la entrega con nombre "Identificación de microservicios.pdf"

El microservicio Learning se encarga de gestionar la lógica de negocio relacionada con las clases y materiales asociados a un curso específico. Este servicio ofrece diversas opciones para manejar de manera eficiente y efectiva los elementos clave del aprendizaje. A continuación, se detallan las funcionalidades principales:

Materiales

En el ámbito de los materiales, el microservicio brinda la capacidad de acceder a la lista completa de materiales que has subido, permitiéndote tener un control detallado sobre tu contribución al curso. Además, ofrece la opción de examinar en detalle cualquier material específico si has comprado un curso y está asociado a el, proporcionando información completa sobre su contenido.

Además, el sistema te permite realizar un seguimiento de los usuarios que han adquirido los materiales que has subido, brindándote una visión clara del impacto de tu contenido en la audiencia. Puedes contribuir aún más al curso subiendo nuevos materiales, así como gestionar la asociación y desasociación de materiales a cursos existentes, lo que te brinda flexibilidad y control.

Para garantizar la actualización constante, el microservicio permite la modificación de la información asociada a un material existente y la eliminación de materiales que ya no son necesarios.

Clases

En el ámbito de las clases, el microservicio ofrece la posibilidad de acceder a información detallada sobre cada clase, incluyendo el correspondiente video educativo. Esto permite a los usuarios obtener una comprensión completa de cada lección y facilita el proceso de aprendizaje.

Adicionalmente, puedes contribuir al contenido del curso subiendo nuevas clases y asociándolas de manera efectiva a cursos específicos. Si es necesario, la plataforma permite la actualización de la información asociada a una clase existente, proporcionándote la flexibilidad para mejorar y ajustar el contenido con el tiempo.

Para mantener la coherencia y relevancia del curso, el microservicio facilita la eliminación de clases que ya no son necesarias, garantizando así un ambiente de aprendizaje actualizado y enfocado en los objetivos del curso.

Así mismo, nuestro microservicio se beneficia de la infraestructura robusta basada en la nube de Google Cloud para el almacenamiento eficiente de archivos asociados a materiales y clases. Utilizando los servicios de almacenamiento de objetos de Google Cloud, el microservicio garantiza un manejo seguro y escalable de los recursos educativos, permitiendo un acceso rápido y fiable a los materiales y clases subidos por los usuarios.

Además, para la comunicación con los demás microservicios, se utiliza RabbitMQ como gestor de mensajería asíncrona, también implementado por nosotros.

Descomposición en microservicios

Important

Puede encontrar la descomposición en microservicios en el documento adjuntado en la entrega con nombre "Identificación de microservicios.pdf"

Customer agreement de la aplicación

Important

Puede encontrar el Customer Agreement de la aplicación en el documento adjuntado en la entrega con nombre "Customer_Agreement_v1.1.pdf"

Si desea acceder a versiones anteriores, visite el repositorio documental del equipo.

Análisis de la capacidad y justificación de costes

Important

Puede encontrar el Análisis de la capacidad y justificación de costes de la aplicación en el documento adjuntado en la entrega con nombre "CapacityAnalysis_v1_0_0.pdf"

Si desea acceder a versiones anteriores, visite el repositorio documental del equipo.

Descripción del API REST del microservicio

  • Puede acceder a la documentación del microservicio de learning a través del siguiente enlace.
  • Puede acceder a la documentación del microservicio de comunicación a través del siguiente enlace.

Justificación de requisitos

Nivel hasta 5 puntos

Microservicio básico completamente implementado:

  • El backend debe ser una API REST tal como se ha visto en clase implementando al menos los métodos GET, POST, PUT y DELETE y devolviendo un conjunto de códigos de estado adecuado. -> Véanse los archivos de la carpeta routes, classes.ts y materials.ts.

  • La API debe tener un mecanismo de autenticación. -> Se utilizó JWT para realizar la autenticación, con las funciones serverless del microservicio de usuarios. Véanse las funciones utilizadas en el archivo jwtUtils.ts.

  • Debe tener un frontend que permita hacer todas las operaciones de la API (este frontend puede ser individual o estar integrado con el resto de frontends). → El equipo utilizó un Frontend común, desarrollado en el repositorio al que se accede a través el siguiente enlace.

Important

En dicho repositorio está el frontend desplegado e integrado con todos los microservicios, aunque esa era la idea en un principio. Como se puede ver en el historial de commits, el grupo encargado de cursos y reviews y el grupo encargado de pagos (mayormente la importancia recae en el de cursos), empezaron a intentar desplegar sin éxito el domingo 21 de enero, aún habiendo compañeros que, en reuniones pasadas con mucha antelación, ya aconsejaron hacer el despliegue cuanto antes para poder integrar el frontend correctamente. Aún con estas advertencias, "hicieron" el despliegue casi definitivo (véase "hicieron" pues siguieron modificando servicios de backend durante todo el día) con los servicios a usar por nosotros el martes 23 de enero, a un día de la entrega. Sabiendo esto, y como previsiblemente no iba a estar ni el backend ni el frontend de dicho microservicio a tiempo, el domingo 21 de enero, teniendo nosotros el frontend a medio terminar esperando dicho despliegue para comprobar la funcionalidades, decidimos revertir cambios e intentar mostrar nuestra parte de frontend de forma estática, mockeando cursos y parte de funcionalidades de materiales debido a la necesidad que había de los servicios de cursos, en gran parte por las comunicaciones asíncronas que no habían hecho a tiempo. Solo se pudo integrar con el microservicio de usuarios, que sí desplegaron en su momento. También cabe destacar que además de mocker los cursos en el frontend como se ha especificado, también lo hemos tenido que hacer en backend, como se puede ver en este extracto de código que aparece abajo. Esto fue debido a que, era necesaria la comunicación con cursos, pero al ellos no tener ni implementado ni desplegado dicho servicios, tuvimos que hacer uso de mocks para que nuestro desarrollo que funcionase y poder mostrarlo. Este desarrollo con mocks lo realizamos en la rama learning del frontend.

router.get(
    '/course/:courseId',
    authUser,
    async (req: Request, res: Response) => {
        try {
            const courseId = req.params.courseId

            // Mock course
            const course = await getCourseById(courseId)

            if (!course) {
                return res.status(404).json({ error: 'Course not found' })
            }

            let decodedToken: IUser = await getPayloadFromToken(
                getTokenFromRequest(req) ?? ''
            )
            const username: string = decodedToken.username

            if (
                !course.access.includes(username) &&
                course.price !== 0 &&
                course.creator !== username
            ) {
                return res.status(403).json({
                    error: 'Unauthorized: You are not the authorized to get this course',
                })
            }

            const classes = await Class.find({ courseId })

            for (const _class of classes) {
                const publicUrl: string = _class.file
                const signedUrl = await generateSignedUrl(
                    publicUrl,
                    bucketName,
                    storage
                )
                _class.file = signedUrl.readUrl
            }

            return res
                .status(200)
                .json(classes.map((_class) => _class.toJSON()))
        } catch (error) {
            return handleError(res, error)
        }
    }
)

El endpoint que se muestra a continuación, encargado de enlazar un material a un curso de forma que así los usuarios que compren dicho curso tengan acceso, no se pudo comprobar al 100% ni usarlo en nuestro frontend mockeado debido a la falta de tiempo por los problemas comentados anteriormente.

router.post(
    '/:id/course/:courseId/associate',
    authUser,
    async (req: Request, res: Response) => {
        try {
            let decodedToken: IUser = await getPayloadFromToken(
                getTokenFromRequest(req) ?? ''
            )
            const username: string = decodedToken.username
            const material = await Material.findById(req.params.id)
            if (!material) {
                return res.status(404).json({ error: 'Material not found' })
            }

            if (material.author !== username) {
                return res.status(403).json({
                    error: 'Unauthorized: You are not the author of this material',
                })
            }

            const data = {
                courseId: req.params.courseId,
                materialId: req.params.id,
            }
            await sendMessage(
                'courses-microservice',
                'notificationAssociateMaterial',
                process.env.API_KEY ?? '',
                JSON.stringify(data)
            )
            return res.status(204).send()
        } catch (error) {
            return res.status(500).send()
        }
    }
)

Por mostrar otro ejemplos se muestra otro endpoint, donde a día 22 de enero, se preguntó si era de verdad necesaria la comunicación asíncrona para las reviews:

router.get('/:id', authUser, async (req: Request, res: Response) => {
    try {
        const idParameter = req.params.id
        if (!isValidObjectId(idParameter, res)) {
            return
        }

        let decodedToken: IUser = await getPayloadFromToken(
            getTokenFromRequest(req) ?? ''
        )
        const username: string = decodedToken.username

        const materialId = req.params.id
        const material = await Material.findById(materialId)
        if (!material) {
            return res.status(404).json({ error: 'Material not found' })
        }

        if (
            material.author === username ||
            material.price === 0 ||
            material.purchasers.includes(username)
        ) {
            const publicUrl: string = material.file

            const signedUrl = await generateSignedUrl(
                publicUrl,
                bucketName,
                storage
            )
            material.file = signedUrl.readUrl

            let materialReview = null
            await redisClient.exists(materialId).then(async (exists: any) => {
                if (exists === 1) {
                    await redisClient.get(materialId).then((reply: any) => {
                        materialReview = reply
                    })
                } else {
                    const message = JSON.stringify({
                        materialId,
                    })
                    await sendMessage(
                        'courses-microservice',
                        'requestMaterialReviews',
                        process.env.API_KEY ?? '',
                        message
                    )
                }
            })

            const materialJSON: Record<string, any> = material.toJSON()
            materialJSON['review'] = materialReview
            return res.status(200).json(materialJSON)
        }
        return res.status(403).json({
            error: 'Unauthorized: You are not the author of this material or you have not purchased it',
        })
    } catch (error) {
        handleError(res, error)
    }
})

Por lo que, aunque ya se especificará más adelante que se realizó el extra de Vista Materializable, solo se pudo comprobar que funcionaba localmente, simulando la respuesta del microservicio de reviews, pues de manera real no se pudo nunca.

  • Debe estar desplegado y accesible en la nube -> Accesible desde Google Cloud:

  • La API que gestione el recurso también debe ser accesible en una dirección bien versionada -> La ruta de la API está bien versionada, especificándose en el hostname (y puerto en caso de ser en local), el texto /v1, indicando que es la primera versión de la API. Para hacer una llamada a un endpoint, habrá que seguir dicha nomenclatura, por ejemplo, /v1/materials/.

  • Se debe tener una documentación de todas las operaciones de la API incluyendo las posibles peticiones y las respuestas recibidas:

    • Puede acceder a la documentación del microservicio de learning a través del siguiente enlace.
    • Puede acceder a la documentación del microservicio de comunicación a través del siguiente enlace.
  • Debe tener persistencia utilizando MongoDB u otra base de datos no SQL. -> Se utilizó MongoDB para la persistencia de datos, véanse los archivos que contiene la carpeta db.

  • Deben validarse los datos antes de almacenarlos en la base de datos (por ejemplo, haciendo uso de mongoose). -> Se ha utilizado mongoose para la validación de los datos antes de almacenarlos, véase en los modelos definidos en la carpeta models.

  • Se debe utilizar gestión del código fuente y mecanismos de integración continua: o El código debe estar subido a un repositorio de Github siguiendo Github flow o El código debe compilarse y probarse automáticamente usando GitHub Actions u otro sistema de integración continua en cada commit. -> Véanse los archivos que contiene la carpeta worflows.

  • Debe haber definida una imagen Docker del proyecto -> Véase el archivo Dockerfile definido.

  • Debe haber pruebas de componente implementadas en Javascript para el código del backend utilizando Jest y pruebas de integración con la base de datos. -> Véase la carpeta test para learning y para communication.

Important

A pesar de tener el archivo de autenticación de Google Cloud en los secrets de GitHub Actions, las pruebas de integración no se ejecutan correctamente en el workflow, pero sí localmente. Por esta razón está configurado para que se salten, en caso de que quiera comprobar el correcto funcionamiento solicítenos el archivo de autenticación para que puedas ejecutarlas.

✅ Diseño de un customer agreement para la aplicación en su conjunto con dos planes de precios que considere características funcionales y extrafuncionales. Este diseño debe contemplar el uso de SendGrid o algún otro servicio externo con un plan de precios múltiple. -> Archivo referenciado anteriormente.

✅ Documento incluido en el repositorio del microservicio (o en el wiki del repositorio en Github) por cada pareja -> Archivo referenciado anteriormente.

✅ Vídeo demonstración de la aplicación en funcionamiento. -> Enlace compartido en el README.txt de la entrega.

✅ Presentación de la aplicación.

Nivel hasta 7 puntos

✅ Todos los requisitos del nivel hasta 5 puntos

✅ Aplicación basada en microservicios básica implementada, i.e. la interacción completa entre todos los microservicios → Esto se ha logrado con la implementación de un microservicio de comunicación que hace uso de RabbitMQ, permitiéndo el envío de mensajes asíncronos entre los distintas microservicios.

✅ Análisis de límites de capacidad del customer agreement asumiendo que esto no tiene implicaciones en el precio. -> Archivo referenciado anteriormente.

✅ Al menos 3 de las características del microservicio avanzado implementados.

Se han implementado las siguientes características:

  • Utilizar algún otro tipo de almacenamiento de datos en cloud como Amazon S3 -> Hemos utilizado Google cloud storage con los buckets para el almacenamiento de ficheros, puede verse en los archivos de los endpoints.

  • Usar el patrón materialized view para mantener internamente el estado de otros microservicios. -> Véase el modelo de usuarios definidos y el archivo de función auxiliar.

  • Implementar cachés o algún mecanismo para optimizar el acceso a datos de otros recursos. Véanse los ficheros donde se hace uso de redis para almacenar las reviews que se le solicitan al microservicios de cursos para los materiales.

Nivel hasta 9 puntos

✅ Todos los requisitos del nivel hasta 7 puntos

✅ Un mínimo de 20 pruebas incluyendo casos positivos y negativos. -> Véase la carpeta test para learning y para communication.

✅ Justificación del coste de cada plan del customer agreement.

✅ Tener el API REST documentado con Swagger:

  • Puede acceder a la documentación del microservicio de learning a través del siguiente enlace.
  • Puede acceder a la documentación del microservicio de comunicación a través del siguiente enlace.

✅ Al menos 5 de las características del microservicio avanzado implementados, que son (aparte de las mencionadas antes):

  • Implementar un frontend con rutas y navegación. Véase el repositorio de frontend del equipo

  • Implementar mecanismos de gestión de la capacidad como throttling o feature toggles. -> Véase su uso en los distintos endpoints, y las funciones definidas para su uso en el archivo storage.ts.

✅ Al menos 3 de las características de la aplicación basada en microservicios avanzada implementados, que son:

  • Implementación de un mecanismo de autenticación homogéneo para todos los microservicios. -> Uso de funciones serverless definidas por el microservicio de usuarios.

  • Hacer uso de un API Gateway con funcionalidad avanzada como un mecanismo de throttling o de autenticación. -> Se ha utilizado Google cloud como plataforma de despliegue, haciendo uso de un Load Balancer.

  • Hacer uso de un sistema de comunicación asíncrono mediante un sistema de cola de mensajes para todos los microservicios. Si no es para todos, debe justificarse de forma razonada -> Hhemos implementado un microservicio que hace uso de RabbitMQ para establecer mensajería asíncrona.

Nivel hasta 10 puntos

✅ Implementar con feature toggles o un mecanismo similar los distintos niveles de funcionalidad ofrecidos a los clientes según el plan de precios elegido.

✅ Al menos 6 características del microservicio avanzado implementados, que son (aparte de las mencionadas antes):

  • Uso de URL firmadas para mejorar la seguridad y utilizar buckets privados

✅ Al menos 4 características de la aplicación basada en microservicios avanzada implementados, que son (aparte de las mencionadas antes):

  • Tener un frontend común que integre los frontends de cada uno de los microservicios. Cada pareja debe ocuparse, al menos, de la parte específica de su microservicio en el frontend común. -> Véase el repositorio de frontend del equipo.

  • Despliegue en Google cloud por parte del todo los equipos, utilizando Terraform para lanzar y configurar la infraestructura.

Extensión del documento del microservicio con un análisis de límites de capacidad del customer agreement asumiendo que esto no tiene implicaciones en el precio.

Archivo referenciado anteriormente.

Extensión del documento del microservicio con un estudio para determinar el coste de cada plan del customer agreeement de forma justificada en base al análisis de capacidad.

Archivo referenciado anteriormente.

Análisis de esfuerzo

Se uso para el conteo de horas la aplicación TrackingTime, integrada en nuestro equipo de Teams para mayor comodidad. Para dicha aplicación se uso el plan gratuito, y aunque al principio se podían generar gráficas, a fecha de 24 de enero, parece que se pasó esa funcionalidad de pago. Para resolver este inconveniente, se ha podido compartir esta URL pública que especifica todas las horas y el desglose de las tareas.