Link del día: John Resig, más personal

Hace un tiempo Josh Resig, famoso por ser el creador de JQuery, anunció en su cuenta de Twitter que estaría brindando una especie de “conferencia de prensa”, una rueda de preguntas y respuestas en Reddit, específicamente en este thread.

Ahí podemos conocer un poco más de lo que él piensa sobre distintos temas, desde que le encantan los memes del Rage Guy hasta las propuestas que estaba pensando en plantear para la reestructuración del estándar del DOM. Podemos saber qué máquina usa y qué software hasta qué cosas estudió en la universidad y cómo hace para concentrarse en su trabajo.

Es el tipo de oportunidad de tener a un caso de éxito bien cerca para preguntar y escuchar respuestas. Desafortunadamente me enteré de esto cuando ya había ocurrido, pero habría estado interesante poder hacer un par de preguntas, no?

Soy un zorrinito preguntón.

(Read more →)

Link del día: Humane.js

Vamos a seguir con otra librería JavaScript, en este caso, Humane.js. Esta en particular se trata de un sistema de envío de mensajes al usuario a través de ventanas que recuerdan mucho al lightbox, sin ser tan intrusivas, ni difíciles de utilizar en el código. Estos mensajitos automáticamente se van luego de un tiempo o podemos esperar a que el usuario realice alguna acción (para asegurarnos de que haya efectivamente leído nuestro mensaje).

Para ser sincero, no lo veo como algo revolucionario o ni siquiera nuevo, pero desde la simplicidad de su utilización hasta lo simple de su implementación (la cual podemos ver en GitHub), hace que sea ideal para implementar en dispositivos en donde el espacio es importante, y la simplicidad del mensaje también. Yo creo que uno de estos se vería muy bien en aplicaciones móviles.

Soy un zorrinito simple.

(Read more →)

Crear sitios de SharePoint programáticamente

El objetivo

Estuve peleando estos días con la posibilidad de crear sitios de SharePoint programáticamente desde mi código. Sin embargo, aún cuando esto fue hecho para un proyecto interno, tomé tanto de la internet que realmente siento que debo devolver algo a la comunidad. Además, no voy a divulgar nada relacionado al proyecto, así que aquí vamos.

Primero, buscando en internet una forma de crear estos sitios programáticamente, me topé con un artículo del 2007 de WinSmarts.com, llamado Programatically create a Sharepoint site based on a site definition. Aquí está el código de ese post:

public static bool CreateSite(string parentSiteURL, string siteURLRequested,
    string siteTitle, string siteTemplateName)
{
    bool returnCondition = false; // Assume failure.
    const Int32 LOCALE_ID_ENGLISH = 1033;
    using (SPSite siteCollection = new SPSite(parentSiteURL))
    {
        SPWeb parentWeb = siteCollection.OpenWeb();
        SPWebTemplateCollection Templates = siteCollection.GetWebTemplates(Convert.ToUInt32(LOCALE_ID_ENGLISH));
        SPWebTemplate siteTemplate = Templates[siteTemplateName];
        if (parentWeb.Webs[siteURLRequested].Exists)
        {
            parentWeb.Webs.Delete(siteURLRequested);
        }
        parentWeb.Webs.Add(siteURLRequested, siteTitle, "", Convert.ToUInt32(LOCALE_ID_ENGLISH), siteTemplate, false, false);

        // All is good?
        returnCondition = true;
    }
    return returnCondition;
}

Lo que yo quería cambiar de esta solución era:

  • Los Locales estaban hardcodeados
  • Los Site templates estaban hardcodeados
  • La descripción del sitio estaba hardcodeado (esa es la cadena vacía en la llamada a webs.add)

Removiendo literales…

Entonces fui a revisar la lista de locales: SharePoint Locale ID (LCID) Table, y una lista de los identificadores de site templates, en un artículo llamado Create a site programmatically in moss.

Bueno… aquí vamos (cuidado, vienen enumeraciones largas):

public enum SPLocales: uint
{
	Afrikaans /*af*/ = 1078,
	Albanian /*sq*/ = 1052,
	Arabic_UnitedArabEmirates /*ar-ae*/ = 14337,
	Arabic_Bahrain /*ar-bh*/ = 15361,
	Arabic_Algeria /*ar-dz*/ = 5121,
	Arabic_Egypt /*ar-eg*/ = 3073,
	Arabic_Iraq /*ar-iq*/ = 2049,
	Arabic_Jordan /*ar-jo*/ = 11265,
	Arabic_Kuwait /*ar-kw*/ = 13313,
	Arabic_Lebanon /*ar-lb*/ = 12289,
	Arabic_Libya /*ar-ly*/ = 4097,
	Arabic_Morocco /*ar-ma*/ = 6145,
	Arabic_Oman /*ar-om*/ = 8193,
	Arabic_Qatar /*ar-qa*/ = 16385,
	Arabic_SaudiArabia /*ar-sa*/ = 1025,
	Arabic_Syria /*ar-sy*/ = 10241,
	Arabic_Tunisia /*ar-tn*/ = 7169,
	Arabic_Yemen /*ar-ye*/ = 9217,
	Armenian /*hy*/ = 1067,
	Azeri_Latin /*az-az*/ = 1068,
	Azeri_Cyrillic /*az-az*/ = 2092,
	Basque /*eu*/ = 1069,
	Belarusian /*be*/ = 1059,
	Bulgarian /*bg*/ = 1026,
	Catalan /*ca*/ = 1027,
	Chinese_China /*zh-cn*/ = 2052,
	Chinese_HongKongSAR /*zh-hk*/ = 3076,
	Chinese_MacauSAR /*zh-mo*/ = 5124,
	Chinese_Singapore /*zh-sg*/ = 4100,
	Chinese_Taiwan /*zh-tw*/ = 1028,
	Croatian /*hr*/ = 1050,
	Czech /*cs*/ = 1029,
	Danish /*da*/ = 1030,
	Dutch_TheNetherlands /*nl-nl*/ = 1043,
	Dutch_Belgium /*nl-be*/ = 2067,
	English_Australia /*en-au*/ = 3081,
	English_Belize /*en-bz*/ = 10249,
	English_Canada /*en-ca*/ = 4105,
	English_Caribbean /*en-cb*/ = 9225,
	English_Ireland /*en-ie*/ = 6153,
	English_Jamaica /*en-jm*/ = 8201,
	English_NewZealand /*en-nz*/ = 5129,
	English_Phillippines /*en-ph*/ = 13321,
	English_SouthAfrica /*en-za*/ = 7177,
	English_Trinidad /*en-tt*/ = 11273,
	English_UnitedKingdom /*en-gb*/ = 2057,
	English_UnitedStates /*en-us*/ = 1033,
	Estonian /*et*/ = 1061,
	Farsi /*fa*/ = 1065,
	Finnish /*fi*/ = 1035,
	Faroese /*fo*/ = 1080,
	French_France /*fr-fr*/ = 1036,
	French_Belgium /*fr-be*/ = 2060,
	French_Canada /*fr-ca*/ = 3084,
	French_Luxembourg /*fr-lu*/ = 5132,
	French_Switzerland /*fr-ch*/ = 4108,
	Gaelic_Ireland /*gd-ie*/ = 2108,
	Gaelic_Scotland /*gd*/ = 1084,
	German_Germany /*de-de*/ = 1031,
	German_Austria /*de-at*/ = 3079,
	German_Liechtenstein /*de-li*/ = 5127,
	German_Luxembourg /*de-lu*/ = 4103,
	German_Switzerland /*de-ch*/ = 2055,
	Greek /*el*/ = 1032,
	Hebrew /*he*/ = 1037,
	Hindi /*hi*/ = 1081,
	Hungarian /*hu*/ = 1038,
	Icelandic /*is*/ = 1039,
	Indonesian /*id*/ = 1057,
	Italian_Italy /*it-it*/ = 1040,
	Italian_Switzerland /*it-ch*/ = 2064,
	Japanese /*ja*/ = 1041,
	Korean /*ko*/ = 1042,
	Latvian /*lv*/ = 1062,
	Lithuanian /*lt*/ = 1063,
	FYRO_Macedonian /*mk*/ = 1071,
	Malay_Malaysia /*ms-my*/ = 1086,
	Malay_Brunei /*ms-bn*/ = 2110,
	Maltese /*mt*/ = 1082,
	Marathi /*mr*/ = 1102,
	Norwegian_Bokmal /*no-no*/ = 1044,
	Norwegian_Nynorsk /*no-no*/ = 2068,
	Polish /*pl*/ = 1045,
	Portuguese_Portugal /*pt-pt*/ = 2070,
	Portuguese_Brazil /*pt-br*/ = 1046,
	Raeto_Romance /*rm*/ = 1047,
	Romanian_Romania /*ro*/ = 1048,
	Romanian_RepublicOfMoldova /*ro-mo*/ = 2072,
	Russian /*ru*/ = 1049,
	Russian_RepublicOfMoldova /*ru-mo*/ = 2073,
	Sanskrit /*sa*/ = 1103,
	Serbian_Cyrillic /*sr-sp*/ = 3098,
	Serbian_Latin /*sr-sp*/ = 2074,
	Setsuana /*tn*/ = 1074,
	Slovenian /*sl*/ = 1060,
	Slovak /*sk*/ = 1051,
	Sorbian /*sb*/ = 1070,
	Spanish_Spain /*es-es*/ = 1034,
	Spanish_Argentina /*es-ar*/ = 11274,
	Spanish_Bolivia /*es-bo*/ = 16394,
	Spanish_Chile /*es-cl*/ = 13322,
	Spanish_Colombia /*es-co*/ = 9226,
	Spanish_CostaRica /*es-cr*/ = 5130,
	Spanish_DominicanRepublic /*es-do*/ = 7178,
	Spanish_Ecuador /*es-ec*/ = 12298,
	Spanish_Guatemala /*es-gt*/ = 4106,
	Spanish_Honduras /*es-hn*/ = 18442,
	Spanish_Mexico /*es-mx*/ = 2058,
	Spanish_Nicaragua /*es-ni*/ = 19466,
	Spanish_Panama /*es-pa*/ = 6154,
	Spanish_Peru /*es-pe*/ = 10250,
	Spanish_PuertoRico /*es-pr*/ = 20490,
	Spanish_Paraguay /*es-py*/ = 15370,
	Spanish_ElSalvador /*es-sv*/ = 17418,
	Spanish_Uruguay /*es-uy*/ = 14346,
	Spanish_Venezuela /*es-ve*/ = 8202,
	Sutu /*sx*/ = 1072,
	Swahili /*sw*/ = 1089,
	Swedish_Sweden /*sv-se*/ = 1053,
	Swedish_Finland /*sv-fi*/ = 2077,
	Tamil /*ta*/ = 1097,
	Tatar /*tt*/ = 1092,
	Thai /*th*/ = 1054,
	Turkish /*tr*/ = 1055,
	Tsonga /*ts*/ = 1073,
	Ukrainian /*uk*/ = 1058,
	Urdu /*ur*/ = 1056,
	Uzbek_Cyrillic /*uz-uz*/ = 2115,
	Uzbek_Latin /*uz-uz*/ = 1091,
	Vietnamese /*vi*/ = 1066,
	Xhosa /*xh*/ = 1076,
	Yiddish /*yi*/ = 1085,
	Zulu /*zu*/ = 1077
}

public enum SPSiteTemplates
{
	TeamSite,
	BlankSite,
	DocumentWorkspace,
	BasicMeetingWorkspace,
	BlankMeetingWorkspace,
	DecisionMeetingWorkspace,
	SocialMeetingWorkspace,
	MultiPageMeetingWorkspace,
	Wiki,
	Blog,
	///
<summary>
	/// A central document management location for an enterprise
	/// </summary>

	DocumentCenter,
	///
<summary>
	/// A central location in which records managers can define routes for incoming files
	/// </summary>

	RecordsCenter1,
	RecordsCenter2,
	PublishingSite,
	///
<summary>
	/// A site for publishing web pages on a schedule with workflow features enabled
	/// </summary>

	PublishingSite2,
	PressReleasesSite,
	///
<summary>
	/// A publishing site for web pages using approval workflows
	/// </summary>

	PublishingSiteWithWorkflow,
	///
<summary>
	/// A site for publishing news and articles
	/// </summary>

	NewsSite,
	///
<summary>
	/// A site for creating, managing, and delivering web pages, dashboards, and Key Performance Indicators (KPIs)
	/// </summary>

	ReportCenter,
	///
<summary>
	/// A starter hierarchy for an intranet divisional portal
	/// </summary>

	SPSPortal,
	///
<summary>
	/// A profile site that includes page layouts with zones
	/// </summary>

	ProfileSite,
	///
<summary>
	/// A site collection preconfigured for revision-controlled, secure content creation and publication
	/// </summary>

	PublishingPortal,
	///
<summary>
	/// Keep in mind that only one of these can be provisioned per Shared Services Provider
	/// </summary>

	MySiteHost,
	///
<summary>
	/// A site designed to deliver the search query and results experience
	/// </summary>

	SearchCenter,
	///
<summary>
	/// A superset of the previous; does not appear in navigation bars
	/// </summary>

	SearchCenter2
}

Un poco de magia enum

Lo que nos queda es:

  • Convertir enums SPLocales a su representación correspondiente en uint
  • Convertir enums SPSiteTemplates a su representación en cadena correspondientes
  • Evitar intentar crear sitios duplicados

Para convertir enums SPLocales a su representación uint, sólo tenemos que hacer el casting correspondiente. Esta es la razón por la que hicimos al enum heredar de unit y setear el valor apropiado en cada elemento.

Para poder convertir enums SPSiteTemplates a cadenas, el método común es utilizar atributos y reflexión, pero personalmente no me gusta esa forma, así que hice una con diccionarios y métodos de extensión. La idea es proveer al enum la posibilidad de convertirse en una cadena, y estas cadenas de ser parseadas en el enum nuevamente. Para tener este tipo de relación en ambos sentidos, usé la implementación de **BiDictionaryOneToOne ** del usuario de StackOverflow’s Joel in Go, en una pregunta de sobre cómo hacer un diccionario buscable key-value y buscable value-key: Bidirectional 1 to 1 Dictionary in C#.

public static class SPSiteTemplateMethodExtension
{
	private const string SP_SITE_TEMPLATE_TEAM_SITE = "STS#0";
	private const string SP_SITE_TEMPLATE_BLANK_SITE = "STS#1";
	private const string SP_SITE_TEMPLATE_DOCUMENT_WORKSPACE = "STS#2";
	private const string SP_SITE_TEMPLATE_BASIC_MEETING_WORKSPACE = "MPS#0";
	private const string SP_SITE_TEMPLATE_BLANK_MEETING_WORKSPACE = "MPS#1";
	private const string SP_SITE_TEMPLATE_DECISION_MEETING_WORKSPACE = "MPS#2";
	private const string SP_SITE_TEMPLATE_SOCIAL_MEETING_WORKSPACE = "MPS#3";
	private const string SP_SITE_TEMPLATE_MULTI_PAGE_MEETING_WORKSPACE = "MPS#4";
	private const string SP_SITE_TEMPLATE_WIKI = "WIKI#0";
	private const string SP_SITE_TEMPLATE_BLOG = "BLOG#0";
	private const string SP_SITE_TEMPLATE_DOCUMENT_CENTER = "BDR#0";
	private const string SP_SITE_TEMPLATE_RECORDS_CENTER_1 = "OFFILE#0";
	private const string SP_SITE_TEMPLATE_RECORDS_CENTER_2 = "OFFILE#1";
	private const string SP_SITE_TEMPLATE_PUBLISHING_SITE = "CMSPUBLISHING#0";
	private const string SP_SITE_TEMPLATE_PUBLISHING_SITE_2 = "BLANKINTERNET#0";
	private const string SP_SITE_TEMPLATE_PRESS_RELEASES_SITE = "BLANKINTERNET#1";
	private const string SP_SITE_TEMPLATE_PUBLISHING_SITE_WITH_WORKFLOW = "BLANKINTERNET#2";
	private const string SP_SITE_TEMPLATE_NEWS_SITE = "SPSNHOME#0";
	private const string SP_SITE_TEMPLATE_REPORT_CENTER = "SPSREPORTCENTER#0";
	private const string SP_SITE_TEMPLATE_SPS_PORTAL = "SPSPORTAL#0";
	private const string SP_SITE_TEMPLATE_PROFILE_SITE = "PROFILES#0";
	private const string SP_SITE_TEMPLATE_PUBLISHING_PORTAL = "BLANKINTERNETCONTAINER#0";
	private const string SP_SITE_TEMPLATE_MY_SITE_HOST = "SPSMYSITEHOST#0";
	private const string SP_SITE_TEMPLATE_SEARCH_CENTER = "SRCHCENTERLITE#0";
	private const string SP_SITE_TEMPLATE_SEARCH_CENTER_2 = "SRCHCENTERLITE#1";

	private static BiDictionaryOneToOne<SPSiteTemplates, string> _templates;
	private static BiDictionaryOneToOne<SPSiteTemplates, string> Templates {
		get {
			if (_templates == null) {
				_templates = new BiDictionaryOneToOne<SPSiteTemplates, string>();
				_templates.Add(SPSiteTemplates.BasicMeetingWorkspace, SP_SITE_TEMPLATE_BASIC_MEETING_WORKSPACE);
				_templates.Add(SPSiteTemplates.BlankMeetingWorkspace, SP_SITE_TEMPLATE_BLANK_MEETING_WORKSPACE);
				_templates.Add(SPSiteTemplates.BlankSite , SP_SITE_TEMPLATE_BLANK_SITE);
				_templates.Add(SPSiteTemplates.Blog , SP_SITE_TEMPLATE_BLOG);
				_templates.Add(SPSiteTemplates.DecisionMeetingWorkspace , SP_SITE_TEMPLATE_DECISION_MEETING_WORKSPACE);
				_templates.Add(SPSiteTemplates.DocumentCenter , SP_SITE_TEMPLATE_DOCUMENT_CENTER);
				_templates.Add(SPSiteTemplates.DocumentWorkspace , SP_SITE_TEMPLATE_DOCUMENT_WORKSPACE);
				_templates.Add(SPSiteTemplates.MultiPageMeetingWorkspace , SP_SITE_TEMPLATE_MULTI_PAGE_MEETING_WORKSPACE);
				_templates.Add(SPSiteTemplates.MySiteHost , SP_SITE_TEMPLATE_MY_SITE_HOST);
				_templates.Add(SPSiteTemplates.NewsSite , SP_SITE_TEMPLATE_NEWS_SITE);
				_templates.Add(SPSiteTemplates.PressReleasesSite , SP_SITE_TEMPLATE_PRESS_RELEASES_SITE);
				_templates.Add(SPSiteTemplates.ProfileSite , SP_SITE_TEMPLATE_PROFILE_SITE);
				_templates.Add(SPSiteTemplates.PublishingPortal , SP_SITE_TEMPLATE_PUBLISHING_PORTAL);
				_templates.Add(SPSiteTemplates.PublishingSite , SP_SITE_TEMPLATE_PUBLISHING_SITE);
				_templates.Add(SPSiteTemplates.PublishingSite2 , SP_SITE_TEMPLATE_PUBLISHING_SITE_2);
				_templates.Add(SPSiteTemplates.PublishingSiteWithWorkflow , SP_SITE_TEMPLATE_PUBLISHING_SITE_WITH_WORKFLOW);
				_templates.Add(SPSiteTemplates.RecordsCenter1 , SP_SITE_TEMPLATE_RECORDS_CENTER_1);
				_templates.Add(SPSiteTemplates.RecordsCenter2 , SP_SITE_TEMPLATE_RECORDS_CENTER_2);
				_templates.Add(SPSiteTemplates.ReportCenter , SP_SITE_TEMPLATE_REPORT_CENTER);
				_templates.Add(SPSiteTemplates.SearchCenter , SP_SITE_TEMPLATE_SEARCH_CENTER);
				_templates.Add(SPSiteTemplates.SearchCenter2 , SP_SITE_TEMPLATE_SEARCH_CENTER_2);
				_templates.Add(SPSiteTemplates.SocialMeetingWorkspace , SP_SITE_TEMPLATE_SOCIAL_MEETING_WORKSPACE);
				_templates.Add(SPSiteTemplates.SPSPortal , SP_SITE_TEMPLATE_SPS_PORTAL);
				_templates.Add(SPSiteTemplates.TeamSite , SP_SITE_TEMPLATE_TEAM_SITE);
				_templates.Add(SPSiteTemplates.Wiki , SP_SITE_TEMPLATE_WIKI);
			}
			return _templates;
		}
	}

	public static string GetSharePointStringKey(this SPSiteTemplates siteTemplate)
	{
		return Templates.GetByFirst(siteTemplate);
	}
}

Sí, eso de ahí es un singleton y podría tener problemas potenciales de concurrencia, pero no estamos esperando agregar/remover/editar items. Además, yo habría ido con un constructor estático, pero los métodos de extensión tienen que implementarse en clases estáticas, y las clases marcadas como estáticas no tienen permitido tener constructores estáticos.

Toques extras

Con esto ya casi había terminado, pero resulta que si queremos verificar que un sitio existe, necesitamos usar su URL completa, pero si usamos el símbolo de los dos puntos ( : ) entonces vamos a tener una linda SPException diciéndonos que la URL es inválida. Lo que tenemos que hacer es usar URLs relativas a la URL del site collection, así que incluí un par de líneas para parsearlas.

Así que, aquí está la última versión del método CreateSite:

public SPWeb CreateSite(string parentSiteURL, string siteURLRequested, string siteTitle, SPSiteTemplates siteTemplate, SPLocales locale, string description)
{
	SPWeb resultWeb = null;

	PerformActionOnSite(parentSiteURL, (siteCollection, parentWeb) =>
	{
		SPWebTemplateCollection Templates = siteCollection.GetWebTemplates(Convert.ToUInt32(locale));
		SPWebTemplate spTemplate = Templates[siteTemplate.GetSharePointStringKey()];

		var rootUrl = siteCollection.Url;
		if (siteURLRequested.ToUpper().StartsWith(rootUrl.ToUpper()))
		{
			siteURLRequested = siteURLRequested.Remove(0, rootUrl.Length);
			if (siteURLRequested.StartsWith("/"))
				siteURLRequested = siteURLRequested.Remove(0, 1);

			if (siteURLRequested.Length == 0) //they were equal, don't create the root site
			{
				resultWeb = parentWeb;
			}
			else if (parentWeb.Webs[siteURLRequested].Exists) //it's not the root site, but it exist anyway
			{
				resultWeb = parentWeb.Webs[siteURLRequested];
			}
			else
			{
				resultWeb = parentWeb.Webs.Add(siteURLRequested, siteTitle, description, Convert.ToUInt32(locale), spTemplate, false, false);
			}
		}
	});

	return resultWeb;
}

Si se sienten confundidos por el método PerformActionOnSite, es un wrapper que creé para automáticamente usar y hacer dispose en los objetos **SPSite **y **SPWeb **.

protected void PerformActionOnSite(string siteUrl, Action<SPSite, SPWeb> action)
{
	using (SPSite site = new SPSite(siteUrl))
	{
		using (SPWeb web = site.OpenWeb())
		{
			action(site, web);
			web.Close();
		}
		site.Close();
	}
}

Ok, entonces ya todo está en su lugar, deberían poder usarlo así::

CreateSite("http://siteCollectionUrl", "http://siteCollectionUrl/newSite", SPSiteTemplates.PublishingSite, SPLocales.English_UnitedStates, "site description");

Eso es todo. Espero que esto sea útil para ustedes. Subí una copia de todo este código en http://files.alphasmanifesto.com/Software/

¡Disfruten!

Referencias:

(Read more →)

Link del día: Cómo atacar un problema de performance

Hablando de la web y performance y todo eso, creo que nunca hemos visto un caso práctico de cómo debería atacarse un problema de performance. Es decir, si sospechamos que hay un problema de estos en un determinado lugar, ¿cuáles son los pasos a ejecutar? Creo que la experiencia juega mucho a favor, porque cada caso es distinto, pero siempre es mejor observar este tipo de situaciones en webs o aplicaciones que tienen una carga pesada diaria, con lo cual un problema de performance es mucho más pequeño y difícil de detectar, y las mejoras que se necesitan son mucho más finas y difíciles de lograr.

En el blog de Sam Saffron, nos cuenta cómo se trata uno de estos problemas en la famosa web Stack Overflow, llamada A day in the life of a slow page at Stack Overflow.

Como podemos ver, las aproximaciones al problema son varias, desde la tecnología utilizada, hasta el tipo de queries que se hacen a base de datos, optimizaciones en las mismas, y hay miles de cosas más que podrían estar involucradas (imágenes, textos, procesamiento de distintos elementos… en fin, es todo un mundo.

Soy un zorrinito performante.

(Read more →)

Link del día: Performance HTML5, CSS3 y DOM, Parte 1: Intro

Acabo de ver dos videos de Paul Irish, del grupo de Google Chrome. Uno de ellos era Google Chrome Developer Tools: 12 Tricks to Develop Quicker. Aquí básicamente nos da una explicación de los Chrome Developer Tools con algunos trucos que no son tan sabidos. Interesante y útil.

Sin embargo, el video que más interesante me resultó es uno de media hora llamado HTML5, CSS3 and DOM Performance.

Este video es terriblemente informativo sobre varias temáticas. Quiero en el futuro poder dedicarle un poco más de tiempo a cada una, pero mientras tanto, dejenmé hacer un resumen de las cosas que se tratan en el video:

  • Reflows: primero se habla de este concepto, relacionado a las operaciones de repintado que un navegador debe realizar. Nos cuenta el caso de Chrome y de Safari (WebKit) y de ciertas operaciones y trucos para evitar. Nos muestra cómo efectivamente podemos monitorear y mejorar esa performance.
    Herramientas relacionadas: Comandos ocultos de Chrome.
  • Animaciones CSS con aceleración por hardware: Mucho del trabajo de repintado puede hacerse de forma tal que el navegador termine delegándolo al GPU de mando en la máquina cliente. Esto trae experiencias muy distintas, y puede forzarse con pocos truquitos, que en este momento son básicamente hacks. Incluso podemos ver el efecto en ciertos dispositivos móviles.
    Herramientas relacionadas: Improving the performance of your HTML5 App (artículo), Elements Complete (demostración)
  • Animación y web workers: Mucho del trabajo que nuestro javascript hace no tiene por qué estar en el thread principal de pintado de la página, esto lleva a las páginas frizadas y funcionando lento. Para eso podemos delegar nuestra ejecución a web workers, que tienen una buena performance y es amigable con la batería.
    Herramientas relacionadas: WebGL Field (demo)
  • Benchmarking: relacionado a todo lo anterior, necesitamos de una forma objetiva y poderosa de poder probar qué elementos son mejores. El ejemplo que Paul menciona es: realmente es más performante usar el === en lugar del == en JavaScript? Ahora tenemos la forma de probarlo fácilmente.
    Herramientas relacionadas: jsPerf (herramienta de test cases para benchmarking), BrowserScope (versión colectiva de pruebas en browsers), Benchmark.js (librería JavaScript para benchmarking y testing).
  • Build scripts: la posibilidad de automatizar el generado de los sitios puede ahorrar muchos problemas en la performance. Así como alguna vez hablamos de mod_pagespeed, Paul menciona otras alternativas.
    Herramientas relacionadas: HTML5 BoilerPlate (librería de startup para generar aplicaciones bajo buenas prácticas).

Espero poder ahondar en cada uno de estos en el futuro. Estén atentos!

Soy un zorrinito performante.

(Read more →)

Link del día: HTML5 Snippets

Todavía me encuentro intentando aplicar los conceptos nuevos de HTML5 en algún proyecto, y dsafortunadamente todavía no he encontrado la oportunidad. Pero sé que al momento de hacerlo, voy a dudar y voy a necesitar cierta ayuda para poder trabajar con sus nuevas características y cómo ellas se reflejan en resultados tangibles.

Para eso, muy seguramente me sea muy útil esta web llamada HTML5 Snippets (muy del estilo de Refactory), que recopila una serie de snippets de código utilizando HTML5, a veces CSS3, y no he visto aún - pero supongo que puede haber - JavaScript. El sitio está apenas comenzando su funcionamiento, con lo que no hay mucho movimiento ni contenido todavía, pero ojalá crezca pronto.

También podemos ayudar a eso loggeándonos (con Twitter) y enviando nuestros propios snippets para que figuren en la web. No sólo podemos escribirlos a mano sino que también podemos importarlos desde JSFiddle (parecido a HTML Instant pero más completo), en donde muy seguramente sea más fácil de programar.

Soy un zorrinito HTML5.

(Read more →)

Link del día: Typography Deconstructed

Para aquellos interesados en el mundo del diseño gráfico, aquí me llega un elemento muy importante en la forma de analizar el texto en los diseños: la forma de elegir la tipografía, pero en este caso, analizando cuáles son los elementos en particular que hacen a una tipografía distinta de otra, y qué características tiene cada una que la hace única.

Desde MicroSiervos, les dejo a Typography Deconstructed. El sitio tiene su sección principal en donde podemos ver un listado de los distintos elementos característicos de una tipografía, explicados cada uno y marcados en un ejemplo. Por supuesto, también tenemos la posibilidad de comprarlo en un poster o de simplemente verlos online, pero el sitio es lo suficientemente bello como para dejarse ver online sin mayores problemas.

Soy un zorrinito tipográfico.

(Read more →)

Link del día: Visualizar las estructuras

Los trabajadores de la informática tienen muy asimilados estos conceptos: pilas, colas, árboles, el camino más corto, ordenamiento, algoritmo, grafo, etc. Todos estos conceptos los hemos ido aprendiendo de una forma u otra, pero al momento de aplicarlos es cuando realmente se prueba nuestro aprendizaje. Personalmente creo que ningún aprendizaje está completo sin una buena visualización de la materia, y es aquí en donde, para las estructuras de datos, hay que inventar algo.

Seguramente lo mismo pensó David Galles, quién desarrolló un software en Java que nos permite visualizar el funcionamiento de estas estructuras. Data Structure Visualizations es el nombre que le otorgó, pero la historia no se queda ahí, sino que HTML5 hizo su entrada y ahora también tenemos la versión web. Denle una mirada, no sólo es interesante, sino entretenido.

Recuerdan La Belleza de Los Algoritmos? Creo que este link calificaba totalmente para estar en ese listado.

Soy un zorrinito estructurado.

(Read more →)

Link del día: Plataformas de Cloud presentes y por venir

Este link de hoy no es tan sustancial como el resto de los que estamos acostumbrados, pero no por eso deja de ser útil. En este caso, es un listado que productos Platform as a Service (PaaS). No todos son pagos, y algunos pagos tienen free trials, si a alguno le interesa probar alguna aproximación de computación distribuída, esta es su oportunidad.

El listado está aquí: List of Current and Upcoming Cloud Platforms. Dicho sea de paso, me encanta el diseño de ese blog. Minimalista en serio.

El listado de la categoría Cloud Platforms de Wikipedia provee algunos nombres más. Y por si no les alcanza todavía, acá tienen el List of Cloud Platfrms de CloudTweaks (2010).

Tampoco se olviden de checkear Cloud Computing Ecosystem, que lo mantienen actualizado desde aquél post en diciembre del 2009.

Soy un zorrinito en las nubes.

(Read more →)

CQRS

Estuve leyendo un poquito últimamente sobre CQRS, que significa Command-Query Responsibility Segregation en inglés (sería “Segregación de responsabilidades de commands-queries” en español, ahora explicaré). No he encontrado una buena y corta explicación al respecto así que aquí va mi intento:

¿Qué significa?

Llamaremos Command (comando) a cada operación en nuestro sistema que impactará el estado de la aplicación. Esto significa, cualquier acción que vaya a llevar un cambio en nuestros datos. (Recuerden, el comportamiento puede ser modelado como datos también, pero podemos considerar estos casos como un subconjunto de las situaciones de las que estamos hablando.) Por ejemplo, agregar un usuario, eliminar una tarea, cambiar un registro o actualizar los permisos pueden ser vistos como commands. Si están familiarizados con el patrón de diseño Command, pueden pensar que esta es la forma correcta de representar estas acciones. La idea encaja correctamente, pero estamos hablando de un modelo arquitectural aquí, así que aunque parezca natural usar este patrón, no es realmente necesario.

Llamaremos Query a cada operación que lea de nuestros datos para devolver un resultado. Por ejemplo, verificar si un cierto nombre de usuario existe, o traer una lista de tareas, verificar si un usuario tiene permisos, etc. Recuerden que las queries no alteran los datos. Como pueden ver, estos pueden ser modelados como un tipo de patrón de diseño command nuevamente, pero no es el punto de este artículo.

Finalmente Segregación de la Responsabilidad (otros usan **Separación **en su lugar) significa que nuestra aplicación usará comnands y queries de forma separada, y que no pueden ser mezclados. Esto significa que nuestro sistema realizará operaciones de escritura y de lectura en tiempos o en espacios lógicos separados.

¿Para qué sirve?

La mejor aproximación para este patrón arquitectural es tener dos conjuntos de datos distintos, uno para lectura y otro para escritura. El de escritura (view data source) está basado en el otro, que podría ser la persistencia actual para nuestros datos. Podemos entonces mejorar nuestro conjunto de datos para la lectura (que no tiene por qué ser relacional, ni normalizado, ni siquiera persistente mientras esté disponible, como un tipo de caché) para tener una aplicación muy veloz, mientras que el trabajo serio se hace con los commands en el otro conjunto de datos.

Las aplicaciones orientadas a tareas pueden entonces delegar las acciones de forma asíncrona para que las tareas-commands (escritura) no ocurran en tiempo real, así permitiéndonos tratar la concurrencia fácilmente, o dando una experiencia increíble en la sensación de velocidad en la aplicación de usuario. Han visto el mensaje de “Enviando email” de GMail y cómo a veces es instantáneo y otras veces puede tardar hasta 30 segundos? Ese es el tipo de experiencia que podrían dar.

El artículo en donde leí esto (MSDN: CQRS on Windows Azure) explica cómo los comandos pueden ser encolados para ser procesados por un thread en segundo plano, de forma que las tareas no tienen que mantener al usuario esperando, y un segundo thread en segundo plano puede estar actualizando los datos para la vista (los que usan las queries para leer), también de forma asíncrona. Nuestra aplicación podría ser la más rápida para la experiencia del usuario, o podríamos tener al usuario esperando y er notificado cuando su tarea se ejecuta (algo como un panel de notificaciones en la aplicación, algo al estilo Facebook que diga “Su reservación ha sido procesada” mientras el usuario sigue navegando por el sitio, quizás?).

Suena demasiado bueno para ser verdad…

Bueno, esta aproximación tiene sus problemillas. Número uno, definitivamente no lo recomendaría para sistemas de misión crítica o de tiempo real. Para nada. De ninguna manera.

Número dos, y tres, cuatro, cinco, etc., es que nos encontramos forzados a proveer un tipo especial de experiencia de usuario en donde las cosas no ocurren en tiempo real. ¿Cómo manejar esto?

Manejando operaciones de forma asíncrona

Al mismo tiempo, hay un problemita cuando necesitamos que nuestras queries funcionan contra datos reales, en tiempo real. Y a veces, no podemos evitar esto. Por ejemplo, ¿qué si necesitáramos hacer validaciones en un nombre de usuario que el usuario nos provee para verificar que no está duplicado? (Por supuesto, podemos diseñar las estrategias de caché para eso, pero aún así, es un problema.) Este tipo de situaciones puede llevar a algunas impurezas en el diseño, ya que deberíamos trabajar contra nuestro conjunto de datos de persistencia, y no el de lectura, pero las aplicaciones pueden ser modeladas en una forma diferente bajo la premisa de nada es urgente. Miremos algunos ejemplos:

  • _ ¿Es el nombre de usuario único?_
    ¿Por qué necesitaríamos nombres de usuario? Usemos email, autenticación externa como Facebook, Twitter, Google, Windows Live, OpenID, etc. No hagamos que el flujo de usuario dependa de un dato.
  • ¿Están los lugares disponibles para reservación?
    Hagamos un query sobre los datos de lectura, digámosle al usuario que puede haber cambios basado en su pedido, que está viendo y que se le enviará una notificación en el futuro avisando si la reservación fue exitosa o no. Hagamos una reservación lo más cercana a lo que el usuario prefería y permitámosle cambiarla luego, etc.
  • Verificar información de pago
    El pago es nunca instantáneo. Permitámosle al usuario proveer algún tipo de información de contacto, y si el pago no ocurre, contactémoslo luego. Avisémosle más tarde si el pago se efectuó o no. Podríamos en el tiempo intermedio darle una membresía temporal o limitada, etc.
  • Mensajería
    A menos que nuestro sistema de mensajería debiera trabajar como un chat en tiempo real, siempre puede ser procesado con determinada espera. En un chat, la gente espera que los mensajes lleguen de forma instantánea, pero con mensajes siempre saben que las esperas son posibles.

Hay muchos otros ejemplos. A menos que estemos trabajando en un sistema de tiempo real, muchas de las tareas pueden ser trabajadas en otro punto del tiempo. Nuevamente, si no estamos trabajando sobre un sistema de RADAR para el ejército.

Resumen

CQRS (Command-Query Responsibility Segregation/Separation) nos permitirá trabajar de forma asíncrona con operaciones de datos, que a veces pueden tomar más de lo esperado. El impacto más importante en esta aproximación son las expectativas que debemos darle al usuario sobre cuándo se ejecutarán las operaciones, pero la parte buena es la estabilidad, velocidad y disponibilidad de nuestra aplicación. Esto nos provee de habilidades especiales para escalar y crecer en las características de la misma.

Más referencias

  • Domain queries in CQRS, Stack Overflow
    Situaciones de tiempo real, aproximaciones a ellas en una aplicación CQRS.
  • CQRS, Task Based UIs, Event Sourcing, agh!, CodeBetter.com
    Este artículo habla de CQRS como patrón de diseño, no como patrón arquitectural. Personalmente no me encuentro en deacuerdo con eso, pero es un muy buen artículo.
  • Clarified CQRS, Udi Dahan
    Un buen artículo explicando CQRS y qué hacer en situaciones específicas.
  • CQRS on Windows Azure, MSDN Magazine
    La versión cloud de CQRS.

(Read more →)