Celebrating 5 Years In Production
(This post is part of the 2024 F# Advent Calendar.)
We started working on a new cloud-based SaaS product called GPS Aggregation (GPSA) in July 2019 for a spin-off of a large Austrian Logostics Company. We were one of two products being developed, the other being for Estimated Time of Arrival (ETA) calculations, which we would be providing raw data for. By the end of 2019 we were in production with an MVP and being used by paying customers. It’s now the end of 2024 and we have been in production for 5 years running 24/7. GPSA is primarily a pipeline of data processing where external data is retrived, parsed, validated and forwarded. The architecture is best described as message-oriented and makes extensive use of queues/topics to asynchronosly decouple the stages of the pipeline.
We spent the majority of the first 3 years concentrating on integrating with as many different GPS Providers as possible and forwarding valid GPS signals to interested parties. We introduced a dashboard to provide information about specific providers, devices and signal sources (credentials). We now support over 450 different GPS Providers, each with their own unique data formats.
We made extensive use of Domain-Driven Design (DDD) to separate the domain into a number of bounded contexts. This allowed us to build our product in a way that was easy to change when the business changes. This proved particularly useful in our case as we often had incomplete domain knowledge and/or no direct access to domain experts, which meant that we tried to keep changes as simple as possible and local to a single bounded context.
Toward the end of 2022, we were processing over 10,000,000 GPS signals per day. This sounds like a large number but only equates to 116 signals per second. The real complexity in GPSA lies in the provider integrations.
During 2023, no new functionality was added to the product as it was being subsumed into the systems of the parent company.
In 2024, we have been concentrating on providing visibility of invalid signals and the health of devices and providers.
A vital part of our process has been to make extensive use of spikes and experiments to try out ideas. This deliberate policy has meant that we have so far avoided analysis paralysis and allows us to make decisions based on real data.
Team size has fluctuated between 3 and 8 over the years and dropped to 0.5 during 2023 but is back up to 4 today.
Choosing F#⌗
We were given the option of using .NET or JVM. We wanted to use a functional programming language and considered F#, Scala, and C#, eventually settling on F# and .NET Core 2.1. We preferred a functional programming language as it has fewer moving parts (mutability, null) and provides a simpler model of execution. We used JetBrains Rider or VSCode + ionide for development.
The Good Bits⌗
F# is a simple language to learn. I wrote a series of blog posts in 2020 that I eventually converted into a free ebook Essential F#. It covers all of the language features needed to work on GPSA in under 200 pages. Most folks that join us have no experience of F# but we are able to help them to become productive within a few weeks. We normally get new folks to implement a new provider handler first and aim to have code in production within a few days.
The code we produce is succinct, robust, and performant.
The F# tools and libraries we use have proven to be very robust. I would like to specifically mention:
- FsToolkit.ErrorHandling
- Npgsql.FSharp
- giraffe
- FSharp.SystemTextJson
- ionide
- paket
Domain modelling is very easy to do in F#. We model types and function signatures with minimal ceremony and they can be easily understood by domain experts as thay are written using the language of the domain.
The core F# language has been very stable for many years. New versions are more about ensuring interop with C# works properly than adding new features. The two major changes for us have been to move from Ply to Task with F# 6 and to move from the applicative style validation to the validation computation expression from FsToolkit.ErrorHandling. We do use the latest syntax in newer code but have chosen to leave existing code as is.
The Bad Bits⌗
The community, whilst awesome, is quite small and the great tools and libraries that we have rely upon a small number of folks to keep them going.
Finding good developers is hard but that applies to all languages/platforms.
It’s sometimes hard to use libraries written for C#, and that includes .NET itself.
Overall⌗
F# and .NET have proven to be great choices for this product. The codebase is about 60% of the size it would be if written in C# and contains almost no null checks, yet is very robust. The upgrades to .NET have led to a surprising improvement in performance.
The Ultimate Question⌗
If we had to make the same choice again, would we choose F#?
Despite my love for F#, this has proven to be a very difficult question for me to answer. On one hand, F# is a fun language to use, has proven to be well up to the task at hand, and doesn’t seem to lack anything except for enthusiastic support from Microsoft. On the other hand, C# has gained many new functional features with many more including union (sum) types to come in (near) future versions. However, the major downside of C# is the defaults of the language will always favour obect-oriented design. Ultimately, given free choice, I would gladly choose F# again. However, I could live with being forced to use C#.