SAFE part 0 – How it works?

In SAFE, the Client side is written in F# thanks to Fable, but you can follow different paradigms. SAFE embraces the Elm paradigm called Model-View-Update.

Model-View-Update paradigm

MVC-like patterns (MVP, MVVM…) are bidirectional and stateful. The Model layer and View layer don’t know each other and communicate through the Controller layer. For instance, if you have an application with a counter and a button “Increment”, in MVC-like pattern when you hit the button, this is treated like this:

Model-View-Update (MVU) is unidirectional and its state is immutable. The same example in MVU is treated as follows:

One of the big advantages of MVU is that it’s purely functional (pure functions, immutable data). So the evolution of the state of your application can be seen as a sequence of distinct states.

Let’s try a metaphor. Imagine you have to write all numbers from 1 to 100 on post-its.
– With a stateful approach, you’ll write one number, then erase it, then write the next number and so on. You used only one post-it but you can only see the last number.
– With an immutable approach, you write a number on a post-it, you detach it, paste it on the stack of used post-its, then write the next number on a new post-it and so on. You can see the last number on the stack, but you can also watch all the previous numbers. So if you want to know how you come to that state, you can easily look at the previous post-it.

This is a trivial example but imagine now the same approaches with a very large and complex model. If you are in the first stateful approach and you detect a value you shouldn’t have, it can be very difficult to reproduce what caused this bug. With the immutable approach, you can go back in time to see exactly when the bug appears. And due to MVU paradigm, you know which message causes this bug and what dispatched it.

Fable counter example

The following example is the default implementation of the SAFE template. I just simplified it a little bit.

Without calls to server

Let’s imagine a very simple application. Just a counter, a button to increment it and another one to decrement it:

Here’s the client code:

type Model = int
type Msg = Increment | Decrement

let init () =
  42, Cmd.none

let update (msg : Msg) (model : Model) : Model * Cmd<Msg> =
  let model' =
    match msg with
    | Increment -> x + 1
    | Decrement -> x - 1
  model', Cmd.none

let view (model : Model) (dispatch : Msg -> unit) =
  div []
    [ button [ OnClick (fun _ -> dispatch Decrement) ] [ str "-" ]
      div [] [ str model ]
      button [ OnClick (fun _ -> dispatch Increment) ] [ str "+" ]
    ]

// Program part
#if DEBUG
open Elmish.Debug
open Elmish.HMR
#endif

Program.mkProgram init update view
#if DEBUG
|> Program.withConsoleTrace
|> Program.withHMR
#endif
|> Program.withReact "elmish-app"
#if DEBUG
|> Program.withDebugger
#endif
|> Program.run

All the program part aims to launch the application using:
– the function to initialize the program (init)
– the function to update the program depending on a message (update)
– the function that represents the main view (view)

The model and the messages are trivial. Model represents the counter and Msg all the possible messages, either Increment or Decrement.

init is not complicated, it returns a tuple with the default model value and a command (Cmd.none). I’ll explain commands later.

update returns the same type as init but its purpose is to do some actions when receiving a message (like updating the model). Here, the update is only updating the model with a trivial pattern matching:
– when the message is Increment, the model is replaced by model + 1
– when the message is Decrement, the model is replaced by model - 1

view represents the HTML which will be generated depending on a model. There are important points to consider:
– almost every function representing an HTML element has 2 parameters, the first one is a list of all attributes, the second is a list of HTML elements in the content
dispatch is a function used to dispatch a message that will be treated by the update function
str is a special function used to write text

Here’s a diagram explaining how it works:

sequence diagram

With server call (client side)

Now the same case but the initial value must be fetched from the server:

type Model = int option
type Msg =
  | Increment
  | Decrement
  | Init of Result<int, exn>

module Server =
  open Shared
  open Fable.Remoting.Client

  let api : ICounterProtocol =
    Proxy.remoting<ICounterProtocol> {
      use_route_builder Route.builder
    }

let init () : Model * Cmd<Msg> =
  let model = None
  let cmd =
    Cmd.ofAsync
      Server.api.getInitCounter
      ()
      (Ok >> Init)
      (Error >> Init)
  model, cmd

let update (msg : Msg) (model : Model) : Model * Cmd<Msg> =
  let model' =
    match model,  msg with
    | Some x, Increment -> Some (x + 1)
    | Some x, Decrement -> Some (x - 1)
    | None, Init (Ok x) -> Some x
    | _ -> None
  model', Cmd.none

let show = function
| Some x -> string x
| None -> "Loading..."

let view (model : Model) (dispatch : Msg -> unit) =
  div []
    [ button [ OnClick (fun _ -> dispatch Decrement) ] [ str "-" ]
      div [] [ str (show model) ]
      button [ OnClick (fun _ -> dispatch Increment) ] [ str "+" ]
      safeComponents ]

I removed the program part because it’s the same code.

There are some differences.

First of all, Model is now a int option because when you initialize the client, the value is fetched from the server, so you don’t have any value. Just as a reminder, Option is defined as follows:

type Option<'a> =
  | Some of 'a
  | None

Same for Msg, a new message Init of Result represents the value when the call to the server has succeed (the int part) and when the call has failed (exn is an Exception). Reminder:

type Result<'ok,'error> = 
  | Ok of 'ok
  | Error of 'error

In the Server module, there’s an api value which is the interface for calling the server. I will not explain this for now, just remember the api exposes a getInitCounter function which takes a unit and returns a Async.

init now executes a command. A command is something that is executed asynchronously and dispatches a message.
Cmd.ofAsync transforms a function returning an Async into a command. It needs 4 arguments:
1. an asynchrounous function with a signature like 'input -> Async<'output>
2. an input of type 'input
3. a function with a signature like 'output -> 'msg for successful call
4. a function with a signature like exn -> 'msg for failed call

In this case:
1. Server.api.getInitCounter is the asynchrounous function, 'input is unit and 'output is int.
2. () is the unit parameter for the asynchronous function.
3. For a successful call which returns 42 (for example), the message to build is a Init (Ok 42). So the function to pass is (fun r -> Init (Ok r)), which can be written like (fun r -> r |> Ok |> Init) and finally refactored with the composition operator (Ok >> Init).
4. Same as 3 but for Error.

update is not really more complex, it just handles the cases where the application is fetching the initial value. Like init, it can also dispatch a message which will be treated by update. It’s unnecessary in this simple example, but I will use this feature in the contact application.

With server call (shared and server side)

The shared side defines 2 things:

module Route =
  let builder typeName methodName =
    sprintf "/api/%s/%s" typeName methodName

type ICounterProtocol =
  { getInitCounter : unit -> Async<int> }

Route.builder defines how the api url is built.

ICounterProtocol is a kind of interface which exposes all functions in the web service.

These 2 things are really important because it will be the key to Client-Server communication.

On the Client side, there was this code:

module Server =
  open Shared
  open Fable.Remoting.Client

  let api : ICounterProtocol =
    Proxy.remoting<ICounterProtocol> {
      use_route_builder Route.builder
    }

To simplify, it says that api is a record with a function that calls the Server depending upon the Route.builder. So when the Client side calls Server.api.getInitCounter, it will send a POST request on http://localhost:8085/api/ICounterProtocol/getInitCounter.

On the Server side, the important part is that kind of code:

let webApp =
  let server : ICounterProtocol =
    { getInitCounter = fun () -> async { return 42 } }

  remoting server {
    use_route_builder Route.builder
  }

server is the concrete implementation of ICounterProtocol and is used to build the endpoints thanks to remoting.

To sum up:
– the Shared side exposes the common interface needed for Client-Server communication
– the Server side uses the Route.builder and an implementation of the interface to generate endpoints.
– the Client side uses the Route.builder to generate an implementation of the interface to hide all the web service calls.

Conclusion

This was a pretty long post. I tried to be as clear as possible, hoping that I haven’t written something horrible.

If you have understood well this post, all the series will be easy to understand. The MVU logic with the command mechanism is really important for the rest of the series.

That’s all for the overview of the mechanisms of Fable, Elmish and SAFE. The next article will begin the contact application.

Advertisements

4 thoughts on “SAFE part 0 – How it works?”

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 )

Google+ photo

You are commenting using your Google+ 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 )

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.