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

Feature Proposal: Centralized Swagger UI for OpenAPI Specification Files #3608

Open
AClerbois opened this issue Apr 11, 2024 · 28 comments
Open
Labels
area-integrations Issues pertaining to Aspire Integrations packages
Milestone

Comments

@AClerbois
Copy link

Feature Proposal

Summary

In the context of the .NET Aspire project, I propose to add a new feature aimed at consolidating all OpenAPI specification files across the project into a unified Swagger UI interface. This would greatly enhance the developer experience by providing a single, centralized location to view and interact with the API documentation generated from OpenAPI specifications.

Problem Statement

Currently, OpenAPI specification files are scattered throughout various parts of our project, making it challenging to locate, view, and test different APIs efficiently. Developers and API consumers have to navigate through multiple Swagger UI instances or look into different directories to find the relevant API documentation. This fragmentation hinders productivity and can lead to inconsistencies in how APIs are understood and used.

Proposed Solution

Implement a mechanism within the .NET Aspire project that automatically scans the project directories for OpenAPI specification files (*.json or *.yaml). Once identified, these files will be aggregated into a single Swagger UI instance. This unified Swagger UI will be automatically updated to reflect changes in the OpenAPI specifications, ensuring that the documentation is always current.

Benefits

  • Centralization: Provides a one-stop-shop for all API documentation, making it easier for developers to find and use the APIs they need.
  • Improved Developer Experience: Facilitates a smoother development process by reducing the time spent searching for API documentation.
  • Consistency: Ensures that all API documentation is presented in a consistent manner, aiding in understanding and integration efforts.
  • Automation: Reduces manual effort required to maintain API documentation visibility as the project evolves.

Implementation Considerations

  • The scanning mechanism should be configurable to allow for inclusion or exclusion of specific directories or files.
  • Consideration should be given to how the unified Swagger UI is hosted within the project (e.g., as part of the build process, within a container, etc.).
  • Security implications of aggregating and exposing API documentation in a single location should be assessed and mitigated.

Request for Comments

I invite the community to provide feedback on this proposal. Any insights on potential challenges, additional benefits, or alternative approaches to achieving this goal would be greatly appreciated.

@davidfowl davidfowl added this to the Backlog milestone Apr 12, 2024
@davidfowl
Copy link
Member

Cool idea, related to #2980

@sayedihashimi
Copy link
Member

If we do this we should also support it in Visual Studio.

@davidfowl davidfowl added feature area-integrations Issues pertaining to Aspire Integrations packages and removed area-dashboard labels Sep 16, 2024
@Foxtrek64
Copy link

Just some (half-baked) general thoughts to help spur discussion

  • Dedicated Swagger service container
var builder = DistributedApplication.CreateBuilder(args);
var swagger = builder.AddSwaggerHost("swagger");

builder.AddProject<Projects.MyCoolProject>("web")
       .WithReference(swagger);
  • A few discovery mechanism ideas
var swagger = builder.AddSwaggerHost("swagger")
    .WithSwaggerFile("**/*.swagger.json"); // With glob to point to swagger files

var someService = builder.AddNonDotNetService("foo")
    .WithSwagger(); // Search output path for swagger file. Possibly accept glob pattern again. Intended scenario is including a swagger file from a docker image or something similar.

var web = builder.AddProject<Projects.MyCoolProject>("web")
    .WithReference(swagger);

// In MyCoolProject.Project.cs
var builder = WebApplication.CreateBuilder(args);
builder.AddSwagger("swagger"); // Wraps standard swagger packages to produce the swagger json file, then provides that back to the swagger host.

My Questions

  • What kind of ergonomics do we want here?
  • Should supporting DotNet projects (a la AddProject<TProject>(string)) feel different than supporting non-DotNet projects (e.g. a PHP web service)?
    • If so, how? Why?
  • How should we handle IDP support, e.g. if Keycloak is used as an auth source for the application overall, how do we secure Swagger with it?
    • This likely falls under the umbrella of IDP integration with Aspire overall and might be a little out of scope.

@davidfowl davidfowl removed the feature label Oct 16, 2024
@captainsafia
Copy link
Member

@Foxtrek64 I like the way that you've modeled this implementation as an integration in your proposal, with both a client and hosting component.

In .NET 9, we added built-in support for OpenAPI document generation via the Microsoft.AspNetCore.OpenApi package. We could consider what it would look like to add a client integration for this, that could help solve issues like this one.

With regard to the hosting side of this, I saw it being inverted a bit:

var apiService1 = builder.AddProject<>();
var apiService2 = builder.AddProject<>();

var swagger = builder.AddSwaggerUi()
  .WithReference(apiService1)
  .WithReference(apiService2);

This way a user could navigate to a certain gateway that incorporated the OpenAPI documentation for all the API services in their application. You could potentially use the document switcher UI to keep things truly centralized.

Side note: although Swagger UI is popular, I've become quite partial to Scalar over the past few months. It would be interesting to explore adding an Aspire hosting integration for Scalar that provided this functionality. @xC0dex -- what are your thoughts on prototyping an Aspire hosting integration around Scalar and seeing what it would look like?

Should supporting DotNet projects (a la AddProject(string)) feel different than supporting non-DotNet projects (e.g. a PHP web service)?
If so, how? Why?

I'm inclined to say the ergonomics should be the same across all project types, especially if the API reference document is an independent resource in-and-off itself instead of something that gets added via a WithReference on the API service....or do we support both models?

One more thing, I was originally gonna close this issue in favor of centralizing all the discussion in #2980 but I think we should keep these two as independent and separate ideas.

  • This issue is about adding hosting/client integrations for OpenAPI document + Swagger UI/Scalar UI/etc.
  • The other issue is about providing an in-dashboard experience for ad-hoc testing and would require spinning up a new UI altogether.

I think the first issue (this one) is a lot more tractable given the APIs that we currently support and the prototyping that has already been done in this project.

@Foxtrek64
Copy link

Foxtrek64 commented Oct 25, 2024

@captainsafia I like the inverted model. By providing the service reference to the service with the open api spec, it makes it really easy to keep the ergonomics consistent regardless of whether the service generating the open api doc is .NET or not.

Also, I think the best way to handle the question of swagger vs scalar or other potential implementations could be something like

var openApi = builder.AddOpenApi();
var swagger = openApi.WithSwaggerUi();
var scalar = openApi.WithScalarUi();
// others

Essentially, the AddOpenApi() piece would produce an OpenApiBuilder which is responsible for aggregating all of the input documents. Vendors, be it Swagger or Scalar or someone else, can then come in and extend OpenApiBuilder to provide output to their web interface.

This also means a really nice tie-in to #2980 - the in-dashboard experience would simply be another output source, just like Swagger and Scalar.

builder.AddOpenApi().WithDashboardUi();

Edit: component registration example
I imagine that the order in which things are added to the builder do not matter, and you should in theory be able to add multiple front ends here - each would just register a container hosting Swagger or Scalar or whatever at its own endpoint. I can't think of a scenario for when you'd want that, but I don't see why it wouldn't be possible.

var openApi = builder.AddOpenApi()
    .WithScalarUi()
    .WithReference(apiService1)
    .WithReference(apiService2);

@captainsafia
Copy link
Member

Also, I think the best way to handle the question of swagger vs scalar or other potential implementations could be something like

var openApi = builder.AddOpenApi();
var swagger = openApi.WithSwaggerUi();
var scalar = openApi.WithScalarUi();
// others

I assume this is code that someone would write in their app host. If so, what purpose does the AddOpenApi call and OpenApiBuilder serve? Is it mostly so you can chain different front-end to the same resource instance?

I think it is a comparison between:

var openApi = builder.AddOpenApi()
    .WithScalarUi()
    .WithSwaggerUi()
    .WithReference(apiService1)
    .WithReference(apiService2);

OR

var scalar = builder.AddScalarUi();
var swagger = builder.AddSwaggerUi();

scalar
  .WithReference(apiService1)
  .WithReference(apiService2);

swagger
  .WithReference(apiService1)
  .WithReference(apiService2);

With the first option being DRYer but requiring more new API. I don't think there's a technical need for an OpenApiBuilder (for example, some requirement to share state between the two that isn't available on IResourceBuilder) but not sure...

@xC0dex
Copy link

xC0dex commented Oct 25, 2024

Hey @captainsafia,
this sounds like a great idea! I'm a fan of Scalar too, so I would be happy to work on an integration for Aspire. I haven’t worked with Aspire before, so I might need a little support 👀.

@captainsafia
Copy link
Member

@xC0dex Nice! I'd recommend checking out https://github.com/davidfowl/AspireSwaggerUI as an example to start with. You'll notice if you look at this file that it uses an approach for serving the Swagger UI that is very familiar to what the Scalar.AspNetCore package currently does so you can use that as a building block for this.

Most of the Aspire-specific logic is in this file and deals with wiring up the endpoints directly to what the API service is actually listening on.

Note: the repo above doesn't use the inverted model that I proposed in #3608 (comment) but it's a good start nonetheless.

@Foxtrek64
Copy link

I assume this is code that someone would write in their app host. If so, what purpose does the AddOpenApi call and OpenApiBuilder serve? Is it mostly so you can chain different front-end to the same resource instance?

Essentially, yes. I think the primary concern here is the input logic - how we aggregate the various sources into a single resource instance that can be given to the chosen front-end. In my opinion, as someone implementing Swagger or Scalar or whatever else, I don't necessarily care where my input comes from or how it's created, I just care that I get input that is in the correct format. I also don't feel it's necessarily my responsibility to need to implement that logic myself. This would lead to a situation where there is inconsistency between builders.

I don't think there's a technical need for an OpenApiBuilder (for example, some requirement to share state between the two that isn't available on IResourceBuilder) but not sure...

I agree. Having looked at some other examples already in the ecosystem, it seems implementing our own builder type would be somewhat unusual. Take this example with Postgres, for example:

var database = builder.AddPostgres("database") // IResourceBuilder<PostgresServerResource>
    .WithPgAdmin();

Here, they're doing what we're trying to accomplish - some back-end service with a bolt-on GUI. I think it may be good to follow the example here - forego the OpenApiBuilder type for IResourceBuilder<OpenApiResource> or something of the like.

With this in mind, I think the first option makes the most sense, both from a consumer standpoint and from a vendor standpoint.

var openApi = builder.AddOpenApi() // IResourceBuilder<OpenApiResource>
    .WithDashboardUi()
    .WithResource(apiResource1)
    .WithResource(apiResource2);

@MermaidIsla
Copy link

I was bored over the weekend so I decided to throw together a quick partial prototype of this feature, in the current context that would be the builder.AddOpenApi().WithDashboardUi();.

I haven't worked with the aspire repo before so this is my first attempt at making something functional.

Forked repo here

Prototype showcase:
https://github.com/user-attachments/assets/ccfeccdb-0d41-4121-a478-b8c1c977441f

@samsp-msft
Copy link
Member

@mikekistler FYI

@samsp-msft
Copy link
Member

If we do this we should also support it in Visual Studio.

+1 - personally, I think that the .http file support is a much better experience than the web based explorers. What is (or was the last time I looked) a good experience to go from a list of APIs on an endpoint to generating a block in the http file for calling that API.

For scenarios like this, I would like to improve the integration between VS and the Aspire dashboard. If an Aspire AppHost has the swagger annotations, what if that resulted in a "API Test" command in the resource view in the dashboard. Clicking it would take you to the .http file for that service, and enumerate the endpoints/APIs available.

@Foxtrek64
Copy link

Foxtrek64 commented Oct 29, 2024

For scenarios like this, I would like to improve the integration between VS and the Aspire dashboard. If an Aspire AppHost has the swagger annotations, what if that resulted in a "API Test" command in the resource view in the dashboard. Clicking it would take you to the .http file for that service, and enumerate the endpoints/APIs available.

To make sure I'm understanding your idea - if we take my example above, you're proposing that we take the AddOpenApi() and bake it in to the Aspire AppHost. And if it detects an OpenAPI file for any members, it automatically renders it on a built-in page in the dashboard - no WithDashboardUi() required.

I'd be on board with that approach, provided that we get a way to extract that OpenAPI information in case someone wants to expose Swagger or Scalar or some alternative UI, which could be good for production purposes where the Aspire Dashboard may be less appropriate.

@MermaidIsla
Copy link

With this in mind, I think the first option makes the most sense, both from a consumer standpoint and from a vendor standpoint.

var openApi = builder.AddOpenApi() // IResourceBuilder<OpenApiResource>
    .WithDashboardUi()
    .WithResource(apiResource1)
    .WithResource(apiResource2);

I've been thinking about this and I probably like this design the most because it leaves a room for easy configuration.

Let's say I would want to configure the path to the OpenApi document. I think something like builder.AddOpenApi(["/path/to/document.json"]) would be nice. It being an array, it would also allow to specify multiple paths.

Further more I think it would be nice to also have the ability to define specific paths for selected resources, so something like .WithResource(apiResource, ["/path/to/extra/document.json"]).

So putting it all together it would look something like this:

var openApi = builder.AddOpenApi(["/path/to/document.json"]) // IResourceBuilder<OpenApiResource>
    .WithDashboardUi()
    .WithResource(apiResource1)                                    // This has one document path.
    .WithResource(apiResource2, ["/path/to/extra/document.json"]); // This has two document paths.

@xC0dex
Copy link

xC0dex commented Nov 1, 2024

Hey @captainsafia,

I’ve put together an MVP for the Scalar integration, based on the AspireSwaggerUI project. Currently, it uses the CDN version of the Scalar API Reference and isn’t configurable yet. From my understanding, the plan for how the UI will be added isn’t fully settled yet, so I didn't spend much time there.

Even though it’s still in a very early stage, I was wondering: should the UI/integration be fully configurable? And would it make sense to use the Scalar.AspNetCore package here, or should everything be shipped through Aspire or an Aspire integration package? I think it would also be possible to update the package or introduce a new one.

During the development, I thought it would enhance the developer experience if the OpenAPI documents from all services could be fetched automagically. I’m considering an endpoint that could provide all the necessary information needed for the integration.

@Foxtrek64
Copy link

I've been thinking about this and I probably like this design the most because it leaves a room for easy configuration.

Let's say I would want to configure the path to the OpenApi document. I think something like builder.AddOpenApi(["/path/to/document.json"]) would be nice. It being an array, it would also allow to specify multiple paths.

Further more I think it would be nice to also have the ability to define specific paths for selected resources, so something like .WithResource(apiResource, ["/path/to/extra/document.json"]).

I really like this configuration approach. A params string[] would work very nicely here. That said, I would really like to push for globbing via Microsoft.Extensions.FileSystemGlobbing. I think it'd be nice to have something like this:

// Just include paths
builder.AddOpenApi(["**/*.document.json", "**/*.swagger.json"]);

// More complicated example. This is NOT intended to be equivalent to the
// preceding example.

Matcher matcher = new();
matcher.AddInclude("**/*.document.json");
matcher.AddExclude("**/*.swagger.json");
builder.AddOpenApi(matcher);

This would of course also apply to IResourceBuilder<OpenApiResource>#WithResource() as well, accepting either params string or a matcher instance.

@MermaidIsla
Copy link

Ooh, I wasn't aware of that class. I like it. This then gives me an idea of having two string[] in builder.AddOpenApi() method allowing to specify both include paths and exclude paths:

// First parameter are include paths, second parameter are exclude paths.
builder.AddOpenApi(["**/*.document.json"], ["**/*.swagger.json"]);

// This complicated example below now matches the preceding example.
Matcher matcher = new();
matcher.AddInclude("**/*.document.json");
matcher.AddExclude("**/*.swagger.json");
builder.AddOpenApi(matcher);

// Writing just exclude paths would be like this
builder.AddOpenApi([], ["**/*.swagger.json"]);

@MermaidIsla
Copy link

I just thought of a question "Would you be able to use multiple UIs at the same time?" I think I can explain it best with the following scenario.

The Scenario

I have some public API services and some internal API services along with possibly more services described in one AppHost.

var builder = DistributedApplication.CreateBuilder(args);

// ... some other services possibly

/* My projects */

var publicApiService1 = builder.AddProject<>();
var publicApiService2 = builder.AddProject<>();

var internalApiService1 = builder.AddProject<>();
var internalApiService2 = builder.AddProject<>();

/* OpenAPI */

builder.AddOpenApi()
    .WithSwaggerUi()
    .WithReference(publicApiService1)
    .WithReference(publicApiService2);

builder.AddOpenApi()
    .WithScalarUi()
    .WithReference(internalApiService1)
    .WithReference(internalApiService2);

builder.Build().Run();

The Question

Would I be able to use multiple UIs, in this case Swagger and Scalar, at the same time or would it throw an exception?

My initial thought is no as it would go against this feature proposal for a centralized UI. In other words it would no longer be compliant with:

Provides a one-stop-shop for all API documentation, making it easier for developers to find and use the APIs they need.

Why would you need two UIs?

Let's assume there are two companies A and B. "Company A" provides public API services. "Company B" would like to use "Company A's" public API services for their app development, for example. It would therefore be nice to give "Company B" a UI to be able to easily develop their app, while still keeping a UI for all "Company A's" internal API services.

There could also be a situation where "Company B" prefers to work with Swagger while "Company A" prefers to work with Scalar.

@Foxtrek64
Copy link

Foxtrek64 commented Nov 20, 2024

I just thought of a question "Would you be able to use multiple UIs at the same time?"

I believe there are valid use cases for wanting multiple UIs. Preventing its use would just be an artificial limitation anyways, and we can't assume to know all uses cases. So making it as open as possible is the best course of action, in my opinion.

A simple example of this is one that applies to me - I've only ever used Swagger, but now I've heard of this new Scalar tool and I want to try it out. So my code diff could look like this:

  builder.AddOpenApi()
      .WithSwaggerUi()
+     .WithScalarUi()
      WithReference(...);

Now I have the ability to try out both.

Also on a semi-related note, if #2980 is merged with it being enabled should any openapi specs be found rather than with an explicit WithDashboardUi(), I think running multiple front-ends will probably be standard practice. As much as I think the Dashboard UI will be helpful, programmers tend to be opinionated and as such will have a preference for their front end.

It would go against the feature proposal for a centralized UI

I think the discussion has lead us in a direction where we don't want that.
Instead, I think the discussion points us towards a system from which any UI can retrieve OpenAPI specifications.
In other words, it's up to Scalar or Swagger to provide the "one stop shop for all API documentation". The Aspire component would just provide the glue between the API testing interface and Aspire.

@Foxtrek64
Copy link

Foxtrek64 commented Nov 22, 2024

Just a thought - I wonder if a parameter to limit the environments the OpenApi engine is loaded in would be good, just because I feel like it would be a common option.

builder.AddOpenApi(); // Always add OpenApi
builder.AddOpenApi(builder.Environment.IsDevelopment() || builder.Environment.IsStaging());

Amendment: This accepts a boolean value just because it makes it easy to work with the existing IHostEnvironment provided by the builder. This also makes it easy to accept other conditions as well, which may be a strong argument in favor of accepting a boolean here rather than specifically limiting it to environments (e.g. via an enum).

Amendment 2: This could also be done by wrapping builder.AddOpenApi() in an if statement, but I feel like conditionally enabling/disabling OpenApi support will be such a common operation that it makes sense to have support for that built in.

@MermaidIsla
Copy link

Amendment 2: This could also be done by wrapping builder.AddOpenApi() in an if statement, but I feel like conditionally enabling/disabling OpenApi support will be such a common operation that it makes sense to have support for that built in.

Isn't this exactly how it's been done when Swagger was still part of ASP.NET template? Like you would do I believe this:

var builder = WebApplication.CreateBuilder(args);

if (builder.Environment.IsDevelopment())
{
    builder.Services.AddOpenApi();
}

var app = builder.Build();

if (app.Environment.IsDevelopment())
{
    app.UseSwagger();
}

app.Run();

I believe this is still a common practice amongst many developers and even in .NET 9 I don't think it has changed much.

@MermaidIsla
Copy link

I really like this configuration approach. A params string[] would work very nicely here. That said, I would really like to push for globbing via Microsoft.Extensions.FileSystemGlobbing. I think it'd be nice to have something like this:

// Just include paths
builder.AddOpenApi(["**/*.document.json", "**/*.swagger.json"]);

// More complicated example. This is NOT intended to be equivalent to the
// preceding example.

Matcher matcher = new();
matcher.AddInclude("**/*.document.json");
matcher.AddExclude("**/*.swagger.json");
builder.AddOpenApi(matcher);

This would of course also apply to IResourceBuilder<OpenApiResource>#WithResource() as well, accepting either params string or a matcher instance.

I re-read this feature proposal again and I feel like I'm missing something here. Correct me if I'm wrong, but globbing would require to know all valid url paths to each individual API services ahead of time at the AppHost level.

Let's say I would want to configure the path to the OpenApi document. I think something like builder.AddOpenApi(["/path/to/document.json"]) would be nice. It being an array, it would also allow to specify multiple paths.

Whereas what I was proposing was that you tell the AppHost exactly where to look for OpenApi documents because it knows addresses to each individual API services so it would do a simple HTTP request for all of them.

For example http://localhost:1234/path/to/document.json. This would either return Status200Ok meaning the data returned is likely an OpenApi document or it would return Status404NotFound meaning no OpenApi document exists.

So my question then is: How does the AppHost know where to look for OpenApi documents?

@Foxtrek64
Copy link

Foxtrek64 commented Dec 23, 2024

I re-read this feature proposal again and I feel like I'm missing something here. Correct me if I'm wrong, but globbing would require to know all valid url paths to each individual API services ahead of time at the AppHost level.

Perhaps I'm misunderstanding your concern, but perhaps something like this could resolve it? I see two primary scenarios:

  1. The OpenApi document is being accessed from a location on disk. As an example, if my Aspire service is made up of three ASP.NET Core apps, and each of them create a Swagger definition file at $(MSBuildThisProjectDirectory)swagger.json, then I can set up a glob to look for **/swagger.json and it would return an enumerable of all of the swagger files in child directories of the working directory.
  2. For web-based services, we already have a discovery model for this. When adding a database, service, you provide a connection name and consumers can reference it by name. I think we could do something similar with OpenAPI:
var builder = DistributedApplication.CreateBuilder(args);

Matcher serviceMatcher = new();
serviceMatcher.AddInclude("**/*.swagger.json");

var service1 = builder.AddProject<Projects.DiskService>(); // Dumps "{version}.swagger.json" to the project directory.
var service2 = builder.AddProject<Projects.WebService>("service2"); // Available at https+http://service2/path/to/swagger.json

var openApi = builder.AddOpenApi()
    .AddScalarUi()
    .WithLocalOpenApiDocument(service1, serviceMatcher) // Relies on copying the generated openapi document to the output location.
    .WithOpenApiUri(service2, "path/to/swagger.json");

In both cases, both would get an enumerable of paths on disk. The WithOpenApiUri() would send a GET request and download the document to a well known working location, then return the path to that document.

There could be some cleanup/simplification here I'm sure, but I think this would at least cover our bases. Do let me know what you think or if I was able to answer your question.

@MermaidIsla
Copy link

MermaidIsla commented Dec 23, 2024

I wasn't aware that you can generate OpenApi documents as a file. I'm only used to having them being served through HTTP GET method. This does explain the use of matcher nicely now.

Though, if I'm reading it correctly:

The WithOpenApiUri() would send a GET request and download the document to a well known working location, then return the path to that document.

This would mean saving it to a file, right? Is it necessary to do that? Not exactly sure about pros or cons of this solution.

In fact probably a better question: Is it better to have all OpenApi documents saved locally on a disk or have them all be available through HTTP GET method?

@Foxtrek64
Copy link

I wasn't aware you can generate OpenApi documents as a file.

I believe ASP.NET's OpenApi generator can spit out local files, but I wanted to provide this functionality to allow additional flexibility. There may be some services out there that provide the file on disk and you want to access it directly.

That would mean saving it to a file, right? Is it necessary to do that?

Good question. Technically speaking, you could save it in memory or just download it on the fly, but some sort of temporary local caching may be good if only for performance reasons. Some documents can be quite large depending on how they break up their APIs, which could lead to a large amount of bloat in memory.

I imagine mechanically, it would be simplest to provide a list of file paths to the front-end service and let it determine how to handle them.

Is it better to have all OpenApi documents saved locally on a disk or have them all be available through HTTP GET method?

I think even if we were to specify a preference, not all vendors will be able to comply with that preference. So the flexibility is important. As for providing them to our chosen front-end, I think the flat file method is probably the best just due to its simplicity.

As an aside, it could be interesting to parse all of the documents and merge them into some sort of in-memory database such as Redis, then have the database be consumed by whatever front-end. That would require vendor buy-in, but it could be a compact solution.

@MermaidIsla
Copy link

Technically speaking, you could save it in memory or just download it on the fly, but some sort of temporary local caching may be good if only for performance reasons.

I feel like there would probably have to be some kind of option to enable caching and disable caching. For example if I'm developing an ASP.NET web api service, I might not want caching on my local machine as I may be in very early stages of the development where things are changing constantly. However another example can be once an ASP.NET web api service is done and is put let's say on a staging environment on a remote server together with more web api services where I would want caching enabled.

The question then becomes: Would there also be different caching methods available? Would there just be one predefined?

I know the answer is probably: "It's better to let the developer have a choice." but sometimes I feel like too many choices may lead to a very complicated system. I guess I'm struggling to answer whether the pros outweigh the cost to build such system.

As an aside, it could be interesting to parse all of the documents and merge them into some sort of in-memory database such as Redis, then have the database be consumed by whatever front-end. That would require vendor buy-in, but it could be a compact solution.

This brings up a really good question as I have no idea who's job it would to be to parse all these documents. Considering there are both local and remote environments, I think local has an easy answer while remote environment currently doesn't really have an answer.

As far as I'm aware currently there is no such Aspire service that would act as a middle man between all the apis/services/databases and all the consumers like ones that collect OpenTelemetry or OpenApi documents.

I'm probably already going out of scope of this feature proposal but it almost feels like it would be great to have something like for the lack of a better word now: Aspire Proxy - a service that just acts like a middle man between all services and consumers, making it easier to hook up multiple consumers for the same purpose such as Swagger and Scalar for OpenApi documents or Aspire Dashboard and Seq for OpenTelemetry data.

@Foxtrek64
Copy link

Foxtrek64 commented Dec 23, 2024

For simplicity, the intial release may benefit from a lack of caching intitially. We could prvide an extension later to provide caching via MemoryCache/Redis/etc. That also provides a nice mechanism for enabling/disabling caching.

var openApi = builder.AddOpenApi().WithScalarUi();

if (Environment.IsStaging() || Environment.IsProduction())
{
    openApi.AddRedisCache(); // Overloads for accepting a redis service and config options.
}

Edit: I still think it should be the responsibility of the Ui implementations to parse and process these documents. Our open api components (via builder.AddOpenApi()) should only aggregate the documents for presentation.

If we later add a caching option, this caching instance would behave like the UI implementation and parse out the documents. We'd probably need some changes internally to support retrieving information from the cache, but we can look at that later.

@MermaidIsla
Copy link

For simplicity, the intial release may benefit from a lack of caching intitially.

Yes, I agree with this 100%.

Edit: I still think it should be the responsibility of the Ui implementations to parse and process these documents. Our open api components (via builder.AddOpenApi()) should only aggregate the documents for presentation.

I must be missing something extremely obvious. Where are the OpenApi documents going to be aggregated exactly if we're talking about a non local environment? I completely understand how it's gonna work on a local machine. I'm just really confused as to how it should work on other environments, if it's even supposed to work (I hope it does, otherwise I feel like it may be losing a full potential).

I created a .NET Aspire Starter App which has an API service and a Web service, along with AppHost and ServiceDefaults projects. When I do a manifest publish, I only get the two services:

{
  "resources": {
    "apiservice": {
      ...
    },
    "webfrontend": {
      ...
    }
  }
}

(I'm actually surprised the dashboard isn't included, but that's likely because I need to enable some kind of configuration)

Anyway what I mean by that there is no AppHost or any other service that would be able to do the job of "aggregating" all the OpenApi documents. Parsing the documents can be done by individual consumers, whether that's Swagger, Scalar or Dashboard, that's absolutely fine.

If this feature proposal means to create a new internal service, shouldn't such service be much more generic and have more purpose? Additionally shouldn't it also work the same on the local machine so there aren't two completely different ways of achieving the same thing?

(If it has been said before, I apologize but it seems like I'm starting to get a little bit lost in where exactly does this feature proposal aims to go)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area-integrations Issues pertaining to Aspire Integrations packages
Projects
None yet
Development

No branches or pull requests

8 participants