Skip to content

Commit

Permalink
Merge pull request #155 from martincostello/Refactor-Initialization
Browse files Browse the repository at this point in the history
Refactor initialization
  • Loading branch information
martincostello authored Nov 10, 2019
2 parents a79272b + b712b0a commit b90845f
Show file tree
Hide file tree
Showing 17 changed files with 145 additions and 117 deletions.
111 changes: 106 additions & 5 deletions src/LondonTravel.Skill/AlexaFunction.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,13 @@
using Alexa.NET.Request;
using Alexa.NET.Response;
using Amazon.Lambda.Core;
using MartinCostello.LondonTravel.Skill.Extensions;
using MartinCostello.LondonTravel.Skill.Intents;
using Microsoft.ApplicationInsights;
using Microsoft.ApplicationInsights.Extensibility;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.Extensions.Logging;

namespace MartinCostello.LondonTravel.Skill
{
Expand All @@ -15,6 +21,18 @@ namespace MartinCostello.LondonTravel.Skill
/// </summary>
public class AlexaFunction
{
/// <summary>
/// The <see cref="IServiceProvider"/> to use.
/// </summary>
private IServiceProvider _serviceProvider;

/// <summary>
/// Initializes a new instance of the <see cref="AlexaFunction"/> class.
/// </summary>
public AlexaFunction()
{
}

/// <summary>
/// Handles a request to the skill as an asynchronous operation.
/// </summary>
Expand All @@ -27,20 +45,99 @@ public async Task<SkillResponse> HandlerAsync(SkillRequest request, ILambdaConte
{
context.Logger.LogLine($"Invoking skill request of type {request.Request.GetType().Name}.");

IServiceProvider serviceProvider = CreateServiceProvider();

var handler = serviceProvider.GetRequiredService<FunctionHandler>();
var handler = _serviceProvider.GetRequiredService<FunctionHandler>();

return await handler.HandleAsync(request);
}

/// <summary>
/// Initializes the skill as an asynchronous operation.
/// </summary>
/// <returns>
/// A <see cref="Task{TResult}"/> representing the asynchronous operation to initialize the skill.
/// </returns>
public Task<bool> InitializeAsync()
{
if (_serviceProvider == null)
{
_serviceProvider = CreateServiceProvider();
}

return Task.FromResult(true);
}

/// <summary>
/// Configures the <see cref="IServiceCollection"/> to use.
/// </summary>
/// <param name="services">The service collection to configure.</param>
protected virtual void ConfigureServices(IServiceCollection services)
{
// No-op
services.AddLogging((builder) =>
{
var options = new LambdaLoggerOptions()
{
Filter = FilterLogs,
};

builder.AddLambdaLogger(options);
});

services.AddHttpClients();
services.AddPolly();

services.TryAddSingleton((_) => SkillConfiguration.CreateDefaultConfiguration());

services.AddSingleton<AlexaSkill>();
services.AddSingleton<FunctionHandler>();
services.AddSingleton<IntentFactory>();
services.AddSingleton((_) => TelemetryConfiguration.CreateDefault());
services.AddSingleton(CreateTelemetryClient);

services.AddSingleton<EmptyIntent>();
services.AddSingleton<HelpIntent>();
services.AddSingleton<UnknownIntent>();

services.AddTransient<CommuteIntent>();
services.AddTransient<DisruptionIntent>();
services.AddTransient<StatusIntent>();
}

/// <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>();

return new TelemetryClient(configuration)
{
InstrumentationKey = config.ApplicationInsightsKey,
};
}

/// <summary>
/// Filters the Lambda logs.
/// </summary>
/// <param name="name">The name of the log.</param>
/// <param name="level">The log level.</param>
/// <returns>
/// <see langword="true"/> to log the message; otherwise <see langword="false"/>.
/// </returns>
private static bool FilterLogs(string name, LogLevel level)
{
if (level < LogLevel.Warning &&
(name.StartsWith("System.", StringComparison.Ordinal) ||
name.StartsWith("Microsoft.", StringComparison.Ordinal)))
{
return false;
}

return true;
}

/// <summary>
Expand All @@ -51,7 +148,11 @@ protected virtual void ConfigureServices(IServiceCollection services)
/// </returns>
private IServiceProvider CreateServiceProvider()
{
return ServiceResolver.GetServiceCollection(ConfigureServices).BuildServiceProvider();
var services = new ServiceCollection();

ConfigureServices(services);

return services.BuildServiceProvider();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ namespace MartinCostello.LondonTravel.Skill
/// <remarks>
/// See https://aws.amazon.com/blogs/developer/announcing-amazon-lambda-runtimesupport/.
/// </remarks>
internal static class Function
public static class FunctionEntrypoint
{
/// <summary>
/// Runs the function using a custom runtime as an asynchronous operation.
Expand All @@ -27,15 +27,15 @@ internal static class Function
/// <returns>
/// A <see cref="Task"/> representing the asynchronous operation to run the function.
/// </returns>
internal static async Task RunAsync(
public static async Task RunAsync(
HttpClient httpClient = null,
CancellationToken cancellationToken = default)
{
var serializer = new JsonSerializer();
var function = new AlexaFunction();

using var handlerWrapper = HandlerWrapper.GetHandlerWrapper<SkillRequest, SkillResponse>(function.HandlerAsync, serializer);
using var bootstrap = new LambdaBootstrap(httpClient ?? new HttpClient(), handlerWrapper);
using var bootstrap = new LambdaBootstrap(httpClient ?? new HttpClient(), handlerWrapper, function.InitializeAsync);

await bootstrap.RunAsync(cancellationToken);
}
Expand Down
6 changes: 0 additions & 6 deletions src/LondonTravel.Skill/InternalsVisibleTo.cs

This file was deleted.

75 changes: 0 additions & 75 deletions src/LondonTravel.Skill/ServiceResolver.cs

This file was deleted.

6 changes: 3 additions & 3 deletions test/LondonTravel.Skill.Tests/AlexaFunctionTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ public AlexaFunctionTests(ITestOutputHelper outputHelper)
public async Task Cannot_Invoke_Function_If_Application_Id_Incorrect()
{
// Arrange
AlexaFunction function = CreateFunction();
AlexaFunction function = await CreateFunctionAsync();

SkillRequest request = CreateIntentRequest("AMAZON.HelpIntent");
request.Session.Application.ApplicationId = "not-my-skill-id";
Expand All @@ -48,7 +48,7 @@ public async Task Cannot_Invoke_Function_If_Application_Id_Incorrect()
public async Task Can_Invoke_Function_If_Locale_Is_Invalid(string locale)
{
// Arrange
AlexaFunction function = CreateFunction();
AlexaFunction function = await CreateFunctionAsync();

SkillRequest request = CreateIntentRequest("AMAZON.HelpIntent");
request.Request.Locale = locale;
Expand All @@ -69,7 +69,7 @@ public async Task Can_Invoke_Function_If_Locale_Is_Invalid(string locale)
public async Task Cannot_Invoke_Function_With_System_Failure()
{
// Arrange
AlexaFunction function = CreateFunction();
AlexaFunction function = await CreateFunctionAsync();
ILambdaContext context = CreateContext();

var error = new SystemExceptionRequest()
Expand Down
2 changes: 1 addition & 1 deletion test/LondonTravel.Skill.Tests/CancelTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ public CancelTests(ITestOutputHelper outputHelper)
public async Task Can_Invoke_Function()
{
// Arrange
AlexaFunction function = CreateFunction();
AlexaFunction function = await CreateFunctionAsync();

SkillRequest request = CreateIntentRequest("AMAZON.CancelIntent");
ILambdaContext context = CreateContext();
Expand Down
14 changes: 7 additions & 7 deletions test/LondonTravel.Skill.Tests/CommuteTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ public CommuteTests(ITestOutputHelper outputHelper)
public async Task Can_Invoke_Function_When_The_Skill_Is_Not_Linked()
{
// Arrange
AlexaFunction function = CreateFunction();
AlexaFunction function = await CreateFunctionAsync();
SkillRequest request = CreateIntentRequestWithToken(accessToken: null);
ILambdaContext context = CreateContext();

Expand Down Expand Up @@ -53,7 +53,7 @@ public async Task Can_Invoke_Function_When_The_Skill_Token_Is_Invalid()
// Arrange
Interceptor.RegisterBundle(Path.Combine("Bundles", "skill-api-invalid-token.json"));

AlexaFunction function = CreateFunction();
AlexaFunction function = await CreateFunctionAsync();
SkillRequest request = CreateIntentRequestWithToken(accessToken: "invalid-access-token");
ILambdaContext context = CreateContext();

Expand All @@ -79,7 +79,7 @@ public async Task Can_Invoke_Function_When_The_Skill_Token_Is_Invalid()
public async Task Can_Invoke_Function_When_The_Skill_Api_Fails()
{
// Arrange
AlexaFunction function = CreateFunction();
AlexaFunction function = await CreateFunctionAsync();
SkillRequest request = CreateIntentRequestWithToken(accessToken: "random-access-token");
ILambdaContext context = CreateContext();

Expand All @@ -106,7 +106,7 @@ public async Task Can_Invoke_Function_When_The_Skill_Is_Linked_And_Has_No_Favori
Interceptor.RegisterBundle(Path.Combine("Bundles", "skill-api-no-favorites.json"));
Interceptor.RegisterBundle(Path.Combine("Bundles", "tfl-line-statuses.json"));

AlexaFunction function = CreateFunction();
AlexaFunction function = await CreateFunctionAsync();
SkillRequest request = CreateIntentRequestWithToken(accessToken: "token-for-no-favorites");
ILambdaContext context = CreateContext();

Expand All @@ -127,7 +127,7 @@ public async Task Can_Invoke_Function_When_The_Skill_Is_Linked_And_Only_The_Eliz
Interceptor.RegisterBundle(Path.Combine("Bundles", "skill-api-elizabeth.json"));
Interceptor.RegisterBundle(Path.Combine("Bundles", "tfl-line-statuses.json"));

AlexaFunction function = CreateFunction();
AlexaFunction function = await CreateFunctionAsync();
SkillRequest request = CreateIntentRequestWithToken(accessToken: "token-for-only-elizabeth-line");
ILambdaContext context = CreateContext();

Expand All @@ -148,7 +148,7 @@ public async Task Can_Invoke_Function_When_The_Skill_Is_Linked_And_Has_One_Favor
Interceptor.RegisterBundle(Path.Combine("Bundles", "skill-api-one-favorite.json"));
Interceptor.RegisterBundle(Path.Combine("Bundles", "tfl-line-statuses.json"));

AlexaFunction function = CreateFunction();
AlexaFunction function = await CreateFunctionAsync();
SkillRequest request = CreateIntentRequestWithToken(accessToken: "token-for-one-favorite");
ILambdaContext context = CreateContext();

Expand All @@ -169,7 +169,7 @@ public async Task Can_Invoke_Function_When_The_Skill_Is_Linked_And_Has_Two_Favor
Interceptor.RegisterBundle(Path.Combine("Bundles", "skill-api-two-favorites.json"));
Interceptor.RegisterBundle(Path.Combine("Bundles", "tfl-line-statuses.json"));

AlexaFunction function = CreateFunction();
AlexaFunction function = await CreateFunctionAsync();
SkillRequest request = CreateIntentRequestWithToken(accessToken: "token-for-two-favorites");
ILambdaContext context = CreateContext();

Expand Down
8 changes: 4 additions & 4 deletions test/LondonTravel.Skill.Tests/DisruptionTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ public async Task Can_Invoke_Function_When_There_Are_No_Disruptions()
// Arrange
Interceptor.RegisterBundle(Path.Combine("Bundles", "tfl-no-disruptions.json"));

AlexaFunction function = CreateFunction();
AlexaFunction function = await CreateFunctionAsync();
SkillRequest request = CreateIntentRequest();
ILambdaContext context = CreateContext();

Expand All @@ -46,7 +46,7 @@ public async Task Can_Invoke_Function_When_There_Is_One_Disruption()
// Arrange
Interceptor.RegisterBundle(Path.Combine("Bundles", "tfl-one-disruption.json"));

AlexaFunction function = CreateFunction();
AlexaFunction function = await CreateFunctionAsync();
SkillRequest request = CreateIntentRequest();
ILambdaContext context = CreateContext();

Expand All @@ -66,7 +66,7 @@ public async Task Can_Invoke_Function_When_There_Are_Multiple_Disruptions()
// Arrange
Interceptor.RegisterBundle(Path.Combine("Bundles", "tfl-multiple-disruptions.json"));

AlexaFunction function = CreateFunction();
AlexaFunction function = await CreateFunctionAsync();
SkillRequest request = CreateIntentRequest();
ILambdaContext context = CreateContext();

Expand All @@ -84,7 +84,7 @@ public async Task Can_Invoke_Function_When_There_Are_Multiple_Disruptions()
public async Task Can_Invoke_Function_When_The_Api_Fails()
{
// Arrange
AlexaFunction function = CreateFunction();
AlexaFunction function = await CreateFunctionAsync();
SkillRequest request = CreateIntentRequest();
ILambdaContext context = CreateContext();

Expand Down
Loading

0 comments on commit b90845f

Please sign in to comment.