[DajSięPoznać#1] F# +WebAPI, NLog, xUnit

Początki

Nie jestem programistą F#, ale chciałbym go poznać. Mądrzy ludzie mówią, że dobry programista powinien raz w roku uczyć się nowego języka. DSP jest dobrą okazją do mocnej nauki F# i udowodnienia, że backend dla aplikacji webowych może być napisany w języku funkcyjnym.

Początek jest prosty. Instalujemy w VS dodatek z galelerii o nazwie F# MVC 5, dzięki któremu możemy od razu założyć projekt WebAPI z gotowym szablonem w F#.

vs

 

REST

Restowe API w takim projekcie jak mój jest oczywiste. A REST w F# ? Poniżej Controller z czterema najważniejszymi metodami: GET, POST, PUT, DELETE

[<CLIMutable>]
type Item = {name: string; id: int }

type ItemsController() =
    inherit ApiController()
    static let values = new List<Item>([{name="car"; id=0}; {name="phone"; id=1}]) //mutable

    member self.Get() = values.ToArray()

    member self.Get(id) : IHttpActionResult =
        if id > values.Count - 1 then
            self.BadRequest() :> _
        else self.Ok(values.[id]) :> _

    member self.Post(item : Item) : IHttpActionResult =
        let newItem = {name=item.name; id= values.Count}
        values.Add(newItem)
        self.Created(String.Format("/api/items/{0}/", id), newItem) :>_

    member self.Delete(id) : IHttpActionResult = 
        let item = values.[id]
        values.Remove(item) |> ignore
        self.Ok() :>_

    member self.Put(item: Item, id) : IHttpActionResult = 
        if id < values.Count then 
            values.First(fun d -> d.id = id) |> values.Remove |> ignore        
        values.Add(item)
        self.Ok() :>_

 

Tak powstał mój pierwszy REST-owy kontroler w F#. Całkiem fajny, krótki. Problem polega na tym, że został stworzony starą dobrą metodą “programowania przez zgadywanie”.

Jeszcze nigdy nikt za daleko tą metodą nie dojechał, więc nadszedł czas na porządną naukę F#. Z każdym przeczytanym rozdziałem “Programming in F# 3.0” narastał zachwyt nad tym językiem, więc to na pewno nie jest ostatni post o F# na tym blogu.

NLog

Projekt dopiero się rodzi, więc to dobry moment na wpięcie loggera. Filip Wojcieszyn pisał na swoim blogu o tym, jak spiąć NLog-a z WebAPI, ja napisałem FSharpową wersję jego klasy Logger.

namespace Byteville.Core
open NLog
open System.Web.Http.Tracing
open System.Collections.Generic
open System
open System.Net.Http
open System.Text

type NLogger() =    
    static let mappingDictionary = new Dictionary<TraceLevel, Action<string>>()
    static do
        let classLogger = LogManager.GetCurrentClassLogger()
        mappingDictionary.Add(TraceLevel.Info, Action<string> classLogger.Info)
        mappingDictionary.Add(TraceLevel.Debug, Action<string> classLogger.Debug)
        mappingDictionary.Add(TraceLevel.Error, Action<string> classLogger.Error)
        mappingDictionary.Add(TraceLevel.Fatal, Action<string> classLogger.Fatal)
        mappingDictionary.Add(TraceLevel.Warn, Action<string> classLogger.Warn)

    static let loggingMap = new Lazy<Dictionary<TraceLevel, Action<string>>>(fun () -> mappingDictionary)

    member x.Log (record: TraceRecord) = 
            let message = new StringBuilder();
            if record.Request <> null then
                if record.Request.Method <> null then
                    message.Append(record.Request.Method) |> ignore
                if record.Request.RequestUri <> null then
                    message.Append(" ").Append(record.Request.RequestUri) |> ignore

            if not(String.IsNullOrWhiteSpace record.Category) then
                message.Append(" ").Append(record.Category) |> ignore
            
            if not(String.IsNullOrWhiteSpace record.Operator) then
                message.Append(" ").Append(record.Operator).Append(" ").Append(record.Operation) |> ignore

            if not(String.IsNullOrWhiteSpace record.Message) then
                message.Append(" ").Append(record.Message) |> ignore

            if record.Exception <> null && 
                not(record.Exception.GetBaseException().Message |> String.IsNullOrWhiteSpace) then
                message.Append(" ").Append(record.Exception.GetBaseException().Message) |> ignore
            
            loggingMap.Value.[record.Level].Invoke(message.ToString())

    interface ITraceWriter with
        member x.Trace (request: HttpRequestMessage, category: string, level: TraceLevel, traceAction: Action<TraceRecord>)  =
            if level <> TraceLevel.Off then
                let record = new TraceRecord(request, category, level)
                traceAction.Invoke(record)
                x.Log(record)

 

Testy

Co tu jeszcze można w takim pierwszym poscie (poście, postcie  ?) napisać ? Można dodać projekt z testami i zobaczyć jak to wygląda w FSharpie. Żaden problem. Styl Arrange – Act -Assert wymusza trochę bardziej proceduralny kod, ale tak też można przecież w F# pisać.

type ItemsControllerTests() = 

    [<Fact>]
    member x.When_Removed_Element_Expect_To_Retrieve_One_Element_Less() =         
        let controller = new ItemsController()
        let count = controller.Get().Length

        controller.Delete(0) |> ignore

        Assert.Equal(count - 1, controller.Get().Length)

    [<Fact>]
    member x.When_Added_Element_Expect_To_Retrieve_One_Element_More() = 
        let controller = new ItemsController()
        let count = controller.Get().Length
        let item = {name="test"; id=3}

        controller.Post(item) |> ignore

        Assert.Equal(count + 1, controller.Get().Length)

 

Podsumowanie

Projekt wystartował. W F# ciekawie będzie pisać REST-owe API. Widać, że ten język pasuje do problemów, które chcę rozwiązać w Byteville. Znając mechanizmy CLR można łatwo odnaleźć się w nowym świecie i w kolejnych etapach trzeba będzie użyć to, co najlepsze w języku !

 

 

Advertisements

One thought on “[DajSięPoznać#1] F# +WebAPI, NLog, xUnit

  1. Pingback: [DajSięPoznać] Podsumowanie | When the smoke is going down

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s