wojciech-dabrowski
wojciech-dabrowski
wojc.dabrowski@gmail.com
there and back again
public class CustomerHasTakenLoanEventHandler : IEventHandler<CustomerHasTakenLoanEvent>
{
...
public void Handle(CustomerHasTakenLoanEvent @event)
{
...
var mailBody = $"Hi, {@event.CustomerFirstName}\n\n" +
$"You have taken a loan for {@event.LoanAmount} {@event.LoanCurrency}.";
SendMail(@event.CustomerMailAddress, mailBody);
}
private void SendMail(string toMailAddress, string mailBody)
{
using (var emailMessage = new MailMessage())
{
emailMessage.To.Add(toMailAddress);
emailMessage.From = mailConfig.FromMailAddress;
emailMessage.Subject = MailSubject;
emailMessage.Body = mailBody;
emailMessage.IsBodyHtml = true;
emailMessage.BodyEncoding = Encoding.Unicode;
using (var smtpClient = new SmtpClient(smtpConfig.SmtpServerHost))
{
smtpClient.Port = smtpConfig.SmtpPort;
smtpClient.UseDefaultCredentials = false;
smtpClient.Credentials = new NetworkCredential(smtpConfig.SmtpUserName, smtpConfig.SmtpUserPassword);
smtpClient.Send(emailMessage);
}
}
}
}
public class LoanHasBeenPaidOffEventHandler : IEventHandler<LoanHasBeenPaidOffEvent>
{
...
public void Handle(LoanHasBeenPaidOffEvent @event)
{
...
var mailBody = $"Hi, {@event.CustomerFirstName}\n\n" +
$"Your loan for {@event.LoanAmount} {@event.LoanCurrency} has been paid off.";
SendMail(@event.CustomerMailAddress, mailBody);
}
private void SendMail(string toMailAddress, string mailBody)
{
using (var emailMessage = new MailMessage())
{
emailMessage.To.Add(toMailAddress);
emailMessage.From = mailConfig.FromMailAddress;
emailMessage.Subject = MailSubject;
emailMessage.Body = mailBody;
emailMessage.IsBodyHtml = true;
emailMessage.BodyEncoding = Encoding.Unicode;
using (var smtpClient = new SmtpClient(smtpConfig.SmtpServerHost))
{
smtpClient.Port = smtpConfig.SmtpPort;
smtpClient.UseDefaultCredentials = false;
smtpClient.Credentials = new NetworkCredential(smtpConfig.SmtpUserName, smtpConfig.SmtpUserPassword);
smtpClient.Send(emailMessage);
}
}
}
}
Repetition mania
https://giant.gfycat.com/TautWarmheartedBetafish.webm
Single responsibility principle application
public class SmtpClientEmailSender : IEmailSender
{
...
public void SendMail(SendEmailModel model)
{
using (var emailMessage = new MailMessage())
{
emailMessage.To.Add(model.ToMailAddress);
emailMessage.From = mailConfig.FromMailAddress;
emailMessage.Subject = model.MailSubject;
emailMessage.Body = model.MailBody;
emailMessage.IsBodyHtml = true;
emailMessage.BodyEncoding = Encoding.Unicode;
using (var smtpClient = new SmtpClient(smtpConfig.SmtpServerHost))
{
smtpClient.Port = smtpConfig.SmtpPort;
smtpClient.UseDefaultCredentials = false;
smtpClient.Credentials = new NetworkCredential(smtpConfig.SmtpUserName, smtpConfig.SmtpUserPassword);
smtpClient.Send(emailMessage);
}
}
}
}
public class CustomerHasTakenLoanEventHandler : IEventHandler<CustomerHasTakenLoanEvent>
{
...
private readonly IEmailSender emailSender;
public CustomerHasTakenLoanEventHandler(IEmailSender emailSender)
{
this.emailSender = emailSender;
}
public void Handle(CustomerHasTakenLoanEvent @event)
{
...
var mailBody = $"Hi, {@event.CustomerFirstName}\n\n" +
$"You have taken a loan for {@event.LoanAmount} {@event.LoanCurrency}.";
var sendEmailModel = new SendEmailModel(@event.CustomerMailAddress, MailSubject, mailBody);
emailSender.SendMail(sendEmailModel);
}
}
public class LoanHasBeenPaidOffEventHandler : IEventHandler<LoanHasBeenPaidOffEvent>
{
...
private readonly IEmailSender emailSender;
public LoanHasBeenPaidOffEventHandler(IEmailSender emailSender)
{
this.emailSender = emailSender;
}
public void Handle(LoanHasBeenPaidOffEvent @event)
{
...
var mailBody = $"Hi, {@event.CustomerFirstName}\n\n" +
$"Your loan for {@event.LoanAmount} {@event.LoanCurrency} has been paid off.";
var sendEmailModel = new SendEmailModel(@event.CustomerMailAddress, MailSubject, mailBody);
emailSender.SendMail(sendEmailModel);
}
}
Common closure principle application
Portable library
One service to serve them all
API Gateway
API Gateway
Lambda
API Gateway
Lambda
SQS
API Gateway
Lambda
SQS
CloudWatch
API Gateway
Lambda
SQS
CloudWatch
SES
API Gateway
Lambda
SQS
CloudWatch
SES
S3
API Gateway
Lambda
SQS
CloudWatch
SES
S3
public class SmtpClientEmailSender : IEmailSender
{
...
public void SendMail(SendEmailModel model)
{
using (var emailMessage = new MailMessage())
{
emailMessage.To.Add(model.ToMailAddress);
emailMessage.From = mailConfig.FromMailAddress;
emailMessage.Subject = model.MailSubject;
emailMessage.Body = model.MailBody;
emailMessage.IsBodyHtml = true;
emailMessage.BodyEncoding = Encoding.Unicode;
using (var smtpClient = new SmtpClient(smtpConfig.SmtpServerHost))
{
smtpClient.Port = smtpConfig.SmtpPort;
smtpClient.UseDefaultCredentials = false;
smtpClient.Credentials = new NetworkCredential(smtpConfig.SmtpUserName, smtpConfig.SmtpUserPassword);
smtpClient.Send(emailMessage);
}
}
}
}
using (var smtpClient = new SmtpClient(smtpConfig.SmtpServerHost))
{
...
smtpClient.Send(emailMessage);
}
public async Task<string> InsertEmailAsync(InsertEmailModel emailModel, ILambdaContext context)
{
var emailStorageId = Guid.NewGuid().ToString();
await UploadEmailToS3(emailStorageId, emailModel);
await sqsClient.SendMessageAsync(
EnvironmentVariables.MailQueue,
JsonConvert.SerializeObject(emailStorageId)
);
return emailStorageId;
}
private async Task UploadEmailToS3(string emailStorageId, InsertEmailModel emailModel)
{
using (var stream = new MemoryStream(Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(emailModel))))
{
var s3PutRequest = new PutObjectRequest
{
BucketName = EnvironmentVariables.MailSendingBucket,
Key = emailStorageId,
InputStream = stream
};
var result = await s3Client.PutObjectAsync(s3PutRequest);
if (result.HttpStatusCode != HttpStatusCode.OK)
{
throw new Exception($"An error occured during putting S3 object."
+ $"Received status code {(int) result.HttpStatusCode} in response");
}
}
}
private async Task PollQueueAsync(ILambdaContext context)
{
var request = new ReceiveMessageRequest
{
QueueUrl = EnvironmentVariables.MailQueue,
MaxNumberOfMessages = EnvironmentVariables.MailNumberPerBatch
};
var sqsResult = await sqsClient.ReceiveMessageAsync(request);
var sendEmailTasks = sqsResult.Messages
.Select(async sqsMessage => await InvokeLambdaAndDeleteMessage(sqsMessage));
await Task.WhenAll(sendEmailTasks);
}
private async Task InvokeLambdaAndDeleteMessage(Message sqsMessage)
{
var request = new InvokeRequest
{
FunctionName = EnvironmentVariables.SendEmailLambdaName,
Payload = sqsMessage.Body
};
var invokeResponse = await lambdaClient.InvokeAsync(request);
if (WasCorrectlySent(invokeResponse))
{
await DeleteMessage(sqsMessage, invokeResponse);
}
}
private async Task DeleteMessage(Message sqsMessage)
{
var deleteMessageRequest = new DeleteMessageRequest
{
QueueUrl = EnvironmentVariables.MailQueue,
ReceiptHandle = sqsMessage.ReceiptHandle
};
await sqsClient.DeleteMessageAsync(deleteMessageRequest);
}
public async Task<bool> SendEmailAsync(string emailTaskId, ILambdaContext context)
{
var insertEmailModel = await GetEmailObjectFromS3(emailTaskId);
var sendEmailRequest = CreateRawEmailModel(insertEmailModel);
var result = await sesClient.SendRawEmailAsync(sendEmailRequest);
return result.HttpStatusCode == HttpStatusCode.OK;
}
private async Task<InsertEmailModel> GetEmailObjectFromS3(string emailTaskId)
{
var s3Object = await s3Client.GetObjectAsync(EnvironmentVariables.MailSendingBucket, emailTaskId);
using (var reader = new StreamReader(s3Object.ResponseStream))
{
var s3Contents = reader.ReadToEnd();
return JsonConvert.DeserializeObject<InsertEmailModel>(s3Contents);
}
}
public async Task<string> InsertEmailAsync(InsertEmailModel insertEmailModelModel)
{
var lambdaRequest = new InvokeRequest
{
FunctionName = insertEmailLambdaName,
Payload = JsonConvert.SerializeObject(insertEmailModelModel)
};
var invokeResult = await lambdaClient.Value.InvokeAsync(lambdaRequest);
CheckLambdaResult(invokeResult);
return invokeResult.Payload.Deserialize<string>();
}
"Resources" : {
"MailSendingBucket": {
"Type": "AWS::S3::Bucket"
},
"InsertEmail" : {
"Type" : "AWS::Serverless::Function",
"Properties": {
"Handler": "ServerlessMicroservice::ServerlessMicroservice.Lambdas.InsertEmailLambda::InsertEmailAsync",
"Environment" : {
"Variables" : {
"MailQueueUrl" : { "Ref" : "MailQueue" },
"MailSendingBucket" : { "Ref" : "MailSendingBucket" },
}
}
}
},
"SendEmail" : {
"Type" : "AWS::Serverless::Function",
"Properties": {
"Handler": "ServerlessMicroservice::ServerlessMicroservice.Lambdas.SendEmailLambda::SendEmailAsync",
"Environment" : {
"Variables" : {
"MailQueueUrl" : { "Ref" : "MailQueue" },
"MailSendingBucket" : { "Ref" : "MailSendingBucket" },
}
}
}
},
Match the solution with requirements
wojciech-dabrowski
wojciech-dabrowski
wojc.dabrowski@gmail.com
Northmill
facebook.com/northmill.se
northmill.com
SpreadIT
facebook.com/SpreadITpl
spreadit.pl