Understanding application dependencies in a single script file
Setup (skip if you are ready)
brew install --cask dotnet-sdk //macwinget install -e --id Microsoft.dotnet //win
test your setup with
dotnet fsi
>"hello";; //always append commands with ;; else is multiline
#quit;; //interactive commands have a trailing # symbol
install vscode and ionide extension (or if you have rider or visualstudio also good to go already),
open an empty folder and create a script file
mkdir onion && cd onion && touch onion.fsx
Here is the code
What is Onion Architecture?
I will simplify this example design by Merging some of the Ports parts within Application..
Domain
First things first we add our domain, we have an Order, and we want to model for now a change price functionality to keep it as simple as possible.
Infrastructure
We want to define our infrastructure layer and ideally we would like also api client to map to domain types, but i felt like in real world many times we allow for a bit more slippery interactions here, letting the application service layer interact directly with api client dtos*.
(* this is not the 100% best practice) You can adjust the example if you wanted to also map apiclient to it’s domain types, but I found this many times in practice it’s a bit irrelevant and adds more clutter/indirection to your code**. (**works-on-my-onion)
Services
This is where our main usecases / workflows / application services sit, in this layer we will map our “end to end” business logic.
We should strive to only compose here, and keep most of our real business logic within Domain (that’s the part we would unit test mostly generally).
Application/EntryPoint
This is where our apps runs and it’s usually the entry point of our execution within a process on the OS/container. Also part of Application ideally*, but it might have a bit of extra dependencies like configuration, deployment, parameters, env vars, etc…).
In case of a web app, this is where our controllers (mvc style) / http-handlers (functional style) are. As you know HTTP sits at the Application Level of the Internet stack.
In this layer we also register our dependencies with DI, which in functional languages with default curried functions, is just partial function application by default, so we don’t need interfaces to be SOLID and apply IOC, just use regular function composition*.
*I could have used also interfaces and e.g. microsoft DI framework for IOC… or others more OO compatible DI frameworks, this is just for simplicity.
Testability
We can easilly mock our dependencies, as they are functions, but with a DI framework would work seamlessly registering the mocks.
And write our test suite…
FIN
Hope you have enjoyed this and feel free to comment if you have different opinions on the layered onion/exagonal architecture pattern, Cheers!