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

Add support for Native AOT (Retarget library to .NET 8) #184

Open
wants to merge 4 commits into
base: v4sdk-development
Choose a base branch
from

Conversation

normj
Copy link
Contributor

@normj normj commented Mar 7, 2025

Issue
#109
#144

Description of changes:
This PR enables IsTrimmable in the 3 packages and address all trim warnings. Note this is targeting the branch that will use V4 of the AWS SDK which has more Native AOT work done for that is required for this library.

The main difference from user's point of view is to use Native AOT they must use the AddMessageBus overload that takes in a JsonSerializerContext. That context must have the source generation setup for all of the POCOs used by the user that represent messages. If they do not use this overload the user will get a trim warning about the code requiring unreferenced code.

I also the libraries to target .NET 8.0 to get the SDK's Native AOT support. Since .NET 6.0 is out of support and we haven't GA the library yet we don't need to start the GA lifecycle on unsupported targets.

The loading messaging config from IConfiguration via the LoadConfigurationFromSettings method is not supported for trimming because that will always requiring loading types that are not known at compile time.

The trickiest part for adding trimming support was the use of MakeGenericType in EnvelopeSerializer. In that class we are taking the user's Type for the message and turning that into MessageEnvelope<User'sType>. That was using the type of reflection via the MakeGenericType and Activator.CreateInstance that doesn't work in a trimming environment. That is because when working with the Type object you can't know at compile time what the type is. To handle that I created an envelope factory on the SubscriberMapping that uses the generic parameters to create the MessageEnvelope<User'sType>. Since the generic parameters are known at compile time we don't have to use the MakeGenericType and Activator.CreateInstance methods.

Testing

Ran unit and integ tests and updated tests to handle new code paths when appropiate.
Build a console application with PublishAot set to true. The code handled publishing and subscribe and no trim warnings were produced and application ran successful.

Code of console application

using AWS.Messaging;
using MessagingLocalAOTTest;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;

var builder = new HostApplicationBuilder();

var queueUrl = "<queue-url>";
builder.Services.AddAWSMessageBus(MessageFunctionJsonSerializerContext.Default, builder =>
{
    builder.AddSQSPublisher<ProductDTO>(queueUrl);
    builder.AddMessageHandler<ProductHandler, ProductDTO>();

    builder.AddSQSPoller(queueUrl);
});

var app = builder.Build();

var task = app.RunAsync();


var publisher = app.Services.GetRequiredService<IMessagePublisher>();

var product = new ProductDTO("1", "Foo", "Bar");

await publisher.PublishAsync(product);

var cancelSource = new CancellationTokenSource();
cancelSource.CancelAfter(5000);

try
{
    Task.WaitAll(new Task[] { task }, cancelSource.Token);
    Console.WriteLine("Application completed");
}
catch (OperationCanceledException) { }

Json serializer context:

using System.Text.Json.Serialization;

namespace MessagingLocalAOTTest;

public record ProductDTO(string ID, string Name, string Description);

[JsonSerializable(typeof(ProductDTO))]
[JsonSourceGenerationOptions(UseStringEnumConverter = true)]
public partial class MessageFunctionJsonSerializerContext : JsonSerializerContext
{
}

Message Handler:

using AWS.Messaging;

namespace MessagingLocalAOTTest;

public class ProductHandler : IMessageHandler<ProductDTO>
{
    public Task<MessageProcessStatus> HandleAsync(MessageEnvelope<ProductDTO> messageEnvelope, CancellationToken token = default)
    {
        Console.WriteLine($"Saving product: {messageEnvelope.Message.Name}");
        return Task.FromResult(MessageProcessStatus.Success());
    }
}

By submitting this pull request, I confirm that you can use, modify, copy, and redistribute this contribution, under the terms of your choice.

@@ -40,6 +41,13 @@ public DefaultMessageManagerFactory(IServiceProvider serviceProvider)
/// <inheritdoc/>
public IMessageManager CreateMessageManager(ISQSMessageCommunication sqsMessageCommunication, MessageManagerConfiguration configuration)
{
return ActivatorUtilities.CreateInstance<DefaultMessageManager>(_serviceProvider, sqsMessageCommunication, configuration);
var manager = new DefaultMessageManager(
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The ActivatorUtilities.CreateInstance call here actually compiled with out trim warnings but failed at the runtime. Easy fix was to directly create the instance instead of relying on ActivatorUtilities.CreateInstance.

@@ -15,20 +15,22 @@
<PackageReadmeFile>README.md</PackageReadmeFile>
<EnforceCodeStyleInBuild>True</EnforceCodeStyleInBuild>
<WarningsAsErrors>CA1727</WarningsAsErrors>
<NoWarn>CS1591</NoWarn>
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CS1591 is the code for no docs on public API surface. I had to add this because the code generated via the JSON serializer for the internal MessageJsonSerializerContext doesn't have any docs. I tried a variety of ways to get the warning to ignore that generated code but nothing stuck.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

1 participant