dotnet / aspire

An opinionated, cloud ready stack for building observable, production ready, distributed applications in .NET
https://learn.microsoft.com/dotnet/aspire
MIT License
3.56k stars 389 forks source link

Extending Aspire orchestration capability to Java-based apps #4491

Open justinyoo opened 2 months ago

justinyoo commented 2 months ago

Background and Motivation

.NET Aspire, as an orchestration engine to build a clound-native application, is capable of bringing other type of applications written in different language like Java (Spring Boot for example). The current API offers both .AddContainer(...) and .AddExecutable(...) for the generic purpose.

However, it would be great if we can provide Java-specific extension methods like AddJavaApp(...) for generic Java app and AddSpringApp(...) for Spring-focused app, as we provide AddNodeApp(...) and AddNpmApp(...).

By doing so, we can wrap Java-specific arguments pre-built so that devs only focus on passing arguments they really need.

I've PoC'd for this suggestion here: https://github.com/aliencube/aspire-contribs/tree/main/src/Aspire.Contribs.Hosting.Java

Proposed API

I've got two API suggestions – one based on ExecutableResource and the other based on ContainerResource.

ExecutableResource-based API

public static class JavaAppHostingExtension
{
    // Add Java app
    public static IResourceBuilder<JavaAppExecutableResource> AddJavaApp(
        this IDistributedApplicationBuilder builder,
        string name,
        string workingDirectory,
        JavaAppExecutableResourceOptions options)
    {

    // Add Spring app
    public static IResourceBuilder<JavaAppExecutableResource> AddSpringApp(
        this IDistributedApplicationBuilder builder,
        string name,
        string workingDirectory,
        JavaAppExecutableResourceOptions options)
    {

ContainerResource-based API

public static class JavaAppHostingExtension
{
    // Add Java app
    public static IResourceBuilder<JavaAppContainerResource> AddJavaApp(
        this IDistributedApplicationBuilder builder,
        string name,
        JavaAppContainerResourceOptions options)
    {

    // Add Spring app
    public static IResourceBuilder<JavaAppContainerResource> AddSpringApp(
        this IDistributedApplicationBuilder builder,
        string name,
        JavaAppContainerResourceOptions options)
    {

I'd also like to introduce the options pattern here to pass some common options like:

// JavaAppExecutableResourceOptions
public class JavaAppExecutableResourceOptions
{
    public string? ApplicationName { get; set; } = "target/app.jar";
    public int Port { get; set; } = 8080;
    public string? OtelAgentPath { get; set; } = null;
    public string[]? Args { get; set; } = null;
}

// JavaAppContainerResourceOptions
public class JavaAppContainerResourceOptions
{
    public string? ContainerRegistry { get; set; } = "docker.io";
    public string? ContainerImageName { get; set; }
    public string ContainerImageTag { get; set; } = "latest";
    public int Port { get; set; } = 8080;
    public int TargetPort { get; set; } = 8080;
    public string? OtelAgentPath { get; set; } = null;
    public string[]? Args { get; set; } = null;
}

NOTE that the OtelAgentPath should be provided for Java app to be OpenTelemetry-able.

Therefore, based on the options instance, devs call the same method, AddSpringApp(...), and the method uses either container or executable.

Usage Examples

With this API suggestion, devs can orchestrate like this:

var containerapp = builder.AddSpringApp(
                              "containerapp",
                              new JavaAppContainerResourceOptions()
                              {
                                  ContainerRegistry = "docker.io",
                                  ContainerImageName = "my-org/my-spring-app",
                                  Port = 8080,
                                  TargetPort = 8080,
                                  OtelAgentPath = "/agents"
                              });

var executableapp = builder.AddSpringApp(
                               "executableapp",
                               workingDirectory: "../Aspire.Contribs.Spring.Maven",
                               new JavaAppExecutableResourceOptions()
                               {
                                   ApplicationName = "target/spring-maven-0.0.1-SNAPSHOT.jar",
                                   Port = 8085,
                                   OtelAgentPath = "../../agents",
                               });

var webapp = builder.AddProject<Projects.Aspire_Contribs_WebApp>("webapp")
                    .WithExternalHttpEndpoints()
                    .WithReference(containerapp)
                    .WithReference(executableapp)
                    .WithReference(apiapp);

Alternative Designs

Risks

davidfowl commented 2 months ago

I think we need to unify the resource types:

  1. I want to be able to run a spring app as a java process
  2. I want to be able to run a spring app in a container
  3. I want to publish a spring app as a container

We have a pattern for 1 and 3 today, this new resource type is number 2. I wonder if there's a way we can represent number 2 without a split resource type.

The other thing I would mention is that that the options object is a bit different from our existing builder pattern API (not sure if it's a bad thing). Feels similar to https://github.com/dotnet/aspire/issues/4472 for .NET project resources. There needs to be a way to say, use this endpoint name as the spring boot server port.

I'd like @mitchdenny's take on this.

justinyoo commented 2 months ago
  1. I want to be able to run a spring app as a java process
  2. I want to be able to run a spring app in a container
  3. I want to publish a spring app as a container

Currently, the .AddExecutable(...) method supports the pattern 1, and the .AddContainer(...) method supports the pattern 3. For the pattern 2, it's already published to a container registry, and we can simply refer it. My gut feeling is that both pattern 2 and 3 can be consolidated, as long as the pattern 2 has a Dockerfile. Let me double check on my end.

The other thing I would mention is that that the options object is a bit different from our existing builder pattern API (not sure if it's a bad thing). Feels similar to https://github.com/dotnet/aspire/issues/4472 for .NET project resources. There needs to be a way to say, use this endpoint name as the spring boot server port.

The introduction of the ...Options class is merely to simplify the number of commonly used arguments.

For the executable (pattern 1), it always uses server port and OTEL agent JAR file path with slight variations like port number value and agent path value.

For the container (pattern 3), yeah, I kind of agree it depends on the existing Dockerfile structure, it may not be necessary. But for the pattern 2, it may be necessary. Let me take a look.

justinyoo commented 2 months ago

I did a few experiments for the pattern 2 and 3 and it really depends on how the container image has been built. Therefore, providing the ...Options class as a collection of common values wouldn't make sense.

For the pattern 1, I also think it's reasonable to leave devs to pass their arguments including the OTEL agent JAR file path, instead of using that ...Options class.

Thinking of the pattern 3, though, it's only valid if the Spring app is under the same repo where .AppHost project is located.

mitchdenny commented 2 months ago

I'd like @mitchdenny's take on this.

I'm starting to think that we have a WithDockerfile extension method which works with IResourceBuilder<ExecutableResource> which effectively results in a container resource being created in the manifest (impacts run mode as well).

justinyoo commented 1 month ago

Now, we can add Python app to .NET Aspire

4142

Can Java follow the similar process?

mitchdenny commented 1 month ago

@justinyoo absolutely!

thangchung commented 1 month ago

Now, we can add Python app to .NET Aspire

4142

Can Java follow the similar process?

+1 for adding Java supports

mitchdenny commented 2 weeks ago

@justinyoo were you interested at taking a run at this?