sexta-feira, 29 de agosto de 2014

Escolher um sistema de RAID

Introdução

Quando se pensa em adquirir ou configurar um Servidor, uma das primeiras preocupações é escolher o sistema de RAID mais adequado.

Neste artigo não se pretende descrever exaustivamente as várias arquitecturas e níveis de RAID, mas sim apresentar uma comparação entre as opções mais utilizadas e tentar perceber qual a melhor opção para cada caso.

Vamos apenas referir que um sistema RAID é composto por um conjunto de 2 ou mais unidades de disco iguais, com dois objectivos base:

  • Maior rapidez de leitura e escrita (I/O) dos dados (striping)
  • Maior segurança dos dados e tolerância a falhas (mirror)

Para quem queira perceber melhor o que é o RAID e as várias opções disponíveis, a Wikipédia oferece uma explicação muito clara e concisa, aqui.

Conteúdo

Níveis de RAID e utilização de discos

Embora não seja o objectivo deste artigo a descrição detalhada das tecnologias de RAID, é útil apresentar uma tabela com a utilização de discos para cada nível.

Nível
Designação
Redundância
Utilização Discos p/ Dados
Nº Mínimo Discos
0
Striping
Não
100%
2 (n*2)
1
Mirror
Sim
50%
2 (n*2)
5
Paridade
Sim
66.6% – 97%
3 (n+1)
6
Paridade (recente)
Sim
50% – 88%
4 (n+2)
01 / 10
Striping + Mirror
Sim
50%
4 (n*2*2)
50
Paridade + Striping
Sim
66.6% – 97%
2 (n*2)
100
Striping + Mirror + Striping
Sim
50%
8 (n*2*2*2)

Nota: não são apresentados os níveis RAID 2, 3 e 4, porque caíram em desuso

Níveis de RAID base

RAID 0

Divide-se a gravação dos dados por dois ou mais discos, em simultâneo, aumentando assim a velocidade de I/O. Num sistema de dois discos, p.ex., o tempo que leva a gravar 1 byte num só disco, permite a gravação simultânea de 2 bytes (um em cada disco). Quantos mais discos tiver o sistema de RAID, mais rápida será a velocidade.

Toda a capacidade dos discos é aproveitada para os dados, todavia, se um dos discos falhar, todo o sistema falha, com perda total dos dados.

RAID 1

Cada disco, onde os dados são gravados, tem outro igual que funciona como espelho (mirror). Neste nível de RAID, os discos são sempre instalados aos pares.

Apesar de cada par ter discos iguais, somente a capacidade de um deles é aproveitada para guardar os dados. Todavia, se um dos discos falhar, o outro entra imediatamente em funcionamento, não havendo falhas no sistema.

RAID 5

Neste nível o foco também está na redundância e recuperação dos dados, mas em vez de existir um disco completo como réplica, a própria unidade garante a protecção dos dados. Isto permite uma poupança de recursos em relação ao RAID 1, podendo, inclusivamente, configurar-se RAID 5 num número ímpar de discos.

A redundância é obtida recorrendo a algoritmos de paridade. Os dados gravados no disco são divididos em pequenos blocos e cada um deles recebe um bit adicional – o bit de paridade, de acordo com a seguinte regra: se a quantidade de bits '1' do bloco for par, seu bit de paridade é '0'; se a quantidade de bits '1' for ímpar, o bit de paridade é '1'.

Estes bits de paridade são gravados numa área específica do próprio disco cujo tamanho é proporcional à quantidade de blocos de dados a controlar. Quando há uma falha (bit desconhecido), basta contar a quantidade de bits ‘1’ do bloco + bit paridade. Se for par, o bit em falta é ‘0’, se for ímpar, o bit em falta é ‘1’.

Tipicamente, numa configuração multidiscos, o espaço reservado para a paridade corresponde ao tamanho de um disco individual. Assim, numa configuração de 3 discos de 500 GB, ficamos com 1 TB para dados e 500 GB para paridade (33.33%).

RAID 0+1 vs RAID 1+0

A combinação dos níveis RAID 0 e RAID 1, permite obter o melhor de dois mundos:

  • O mirror dá-nos o mais alto nível de disponibilidade e os mais rápidos tempos de reconstrução, quando um disco falha;
  • O striping é a base da alta performance de I/O.

A combinação destes dois níveis, pode ser feita de duas formas:

  • RAID 01: primeiro fazemos o stripe e depois o mirror;
  • RAID 10: primeiro fazemos o mirror e depois o stripe.

À primeira vista parece que os processos são semelhantes e comutativos, mas não são. Vamos perceber porquê (exemplos para 3+3 discos):

RAID 0+1 ou RAID 01

Primeiro faz-se o stripe dos discos:

E depois faz-se o mirror do conjunto de discos em stripe:

 

RAID 1+0 ou RAID 10 

Primeiro faz-se o mirror de cada um dos discos:

E depois faz-se o stripe pelos pares de discos em mirror:

 

Diferenças

OK, a arquitectura interna pode ser um pouco diferente, mas o resultado final é o mesmo: usamos o mesmo número de discos, temos o mesmo disco lógico, fazemos striping por 3 discos (velocidade a triplicar) e temos redundância de dados. Porque é que nos temos que preocupar?

Temos que nos preocupar porque a grande diferença está na tolerância a falhas.

Relembremos que o RAID 1 tem uma tolerância de 1 disco por cada par em mirror.

Na configuração em RAID 01, temos apenas um mirror (de um stripe de 3 discos), pelo que o sistema apenas permite falha num dos discos físicos. Se um disco falhar, o sistema, automaticamente, começa a usar o outro bloco do mirror. Se falhar um segundo disco, desse bloco, o sistema pára.

No caso do RAID 10, temos 3 mirrors, pelo que o sistema permite a falha de um disco em cada par. Por exemplo, podem falhar os discos 1, b e 3 e o sistema continuar a funcionar. Claro que se falharem dois discos do mesmo par em mirror (p.ex. 1 e a) o sistema pára também.

A conclusão é que temos mais vantagem em usar o RAID 10 em vez do RAID 01 porque, com o mesmo número de discos, obtemos a mesma performance mas uma melhor tolerância a falhas.

RAID 10 vs RAID 50

RAID 50

De forma análoga ao RAID 10, numa configuração de RAID 50, primeiro faz-se RAID 5 e de seguida faz-se stripe aos discos já com redundância.

Diferenças

Performance
Uma configuração RAID 50 permite leituras mais rápidas que numa RAID 10, com o mesmo número de discos. No entanto, as operações de escrita em RAID 50 são mais lentas do que em RAID 10. Esta diferença torna-se muito significativa em operações de escrita intensiva (p.ex. bases de dados).

A correcção de performance de um sistema em RAID 50 é cara, exigindo ou a compra de muito mais discos RAID 5 do que aqueles inicialmente previstos, ou a conversão num sistema RAID 10, que tem um menor custo por operação de escrita.

Uma configuração RAID 50, apesar de ser tolerante à falha de uma disco, sofre uma degradação massiva de performance quando isto ocorre. Numa configuração RAID 10, se um disco falhar, o sistema continua a funcionar sem qualquer degradação de performance.

Redundância
Uma configuração RAID 10 oferece mais redundância de dados que uma RAID 50. 

Arquitectura
Uma configuração RAID 10 oferece maior flexibilidade de arquitectura do que uma RAID 50. Para além disso, a quantidade de espaço livre também é minimizada ao usar uma configuração RAID 10.

Controlador
Uma configuração RAID 5 exige uma placa controladora de alto desempenho. Se a implementação de RAID 5 estiver a ser feita pelo sistema operativo,  então o resultado será o de abrandar o desempenho do computador. No caso de uma configuração RAID 10, qualquer controlador de hardware pode ser utilizado.

Ocorrência falhas
Uma configuração RAID 50 tem uma probabilidade de falha, pelo menos, 3 vezes superior a uma configuração RAID 10 equivalente. E quanto maior for o número de discos RAID 5, mais aumenta esta probabilidade.

Recuperação de dados
A recuperação de dados numa configuração RAID 5 vai consumir cerca de metade da capacidade de IOPS (Operações de I/O por segundo) e ainda alguns ciclos do controlador ou do CPU. A recuperação de dados numa configuração RAID 10 é tão simples como copiar um disco.

Custos
A principal motivação para o aparecimento do RAID 5 foi o elevado custo por byte dos discos, para fazer RAID 1. Mas isso foi conseguido à custa do sacrifício da performance. Hoje em dia o custo dos discos já não é tão significativo e a exigência de um controlador mais sofisticado provoca que os sistemas em RAID 50 não ofereçam vantagens económicas significativas em relação ao RAID 10.

Conclusões

Pelo que foi exposto, na grande maioria dos cenários, uma configuração RAID 10 é a que apresenta mais vantagens.

O RAID 01 não oferece qualquer vantagem sobre o RAID 10 e o RAID 50 tem graves problemas de performance nas operações de escrita. Este último poderá ser equacionado quando há um pequeno número de discos (até 3) ou em sistemas em que o peso está nas operações de leitura.

Em qualquer dos casos, deve-se fazer sempre uma análise cuidadosa do cenário, requisitos e recursos disponíveis para implementar uma solução de RAID.

Referências

http://www.zdnet.com/blog/storage/raid-10-is-the-cadillac-of-raid/131

http://www.miracleas.com/BAARF/RAID5_versus_RAID10.txt

http://www.miracleas.com/BAARF/1.Millsap2000.01.03-RAID5.pdf

quarta-feira, 20 de agosto de 2014

Microsoft ASP.NET MVC– A história até agora

Introdução

Este artigo apresenta uma visão rápida da história do ASP.NET MVC e dos recursos e funcionalidades mais importantes, introduzidos nas principais versões.

Este será o primeiro de uma série de artigos dedicados ao ASP.NET MVC. Constitui apenas uma pequena introdução, recomendando-se a consulta da vasta documentação online sobre o tema, começando pela página oficial da Microsoft, aqui.

Conteúdo

ASP.NET MVC 1.0 (2009)

A primeira versão oficial foi lançada em 2009 e trouxe todas as características fundamentais da framework, que perduram até hoje:

  • Para começar, o próprio conceito de MVC, com o pipeline simplificado de processamento e separação do processamento do pedido (no Controller) e a renderização do output (na View);
  • O conceito de Routing (que foi incorporado, de seguida, na Framework .NET);
  • Helpers simples para renderizar tags HTML;
  • Helpers para criar facilmente links e forms AJAX;
  • Ligação automática de formulários submetidos (posted forms) a objetos .NET e uma espécie de validação do modelo.

Este foi um grande passo em direção a uma nova web, mas a primeira versão obrigava a demasiado desenvolvimento de infra-estrutura, por forma a ser produtivo em cenários empresariais e em grandes aplicações.

O ASP.NET MVC foi também o primeiro produto da Microsoft a ser realmente extensível. A maioria dos componentes do núcleo podiam ser estendidos ou mesmo totalmente substituídos por implementações próprias.

ASP.NET MVC 2 (2010)

No ano seguinte, foi lançada a segunda versão da framework ASP.NET MVC. O foco desta atualização foi aumentara a produtividade e  facilitar a manutenção nas grandes aplicações:

  • Validação do modelo com base em atributos, tanto do lado do servidor como do lado do cliente;
  • Introdução das Areas, para particionar as grandes aplicações;
  • Html Templated Helpers, para renderizar automaticamente formulários de edição e páginas de exibição, com base no modelo e nos atributos aplicados sobre o mesmo;
  • Controladores assíncronos;
  • HTML Helpers baseados em Expressões Lambda para remover a maioria das “strings mágicas” anteriormente necessárias nos Html Helpers.

ASP.NET MVC 3 (2011)



No início de 2011, foi lançada a versão 3 do ASP.NET MVC, juntamente com uma pilha de outras ferramentas muito interessantes, como o NuGet, IIS Express e SQL Server Express.

As novidades introduzidas nesta versão foram:

  • Só funciona em .NET 4;
  • Novos modelos de projecto, com suporte para HTML5 e CSS3;
  • Validação com unobtrusive javascript e melhor desempenho geral do javascript;
  • Validação remota e melhoria geral da validação do modelo;
  • Dependency Resolver incorporado;
  • Razor: o novo motor para Views;
  • Suporte para vários motores para Views, i.e., Web Forms, Razor ou open source;
  • Melhorias do controlador como a propriedade ViewBag e tipos de ActionResults;
  • Filtros globais;
  • Cache de output para página parcial.

ASP.NET MVC 4 (2012)




Em 2012, é lançada a versão 4 do ASP.NET MVC com algumas novidades significativas:

  • ASP.NET Web API, uma framework que simplifica a criação de serviços HTTP e serve uma grande variedade de clientes;
  • Renderização adaptável e outras melhorias “look-n-feel” nos modelos padrão de projeto;
  • Um modelo de projeto verdadeiramente vazio;
  • Introduzido novo modelo de projecto Mobile, baseado em jQuery Mobile;
  • Suporte para adicionar controladores de outras pastas de projecto;
  • Controlo de tarefas para controladores assíncronos;
  • Controlo de Bundling and Minification através de web.config;
  • Suporte para logins  OAuth e OpenID com a biblioteca DotNetOpenAuth;
  • Suporte para o Windows Azure SDK 1.6 e posteriores.

ASP.NET MVC 5 (2013)



No último trimestre de 2013 foi lançada a versão 5 do ASP.NET MVC, que passou a ser a versão standard de MVC do Visual Studio 2013. As principais novidades introduzidas foram:

  • O Bootstrap substitui o modelo padrão MVC;
  • ASP.NET Identity para autenticação e gestão de identificação;
  • Authentication Filters para autenticação customizada de utilizador ou por provedor de autenticação de terceiros;
  • É agora possível substituir filtros num método ou controlador;
  • O Attribute Routing está agora integrado no MVC 5.

ASP.NET MVC 5.1 (Janeiro 2014)

  • Melhorias no Attribute Routing;
  • Suporte para tipos Enum nas Views;
  • Suporte para Bootstrap nos modelos de editores;
  • Validação não intrusiva para os atributos de modelo MinLengthAttribute e MaxLengthAttribute;
  • Suporte para o contexto this no Unobtrusive Ajax.

ASP.NET MVC 5.2 (Julho 2014)

  • Melhorias no Attribute Routing.

Todas as versões do ASP.NET MVC

A título de curiosidade lista de todas as versões do ASP.NET MVC, lançadas pela Microsoft, é a seguinte:

Data
Versão
2007-12-10
ASP.NET MVC CTP
2009-03-13
ASP.NET MVC 1.0   (download)
2009-12-06
ASP.NET MVC 2 RC
2010-02-04
ASP.NET MVC 2 RC 2
2010-03-10
ASP.NET MVC 2   (download)
2010-10-06
ASP.NET MVC 3 Beta
2010-11-09
ASP.NET MVC 3 RC
2010-12-10
ASP.NET MVC 3 RC 2
2011-01-13
ASP.NET MVC 3   (download)
2011-09-20
ASP.NET MVC 4 Developer Preview
2012-02-15
ASP.NET MVC 4 Beta
2012-05-31
ASP.NET MVC 4 RC
2012-08-15
ASP.NET MVC 4   (download)
2013-05-30
ASP.NET MVC 4 4.0.30506.0
2013-06-26
ASP.NET MVC 5 Preview
2013-08-23
ASP.NET MVC 5 RC 1 [a]
2013-10-17
ASP.NET MVC 5 [a]
2014-01-17
ASP.NET MVC 5.1 [a]
2014-02-10
ASP.NET MVC 5.1.1 [a]
2014-04-04
ASP.NET MVC 5.1.2 [a]
2014-06-22
ASP.NET MVC 5.1.3 [a]
2014-07-01
ASP.NET MVC 5.2.0 [a]

[a] http://www.nuget.org/packages/Microsoft.AspNet.Mvc

O custo do Código Morto

As Funcionalidades Vestigiais (no sentido da evolução dos sistemas) são uma praga comum no software e há um tipo especial dessas funcionalidades que muitas vezes passa despercebido.

Geralmente as Funcionalidades Vestigiais são recursos utilizáveis que, simplesmente, não são usados. Mas, internamente, cada sistema tem partes de código que não são sequer expostas, que não suportam funcionalidades a que os utilizadores possam aceder. São muitas vezes o resultado de experiências, ou de uma funcionalidade vestigial que não foi totalmente eliminada. Vamos referir-nos a isto como código morto.

Um pouco de código morto não é um problema. No entanto, quando a quantidade cresce, pode rapidamente sair fora de controlo. E o código morto tende a aumentar, não a diminuir. Porquê? Porque a maioria dos programadores têm medo de remover código. Eles preferem entrar, fazer as suas alterações e sair. Tudo de forma a perturbar o sistema o mínimo possível.

Há muitas razões para isto:

  • Eles podem não entender o que é suposto o sistema fazer;
  • Pode não haver uma explicação calara sobre o que o sistema deve fazer;
  • Eles podem não saber o que é o que não é código morto;
  • Podem não existir testes suficientes para os ajudar a ter confiança de que não estão a eliminar algo importante;
  • Provavelmente não lhes é dado o tempo necessário para pesquisar e remover o código morto;
  • E se alguma vez o programador cometer um erro no processo de tentar limpar parte do lixo interno, será castigado.

A maioria dos sistemas de controlo de versão são tão avançados que qualquer mudança ao sistema está acessível instantaneamente, mesmo sem uma conexão à internet. No entanto, é cada vez mais comum ver os programadores a comentar ou deixar código morto para trás, em vez de removê-lo. Para muitos programadores, simplesmente não há incentivo para remover código morto e, em vez disso, todos os motivos para não o fazer. É por isso que tende a crescer.

Mas por que isto é importante? É importante porque é a desordem. Se já visitou a casa de alguém que acumula coisas, consegue imaginar o que será estudar uma base de código que está cheio de código morto. Imagine que precisa encontrar alguma coisa na casa de um colecionador. Vai demorar um bocado. A mesma coisa acontece quando tentamos alterar código que está a transbordar de código morto. O código morto fica no caminho do código útil.

O código morto nem sempre é óbvio. E tem uma grande tendência para afectar o modo como os programadores acham que o sistema realmente funciona. Muitas vezes, é necessário estudar o sistema detalhadamente para o conseguir descobrir. Sem tempo para fazer este estudo, o código morto concorre com a capacidade mental de entender como um sistema funciona.

Código morto gera código morto. Código morto frequentemente usa outro código. Este código, por sua vez, pode ser código morto, se é usado apenas por outro código morto. Pense na casa do colecionador. Naquele conjunto extra de prateleiras, para guardar todas as coisas que nunca vai usar. Esse conjunto extra de prateleiras não seria necessário se não fosse por causa da desordem.

Mas não é apenas a desordem. Porque é difícil reconhecer o código morto, os programadores vão fazer tudo para o proteger, assim como ao resto do código útil do sistema. E justamente por isso, eles não querem ser imprudentes.

A posse não vem sem um custo. O código morto custa dinheiro real para manter.

O código morto torna difícil alterar um sistema. O Refactoring, ou a reescrita, é muito mais difícil se tiver que considerar o impacto do código morto. Os componentes críticos do sistema são constrangidos por código morto. Estes constrangimentos causam dificuldades na adaptação a novas funcionalidades. O código morto tende a cristalizar e nunca mudar, ancorando assim outras partes do sistema, de forma análoga à fixação de um barco.

Por outro lado, as consequências secundárias podem ser desastrosos. O código morto pode ser usado acidentalmente quando alguém altera o sistema no futuro. Porque é código morto, é altamente improvável que ele se comporte como esperado. Provavelmente está incompleto e, definitivamente, não foi bem testado. Consequentemente, a sua utilização pode ** introduzir defeitos*.

Como eliminar o Código Morto 

Felizmente, é bastante fácil de limpar o código morto.

Ao crescer, adorei participar em acampamentos de fim de semana, como Escoteiro. Depois de um fim de semana divertido, fazíamos o nosso melhor para deixar o acampamento melhor do que o encontramos.

Depois de um árduo dia de trabalho, deixe o seu código um pouco melhor do que o encontrou.

Se toda a gente comprar essa filosofia, os esforços serão recompensados. Os programadores devem manter a vista fora do código morto. Os gestores devem apoiar, e não desanimar, os programadores. Antes que se perceba, o código morto desaparecerá. De seguida, podemos passar a problemas maiores.

O código morto explicado de forma simples

Tradução livre do artigo “The cost of dead code and the only technique you need to know to clean it up”, de Wes McClure.

segunda-feira, 11 de agosto de 2014

Mecanismo de Globalização de Base de Dados

Introdução

Hoje em dia a maioria das aplicações que utilizamos (principalmente as baseadas na Internet) permitem a apresentação do seu user interface (UI) em várias línguas. Esta é uma das funcionalidades de internacionalização e localização de aplicações.

Também é comum as aplicações terem conteúdos dinâmicos (como opções de menu, links, listas de opções, etc.), que podem estar guardados numa Base de Dados (BD). Se a aplicação for globalizada, então é necessário termos os conteúdos provenientes da BD em várias línguas também.

Neste artigo vamos analisar um mecanismo que permite guardar conteúdos em múltiplas línguas, numa BD e fazer a sua leitura de uma forma consistente e automática, na língua pretendida.

Scripts: todos os scripts com os exemplos deste artigo podem ser encontrados aqui.

Conteúdo

Compreender o problema

Num mundo globalizado como o actual, é essencial desenvolver aplicações globalizadas, i.e. que possuam mecanismos automáticos de internacionalização e localização.

Neste contexto, entende-se globalização como a capacidade das aplicações apresentarem mensagens em diferentes línguas e culturas (moeda, formato números, unidades medida, etc.), dependendo da localização do utilizador.

As diversas tecnologias e frameworks de programação de software têm apresentado, ao longo do tempo, mecanismos de globalização que permitem, com relativa facilidade, apresentar mensagens localizadas, conforme o objectivo mencionado no parágrafo anterior. Tipicamente, os textos das mensagens são colocados em ficheiros de resources separados por cada Língua/Cultura (p.ex. pt-PT=Português de Portugal ou pt-BR= Português do Brasil), que são chamados automaticamente, conforme a cultura activa.

Estes mecanismos funcionam para textos que estejam embebidos no próprio código fonte das aplicações. Todavia, é comum as aplicações terem textos e conteúdos dinâmicos  guardados em BD. Vejamos dois exemplos:

  • Uma dada aplicação tem menus dinâmicos, cujas opções dependem da licença de software e do perfil de cada utilizador. As várias opções dos menus (etiqueta a apresentar, perfil de acesso, recursos, etc.) estão guardadas na BD.
  • Numa dada aplicação é pedido ao utilizador que preencha os seus dados pessoais, existindo alguns campos em que tem que escolher um valor duma lista, como p.ex. estado civil, regime casamento, género, tipo de documento identificação, habilitações literárias, etc. Estas listas de valores estão guardadas em tabelas na BD.

No primeiro caso temos as etiquetas das opções dos menus que necessitam ser globalizadas, no segundo caso, temos dados de referência (as listas de valores) que necessitam ser globalizadas, também.

A questão que se coloca é como fazer a globalização destes dados, guardados em BD.

Visão geral

Com este mecanismo, a BD mantém a estrutura das tabelas originais, com os dados de negócio. Estas tabelas vão conter os dados no idioma e cultura neutro ou por defeito (para o presente artigo, vamos considerar que é pt-PT).

Vai ser criada uma tabela adicional, que regista as informações localizadas para os campos das outras tabelas, que necessitem tradução.

Como alternativa a ter uma única tabela com a informação localizada para TODAS as outras tabelas, esta pode ser dividida por várias tabelas, dependendo, por exemplo, da área funcional. Em qualquer dos casos, deve sempre ser muito claro qual a tabela de localização a utilizar.

As principais vantagens deste método são as seguintes:

  • Tem pouco impacto sobre o desenvolvimento da aplicação, porque toda a selecção da localização pode ser realizada na BD. A única questão é que todas as consultas à BD devem ser realizadas por meio de uma Stored Procedure (SP), com o código de cultura como parâmetro;
  • O método de localização permite utilizar valores predefinidos ou por defeito, que geralmente são os valores da tabela principal;
  • Apenas os dados localizados são registados, nenhum dado desnecessário é replicado;
  • É muito fácil mudar para uma abordagem não globalizada. Consultam-se simplesmente os dados das tabelas principais e não se utiliza a tabela localizada.

Objectos de Base de Dados utilizados pelo Mecanismo

Este método pode ser aplicado a qualquer tido de BD. Para efeitos da descrição e dos exemplos apresentados neste artigo, vamos utilizar o Microsoft SQL Server.

Objecto Tipo Descrição
CultureInfo Tabela
Esta tabela irá registar as informações de referência para os idiomas e culturas disponíveis no sistema.
Todos os dados localizados devem ter uma chave externa com o identificador de cultura obtido desta tabela.
ObjectGlobalization Tabela
Esta tabela irá registar a informação localizada para todas as tabelas na base de dados, utilizando um modelo EAV (Modelo Entidade Atributo Valor).
Cada linha tem uma chave que identifica a tabela, a coluna, a cultura e o Id da linha para os dados localizados.
GetGlobalizedValue Função
Esta é uma função auxiliar que permite ler o valor localizado a partir da tabela anterior, utilizando os campos-chave como parâmetros.
GetGlobalizedTextValue Função
Esta função é uma variação da função anterior que lê apenas valores de texto (nvarchar).
usp_SaveGlobalizedValue SP
Esta é uma SP auxiliar, para guardar os dados localizados na tabela ObjectGlobalization

Tabela CultureInfo

CultureName DisplayName ISO_639x_Value Culturecode
af-ZA Afrikaans - South Africa 0x0436 AFK
ar-AE Arabic - United Arab Emirates 0x3801 ARU
ar-BH Árabe - Bahrain 0x3C01 ARH
ar-DZ Árabe - Algeria 0x1401 ARG

O conteúdo da tabela CultureInfo é trivial.

Tabela ObjectLocalization

ObjectName ColumnName Culture RowId Value
dbo.PortType Name pt-PT 1 Porto Marítimo
dbo.PortType Name pt-PT 2 Porto Seco
GLOBAL_VALUE Description en-US 0 No
GLOBAL_VALUE Description en-US 1 Yes
GLOBAL_VALUE Description en-US All All
GLOBAL_VALUE Description pt-PT 0 Não
GLOBAL_VALUE Description pt-PT 1 Sim
GLOBAL_VALUE Description pt-PT All Todos

Coluna Tipo Descrição
ObjectName Varchar(128)
Normalmente esta coluna regista o Nome de tabela.
Para casos especiais, pode utilizar um nome de convenção, como GLOBAL_VALUE
ColumnName Varchar(128) Este é o nome da coluna que deseja localizar
Culture Varchar(10) Esta é a chave externa para a tabela CultureInfo
RowId Sql_Variant
Regista o valor na coluna de Id da linha na tabela original. Este valor de Id pode ser qualquer tipo de dados do SQL.
Value Sql_Variant Esta coluna regista o valor localizado
ValueText Varchar(max)
Esta coluna regista o valor de texto localizado. Ela pode ser utilizada como uma alternativa à coluna de valor
ValueXML XML
Esta coluna regista o valor localizado XML. Ela pode ser utilizada como uma alternativa à coluna de valor também

Sempre que for necessário criar um valor localizado, basta inserir uma nova linha na tabela ObjectGlobalization com o nome da tabela, o nome da coluna e o valor de Id para identificar a linha e o valor localizado. Existe uma SP para executar esta tarefa.

Function GetGlobalizedValue

Parâmetro Tipo Descrição
@ObjectName Varchar(128)
O nome do objecto a pesquisar na tabela ObjectGlobalization
@ColumnName Varchar(128) O nome da coluna a pesquisar na tabela ObjectGlobalization
@Culture Varchar(10) O nome da cultura a pesquisar na tabela ObjectGlobalization
@RowId Sql_Variant O Id da linha a pesquisar na tabela ObjectGlobalization
@Default Sql_Variant
Este é o valor por defeito, opcional, para a função, se a consulta sobre a tabela ObjectGlobalization não retornar dados. É tipicamente o valor da tabela original (cultura predefinida)

Exemplos de utilização:

SELECT GetGlobalizedValue('PortType', 'Name', 'fr-FR', PortTypeId, PortTypeName)
FROM PortType

Este exemplo lê o valor localizado, em Francês, da coluna Name da tabela PortType, utilizando o valor da coluna PortTypeName original como valor predefinido.

SET @label=GetGlobalizedValueText('GLOBAL_VALUE', 'Name', 'pt-PT', 'All', 'All')

Este exemplo define a variável @label para o valor localizado em Português da string 'All' com o valor em Inglês predefinido.

Neste caso, o nome de objecto, nome de coluna e identificador de linha são convenções, uma vez que não é um valor de uma tabela.

Function GetGlobalizedValueText

Faz o mesmo que a função anterior, com a única diferença de que o valor retornado é obtido a partir da coluna ValueText na tabela ObjectGlobalization.

Stored Procudure usp_SaveGlobalizedValue

Faz o mesmo que a função anterior, com a única diferença de que o valor retornado é obtido a partir da coluna ValueText na tabela ObjectGlobalization.

Parâmetro Tipo Descrição
@ObjectName Varchar(128) O nome do objecto a registar na tabela ObjectGlobalization
@ColumnName Varchar(128) O nome da coluna a registar na tabela ObjectGlobalization
@Culture Varchar(10) O nome da cultura a registar na tabela ObjectGlobalization
@RowId Sql_Variant O Id da linha a registar na tabela ObjectGlobalization
@Value Sql_Variant
O valor localizado para coluna especificada a registar na tabela ObjectGlobalization
@ValueText Varchar(max)
Um valor de texto localizado alternativo, para a coluna especificada, a registar na tabela ObjectGlobalization. Em casos práticos, este será o valor usado principalmente.
@ValueXml XML
Um valor XML localizado alternativo, para a coluna especificada, a registar na tabela ObjectGlobalization
@username Varchar(10)
Um parâmetro opcional para registar o nome do utilizador responsável pelo registo da informação localizada. Se este parâmetro não for especificado, o utilizador do sistema será registado.

Quando for necessário criar ou actualizar um registo na tabela ObjectGlobalization deve-se utilizar esta SP.

Exemplos de utilização:

EXEC    [dbo].[SaveGlobalizedValue]
        @ObjectName = 'PortType',
        @ColumnName = 'Name',
        @Culture     = 'fr-FR',
        @RowID     = 333,
        @Value     = NULL,
        @ValueText     = 'Port au Prince',
        @ValueXml     = NULL,
        @username     = 'kzinga'

Este exemplo regista (ou actualiza) a informação localizada para um dos valores em Francês que seriam lidos no exemplo anterior na função GetGlobalizedValue.

Considerações alternativas

Existem vários métodos de localização de BD, que podem ser tipificados nas secções que se seguem.

Embora cada um destes métodos apresente as suas vantagens, existem algumas desvantagens significativas em comparação com o método de Globalização de Objectos apresentado anteriormente.

Base de Dados Clone

Utilizando este método cria-se um clone completo da BD. O resultado é uma cópia exacta da estrutura original, incluindo todas as tabelas e campos. As BDs diferem apenas no nome e os conteúdos em diferentes idiomas.

Este método tem as desvantagens de exigir uma gestão complexa de conexões e uma duplicação desnecessária de dados (de todos os campos não localizados).

Field Database Localization

Utilizando este método, os valores dos campos localizados são criados na mesma linha da tabela. Os campos localizados são iguais ao campo original, com a excepção de conterem dados num idioma diferente e, é claro, os seus nomes diferem.

A principal desvantagem consiste no facto de que o código deve escolher dinamicamente a coluna com base no idioma selecionado e, claro, parece uma boa solução quando se começa com 2 idiomas, mas se o sistema cresce e precisar de um 3 º idioma? E um 4º? …

Linha Clone

Cada linha da tabela original é copiada para cada idioma. As linhas copiadas são iguais ao original, com a excepção de o valor do campo de idioma ser diferente. O resultado pode ter esta visualização:

As principais desvantagens são que, embora tenha pequeno impacto sobre o desenvolvimento da aplicação (o idioma é usado como chave para selecionar a linha e não na coluna) o conteúdo da tabela vai crescer e vai aumentar a complexidade de administrá-la.

Tabela Clone

Este método adiciona tabelas localizadas para as tabelas que se deseja localizar. As tabelas localizadas contêm somente a chave primária e os campos que são localizados. O resultado é escrito nas novas tabelas com a mesma estrutura. As tabelas de base de dados só têm nomes diferentes, como Descricao e Descricao_ja:

Outra opção para este método é ter a tabela original com todos os campos e a tabela localizada apenas com códigos de idioma e os campos localizados. É uma espécie do exemplo de Linha Clone, mas com as linhas localizadas numa tabela separada.

Tal como o método de localização de campo, a principal desvantagem é que o código deve escolher dinamicamente o nome de tabela com base no idioma selecionado. E se o número de idiomas utilizados pelo sistema começar a crescer, acabará com um número excessivo de tabelas.

Referências

http://www.sisulizer.com/localization/software/server-desktop-database.shtml

http://www.sqlservercentral.com/Forums/Topic205432-230-1.aspx