JasperFx / wolverine

Supercharged .NET server side development!
https://wolverinefx.net
MIT License
1.24k stars 135 forks source link

Better support for F# projects #696

Open belczyk opened 9 months ago

belczyk commented 9 months ago

Is your feature request related to a problem? Please describe. I have a mid-sized application that uses event sourcing and a custom mediator. I'd like to rewrite it and use Wolverine and F#. I started PoC and discovered a few problems when using idiomatic F# with Wolverine. Some examples:

  1. Message handling functions can be static and placed in static class, which is excellent because F# modules are static classes; however, the convention is that functions' names inside modules start with a lowercase letter, and Wolverine uses case-sensitive matching for the function name (solet handle (e : MyEvent) = ... won't work, use of [<WolverineHandler>] attribute is a solution here but I'm not big fan of F# syntax for attributes)
  2. Wolverine assumes that if a class has multiple handlers, all of them will be called Handle, which makes sense in C#; Handle is overloaded with different message types as arguments. F# modules do not support overloading, so there can be only one Handle function per module (I haven't tried yet to use Handle, Handles, Consume, and Consumes in one module :D).
  3. Wolverine uses overloaded methods in many places for configuration or for interacting with the framework; in C#, it works fine, but the F# compiler is a little bit more strict about types, and oftentimes, it fails to decide which overload to choose, forcing the developer to cast stuff beforehand. This problem is common for .net libraries and is usually solved by exposing a F# specific interface (usually it is a small package called XXX.FSharp).

Describe the solution you'd like

  1. and 2. Minimum change - change convention for handler methods to name starts with "handle," "consume," etc., make check case insensitive. Full-blown solution: add discovery configuration for handler methods similar to discovery options for handler types.
  2. The best solution would be to have Wolverine.FSharp wrapper, which makes the API work nicely with F# (including pattern matching, pipe forward operator, and F# data structures)

Describe alternatives you've considered

The alternative is to use [<WolverineHandler>] for every handler method and build a F# friendly wrapper in my solution for Wolverine API (but if I can take advantage of it, probably every F# developer could make use of it).

nkosi23 commented 9 months ago

I've been using Wolverine with F# and can confirm that I myself faced frictions with the first two points.

Regarding the first one, I have indeed resorted to putting WolverineHandler attributes on every handler method. I find your idea of expanding the default conventions used by the framework quite a neat solution. As far as i am concerned, I'd suggest adding a default convention matching the below:

The second point is related to the first one I guess. I'd just note that it does not occur if you put an attribute everywhere (not an ideal solution I agree).

In terms of your wrapper solution, someone created a wrapper similar to what you allude to for MartenDb, see the Marten.FSharp library. I just mention it as a potential reference to illustrate what this solution may look like in practice. i'm not sure if the core team would be happy to include such wrappers in the base library though.

jeremydmiller commented 9 months ago

Hey folks, I'm very open to making changes to Wolverine to make it be more F# friendly, just need more feedback. And hopefully some help from real F# devs too if anybody is interested in helping out with that

BrianVallelunga commented 9 months ago

We are building a platform that uses F# on the back-end as well and have used the Wolverine HTTP support and handlers.

With regards to the handler-naming discovery, we get around the multiple-handler method issue by creating a single handler module per handler. This helps organize our code, though I can see the benefit of grouping some handlers together. Having case-insensitive matching for the handler function/method seems like a useful low-cost improvement though.

I'd definitely be interested in some sort of F# wrapper/helper project, but I don't have enough experience with other wrappers to know what would be valuable in that project yet. I'd be happy to contribute to that project if someone wants to start listing out some ideas for what it would contain.