Culture-aware business objects

In the past I have had to deal with globalization many times. Strangely I haven't been able to decide on a truly satisfying design pattern that works in all scenarios. In this case, I'm not referring to static labels and stuff like that, which you'd probably want to push into resource files, but rather dynamic text elements.

To clarify my intent, a assume this simple scenario. We have a Product object, which has the following definition:

class Product
{
	public Guid Id { get; set; }
	public string Name { get; set; }
	public string Description { get; set; }
	public double Price { get; set; }
}

Extremely simple, right? Ok, so now suppose that we have some sort of administrative interface where a website's administrator can manage products (CRUD). All is still well ...


But, then suppose that the company that owns the website, decides to go global. They want to sell products world-wide and thus need to accommodate localized descriptions of their products. The issue we are then faced with, making the string values culture aware can be tackled in various different ways. to name a few:

  • We could leave our object model as is, and just modify the datastore to persist the same Person object for each culture we want to support;
  • We could break our Person object into two separate objects (culture aware & culture unaware) and append a list of the aware objects to the unaware (base) object. obviously, we should modify our data layer accordingly;
  • We could wrap the culture specific fields in a newly defined type;

In the past, I've had most success with the second option and was forced to use option 1 in some scenarios where I had to work with some legacy code. The reason I don't like option 1 is that it makes for odd and de-normalized persistence. Also, I don't really like option 2, as it causes complex(er) object models. You get a lot of 'noise'. Moreover, it tends to result in hard(er) to read code. So, during my last globalization endeavor, I decided to explore option 3. 

In my case, specifically, only the string values were to become culture aware and persistence was done using XML Serialization techniques (no database). I ended up creating the following simple wrapper class:
 
public class CultureNameIndexedString : SerializableDictionary
 {
        /// 
        /// Sets the value of the specified string in the specified culture.
        /// 
        /// Culture of the value
        /// Value to set
        public void Set(CultureInfo culture, string value)
        {
            Set(culture.Name, value);
        }

        /// 
        /// Sets the value of the specified string in the specified name.
        /// 
        /// Name of the culture for the value
        /// Value to set
        public void Set(string name, string value)
        {
            if (string.IsNullOrEmpty(name)) throw new ArgumentNullException("name");
            
            if (!string.IsNullOrEmpty(value))
            {
                var key = name.ToLower();

                if (ContainsKey(key))
                {
                    this[key] = value;
                }
                else
                {
                    Add(key, value);
                }
            }
            else
            {
                if (ContainsKey(name))
                {
                    Remove(name);
                }
            }
        }

        /// 
        /// Tries to get the value of this string for the current culture, returning
        /// the string in the default culture is this fails.
        /// 
        /// Value
        public string GetCurrentOrDefault()
        {
            var value = GetSpecific(CultureHelper.CurrentCulture);

            if (string.IsNullOrEmpty(value))
            {
                value = GetSpecific(CultureHelper.DefaultCulture);
            }
            
            return value;
        }

        /// 
        /// Tries to get the value of this string for the specified culture, returning
        /// the string in the default culture is this fails.
        /// 
        /// Culture of the value
        /// Value
        public string GetSpecificOrDefault(CultureInfo culture)
        {
            var value = GetSpecific(culture);

            if (string.IsNullOrEmpty(value))
            {
                value = GetSpecific(CultureHelper.DefaultCulture);
            }
            
            return value;
        }

        /// 
        /// Tries to get the value of this string for the specified culture, returning
        /// the string in the default culture is this fails.
        /// 
        /// Culture (name) of the value
        /// Value
        public string GetSpecificOrDefault(string name)
        {
            var value = GetSpecific(name);

            if (string.IsNullOrEmpty(value))
            {
                value = GetSpecific(CultureHelper.DefaultCulture);
            }
            
            return value;
        }

        /// 
        /// Tries to get the value of this string for the specified culture
        /// 
        /// Culture of the value
        /// Value or null
        public string GetSpecific(CultureInfo culture)
        {
            if (culture == null) throw new ArgumentNullException("culture");

            return GetSpecific(culture.Name);
        }

        /// 
        /// Tries to get the value of this string for the specified name
        /// 
        /// Culture (name) of the value
        /// Value or null
        public string GetSpecific(string name)
        {
            if (string.IsNullOrEmpty(name)) throw new ArgumentNullException("name");

            var key = name.ToLower();

            return ContainsKey(key) ? this[key] : null;
        }

        #region Overrides

        public override string ToString()
        {
            return GetCurrentOrDefault();
        }

        #endregion
 }

Note1: The CultureHelper class just wraps configuration issues, and is irrelevant for this post.
Note2: I used an ActionFilter - yes, it was an MVC project - to set the current culture for each request.

As persistence of objects was achieved using serialization, I was forced to use an alternative to the IDictionary<TK, TV> class. As I strongly believe in reusing what's there, I googled around for a while and found the code here.

Then I modified my (fictive) Person class like so:
class Product
{
	public Product()
	{
		Name = new CultureNameIndexedString();
		Description = new CultureNameIndexedString();
	}

	public Guid Id { get; set; }
	public CultureNameIndexedString Name { get; set; }
	public CultureNameIndexedString Description { get; set; }
	public double Price { get; set; }
}
Note: Because of the need for XML Serialization, I used concrete type definitions rather than interfaces.

Obviously, I wanted to limit the impact of my changes - especially in the UI - so I created a simple override for the ToString() method. So, read-only access to the strings was unchanged. Obviously, for in some cases, I needed to get the value for a specific culture, typically in the CRUD screens. Setting values, sadly, has become quite different, like so:
var person = new Product { Id = id, Price = price };

person.Name.Set("nl-NL", "Schoen");
person.Name.Set("en-US", "Shoe");
person.Name.Set("it-IT", "Scarpa");
I must say I'm quite content with the way this worked out. Integrating this, in a fairly simple object model, took me only a few hours. Thanx to .NET's brilliant XML Serialization capabilities, I didn't even have to change the data access code! Obviously I broke compatibility with previously serialized data, but you can't have it all! ;-)

For other projects, what I'm concerned about, is how to handle immutable objects. That would be slightly more complex to implement, as I would not be able to simply use a dictionary. Another thing we could do, is extend the above functionality to work with other values as well, perhaps even images ... (it happens that also images need to change, based on the culture)

What do you think? Are there better alternatives? What do you use?

.NET Development on a Mac

I have always been attracted to Apples designs. With the previous models of the MacBook Pro, Apple to me, has proven that it has some first class minimalistic designers at his disposal. About a year ago I could finally shove my HP NC6220 (with great 1440x900 display) aside and bought myself a 17” MacBook Pro.

At first my plan was to use the Mac with Windows Vista 64-bit installed on it. But when I first started up the machine I quickly changed my mind. Not that OSX is that much more beautiful than Windows, just because it was refreshingly different and fast. I than changed my plans and decided to use BootCamp, having both an OSX and a Vista partition.

I don't know how you go about using a new gadget, but I become childishly enthusiastic and impatient. Who needs a manual if you can fiddle about? As I planned to repartition the disk anyway I began playing with the OS until I thought it would suit me best.

Hooked

It wasn't until I started to use my e-mail and address book that I really got hooked. Combined with the Google Apps setup and the Plaxo synchronization tool I was suddenly up-to-date with all my resources. Within a few months Nova Media came out with an iSync plugin for my dual SIM Samsung SGH-D880 and I have never, ever given any thought to synchronizing since.

As my Mac came with Leopard pre-installed, it also came with time-machine. Even though I resent the so-told 3D monkey proof interface, it is a means for backing up that does not disturb you ever while you work. Along with my Mac I purchased an external 1TB Lacie drive, and the TimeMachine partition of 320GB is still 50% empty. On various occasions I have managed to rescue/restore files deleted in one of my feared and famous hard-drive clean-up sessions.

You might also be amazed about the quality of the free and relatively cheap software available for OSX. Adium for example, surpasses anything I have used in the past for my IM needs. OmniFocus and OmniPlan and OfficeTime are rock-solid and well thought through tools I use on a daily basis.

Integration with my Canon DSLR works without any configuration or installation, unlike on Windows. The iLife suite offers a great video editing tool, and it was installed out-of-the-box.

Have you ever worked on a Windows machine that has ben installed more than a year ago? I am doing it on OSX. It is still just as fast and stable as when I booted it up the first time. I'm not 100% sure this is because of the OS, or because working with the virtualization allows me to better separate environments and tools, but I think it's worth mentioning. Also, I tend to just hibernate my Mac instead of shutting it down every time, this immensely increases productivity: Open it up and you're good to go.

So? .NET?

Ok, so I soon decided to use OSX as my base OS, but I still had the need for Windows, the .NET Framework, Visual Studio and IIS, my trusted domain. After reading various articles on both Parallels and VMWare Fusion I decided on the latter and haven't had any regrets.

Good thing about using VMWare Fusion opposed to a BootCamp setup, is that it allows me to run many different OS'es when I need them. I could now develop within a Windows Vista/XP VM and have a Linux VM running in the background with MySQL or whatever exotic platform or application. In building projects that integrate into a bigger enterprise network, consisting of more than one platform, this has proven very effective. I've come to learn that using Windows-ports of especially Linux software usually comes with quirks and thus hard to debug deployment issues.

Until this day, I haven't come across any situation in which my Windows VM's proved to suffer from being virtualized. In terms of performance using Vista Business 64-bit in a VM as my development environment, is much faster than XP performed on my old HP.

As many of the companies i've worked for also use VMWare for server virtualization, I have also had customers suppling me with a full-blown virtualized mirror of their production environment. This saved me spending hours on location testing in a real-live environment.

In terms of testing, of course virtualization has already proven itself. I can now easilly test sites on Linux, Windows and Mac with ease.

Limitations

Probably running the Windows directly would result in slightly better results, but than i'd loose OSX and all the good that comes with it. Also running more than 2 VM's on top of OSX is really pushing the limits. As I have 4 GB and (obviously) just 1 physical hard drive the VM's start to influence  each other's performance negatively.

My MacBook Pro sports a 17” 1920x1200 display. This is great when developing, as the keyboard is of regular size and the high screen resolutions is very handy in Visual Studio and SQL Enterprise Manager. The down-side is that traveling with a laptop this big is plain impractical. Not only because the leg-room on the Transavia flights seems to reduce by the month, but the size and weight make traveling with only hand luggage hard. Are they squeezing in more rows of seats, or is it just my imagination? My debating skills have improved though, after convincing many, many, many flight attendants to allow me to bring my uber-gadget and my suitcase aboard. I'm not sure what i'd leave behind if they wouldn't  have given in … ;)

To me, the new Apple laptops esthetically lost their edge (who the hell came up with the black keyboard?), but i've read that the internals only got better. I'm not sure i'd accept the glossy screen if I would be buying right now. I see some pretty and powerful machines coming from Sony and Asus that have more appeal to them.

The MacBook Pro is no doubt a hot machine. But it is also literally! The aluminum of the casing reaches immensely high temperatures. I guess the pretty design didn't mix well with the practical cooling needs. Working with it on your lap is not always fun, I bought myself an iRain stand and tend to use it on a desk otherwise.

Concluding

I'm extremely content with my setup and hope to use it for another couple of years before upgrading. If the above wasn't clear enough, I'll conclude with this concrete statement: After a year of intensive work on my Mac, I can recommend using a MacBook Pro for .Net development to anyone who wants 'best of both worlds'.
Powered by BlogEngine.NET 1.5.0.7