Backend
This section will provide an overview of CloudSeed’s App backend written in F# / .NET.
The App Backend is the core of CloudSeed. This is where all the logic heavy lifting takes place and thus is the main value driver of the CloudSeed boilerplate.
The Backend provides:
- Language: F#
- Web Server: Giraffe on ASP.NET
- Data Layer:
- Testing: XUnit (with FsUnit)
The linked docs for each library are the de facto place for information on how to utilize it. CloudSeed aims to be simple so we make as few abstractions as possible to limit unnecessary complexity.
We’ll give an overview of each domain, the general approach, and walkthroughs of common operations here. This should give a high level understanding of how it works and provide a jumping off point for further research.
Backend: Web Server
The core part of the App is Program.fs
. This is the App’s composition root where we read in configuration, build up our routes and ServiceTree, and start the webserver.
The Web Server runs on Giraffe - a light-weight, scalable server that has great devx, good support, and all the power of ASP.NET.
For a quick overview of Giraffe Web Servers, see: Build a simple F# web API with Giraffe
Routing
We use Giraffe’s routing. All routes are built (and their dependencies injected) in Routs.fs
References:
Backend: Data
Most services will need a way to store, access, and maintain data so we provide a pre-configured setup (and examples) for performing each - simply and scalably. CloudSeed aims to be simple so we use libraries with good support and minimal “magic”.
In the name of simplicity we also assume you will use a standard SQL / Relational persistence system and that Postgres is the most common choice so this is what CloudSeed is setup to serve. This can be easily changed, it’s just not configured out of the box.
ORM (Object Relational Mapper)
You could write raw *QL
for data storage / access. That’s fine. But for most services / developers an ORM makes this process easier and safer.
As such, we tap Dapper for this job - it’s simple, super fast, and utilized by a large chunk of .NET land. It makes it easy to get as close to SQL as you want while taking away a lot of the tedium involved in that and has great library support for most common SQL DBs.
References:
- Learn Dapper
- Working examples of Reading / Writing with Dapper are available in the CloudSeed repo, see: CounterDataRepository.fs
Migrations
“Migrations” is a term roughly used to refer to maintaining your DB schema. For example, if you decide to add / remove / rename a column / field of a data type in your persistence, you’ll probably need to update your schema and / or have a way to update your objects so they are consistent.
There are a few ways to go about this, most popularly:
- Full Migrations - Run a big script to update your data / schema
- Lazy Migrations - Deal with inconsistencies as you read your data out (lazily update)
You can do whichever one you want but Full Migrations tends to be the one which requires a bit more setup up front. In a lot of .NET land people use EF for ORM / migrations but we find that EF is a bit too magical and clashes with idiomatic F# so we avoid it.
Instead, we utilize DBUp. It’s about as simple as you can get and has good support for various DBs / from the community.
- All DBUp Database Upgrade Scripts are found in DatabaseUpgradeScripts
- These scripts will be executed in alphabetical order - it’s recommended that you use a naming convention like
DBUPNNNNNN-READABLENAME
to ensure execution order and readability
- These scripts will be executed in alphabetical order - it’s recommended that you use a naming convention like
Backend: Data - FAQ
Q: What if you want to change the Persistence layer?
- If you’re using a different, popular SQL / Relational store - Dapper ORM supports most of these so will just need to install / update a package
- If you want to use a different kind of persistence (NoSQL, etc) - you’ll need to swap out the ORM / Migrations as befits your Persistence of choice.
Testing
Test Command: Available in Quickstart section
Testing with these libraries is relatively straightforward if you read the docs.
Where testing gets a little complicated with CloudSeed is that it favors full integration tests (with live Persistence) vs mocks. This is because tests are only as good as they reflect the prod environment - leaving out / mocking Persistence is a big risk area for disparity.
CloudSeed is pre-configured with a containerized Postgres DB. When you run your tests it will spin up a local DB (using docker-compose) to test any Migrations / Persistence (if you have tests that store / access stuff).
Testing - FAQ
Q: I don’t like integration tests. How do I turn them off?
- If you don’t like full integration tests, you can easily remove them from the Tests project.