Example of branch by abstraction for an ASP.NET MVC 3 application where the abstraction is different countries
I work on an ASP.NET MVC 3 application where branches in version control have been created for each country. This makes it difficult sync changes between the branches and the problem will increase as the number of countries increases. I have read about branching by abstraction but have never actually used it so I'm struggling to comprehend how this would be actually be implemented in a reasonably complex project.
Can anyone explain how branching by abstraction can be done for an ASP.NET MVC application where the abstraction is a country, and the application is a n-tier type affair with service and data layers?
A few of the things I can't get my head round are:
- Abstracting view stuff. E.g. The aspx/cshtml files, javascript, css etc.
- Abstracting controllers
- How do you manage the extra complexity of having all your for code every country in one branch? (Note, by one branch I mean the default branch, main开发者_运维技巧line, whatever you want to call it)
- How you you manage feature toggles for each country? E.g. Potentially you could have many many features for each country that were not ready for release and controller by a toggle)
- How do you select the appropriate country when deploying?
- How do you run unit tests for country specific functionality?
UPDATE
I'm not just talking about language changes. Much of the service layer logic for each country is the same, but in certain key areas it's different, likewise for the data layer. Also view content and layout can vary, javascript can vary and controller logic can vary.
Why do you use branches to handle localization? imho it's madness.
There are several ways to do localizations without having one code base per language.
For instance. By making your own view engine on top of an existing one you can load views from external assemblies. Or you can create one view per language.
You can also use string tables in your views to make them localized.
Abstracting view stuff. E.g. The aspx/cshtml files, javascript, css etc.
Javascripts: I have one javascript with all my logic which uses variables instead of strings to show messages. In this way I can load another javascript before the logic script (for instance myplugin.sv.js
before myplugin.js
to get the swedish language.
The most common way for aspx/cshtml files is to use string tables in them.
Localize css? Why?
Abstracting controllers
huh? Why? Use string tables here too.
How do you manage the extra complexity of having all your for code every country in one branch?
Don't use branches.
How you you manage feature toggles for each abstraction?
huh?
How do you select the appropriate country when deploying?
Use the same site for all languages. Either use the IP address, Accept-Language
http header or the user's language preference to select language. Language is specified by setting Thread.CurrentThread.CurrentCulture
and Thread.CurrentThread.CurrentUICulture
How do you run unit tests for country specific functionality?
Don't. The logic should be the same.
Update in response to your comment
I've written my fair amount of systems where content is dynamically loaded into the application/service. I've never used different code bases even if different customers use different features in multi-tenant systems (SaaS).
First of all, you need to accept two things:
a) All content is loaded at all time (not talking about translations, but features). b) Use proper/standard localization methods, but no need to translate all features to all languages.
What you do is simply control which content to load and use standard techniques to get translated features. The control is made by either checking the current language (Thread.CurrentThread.CurrentCulture.Name
) or any homebrewn control.
Ask new questions to get more details.
Update 2
I wouldn't rely on the IoC to provide language specific features, but use reflection and a FeatureProvider
class to do that for me.
For instance, let's say that you have a feature called ISalaryCalculator
which takes into account all local tax rules etc. then I would create something like this:
// 1053 is the LCID (read about LCID / LocaleId in MSDN)
[ForLanguage(1053)]
public SwedishSalaryCalculator : ISalaryCalculator
{
}
And have a FeatureProvider to load it:
public class FeatureProvider
{
List<Assembly> _featureAssemblies;
public T GetLocalFeature<T>()
{
var featureType = typeof(T);
foreach (var assembly in _featureAssemblies)
{
foreach (var type in assembly.GetTypes().Where(t => featureType.IsAssignableFrom(t))
{
var attribute = type.GetCustomAttributes(typeof(ForLanguageAttribute)).First();
if (attribute.LocaleId == Thread.CurrentThread.CurrentCulture.LCID)
return (T)Activator.CreateInstance(type);
}
}
return null;
}
}
And to get the feature (for the current language):
var calculator = _featureManager.GetLocalFeature<ISalaryCalculator>();
var salaryAfterTaxes = calculator.GetSalaryAfterTax(400000);
In a simple scenario (CMS/not much or shared business logic) you are dealing with just content in different languages and:
- Layout (left to right vs right to left) - you will have two CSS files
- Localized (translated) content
Layout CSS can be in separate files and be pulled in depending on the User Account's culture or site region setting.
Localization can reside in different assemblies - one for each culture or in separate string table files (as already mentioned).
In a more complex scenario, where you are developing an application for sensitive cultures and regions where:
- You have to be careful about the content you display - for example an image can be very offensive in one country but perfectly fine in another country.
- Different business logic (e.g. think financial application, etc)
Most of the content issues you can solve with CSS, but even if you need heavily customized views as has already been mentioned you can create a view engine that pulls a view file based on the current culture (e.g. Index.en-Us.cshtml, Index.ru.cshtml, etc)
For the different business rules you must have a good design and architecture utilizing Inversion of control (e.g. dependency injection) and patterns such as the Strategy, state, Template method, etc. Given that you have that in place you will be able to create an IoC container project/assembly per culture at run time and your UI will be unaware of the difference since it will be simply consuming some services defined by those interfaces.
Slightly more tricky if your ViewModels are significantly different between cultures, but if they are you probably want to maybe split View + Controller per culture in different assemblies and again use IoC to configure the routing at run time initialization.
Clearly deploying multi-cultural sites is more complex than non-such and no matter which way you go (site instance per culture or one site instance for all cultures) you will probably want to invest in some tooling to be able to script multi-culture deployment.
And last - regarding the unit tests - if you already have a good IoC based design testing per culture will be a matter of using and configuring the right dependency container prior to running a set of unit tests.
With such a setup the teams of developers should be working in relatively self-contained environment, since your project will be logically and physically be partitioned to support multiple teams working on separate parts of it at the same time. It also shouldn't matter whether on branches or trunk/head. Of course if everything is a big mess in just a few projects and people have to constantly merge in - you are in a very bad state and your future isn't bright.
Final words: It's not about branching - it's about design, architecture, partitioning (solutions, projects and assemblies) and last but not least about automation (build and test).
This is a little off topic, but I believe you're heading in a more difficult direction than you need to. Localization is built into ASP.NET and the MVC framework can take advantage of it also.
The short story is that you basically make a resx for each language you'd like to support. You then use Resources.Global.YourLocalizedStringPropertyName
in your views.
This blog post is the long story. Scott Hanselman goes over javascript localization and other finer points. It's definitely worth a read and will probably save you incredible amounts of work to use the built in functionality instead of building it yourself:
http://www.hanselman.com/blog/GlobalizationInternationalizationAndLocalizationInASPNETMVC3JavaScriptAndJQueryPart1.aspx
精彩评论