Hanalei, Hawaii Tuesday, February 09, 2010

ASP.NET MVC: MVC Storefront, Part 5 - Globalization

In this webcast I create a fictional curveball, wherein the "client" sent some test data that they finally were able to get their IT department to extract. In reviewing the test data, I find some very interesting changes to the DB that I need to make.

In this webcast I create a fictional curveball, wherein the "client" sent some test data that they finally were able to get their IT department to extract. In reviewing the test data, I find some very interesting changes to the DB that I need to make...

 

Previously, On The MVC Storefront

  • Part 1: Architectural Discussion and Overview. I cover the initial architecture here, in part 1, where I discuss the Repository Pattern with Ayende Rahien and Steve Harman. I also ask Phil Haack what's first: The Test Chicken or the Model Egg.
  • Part 2: The Repository Pattern. I walk through our Repository implementation and write out the first main set of unit tests. I also  structure up the initial service layer methods and interfaces.
  • Part 3: Pipes and Filters: I discuss the feedback from Parts 1 and 2, and then dive into the Pipes and Filters implementation on the IQueryable repository.
  • Part 4: Linq To Sql Spike. I create a "spike" - a bit of functionality to test a thought or inspiration - to make sure that Linq To Sql will work for my data access pattern.
  • The Code is downloadable from here


Introducing Our Client
I spose it's no big surprise that the client is ME :). Well that's not entirely true - I've tried to maintain the the client is really a hydra of sorts - you, some big-whigs at Microsoft, and me.

I'm targeting the original functionality in the ASP.NET 2.0 Commerce Starter Kit (now dashCommerce). Note that I'm not trying to recreate it - I'm just using the spec for the CSK as my initial guideline. This is completely malleable and I'm sure I'll change along the way.

That Shifty Client!
One major change I always wanted to make was to build in Globalization to the CSK. I never really got around to it, and so my "client", along with sending the test data, has asked that I make sure that I demo the globalized aspect of the site for the first iteration.

Normally I might push back, but the client has been nice enough to give me a few more days to build this in. You might think about scrambling at this point, but it's why [Deity] created ORMs - to abstract the DB away when you need to, and right now I need to :).

Picking Up The Pace
It's tough for me to gauge what people want to see more of, to provide this, and to offer it in a watchable format. I'd like to hear your thoughts on the pace here!

Download Part 5 Here.

Technorati Tags:


Jame - April 24, 2008 -

Just wanted to say that the in-depth coverage you're giving to using Linq in a multi-tier system is much appreciated. I'm almost more interested in this stuff than the actual MVC part! One thing I'm curious about is URLs. You said your fictional client wants their categories to be globalized, but does that include a category name that might be used in a URL? If not, maybe you can add that "curve ball" to one of your upcoming podcasts? I'm interested to see how you'd handle routing in that situation. Globalizing URLs is something I've had to do for a client in the past and it's a royal pain. Especially for languages that have umlauts and tildes and such since you have to make sure removing them doesn't fundamentally alter the meaning - like changing the Spanish "year" to "rectum" by removing a tilde...

Fredrik Normén - April 24, 2008 -

I'm so impressed about your presentation technique. So wonderful and wow!

About globalization, this is really interesting.. for example let's assume we have a Category "MyCategory". I ask my Repository for the Category. What I get is of course an instance of the Category. When I ask for the Description, I get my Description, probably in my language. Another person from another country asks for the same Category. What will happen here? Well he/she will probably get a new instance, but that's make two copy, a clone but the Description will now be in another language. But it's still the same category and should it be cloned? In a real word, we shouldn't. What we do is a translation of the description, right?

To avoid to have two copies of the same Category, we can either implement a Translator, which will translate the Description into the correct language, or we can make sure the Category entity hold every translation in memory, and the Description property could make sure the correct description is returned, maybe even use Lazy load, or just KISS (keep it simple stupid) and make two copies in memory. In Sweden where live, globalization is important, we solved this by just KISS. But I think this is still interesting.

One thing about your screencast, when you do the test against your SqlCategoryRepository, you do an integration test. When using TDD you will use your mock objects, because when you switch to use your SqlCategoryRepository in a test, you end up doing an integration test. Correct me if I'm wrong.

Evan - April 24, 2008 -

So if this were a real application, I'd have to take issue with the way you are doing globalization, but since this is for a demo, I'll leave it at that. You get a pass because of that. I will give a brief explaination for someone who might try and copy your design for production though.

In short, you missed the culture hierarchies which allow you to subclass British English under English (which English is presumably American English for your app). That allows you to only add British-specific translations where needed--instead of keeping full copies of both British and American English (which will be 95% the same).

That allows your app at runtime to request en-UK and get the specific pieces of content assigned to that culture, while defaulting the rest of the content to the culture above it in the tree, "en" (aka American English).

It can save a crapload in translation and administrative costs/overhead.

The only reason that I bring it up is that it tends to force you to do the culture lookup stuff inside the object model, instead of part of the sql query. Otherwise, you end up hitting the database a million times. It forces your design to shift from database centric to application centric (unless you just want to go write a crapload of t-sql functions to do recursive content lookups based on the culture hierarchy).

The difference is subtle, but in a nutshell, you have default content for a given language (en) then you localize that language per culture (en-UK) using only the delta beween itself and the parent (en).

English is probably a dumb example. It makes a lot more sense when you figure out that there are a zillion versions of spanish. Then you can pick a default spanish version (say, es-MX) and then localize for all the spanish variations by only using the deltas.

Rob Conery - April 24, 2008 -

@Jame:

>>> One thing I'm curious about is URLs. You said your fictional client wants their categories to be globalized, but does that include a category name that might be used in a URL?<<<

Awesome :). This is precisely why building this in the public forum is very important. One might be tempted to say URLs are malleable - but what about Chinese/Japanese! I'll add this to the next screencast - very good feedback.

@Fredrik:

Great point - and this is something that Damien and I talked about a lot yesterday, but in reference to Products. On one hand, a Product with different data is indeed a different object. However if I was to add that product to my basket, then switch to the German site and add it again - that wouldn't make sense! Unless the product we're talking about is a Magazine :).

Point is - you're right, sort of. I have a way to deal with this and I'll bring it up in the next webcast - great point of feedback.

RE the test on SQLCatalogRepository - yes this in an integration test as a Spike, so I'm not considering this as part of my test suite, just yet.

>>>So if this were a real application, I'd have to take issue with the way you are doing globalization, but since this is for a demo, I'll leave it at that<<<

I'm hoping people will regard this as real, so fire away :).

>>>In short, you missed the culture hierarchies which allow you to subclass British English under English <<<

Damien and I talked about this as well - and he brought up that Windows doesn't even recognize "proper English" :). I did indeed just focus on "en" rather than "en-GB" - that's too specific for what i think I need. But I might change my mind based on feedback. I understand where you're going - but the question at this point is "is it worth it to have a separate table for colour versus color?". I might say no to that - but then again I'm American :).

Damien Guard - April 24, 2008 -

Adding support for a locale such as en-gb to share en but deviate when necessary is still possible with this design.

What we could do at the lower level is pickup all the cultures that might satisfy the user (en-gb, en, en-US) in preference order then supply that list to the CategoryDetailCulture query retrieving the first one in order of preference.

I take your point though that the current design isn't infinitely flexible, what we discussed on Skype was adding a good starting point for localisation we could expand upon as the project develops without overwhelming it at this stage.

[)amien

Josh Stodola - April 25, 2008 -

Great video, Rob! Fabulous explanation. I am learning.

Evan - April 25, 2008 -

@Rob

haha..yeah..color probably isn't a good example

but if your website was selling "big pink rubbers" what would you think? Your British counterpart might pick up a rubber (eraser) to go with a pack of pencils.

Fredrik Norm&#233;n - April 25, 2008 -

I have struggle a lot with globalization, I wish everyone use English ;)

We decided to use the LCID instead of the culture code, well I think we made this based on how we done it during the Windows DNA time. Globalization in Europe is more an issue than in US. Our customers in England wanted it to be pure British English etc all country was strict to get the words in their language. We had some problems when I come to Polish, they use strange characters ;) We got some problems to handle Unicode. If I remember correctly we had to save our source code by selecting a specific encoding form the Save As dialogue to get it work. Don't know if that is needed know. Some other issue we needed to handle was that some countries read from right to left, and left to right.

Something we newbie's at that stage didn't thought so much about was the way to display the text. For example the way a sentence is build, is different for different countries. We solved is thanks to string formatting, so for example we save text in the following format:

My name is {0} and I'm {1} years old.

We then use String.Format, before that we just did it all wrong ;)

We hired a guy that worked for a company which translates Microsoft products to other languages, so he helped us a lot to get it right. It sounds easy to add globalization, but there are so many factors to think of to get it all done right. When we thought we were finished, we got a new task, believe it or not but a customer wanted support for jargon.

For example in his cooperation some office uses different words for Products. Some uses Items, some Products, some Unites etc. Well this sounds stupid but that is true, company don't use the same words for the same thing they use different words. This was important for him, because if the application displays the text Product, the users refer it to something else what another company does because they use the word Item instead of Product in the organization, so to display another word than they was using, could confuse them.

Some other problem we had was to handle different date format, for example Norway uses "." as a separator, the ISO standard uses "-", etc. If I remember correctly .Net and SQL was handle this badly, and we got several problems. This was a long time ago, so maybe we didn't do it right at that time.

Luke Duff - April 25, 2008 -

The problem I've had with that approach of separating the database model with the business model is that the business model leans towards being read-only. The simplified projection doesn't help you much if you need to do updates/inserts.

If your Category class simply has "Title" how do you re-use that class in an administrative interface where multiple titles (one for each culture) need to be entered for each Category?

It seems like I'm always starting off on projects like you're doing here but I end up always having to hammer the Business model back into the shape of the database over time.

Josh - April 25, 2008 -

It concerns me that you refer to [Deity] as you do. Surely IDeity would be more appropriate... ;)

Jamie - April 25, 2008 -

>>> It concerns me that you refer to [Deity] as you do. Surely IDeity would be more appropriate... ;) <<<

What you really need is an IDiety factory. Or better yet, a factory that creates IDiety factories... Or maybe an IDietyFactoryCreatorFactory... Aw crap, I think I fell down the rabbit hole again...

Michael Hanney - April 25, 2008 -

Thanks for the great screen-casts. Your delivery is excellent. I'm looking forward to seeing more on the Controllers and Views. I like the pace and I'm sure we'll be seeing Controllers and Views soon.

A real-world scenario I face today is the client's expectation to see dynamic web pages early in the project. Getting paid depends on it. Would you feel comfortable building out one or two controllers and views that use objects from an incomplete class model?

The ability to extend the model in consecutive development/release iterations is what I hope to get out of a testable MVC framework.

I'd hope the tool-set will enable us to embrace not only the curve-ball requirements, but also the 'release early and often' ideology. This far into a project I would be required to show at least one webpage (not including Home and About). I would perhaps not have created the classes for the product reviews feature until I had a working product page, for example. This way, when the launch day deadline goes whooshing past, at least the website is deployed, functional and billable. Product review would be factored in later and deployed when confidence is high that integration of the new feature has not broken existing features.

Perry - April 25, 2008 -

Another great webcast!

I wanted to know your thoughts about abstracting data objects. So clearly in this webcast you abstract the entity classes and map them to other classes ex: Category.

What happens when you get to your middle tier? Do you map/transform Commerce.MVC.Data.Category to Commerce.MVC.Services.Category? And then what happens at the UI tier?

Typically, I let the entities from the *.Data tier be returned to middle tier and it is there that I map/transform the object. This is one of the reasons I don't like LINQ to SQL too much and prefer NHibernate or LINQ to Entities.

What do others think about this?

Shawn Miller - April 26, 2008 -

So to Luke Duff's point above... we almost have another chicken and egg situation where you've manually entered/imported data into the database to test that the database layer can retrieve data and map it into the business layer.

So maybe I'm getting a step or two ahead, but what about the C, U, and D of the CRUD operations. Should we be testing that we can create data BEFORE we test that we can retrieve?

kYann - April 28, 2008 -

Hi,

There is one thing that you didn't think about. It's that a product can have many "offer". I mean, for one product you could buy the product new or used (like amazon does).

And for these case, you need to have another table where you stock the price, the type and the seller of the offer.

I know it's certainly not a "usual" case, but for me it's a real case (it's routine).

If you had this separation in your data model you'll certainly end up with some performance problem to retrieve a list of product with his different price. And this would be stupid to have this performance issue for a website that have one offer by product.

So here you should have a business model that handle that kind of separation and a default data model that doesn't.

This way if someone needed that separation he would just have to make the proper data model, make some change in the repository and the rest of the application won't change !

Rob Conery - April 28, 2008 -

Hi Kyann - you're right, selling used products isn't a consideration at this point. From what you're talking about, it sounds like an auction setup, and that's not what we're after with this site.

I don't think that a data model can introduce performance problems straight away - and generally if you start ranking perf at the top of the list when designing, you'll end up with some interesting structures :).

kYann - April 29, 2008 -

It's not an auction system. It's like the marketplace of amazon but not necessarly a marketplace.

Anyway, another consideration is to have product that can be parameterized when added to the cart. Like you have a tee-shirt and you wanna select the size and color ...

And this is something that is a usual case.

alberto - April 29, 2008 -

The ProductName should be part of the ProductCultureDetail too. ;)

Rob Conery - April 29, 2008 -

@alberto: A product name is pretty distinct - Nintendo Wii is an example. Companies don't generally change the name of a product to fit a culture - but if this is an issue we can do that easily (without changing the model even :)

Jimit Ndiaye - April 30, 2008 -

@Jame - So far the app isn't multi-tiered; just multi-layered. While we're on the subject, Rob, will there be a multi-tier story? It'd be interesting to see how you'd handle client-side change-tracking etc. in such a scenario.

I second Shawn Miller's comments - when are we gonna start doing the CUD in CRUD? I think it follows logically to verify that you can create and update data before checking that you can retrieve it. Deleting existing data might come in after verifying you can retrieve the data.

@Perry - Unlike Nhibernate, Linq2Entities has no POCO story (though now they've got some IPOCO implementations using PostSharp). Using mapping files (*.map) the need to map between model classes and data classes in Linq2SQL is voided since one can map directly to your model classes (POCO). Rob, is there any particular reason why you're not using that option here instead of having two sets of classes?

Rob Conery - April 30, 2008 -

Hi Jimit:

>>>So far the app isn't multi-tiered; just multi-layered<<<

Can you explain what you mean by this?

>>>when are we gonna start doing the CUD in CRUD? I think it follows logically to verify that you can create and update data before checking that you can retrieve it<<<

I'll do this when it's needed - which is coming up in the next few webcasts where I'll use the shopping cart.

>>>Rob, is there any particular reason why you're not using that option here instead of having two sets of classes<<<

Not sure which option you're referring to. I don't want to support an XML file when LINQ will do it nicely with less code, that's immediately readable.

Stephen - April 30, 2008 -

I think the argument with tier vs layer is that tiers are thought of as massively independent systems, like asp.net to sql, or asp.net to the browser..

Layers are more defined in terms of an application, layers inside a tier essentially.

I think the distinction can become somewhat blurred however..

Jimit Ndiaye - May 5, 2008 -

@Rob

About tiering - I was talking about something along the lines of what Stephen said. My question was on how you'd handle the a proper n-tier scenario with web services hosting the business logic interjected in between the web client and your database. Client-side change-tracking may become necessary in order to keep track of what exactly has been modified, added or deleted.

About the mapping file option, the LINQ datacontext's constructor takes a mapping source as parameter which can either be an AttributeMappingSource (the default or an XML mapping source which is the alternative I'm asking if you considered using. With the mapping file option you can create your domain model to be persistence-ignorant using POCOs. Might help ease the really nasty headache i see building up when you go the other way round and start pushing objects into your database. ;)

Globalisasi ASP.NET MVC - Agus Suhanto - April 27, 2009 - [...] Rob Conery mencoba membahas implementasi globalisasi dalam ASP.NET MVC. Posting Rob bisa dibaca di sini.Dalam posting ini saya akan mencoba mengetengahkan salah satu implementasi globalisasi di ASP.NET [...]
Gecko