The restaurant core code, a.k.a. the domain
This post is a follow-up to a past post where F# was the language we used to build the entire application. We used Giraffe as a way to setup the ASP.NET Core infrastructure. This post, however, will only use F# at the core or domain of the application. This is the place where F# really shines. The goal of this post is to show that these two .NET languages work very well together, each in their respective field.
To start off, we can re-use our domain code from the previous post — I will not go over this code again. The important thing to know is that this code represents a simple reservation system for a restaurant, where each day can fill up with up to 20 people, spread across one or more reservations. The focused reader will see that it is not exactly the same code. It adds CompiledName
attributes and uses methods like GetName
to soften the interoperability towards C#.
For more information on these interop code techniques, see my other post: An F# Primitive Giraffe Wearing Lenses: A Ports and Adapters Story.
This post is not necessarily about this logic, but more about how this logic can be used in a C# environment with ease.
The translation layer, a.k.a. the adapter
The previous post talked about having an ‘adapter’ layer that translates the outside models (DTO and DB) toward the domain models. That has not changed: now, these models are expressed in regular C#.
This should not be new code, but I will show it as if it is, to be on the same page. Notice that I did not do anything special. This is roughly how these models would be defined.
To go from our port models toward our domain models, we can create an adapter instance that does this translation for us. Due to the way OOP applications are structured, these adapters will have to be injected into other classes for them to be tested properly.
The following code sample shows how the DTO request model can be translated toward the domain reservation model. Notice that the ValidationResult
type is used when the translation happens towards the domain and the center of the application. This validation type is part of the FPrimitive package and is a C# alternative to the built-in F# Result
type. The conversion happens automatically. Notice that the F# domain and the C# adapter do not have any additional conversion. The language barrier is completely gone, which is what you want when you incorporate these two languages.
The same kind of adapter can be made for the DB entity type.
In the same way, we can create an interface for our F# Restaurant
module. As we use functions and not objects, it can be strange to use these directly in a C# application. To avoid confusion, we can create a proxy for the module and use it like an object. This extraction makes it testable from an OOP perspective.
This is probably the only real difference in F#/C# interoperability. This extraction may in certain places not hold much added value, as it is part of the service. To be fully testable, you may want to extract it like this, though.
💡 The System.Outcome
model is also a model that is by default available via FPrimitive. It provides C# devs with yet another alternative to F#’s ‘Result’ model, if the result is not a validation result, like in this case: a reservation error and no validation error.
The ASP.NET Core application, a.k.a. the port
The ‘port’ is the part that talks to the outside world. In this case, we will use a minimized ASP.NET Core application with a single API controller that takes in our reservation request DTO (data-transfer object). The following code shows how everything comes together. This includes how the adapters translate the DTOs to the domain, how the domain runs its actions, and how the adapters translate the result back towards DTOs — making a true union architecture.
Usually, this direct-repository access is also hidden behind services to make the API controller methods more clean and readable, but for the sake of simplicity, all the needed objects are injected here. These services, however, could hide both the translation and the proxy, so that the inputs/outputs of the service are still port models.
💡 The Traverse
is a functional concept for something called ‘foldable types‘. The result of the _repository.GetAll()
is a sequence of ReservationEntity
models and we need them all be transferred to a Reservation
domain model. If we would do a simple map/select, we would end up with a sequence of ValidationResult
models, while we actually want a ValidationResult
of a sequence. That is what the ‘Traverse’ does for us. It is an extension available in the FPrimitive library and a well-known concept in the functional programming community.
Conclusion
Both C# and F# are .NET languages, but we do not often see them combined. My theory is that it is not because they are not compatible, but because the two camps do not see the benefits of each other. Most of the external .NET libraries are written for C# and I think that is not only because it is a more popular language, but also because it expresses side effects better. F#, on the other hand, does a far better job of expressing the domain and translation toward it. If we combine the two, we get the best of both worlds and get a secure and fun workplace for free.
Thanks for reading!
Stijn
Subscribe to our RSS feed