#CRUD-L Una aplicación que gestiona datos utiliza ampliamente las operaciones CRUD-L (Create, Read, Update, Delete y List). El marco de desarrollo CRUD-L de Devkron facilita y automatiza la implementación de estas operaciones sobre las entidades de datos del sistema. CRUD-L está implementado según el patrón MVC <a href="https://en.wikipedia.org/wiki/Model-view-controller" target="_blank">Model View Controller</a> <img src="https://docs.induxsoft.net/es/devkron/Sitios-y-aplicaciones-Web-con-DKL/img/crud-l-mvc.svg"> ## Instalación y configuración Este marco se encuentra en el repositorio <a href="https://github.com/Induxsoft/dkl-web" target="_blank">https://github.com/Induxsoft/dkl-web</a> De manera predeterminada, los archivos controller.dkl, entity.dk, error.dk, form.dk, list.dk, mysql.dbr.dkh, view.dk, websencia.dk deben de estar en una carpeta llamada 'crudl' directamente en la raíz de binarios de Devkron (junto a la carpeta 'web' o 'fastcgi'). Estos archivos se usan por todos los sitios Web. * entry-point.dkl. Es el punto de entrada para el controlador y debe ser colocado dentro de la carpeta del sitio Web que lo va a emplear para que sus configuraciones sean exclusivas. * controller.dkl. Es el controlador principal y es responsable de la selección del modelo y vista apropiado para cada tipo de entidad.. * entity.dk. Es el modelo genérico que implementa las acciones CRUD-L con entidades de datos que se almacenan en tablas con el patrón de diseño de Devkron (Induxsoft TableModel) previsto por la biblioteca de funciones dbr.dkh y que incluye los campos de control ```sys_pk, sys_guid, sys_recver, sys_dtcreated, sys_timestamp y sys_deleted```. <a href="https://docs.induxsoft.net/es/devkron/Bibliotecas-de-funciones/dbr/dbr.md" target="_blank">Más información aquí</a> * view.dk. Es la vista genérica que proporciona una salida en JSON para las entidades provistas por el modelo. * error.dk. Salida de error, esta es una vista predeterminada en caso de error, probablemente deberá modificarla para adecuarla a sus requisitos de usabilidad y estilo. * mysql.dbr.dkh. Contiene funciones específicas para MySQL, incluyendo la suplantación de identidad de una base de datos en otra. * websencia.dk. Contiene funciones para renderizar vistas creadas con Websencia (páginas en formato JSON) ### Conexión a la base de datos Existen tres formas de conectar con la base de datos: 1. Conexión a través del proveedor de indentidades. La autenticación de la identidad del usuario y conexión a la base de datos llegan establecidos por la capa de autorización. 2. Conexión por suplantación de identidad. La identidad del usuario y sus permisos se establecen por la capa de autorización, pero se deberá obtener un identificador de sesión en la base de datos. 3. Conexión constante. La base de datos y el usuario son establecidos como valores constantes en el punto de entrada (entry-point.dkl). Dependiendo la forma de conexión elegida, el controlador requiere que se suministren parámetros a través de parámetros en la url (recuerde que los parámetros de url se encuentran disponibles en @http_context/request/get). ### Parámetros de URL conocidos por el controlador * ```_entities_type``` Este parámetro en la url indica el tipo de entidades sobre los que aplicarán las operaciones. * ```_entity_id``` Este parámetro indica la clave primaria o identificador de la facilita cuando se requiere (opcional) * ```_key``` Indica el nombre del campo que usado específicamente como identificador, si no se indica se asume el campo ```sys_pk``` o ```sys_guid``` si ```_entity_id``` es alfanumérico * ```_app_group``` (Opcional) El nombre de un grupo de aplicaciones configurado en el archivo connections.xml. * ```_connection``` (Opcional) El nombre de una conexión en el grupo de aplicaciones indicado. * ```ws``` (Opcional) Identificador de espacio de trabajo si la conexión se establece por suplantación de identidad desde un workspace de Induxsoft. Para cualquier forma de conexión deberá indicar ```_entities_type```, los demás solo se requieren si así lo considera en sus configuraciones. ### Mapeo hacia el punto de entrada Aunque los parámetros de Url puede establecerse como tales, resulta conveniente indicarlos a través de mapas en routes.map ``` /misistema/{_entities_type}/{_entity_id?} > carpertademisistema/entry-point.dkl ``` Ejemplos: * Url sin mapa: ```https://midominio.com/misistema/entry-point.dkl?_entities_type=cliente&_entity_id=10``` * Url con mapa: ```https://midominio.com/misistema/cliente/10``` ### Configuraciones en el punto de entrada entry-point.dkl Es el punto de entrada y el lugar en donde se puede configurar el funcionamiento del marco. #### Enmascaramiento de url Para brindar Urls más descriptivas y organizar mejor su sistema, es posible enmascarar los puntos finales y redirigirlos a entidades específicas. Por ejemplo suponga que tiene un CRUD-L para comprobantes de movimientos de almacen. Las entidades podrían llamarse ```moventrada``` y ```movsalida```, lo que significaría tener rutas como: ``` /misistema/moventrada /misistema/movsalida ``` No obstante estas rutas son más descriptivas y ofrecen una mejor organización: ``` /misistema/inventarios/movimientos/entrada /misistema/inventarios/movimientos/salida ``` Para lograr esto use las funciones ```crudl.routes.pattern``` y ```crudl.routes.entity``` junto con un enrutamiento en routes.map Ejemplo (entry-point.dkl) ``` do crudl.routes.pattern("/module/submodule/process") do crudl.routes.entity("inventario/movimientos/entrada","moventrada") do crudl.routes.entity("inventario/movimientos/salida","movesalida") ``` (routes.map) ``` /{module}/{submodule}/{process}/{_entity_id?} > entry-point.dkl ``` #### Variables globales configurables ``` @path_root="web/nombre_host" @base_path="ruta relativa en donde se encuentran las entidades" @db_engine="Contiene el tipo de gestor de base de datos, por defecto viene con MY_SQL" // Establezca estos valores únicamente si no va a conectarse a través de la capa de autorización de bases de datos @crudl.qname="Nombre cualificado hacia una conexión de la base de datos" //Solo si la conexión y usuario serán constantes @crudl.user="Nombre del usuario" @crudl.pwd="Contraseña del usuario" /* Si no se define el valor de @crudl.qname puede pasarse la conexión por la url como se muestra a continuación el archivo controller.dkl esta preparado para recibir variables a través de una petición GET para poder definir una conexión o bien las entidades. - _app_group //define la aplicación hacia el archivo de conexión de devkron - _connection //define la conexión hacia el archivo de conexión de devkron ejemplo http://myhost/entry-point.dkl?_app_group=myapp&_connection=myconnection&_entities_type=myentidad También puede utilizar un enrutamiento como lo muestra en: https://docs.induxsoft.net/es/devkron/Sitios-y-aplicaciones-Web-con-DKL/flujo-http.md */ ``` ### Lógica del controlador <img src="https://docs.induxsoft.net/es/devkron/Sitios-y-aplicaciones-Web-con-DKL/img/flujo-controlador-mvc-crudl.svg"/> #### Selección de operación Con base en el método HTTP y la URL <table class="table"> <thead> <tr> <th>Método</td> <th>...{_entities_type}/</td> <th>...{_entities_type}/{_entity_id}</td> </tr> </thead> <tbody> <tr> <td>GET</td> <td>list</td> <td>read</td> </tr> <tr> <td>POST</td> <td>create</td> <td>error</td> </tr> <tr> <td>PUT</td> <td>error</td> <td>update</td> </tr> <tr> <td>PATCH</td> <td>error</td> <td>update</td> </tr> <tr> <td>DELETE</td> <td>error</td> <td>delete</td> </tr> </tbody> </table> #### Selección de vista La vista (salida devuelta) se basa en el tipo de contenido indicado en el encabezado ```Accept``` de la solicitud HTTP y la operación realizada Si ```Accept``` incluye el tipo y subtipo MIME ```text/html```, se intentará responder contenido HTML de acuerdo a la siguiente tabla: <table class="table"> <thead> <tr> <th>Operación</td> <th>Éxito</td> <th>Fracaso</td> </tr> </thead> <tbody> <tr> <td>LIST</td> <td>list view</td> <td>error view</td> </tr> <tr> <td>READ</td> <td>form view</td> <td>error view</td> </tr> <tr> <td>CREATE</td> <td>redirect</td> <td>form view</td> </tr> <tr> <td>UPDATE</td> <td>redirect</td> <td>form view</td> </tr> <tr> <td>DELETE</td> <td>redirect</td> <td>error view</td> </tr> </tbody> </table> Si ```Accept``` NO incluye ```text/html```, la respuesta será JSON (```Content-type:application/json```) Cada tipo de entidad deberá tener una carpeta con su nombre en la ruta indicada por ```@base_path``` Por ejemplo para una entidad ```cliente``` con ```@base_path="/misistema/entidades"``` puede tener los siguientes archivos: ``` /misistema/entidades/cliente/form.dk /misistema/entidades/cliente/list.dk /misistema/entidades/cliente/model.dk /misistema/entidades/cliente/controller.dk ``` Donde * form.dk Es la vista específica de la entidad para la presentación como formulario * list.dk Es la vista específica de las entidades presentadas como lista * controller.dk Es un controlador específico para la entidad (opcional) * model.dk Es un modelo específico para la entidad (opcional) En general, si no se ha existe un archivo controller.dk o model.dk en la carpeta de la entidad, se usan el controlador (controller.dk) y modelo (entity.dk) predefinidos. #### Operación y formatos de datos de respuesta La operación se determina con base en el método HTTP utilizado en la solicitud de una url. * Create. Método HTTP POST sin el parámetro ```_entity_id``` o con ```_entity_id=_new``` * Read. Método HTTP GET indicando el parámetro ```_entity_id``` * Update. Método HTTP PATCH con el parámetro ```_entity_id``` o POST con encabezado HTTP ```Accept:text/html``` y el parámetro ```_entity_id``` * Delete. Método HTTP DELETE indicando el parámetro ```_entity_id``` * List. Método HTTP GET sin indicar el parámetro ```_entity_id``` El formato de los datos de respuesta será HTML producido por una vista específica si el encabezado Accept de la solicitud HTTP es text/html, en cualquier otro caso responderá la vista genérica (view.dk) con los datos proporcionados por el modelo como JSON. Las operaciones CREATE y UPDATE aceptan datos codificados como ```application/json``` útil si se envían programáticamente con Javascript (u otros lenguajes/herramientas) o bien, como ```application/x-www-form-urlencoded``` que es simple de enviar mediante formularios HTML estándar. #### Interfaz Web (Html) La implementación del CRUD-L con interfaz de usuario Web con vistas específicas realiza las acciones de adición y edición de elementos siguiendo el patrón <a href="https://es.wikipedia.org/wiki/Post/Redirect/Get" target="_blank">PRG (Post - Redirect - Get).</a> Para solicitar el contenido HTML del formulario vacío para agregar, se realiza una solicitud GET con ```_entity_id=_new```, mientras que para obtener el formulario con los datos de una entidad para editarla se indica su identificador (```sys_pk```, ```sys_guid``` u otro); el valor del identificador para adición (_new) puede establecerse a través de la variable global ```@entity_id_blank="_new"``` <img src="https://docs.induxsoft.net/es/devkron/Sitios-y-aplicaciones-Web-con-DKL/img/prg-pattern.svg"> Siempre debe establecerse el encabezado HTTP Accept: text/html para que la respuesta del método POST usado para agregar (create) o actualizar (update) envíe la redirección (encabezado Location) en lugar de los datos en JSON de la entidad creada o actualizada. Se ha implementado el comportamiento de actualización en el método POST (además de en PATCH) para facilitar la programación del lado del agente del usuario (navegador). #### Web services REST Como puede deducirse, las operaciones Create, Read, Update, Delete y List que son invocadas a través de los correspondientes métodos HTTP (POST, GET, PATCH y DELETE) sin el encabezado ACCEPT: text/html, implementan automáticamente la funcionalidad esperada para un servicio <a href="https://en.wikipedia.org/wiki/REST" target="_blank">REST</a> Funcionando como Web services, las vistas específicas no se emplean, pero sí los modelos específicos si existen. Se espera que la carga útil (payload) se envíe en JSON y la respuesta generada por la vista genérica (view.dk) será tamién JSON. Puede deshabilitar este comportamiento automático estableciendo ```@auto_crud=@false``` en el entry-point.dkl ##### CREATE Crea (agrega) una nueva entidad. ###### Solicitud End points válidos: * ```{aplicación}/{conexión}/{tipo_entidad_plural}/``` * ```{aplicación}/{conexión}/{tipo_entidad_plural}/{identificador}/``` Método HTTP: ```POST``` Content-Type: ```application/JSON;charset=utf-8``` Carga útil: ```JSON``` ###### Respuesta * Content-Type: ```application/JSON;charset=utf-8``` * Código de estado de respuesta HTTP: ```200 (Ok)``` * Cuerpo de la respuesta: ```Entidad completa en JSON``` ###### Observaciones Si no se incluye el id de la entidad en la URL de solicitud HTTP, deberá incluirse en el cuerpo de la carga útil; si se incluye en ambos lugares se le dará prioridad a la URL. ##### READ Obtiene una entidad completa ###### Solicitud End point válido: * ```{aplicación}/{conexión}/{tipo_entidad_plural}/{identificador}/``` Método HTTP: ```GET``` ###### Respuesta * Content-Type: ```application/JSON;charset=utf-8``` * Código de estado de respuesta HTTP: ```200 (Ok)``` * Cuerpo de la respuesta: ```Entidad completa en JSON``` ##### UPDATE Actualiza una entidad ###### Solicitud End point válido: * ```{aplicación}/{conexión}/{tipo_entidad_plural}/{identificador}/``` Métodos HTTP: ```PUT``` y ```PATCH``` ###### Respuesta * Content-Type: ```application/JSON;charset=utf-8``` * Código de estado de respuesta HTTP: ```200 (Ok)``` * Cuerpo de la respuesta: ```Entidad completa en JSON``` ###### Observaciones Use PUT si va a incluir todos los campos de la entidad, en caso contrario use PATCH. Si usa PUT y no incluye todos los campos, los que no se hayan incluído se establecerán a NULL o su valor predeterminado. ##### DELETE Elimina una entidad ###### Solicitud End point válido: * ```{aplicación}/{conexión}/{tipo_entidad_plural}/{identificador}/``` Método HTTP: ```DELETE``` ###### Respuesta * Código de estado de respuesta HTTP: ```204 (Sin cuerpo en la respuesta)``` * Cuerpo de la respuesta: ```Vacío``` ##### LIST Devuelve una lista de todas las entidades ###### Solicitud End point válido: * ```{aplicación}/{conexión}/{tipo_entidad_plural}/``` Método HTTP: ```GET``` ###### Respuesta * Content-Type: ```application/JSON;charset=utf-8``` * Código de estado de respuesta HTTP: ```200 (Ok)``` * Cuerpo de la respuesta: ```Array de entidades en JSON``` ###### Observaciones Pueden establecerse parámetros de filtro, orden, tamaño de la lista, etcétera en la URL ##### Identificadores y campos de sistema Las entidades con fuerte control de concurrencia implementan el patrón de diseño de tablas de bases de datos relacionales de Devkron, que incluye al menos los siguientes campos controlados por el sistema: * ```sys_pk``` * ```sys_guid``` * ```sys_dtcreated``` * ```sys_timestamp``` * ```sys_recver``` * ```sys_deleted``` Puede usar como identificador tanto ```sys_pk```,```sys_guid``` o el campo que se haya definido como tal. En actualizaciones de entidades que incluyan sys_recver, siempre deberá enviarlo en la carga útil. Ningún campo de sistema (```sys_*```) es actualizable por el usuario (programador). ##### Parámetros pre-definidos Por conveniencia, los parámetros pre-definidos (previamente conocidos) inician con el caracter _ * ```_method``` Método HTTP * ```_entities_type``` Nombre del tipo de entidades * ```_entity_id``` Identificador de la entidad * ```_limit``` Cantidad máxima de elementos a devolver en LIST * ```_fields``` lista delimitada por comas de los campos a devolver (READ y LIST) * ```_order``` especificación del orden de la lista (LIST) * ```_start``` Índice de inicio de la lista * ```_key``` Nombre del campo clave usado ## Implementación ### Implementación del modelo Para definir un modelo personalizado que extienda la funcionalidad predeterminada debe crear un archivo denominado ```model.dk``` en la carpeta de la entidad de datos. #### Variables globales del modelo Las siguientes variables globales permiten configurar varias características de la funcionalidad predeterminada. ``` // Nombre de la tabla subyacente, si no se establece se asume el nombre del parámetro _entities_type @table_name="" // Nombre del campo clave predeterminado (no debería modificar este valor) @keyfield="sys_pk" // Consulta SQL para obtener los datos de 1 elemento de datos (usada en la operación READ), aquí puede definir uniones, renombrar o limitar los campos a devolver @read_query="select * from #<@table_name> where #<@keyfield>=@_entity_id limit 1;" // Consulta para obtener la clave primaria de una tabla 'principal o maestra' @get_sys_pk="select sys_pk from #<@table_name> where #<@keyfield>=@_entity_id and ifnull(sys_deleted,0)=0 limit 1;" // Consulta para obtener la lista de elementos de datos (usada en la operación LIST) @list_query="select * from #<@table_name> where ifnull(sys_deleted,0)=0;" // Lista de campos considerados de sistema (no es necesario modificar nada) @sys_fields="sys_pk, sys_guid, sys_dtcreated, sys_timestamp, sys_recver,sys_deleted, sys_lock" // Campos que se actualizaran (operación UPDATE), * indica que todos los que se envíen se actualizaran o bien puede indicar una lista delimitada por comas @update_fields="*" // Campos que se establecerán en una inserción (operación CREATE), * indica que todos los que se envíen se establecerán o bien puede indicar una lista delimitada por comas @create_fields="*" // Campos que se excluirán en una operación CREATE @create_exclude_fields="" // Campos que se excluirán en una operación UPDATE @update_exclude_fields="" // Alias de campos en la actualización, es una lista de pares alias:campo delimitados por comas @update_alias_fields="" // Alias de campos en la inserción (CREATE), es una lista de pares alias:campo delimitados por comas @create_alias_fields="" // Establece que cuando se realicen operaciones de entidades con elementos dependientes (hijos maestro/detalle) // se realizará en el contexto de una transacción de base de datos (no debería modificar este valor) @use_transaction=@true ``` #### Referencias a funciones del modelo La extensión (personalización) del modelo debe realizarse mediante la creación de funciones que realicen las tareas específicas necesarias y además, estableciendo punteros de referencia a ellas para que se integren al flujo previsto. Las siguientes variables globales son referencias a funciones: * ```@create``` Referencia a una función que realiza la inserción de datos, requiere los parámetros: &db, &params, &data * ```@read``` Referencia a una función que devuelve un objeto de datos, requiere los parámetros: &db, &params * ```@update``` Referencia a una función que realiza la actualización de datos, requiere los parámetros: &db, &params, &data * ```@delete``` Referencia a una función que elimina un elemento de datos, requiere los parámetros: &db, &params * ```@list``` Referencia a una función que devuelve una lista de elementos de datos, requiere los parámetros: &db, &params * ```@blank``` Referencia a una función que devuelve un elemento de datos vacío (o con datos predeterminados), requiere los parámetros: &db, &params Use la sentencia ```point to``` de Devkron como en el ejemplo: ``` mifuncion_update::&db,&params,&data { // Escriba aquí la funcionalidad para actualizar los datos que recibe a través de data en la base de datos db con los parámetros (generalmente de url) de params si fuese necesario } // Reemplace la función predeterminada de actualización por su propia implementación point @update to mifuncion_update ``` ### Implementación de las vistas Las vitas se ejecutan en un contexto diferente al programa principal que responde a la solicitud HTTP, por lo que reciben la información necesaria a través de la variable global ```@crud_context``` que tiene los siguientes miembros: * ```output``` Es una referencia a los datos de salida (que generalmente serán pintados en el formulario, usualmente se trata de los campos del elemento de datos) * ```.``` Es un alias de output * ```input``` Es una referencia a los datos de entrada de la operación (usualmente lo que se envió desde el formulario) * ```parameters``` Una referencia a los parámetros de la solicitud (usualmente parámetros de la URL) * ```error``` Es nulo si todo ha ido bien o una referencia a un objeto con información del error ocurrido * ```database``` Es una referencia a la base de datos * ```http``` Es una referencia a todo el objeto ```@http_context``` de la solicitud HTTP en curso #### Vista de lista Para definir una vista de lista personalizada se require un archivo llamado ```list.dk``` en la carpeta de la entidad. #### Vista de formulario Para definir una vista de formulario personalizada se require un archivo llamado ```form.dk``` en la carpeta de la entidad. ## Otras configuraciones ### Cambio del motor de base de datos cuando se requiere la suplantación de identidad Modifique controller.dkl únicamente si el gestor de base datos que va utilizar es diferente a MY_SQL Ejemplo ``` @db_engine="sql_server" //variable global de entry-point switch @db_engine //bloque de código al principio de controller.dkl { case "sql_server" { include "crudl/sql_server.dbr.dkh" } } ``` sql_server.dbr.dkh debe implementar las mismas funciones que mysql.dbr.dkh