Agosto 2015

Volume 30 numero 8

Il presente articolo è stato tradotto automaticamente.

Connesso al Cloud Mobile Apps - creare un servizio Web con WebJobs e Azure Web Apps

Da Erik Reitan

Oggi, molte applicazioni di mobile sono collegati a uno o più servizi Web che forniscono dati preziosi e interessanti. Durante la progettazione e lo sviluppo di tali applicazioni, l'approccio più semplice da adottare è quello di effettuare chiamate di API REST dirette a tali servizi e quindi elaborare la risposta nel client. Tuttavia, questo approccio ha una serie di svantaggi. Per uno, ogni chiamata di rete e ogni bit di elaborazione lato client consuma larghezza di banda e potenza della batteria preziosi. Inoltre, vasta elaborazione lato client può richiedere un po' di tempo per eseguire, soprattutto su hardware di fascia bassa, rendendo l'applicazione meno reattiva. E diversi servizi Web potrebbero imporre limitazioni di limitazione, il che significa che una soluzione puramente client-side non scalerà prontamente a un numero maggiore di utenti.

Di conseguenza, ha senso in molti scenari — specialmente quelli che estrarre dati da più origini — per creare il tuo back-end, a cui si possono scaricare alcuni compiti. Come parte del nostro lavoro in Microsoft come un team che sviluppa contenuti per ASP.NET, Microsoft Azure e strumenti di sviluppo cross-platform in Visual Studio, abbiamo creato un esempio specifico di questo approccio.

In questo articolo in due parti, discutiamo il nostro approccio, alcune delle sfide che abbiamo incontrato e alcune delle lezioni che abbiamo imparato durante lo sviluppo la nostra applicazione. Questa applicazione, che abbiamo chiamato "Altostratus" (un interessante tipo di nuvola), le ricerche dello Stack Overflow e Twitter per argomenti specifici, che noi chiamiamo "conversazioni". Abbiamo scelto questi due provider perché entrambi hanno buona Web API. L'applicazione ha due componenti principali:

  • Una nuvola indietro fine, ospitato su Azure. Il back-end periodicamente effettua richieste ai fornitori e aggrega i dati nel modulo che è più adatto per il client. Questo evita problemi di limitazione, riduce i problemi di latenza in provider, riduce al minimo l'elaborazione sul lato client e riduce il numero di richieste di rete dal client. Un compromesso è che il WebJob viene eseguito ogni poche ore, dunque non si ottengono dati in tempo reale.
  • Un leggero client mobile app, creato con Xamarin per eseguire su Windows, Android e iOS (vedere Figura 1). Il client mobile recupera i dati aggregati da back-end e lo presenta all'utente. Mantiene anche una cache sincronizzata dei dati in un database locale per una buona esperienza non in linea e tempi di avvio più veloce.

The Xamarin Mobile Client in esecuzione su un Tablet Android (a sinistra), un Windows Phone (al centro) e un iPhone (a destra)
Figura 1 The Xamarin Mobile Client in esecuzione su un Tablet Android (a sinistra), un Windows Phone (al centro) e un iPhone (a destra)

Gli utenti possono facoltativamente accedere a mobile client utilizzando provider sociale (Google o Facebook). Quando l'accesso, gli utenti possono impostare preferenze che ottimizzano ulteriormente le comunicazioni con il cliente. In particolare, possono selezionare quali aree di interesse per ricevere e il numero massimo di conversazioni all'interno di ogni area tematica. Le preferenze dell'utente vengono memorizzate sul back-end, quindi un utente può accedere da qualsiasi client e ottenere la stessa esperienza. Per dimostrare questa idea, abbiamo anche creato un semplice client Web che parla di back-end stesso.

Come parte di questo progetto, abbiamo anche voluto utilizzare gli strumenti di application lifecycle management (ALM) costruiti in Visual Studio e Visual Studio Online per gestire gli sprint e il backlog di lavoro e per eseguire unit test, integrazione continua (CI) e continuo distribuzione (CD) automatici.

Questo articolo di due parti Esplora i dettagli del nostro progetto. Parte 1 si concentra sul back-end e il nostro uso di strumenti ALM (DevOps). Parleremo anche di alcune delle sfide che abbiamo incontrato e alcune delle lezioni apprese, come:

  • Come automatizzare in modo sicuro la distribuzione di password per applicazioni non Web.
  • Come gestire i timeout di automazione Azure.
  • Elaborazione in background efficiente e ben informato.
  • CI/CD limitazioni e soluzioni alternative.

Parte 2 discuterà come facevamo Xamarin destinati a più piattaforme client mobile, tra cui autenticazione e gestione di una cache sul lato client sincronizzata dei dati.

Architecture

Figura 2 viene illustrata l'architettura ad alto livello per la soluzione di altostrati.

  • Sul back-end, utilizziamo il servizio App Azure per ospitare l'applicazione Web e Database SQL Azure per archiviare i dati in un database relazionale. Usiamo Entity Framework (EF) per accedere ai dati.
  • Usiamo WebJobs Azure per eseguire un'attività in background pianificata che aggrega i dati dallo Stack Overflow e Twitter e lo scrive nel database. Il WebJob può essere facilmente esteso per aggregare i dati da altre fonti.
  • Il client mobile viene creato utilizzando Xamarin e comunica con il back-end utilizzando una semplice API REST.
  • L'API REST è implementato utilizzando ASP.NET Web API, che è un framework per la creazione di servizi HTTP in Microsoft .NET Framework.
  • Il client Web è un'applicazione relativamente semplice di JavaScript. Abbiamo usato la libreria KnockoutJS per l'associazione dati e jQuery per le chiamate AJAX.

architettura di Altostratus
Figura 2 architettura di Altostratus

Lo Schema del Database

Utilizziamo EF Code First per definire lo schema del database e gestire il database SQL di back-end. Come Figura 3 spettacoli, il database memorizza le seguenti entità:

  • Provider: Un'origine dati, come Twitter o Overflow dello Stack.
  • Conversazione: Un elemento da un provider. Per Overflow dello Stack, ciò corrisponde ad una domanda con risposte. Per Twitter, corrisponde a un tweet. (Potrebbe anche essere un tweet con le risposte, ma noi non implementare tale funzionalità).
  • Categoria: Il soggetto per una conversazione, ad esempio "Azure" o "ASP.NET."
  • Tag: Una stringa di ricerca per una specifica categoria e il provider. Questi corrispondono ai tag in Overflow dello Stack ("azure-web-sites") e hash tag in Twitter ("#azurewebsites"). Il back-end utilizzati per i provider di query. L'utente finale non vederli.
  • UserPreference: Memorizza le preferenze di ogni utente.
  • UserCategory: Definisce una tabella di join per UserPreference e categoria.

il modello di dati Altostratus
Figura 3 il modello di dati Altostratus

In generale, la creazione, lettura, aggiornamento, eliminazione (CRUD) codice nell'applicazione Altostratus è tipico per EF Code First. Una speciale considerazione ha a che fare con la gestione di database semina e migrazioni. Codice prima crea e semi di un nuovo database se non esiste alcun database la prima volta che un programma tenta di accedere ai dati. Perché il primo tentativo di accesso ai dati potrebbe accadere nella WebJob, abbiamo il codice nel metodo Main di WebJob di utilizzare la MigrateDatabase EF­ToLatestVersion inizializzatore:

static void Main()
{
  Task task;
  try
  {
    Database.SetInitializer<ApplicationDbContext>(
      new MigrateDatabaseToLatestVersion<ApplicationDbContext,
        Altostratus.DAL.Migrations.Configuration>());

Senza questo codice, il WebJob sarebbe utilizzare l'inizializzatore CreateDatabaseIfNotExists per impostazione predefinita, che non seeding del database. Il risultato sarebbe un database vuoto ed errori quando l'applicazione tenta di leggere dati da tabelle vuote.

Progettare il WebJob

WebJobs fornisce una soluzione ideale per le attività in esecuzione in background, che è il lavoro che in precedenza avrebbe richiesto un ruolo di lavoro Azure dedicato eseguire. È possibile eseguire WebJobs su un'applicazione Azure Web senza alcun costo aggiuntivo. Si può leggere sui vantaggi che webjobs fornire sopra ruoli di lavoro a Troy Hunts post del blog, "WebJobs Azure sono incredibili e dovrebbe iniziare a utilizzarle diritto ora!" (bit.ly/1c28yAk).

WebJob per la nostra soluzione viene eseguito periodicamente tre funzioni: ottenere dati di Twitter, ottenere dati di Overflow dello Stack ed eliminare i dati obsoleti. Queste funzioni sono indipendenti, ma deve essere eseguiti in modo sequenziale perché condividono lo stesso contesto EF. Al termine un WebJob, il portale di Azure Mostra lo stato di ciascuna funzione (vedere Figura 3). Se la funzione viene completata, è contrassegnato con un messaggio di successo verde, e se viene generata un'eccezione, è contrassegnato con un messaggio di errore rosso.

Gli errori non sono infrequenti in Altostratus perché usiamo il provider gratuito di Overflow dello Stack e Twitter API. Query, in particolare, sono limitate: Se si supera il limite di query, i provider restituiscono un errore di limitazione. Questo è stato dei motivi principali per la creazione di back-end in primo luogo. Anche se sarebbe molto semplice per un app mobile effettuare richieste a questi provider direttamente, è possibile che un numero crescente di utenti rapidamente potrebbe raggiungere il limite di limitazione. Invece, il back-end può fare solo poche richieste periodiche per raccogliere e aggregare i dati.

Un problema che abbiamo incontrati era intorno WebJob la gestione degli errori. Normalmente, se qualsiasi funzione nel WebJob genera un'eccezione, l'istanza di WebJob è terminata, non eseguire le rimanenti e l'intera esecuzione di WebJob è contrassegnato come non riuscito. Al fine di eseguire tutte le attività e mostrare un errore a livello di funzione, il WebJob necessario intercettare eccezioni. Se qualsiasi funzione in Main genera, registriamo l'ultima eccezione e generare nuovamente, quindi il WebJob ottiene contrassegnato come non riuscito. Il codice di pseudo in Figura 4 illustra questo approccio. (Il download di progetto contiene il codice completo).

Figura 4 cattura e ri-generazione di eccezioni

static void Main()
{
  Task task;
  try
  {
    Exception _lastException = null;
    try
    {
      task = host.CallAsync("Twitter");
      task.Wait();
    }
    catch (Exception ex)
    {
      _lastException = ex;
    }
    try
    {
      task = host.CallAsync("StackOverflow");
      task.Wait();
    }
    catch (Exception ex)
    {
      _lastException = ex;
    }
    task = host.CallAsync("Purge Old Data");
    task.Wait();
    if (_lastException != null)
    {
      throw _lastException;
    }
  }
  catch (Exception ex)
  {
  }
}

In Figura 4, solo l'ultima eccezione è indicato presso la WebJob livello. A livello di funzione, ogni eccezione viene registrato, quindi eccezioni non sono perso. Figura 5 Mostra la dashboard per un WebJob che è stato eseguito correttamente. È possibile drill-down in ogni funzione per vedere l'output di dati diagnostici.

successo WebJob Run
Figura 5 successo WebJob Run

Progettare l'API REST

L'app mobile comunica al back-end tramite una semplice API REST, che abbiamo implementato utilizzando API ASP.NET Web 2. (Si noti che in ASP.NET 5, Web API viene unito il framework MVC 6, rendendo più semplice per incorporare entrambi in una Web app).

Figura 6 riassume la nostra API REST.

Figura 6 REST API Riassunto

OTTENERE api/categorie Ottiene le categorie.
GET api/conversazioni? from = data-iso-8601 Ottiene le conversazioni.
OTTENERE api/userpreferences Ottiene le preferenze dell'utente.
METTERE api/userpreferences Aggiorna le preferenze dell'utente.

Tutte le risposte sono in formato JSON. Ad esempio, Figura 7 Mostra una risposta HTTP per le conversazioni.

Figura 7 una risposta HTTP per le conversazioni

HTTP/1.1 200 OK
Content-Length: 93449
Content-Type: application/json; charset=utf-8
Server: Microsoft-IIS/8.0
Date: Tue, 21 Apr 2015 22:38:47 GMT
[
  {
    "Url": "http://twitter.com/815911142/status/590317675412262912",
    "LastUpdated": "2015-04-21T00:54:36",
    "Title": "Tweet by rickraineytx",
    "Body": "Everything you need to know about #AzureWebJobs is here.
      <a href=\"http://t.co/t2bywUQoft\"">http://t.co/t2bywUQoft</a>",
    "ProviderName": "Twitter",
    "CategoryName": "Azure"
  },
  // ... Some results deleted for space
]

Per le conversazioni, non richiediamo la richiesta deve essere autenticato. Ciò significa che l'app mobile può sempre mostrare dati significativi senza richiedere all'utente di accedere in primo luogo. Ma abbiamo anche voluto dimostrare di avere il back-end eseguire ottimizzazioni aggiuntive quando un utente si connette. La nostra app client consente all'utente di selezionare quali categorie di conversazioni per visualizzare, insieme a un limite di conversazione. Così se una richiesta è autenticata (cioè l'utente connesso in app), il back-end filtra automaticamente la risposta basata su tali preferenze. Questo limita la quantità di dati che devono essere elaborati dal client, che, nel tempo, riduce le esigenze di larghezza di banda, memoria, archiviazione e batteria di alimentazione.

L'API di conversazioni prende anche un optional "da" parametro nella stringa di query. Se specificato, questo consente di filtrare i risultati per includere solo le conversazioni aggiornate dopo tale data:

GET api/conversations?from=2015-04-20T03:59Z

Questo riduce al minimo le dimensioni della risposta. Il nostro client mobile utilizza questo parametro per chiedere solo i dati che necessari per sincronizzare la cache.

Abbiamo potuto progettato l'API utilizzare stringhe di query per comunicare le preferenze di un utente su una base di per-richiesta, significato che tali preferenze sarebbero state mantenute solo nel client. Che avrebbe evitato la necessità di autenticazione. Mentre questo approccio avrebbe funzionato bene per scenari di base, abbiamo voluto fornire un esempio che potrebbe essere estesa a situazioni più complicate, dove le stringhe di query sarebbe insufficiente. Memorizzare le preferenze sul back-end significa anche esse vengono automaticamente applicate su ogni client dove lo stesso utente accede a.

Oggetti di trasferimento di dati (DTOs)

L'API REST è il confine tra lo schema del database e la rappresentazione di filo. Non abbiamo voglia di serializzare i modelli EF direttamente:

  • Essi contengono informazioni che il client non ha bisogno, come chiavi esterne.
  • Le API possono rendere vulnerabile al sovra-invio. (Eccessivo distacco è quando un client Aggiorna campi di database che non trattino per esporre per gli aggiornamenti. Può verificarsi quando si converte un payload di richiesta HTTP direttamente in un modello EF senza convalidare l'input sufficientemente. Vedi bit.ly/1It1wl2 per ulteriori informazioni.)
  • La "forma" dei modelli EF è progettata per la creazione di tabelle di database e non è ottima per il cliente.

Pertanto, abbiamo creato un set di dati trasferimento oggetti (DTOs), che sono solo c# classi che definiscono il formato per le risposte di API REST. Ad esempio, qui è il nostro modello EF per categorie:

public class Category
{
  public int CategoryID { get; set; }
  [StringLength(100)]
  public string Name { get; set; }  // e.g: Azure, ASP.NET
  public ICollection<Tag> Tags { get; set; }
  public ICollection<Conversation> Conversations { get; set; }
}

L'entità di categoria ha una chiave primaria (CategoryID) e proprietà di navigazione (tag e conversazioni). Le proprietà di navigazione lo rendono facile da seguire le relazioni in EF — ad esempio, per trovare tutti i tag per una categoria.

Quando il cliente chiede per le categorie, ha bisogno solo di un elenco di nomi di categoria:

[ "Azure", "ASP.NET" ]

Questa conversione è facile da eseguire utilizzando un'istruzione Select di LINQ nel metodo di controller Web API:

public IEnumerable<string> GetCategories()
{
  return db.Categories.Select(x => x.Name);
}

L'entità di UserPreference è un po' più complicato:

public class UserPreference
{
  // FK to AspNetUser table Id   
  [Key, DatabaseGenerated(DatabaseGeneratedOption.None)]
  public string ApplicationUser_Id { get; set; }
  public int ConversationLimit { get; set; }    
  public int SortOrder { get; set; }            
  public ICollection<UserCategory> UserCategory { get; set; }
  [ForeignKey("ApplicationUser_Id")]
  public ApplicationUser AppUser { get; set; }
}

ApplicationUser_Id è una chiave esterna nella tabella utente. UserCategory punta a una tabella di collegamento, che crea una relazione molti-a-molti tra le categorie e le preferenze dell'utente.

Ecco come vogliamo questo per apparire al client:

public class UserPreferenceDTO
{
  public int ConversationLimit { get; set; }
  public int SortOrder { get; set; }
  public ICollection<string> Categories { get; set; }
}

Questo nasconde le cose che sono dettagli di implementazione dello schema del database, come chiavi esterne e tabelle di collegamento, e appiattisce i nomi delle categorie in un elenco di stringhe.

L'espressione LINQ per convertire UserPreference in UserPreference­DTO è piuttosto complesso, così abbiamo usato AutoMapper invece. AutoMapper è una libreria che i mapping tra tipi di oggetto. L'idea è quella di definire un mapping di una volta e quindi utilizzare AutoMapper per eseguire il mapping per voi.

Configuriamo AutoMapper all'avvio dell'app:

Mapper.CreateMap<Conversation, ConversationDTO>();
Mapper.CreateMap<UserPreference, UserPreferenceDTO>()
  .ForMember(dest => dest.Categories,
             opts => opts.MapFrom(
               src => src.UserCategory.Select(
                 x => x.Category.Name).ToList()));
              // This clause maps ICollection<UserCategory> to a flat
              // list of category names.

La prima chiamata a CreateMap Mappe: conversazione a ConversationDTO, utilizzando convenzioni di mapping predefinito di AutoMapper. Per UserPreference, il mapping è meno semplice, quindi c'è qualche configurazione aggiuntiva.

Dopo aver configurato l'AutoMapper, mapping di oggetti è facile:

var prefs = await db.UserPreferences
  .Include(x => x.UserCategory.Select(y => y.Category))  
  .SingleOrDefaultAsync(x => x.ApplicationUser_Id == userId);
var results = AutoMapper.Mapper.Map<UserPreference,
  UserPreferenceDTO>(prefs);

AutoMapper è abbastanza intelligente per convertire questo in un'istruzione LINQ , così il seleziona accada sul database.

Autenticazione e autorizzazione

Usiamo un account di accesso sociale (Google e Facebook) per autenticare gli utenti. (Il processo di autenticazione verrà descritto in dettaglio nella parte 2 di questo articolo.) Dopo Web API auth­enticates la richiesta, il controller di Web API può utilizzare queste informazioni per autorizzare le richieste e a cercare l'utente nel database utente.

Per limitare un API REST agli utenti autorizzati, noi decorare la classe controller con l'attributo [autorizza]:

[Authorize]
public class UserPreferencesController : ApiController

Ora se una richiesta di api/userpreferences non è autorizzata, Web API restituisce automaticamente un errore 401:

HTTP/1.1 401 Unauthorized
Content-Type: application/json; charset=utf-8
WWW-Authenticate: Bearer
Date: Tue, 21 Apr 2015 23:55:47 GMT
Content-Length: 68
{
  "Message": "Authorization has been denied for this request."
}

Si noti che API Web aggiunto un'intestazione WWW-Authenticate per la risposta. Questo indica al client quale tipo di schema di autenticazione è supportato — in questo caso, i token di portatore di OAuth2.

Per impostazione predefinita, [autorizza] presuppone che ogni utente autenticato è inoltre autorizzato, e solo le richieste anonime vengono bloccate. Puoi anche limitare l'autorizzazione agli utenti in ruoli specificati (ad esempio gli amministratori), o implementare un filtro di autorizzazione personalizzato per scenari più complessi di autorizzazione.

L'API di conversazioni è un caso più interessante: Abbiamo consentire le richieste anonime, ma applicare logica aggiuntiva quando la richiesta viene autenticata. Il codice seguente controlla se la richiesta corrente è autenticata e, in caso affermativo, ottiene le preferenze dell'utente dal database:

if (User.Identity.IsAuthenticated)
{
  string userId = User.Identity.GetUserId();
  prefs = await db.UserPreferences
    .Include(x => x.UserCategory.Select(y => y.Category))
    .Where(x => x.ApplicationUser_Id == userId).SingleOrDefaultAsync();
}

Se prefs è diverso da null, usiamo le preferenze per modellare la query EF. Per le richieste anonime, possiamo gestire una query predefinita.

Feed di dati

Come accennato in precedenza, nostra app estrae dati da Overflow dello Stack e Twitter. Uno dei nostri principi di progettazione era quello di prendere i dati da più diversi fonti e aggregarli in una fonte singola, normalizzata. Questo semplifica l'interazione tra i client e il back-end, perché i clienti non è necessario conoscere il formato dei dati per qualsiasi provider particolare. Nel back-end, abbiamo implementato un modello di provider che rende facile aggregare fonti supplementari, senza richiedere alcuna modifica nell'API Web o client. I provider espongono un'interfaccia coerente:

interface IProviderAPI
{
   Task<IEnumerable<Conversation>> GetConversationsAsync(
     Provider provider, Category category,
     IEnumerable<Tag> tags, DateTime from, int maxResults, TextWriter logger);
}

Le API di Twitter e di Overflow dello Stack forniscono l'abbondanza di capacità, ma con quella possibilità è complessità. Abbiamo trovato che i pacchetti StacMan e LINQtoTwitter NuGet fatti lavorare con le API molto più facile. LINQtoTwitter e StacMan sono ben documentati, attivamente supportati e open source e sono facili da usare con C#.

Gestione di password e altri segreti

Abbiamo seguito l'articolo, "Procedure consigliate per la distribuzione di password e altri dati sensibili a ASP.NET e Azure App Service" (bit.ly/1zlNiQI), quali mandati mai controllo password nel codice sorgente. Memorizziamo segreti solo nei file di configurazione ausiliaria sulle macchine di sviluppo locale. Per distribuire l'applicazione Azure, usiamo Windows PowerShell o il portale di Azure.

Questo approccio funziona bene per l'applicazione Web. Ci siamo spostati i segreti al file Web. config con il markup seguente:

<appSettings file="..\..\AppSettingsSecrets.config">
</appSettings>

Lo spostamento del file fino a due livelli dalla directory di origine significa che è completamente fuori di directory della soluzione e non viene aggiunto al controllo del codice sorgente.

Il file app. config utilizzato da un'applicazione console (WebJobs) non supporta i percorsi relativi, ma supporta percorsi assoluti. È possibile utilizzare un percorso assoluto per spostare i tuoi segreti dalla directory del progetto. Il markup seguente aggiunge i segreti nel C:\secrets\AppSettingsSecrets.config file e dati non sensibili nel file app. config:

<configuration>
  <appSettings file="C:\secrets\AppSettingsSecrets.config">
    <add key="TwitterMaxThreads" value="24" />
    <add key="StackOverflowMaxThreads" value="24" />
    <add key="MaxDaysForPurge" value="30" />
  </appSettings>
</configuration>

Per gestire i segreti nel nostro script di Windows PowerShell usiamo il cmdlet Export-CliXml per esportare i segreti crittografati su disco e Import-CliXml per leggere i segreti.

Automatizzare tutto

DevOps è consigliabile è quello di automatizzare tutto. Abbiamo originariamente scritto gli script di Windows PowerShell per creare le risorse Azure richiede la nostra app (app Web, database, archiviazione) e associare tutte le risorse, ad esempio l'impostazione della stringa di connessione e l'app segreti di impostazioni.

La distribuzione guidata di Visual Studio fa un buon lavoro di automazione della distribuzione di Azure, ma ci sono ancora diversi passaggi manuali per distribuire e configurare l'app:

  • Immettere la password dell'account administrator sul Database SQL Azure.
  • Entrando i segreti di impostazioni di app per il Web app e WebJob.
  • Entrando le stringhe di conto di deposito WebJob per agganciare WebJob monitoraggio.
  • Aggiornando l'URL di distribuzione nelle console sviluppatore Facebook e Google, per attivare gli accessi sociali OAuth.

Un nuovo URL della distribuzione richiede di aggiornare il tuo URL di autenticazione OAuth provider, quindi non non c'è alcun modo per automatizzare l'ultimo passaggio senza l'utilizzo di un nome di dominio personalizzato. Il nostro script di Windows PowerShell creare tutte le risorse Azure abbiamo bisogno e agganciarle, quindi tutto ciò che può essere automatizzato è automatizzato.

La prima volta che si distribuisce un WebJob da Visual Studio, viene chiesto di impostare una pianificazione per l'esecuzione il WebJob. (Corriamo il WebJob ogni tre ore.) Al momento di questa scrittura, c'è nessun modo in Windows PowerShell per impostare una pianificazione WebJob. Dopo aver eseguito la creazione di Windows PowerShell e gli script di distribuzione, è necessario il passaggio aggiuntivo una tantum di WebJob da Visual Studio per impostare la pianificazione di distribuzione, o è possibile impostare una pianificazione sul portale.

Abbiamo subito scoperto che gli script di Windows PowerShell sarebbero talvolta timeout durante il tentativo di creare una risorsa. Utilizzando l'approccio di Azure PowerShell tradizionale, non esiste alcun modo semplice per gestire un errore casuale di creazione di risorse. L'eliminazione di tutte le risorse che sono state create con successo richiede un banale script che potrebbe eventualmente eliminare risorse che non si intendeva rimuovere. Anche quando lo script è successo, dopo aver completato il test, è necessario eliminare tutte le risorse lo script creato. Tenere traccia di risorse da eliminare dopo l'esecuzione di un test è banale ed error-prone.

Si noti che timeout di creazione di risorse non è un difetto con Azure. Creare in remoto risorse complesse, ad esempio un server di dati o Web app, è intrinsecamente molto tempo. Applicazioni cloud devono essere definite fin dall'inizio per affrontare i timeout e il fallimento.

Azure Resource Manager (ARM) per il salvataggio

ARM consente di creare risorse come un gruppo. Questo consente di creare tutte le vostre risorse e gestire gli errori temporanei, quindi se lo script non riesce a creare una risorsa, è possibile tentare nuovamente. Inoltre, la pulizia è facile. Si elimina semplicemente il gruppo di risorse, e tutti gli oggetti dipendenti vengono eliminati automaticamente.

Gli errori temporanei si verificano raramente e in genere solo una riprova è necessaria per la riuscita dell'operazione. Il seguente frammento da uno script di Windows PowerShell Mostra un approccio semplice per implementare la logica di riprova con backoff lineare quando si utilizza un modello di braccio:

$cnt = 0
$SleepSeconds = 30$ProvisioningState = 'Failed'while ( [string]::Compare($ProvisioningState, 'Failed', $True) -eq 0 -and ($cnt -lt 4 ) ){   My-New-AzureResourceGroup -RGname $RGname `    -WebSiteName $WebSiteName -HostingPlanName $HostingPlanName
  $RGD = Get-AzureResourceGroupDeployment -ResourceGroupName $RGname  $ProvisioningState = $RGD.ProvisioningState  Start-Sleep -s ($SleepSeconds * $cnt)  $cnt++}

My-New-AzureResourceGroup è una funzione di Windows PowerShell abbiamo scritto che esegue il wrapping di una chiamata al cmdlet New-AzureResourceGroup, che utilizza un modello di braccio per creare risorse di Azure. La chiamata New-AzureResourceGroup avrà quasi sempre esito positivo, ma la creazione di risorse specificate nel modello possibile timeout.

Se qualsiasi risorsa non è stata creata, lo stato di provisioning è fallito, e lo script verrà dormire e riprovare. Durante un nuovo tentativo, risorse che già con successo sono state create non sono ricreate. Lo script precedente tenta tre tentativi. (Quattro errori in una riga indicano quasi certamente un errore di non transitorio).

L'idempotenza che braccio fornisce è estremamente utile negli script che creano molte risorse. Non stiamo suggerendo di avete bisogno di questa logica di riprova in tutte le distribuzioni, ma braccio ti dà questa opzione quando è utile.  Vedere "Uso di PowerShell Azure con Azure Resource Manager" (bit.ly/1GyaMzv).

Costruire l'integrazione e la distribuzione automatica

Visual Studio Online ha reso facile impostare la pagina di integrazione continua (CI). Ogni volta che il codice viene controllato in Team Foundation Server (TFS), automaticamente attiva una compilazione ed esegue i test di unità. Abbiamo impiegato anche erogazione continua (CD). Se la compilazione e unit test automatizzati hanno esito positivo, l'applicazione viene distribuita automaticamente al nostro sito di test di Azure. È possibile leggere informazioni sull'impostazione di CI/CD a Azure alla pagina di documentazione, "Erogazione continua a Azure Using Visual Studio Online" (bit.ly/1OkMkaW).

All'inizio del dev ciclo, quando c'erano tre o più di noi attivamente il controllo nel codice sorgente, abbiamo utilizzato il valore predefinito costruire trigger di definizione per dare il via il ciclo di generazione/test/distribuzione in 3, dal lunedì al venerdì. Questo ha funzionato bene, dandoci la possibilità di fare un rapido test sul sito Web quando abbiamo iniziato a lavorare ogni mattina. Come la base di codice stabilizzato e archiviazioni erano meno frequenti, ma forse più critica, che impostiamo il trigger su modalità CI, il check-in modo ogni innescherebbe il processo. Quando siamo arrivati alla fase di "Codice ripulire", con frequenti cambiamenti di basso rischio, impostiamo il grilletto al rotolamento compilazioni, dove il ciclo di distribuzione/build/test è avviato al massimo ogni ora. Figura 8 Mostra un riepilogo di compilazione che include copertura distribuzione e test.

Riepilogo della compilazione
Figura 8 Riepilogo della compilazione

Conclusioni

In questo articolo, abbiamo visto anche alcune delle considerazioni durante la creazione di un back-end nube che aggrega ed elabora i dati e lo serve ai client mobili. Nessun singolo pezzo della nostra applicazione di esempio è terribilmente complicato, ma ci sono un sacco di parti in movimento, che è tipico di soluzioni cloud-based. Abbiamo anche visto come Visual Studio Online reso possibile per un piccolo team eseguire continuo costruisce e distribuzione continuo senza un manager DevOps dedicato.

Nella seconda parte, vedremo in dettaglio l'applicazione client e come forme di Xamarin ha reso facile per piattaforme multiple con un minimo di codice specifico della piattaforma di destinazione. Approfondiremo anche i misteri della OAuth2 per social login.


Rick Anderson funziona come uno scrittore di programmazione senior per Microsoft, concentrandosi su ASP.NET MVC, Microsoft Azure ed Entity Framework. È possibile seguirlo su Twitter a twitter.com/RickAndMSFT.

Kraig Brockschmidt lavora come sviluppatore senior contenuto per Microsoft e si concentra su piattaforme mobile apps. Egli è l'autore di "Programmazione Windows Store Apps con HTML, CSS e JavaScript" (due edizioni) da Microsoft Press e Blog su kraigbrockschmidt.com.

Tom Dykstra è un content developer senior presso Microsoft, concentrandosi su Microsoft Azure e ASP.NET.

Erik Reitan è un content developer senior presso Microsoft. Egli si concentra su Microsoft Azure e ASP.NET. Seguirlo su Twitter a twitter.com/ReitanErik.

Mike Wasson è un content developer presso Microsoft. Per molti anni ha documentato le API multimediali Win32. Attualmente scrive di Microsoft Azure e ASP.NET.

Grazie ai seguenti esperti tecnici per la revisione di questo articolo: Michael Collier, John de Havilland, Brady Gaster, Ryan Jones, Vijay Ramakrishnan e Pranav Rastogi