October 2019
Volume 34 Number 10
[F#]
SAFE Stack: Functional Web Programming for .NET Core
As a longtime .NET developerwho’s spent years in the C# world, I was used to working with shrink-wrapped Web frameworks designed for C#, such as the ubiquitous ASP.NET MVC and Web API frameworks.
When I started using F# a few years ago, I gained access to a flexible, powerful and pragmatic language that ran on a framework I already knew, and opened the door to a much more open and dynamic ecosystem than I was used to. However, working with Web applications was an uncomfortable experience: The server side meant either using ASP.NET, and trying to shoehorn F# into an object-oriented (OO) framework—or giving up ASP.NET completely and using a totally different Web server. Meanwhile, working on the client invariably meant using JavaScript, throwing the type system away completely and needing to upskill to deal with its unique “features.” Moreover, deploying F# applications into Azure had no support from Microsoft and little or no documentation on how to do this yourself.
The SAFE Stack changes all that. This open source stack allows you to write F# Web apps without needing to fit your code into an OO framework or library or require you to be an expert in CSS or HTML to create rich, compelling client-side Web applications. The default SAFE stack is made up of the following four components:
- Saturn: A lightweight F# library that sits on top of ASP.NET Core to provide a high-performance and reliable Web server.
- Azure: Microsoft’s cloud platform offering, used for hosting and platform services.
- Fable: A library that allows seamless interop between F# and JavaScript in the browser.
- Elmish: A library that allows you to write UIs that fit into functional programming (FP) best practices rather than, for example, MVVM or MVC.
Let's start by looking at the SAFE stack from a programming model point of view, looking at the three core libraries.
Why F#?
Your first reaction on reading this might be: “Why bother with F#?” Or, perhaps, “F# for Web programming? I thought F# was just for math and science!” The truth is, F# is a general-purpose programming language that runs on .NET and .NET Core—just like C#—and can work with the entire NuGet ecosystem of libraries, with excellent interop with C#.
The difference is that where C# has language features that encourage a style of programming that leads to stateful, object-oriented, imperative and statement-based programming, F# leads to writing stateless, function-oriented, declarative and expression-based applications.
So while you can implement the exact same types of applications in both languages, such as line-of-business Web applications that use a SQL Server database, or highly scalable back-end services hosted in Azure, the way you “join the dots” and orchestrate calls and errors to .NET methods, NuGet packages and external services is different.
Indeed, C# already has some FP features, primarily those introduced in C#3 that revolve around LINQ—and that’s just the beginning. There’s a good reason FP-esque features are cropping up in many languages that have OO roots, because they lead to fewer bugs and easier-to-read code. So, if you enjoy using tuples, switch expressions and non-nullability, F# will almost certainly be a better fit—they’re the “default” way of writing code.
Enough about F#. Let's start by looking at the SAFE stack from a programming model point of view, looking at the three core libraries.
Saturn: Functional Programming on the Server
As you probably know, especially in today’s world of distributed applications, microservices and containers, writing stateless Web applications often leads to simpler, more scalable systems. In fact, if you think about Web programming fundamentals, you’ll see that the entire HTTP model is actually akin to a simple stateless function definition: an input over HTTP—such as a GET request to a specific URI—which returns an HTTP response code and some payload—for example, 200 and some JSON data; there’s nothing intrinsically OO about it.
Of course, when you look at ASP.NET, you often think of things like controller classes, dependency injection and so on, but ultimately those are just layers wrapped on top of this simple model. Saturn takes a different approach to a standard ASP.NET application by creating a lightweight programming paradigm that allows you to directly tie HTTP endpoints into functions that return data—in other words, matching the HTTP input/output model.
Here’s a look at the “Hello, world!” for Saturn. I can fit the entire program into a single code snippet because Saturn wraps all the ASP.NET boilerplate for you and lets you focus on routing and business logic:
let myRoutes = router {
get "/api/foo" (text "Hello, world!")
}
let app = application {
use_router myRoutes
}
run app
As you can see, Saturn hooks up GET requests to the “/api/foo” endpoint to a code block that simply returns the text, “Hello, world!”. The nice thing about Saturn is that while it doesn’t force you to get involved with the whole ASP.NET pipeline, it doesn’t stop you from doing it, either. If fact, you can easily access the ASP.NET context object and seamlessly make use of all of the ASP.NET Core-compatible NuGet packages.
Saturn makes use of some F# smarts to create a succinct DSL for writing and composing routes such as get and post functions that map to the equivalent HTTP verbs, and uses simple helper functions like text, json and xml to quickly create results that set the appropriate values on the HTTP response.
Here’s a sample that illustrates how simple routing can be using several additional powerful features of Saturn and F#:
let loadOrderDetails (customerId:int) =
{| OrderCount = 10
CustomerName = "Fred Smith" |} // loading real data elided...
let orderRoutes = router {
getf "/customer/%i" (loadOrderDetails >> json)
put "/customer" saveOrderDetails // etc.
}
let topLevelRoutes = router {
get "/api/foo" (text "Hello, world!")
forward "/api/orders" orderRoutes
}
Here are some of the points to note from this code snippet:
Route Composition: orderRoutes is “composed” into topLevelRoutes using the forward keyword, so that all routes in orderRoutes are automatically prefixed with /api/orders.
Type safe parameterization: Using F# string placeholder support, %i indicates a route should supply a number, such as api/orders/customer/10, to load orders for customer 10.
Seamless function composition: Using the >> (compose) operator, I “plug together” the loadOrderDetails and json functions to return a record with the order count and customer name, before converting it into JSON.
When you combine Saturn’s more powerful features, such as controllers, with the powerful F# type system and excellent data capabilities, such as type providers, on top of the rock-solid ASP.NET Core platform, you can create high-performing, reliable, data-centric Web APIs incredibly quickly.
Life in the Browser with Fable
The browser is where it’s at these days, and you’d be hard pushed to find any so-called “full-stack” developer roles that don’t require proficiency in JavaScript, which, despite its many shortcomings, is now the de facto standard for client-side Web programming. Fable is SAFE’s gateway to writing and executing standard F# in the browser without leaving the JavaScript ecosystem.
Why would you want to do this? Because F# lets you take full advantage of its powerful features—from a compiler that can trap types of errors that many other languages can’t, to providing a simple-to-use functional programming model—whilst still letting you take advantage of JavaScript libraries and tools.
There’s even a page on the Fable Web site (fable.io/repl) that lets you experiment and see the JavaScript that gets generated by Fable from F# as you type (see Figure 1)!
Figure 1 The Fable REPL
Fable is much more than just a simple “transpiler” for F# to JavaScript: It allows you to “hook into” the standard JavaScript ecosystem using well-known tools such as Babel and WebPack to write standard F# that can interact with the JavaScript world. Rather than a closed framework or environment, Fable is more like a gateway into the JavaScript world. Fable doesn’t try to reinvent the wheel—for example, it doesn’t actually emit JS itself, but uses the well-known Babel tool to do this—meaning that the emitted JavaScript is high-quality and a known quantity. The JS world has a huge ecosystem and set of libraries on top of which, just as Saturn does with the ASP.NET world, you can interact with using idiomatic F#.
Unlike, for example, Web Assembly, which is more of a “sandbox” inside the browser, Fable applications live inside the JavaScript ecosystem, so you can naturally use any existing third-party JavaScript library out there from F#. In effect, Fable applications aren’t F# applications running on .NET and the Common Language Runtime (CLR), they’re F# applications running in the browser, using NPM packages and Web tools in the JavaScript ecosystem.
Fable also comes out-of-the-box with type-safe wrappers around core HTML and Web concepts (such as DOM, Web storage and geolocation), as well as a set of ready-made wrappers for common JavaScript libraries. You can also make your own; Fable adds F# extensions that make interop simple, and writing simple F# wrappers is quick and easy to do—or you can generate them from TypeScript DefinitelyTyped packages.
Elmish and the MVU Pattern
Even the JavaScript integration of Fable isn’t enough to write complex Web applications, however. What’s needed is some sort of “pattern” to write applications that developers can follow. Libraries and frameworks such as AngularJS, Vue and React provide such patterns for JavaScript developers. Unfortunately, most of them are heavily influenced by OO patterns, which means FP developers often have to shoehorn their code into a different way of coding.
Thankfully, there’s a better way. The Elm programming language came up with a great FP-friendly paradigm for writing UIs, which has since been adopted in the F# world as the Model-View-Update (or MVU) pattern. There are now .NET “Elmish” libraries for Web, Windows Presentation Foundation, console and (thanks to Microsoft’s excellent Fabulous package) even Xamarin applications. Figure 2 illustrates the MVU pattern at a high level:
Figure 2 The Model-View-Update Pattern
Let’s see how this translates into code! Figure 3 shows a simple MVU application that allows you to press a button to increment a counter displayed on the page.
Figure 3 A Simple MVU Application
/// Your data model
type Model =
{ Counter : int }
/// The different types of messages
type Msg =
| Increment
/// Given the current model and a message, give back an updated model
let update msg model =
match msg with
| Increment -> { Counter = model.Counter + 1 }
/// Generates the view for the supplied model
let view model dispatch =
div [ Class "main-container" ] [
span [ ] [ str (sprintf "Current value: %i" model.Counter) ]
button [ OnClick (fun e -> dispatch Increment) ] [ str "Increment" ]
]
There are a few moving parts here, so let’s take it step-by-step:
You define a “model”: This is a representation of the state of your application; all the data you want to display or work with in the application goes here. In F#, this is best achieved using a record (think of this as a POCO, but as a first-class citizen of the language). In this case, I’m recording the current counter value to display.
You define the “messages” that can occur: Here, I just define one—the event that will fire whenever someone clicks the button to increment the counter value. In F#, instead of using class-based inheritance, you use a discriminated union to model the “is-a” relationship. Every time you create a new message type, you add it to the Message type.
You handle all messages: The update function is responsible for taking the current model and a single message, and giving back a new version of the model with any changes applied. Note that you don’t mutate the existing model; you copy and update the existing model with a new version, which fits perfectly with FP standard practice. You also use pattern matching to safely “unwrap” the message into the Increment message type.
You design your View: The view function takes in the current model, and creates some form of “view” that will be rendered to the screen. In this case, SAFE uses a sweet library that provides a strongly typed DSL over the React framework.
Let’s dig a little deeper into Elmish now, by taking note of some additional interesting things in the previous sample Elmish.
Strongly typed: As you can see in Figure 4, functions and types for React elements such as div and input, and events such as OnClick, are all strongly typed with full IntelliSense.
Figure 4 Type Safe IntelliSense for the Browser
Lightweight syntax: F# can easily represent a hierarchical view of HTML-like elements. For example, F# has language support for lists—two bracket characters “[ ]” represent a generic list of elements.
Handling user input: The use of the dispatch function allows you to send messages from the UI back into the update function, completing the MVU loop.
Using React: Aside from being a rock-solid and performant library created by Facebook, one of the reasons Elmish sits on top of React is that it provides a highly efficient “diff engine” that can rapidly compare two “virtual” views and update just the HTML elements that have been modified. It’s this diff engine that means Elmish can use immutable models and views, but still have excellent performance.
A Word on Pattern Matching
C# 8.0 has introduced a variant of pattern matching with switch expressions. It’s a pretty good implementation, although it doesn’t support one of the key features that F# discriminated unions give, called exhaustive matching. For example, if I were to define a new Decrement message to reduce the counter value, the compiler would immediately tell me where I needed to “fill in” the blanks until it was sure that I had accounted for all possible messages, as shown in Figure 5.
Figure 5 Exhaustive Pattern Matching in Action
This kind of exhaustive matching is one of the reasons F# is such a fun and enjoyable experience—it really helps you to do the “right” thing from the get-go, meaning much less time fighting in the debugger later on.
Value Adds with SAFE
Now that you’ve seen the different components that make up the default SAFE stack, let’s look at some of the other benefits that might not be apparent yet.
Code sharing: Because Fable does such an excellent job writing JavaScript from F#, you can easily share code between client and server—from simple data types to entire modules and functions. For example, you can seamlessly share data contracts between client and server (rather than, for example, JSON or TS types and C# classes) and perform common validation on the client and server, as well as start to make more complex client applications without worrying about increased complexity.
Cloud support: The SAFE stack has great support for different cloud hosting providers. For example, on the Azure front there’s built-in support for Azure deployments through ARM templates, as well as an Azure Storage-type provider that makes access to your Azure Storage data incredibly easy. If you want Docker or GCP support, that’s included out-of-the-box, as well. Figure 6 shows a sample search application using Azure Search and Storage and is hosted on the Azure App Service.
Figure 6 A Rich Data-Driven Application Using Azure Services
Extensible model: SAFE is more than just a bundled “package” of different technologies; there’s a growing ecosystem around it, including libraries that allow seamless client-server interaction through contracts, reactive push-based messaging libraries and WebSocket abstractions.
Debugging support: SAFE changes how you work with F# Web applications. On the server you can use tools like dotnet watch to provide a rapid loop for code changes, but client apps go one step further and take advantage of hot module reloading to allow you to make changes to running applications without restarting, at all! And, thanks to some of the smarts of Fable and the JavaScript community, you can even debug both the client and server of your application directly from a single IDE—setting breakpoints and viewing stack traces across both .NET and JavaScript runtimes, both in F#.
Getting Started with SAFE
How do you get started? Here are a few options to get going:
The SAFE Template If you want to dive right in and create a new “empty” SAFE app today, there’s a full dotnet project template that has all the bells and whistles, such as generation of ARM templates for Azure, Docker support and so on. There are a couple of prerequisites (bit.ly/2Z8fg0k), but once they’re taken care of you can create a new SAFE app like so:
dotnet new -i SAFE.Template
dotnet new SAFE
Open the folder with VS Code, go to the Debug panel, select Debug SAFE App and press F5. After a short delay while all the JavaScript dependencies are downloaded, a basic SAFE application will start in a Chrome instance.
You can now experiment with the application, creating breakpoints using F9 or clicking in the gutter. Check out how in Figure 7 the counter value in the Local panel shows the value of the counter (42).
Figure 7 Unified Client and Server Debugging
Try the SAFE Dojo You’ll find an introductory “dojo” exercise at bit.ly/2TNcixh that introduces you to the different components, starting from a slightly larger “shell” of an application and a set of exercises that you need to complete. It’s takes around 60 to 90 minutes and is well worth doing.
Find out More There’s also a comprehensive set of documentation and guidance on the SAFE homepage, safe-stack.github.io/docs. From there, you can find user group talks in an area near you; contact the team through social media; and ask questions or reach out to one of the professional organizations that offer SAFE Stack training and consulting.
I hope this article has you excited and interested in trying out both F# and the SAFE Stack—have fun!
Isaac Abraham is a .NET developer, trainer and the director of Compositional IT. He’s also a Microsoft MVP for his contributions to functional programming and the cloud.
Thanks to the following Microsoft technical expert for reviewing this article: Phillip Carter