inmensia |
Stratos
Juan Mellado, 9 Mayo, 2008 - 16:11
Hace unos cuantos posts comenté la necesidad de guardar en la base de datos la clave del usuario codificada utilizando algún sistema de encriptación. Para romper un poco la monotonía de tanta teoría acerca del diseño de estas últimas semanas decidí intentar escribir mi propia clase para realizar la encriptación. No obstante, habida cuenta de que mi única experiencia práctica previa al respecto había sido el uso de la función MD5 que implementa PHP, casi me decantaba a priori por utilizar alguna librería gratuita disponible en C++, el lenguaje con el que voy a trabajar. Sin embargo, buscando información por Internet me he llevado la grata sorpresa de comprobar que los sistemas más populares de encriptación en realidad se basan en algoritmos que resultan sencillos de implementar. El algoritmo del MD5 (Message-Digest algorithm 5) por ejemplo, descrito en el RFC 1321, consiste en la ejecución de cinco simples pasos. Admite como entrada un bloque de información a codificar de cualquier longitud dada, y devuelve un código hash de 128 bits (16 bytes) que normalmente suele representarse en hexadecimal como una cadena de texto legible de 32 bytes. Su uso más común, además de la codificación de claves de usuario, ha sido tradicionalmente la firma de aplicaciones, es decir, proporcionar un código único (el hash) que garantice que un fichero es realmente el fichero que dice ser. Desgraciadamente este último uso ha dejado de ser práctico hoy en día, ya que existen métodos que añadiendo bytes extras a un archivo cualquiera consiguen que genere un código hash concreto. Al parecer el algoritmo es susceptible a las colisiones y han conseguido sacar partido de esa debilidad. No obstante, aún sigue siendo una alternativa válida para la codificación de claves. Los cinco pasos del algoritmo son los siguientes: 1) Añadir bits de relleno al bloque de información a codificar. Con este paso se busca hacer la longitud del bloque en bits (no bytes) congruente con 448 módulo 512. Es decir, que le falten 64 bits para ser múltiplo exacto de 512 bits (448 = 512 – 64). Esto debe hacerse siempre, incluso aunque la longitud inicial ya cumpla esta condición. Para extender el bloque se añadirá primero un bit a 1, y luego el resto a 0 hasta completar el tamaño que sea necesario. Pensando en bytes, que es como normalmente se trabaja, lo que pide este paso es que el tamaño del bloque sea congruente con 56 módulo 64. Es decir, que le falten 8 bytes para ser múltiplo entero de 64 bytes. El hecho de que este paso tenga que aplicarse siempre es lo que hace que incluso las cadenas vacías tengan un hash. 2) Añadir la longitud del bloque de información a codificar al propio bloque de información. En este paso lo que se hace es añadir la longitud inicial del bloque en bits (no bytes) al final del resultado del paso anterior. La longitud se agrega en formato little-endian (primero los bytes menos significativos) con un tamaño máximo de 8 bytes. Si la longitud ocupa más de 8 bytes entonces los más significativos simplemente se pierden sin que ello suponga ningún problema para el algoritmo. En este paso, al añadir los 8 bytes que se reservaron en el paso anterior, se consigue que el bloque sea múltiplo entero de 64 bytes. 3) Inicializar las variables intermedias de trabajo. Para el cálculo del hash se utilizan cuatro variables intermedias enteras de 32 bits denotadas como A, B, C y D. Cada una de ellas toma un valor inicial constante predefinido: A = 0x674523014) Procesar la información en bloques de 16 enteros de 32 bits. A resultas de los dos primeros pasos, el bloque de información acaba teniendo una longitud que es un múltiplo entero de 512 bits (64 bytes), por lo que se puede trabajar con él tomando bloques de 16 palabras enteras sin signo de 32 bits (4 bytes) cada una (4 * 16 = 64). En este punto el algoritmo define cuatro funciones denotadas como F, G, H e I, que admiten tres enteros sin signo de 32 bits y generan otro entero sin signo de 32 bits: F(x, y, z) = (x AND y) OR ( (NOT x) AND z )El propósito de estas funciones es extraer las características diferenciales del bloque de información, operando a nivel de bit, con el objetivo de generar el hash que lo identifique de forma unívoca. El propósito concreto de cada una, así como la base matemática en la que se sustenta todo el proceso queda fuera de mi comprensión. Las funciones se aplican dentro de un bucle en el que se van leyendo enteros sin signo de 32 bits del bloque, en tandas de 16 en 16, con el propósito de generar unos nuevos valores que se irán sumando a las variables A, B, C y D. Para preservar el valor original de las variables se guardan en una variables auxiliares al principio de cada iteración: AA = ALlegado este punto el algoritmo presenta cierta dificultad por la forma en que está descrito, agravado por el hecho de que el propio RFC contiene una errata (hay una "t" en lugar de una "i" en un par de líneas). Después de leerlo un par de veces, se entiende que hay que realizar 64 operaciones dentro del bucle en cada iteración. Aplicando la función F en las 16 primeras operaciones, la G en las 16 segundas, la H en las terceras, y la I en las cuartas. En el documento se utiliza una tupla en la forma [abcd k s i] para señalar las 16 operaciones a realizar con cada función (de izquierda a derecha, y de arriba hacia abajo): [ABCD 0 7 1] [DABC 1 12 2] [CDAB 2 17 3] [BCDA 3 22 4]La forma en la que ha de interpretarse las tuplas es la siguiente: a = b + ((a + FUNCTION(b,c,d) + BUFFER[k] + SIN[i]) <<< s)Para las primeras 16 operaciones FUNCTION sería F, para las 16 segundas G, y así sucesivamente. BUFFER[k] es simplemente el elemento k-ésimo dentro del bloque de 16 enteros sin signo que se esté procesando en ese momento dentro del bucle. SIN[i] es el valor absoluto de la parte entera del seno (la conocida función trigonométrica) de i (en radianes) multiplicado por 4.294.967.296 (2 elevado a 32). Este término mosquea un poco al principio, pero puede encontrarse los valores precalculados en un apéndice del propio RFC, que incluye un implementación completa en C. Por último, "<<<" es una rotación circular hacia la izquierda (lo que sale por la izquierda vuelve a entrar por la derecha) de tantos bits como indique "s". Es decir, expandiendo las tuplas se obtiene: A = B + ((A + F(B,C,D) + BUFFER[0] + 0xd76aa478) <<< 7)Finalmente se suman los valores obtenidos con los previos salvaguardados y se procede con la siguiente iteración: A = AA + A5) Componer el resultado final. El código hash resultante es la concatenación de A, B, C y D, empezando con el byte menos significativo de A y terminando con el más significativo de D.
Juan Mellado, 3 Mayo, 2008 - 12:10
Hace tres meses ya de mi último juego, así que va siendo hora de que me ponga manos a la obra con el siguiente. Esta vez me gustaría pasar más tiempo trabajando el apartado gráfico. Algo complicado, ya que normalmente mis objetivos/preferencias son de carácter técnico y no artístico. Tenía pensado hacer algo de tipo "tuberías", como los populares "Candy Train" (no encuentro el link, supongo que ya lo habrán descatalogado) o Rocket Mania de PopCap. Siempre me ha gustado la simplicidad, en lo referente a su jugabilidad, de esos dos programas. Acerca del primero de ellos, recuerdo que es una idea que incluso a mi se me ocurrió una día que viajaba en tren. Cuando llegué a casa busqué por Internet para ver si existía algo parecido. Después de seguir un par de enlaces pude comprobar que no era precisamente el colmo de la originalidad lo que se me había ocurrido. ![]() Para mi juego, tengo apuntadas varias ambientaciones distintas sobre las que poder trabajar. La primera es bastante obvia, la he llamado "One Way!", y consistiría en construir una carretera por la que circule un coche. E incluso mejor, un taxis, e incluso un autobús, ya que entonces se podría jugar (nunca mejor dicho) con conceptos como "parada" o "viajero" para establecer objetivos fáciles de identificar por los jugadores. Las señales de tráfico o los semáforos también podrían estar bien, sobre todo porque son señalizaciones con un significado conocido por todos y que no requerirían mayor explicación. La mayor dificultad para mi reside en el dibujado de un vehículo desde una perspectiva aérea, sobre todo con diversos ángulos de giro. Otra alternativa que se me ocurrió, a la que llamo "Jungle Path", consistiría en sustituir la carretera por una especie de liana, y el autobús por un mono que se moviera por ella. Los objetivos serían probablemente la recolección de comida, algún de tipo de fruta, y evitar a otros animales salvajes. Naturalmente con este diseño se me plantea el mismo problema que antes, ya que la creación de los gráficos se me hace aún más complicada que con la opción anterior. Para simplificar creo que me bastaría con algún tipo de representación simbólica, como una especie de token con las cabezas de los animales por ejemplo. El tercer diseño, por nombre "Cheese Maze", utilizaría un laberinto clásico como escenario, con ratones blancos a modo de cobayas como protagonistas y trozos de queso como objetivos. Curiosamente, aunque no acabo de concretarla, esta es la única opción para la que me he atrevido a hacer algo. Por una parte el laberinto, generado aleatoriamente, que se puede ver en la primera imagen de este post, tal y cual lo dibuja el prototipo actualmente. Teniendo en cuenta que está hecho en JavaScript no me puedo quejar del resultado, sobre todo por la continuidad de las paredas y las sombras, aunque hay mucho que pulir todavía. También he intentado dibujar un ratón, segunda imagen de este post, aunque el resultado no acaba de convencerme. ![]() Tengo algunas otras ideas sobre el asunto, pero es bastante probable que opte por una solución mucho más sencilla y acabe utilizando unas simples tuberías o mangueras para hacer pasar agua de un lado a otro del tablero, desde un grifo hasta una boca de riego o algo parecido. No me siento muy positivo hoy, creo que esto va ir para largo. Trabajar todos estos gráficos píxel a píxel suele llevarme mucho tiempo, y no pocos ajustes, hasta llegar a obtener un resultado que acabe de gustarme. El proceso que sigo es bastante básico y es el que siempre sugieren todos los tutoriales. Primero busco imágenes de referencia, después dibujo el contorno con las formas básicas (line-art), a continuación escojo la paleta de colores, los aplico prestando atención a la posición de las luces, y por último remato los detalles. Suena sencillo, pero me cuesta un mundo, será cuestión de practicar más a menudo. A escala tan pequeña todos y cada uno de los pixeles individualmente cuentan por si solos una barbaridad. Un simple píxel fuera de su sitio puede estropear el suave curso que se supone debería seguir una línea de contorno, por no mencionar como un simple cambio de tonalidad puede suponer el éxito o fracaso de un gráfico. Siempre he pensado que para apreciar mejor estas pequeñas obras de arte hay que ampliarlas de tamaño, para ver detalles que con sus dimensiones normales lucen fantásticas, y comprobar que se han resuelto con tan solo cuatro o cinco pixeles de distinto color. Hay mucho material increíble que puede encontrarse por Internet. Y es que parece que la masificación en el uso de dispositivos móviles ha hecho resurgir este noble arte del píxel-art que parecía destinado a quedar relegado a un segundo plano por el hoy omnipresente 3D.
Juan Mellado, 2 Mayo, 2008 - 15:34
Este post podría llevar perfectamente por título "Listas de valores, y otras alternativas", pero creo que el que le he puesto es más divertido. Incluso habrá hecho que alguno lea hasta aquí. Es algo bastante común que a la hora de crear un personaje se pueda elegir el sexo del mismo, pudiendo seleccionar normalmente entre hombre y mujer. Una elección sencilla. Sin embargo, para almacenar ese valor en una base de datos existen muchas posibles alternativas que merecen la pena ser estudiadas. En un diseño relacional tradicional, siguiendo el libro al pie de la letra, crearíamos una tabla de sexos y una relación (foreign key) con la tabla de personajes. Esta elección, que parece de lo más natural, en realidad pocas veces es la escogida. Aunque este es uno de los pilares básicos sobre los que se apoyan las base de datos relacionales, la normalización, raramente acaba de asimilarse para conceptos tan sencillos como el "sexo", en el que sólo se puede elegir entre dos valores posibles prefijados de antemano. ![]() Una alternativa bastante común a la creación de una nueva tabla es utilizar una columna de tipo texto, en la propia tabla de personajes en este caso, que almacene un carácter para distinguir entre "hombre" y "mujer". En castellano podría ser "H" y "M", por ejemplo, aunque más de uno podría distinguir la "H" como inicial de "hembra" (algo políticamente incorrecto, al parecer). De igual forma que una persona de habla inglesa podría asumir que la "M" es una abreviatura de "male", sobre todo recordando el hecho de que yo estoy utilizando nombres en inglés para las tablas y columnas. Pero esta elección no sólo plantea el problema de elegir que letras utilizar, sino que obliga a que la decisión que se tome esté bien documentada. Por que, ¿qué ocurre cuando la tabla está vacía?, ¿sabría alguien ajeno al proceso de diseño decir qué valores debería contener esa columna? No. Para evitar las confusiones que plantea la solución anterior, puede escogerse un enfoque algo distinto basándose en el hecho que sólo hay dos posibles valores donde elegir, tomándose el tipo de la columna como "Boolean". De esta forma, una columna de nombre "IS_MALE" eliminaría toda ambigüedad. Desgraciadamente "Boolean" no es un tipo estándar y no debería utilizarse aunque se encuentre soportado. A modo de anécdota, comentar que a veces se toma un camino intermedio entre esta solución y la anterior, definiendo una columna "IS_MALE" de tipo texto que puede tomar el valor "T" o "F", o sea, "true" o "false". El mismo perro con distinto collar. Algunos gestores de base de datos permiten definir reglas de validación (check constraints) sobre los valores que puede contener una columna concreta, de forma que sólo pueda almacenarse en ella valores que se encuentren dentro de un rango dado. De esta forma, cualquier intento de insertar o actualizar un registro con un valor fuera de dicho rango hace que el gestor retorne un error. Esto presenta la ventaja además de que el modelo queda auto-documentado al formar parte, la propia validación, de la definición de la tabla. El inconveniente es que no todos los gestores soportan este tipo de validaciones. Algunos, como MySQL, simplemente las parsean correctamente, pero no fuerzan su cumplimiento, y sugieren métodos alternativos como tipos "enumerados" ( Otro tema que se plantea a la hora de utilizar una solución u otra es la ocupación, el tamaño de almacenamiento requerido para la columna, ya sea en memoria o disco. Si se utiliza una tabla propia con una foreign key entonces hay sumar 4 bytes por personaje. Para un juego con cien cuentas registradas, con 4 personajes por cuenta, no debería suponer mayor problema con los estándares de almacenamiento actuales. E incluso para un millón de cuentas la cifra sigue siendo ridícula, sobre todo porque nunca se instanciarán todos los jugadores a un mismo tiempo en un mismo servidor. No obstante, si se detecta que la ocupación es un factor crítico, entonces se puede disminuir a 1 byte por registro utilizando la solución de la columna de texto. E incluso más, se puede reducir a 1 bit por registro almacenando el sexo del personaje en una columna cuyo contenido se trate como un colección de flags, en donde cada bit, o conjunto de ellos, tenga un significado concreto. A mi particularmente me parece que empezar diseñando un modelo relacional con flags es un claro ejemplo de "optimización temprana". Hay mucha gente que tiene una especie de fijación en la cantidad de bits desaprovechados a priori. Está bien pensar en ello, conocer al enemigo, pero siempre con una perspectiva del contexto y del dominio. Si las pruebas de volumen aconsejan optimizar la ocupación de la información, entonces se deberían plantear otra clase de alternativas, como por ejemplo tener dos tipos distintos para las claves primarias. Un tipo estándar de 4 bytes para las tablas con un gran número de registros, y otro tipo con menos requerimientos de ocupación, incluso de 1 byte, para las tablas con un número pequeño de registros. Lo que no se puede hacer de partida es renunciar a diseñar un modelo normalizado. Para evitar divagar en exceso, hace unos cuantos posts atrás comenté la necesidad que tenía de basarme en una temática concreta para hacer el modelo, en vez de intentar hacer algo demasiado genérico. Yo elegí un juego ambientado en el salvaje oeste, y más concretamente con referencias a la nación india. En este caso sólo se podría elegir entre hombre y mujer a la hora de crear personajes. Se crearía una tabla de sexos, con dos registros, y cada personaje apuntaría al registro adecuado. Lo curioso es que la idea de crear una tabla para contener sólo dos registros estáticos es algo que parece molestar a mucha gente. Lo que hay que ver es las posibilidades que ofrece, sobre todo porque los requerimientos siempre cambian. Por ejemplo, puede decidirse que las características intrínsecas de cada personaje, como su fuerza inicial, vengan determinadas por su sexo, lo que se solventaría añadiendo atributos a la nueva tabla de una forma bastante natural. O puede decidirse que algunos tipos de clases (razas) tengan otro género (hermafrodita), o carecer completamente de él (asexuado), lo que se solventaría simplemente incluyendo nuevos registros en la tabla. Aún así, hay bastantes expertos que recomiendan juntar todas las tablas con escasos registros y atributos comunes en una única tabla "listas de valores", con un ID, un tipo, y una descripción, aunque representen conceptos distintos. El problema surge en el momento que es necesario añadir un atributo específico para un concepto concreto, momento en que se rompe el esquema, y es necesario crear una tabla nueva para esa lista concreta que necesita dicho atributo y cambiar todas las referencias que pudieran existir. Tarea que normalmente es bastante tediosa de realizar cuando el sistema se encuentra ya en producción, y que puede llegar a suponer tener que realizar una parada del mismo.
Juan Mellado, 25 Abril, 2008 - 16:47
En el esquema que puse en el post anterior se echaban en falta los tipos de cada columna. Esta omisión fue intencionada. Decidir que tipo, tamaño, y opciones, ha de tener cada columna es otra de las cuestiones para las que, una vez más, hay que tomar muchos pequeños detalles en consideración. ![]() Para los IDs que conforman la clave primaria de cada tabla, un tipo numérico entero sin signo auto-incremental es la elección que resulta más natural. Y si se quiere portabilidad entre varias base de datos lo mejor es ceñirse a los tipos que define el estándar SQL. Para este diseño en concreto tomaré INTEGER, que es soportado tanto por MySQL como PostgreSQL: Ambos gestores requieren 4 bytes de almacenamiento para las columnas definidas con este tipo, ofreciendo un rango de valores que va desde –2147483648 hasta 2147483647. MySQL utiliza INT como sinónimo de INTEGER, y permite además definir la columna como UNSIGNED, características no soportadas por PostgreSQL. Definir la columna como auto-incremental es un poco más problemático en la medida que cada gestor implementa esta característica de una forma distinta. A mi me resulta de lo más natural utilizar objetos de tipo secuencia (SEQUENCE), ya que trabajo habitualmente con Oracle, pero desgraciadamente este tipo de objetos no es soportado por MySQL. En MySQL se debe utilizar el atributo AUTO_INCREMENT, mientras que en PostgreSQL hay que utilizar SERIAL, que a la postre deriva en la creación automática de una secuencia asociada a la columna. Para acabar de complicar aún más el asunto, MySQL también soporta SERIAL, pero como sinónimo de BIGINT UNSIGNED NOT NULL AUTO_INCREMENT UNIQUE, lo que en PostgreSQL es equivalente a BIGSERIAL. ¿Quién dijo "estándares"? La única forma de salvar todos estos obstáculos parece ser tener que mantener dos scripts separados, definiendo las PRIMARY KEY en MySQL como INT UNSIGNED NOT NULL AUTO_INCREMENT, y como SERIAL en PostgreSQL. Otra opción sería implementar un método propio para la generación de claves primarias, algo que normalmente no se aconseja hacer en la medida que implica duplicar funcionalidad ya existente en el gestor de base de datos. Para el resto de columnas de tipo numérico que sea necesario ir añadiendo a partir de aquí habrá que ir viendo una a una cual es la mejor opción, aunque por lo general con INTEGER, y tal vez SMALLINT (2 bytes y rango de –32768 a 32767), sea suficiente. No acabo de imaginar en este momento un atributo para el que se necesite una precisión mayor o el uso de valores en coma flotante dentro del contexto en el que estoy trabajando. Por su parte, para los campos alfanuméricos, voy a utilizar el tipo estándar VARCHAR que es soportado tanto por MySQL como PostgreSQL: Al usar este tipo se debe indicar la longitud máxima permitida para la cadena, produciéndose un error si se supera dicho tamaño en una inserción o actualización, y con la peculiaridad que los posible espacios en blanco existentes al final de las cadenas son automáticamente eliminados por el gestor. En MySQL el tamaño de una columna con este tipo depende del tamaño máximo permitido para las filas (65.536 bytes normalmente) y del juego de caracteres utilizado, teniendo un sobrecoste de 1 byte si la cadena no requiere más de ¿255 bytes? y de 2 bytes en caso contrario. En PostgreSQL el tamaño de la columna depende del tamaño de la cadena, teniendo un sobrecoste de 1 byte para cadenas de hasta 126 bytes y de 4 bytes en caso contrario. La longitud de un columna de tipo texto debe darse en función de su objetivo. Por ejemplo, para almacenar los nombres de los personajes (columna NAME de la tabla CHARACTER) se puede poner un tamaño arbitrario que sepamos que cubra un gran rango de posibilidades, 20 caracteres por ejemplo. Ahora bien, hay que tener en cuenta que el espacio real que necesite la columna puede ser distinto de la longitud de la cadena almacenada, sobre todo en función del juego de caracteres que se utilice: ASCII, UNICODE, UTF-8, ... El soporte para i18n ("internacionalización", es decir, una palabra que empieza por "i" seguida de 18 caracteres y acabada en "n") merece un post entero para él solo. Columnas como EMAIL y PASSWORD de la tabla ACCOUNT, a pesar de que uso sea obvio, requieren también pensárselo un poco. Por ejemplo, la longitud máxima permitida para una cuenta de correo según el estándar (RFC 2822 y RFC 2821) es de 320 caracteres, algo que casi nunca se tiene en cuenta ya que no resultaría práctico para ningún usuario tener que recordar una cuenta con un nombre de ese tamaño. De igual forma que no se tiene en cuenta que la parte local de una dirección de correo (lo que viene antes de la arroba) debería tratarse de forma estricta en función de sus minúsculas y mayúsculas, algo que casi ningún servidor de correo ha llegado a implementar nunca jamás. Por su parte, para el campo PASSWORD, he utilizado una longitud de 32 caracteres pensando que la clave de los usuarios se almacenará en formato texto utilizando algún tipo de mecanismo de encriptación como MD5. Para el resto de columnas de tipo alfanumérico que sea necesario ir añadiendo a partir de aquí habrá que ir viendo una a una cual es la mejor opción. Para columnas que sólo precisen almacenar un carácter bastará con un CHAR(1). Y en caso de necesitar almacenar una enorme cantidad de texto tal vez una solución sea utilizar el tipo TEXT, que es soportado tanto por MySQL como PostgreSQL, a pesar de no ser estándar. Para finalizar, indicar que sobre los campos EMAIL y NAME hay definidos índices únicos, para que el propio gestor impida físicamente que puedan existir dos registros con una misma cuenta de correo o con un mismo nombre de personaje.
Juan Mellado, 19 Abril, 2008 - 09:40
Llega el momento de esbozar las primeras tablas. Pero antes de saltar directamente a explicar detalles del modelo que aparece en la imagen que acompaña a este post, indicar que el diseño ha sido creado utilizando MySQL Workbench, un editor gráfico gratuito de código abierto que desarrolla la propia MySQL. Es el primero que he encontrado, y simplemente lo he escogido porque tiene la típica opción de generar scripts SQL a partir del modelo diseñado gráficamente. ![]() El diseño inicial es muy simple, apenas un par de tablas con los campos básicos. Aunque tengo que confesar que en realidad no es el primero que hice. El anterior era más grande y soportaba muchas más características. El problema de este tipo de proyectos personales, en los que se trabaja sin presupuesto ni calendario, es que es muy fácil hacer un diseño monstruosamente grande sólo por el placer de ver si se es capaz de meter en él todo un sin fin de features. Al final decidí quitar todo los añadidos superfluos y dejar lo mínimo imprescindible para resaltar un hecho: las miles de pequeñas decisiones que se tienen que tomar cada vez que se realiza un diseño. Me imagino que más de uno que esté viendo el modelo estará bastante decepcionado con él, ¿tanta palabrería para esto?. La sorpresa es que aún me queda bastante charla por delante antes de entrar en materia. Porque aunque parezca un diseño ridículo, que lo es, en realidad hay muchos pequeños detalles que hay que tener en cuenta. - Prefijo (1): Tengo la costumbre de poner las iniciales del proyecto al que pertenecen todas las tablas de una base de datos, y resulta que hasta ahora he estado hablando todo el rato de un diseño de MMORPG, con temática, pero sin nombre. Para salir del paso he decidido llamarle "Estirpes" (codename), de ahí el prefijo "EST" que se observa en los nombres. En lo sucesivo, si hablo de la tabla ACCOUNT se sobreentenderá que me refiero a EST_ACCOUNT a menos que indique expresamente lo contrario. - Prefijo (2): Para el resto de objetos de la base de datos que no sean tablas utilizaré un prefijo que indique su naturaleza. Por ejemplo, en el diseño se puede ver que he utilizado "IDX" para los índices y "FK" para las foreign keys. Aunque en algunos casos la nomenclatura de los objetos vendrá determinado por las características propias de cada gestor, MySQL por ejemplo obliga a llamar PRIMARY a la clave primaria de cada tabla. Puede parecer que añadir prefijos no tiene mucha utilidad, y es más, puede parecer que es un incordio tener que escribirlos cada vez, pero en la práctica pueden ser de bastante utilidad en función de cómo se diseñe la capa de abstracción para el acceso a base de datos. Si se separan correctamente las capas, haciendo que los nombres de los objetos en base de datos sean independientes de los nombres con los que se referencian realmente en el software, entonces se pueden tener hasta varias versiones de una misma tabla en una misma instancia de base de datos. Por ejemplo, las versiones "EST_0_2_07" y "EST_0_2_09". Drupal lo hace así por ejemplo. En algunos RDBMS esto mismo se puede conseguir con una buena gestión de permisos, la creación de sinónimos, o una combinación de ambos. - Inglés: He decidido poner los nombres en inglés. Esto es más una decisión personal que otra cosa. Da igual que una tabla se llame "cuenta" o "account". El problema es que después de pasarme un montón de años trabajando en proyectos con una plantilla de desarrolladores de varios países, algunos de los cuales no hablaban español, utilizar el inglés para este tipo de cosas me parece de lo más natural. - Singular: El nombre de todas la tablas y columnas irá en singular. Añadir "s" o "es" al final de un nombre no aporta nada. Además, a veces ocurre que el plural de las palabras en inglés obliga a cambiar la forma en que se escriben. Por ejemplo, "query" se convierte en "queries". Esta regla no es siempre conocida por todo el mundo, y al final no es extraño encontrar "querys" o "queryes", lo que dificulta un poco la compresión, además de quedar bastante mal cuando alguien ajeno al proyecto lo ve por primera vez. - Prefijo (3): Para las columnas que denoten identificadores (IDs) utilizaré el prefijo "id" seguido de un nombre significativo. Para el resto de tipos de columnas no utilizaré ningún prefijo en particular, aunque es probable que a las fechas les añada el sufijo "date". Curiosamente, la nomenclatura de los campos, aunque pueda parecer algo de lo más sencillo de decidir, tiene un montón de variantes que cada cual adopta como si de su propia religión se tratara. ¿O pensabais que eso sólo se daba con los lenguajes de programación? Hay una corriente por ejemplo que dice que los nombres de los campos tienen que tener el menor número de letras posible, por el hecho de que se van a estar utilizando constantemente. Por ejemplo, en vez de "id_character", se sugiere "idc". A mi me parece que para modelos muy pequeños puede ser una opción, pero para modelos medios y grandes no resulta práctico. De igual forma hay quienes se empeñan en poner una abreviatura del tipo de los campos como prefijos en los nombres de las columnas. A mi eso me recuerda a la "notación húngara", una forma de nomenclatura que con el paso del tiempo ha demostrado no ser muy acertada, al arrastrar detalles de implementación a las interfaces. - Foreign keys: Para las foreign keys utilizaré una convención bastante común, y que consiste en utilizar el mismo nombre para las columnas en la tabla origen y la destino. - Campos de Auditoria: Los campos de auditoria son columnas que se añaden a las tablas para dejar constancia a nivel de registro de que usuario o programa, y cuando, lo insertó, o cuando se produjo la última modificación del mismo. Nombres típicos de estas columnas son "create_date", "last_update_date", "create_user", "create_program", ... Son columnas que se pueden declarar durante la fase de desarrollo con objeto de facilitar las labores de programación y prueba, y eliminar posteriormente al realizar el pase al entorno de producción. Para este diseño voy a prescindir de ellas para no cargar en exceso el modelo. |