Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 35 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# Include any files or directories that you don't want to be copied to your
# container here (e.g., local build artifacts, temporary files, etc.).
#
# For more help, visit the .dockerignore file reference guide at
# https://docs.docker.com/go/build-context-dockerignore/

**/.classpath
**/.dockerignore
**/.env
**/.git
**/.gitignore
**/.project
**/.settings
**/.toolstarget
**/.vs
**/.vscode
**/.next
**/.cache
**/*.*proj.user
**/*.dbmdl
**/*.jfm
**/charts
**/docker-compose*
**/compose.y*ml
**/Dockerfile*
**/node_modules
**/npm-debug.log
**/obj
**/secrets.dev.yaml
**/values.dev.yaml
**/build
**/dist
LICENSE
README.md
images
9 changes: 9 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# AWS Credentials
AWS_ACCESS_KEY_ID=""
AWS_SECRET_ACCESS_KEY=""
AWS_REGION="us-east-1"

# Dynamo Table
ACTIVITY_TABLE="ACTIVITY_TABLE"
USER_TABLE="USER_TABLE"
TRANSACTION_TABLE="TRANSACTION_TABLE"
26 changes: 26 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# syntax=docker/dockerfile:1
# --------- Builder Image ---------
FROM public.ecr.aws/lambda/nodejs:20 AS builder
WORKDIR /usr/app

RUN --mount=type=bind,source=package.json,target=package.json \
--mount=type=bind,source=package-lock.json,target=package-lock.json \
--mount=type=cache,target=/root/.npm \
npm ci

COPY . .

RUN npm run build

# --------- Runtime Image ---------
FROM public.ecr.aws/lambda/nodejs:20 AS runtime

ENV LAMBDA_TASK_ROOT=/var/task

WORKDIR ${LAMBDA_TASK_ROOT}

COPY --from=builder /usr/app/dist/* ./

EXPOSE 8080

CMD ["lambda.executePaymentHandler"]
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
# Reto técnico iO - Backend

## Solución:
[SOLUTION.md](SOLUTION.md)

## Descripción:
Se requiere implementar un proyecto serverless de registro de pagos y consulta de transacciones. A continuación se muestran los diagramas correspondientes:

Expand Down
36 changes: 36 additions & 0 deletions SOLUTION.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# Serverless App

You should first review the technical challenge so you have more context. Go to: [Readme page](README.md)

### Requirements
- SAM
- AWS CLI
- CDK (Cloud Development Kit)
- Node.js
- Typescript

### Arquitecture
Let's take a look at the directory structure

1. The infraestructure as code is in `iac` directory
2. The lambdas are in `src` and the modules are distributed using the cqrs pattern

### Run locally
First you must install the dependencies in both project iac and the main package and run:
`npm install`

Now you can try the endpoints using SAM:
`sam local start-api`

### Deploy
If you want to deploy this project, run the following commands:
1. `npm run build`
2. `cd iac`
3. `cdk bootstrap`
4. `cdk deploy`

### Playground
This project is available on these endpoints:
- `GET https://wwarlsw6l1.execute-api.us-east-1.amazonaws.com/dev/v1/transactions?transactionId={transactionId}`
- `POST https://wwarlsw6l1.execute-api.us-east-1.amazonaws.com/dev/v1/payments`
- `POST https://wwarlsw6l1.execute-api.us-east-1.amazonaws.com/dev/v1/payment-processor`
23 changes: 23 additions & 0 deletions compose.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# Comments are provided throughout this file to help you get started.
# If you need more help, visit the Docker Compose reference guide at
# https://docs.docker.com/go/compose-spec-reference/

# Here the instructions define your application as a service called "server".
# This service is built from the Dockerfile in the current directory.
# You can add other services your application may depend on here, such as a
# database or a cache. For examples, see the Awesome Compose repository:
# https://github.com/docker/awesome-compose
services:
lambda:
build:
context: .
target: runtime
env_file:
- .env
ports:
- 9000:8080
develop:
watch:
- action: rebuild
path: ./dist/lambda.js
target: /var/task/lambda.js
13 changes: 13 additions & 0 deletions env-vars.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"GetTransactionsFunction": {
"TRANSACTION_TABLE": "ServerlessAppStack-TransactionsTable0A011FCB-1PLNRR384FE6Q"
},
"ExecutePaymentFunction": {
"ACTIVITY_TABLE": "ServerlessAppStack-ActivityTable0B58B12C-1TWRDUE7KVAAA",
"TRANSACTION_TABLE": "ServerlessAppStack-TransactionsTable0A011FCB-1PLNRR384FE6Q",
"PAYMENT_PROCESSOR_URL": "https://9ateosf7cj.execute-api.us-east-1.amazonaws.com/dev"
},
"GetUsersFunction": {
"USER_TABLE": "ServerlessAppStack-UsersTable9725E9C8-12S9BCI09OW4R"
}
}
8 changes: 8 additions & 0 deletions iac/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
*.js
!jest.config.js
*.d.ts
node_modules

# CDK asset staging directory
.cdk.staging
cdk.out
6 changes: 6 additions & 0 deletions iac/.npmignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
*.ts
!*.d.ts

# CDK asset staging directory
.cdk.staging
cdk.out
14 changes: 14 additions & 0 deletions iac/Challenge.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# Welcome to your CDK TypeScript project

This is a blank project for CDK development with TypeScript.

The `cdk.json` file tells the CDK Toolkit how to execute your app.

## Useful commands

* `npm run build` compile typescript to js
* `npm run watch` watch for changes and compile
* `npm run test` perform the jest unit tests
* `npx cdk deploy` deploy this stack to your default AWS account/region
* `npx cdk diff` compare deployed stack with current state
* `npx cdk synth` emits the synthesized CloudFormation template
14 changes: 14 additions & 0 deletions iac/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# Welcome to your CDK TypeScript project

This is a blank project for CDK development with TypeScript.

The `cdk.json` file tells the CDK Toolkit how to execute your app.

## Useful commands

* `npm run build` compile typescript to js
* `npm run watch` watch for changes and compile
* `npm run test` perform the jest unit tests
* `npx cdk deploy` deploy this stack to your default AWS account/region
* `npx cdk diff` compare deployed stack with current state
* `npx cdk synth` emits the synthesized CloudFormation template
20 changes: 20 additions & 0 deletions iac/bin/cdk.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
#!/usr/bin/env node
import 'source-map-support/register';
import * as cdk from 'aws-cdk-lib';
import { ServerlessAppStack } from '../lib/serverless-app.stack';

const app = new cdk.App();
new ServerlessAppStack(app, 'ServerlessAppStack', {
/* If you don't specify 'env', this stack will be environment-agnostic.
* Account/Region-dependent features and context lookups will not work,
* but a single synthesized template can be deployed anywhere. */

/* Uncomment the next line to specialize this stack for the AWS Account
* and Region that are implied by the current CLI configuration. */
env: {
account: process.env.CDK_DEFAULT_ACCOUNT,
region: process.env.CDK_DEFAULT_REGION
},

/* For more information, see https://docs.aws.amazon.com/cdk/latest/guide/environments.html */
});
72 changes: 72 additions & 0 deletions iac/cdk.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
{
"app": "npx ts-node --prefer-ts-exts bin/cdk.ts",
"watch": {
"include": [
"**"
],
"exclude": [
"README.md",
"cdk*.json",
"**/*.d.ts",
"**/*.js",
"tsconfig.json",
"package*.json",
"yarn.lock",
"node_modules",
"test"
]
},
"context": {
"@aws-cdk/aws-lambda:recognizeLayerVersion": true,
"@aws-cdk/core:checkSecretUsage": true,
"@aws-cdk/core:target-partitions": [
"aws",
"aws-cn"
],
"@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true,
"@aws-cdk/aws-ec2:uniqueImdsv2TemplateName": true,
"@aws-cdk/aws-ecs:arnFormatIncludesClusterName": true,
"@aws-cdk/aws-iam:minimizePolicies": true,
"@aws-cdk/core:validateSnapshotRemovalPolicy": true,
"@aws-cdk/aws-codepipeline:crossAccountKeyAliasStackSafeResourceName": true,
"@aws-cdk/aws-s3:createDefaultLoggingPolicy": true,
"@aws-cdk/aws-sns-subscriptions:restrictSqsDescryption": true,
"@aws-cdk/aws-apigateway:disableCloudWatchRole": true,
"@aws-cdk/core:enablePartitionLiterals": true,
"@aws-cdk/aws-events:eventsTargetQueueSameAccount": true,
"@aws-cdk/aws-iam:standardizedServicePrincipals": true,
"@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true,
"@aws-cdk/aws-iam:importedRoleStackSafeDefaultPolicyName": true,
"@aws-cdk/aws-s3:serverAccessLogsUseBucketPolicy": true,
"@aws-cdk/aws-route53-patters:useCertificate": true,
"@aws-cdk/customresources:installLatestAwsSdkDefault": false,
"@aws-cdk/aws-rds:databaseProxyUniqueResourceName": true,
"@aws-cdk/aws-codedeploy:removeAlarmsFromDeploymentGroup": true,
"@aws-cdk/aws-apigateway:authorizerChangeDeploymentLogicalId": true,
"@aws-cdk/aws-ec2:launchTemplateDefaultUserData": true,
"@aws-cdk/aws-secretsmanager:useAttachedSecretResourcePolicyForSecretTargetAttachments": true,
"@aws-cdk/aws-redshift:columnId": true,
"@aws-cdk/aws-stepfunctions-tasks:enableEmrServicePolicyV2": true,
"@aws-cdk/aws-ec2:restrictDefaultSecurityGroup": true,
"@aws-cdk/aws-apigateway:requestValidatorUniqueId": true,
"@aws-cdk/aws-kms:aliasNameRef": true,
"@aws-cdk/aws-autoscaling:generateLaunchTemplateInsteadOfLaunchConfig": true,
"@aws-cdk/core:includePrefixInUniqueNameGeneration": true,
"@aws-cdk/aws-efs:denyAnonymousAccess": true,
"@aws-cdk/aws-opensearchservice:enableOpensearchMultiAzWithStandby": true,
"@aws-cdk/aws-lambda-nodejs:useLatestRuntimeVersion": true,
"@aws-cdk/aws-efs:mountTargetOrderInsensitiveLogicalId": true,
"@aws-cdk/aws-rds:auroraClusterChangeScopeOfInstanceParameterGroupWithEachParameters": true,
"@aws-cdk/aws-appsync:useArnForSourceApiAssociationIdentifier": true,
"@aws-cdk/aws-rds:preventRenderingDeprecatedCredentials": true,
"@aws-cdk/aws-codepipeline-actions:useNewDefaultBranchForCodeCommitSource": true,
"@aws-cdk/aws-cloudwatch-actions:changeLambdaPermissionLogicalIdForLambdaAction": true,
"@aws-cdk/aws-codepipeline:crossAccountKeysDefaultValueToFalse": true,
"@aws-cdk/aws-codepipeline:defaultPipelineTypeToV2": true,
"@aws-cdk/aws-kms:reduceCrossAccountRegionPolicyScope": true,
"@aws-cdk/aws-eks:nodegroupNameAttribute": true,
"@aws-cdk/aws-ec2:ebsDefaultGp3Volume": true,
"@aws-cdk/aws-ecs:removeDefaultDeploymentAlarm": true,
"@aws-cdk/custom-resources:logApiResponseDataPropertyTrueDefault": false
}
}
8 changes: 8 additions & 0 deletions iac/jest.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
module.exports = {
testEnvironment: 'node',
roots: ['<rootDir>/test'],
testMatch: ['**/*.test.ts'],
transform: {
'^.+\\.tsx?$': 'ts-jest'
}
};
60 changes: 60 additions & 0 deletions iac/lib/serverless-app.stack.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import * as cdk from 'aws-cdk-lib';
import { Construct } from 'constructs';
import { DynamoTables } from './stack/dynamo-tables';
import { Lambdas } from './stack/lambdas';
import { StepFunctions } from './stack/step-functions';
import { ApiGateway } from './stack/api-gateway';
import { DynamoEventSource, SqsDlq } from 'aws-cdk-lib/aws-lambda-event-sources';
import { StartingPosition } from 'aws-cdk-lib/aws-lambda';
import { Queue } from 'aws-cdk-lib/aws-sqs';
import { PolicyDocument, PolicyStatement, Role, ServicePrincipal } from 'aws-cdk-lib/aws-iam';

export class ServerlessAppStack extends cdk.Stack {
constructor(scope: Construct, id: string, props?: cdk.StackProps) {
super(scope, id, props);

const dynamoTables = new DynamoTables(this);
const lambdas = new Lambdas(this);
const stepFunctions = new StepFunctions(this, {
usersTable: dynamoTables.usersTable,
processPaymentFunction: lambdas.processPaymentFunction,
});
const apiGateway = new ApiGateway(this);
const apigatewayRole = new Role(this, 'ApiGatewayStepFunctionsRole', {
assumedBy: new ServicePrincipal('apigateway.amazonaws.com'),
inlinePolicies: {
startSyncExecutionPolicy: new PolicyDocument({
statements: [
new PolicyStatement({
actions: ['states:StartSyncExecution'],
resources: [stepFunctions.paymentStateMachine.stateMachineArn],
}),
],
}),
}
});

apiGateway.setupApiGateway(apigatewayRole, {
getTransactionsHandler: lambdas.getTransactionsFunction,
getUserHandler: lambdas.getUserFunction,
paymentProcessor: lambdas.paymentProcessorFunction,
stateMachine: stepFunctions.paymentStateMachine,
});

dynamoTables.grantPermissions(lambdas);

// Add environment variables to the lambdas
lambdas.getTransactionsFunction.addEnvironment('TRANSACTION_TABLE', dynamoTables.transactionsTable.tableName);
lambdas.getUserFunction.addEnvironment('USER_TABLE', dynamoTables.usersTable.tableName);
lambdas.paymentProcessorFunction.addEnvironment('PAYMENT_PROCESSOR_URL','https://9ateosf7cj.execute-api.us-east-1.amazonaws.com/dev');
lambdas.paymentProcessorFunction.addEnvironment('TRANSACTION_TABLE', dynamoTables.transactionsTable.tableName);
lambdas.registerActivityFunction.addEnvironment('ACTIVITY_TABLE', dynamoTables.activityTable.tableName);

// Add event sources to the lambdas
lambdas.registerActivityFunction.addEventSource(new DynamoEventSource(dynamoTables.transactionsTable, {
startingPosition: StartingPosition.TRIM_HORIZON,
batchSize: 5,
onFailure: new SqsDlq(new Queue(this, 'DLQ')),
}));
}
}
Loading