-
Notifications
You must be signed in to change notification settings - Fork 1
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
PYIC-6584: POC for async SQS messaging #2020
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
package uk.gov.di.ipv.core.library.service; | ||
|
||
public class AuditException extends RuntimeException { | ||
public AuditException(String message, Exception e) { | ||
super(message, e); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
|
@@ -2,48 +2,76 @@ | |||||
|
||||||
import com.fasterxml.jackson.core.JsonProcessingException; | ||||||
import com.fasterxml.jackson.databind.ObjectMapper; | ||||||
import software.amazon.awssdk.http.urlconnection.UrlConnectionHttpClient; | ||||||
import software.amazon.awssdk.services.sqs.SqsClient; | ||||||
import org.apache.logging.log4j.LogManager; | ||||||
import org.apache.logging.log4j.Logger; | ||||||
import software.amazon.awssdk.http.crt.AwsCrtAsyncHttpClient; | ||||||
import software.amazon.awssdk.services.sqs.SqsAsyncClient; | ||||||
import software.amazon.awssdk.services.sqs.model.SendMessageRequest; | ||||||
import software.amazon.awssdk.services.sqs.model.SendMessageResponse; | ||||||
import uk.gov.di.ipv.core.library.auditing.AuditEvent; | ||||||
import uk.gov.di.ipv.core.library.exceptions.SqsException; | ||||||
import uk.gov.di.ipv.core.library.helpers.LogHelper; | ||||||
|
||||||
import java.util.ArrayList; | ||||||
import java.util.List; | ||||||
import java.util.concurrent.CompletableFuture; | ||||||
import java.util.concurrent.ExecutionException; | ||||||
|
||||||
import static software.amazon.awssdk.regions.Region.EU_WEST_2; | ||||||
import static uk.gov.di.ipv.core.library.config.EnvironmentVariable.SQS_AUDIT_EVENT_QUEUE_URL; | ||||||
|
||||||
public class AuditService { | ||||||
private final SqsClient sqs; | ||||||
private static final Logger LOGGER = LogManager.getLogger(); | ||||||
|
||||||
private final SqsAsyncClient sqs; | ||||||
private final String queueUrl; | ||||||
private final ObjectMapper objectMapper; | ||||||
|
||||||
public AuditService(SqsClient sqs, ConfigService configService) { | ||||||
private List<CompletableFuture<SendMessageResponse>> events = new ArrayList<>(); | ||||||
|
||||||
public AuditService(SqsAsyncClient sqs, ConfigService configService) { | ||||||
this.sqs = sqs; | ||||||
this.queueUrl = configService.getEnvironmentVariable(SQS_AUDIT_EVENT_QUEUE_URL); | ||||||
this.objectMapper = new ObjectMapper(); | ||||||
} | ||||||
|
||||||
public AuditService(SqsClient sqs, ConfigService configService, ObjectMapper objectMapper) { | ||||||
public AuditService( | ||||||
SqsAsyncClient sqs, ConfigService configService, ObjectMapper objectMapper) { | ||||||
this.sqs = sqs; | ||||||
this.queueUrl = configService.getEnvironmentVariable(SQS_AUDIT_EVENT_QUEUE_URL); | ||||||
this.objectMapper = objectMapper; | ||||||
} | ||||||
|
||||||
public static SqsClient getSqsClient() { | ||||||
return SqsClient.builder() | ||||||
public static SqsAsyncClient getSqsClient() { | ||||||
return SqsAsyncClient.builder() | ||||||
.region(EU_WEST_2) | ||||||
.httpClientBuilder(UrlConnectionHttpClient.builder()) | ||||||
.httpClientBuilder(AwsCrtAsyncHttpClient.builder()) | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The AWS docs seem to imply that we should use the AWS CRT client for the sync case as well (https://docs.aws.amazon.com/sdk-for-java/latest/developer-guide/http-configuration.html#http-clients-recommend) - unless it's a snapstart thing to prefer the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What's the reason for preferring this? In any case, I probably won't change for this PR, but whoever picks up https://govukverify.atlassian.net/browse/PYIC-6883 can update if they wish There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think functionaly there's no difference, but if I was looking at it for the first time I'd wonder why we have to pass in a builder? Is something weird happening under the hood (it's not). But the way i've suggested seems more...expected? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The docs show using builder unless you want to provide your own shared HTTP Client, but I suspect in our case it'll make no difference. (https://docs.aws.amazon.com/sdk-for-java/latest/developer-guide/http-configuration-crt.html) I wonder if it changes the laziness at all (e.g. will it defer creating the client until it needs it?) |
||||||
.build(); | ||||||
} | ||||||
|
||||||
public void sendAuditEvent(AuditEvent auditEvent) throws SqsException { | ||||||
try { | ||||||
sqs.sendMessage( | ||||||
SendMessageRequest.builder() | ||||||
.queueUrl(queueUrl) | ||||||
.messageBody(objectMapper.writeValueAsString(auditEvent)) | ||||||
.build()); | ||||||
var event = | ||||||
sqs.sendMessage( | ||||||
SendMessageRequest.builder() | ||||||
.queueUrl(queueUrl) | ||||||
.messageBody(objectMapper.writeValueAsString(auditEvent)) | ||||||
.build()); | ||||||
events.add(event); | ||||||
} catch (JsonProcessingException e) { | ||||||
throw new SqsException(e); | ||||||
} | ||||||
} | ||||||
|
||||||
// Await audit events MUST be called before the end of each handler | ||||||
public void awaitAuditEvents() { | ||||||
try { | ||||||
var eventsToAwait = events; | ||||||
events = new ArrayList<>(); | ||||||
CompletableFuture.allOf(eventsToAwait.toArray(new CompletableFuture[0])).get(); | ||||||
} catch (InterruptedException | ExecutionException e) { | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If we get an InterruptedException (which I can't imagine we will), do we need to call |
||||||
LOGGER.error(LogHelper.buildErrorMessage("Failed to send audit event(s)", e)); | ||||||
throw new AuditException("Failed to send audit event(s)", e); | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is a RuntimeException to ease its use - this means an audit failure will fail the lambda, but this is probably a good thing! I'm in discussion about whether any of the audit events can be marked as non-critical (i.e. we can ignore if they fail), but for now let's assume that they all are, because TICF may need them. |
||||||
} | ||||||
} | ||||||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The scary thing about this approach is that if we don't call this, then the lambda runtime will suspend and (potentially) interrupt an in-progress request.
I can't think of a great solution to this beyond 'make sure it's included in all the handlers' and 'never construct a new audit service outside of a handler'.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do you think we'd catch this in the pipeline before any mistakes hit prod?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Probably not, which is the scary part :/