Wednesday, 27 June 2018

This tutorial will get you started with Azure service bus, dotnet core and F#

With the release of the dotnet 2.0 framework things are really getting interesting. So I tried to combine dotnet core, F# and Azure service bus. Seems reasonable at first, but as F# is quite new to me I struggled to find any tutorial. Apparently everyone is assuming that when you work in F# you'll be able to figure things out from the C# tutorials. With this tutorial that's about to change.

Create an Azure Service bus

First we need our Service Bus. Go to the azure portal
Click on the new button and search for "Service Bus" and click "Create"
Create an Azure service bus
I'll assume you know the drill to complete the wizard. Here is what I took:
Service bus namespace
Once the Service bus is created you'll find it under the resource group you defined.
Service bus
Before we can start using the service bus, you need to create a queue. I named the queue "demoqueue". You can leave the other default settings.
create queue
The last thing to do is to get the Service bus connectionstring. Click on the "Shared access policies" of the service bus (make sure you're on the service bus pane and not the queue pane). Click on the policy "RootManageSharedAccessKey" to reveal the connection string.
Service bus connection string
The Azure side is all set up.

Create a dotnet core solution

You can use Visual Studio to create .NET Core solutions, but an editor as VS Code is cross platorm and is quite fast to work with. I used VS Code to get the job done, but for the Visual Studio users we'll create a solution file as well.
Create a new directory "SbDemo" 
Next we'll create a solution file via the dotnet CLI:
dotnet new sln --name DevProtocol.Azure.SbDemo  
Create 2 applications. The SbPush will push messages on our service bus, the SbReceive will receive them.
dotnet new console -lang F# --name DevProtocol.Azure.SbDemo.SbPush  
dotnet new console -lang F# --name DevProtocol.Azure.SbDemo.SbReceive  
Add the projects to your solution file
dotnet sln add DevProtocol.Azure.SbDemo.SbPush\DevProtocol.Azure.SbDemo.SbPush.fsproj  
dotnet sln add DevProtocol.Azure.SbDemo.SbReceive\DevProtocol.Azure.SbDemo.SbReceive.fsproj  

Push messages to your Service bus

We'll start off with pushing messages to the queue. 
Open up the project with VSCode
code .  

Add the Service bus Nuget

Open your project file DevProtocol.Azure.SbDemo.SbPush.fsproj. You'll notice an ItemGroup that contains the files in your project (currently only Program.fs). We'll add a NuGet reference by adding a second ItemGroup
  <ItemGroup>
    <PackageReference Include="Microsoft.Azure.ServiceBus" Version="2.0.0" />
  </ItemGroup>
Now you can open up the build in terminal in VSCode (shortcut: CTRL+`) and restore the package(s). Once the package is restored you can get intellisense.
dotnet restore  

Adding files to the project

Add 2 new files (Sb.fs and Data.fs) to the SbPush project and register them in the DevProtocol.Azure.SbDemo.SbPush.fsproj file. Add them in the existing ItemGroup where the Program.fs file is already included. Make sure you have the same file order as defined below as he file order in F# is important:
<ItemGroup>  
    <Compile Include="Data.fs" />
    <Compile Include="Sb.fs" />
    <Compile Include="Program.fs" />
</ItemGroup>  

The data

The Data.fs file is simple. We'll use a list of integers to push to the service bus
namespace DevProtocol.Azure.SbDemo.SbPush

module Data =  
    let getData() =
        let myData = [1..10]
        myData |>
        List.map (fun x -> x + 10)

Sending messages

The Sb.fs file is a bit more complex. First we need to add the Microsoft.Azure.ServiceBus namespace
open Microsoft.Azure.ServiceBus  
In the Sb module we define our connectionstring we got from the Azure portal earlier on and the queuename
namespace DevProtocol.Azure.SbDemo.SbPush

open System.Text  
open Microsoft.Azure.ServiceBus

module Sb =  
    let connectionstring = "<your connectionstring>"
    let queuePath = "<your queuename>"
In the same Sb module we now can create a sendData method that has a list of strings as input (defined by the parameter data). First we create a queueclient with our connectionstring and queuepath.
let sendData data =  
        let queueClient = QueueClient(connectionstring, queuePath)    
The queueClient has a SendAsync method. So we'll define an async sendMessage that will print out our message to the console and then send it via the queueclient. Unlike C#, the F# methods don't have the async keyword in the method declaration, but around the async workflow. We capture the result of the async task with a "let!" instead of a normal "let". Note that an async workflow contains the return keyword.
let sendMessage (queueClient:IQueueClient) (messageText:string) =  
    async {
        printfn "%s" messageText
        let message = Message(Encoding.UTF8.GetBytes messageText)
        let! result = queueClient.SendAsync(message) |> Async.AwaitTask
        return result
    }   
The only thing we need to do now is loop over our input list (named data) and send every string via the sendMessage method and close the connection of the queueClient:
async {  
    let result =  List.iter(fun x -> sendMessage queueClient x |> Async.RunSynchronously) data
    queueClient.CloseAsync() |> Async.AwaitTask
    |> ignore
    return result
}
So the Sb.fs file looks like:
namespace DevProtocol.Azure.SbDemo.SbPush

open System.Text  
open Microsoft.Azure.ServiceBus

module Sb =  
    let connectionstring = "<your connectionstring>"
    let queuePath = "<your queuename>"

    let sendData data =
        let queueClient = QueueClient(connectionstring, queuePath)    
        let sendMessage (queueClient:IQueueClient) (messageText:string) =
            async {
                printfn "%s" messageText
                let message = Message(Encoding.UTF8.GetBytes messageText)
                let! result = queueClient.SendAsync(message) |> Async.AwaitTask
                return result
            }            
        async {
            let result =  List.iter(fun x -> sendMessage queueClient x |> Async.RunSynchronously) data
            queueClient.CloseAsync() |> Async.AwaitTask
            |> ignore
            return result
        }
Note that the sendMessage method is a function defined within the sendData function. As functions in F# are first-class citizens, we can use it to restrict the scope of a function.

Put it together

In the Program.fs file we send our data via the service bus
open System  
open DevProtocol.Azure.SbDemo.SbPush.Sb  
open DevProtocol.Azure.SbDemo.SbPush.Data

[<EntryPoint>]
let main argv =  
    printfn "Start sending messages"
    sendData (getData() |> List.map(fun x -> sprintf "%s%A" "Message" x )) |> Async.RunSynchronously
    printfn "Ended sending messages"
    0

Verify the result

Now build the DevProtocol.Azure.SbDemo.SbPush project. cd into the project folder and execute
dotnet build  
dotnet run  
The console will output Message11 till Message20. Now we can already verify if our messages arrived on the queue. Open up the azure portal and go to the queue overview. You'll notice that 10 messages arrived
Messages on the queue

Receive messages from your Service bus

Now that we can push messages on our Service bus, you might get excited to get them back.

Add the Service bus Nuget

Open up the DevProtocol.Azure.SbDemo.SbReceive.fsproj file and add the Service bus reference:
<ItemGroup>  
    <PackageReference Include="Microsoft.Azure.ServiceBus" Version="2.0.0" />
</ItemGroup>  

Add a file to the project

Add a new file (Sb.fs) to the SbPush project and register it in the DevProtocol.Azure.SbDemo.SbReceive.fsproj file. Add it in the existing ItemGroup where the Program.fs file is already included. Make sure you have the same file order as defined below as he file order in F# is important:
<ItemGroup>  
    <Compile Include="Sb.fs" />
    <Compile Include="Program.fs" />
</ItemGroup>  

Receiving messages

To receive messages we first add the Microsoft.Azure.ServiceBus namespace
open Microsoft.Azure.ServiceBus  
In the Sb module we define jus like we dit for pushing messages our connectionstring we got from the Azure portal earlier on and the queuename
namespace DevProtocol.Azure.SbDemo.SbReceive

open Microsoft.Azure.ServiceBus  
open System.Threading  
open System.Text  
open System.Threading.Tasks

module Sb =  
    let connectionstring = "<your connectionstring>"
    let queuePath = "<your queuename>"
Now we define the method to receive the data
let receiveData() =  
    let queueClient = QueueClient(connectionstring, queuePath) 
    let exceptionReceivedHandler (args:ExceptionReceivedEventArgs) =
        printfn "Got an exception: %A" args.Exception
        Task.CompletedTask
    let processMessage (message:Message) (token:CancellationToken) =
            printfn "Received message: %s" (Encoding.UTF8.GetString(message.Body))
            queueClient.CompleteAsync(message.SystemProperties.LockToken)|> Async.AwaitTask |> ignore
            Task.CompletedTask
    let msgOptions = new MessageHandlerOptions(fun x -> exceptionReceivedHandler(x))
    msgOptions.AutoComplete <- false
    let messagehandler = queueClient.RegisterMessageHandler(processMessage, msgOptions)
    messagehandler |> ignore

Put it together

From the main method in the Program.fs file we'll start listening for message.
open System  
open DevProtocol.Azure.SbDemo.SbReceive

[<EntryPoint>]
let main argv =  
    printfn "Start receiving messages"
    Sb.receiveData()
    Console.ReadLine() |> ignore
    0

Verify the result

Now build the DevProtocol.Azure.SbDemo.SbReceive project. cd into the project folder and execute
dotnet build  
dotnet run  
Now you'll receive all your messages back in the console. Go back to the portal and verify your queue messages. The count will be 0 again!

Final Thoughts

Setting up a Service bus in Azure is really a breeze. With the knowledge of some dotnet CLI commands you can quickly setup a new dotnet core project even in F#. Now there's at least one more tutorial to get you started with F# and off course dotnet core and Azure as well.

Next steps

As Andrii Chebukin pointed out in a comment to this post, you can optimise the code by using FusionTasks. 
First add a packate reference in the DevProtocol.Azure.SbDemo.SbReceive.fsproj file and the DevProtocol.Azure.SbDemo.SbPush.fsproj file
<ItemGroup>  
    <PackageReference Include="FSharp.Control.FusionTasks.FS41" Version="1.1.0" />
    <PackageReference Include="Microsoft.Azure.ServiceBus" Version="2.0.0" />
  </ItemGroup>
Now we basically can get rid of the "Async.AwaitTask" statements.
As a result a code block like
async {  
    let result =  List.iter(fun x -> sendMessage queueClient x |> Async.RunSynchronously) data
    queueClient.CloseAsync() |> Async.AwaitTask
    |> ignore
    return result
}
becomes
async {  
    let tasks = List.map(fun x -> sendMessage queueClient x) data
    let! result = Async.Parallel tasks
    do! queueClient.CloseAsync()
    return result
        }
Andril was kind enough to send me a pull request with a code update to use FusionTasks. So, definitely check out the code on GitHub.

Download

Download the code from my github.

No comments:

Post a Comment