Activando Actividades que no han sido Activadas: Actividades de SharePoint

“The list of workflow actions on the server references an assembly that does not exist.  Some actions will not be available.  The assembly strong name is bla-bla-bla.  Contact your server administrator for more information.”

Error encontrado a último minuto, cuando estaba preparando una conferencia que debía dar el lunes a primera hora (junto con Hadi Hariri, aunque él estaba hablando de cosas completamente diferentes). Mala cosa, ya tienes todo pensado y preparado y en el ultimo pedazo de código para rematar brillantemente la hora de conferencia, te sale SharePoint con algo por el estilo…

Empecemos por el principio. Todo el asunto se trataba de SharePoint y el Windows WorkFlow Foundation: juntando mundos que andan por caminos separados: SharePoint y la Fundación no tienen en principio nada que ver el uno con el otro, pero cuando se ponen a trabajar juntos se pueden hacer cosas realmente brillantes. Y hay dos formas para crear Flujos de Trabajo en SharePoint: SharePoint Designer o Visual Studio; otros dos mundos separados, que en principio no tienen nada que ver el uno con el otro (es más, hasta llegan a ser enemigos), pero que si se pueden conjugar de una u otra forma, nos pueden solucionar problemas peliagudos.

El problema que he estado viendo en varios proyectos los últimos tiempos es que los Flujos de Trabajo se están volviendo más y más complicados: las variables de entrada no hacen más que aumentar, los caminos a recorrer son cada vez más intrincados, las reglas de negocios menos y menos manejables. Por el otro lado, Flujos tienden a ser utilizados una sola vez, en una sola Lista o Librería (también como consecuencia de que cada vez son más especializados en realizar una tarea específica). Crear Flujos de Trabajo de este tipo es imposible de hacer con el SharePoint Designer, hay que usar Visual Studio, por lo tanto son bastante costosos de desarrollar (tiempo de desarrollador no es especialmente barato), y si hay que modificarlos de vez en cuando, el problema no hace más que aumentar, y nuestros clientes están cada vez menos contentos…

Con SharePoint Designer se pueden “ensamblar” Flujos de Trabajo de una forma realmente sencilla, y es muy fácil contarle a alguien que trabaje directamente con la instalación de SharePoint como hacerlo en muy poco tiempo. Pero el Designer no tiene ninguna capacidad para hacer cosas realmente interesantes. Como combinar los puntos fuertes de los dos (hacer lo que nos de la gana con VS, hacerlo fácilmente con SharePoint) para mitigar los puntos flacos de los dos (costos y conocimientos necesarios para usar VS, falta de posibilidades en Designer)? La respuesta es Actividades…

Actividades son esos pequeños bloques de funcionalidad que están en el Cuadro de Herramientas de Visual Studio y en las Acciones del Designer. Qué tal si dividimos el problema en bloques funcionales, creamos Actividades para ellos en Visual Studio y los instalamos en Designer de tal forma que nuestro cliente se “ensamble” sus Flujos por sí mismo? Combinamos mundos divergentes (el titulo de la conferencia) para solucionar un problema real y actual: bajar costos y aumentar flexibilidad. Un ejemplo que me he topado con frecuencia es en instalaciones de SharePoint en Universidades en donde hay un formulario para que nuevos estudiantes se inscriban: algunas facultades quieren que después de que el formulario se ha recibido, primero se haga una cita para conversar con el interesado, y luego se registra a la persona en el sistema. Otras facultades quieren registrar el interesado directamente para que no se les escape (las facultades de ingeniería por ejemplo… nadie quiere estudiar ingeniería, así que si a alguien le da por ahí, hay que agarrarlo inmediatamente) y luego hacer la cita. Como construir el Flujo detrás del formulario? De tal forma que pueda seguir los dos caminos dependiendo de variables de entrada? O crear dos Flujos, a los que hay que instalar, darles mantenimiento, etc? Porque no crear una Actividad “Formulario” (que se comunica con un formulario de InfoPath, por ejemplo), otra Actividad “Hacer Cita” (que mira en los calendarios de los entrevistadores, reserva tiempos en ellas, manda E-mails a todo el mundo) y una tercera Actividad “Registrar” (que crea un numero de registro, hace cosas en PeopleSoft, etc.), luego registramos las Actividades (creadas en Visual Studio) en SharePoint Designer y finalmente algún empleado de nuestro cliente se encarga de crear la Lista necesaria y ensamblar las Actividades en el orden indicado para crear el Flujo? Este es un ejemplo muy sencillo, pero al mismo tiempo muy real…

Bueno, de eso se trataba mi conferencia, y lo único que me faltaba al final era configurar a SharePoint Designer para que me mostrara una Actividad de ejemplo que ya tenía lista. Y allí me salió el error. Por eso de que no hay información de Microsoft al respecto (por alguna razón extraña ya ni me da rabia, estoy tan acostumbrado…), me he sacado un Profiler del bolsillo para ver qué era lo que pasaba cuando arrancaba a Designer y en el momento de intentar ver la Actividad. Pues bien, lo primero interesante que ocurre es que el Designer llama al método “FetchLegalWorkflowActions” (del que hay una página de información de Microsoft, http://msdn.microsoft.com/en-us/library/ms774779.aspx, que por supuesto no dice nada), pero que me indica que el Designer está usando WebServices para mirar en el archivo de configuración del sitio… hmmmm… todos los días se aprende algo nuevo… Luego se van recorriendo todos los ensamblados en la configuración y revisando si los encuentra en el GAC. Y si no encuentra alguno, sale el mensaje. El problema es que mi ensamblado estaba perfectamente firmado e instalado en el GAC, y lo peor de todo, que SharePoint lo encontraba sin problemas. Así que el proceso que realiza el Designer es muy sencillo, y todo estaba perfectamente configurado, pero el error me seguía saliendo y todo el asunto se atrancaba miserablemente…

Después de revisar todo una y otra y otra vez y ejecutar mil y un iisreset, estaba empezando a pensar en cambiar mi conferencia a algo así como “Posibles causas del embarazo de las gallinas verdes australianas”… así que me dio por cerrar a SharePoint Designer y reiniciarlo de nuevo… brillante, genial, fenomenal !!! Funciona!!!… Mi Actividad es visible y usable, y nada de errores… Muchas gracias Microsoft, me han proporcionado una de las horas más miserables de mi vida intentando encontrar un error inexistente, y todo gracias a que alguno de sus programadores no hizo su trabajo bien hecho, y gracias a que sus probadores no probaron el producto bien probado… Pero pude dar mi conferencia, y hasta me parece que a por lo menos una persona le ha gustado…

Gustavo – http://www.gavd.net/servers/
Escriba un Comentario que me haga reir…

Cuantos elementos caben en una Lista de SharePoint?

2000 según Microsoft. No, no es cierto, después de 2000 elementos la Lista empieza a tener problemas para mostrar los elementos en pantalla (http://www.gavd.net/servers/sharepointv3/spsv3_item.aspx?top=inf&itm=382 ). Así que en realidad cuantos elementos se pueden meter en una Lista antes de que SharePoint diga “hasta aquí voy yo” y deje de funcionar? De nuevo, según Microsoft, por los cinco millones…

Pero una cosa es meter elementos en una Lista, y otra muy diferente poder hacer algo con ellos. Si después de una cierta cantidad es infinitamente demorado encontrar uno de los elementos, no tenemos nada con cinco millones…

Problema: Una intranet de una empresa con más o menos 20.000 usuarios. Cada mes el sistema de administración (separado de SharePoint) suministra un archivo pdf (7 kb) con el extracto del salario de cada empleado (usuario) y uno de los requisitos de la Intranet es que los usuarios puedan ver sus extractos en la Intranet, y de una forma segura. Esto significa que cada mes habrá que meter por lo menos 20.000 archivos pdf en algún lado en el Portal… en una Librería, por supuesto, pues SharePoint es todo Listas y Librerías.

Especulación: Usar una Librería por año? Un cuarto de millón de elementos en una Librería debería ser posible según Microsoft, pero cuanto se demora SharePoint en encontrar un archivo de un usuario de un determinado mes?

Una Librería por mes? Si la Librería puede con cinco millones, tiene que poder con 20.000, pero la pregunta sigue: que tan rápido se puede encontrar un elemento?

Una Librería por usuario (en MiSitio, por ejemplo), con solamente sus archivos? Y cuanto se demora el sistema en subir 20.000 archivos en 20.000 diferentes sitios?

Pruebas: Como no existen estadísticas al respecto, lo mejor es probar a ver qué pasa. Primero crear una Librería e irle metiendo archivos hasta que reviente (o no), y al mismo tiempo ver cuánto tiempo cuesta encontrar un elemento random en ella. Algunos resultados:

 

Número de Elementos en la Lista Tiempo para encontrar un Elemento (milisegundos)
10 2,156
100 2,170
1.000 2,640
10.000 6,420
100.000 37,125

 

La búsqueda se realizó “a lo bestia”, es decir recorriendo uno por uno de los elementos en busca de uno generado en forma random. Esto se hizo así para ver que tan ágil es el Modelo de Objetos para “loopear” (no muy ágil, por lo visto). Haciendo la búsqueda con una consulta CAML (la forma inteligente) los resultados varían de 2,203 milisegundos para encontrar un elemento entre 1.000 y 2,296 en una Librería con 100.000 elementos (eso está mucho mejor). Y si la columna de búsqueda en la Lista se indexa, cuesta 2,140 milisegundos en la Librería con 100.000 elementos.

Nota separada No. 1: parece que siempre hay un “threshold” de 2 milisegundos, probablemente el tiempo necesario para hacer que el JIT se despierte, y para que toda la burocracia de DotNet empiece a funcionar.

Nota separada No. 2: es interesante ver cómo crece la Base de Datos de contenido de SharePoint: con los 100.000 elementos (de 7 kb cada uno) creció en 797.076.928 bytes, lo que indica que hay un “desperdicio” de 10%… probablemente no desperdicio sino espacio para los meta-datos o algo así…

Conclusión: Buscar a lo bestia es de lo mas bestia… no hacerlo. Buscar con consultas CAML funciona de maravilla, y no importa si hay que buscar en una Librería con 100 o con 100.000 elementos, el tiempo de búsqueda es igual. Y si se indexa la columna a buscar, mejor aún. Y hay una carga interna de 2 milisegundos de la que no nos libramos por nada del mundo…

Y como se ha hecho el asunto: Un SharePoint Job ejecuta cada día al amanecer y escanea un directorio en busca de los archivos generados (solamente una vez al mes encontrara algo, pero eso no se lo vamos a contar para que no se vuelva perezoso). Cuando encuentra archivos, el Job crea una Librería y sube todos los archivos del mes de una sola vez, asegurando los derechos de cada archivo para que solamente el dueño tenga suficientes derechos para leerlo. Una WebPart revisa las Librerías de cada mes en busca de los archivos de salario del CurrentUser y muestra lo que encuentra. Solucionado. SharePoint contento, cliente contento, todos contentos…

Nota: como subir 100.000 elementos a una Librería de SharePoint manualmente es bastante aburrido, se ha usado una herramienta gratis, el “BulkFiller”, que pueden encontrar en http://www.sharepartsshop.com (búsquelo en la sección de “Gratis”); subir 100.000 elementos a una Librería cuesta un par de minutos, increíble. Y para borrarlos, en el mismo sitio hay otra herramienta, el “BulkCleaner”, que hace lo mismo pero al revés, y aun mas rápido…

Gustavo – http://www.gavd.net/servers/
Escriba un Comentario que me haga reir…

La velocidad de SharePoint

Por fin un par minutos de tranquilidad para poder hacer algo interesante con SharePoint, no el cotidiano tira-y-afloja con clientes que no saben que es lo que quieren, y que cuando les cuentas que es lo que quieren, empiezan a creer que son “conocedores” de SharePoint… yo solo conozco a dos personas que sean “conocedoras” de SharePoint, y ninguna de las dos es cliente mío…

Perdón por irme por las ramas. Al tema: Velocity, el nuevo juguete de Microsoft. Jorge Serrano ya ha contado de que se trata (“Microsoft Project Code Named Velicity” http://geeks.ms/blogs/jorge/archive/2008/06/03/microsoft-project-code-named-velocity-ctp1.aspx e “Información general sobre el proyecto Velocity” http://geeks.ms/blogs/jorge/archive/2008/06/07/informaci-243-n-general-sobre-el-proyecto-velocity.aspx) así que no me pongo a repetir lo que ya el contó bien contado. El asunto se trata de cacheo, algo con lo que SharePoint siempre ha tenido una relación amor-odio, o, aun mejor dicho, una relación sado-masoquista.

Cacheo de datos es estupendo en aplicaciones Web. Por la forma intrínseca de este tipo de aplicaciones, datos tienen que ser generados una y otra y otra vez, así que meterlos en un depósito temporal para no tener que hacer todo el proceso continuamente es una excelente idea. SharePoint es una aplicación Web, ergo, debe usar cacheo. Y en realidad lo hace: MOSS utiliza las técnicas de cacheo de datos que proporciona el ASP.NET 2.0 y le agrega un mecanismo más preciso (Profile Caching) para mejorar la granularidad (como se puede traducir “granularity” al cristiano?), permitiendo crear perfiles de cacheo diferentes que se puedan aplicar a colecciones de sitios o sitios individuales. Como nota curiosa, WSS no dispone del mecanismo de cacheo que tiene MOSS… y es por eso de la relación rara de que hablábamos anteriormente.

En realidad, a SharePoint no le gusta usar cacheo. Inclusive en las versiones anteriores se podía activar por medio de un cambio en el web.config, pero era prácticamente prohibido hacerlo. Por una razón muy sencilla: SharePoint es un sistema creado para suministrar información a cientos de miles de usuarios, dándole información personalizada a cada uno de ellos (piense nada más que cada usuario puede modificar su propia interface) y si le vamos a dar cache a cada usuario, simplemente no existe batería de servidores con suficiente memoria RAM para mantener el cacheo; o habría que limitar el cacheo a un par de segundos, lo que tampoco tiene mucho sentido.

Pero a SharePoint 2007 le pusieron todo el asunto de Content Management, con lo que ya SharePoint no solamente sirve para crear sitios de trabajo individuales e individualizados, sino también para crear sitios Web comunes y corrientes (Publishing Feature). Y como en sitios de este tipo la información cambia muy poco en el tiempo, y no cambia para nada para cada usuario, cacheo es simpatiquísimo. Así que SharePoint 2007 tiene cacheo, pero solamente para MOSS, pues WSS no tiene la Característica de Publicación, así que no se puede usar para crear sitios Web comunes y corrientes, y, ergo de nuevo, no queremos meterle cacheo…

Como nota al lado, otra de las cosas simpáticas de cacheo en SharePoint es que en granjas de servidores se ve frecuentemente que la información presentada no es consecuente (o, por lo menos, eso es lo que mis queridos clientes siempre dicen); veamos: servidor A tiene en memoria pagina A por 60 segundos; la información de la pagina ha sido cambiada en el momento que el cache del servidor B ha expirado, así que ahora los dos servidores tienen información diferente; el usuario que acaba de ver la pagina desde el servidor A regresa a la pagina, pero esta vez es redirigido por el Load Balancing al servidor B: la información es diferente; vuelve a hacer un refresco de la pagina, y el Load Balancing lo manda al servidor A: regreso a la información antigua… estoy empezando a creer que al final mis pobres clientes hasta puede que tengan razón…

Pero bueno, es el día de irse por las ramas… Velocity: sistema para distribuir el cacheo entre los diferentes servidores de la granja, evitando, de pasada, los problemas de los que hemos estado hablando… suena bien para metérselo a SharePoint… manos a la obra: bajar el software (2.5 MB, no está mal)… buscar una granja de prueba de SharePoint (dos front end, nada del otro mundo)… intentar instalar el software… empieza a instalar sin problemas… llegamos a la pantalla de configuración… error… cancelar el error y seguir adelante… error… volverlo a intentar… error… bueno, al fin y al cabo es software beta… y a SharePoint no le gusta ese asunto del cacheo… habra que esperar hasta la proxima version…

Gustavo – http://www.gavd.net/servers/
Escriba un Comentario que me haga reir…

SharePoint AllWebs no hace lo que yo quiero… y si lo hace, lo hace mal

Pregunta para los fanáticos de optimalización en programación: como leer todas las subWebs de un SPSite de SharePoint de la forma más rápida, efectiva y eficiente posible?

Primero que todo el problema: para un portal (bastante) grande de SharePoint, en donde al final habrá algunos miles de subSites, es necesario crear una especie de “árbol” con la estructura. Usar un cacheo del árbol después de que se ha leído de arriba abajo mejora el rendimiento, por supuesto, pero primero hay que haber leído todos los sitios y sus subsitios y sus subsitios ad infinitum… La estructura es bastante dinámica, cambiando en (en el peor de los casos) dentro de minutos, así que el cacheo no se puede mantener por más de algunos minutos sin correr el riesgo de que el usuario pierda información.

Lo primero es que hay que usar recursividad para poder entrar por todas partes, eso es seguro. Pero dentro del lazo de la recursión es necesario conseguir el SPWebCollection de alguna forma. En principio tenemos dos maneras: con “SPSite.AllWebs()” o con “SPSite.OpenWeb().GetSubwebsForCurrentUser()”. El primer método se descarta bastante rápido por problemas de seguridad: si el usuario que está creando el árbol no tiene derechos suficientes en una u otra SPWeb en el camino, recibirá simplemente un “Access Denied” sin más, y el asunto se detiene irremediablemente. Peor aún, el usuario tiene que tener derechos de “Full Control”, de otra forma SharePoint simplemente se niega a seguir adelante.

Así que queda el segundo método. Para mi gran sorpresa, tampoco funciona en la forma deseada. Por una u otra razón, el método tampoco devuelve todas las subWebs directamente bajo la Web actual. Después de renegar, sudar, llorar y rogar, al final resulta que hay que usar el asunto por medio del “SPSite.RootWeb.GetSubwebsForCurrentUser()” y no por medio del “OpenWeb()”. Porque? Ni idea… según la fantastica información proporcionada por el SDK, la propiedad RootWeb “Gets the root Web site of the site collection” y el método “OpenWeb” “Returns the site that is associated with the URL that is used in an SPSite constructor”… más claro no canta una gallina… y según mi humilde entender, los dos hacen lo mismo, pero de diferente manera… lo de diferente manera es seguro, en cualquier caso…

Pero el asunto va por otro lado. AllWebs y GetSubwebsForCurrentUser son simplemente asesinos del rendimiento, pues los dos no hacen más que un bucle de web en web para sacar la lista requerida. Cuando estamos hablando de unos cuantos de sitios, que hay que leer de vez en cuando, simplemente no importa como lo hacen. Cuando hay que leer miles de sitios con mucha frecuencia, los servidores de la granja simplemente se van a 100% de CPU, y el usuario se puede ir a tomar un café mientras la información aparece en pantalla.

Por eso de que a veces quedan marcas en la memoria de cosas leídas hace un montón de tiempo, me he acordado de un documento de Steve Peschka (Microsoft) de hace más de un año: “Working with large lists in Office SharePoint Server 2007” (http://go.microsoft.com/fwlink/?LinkId=95450&clcid=0x409) que describe cómo usar la clase “PortalSiteMapProvider”. Esta es una clase de SharePoint que probablemente solo conoce el desarrollador que la hizo, y que fue creada originalmente para ayudar a cachear la navegación (según las palabras de Steve Peschka). La clase contiene un “PortalWebSiteMapNode” que a su vez contiene la propiedad “GetChildNodes” que nos entrega una colección de los subWebs que estamos buscando… perfecto… casi perfecto… hmmm… inservible… “PortalSiteMapProvider” es una clase de MOSS y yo lo necesito para WSS…

Gustavo – http://www.gavd.net/servers/
Escriba un Comentario que me haga reir…