Skip to content

feat: add initial AWS implementation#24

Closed
jeastham1993 wants to merge 16 commits into
mainfrom
feat/aws-impl
Closed

feat: add initial AWS implementation#24
jeastham1993 wants to merge 16 commits into
mainfrom
feat/aws-impl

Conversation

@jeastham1993

Copy link
Copy Markdown
Collaborator

No description provided.

@jeastham1993 jeastham1993 requested a review from Copilot June 6, 2025 14:40
Comment thread user-management/infra/aws/src/Program.cs Outdated
Comment thread user-management/infra/aws/src/InstrumentedFunction.cs Outdated

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

Add initial AWS support for the User Management service, including message processing and infrastructure deployment.

  • Introduce SQS worker to handle StickerClaimedEventV1 messages.
  • Implement SNS publisher with CloudEvents for user registration announcements.
  • Define CDK stack for deploying queues, topics, Lambdas, and API Gateway.

Reviewed Changes

Copilot reviewed 40 out of 40 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
src/Stickerlandia.UserManagement.AWS/Stickerlandia.UserManagement.AWS.csproj Bump and add AWS SDK and CloudEvents package references.
src/Stickerlandia.UserManagement.AWS/SqsStickerClaimedWorker.cs New worker to poll and process SQS messages.
src/Stickerlandia.UserManagement.AWS/SnsEventPublisher.cs Publisher converting events to CloudEvents and sending to SNS.
src/Stickerlandia.UserManagement.AWS/ServiceExtensions.cs Register AWS clients, Lambda hosting, and messaging adapters.
infra/aws/src/UserManagementServiceStack.cs CDK stack for DynamoDB, SQS, SNS, Lambdas, and API Gateway.
Comments suppressed due to low confidence (2)

user-management/src/Stickerlandia.UserManagement.AWS/SqsStickerClaimedWorker.cs:19

  • [nitpick] Public classes like this should have XML doc comments summarizing their role and public surface to improve maintainability and discoverability.
public class SqsStickerClaimedWorker : IMessagingWorker

user-management/src/Stickerlandia.UserManagement.AWS/SqsStickerClaimedWorker.cs:38

  • The message processing logic, including JSON deserialization and exception handling, should have unit tests to validate both happy and failure paths.
private async Task ProcessMessageAsync(Message message)

Comment thread user-management/src/Stickerlandia.UserManagement.AWS/SnsEventPublisher.cs Outdated
Comment thread user-management/infra/aws/src/UserManagementServiceStack.cs Outdated
Comment thread user-management/src/Stickerlandia.UserManagement.AWS/SqsStickerClaimedWorker.cs Outdated
jeastham1993 and others added 7 commits June 6, 2025 16:47
Co-authored-by: datadog-datadog-prod-us1[bot] <88084959+datadog-datadog-prod-us1[bot]@users.noreply.github.com>
…ublisher.cs

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
…rClaimedWorker.cs

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
@jeastham1993 jeastham1993 requested a review from scottgerring June 6, 2025 14:51

@scottgerring scottgerring left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I had a skim and added a couple of small comments, but it struck me that we probably want to formalise our deployment targets before we go much further down the road with IaC. What do you think?

I jotted down what I recall we've discussed so far over on #26

Comment thread user-management/infra/aws/src/InstrumentedFunction.cs Outdated
//TODO: Implement AWS Lambda support
var apiLambdaFunction = builder.AddAWSLambdaFunction<Projects.Stickerlandia_UserManagement_Api>("UsersApi",
"Stickerlandia.UserManagement.Api")
.WithEnvironment("ConnectionStrings__messaging", resources.MessagingResource)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This env var style grosses me out a bit, but it's the established pattern in dotnet, right?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, but not a strict rule. What would you prefer? All uppercase?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm going to leave this for the moment, can fix in a later update if needed.

Comment thread user-management/src/Stickerlandia.UserManagement.Aspire/Program.cs
Comment on lines +12 to +96
export class UserServiceStack extends cdk.Stack {
constructor(scope: Construct, id: string, props?: cdk.StackProps) {
super(scope, id, props);

const serviceName = "user-service";
const environment = process.env.ENV || "dev";
const version = process.env.VERSION || "latest";

const network = new Network(this, "Network", {
networkName: `${serviceName}-${environment}-vpc`,
});

const ddSite = process.env.DD_SITE || "datadoghq.com";
const ddApiKey = process.env.DD_API_KEY || "";

const cluster = new Cluster(this, "ApiCluster", {
vpc: network.vpc,
clusterName: `${serviceName}-${environment}`,
});

const sharedProps: SharedProps = {
connectionString:
"Server=ep-divine-snow-abgipduo-pooler.eu-west-2.aws.neon.tech;Port=5432;Database=stickerlandia-users;User Id=stickerlandia-users_owner;Password=npg_buwe2PoK1NgV;sslmode=require;",
serviceName: "user-service",
environment: process.env.ENV || "dev",
version: process.env.VERSION || "latest",
team: "users",
domain: "users",
datadog: {
apiKey: ddApiKey,
site: ddSite,
lambda: new DatadogLambda(this, "DatadogLambda", {
apiKey: ddApiKey,
site: ddSite,
}),
ecsFargate: new DatadogECSFargate({
// One of the following 3 apiKey params are required
apiKey: ddApiKey,
cpu: 256,
memoryLimitMiB: 512,
isDatadogEssential: true,
isDatadogDependencyEnabled: true,
site: ddSite,
clusterName: cluster.clusterName,
environmentVariables: {},
dogstatsd: {
isEnabled: true,
},
apm: {
isEnabled: true,
traceInferredProxyServices: true,
},
logCollection: {
isEnabled: true,
fluentbitConfig: {
firelensOptions: {
enableECSLogMetadata: true,
},
logDriverConfig: {
hostEndpoint: `http-intake.logs.${ddSite}`,
serviceName: serviceName,
},
},
},
env: environment,
service: serviceName,
version: version,
}),
},
};

const api = new Api(this, "Api", {
sharedProps: sharedProps,
vpc: network.vpc,
cluster: cluster,
});

const backgroundWorkers = new BackgroundWorkers(this, "BackgroundWorkers", {
sharedProps: sharedProps,
stickerClaimedQueue: api.stickerClaimedQueue,
stickerClaimedDLQ: api.stickerClaimedDLQ,
userRegisteredTopic: api.userRegisteredTopic,
});
}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Quality Violation

This class is unnecessary (...read more)

This rule advises against the unnecessary use of classes that contain only static members, or nothing. In JavaScript, classes are primarily used for object-oriented programming, where each instance of a class has its own state and behavior. Static members, on the other hand, belong to the class itself and not to any instance of the class.

When a class contains only static members, it does not make use of JavaScript's object-oriented capabilities, and it can be more difficult to understand, test, and maintain than necessary. In order to avoid this issue, consider using regular functions and variables instead of static class members. This makes your code easier to understand and maintain, and it allows you to make better use of JavaScript's features.

View in Datadog  Leave us feedback  Documentation

Comment on lines +25 to +117
export class BackgroundWorkers extends Construct {
constructor(scope: Construct, id: string, props: BackgroundWorkersProps) {
super(scope, id);

const eventBus = new EventBus(this, "UserManagementEventBus", {
eventBusName: `${props.sharedProps.serviceName}-${props.sharedProps.environment}-bus`,
});

const environmentVariables = {
POWERTOOLS_SERVICE_NAME: props.sharedProps.serviceName,
POWERTOOLS_LOG_LEVEL:
props.sharedProps.environment === "prod" ? "WARN" : "INFO",
ENV: props.sharedProps.environment,
ConnectionStrings__messaging: "",
ConnectionStrings__database: props.sharedProps.connectionString,
Aws__UserRegisteredTopicArn: props.userRegisteredTopic.topicArn,
Aws__StickerClaimedQueueUrl: props.stickerClaimedQueue.queueUrl,
Aws__StickerClaimedDLQUrl: props.stickerClaimedDLQ.queueUrl,
DRIVING: "ASPNET",
DRIVEN: "AWS",
DISABLE_SSL: "true",
};

const stickerClaimedWorker = new InstrumentedLambdaFunction(
this,
"StickerClaimedWorkerFunction",
{
sharedProps: props.sharedProps,
handler:
"Stickerlandia.UserManagement.Lambda::Stickerlandia.UserManagement.Lambda.Sqs_StickerClaimed_Generated::StickerClaimed",
buildDef:
"../../src/Stickerlandia.UserManagement.Lambda/",
functionName: "sticker-claimed-worker",
environment: environmentVariables,
memorySize: 1024,
timeout: Duration.seconds(25),
logLevel: props.sharedProps.environment === "prod" ? "WARN" : "INFO",
onFailure: new SqsDestination(props.stickerClaimedDLQ),
}
);

stickerClaimedWorker.function.addEventSource(
new SqsEventSource(props.stickerClaimedQueue, {
batchSize: 10,
reportBatchItemFailures: true,
})
);

const rule = new Rule(this, "StickerClaimedEventRule", {
eventBus: eventBus,
ruleName: `${props.sharedProps.serviceName}-${props.sharedProps.environment}-sticker-claimed-rule`,
eventPattern: {
source: [`${props.sharedProps.environment}.stickers`],
detailType: ["users.stickerClaimed.v1"],
},
});
rule.addTarget(new SqsQueue(props.stickerClaimedQueue));

const outboxWorker = new InstrumentedLambdaFunction(
this,
"OutboxWorkerFunction",
{
sharedProps: props.sharedProps,
handler:
"Stickerlandia.UserManagement.Lambda::Stickerlandia.UserManagement.Lambda.OutboxFunctions_Worker_Generated::Worker",
buildDef:
"../../src/Stickerlandia.UserManagement.Lambda/",
functionName: "outbox-worker",
environment: environmentVariables,
memorySize: 1024,
timeout: Duration.seconds(50),
logLevel: props.sharedProps.environment === "prod" ? "WARN" : "INFO",
onFailure: new SqsDestination(props.stickerClaimedDLQ),
}
);
props.userRegisteredTopic.grantPublish(outboxWorker.function);

const outboxWorkerSchedule = new Rule(this, "OutboxWorkerSchedule", {
description: "Trigger outbox worker every 1 minute",
schedule: Schedule.rate(Duration.minutes(1)),
});

// Add the Lambda function as a target
outboxWorkerSchedule.addTarget(
new LambdaFunction(outboxWorker.function, {
retryAttempts: 2,
event: RuleTargetInput.fromObject({
run: true,
}),
})
);
}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Quality Violation

This class is unnecessary (...read more)

This rule advises against the unnecessary use of classes that contain only static members, or nothing. In JavaScript, classes are primarily used for object-oriented programming, where each instance of a class has its own state and behavior. Static members, on the other hand, belong to the class itself and not to any instance of the class.

When a class contains only static members, it does not make use of JavaScript's object-oriented capabilities, and it can be more difficult to understand, test, and maintain than necessary. In order to avoid this issue, consider using regular functions and variables instead of static class members. This makes your code easier to understand and maintain, and it allows you to make better use of JavaScript's features.

View in Datadog  Leave us feedback  Documentation


namespace Stickerlandia.UserManagement.Lambda;

public class Sqs(ILogger<Sqs> logger, IServiceScopeFactory serviceScopeFactory, OutboxProcessor outboxProcessor)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟠 Code Quality Violation

This class is considered dead code because it has a private constructor that isn't instantiated within the class and no public static declarations. (...read more)

Classes with a private constructor can't be instantiated outside of the class itself. Because they are unreachable, they should be removed or made public.

An exception is made for classes that access their own constructors (like a singleton), and classes that derive from System.Runtime.InteropServices.SafeHandle.

View in Datadog  Leave us feedback  Documentation

@jeastham1993 jeastham1993 deleted the feat/aws-impl branch June 17, 2025 19:56
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants