lunes, 16 de diciembre de 2013

2013: A mitad de todo


"A computer is like air conditioning – it becomes useless when you open Windows".
-Linus Torvalds


Como viene siendo ya tradición, en el último post del año doy repaso a algunos acontecimientos interesantes que se han producido.

Con respecto a JEE, este junio se presentó la nueva especificación JEE 7, justo cuando se cumplen 10 años de la última especificación con el nombre J2EE, la 1.4, tras la cual se redenominó y pasó a llamarse JEE.

También hay movimientos en cuanto a los servidores de aplicaciones. Mientras Jboss ultima su nuevo Wildfly 8 (no sólo para renombrar Jboss AS sino para evitar confusión y favorecer el mercado a JBoss EAP) Oracle empieza a dar los pasos que me temía tras la compra de Sun Microsystems en 2010, con la decisión de no continuar el soporte comercial de Glassfish a partir del 2014.

En efecto, los efectos prácticos de la publicación de la actualización del roadmap de Glassfish el pasado Noviembre, según mi lectura, es que en apenas dos años Glasfish dejará de existir tal y como está ahora y volverá a ser el mismo elemento de juguete y testing sobre el que nació.

Salvo que me equivoque en mi vaticinio, Oracle va a realizarle una jugada perfecta a Red Hat, ya que mucha gente buscará sus alternativas de código abierto (con soporte opcional) en la única alternativa que queda: Wildfly. Supongo que la intención de Oracle es aglomerar tecnología y reducir costes para consolidar su soporte en WLS y forzar a la migración de sus clientes comerciales de Glassfish. Seguramente lo conseguirá en sus clientes actuales, pero no creo que le ayude a conseguir nuevos clientes del segmento medio, donde Red Hat tiene una estrategia clara de comienzo y apoyo en proyectos pequeños y medianos tanto en su nacimiento como en su crecimiento, sin markups altos que puedan suponer un freno.

En el otro tema de interés del blog, llevamos un diciembre muy interesante de observación en el que podemos ver a un brillante Venus ponerse al Oeste tras el Sol mientras vemos salir también un luminoso Júpiter por el Este que podemos disfrutar durante toda la noche. Unos prismáticos y un trípode son suficientes para que cualquier observador pueda deleitarse con la observación de sus cuatro satélites más visibles (Calisto, Io, Europa y Ganímedes).

Desgraciadamente, nos hemos perdido un espectáculo que hubiera sido increíble: ISON, de haber sobrevivido, podría haber alcanzado una magnitud de -4,5 y habría sido visible a simple vista en el cielo matutino. Nos conformaremos ahora con recoger muestras del polvo que, en Enero, caerá en nuestro planeta del cometa más observado de la historia.



2013. El primer año con los cuatro dígitos diferentes desde 1987. Un durísimo año en una transición que no termina de producirse. El nuevo año comienza en miércoles, casi a mitad de semana, como una señal de que, en realidad, estamos a mitad de todo y aún a comienzo de nada. En cualquier caso, en lugar de pensar en que queda el resto de semana, prefiero quedarme con la idea de que el lunes y el martes ya han pasado.

Feliz 2014.

viernes, 29 de noviembre de 2013

JPA: Tips and tricks

Photo credit: imelenchon
“Good code is its own best documentation.”

(Steve McConnell)




Cualquiera que lleve varios años desarrollando aplicaciones empresariales Java habrá usado en mayor o menor medida algún ORM para la persistencia en bases de datos de nuestros objetos, dadas las ventajas que aporta al desarrollo y mantenimiento.

Las enormes ventajas de eficiencia, portabilidad, legibilidad y mantenimiento del código, además de la productividad que aporta evitando escribir fontanería manual de serialización/deserialización de datos a objetos lo hacen prácticamente imprescindible en cualquier proyecto que acceda a bases de datos. Sin embargo, me he encontrado con casos donde hay que usar elementos no portables de JPA o, directamente, no usar JPA en absoluto.  El rendimiento suele ser la razón fundamental y más frecuente, pero hay otras, como la legibilidad y mantenibilidad o simplemente, la sencillez. Son los siguientes casos:
  • Tratamiento o movimiento de datos masivo (grandes transacciones con operaciones de inserción, actualización o borrado). En estos casos, realizarlo a través de los objetos o vía JPQL no es eficiente. Es mejor realizar consultas nativas (NativeQuery) o funciones/procedimientos almacenados.
  • JPQL ineficiente. Hay casos donde, por ejemplo, un OUTER JOIN con JPQL no realiza lo que queremos. Además, no existe (al menos yo no conozco ninguno, se agradecen aportaciones ;-) ningún cliente JPQL que nos permita probar y testar consultas JPQL con lo que realizar consultas complejas en JPQL es una tarea tremendamente ardua y pesada. En estos casos, las consultas se crean y prueban en SQL nativo y se implementan como tales.
  • Presentación de datos tabulares estadísticos o calculados con sumatorios, agregados, agrupados, etc.... Hay veces que se necesita una tabla (normalmente de cálculo) producto de una consulta compleja que no coincide con nuestro modelo de datos. En estos casos la "fontanería" con objetos es ilegible o directamente intratable.
No hay que complicarse en exceso y usar el principio KISS. Si el problema se puede solucionar de forma sencilla, elegante y eficiente usando SQL nativo o funciones almacenadas, el "purismo" de la portabilidad no puede llevarse al extremo de realizar una aplicación ineficiente o inmantenible.

Dicho esto, mi recomendación es usar JPA por defecto en cualquier proyecto que trabaje con bases de datos y plantearse usar SQL nativo o funciones almacenadas exclusivamente en los casos estrictamente necesarios en los que el rendimiento, la mantenibilidad, legibilidad o simpleza así lo aconsejen.

En mi experiencia usando JPA 1.0 (Toplink Essentials) y JPA 2.0 (EclipseLink) en diversos proyectos he recopilado los siguientes consejos prácticos, trucos y advertencias que suelo tener en cuenta y que voy a compartir aquí:

1.- NamedQuerys en fichero orm.xml (y no como anotaciones en las entidades)

Aunque la especificación JPA enfatiza el uso de anotaciones, puedes usar el fichero de mapeo JPA orm.xml para almacenar los metadatos. Aunque el uso de anotaciones o descriptores XML es una cuestión de gustos, considero especialmente útil usar descriptores XML cuando nos apoyamos en wizards y herramientas de generación de código (como Dali), ya que éstas suelen sobreescribir nuestros ficheros de entidad perdiendo así todas nuestras NamedQuerys. Almacenar las NamedQuery en el fichero orm.xml nos permitirá no perderlas cuando Dali o cualquier otro generador de código sobreescriba nuestras entidades.

Es decir, en lugar de ésto en nuestras entidades:
@NamedQueries( {
    @NamedQuery(name = "Profile.findAll", 
              query = "SELECT o FROM Profile o ORDER BY o.name"),
    @NamedQuery(name = "Profile.findByName", 
               query = "SELECT o FROM Profile o WHERE UPPER(o.name) LIKE :name ORDER BY o.name")
})

Escribirlo en el fichero orm.xml:

  
    
      PROPERTY
    
  
  
      <![CDATA[SELECT o FROM Tag o INNER JOIN o.tagProfiles tp 
                         WHERE tp.profile <> :profile ORDER BY o.name]]>
  
  
      <![CDATA[SELECT o FROM Profile o ORDER BY o.name]]>
  
  
      <![CDATA[SELECT o FROM Profile o WHERE UPPER(o.name) LIKE :name ORDER BY o.name]]>
  


2.- Obtener objetos manejados y sincronizados

Es posible que te hayas encontrado en algún momento con errores del tipo:

java.lang.IllegalArgumentException: Entity must be managed to call remove: __MiObjeto__, try merging the detached and try the remove again.

La caché de entidades de JPA es manejada por el framework internamente. Es posible que un entity de tu aplicación, que recuperaste vía em.find() o JPQL, ya no esté en dicha caché porque haya sido reciclado (aunque en tu código apenas "hayan pasado" un par de llamadas de métodos o un par de líneas de código ;-). En estos casos es cuando te encuentras con el primer error, por ejemplo, si llamas a EntityManager.remove() de un objeto "detached".

Una solución que se te puede ocurrir es usar EntityManager.refresh() para "refrescar" el objeto y sincronizarlo con la base de datos, es decir: para hacerlo "manejado" nuevamente... Pero te puedes encontrar con otro error:

refresh() cannot be called on a detached entity


Para asegurarte que tienes un objeto "manejado" y, en cualquier caso, para estar seguro que puedes modificar un objeto sincronizado con la base de datos, una la combinación de find() y refresh() siguiente:

Usuario e = em.find(Usuario.class, id); 
try {
  em.refresh(e); 
} catch( EntityNotFoundException ex ) { 
  e = null; 
}

3.- Uso de callbacks. El caso especial de la anotación @PreUpdate

Las anotaciones @Pre* y @Post* para callbacks methods tienen una utilidad ciertamente limitada. De hecho, no hay más que leer la sección 3.5 de la especificación JPA:
“In general, the lifecycle method of a portable application should not invoke EntityManager or Query operations, access other entity instances, or modify relationships within the same persistence context. A lifecycle callback method may modify the non-relationship state of the entity on which it is invoked.”

Existen razones técnicas de peso para lo anterior (por ejemplo, evitar bucles infinitos que pueden dar al traste con una aplicación), pero desde el punto de vista del desarrollo está muy lejos de constituir un equivalente a los disparadores en la base de datos, ya que sólo podemos trabajar con la propia entidad, e incluso, con restricciones, como ahora veremos.

La anotación @PrePersist nos permite establecer un estado en una entidad antes de que ésta sea persistida. ¿Funciona igual @PreUpdate? Vamos a verlo.

Imaginemos que tenemos una entidad con un campo campoDato que, cuando se modifica, debe tener su fecha correspondiente de modificación en su campo aparejado fechaCampoDato. Para olvidarnos de éste último, podríamos tener la tentación de hacer algo como esto:

@PrePersist
@PreUpdate
public void preUpdate() {
    if ( campoDato != null) {
        // Si tiene campoDato, hay que garantizar su fecha también
        if ( fechaCampoDato == null) {
            fechaCampoDato = new Date();
        }
    }
}

Sin embargo, no funciona (al menos en EclipseLink) ¿Por qué? Porque para la verificación de modificación ("dirty check"), los campos modificados son detectados antes de llamar al método @PreUpdate y, por tanto, los cambios realizados en el método @PreUpdate no son detectados. Nuevamente, esto está realizado así por razones de rendimiento debido a que las verificaciones de modificación son muy costosas.

En definitiva, los métodos callback no son la panacea en lo referente a la gestión del ciclo de vida de nuestras entidades.

4.- ¿JPQL demasiado lenta?

Cuando tenemos un modelo complejo con muchas relaciones, algunas ejecuciones de JPQL en el que muchas entidades se vean involucradas (muchos JOIN) pueden resultar inaceptablemente lentas. Especialmente cuando nos percatamos que la consulta nativa SQL equivalente apenas lleva unos pocos milisegundos. En estos casos, optamos por realizar una consulta nativa SQL ( createNativeQuery(java.lang.String sqlString,java.lang.Class resultClass) ) obteniendo también un rendimiento lamentable, cuando la misma consulta SQL se ejecuta centenas de veces más rápido en nuestro cliente SQL. ¿Qué está pasando? ¿Cómo solucionarlo?

En estos casos el problema no es la consulta: es el propio JPA, o más concretamente, la resolución de entidades de JPA. Cuando se usan NamedQuery o entity-mapped-nativequery (es decir, nativas usando mapping "automático" a la entidad de retorno), JPA realiza la consulta y mapeo de las entidades devueltas, resolviendo en éstas las relaciones involucradas en la consulta. Es decir, que si tengo una entidad con varias relaciones, resuelve las listas de estas relaciones, generando una cantidad de consultas enorme y consumiendo, por tanto, muchísimo tiempo.

La solución para estos casos es simple: realizar una consulta nativa que devuelva los identificadores de las entidades en lugar de las entidades, para luego resolver las entidades manualmente. Por ejemplo:

public List<User> getUserByProfile(Integer profileId) {

    List<Vector> rows = null;
    List<User> users = new ArrayList<User>();
    String query;
    try {
        query = "select distinct t1.id " +
            "  from t1,t2,t3,t4,t5" +
            " where t1.f1_id = t2.id" +
            "   and t2.f2_id = t3.id" +
            "   and t4.f4_id = t5.id" +
            "   and t5.id = " +
            "   and t2.end_date is null" +
            "   and exists (select * from a " + 
            "       where date is null and h = t5.id)" + 
            "   and t5.id = "+ profileId;

        Query q = em.createNativeQuery(query);
        log.debug("query {}", query);
        rows = q.getResultList();
        for ( Vector row : rows ) {
            users.add(em.find(User.class, (User)row.get(0)) );
        }
        return users;
    } catch (Exception e) {
        log.error("Excepcion ", e);
    }
}

Podrás comprobar que la solución anterior, aún siendo poco "bonita" y  más tediosa, es centenares de veces más rápida que dejar a JPA que resuelva las entidades. La razón es muy sencilla: con esta forma, no se resuelven innecesariamente las relaciones de las tablas en la consulta.

4.- @PrivateOwned o tratamiento de huérfanos

Es frecuente confundir el atributo cascade y pensar que éste se encarga de todo. Pero no es así. Si tenemos una relación @OneToMany(cascade=ALL) y borramos el padre, sus hijos también se eliminarán. Pero, ¿y si queremos borrar alguna entidad hija? Si tenemos un objeto A que tiene una lista de objetos B hijos y borramos el segundo, de la lista (usando remove()), una llamada a merge() no eliminará el objeto dereferenciado. ¿Por qué? Porque el objeto sigue siendo una entidad que no ha sido explícitamente eliminada. Hemos borrado una referencia a él, pero no la única y, en todo caso, el objeto sigue manteniendo la referencia a su padre.

Muchas veces nos encontramos la definición del atributo @PrivateOwned así: "Use @PrivateOwned to specify that a relationship is privately owned"... lo cual obviamente, no aclara mucho. Lo voy a explicar aquí un poco más claro: @PrivateOwned implica que los hijos pertenecientes a la lista no deben existir sin el padre, con lo que, al quedar dereferenciados, deberán ser eliminados. Podrás usar este atributo para buena parte de las relaciones @OneToMany cuyos hijos tengan una dependencia funcional absoluta del padre y en las que normalmente trabajarás con el objeto padre como una unidad única y no con los hijos de forma independiente. Es decir, en aquellas donde un objeto hijo "huérfano" no tiene sentido.

@OneToMany(mappedBy = "padre", cascade={CascadeType.ALL})
@PrivateOwned
private List<Hijo> hijos;

Con JPA 1.0, los objetos hijos "huérfanos" deben eliminarse explícitamente con em.remove() ya que este atributo sólo está disponible para JPA 2.0.

5.- Logging

Durante el ciclo de desarrollo, es una buena idea activar el logging de las sentencias SQL que nuestro proveedor de JPA realiza en tiempo de ejecución, para tener visibilidad de qué está ocurriendo en cada momento. En EclipseLink, se configura a través de una propiedad en el fichero persistence.xml.

<properties>
   <property name="eclipselink.logging.level" value="FINE"/>
</properties>


6.- JPA Caching

La caché de nivel 2, L2 o shared cache, es una caché más allá del EntityManager: es una caché global para toda la unidad de persistencia (PersistenceUnit). En general, la caché es un elemento importante y complejo cuya correcta configuración puede tener un significativo impacto en nuestra aplicación. Explicar estos detalles excede el alcance de este artículo y hay mucha información ya publicada al respecto. No obstante, expondré aquí mi experiencia con TopLink (JPA 1.0) y EclipseLink (JPA 2.0)

JPA 1.0

Mi experiencia con Toplink Essentials es que es conveniente desactivar la shared cache, salvo que la base de datos no tenga modificaciones externas simultáneamente a nuestra aplicación (otras aplicaciones, etc), lo cual no suele ocurrir (en mi caso, nunca). Así que, como norma general, la opción habitual es configurarlo en el persistence.xml:

<properties>
   <property name="toplink.cache.shared.default" value="false"/>
</properties>  

 JPA 2.0

EclipseLink ofrece mayores niveles de configuración y ajuste que permiten una configuración más fina y granulada, aunque aún me quedan por probar el comportamiento en producción de muchas opciones. De momento, una buena opción es desactivar la shared cache para todas las entidades y permitir sobreescribir esta configuración para entidades concretas vía orm.xml o anotación @Cacheable en función de nuestra aplicación.

<properties>
   <shared-cache-mode>ENABLE_SELECTIVE</shared-cache-mode>
</properties>



Referencias y más información:

domingo, 29 de septiembre de 2013

Migrando a JEE 6 / EJB 3.1 (II)


“A programmer is a person who passes as an exacting expert on the basis of being able to turn out, after innumerable punching, an infinite series of incomprehensive answers calculated with micrometric precisions from vague assumptions based on debatable figures taken from inconclusive documents and carried out on instruments of problematical accuracy by persons of dubious reliability and questionable mentality for the avowed purpose of annoying and confounding a hopelessly defenseless department that was unfortunate enough to ask for the information in the first place.”
(IEEE Grid newsmagazine)

En esta segunda parte de esta serie (podéis ver la primera parte aquí) comentaré mi experiencia con el famoso nuevo sistema estandarizado o único de nombres JNDI (portable JNDI names).

Tras varias aplicaciones, he llegado a una nomenclatura que me ha parecido adecuada para el nombrado de mis objetos y recursos siguiendo mi propio estándar.:


  1. Declaración de EJB: defino un nombre de aplicación y declaro todos los ejb con la nomenclatura general "global" usando dicho nombre:
  2. @EJB(name="java:global/nombre_aplicacion/nombre_ejb", beanInterface=nombre_ejb_interfaz_local.class)
  3. Acceso a referencias de EJB locales: la declaración la hago usando siempre:
  4. @EJB(name="java:module/nombre_ejb")
  5. Acceso a referencias locales de EJB desde otros módulos: usando la nomenclatura 
  6. java:global/nombre_ejb
  7. Acceso a recursos: igual que antes, pero usando "lookup" en lugar de "mappedName" en la notación @Resource


A continuación muestro ejemplos de cómo cambia la declaración de elementos con EJB 3 (JEE5) a EJB 3.1 (JEE6).

con EJB 3 (JEE5) con EJB 3.1 (JEE6)
Declaración
@Stateless(name="BalteusService",
mappedName="ejb/BalteusService")
public class BalteusService;
@Stateless()
@EJB(name="java:global/BalteusApp/BalteusService", 
  beanInterface=BalteusServiceLocal.class)
EJB Ref
@EJB 
private OtherServiceLocal otherService
@EJB(name="java:module/OtherService")
private OtherServiceLocal otherService
Resource Ref
@Resource(mappedName="jdbc/BalteusDS")
private DataSource BalteusDS;
 @Resource(lookup="jdbc/BalteusDS")
private DataSource BalteusDS;

Para la utilización de interfacez locales de EJB desde otros módulos de un EAR (por ejemplo, desde una aplicación web), donde antes usaba el típico patrón ServiceLocator, ahora uso una adaptación del patron BeanLocator (que es una especie de ServiceLocator 2.0), descrito por Adam Bien en su libro (http://www.adam-bien.com/roller/abien/entry/ejb_3_1_beanlocator_when).

La forma de utilizar el patrón 

/**
public class BeanLocator {

  * Para realizar el lookup de interfaces locales
  *
  * Asume que los servicios están declarados como "java:global/XXXService"
  * (p.e. @EJB(name="java:global/UserService",beanInterface=UserServiceLocal.class) ), es decir,
  * asume que el nombre JNDI declarado es el nombre de la interfaz eliminando el sufijo "Local"
  * @param clazz
  * @return
  */
  public static <T> T lookup(Class<T> clazz) {
    String name = clazz.getSimpleName();
    return lookup(clazz,"java:global/cestelJEEApp/" + name.substring(0, name.indexOf("Local")));
  }
}

Con este BeanLocator, obtener una referencia a la interfaz local, es algo muy sencillo. Por ejemplo:

PaisServiceLocal paisService = BeanLocator.lookup(PaisServiceLocal.class);


Referencias y más información:

miércoles, 6 de marzo de 2013

...Y cuatro


"No sé por qué me salvó la vida. Quizá en esos últimos años él amó la vida con más intensidad que nunca, no sólo su vida, la de cualquiera, mi vida. Y lo único que quería eran las mismas respuestas que el resto de nosotros: ¿de dónde vengo?, ¿a dónde voy?, ¿cuánto tiempo me queda? Todo lo que podía hacer era quedarme allí y verlo morir"- Deckard
Blade Runner


Hoy este blog cumple su cuarto año en la que probablemente sea la situación socio-económico-política más difícil de la gente de mi generación. Es difícil apartar el contexto en el que este blog se publica y evitar la tentación de escribir algo sobre la crisis y la actualidad. Y aunque este blog tiene y seguirá teniendo una temática exclusivamente técnica (o tecnológico-científica, si incluyo los temas de astronomía y actualidad tecnológica) es inevitable que se impregne del desánimo y la indignación social en la que vivimos.

Quizá por ello he querido (una vez más) traer aquí la secuencia final de Blade Runner, invocando la misma catarsis y perplejidad existencial que provocaba en Deckard... y probablemente aguardando la renovación que se supone que esperamos y deseamos muchos.

2013 (C) Fran Barquero

Gracias, Fran, por la ilustración de aniversario de este Blog.

Saludos y gracias por tu visita.


viernes, 15 de febrero de 2013

Error del instalador Unix de glassfish 3.1.2.2


A la vista de suficientes ojos, todos los errores resultan evidentes.
Ley de Linus, formulada por Linus Torvalds, (1997).

Si has instalado Glassfish 3.1.2.2 en un servidor Unix en español y has seleccionado "Instalación personalizada", habrás sufrido el extraño error:

org.jdom.input.JDOMParseException: Error on line 98: El tipo de elemento "htmlpanel" 
debe ir seguido de una de estas especificaciones de atributo: ">" o "/>".
WARNING: No se ha podido procesar un evento de navegación para 
command=AC_NEXT [Comando=AC_NEXT Error=Invalid SwiXML Descriptor. ]

La interfaz gŕafica presenta una imagen como la siguiente:


Tal y como me imaginé, tiene que ver con el poco cuidado que está teniendo Oracle con los idiomas últimamente en los instaladores (el instalador de la versión 3.1 también adolecía de similares problemas). La solución es muy sencilla: ejecutar el instalador forzando el idioma inglés, por ejemplo, así:

szarza@szarza:~$ LANG=EN ./glassfish-3.1.2.2-unix.sh

con lo que podremos acceder a la instalación personalizada sin problemas.



Supongo que existe algún error en los XML del instalador con recursos en español que no se escapan debidamente.

Espero que os ayude en casos similares.

viernes, 14 de diciembre de 2012

2012: El fin de los magufos


“Windows NT addresses 2 Gigabytes of RAM, which is more than any application will ever need.”
(Microsoft, on the development of Windows NT, 1992)

Este año que termina es el año favorito de los magufos. Tras las bobadas del fallido fin del mundo del año 2000, llega el siguiente fin del mundo: el 21 de Diciembre de 2012. Ya veremos cuando será el próximo...

Ayer se me ocurrió escribir sobre esto pensando en el pasado 12-12-12, al que se le atribuyeron no pocas insensateces cuando todo el mundo sabe que lo único importante de ese día es que es el cumpleaños de uno de mis hermanos. Con las profecías chorradas de Nostradamus ya cubiertas de polvo, a los charlatanes cuentistas y demás inventores de tonterías, les dió por mirar, mira tú, nada más y nada menos que a uno de los calendarios Maya... ¡Y dio hasta para una película!

En fin. Queridos lectores, lamento ser yo quien os fastidie el magno espectáculo, pero el 21 de diciembre de 2012 a las 11:12 UTC no va a ser el fin del mundo. Ocurrirá lo que todos los años en torno al mismo momento aproximadamente: el solsticio. El universo no se rige por nuestras formas de contar las cosas. La verdad es que el fin del mundo será un impresionante espectáculo, pero aún nadie sabe cuando sucederá porque, obviamente, no depende de ningún adivinador. No hay problema. La humanidad tiene butacas reservadas para ese espectáculo. Y es seguro que sucederá antes de los próximos 3.000 millones de años... Me apuesto una cena. En todo caso, a los engañabobos les quedará el refugio del próximo año, un 13, que dará para más bobadas que ayuden a sus ingenuos cŕedulos a olvidar sus "predicciones" anteriores.

En medio de esta agitada crisis, la comunidad científica nos regaló este año con el que probablemente será el primer descubrimiento más significativo de este siglo: una nueva partícula que concuerda con el bosón de Higgs. Si se confirma el descubrimiento de la maldita partícula ("The goddamn particle", no the "God Particle", como el editor de Leon Lederman transformó con disparatado desatino), la física podría corroborar el modelo estándar en los próximos años o, en todo caso, permitirá extraordinarios avances en física.

Más prosaico pero no menos importante: Blade Runner cumple su 30 aniversario este año. Ya conocen mis lectores habituales mi devoción por esa película.

Este segundo semestre del año mi actividad en el blog ha sido muy poca por razones diversas que no vienen a cuento ahora, pero soy consciente de ello. Y es que este año está siendo francamente difícil para mi en términos de tiempo libre para el blog (y también en otros que tampoco vienen al caso ahora).

Con suerte, el 21 de Diciembre se podrían acabar las magufadas de una vez por todas, aplastadas por la tozuda y sencilla realidad... pero no se debería a los mayas, sino al uso de nuestro cerebro con la mejor de las herramientas: el pensamiento científico... y a falta de ello, el sentido común.

A por el 13!!

Feliz año nuevo.


Referencias y más información:
Related Posts Plugin for WordPress, Blogger...
cookieassistant.com