Application architecture/composition in F#


Answers

Pass C# interfaces/classes to F#

I think you could choose between 1 and 2. These are certainly the two most reasonable approaches.

I think that the option 2 might be better, because the F# implementation can then stay in simple F# modules using the most idiomatic F# style (such as passing functions around as arguments). The idiomatic F# code is not always easy to use from C#, so if you wrap it in an F# class that implements the required C# interfaces, you are hiding the "F# details" from the C# code, which is good.

Have a look at:

Question

I have been doing SOLID in C# to a pretty extreme level in recent times and at some point realized I'm essentially not doing much else than composing functions nowadays. And after I recently started looking at F# again, I figured that it would probably be the much more appropriate choice of language for much of what I'm doing now, so I'd like to try and port a real world C# project to F# as a proof of concept. I think I could pull off the actual code (in a very un-idiomatic fashion), but I can't imagine what an architecture would look like that allows me to work in a similarly flexible fashion as in C#.

What I mean by this is that I have lots of small classes and interfaces that I compose using an IoC container, and I also use patterns like Decorator and Composite a lot. This results in an (in my opinion) very flexible and evolvable overall architecture that allows me to easily replace or extend functionality at any point of the application. Depending on how big the required change is, I might only need to write a new implementation of an interface, replace it in the IoC registration and be done. Even if the change is bigger, I can replace parts of the object graph while the rest of the application simply stands as it did before.

Now with F#, I don't have classes and interfaces (I know I can, but I think that's beside the point when I want to do actual functional programming), I don't have constructor injection, and I don't have IoC containers. I know I can do something like a Decorator pattern using higher order functions, but that doesn't quite seem to give me the same kind of flexibility and maintainability as classes with constructor injection.

Consider these C# types:

public class Dings
{
    public string Lol { get; set; }

    public string Rofl { get; set; }
}

public interface IGetStuff
{
    IEnumerable<Dings> For(Guid id);
}

public class AsdFilteringGetStuff : IGetStuff
{
    private readonly IGetStuff _innerGetStuff;

    public AsdFilteringGetStuff(IGetStuff innerGetStuff)
    {
        this._innerGetStuff = innerGetStuff;
    }

    public IEnumerable<Dings> For(Guid id)
    {
        return this._innerGetStuff.For(id).Where(d => d.Lol == "asd");
    }
}

public class GeneratingGetStuff : IGetStuff
{
    public IEnumerable<Dings> For(Guid id)
    {
        IEnumerable<Dings> dingse;

        // somehow knows how to create correct dingse for the ID

        return dingse;
    }
}

I'll tell my IoC container to resolve AsdFilteringGetStuff for IGetStuff and GeneratingGetStuff for its own dependency with that interface. Now if I need a different filter or remove the filter altogether, I may need the respective implementation of IGetStuff and then simply change the IoC registration. As long as the interface stays the same, I don't need to touch stuff within the application. OCP and LSP, enabled by DIP.

Now what do I do in F#?

type Dings (lol, rofl) =
    member x.Lol = lol
    member x.Rofl = rofl

let GenerateDingse id =
    // create list

let AsdFilteredDingse id =
    GenerateDingse id |> List.filter (fun x -> x.Lol = "asd")

I love how much less code this is, but I lose flexibility. Yes, I can call AsdFilteredDingse or GenerateDingse in the same place, because the types are the same - but how do I decide which one to call without hard coding it at the call site? Also, while these two functions are interchangeable, I now cannot replace the generator function inside AsdFilteredDingse without changing this function as well. This isn't very nice.

Next attempt:

let GenerateDingse id =
    // create list

let AsdFilteredDingse (generator : System.Guid -> Dings list) id =
    generator id |> List.filter (fun x -> x.Lol = "asd")

Now I have composability by making AsdFilteredDingse a higher order function, but the two functions are not interchangeable anymore. On second thought, they probably shouldn't be anyway.

What else could I do? I could mimic the "composition root" concept from my C# SOLID in the last file of the F# project. Most files are just collections of functions, then I have some kind of "registry", which replaces the IoC container, and finally there is one function that I call to actually run the application and that uses functions from the "registry". In the "registry", I know I need a function of type (Guid -> Dings list), which I'll call GetDingseForId. This is the one I call, never the individual functions defined earlier.

For the decorator, the definition would be

let GetDingseForId id = AsdFilteredDingse GenerateDingse

To remove the filter, I'd change that to

let GetDingseForId id = GenerateDingse

The downside(?) of this is that all functions that use other functions would sensibly have to be higher order functions, and my "registry" would have to map all functions that I use, because the actual functions defined earlier can't call any functions defined later, in particular not those from the "registry". I might also run into circular dependency issues with the "registry" mappings.

Does any of this make sense? How do you really build an F# application to be maintainable and evolvable (not to mention testable)?




The main risk is that your layering will erode over time as you make code changes. This happens because you or your team don't realize when they are making a code change that they are breaking the layering (relatively obvious when you had separate projects).

If it is just you and you have the whole codebase clearly in your mind this probably isn't a problem. Otherwise you could use namespaces to make it easier to know to which layer any class is considered to belong.

Of course there is nothing in the language preventing bad dependencies between namespaces creeping in. To be sure of this you would need something outside of the language to define and enforce the layering. This typically means using a separate tool to define the architectural components and dependency rules, map them to the code, and detect when rules are violated. Studio Ultimate has a layer diagram which will do this, as will other tools like Lattix (dependency matrix) and Structure101 (architecture diagrams).




So long as the interface is defined in an assembly referenced by the F# project you can just implement MyObjectX directly

type MyObjectX(signal : ISignalProvider) = 
   let mutable operationMode : int = 0;   // assuming this changes

   interface IMathSolver with 
       member x.GetMethodMathSolver() : double =
          signal.GetThreshold()
          // ... 
       member x.MethodBusinessSolver() : double = 
          ... 



How to design a pluggable system in functional style?

You don't have to define interfaces in order to make an architecture pluggable in F#. Functions are already composable.

You can write your system Outside-In, starting with the desired, overall behaviour of your system. For example, here's a function I recently wrote that transitions a Polling Consumer from a state where no message was received, into a new state:

let idle shouldSleep sleep (nm : NoMessageData) : PollingConsumer =
    if shouldSleep nm
    then sleep () |> Untimed.withResult nm.Result |> ReadyState
    else StoppedState ()

This is a higher order function. While I was writing it, I discovered that it depended on the auxiliary functions shouldSleep and sleep, so I added these to the argument list. The compiler then automatically infers that e.g. shouldSleep must have the type NoMessageData -> bool. That function is a Dependency. The same goes for the sleep function.

As a second step, it turns out that a reasonable implementation of a shouldSleep function ends up looking like this:

let shouldSleep idleTime stopBefore (nm : NoMessageData) =
    nm.Stopped + idleTime < stopBefore

Never mind if you don't know what it all does. It's the composition of functions that matter here. In this case, we've learned that this particular shouldSleep function has the type TimeSpan -> DateTimeOffset -> NoMessageData -> bool, which isn't quite the same as NoMessageData -> bool.

It's pretty close, though, and you can use partial function application to go the rest of the distance:

let now' = DateTimeOffset.Now
let stopBefore' = now' + TimeSpan.FromSeconds 20.
let idleTime' = TimeSpan.FromSeconds 5.
let shouldSleep' = shouldSleep idleTime' stopBefore'

The shouldSleep' function is a partial application of the shouldSleep function, and has the desired type NoMessageData -> bool. You can compose this function into the idle function, together with an implementation of its sleep dependency.

Since the lower-order function has the correct type (the correct function signature), it just clicks into place; no casting is necessary in order to achieve this.

The idle, shouldSleep and shouldSleep' functions can be defined in different modules, in different libraries, and you can pull them all together using a process similar to Pure DI.

If you want to see a more comprehensive example of composing an entire application from individual functions, I provide an example in my Functional Architecture with F# Pluralsight course.