dotnet / orleans

Cloud Native application framework for .NET
https://docs.microsoft.com/dotnet/orleans
MIT License
10.14k stars 2.04k forks source link

Could not find an implementation for interface OrleansFSharp.Grains #8393

Open zxhelenxz opened 1 year ago

zxhelenxz commented 1 year ago

Hi, I was following the Quick Start tutorial on Orleans using F# and I ran into this exception This is the link to the tutorial https://learn.microsoft.com/en-us/dotnet/orleans/quickstarts/build-your-first-orleans-app?tabs=visual-studio

Please help me with this. Thank you!

System.ArgumentException: Could not find an implementation for interface OrleansFSharp.Grains+IUrlShortenerGrain
   at Orleans.GrainInterfaceTypeToGrainTypeResolver.GetGrainType(GrainInterfaceType interfaceType) in /_/src/Orleans.Core/Core/GrainInterfaceTypeToGrainTypeResolver.cs:line 102
   at Orleans.GrainFactory.GetGrain(Type interfaceType, IdSpan grainKey, String grainClassNamePrefix) in /_/src/Orleans.Core/Core/GrainFactory.cs:line 214
   at Orleans.GrainFactory.GetGrain[TGrainInterface](String primaryKey, String grainClassNamePrefix) in /_/src/Orleans.Core/Core/GrainFactory.cs:line 60
   at Program.Pipe #4 input at line 21@23-1.MoveNext() in /Users/tringuyen/source/OrleansFSharp/OrleansFSharp/Program.fs:line 25
   at Microsoft.AspNetCore.Http.RequestDelegateFactory.ExecuteTaskResult[T](Task`1 task, HttpContext httpContext)
   at Microsoft.AspNetCore.Routing.EndpointMiddleware.<Invoke>g__AwaitRequestTask|6_0(Endpoint endpoint, Task requestTask, ILogger logger)
   at Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddlewareImpl.Invoke(HttpContext context)
open System
open System.Threading.Tasks
open Microsoft.AspNetCore.Builder
open Microsoft.AspNetCore.Http
open Microsoft.Extensions.Hosting
open Orleans
open Orleans.Hosting
open Orleans.Runtime

    [<GenerateSerializer>] 
    type UrlDetails = {FullUrl: string; ShortenedRouteSegment: string}

    type IUrlShortenerGrain =
        inherit IGrainWithStringKey
        abstract member GetUrl: unit -> Task<string>
        abstract member SetUrl: string -> Task

    type UrlShortenerGrain ([<PersistentState("url", "urls")>] state : IPersistentState<UrlDetails>) =
        inherit Grain()
        interface IUrlShortenerGrain with
            override this.GetUrl() =
                Task.FromResult(state.State.FullUrl)
            override this.SetUrl fullUrl =
                state.State <- {FullUrl = fullUrl; ShortenedRouteSegment = this.GetPrimaryKeyString() }
                task {do! state.WriteStateAsync ()}

[<EntryPoint>]
let main args =
    let builder = WebApplication.CreateBuilder(args)
    builder.Host.UseOrleans(Action<ISiloBuilder>(
        fun siloBuilder ->
            siloBuilder.UseLocalhostClustering() |> ignore
            siloBuilder.AddMemoryGrainStorage("urls") |> ignore
            ()
    )) |> ignore
    let app = builder.Build()
    app.MapGet("/shorten/{redirect}", Func<IGrainFactory, HttpRequest, string, Task<IResult>>(
        fun grains request redirect ->
            task {
                let shortenedRouteSegment = Guid.NewGuid().GetHashCode().ToString("X")
                let shortenerGrain = grains.GetGrain<IUrlShortenerGrain>(shortenedRouteSegment)
                do! shortenerGrain.SetUrl(redirect)
                let resultBuilder = UriBuilder($"{request.Scheme}://{request.Host.Value}")
                resultBuilder.Path <- $"/go/{shortenedRouteSegment}"
                return Results.Ok(resultBuilder.Uri)
            }
        )) |> ignore
    app.MapGet("/go/{shortenedRouteSegment}", Func<IGrainFactory, string, Task<IResult>>(
        fun grains shortenedRouteSegment  ->
            task {
               let shortenerGrain = grains.GetGrain<IUrlShortenerGrain>(shortenedRouteSegment)
               let! url = shortenerGrain.GetUrl()
               return Results.Ok(url)
            }

        )) |> ignore
    app.MapGet("/", Func<string>( fun () -> "Hello world")) |> ignore

    app.Run()
    0 // Exit code
ReubenBond commented 1 year ago

Using F# has a quirk: Orleans relies on code generation and emits C# code. It supports analyzing F# code (eg, with handling for sum types), but relies on having the C# build chain.

To make it work, you need to add a dummy C# project and add this directive to it: https://github.com/dotnet/samples/blob/d9b8db3ccbfc543dcd092a898c5fe3feb1f3e354/orleans/FSharpHelloWorld/HelloWorld/Program.cs#L8

Practically, this requires separating your application so that you can have a C# project which references your F# project.

zxhelenxz commented 1 year ago

@ReubenBond Thank you! I had run into that sample project before but was confused why there was csharp in that.

So the rule of thumb is the Orleans actual runner (Console, windows service, webapi, etc) must be written in C# and consumes grain and core logic in an F# library project right?