Skip to content

Commit 906abac

Browse files
authored
Merge pull request #1 from aws-samples/sliedig-ns-refactor
Sliedig ns refactor
2 parents d12a087 + 58fd04d commit 906abac

File tree

74 files changed

+3727
-2348
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

74 files changed

+3727
-2348
lines changed

.github/workflows/build.yml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,9 @@ name: Build .NET
22

33
on:
44
push:
5-
branches: [develop, main]
5+
branches: [ develop, main ]
66
pull_request:
7-
branches: [develop, main]
7+
branches: [ develop, main ]
88

99
defaults:
1010
run:
@@ -19,7 +19,7 @@ jobs:
1919
- name: Setup .NET 6.0
2020
uses: actions/setup-dotnet@607fce577a46308457984d59e4954e075820f10a
2121
with:
22-
dotnet-version: 6.0.405
22+
dotnet-version: 6.0.414
2323
- name: Install solution dependencies
2424
run: dotnet restore
2525
- name: Build

.gitignore

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
11
.DS_Store
22
.idea/*
33
**/.aws-sam/
4-
*.DotSettings.user
4+
*.DotSettings.user
5+
.vscode/*
6+
UnicornProperties.sln.DotSettings.user
7+
**/cdk.out/

README.md

Lines changed: 13 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,51 +1,32 @@
1-
![AWS Serverless Developer Experience Workshop Reference Architecture](./docs/workshop_logo.png)
1+
<img src="./docs/workshop_logo.png" alt="AWS Serverless Developer Experience Workshop Reference Architecture" width="80%" />
22

3-
# AWS Serverless Developer Experience workshop reference architecture (Python)
3+
# AWS Serverless Developer Experience workshop reference architecture (.NET)
44

55
This repository contains the reference architecture for the AWS Serverless Developer Experience workshop.
66

7-
The AWS Serverless Developer Experience workshop provides you with an immersive experience of a serverless developer.
8-
The goal is to provide you with an hands-on experience building a serverless solution using
9-
the [AWS Serverless Application Model (AWS SAM)](https://aws.amazon.com/serverless/sam/) and AWS SAM CLI.
7+
The AWS Serverless Developer Experience workshop provides you with an immersive experience as a serverless developer. The goal of this workshop is to provide you with hands-on experience building a serverless solution using the [**AWS Serverless Application Model (AWS SAM)**](https://aws.amazon.com/serverless/sam/) and **AWS SAM CLI**.
108

11-
Along the way, we want to demonstrate principles of event-driven distributed architecture, orchestration, and serverless
12-
observability, and how to apply them in code.
9+
Along the way, you will learn about principals of distributed event-driven architectures, messaging patterns, orchestration, and observability and how to apply them in code. You will explore exciting open-source tools, the core features of Powertools for AWS Lambda, and simplified CI/CD deployments supported by AWS SAM Pipelines.
1310

14-
We'll also explore open-source tools, core features of AWS Lambda Powertools, and serverless CI/CD deployments. You can
15-
choose choose to run this workshop in a runtime of your choice — Python, TypeScript, Java, and .NET — and work with your
16-
own developer setup or use AWS Cloud9 to build the services.
11+
At the end of this workshop, you will be familiar with Serverless developer workflows and microservice composition using AWS SAM, Serverless development best practices, and applied event-driven architectures.
1712

18-
This workshop will take approximately 4 hours to complete. We are assuming that you have some practical development
19-
skills in one of the supported runtimes, and are familiar with some of the services that we will use in this solution
20-
which include: [Amazon API Gateway](https://aws.amazon.com/apigateway/), [AWS Lambda](https://aws.amazon.com/lambda/)
21-
, [Amazon EventBridge](https://aws.amazon.com/eventbridge/)
22-
, [AWS Step Functions](https://aws.amazon.com/step-functions/) and [Amazon DynamoDB](https://aws.amazon.com/dynamodb/).
23-
24-
## About the Architecture
13+
## Introducing the Unicorn Properties architecture
2514

2615
![AWS Serverless Developer Experience Workshop Reference Architecture](./docs/architecture.png)
2716

2817
Our use case is based on a real estate company called **Unicorn Properties**.
2918

30-
As a real estate agency, **Unicorn Properties** needs to manage the publishing of new property listings and sale
31-
contracts linked to individual properties, and provide a way for their customers to view approved property listings.
19+
As a real estate agency, **Unicorn Properties** needs to manage the publishing of new property listings and sale contracts linked to individual properties, and provide a way for their customers to view approved property listings.
20+
21+
To support their needs, Unicorn Properties have adopted a serverless, event-driven approach to designing their architecture. This architecture is centred around two primary domains: **Contracts** (managed by the Contracts Service) and **Properties** (managed by the Web and Properties Services).
3222

33-
To support their needs, Unicorn Properties have adopted a serverless, event-driven approach to designing their
34-
architecture. This architecture is centred around two primary domains: boundaries–Contracts (managed by the Contracts
35-
Service) and Properties (which are managed by the Properties Web and Properties Services).
23+
The **Unicorn Contracts** service (namespace: `Unicorn.Contracts`) is a simplified service that manages the contractual relationship between a seller of a property and Unicorn Properties. Contracts are drawn up that define the property for sale, the terms and conditions that Unicorn Properties sets, and how much it will cost the seller to engage the services of the agency.
3624

37-
The **Contracts Service** is a simplified service that manages the contractual relationship between a seller of a
38-
property and Unicorn Properties. Contracts are drawn up that define the property for sale, the terms and conditions that
39-
Unicorn Properties sets, and how much it will cost the seller to engage the services of the agency.
25+
The **Unicorn Web** (namespace: `Unicorn.Web`) manages the details of a property listing to be published on the Unicorn Properties website. Every property listing has an address, a sale price, a description of the property, and some photos that members of the public can look at to get them interested in purchasing the property. Only properties that have been approved for publication can be made visible to the public.
4026

41-
The **Properties Web** service manages the details of a property listing to be published on the Unicorn Properties
42-
website. Every property listing has an address, a sale price, a description of the property, and some photos that
43-
members of the public can look at to get them interested in purchasing the property. **Only properties that have been
44-
approved for publication can be made visible to the public**.
27+
The **Unicorn Properties** service (namespace: `Unicorn.Properties`) approves a property listings. This service implements a workflow that checks for the existence of a contract, makes sure that the content and the images are safe to publish, and finally checks that the contract has been approved. We don’t want to publish a property until we have an approved contract!
4528

46-
The **Properties Service** approves a listing. This service implements a workflow that checks for the existence of a
47-
contract, makes sure that the content and the images are safe to publish, and finally checks that the contract has been
48-
approved. We don’t want to publish a property until we have an approved contract!
29+
Have a go at building this architecture yourself! Head over to the [Serverless Developer Experience Workshop](https://catalog.workshops.aws/serverless-developer-experience) for more details.
4930

5031
## Credits
5132

Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
// SPDX-License-Identifier: MIT-0
3+
4+
using System;
5+
using System.Collections.Generic;
6+
using System.Text.Json;
7+
using System.Threading.Tasks;
8+
using Amazon.DynamoDBv2;
9+
using Amazon.DynamoDBv2.Model;
10+
using Amazon.Lambda.SQSEvents;
11+
using FizzWare.NBuilder;
12+
using NSubstitute;
13+
using Xunit;
14+
using Xunit.Abstractions;
15+
16+
namespace Unicorn.Contracts.ContractService.Tests;
17+
18+
[Collection("Sequential")]
19+
public class ContractEventHandlerTest
20+
{
21+
private readonly ITestOutputHelper _testOutputHelper;
22+
23+
public ContractEventHandlerTest(ITestOutputHelper testOutputHelper)
24+
{
25+
_testOutputHelper = testOutputHelper;
26+
27+
// Set env variable for Powertools Metrics
28+
Environment.SetEnvironmentVariable("DYNAMODB_TABLE", "uni-prop-local-contract-ContractsTable");
29+
Environment.SetEnvironmentVariable("POWERTOOLS_METRICS_NAMESPACE", "ContractService");
30+
}
31+
32+
[Fact]
33+
public async Task Create_contract_saves_message_with_new_status()
34+
{
35+
// Arrange
36+
var eventPayload = Builder<ApiGwSqsPayload>.CreateNew()
37+
.With(x => x.property_id = "usa/anytown/main-street/777")
38+
.With(x => x.seller_name = "any seller")
39+
.With(x => x.address = new address() { city = "anytown", number = 777, street = "main-street" })
40+
.Build();
41+
42+
var sqsEvent = new SQSEvent()
43+
{
44+
Records = new List<SQSEvent.SQSMessage>
45+
{
46+
new()
47+
{
48+
Body = JsonSerializer.Serialize(eventPayload),
49+
MessageAttributes = new Dictionary<string, SQSEvent.MessageAttribute>
50+
{
51+
{ "HttpMethod", new SQSEvent.MessageAttribute { StringValue = "POST" } }
52+
}
53+
}
54+
}
55+
};
56+
57+
var mockDynamoDbClient = Substitute.ForPartsOf<AmazonDynamoDBClient>();
58+
mockDynamoDbClient.PutItemAsync(Arg.Any<PutItemRequest>())
59+
.Returns(new PutItemResponse());
60+
61+
var context = TestHelpers.NewLambdaContext();
62+
63+
// Act
64+
var function = new ContractEventHandler(mockDynamoDbClient);
65+
await function.FunctionHandler(sqsEvent, context);
66+
67+
// Assert
68+
await mockDynamoDbClient.Received(1).PutItemAsync(Arg.Any<PutItemRequest>());
69+
70+
}
71+
72+
[Fact]
73+
public async Task Update_contract_saves_message_with_new_status()
74+
{
75+
// Set up
76+
var eventPayload = Builder<ApiGwSqsPayload>.CreateNew()
77+
.With(x => x.property_id = "usa/anytown/main-street/111")
78+
.With(x => x.seller_name = "any seller")
79+
.With(x => x.address = new address() { city = "anytown", number = 111, street = "main-street" })
80+
.Build();
81+
82+
var sqsEvent = new SQSEvent()
83+
{
84+
Records = new List<SQSEvent.SQSMessage>
85+
{
86+
new()
87+
{
88+
Body = JsonSerializer.Serialize(eventPayload),
89+
MessageAttributes = new Dictionary<string, SQSEvent.MessageAttribute>
90+
{
91+
{ "HttpMethod", new SQSEvent.MessageAttribute { StringValue = "PUT" } }
92+
}
93+
}
94+
}
95+
};
96+
97+
var mockDynamoDbClient = Substitute.ForPartsOf<AmazonDynamoDBClient>();
98+
mockDynamoDbClient.UpdateItemAsync(Arg.Any<UpdateItemRequest>())
99+
.Returns(new UpdateItemResponse());
100+
101+
var context = TestHelpers.NewLambdaContext();
102+
103+
// Arrange
104+
var function = new ContractEventHandler(mockDynamoDbClient);
105+
await function.FunctionHandler(sqsEvent, context);
106+
107+
// Assert
108+
await mockDynamoDbClient.Received(1)
109+
.UpdateItemAsync(Arg.Any<UpdateItemRequest>());
110+
}
111+
}
112+
public class ApiGwSqsPayload
113+
{
114+
public string property_id { get; set; }
115+
public address address { get; set; }
116+
public string seller_name { get; set; }
117+
}
118+
119+
public class address
120+
{
121+
public int number { get; set; }
122+
public string? street { get; set; }
123+
public string? city { get; set; }
124+
public string country { get; } = "USA";
125+
}

Unicorn.Contracts/ContractsService.Test/ContractsService.Tests.csproj

Lines changed: 13 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -4,17 +4,19 @@
44
<TargetFramework>net6.0</TargetFramework>
55
<AssemblyName>Unicorn.Contracts.ContractService.Tests</AssemblyName>
66
<RootNamespace>Unicorn.Contracts.ContractService.Tests</RootNamespace>
7+
<Nullable>enable</Nullable>
78
</PropertyGroup>
89

910
<ItemGroup>
10-
<PackageReference Include="Amazon.Lambda.Core" Version="2.1.0" />
11-
<PackageReference Include="Amazon.Lambda.DynamoDBEvents" Version="2.1.1" />
11+
<PackageReference Include="Amazon.Lambda.Core" Version="2.2.0" />
12+
<PackageReference Include="Amazon.Lambda.DynamoDBEvents" Version="2.3.0" />
1213
<PackageReference Include="Amazon.Lambda.TestUtilities" Version="2.0.0" />
13-
<PackageReference Include="AWSSDK.DynamoDBv2" Version="3.7.200.17" />
14-
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.7.0" />
15-
<PackageReference Include="NSubstitute" Version="5.0.0" />
16-
<PackageReference Include="xunit" Version="2.5.0" />
17-
<PackageReference Include="xunit.runner.visualstudio" Version="2.5.0">
14+
<PackageReference Include="AWSSDK.DynamoDBv2" Version="3.7.300.7" />
15+
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.8.0" />
16+
<PackageReference Include="NBuilder" Version="6.1.0" />
17+
<PackageReference Include="NSubstitute" Version="5.1.0" />
18+
<PackageReference Include="xunit" Version="2.6.2" />
19+
<PackageReference Include="xunit.runner.visualstudio" Version="2.5.4">
1820
<PrivateAssets>all</PrivateAssets>
1921
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
2022
</PackageReference>
@@ -25,29 +27,11 @@
2527
</ItemGroup>
2628

2729
<ItemGroup>
28-
<None Update="events\create_valid_event.json">
29-
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
30+
<None Update="events\create_contract_valid_payload_1.json">
31+
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
3032
</None>
31-
<None Update="events\create_empty_dict_body_event.json">
32-
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
33-
</None>
34-
<None Update="events\create_missing_body_event.json">
35-
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
36-
</None>
37-
<None Update="events\create_wrong_event.json">
38-
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
39-
</None>
40-
<None Update="events\update_empty_dict_body_event.json">
41-
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
42-
</None>
43-
<None Update="events\update_missing_body_event.json">
44-
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
45-
</None>
46-
<None Update="events\update_valid_event.json">
47-
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
48-
</None>
49-
<None Update="events\update_wrong_event.json">
50-
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
33+
<None Update="events\update_contract_valid_payload_1.json">
34+
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
5135
</None>
5236
</ItemGroup>
5337

Unicorn.Contracts/ContractsService.Test/CreateContractFunctionTest.cs

Lines changed: 0 additions & 62 deletions
This file was deleted.

Unicorn.Contracts/ContractsService.Test/TestHelpers.cs

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
using Amazon.Lambda.APIGatewayEvents;
88
using Amazon.Lambda.Core;
99
using Amazon.Lambda.Serialization.SystemTextJson;
10+
using Amazon.Lambda.SQSEvents;
1011
using Amazon.Lambda.TestUtilities;
1112

1213
namespace Unicorn.Contracts.ContractService.Tests;
@@ -30,6 +31,21 @@ public static APIGatewayProxyRequest LoadApiGatewayProxyRequest(string filename)
3031
return request;
3132
}
3233

34+
public static SQSEvent LoadSqsEvent(string filename)
35+
{
36+
var serializer = Activator.CreateInstance(typeof(DefaultLambdaJsonSerializer)) as ILambdaSerializer;
37+
38+
SQSEvent sqsEvent = null;
39+
40+
using var fileStream = LoadJsonTestFile(filename);
41+
if (serializer != null)
42+
{
43+
sqsEvent = serializer.Deserialize<SQSEvent>(fileStream);
44+
}
45+
46+
return sqsEvent;
47+
}
48+
3349
// This utility method takes care of removing the BOM that System.Text.Json doesn't like.
3450
public static MemoryStream LoadJsonTestFile(string filename)
3551
{
@@ -51,5 +67,5 @@ public static TestLambdaContext NewLambdaContext()
5167
AwsRequestId = Guid.NewGuid().ToString("D")
5268
};
5369
}
54-
70+
5571
}

0 commit comments

Comments
 (0)