Este artigo foi traduzido por máquina.

.NET Framework

Camadas de acesso adaptável + injeção de dependência = produtividade

Ulrik Born

Um dos desafios mais difíceis quando Gerenciando e desenvolvendo o código fonte de uma solução corporativa complexa são para garantir a base de código permanece consistente, intuitivo e altamente testável sendo mantida e estendida por várias equipes dentro de um departamento de desenvolvimento de software de grande. O problema central é que os desenvolvedores geralmente têm um número de regras e orientações a seguir e um conjunto de bibliotecas de utilitário para usar, que tendem a causar suas soluções para se tornar maior e mais complexo.Isto é porque eles acabam aprimorando a implementação da lógica de negócios intuitiva, ideal para torná-lo a aderir às regras e ajuste fixo APIs. Isso geralmente significa mais trabalho para os desenvolvedores, mais bugs, menos padronização, menos reutilização e reduziu a produtividade e qualidade geral.

Eu sou um desenvolvedor sênior para um banco de investimento online líder e observado como esses desafios podem limitar a produtividade. Este artigo é um estudo de caso que se apresenta como nossa equipe de desenvolvimento analisou e superar os desafios pelo uso inovador de geração de código de tempo de execução e injeção de dependência (DI). Você pode não concordar com algumas das opções de design da nossa equipe, mas acredito que você vai concordar que eles representam uma maneira fresca e eficiente de abordar alguns desafios arquitetônicos comuns.

Minha empresa tem um departamento de desenvolvimento de software grande in-house (trabalhando em dois continentes) que constantemente mantém e amplia a nossa grande base de código do Microsoft .NET Framework. Nossa base de código é focada em inúmeros serviços essenciais do Windows que compõem o sistema de negociação de alta performance, baixa latência, hospedado em nossos data centers. Nós temos um número de equipes de plataforma que protegem o ambiente de base de código e tempo de execução, além de muitas equipes que continuamente (em paralelo) melhorar e estendem o sistema do projeto.

Trabalhei em equipes de plataforma há vários anos e experimentou o lado negativo de um inconsistente e excessivamente complexo codebase durante inúmeras opiniões e oferecer suporte a casos. Há dois anos, decidimos abordar estas questões, e encontrei os seguintes problemas:

  • Tivemos muitas soluções para os mesmos problemas fundamentais. Um bom exemplo é que a maioria dos nossos serviços Windows teve sua maneira única de combinar as diversas APIs em um serviço simples com suporte adequado para registro em log, rastreamento, acesso de banco de dados e assim por diante.
  • Nossas implementações de lógica de negócios ou eram simples —­mas não testável de unidade e muito ingênua, não aderindo às diretrizes — ou excessivamente complexa devido a um monte de código de encanamento. Um exemplo comum: simples código que trabalhou diretamente no .NET SQL Server API versus código complexo que gastou muito mais linhas no encanamento trivial para apoiar tentativas automáticas, cache e assim por diante do que na lógica de negócios reais.
  • Tivemos bibliotecas utilitário suporta a maioria dos nossos princípios arquiteturais e diretrizes de codificação, mas eles foram implementados em vários estilos diferentes e evoluiu de forma independente. Então, mesmo quando usá-los como ditada pelas orientações, cada solução de recurso acabou tendo uma pegada enorme em termos de assemblies referenciados e exposição para alterações de API. Isso tornava uma tarefa complexa em si mesmo para colocar um novo recurso na produção e também tornava difícil atualizar as bibliotecas do utilitário.
  • O conjunto global de diretrizes e regras para aplicar e utilitários para usar simplesmente foi tão grande que somente nosso mais experientes desenvolvedores tinham uma chance justa de entender tudo e a barreira de entrada para novos desenvolvedores era extremamente alta. Isto significava que um monte de código fora do padrão foi escrito, também para serem descartados mais tarde ou para atingir a produção com maior inconsistência.
  • Vários dos nossos principais serviços do Windows tinham central "pontos de registo", onde todas as equipes de projeto tinham que tocar o mesmo código — por exemplo, uma grande mudança de instrução despachando comandos ou empregos. Isto tornou não trivial para mesclar o código em nosso ramo principal.

Florescem, estes problemas não foram novas nem exclusivo para nós, e um número de padrões de design bem estabelecida descreve como resolver esses problemas:

  • O padrão de fachada esconde todos os detalhes de acessar um recurso complexo por trás de uma interface de camada de acesso simples. Isto facilita a implementações de lógica de negócio limpo e testável onde os recursos externos podem ser facilmente ridicularizados fora em testes.
  • DI — ou recipientes de inversão de controle (IoC) — permite que componentes para ser menos rígida e, portanto, mais fácil de estender, manter e combinar. Esta técnica também facilita a zombar de componentes selecionados e, assim, aumentar a capacidade de teste.
  • Biblioteca bem projetado utilitário APIs não force o código do consumidor para ser mexido; em vez disso, eles oferecem suporte a aplicação intuitiva.

Nós estava cientes desses padrões há anos e também tinha aplicado--los todos em várias formas em toda a nossa base de código. Mas alguns problemas fundamentais limitou significativamente o sucesso desses padrões. Primeiro, o padrão de fachada não elimina a necessidade para um monte de código de encanamento — ele apenas move para outra classe e geralmente significa mais trabalho para o desenvolvedor. Em segundo lugar, a menos que o contêiner DI descobre automaticamente seus componentes em tempo de execução (por exemplo, através de atributos), ainda exigirá um registo central e na realidade só apresentará uma camada adicional para a execução. E, finalmente, é caro e extremamente difícil de projetar e implementar as APIs que são intuitivos, flexível e útil ao mesmo tempo.

Por isso criamos adaptáveis acessar camadas

Após um número de sessões de brainstorming, viemos com uma única solução poderosa e flexível para todos esses problemas. A idéia básica é esconder todas as APIs atrás de interfaces de acesso atribuído-camada e construir um mecanismo de implementação que pode implementar essas interfaces em tempo de execução de uma forma que segue todas as normas e diretrizes. Chamamos esta técnica de camadas de acesso adaptável (AAL), porque cada solução define as interfaces de acesso-camada que necessita em uma maneira altamente flexível. Já combinou o motor AAL implementação com o código aberto, orientado para o atributo Autofac DI recipiente e alcançado um quadro flexível de serviço Windows que faz a limpas, intuitivas e testáveis implementações a opção mais fácil. Figura 1 ilustra como o tamanho, a pegada e a complexidade de uma solução são reduzidos drasticamente quando AAL é usada para desacoplar a implementação da lógica de núcleo de todas as bibliotecas e APIs circundante. As caixas azuis representam as pegadas de uma única solução, implementada com AAL (à esquerda) e sem (à direita).

Adaptive Access Layers (Depicted on the Left) Dramatically Reduce Solution Complexity and Footprint
Figura 1 acesso adaptável camadas (retratadas à esquerda) dramaticamente reduzem a pegada e a complexidade de solução

Uma característica chave da técnica AAL é que nos dá um lugar central comum para implementar nossas melhores práticas e orientações sem poluir o código da lógica de negócios. A esse respeito, o AAL é semelhante a programação orientada a aspecto (AOP), várias características de interceptação e técnicas de proxy. A principal diferença é que AAL esconde o subjacente APIs do código da lógica de negócio consumindo, Considerando que as outras técnicas ainda expõem-los e assim aumentam significativamente a pegada de solução.

Para ilustrar a ideia, falarei sobre uma camada de acesso simples entre alguma lógica de negócios e o log de eventos do Windows padrão. Considere um serviço de registro de ordem que registra as ordens recebidas em um banco de dados. Se o banco de dados chamada falhar, o serviço deve escrever um erro no log de eventos.

Na abordagem clássica, isto pode envolver uma chamada para o método EventLog. WriteEntry .NET e poderia parecer o código em Figura 2. Esta abordagem não é ideal, por duas razões. Primeiro, não é well-suited para testes de unidade, como o teste teria que inspecionar o log de eventos no computador executando os testes de unidade para validar que uma entrada com o texto correto foi escrita. E segundo, quatro linhas de canalização trivial código vai "poluir" uma parte do núcleo da lógica de negócios.

Figura 2 clássico evento Log acesso

public OrderConfirmation RegisterOrder(Order order)
{ 
  try 
  {  
    // Call database to register order and return confirmation 
  } 
  catch (Exception ex) 
  {   
    string msg = string.Format("Order {0} not registered due to error: {1}",     
      order.OrderId,     
      ex.Message);   
    _eventLog.WriteEntry(msg, 1000, EventLogEntryType.Error); 
  }
}

Ambos estes problemas são abordados através da introdução de uma interface entre a lógica de negócios e a classe EventLog subjacente de AAL. Tal uma camada é ilustrada no código a seguir:

[EventLogContract("OrderService")]
public interface IOrderServiceEventLog{
    [EventEntryContract(1000, EventLogEntryType.Error,
        "Order {0} not reg due to error: {1}"]
    void OrderRegistrationFailed(int orderId, string message);
}

A camada é definida pela interface atribuído IOrderServiceEventLog que é implementado através de uma classe dinâmica pelo mecanismo de implementação em tempo de execução. A interface em si possui o atributo [EventLogContract] para permitir que o mecanismo de implementação para reconhecê-lo como uma camada de acesso de log de eventos. O único parâmetro é o nome do log de eventos para o qual a alvo. Não há restrições sobre o nome de interface ou o número de métodos nele. Cada método deve retornar void (não há nenhum valor de retorno significativo ao escrever informações no log de eventos) e tem o atributo [EventEntryContract]. O atributo leva todas as entradas de metadados fixo (id, severidade e formatação) como parâmetros que isto já não precisa ser na lógica de negócios.

Usando a camada de acesso da interface, a lógica de negócios de Figura 2 torna-se muito menores e mais clara:

public OrderConfirmation RegisterOrder(Order order)
{
    try  
    {
        // Call database to register order and return confirmation
    }
    catch (Exception ex)  
    {
        _logLayer.OrderRegistrationFailed(order.Id, ex.Message);   
    }
}

O método RegisterOrder exemplo agora é simples, altamente legível e muito mais testáveis, porque a validação não requer inspeção do log de eventos mas sim apenas uma pequena classe de simulação implementando a interface da camada de acesso. Outra vantagem é que a interface IOrderServiceEventLog pode mapear a interação do log de eventos completo pelo serviço de toda a ordem e, portanto, fornecer um simples ainda uma visão completa do que as entradas de log de eventos do sistema grava.

Observação: Como um breve aparte, o recente semântica Logging Application Block [laje] sobre a nova classe .NET 4.5 EventSource abraça as mesmas idéias de mover o metadados do código em atributos e expor métodos de log personalizado, com rigidez de tipos em vez de alguns métodos de uso geral. Para usar a laje, os desenvolvedores devem implementar uma classe personalizada derivada da classe EventSource e usar essa classe em toda a base de código. Acredito que a nossa abordagem é tão poderosa como laje, mas mais fácil de usar, como ele só exige que os desenvolvedores definir a interface e não a implementação da classe. Uma característica chave da classe EventSource é que ele oferece suporte a log de evento estruturado através de um conjunto configurável de pias. Nossa implementação de camada de acesso atualmente não suporta log estruturado mas poderia facilmente ser estendida para fazê-lo porque tem acesso às informações estruturadas através de parâmetros do método de camada de acesso.)

Eu ainda não considerado o verdadeiro corpo do método RegisterOrder, ou seja a chamada para algum banco de dados do SQL Server armazenados procedimento para manter a ordem para processamento adicional. Se minha equipe esta implementado usando o .NET SqlClient API, seria pelo menos 10 linhas de código trivial para criar uma instância SqlConnection e SqlCommand instância, preencher o comando com parâmetros nas propriedades de ordem, executar o comando e leia de novo o conjunto de resultados. Se fôssemos cumprir exigências adicionais tais como tentativas automáticas no caso de bloqueios de banco de dados ou time-outs, podemos facilmente acabar com 15 a 20 linhas de código apenas para fazer uma ligação bastante simples. E tudo isso seria necessário apenas porque o destino da chamada passou a ser um procedimento armazenado, ao invés de um método de .NET no processo. De uma perspectiva de lógica de negócios, não há absolutamente nenhuma razão por que nossa implementação do núcleo deve ser tão confuso e complexo só porque o processamento cruza de um sistema para outro.

Com a introdução de uma camada de acesso adaptável de banco de dados semelhante à camada de acesso de log de eventos, podemos implementar o corpo como simples e testável:

public OrderConfirmation RegisterOrder(Order order)
{
  try
  {
    return _ordersDbLayer.RegisterOrder(order);
  }
  catch (Exception ex)
  {
    _logLayer.OrderRegistrationFailed(order.Id, ex.Message);
   }
}

Até agora, eu tenho ilustrado as idéias, flexibilidade e poder de AAL. Agora vou prosseguir com uma inspeção mais detalhada das camadas de acesso já desenvolvemos e achei útil. Vou começar com a camada de acesso de banco de dados acima mencionados.

Camadas de acesso do banco de dados acesso de banco de dados é uma parte central da maioria dos sistemas da empresa, incluindo a nossa. Sendo um facilitador chave de graves de comércio online de instrumentos financeiros, precisa conhecer alguns desempenho estrito e requisitos de segurança exigidos por nossos clientes e as autoridades financeiras e, portanto, são obrigados a salvaguardar nossos bancos de dados com cuidado. Geralmente fazemos isso apenas fazendo acesso de banco de dados através de procedimentos armazenados, como o que nos permite aplicar regras de segurança refinadas e rever todas as consultas de banco de dados de desempenho e servidor de carga antes de atingirem os nossos sistemas de produção.

Já avaliamos cuidadosamente se ferramentas de mapeamento objeto-relacional (ORM), tais como o Estrutura de Entidade poderia nos ajudar a alcançar o código mais simples e mais testável sem mover-se longe de procedimentos armazenados. Nossa conclusão foi que o Estrutura de Entidade é uma solução extremamente atraente, mas se baseia fortemente em ser capaz de compor e executar instruções SQL complexas em tempo de execução. Ele pode mapear procedimentos armazenados, mas quando limitado a apenas procedimentos armazenados de mapeamento, perde a maioria de seus benefícios. Por esse motivo, decidimos implementar nosso próprio quadro de acesso a banco de dados como uma camada de acesso adaptável de banco de dados.

Nossa implementação oferece suporte a chamadas de procedimento armazenado, seleciona em vistas e inserções em massa eficazes através da funcionalidade de cópia em massa SQL Server . Ele pode mapear dados de entrada diretamente de propriedades de classe de objeto de transferência de dados (DTO) para parâmetros de procedimento armazenado e da mesma forma pode mapear colunas do conjunto de resultados em Propriedades de classe. Isto facilita uma sintaxe clara e direta quando se faz o acesso de banco de dados em código .NET.

O código a seguir mostra uma camada simples que combina com o serviço de registo de pedidos de amostra:

[DatabaseContract("Orders")]
public interface IOrdersDatabase{
  [StoredProcedureContract("dbo.RegisterOrder",
    Returns=ReturnOption.SingleRow)]
   OrderConfirmation RegisterOrder(Order order);
 }

Esse código mapeia um único procedimento armazenado e transforma a única linha no resultado definido em uma instância de OrderConfirmation inicializada a partir as colunas do conjunto de resultado. Os parâmetros de procedimento armazenado mapeado situam-se nas propriedades de instância determinada ordem. Esse comportamento de mapeamento é definido na [StoredProcedure­contrato] atributo e, portanto, não mais é necessário a implementação da lógica de negócios, fazendo isso, clara e legível.

Já implementamos algumas funcionalidades bastante avançadas na camada de acesso a banco de dados porque concluímos que é uma maneira simples e eficiente de oferecer funcionalidade padrão para nossos desenvolvedores sem restringir a sua liberdade para implementar a sua lógica de negócios da forma mais natural e intuitiva.

Um dos recursos suportados é sustentação sem emenda para granel inserindo linhas através da funcionalidade de cópia em massa SQL. Nosso suporte permite que nossos desenvolvedores definir um método simples que utiliza uma coleção enumerável de uma classe DTO representando as linhas para inserir como entrada. A camada de acesso lida com todos os detalhes e desse modo alivia a lógica de negócios de 15 a 20 linhas de código complexo, centrada no banco de dados. Este suporte de cópia em massa é um exemplo perfeito de uma operação de conceitualmente simples — para inserir linhas em uma tabela de forma eficiente — que normalmente acaba sendo um pouco complexo para implementar simplesmente porque a classe .NET Framework SqlBulkCopy subjacente acontece trabalhar um IDataReader em vez de diretamente em nossa aula DTO.

A camada de acesso de banco de dados foi o primeiro a ser implementado, e tem sido um enorme sucesso desde o início. Nossa experiência é que escrevemos mais simples e menos linhas de código com ele e nossas soluções naturalmente se tornar altamente unidade testável. Com base nestes resultados positivos, percebemos rapidamente que podemos nos beneficiar introduzindo AAL entre nosso código da lógica de negócios e vários outros recursos externos.

Camadas de acesso de serviço nossa implementação do sistema de negociação é altamente orientada a serviços, e comunicação inter-serviços robusta é essencial para nós. Nosso protocolo padrão é o Windows Communication Foundation (WCF) e temos um monte de código focado em fazer chamadas de WCF.

A maioria dessas implementações segue o mesmo padrão geral. Primeiro, os endereços dos pontos de extremidade são resolvidos (normalmente corremos nossos serviços em configurações ativo-ativo ou ativo-passivo). A classe ChannelFactory .NET é usada para criar uma implementação de classe do canal na qual o método desejado é invocado. Se o método for bem-sucedido, o canal é fechado e descartado, mas se ele falhar, a exceção tem que ser inspecionado. Em alguns casos, faz sentido repetir o método sobre o mesmo ponto de extremidade, enquanto em outros cenários, é melhor fazer um failover automático e repetição em um dos pontos de extremidade disponível. Além de tudo isso, muitas vezes queremos colocar em quarentena um ponto de extremidade falhou por um curto período de tempo a fim de não sobrecarregá-lo com as tentativas de conexão e na ausência de chamadas de método.

Está longe de ser trivial para escrever uma aplicação correcta deste padrão, e pode facilmente levar 10 a 15 linhas de código. E mais uma vez, esta complexidade é introduzida apenas porque a lógica de negócios, que precisamos chamar acontece ser hospedado em outro serviço e não no processo. Já implementamos uma camada de acesso de serviço adaptável para eliminar essa complexidade e torná-lo mais simples e seguro para chamar um método remoto que seja chamar um método em processo.

Os princípios e o fluxo de trabalho são idênticas da camada de acesso a banco de dados. O desenvolvedor escreve uma interface atribuída que mapeia apenas os métodos que ela precisa para ligar, e nosso mecanismo de implementação cria um tipo de tempo de execução que implementa a interface com o melhor comportamento prática conforme especificado nos atributos.

O código a seguir mostra uma camada de acesso pequeno serviço que mapeia um único método:

[ServiceAccessLayer(typeof(IStatisticsSvc), 
  "net.tcp", "STATISTICS_SVC"]
public interface ISalesStatistics{
  [ServiceOperationContract("GetTopSellingItems")]
  Product[] GetTopProducts(int productCategory);
}

O atributo interface identifica a base atribuída de planície [ServiceContract] interface (para ser usado com a chamada interna para ChannelFactory), o protocolo a ser usado e a identificação do serviço de chamar. Este último é usado como uma chave em nosso localizador de serviço para resolver os endereços de ponto de extremidade real em tempo de chamada. A camada de acesso irá por padrão usar ligação padrão do WCF para o protocolo determinado, mas isto pode ser personalizado, definindo as propriedades adicionais no atributo [ServiceAccessLayer].

O único parâmetro para o ServiceOperationContract é o verbo de ação que identifica o método mapeado do subjacente contrato de serviço WCF. Outros parâmetros opcionais para o atributo especificam se os resultados de chamada de serviço devem ser armazenados em cache e seja sempre seguro para automaticamente failover a operação mesmo se a primeira chamada de ponto de extremidade do WCF falha após o código foi executado sobre o serviço de destino.

Outras camadas de acesso nós também construímos AAL semelhante para os arquivos de rastreamento, os contadores de desempenho e nosso ônibus de mensagem. Eles são todos baseados nos mesmos princípios ilustrados por exemplos anteriores — ou seja, para permitir que a lógica de negócios expressar o seu acesso a recursos no mais simples possível caminho movendo todos os metadados em atributos.

Integração de injeção de dependência

Com camadas de acesso, nossos desenvolvedores já não precisam implementar um monte de código de encanamento trivial, mas deve haver uma maneira de invocar o mecanismo de implementação AAL para obter uma instância do tipo runtime implementada, sempre que o recurso mapeado externo tem que ser chamado. O mecanismo de implementação pode ser chamado diretamente, mas isso iria contra os nossos princípios de manter a lógica de negócios limpos e testável.

Temos abordado este problema registrando nosso mecanismo de implementação como uma fonte de registro dinâmico com Autofac tal que ele obtém chamado sempre que Autofac não é possível resolver uma dependência com qualquer um dos registros estáticos. Neste caso, Autofac pedirá o mecanismo de implementação se pode resolver uma determinada combinação de tipo e a identificação. O motor irá inspecionar o tipo e fornecer uma instância do tipo se o type é uma interface de camada de acesso atribuído.

Neste lugar, nós estabelecemos um ambiente onde implementações da lógica de negócio simplesmente podem declarar sua camada de acesso a tipos de interface e tomá-los como parâmetros (por exemplo, em construtores de classe) e, em seguida, a confiança que o contêiner DI será capaz de resolver esses parâmetros invocando o mecanismo de implementação nos bastidores. Tais implementações, naturalmente, irão trabalhar em interfaces e ser fácil de testar, basta apenas algumas aulas de simulação para implementar essas interfaces.

Implementação

Todas nossas camadas de acesso são implementadas usando as mesmas técnicas. A idéia geral é implementar toda a funcionalidade no código c# simples em uma classe base abstrata e então use somente emitir para gerar uma classe fina que deriva da classe base e implementa a interface. O corpo emitido de cada método de interface simplesmente encaminha a execução de um método de execução geral na classe base.

A assinatura deste método geral é:

object Execute(Attribute, MethodInfo, object[], TAttributeData)

O primeiro parâmetro é o atributo de método de método da interface de camada de acesso do que o método Execute é chamado. Geralmente, ele contém todos os metadados (por exemplo, nome do procedimento armazenado, especificação de repetição e assim por diante) necessários para o método Execute fornecer o comportamento de tempo de execução correta.

O segundo parâmetro é a refletida instância MethodInfo para o método de interface. Ele contém informações completas sobre o método implementado — incluindo os tipos e nomes de parâmetros do método — e é usado pelo método Execute para interpretar o terceiro parâmetro. Ele mantém os valores de todos os parâmetros para a chamada de método atual interface. O método Execute normalmente encaminha esses valores para o recurso subjacente API, por exemplo, como parâmetros de procedimentos armazenados.

O quarto parâmetro é um tipo personalizado que mantém fixada de dados a ser usado em cada invocação do método, a fim de torná-lo mais eficiente possível. Os dados fixos são inicializados uma vez (por um método na classe base abstrata) quando o motor implementa a classe de tempo de execução. Camadas de acesso nosso banco de dados usam esse recurso para inspecionar procedimentos armazenados apenas uma vez e preparar um SqlCommand modelo pronto para ser usado quando o método é invocado.

O atributo e MethodInfo parâmetros passados para o método Execute também são refletidos apenas uma vez e reutilizados em cada invocação de método, para minimizar a sobrecarga por chamada.

O valor de retorno de Execute é usado como o valor de retorno para o método de interface implementada.

Essa estrutura é bastante simples e acabou por ser flexível e poderosa. Nós já é reutilizado em todas as nossas camadas de acesso através de uma classe base abstrata, comum do AccessLayerBase. Ele implementa toda a lógica necessária para inspecionar uma interface atribuído e conduzir o processo de emissão de uma nova classe de tempo de execução. Cada categoria de camada de acesso tem sua própria classe base abstrata especializado derivado de AccessLayerBase. Ele contém a implementação real de acessar o recurso externo, por exemplo, fazer um procedimento armazenado de acordo com todas as nossas melhores práticas. Figura 3 mostra a hierarquia de classes de implementação de uma interface de camada de acesso de banco de dados de amostra. A seção azul é o quadro AAL; a seção vermelha é a interface atribuída definida pela solução de recurso de lógica de negócios; e a seção verde é a classe de execução emitida pelo mecanismo de implementação de AAL.

An Access-Layer Implementation Outline
Figura 3 um resumo de aplicação de camada de acesso

Figura 3 também ilustra como já deixamos as classes base implementar um conjunto de interfaces públicas (derivando de IAccessLayer) para expor informações de chave comportamentais. Isto não deve ser usado por implementações de lógica de negócios, mas sim pela lógica de infra-estrutura — por exemplo acompanhar sempre que uma chamada de procedimento armazenado falha.

Essas interfaces de camada de acesso também são úteis em alguns casos especiais onde negócios ou requisitos técnicos exigem que o acesso ao recurso subjacente por trás da camada de acesso é feito de uma maneira não é totalmente suportada pelo AAL. Com essas interfaces, nossos desenvolvedores podem usar AAL mas interceptar e ajustar as operações subjacentes para atender requisitos especiais. Um bom exemplo disso é o evento IDatabaseAccessLayer.ExecutingCommand. Isto é gerado antes um SqlCommand é executado e permite-nos para personalizá-lo, alterando as coisas tais como parâmetros ou valores de tempo limite.

Emissão de relatórios e verificação

As interfaces AAL atribuídas de uma solução de lógica de negócios também nos permitem refletir os binários compilados em tempo de compilação e extrair um número de relatórios úteis. Nossa equipe incorporou isso em nossas compilações Team Foundation Server (TFS) tal que cada compilação saída agora inclui alguns informativos, pequenos arquivos XML.

Tempo de compilação de relatórios camada de acesso a banco de dados informa a lista completa de tudo armazenados procedimentos, exibições e granel insere sendo acessado. Usamos isto para simplificar comentários e verificar que todos os necessários objetos de banco de dados foram corretamente implantados e configurados antes de liberar a lógica de negócios.

Da mesma forma, nossa camada de acesso de log de eventos informa a lista completa de entradas de log de eventos que pode gerar um serviço. Nossos passos de pós-compilação pegar essa informação e transformação-lo em um pacote de gerenciamento para o Microsoft System Center Operations Manager vigilância de ambiente de produção. Isto é inteligente porque assegura que o Operations Manager está sempre actualizado com informação adequada sobre como melhor lidar com questões de produção.

Automatizada Microsoft Installer pacotes aplicamos as mesmas técnicas de reflexão para colheita valiosa contribuição para os pacotes do Microsoft Installer (MSI) geramos para os nossos serviços de Windows como a etapa final no nosso TFS constrói. Um ponto-chave para estes pacotes é instalar e configurar o log de eventos e contadores de desempenho para garantir que eles correspondam a lógica de negócios a ser implantada. A compilação extrai o log de evento nomes e definições de contador de desempenho de binários e gera automaticamente um pacote MSI que instala estes nomes e definições.

Verificação de tempo de execução um dos erros mais comuns relatados do nosso ambiente de produção costumava ser que um serviço havia tentado chamar um procedimento armazenado que não existe ou existiu com a assinatura errada o banco de dados de produção. Esses tipos de erros aconteceu porque perdemos a implantação de todos os objetos de banco de dados necessários quando implantamos um serviço do Windows para produção. A questão crítica aqui não era a implantação de falta em si, como isso pode ser corrigido facilmente, mas mais o fato de que o erro não aconteceu durante a implantação, mas normalmente mais adiante, quando o procedimento armazenado foi primeiro chamado durante o horário comercial. Usamos a lista baseada em reflexão de todos os objetos de banco de dados acessado para resolver este problema, permitindo que nossos serviços Windows validar a existência e a validade de todos os objetos durante a inicialização do serviço. O serviço simplesmente percorre a lista de objetos, em seguida, consulta o banco de dados para cada um verificar o que será capaz de acessar o objeto quando necessário. Desta forma, nos mudamos todos esses erros de horário comercial para o tempo de implantação, quando é muito mais seguro e mais fácil de corrigi-los.

Eu listei esses usos adicionais para ilustrar dos principais benefícios da AAL. Com quase completa informação sobre o comportamento de serviço facilmente disponível através de reflexão, abre uma nova dimensão de inteligente relatórios, construção, automação e monitoramento. Nossa equipe tem colhido alguns destes benefícios até agora, mas nós vemos um número de aplicações adicionais interessantes pela frente.

Produtividade e qualidade

AAL, concebido e implementado ao longo dos últimos dois anos, tem provado para ser um facilitador extremamente poderoso de maior produtividade do desenvolvedor e a solução de maior qualidade para nossa equipe de dev. Nós reduzimos nossos custos para a preparação de um novo serviço do Windows de semanas para horas e para estender os serviços existentes de dias para minutos. Isto tem melhorado nossa agilidade e assim fez mais barato para nós desenvolver nossas ofertas ao cliente.

Nossas camadas de acesso são adequadas ao implementar a grande maioria das nossas soluções de negócios. No entanto, temos alguns casos especiais onde não cabem — normalmente em cenários altamente configuráveis, como quando o nome do procedimento armazenado para chamar é ler a partir de uma tabela de configuração e não conhecido em tempo de compilação. Nossa equipe tem escolhido deliberadamente não suportam tais casos para evitar a complexidade da estrutura adicional que será introduzida. Em vez disso, permitimos que nossos desenvolvedores usar as APIs .NET simples nesses casos isolados.

A solução AAL em si não é grande e foi desenvolvida dentro de alguns période durante um período de dois anos. Assim, nosso investimento inicial não tem sido muito alto e já alcançou status break-even através de muitas horas de desenvolvimento e suporte a salvo.

Claro, o desafio de ter uma plataforma altamente versátil e amplamente utilizada é que pode se tornar um ponto único de falha. Nós já esta atenuada por ter cobertura de teste de unidade completa da solução AAL e lançando novas versões do mesmo de forma controlada pelo serviço. Você também poderia argumentar que a abordagem AAL nse introduz complexidade adicional no nosso sistema e as forças de nossos desenvolvedores para aprender uma nova camada de abstração. Mas acreditamos que isso é mais do que compensado pelo aumento global da produtividade e qualidade.

Outra preocupação que conhecemos é que devemos manter o foco na arquitetura e design global e não apenas fazer o Windows services para tudo simplesmente porque essa abordagem tornou-se tão barata e fácil. Às vezes uma solução de terceiros ou um hospedado no IIS Windows processo Activation Service (WAS) oferece uma solução global melhor, mesmo que ele adiciona a diversidade de nosso ambiente de produção.

Ulrik Born detém um mestrado em tecnologia da informação da Universidade Técnica da Dinamarca e tem trabalhado nos últimos 15 anos como desenvolvedor e arquiteto, na plataforma Windows. Ele é um desenvolvedor para Saxo Bank, um banco de investimento baseado na Internet.

Agradecemos aos seguintes especialistas técnicos pela revisão deste artigo: Jonas Gudjonsson (Saxo Bank), James McCaffrey (Microsoft), Grigori Melnick (Microsoft) e Fernando Simonazzi (Microsoft)
Arquiteto Jonas Gudjonsson, VP e empresa, é parte do CTO escritório responsável para a arquitetura geral do Saxo Bank, incluindo estratégia, princípios e diretrizes dos quatro domínios arquitetônicas

Dr. James McCaffrey trabalha para a Microsoft, no campus de Redmond, Wash.. Ele trabalhou em vários produtos da Microsoft, como o Internet Explorer e o MSN Busca. Ele é o autor de ".NET Test Automation Recipes" (Apress, 2006) e pode ser contatado em jammc@microsoft.com.

Dr. Grigori Melnik é diretor gerente de programas na Microsoft patterns & equipe de práticas. Hoje em dia ele dirige a Microsoft Enterprise Library, unidade, CQRS jornada e projetos de padrões NUI. Ele também promove o projeto por isso eficiência. Antes disso, ele era um engenheiro de software e pesquisador há tempo suficiente para recordar a alegria de programação em Fortran. Grigori fala ao redor do mundo sobre os temas de reutilização de código, métodos ágeis, computação de nuvem e testes de software. Ele também atua no Conselho Consultivo do IEEE Software. Blogs de He em https://blogs.msdn.com/agile

Fernando Simonazzi é um desenvolvedor de software e arquiteto com mais de 15 anos de experiência profissional. Ele tem sido um colaborador Microsoft patterns & projetos de práticas, incluindo várias versões do Enterprise Library, unidade, CQRS jornada e prisma. Fernando é um associado na Clarius Consulting.