Skip to content

Commit afea477

Browse files
committed
feat: add arch diagram and README for AWS infra
1 parent 22dfdd6 commit afea477

7 files changed

Lines changed: 131 additions & 24 deletions

File tree

user-management/CLAUDE.md

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
# CLAUDE.md
2+
3+
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
4+
5+
## Architecture Overview
6+
7+
This is a .NET 8 User Management Service implementing ports and adapters architecture with platform adaptability by design. The service can run on Azure, AWS, or any cloud-agnostic container orchestrator.
8+
9+
### Core Components
10+
- **Core** (`Stickerlandia.UserManagement.Core`) - Domain services and business logic
11+
- **Auth** (`Stickerlandia.UserManagement.Auth`) - Authentication using OpenIddict
12+
- **Agnostic** (`Stickerlandia.UserManagement.Agnostic`) - Cloud-agnostic implementations (Kafka, Postgres)
13+
- **AWS** (`Stickerlandia.UserManagement.AWS`) - AWS-specific implementations (SNS, SQS)
14+
- **Azure** (`Stickerlandia.UserManagement.Azure`) - Azure-specific implementations (Service Bus)
15+
16+
### Driving Adapters (Entry Points)
17+
- **Api** - ASP.NET minimal API (`/api/users/v1`)
18+
- **Worker** - Background worker service
19+
- **FunctionApp** - Azure Functions
20+
- **Lambda** - AWS Lambda functions
21+
22+
## Development Commands
23+
24+
### Local Development with .NET Aspire
25+
Run from `src/Stickerlandia.UserManagement.Aspire/`:
26+
27+
```bash
28+
# Agnostic services (Kafka, Postgres)
29+
dotnet run -lp agnostic
30+
31+
# Azure services (Azure Functions, Service Bus, Postgres)
32+
dotnet run -lp azure_native
33+
34+
# AWS services (Lambda, SNS, SQS, Postgres)
35+
dotnet run -lp aws_native
36+
```
37+
38+
### Testing
39+
40+
**Unit Tests:**
41+
```bash
42+
cd tests/Stickerlandia.UserManagement.UnitTest
43+
dotnet test
44+
```
45+
46+
**Integration Tests (requires environment variables):**
47+
```bash
48+
cd tests/Stickerlandia.UserManagement.IntegrationTest
49+
50+
# Agnostic integration tests
51+
export DRIVING=AGNOSTIC && export DRIVEN=AGNOSTIC && dotnet test
52+
53+
# Azure integration tests
54+
export DRIVING=AZURE && export DRIVEN=AZURE && dotnet test
55+
56+
# AWS integration tests
57+
export DRIVING=AWS && export DRIVEN=AWS && dotnet test
58+
```
59+
60+
### Infrastructure Deployment
61+
62+
**AWS CDK (from `infra/aws/`):**
63+
```bash
64+
npm run build # Compile TypeScript
65+
npm run test # Run infrastructure tests
66+
npm run cdk # CDK commands
67+
```
68+
69+
**Azure Terraform (from `infra/azure/`):**
70+
Standard Terraform commands for Azure resources.
71+
72+
## Code Quality Standards
73+
74+
- **Static Analysis**: Enforced via `Directory.build.props` and `CodeAnalysis.props`
75+
- **Warnings as Errors**: All projects treat warnings as errors
76+
- **Nullable Reference Types**: Enabled across all projects
77+
- **Implicit Usings**: Enabled for cleaner code
78+
79+
## Event Architecture
80+
81+
The service publishes and consumes events via:
82+
- **Outbox Pattern**: Implemented for reliable event publishing
83+
- **Platform-specific Messaging**: SNS/SQS (AWS), Service Bus (Azure), Kafka (Agnostic)
84+
- **Event Schemas**: Documented in `docs/async_api.yaml`
85+
86+
## Key Patterns
87+
88+
- **CQRS**: Commands and queries are separated in the Core domain
89+
- **Dependency Injection**: All adapters registered via service extensions
90+
- **Configuration**: Platform-specific configuration in each adapter
91+
- **Background Processing**: Handled by workers, functions, or lambdas depending on platform

user-management/README.md

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,23 @@ The project follows a ports and adapters architecture style, split down into `Dr
2323
- **Stickerlandia.UserManagement.Auth** - Core library for auth concerns using [OpenIddict](https://documentation.openiddict.com/)
2424

2525

26+
### AWS Architecture
27+
28+
![AWS architecture diagram](./docs/aws_arch.png)
29+
30+
The AWS native implementation of the user management service uses a combination of containers and functions as a service (FaaS), as well as native messaging services. The different components on this diagram tie to specific classes in the AWS CDK IaC project:
31+
32+
- [API](./infra/aws/lib/api.ts)
33+
- Amazon ECS for container orchestration, with Fargate providing the underlying compute
34+
- A traditional Postgres database, for use with the [OpenIddict](https://documentation.openiddict.com/) auth libraries
35+
- [Background Workers](./infra/aws/lib/background-workers.ts)
36+
- AWS Lambda for the compute, both handling external events and running on a schedule to process items from the outbox
37+
- Rules are defined on a shared external Amazon Event Bridge event bus
38+
- Amazon SQS provides durability at the boundaries of the system
39+
- Internal domian events are published to Amazon SNS
40+
- [Shared](./infra/aws/lib/sharedResources.ts)
41+
- Currently, these are manually created. Eventually these will be created in an external stack and this construct will pull from
42+
2643
## API Endpoints
2744

2845
### User Management API (`/api/users/v1`)

user-management/docs/aws_arch.png

1.8 MB
Loading

user-management/infra/aws/lib/api.ts

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,8 @@ import {
55
Cluster,
66
ContainerImage,
77
CpuArchitecture,
8-
FargateService,
98
FargateTaskDefinition,
109
FirelensLogRouterType,
11-
LogDriver,
1210
LogDrivers,
1311
OperatingSystemFamily,
1412
Protocol,
@@ -17,14 +15,10 @@ import { Repository } from "aws-cdk-lib/aws-ecr";
1715
import { Topic } from "aws-cdk-lib/aws-sns";
1816
import { Queue } from "aws-cdk-lib/aws-sqs";
1917
import {
20-
ApplicationLoadBalancer,
21-
ApplicationTargetGroup,
22-
TargetType,
2318
ApplicationProtocol,
24-
HealthCheck,
2519
Protocol as HealthCheckProtocol,
2620
} from "aws-cdk-lib/aws-elasticloadbalancingv2";
27-
import { Duration, CfnOutput } from "aws-cdk-lib";
21+
import { Duration } from "aws-cdk-lib";
2822
import { ApplicationLoadBalancedFargateService } from "aws-cdk-lib/aws-ecs-patterns";
2923

3024
export class ApiProps {

user-management/infra/aws/lib/background-workers.ts

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import { SubnetType, Vpc } from "aws-cdk-lib/aws-ec2";
21
import { Construct } from "constructs";
32
import { SharedProps } from "./constructs/shared-props";
43
import { InstrumentedLambdaFunction } from "./constructs/instrumented-function";
@@ -7,7 +6,7 @@ import { IQueue } from "aws-cdk-lib/aws-sqs";
76
import { SqsEventSource } from "aws-cdk-lib/aws-lambda-event-sources";
87
import { SqsDestination } from "aws-cdk-lib/aws-lambda-destinations";
98
import {
10-
EventBus,
9+
IEventBus,
1110
Rule,
1211
RuleTargetInput,
1312
Schedule,
@@ -17,6 +16,7 @@ import { ITopic } from "aws-cdk-lib/aws-sns";
1716

1817
export interface BackgroundWorkersProps {
1918
sharedProps: SharedProps;
19+
sharedEventBus: IEventBus;
2020
stickerClaimedQueue: IQueue;
2121
stickerClaimedDLQ: IQueue;
2222
userRegisteredTopic: ITopic;
@@ -26,10 +26,6 @@ export class BackgroundWorkers extends Construct {
2626
constructor(scope: Construct, id: string, props: BackgroundWorkersProps) {
2727
super(scope, id);
2828

29-
const eventBus = new EventBus(this, "UserManagementEventBus", {
30-
eventBusName: `${props.sharedProps.serviceName}-${props.sharedProps.environment}-bus`,
31-
});
32-
3329
const environmentVariables = {
3430
POWERTOOLS_SERVICE_NAME: props.sharedProps.serviceName,
3531
POWERTOOLS_LOG_LEVEL:
@@ -71,7 +67,7 @@ export class BackgroundWorkers extends Construct {
7167
);
7268

7369
const rule = new Rule(this, "StickerClaimedEventRule", {
74-
eventBus: eventBus,
70+
eventBus: props.sharedEventBus,
7571
ruleName: `${props.sharedProps.serviceName}-${props.sharedProps.environment}-sticker-claimed-rule`,
7672
eventPattern: {
7773
source: [`${props.sharedProps.environment}.stickers`],

user-management/infra/aws/lib/network.ts renamed to user-management/infra/aws/lib/sharedResources.ts

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,23 @@
1-
import { SubnetType, Vpc } from "aws-cdk-lib/aws-ec2";
1+
import { IVpc, SubnetType, Vpc } from "aws-cdk-lib/aws-ec2";
2+
import { EventBus, IEventBus } from "aws-cdk-lib/aws-events";
23
import { Construct } from "constructs";
3-
export interface NetworkProps {
4+
export interface SharedResourcesProps {
45
networkName: string;
56
}
67

7-
// TODO: move this to a shared infra project to allow one network across multiple services
8-
export class Network extends Construct {
9-
vpc: Vpc;
10-
constructor(scope: Construct, id: string, props: NetworkProps) {
8+
// TODO: move the creation of these resources to a seperate stack and update this stack to pull from SSM parameters
9+
export class SharedResources extends Construct {
10+
vpc: IVpc;
11+
sharedEventBus: IEventBus; // Placeholder for shared EventBus ARN or name
12+
constructor(scope: Construct, id: string, props: SharedResourcesProps) {
1113
super(scope, id);
1214

15+
this.sharedEventBus = new EventBus(this, "UserManagementEventBus", {
16+
eventBusName: `stickerlandia-shared-event-bus`,
17+
});
18+
19+
// TODO: Add creation of ALB
20+
1321
this.vpc = new Vpc(this, "Vpc", {
1422
vpcName: props.networkName,
1523
maxAzs: 2,

user-management/infra/aws/lib/user-service-stack.ts

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import * as cdk from "aws-cdk-lib";
22
import { Construct } from "constructs";
33
import { SharedProps } from "./constructs/shared-props";
44
import { DatadogECSFargate, DatadogLambda } from "datadog-cdk-constructs-v2";
5-
import { Network } from "./network";
5+
import { SharedResources } from "./sharedResources";
66
import { Api } from "./api";
77
import { Cluster } from "aws-cdk-lib/aws-ecs";
88
import { BackgroundWorkers } from "./background-workers";
@@ -22,15 +22,15 @@ export class UserServiceStack extends cdk.Stack {
2222
throw new Error("CONNECTION_STRING environment variable is required");
2323
}
2424

25-
const network = new Network(this, "Network", {
25+
const sharedResources = new SharedResources(this, "SharedResources", {
2626
networkName: `${serviceName}-${environment}-vpc`,
2727
});
2828

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

3232
const cluster = new Cluster(this, "ApiCluster", {
33-
vpc: network.vpc,
33+
vpc: sharedResources.vpc,
3434
clusterName: `${serviceName}-${environment}`,
3535
});
3636

@@ -86,12 +86,13 @@ export class UserServiceStack extends cdk.Stack {
8686

8787
const api = new Api(this, "Api", {
8888
sharedProps: sharedProps,
89-
vpc: network.vpc,
89+
vpc: sharedResources.vpc,
9090
cluster: cluster,
9191
});
9292

9393
const backgroundWorkers = new BackgroundWorkers(this, "BackgroundWorkers", {
9494
sharedProps: sharedProps,
95+
sharedEventBus: sharedResources.sharedEventBus,
9596
stickerClaimedQueue: api.stickerClaimedQueue,
9697
stickerClaimedDLQ: api.stickerClaimedDLQ,
9798
userRegisteredTopic: api.userRegisteredTopic,

0 commit comments

Comments
 (0)