Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added ASP.NET Core co-hosting sample #6130

Merged
merged 4 commits into from
Jan 10, 2020
Merged

Added ASP.NET Core co-hosting sample #6130

merged 4 commits into from
Jan 10, 2020

Conversation

siennathesane
Copy link
Contributor

Pretty straightfoward, this sample shows how to use ASP.NET Core 3.0 with Orleans 3.0 in a hosted service model, where the ASP.NET Core framework runs the silo and client as hosted services, exposing a REST API for the grain.

Signed-off-by: Mike Lloyd mike@reboot3times.org

Signed-off-by: Mike Lloyd <mike@reboot3times.org>
@SebastianStehle
Copy link
Contributor

Sorry, but this is really wrong. You can just use the UseOrleans(...) extension method in your host-builder and then inject IGrainFactory everywhere. There is no need for all this code, you can create a minimum sample with 3 code files.

@SebastianStehle
Copy link
Contributor

The dashboard samples work in a similar way: https://github.com/OrleansContrib/OrleansDashboard/blob/master/Tests/TestHosts/TestHostCohosted2/Program.cs

We just inject the IGrainFactory to the DashboardMiddleware.

@siennathesane
Copy link
Contributor Author

Sorry, but this is really wrong.

It's not really wrong - just because you're unsatisfied with it doesn't make it wrong. I looked at your pattern from other issues before this one, and that's what I was using in 2.2. It didn't survive the upgrade to 3.0 because the ASP.NET Core team made changes to IWebHostBuilder, which I include in my sample.

The whole point of a sample is to show one of many ways of doing things. If you look closely, you'll notice I pulled the client hosted service straight from the existing 3.0 hello world sample - I did the same thing with the silo, using an interface the Orleans team provided. The only fundamental difference between the existing one and this one I'm suggesting is I wrap it in an aspnet core wrapper. I'm not doing any black magic, not implementing any bad patterns, I'm providing a simple sample with a basic opinion of how to run Orleans as a background process for aspnet core.

This PR's focus is designed to be one possible way of doing things, not the only way. If you think there's a different pattern, and you want to make sure folks are aware of that pattern, you are more than welcome to submit your own PR with a pattern you want to advertise - and when I filed this, I didn't see any PRs by you.

@SebastianStehle
Copy link
Contributor

This was also possible before 3.0, but you are right: Wrong is the wrong term ;)

IMHO the point of the samples should be to show best practices. Getting the silo to be initialized and shutdown correctly is not so easy. Everything is already done by the Orleans team. If you have a different approach you should discuss it, why and when it is useful. Just giving alternatives is confusing for new users.

Afaik using Silo(Host)Builder is the new and official way to initialize new orleans and this is also what is very likely supported by extensions such as dashboard or storage providers.

@siennathesane
Copy link
Contributor Author

Getting the silo to be initialized and shutdown correctly is not so easy.

Agreed, this is why my sample demonstrates how to correctly start and stop a silo using their version of IHostedService with StartAsync() and StopAsync(), using cancellation tokens from the upstream service collection. Using that interface method in the way I'm demonstrating is about the safest way you can use it outside of the UseOrleans() extension.

Everything is already done by the Orleans team.

Yes, but they still struggle with comprehensive and clear documentation - the documentation website is a mess (not being mean, just pointing out it could be much better). Samples are ways to expose other things you can do with Orleans outside what exists in the documentation. For example, there is a healthcheck sample in 2.3, but the documentation doesn't talk anything about it. The health check logic in 3.0 changed drastically from 2.3 to 3.0 because they removed IMembershipOracle, breaking the sample and making it incompatible with 3.0.

If you have a different approach you should discuss it, why and when it is useful.

If your primary feedback on the sample is, there needs to be more documentation, then I won't disagree, but I will point out that most samples have little or no documentation and having this pattern be singled out for that specific reason seems a bit pointed.

The value of this pattern is when you want full control of the silo configuration but you want to decouple it's deployment configuration. Think of it as a sidecar pattern, but as a process instead of a container/etc.

So, in this case, Project A could define some OSS library and define the silo configuration and service interfaces it needs, but not be interested in providing a deployment mechanism.

Project B could be interested in a deployment mechanism, but want to use a different instantiation of the service interfaces (maybe they want to use the Minio SDK instead of the Amazon SDK) - this allows them to do that.

using Silo(Host)Builder is the new and official way to initialize new orleans

I do use it, in the Silo sub-project - it's part of the constructor, and then the silo lifecycle is managed by the IHostedService lifecycle.

@SebastianStehle
Copy link
Contributor

I don't see how your approach gives you more control over the configuration than the official way. This sample is fine for a blog post or so but as a project owner I would not accept it as a PR.

What I also don't get: Why do you need a normal client? You can just inject IGrainFactory into your code and then your calls to local grains don't need serialization and networking anymore.

Btw: I have written this project to connect silo builder with WebHost: https://github.com/OrleansContrib/WebHostCompatibilityLayer/tree/master/Orleans.WebHostCompatibilityLayer

@siennathesane
Copy link
Contributor Author

as a project owner I would not accept it as a PR

I appreciate you care about this project, as do I, but your feedback has been argumentative instead of constructive. If you no longer have anything constructive to add, as you aren't the project owner, I'm respectfully asking you to stop providing feedback.

You can just inject IGrainFactory into your code and then your calls to local grains don't need serialization and networking anymore.

Sure, and in this very specific case, that's relevant, but if you want to scale this design pattern, you need proper networking and serialization support. With this pattern, I could change only the clustering providers and be able to scale to the maximum Orleans supports. This is about sustainability and decoupling for the least amount of overall effort.

Btw: I have written this project to connect silo builder with WebHost

Great, you should add it as a sample.

@siennathesane
Copy link
Contributor Author

@ReubenBond can I please get a review on this? Thanks!

@ReubenBond ReubenBond self-assigned this Jan 9, 2020
@ReubenBond ReubenBond added this to the Triage milestone Jan 9, 2020
@ReubenBond
Copy link
Member

A sample demonstrating ASP.NET & Orleans living side-by-side in the same process is useful.

I think this should not use a separate hosted service for the silo, but instead share a single host builder (similar to the Blazor sample) & make this a cohosting sample. The cohosting uses an IHostedService internally.

I'd consider sharing a single DI container to be best practice - though people can individually use a separate IHostedService + DI container if they wish, but I wouldn't give that as guidance.

I also recommend using the IClusterClient which the silo exposes via DI, rather than a separate ClusterClient.

I'll PR your branch with what I'm suggesting.

@SebastianStehle
Copy link
Contributor

@ReubenBond Can you not just use IGrainFactory? Then you have not to differentiate whether you are in the grain context or not. This is helpful when you write services.

@ReubenBond
Copy link
Member

@SebastianStehle yes, certainly

@ReubenBond ReubenBond changed the title samples: added aspnet core with hosted services sample. Added ASP.NET Core co-hosting sample Jan 10, 2020
@ReubenBond ReubenBond merged commit d70b845 into dotnet:master Jan 10, 2020
@ReubenBond
Copy link
Member

I didn't mean to rename the PR. It turns out a Firefox extension took that liberty upon itself
image

@ewilazarus
Copy link

Hello, there.

I'm new to Orleans and want to get my foot wet. After reading the docs and some googling, I found this sample and found it a great starting point.

My question is: is this production ready? Is there any downside regarding co-hosting asp.net core host and silo host?

Thanks a lot!

@sergeybykov
Copy link
Contributor

My question is: is this production ready?

Orleans was first used in production in late 2011. A significant number of production systems are built with it and run today.

Is there any downside regarding co-hosting asp.net core host and silo host?

The only downside I'm aware of is the loss of performance isolation between frontend and backend layers. For example, if a bug in the frontend code causes high CPU utilization, it is harder to root-cause that when the same process is used. It is much more obvious when a frontend running as a separate process is hot.

@siennathesane
Copy link
Contributor Author

@ewilazarus

Is there any downside regarding co-hosting asp.net core host and silo host?

Yes and no. To @sergeybykov's point, there's definitely an aspect of Orleans vs ASP.NET Core debugging challenges as the workloads are not isolated, but good test cases for your Grains and Controllers will really help prevent too much nuance.

When I wrote this demo, the intent behind the demo was to showcase web-based cluster computing, where you can deploy N API-based web servers that share execution across a cluster, where scalability is simple, nodes can come and go, etc. I used to teach how to do all the same things with Spring Cloud (circuit breakers, Hysterix, Eureka, Data Flow, etc.) and when I was teaching modern, cloud-native .NET Core, I used this as one of the examples of how to gain all the benefits of Spring without any of the hassle. So from a production ready standpoint, this example is viable for production, but as @sergeybykov called out, there's a loss of performance isolation.

In my use cases where I've taken this design into production, I use the web API controllers as the thinnest layer of interface between the Grains and the outside world, so my controllers are basically REST interfaces into the Grains, there's no business logic of any kind in my controllers, I basically just pass the payload directly to the respective Grains with some basic exception handling and claim checking. This use case isn't for everyone, and it has pros and cons like every design pattern.

Does that help provide some context?

@ewilazarus
Copy link

Sure it does!

Thanks a lot @mxplusb!

@vivek306
Copy link

vivek306 commented Jul 11, 2020

Hi there, I am new to Orleans too and this demo seems to be a great starting point.

for those of you getting the bellow error while using
UseDashboard in SiloBuilder
'Cannot find an implementation class for grain interface: AspNetCoreCohosting.Interfaces.IHelloWorld. Make sure the grain assembly was correctly deployed and loaded in the silo.' but works perfectly fine without it.

Note: Found the solution to this, just had to add

                .ConfigureApplicationParts(parts =>
                {
                    parts.AddApplicationPart(typeof(HelloWorldGrain).Assembly).WithReferences();
                })

@mxplusb regarding the payload,
there's no business logic of any kind in my controllers, I basically just pass the payload directly to the respective Grains

if I were to change the HelloWorldGrain -> SayHello function to take 10 seconds to compute, would that not block the api request for 10 seconds to response? Is there a way for the API to just make the call and the grain can notify or do something after?

    public async Task<string> SayHello()
    {
        await Task.Delay(10000);
        return "Hello world!";
    }

Thanks :)

@siennathesane
Copy link
Contributor Author

if I were to change the HelloWorldGrain -> SayHello function to take 10 seconds to compute, would that not block the api request for 10 seconds to response?

In this example, yes. My grains usually return a claim check, meaning they generate a GUID which represents a global task that's callable at a later time. If you've ever used the Azure API, that's an implementation of claim checking, when it returns an OperationResult object.

For example, the workflow in my code looks something like this:

  1. Call the REST API.
  2. Controller generates a guid.
  3. Controller calls a claim manager Grain which takes the guid, throws it in a cache for state updates and then sends the request onto a queue.
  4. Controller returns a JSON object with the guid from the claim manager Grain and the current state.
  5. Streaming Grains read from the queue to call a workflow manager Grain to handle the incoming request and execute business logic, notifying the claim manager when a state is updated.
  6. At a later time once things have been processed, call the REST API with the guid from earlier.
  7. Controller calls claim manager Grain with the guid.
  8. Claim manager Grain calls the CRUD Grain for the response data.
  9. Claim manager Grain returns the response data to the controller.
  10. Controller returns the data to the caller.

All of the grains and queues are all part of the same ASP.NET Core deployment in this example, I just use queuing and claim checks to detach from the web request. All the processing still happens in the web server cluster, so there are always things running in the cluster. I like this design pattern because it allows me to have one single cluster, one single deployment, and then I just scale it accordingly because Orleans handles the scheduling and resource management.

I treat Orleans as a code-only Functions as a Service compute cluster that I can cohost within ASP.NET Core, so I write the majority of my interfaces and Grains under that design pattern. Every Grain is just a simple CRUD interface that I can build a REST API on top of, and then I just use the claim check pattern as the "middleware"/"middle man" between the Grain and the Controller. I've been a backend developer for a long time, and with the advent of microservices in the industry, I got really frustrated with having to manage 10+ deployments of different services that each do one thing, it makes for this really miserable operations experience and it was just impossible to maintain over time. So by focusing on a FaaS design pattern and cohosting it in my ASP.NET Core web servers, I can have a single deployment with a single cluster that does everything I need it to do. Cluster is running low on compute resources? kubectl scale deployment/cluster --replicas=N+5. And with that, it's done and over. The operations model is so much easier on humans and Orleans is so good at what it does that I don't care if there's a small overhead with cohosting because the financial cost of the overhead is so much less than the financial cost of my time trying to troubleshoot some microservice design pattern and keep the system running.

@vivek306
Copy link

Great thanks :), I will start looking into Grains ClaimsManager.

@vivek306
Copy link

One last question, how did u host it to azure and take advantage of the Orleans and silo expansion.

Thanks.

@siennathesane
Copy link
Contributor Author

how did u host it to azure and take advantage of the Orleans and silo expansion.

@vivek306 I deploy it to Kubernetes and then I can host it wherever Kubernetes can run. :)

@github-actions github-actions bot locked and limited conversation to collaborators Dec 2, 2023
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants