Barramento de Serviço do Windows Azure AppFabric

Crie um cliente contínuo usando Bibliotecas de classes portáteis

David Kean

Baixar o código de exemplo

Acho que tenho sorte por viver na época dos dispositivos continuamente conectados. Adoro poder responder a emails usando meu telefone enquanto volto de ônibus para casa. É maravilhoso poder usar o Skype para falar com minha família do outro lado do mundo e me conectar com jogadores de todo o país com as mesmas afinidades em meu Xbox. No entanto, neste mundo de conectividade permanente com a Internet, há, como colocado por Joshua Topolsky“, um link ausente em nossa experiência de computação” (engt.co/9GVeKl). 

Esse link ausente é referente à falta do que Topolsky chama de um cliente contínuo, isto é, uma solução para o fluxo de trabalho interrompido que ocorre atualmente quando você muda de um dispositivo para outro. À medida que alterno entre meu PC, tablet e telefone em um dia típico, minha sessão de navegação atual, documentos, janelas e estados dos aplicativos deveriam fluir naturalmente para todos eles. Dessa maneira, eu gastaria menos tempo alternando contextos e mais tempo em trabalho real e entretenimento.

Neste artigo, mostrarei como criar um aplicativo de cliente simples e contínuo que se estende por vários dispositivos e plataformas. Usarei as novas PCLs (Bibliotecas de classes portáteis) para facilitar o desenvolvimento de um aplicativo entre as plataformas e a nuvem, particularmente o Barramento de Serviço do Windows Azure AppFabric, para manipular a comunicação entre os dispositivos.

A caminho de casa …

É final da tarde e estou no trabalho tentando corrigir aquele último bug rapidamente para evitar o tráfego do horário de pico. A chamada inevitável chega: “Querido, quando estiver a caminho de casa, você pode comprar leite, pão e grão-de-bico?" Desligo, vou até o supermercado e percebo que esqueci o que comprar. Acabo indo para casa com itens que já temos na despensa. É frustrante, e a solução atual tende a envolver muitas chamadas telefônicas de um lado para outro: “Você disse ervilhas congeladas ou grão-de-bico?” “Grão-de-bico. E já que está ai, você pode comprar papel higiênico?”

Para ajudar a minimizar nossas tensões matrimoniais em torno desse problema específico (os outros precisarão esperar por outro dia), eu escrevo um aplicativo simples chamado "On Your Way Home", que é executado em nossos dispositivos baseados no Windows Phone e nos tablets Windows 8 beta e que permite que minha esposa e eu controlemos facilmente nossa lista de compras. Ele nos manterá informados, em tempo real, sobre quaisquer alterações na lista de compras, de forma que saberemos exatamente o que precisamos comprar a qualquer hora.

Como um smartphone, que executa um Windows Phone, e um tablet baseado em Windows 8 são dispositivos diferentes, com diferentes formas do Microsoft .NET Framework e do Windows, usarei as PCLs para abstrair as diferenças de plataforma e poder compartilhar o máximo possível da lógica dos aplicativos, incluindo toda a comunicação com o Barramento de Serviço do Windows Azure AppFabric. Também usarei o padrão Model-View-ViewModel (MVVM) (bit.ly/GW7l) para facilitar o uso dos mesmos Models e ViewModels de nossas Views específicas ao dispositivo.

Bibliotecas de classes portáteis

No passado, o desenvolvimento entre plataformas no .NET Framework não era fácil. Embora o .NET Framework tivesse grandes sonhos como um tempo de execução entre plataformas, a Microsoft ainda não cumpriu a promessa. Se você já tentou entregar um aplicativo ou estrutura baseada no .NET Framework que se estendesse por vários dispositivos, terá percebido que algumas coisas se colocaram no caminho.

No lado do tempo de execução - A fatoração do assemby, o controle de versão e os nomes dos assemblies são diferentes entre as plataformas .NET. Por exemplo, o System.Net.dll no .NET Framework, que contém APIs de rede ponto a ponto, significa alguma coisa completamente diferente no Silverlight, onde contém a pilha de rede principal. Para localizar essas APIs no .NET Framework, você precisará fazer referência ao System.dll. As versões do assembly também não são as mesmas. O Silverlight adota o 2.0.5.0 para as versões 2.0 a 4, enquanto as 2.0.0.0 e 4.0.0.0 foram adotadas para as versões 2.0 a 4 do .NET Framework. Essas diferenças, no passado, impediam que um assembly compilado para uma plataforma executasse em outra.

No lado do Visual Studio - desde o início você precisa decidir a plataforma a ser usada, o .NET Framework, o Silverlight ou o Windows Phone. Depois de tomar a decisão, é extremamente difícil mudar ou dar suporte a uma nova plataforma. Por exemplo, se você estiver usando o .NET Framework, o uso do .NET Framework e do Silverlight significará criar um novo projeto e copiar ou vincular os arquivos existentes a esse projeto. Se tiver sorte, você terá fatorado seu aplicativo de tal maneira que as partes específicas à plataforma sejam facilmente substituídas. Caso contrário (e talvez o mais provável), você precisará usar #if PLATFORM para cada erro de compilação até conseguir uma compilação limpa.

É aqui que as PCLs podem ajudar. As PCLs, disponíveis como um complemento gratuito para o Visual Studio 2010 (bit.ly/ekNnsN) e internas no Visual Studio 11 beta, fornecem uma maneira fácil de usar várias plataformas em um único projeto. É possível criar uma nova PCL, escolher as estruturas que deseja usar (consulte a Figura 1) e começar a escrever o código. Nos bastidores, as ferramentas da PCL podem tratar as diferenças na API e filtrar o IntelliSense de forma que você veja apenas as classes e os números que estão disponíveis e funcionam em todas as estruturas selecionadas. O assembly resultante pode então ser referenciado e executado, sem nenhum alteração, em todas as estruturas indicadas.

Portable Class Library Target Frameworks
Figura 1 Estruturas de destino da Biblioteca de classes portáteis

Layout da solução

Uma maneira típica de organizar um aplicativo entre plataformas usando uma PCL é ter um ou mais projetos portáteis contendo os componentes compartilhados, e ter projetos específicos para cada plataforma que referencia esses projetos. Para este aplicativo, precisarei de duas soluções do Visual Studio, uma criada no Visual Studio 2010 (OnYourWayHome.VS2010) contendo meu aplicativo do Windows Phone e uma criada no Visual Studio 11 (OnYourWayHome.VS11) contendo meu aplicativo no estilo Windows Metro. Preciso de várias soluções porque, na hora de escrever, o Windows Phone SDK 7.1 funciona apenas sobre o Visual Studio 2010, enquanto que as novas ferramentas do Windows 8 estão disponíveis apenas como parte do Visual Studio 11. Não existe atualmente uma única versão que dê suporte aos dois. Mas não se desespere, um novo recurso disponível no Visual Studio 11 me ajuda aqui. Posso abrir a maioria dos projetos criados na versão anterior sem precisar convertê-los no novo formato. Isso permite que eu tenha um único projeto de PCL e faça referência a ele nas duas soluções.

As Figuras 2 e 3 mostram o layout do projeto de meu aplicativo. O OnYour­WayHome.Core, um projeto de PCL, contém os modelos, os modelos de exibição, os serviços comuns e as abstrações da plataforma. O OnYour­WayHome.ServiceBus, também um projeto de PCL, contém as versões portáteis das APIs que falarão com o Windows Azure. Os dois projetos são compartilhados entre a solução do Visual Studio 2010 e o Visual Studio 11. O OnYourWayHome.Phone e o OnYourWayHome.Metro são projetos específicos à plataforma para uso no Windows Phone 7.5 e no .NET para aplicativos em estilo Metro, respectivamente. Esses projetos contêm as exibições específicas ao dispositivo (como as páginas do aplicativo) e implementações das abstrações encontradas em OnYourWayHome.Core e em OnYourWayHome.ServiceBus.

Windows Phone Project Layout in Visual Studio 2010
Figura 2 Layout do projeto do Windows no Visual Studio 2010

Windows Metro-Style App Project Layout in Visual Studio 11
Figura 3 Layout do projeto do aplicativo estilo Metro no Visual Studio 11

Convertendo bibliotecas existentes em PCLs

Para a comunicação com o Windows Azure, baixei o exemplo de REST baseado no Silverlight no servicebus.codeplex.com e o converti em um projeto de PCL. Algumas bibliotecas são mais fáceis de converter do que outras, mas você inevitavelmente se deparará com situações onde um determinado tipo ou método não está disponível. Estes são alguns dos motivos típicos pelos quais uma determinada API talvez não tenha suporte nas PCLs:

A API não é implementada por todas as plataformas - A E/S de arquivos tradicionais do .NET Framework, como o System.IO.File e o System.IO.Directory, estão nessa categoria. O Silverlight e o Windows Phone usam as APIs System.IO.IsolatedStorage (embora sejam diferentes da versão do .NET Framework), enquanto os aplicativos estilo Windows 8 Metro usam Windows.Storage.

A API não é compatível em todas as plataformas - Algumas APIs têm a mesma aparência, mas é difícil ou impossível escrever código para elas de uma maneira portátil e consistente. ThreadStaticAttribute, que permite que campos estáticos tenham um valor exclusivo para cada thread, é um exemplo. Embora ele esteja presente nas plataformas Windows Phone e Xbox, nenhum de seus tempos de execução dão suporte a ele.

A API é considerada obsoleta ou herdada - Essas APIs contêm comportamento que provavelmente não estará presente em plataformas futuras ou foram substituídas por tecnologias mais novas. BackgroundWorker é um exemplo disso. Ela foi substituída por Task e os novos recursos de programa assíncrono no Visual Studio 11 beta.

Nosso tempo está esgotado - A maioria das APIs foram escritas sem considerar portabilidade. Gastamos uma boa quantidade de tempo analisando cada API para garantir que ela possa ser programada de uma maneira portátil. Isso pode envolver a execução de ajustes ou adições na API para torná-la portátil. Devido ao tempo e ao esforço envolvidos, na primeira versão das PCLs que disponibilizamos no Visual Studio Gallery, priorizamos as APIs de alto valor, altamente usadas. System.Xml.Linq.dll e System.ComponentModel.DataAnnotations.dll são exemplos de APIs que não estavam disponíveis nessa primeira versão, mas que agora estão disponíveis na versão beta do Visual Studio 11.

Há duas maneiras diferentes de tratar uma API que se encaixa em um desses cenários. Alguma vezes há uma simples substituição. Por exemplo, os métodos Close (Stream.Close, TextWriter.Close e assim por diante) foram preteridos na PCL e substituídos por Dispose. Nesses casos, trata-se apenas de substituir uma chamada para o método anterior por uma chamada para o mais recente. Mas algumas vezes isso é um pouco mais difícil e exige mais trabalho. Uma situação que encontrei ao converter as APIs de Barramento de Serviço envolvia o provedor de código hash HMAC SHA256. Ele não está disponível em uma PCL devido às diferenças de criptografia entre os aplicativos estilo Metro e do Windows Phone. Os aplicativos do Windows Phone usam as APIs baseadas em .NET para criptografar, descriptografar e fazer hash de dados, enquanto os aplicativos estilo Metro usam as novas APIs nativas do WinRT (Tempo de execução do Windows Runtime).

O código específico que não pôde ser criado após a conversão era o seguinte:

using (HMACSHA256 sha256 = new HMACSHA256(issuerSecretBytes))
{
  byte[] signatureBytes = sha256.ComputeHash(Encoding.UTF8.GetBytes(token));
  signature = Convert.ToBase64String(signatureBytes);
}

Para ajudar a ligar as lacunas entre as APIs de criptografia do Phone e as APIs de criptografia do WinRT, inventei uma abstração de plataforma representando o requisitos do Barramento de Serviço. Neste caso, o Barramento de Serviço precisava de uma maneira de calcular um hash HMAC SHA256:

public abstract class ServiceBusAdapter
{
  public static ServiceBusAdapter Current
  {
    get;
    set;
  }
  public abstract byte[] ComputeHmacSha256(byte[] secretKey, byte[] data);
}

Adicionei o ServiceBusAdapter ao projeto portátil e uma propriedade estática para definir a abstração atual, que se tornará importante mais tarde. Em seguida, criei as implementações HMAC SHA256 específicas ao Windows Phone e ao Windows 8 dessa abstração e coloquei-as em seus respectivos projetos, conforme mostrado na Figura 4.

Figura 4 Implementações HMAC SHA256 para Windows Phone e Windows 8

// Windows Phone implementation
public class PhoneServiceBusAdapter : ServiceBusAdapter
{
  public override byte[] ComputeHmacSha256(byte[] secretKey, byte[] data)
  {
    using (var cryptoProvider = new HMACSHA256(secretKey))
    {
      return cryptoProvider.ComputeHash(data);
    }
  }
}
// Windows 8 implementation
public class MetroServiceBusAdapter : ServiceBusAdapter
{
  private const string HmacSha256AlgorithmName = "HMAC_SHA256";
  public override byte[] ComputeHmacSha256(byte[] secretKey, byte[] data)
  {
    var provider = MacAlgorithmProvider.OpenAlgorithm(HmacSha256AlgorithmName);
    var key = provider.CreateKey(_secretKey.AsBuffer());
    var hashed = CryptographicEngine.Sign(key, buffer.AsBuffer());
    return hashed.ToArray();
  }
}

Na inicialização do projeto do Windows Phone, inicializei o Barramento de Serviço definindo o adaptador específico ao Phone como o adaptador atual:

ServiceBusAdapter.Current = new PhoneServiceBusAdapter();

Fiz a mesma coisa para o projeto do Windows 8:

ServiceBusAdapter.Current = new MetroServiceBusAdapter();

Com tudo definido, alterei o código não compilado original para chamar por meio do adaptador:

var adapter = ServiceBusAdapter.Current;
byte[] signatureBytes = adapter.ComputeHmacSha256(issuerSecretBytes, Encoding.UTF8.GetBytes(token));

Assim, embora haja duas maneiras diferentes de calcular o hash dependendo da plataforma, o projeto portátil fala com as duas usando uma única interface. Isso pode exigir um pouco de trabalho no início, mas posso reutilizar a infraestrutura facilmente ao deparar-me com mais APIs que precisam de ponte entre as plataformas.

Como uma anotação rápida, usei uma propriedade estática para acessar e registrar o adaptador, o que facilita mover APIs existentes usando o adaptador. Se estiver usando uma estrutura de injeção de dependência, como a Managed Extensibility Framework (MEF), a Unity ou a Autofac, você descobrirá que é natural registrar o adaptador específico à plataforma no contêiner e fazer com que o contêiner “injete” o adaptador nos componentes portáteis que precisam dele.

Layout do aplicativo

Meu aplicativo de lista de compras, On Your Way Home, tem duas exibições simples: A ShoppingListView, que exibe os itens atuais na lista de compras, e a AddGroceryItemView, que permite que um usuário adicione mais itens à lista. As Figuras 5 e 6 mostram as versões do Windows Phone dessas exibições.

ShoppingListView
Figura 5 ShoppingListView

AddGroceryItemView
Figura 6 AddGroceryItemView

A ShoppingListView mostra todos os itens que ainda precisam ser comprados, com a ideia de que à medida que anda pelo supermercado, você marca cada item adicionado ao carrinho. Depois de comprar os itens, um clique em check-out faz com que os itens marcados sejam eliminados da lista indicando que não precisam mais ser comprados. Os dispositivos que compartilham a mesma lista de compras instantaneamente (bem, tão instantaneamente como a rede por trás deles permite) veem as alterações feitas por outra pessoa.

As Views, que residem nos projetos específicos à plataforma, consistem principalmente em XAML e têm muito pouco código por trás, o que limita a quantidade de código que você precisa duplicar entre as duas plataformas. Usando a vinculação de dados XAML, as Views se associam a ViewModels portáteis que fornecem os comandos e os dados que executam as Views. Como não existe uma estrutura comum de interface do usuário que seja fornecida em todas as plataformas, os projetos de PCL não podem fazer referência a APIs específicas à interface do usuário. No entanto, ao usar estruturas que dão suporte a elas, os projetos de PCL podem tirar proveito das APIs que são tipicamente usadas por ViewModels. Isso inclui os principais tipos que fazem o trabalho de vinculação de dados XAML, como INotifyPropertyChanged, ICommand e INotifyCollectionChanged. Além disso, a estrutura XAML do WinRT também não dá suporte a eles, o System.ComponentModel.DataAnnotations e o INotifyDataErrorInfo foram adicionados para fornecer integridade e isso permite estruturas personalizadas de validação de XAML para dar suporte aos ViewModels/Models portáteis.

As Figuras 7 e 8 mostram exemplos de interações de View/ViewModel. A Figura 7 mostra os controles na versão do Windows Phone de AddGroceryItemView e de suas associações. Esses controles estão associados com as propriedades no AddGroceryItemViewModel, que é compartilhado com os projetos do Windows Phone e do Windows 8, conforme mostrado na Figura 8.

Figure 7 Controles de AddGroceryItemView para Windows Phone

<StackPanel>
  <TextBox Text="{Binding Name, Mode=TwoWay}"
           Width="459"
           Height="80" />
  <Button Command="{Binding Add}"
          Content="add"
          Margin="307,5,0,0" />
  <TextBlock Text="{Binding NotificationText}"
             Margin="12,5,0,0"/>
</StackPanel>

Figura 8 Classe AddGroceryItemViewModel para Windows 8

public class AddGroceryItemViewModel : NavigatableViewModel
{
  private string _name;
  private string _notificationText;
  private ICommand _addCommand;
  [...]
  public ICommand AddCommand
  {
    get { return _addCommand ?? (_addCommand = new ActionCommand(Add)); }
  }
  public string Name
  {
    get { return _name ?? String.Empty; }
    set { base.SetProperty(ref _name, value, "Name"); }
  }
  public string NotificationText
  {
    get { return _notificationText ?? string.Empty; }
    set { base.SetProperty(ref _notificationText, value, "NotificationText"); }
  }
}

Origem do evento

O On Your Way Home é baseado fortemente no conceito de origem do evento (bit.ly/3SpC9h). Essa é a ideia de que todas as alterações de estado em um aplicativo são publicadas e armazenadas como uma sequência de eventos. Neste contexto, o evento não se refere à coisa definida pela palavra-chave event do C# (embora a ideia seja a mesma), mas sim às classes concretas que representam uma única alteração no sistema. Essas classes são publicadas por meio do que é chamado de um agregador de eventos, que notifica um ou mais manipuladores que trabalham em resposta ao evento. (Para obter mais informações sobre a agregação de eventos, consulte o artigo de Shawn Wildermuth, “Aplicativos Web compostos com Prism” em msdn.microsoft.com/magazine/dd943055.)

Por exemplo, o evento que representa um item de supermercado sendo adicionado à lista de compras é parecido com o mostrado na Figura 9.

Figura 9 Evento de item adicionado

// Published when a grocery item is added to a shopping list
[DataContract]
public class ItemAddedEvent : IEvent
{
  public ItemAddedEvent()
  {
  }
  [DataMember]
  public Guid Id
  {
    get;
    set;
  }
  [DataMember]
  public string Name
  {
    get;
    set;
  }
}

A classe ItemAddedEvent contém informações sobre o evento: neste caso, o nome do item de supermercado que foi adicionado e uma ID que é usada para representar exclusivamente o item dentro da lista de compras. Os eventos também são marcados com [DataContract], o que os torna mais fáceis de serem serializados no disco ou enviados eletronicamente.

Esse evento é criado e publicado quando o usuário clica no botão de adição na AddGroceryItemView, conforme mostrado na Figura 10.

Figura 10 Publicando o evento

public class AddGroceryItemViewModel : NavigatableViewModel
{
  private readonly IEventAggregator _eventAggregator;
  [...]
  // Adds an item to the shopping list
  private void Add()
  {
    var e = new ItemAddedEvent();
    e.Id = Guid.NewGuid();
    e.Name = Name;
    _eventAggregator.Publish(e);
    NotificationText = String.Format("{0} was added to the shopping list.", Name);
    Name = string.Empty;
  }
}

Observe que esse método não faz nenhuma alteração direta na lista de compras. Ele simplesmente publica o ItemAddedEvent ao agregador de eventos. Um dos manipulares de eventos tem a responsabilidade de ouvir esse evento para fazer alguma coisa com ele. Neste caso, a classe chamada ShoppingList assina e manipula o evento, conforme mostrado na Figura 11.

Figura 11 A classe ShoppingList

public class ShoppingList : IEventHandler<ItemAddedEvent>                                        
{
  public ShoppingList(IEventAggregator eventAggregator)
  {
    Requires.NotNull(eventAggregator, "eventAggregator");
    _eventAggregator = eventAggregator;
    _eventAggregator.Subscribe<ItemAddedEvent>(this);
  }
  [...]
  public ReadOnlyObservableCollection<GroceryItem> GroceryItems
  {
    get { return _groceryItems; }
  }
  public void Handle(ItemAddedEvent e)
  {
    var item = new GroceryItem();
    item.Id = e.Id;
    item.Name = e.Name;
    item.IsInCart = false;
    _groceryItems.Add(item);
  }
}
}

Toda vez que o ItemAddedEvent é publicado, a ShoppingList cria um novo GroceryItem usando os dados do evento e adiciona-o à lista de compras. A ShoppingListView, que está associada indiretamente à mesma lista por meio de seu ShoppingListViewModel, também é atualizada. Isso significa que quando o usuário navega de volta para a página da lista de compras, os itens que acabou de adicionar à lista são mostrados conforme esperado. Os processos de remoção de um item da lista de compras, de adição de um item ao carrinho e de check-out são todos manipulados usando o mesmo padrão de publicação/assinatura de eventos.

À primeira vista, isso pode parecer muita falta de direção para alguma coisa tão simples como adicionar itens a uma lista de compras: o método AddGroceryItemViewModel.Add publica um evento no IEventAggregator, que o passa para a ShoppingList, que o adiciona à lista de supermercado. Por que o método AddGroceryItemViewModel.Add simplesmente ignora o IEventAggregator e adiciona o novo GroceryItem diretamente à ShoppingList? Boa pergunta. A vantagem de tratar todas as alterações de estado no sistema como eventos é que isso incentiva todas as partes individuais do aplicativo a serem mais acopladas de maneira menos rígida. Como o publicador e os assinantes não têm conhecimento um do outro, a inserção de um novo recurso no pipeline, como a sincronização dos dados para a nuvem e vice-versa, é muito mais simples.

Sincronização de dados na nuvem

Cobri a funcionalidade básica do aplicativo em execução em um único dispositivo, mas ainda há o problema da obtenção das alterações que um usuário faz na lista de compras em outros dispositivos e vice-versa. É aqui que entra o Barramento de Serviço do Windows Azure AppFabric.

O Barramento de Serviço do Windows Azure AppFabric é um recurso que permite que os aplicativos e serviços conversem facilmente uns com os outros pela Internet, evitando as complexidades de navegação pelos obstáculos de comunicação, como firewalls e dispositivos NAT (conversão de endereços de rede). Ele fornece pontos de extremidade REST e Windows Communication Foundation (WCF) hospedados pelo Windows Azure e está situado entre o publicador e o assinante.

Há três maneiras principais de se comunicar usando o Barramento de Serviço do Windows Azure AppFabric. Para fins de meu aplicativo, no entanto, cobrirei apenas Tópicos. Para obter uma visão geral completa, consulte "Uma introdução ao Barramento de Serviço do Windows Azure AppFabric” em bit.ly/uNVaXG.

Para os publicadores, um Tópico de Barramento de Serviço é parecido com uma grande fila na nuvem (consulte a Figura 12). Completamente sem saber quem está ouvindo, os publicadores enviam mensagens por push ao Tópico, onde são mantidas ad infinitum até que sejam solicitadas por um assinante. Para obter mensagens da fila, os assinantes efetuam pull de uma Assinatura, que filtra mensagens publicadas no Tópico. As assinaturas funcionam como uma fila particular, e as mensagens removidas de uma Assinatura ainda serão vistas por outras Assinaturas se seus próprios filtros as incluírem.

Service Bus Topic
Figura 12 Tópico de Barramento de Serviço

No On Your Way Home, a classe AzureServiceEventHandler é a ponte entre o aplicativo e o Barramento de Serviço. Semelhante à ShoppingList, ela também implementa IEventHandler<T>, mas em vez de eventos específicos, a AzureServiceEventHandlers pode manipular todos eles, conforme mostrado na Figura 13.

The AzureServiceEventHandler Class
Figura 13 A classe AzureServiceEventHandler

public class AzureServiceBusEventHandler : DisposableObject, IEventHandler<IEvent>, IStartupService
{
  private readonly IAzureServiceBus _serviceBus;
  private readonly IAzureEventSerializer _eventSerializer;
  public AzureServiceBusEventHandler(IEventAggregator eventAggregator,
    IAzureServiceBus serviceBus, IAzureEventSerializer eventSerializer)
  {
    _eventAggregator = eventAggregator;
    _eventAggregator.SubscribeAll(this);
    _serviceBus = serviceBus;
    _serviceBus.MessageReceived += OnMessageReceived;
    _eventSerializer = eventSerializer;
  }
  [...]
  public void Handle(IEvent e)
  {
    BrokeredMessage message = _eventSerializer.Serialize(e);
    _serviceBus.Send(message);
  }
}

Cada alteração que um usuário faz no estado da lista de compras é manipulada por AzureServiceBusEventHandler e enviada por push diretamente para a nuvem. Nem AddGroceryItemViewModel, que publica o evento, nem ShoppingList, que o manipula no dispositivo local, está ciente de que isso ocorre.

A viagem de volta da nuvem é onde uma arquitetura baseada em eventos realmente se paga. Quando detecta que uma nova mensagem foi recebida no Barramento de Serviço (por meio do evento C# IAzureServiceBus.MessageReceived), o AzureServiceEventHandler reverte o que fez anteriormente e desserializa a mensagem recebida de volta para um evento. A partir daí, ela é publicada novamente por meio do agregador de eventos, que faz com que ela seja tratada como se o evento viesse de dentro do aplicativo, conforme mostrado na Figura 14.

Figura 14 Desserializando uma mensagem recebida

public class AzureServiceBusEventHandler : DisposableObject, IEventHandler<IEvent>,
  IStartupService
{
  private readonly IAzureServiceBus _serviceBus;
  private readonly IAzureEventSerializer _eventSerializer;
  [...]
  private void OnMessageReceived(object sender, MessageReceivedEventArgs args)
  {
    IEvent e = _eventSerializer.Deserialize(args.Message);
    _eventAggregator.Publish(e);
  }
}

A ShoppingList não tem conhecimento (nem se importa) sobre a origem do evento e manipula os que são recebidos do Barramento de Serviço/nuvem como se viessem diretamente da entrada de um usuário. Atualiza sua lista de supermercado, que, por sua vez, faz com que todas as exibições associadas àquela lista também sejam atualizadas.

Se você prestar atenção especial, observará um pequeno problema com o fluxo de trabalho: os eventos que são enviados para a nuvem do dispositivo local voltam para aquele mesmo dispositivo e provocam duplicação dos dados. O pior é que as alterações para outras listas de compras não relacionadas também virão para aquele dispositivo. Não sei o que você acha disso, mas eu realmente não quero ver opções de mantimentos de outras pessoas aparecendo em minha lista de compras. Para evitar isso, um Tópico de Barramento de Serviço é criado por lista, e uma Assinatura por dispositivo, que ouve o Tópico. Quando as mensagens são publicadas no Tópico a partir do dispositivo, uma propriedade contendo a ID do dispositivo é enviada junto com as mensagens, que é usada pelo filtro de Assinaturas para excluir mensagens vindas de seu próprio dispositivo. A Figura 15 mostra esse fluxo de trabalho.

Device-to-Device Workflow
Figura 15 Fluxo de trabalho de dispositivo para dispositivo

Conclusão

Abordei muitas coisas neste artigo: As Bibliotecas de classes portáteis simplificaram minha solução e reduziram significativamente a quantidade de código que precisei escrever para usar as duas plataformas. Além disso, a alteração do estado do aplicativo por meio de eventos facilitou muito a sincronização do estado com a nuvem. No entanto, ainda há muita coisa que não mencionei, que você desejará fatorar ao desenvolver um cliente contínuo. Não mencionei o cache de eventos offline e a tolerância a falhas (e se a rede não estiver disponível quando eu publicas um evento?), conflitos de mesclagem (e se outro usuário fizer uma alteração que entre em conflito com a minha?), reprodução (e se eu conectar um novo dispositivo à lista de compras, como ele será atualizado?), controle de acesso (como evito que usuários não autorizados acessem dados que não deveriam?) e, finalmente, persistência. No código de exemplo do artigo, o aplicativo não salva a lista de compras entre inicializações. Deixarei isso como um exercício para você. Pode ser um desafio interessante se você desejar brincar com o código. Uma maneira simples (ou melhor, tradicional) de abordar a persistência pode ser colocar um gancho diretamente na classe ShoppingList, tornar os objetos de GroceryItem serializáveis e salvá-los em um arquivo. No entanto, antes de usar esse roteiro, pare e pense sobre ele: Como a ShoppingList já manipula eventos nativamente e não se preocupa com sua origem, a sincronização dos dados para a nuvem e da nuvem é surpreendentemente semelhante a salvar e restaurar dados do disco, não é?

David Kean é desenvolvedor da equipe do .NET Framework na Microsoft e trabalha com a equipe de BCL (Bibliotecas de classes base). Anteriormente, ele trabalhou com a ferramenta FxCop, que é muito adorada, mas muito mal-interpretada, e sua parente, a Análise de Código do Visual Studio. Procedente de Melbourne, Austrália, está agora baseado em Seattle, Washington, com sua esposa, Lucy, e três filhos, Jack, Sarah e Ben. Ele pode ser localizado no blog davesbox.com.

Agradecemos aos seguintes especialistas técnicos pela revisão deste artigo: Nicholas Blumhardt e Immo Landwerth