Skip to content

Commit

Permalink
Publish as AoT (#967)
Browse files Browse the repository at this point in the history
- Remove ApplicationInsights as it is not AoT-compatible (and I never look at the data anyway).
- Use System.Text.Json for serialization instead of Newtonsoft.Json to support AoT.
- Remove unused/duplicate custom `JsonSerializerContext`.
- Add more end-to-end tests.
- Enable publishing the Lambda for AoT.
- Increase code coverage for ending a session.
- Use expression body.
* Switch to x64 as until there's a GitHub Actions Linux runner with arm64 support, need to switch back to x64 as it is not possible to compile AoT for arm64 on an x64 Linux image.
- Use invariant globalisation and remove Microsoft.ICU.ICU4C.Runtime to reduce the AoT deployment size.
- Increase TfL API timeout to 7.5 seconds.
  • Loading branch information
martincostello authored Nov 27, 2023
1 parent e9095df commit 09568ce
Show file tree
Hide file tree
Showing 66 changed files with 1,134 additions and 205 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ jobs:
- name: Create Lambda ZIP file
if: runner.os == 'Linux'
run: |
cd "./artifacts/publish/LondonTravel.Skill/release_linux-arm64" || exit
cd "./artifacts/publish/LondonTravel.Skill/release_linux-x64" || exit
if [ -f "./bootstrap" ]
then
chmod +x ./bootstrap
Expand Down
12 changes: 12 additions & 0 deletions Directory.Build.targets
Original file line number Diff line number Diff line change
Expand Up @@ -30,4 +30,16 @@
<ReportGenerator ReportFiles="@(CoverletReport)" ReportTypes="$(ReportGeneratorReportTypes)" Tag="$(Version)" TargetDirectory="$(ReportGeneratorTargetDirectory)" Title="$(AssemblyName)" VerbosityLevel="Warning" />
<Exec Condition=" '$(ReportGeneratorOutputMarkdown)' == 'true' " Command="pwsh -Command %22('$(_MarkdownSummaryPrefix)' + [System.Environment]::NewLine + [System.Environment]::NewLine + (Get-Content $([System.IO.Path]::Combine($(ReportGeneratorTargetDirectory), 'SummaryGithub.md')) | Out-String) + [System.Environment]::NewLine + [System.Environment]::NewLine + '$(_MarkdownSummarySuffix)') >> $(GITHUB_STEP_SUMMARY)%22" />
</Target>
<!--
HACK Workaround warnings from Microsoft.Extensions.Http.Resilience until dotnet/extensions#4622 is fixed in .NET 8.0.2(?)
-->
<PropertyGroup>
<NoWarn>$(NoWarn);IL2026</NoWarn>
<TrimmerSingleWarn>false</TrimmerSingleWarn>
</PropertyGroup>
<Target Name="SuppressDotNetExtensionsWarnings" BeforeTargets="PrepareForILLink">
<ItemGroup>
<IlcArg Include="--singlewarnassembly:Microsoft.Extensions.Http.Resilience" />
</ItemGroup>
</Target>
</Project>
4 changes: 1 addition & 3 deletions Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,14 @@
<PackageVersion Include="Alexa.NET" Version="1.22.0" />
<PackageVersion Include="Amazon.Lambda.Core" Version="2.2.0" />
<PackageVersion Include="Amazon.Lambda.RuntimeSupport" Version="1.10.0" />
<PackageVersion Include="Amazon.Lambda.Serialization.Json" Version="2.1.1" />
<PackageVersion Include="Amazon.Lambda.Serialization.SystemTextJson" Version="2.4.0" />
<PackageVersion Include="Amazon.Lambda.TestUtilities" Version="2.0.0" />
<PackageVersion Include="AWSSDK.Lambda" Version="3.7.300.1" />
<PackageVersion Include="coverlet.msbuild" Version="6.0.0" />
<PackageVersion Include="GitHubActionsTestLogger" Version="2.3.3" />
<PackageVersion Include="JustEat.HttpClientInterception" Version="4.0.0" />
<PackageVersion Include="MartinCostello.Logging.XUnit" Version="0.3.0" />
<PackageVersion Include="MartinCostello.Testing.AwsLambdaTestServer" Version="0.8.0" />
<PackageVersion Include="Microsoft.ApplicationInsights" Version="2.21.0" />
<PackageVersion Include="Microsoft.AspNetCore.WebUtilities" Version="8.0.0" />
<PackageVersion Include="Microsoft.Extensions.Configuration.Binder" Version="8.0.0" />
<PackageVersion Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="8.0.0" />
Expand All @@ -23,7 +22,6 @@
<PackageVersion Include="Microsoft.Extensions.Http" Version="8.0.0" />
<PackageVersion Include="Microsoft.Extensions.Http.Resilience" Version="8.0.0" />
<PackageVersion Include="Microsoft.Extensions.Logging.Console" Version="8.0.0" />
<PackageVersion Include="Microsoft.ICU.ICU4C.Runtime" Version="72.1.0.3" />
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.8.0" />
<PackageVersion Include="Newtonsoft.Json" Version="13.0.3" />
<PackageVersion Include="Polly.Core" Version="$(PollyVersion)" />
Expand Down
3 changes: 2 additions & 1 deletion build.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -91,10 +91,11 @@ function DotNetPublish {

if ($IsLinux -And (-Not $UseManagedRuntime)) {
$additionalArgs += "--runtime"
$additionalArgs += "linux-arm64"
$additionalArgs += "linux-x64"
$additionalArgs += "--self-contained"
$additionalArgs += "true"
$additionalArgs += "/p:AssemblyName=bootstrap"
$additionalArgs += "/p:PublishAot=true"
$additionalArgs += "/p:PublishReadyToRun=true"
}

Expand Down
34 changes: 6 additions & 28 deletions src/LondonTravel.Skill/AlexaFunction.cs
Original file line number Diff line number Diff line change
@@ -1,12 +1,9 @@
// Copyright (c) Martin Costello, 2017. All rights reserved.
// Licensed under the Apache 2.0 license. See the LICENSE file in the project root for full license information.

using Alexa.NET.Request;
using Alexa.NET.Response;
using MartinCostello.LondonTravel.Skill.Extensions;
using MartinCostello.LondonTravel.Skill.Intents;
using Microsoft.ApplicationInsights;
using Microsoft.ApplicationInsights.Extensibility;
using MartinCostello.LondonTravel.Skill.Models;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
Expand Down Expand Up @@ -81,7 +78,11 @@ public async Task<SkillResponse> HandlerAsync(SkillRequest request)

Log.InvokingSkillRequest(logger, request.Request.Type);

return await handler.HandleAsync(request);
var converted = request.FromModel();

var response = await handler.HandleAsync(converted);

return response.ToModel();
}

/// <summary>
Expand Down Expand Up @@ -133,8 +134,6 @@ protected virtual void ConfigureServices(IServiceCollection services)
services.AddSingleton<FunctionHandler>();
services.AddSingleton<IntentFactory>();
services.AddSingleton<IValidateOptions<SkillConfiguration>, ValidateSkillConfiguration>();
services.AddSingleton((_) => TelemetryConfiguration.CreateDefault());
services.AddSingleton(CreateTelemetryClient);
services.AddSingleton((p) => p.GetRequiredService<IOptions<SkillConfiguration>>().Value);
services.AddSingleton(configuration);

Expand All @@ -160,27 +159,6 @@ protected virtual void Dispose(bool disposing)
}
}

/// <summary>
/// Creates an <see cref="TelemetryClient"/>.
/// </summary>
/// <param name="serviceProvider">The <see cref="IServiceProvider"/> to use.</param>
/// <returns>
/// The created instance of <see cref="TelemetryClient"/>.
/// </returns>
private static TelemetryClient CreateTelemetryClient(IServiceProvider serviceProvider)
{
var config = serviceProvider.GetRequiredService<SkillConfiguration>();

var configuration = serviceProvider.GetRequiredService<TelemetryConfiguration>();

if (!string.IsNullOrEmpty(config.ApplicationInsightsConnectionString))
{
configuration.ConnectionString = config.ApplicationInsightsConnectionString;
}

return new TelemetryClient(configuration);
}

/// <summary>
/// Creates the <see cref="ServiceProvider"/> to use.
/// </summary>
Expand Down
46 changes: 2 additions & 44 deletions src/LondonTravel.Skill/AlexaSkill.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
using Alexa.NET.Request;
using Alexa.NET.Request.Type;
using Alexa.NET.Response;
using Microsoft.ApplicationInsights;
using Microsoft.Extensions.Logging;

namespace MartinCostello.LondonTravel.Skill;
Expand All @@ -16,11 +15,9 @@ namespace MartinCostello.LondonTravel.Skill;
/// Initializes a new instance of the <see cref="AlexaSkill"/> class.
/// </remarks>
/// <param name="intentFactory">The factory to use for the skill intents.</param>
/// <param name="telemetry">The telemetry client to use.</param>
/// <param name="logger">The logger to use.</param>
internal sealed class AlexaSkill(
IntentFactory intentFactory,
TelemetryClient telemetry,
ILogger<AlexaSkill> logger)
{
/// <summary>
Expand Down Expand Up @@ -57,8 +54,6 @@ public SkillResponse OnError(Exception exception, Session session)
{
Log.HandlerException(logger, exception, session.SessionId);

TrackException(exception, session);

return SkillResponseBuilder
.Tell(Strings.InternalError)
.Build();
Expand All @@ -75,8 +70,6 @@ public SkillResponse OnError(Exception exception, Session session)
/// </returns>
public async Task<SkillResponse> OnIntentAsync(Intent intent, Session session)
{
TrackEvent(intent.Name, session, intent);

IIntent userIntent = intentFactory.Create(intent);

return await userIntent.RespondAsync(intent, session);
Expand All @@ -91,7 +84,7 @@ public async Task<SkillResponse> OnIntentAsync(Intent intent, Session session)
/// </returns>
public SkillResponse OnLaunch(Session session)
{
TrackEvent("LaunchRequest", session);
Log.SessionLaunched(logger, session.User.UserId, session.SessionId);

return SkillResponseBuilder
.Tell(Strings.LaunchResponse)
Expand All @@ -108,45 +101,10 @@ public SkillResponse OnLaunch(Session session)
/// </returns>
public SkillResponse OnSessionEnded(Session session)
{
TrackEvent("SessionEndedRequest", session);
Log.SessionEnded(logger, session.User.UserId, session.SessionId);

return SkillResponseBuilder
.Tell(Strings.SessionEndResponse)
.Build();
}

private Dictionary<string, string> ToTelemetryProperties(Session session)
{
bool hasAccessToken = !string.IsNullOrEmpty(session.User?.AccessToken);

#pragma warning disable CA1308
return new Dictionary<string, string>()
{
["hasAccessToken"] = hasAccessToken.ToString(CultureInfo.InvariantCulture).ToLowerInvariant(),
["sessionId"] = session.SessionId,
["userId"] = session.User?.UserId,
};
#pragma warning restore CA1308
}

private void TrackEvent(string eventName, Session session, Intent intent = null)
{
Dictionary<string, string> properties = ToTelemetryProperties(session);

if (intent?.Slots?.Count > 0)
{
foreach (var slot in intent.Slots.Values)
{
properties[$"slot:{slot.Name}"] = slot.Value;
}
}

telemetry.TrackEvent(eventName, properties);
}

private void TrackException(Exception exception, Session session)
{
Dictionary<string, string> properties = ToTelemetryProperties(session);
telemetry.TrackException(exception, properties);
}
}
9 changes: 8 additions & 1 deletion src/LondonTravel.Skill/AppJsonSerializerContext.cs
Original file line number Diff line number Diff line change
@@ -1,18 +1,25 @@
// Copyright (c) Martin Costello, 2017. All rights reserved.
// Licensed under the Apache 2.0 license. See the LICENSE file in the project root for full license information.

using System.Diagnostics.CodeAnalysis;
using System.Text.Json.Serialization;
using MartinCostello.LondonTravel.Skill.Clients;
using MartinCostello.LondonTravel.Skill.Models;

namespace MartinCostello.LondonTravel.Skill;

/// <summary>
/// A class representing the <see cref="JsonSerializerContext"/> to use for the application.
/// </summary>
[ExcludeFromCodeCoverage]
[JsonSerializable(typeof(IList<Line>))]
[JsonSerializable(typeof(IList<ServiceDisruption>))]
[JsonSerializable(typeof(LinkAccountCard))]
[JsonSerializable(typeof(SkillRequest))]
[JsonSerializable(typeof(SkillResponse))]
[JsonSerializable(typeof(SkillUserPreferences))]
[JsonSerializable(typeof(StandardCard))]
[JsonSourceGenerationOptions(PropertyNamingPolicy = JsonKnownNamingPolicy.CamelCase)]
internal sealed partial class AppJsonSerializerContext : JsonSerializerContext
public sealed partial class AppJsonSerializerContext : JsonSerializerContext
{
}
2 changes: 1 addition & 1 deletion src/LondonTravel.Skill/Clients/Line.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ namespace MartinCostello.LondonTravel.Skill.Clients;
/// <summary>
/// A class representing a line. This class cannot be inherited.
/// </summary>
internal sealed class Line
public sealed class Line
{
/// <summary>
/// Gets or sets the line's name.
Expand Down
2 changes: 1 addition & 1 deletion src/LondonTravel.Skill/Clients/LineStatus.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ namespace MartinCostello.LondonTravel.Skill.Clients;
/// <summary>
/// A class representing a line's status. This class cannot be inherited.
/// </summary>
internal sealed class LineStatus
public sealed class LineStatus
{
/// <summary>
/// Gets or sets the status severity.
Expand Down
2 changes: 1 addition & 1 deletion src/LondonTravel.Skill/Clients/LineStatusSeverity.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ namespace MartinCostello.LondonTravel.Skill.Clients;
/// <summary>
/// An enumeration of line status severities.
/// </summary>
internal enum LineStatusSeverity
public enum LineStatusSeverity
{
SpecialService = 0,

Expand Down
2 changes: 1 addition & 1 deletion src/LondonTravel.Skill/Clients/ServiceDisruption.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ namespace MartinCostello.LondonTravel.Skill.Clients;
/// <summary>
/// A class representing a service disruption. This class cannot be inherited.
/// </summary>
internal sealed class ServiceDisruption
public sealed class ServiceDisruption
{
/// <summary>
/// Gets or sets the description of the disruption.
Expand Down
2 changes: 1 addition & 1 deletion src/LondonTravel.Skill/Clients/SkillUserPreferences.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ namespace MartinCostello.LondonTravel.Skill.Clients;
/// <summary>
/// A class representing a user's preferences for the skill. This class cannot be inherited.
/// </summary>
internal sealed class SkillUserPreferences
public sealed class SkillUserPreferences
{
/// <summary>
/// Gets or sets the user's favorite lines.
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ public static IHttpClientBuilder ApplyDefaultConfiguration(this IHttpClientBuild
private static void ApplyDefaultConfiguration(HttpClient client)
{
client.DefaultRequestHeaders.UserAgent.Add(_userAgent.Value);
client.Timeout = TimeSpan.FromSeconds(5);
client.Timeout = TimeSpan.FromSeconds(7.5);
}

/// <summary>
Expand Down
7 changes: 3 additions & 4 deletions src/LondonTravel.Skill/FunctionEntrypoint.cs
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
// Copyright (c) Martin Costello, 2017. All rights reserved.
// Licensed under the Apache 2.0 license. See the LICENSE file in the project root for full license information.

using Alexa.NET.Request;
using Alexa.NET.Response;
using Amazon.Lambda.RuntimeSupport;
using Amazon.Lambda.Serialization.Json;
using Amazon.Lambda.Serialization.SystemTextJson;
using MartinCostello.LondonTravel.Skill.Models;

namespace MartinCostello.LondonTravel.Skill;

Expand All @@ -30,7 +29,7 @@ public static async Task RunAsync<T>(
CancellationToken cancellationToken = default)
where T : AlexaFunction, new()
{
var serializer = new JsonSerializer();
var serializer = new SourceGeneratorLambdaJsonSerializer<AppJsonSerializerContext>();
await using var function = new T();

using var bootstrap = LambdaBootstrapBuilder
Expand Down
12 changes: 12 additions & 0 deletions src/LondonTravel.Skill/Log.cs
Original file line number Diff line number Diff line change
Expand Up @@ -57,4 +57,16 @@ public static partial void SystemError(
Level = LogLevel.Information,
Message = "Invoking skill request of type {RequestType}.")]
public static partial void InvokingSkillRequest(ILogger logger, string requestType);

[LoggerMessage(
EventId = 8,
Level = LogLevel.Debug,
Message = "Launched session for user Id {UserId} and session Id {SessionId}.")]
public static partial void SessionLaunched(ILogger logger, string userId, string sessionId);

[LoggerMessage(
EventId = 9,
Level = LogLevel.Debug,
Message = "Ended session for user Id {UserId} and session Id {SessionId}.")]
public static partial void SessionEnded(ILogger logger, string userId, string sessionId);
}
Loading

0 comments on commit 09568ce

Please sign in to comment.