Skip to content

Commit

Permalink
Added email delivery of messages
Browse files Browse the repository at this point in the history
  • Loading branch information
jezzsantos committed Jan 13, 2024
1 parent 7900af7 commit 0244c50
Show file tree
Hide file tree
Showing 48 changed files with 1,132 additions and 321 deletions.
22 changes: 13 additions & 9 deletions docs/design-principles/0030-recording.md
Original file line number Diff line number Diff line change
Expand Up @@ -131,33 +131,37 @@ These are generally non-technical or system performance-related events but more

This is simply achieved by leveraging the infrastructure of the `ILoggerFactory` of .NET.

## Recorder
## The Recorder

The `HostRecorder` is used in all running hosts (e.g., ApiHosts, WebsiteHost, FunctionHosts and TestingHosts).

The adapters that are used by the `HostRecorder` to send telemetry to its various infrastructure components are configurable via `RecordingOptions,` which are tailored for each host and to the specific cloud environment the `HostRecorder` is running in.
The adapters that are used by the `HostRecorder` to send telemetry to its various infrastructure components are configurable via `RecordingOptions,` which are tailored for each host and to the specific cloud environment the `HostRecorder` is running in (i.e. Azure or AWS).

For example, this is the default infrastructure that is used by the `HostRecorder` when deployed to Azure.

> Similar components are used, by default, when deployed to AWS, except that Application Insights is replaced by Cloud Watch, the queues are replaced by SQS queues, and the Audits are stored in an RDS store.
![Azure Recording](../images/Recorder-Azure.png)

![](../images/Recorder-Azure.png)
When deployed to AWS, Application Insights is replaced by Cloud Watch, the queues are replaced by SQS queues, and the Audits are stored in an RDS database.

![AWS Recording](../images/Recorder-AWS.png)

No matter what the host actually is, there are two main flows of data that are handled by the `HostRecorder`. Both are high in reliability and both are asynchronous.

### Traces, Crashes, Measures

By default, all Traces, Crashes, and Measures are offloaded to Application Insights directly using the Application Insights `TelemetryClient` SDK.
When deployed to Azure, all Traces, Crashes, and Measures are offloaded to Application Insights directly using the Application Insights `TelemetryClient` SDK.

> The client SDK for Application Insights manages buffering and store-and-forward of this data asynchronously so that individual HTTP requests can return immediately without paying the tax of uploading the data to the cloud. The SDK supports buffering, retries, and other strategies to deliver the data reliably (using the `ServerTelemetryChannel`). See https://learn.microsoft.com/en-us/azure/azure-monitor/app/telemetry-channels for more details.
> This SDK manages buffering and store-and-forward of this data asynchronously so that individual HTTP requests can return immediately without paying the tax of uploading the data to the cloud. The SDK supports buffering, retries, and other strategies to deliver the data reliably (using the `ServerTelemetryChannel`). See https://learn.microsoft.com/en-us/azure/azure-monitor/app/telemetry-channels for more details.
When deployed to AWS, all Traces, Crashes and Measures are offloaded to CloudWatch X-Ray using the client SDK.

### Audits and Usages

Audits are ultimately destined to be stored permanently in a database. Usages are typically relayed to a remote 3rd party system.
Audits are ultimately destined to be stored permanently in a database. Usages are typically relayed to a remote 3rd party system (i.e. Google Analytics, MixPanel, Amplitude etc).

In both cases, to avoid tying up the client's HTTP request, the telemetry for these types is first stored on a reliable queue, and then later, an API call to the Ancillary API is issued that delivers the telemetry to its respective destination.
In both cases, to avoid tying up the client's HTTP request, the telemetry for these types is first "scheduled" on a reliable queue, and then later, an API call to the Ancillary API is issued that "delivers" the telemetry to its final destination.

> In Azure, an Azure Function is triggered when the telemetry arrives on a queue, and the Azure Function simply calls an API in the Ancillary API to deliver the telemetry to its destination. The Azure Function Trigger is a reliable means to handle this process, since if the API call fails, the message will return to the queue, and subsequent failures will result in the message moving to the poison queue, to be dealt with manually.
> On Azure, an Azure Function is triggered when the telemetry arrives on a queue, and the Azure Function simply calls an API in the Ancillary API to deliver the telemetry to its destination. The Azure Function Trigger is a reliable means to handle this process, since if the API call fails, the message will return to the queue, and subsequent failures will result in the message moving to the poison queue, to be dealt with manually.
> In AWS, a Lambda does the same job as the Azure Function above, and the SQS adapter is configured with poison queues (a.k.a a dead-letter queue) to operate in exactly the same way.
Expand Down
26 changes: 26 additions & 0 deletions docs/design-principles/0090-authentication-authorization.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# Authentication & Authorization

## Design Principles

## Implementation

### Password Credential Authentication

### Token Authorization

### HMAC Authentication/Authorization

### APIKey Authorization

### Cookie Authentication

### Cookie Authorization

* Usually performed by a BackendForFrontend component, reverse-proxies the token hidden in the cookie, into a token passed to the backend

### Declarative Authorization Syntax

### Role Based Authorization

### Feature Based Authorization

15 changes: 0 additions & 15 deletions docs/design-principles/0090-authentication-authorization.md.md

This file was deleted.

18 changes: 18 additions & 0 deletions docs/design-principles/0100-email-delivery.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Email Delivery

## Design Principles

Many processes in the backend of a SaaS product aim to alert the user to activities or processes in a SaaS product, that warrant their attention, and most of these notifications/alerts are ultimately delivered by email (albeit some are delivered by other means too, i.e. in-app, SMS texts etc).

Sending emails is often done via 3rd party systems like: SendGrid, MailGun, PostMark etc., over HTTP. Due to its nature, this mechanism isn't very reliable, especially with systems under load.

* We need to "broker" between the sending of emails and the delivering of them to make the entire process more reliable, and we need to provide observability when things go wrong.
* Since an inbound API request to any API backend can yield of the order ~10 emails per API call, delivering them reliably across HTTP can require minutes of time. If you consider the possibility of retries and back-offs etc. We simply could not afford to keep API clients blocked and waiting while email delivery takes place, let alone the risk of timing out their connection to the inbound API call in the first place.

Fortunately, an individual email arriving in a person inbox is not a time-critical and synchronous usability function to begin with. Some delay is anticipated.

Thus we need to take advantage of all these facts and engineer a reliable mechanism.

## Implementation

![Email Delivery](../../docs/images/Email-Delivery.png)
Binary file added docs/images/Email-Delivery.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified docs/images/Sources.pptx
Binary file not shown.
3 changes: 2 additions & 1 deletion src/AWSLambdas.Api.WorkerHost/HostExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -35,13 +35,14 @@ public static void AddDependencies(this IServiceCollection services, IConfigurat
#endif

services.AddSingleton<IRecorder>(c =>
new CrashTraceOnlyRecorder("Azure API Lambdas", c.Resolve<ILoggerFactory>(),
new CrashTraceOnlyRecorder("AWS API Lambdas", c.Resolve<ILoggerFactory>(),
c.Resolve<ICrashReporter>()));
services.AddSingleton<IServiceClient>(c =>
new InterHostServiceClient(c.Resolve<IHttpClientFactory>(),
c.Resolve<JsonSerializerOptions>(),
c.Resolve<IHostSettings>().GetAncillaryApiHostBaseUrl()));
services.AddSingleton<IQueueMonitoringApiRelayWorker<UsageMessage>, DeliverUsageRelayWorker>();
services.AddSingleton<IQueueMonitoringApiRelayWorker<AuditMessage>, DeliverAuditRelayWorker>();
services.AddSingleton<IQueueMonitoringApiRelayWorker<EmailMessage>, DeliverEmailRelayWorker>();
}
}
24 changes: 24 additions & 0 deletions src/AWSLambdas.Api.WorkerHost/Lambdas/DeliverEmail.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
using Amazon.Lambda.Annotations;
using Amazon.Lambda.Core;
using Amazon.Lambda.SQSEvents;
using Application.Persistence.Shared.ReadModels;
using AWSLambdas.Api.WorkerHost.Extensions;
using Infrastructure.Workers.Api;

namespace AWSLambdas.Api.WorkerHost.Lambdas;

public class DeliverEmail
{
private readonly IQueueMonitoringApiRelayWorker<EmailMessage> _worker;

public DeliverEmail(IQueueMonitoringApiRelayWorker<EmailMessage> worker)
{
_worker = worker;
}

[LambdaFunction]
public async Task<bool> Run(SQSEvent sqsEvent, ILambdaContext context)
{
return await sqsEvent.RelayRecordsAsync(_worker, CancellationToken.None);
}
}
17 changes: 17 additions & 0 deletions src/AWSLambdas.Api.WorkerHost/serverless.template
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,23 @@
"PackageType": "Zip",
"Handler": "AWSLambdas.Api.WorkerHost::AWSLambdas.Api.WorkerHost.Lambdas.DeliverAudit_Run_Generated::Run"
}
},
"AWSLambdasApiWorkerHostLambdasDeliverEmailRunGenerated": {
"Type": "AWS::Serverless::Function",
"Metadata": {
"Tool": "Amazon.Lambda.Annotations"
},
"Properties": {
"Runtime": "dotnet6",
"CodeUri": ".",
"MemorySize": 256,
"Timeout": 30,
"Policies": [
"AWSLambdaBasicExecutionRole"
],
"PackageType": "Zip",
"Handler": "AWSLambdas.Api.WorkerHost::AWSLambdas.Api.WorkerHost.Lambdas.DeliverEmail_Run_Generated::Run"
}
}
}
}
Loading

0 comments on commit 0244c50

Please sign in to comment.