The Greater Helsinki Area F# User Group - F# & Azure - FI EN

Azure Blob- ja Table Storage

Käytetään edellä tehtyä WorkerRole-projektia. Käytämme myös Windows Azure Storage -kirjastoa. (Aiemmassa versiossa tätä ohjetta käytettiin Fog-kirjastoa, mutta se ei toiminut hyvin uusimpien Azure SDK:iden kanssa.)

Ensimmäisenä on lisättävä connectionstringit Azuren storageille. Tämä tapahtuu näin:

Avaa deployment-projektin ServiceDefinition.csdef ja lisää sinne settingit: TableStorageConnectionString ja BlobStorageConnectionString. Nämä voivat emulator-ympäristössä olla tyhjät.

Tiedostojen sisällössä attribuutti name="FSharpAzure" viittaa solutionin nimeen ja schemaVersion="2013-10.2.2" käytettyyn AzureSDK:iin, tässä 2.2, mutta 2.3 olisi schemaVersion="2014-01.2.3"ja 2.4 olisi schemaVersion="2014-06.2.4".

<?xml version="1.0" encoding="utf-8"?>
<ServiceDefinition name="FSharpAzure" 
xmlns="http://schemas.microsoft.com/ServiceHosting/2008/10/ServiceDefinition"
schemaVersion="2013-10.2.2">
  <WorkerRole name="WorkerRole1" vmsize="ExtraSmall">
    <Endpoints>
      <InputEndpoint name="Endpoint1" protocol="http" port="80" localPort="80" />
    </Endpoints>
    <Imports>
      <Import moduleName="Diagnostics" />
    </Imports>
    <ConfigurationSettings>
      <Setting name="TableStorageConnectionString" />
      <Setting name="BlobStorageConnectionString" />
    </ConfigurationSettings>    
  </WorkerRole>
</ServiceDefinition>

Muokkaa myös tiedostoja ServiceConfiguration.Cloud.cscfg ja ServiceConfiguration.Local.cscfg sisältämään vastaavat settingit:

<?xml version="1.0" encoding="utf-8"?>
<ServiceConfiguration serviceName="FSharpAzure" 
xmlns="http://schemas.microsoft.com/ServiceHosting/2008/10/ServiceConfiguration"
osFamily="4" osVersion="*" schemaVersion="2013-10.2.2">
  <Role name="WorkerRole1">
    <Instances count="1" />
    <ConfigurationSettings>
      <Setting name="Microsoft.WindowsAzure.Plugins.Diagnostics.ConnectionString" 
      value="UseDevelopmentStorage=true" />
      <Setting name="TableStorageConnectionString" value="UseDevelopmentStorage=true" />
      <Setting name="BlobStorageConnectionString" value="UseDevelopmentStorage=true" />
    </ConfigurationSettings>
  </Role>
</ServiceConfiguration>

Emulaattorille kelpaa arvo value="UseDevelopmentStorage=true", mutta tuotannossa tämä connection-string on jotain muuta. Ohjeet siihen löytyy netistä.

F-Sharp skripti-tiedostojen käyttö

Näiden koodien ajaminen toimii interactive-ympäristöstä tiettyyn pisteeseen asti, mutta itse Azuren kutsukoodien suoritus ei. Voit siis lisätä koodien alkuun:

1: 
2: 
3: 
4: 
5: 
#if INTERACTIVE
#r "../packages/Fog.0.1.4.1/Lib/Net40/Fog.dll"
#r "System.Data.Services.Client.dll"
#r "Microsoft.WindowsAzure.StorageClient.dll"
#endif

Näissä on huomattava se, että NuGet on voinut hakea sinulle eri version komponentista kuin mitä tässä esimerkissä. Joten jos Visual Studio alleviivaa rivin punaisella ja valittaa, että tiedostoa ei löydy, niin tämä voi johtua siitä, että sinulla on eri polussa uudempi versio siitä. Toinen F#:ssa tyypillinen tapa on lisätä projektiin yksi tiedosto tyyppiä Script File (*.fsx), joka suoritetaan vain interactive-tyylisessä scriptaus-ajoissa, mutta jää itsestään käännöksen ulkopuolelle. Nämä #r:t toimivat myös siellä, ja projektin .fs-tiedoston voi ladata käskyillä:

1: 
2: 
#load "MyLogics.fs"
open MyLogics

Välimuisti ja asetusten haku

Tässä koodi, joka toimii sekä Blob:in että Table:n yhteydessä. Avaa logiikka"luokka" (=tiedosto) ja lisää siihen seuraava koodi:

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
module MyLogics

open System
open Microsoft.WindowsAzure.ServiceRuntime
open Microsoft.WindowsAzure.Storage

open System.Collections.Concurrent
let dict = ConcurrentDictionary() //for caching

let fetchSetting = RoleEnvironment.GetConfigurationSettingValue >> CloudStorageAccount.Parse

Käytetään memoize:a välimuistitukseen. Memoize/memoization poikkeaa normaalista cache-välimuistista siten, että välimuistitetaankin funktio, eikä vain sen parametreja.

Azure Blob Storage

Blob-storage on simppeli tietovarasto esim. tiedostoja varten. Lisää edellisen perään seuraava toiminnallisuus:

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
12: 
13: 
14: 
15: 
16: 
17: 
18: 
19: 
20: 
21: 
22: 
23: 
24: 
25: 
26: 
let blobConnection (container:string) = 
    let account = fetchSetting "BlobStorageConnectionString"
    let client = account.CreateCloudBlobClient()
    client.GetContainerReference(container.ToLower())

let uploadBlobToContainer containerName blobName (item:string) = 
    let memoize f = fun x -> dict.GetOrAdd(Some x, lazy (f x)).Force()
    let container = memoize(fun cont -> blobConnection cont) containerName
    async {
        let! ok = container.CreateIfNotExistsAsync() |> Async.AwaitTask
        let blob = container.GetBlockBlobReference(blobName)
        let enc = System.Text.Encoding.ASCII.GetBytes(item)
        use ms = new System.IO.MemoryStream(enc, 0, enc.Length)
        do! blob.UploadFromStreamAsync ms |> Async.AwaitIAsyncResult |> Async.Ignore
    }

let addToBlob = uploadBlobToContainer "testContainer" "testBlob"

open System
open FSharp.Data
let demoData =
    let ``scientist of the day `` = 
        FreebaseData.GetDataContext().Commons.Computers.``Computer Scientists``
        |> Seq.skip DateTime.Now.DayOfYear //
        |> Seq.head
    "Scientist of the day: " + ``scientist of the day ``.Name

Nyt voit lisätä kutsun tähän WorkerRole.fs:n wr.OnStart() -metodissa (ennen kutsua base.OnStart()):

1: 
MyLogics.demoData |> MyLogics.addToBlob |> Async.RunSynchronously

Kun ajat softan (F5), niin Server Explorer:iin (ei Solution Explorer) on (refresh:in jälkeen) ilmestynyt seuraava blobi, jonka voit tupla-klikata auki, jonka blob-listasta voit taas tupla-klikata itse tiedot auki:

Azure Table Storage

Azure Table Storage on NoSQL-henkinen tietovarasto.

Ohessa koodiesimerkki sen käyttöön:

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
12: 
13: 
14: 
15: 
16: 
17: 
18: 
19: 
20: 
21: 
22: 
23: 
24: 
25: 
26: 
27: 
28: 
29: 
30: 
31: 
32: 
33: 
34: 
35: 
36: 
37: 
38: 
39: 
40: 
41: 
42: 
43: 
44: 
let ``Azure dvd table`` = "Dvd"

[<Measure>]
type stars

open Microsoft.WindowsAzure.Storage.Table

type MyDvdEntity(partitionKey, rowKey, name, rating) = 
    inherit TableEntity(partitionKey, rowKey)
    new(name, rating) = MyDvdEntity("defaultPartition", System.Guid.NewGuid().ToString(), name, rating)
    new() = MyDvdEntity("", 0<stars>)
    member val Name = name with get, set
    member val Rating = rating with get, set

let tableConnection tableName = 
    let account = fetchSetting "TableStorageConnectionString"
    let client = account.CreateCloudTableClient()
    client.GetTableReference(tableName)

let doAction tableName operation = 
    let memoize f = fun x -> dict.GetOrAdd(Some x, lazy (f x)).Force()
    let table = memoize(fun tb -> tableConnection tb) tableName
    async {
        let! created = table.CreateIfNotExistsAsync() |> Async.AwaitTask
        return! table.ExecuteAsync(operation) |> Async.AwaitTask
    }

let addDvd name rating = 
    let dvd = MyDvdEntity(
                PartitionKey = "myPartition",
                RowKey = System.Guid.NewGuid().ToString(),
                Name = name,
                Rating = rating
              )
    dvd, dvd
    |> TableOperation.Insert //Insert, Delete, Replace, etc.
    |> doAction ``Azure dvd table``
    |> Async.RunSynchronously

let updateDvd dvd = 
    dvd |> TableOperation.Replace |> doAction ``Azure dvd table`` |> Async.RunSynchronously

let deleteDvd dvd = 
    dvd |> TableOperation.Delete |> doAction ``Azure dvd table`` |> Async.RunSynchronously

...ja vastaavasti tätä kutsutaan WorkerRole.fs-tiedostosta, kutsu OnStart-metodiin, ennen base.OnStart:ia, esim:

1: 
2: 
3: 
let dvd, result = MyLogics.addDvd "Godfather" 4<stars>
dvd.Rating <- 5<MyLogics.stars>
let result2 = MyLogics.updateDvd dvd

Varsinainen Table Storage:n kyselykieli on varsin suppea, eikä uusi Table Service Layer tue LINQ:a, joten kyselyt pitää tehdä joko TableOperation.Retrieve-metodin kautta tai rakentamalla erikseen TableQuery ja suorittamalla se ExecuteQuery-metodilla.

Takaisin valikkoon

namespace System
namespace Microsoft
namespace Microsoft.WindowsAzure
namespace System.Collections
namespace System.Collections.Concurrent
val dict : keyValuePairs:seq<'Key * 'Value> -> System.Collections.Generic.IDictionary<'Key,'Value> (requires equality)

Full name: Microsoft.FSharp.Core.ExtraTopLevelOperators.dict
Multiple items
val string : value:'T -> string

Full name: Microsoft.FSharp.Core.Operators.string

--------------------
type string = System.String

Full name: Microsoft.FSharp.Core.string
union case Option.Some: Value: 'T -> Option<'T>
val async : AsyncBuilder

Full name: Microsoft.FSharp.Core.ExtraTopLevelOperators.async
Multiple items
type Async
static member AsBeginEnd : computation:('Arg -> Async<'T>) -> ('Arg * AsyncCallback * obj -> IAsyncResult) * (IAsyncResult -> 'T) * (IAsyncResult -> unit)
static member AwaitEvent : event:IEvent<'Del,'T> * ?cancelAction:(unit -> unit) -> Async<'T> (requires delegate and 'Del :> Delegate)
static member AwaitIAsyncResult : iar:IAsyncResult * ?millisecondsTimeout:int -> Async<bool>
static member AwaitTask : task:Task<'T> -> Async<'T>
static member AwaitWaitHandle : waitHandle:WaitHandle * ?millisecondsTimeout:int -> Async<bool>
static member CancelDefaultToken : unit -> unit
static member Catch : computation:Async<'T> -> Async<Choice<'T,exn>>
static member FromBeginEnd : beginAction:(AsyncCallback * obj -> IAsyncResult) * endAction:(IAsyncResult -> 'T) * ?cancelAction:(unit -> unit) -> Async<'T>
static member FromBeginEnd : arg:'Arg1 * beginAction:('Arg1 * AsyncCallback * obj -> IAsyncResult) * endAction:(IAsyncResult -> 'T) * ?cancelAction:(unit -> unit) -> Async<'T>
static member FromBeginEnd : arg1:'Arg1 * arg2:'Arg2 * beginAction:('Arg1 * 'Arg2 * AsyncCallback * obj -> IAsyncResult) * endAction:(IAsyncResult -> 'T) * ?cancelAction:(unit -> unit) -> Async<'T>
static member FromBeginEnd : arg1:'Arg1 * arg2:'Arg2 * arg3:'Arg3 * beginAction:('Arg1 * 'Arg2 * 'Arg3 * AsyncCallback * obj -> IAsyncResult) * endAction:(IAsyncResult -> 'T) * ?cancelAction:(unit -> unit) -> Async<'T>
static member FromContinuations : callback:(('T -> unit) * (exn -> unit) * (OperationCanceledException -> unit) -> unit) -> Async<'T>
static member Ignore : computation:Async<'T> -> Async<unit>
static member OnCancel : interruption:(unit -> unit) -> Async<IDisposable>
static member Parallel : computations:seq<Async<'T>> -> Async<'T []>
static member RunSynchronously : computation:Async<'T> * ?timeout:int * ?cancellationToken:CancellationToken -> 'T
static member Sleep : millisecondsDueTime:int -> Async<unit>
static member Start : computation:Async<unit> * ?cancellationToken:CancellationToken -> unit
static member StartAsTask : computation:Async<'T> * ?taskCreationOptions:TaskCreationOptions * ?cancellationToken:CancellationToken -> Task<'T>
static member StartChild : computation:Async<'T> * ?millisecondsTimeout:int -> Async<Async<'T>>
static member StartChildAsTask : computation:Async<'T> * ?taskCreationOptions:TaskCreationOptions -> Async<Task<'T>>
static member StartImmediate : computation:Async<unit> * ?cancellationToken:CancellationToken -> unit
static member StartWithContinuations : computation:Async<'T> * continuation:('T -> unit) * exceptionContinuation:(exn -> unit) * cancellationContinuation:(OperationCanceledException -> unit) * ?cancellationToken:CancellationToken -> unit
static member SwitchToContext : syncContext:SynchronizationContext -> Async<unit>
static member SwitchToNewThread : unit -> Async<unit>
static member SwitchToThreadPool : unit -> Async<unit>
static member TryCancelled : computation:Async<'T> * compensation:(OperationCanceledException -> unit) -> Async<'T>
static member CancellationToken : Async<CancellationToken>
static member DefaultCancellationToken : CancellationToken

Full name: Microsoft.FSharp.Control.Async

--------------------
type Async<'T>

Full name: Microsoft.FSharp.Control.Async<_>
static member Async.AwaitTask : task:System.Threading.Tasks.Task<'T> -> Async<'T>
namespace System.Text
type Encoding =
  member BodyName : string
  member Clone : unit -> obj
  member CodePage : int
  member DecoderFallback : DecoderFallback with get, set
  member EncoderFallback : EncoderFallback with get, set
  member EncodingName : string
  member Equals : value:obj -> bool
  member GetByteCount : chars:char[] -> int + 3 overloads
  member GetBytes : chars:char[] -> byte[] + 5 overloads
  member GetCharCount : bytes:byte[] -> int + 2 overloads
  ...

Full name: System.Text.Encoding
property System.Text.Encoding.ASCII: System.Text.Encoding
System.Text.Encoding.GetBytes(s: string) : byte []
System.Text.Encoding.GetBytes(chars: char []) : byte []
System.Text.Encoding.GetBytes(chars: char [], index: int, count: int) : byte []
System.Text.Encoding.GetBytes(chars: nativeptr<char>, charCount: int, bytes: nativeptr<byte>, byteCount: int) : int
System.Text.Encoding.GetBytes(s: string, charIndex: int, charCount: int, bytes: byte [], byteIndex: int) : int
System.Text.Encoding.GetBytes(chars: char [], charIndex: int, charCount: int, bytes: byte [], byteIndex: int) : int
namespace System.IO
Multiple items
type MemoryStream =
  inherit Stream
  new : unit -> MemoryStream + 6 overloads
  member CanRead : bool
  member CanSeek : bool
  member CanWrite : bool
  member Capacity : int with get, set
  member Flush : unit -> unit
  member GetBuffer : unit -> byte[]
  member Length : int64
  member Position : int64 with get, set
  member Read : buffer:byte[] * offset:int * count:int -> int
  ...

Full name: System.IO.MemoryStream

--------------------
System.IO.MemoryStream() : unit
System.IO.MemoryStream(capacity: int) : unit
System.IO.MemoryStream(buffer: byte []) : unit
System.IO.MemoryStream(buffer: byte [], writable: bool) : unit
System.IO.MemoryStream(buffer: byte [], index: int, count: int) : unit
System.IO.MemoryStream(buffer: byte [], index: int, count: int, writable: bool) : unit
System.IO.MemoryStream(buffer: byte [], index: int, count: int, writable: bool, publiclyVisible: bool) : unit
static member Async.AwaitIAsyncResult : iar:System.IAsyncResult * ?millisecondsTimeout:int -> Async<bool>
static member Async.Ignore : computation:Async<'T> -> Async<unit>
namespace Microsoft.FSharp.Data
module Seq

from Microsoft.FSharp.Collections
val skip : count:int -> source:seq<'T> -> seq<'T>

Full name: Microsoft.FSharp.Collections.Seq.skip
val head : source:seq<'T> -> 'T

Full name: Microsoft.FSharp.Collections.Seq.head
static member Async.RunSynchronously : computation:Async<'T> * ?timeout:int * ?cancellationToken:System.Threading.CancellationToken -> 'T
Multiple items
type MeasureAttribute =
  inherit Attribute
  new : unit -> MeasureAttribute

Full name: Microsoft.FSharp.Core.MeasureAttribute

--------------------
new : unit -> MeasureAttribute
Multiple items
type Guid =
  struct
    new : b:byte[] -> Guid + 4 overloads
    member CompareTo : value:obj -> int + 1 overload
    member Equals : o:obj -> bool + 1 overload
    member GetHashCode : unit -> int
    member ToByteArray : unit -> byte[]
    member ToString : unit -> string + 2 overloads
    static val Empty : Guid
    static member NewGuid : unit -> Guid
    static member Parse : input:string -> Guid
    static member ParseExact : input:string * format:string -> Guid
    ...
  end

Full name: System.Guid

--------------------
System.Guid()
System.Guid(b: byte []) : unit
System.Guid(g: string) : unit
System.Guid(a: int, b: int16, c: int16, d: byte []) : unit
System.Guid(a: uint32, b: uint16, c: uint16, d: byte, e: byte, f: byte, g: byte, h: byte, i: byte, j: byte, k: byte) : unit
System.Guid(a: int, b: int16, c: int16, d: byte, e: byte, f: byte, g: byte, h: byte, i: byte, j: byte, k: byte) : unit
System.Guid.NewGuid() : System.Guid
val set : elements:seq<'T> -> Set<'T> (requires comparison)

Full name: Microsoft.FSharp.Core.ExtraTopLevelOperators.set

Creative Commons -copyright Tuomas Hietanen, 2014, thorium(at)iki.fi, Creative Commons