dotnet / aspire-samples

MIT License
658 stars 195 forks source link

How to Aspire with 100s of services, Executables? #59

Closed jkears closed 10 months ago

jkears commented 10 months ago

When creating an Aspire app host, I following this logic ...
builder.AddProject<Projects.NextWare_UI_Builder_EditServices_API>("nextwareuibuildereditservices");

For this I need to add a project reference to the NextWare_UI_Builder_EditServices_API project in the Aspire AppHost.

Does that mean that our Aspire AppHost project would need to have a project dependency on each of the 100+ service projects or is there another way to configure this?

And, what is meant by executables in the Dashboard?

DamianEdwards commented 10 months ago

In the next preview you'll be able to add projects without a project reference, by passing a path to a project file.

Executables can be composed in as resources to be launched by the AppHost, e.g. that's how the Node sample in this repo works, by running npm start.

davidfowl commented 10 months ago

Does that mean that our Aspire AppHost project would need to have a project dependency on each of the 100+ service projects or is there another way to configure this?

If you have 100 projects, you’ll need 100 calls (you can write a loop)

jkears commented 10 months ago

@DamianEdwards and @davidfowl many thanks!

I think paths to each separate project file would be ideal, and by path I hope you mean Git path and File path?? Git support would be ideal, at least for our needs.

We have developed our own in-house DDD-based, Domain Modeling tool (screen shot below). This tool includes a VS Code Ext. that we utilize to capture and model any business domain as Aggregates within Bounded Contexts (Services), complete with Command and Event definitions, Value Objects, Enums, all of which gets code generated into backing services via Rosslyn code-gen.

We publish every model into a separate Meta-Model registry service. this is so that within other domain models we can establish related references to Aggregates situated within external services outside of the domain being modeled.

We code generate REST, gRPC and GraphQL, the later of which participates within a GraphQL Gateway service, that composes across a myriad of these generated services.

The model below is from our UI Editor services project. We are building an App Builder for Blazor and the aggregate showing is used to import 3rd party Blazor component libraries.

image

We code-generate Dapr integrations, and deploy to ACA. We use GitHub Actions to trigger build/test upon check-in.

We used Tye extensively, and thus further code-generate a Tye.yml file that contains all of the services within a given domain model which allows us to spin up each as named services, and hoping to replace with Aspire.

The GraphQL Gateways utilize Redis to synchronize the GraphQL Schema for each service, and we register both Core services and Application services using Named HTTP Clients, one per each service as per this code-generated sample ...

/// <summary>
///
/// CoreServicesEndPointConfiguration.cs
/// 
/// Generates a endpoint for each core domain service  
/// 
/// 
/// *****************************  Warning  ********************************
/// 
/// Do not modify the contents of this file! Any changes made to this file 
/// will be deleted upon next code generation.  
/// 
/// ************************************************************************
/// 
/// 
/// </summary>
/// 
namespace NextWare.UI.Builder.EditServices.API.DependencyInjection
{
    public static class CoreServicesEndPointConfiguration
    {
        public static IServiceCollection AddEndPointsCoreServices(this IServiceCollection services, ConfigurationManager configuration)
        {
            services.AddHttpClient(WellKnownCoreServicesSchemaNames.AuthorizationServices, c =>
            {
                c.BaseAddress = new Uri($"http://{WellKnownCoreServicesSchemaNames.AuthorizationServices}/graphQL");
            }).AddHttpMessageHandler(() => new InvocationHandler()).AddHeaderPropagation();
            services.AddHttpClient(WellKnownCoreServicesSchemaNames.MetaModelServices, c =>
            {
                c.BaseAddress = new Uri($"http://{WellKnownCoreServicesSchemaNames.MetaModelServices}/graphQL");
            }).AddHttpMessageHandler(() => new InvocationHandler()).AddHeaderPropagation();
            services.AddHttpClient(WellKnownCoreServicesSchemaNames.ValidationServices, c =>
            {
                c.BaseAddress = new Uri($"http://{WellKnownCoreServicesSchemaNames.ValidationServices}/graphQL");
            }).AddHttpMessageHandler(() => new InvocationHandler()).AddHeaderPropagation();
            services.AddHttpClient(WellKnownCoreServicesSchemaNames.BusinessRuleDefinitionServices, c =>
            {
                c.BaseAddress = new Uri($"http://{WellKnownCoreServicesSchemaNames.BusinessRuleDefinitionServices}/graphQL");
            }).AddHttpMessageHandler(() => new InvocationHandler()).AddHeaderPropagation();
            services.AddHttpClient(WellKnownCoreServicesSchemaNames.ReactionPolicyServices, c =>
            {
                c.BaseAddress = new Uri($"http://{WellKnownCoreServicesSchemaNames.ReactionPolicyServices}/graphQL");
            }).AddHttpMessageHandler(() => new InvocationHandler()).AddHeaderPropagation();
            services.AddHttpClient(WellKnownCoreServicesSchemaNames.WorkFlowServices, c =>
            {
                c.BaseAddress = new Uri($"http://{WellKnownCoreServicesSchemaNames.WorkFlowServices}/graphQL");
            }).AddHttpMessageHandler(() => new InvocationHandler()).AddHeaderPropagation();
            services.AddHttpClient(WellKnownCoreServicesSchemaNames.GatewayServices, c =>
            {
                c.BaseAddress = new Uri($"http://{WellKnownCoreServicesSchemaNames.GatewayServices}/graphQL");
            }).AddHttpMessageHandler(() => new InvocationHandler()).AddHeaderPropagation();
            services.AddHttpClient(WellKnownCoreServicesSchemaNames.ACLServices, c =>
            {
                c.BaseAddress = new Uri($"http://{WellKnownCoreServicesSchemaNames.ACLServices}/graphQL");
            }).AddHttpMessageHandler(() => new InvocationHandler()).AddHeaderPropagation();
            services.AddHttpClient(WellKnownCoreServicesSchemaNames.CICDServices, c =>
            {
                c.BaseAddress = new Uri($"http://{WellKnownCoreServicesSchemaNames.CICDServices}/graphQL");
            }).AddHttpMessageHandler(() => new InvocationHandler()).AddHeaderPropagation();
            return services;
        }
    }
}

With respect to GraphQL Gateways, our Gateway generator, integrates with our Meta Model Services to retrieve all service definitions, that as the user selects, generate into a separate gateway project/solution service.

Below is and example of a Gateway that was derived from some of our ERP services to compose a GraphQL Gateway service for ERP services...

/// <summary>
///
/// EndPointConfiguration.cs
/// 
/// Generates a endpoint for each domain service in domain model for GraphQL Gateway
///
/// 
/// *****************************  Warning  ********************************
/// 
/// Do not modify the contents of this file! Any changes made to this file 
/// will be deleted upon next code generation.  
/// 
/// ************************************************************************
/// 
/// 
/// </summary>
/// 
namespace NextWare.Gateway.DependencyInjection
{
    public static class EndPointConfiguration
    {
        public static IServiceCollection AddEndPoints(this IServiceCollection services, ConfigurationManager configuration)
        {
            services.AddHttpClient(WellKnownSchemaNames.nextwareerpafchatservices, c =>
            {
                c.BaseAddress = new Uri($"http://{WellKnownSchemaNames.nextwareerpafchatservices}");
            }).AddHttpMessageHandler(() => new InvocationHandler()).AddHeaderPropagation();
            services.AddHttpClient(WellKnownSchemaNames.nextwareerpafcommonservices, c =>
            {
                c.BaseAddress = new Uri($"http://{WellKnownSchemaNames.nextwareerpafcommonservices}");
            }).AddHttpMessageHandler(() => new InvocationHandler()).AddHeaderPropagation();
            services.AddHttpClient(WellKnownSchemaNames.nextwareerpafdocumentmgmtservices, c =>
            {
                c.BaseAddress = new Uri($"http://{WellKnownSchemaNames.nextwareerpafdocumentmgmtservices}");
            }).AddHttpMessageHandler(() => new InvocationHandler()).AddHeaderPropagation();
            services.AddHttpClient(WellKnownSchemaNames.nextwareerpaferpexternalservices, c =>
            {
                c.BaseAddress = new Uri($"http://{WellKnownSchemaNames.nextwareerpaferpexternalservices}");
            }).AddHttpMessageHandler(() => new InvocationHandler()).AddHeaderPropagation();
            services.AddHttpClient(WellKnownSchemaNames.nextwareerpafintegrationservices, c =>
            {
                c.BaseAddress = new Uri($"http://{WellKnownSchemaNames.nextwareerpafintegrationservices}");
            }).AddHttpMessageHandler(() => new InvocationHandler()).AddHeaderPropagation();
            services.AddHttpClient(WellKnownSchemaNames.nextwareerpafmetadatamodelservices, c =>
            {
                c.BaseAddress = new Uri($"http://{WellKnownSchemaNames.nextwareerpafmetadatamodelservices}");
            }).AddHttpMessageHandler(() => new InvocationHandler()).AddHeaderPropagation();
            services.AddHttpClient(WellKnownSchemaNames.nextwareerpbpapprovalroutingservices, c =>
            {
                c.BaseAddress = new Uri($"http://{WellKnownSchemaNames.nextwareerpbpapprovalroutingservices}");
            }).AddHttpMessageHandler(() => new InvocationHandler()).AddHeaderPropagation();
            services.AddHttpClient(WellKnownSchemaNames.nextwareerpbpbusinessprocessservices, c =>
            {
                c.BaseAddress = new Uri($"http://{WellKnownSchemaNames.nextwareerpbpbusinessprocessservices}");
            }).AddHttpMessageHandler(() => new InvocationHandler()).AddHeaderPropagation();
            services.AddHttpClient(WellKnownSchemaNames.nextwareerpbpdiagramservices, c =>
            {
                c.BaseAddress = new Uri($"http://{WellKnownSchemaNames.nextwareerpbpdiagramservices}");
            }).AddHttpMessageHandler(() => new InvocationHandler()).AddHeaderPropagation();
            services.AddHttpClient(WellKnownSchemaNames.nextwareerpbpreportingservices, c =>
            {
                c.BaseAddress = new Uri($"http://{WellKnownSchemaNames.nextwareerpbpreportingservices}");
            }).AddHttpMessageHandler(() => new InvocationHandler()).AddHeaderPropagation();
            services.AddHttpClient(WellKnownSchemaNames.nextwareerpbpsupportservices, c =>
            {
                c.BaseAddress = new Uri($"http://{WellKnownSchemaNames.nextwareerpbpsupportservices}");
            }).AddHttpMessageHandler(() => new InvocationHandler()).AddHeaderPropagation();
            services.AddHttpClient(WellKnownSchemaNames.nextwareerpecomdeliveryservices, c =>
            {
                c.BaseAddress = new Uri($"http://{WellKnownSchemaNames.nextwareerpecomdeliveryservices}");
            }).AddHttpMessageHandler(() => new InvocationHandler()).AddHeaderPropagation();
            services.AddHttpClient(WellKnownSchemaNames.nextwareerpecominventoryservices, c =>
            {
                c.BaseAddress = new Uri($"http://{WellKnownSchemaNames.nextwareerpecominventoryservices}");
            }).AddHttpMessageHandler(() => new InvocationHandler()).AddHeaderPropagation();
            services.AddHttpClient(WellKnownSchemaNames.nextwareerpecominvoiceservices, c =>
            {
                c.BaseAddress = new Uri($"http://{WellKnownSchemaNames.nextwareerpecominvoiceservices}");
            }).AddHttpMessageHandler(() => new InvocationHandler()).AddHeaderPropagation();
            services.AddHttpClient(WellKnownSchemaNames.nextwareerpecomorderservices, c =>
            {
                c.BaseAddress = new Uri($"http://{WellKnownSchemaNames.nextwareerpecomorderservices}");
            }).AddHttpMessageHandler(() => new InvocationHandler()).AddHeaderPropagation();
            services.AddHttpClient(WellKnownSchemaNames.nextwareerpecomorgmgmtservices, c =>
            {
                c.BaseAddress = new Uri($"http://{WellKnownSchemaNames.nextwareerpecomorgmgmtservices}");
            }).AddHttpMessageHandler(() => new InvocationHandler()).AddHeaderPropagation();
            services.AddHttpClient(WellKnownSchemaNames.nextwareerpecompaymentservices, c =>
            {
                c.BaseAddress = new Uri($"http://{WellKnownSchemaNames.nextwareerpecompaymentservices}");
            }).AddHttpMessageHandler(() => new InvocationHandler()).AddHeaderPropagation();
            services.AddHttpClient(WellKnownSchemaNames.nextwareerpecomstoreservices, c =>
            {
                c.BaseAddress = new Uri($"http://{WellKnownSchemaNames.nextwareerpecomstoreservices}");
            }).AddHttpMessageHandler(() => new InvocationHandler()).AddHeaderPropagation();
            services.AddHttpClient(WellKnownSchemaNames.nextwareerpftabcmgmtservices, c =>
            {
                c.BaseAddress = new Uri($"http://{WellKnownSchemaNames.nextwareerpftabcmgmtservices}");
            }).AddHttpMessageHandler(() => new InvocationHandler()).AddHeaderPropagation();
            services.AddHttpClient(WellKnownSchemaNames.nextwareerpftdimensionservices, c =>
            {
                c.BaseAddress = new Uri($"http://{WellKnownSchemaNames.nextwareerpftdimensionservices}");
            }).AddHttpMessageHandler(() => new InvocationHandler()).AddHeaderPropagation();
            services.AddHttpClient(WellKnownSchemaNames.nextwareerpftfixedassetsservices, c =>
            {
                c.BaseAddress = new Uri($"http://{WellKnownSchemaNames.nextwareerpftfixedassetsservices}");
            }).AddHttpMessageHandler(() => new InvocationHandler()).AddHeaderPropagation();
            services.AddHttpClient(WellKnownSchemaNames.nextwareerpftgeneralledgerservices, c =>
            {
                c.BaseAddress = new Uri($"http://{WellKnownSchemaNames.nextwareerpftgeneralledgerservices}");
            }).AddHttpMessageHandler(() => new InvocationHandler()).AddHeaderPropagation();
            services.AddHttpClient(WellKnownSchemaNames.nextwareerpftgrcservices, c =>
            {
                c.BaseAddress = new Uri($"http://{WellKnownSchemaNames.nextwareerpftgrcservices}");
            }).AddHttpMessageHandler(() => new InvocationHandler()).AddHeaderPropagation();
            services.AddHttpClient(WellKnownSchemaNames.nextwareerpfttaxservices, c =>
            {
                c.BaseAddress = new Uri($"http://{WellKnownSchemaNames.nextwareerpfttaxservices}");
            }).AddHttpMessageHandler(() => new InvocationHandler()).AddHeaderPropagation();
            services.AddHttpClient(WellKnownSchemaNames.nextwareerpfttransactionservices, c =>
            {
                c.BaseAddress = new Uri($"http://{WellKnownSchemaNames.nextwareerpfttransactionservices}");
            }).AddHttpMessageHandler(() => new InvocationHandler()).AddHeaderPropagation();
            services.AddHttpClient(WellKnownSchemaNames.nextwareerphrcareermgmtservices, c =>
            {
                c.BaseAddress = new Uri($"http://{WellKnownSchemaNames.nextwareerphrcareermgmtservices}");
            }).AddHttpMessageHandler(() => new InvocationHandler()).AddHeaderPropagation();
            services.AddHttpClient(WellKnownSchemaNames.nextwareerphremployeeservices, c =>
            {
                c.BaseAddress = new Uri($"http://{WellKnownSchemaNames.nextwareerphremployeeservices}");
            }).AddHttpMessageHandler(() => new InvocationHandler()).AddHeaderPropagation();
            services.AddHttpClient(WellKnownSchemaNames.nextwareerphrhumanresourceservices, c =>
            {
                c.BaseAddress = new Uri($"http://{WellKnownSchemaNames.nextwareerphrhumanresourceservices}");
            }).AddHttpMessageHandler(() => new InvocationHandler()).AddHeaderPropagation();
            services.AddHttpClient(WellKnownSchemaNames.nextwareerphrpayrollservices, c =>
            {
                c.BaseAddress = new Uri($"http://{WellKnownSchemaNames.nextwareerphrpayrollservices}");
            }).AddHttpMessageHandler(() => new InvocationHandler()).AddHeaderPropagation();
            services.AddHttpClient(WellKnownSchemaNames.nextwareerpkmknowledgemgmtservices, c =>
            {
                c.BaseAddress = new Uri($"http://{WellKnownSchemaNames.nextwareerpkmknowledgemgmtservices}");
            }).AddHttpMessageHandler(() => new InvocationHandler()).AddHeaderPropagation();
            services.AddHttpClient(WellKnownSchemaNames.nextwareerpkmresearchservices, c =>
            {
                c.BaseAddress = new Uri($"http://{WellKnownSchemaNames.nextwareerpkmresearchservices}");
            }).AddHttpMessageHandler(() => new InvocationHandler()).AddHeaderPropagation();
            services.AddHttpClient(WellKnownSchemaNames.nextwareerpplmindustryservices, c =>
            {
                c.BaseAddress = new Uri($"http://{WellKnownSchemaNames.nextwareerpplmindustryservices}");
            }).AddHttpMessageHandler(() => new InvocationHandler()).AddHeaderPropagation();
            services.AddHttpClient(WellKnownSchemaNames.nextwareerpplmproductlinemgmtservices, c =>
            {
                c.BaseAddress = new Uri($"http://{WellKnownSchemaNames.nextwareerpplmproductlinemgmtservices}");
            }).AddHttpMessageHandler(() => new InvocationHandler()).AddHeaderPropagation();
            services.AddHttpClient(WellKnownSchemaNames.nextwareerpplmstrategicmgmtservices, c =>
            {
                c.BaseAddress = new Uri($"http://{WellKnownSchemaNames.nextwareerpplmstrategicmgmtservices}");
            }).AddHttpMessageHandler(() => new InvocationHandler()).AddHeaderPropagation();
            services.AddHttpClient(WellKnownSchemaNames.nextwareerpproppropertymgmtservices, c =>
            {
                c.BaseAddress = new Uri($"http://{WellKnownSchemaNames.nextwareerpproppropertymgmtservices}");
            }).AddHttpMessageHandler(() => new InvocationHandler()).AddHeaderPropagation();
            services.AddHttpClient(WellKnownSchemaNames.nextwareerppurchpurchaseservices, c =>
            {
                c.BaseAddress = new Uri($"http://{WellKnownSchemaNames.nextwareerppurchpurchaseservices}");
            }).AddHttpMessageHandler(() => new InvocationHandler()).AddHeaderPropagation();
            services.AddHttpClient(WellKnownSchemaNames.nextwareerpsalescmsservices, c =>
            {
                c.BaseAddress = new Uri($"http://{WellKnownSchemaNames.nextwareerpsalescmsservices}");
            }).AddHttpMessageHandler(() => new InvocationHandler()).AddHeaderPropagation();
            services.AddHttpClient(WellKnownSchemaNames.nextwareerpsalescrmservices, c =>
            {
                c.BaseAddress = new Uri($"http://{WellKnownSchemaNames.nextwareerpsalescrmservices}");
            }).AddHttpMessageHandler(() => new InvocationHandler()).AddHeaderPropagation();
            services.AddHttpClient(WellKnownSchemaNames.nextwareerpsalesmarketingservices, c =>
            {
                c.BaseAddress = new Uri($"http://{WellKnownSchemaNames.nextwareerpsalesmarketingservices}");
            }).AddHttpMessageHandler(() => new InvocationHandler()).AddHeaderPropagation();
            services.AddHttpClient(WellKnownSchemaNames.nextwareerpsalespointofsaleservices, c =>
            {
                c.BaseAddress = new Uri($"http://{WellKnownSchemaNames.nextwareerpsalespointofsaleservices}");
            }).AddHttpMessageHandler(() => new InvocationHandler()).AddHeaderPropagation();
            services.AddHttpClient(WellKnownSchemaNames.nextwareerpsalessalesservices, c =>
            {
                c.BaseAddress = new Uri($"http://{WellKnownSchemaNames.nextwareerpsalessalesservices}");
            }).AddHttpMessageHandler(() => new InvocationHandler()).AddHeaderPropagation();
            services.AddHttpClient(WellKnownSchemaNames.nextwareerpsalessubscriptionservices, c =>
            {
                c.BaseAddress = new Uri($"http://{WellKnownSchemaNames.nextwareerpsalessubscriptionservices}");
            }).AddHttpMessageHandler(() => new InvocationHandler()).AddHeaderPropagation();
            return services;
        }
    }
}

I have yet to review your Dapr sample in depth, but I am hoping that since we already adopted a similar end-point naming approach, and are using Dapr, that we won't have too much trouble refactoring our code-generation solution to support Aspire.

BTW, I am in love with the tracing and metrics... WOW!!!

I am having one issue that I am trying to track down. When I add a reference to one other API project (code-generated so these are similar in nature), I am getting a port (5001), already in use exception. I noticed Scott's AZ Demo that we can turn generate synthetized BICEP files, showing the deployment configurations.

Are there similar hidden commands that we can run to determine in my case where this duplicate port issue is originating from?

For example, the following Aspire viewed/captured error (below), is occurring because I am not including our Reaction Policy Service, which the service that is running attempts to connect with to retrieve Service Bus Event subscription and publications for that given service, however when I include that service or any other service, I get that 5001 exception.

image

This is a simple AppHost configuration example, that is failing with that duplicate 5001 port exception ...

image

davidfowl commented 10 months ago

Do these projects have a launchSettings? If they don't and they are ASP.NET Core projects, they will fallback to binding to 5000 by default. If you look at the dashboard, what is under Endpoints for each of those projects?

jkears commented 10 months ago

@davidfowl yes, there are launchSettings shown below... however no endpoints are showing up...

image

Exception ... image

launchSettings as follows

{ "iisSettings": { "windowsAuthentication": false, "anonymousAuthentication": true, "iisExpress": { "applicationUrl": "http://localhost:55257/", "sslPort": 0 } }, "profiles": { "IIS Express": { "commandName": "IISExpress", "launchBrowser": true, "launchUrl": "api/values", "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development" } }, "NextWare.ProjectName.DomainName.API": { "commandName": "Project", "launchBrowser": true, "launchUrl": "api/values", "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development" }, "applicationUrl": "http://localhost:55258/" }, "Docker": { "commandName": "Docker", "launchBrowser": true, "launchUrl": "{Scheme}://{ServiceHost}:{ServicePort}/api/values" } } }

{ "iisSettings": { "windowsAuthentication": false, "anonymousAuthentication": true, "iisExpress": { "applicationUrl": "http://localhost:55257/", "sslPort": 0 } }, "profiles": { "IIS Express": { "commandName": "IISExpress", "launchBrowser": true, "launchUrl": "api/values", "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development" } }, "NextWare.ProjectName.DomainName.API": { "commandName": "Project", "launchBrowser": true, "launchUrl": "api/values", "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development" }, "applicationUrl": "http://localhost:55258/" }, "Docker": { "commandName": "Docker", "launchBrowser": true, "launchUrl": "{Scheme}://{ServiceHost}:{ServicePort}/api/values" } } }

{ "iisSettings": { "windowsAuthentication": false, "anonymousAuthentication": true, "iisExpress": { "applicationUrl": "http://localhost:55269/", "sslPort": 0 } }, "profiles": { "IIS Express": { "commandName": "IISExpress", "launchBrowser": true, "launchUrl": "api/values", "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development" } }, "NextWare.ProjectName.DomainName.API": { "commandName": "Project", "launchBrowser": true, "launchUrl": "api/values", "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development" }, "applicationUrl": "http://localhost:55288/" }, "Docker": { "commandName": "Docker", "launchBrowser": true, "launchUrl": "{Scheme}://{ServiceHost}:{ServicePort}/api/values" } } }

jkears commented 10 months ago

... and for giggles, when I run this in Tye as per this ...

image

image

... I do not see any references to port 5001 anywhere. This is the exact set of services (excluding the Reaction Policy Service) which fail due to port 5001 already being defined.

jkears commented 10 months ago

Also, and likely a daft comment, but when I run only one of the 3 services (any one of them), by themselves, I do not see the duplicate 5001 exception, however, if at the same time I run NetStat with one service, I do not see anything listening at 5001. Same is true if I try to run all 2 or more, i.e. no port 5001 show up in NetStat and so I am assuming the use of 5001 is temporary otherwise would I not see a reference in NetStat listings?

davidfowl commented 10 months ago

It's picking the first launch profile. If you're not using IIS express, you can delete it, or you can use .WithLaunchProfile (e.g. WithLaunchProfile("NextWare.ProjectName.DomainName.API")) to pick the right one.

DamianEdwards commented 10 months ago

Also note that the launch profile name used to launch the AppHost project is matched to the launch profile name of the composed projects, e.g. if you launch the AppHost with launch profile "http", it will try to pick launch profile named "http" for all composed projects. This can be used to have separate launch profiles for the projects for when they're launched via the AppHost or launched in isolation.

jkears commented 10 months ago

@DamianEdwards and @davidfowl You are both awesome and I thank you for your inputs...

I cleaned up the launchsettings for all 3 services and no more duplicate 5001 issues...

image

Errors on UI Editor Services relate to Dapr ... image

The above errors are as a result of that service attempting to communicate with the Reaction Policy service (second in list) at start-up to obtain the Azure Service Bus events it needs to subscribe to and publish.

I will review the Dapr Aspire sample, and will work through the Dapr integrations to get them working.

The lesson here for me was that our launch settings were a mess. Because we used Tye, I don't think we were as dependent of launch settings. Once these were cleaned up, then the 5001 issues goes away and we see endpoints in the Dashboard. Thank you David, you were bang on as that being our core issue.

Also, I am now using the same name in the profile that I register with Aspire, thanks for pointing that out @DamianEdwards .. {

"profiles": {

"nextwareuibuildereditservices": {
  "commandName": "Project",
  "launchBrowser": true,
  "launchUrl": "api/values",
  "environmentVariables": {
    "ASPNETCORE_ENVIRONMENT": "Development"
  },
  "applicationUrl": "http://localhost:55250/"
}

} }

Our names are derived from our Domain Model, which is why they can be lengthy and less intuitive then perhaps could be. We will improve this using our Meta Model, to generate unique ports into our Meta Model service registry as each Domain model deploys into on every model generation. We will generate a unique launch setting per service API project and use our Meta Model service registry, to track deployments, and roll-backs.

BTW, We so loved spinning up 40 services using Tye. Tye helped to get us to where we are, but Aspire is like Tye on steroids and I love where you are taking this puppy.

Our initial use for Aspire is to stand-up an Aspire App Host that will over-arch our current Blazor based, Page Builder design (part of a larger, more comprehensive, Blazor App Builder) as illustrated in the attached design.

image

This design leverages .Net 8 features by mixing all Blazor component models, SSR, Static, plus server and WASM Interactive component models. The Page Builder will allow our users to design pages via drag-n-drop interface. As may be evident, we are including the ability to edit and test GraphQL integrations with the above GraphQL Gateway in the current Aspire App Host, so having Aspire over-arch front-end to back-end should greatly enhance our development life-cycle.

Anyway, thanks again for jumping in and providing me the guidance on Aspire. I realize it's early days for Aspire, but I just wanted to say thank you for this innovative solution, it's invaluable for us and the community at large!

Aspire ROCKS!

Cheers John

jkears commented 10 months ago

@davidfowl and @DamianEdwards

I reviewed the Dapr sample project but am still struggling with the Dapr side of things.

I followed the sample, however I think the issue I am having relates to the manner by which we integrate with Dapr sidecars within our current framework.

The following depicts the service composition that we currently are experimenting with, the services showing with dashes are not part of the current experiment ...

image

In the above diagram, we can run GraphQL queries on the Core Services GateWay that may span multiple Aggregates should they relate to one-another via Agg IDs. The GraphQL Gateway does a great job at dispatching the correct queries to the respective aggregate locations.

As such, we use'Named HTTP clients', which previously worked with Tye. With respect to intra-messaging between services we setup Named Clients so that it works with our GraphQL service provider (HotChocolate-HC), as HC only knows of the Named Http client, and has no idea we bridge this over dapr.

In Tye, we would register a named client as follows ...

 services.AddHttpClient(WellKnownCoreServicesSchemaNames.ReactionPolicyServices, c =>
 {
     c.BaseAddress = new Uri($"http://{WellKnownCoreServicesSchemaNames.ReactionPolicyServices}/graphQL");
 }).AddHttpMessageHandler(() => new InvocationHandler()).AddHeaderPropagation();

... where in this example, WellKnownCoreServicesSchemaNames.ReactionPolicyServices = ' nextwarecoreserviceseventintegrationreactionpolicyservices'.

Please note the use of **InvocationHandler()**.

This is a Dapr API that injects a Dapr side car for the given named client.

If I assume that the name passed in becomes both the named client that HC injects, and the DaprId, AppId for the Dapr SideCar, then the following AppHost configuration should work (as per the diagram layout above), but it does not ...

var builder = DistributedApplication.CreateBuilder(args);

// Reaction Policy Services 
var reactionPolicyService =
    builder.AddProject<Projects.NextWare_CoreServices_EventIntegration_ReactionPolicyServices_API>(
        "nextwarecoreserviceseventintegrationreactionpolicyservicesapi")
    .WithDaprSidecar("nextwarecoreserviceseventintegrationreactionpolicyservices");

// GraphQL Gateway Project that spans multiple Core Services (todo: add the other Core Services aside from ReactionPolicyServices)
var coreServicesGateWay =
    builder.AddProject<Projects.NextWare_GW_Core_Services_GraphQL_Gateway>("nextwarecoreservicesgwgatewayservicesapi")
        .WithReference(reactionPolicyService)
    .WithDaprSidecar("nextwarecoreservicesgwgatewayservices");

// UI Editor Services integrates with core services gateway to query ReactionPolicyServices

var editServicesAPI = 
    builder.AddProject<Projects.NextWare_UI_Builder_EditServices_API>("nextwareuibuildereditservicesapi")
    .WithDaprSidecar("nextwareuibuildereditservices")
    .WithReference(coreServicesGateWay);

// GraphQL Gateway Project that spans Editor Services and UI Builder
builder.AddProject<Projects.NextWare_UI_Builder_GraphQLGateway>("nextwareuibuildergatewayapi")
    .WithDaprSidecar("nextwareuibuildergateway")
    .WithReference(editServicesAPI);

builder.Build().Run();

image

Questions:

  1. In Tye, we use to see a separate SideCar per each service and Tye would randomize the port for that sidecar but in the case of 'WithDaprSidecar' extension, where is that happening?

  2. Should we be creating a profile per each SideCar inside the same launchsettings of each respective service?

  3. Because of how I create the Name HTTP Client, do we need the WithDaprSideCar?

Sorry to be a pest but I feel I am super close and this would be awesome to get resolved for the community.

Cheers John

jkears commented 10 months ago

I completely missed this one line ...

image ... and with that I now see the Dapr exes running in Aspire Dashboard... image

While that has not fully resolved all issues, I did see a successful GraphQL query between 2 services, via the Dapr side-car.

Sweet stuff!

jkears commented 10 months ago

Victory is mine!

From an running service in a back-ground task that resolves Service Bus Event subscription retrieval from another service indirect via a GraphQL Gateway service ... sweet jimmity-crickets it works!

image

davidfowl commented 10 months ago

In preview2, that line will be called implicitly with any call to WithDaprSideCar 😄

jkears commented 10 months ago

@davidfowl thank you for that insight.

We are all in on Aspire, and have already discovered issues we had no idea were in play... as can be seen in the trace below, the last two two transactions are both acquiring a Client Credential access token from our Azure, B2C, which we use as a form of trust between internal services via Dapr. It's obvious our in memory cache of said token is not working.

Given this particular transaction, only occurs once and at that start of a service, running on a background thread, it's not a show stopper, but thanks to Aspire we at least know of that issue.

image

We look forward to preview2 and the evolution of Aspire.

Cheers

jkears commented 10 months ago

The final V1 design that illustrates where Aspire plugs in, which is just about everywhere.

image

The trace spans tell us so what truly is happening under the covers, provides a more comprehensive understanding of how data flows through out our apps.

Many thanks Aspire Team, great job!