diff --git a/EDAWorkshop-01.pptx b/EDAWorkshop-01.pptx index 3f70916..c595f70 100644 Binary files a/EDAWorkshop-01.pptx and b/EDAWorkshop-01.pptx differ diff --git a/EDAWorkshop-03.pptx b/EDAWorkshop-03.pptx index 10d80af..d44fccd 100644 Binary files a/EDAWorkshop-03.pptx and b/EDAWorkshop-03.pptx differ diff --git a/EDAWorkshop-04.pptx b/EDAWorkshop-04.pptx new file mode 100644 index 0000000..02ee996 Binary files /dev/null and b/EDAWorkshop-04.pptx differ diff --git a/EDAWorkshop-05.pptx b/EDAWorkshop-05.pptx new file mode 100644 index 0000000..33501ab Binary files /dev/null and b/EDAWorkshop-05.pptx differ diff --git a/OrderProcessingSystem.drawio b/OrderProcessingSystem.drawio new file mode 100644 index 0000000..0ed58d5 --- /dev/null +++ b/OrderProcessingSystem.drawio @@ -0,0 +1,156 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/README.md b/README.md index ec9cf39..3183497 100644 --- a/README.md +++ b/README.md @@ -68,5 +68,6 @@ Hands-on full-day workshop where you will design, develop, and publish a serverl | Event | Location | Date | Time | Room | Downloads | |-------|:--------:|-----:|-----:|-----:|----------:| | [Beer City Code](https://www.beercitycode.com/) | Grand Rapids, MI | August 4, 2023 | 9:00 am EDT | Room 264 | Available Afterwards | -| [dev up](https://www.devupconf.org/) | St. Charles, MO | August 30, 2023 | 8:30 am CDT| Junior Ballroom C | Available Afterwards | -| [TechBash](https://techbash.com/) | Pocono Manor, PA | November 7, 2023 | TBA | TBA | Availalbe Afterwards | \ No newline at end of file +| [dev up](https://www.devupconf.org/) (Canceled) | St. Charles, MO | August 30, 2023 | 8:30 am CDT| Junior Ballroom C | Available Afterwards | +| [TechBash](https://techbash.com/) | Pocono Manor, PA | November 7, 2023 | TBA | TBA | Available Afterwards | +| [CodeMash](https://codemash.org/session-details/?id=532679) | Sandusky, OH | January 10, 2024 | TBA | TBA | Available Afterwards | \ No newline at end of file diff --git a/Workshop/GuidedWalkthough/02-DatabaseConnections.md b/References/02-DatabaseConnections.md similarity index 100% rename from Workshop/GuidedWalkthough/02-DatabaseConnections.md rename to References/02-DatabaseConnections.md diff --git a/Workshop/User Stories and Events.txt b/References/User Stories and Events.txt similarity index 100% rename from Workshop/User Stories and Events.txt rename to References/User Stories and Events.txt diff --git a/Workshop/User Stories.docx b/References/User Stories.docx similarity index 100% rename from Workshop/User Stories.docx rename to References/User Stories.docx diff --git a/Workshop/UserStories.md b/References/UserStories.md similarity index 100% rename from Workshop/UserStories.md rename to References/UserStories.md diff --git a/Resources/Images/OrderProcessingSystem.drawio.png b/Resources/Images/OrderProcessingSystem.drawio.png new file mode 100644 index 0000000..c5e535f Binary files /dev/null and b/Resources/Images/OrderProcessingSystem.drawio.png differ diff --git a/Workshop/GuidedWalkthough/00-ConnectToAzureAccount.md b/Workshop/GuidedWalkthough/00-ConnectToAzureAccount.md new file mode 100644 index 0000000..fc303c5 --- /dev/null +++ b/Workshop/GuidedWalkthough/00-ConnectToAzureAccount.md @@ -0,0 +1,29 @@ +# 00 - Connect to Azure + +## User Story +As a member of the Building Bricks' e-commerce backend development team, I want to access the Azure subscription so that I can perform my work on the project. + +### Definition of Done +The developer has a valid Azure subscription and can access it from the command line. + +--- + +## Workshop Exercises +For some of the workshop, we will be using Azure. You will need to have an Azure account to complete the workshop. If you do not have an Azure account, you can create one for free [here](https://azure.microsoft.com/en-us/free/). + +### Install Azure CLI (00A) +The Azure CLI is a command-line tool providing a great experience for managing Azure resources. The CLI is designed to make scripting easy, query data, support long-running operations, and more. It is available across Azure services and is designed to be consistent, easy to use, and easy to learn. + +1. Navigate to [Install Azure CLI on Windows](https://learn.microsoft.com/en-us/cli/azure/install-azure-cli-windows?tabs=azure-cli) +1. Follow the instructions to install the Azure CLI + +### Login to Azure (00B) +1. Open a command prompt +1. Type `az login` +1. Follow the instructions to login to Azure +1. Type `az account list` +1. Verify that the subscription you want to use is listed +1. Type `az account set --subscription ` +1. Verify that the correct subscription is set +1. Type `az account show` +1. Verify that the correct subscription is set \ No newline at end of file diff --git a/Workshop/GuidedWalkthough/01-Configuration.md b/Workshop/GuidedWalkthough/01-Configuration.md index 33469ca..b268404 100644 --- a/Workshop/GuidedWalkthough/01-Configuration.md +++ b/Workshop/GuidedWalkthough/01-Configuration.md @@ -1,15 +1,58 @@ -# 01 - Configuration and Project Setup -First we will setup the configuration services so our applications can get secrets and settings without having to put compromising information in your source code repositories. +# 01 - Project Setup and Configuration + +## User Story +As the architect, I want to setup the project and configure the initial Azure resources so that the development team can begin developing the solution. + +### Definition of Done +The project has been setup and the initial Azure resources have been configured. + +--- + +## Workshop Exercises **Tasks** -- 01A - [Create a resource group](#create-a-resource-group-01a) -- 01B - [Create an Azure Key Vault vault](#create-an-azure-key-vault-vault-01b) -- 01C - [Create an App Configuration store](#create-an-app-configuration-store-01c) -- 01D - [Create Cosmos DB account](#create-cosmos-db-account-01d) -- 01E - [Add Cosmos DB Primary Key to Key Vault](#add-cosmos-db-primary-key-to-key-vault-01e) -- 01F - [Initialize the GitHub Repo for the Workshop](#initialize-the-github-repo-for-the-workshop-O1f) - -## Create a resource group (01A) +- 01A - [Initialize GitHub Repo](#initialize-github-repo-01a) +- 01B - [Clone GitHub Repository](#clone-github-repo-01b) +- 01C - [Create a resource group](#create-a-resource-group-01c) +- 01D - [Create an Azure Key Vault vault](#create-an-azure-key-vault-vault-01d) +- 01E - [Create an App Configuration store](#create-an-app-configuration-store-01e) +- 01F - [Create Cosmos DB account](#create-cosmos-db-account-01f) +- 01G - [Add Cosmos DB Primary Key to Key Vault](#add-cosmos-db-primary-key-to-key-vault-01g) +- 01H - [Initialize the GitHub Repo for the Workshop](#initialize-the-github-repo-for-the-workshop-O1h) + +### Initialize GitHub Repo (01A) +Log into your GitHub account and create a new repository for the workshop + +Log into your GitHub account and fork the base Order Processing System repository into your account. + +1. Navigate to the [base Order Processing System repository](https://github.com/TaleLearnCode/OrderProcessingSystem-Base). +1. Click the **Fork** button in the upper right corner of the page. + +![Screenshot of the base Order Processing System repository with the Fork button highlighted.](images/01-Configuration/01A01-ForkBaseRepo.png) + +1. Select your GitHub account as the location to fork the repository and specify the name of the new repository + +![Screenshot of the Create a new fork GitHub page.](images/01-Configuration/01A02-CreateAFork.png) + +1. Once the fork is complete, you will be taken to your forked repository. +1. Click the **Settings** tab. +1. CLick the **Branches** menu item. +1. Click the **Add branch protection rule** button. +1. Enter **main** for the **Branch name pattern**. +1. Check the **Require a pull request before merging** checkbox. +1. Ensure the **Require approvals** checkbox is checked. +1. Create the **Create** button. + +### Clone GitHub Repo (01B) +1. Click the **Code** tab. +1. CLick the **Code** button. +1. Click the copy button in order to copy the HTTPS URL to the clipboard. +1. Open Visual Studio +1. From the **Start** page, select **Clone a repository**. +1. Past the URL copied in step 3 into the **Repository location** field. +1. Click the **Clone** button. + +### Create a resource group (01C) A resource group is a container that holds related resources for an Azure solution. The resource group can include all the resources for the solution, or only those resources that you want to manage as a group. You decide how you want to allocate resources to resource groups based on what makes the most sense for your organization. Generally, add resources that share the same lifecycle to the same resource group so you can easily deploy, update, and delete them as a group. The resource group stores metadata about the resources. Therefore, when you specify a location for the resource group, you are specifying where that metadata is stored. For compliance reasons, you may need to ensure that your data is store in a particular reason. @@ -51,7 +94,7 @@ For this workshop, everything you create will be done so in the same resource gr ![Screenshot of Resource group created notification.](images/01-Configuration/01A07-Notification.png) -## Create an Azure Key Vault vault (01B) +### Create an Azure Key Vault vault (01D) Centralized storage of application secrets in Azure Key Vault allows you to control their distribution. Key Vault greatly reduces the changes that secrets may be accidently leaked. When application developers use Key Vault, they no longer need to store security information in their application. Not having to store security information in applications eliminates the need to make this information part of the code. For example, an application may need to connect to a databae. Instead of storing the connection string in the application's code, you can store it securely in Key Vault. 1. From the resource group resource listing page, click the **+ Create** button @@ -80,7 +123,7 @@ Centralized storage of application secrets in Azure Key Vault allows you to cont ![Screenshot of the Review + create screen.](images/01-Configuration/01B04-ReviewAndCreate.png) -## Create an App Configuration store (01C) +### Create an App Configuration store (01E) Azure App Configuration is an Azure service designed to help you centrally manage your app settings and feature flags. In this step, you will create an App Configuration store to be used for the workshop. @@ -106,8 +149,20 @@ Azure App Configuration is an Azure service designed to help you centrally manag 4. Click the **Review + create** button. 5. Click the **Create** button. The deployment might take a few minutes. +1. Click the **Go to resource** button. +1. Click the **Access control (IAM)** menu option. +1. Click the **+ Add role assignment** button. +1. Select the **App Configuration Data Reader** role. +1. Click the **Next** button. +1. CLick the **+ Select members** link. +1. Select your account. +1. Click the **Select** button. -## Create Cosmos DB account (01D) +![Screenshot of the Add role assignment page.](images/01-Configuration/01F06-SelectMembers.png) + +14. Click the **Review + assign** button. + +### Create Cosmos DB account (01F) Azure Cosmos DB is Microsoft's globally distributed multi-model database service. You can use Azure Cosmos DB to quickly create and query key/value databses, document databses, and graph databases. This approach benefits from the global distribution and horizontal scale capabilities at the core of Azure Cosmos DB. 1. From the Azure portal menu or the **Home page**, select **+ Create a resource**. @@ -152,8 +207,8 @@ It takes a few minutes to create the account. Wait for the portal page to displ ![Screenshot of Cosmos DB Keys page.](images/01-Configuration/01D08-Keys.png) -## Add Cosmos DB Primary Key to Key Vault (01E) -1. Search for the Azure Key Vault you created in step 01B. +### Add Cosmos DB Primary Key to Key Vault (01G) +1. Search for the Azure Key Vault you created in step 01C. ![Screenshot of Cosmos Search.](images/01-Configuration/01E01-PortalSearch.png) @@ -168,6 +223,7 @@ It takes a few minutes to create the account. Wait for the portal page to displ 4. Enter the following values: | Setting | Value | +|---------|-------| | Name | A unique name for the secret you are creating. | | Secret Value | The primary key you copy from the Azure Cosmos DB page. | @@ -186,14 +242,14 @@ It takes a few minutes to create the account. Wait for the portal page to displ ![Screenshot of Key Vault secret version page.](images/01-Configuration/01E07-KeyVaultSecretVersion.png) -## Initialize the GitHub Repo for the Workshop (O1F) -1. Log into your GitHub account and create a new repository for the workshop -2. From within your workshop repository, click on the **Settings** tab -3. Click on the **Secrets and variables** menu item and then the **Actions** menu item +### Initialize the GitHub Repo for the Workshop (O1H) +1. Return to your *Order Processing System* GitHub repository +1. From within your workshop repository, click on the **Settings** tab +1. Click on the **Secrets and variables** menu item and then the **Actions** menu item ![](images/01-Configuration/01F01-GitHubActions.png) -4. Click on the **New repository secret** button +1. Click on the **New repository secret** button ![](images/01-Configuration/01F02-ActionSecrets.png) @@ -251,19 +307,23 @@ jobs: "Cosmos": { "Uri": "{CosmosDBUri}" }, - "ProductManagement": { + "Product": { "Cosmos": { "DatabaseId": "products", "Metadata": { "ContainerId": "metadata", "PartitionKey": "/metadataType" }, - "ProductsByAvailability": { - "ContainerId": "productsByAvailability", + "Merchandise": { + "ContainerId": "merchandise", + "PartitionKey": "/id" + }, + "MerchandiseByAvailability": { + "ContainerId": "merchandiseByAvailability", "PartitionKey": "/availabilityId" }, - "ProductsByTheme": { - "ContainerId": "prorductsByTheme", + "MerchandiseByTheme": { + "ContainerId": "merchandiseByTheme", "PartitionKey": "/themeId" } } @@ -271,7 +331,7 @@ jobs: } ~~~ -13. Replace *{CosmosDBUri}* with the URI value copied in step 01D +13. Replace *{CosmosDBUri}* with the URI value copied in step 01E 14. Click the **Commit changes** button 15. Add a file named *config/secretreferences.json* 16. Paste the following into the *config/secretreferences.json* file @@ -284,7 +344,7 @@ jobs: } ~~~ -17. Replace *[SecretIdentifier]* with the secret identifier noted from step 01E without the version reference +17. Replace *[SecretIdentifier]* with the secret identifier noted from step 01F without the version reference ![](images/01-Configuration/01F05-SecretReferences.png) diff --git a/Workshop/GuidedWalkthough/02-InitializeDatabases.md b/Workshop/GuidedWalkthough/02-InitializeDatabases.md new file mode 100644 index 0000000..0a4dd50 --- /dev/null +++ b/Workshop/GuidedWalkthough/02-InitializeDatabases.md @@ -0,0 +1,245 @@ +#02 - Initialize Databases + +## User Story +As the architect, I want to initialize the databases so that I can use them in the workshop. + +### Definition of Done +- [ ] The Core database has been created and the schema and data have been initialized +- [ ] The Inventory database has been created and the schema and data have been initialized +- [ ] The Notice database has been created and the schema and data have been initialized +- [ ] The Purchase database has been created and the schema and data have been initialized +- [ ] The Shipping database has been created and the schema and data have been initialized +- [ ] The database secrets have been added to Key Vault +- [ ] The App Config settings have been updated to use the Key Vault secrets + +--- + +## Workshop Exercises + +**Tasks** +- 02A - [Create the Core Database](#create-the-core-database-02a) +- 02B - [Create the Inventory Database](#create-the-inventory-database-02b) +- 02C - [Create the Notice Database](#create-the-notice-database-02c) +- 02D - [Create the Purchase Database](#create-the-purchase-database-02d) +- 02E - [Create the Shipping Database](#create-the-shipping-database-02e) +- 02F - [Initialize the Core Database Schema and Data](#create-the-core-database-schema-02f) +- 02G - [Initialize the Inventory Database Schema and Data](#create-the-inventory-database-schema-02f) +- 02H - [Initialize the Notice Database Schema and Data](#create-the-notice-database-schema-02h) +- 02I - [Initialize the Purchase Database Schema and Data](#create-the-purchase-database-schema-02i) +- 02J - [Add database secrets to Key Vault](#add-database-secrets-to-key-vault-02j) +- 02K - [Add Key Vault reference to the App Config settings](#add-key-vault-reference-to-app-config-settings-02k) +- 02L - [Add Azure SQL settings to App Config settings](#add-azure-sql-settings-to-app-config-settings-02L) + +### Create the Core Database (02A) +1. Navigate to the [Azure Portal](https://portal.azure.com) +1. Click the **Create a resource** button in the top left corner of the portal +1. Search for **SQL Database** and click the **Create** button +1. Fill out the form with the following values: + - **Subscription**: Select the appropriate Azure subscription + - **Resource Group**: Select the resource group you created in the previous section + - **Database name**: Enter a name for your database + - **Server**: Click **Create new** and enter a name for your server + - **Authentication method**: Select **Use SQL authentication** + - **Server admin login**: Enter a username for your server + - **Password**: Enter a password for your server + - **Location**: Select the location you used for your resource group + - **Want to use SQL elastic pool?**: Select **No** + - **Workload environment**: Select **Development** +1. Click the **Review + create** button +1. CLick the **Create** button +1. After the database has been created, click on the **Go to Resource** button +1. Under **Configure access**, click the **Configure** button +1. From the **Public access** tab, ensure that **Selected networks** is selected +1. CLick the **Add your client IPv4 address(xxx.xxx.xxx.xxx)** button +1. Ensure that **Allow Azure services and resources to access this server** is selected +1. Click the **Save** button + +![Screenshot of the Azure SQL Networking page](/images/02-InitializeDatabases/02A01-Networking.png) + +### Create the Inventory Database (02B) +1. Click the **Overview** button on the left side of the portal +1. Click the **Create database** button +1. Fill out the form with the following values: + - **Database name**: Enter a name for your database + - **Want to use SQL elastic pool?**: Select **No** + - **Workload environment**: Select **Development** +1. Click the **Review + create** button +1. CLick the **Create** button +1. Click the **Go to resource** button +1. Click the **Server name** link + +![Screenshot of the Azure SQL Server page](/images/02-InitializeDatabases/02B01-ServerName.png) + +### Create the Notice Database (02C) +1. Click the **Create database** button +1. Fill out the form with the following values: + - **Database name**: Enter a name for your database + - **Want to use SQL elastic pool?**: Select **No** + - **Workload environment**: Select **Development** +1. Click the **Review + create** button +1. CLick the **Create** button +1. Click the **Go to resource** button +1. Click the **Server name** link + +### Create the Purchase Database (02D) +1. Click the **Create database** button +1. Fill out the form with the following values: + - **Database name**: Enter a name for your database + - **Want to use SQL elastic pool?**: Select **No** + - **Workload environment**: Select **Development** +1. Click the **Review + create** button +1. CLick the **Create** button +1. Click the **Go to resource** button +1. Click the **Server name** link + +### Create the Shipping Database (02E) +1. Click the **Create database** button +1. Fill out the form with the following values: + - **Database name**: Enter a name for your database + - **Want to use SQL elastic pool?**: Select **No** + - **Workload environment**: Select **Development** +1. Click the **Review + create** button +1. CLick the **Create** button +1. Click the **Go to resource** button +1. Click the **Server name** link + +### Initialize the Core Database Schema and Data (02F) +1. From the database listing, click on the *Core* database that you created +1. Hover over the **Server name** link and click the **Copy** button +1. From Visual Studio, right click on the *Core.Database* project and select **Publish** +1. The **Publish Database** window will appear, click the **Edit** button + +![Screenshot of the Publish Database window](images/02-InitializeDatabases/02F01-PublishDatabase.png) + +1. Click the **Browse** tab +1. Paste the server name into the **Server name** field +1. Change **Authentication** to **SQL Server Authentication** +1. Enter the appropriate **User Name** and **Password** for the server +1. Ensure that **Save Password** is checked +1. Select the **Core** database from the **Database name** dropdown +1. Set **Trust Server Certificate** to **True** +1. Click the **OK** button + +![Screenshot of the Publish Database window](images/02-InitializeDatabases/02F02-Connect.png) + +1. CLick the **Publish** button + +### Initialize the Inventory Database Schema and Data (02G) +1. From Visual Studio, right click on the *Inventory.Database* project and select **Publish** +1. The **Publish Database** window will appear, click the **Edit** button +1. From the **History** tab, click on the **Core** database you just used +1. Click **Show Connection Properties** +1. Select the **Inventory** database from the **Database name** dropdown +1. Set **Trust Server Certificate** to **True** +1. Click the **OK** button + +![Screenshot of the Publish Database window](images/02-InitializeDatabases/02G01-Connect.png) + +1. CLick the **Publish** button + +### Initialize the Notice Database Schema and Data (02H) +1. From Visual Studio, right click on the *Notice.Database* project and select **Publish** +1. The **Publish Database** window will appear, click the **Edit** button +1. From the **History** tab, click on the **Inventory** database you just used +1. Click **Show Connection Properties** +1. Select the **Notice** database from the **Database name** dropdown +1. Set **Trust Server Certificate** to **True** +1. Click the **OK** button +1. CLick the **Publish** button + +### Initialize the Purchase Database Schema and Data (02I) +1. From Visual Studio, right click on the *Purchase.Database* project and select **Publish** +1. The **Publish Database** window will appear, click the **Edit** button +1. From the **History** tab, click on the **Notice** database you just used +1. Click **Show Connection Properties** +1. Select the **Purchase** database from the **Database name** dropdown +1. Set **Trust Server Certificate** to **True** +1. Click the **OK** button +1. CLick the **Publish** button + +### Initialize the Shipping Database Schema and Data (02I) +1. From Visual Studio, right click on the *Shipping.Database* project and select **Publish** +1. The **Publish Database** window will appear, click the **Edit** button +1. From the **History** tab, click on the **Shipping** database you just used +1. Click **Show Connection Properties** +1. Select the **Purchase** database from the **Database name** dropdown +1. Set **Trust Server Certificate** to **True** +1. Click the **OK** button +1. CLick the **Publish** button + +### Add database secrets to Key Vault (02J) +1. Navigate to the [Azure Portal](https://portal.azure.com) +1. Open the previously created Key Vault account +1. Click the **Secrets** option on the left-hand menu +1. CLick the **+ Generate/Import** button +1. Enter the following information: + +| Field | Value | +|-------|-------| +| Name | AzureSQLUserId | +| Secret value | Identifier of the Azure SQL user you created in step 2A | + +6. Click the **Create** button +1. CLick the **+ Generate/Import** button +1. Enter the following information: + +| Field | Value | +|-------|-------| +| Name | AzureSqlPassword | +| Secret value | Password for the Azure SQL user you created in step 2A | + +9. Click the **Create** button + +### Add Key Vault reference to the App Config settings (02K) +1. Navigate to the GitHub repository you created for the workshop +1. Edit the **OrderProcessingSystem/config/secretreferences.json** file +1. Add the AzureSql:UserId and AzureSql:Password elements + +~~~ + "AzureSql":{ + "UserId": "{\"uri\":\"https://{KEY_VAULT_ENDPOINT}/secrets/AzureSqlUserId\"}", + "Password": "{\"uri\":\"https://{KEY_VAULT_ENDPOINT}/secrets/AzureSqlPassword\"}" + } +~~~ + +4. Click the **Commit changes...** button + +### Add Azure SQL settings to App Config settings (02L) +1. Edit the **OrderProcessingSystem/config/appsettings.json** file +1. Add the AzureSql:DataSource, Core:AzureSql:Catalog, Inventory:AzureSql:Catalog, Notice:AzureSql:Catalog, Purchase:AzureSql:Catalog, and Shipping:AzureSql:Catalog elements + +~~~ +"AzureSql": { + "DataSource": "sql-ops-walkthrough.database.windows.net" +}, +"Core": { + "AzureSql": { + "Catalog": "sqldb-core-walkthrough-use" + } +}, +"Inventory": { + "AzureSql": { + "Catalog": "sqldb-inventory-walkthrough-use" + } +}, +"Notice": { + "AzureSql": { + "Catalog": "sqldb-notice-walkthrough-use" + } +}, +"Purchase": { + "AzureSql": { + "Catalog": "sqldb-purchase-walkthrough-use" + } +}, +"Shipping": { + "AzureSql": { + "Catalog": "sqldb-shipping-walkthrough-use" + } +} +~~~ + +3. Click the **Commit changes...** button +1. Click on the **Actions** tab and validate that the **AppConfig** workflow completed successfully twice + +![Screenshot of GitHubs Actions being completed](images/02-InitializeDatabases/02L-ActionsCompleted.png) \ No newline at end of file diff --git a/Workshop/GuidedWalkthough/03-InitializeProductDatabase.md b/Workshop/GuidedWalkthough/03-InitializeProductDatabase.md new file mode 100644 index 0000000..4264dd3 --- /dev/null +++ b/Workshop/GuidedWalkthough/03-InitializeProductDatabase.md @@ -0,0 +1,308 @@ +#03 - Initialize Product Database + +## User Story +As a product manager, I want to be able to manage the products that are available for purchase. + +### Definition of Done +- The product database is created +- The product database data is loaded +- The product database change feed triggers are created + +--- + +The product database is hosted on Cosmos DB. During this step of the workshop, we will initialize that database's data. + +**Tasks** +- 03A - [Create the Products database](#create-the-products-database-03a) +- 03B - [Create the database containers](#create-the-database-containts-03b) +- 03C - [Create Cosmos DB Change Feed Triggers](#create-cosmos-db-change-feed-triggers-03c) +- 03D - [Run the load product database process](#run-the-load-product-database-process-03d) + +At the completion of the tasks, you should commit the changes to your GitHub repository. + +### Create the Products database (03A) +1. From the [Azure Portal](https://portal.azure.com), click the Cosmos DB account you previously created + +![Screenshot of the Azure Portal](images/03-InitializeProductDatabase/03A01-AzurePortal.png) + +2. Click the **Data Explorer** link in the left-hand menu +1. From the **New Container** dropdown button, select **New Database** +1. Enter **products** as the **Database id** and click **OK** + +### Create the database containers (03B) +**Merchandise Container** +1. Hover over the **products** database and click the **New Container** button +1. Enter **merchandise** as the **Container id** +1. Enter **/id** as the **Partition key** +1. Click the **OK** button + +![Screenshot of the New Container dialog](images/03-InitializeProductDatabase/03B05-CreateMerchandiseContainer.png) + +**Merchandise by Availability Container** +1. Hover over the **products** database and click the **New Container** button +1. Enter **merchandiseByAvailability** as the **Container id** +1. Enter **/availabilityId** as the **Partition key** +1. Click the **OK** button + +**Merchandise by Theme Container** +1. Hover over the **products** database and click the **New Container** button +1. Enter **merchandiseByTheme** as the **Container id** +1. Enter **/themeId** as the **Partition key** +1. Click the **OK** button + +**Metadata Container** +1. Hover over the **products** database and click the **New Container** button +1. Enter **metadata** as the **Container id** +1. Enter **/metadataType** as the **Partition key** +1. Click the **OK** button + +After creating your containers, you Data Explorer should look like this: + +![Screenshot of the Data Explorer](images/03-InitializeProductDatabase/03B06-DataExplorer.png) + +### Create Cosmos DB Change Feed Triggers (03C) +1. From Visual Studio, right click the Product solution folder and select **Add -> New Project** +1. Search for **Azure Functions**, select the **Azure Functions** template, and click the **Next** button + +![Screenshot of the Add a new project dialog](images/03-InitializeProductDatabase/03B01-AddANewProject.png) + +3. Name the project **Product.Functions** +1. Set the **Location** to the **Product** solution folder +1. Click the **Next** button + +![Screenshot of the Configure your new project dialog](images/03-InitializeProductDatabase/03B02-ConfigureYourNewProject.png) + +6. Select the *.NET 7.0 Isolated* **Functions worker** +7. Select the *Cosmos DB Trigger* **Function** +8. Click the **Create** button + +![Screenshot of the Additional information dialog](images/03-InitializeProductDatabase/03B03-AdditionalInformation.png) + +9. Delete the **Function1.cs** file from the **Product.Functions** project +10. Right click the **Product.Functions** project and select **Add Project Reference** +111. Select the **Product.Services** project and click the **OK** button + +![Screenshot of the Additional information dialog](images/03-InitializeProductDatabase/03B04-ReferenceManager.png) + +12. Open the **Program.cs** file in the **Product.Functions** project +13. Replace the existing code with the following code + +``` +using BuildingBricks.Core; +using BuildingBricks.Product.Services; +using Microsoft.Azure.Cosmos; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; + +string environment = Environment.GetEnvironmentVariable("AZURE_FUNCTIONS_ENVIRONMENT")!; +string appConfigEndpoint = Environment.GetEnvironmentVariable("AppConfigEndpoint")!; +ConfigServices configServices = new(appConfigEndpoint, environment); + +using CosmosClient cosmosClient = new(configServices.CosmosUri, configServices.CosmosKey); +Database database = cosmosClient.GetDatabase(configServices.ProductCosmosDatabaseId); + +MerchandiseServices merchandiseServices = new(database.GetContainer(configServices.ProductMerchandiseContainerId)); +MerchandiseByAvailabilityServices merchandiseByAvailabilityServices = new(configServices, database.GetContainer(configServices.ProductsByAvailabilityContainerId)); +MerchandiseByThemeServices merchandiseByThemeServices = new(configServices, database.GetContainer(configServices.ProductsByThemeContainerId)); +AvailabilityServices availabilityServices = new(configServices, database.GetContainer(configServices.ProductMetadataContainerId)); + +IHost host = new HostBuilder() + .ConfigureFunctionsWorkerDefaults() + .ConfigureServices(s => + { + s.AddSingleton((s) => { return configServices; }); + s.AddSingleton((s) => { return merchandiseServices; }); + s.AddSingleton((s) => { return merchandiseByAvailabilityServices; }); + s.AddSingleton((s) => { return merchandiseByThemeServices; }); + s.AddSingleton((s) => { return availabilityServices; }); + }) + .Build(); + +host.Run(); +``` + +14. Right click the **Product.Functions** project and **Add > New Folder** +15. Name the new folder **Functions** +16. Right click the **Functions** folder and **Add > Class** +17. Name the new class **MetadataChangeMonitor.cs** +18. Replace the existing code with the following code + +``` +using BuildingBricks.Product.Models; +using BuildingBricks.Product.Services; +using Microsoft.Azure.Functions.Worker; +using Microsoft.Extensions.Logging; + +namespace BuildingBricks.Product.Functions; + +public class MetadataChangeMonitor +{ + + private readonly ILogger _logger; + private readonly MerchandiseServices _merchandiseServices; + + public MetadataChangeMonitor( + ILoggerFactory loggerFactory, + MerchandiseServices merchandiseServices) + { + _logger = loggerFactory.CreateLogger(); + _merchandiseServices = merchandiseServices; + } + + [Function("MetadataChangeMonitor")] + public async Task RunAsync([CosmosDBTrigger( + databaseName: "products", + containerName: "metadata", + Connection = "ConnectionString", + LeaseContainerName = "metadataLeases", + CreateLeaseContainerIfNotExists = true)] IReadOnlyList changedMetadata) + { + if (changedMetadata != null && changedMetadata.Count > 0) + { + foreach (IMetadata changedMetadataItem in changedMetadata) + { + if (changedMetadataItem.MetadataType == Constants.MetadataType.Availability) + { + Availability changedAvailability = (Availability)changedMetadataItem; + _logger.LogInformation("Updated Availability: {availabilityId} - {availabilityName}", changedAvailability.Id, changedAvailability.Name); + List merchandiseToUpdate = await _merchandiseServices.GetListByAvailabilityAsync(changedAvailability.Id); + foreach (Merchandise merchandise in merchandiseToUpdate) + { + if (merchandise.Availability != changedAvailability.Name) + { + _logger.LogInformation("Updated Merchandise (Availability): {productId} - {productName}", merchandise.Id, merchandise.Name); + merchandise.Availability = changedAvailability.Name; + await _merchandiseServices.UpsertAsync(merchandise); + } + } + } + else if (changedMetadataItem.MetadataType == Constants.MetadataType.Theme) + { + Theme changedTheme = (Theme)changedMetadataItem; + _logger.LogInformation("Updated Theme: {themeId} - {themeName}", changedTheme.Id, changedTheme.Name); + foreach (ThemeMerchandise themeMerchandise in changedTheme.Merchandises) + { + Merchandise merchandise = await _merchandiseServices.GetAsync(themeMerchandise.ItemNumber); + if (merchandise.ThemeName != changedTheme.Name) + { + merchandise.ThemeName = changedTheme.Name; + await _merchandiseServices.UpsertAsync(merchandise); + _logger.LogInformation("Updated Merchandise (Theme): {productId} - {productName}", merchandise.Id, merchandise.Name); + } + } + } + else + { + _logger.LogError("MetadataType {metadataType} is not supported.", changedMetadataItem.MetadataType); + } + } + } + } + +} +``` + +19. Right click the **Product.Functions** project and **Add > New Folder** +20. Name the new folder **Functions** +21. Right click the **Functions** folder and **Add > Class** +22. Name the new class **MerchandiseChangeMonitor.cs** +23. Replace the existing code with the following code + +``` +using BuildingBricks.Product.Models; +using BuildingBricks.Product.Services; +using Microsoft.Azure.Functions.Worker; +using Microsoft.Extensions.Logging; + +namespace BuildingBricks.Product.Functions; + +public class MerchandiseChangeMonitor +{ + + private readonly ILogger _logger; + private readonly MerchandiseByAvailabilityServices _merchandiseByAvailabilityServices; + private readonly MerchandiseByThemeServices _merchandiseByThemeServices; + + public MerchandiseChangeMonitor( + ILoggerFactory loggerFactory, + MerchandiseByAvailabilityServices merchandiseByAvailabilityServices, + MerchandiseByThemeServices merchandiseByThemeServices) + { + _logger = loggerFactory.CreateLogger(); + _merchandiseByAvailabilityServices = merchandiseByAvailabilityServices; + _merchandiseByThemeServices = merchandiseByThemeServices; + } + + [Function("MerchandiseChangeMonitor")] + public async Task RunAsync([CosmosDBTrigger( + databaseName: "products", + containerName: "merchandise", + Connection = "ConnectionString", + LeaseContainerName = "merchandiseLeases", + CreateLeaseContainerIfNotExists = true)] IReadOnlyList changedMerchandises) + { + if (changedMerchandises != null && changedMerchandises.Count > 0) + { + foreach (Merchandise changedMerchandise in changedMerchandises) + { + if (changedMerchandise.TTL > 0) + { + _logger.LogInformation("Deleted Merchandise: {productId} - {productName}", changedMerchandise.Id, changedMerchandise.Name); + await _merchandiseByAvailabilityServices.DeleteAsync(changedMerchandise); + await _merchandiseByThemeServices.DeleteAsync(changedMerchandise); + } + else + { + _logger.LogInformation("Updated Merchandise: {productId} - {productName}", changedMerchandise.Id, changedMerchandise.Name); + await _merchandiseByAvailabilityServices.UpsertAsync(changedMerchandise); + await _merchandiseByThemeServices.UpsertAsync(changedMerchandise); + } + } + } + } + +} +``` + +24. Open the local.settings.json file in the **Product.Functions** project +25. Add the following settings to the **Values** section + +* AppConfigEndpoint - To the App Configuration endpoint you created in the previous step +* ConnectionString - To the Cosmos DB connection string you created in the previous step + +``` +{ + "IsEncrypted": false, + "Values": { + "FUNCTIONS_WORKER_RUNTIME": "dotnet-isolated", + "AppConfigEndpoint": "", + "ConnectionString": "" + } +} +``` + +### Run the load product database process (03D) +1. Open the **Program.cs** file in the **Product.LoadDatabase** project +1. On line 12, replace **appConfigEndpoint** with the App Configuration endpoint you created in the previous step +1. Right-click on the **OrderProcessingSystem** solution and select **Configure Startup Projects** +1. Select **Multiple startup projects** +1. Set the following projects to **Start**: + * Product.Functions + * Product.LoadDatabase +1. Click the OK button + +![Screenshot of the Configure Startup Projects dialog](images/03-InitializeProductDatabase/03C01-ConfigureStartupProjects.png) + +7. Press **F5** to run the solution +1. Wait for the **Product.Functions ** project console window to display "Worker process started and initialized." + +![Screenshot of the Product.Functions console window](images/03-InitializeProductDatabase/03C02-AzureFunctionsCLI.png) + +9. Enter the path to the data directory (for example, D:\Repos\OrderProcessingSystem\data\products) +1. Allow the process to run until it completes + +![Screenshot of the Product.LoadDatabase console window](images/03-InitializeProductDatabase/03C03-DatabaseLoadComplete.png) +*Load.Database Console Window* + +![Screenshot of the Product.Functions console window](images/03-InitializeProductDatabase/03C04-AzureFunctionComplete.png) +*Load.Functions Console Window* \ No newline at end of file diff --git a/Workshop/GuidedWalkthough/04-PlaceOrder.md b/Workshop/GuidedWalkthough/04-PlaceOrder.md new file mode 100644 index 0000000..b3a3075 --- /dev/null +++ b/Workshop/GuidedWalkthough/04-PlaceOrder.md @@ -0,0 +1,554 @@ +# 04 - Place Order (Purchase) + +## User Story +The Purchase system receives the purchase order details from the e-commerce website, saves the data, and initiates the order processing workflow. + +### Definition of Done +- The customer order is saved to the Purchase database +- The Order Placed message is sent to the appropriate Service Bus queue +- The Order Placed message is sent to the appropriate Event Hub + +--- + +## Workshop Exercises + +**Tasks** +- 04A - [Create the Service Bus namespace](#create-the-service-bus-namespace-04a) +- 04B - [Create the Place Order Service Bus queue](#create-the-service-bus-queue-04b) +- 04C - [Add a shared access policy for Purchase to access the Place Order Service Bus queue](#add-a-shared-access-policy-for-purchase-to-access-the-place-order-service-bus-queue-04c) +- 04D - [Create the Event Hubs namespace](#create-the-event-hubs-namespace-04d) +- 04E - [Create the Place Order Event Hub](#create-the-place-order-event-hub-04e) +- 04F - [Add a shared access policy for Purchase to access the Place Order Event Hub](#add-a-shared-access-policy-for-purchase-to-access-the-place-order-event-hub-04f) +- 04G - [Add service logic for user story](#add-service-logic-for-user-story-04g) +- 04H - [Create restful endpoint to initiate the Place Order user story](#create-restful-endpoint-to-initiate-place-order-user-story-04h) +- 04I - [Test the Place Order User Story](#test-the-place-order-user-story-4i) + +### Create the Service Bus namespace (04A) +1. Navigate to the [Azure Portal](https://portal.azure.com/) +1. Click the **Create a resource** button +1. Search for **Service Bus** +1. Click the **Create** button on the *Service Bus* service offering +1. Enter the following information: + +- **Subscription**: The subscription you have been using for the workshop +- **Resource Group**: The resource group you have been using for the workshop +- **Namespace Name**: A globally unique name to identify the *Service Bus* namespace +- **Location**: East US +- **Pricing tier:** Standard + +![Screenshot of the Service Bus Create Namespace page](images/04-PlaceOrder/04A-CreateNamespace.png) + +6. Click the **Review + create** button +1. Click the **Create** button + +### Create the Place Order Service Bus queue (04B) +1. Once the *Service Bus* namespace created above is completed, click on the **Go to resource** button +1. Click on the **+ Queue** button +1. In the **Create queue** dialog, enter the following information: + +- **Name**: The name of the place order queue +- **Enable sessions**: Checked + +4. Click the **Create** button + +### Add a shared access policy for Purchase to access the Place Order Service Bus queue (04C) +1. Click **Queues** option on the left-hand menu +1. Click on the queue you just created +1. Click on the **Shared access policies** option on the left-hand menu +1. Click the **+ Add** button +1. In the **Add SAS Policy** blade, provide the enter the following: + +- **Policy name**: Purchase +- **Manage**: Unchecked +- **Send**: Checked +- **Listen**: Unchecked + +![Screenshot of the Add SAS Policy](images/04-PlaceOrder/04C-AddSASPolicy.png) + +6. Click the **Create** button + +**Add the connection string to Key Vault** +1. Click on the policy you just created +1. Copy the **Primary Connection String** +1. Navigate to the Key Vault account you created for the workshop +1. Click on the **Secrets** option on the left-hand menu +1. Click the **+ Generate/Import** button +1. Enter *ServiceBus-OrderPlaced-Purchase* in the **Name** field +1. Paste the copied primary key into the **Secret value** removing the **EntityPath** portion of the key string. + +For example, change: +~~~ +Endpoint=sb://sbns-orderprocessingsystem-walkthrough-use.servicebus.windows.net/;SharedAccessKeyName=Purchase;SharedAccessKey=xxxxxxxxxx;EntityPath=sbq-placeorder-walkthrough-use +~~~ + +To: +~~~ +Endpoint=sb://sbns-orderprocessingsystem-walkthrough-use.servicebus.windows.net/;SharedAccessKeyName=Purchase;SharedAccessKey=xxxxxxxxxx +~~~ + +8. Click the create button + +**Add the Key Vault reference to the App Config service** +1. Navigate to your GitHub repository for the workshop +1. Open the **OrderProcessingSystem/config/servicereferences.json** file +1. Add the Purchase:ServiceBus:PlaceOrder:ConnectionString entry + +~~~ + "Purchase": { + "SeviceBus": { + "PlaceOrder": { + "ConnectionString": "{\"uri\":\"{YOUR-KEYVALUT-ENDPOINT}/secrets/ServiceBus-OrderPlaced-Purchase\"}" + } + } + } +~~~ + +4. Click the **Commit Changes** button. + +**Add the Service Bus Queue name configuration value** +1. Open the **OrderProcessingSystem/config/appsettings.json** file +1. Add the Purchase:ServiceBus:PlaceOrder:QueueName entry + +~~~ + "Purchase": { + "ServiceBus": { + "PlaceOrder": { + "QueueName": "{QUEUE-NAME}" + } + } + } +~~~ + +3. Click the **Commit Changes** button + +**Verify App Config Action ran correctly** + +1. Click on the **Actions** tab +1. Ensure that the two association runs ran successfully + +![Screenshot showing that the Azure App Config workflows successfully completed](images/04-PlaceOrder/04C-ActionsCompleted.png) + +### Create the Event Hubs namespace (04D) +1. Navigate to the [Azure Portal](https://portal.azure.com) +1. Click the **+ Add Resource** button +1. Search for **Event Hubs** +1. Click on the **Event Hubs** service offering +1. Click on the **Create** button +1. Enter the following information: + +| Field | Value| +|-------|------| +| Subscription | The subscription you have been using for the workshop | +| Resource group | The resource group you have been using for the workshop | +| Namespace name | A globally unique name for the Event Hub namespace | +| Pricing tier | Standard | +| Throughput Units | 1 | +| Enable Auto-Inflate | Unchecked | + +![Screenshot of the Create Namespace page](images/04-PlaceOrder/04D-CreateNamespace.png) + +7. Click the **Review + create** button +1. Click the **Create** button + +### Create the Place Order Event Hub (04E) +1. Once the Event Hubs namespaces created above is completed, click the **Go to resource** button +1. Click the **+ Event Hub** button +1. Enter the following information: + +| Field | Value | +|-------|-------| +| Name | The name of the Order Placed event hub | +| Partition count | 1 | +| Cleanup policy | Delete | +| Retention time (hrs) | 1 | + +![Screenshot of the Create Event Hub page](images/04-PlaceOrder/04E-CreateEventHub.png) + +4. Click the **Review + create** button +1. Click the **Create** button + +### Add a shared access policy for Purchase to access the Place Order Event Hub (04F) +1. From the **Event Hubs** listing, click on the event hub you just created. +1. Click on the **Shared access policies** option from the left-hand menu +1. Click the **Add** button +1. In the **Add SAS Policy** blade, enter the following: + +| Field | Value | +|-------|-------| +| Policy name | Purchase | +| Manage | Unchecked | +| Send | Check | +| Listen | Unchecked | + +4. Click the **Create** button + +**Copy the SAS Policy Connection String** +1. Click on the policy you just created +1. Copy the **Connection string-primary key** + +**Add the connection string to Key Vault** +1. Navigate to the Key Vault you created for the workshop +1. Click on the **Secrets** option in the left-hand menu +1. Click the **+ Generate/Import** button +1. Enter the following information + +| Field | Value | +|--------------|-------------------------------| +| Name | EventHub-OrderPlaced-Purchase | +| Secret value | The copied key | + +5. Click the **Create** button + +**Add the Key Vault reference to Azure App Config** +1. Navigate to the GitHub repository you created for the workshop +1. Edit the **OrderProcessingSystem/config/secretreferences.json** file +1. Add the EventHubs:PlaceOrder:ConnectionString element + +~~~ + "Purchase": { + "SeviceBus": { + "PlaceOrder": { + "ConnectionString": "{\"uri\":\"https://{KEYVAULT-ENDPOINT-URL}/secrets/ServiceBus-OrderPlaced-Purchase\"}" + } + }, + "EventHubs": { + "PlaceOrder": { + "ConnectionString": "{\"uri\":\"https://{KEYVAULT-ENDPOINT-URL}/secrets/EventHub-OrderPlaced-Purchase\"}" + } + } + } +~~~ + +4. Click the **Commit changes...** button +1. Verify that the AppConfig GitHub Action completed successfully + +### Add service logic for user story (04G) +1. From Visual Studio, right click on the **Purchase.Services** and select **Add > Class** +1. Name the new class **PurchaseServices.cs** +1. Replace the existing code with the following: + +~~~ +namespace BuildingBricks.Purchase; + +public class PurchaseServices : ServicesBase +{ + + public PurchaseServices(ConfigServices configServices) : base(configServices) { } + +} +~~~ + +**Add logic to create the CustomerPurchase database record** +Add the following private methods to the PurchaseServices class: + +~~~ + +using BuildingBricks.Purchase.Models; +using BuildingBricks.Purchase.Requests; + +private async Task CreatePurchaseRecordAsync(PlaceOrderRequest placeOrderRequest) +{ + string purchaseId = Guid.NewGuid().ToString(); + using PurchaseContext purchaseContext = new(_configServices); + CustomerPurchase customerPurchase = new() + { + CustomerPurchaseId = purchaseId, + CustomerId = placeOrderRequest.CustomerId, + PurchaseLineItems = BuildPurchaseItemsList(placeOrderRequest, purchaseId) + }; + await purchaseContext.CustomerPurchases.AddAsync(customerPurchase); + await purchaseContext.SaveChangesAsync(); + return customerPurchase; +} + +private static List BuildPurchaseItemsList(PlaceOrderRequest placeOrderRequest, string purchaseId) +{ + List purchaseLineItems = new(); + foreach (PlaceOrderItem item in placeOrderRequest.Items) + { + purchaseLineItems.Add(new PurchaseLineItem + { + CustomerPurchaseId = purchaseId, + ProductId = item.ProductId, + Quantity = item.Quantity + }); + } + return purchaseLineItems; +} + +~~~ + +**Add logic to build the Event Hub/Service Bus message** +Add the following BuildOrderPlacedMessage method to the PurchaseServices class: + +~~~ +private static OrderPlacedMessage BuildOrderPlacedMessage(CustomerPurchase customerPurchase) +{ + + OrderPlacedMessage orderPlacedMessage = new() + { + PurchaseId = customerPurchase.CustomerPurchaseId, + CustomerId = customerPurchase.CustomerId, + Items = new List() + }; + + foreach (PurchaseLineItem? purchaseLineItem in customerPurchase.PurchaseLineItems) + if (purchaseLineItem is not null) + orderPlacedMessage.Items.Add(new() + { + CustomerId = customerPurchase.CustomerId, + PurchaseId = customerPurchase.CustomerPurchaseId, + PurchaseItemId = purchaseLineItem.PurchaseLineItemId, + ProductId = purchaseLineItem.ProductId, + Quantity = purchaseLineItem.Quantity + }); + + return orderPlacedMessage; + +} +~~~ + +**Add logic to Serialize the Product List** +Add the following BuildSerializedProductPurchaseMessageList method to the PurchaseServices class: + +~~~ +private static List BuildSerializedProductPurchaseMessageList(OrderPlacedMessage orderPlacedMessage) +{ + List response = new(); + foreach (ProductPurchasedMessage? productPurchased in orderPlacedMessage.Items) + if (productPurchased is not null) + response.Add(JsonSerializer.Serialize(productPurchased)); + return response; +} +~~~ + +**Add the method to coordinate the place order activities** +Add the following PlaceOrderAsync method to the PurchaseServices class: + +~~~ +public async Task PlaceOrderAsync(PlaceOrderRequest placeOrderRequest) +{ + + // Create the purchase database record + CustomerPurchase customerPurchase = await CreatePurchaseRecordAsync(placeOrderRequest); + + // Send the order placed message to the event hub + OrderPlacedMessage orderPlacedMessage = BuildOrderPlacedMessage(customerPurchase); + await SendMessageToEventHubAsync( + _configServices.PurchasePlaceOrderEventHubConnectionString, + JsonSerializer.Serialize(orderPlacedMessage)); + + // Send the product purchased message to the service bus + await SendSessionMessageBatchToServiceBusAsync( + _configServices.PurchaseServiceBusPlaceOrderConnectionString, + _configServices.PurchaseServiceBusPlaceOrderServiceBusQueueName, + customerPurchase.CustomerPurchaseId, + BuildSerializedProductPurchaseMessageList(orderPlacedMessage)); + + return customerPurchase.CustomerPurchaseId; + +} +~~~ + +### Create restful endpoint to initiate the Place Order user story (4H) +1. For Visual Studio, righ-click on the **Purchase** solution folder and select the **Add > New Project** option. +1. Select the **Azure Functions* project template +1. From the **Configure your new project** dialog, enter the following values: + +| Field | Value | +|-------|-------| +| Project name | Purchase.Functions | +| Location | The Purchase subfolder in your solution directory | + +![Screenshot of the Configure your new project dialog](images/04-PlaceOrder/04G-ConfigureYourNewProject.png) + +4. From the **Additional information** dialog, enter the following values: + +| Field | Value | +|------------------|-------------------| +| Functions worker | .NET 7.0 Isolated | +| Function | Http trigger | + +![Screenshot of the Configure your new project dialog](images/04-PlaceOrder/04G-AdditionalInformation.png) + +5. Click the **Create** button +1. Delete the generated **Function1.cs** file +1. Add project references to the Core.Azure.Functions and Purchase.Services projects. + +![Screenshot of the Configure your new project dialog](images/04-PlaceOrder/04G-ReferenceManager.png) + +8. Open the **Program.cs** file in the **Purchase.Functions** project and replace the code with the following: + +~~~ +using BuildingBricks.Core; +using BuildingBricks.Purchase; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using System.Text.Json; +using System.Text.Json.Serialization; + +string environment = Environment.GetEnvironmentVariable("AZURE_FUNCTIONS_ENVIRONMENT")!; +string appConfigEndpoint = Environment.GetEnvironmentVariable("AppConfigEndpoint")!; +ConfigServices configServices = new ConfigServices(appConfigEndpoint, environment); + +PurchaseServices purchaseServices = new(configServices); + +var host = new HostBuilder() + .ConfigureFunctionsWorkerDefaults() + .ConfigureServices(s => + { + s.AddSingleton((s) => { return purchaseServices; }); + s.AddSingleton((s) => + { + return new JsonSerializerOptions + { + PropertyNamingPolicy = JsonNamingPolicy.CamelCase, + DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull, + DictionaryKeyPolicy = JsonNamingPolicy.CamelCase, + WriteIndented = true + }; + }); + }) + .Build(); + +host.Run(); +~~~ + +9. Open the **local.settings.json** file in the **Purchase.Functions** project and add the AppConfigEndpoint setting: + +~~~ +{ + "IsEncrypted": false, + "Values": { + "AzureWebJobsStorage": "UseDevelopmentStorage=true", + "FUNCTIONS_WORKER_RUNTIME": "dotnet-isolated", + "AppConfigEndpoint": "{APP_CONFIG_ENDPOINT}" + } +} +~~~ + +10. Open the **host.json** file in the **Purchase.Functions** project and add the routePrefix extension setting: + +~~~ +{ + "version": "2.0", + "logging": { + "applicationInsights": { + "samplingSettings": { + "isEnabled": true, + "excludedTypes": "Request" + }, + "enableLiveMetricsFilters": true + } + }, + "extensions": { + "http": { + "routePrefix": "" + } + } +} +~~~ + +11. Right-click on the **Purchase.Functions** project and select **Add > New Folder**; name the folder **Functions** +1. Right-click on the **Functions** folder and select **Add > Class** +1. Name the new class **PlaceOrder.cs** +1. Replace the auto-generated code with the following: + +~~~ +using BuildingBricks.Purchase.Requests; +using Microsoft.Azure.Functions.Worker; +using Microsoft.Azure.Functions.Worker.Http; +using Microsoft.Extensions.Logging; +using System.Text.Json; +using TaleLearnCode; + +namespace BuildingBricks.Purchase.Functions; + +public class PlaceOrder +{ + + private readonly ILogger _logger; + private readonly JsonSerializerOptions _jsonSerializerOptions; + private readonly PurchaseServices _purchaseServices; + + public PlaceOrder( + ILoggerFactory loggerFactory, + JsonSerializerOptions jsonSerializerOptions, + PurchaseServices purchaseServices1) + { + _logger = loggerFactory.CreateLogger(); + _jsonSerializerOptions = jsonSerializerOptions; + _purchaseServices = purchaseServices1; + } + + [Function("PlaceOrder")] + public async Task RunAsync( + [HttpTrigger(AuthorizationLevel.Function, "post", Route = "purchases")] HttpRequestData request) + { + try + { + PlaceOrderRequest placeOrderRequest = await request.GetRequestParametersAsync(_jsonSerializerOptions); + string orderId = await _purchaseServices.PlaceOrderAsync(placeOrderRequest); + return request.CreateCreatedResponse($"purchases/{orderId}"); + } + catch (Exception ex) when (ex is ArgumentNullException) + { + return request.CreateBadRequestResponse(ex); + } + catch (Exception ex) + { + _logger.LogError("Unexpected exception: {ExceptionMessage}", ex.Message); + return request.CreateErrorResponse(ex); + } + } + +} +~~~ + +15. Right-click the **OrderProcessingSystem** solution and select **Configure Startup Projects** +1. Update the projects being started, to Purchase.Functions and Product.Functions. + +![Screenshot of the Configure Startup Projects dialog](images/04-PlaceOrder/04G-ConfigureStartupProjects.png) + +### Test the Place Order User Story (4I) +1. Press **F5** to run the solution +1. Copy the **PlaceOrder** endpoint + +![Screenshot of the Purchase.Functions Azure Functions CLI](images/04-PlaceOrder/04H-AzureFunctionCLI.png) + +3. Open Postman and create a new request +1. Change the HTTP verb to **Post** +1. Paste the **PlaceOrder** endpoint URL +1. Click the **Body** tab +1. Select **raw** and **JSON** +1. Enter the JSON below: + +~~~ +{ + "customerId": 1, + "items": + [ + { + "productId": "10255", + "quantity": 1 + } + ] +} +~~~ + +![Screenshot of Postman](images/04-PlaceOrder/04H-PostmanSetup.png) + +9. Click the **Send** button + +### Validate that the messages were created (4I) +View the *Purchase.Functions* console window and validate that there is a *Executed 'Functions.PlaceOrder'* message + +![Screenshot of the Purchase.Functions console window](images/04-PlaceOrder/4I-PurchaseFunctionsConsole.png) + +From the **Azure Portal**, navigate to the Event Hub and validate that there is message traffic. + +![Screenshot of the Event Hub metrics](images/04-PlaceOrder/4I-EventHubMetrics.png) + +From the **Azure Portal**, navigate to the Service Bus Queue, click the **Service Bus Explorer**, and then click the **Peek from start** button. You should see a message for each successful run of the Place Order user story. + +![Screenshot of the Service Bus Explorer](images/04-PlaceOrder/04I-SeviceBusExplorer.png) \ No newline at end of file diff --git a/Workshop/GuidedWalkthough/05-SendOrderConfirmation.md b/Workshop/GuidedWalkthough/05-SendOrderConfirmation.md new file mode 100644 index 0000000..3522428 --- /dev/null +++ b/Workshop/GuidedWalkthough/05-SendOrderConfirmation.md @@ -0,0 +1,379 @@ +# 05 - Send Order Confirmation (Notice) + +## User Story +After receiving a notification from the Purchase system that a purchase has been made, the Notice system will send the customer an email confirming the order. The email confirmation shall be logged for compliance reasons. + +### Definition of Done +- The Notice system shall send an email confirmation to the customer. +- The Notice system shall log the email confirmation for compliance reasons. + +--- + +## Workshop Exercises + +**Tasks** +- 05A - [Create a consumer group for Notice on the Place Order event hub](#create-a-consumer-group-for-notice-on-the-place-order-event-hub-05a) +- 05B - [Add a shared access policy for Notice to access the Place Order event hub](#add-a-shared-access-policy-for-notice-to-access-the-place-order-event-hub-05a) +- 05C - [Create the Email Communication Service resource](#create-the-email-communication-service-resource-05b) +- 05D - [Provision email domain](#provision-email-domain-05c) +- 05E - [Create a Communication Service Resource](#create-a-communication-service-resource-05d) +- 05F - [Connect the email domain to the Communication Service resource](#connect-the-email-domain-to-the-communication-service-resource-05e) +- 05G - [Add Sender Email Address to the App Config](#add-sender-email-address-to-the-app-config-05f) +- 05H - [Add Communication Service Connection String to Key Vault](#add-communication-service-connection-string-to-key-vault-05g) +- 05I - [Add service logic for user story](#add-service-logic-for-user-story-05h) +- 05J - [Create an Azure Function to trigger the email confirmation to be sent](#create-an-azure-function-to-trigger-the-email-confirmation-to-be-sent-05i) +- 05K - [Test the Send Order Confirmation User Story](test-the-send-order-confirmation-user-story-05j) + +### Create a consumer group for Notice on the Place Order event hub (05A) +1. From the [Azure Portal](https://portal.azure.com), navigate to the Event Hub namespace you created for the workshop. +1. From the **Event Hubs** listing, click on the **Order Placed** event hub. +1. Click on the **Consumer groups** option under **Entities** from the left-hand navigation pane. +1. Click on the **+ Consumer group** button. +1. Enter 'notice' in the **Name** field and click the **Create** button. +1. + +### Add a shared access policy for Notice to access the Place Order event hub (05B) +1. Navigate to the **OrderPlaced** Event Hub +1. Click on the **Shared access policies** option from the left-hand menu +1. Click the **Add** button +1. In the **Add SAS Policy** blade, enter the following: + +| Field | Value | +|-------------|-----------| +| Policy name | Notice | +| Manage | Unchecked | +| Send | Unchecked | +| Listen | Checked | + +4. Click the **Create** button + +**Copy the SAS Policy Connection String** +1. Click on the policy you just created +1. Copy the **Connection string-primary key** + +### Create the Email Communication Service resource (05C) +1. Navigate to the [Azure portal](https://portal.azure.com) to create a new resource. +1. Search for Email Communications Service and hit enter. Select **Email Communication Services** and press **Create**. + +![Screenshot of searching for Email Communication Services](images/05-SendOrderConfirmation/email-communication-search.png) +![Screenshot of Email Communication Services search result](images/05-SendOrderConfirmation/email-communication-create.png) + +3. Complete the required information on the basics tab: + +- Select the Azure subscription you have been using for the workshop. +- Select the resource group you created for the workshop. +- Select **United States** as the data location. + +4. Click the **Review + create** button + +![Screenshot of the Create resource page](images/05-SendOrderConfirmation/email-communication-create-review.png) + +5. Wait for the validation to pass. Click **Create**. +1. Wait for the Deployment to complete. Click **Go to Resource** to navigate to the Communication Service Overview Page. + +### Provision email domain (5D) +1. From the *Email Communication Service* page, click on the **Provision domains** option from the left-hand menu +1. Click on the **1-click add** button + +![Screenshot of the Provision domains blade](images/05-SendOrderConfirmation/provision-domains.png) + +After a minute or two, the email domain will be created. + +![Screenshot of the email domain being provisioned](images/05-SendOrderConfirmation/domain-provisioned.png) + +### Create a Communication Service Resource (05E) +1. Navigate to the [Azure portal](https://portal.azure.com) to create a new resource. +1. Search for Communication Services and hit enter. Select **Communication Services** and press **Create**. +1. Complete the required information + +- Select the Azure subscription you have been using for the workshop. +- Select the resource group you created for the workshop. +- End the name of the resource + +4. Click the **Review + Create** button + +![Screenshot of the Create Resource page](images/05-SendOrderConfirmation/create-comunication-service.png) + +5. Wait for the validation to pass. Click **Create**. +1. Wait for the Deployment to complete. Click **Go to resource** to navigate to the Communication Service page. + +### Connect the email domain to the Communication Service resource (05F) +1. In the Azure Communication Service Resource overview page, click the **Domains** on the left navigation panel under Email +2. Click the **Connect domain** button +3. Select the email domain create above + +![Screenshot of the Connect email domains](images/05-SendOrderConfirmation/connect-demail-domains.png) + +4. Click the **Connect** button. +5. CLick the **Try Email** on the left navigation panel under Email +1. Enter the required information and click the **Send** button +1. Validate the email is received + +### Add Sender Email Address to the App Config (05G) +1. From the **Try Email** screen, copy the *from* email address + +![Screenshot of the Try Email screen with the from email address highlighted](images/05-SendOrderConfirmation/acs-try-email.png) + +2. Navigate to the GitHub repository you created for the workshop. +1. Open the **config/appsettings.json** file in edit mode. +1. Add the Notice:SenderAddress element. + +~~~ +"Notice": { + "AzureSql": { + "Catalog": "{NOTICE_CATALOG_NAME}" + }, + "SenderAddress": "{SENDER_EMAIL_ADDRESS}" +} +~~~ + +5. Click the **Commit changes...** button. +1. Validate that the AppConfig workflow completed successfully. + +### Add Communication Service Connection String to Key Vault (05H) +1. Click on the **Keys** option under **Settings** on the left-hand navigation panel. +1. Click the **Copy** button on the **Primary key - Connection string**. + +![Screenshot of the ACS Keys page with the copy button highlighted](images/05-SendOrderConfirmation/acs-keys.png) + +3. Navigate to the Key Vault you created for the workshop. +1. Click the **+ Generate/Import** button. +1. Enter the following information: + +| Field | Value | +|--------------|---------------------------------------------------------------------------| +| Name | ACSConnectionString | +| Secret Value | The Azure Communication Services connection string you previously copied. | + +6. Click the **Create** button. +1. Navigate to the GitHub repository you create for the project. +1. Open the config/SecretReferences.json file for edit +1. Add the AzureCommunicationServices:ConnectionString element + +~~~ +"AzureCommunicationServices": { + "ConnectionString": "{\"uri\":\"https://{KEY_VAULT_ENDPOINT}/secrets/ACSConnectionString\"}" +} +~~~ + +10. Click the **Commit changes...** button. +1. Validate that the **AppConfig** workflow completed successfully. + +### Add service logic for user story (05I) +1. Add the **Azure.Communication.Email** NuGet package to the **Notice.Services** project +1. Right click on the **Notice.Services** and select **Add > Class** +1. Name the new class **NoticeServices.cs** +1. Replace the existing code with the following: + +~~~ +namespace BuildingBricks.Notice; + +public class NoticeServices : ServicesBase +{ + + public NoticeServices(ConfigServices configServices) : base(configServices) { } + +} +~~~ + +**Add generic logic to send emails** +Add the following private methods to the NoticeServices class: + +~~~ +using Azure; +using Azure.Communication.Email; +using BuildingBricks.Notice.Models; + +private async Task SendEmailAsync( + int customerId, + int noticeTypeId, + string subject, + string htmlContent, + string plainTextContent) +{ + using NoticeContext noticeContext = new(_configServices); + Customer? customer = await noticeContext.Customers.FindAsync(customerId); + if (customer is not null) + { + await noticeContext.NoticeLogs.AddAsync(new() + { + NoticeLogId = await SendEmailAsync(subject, htmlContent, plainTextContent, customer.EmailAddress), + NoticeTypeId = noticeTypeId, + CustomerId = customer.CustomerId, + NoticeBody = htmlContent + }); + await noticeContext.SaveChangesAsync(); + } +} + +private async Task SendEmailAsync( + string subject, + string htmlContent, + string plainTextContent, + string recipientAddress) +{ + EmailClient emailClient = new(_configServices.AzureCommunicationServicesConnectionString); + EmailSendOperation emailSendOperation = await emailClient.SendAsync( + WaitUntil.Completed, + _configServices.NoticeSenderAddress, + recipientAddress, + subject, + htmlContent, + plainTextContent); + return emailSendOperation.Id; +} +~~~ + +**Add logic to send the order confirmation email +Add the following SendOrderConfirmationAsync method to the PurchaseServices class: + +~~~ +public async Task SendOrderConfirmationAsync(OrderPlacedMessage orderPlacedMessage) +{ + string subject = $"Order Confirmation - {orderPlacedMessage.PurchaseId}"; + string htmlContent = $"

Thank you for your order. Your order number is {orderPlacedMessage.PurchaseId}.

"; + string plainTextContent = $"Thank you for your order. Your order number is {orderPlacedMessage.PurchaseId}."; + await SendEmailAsync(orderPlacedMessage.CustomerId, NoticeTypes.OrderConfirmation, subject, htmlContent, plainTextContent); +} +~~~ + +### Create an Azure Function to trigger the email confirmation to be sent (5J) +1. For Visual Studio, right-click on the **Purchase** solution folder and select the **Add > New Project** option. +1. Select the **Azure Functions* project template +1. From the **Configure your new project** dialog, enter the following values: + +| Field | Value | +|--------------|-------| +| Project name | Notice.Functions | +| Location | The Notice subfolder in your solution directory | + +![Screenshot of the Configure your new project dialog](images/05-SendOrderConfirmation/configure-your-new-project.png) + +4. From the **Additional information** dialog, enter the following values: + +| Field | Value | +|------------------|-------------------| +| Functions worker | .NET 7.0 Isolated | +| Function | Event Hub trigger | + +![Screenshot of the Configure your new project dialog](images/05-SendOrderConfirmation/additional-information.png) + +5. Click the **Create** button +1. Delete the generated **Function1.cs** file +1. Add project references to the Core.Azure.Functions and Notice.Services projects. +1. Open the **Program.cs** file in the **Notice.Functions** project and replace the code with the following: + +~~~ +using BuildingBricks.Core; +using BuildingBricks.Notice; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; + +string environment = Environment.GetEnvironmentVariable("AZURE_FUNCTIONS_ENVIRONMENT")!; +string appConfigEndpoint = Environment.GetEnvironmentVariable("AppConfigEndpoint")!; +ConfigServices configServices = new ConfigServices(appConfigEndpoint, environment); + +NoticeServices noticeServices = new(configServices); + +var host = new HostBuilder() + .ConfigureFunctionsWorkerDefaults() + .ConfigureServices(s => + { + s.AddSingleton((s) => { return noticeServices; }); + }) + .Build(); + +host.Run(); +~~~ + +9. Open the **local.settings.json** file in the **Notice.Functions** project and add the AppConfigEndpoint setting: + +~~~ +{ + "IsEncrypted": false, + "Values": { + "AzureWebJobsStorage": "UseDevelopmentStorage=true", + "FUNCTIONS_WORKER_RUNTIME": "dotnet-isolated", + "AppConfigEndpoint": "{APP_CONFIG_ENDPOINT}", + "PlaceOrderConnectionString": "{EVENT_HUB_CONNECTION_STRING}", + "PlaceOrderEventHub": "{EVENT_HUB_NAME}", + "PlaceOrderConsumerGroup": "notice, + } +} +~~~ + +10. Right-click on the **Notice.Functions** project and select **Add > New Folder**; name the folder **Functions** +1. Right-click on the **Functions** folder and select **Add > Class** +1. Name the new class **PlaceOrderMonitor.cs** +1. Replace the auto-generated code with the following: + +~~~ +using Azure.Messaging.EventHubs; +using BuildingBricks.EventMessages; +using Microsoft.Azure.Functions.Worker; +using Microsoft.Extensions.Logging; +using System.Text.Json; + +namespace BuildingBricks.Notice.Functions; + +public class PlaceOrderMonitor +{ + + private readonly ILogger _logger; + private readonly NoticeServices _noticeServices; + + public PlaceOrderMonitor( + ILoggerFactory loggerFactory, + NoticeServices noticeServices) + { + _logger = loggerFactory.CreateLogger(); + _noticeServices = noticeServices; + } + + [Function("Notice-PlaceOrderMonitor")] + public async Task RunAsync([EventHubTrigger("%PlaceOrderEventHub%", Connection = "PlaceOrderConnectionString", ConsumerGroup = "%PlaceOrderConsumerGroup%")] EventData[] eventMessages) + { + foreach (EventData eventMessage in eventMessages) + { + OrderPlacedMessage? orderPlacedMessage = JsonSerializer.Deserialize(eventMessage.EventBody); + if (orderPlacedMessage is not null) + { + _logger.LogInformation("Sending Confirmation Email for Purchase {PurchaseId}", orderPlacedMessage.PurchaseId); + await _noticeServices.SendOrderConfirmationAsync(orderPlacedMessage); + } + } + } + +} +~~~ + +15. Right-click the **OrderProcessingSystem** solution and select **Configure Startup Projects** +1. Add Notice.Functions to the list of projects to be started + +![Screenshot of the Configure Startup Projects dialog](images/05-SendOrderConfirmation/configure-startup-projects.png) + +### Test the Send Order Confirmation User Story (5K) +1. Open Postman and create a new request +1. Change the HTTP verb to **Post** +1. Paste the **PlaceOrder** endpoint URL +1. Click the **Body** tab +1. Select **raw** and **JSON** +1. Enter the JSON below: + +~~~ +{ + "customerId": 1, + "items": + [ + { + "productId": "10255", + "quantity": 1 + } + ] +} +~~~ + +![Screenshot of Postman](images/04-PlaceOrder/04H-PostmanSetup.png) + +7. Click the **Send** button +8. Validate that the confirmation email is sent \ No newline at end of file diff --git a/Workshop/GuidedWalkthough/06-InitializeShipment.md b/Workshop/GuidedWalkthough/06-InitializeShipment.md new file mode 100644 index 0000000..a5bf8fc --- /dev/null +++ b/Workshop/GuidedWalkthough/06-InitializeShipment.md @@ -0,0 +1,199 @@ +# 06 - Initialize Shipment (Shipping) + +## User Story +When an order is placed, the Shipping services initializes the shipping record so the shipping department can forecast upcoming work. + +### Definition of Done +- A new shipment record is created in the Shipping database + +--- + +## Workshop Exercises + +**Tasks** +- 06A - [Add consumer group for Shipping on the Order Placed event hub](add-consumer-group-for-shipping-on-the-order-placed-event-hub-06a) +- 06B - [Add a shared access policy for Shipping to access the Order Placed event hub](#add-a-shared-access-policy-for-shipping-to-access-the-order-placed-event-hub-06b) +- 06C - [Add service logic for user story](#add-service-logic-for-user-story-06c) +- 06D - [Create an Azure Function to watch for orders being placed](create-an-azure-function-to-watch-for-orders-being-placed-06d) +- 06E - [Test the Initialize Shipment User Story](test-the-initialize-shipment-user-story-06e) + +### Add consumer group for Shipping on the Order Placed event hub (06A) +1. From the [Azure Portal](https://azure.portal.com), navigate to the Event Hub namespace created from the workshop. +1. Click on the **Order Placed** event hub from the **Event Hubs** listing. +1. Click on the **Consumer groups** option under **Entities** on the left-hand navigation panel +1. Click the **+ Consumer group** button. +1. Enter *shipping* in the **Name** field. +1. Click the **Create** button. + +### Add a shared access policy for Shipping to access the Order Placed event hub (06B) +1. Click on the **Shared access policies** option under **Settings** on the left-hand navigation panel. +1. Click the **+ Add** button +1. In the **Add SAS Policy** blade, provide the enter the following: + +- **Policy name**: Shipping +- **Manage**: Unchecked +- **Send**: Unchecked +- **Listen**: Checked + +7. Click the **Create** button +1. Click on the policy you just created +1. Copy the **Connection string-primary key** + +### Add service logic for user story (06C) +1. From Visual Studio, open the **ShippingServices.cs** file. +1. Add the **CreateCustomerPurchaseAsync** method to the ShippingServices class. + +~~~ +private static async Task CreateCustomerPurchaseAsync(OrderPlacedMessage orderPlacedMessage, ShippingContext shippingContext) +{ + + CustomerPurchase customerPurchase = new() + { + CustomerPurchaseId = orderPlacedMessage.PurchaseId, + CustomerId = orderPlacedMessage.CustomerId + }; + await shippingContext.SaveChangesAsync(); + + foreach (ProductPurchasedMessage item in orderPlacedMessage.Items) + customerPurchase.OrderItems.Add(new() + { + OrderItemId = item.PurchaseItemId, + CustomerOrderId = item.PurchaseId, + ProductId = item.ProductId, + Quantity = item.Quantity + }); + await shippingContext.SaveChangesAsync(); + + return customerPurchase; + +} +~~~ + +3. Add the **CreateShipmentAsync** method to the ShippingServices class. + +~~~ +private static async Task CreateShipmentAsync(CustomerPurchase customerPurchase, ShippingContext shippingContext) +{ + customerPurchase.Shipments.Add(new() + { + ShipmentStatusId = ShipmentStatuses.Inventory + }); + await shippingContext.SaveChangesAsync(); + return customerPurchase.Shipments.First().ShipmentId; +} +~~~ + +4. Add the **InitializeShipmentAsync** method to the ShippingServices class. + +~~~ +public async Task InitializeShipmentAsync(OrderPlacedMessage orderPlacedMessage) +{ + using ShippingContext shippingContext = new(_configServices); + CustomerPurchase customerPurchase = await CreateCustomerPurchaseAsync(orderPlacedMessage, shippingContext); + return await CreateShipmentAsync(customerPurchase, shippingContext); +} +~~~ + +### Create an Azure Function to watch for orders being placed (06D) +1. From Visual Studio, right-click on the **Functions** folder within the **Shipping.Functions** folder and click the **Add > New Azure Function** +1. Enter **OrderPlacedMonitor.cs** for the name of the new Azure Function class. + +![Screenshot of the Add New Function dialog](images/06-InitializeShipment/add-new-item.png) + +3. Click the **Add** button +1. Select the **Event Hub trigger** and specify the following values: + +| Field | Value | +|--------------------------------|-----------------------------------| +| Connection string setting name | InventoryReservedConnectionString | +| Event Hub name | %InventorReservedEventHub% | + +5. Click the **Add** button. +1. Replace the auto-generated code with the following: + +~~~ +using Azure.Messaging.EventHubs; +using BuildingBricks.EventMessages; +using BuildingBricks.Shipping; +using Microsoft.Azure.Functions.Worker; +using Microsoft.Extensions.Logging; +using System.Text.Json; + +namespace Shipping.Functions; + +public class OrderPlacedMonitor +{ + + private readonly ILogger _logger; + private readonly ShippingServices _shippingServices; + + public OrderPlacedMonitor( + ILogger logger, + ShippingServices shippingServices) + { + _logger = logger; + _shippingServices = shippingServices; + } + + [Function(nameof(OrderPlacedMonitor))] + public async Task RunAsync([EventHubTrigger("%OrderPlacedEventHub%", Connection = "OrderPlacedConnectionString", ConsumerGroup = "%OrderPlacedConsumerGroup%")] EventData[] eventMessages) + { + foreach (EventData eventMessage in eventMessages) + { + OrderPlacedMessage? orderPlacedMessage = JsonSerializer.Deserialize(eventMessage.EventBody); + if (orderPlacedMessage is not null) + { + _logger.LogInformation("Initializing shipment for order #{OrderNumber}", orderPlacedMessage.PurchaseId); + await _shippingServices.InitializeShipmentAsync(orderPlacedMessage); + } + } + } + +} +~~~ + +7. Open the **local.settings.json** file within the **Purchase.Functions** project. +1. Add the InventoryReservedConnectionString, InventoryReservedEventHub, and InventoryReservedConsumerGroup values. + +~~~ +{ + "IsEncrypted": false, + "Values": { + "AzureWebJobsStorage": "UseDevelopmentStorage=true", + "FUNCTIONS_WORKER_RUNTIME": "dotnet-isolated", + "AppConfigEndpoint": "{APP_CONFIG_ENDPOINT}", + "OrderPlacedConnectionString": "{EVENT_HUB_CONNECTION_STRING}", + "OrderPlacedEventHub": "{EVENT_HUB_NAME}", + "OrderPlacedConsumerGroup": "shipping" + } +} +~~~ + +9. Right-click the **OrderProcessingSystem** solution and select **Configure Startup Projects** +1. Add Shipping.Functions to the list of projects to be started + +### Test the Initialize Shipment User Story (06E) +1. Open Postman and create a new request +1. Change the HTTP verb to **Post** +1. Paste the **PlaceOrder** endpoint URL +1. Click the **Body** tab +1. Select **raw** and **JSON** +1. Enter the JSON below: + +~~~ +{ + "customerId": 1, + "items": + [ + { + "productId": "10255", + "quantity": 1 + } + ] +} +~~~ + +![Screenshot of Postman](images/04-PlaceOrder/04H-PostmanSetup.png) + +7. Click the **Send** button +8. Validate that the appropriate Purchase.CustomerPurchase, Purchase.OrderItem, Shipping.Shipment records were created. \ No newline at end of file diff --git a/Workshop/GuidedWalkthough/07-ReserveInventory.md b/Workshop/GuidedWalkthough/07-ReserveInventory.md new file mode 100644 index 0000000..9da085c --- /dev/null +++ b/Workshop/GuidedWalkthough/07-ReserveInventory.md @@ -0,0 +1,331 @@ +# 07 - Reserve Inventory (Inventory) + +## User Story +After receiving a notification from the Purchase system that a purchase has been made, the Inventory system will reserved the purchase products and send a notification of such. + +### Definition of Done +- [ ] A new Inventory.InventoryTransaction record is created with the InventoryActionId of ReservedForOrder +- [ ] A new Inventory.InventoryReserved message is sent to the Inventory Reserved Event Hub +- [ ] The Inventory.InventoryReserved message contains the following information: + - [ ] CustomerId + - [ ] OrderId + - [ ] ProductId + - [ ] ProductName + - [ ] QuantityOnHand + - [ ] Backordered + +- An Inventory Transaction record is created to reserve the inventory for the order +- An Inventory Reserved message is sent to the Inventory Reserved Event Hub + +--- + +## Workshop Exercises + +**Tasks** +- 07A - [Add a shared access policy for Inventory to access the Place Order Service Bus queue](#add-a-shared-access-policy-for-inventory-to-access-the-place-order-service-bus-queue-07a) +- 07B - [Create the Inventory Reserved Event Hub](#create-the-inventory-reseved-event-hub-07b) +- 07C - [Add a shared access policy for Inventory to access the Inventory Reserved Event Hub](#add-a-shared-access-policy-for-inventory-to-access-the-inventory-reserved-event-hub-07c) +- 07D - [Add service logic for user story](#add-service-logic-for-user-story-07d) +- 07E - [Test the Reserve Inventory User Story](#test-the-reserve-inventory-user-story-07e) + +### Add a shared access policy for Inventory to access the Place Order Service Bus queue (07A) +1. From the [Azure Portal](https://portal.azure.com), click on the Service Bus account created for the workshop +1. Click **Queues** option on the left-hand menu +1. Click on the PlaceOrder queue +1. Click on the **Shared access policies** option on the left-hand menu +1. Click the **+ Add** button +1. In the **Add SAS Policy** blade, provide the enter the following: + +- **Policy name**: Inventory +- **Manage**: Unchecked +- **Send**: Unchecked +- **Listen**:Checked + +7. Click the **Create** button +1. Click on the policy you just created +1. Copy the **Connection string-primary key** + +### Create the Inventory Reserved Event Hub (07B) +1. From the [Azure Portal](https://portal.azure.com), navigate to the Event Hub Namespace you created for this workshop. +1. Click the **+ Event Hub** button +1. Enter the following information: + +| Field | Value | +|-------|-------| +| Name | The name of the Order Placed event hub | +| Partition count | 1 | +| Cleanup policy | Delete | +| Retention time (hrs) | 1 | + +![Screenshot of the Create Event Hub page](images/06-ReserveInventory/create-event-hub.png) + +4. Click the **Review + create** button +1. Click the **Create** button + +### Add a shared access policy for Inventory to access the Inventory Reserved Event Hub (07C) +1. From the **Event Hubs** listing, click on the **Inventory Reserved** event hub you just created. +1. Click on the **Shared access policies** option under **Settings* from the left-hand menu +1. Click the **Add** button +1. In the **Add SAS Policy** blade, enter the following: + +| Field | Value | +|-------------|-----------| +| Policy name | Inventory | +| Manage | Unchecked | +| Send | Checked | +| Listen | Unchecked | + +4. Click the **Create** button + +**Copy the SAS Policy Connection String** +1. Click on the policy you just created +1. Copy the **Connection string-primary key** + +**Add the connection string to Key Vault** +1. Navigate to the Key Vault you created for the workshop. +1. Click on the **Secrets** option in the left-hand menu +1. Click the **+ Generate/Import** button +1. Enter the following information + +| Field | Value | +|--------------|--------------------------------------| +| Name | EventHub-InventoryReserved-Inventory | +| Secret value | The copied key | + +5. Click the **Create** button + +**Add the Key Vault reference to Azure App Config** +1. Navigate to the GitHub repository you created for the workshop +1. Edit the **config/secretreferences.json** file +1. Add the Inventory:EventHubs:PlaceOrder:ConnectionString element + +~~~ +"Inventory": { + "EventHubs": { + "InventoryReserved": { + "ConnectionString": "{\"uri\":\"https://kv-opswalkthough.vault.azure.net/secrets/EventHub-InventoryReserved-Inventory\"}" + } + } +} +~~~ + +4. Click the **Commit changes...** button +1. Verify that the AppConfig GitHub Action completed successfully + +### Add service logic for user story (07D) +1. Right click on the **Inventory.Services** and select **Add > Class** +1. Name the new class **InventoryServices.cs** +1. Replace the existing code with the following: + +~~~ +namespace BuildingBricks.Inventory; + +public class InventoryServices : ServicesBase +{ + + public InventoryServices(ConfigServices configServices) : base(configServices) { } + +} +~~~ + +**Add logic to get the inventory status of a product** +Add the following private methods to the InventoryServices class: + +~~~ +private static async Task GetInventoryStatusAsync(string productId, InventoryContext inventoryContext) +{ + List inventoryTransactions = await inventoryContext.InventoryTransactions.Where(x => x.ProductId == productId).ToListAsync(); + if (inventoryTransactions.Any()) + return new InventoryUpdatedMessage() + { + ProductId = productId, + InventoryOnHand = inventoryTransactions.Sum(x => x.InventoryCredit) - inventoryTransactions.Sum(x => x.InventoryDebit), + InventoryReserved = inventoryTransactions.Sum(x => x.InventoryReserve), + InventoryAvailable = inventoryTransactions.Sum(x => x.InventoryCredit) - (inventoryTransactions.Sum(x => x.InventoryDebit) + inventoryTransactions.Sum(x => x.InventoryReserve)), + LastUpdate = inventoryTransactions.Max(x => x.ActionDateTime) + }; + else + return null; +} +~~~ + +**Add logic to perform tasks for user story** +Add the following ReserveItemForOrderAsync method to the InventoryServices class: + +~~~ +public async Task ReserveItemForOrderAsync(ProductPurchasedMessage productPurchasedMessage) +{ + + using InventoryContext inventoryContext = new(_configServices); + Product? product = await inventoryContext.Products.FirstOrDefaultAsync(x => x.ProductId == productPurchasedMessage.ProductId); + if (product is not null) + { + + // Update the inventory status + InventoryTransaction inventoryTransaction = new() + { + ProductId = product.ProductId, + InventoryActionId = InventoryActions.ReservedForOrder, + InventoryReserve = productPurchasedMessage.Quantity, + OrderNumber = productPurchasedMessage.PurchaseId + }; + await inventoryContext.InventoryTransactions.AddAsync(inventoryTransaction); + await inventoryContext.SaveChangesAsync(); + + // Get the inventory status + InventoryUpdatedMessage? inventoryStatusResponse = await GetInventoryStatusAsync(product.ProductId, inventoryContext); + + // Send the inventory reserved message + InventoryReservedMessage inventoryReservedMessage = new() + { + CustomerId = productPurchasedMessage.CustomerId, + OrderId = productPurchasedMessage.PurchaseId, + ProductId = productPurchasedMessage.ProductId, + ProductName = product.ProductName, + QuantityOnHand = inventoryStatusResponse?.InventoryOnHand - inventoryStatusResponse?.InventoryReserved ?? 0, + Backordered = inventoryStatusResponse is null || inventoryStatusResponse.InventoryAvailable < 1 + }; + + await SendMessageToEventHubAsync( + _configServices.InventoryEventHubsInventoryReservedConnectionString, + _configServices.InventoryEventHubsInventoryReservedEventHubName, + JsonSerializer.Serialize(inventoryReservedMessage)); + + } + +} +~~~ + +### Create an Azure Function to trigger the inventory to be reserved (7C) +1. From Visual Studio, right-click on the **Inventory** solution folder and select the **Add > New Project** option. +1. Select the **Azure Functions* project template +1. From the **Configure your new project** dialog, enter the following values: + +| Field | Value | +|--------------|----------------------------------------------------| +| Project name | Inventory.Functions | +| Location | The Inventory subfolder in your solution directory | + +![Screenshot of the Configure your new project dialog](images/06-ReserveInventory/configure-your-new-project.png) + +4. From the **Additional information** dialog, enter the following values: + +| Field | Value | +|------------------|---------------------------| +| Functions worker | .NET 7.0 Isolated | +| Function | Service Bus Queue trigger | + +![Screenshot of the Configure your new project dialog](images/06-ReserveInventory/additional-information.png) + +5. Click the **Create** button +1. Delete the generated **Function1.cs** file +1. Add project reference to the Inventory.Services project. +1. Open the **Program.cs** file in the **Inventory.Functions** project and replace the code with the following: + +~~~ +using BuildingBricks.Core; +using BuildingBricks.Inventory; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; + +string environment = Environment.GetEnvironmentVariable("AZURE_FUNCTIONS_ENVIRONMENT")!; +string appConfigEndpoint = Environment.GetEnvironmentVariable("AppConfigEndpoint")!; +ConfigServices configServices = new ConfigServices(appConfigEndpoint, environment); + +InventoryServices inventoryServices = new(configServices); + +IHost host = new HostBuilder() + .ConfigureFunctionsWorkerDefaults() + .ConfigureServices(s => + { + s.AddSingleton((s) => { return inventoryServices; }); + }) + .Build(); + +host.Run(); +~~~ + +9. Open the **local.settings.json** file in the **Inventory.Functions** project and add the AppConfigEndpoint setting: + +~~~ +{ + "IsEncrypted": false, + "Values": { + "AzureWebJobsStorage": "UseDevelopmentStorage=true", + "FUNCTIONS_WORKER_RUNTIME": "dotnet-isolated", + "AppConfigEndpoint": "{APP_CONFIG_ENDPOINT}", + "ServiceBusConnectionString": "{SERVICE_BUS_CONNECTION_STRING}", + "OrderPlacedQueue": "{ORDER_PLACED_QUEUE}" + } +} +~~~ + +10. Right-click on the **Inventory.Functions** project and select **Add > New Folder**; name the folder **Functions** +1. Right-click on the **Functions** folder and select **Add > Class** +1. Name the new class **PlaceOrderMonitor.cs** +1. Replace the auto-generated code with the following: + +~~~ +using Azure.Messaging.ServiceBus; +using BuildingBricks.Core.EventMessages; +using BuildingBricks.Inventory; +using Microsoft.Azure.Functions.Worker; +using Microsoft.Extensions.Logging; + +namespace Inventory.Functions; + +public class OrderPlacedMonitor +{ + + private readonly ILogger _logger; + private readonly InventoryServices _inventoryServices; + + public OrderPlacedMonitor( + ILogger logger, + InventoryServices inventoryServices) + { + _logger = logger; + _inventoryServices = inventoryServices; + } + + [Function("Inventory-OrderPlacedMonitor")] + public async Task RunAsync([ServiceBusTrigger("%OrderPlacedQueue%", Connection = "ServiceBusConnectionString", IsSessionsEnabled = true)] ServiceBusReceivedMessage message) + { + _logger.LogInformation("Message ID: {id}", message.MessageId); + ProductPurchasedMessage productPurchasedMessage = message.Body.ToObjectFromJson(); + await _inventoryServices.ReserveItemForOrderAsync(productPurchasedMessage); + } + +} +~~~ + +15. Right-click the **OrderProcessingSystem** solution and select **Configure Startup Projects** +1. Add Inventory.Functions to the list of projects to be started + +![Screenshot of the Configure Startup Projects dialog](images/06-ReserveInventory/![Screenshot of the Configure Startup Projects dialog](images/06-ReserveInventory/configure-startup-projects.png).png) + +### Test the Reserve Inventory User Story (7E) +1. Open Postman and create a new request +1. Change the HTTP verb to **Post** +1. Paste the **PlaceOrder** endpoint URL +1. Click the **Body** tab +1. Select **raw** and **JSON** +1. Enter the JSON below: + +~~~ +{ + "customerId": 1, + "items": + [ + { + "productId": "10255", + "quantity": 1 + } + ] +} +~~~ + +![Screenshot of Postman](images/04-PlaceOrder/04H-PostmanSetup.png) + +7. Click the **Send** button +8. Validate that the Inventory.InventoryTransaction record was created \ No newline at end of file diff --git a/Workshop/GuidedWalkthough/08-UpdatePurchaseStatusOnInventoryReserve.md b/Workshop/GuidedWalkthough/08-UpdatePurchaseStatusOnInventoryReserve.md new file mode 100644 index 0000000..6cb1a99 --- /dev/null +++ b/Workshop/GuidedWalkthough/08-UpdatePurchaseStatusOnInventoryReserve.md @@ -0,0 +1,176 @@ +# 08 - Update Purchase Status on Inventory Reserve (Inventory) + +## User Story +After receiving a notification from the Inventory system that the inventory has been reserved for an order, the Purchase system will update the status of the purchase line item. If all of the line items for the purchase have been updated, then the purchase status will be updated. + +### Definition of Done +- The appropriate purchase line item is updated with the reserved status +- If all of the purchase line items have been updated, then the purchase status is updated + +--- + +## Workshop Exercises + +**Tasks** +- 08A - [Add consumer group for Purchase on the Inventory Reserved Event Hub](#add-consumer-group-for-purchase-on-the-invnetory-reserved-event-hub-08a) +- 08B - [Add service logic for user story](#add-service-logic-for-user-story-08b) +- 08C - [Create an Azure Function to trigger the purchase update upon inventory being reserved](#create-an-azure-function-to-trigger-the-purchase-update-upon-inventory-being-reserved-08c +- 08D - [Test the Update Purchase Status on Inventory Reserve User Story](#test-the-update-purchase-status-on-inventory-reserve-user-story-08d) + +### Add consumer group for Purchase on the Inventory Reserved Event Hub (08A) +1. From the [Azure Portal](https://azure.portal.com), navigate to the Event Hub namespace created from the workshop. +1. Click on the **Inventory Reserved** event hub from the **Event Hubs** listing. +1. Click on the **Consumer groups** option under **Entities** on the left-hand navigation panel +1. Click the **+ Consumer group** button. +1. Enter *purchase* in the **Name** field. +1. Click the **Create** button. + +### Add a shared access policy for Purchase to access the Inventory Reserved event hub (08A) +1. Click on the **Shared access policies** option under **Settings** on the left-hand navigation panel. +1. Click the **+ Add** button +1. In the **Add SAS Policy** blade, provide the enter the following: + +- **Policy name**: Purchase +- **Manage**: Unchecked +- **Send**: Unchecked +- **Listen**:Checked + +7. Click the **Create** button +1. Click on the policy you just created +1. Copy the **Connection string-primary key** + +### Add service logic for user story (07B) +1. From Visual Studio, open the **PurchaseServices.cs** file. +1. Add the **UpdatePurchaseItemStatusAsync** method to the PurchaseServices class. + +~~~ + public async Task UpdatePurchaseItemStatusAsync( + string purchaseId, + string productId, + int purchaseStatusId) + { + + using PurchaseContext purchaseContext = new(_configServices); + CustomerPurchase? purchase = await purchaseContext.CustomerPurchases + .Include(x => x.PurchaseLineItems) + .FirstOrDefaultAsync(x => x.CustomerPurchaseId == purchaseId); + if (purchase is not null) + { + + PurchaseLineItem? purchaseLineItem = purchase.PurchaseLineItems.FirstOrDefault(x => x.ProductId == productId); + if (purchaseLineItem is not null) + { + purchaseLineItem.PurchaseStatusId = purchaseStatusId; + await purchaseContext.SaveChangesAsync(); + } + + if (purchase.PurchaseLineItems.FirstOrDefault(x => x.PurchaseStatusId != purchaseStatusId) is null && purchase.PurchaseStatusId != purchaseStatusId) + { + purchase.PurchaseStatusId = purchaseStatusId; + await purchaseContext.SaveChangesAsync(); + } + } + + } +~~~ + +### Create an Azure Function to trigger the purchase update upon inventory being reserved (8C) +1. From Visual Studio, right-click on the **Functions** folder within the **Purchase.Functions** folder and click the **Add > New Azure Function** +1. Enter **InventoryReservedMonitor.cs** for the name of the new Azure Function class. + +![Screenshot of the Add New Function dialog](images/07-UpdatePurchaseStatusOnInventoryReserve/add-new-item.png) + +3. Click the **Add** button +1. Select the **Event Hub trigger** and specify the following values: + +| Field | Value | +|--------------------------------|-----------------------------------| +| Connection string setting name | InventoryReservedConnectionString | +| Event Hub name | %InventorReservedEventHub% | + +5. Click the **Add** button. +1. Replace the auto-generated code with the following: + +~~~ +using Azure.Messaging.EventHubs; +using BuildingBricks.EventMessages; +using BuildingBricks.Purchase.Constants; +using Microsoft.Azure.Functions.Worker; +using Microsoft.Extensions.Logging; +using System.Text.Json; + +namespace BuildingBricks.Purchase.Functions; + +public class InventoryReservedMonitor +{ + + private readonly ILogger _logger; + private readonly PurchaseServices _purchaseServices; + + public InventoryReservedMonitor( + ILogger logger, + PurchaseServices purchaseServices) + { + _logger = logger; + _purchaseServices = purchaseServices; + } + + [Function(nameof(InventoryReservedMonitor))] + public async Task RunAsync([EventHubTrigger("%InventoryReservedEventHub%", Connection = "InventoryReservedConnectionString", ConsumerGroup = "%InventoryReservedConsumerGroup%")] EventData[] eventMessages) + { + foreach (EventData eventMessage in eventMessages) + { + InventoryReservedMessage? inventoryReservedMessage = JsonSerializer.Deserialize(eventMessage.EventBody); + if (inventoryReservedMessage is not null) + { + _logger.LogInformation("Inventory Reserved Event Received"); + await _purchaseServices.UpdatePurchaseItemStatusAsync(inventoryReservedMessage.OrderId, inventoryReservedMessage.ProductId, PurchaseStatuses.Reserved); + } + } + } + +} +~~~ + +7. Open the **local.settings.json** file within the **Purchase.Functions** project. +1. Add the InventoryReservedConnectionString, InventoryReservedEventHub, and InventoryReservedConsumerGroup values. + +~~~ +{ + "IsEncrypted": false, + "Values": { + "AzureWebJobsStorage": "UseDevelopmentStorage=true", + "FUNCTIONS_WORKER_RUNTIME": "dotnet-isolated", + "AppConfigEndpoint": "{APP_CONFIG_ENDPOINT}", + "InventoryReservedConnectionString": "{INVENTORY_RESERVED_EVENT_HUB_CONNECTION_STRING}", + "InventoryReservedEventHub": "{INVENTORY_RESERVED_EVENT_HUB_NAME}", + "InventoryReservedConsumerGroup": "purchase" + } +} +~~~ + +### Test the Update Purchase Status on Inventory Reserve User Story (08D) +1. Open Postman and create a new request +1. Change the HTTP verb to **Post** +1. Paste the **PlaceOrder** endpoint URL +1. Click the **Body** tab +1. Select **raw** and **JSON** +1. Enter the JSON below: + +~~~ +{ + "customerId": 1, + "items": + [ + { + "productId": "10255", + "quantity": 1 + } + ] +} +~~~ + +![Screenshot of Postman](images/04-PlaceOrder/04H-PostmanSetup.png) + +7. Click the **Send** button +8. Validate that the appropriate Purchase.CustomerPurchase and Purchase.PurchaseLineItem record was updated \ No newline at end of file diff --git a/Workshop/GuidedWalkthough/09-SendBackorderNotice.md b/Workshop/GuidedWalkthough/09-SendBackorderNotice.md new file mode 100644 index 0000000..d60acbe --- /dev/null +++ b/Workshop/GuidedWalkthough/09-SendBackorderNotice.md @@ -0,0 +1,156 @@ +# 09 - Send Backorder Notice (Notice) + +## User Story +The Notice system monitors the Inventory Reserved messages looking for backorder notices. Upon receiving a backorder notice, the Notice system will send the customer informing them that the order has been backordered. + +### Definition of Done +The Notice system will send an email to the customer informing them that the order has been backordered. + +--- + +## Workshop Exercises + +**Tasks** +- 09A - [Add consumer group for Notice on the Inventory Reserved Event Hub](#add-consumer-group-for-nootice-on-the-inventory-reserved-event-hub-09a) +- 09B - [Add service logic for user story](#add-service-logic-for-user-story-09b) +- 09C - [Create an Azure Function to watch for backorder notices](#create-an-azure-function-to-watch-for-backorder-notices-09c) +- 09D - [Test the Send Backorder Notice User Story](#test-the-send-backorder-notice-user-story-09d) + +### Add consumer group for Notice on the Inventory Reserved Event Hub (09A) +1. From the [Azure Portal](https://azure.portal.com), navigate to the Event Hub namespace created from the workshop. +1. Click on the **Inventory Reserved** event hub from the **Event Hubs** listing. +1. Click on the **Consumer groups** option under **Entities** on the left-hand navigation panel +1. Click the **+ Consumer group** button. +1. Enter *notice* in the **Name** field. +1. Click the **Create** button. + +### Add a shared access policy for Purchase to access the Inventory Reserved event hub (06A) +1. Click on the **Shared access policies** option under **Settings** on the left-hand navigation panel. +1. Click the **+ Add** button +1. In the **Add SAS Policy** blade, provide the enter the following: + +- **Policy name**: Notice +- **Manage**: Unchecked +- **Send**: Unchecked +- **Listen**:Checked + +7. Click the **Create** button +1. Click on the policy you just created +1. Copy the **Connection string-primary key** + +### Add service logic for user story (09B) +1. From Visual Studio, open the **NoticeServices.cs** file. +1. Add the **SendBackorderNoticeAsync** method to the NoticeServices class. + +~~~ + public async Task SendBackorderNoticeAsync(InventoryReservedMessage inventoryReservedMessage) + { + string subject = $"Backorder Notice - {inventoryReservedMessage.OrderId}"; + string htmlContent = $"

Thank you for your order. Unfortunately, the {inventoryReservedMessage.ProductId} - {inventoryReservedMessage.ProductName} is on backorder and will be shipped as soon as it is back in stock.

"; + string plainTextContent = $"Thank you for your order. Your order number is {inventoryReservedMessage.OrderId}. Your order is on backorder."; + await SendEmailAsync(inventoryReservedMessage.CustomerId, NoticeTypes.BackorderNotice, subject, htmlContent, plainTextContent); + } +~~~ + +### Create an Azure Function to watch for backorder notices (9C) +1. From Visual Studio, right-click on the **Functions** folder within the **Notice.Functions** folder and click the **Add > New Azure Function** +1. Enter **InventoryReservedMonitor.cs** for the name of the new Azure Function class. + +![Screenshot of the Add New Function dialog](images/07-UpdatePurchaseStatusOnInventoryReserve/add-new-item.png) + +3. Click the **Add** button +1. Select the **Event Hub trigger** and specify the following values: + +| Field | Value | +|--------------------------------|-----------------------------------| +| Connection string setting name | InventoryReservedConnectionString | +| Event Hub name | %InventorReservedEventHub% | + +5. Click the **Add** button. +1. Replace the auto-generated code with the following: + +~~~ +using Azure.Messaging.EventHubs; +using BuildingBricks.EventMessages; +using Microsoft.Azure.Functions.Worker; +using Microsoft.Extensions.Logging; +using System.Text.Json; + +namespace BuildingBricks.Notice.Functions; + +public class InventoryReservedMonitor +{ + + private readonly ILogger _logger; + private readonly NoticeServices _noticeServices; + + public InventoryReservedMonitor( + ILogger logger, + NoticeServices noticeServices) + { + _logger = logger; + _noticeServices = noticeServices; + } + + [Function("Notice-InventoryReservedMonitor")] + public async Task RunAsync([EventHubTrigger("%InventoryReservedEventHub%", Connection = "InventoryReservedConnectionString", ConsumerGroup = "%InventoryReservedConsumerGroup%")] EventData[] eventMessages) + { + foreach (EventData eventMessage in eventMessages) + { + _logger.LogInformation("Inventory Reserved Event Received"); + InventoryReservedMessage? inventoryReservedMessage = JsonSerializer.Deserialize(eventMessage.EventBody); + if (inventoryReservedMessage is not null && inventoryReservedMessage.Backordered) + { + _logger.LogWarning("Inventory Reserved Event Received for Backordered Purchase {PurchaseId}", inventoryReservedMessage.ProductId); + await _noticeServices.SendBackorderNoticeAsync(inventoryReservedMessage); + } + } + } + +} +~~~ + +7. Open the **local.settings.json** file within the **Purchase.Functions** project. +1. Add the InventoryReservedConnectionString, InventoryReservedEventHub, and InventoryReservedConsumerGroup values. + +~~~ +{ + "IsEncrypted": false, + "Values": { + "AzureWebJobsStorage": "UseDevelopmentStorage=true", + "FUNCTIONS_WORKER_RUNTIME": "dotnet-isolated", + "AppConfigEndpoint": "{APP_CONFIG_ENDPOINT}", + "PlaceOrderConnectionString": "{PLACE_ORDER_EVENT_HUB_CONNECTION_STRING}", + "PlaceOrderEventHub": "{PLACE_ORDER_EVENT_HUB_NAME}" + "InventoryReservedConnectionString": "{INVENTORY_RESERVED_EVENT_HUB_CONNECTION_STRING}", + "InventoryReservedEventHub": "{INVENTORY_RESERVED_EVENT_HUB_NAME}", + "InventoryReservedConsumerGroup": "purchase" + } +} +~~~ + +### Test the Send Backorder Notice User Story (09D) +1. Open Postman and create a new request +1. Change the HTTP verb to **Post** +1. Paste the **PlaceOrder** endpoint URL +1. Click the **Body** tab +1. Select **raw** and **JSON** +1. Enter the JSON below: + +~~~ +{ + "customerId": 1, + "items": + [ + { + "productId": "10295", + "quantity": 1 + } + ] +} +~~~ + +![Screenshot of Postman](images/04-PlaceOrder/04H-PostmanSetup.png) + +7. Click the **Send** button +8. Validate that the appropriate Purchase.CustomerPurchase and Purchase.PurchaseLineItem record was updated \ No newline at end of file diff --git a/Workshop/GuidedWalkthough/10-InventoryUpdatedNotice.md b/Workshop/GuidedWalkthough/10-InventoryUpdatedNotice.md new file mode 100644 index 0000000..fd3e343 --- /dev/null +++ b/Workshop/GuidedWalkthough/10-InventoryUpdatedNotice.md @@ -0,0 +1,214 @@ +# 10 - Inventory Updated Notice (Inventory) + +## User Story +When there is an update to the inventory of a product, the Inventory system will send out an event message with details of the updated inventory so that subscribed systems can performed needed actions. + +### Definition of Done +Event message indicating that the inventory has been updated is sent out. + +--- + +## Workshop Exercises + +**Tasks** +- 10A - [Turn on change tracking on the Inventory database](#turn-on-change-tracking-on-the-inventory-database-10a) +- 10B - [Create Inventory Updated Event Hub](#create-inventory-updated-event-hub-10b) +- 10C - [Add a shared access policy for Inventory to access the Inventory Updated event hub](#add-a-shared-access-policy-for-inventory-to-access-the-inventory-updated-event-hub-10c) +- 10D - [Add service logic for user story](#add-service-logic-for-user-story-10d) +- 10E - [Create an Azure Function to watch for changes to the Inventory.InventoryTransaction table](#create-an-azure-function-to-watch-for-changes-to-the-inventory.inventorytransaction-table-10e) +- 10F - [Test the Inventory Updated Notice User Story](#test-the-inventory-updated-notice-user-story-10f) + +### Turn on change tracking on the Inventory database (10A) +1. Execute the **EnableChangeTracking.sql** file from the **Inventory.Database** project on the **Inventory** database. + +### Create Inventory Updated Event Hub (10B) +1. From the [Azure Portal](https://portal.azure.com), navigate to the Event Hub namespace you created for the workshop. +1. Click the **+ Event Hub** button. +1. Enter the following information: + +| Field | Value | +|----------------------|---------------------------------------------| +| Name | The name of the Inventory Updated event hub | +| Partition count | 1 | +| Cleanup policy | Delete | +| Retention time (hrs) | 1 | + +![Screenshot of the Create Event Hub page](images/09-InventoryUpdatedNotice/create-event-hub.png) + +4. Click the **Review + create** button +1. Click the **Create** button + +### Add a shared access policy for Inventory to access the Inventory Updated event hub (10C) +1. From the **Event Hubs** listing, click on the event hub you just created +1. Click on the **Shared access policies** option under **Settings** from the left-hand navigation pane +1. Click the **Add** button +1. In the **Add SAS Policy** blade, enter the following: + +| Field | Value | +|-------------|-----------| +| Policy name | Inventory | +| Manage | Unchecked | +| Send | Check | +| Listen | Unchecked | + +4. Click the **Create** button + +**Copy the SAS Policy Connection String** +1. Click on the policy you just created +1. Copy the **Connection string-primary key** + +**Add the connection string to Key Vault** +1. Navigate to the Key Vault you created for the workshop +1. Click on the **Secrets** option in the left-hand menu +1. Click the **+ Generate/Import** button +1. Enter the following information + +| Field | Value | +|--------------|-------------------------------------| +| Name | EventHub-InventoryUpdated-Inventory | +| Secret value | The copied key | + +5. Click the **Create** button + +**Add the Key Vault reference to Azure App Config** +1. Navigate to the GitHub repository you created for the workshop +1. Edit the **OrderProcessingSystem/config/secretreferences.json** file +1. Add the Inventory:EventHubs:InventoryUpdated:ConnectionString element + +~~~ +"Inventory": { + "EventHubs": { + "InventoryReserved": { + "ConnectionString": "{\"uri\":\"https://{KEYVAULT-ENDPOINT-URL}/secrets/EventHub-InventoryReserved-Inventory\"}" + }, + "InventoryUpdated": { + "ConnectionString": "{\"uri\":\"https://{KEYVAULT-ENDPOINT-URL}/secrets/EventHub-InventoryUpdated-Inventory\"}" + } + } +} +~~~ + +4. Click the **Commit changes...** button +1. Verify that the AppConfig GitHub Action completed successfully + +### Add service logic for user story (10D) +1. From Visual Studio, open the **InventoryServices.cs** file. +1. Add the **GetInventoryStatusAsync** method to the InventoryServices class. + +~~~ +public async Task GetInventoryStatusAsync(string productId) +{ + using InventoryContext inventoryContext = new(_configServices); + return await GetInventoryStatusAsync(productId, inventoryContext); +} +~~~ + +3. Add the **InventoryUpdatedAsync** method to the InventoryServices class. + +~~~ +public async Task InventoryUpdatedAsync(string productId) +{ + InventoryUpdatedMessage? inventoryUpdatedMessage = await GetInventoryStatusAsync(productId); + if (inventoryUpdatedMessage is not null) + await SendMessageToEventHubAsync( + _configServices.InventoryEventHubsInventoryUpdatedConnectionString, + _configServices.InventoryEventHubsInventoryUpdatedEventHubName, + JsonSerializer.Serialize(inventoryUpdatedMessage)); +} +~~~ + +### Create an Azure Function to watch for changes to the Inventory.InventoryTransaction table (10E) +1. From Visual Studio, right-click on the **Functions** folder within the **Inventory.Functions** folder and click the **Add > New Azure Function** +1. Enter **InventoryUpdated.cs** for the name of the new Azure Function class. + +![Screenshot of the Add New Function dialog](images/09-InventoryUpdatedNotice/add-new-item.png) + +3. Click the **Add** button +1. Select the **SQL trigger** and specify the following values: + +| Field | Value | +|--------------------------------|------------------------------------| +| Connection string setting name | SqlConnectionString | +| Event Hub name | [Inventory].[InventoryTransaction] | + +5. Click the **Add** button. +1. Replace the auto-generated code with the following: + +~~~ +using BuildingBricks.Inventory; +using BuildingBricks.Inventory.Models; +using Microsoft.Azure.Functions.Worker; +using Microsoft.Azure.Functions.Worker.Extensions.Sql; +using Microsoft.Extensions.Logging; + +namespace Inventory.Functions; + +public class InventoryUpdated +{ + + private readonly ILogger _logger; + private readonly InventoryServices _inventoryServices; + + public InventoryUpdated(ILoggerFactory loggerFactory, InventoryServices inventoryServices) + { + _logger = loggerFactory.CreateLogger(); + _inventoryServices = inventoryServices; + } + + [Function("InventoryUpdated")] + public async Task RunAsync( + [SqlTrigger("[Inventory].[InventoryTransaction]", "SqlConnectionString")] IReadOnlyList> changes, + FunctionContext context) + { + foreach (SqlChange change in changes) + { + _logger.LogInformation("InventoryUpdated: {ProductId}", change.Item.ProductId); + await _inventoryServices.InventoryUpdatedAsync(change.Item.ProductId); + } + } + +} +~~~ + +7. Open the **local.settings.json** file within the **Purchase.Functions** project. +1. Add the InventoryReservedConnectionString, InventoryReservedEventHub, and InventoryReservedConsumerGroup values. + +~~~ +{ + "IsEncrypted": false, + "Values": { + "AzureWebJobsStorage": "UseDevelopmentStorage=true", + "FUNCTIONS_WORKER_RUNTIME": "dotnet-isolated", + "AppConfigEndpoint": "{APP-CONFIG-ENDPOINT}", + "ServiceBusConnectionString": "{SERVICE_BUS_CONNECTION_STRING}", + "OrderPlacedQueue": "{ORDER_PLACED_QUEUE_NAME}", + "SqlConnectionString": "{INVENTORY_DATABASE_CONNECTION_STRING}" + } +} +~~~ + +### Test the Inventory Updated Notice User Story (10F) +1. Open Postman and create a new request +1. Change the HTTP verb to **Post** +1. Paste the **PlaceOrder** endpoint URL +1. Click the **Body** tab +1. Select **raw** and **JSON** +1. Enter the JSON below: + +~~~ +{ + "customerId": 1, + "items": + [ + { + "productId": "10255", + "quantity": 1 + } + ] +} +~~~ + +![Screenshot of Postman](images/04-PlaceOrder/04H-PostmanSetup.png) + +7. Click the **Send** button +8. Validate that the appropriate Purchase.CustomerPurchase and Purchase.PurchaseLineItem record was updated \ No newline at end of file diff --git a/Workshop/GuidedWalkthough/11-ProductAvailabilityUpdate.md b/Workshop/GuidedWalkthough/11-ProductAvailabilityUpdate.md new file mode 100644 index 0000000..22a3c08 --- /dev/null +++ b/Workshop/GuidedWalkthough/11-ProductAvailabilityUpdate.md @@ -0,0 +1,197 @@ +# 11 - Product Availability Update (Product) + +## User Story +When the Product service receives an Inventory Updated event message, the affected product availability will be updated. The updated product availability will be based upon the following: + +| Inventory Level | Product Availability | +|-------------------------------|------------------------| +| More than 0 | Available Now | +| Less than 1 but more than -10 | Backordered | +| Less than -10 | Temporary Out of Stock | + +### Definition of Done +The Product database is updated with the appropriate product availability. + +--- + +## Workshop Exercises + +**Tasks** +- 11A - [Add consumer group for Product on the Inventory Updated Event Hub](#add-consumer-group-for-product-on-the-invnetory-updated-event-hub-11a) +- 11B - [Add a shared access policy for Purchase to access the Inventory Reserved event hub](#add-a-shared-access-policy-for-purchase-to-access-th-inventory-reserved-event-hub-11b) +- 11C - [Create an Azure Function to watch for inventory updated notices](#create-an-azure-function-to-watch-for-inventory-updated-notices-11c) +- 11D - [Test the Product Availability Update User Story](#test-the-product-availability-update-user-story-11d) + +### Add consumer group for Product on the Inventory Updated Event Hub (11A) +1. From the [Azure Portal](https://azure.portal.com), navigate to the Event Hub namespace created from the workshop. +1. Click on the **Inventory Updated** event hub from the **Event Hubs** listing. +1. Click on the **Consumer groups** option under **Entities** on the left-hand navigation panel +1. Click the **+ Consumer group** button. +1. Enter *product* in the **Name** field. +1. Click the **Create** button. + +### Add a shared access policy for Purchase to access the Inventory Reserved event hub (11B) +1. Click on the **Shared access policies** option under **Settings** on the left-hand navigation panel. +1. Click the **+ Add** button +1. In the **Add SAS Policy** blade, provide the enter the following: + +- **Policy name**: Product +- **Manage**: Unchecked +- **Send**: Unchecked +- **Listen**: Checked + +7. Click the **Create** button +1. Click on the policy you just created +1. Copy the **Connection string-primary key** + +### Create an Azure Function to watch for inventory updated notices (11C) +1. From Visual Studio, right-click on the **Functions** folder within the **Notice.Functions** folder and click the **Add > New Azure Function** +1. Enter **InventoryUpdatedMonitor.cs** for the name of the new Azure Function class. + +![Screenshot of the Add New Function dialog](images/10-ProductAvailabilityUpdate/add-new-item.png) + +3. Click the **Add** button +1. Select the **Event Hub trigger** and specify the following values: + +| Field | Value | +|--------------------------------|-----------------------------------| +| Connection string setting name | InventoryUpdatedConnectionString | +| Event Hub name | %InventorUpdatedEventHub% | + +![Screenshot of the New Azure Function dialog](images/10-ProductAvailabilityUpdate/new-azure-function.png) + +5. Click the **Add** button. +1. Replace the auto-generated code with the following: + +~~~ +using Azure.Messaging.EventHubs; +using BuildingBricks.Core.EventMessages; +using BuildingBricks.Product.Models; +using BuildingBricks.Product.Services; +using Microsoft.Azure.Functions.Worker; +using Microsoft.Extensions.Logging; +using System.Text.Json; + +namespace BuildingBricks.Product.Functions; + +public class InventoryUpdatedMonitor +{ + + private readonly ILogger _logger; + private readonly MerchandiseServices _merchandiseServices; + private readonly AvailabilityServices _availabilityServices; + + private const int _availableNow = 1; + private const int _backordered = 2; + private const int _comingSoon = 3; + private const int _outOfStock = 4; + + public InventoryUpdatedMonitor( + ILogger logger, + MerchandiseServices merchandiseServices, + AvailabilityServices availabilityServices) + { + _logger = logger; + _merchandiseServices = merchandiseServices; + _availabilityServices = availabilityServices; + } + + [Function(nameof(InventoryUpdatedMonitor))] + public async Task Run([EventHubTrigger("%InventoryUpdatedEventHub%", Connection = "InventoryUpdatedConnectionString", ConsumerGroup = "%InventoryUpdatedConsumerGroup%")] EventData[] eventMessages) + { + foreach (EventData eventMessage in eventMessages) + { + InventoryUpdatedMessage? inventoryUpdatedMessage = JsonSerializer.Deserialize(eventMessage.EventBody); + if (inventoryUpdatedMessage is not null) + { + _logger.LogInformation("Inventory Updated - ProductId: {ProductId} - Available: {Availability}", inventoryUpdatedMessage.ProductId, inventoryUpdatedMessage.InventoryAvailable); + List availabilities = await _availabilityServices.GetListAsync(); + Merchandise merchandise = await _merchandiseServices.GetAsync(inventoryUpdatedMessage.ProductId); + if (inventoryUpdatedMessage.InventoryAvailable > 0) + { + Availability? availableNow = availabilities.FirstOrDefault(x => x.LegacyId == _availableNow); + if (availableNow is not null && merchandise.AvailabilityId != availableNow.Id) + { + _logger.LogWarning("{ProductId} is now available", inventoryUpdatedMessage.ProductId); + await UpdateMerchandiseAvailability(merchandise, availableNow); + } + } + else if (inventoryUpdatedMessage.InventoryAvailable >= -10) + { + Availability? backordered = availabilities.FirstOrDefault(x => x.LegacyId == _backordered); + if (backordered is not null && merchandise.AvailabilityId != backordered.Id) + { + _logger.LogWarning("{ProductId} is now backordered", inventoryUpdatedMessage.ProductId); + await UpdateMerchandiseAvailability(merchandise, backordered); + } + } + else if (inventoryUpdatedMessage.InventoryAvailable < -10) + { + Availability? outOfStock = availabilities.FirstOrDefault(x => x.LegacyId == _outOfStock); + if (outOfStock is not null && merchandise.AvailabilityId != outOfStock.Id) + { + _logger.LogWarning("{ProductId} is now out of stock", inventoryUpdatedMessage.ProductId); + await UpdateMerchandiseAvailability(merchandise, outOfStock); + } + } + else + { + _logger.LogWarning("{ProductId} has no change in availability status", inventoryUpdatedMessage.ProductId); + } + } + } + } + + private async Task UpdateMerchandiseAvailability(Merchandise merchandise, Availability availability) + { + merchandise.AvailabilityId = availability.Id; + merchandise.Availability = availability.Name; + await _merchandiseServices.ReplaceAsync(merchandise); + } + +} +~~~ + +7. Open the **local.settings.json** file within the **Purchase.Functions** project. +1. Add the InventoryReservedConnectionString, InventoryReservedEventHub, and InventoryReservedConsumerGroup values. + +~~~ +{ + "IsEncrypted": false, + "Values": { + "AzureWebJobsStorage": "UseDevelopmentStorage=true", + "FUNCTIONS_WORKER_RUNTIME": "dotnet-isolated", + "AppConfigEndpoint": "{APP_CONFIG_ENDPOINT}", + "ConnectionString": "{COSMOS_CONNECTION_STRING}", + "InventoryUpdatedConnectionString": "{INVENTORY_UPDATED_EVENT_HUB_CONNECTION_STRING}", + "InventoryUpdatedEventHub": "{INVENTORY_UPDATED_EVENT_HUB_NAME}", + "InventoryUpdatedConsumerGroup": "{INVNETORY_UPDATED_EVENT_HUB_CONSUMER_GROUP}" + } +} +~~~ + +### Test the Product Availability Update User Story (11D) +1. Open Postman and create a new request +1. Change the HTTP verb to **Post** +1. Paste the **PlaceOrder** endpoint URL +1. Click the **Body** tab +1. Select **raw** and **JSON** +1. Enter the JSON below: + +~~~ +{ + "customerId": 1, + "items": + [ + { + "productId": "10295", + "quantity": 1 + } + ] +} +~~~ + +![Screenshot of Postman](images/04-PlaceOrder/04H-PostmanSetup.png) + +7. Click the **Send** button +8. Validate that the appropriate Purchase.CustomerPurchase and Purchase.PurchaseLineItem record was updated \ No newline at end of file diff --git a/Workshop/GuidedWalkthough/12-StartPickingOrder.md b/Workshop/GuidedWalkthough/12-StartPickingOrder.md new file mode 100644 index 0000000..910a3fc --- /dev/null +++ b/Workshop/GuidedWalkthough/12-StartPickingOrder.md @@ -0,0 +1,160 @@ +# 12 - Start Picking Order (Shipping) + +## User Story +When the Shipping services receives an Inventory Reserved message, the order is picked for shipment. + +### Definition of Done +The Shipment record is updated to indicate that the order is ready to be picked. + +--- + +## Workshop Exercises + +**Tasks** +- 12A - [Add consumer group for Shipping on the Inventory Reserved Event Hub](#add-consumer-group-for-shipping-on-the-inventory-reserved-event-hub-12a) +- 12B - [Add a shared access policy for Shipping to access the Inventory Reserved event hub](#add-a-shared-access-policy-for-shipping-to-access-the-inventory-reserved-event-hub-12b) +- 12C - [Add service logic for user story](#add-service-logic-for-user-story-12c) +- 12D - [Create an Azure Function to watch for inventory reserved notices](#create-an-azure-function-to-watch-for-inventory-reserved-notices-12d) +- 12E - [Test the Start Picking Order User Story](#test-the-start-picking-order-user-story-12e) + +### Add consumer group for Shipping on the Inventory Reserved Event Hub (12A) +1. From the [Azure Portal](https://azure.portal.com), navigate to the Event Hub namespace created from the workshop. +1. Click on the **Inventory Reserved** event hub from the **Event Hubs** listing. +1. Click on the **Consumer groups** option under **Entities** on the left-hand navigation panel +1. Click the **+ Consumer group** button. +1. Enter *shipping* in the **Name** field. +1. Click the **Create** button. + +### Add a shared access policy for Shipping to access the Inventory Reserved event hub (12B) +1. Click on the **Shared access policies** option under **Settings** on the left-hand navigation panel. +1. Click the **+ Add** button +1. In the **Add SAS Policy** blade, provide the enter the following: + +- **Policy name**: Shipping +- **Manage**: Unchecked +- **Send**: Unchecked +- **Listen**: Checked + +7. Click the **Create** button +1. Click on the policy you just created +1. Copy the **Connection string-primary key** + +### Add service logic for user story (12C) +1. From Visual Studio, open the **ShippingServices.cs** file. +1. Add the **StartPickingOrderAsync** method to the ShippingServices class. + +~~~ +public async Task StartPickingOrderAsync(string orderId) +{ + using ShippingContext shippingContext = new(_configServices); + CustomerPurchase? customerPurchase = await shippingContext.CustomerPurchases + .Include(x => x.Shipments) + .FirstOrDefaultAsync(x => x.CustomerPurchaseId == orderId); + if (customerPurchase is not null && customerPurchase.Shipments.Any()) + { + foreach (Shipment shipment in customerPurchase.Shipments) + shipment.ShipmentStatusId = ShipmentStatuses.Picking; + await shippingContext.SaveChangesAsync(); + } +} +~~~ + +### Create an Azure Function to watch for inventory reserved notices (12D) +1. From Visual Studio, right-click on the **Functions** folder within the **Shipping.Functions** folder and click the **Add > New Azure Function** +1. Enter **InventoryReservedMonitor.cs** for the name of the new Azure Function class. +1. Click the **Add** button +1. Select the **Event Hub trigger** and specify the following values: + +| Field | Value | +|--------------------------------|-----------------------------------| +| Connection string setting name | InventoryReservedConnectionString | +| Event Hub name | %InventorReservedEventHub% | + +5. Click the **Add** button. +1. Replace the auto-generated code with the following: + +~~~ +using Azure.Messaging.EventHubs; +using BuildingBricks.EventMessages; +using BuildingBricks.Shipping; +using Microsoft.Azure.Functions.Worker; +using Microsoft.Extensions.Logging; +using System.Text.Json; + +namespace Shipping.Functions; + +public class InventoryReservedMonitor +{ + private readonly ILogger _logger; + private readonly ShippingServices _shippingServices; + + public InventoryReservedMonitor( + ILogger logger, + ShippingServices shippingServices) + { + _logger = logger; + _shippingServices = shippingServices; + } + + [Function(nameof(InventoryReservedMonitor))] + public async Task RunAsync([EventHubTrigger("%InventoryReservedEventHub%", Connection = "InventoryReservedConnectionString", ConsumerGroup = "%InventoryReservedConsumerGroup%")] EventData[] eventMessages) + { + foreach (EventData eventMessage in eventMessages) + { + InventoryReservedMessage? inventoryReservedMessage = JsonSerializer.Deserialize(eventMessage.EventBody); + if (inventoryReservedMessage is not null) + { + _logger.LogInformation("Inventory reserved for Order #{OrderNumber}", inventoryReservedMessage.OrderId); + await _shippingServices.StartPickingOrderAsync(inventoryReservedMessage.OrderId); + } + } + } + +} +~~~ + +7. Open the **local.settings.json** file within the **Purchase.Functions** project. +1. Add the InventoryReservedConnectionString, InventoryReservedEventHub, and InventoryReservedConsumerGroup values. + +~~~ +{ + "IsEncrypted": false, + "Values": { + "AzureWebJobsStorage": "UseDevelopmentStorage=true", + "FUNCTIONS_WORKER_RUNTIME": "dotnet-isolated", + "AppConfigEndpoint": "{APP_CONFIG_ENDPOINT}", + "OrderPlacedConnectionString": "{ORDER_PLACED_EVENT_HUB_CONNECTION_STRING}", + "OrderPlacedEventHub": "{ORDER_PLACED_EVENT_HUB_NAME}", + "OrderPlacedConsumerGroup": "shipping", + "InventoryReservedConnectionString": "{INVENTORY_RESERVED_EVENT_HUB_CONNECTION_STRING}", + "InventoryReservedEventHub": "{INVENTORY_RESERVED_EVENT_HUB_NAME}", + "InventoryReservedConsumerGroup": "shipping" + } +} +~~~ + +### Test the Start Picking Order User Story (12E) +1. Open Postman and create a new request +1. Change the HTTP verb to **Post** +1. Paste the **PlaceOrder** endpoint URL +1. Click the **Body** tab +1. Select **raw** and **JSON** +1. Enter the JSON below: + +~~~ +{ + "customerId": 1, + "items": + [ + { + "productId": "10255", + "quantity": 1 + } + ] +} +~~~ + +![Screenshot of Postman](images/04-PlaceOrder/04H-PostmanSetup.png) + +7. Click the **Send** button +8. Validate that the appropriate Shipping.Shipment record was updated \ No newline at end of file diff --git a/Workshop/GuidedWalkthough/13-OrderFulfillment.md b/Workshop/GuidedWalkthough/13-OrderFulfillment.md new file mode 100644 index 0000000..7892a97 --- /dev/null +++ b/Workshop/GuidedWalkthough/13-OrderFulfillment.md @@ -0,0 +1,156 @@ +# 13 - Order Fulfillment (Shipping) + +## User Story +Once an order is shipped, the Shipping system will send out an OrderShippedMessage message. + +### Definition of Done +The Order Shipped message is sent to the Order Shipped event hub. + +--- + +## Workshop Exercises + +**Tasks** +-13A - [Create the Order Shipped event hub](#create-the-order-shipped-event-hub-13a) +-13B - [Create the Shipping shared access policy](#create-the-shipping-shared-access-policy-13b) +-13C - [Add the event hub connection string to Key Vault](#add-the-event-hub-connection-string-to-key-vault-13c) +-13D - [Add service logic for user story](#add-service-logic-for-user-story-13d) +-13E - [Test the Product Availability Update User Story](#test-the-product-availability-update-user-story-13e) + +### Create the Order Shipped event hub (13A) +1. From the [Azure Portal](https://azure.portal.com), navigate to the Event Hub namespace created for the workshop. +1. Click the **+ Event Hub** button. +1. Enter the following information: + +| Field | Value | +|----------------------|------------------------| +| Name | Name for the event hub | +| Partition count | 1 | +| Cleanup policy | Delete | +| Retention time (hrs) | 1 | + +4. Click the **Review + create** button. +5. Click the **Create** button. + +### Create the Shipping shared access policy (13B) +1. From the **Event Hubs** listing, click on the event hub you just created. +1. Click the **Shared access policies** under the **Settings** on the left-hand navigation pane. +1. Click the **+ Add** button. +1. Enter the following values: + +| Field | Value | +|-------------|-----------| +| Policy name | Shipping | +| Manage | Unchecked | +| Send | Checked | +| Listen | Unchecked | + +5. Click the **Create** button. + +### Add the event hub connection string to Key Vault (13C) +1. From the [Azure Portal](https://azure.portal.com), navigate to the Key Vault created for the workshop. +1. Click the **Secrets** option under the **Objects** on the left-side navigation pane. +1. Click the **+ Generate/Import** button. +1. Enter the following values: + +| Field | Value | +|--------------|---------------------------------------------| +| Name | EventHub-OrderShipped-Shipping | +| Secret value | Connection string copied from previous step | + +5. Click the **Create** button. +1. From the GitHub repository you created for the workshop, open the **config/secretreferences.json** in edit mode. +1. Add the **Shipping:EventHubs:OrderShipped:ConnectionString** element: + +~~~ +"Shipping": { + "EventHubs": { + "OrderShipped": { + "ConnectionString": "{\"uri\":\"https://kv-opswalkthough.vault.azure.net/secrets/EventHub-OrderShipped-Shipping\"}" + } + } +} +~~~ + +8. Click the **Commit changes...** button. +1. Validate that the **AppConfig** workflow completes successfully. + +### Add service logic for user story (13D) +1. From Visual Studio, open the **ShippingServices.cs** file. +1. Add the **SendEventMessageIfNecessaryAsync** method to the ShippingServices class. + +~~~ +private async Task SendEventMessageIfNecessaryAsync(Shipment shipment) +{ + if (shipment.ShipmentStatusId == ShipmentStatuses.Shipped) + { + OrderShippedMessage orderShippedMessage = new() + { + OrderId = shipment.CustomerPurchaseId, + Carrier = shipment.ShippingCarrier.ShippingCarrierName, + TrackingNumber = shipment.TrackingNumber + }; + await SendMessageToEventHubAsync(_configServices.ShippingOrderShippedEventHubConnectionString, JsonSerializer.Serialize(orderShippedMessage)); + } +} +~~~ + +3. Update the **UpdateShipmentStatusAsync** method to call the **SendEventMessageIfNecessaryAsync** method. + +~~~ +public async Task UpdateShipmentStatusAsync( + int shipmentId, + UpdateShipmentStatusRequest updateShipmentStatusRequest) +{ + + using ShippingContext shippingContext = new(_configServices); + + // Retrieve the shipment to be updated + Shipment? shipment = await shippingContext.Shipments + .Include(x => x.CustomerPurchase) + .FirstOrDefaultAsync(x => x.ShipmentId == shipmentId) + ?? throw new ArgumentNullException(nameof(shipmentId)); + + // Validate the specified shipment status + ShipmentStatus? shipmentStatus = await shippingContext.ShipmentStatuses.FirstOrDefaultAsync(x => x.ShipmentStatusId == updateShipmentStatusRequest.ShipmentStatusId) + ?? throw new ArgumentOutOfRangeException(nameof(updateShipmentStatusRequest), "Invalid shipping status specified."); + + // Validate the specified shipping carrier + ShippingCarrier? carrier = null; + if (updateShipmentStatusRequest.CarrierId is not null) + { + carrier = await shippingContext.ShippingCarriers.FirstOrDefaultAsync(x => x.ShippingCarrierId == updateShipmentStatusRequest.CarrierId) + ?? throw new ArgumentOutOfRangeException(nameof(updateShipmentStatusRequest), "Invalid shipping carrier specified."); + } + + // Update the shipment record + shipment.ShipmentStatusId = shipmentStatus.ShipmentStatusId; + shipment.ShippingCarrierId = carrier?.ShippingCarrierId ?? null; + shipment.TrackingNumber = updateShipmentStatusRequest.TrackingNumber; + await shippingContext.SaveChangesAsync(); + + // Send event message if necessary + await SendEventMessageIfNecessaryAsync(shipment); + +} +~~~ + +### Test the Product Availability Update User Story (13E) +1. Open Postman and create a new request +1. Change the HTTP verb to **Post** +1. Paste the **UpdateShippingStatus** endpoint URL +1. Click the **Body** tab +1. Select **raw** and **JSON** +1. Enter the JSON below: + +~~~ +{ + "shipmentStatusId": 4, + "carrierId": 1, + "trackingNumber": "1234567890" +} +~~~ + +![Screenshot of Postman](images/13-OrderFulfillment/postman.png) + +7. Click the **Send** button \ No newline at end of file diff --git a/Workshop/GuidedWalkthough/14-UpdatePurchaseStatusOnShipment.md b/Workshop/GuidedWalkthough/14-UpdatePurchaseStatusOnShipment.md new file mode 100644 index 0000000..4f79ce1 --- /dev/null +++ b/Workshop/GuidedWalkthough/14-UpdatePurchaseStatusOnShipment.md @@ -0,0 +1,13 @@ +# 14 - Update Purchase Status on Shipment (Purchase) + +## User Story +Once an order is shipped, the Purchase system updates the status of the order. + +### Definition of Done +- The customer purchase status is updated. + +--- + +## Workshop Exercises + +**Tasks** \ No newline at end of file diff --git a/Workshop/GuidedWalkthough/15-SendOrderShipmentNotice.md b/Workshop/GuidedWalkthough/15-SendOrderShipmentNotice.md new file mode 100644 index 0000000..a51ffd4 --- /dev/null +++ b/Workshop/GuidedWalkthough/15-SendOrderShipmentNotice.md @@ -0,0 +1,14 @@ +# 15 - Send Order Shipment Notice (Notice) + +## User Story +Once an order is shipped, the Notice system sends an email to the customer informing them that the order has been shipped. + +### Definition of Done +- An email to the customer indicating that the order has been shipped is sent. +- The order shipped email is logged. + +--- + +## Workshop Exercises + +**Tasks** diff --git a/Workshop/GuidedWalkthough/16-ShipmentDelivered.md b/Workshop/GuidedWalkthough/16-ShipmentDelivered.md new file mode 100644 index 0000000..2d37cae --- /dev/null +++ b/Workshop/GuidedWalkthough/16-ShipmentDelivered.md @@ -0,0 +1,13 @@ +# 16 - Shipment Delivered (Shipping) + +## User Story +Once an order is delivered, the Shipping system will send out an OrderDeliveredMessage message. + +### Definition of Done +* The `OrderDeliveredMessage` message is sent out by the Shipping system. + +--- + +## Workshop Exercises + +**Tasks** diff --git a/Workshop/GuidedWalkthough/17-UpdatePurchaseStatusOnShipmentDelivery.md b/Workshop/GuidedWalkthough/17-UpdatePurchaseStatusOnShipmentDelivery.md new file mode 100644 index 0000000..141f90f --- /dev/null +++ b/Workshop/GuidedWalkthough/17-UpdatePurchaseStatusOnShipmentDelivery.md @@ -0,0 +1,13 @@ +# 17 - Update Purchase Status on Shipment Delivery (Purchase) + +## User Story +Upon receiving the Order Delivered event message, the Purchase system will update the status of the order. + +### Definition of Done +- The purchase order status is updated to `DELIVERED` in the database. + +--- + +## Workshop Exercises + +**Tasks** diff --git a/Workshop/GuidedWalkthough/18-SendOrderDeliveredNotice.md b/Workshop/GuidedWalkthough/18-SendOrderDeliveredNotice.md new file mode 100644 index 0000000..bdd0d82 --- /dev/null +++ b/Workshop/GuidedWalkthough/18-SendOrderDeliveredNotice.md @@ -0,0 +1,14 @@ +# 18 - Send Order Delivered Notice (Notice) + +## User Story +Once an order is delivered, the Notice sends an email to the customer informing them that the order has been delivered. + +### Definition of Done +- An email to the customer indicating that the order has been delivered is sent. +- The order delivered email is logged. + +--- + +## Workshop Exercises + +**Tasks** diff --git a/Workshop/GuidedWalkthough/images/01-Configuration/01A01-ForkBaseRepo.png b/Workshop/GuidedWalkthough/images/01-Configuration/01A01-ForkBaseRepo.png new file mode 100644 index 0000000..36e2599 Binary files /dev/null and b/Workshop/GuidedWalkthough/images/01-Configuration/01A01-ForkBaseRepo.png differ diff --git a/Workshop/GuidedWalkthough/images/01-Configuration/01A02-CreateAFork.png b/Workshop/GuidedWalkthough/images/01-Configuration/01A02-CreateAFork.png new file mode 100644 index 0000000..d0e9725 Binary files /dev/null and b/Workshop/GuidedWalkthough/images/01-Configuration/01A02-CreateAFork.png differ diff --git a/Workshop/GuidedWalkthough/images/01-Configuration/01F06-SelectMembers.png b/Workshop/GuidedWalkthough/images/01-Configuration/01F06-SelectMembers.png new file mode 100644 index 0000000..e78bd99 Binary files /dev/null and b/Workshop/GuidedWalkthough/images/01-Configuration/01F06-SelectMembers.png differ diff --git a/Workshop/GuidedWalkthough/images/02-InitializeDatabases/02A01-Networking.png b/Workshop/GuidedWalkthough/images/02-InitializeDatabases/02A01-Networking.png new file mode 100644 index 0000000..e1b335e Binary files /dev/null and b/Workshop/GuidedWalkthough/images/02-InitializeDatabases/02A01-Networking.png differ diff --git a/Workshop/GuidedWalkthough/images/02-InitializeDatabases/02B01-ServerName.png b/Workshop/GuidedWalkthough/images/02-InitializeDatabases/02B01-ServerName.png new file mode 100644 index 0000000..f4a2a3b Binary files /dev/null and b/Workshop/GuidedWalkthough/images/02-InitializeDatabases/02B01-ServerName.png differ diff --git a/Workshop/GuidedWalkthough/images/02-InitializeDatabases/02F01-PublishDatabase.png b/Workshop/GuidedWalkthough/images/02-InitializeDatabases/02F01-PublishDatabase.png new file mode 100644 index 0000000..bf001c9 Binary files /dev/null and b/Workshop/GuidedWalkthough/images/02-InitializeDatabases/02F01-PublishDatabase.png differ diff --git a/Workshop/GuidedWalkthough/images/02-InitializeDatabases/02F02-Connect.png b/Workshop/GuidedWalkthough/images/02-InitializeDatabases/02F02-Connect.png new file mode 100644 index 0000000..0e861d6 Binary files /dev/null and b/Workshop/GuidedWalkthough/images/02-InitializeDatabases/02F02-Connect.png differ diff --git a/Workshop/GuidedWalkthough/images/02-InitializeDatabases/02G01-Connect.png b/Workshop/GuidedWalkthough/images/02-InitializeDatabases/02G01-Connect.png new file mode 100644 index 0000000..a43fe0c Binary files /dev/null and b/Workshop/GuidedWalkthough/images/02-InitializeDatabases/02G01-Connect.png differ diff --git a/Workshop/GuidedWalkthough/images/02-InitializeDatabases/02L-ActionsCompleted.png b/Workshop/GuidedWalkthough/images/02-InitializeDatabases/02L-ActionsCompleted.png new file mode 100644 index 0000000..fe3ca71 Binary files /dev/null and b/Workshop/GuidedWalkthough/images/02-InitializeDatabases/02L-ActionsCompleted.png differ diff --git a/Workshop/GuidedWalkthough/images/03-InitializeProductDatabase/03A01-AzurePortal.png b/Workshop/GuidedWalkthough/images/03-InitializeProductDatabase/03A01-AzurePortal.png new file mode 100644 index 0000000..c5f6f4a Binary files /dev/null and b/Workshop/GuidedWalkthough/images/03-InitializeProductDatabase/03A01-AzurePortal.png differ diff --git a/Workshop/GuidedWalkthough/images/03-InitializeProductDatabase/03B01-AddANewProject.png b/Workshop/GuidedWalkthough/images/03-InitializeProductDatabase/03B01-AddANewProject.png new file mode 100644 index 0000000..4fad3a2 Binary files /dev/null and b/Workshop/GuidedWalkthough/images/03-InitializeProductDatabase/03B01-AddANewProject.png differ diff --git a/Workshop/GuidedWalkthough/images/03-InitializeProductDatabase/03B02-ConfigureYourNewProject.png b/Workshop/GuidedWalkthough/images/03-InitializeProductDatabase/03B02-ConfigureYourNewProject.png new file mode 100644 index 0000000..0d85e8e Binary files /dev/null and b/Workshop/GuidedWalkthough/images/03-InitializeProductDatabase/03B02-ConfigureYourNewProject.png differ diff --git a/Workshop/GuidedWalkthough/images/03-InitializeProductDatabase/03B03-AdditionalInformation.png b/Workshop/GuidedWalkthough/images/03-InitializeProductDatabase/03B03-AdditionalInformation.png new file mode 100644 index 0000000..237a192 Binary files /dev/null and b/Workshop/GuidedWalkthough/images/03-InitializeProductDatabase/03B03-AdditionalInformation.png differ diff --git a/Workshop/GuidedWalkthough/images/03-InitializeProductDatabase/03B04-ReferenceManager.png b/Workshop/GuidedWalkthough/images/03-InitializeProductDatabase/03B04-ReferenceManager.png new file mode 100644 index 0000000..7b9a9e1 Binary files /dev/null and b/Workshop/GuidedWalkthough/images/03-InitializeProductDatabase/03B04-ReferenceManager.png differ diff --git a/Workshop/GuidedWalkthough/images/03-InitializeProductDatabase/03B05-CreateMerchandiseContainer.png b/Workshop/GuidedWalkthough/images/03-InitializeProductDatabase/03B05-CreateMerchandiseContainer.png new file mode 100644 index 0000000..5781c33 Binary files /dev/null and b/Workshop/GuidedWalkthough/images/03-InitializeProductDatabase/03B05-CreateMerchandiseContainer.png differ diff --git a/Workshop/GuidedWalkthough/images/03-InitializeProductDatabase/03B06-DataExplorer.png b/Workshop/GuidedWalkthough/images/03-InitializeProductDatabase/03B06-DataExplorer.png new file mode 100644 index 0000000..79801fe Binary files /dev/null and b/Workshop/GuidedWalkthough/images/03-InitializeProductDatabase/03B06-DataExplorer.png differ diff --git a/Workshop/GuidedWalkthough/images/03-InitializeProductDatabase/03C01-ConfigureStartupProjects.png b/Workshop/GuidedWalkthough/images/03-InitializeProductDatabase/03C01-ConfigureStartupProjects.png new file mode 100644 index 0000000..d7ebd3b Binary files /dev/null and b/Workshop/GuidedWalkthough/images/03-InitializeProductDatabase/03C01-ConfigureStartupProjects.png differ diff --git a/Workshop/GuidedWalkthough/images/03-InitializeProductDatabase/03C02-AzureFunctionsCLI.png b/Workshop/GuidedWalkthough/images/03-InitializeProductDatabase/03C02-AzureFunctionsCLI.png new file mode 100644 index 0000000..5c6d31a Binary files /dev/null and b/Workshop/GuidedWalkthough/images/03-InitializeProductDatabase/03C02-AzureFunctionsCLI.png differ diff --git a/Workshop/GuidedWalkthough/images/03-InitializeProductDatabase/03C03-DatabaseLoadComplete.png b/Workshop/GuidedWalkthough/images/03-InitializeProductDatabase/03C03-DatabaseLoadComplete.png new file mode 100644 index 0000000..202f5a2 Binary files /dev/null and b/Workshop/GuidedWalkthough/images/03-InitializeProductDatabase/03C03-DatabaseLoadComplete.png differ diff --git a/Workshop/GuidedWalkthough/images/03-InitializeProductDatabase/03C04-AzureFunctionComplete.png b/Workshop/GuidedWalkthough/images/03-InitializeProductDatabase/03C04-AzureFunctionComplete.png new file mode 100644 index 0000000..7229933 Binary files /dev/null and b/Workshop/GuidedWalkthough/images/03-InitializeProductDatabase/03C04-AzureFunctionComplete.png differ diff --git a/Workshop/GuidedWalkthough/images/04-PlaceOrder/04A-CreateNamespace.png b/Workshop/GuidedWalkthough/images/04-PlaceOrder/04A-CreateNamespace.png new file mode 100644 index 0000000..f583def Binary files /dev/null and b/Workshop/GuidedWalkthough/images/04-PlaceOrder/04A-CreateNamespace.png differ diff --git a/Workshop/GuidedWalkthough/images/04-PlaceOrder/04C-ActionsCompleted.png b/Workshop/GuidedWalkthough/images/04-PlaceOrder/04C-ActionsCompleted.png new file mode 100644 index 0000000..c5b6a48 Binary files /dev/null and b/Workshop/GuidedWalkthough/images/04-PlaceOrder/04C-ActionsCompleted.png differ diff --git a/Workshop/GuidedWalkthough/images/04-PlaceOrder/04C-AddSASPolicy.png b/Workshop/GuidedWalkthough/images/04-PlaceOrder/04C-AddSASPolicy.png new file mode 100644 index 0000000..8625773 Binary files /dev/null and b/Workshop/GuidedWalkthough/images/04-PlaceOrder/04C-AddSASPolicy.png differ diff --git a/Workshop/GuidedWalkthough/images/04-PlaceOrder/04D-CreateNamespace.png b/Workshop/GuidedWalkthough/images/04-PlaceOrder/04D-CreateNamespace.png new file mode 100644 index 0000000..7d66846 Binary files /dev/null and b/Workshop/GuidedWalkthough/images/04-PlaceOrder/04D-CreateNamespace.png differ diff --git a/Workshop/GuidedWalkthough/images/04-PlaceOrder/04E-CreateEventHub.png b/Workshop/GuidedWalkthough/images/04-PlaceOrder/04E-CreateEventHub.png new file mode 100644 index 0000000..2c28ae5 Binary files /dev/null and b/Workshop/GuidedWalkthough/images/04-PlaceOrder/04E-CreateEventHub.png differ diff --git a/Workshop/GuidedWalkthough/images/04-PlaceOrder/04G-AdditionalInformation.png b/Workshop/GuidedWalkthough/images/04-PlaceOrder/04G-AdditionalInformation.png new file mode 100644 index 0000000..8e8baba Binary files /dev/null and b/Workshop/GuidedWalkthough/images/04-PlaceOrder/04G-AdditionalInformation.png differ diff --git a/Workshop/GuidedWalkthough/images/04-PlaceOrder/04G-ConfigureStartupProjects.png b/Workshop/GuidedWalkthough/images/04-PlaceOrder/04G-ConfigureStartupProjects.png new file mode 100644 index 0000000..3f89185 Binary files /dev/null and b/Workshop/GuidedWalkthough/images/04-PlaceOrder/04G-ConfigureStartupProjects.png differ diff --git a/Workshop/GuidedWalkthough/images/04-PlaceOrder/04G-ConfigureYourNewProject.png b/Workshop/GuidedWalkthough/images/04-PlaceOrder/04G-ConfigureYourNewProject.png new file mode 100644 index 0000000..0be604d Binary files /dev/null and b/Workshop/GuidedWalkthough/images/04-PlaceOrder/04G-ConfigureYourNewProject.png differ diff --git a/Workshop/GuidedWalkthough/images/04-PlaceOrder/04G-ReferenceManager.png b/Workshop/GuidedWalkthough/images/04-PlaceOrder/04G-ReferenceManager.png new file mode 100644 index 0000000..7b6af56 Binary files /dev/null and b/Workshop/GuidedWalkthough/images/04-PlaceOrder/04G-ReferenceManager.png differ diff --git a/Workshop/GuidedWalkthough/images/04-PlaceOrder/04H-AzureFunctionCLI.png b/Workshop/GuidedWalkthough/images/04-PlaceOrder/04H-AzureFunctionCLI.png new file mode 100644 index 0000000..0451add Binary files /dev/null and b/Workshop/GuidedWalkthough/images/04-PlaceOrder/04H-AzureFunctionCLI.png differ diff --git a/Workshop/GuidedWalkthough/images/04-PlaceOrder/04H-PostmanSetup.png b/Workshop/GuidedWalkthough/images/04-PlaceOrder/04H-PostmanSetup.png new file mode 100644 index 0000000..df5e490 Binary files /dev/null and b/Workshop/GuidedWalkthough/images/04-PlaceOrder/04H-PostmanSetup.png differ diff --git a/Workshop/GuidedWalkthough/images/04-PlaceOrder/04I-SeviceBusExplorer.png b/Workshop/GuidedWalkthough/images/04-PlaceOrder/04I-SeviceBusExplorer.png new file mode 100644 index 0000000..bd9ab3b Binary files /dev/null and b/Workshop/GuidedWalkthough/images/04-PlaceOrder/04I-SeviceBusExplorer.png differ diff --git a/Workshop/GuidedWalkthough/images/04-PlaceOrder/4I-EventHubMetrics.png b/Workshop/GuidedWalkthough/images/04-PlaceOrder/4I-EventHubMetrics.png new file mode 100644 index 0000000..9e59217 Binary files /dev/null and b/Workshop/GuidedWalkthough/images/04-PlaceOrder/4I-EventHubMetrics.png differ diff --git a/Workshop/GuidedWalkthough/images/04-PlaceOrder/4I-PurchaseFunctionsConsole.png b/Workshop/GuidedWalkthough/images/04-PlaceOrder/4I-PurchaseFunctionsConsole.png new file mode 100644 index 0000000..c7166ec Binary files /dev/null and b/Workshop/GuidedWalkthough/images/04-PlaceOrder/4I-PurchaseFunctionsConsole.png differ diff --git a/Workshop/GuidedWalkthough/images/05-SendOrderConfirmation/acs-keys.png b/Workshop/GuidedWalkthough/images/05-SendOrderConfirmation/acs-keys.png new file mode 100644 index 0000000..e3c1bd5 Binary files /dev/null and b/Workshop/GuidedWalkthough/images/05-SendOrderConfirmation/acs-keys.png differ diff --git a/Workshop/GuidedWalkthough/images/05-SendOrderConfirmation/acs-try-email.png b/Workshop/GuidedWalkthough/images/05-SendOrderConfirmation/acs-try-email.png new file mode 100644 index 0000000..c9f69e3 Binary files /dev/null and b/Workshop/GuidedWalkthough/images/05-SendOrderConfirmation/acs-try-email.png differ diff --git a/Workshop/GuidedWalkthough/images/05-SendOrderConfirmation/additional-information.png b/Workshop/GuidedWalkthough/images/05-SendOrderConfirmation/additional-information.png new file mode 100644 index 0000000..78112f6 Binary files /dev/null and b/Workshop/GuidedWalkthough/images/05-SendOrderConfirmation/additional-information.png differ diff --git a/Workshop/GuidedWalkthough/images/05-SendOrderConfirmation/configure-startup-projects.png b/Workshop/GuidedWalkthough/images/05-SendOrderConfirmation/configure-startup-projects.png new file mode 100644 index 0000000..1647c6e Binary files /dev/null and b/Workshop/GuidedWalkthough/images/05-SendOrderConfirmation/configure-startup-projects.png differ diff --git a/Workshop/GuidedWalkthough/images/05-SendOrderConfirmation/configure-your-new-project.png b/Workshop/GuidedWalkthough/images/05-SendOrderConfirmation/configure-your-new-project.png new file mode 100644 index 0000000..7daee7c Binary files /dev/null and b/Workshop/GuidedWalkthough/images/05-SendOrderConfirmation/configure-your-new-project.png differ diff --git a/Workshop/GuidedWalkthough/images/05-SendOrderConfirmation/connect-demail-domains.png b/Workshop/GuidedWalkthough/images/05-SendOrderConfirmation/connect-demail-domains.png new file mode 100644 index 0000000..0ca8223 Binary files /dev/null and b/Workshop/GuidedWalkthough/images/05-SendOrderConfirmation/connect-demail-domains.png differ diff --git a/Workshop/GuidedWalkthough/images/05-SendOrderConfirmation/create-comunication-service.png b/Workshop/GuidedWalkthough/images/05-SendOrderConfirmation/create-comunication-service.png new file mode 100644 index 0000000..07146a8 Binary files /dev/null and b/Workshop/GuidedWalkthough/images/05-SendOrderConfirmation/create-comunication-service.png differ diff --git a/Workshop/GuidedWalkthough/images/05-SendOrderConfirmation/domain-provisioned.png b/Workshop/GuidedWalkthough/images/05-SendOrderConfirmation/domain-provisioned.png new file mode 100644 index 0000000..24886dc Binary files /dev/null and b/Workshop/GuidedWalkthough/images/05-SendOrderConfirmation/domain-provisioned.png differ diff --git a/Workshop/GuidedWalkthough/images/05-SendOrderConfirmation/email-communication-create-review.png b/Workshop/GuidedWalkthough/images/05-SendOrderConfirmation/email-communication-create-review.png new file mode 100644 index 0000000..02bbdb1 Binary files /dev/null and b/Workshop/GuidedWalkthough/images/05-SendOrderConfirmation/email-communication-create-review.png differ diff --git a/Workshop/GuidedWalkthough/images/05-SendOrderConfirmation/email-communication-create.png b/Workshop/GuidedWalkthough/images/05-SendOrderConfirmation/email-communication-create.png new file mode 100644 index 0000000..752a445 Binary files /dev/null and b/Workshop/GuidedWalkthough/images/05-SendOrderConfirmation/email-communication-create.png differ diff --git a/Workshop/GuidedWalkthough/images/05-SendOrderConfirmation/email-communication-overview.png b/Workshop/GuidedWalkthough/images/05-SendOrderConfirmation/email-communication-overview.png new file mode 100644 index 0000000..c1e4371 Binary files /dev/null and b/Workshop/GuidedWalkthough/images/05-SendOrderConfirmation/email-communication-overview.png differ diff --git a/Workshop/GuidedWalkthough/images/05-SendOrderConfirmation/email-communication-search.png b/Workshop/GuidedWalkthough/images/05-SendOrderConfirmation/email-communication-search.png new file mode 100644 index 0000000..fe26e65 Binary files /dev/null and b/Workshop/GuidedWalkthough/images/05-SendOrderConfirmation/email-communication-search.png differ diff --git a/Workshop/GuidedWalkthough/images/05-SendOrderConfirmation/provision-domains.png b/Workshop/GuidedWalkthough/images/05-SendOrderConfirmation/provision-domains.png new file mode 100644 index 0000000..af42dda Binary files /dev/null and b/Workshop/GuidedWalkthough/images/05-SendOrderConfirmation/provision-domains.png differ diff --git a/Workshop/GuidedWalkthough/images/06-InitializeShipment/add-new-item.png b/Workshop/GuidedWalkthough/images/06-InitializeShipment/add-new-item.png new file mode 100644 index 0000000..0b34e95 Binary files /dev/null and b/Workshop/GuidedWalkthough/images/06-InitializeShipment/add-new-item.png differ diff --git a/Workshop/GuidedWalkthough/images/06-ReserveInventory/additional-information.png b/Workshop/GuidedWalkthough/images/06-ReserveInventory/additional-information.png new file mode 100644 index 0000000..43f2cc1 Binary files /dev/null and b/Workshop/GuidedWalkthough/images/06-ReserveInventory/additional-information.png differ diff --git a/Workshop/GuidedWalkthough/images/06-ReserveInventory/configure-startup-projects.png b/Workshop/GuidedWalkthough/images/06-ReserveInventory/configure-startup-projects.png new file mode 100644 index 0000000..b800bfb Binary files /dev/null and b/Workshop/GuidedWalkthough/images/06-ReserveInventory/configure-startup-projects.png differ diff --git a/Workshop/GuidedWalkthough/images/06-ReserveInventory/configure-your-new-project.png b/Workshop/GuidedWalkthough/images/06-ReserveInventory/configure-your-new-project.png new file mode 100644 index 0000000..a92edb0 Binary files /dev/null and b/Workshop/GuidedWalkthough/images/06-ReserveInventory/configure-your-new-project.png differ diff --git a/Workshop/GuidedWalkthough/images/06-ReserveInventory/create-event-hub.png b/Workshop/GuidedWalkthough/images/06-ReserveInventory/create-event-hub.png new file mode 100644 index 0000000..8e3c55d Binary files /dev/null and b/Workshop/GuidedWalkthough/images/06-ReserveInventory/create-event-hub.png differ diff --git a/Workshop/GuidedWalkthough/images/06-ReserveInventory/event-hub-namespace.png b/Workshop/GuidedWalkthough/images/06-ReserveInventory/event-hub-namespace.png new file mode 100644 index 0000000..ad48aba Binary files /dev/null and b/Workshop/GuidedWalkthough/images/06-ReserveInventory/event-hub-namespace.png differ diff --git a/Workshop/GuidedWalkthough/images/07-UpdatePurchaseStatusOnInventoryReserve/add-new-item.png b/Workshop/GuidedWalkthough/images/07-UpdatePurchaseStatusOnInventoryReserve/add-new-item.png new file mode 100644 index 0000000..136aeeb Binary files /dev/null and b/Workshop/GuidedWalkthough/images/07-UpdatePurchaseStatusOnInventoryReserve/add-new-item.png differ diff --git a/Workshop/GuidedWalkthough/images/09-InventoryUpdatedNotice/add-new-item.png b/Workshop/GuidedWalkthough/images/09-InventoryUpdatedNotice/add-new-item.png new file mode 100644 index 0000000..1665df8 Binary files /dev/null and b/Workshop/GuidedWalkthough/images/09-InventoryUpdatedNotice/add-new-item.png differ diff --git a/Workshop/GuidedWalkthough/images/09-InventoryUpdatedNotice/create-event-hub.png b/Workshop/GuidedWalkthough/images/09-InventoryUpdatedNotice/create-event-hub.png new file mode 100644 index 0000000..2bee0c7 Binary files /dev/null and b/Workshop/GuidedWalkthough/images/09-InventoryUpdatedNotice/create-event-hub.png differ diff --git a/Workshop/GuidedWalkthough/images/10-ProductAvailabilityUpdate/add-new-item.png b/Workshop/GuidedWalkthough/images/10-ProductAvailabilityUpdate/add-new-item.png new file mode 100644 index 0000000..c766c03 Binary files /dev/null and b/Workshop/GuidedWalkthough/images/10-ProductAvailabilityUpdate/add-new-item.png differ diff --git a/Workshop/GuidedWalkthough/images/10-ProductAvailabilityUpdate/new-azure-function.png b/Workshop/GuidedWalkthough/images/10-ProductAvailabilityUpdate/new-azure-function.png new file mode 100644 index 0000000..4756d56 Binary files /dev/null and b/Workshop/GuidedWalkthough/images/10-ProductAvailabilityUpdate/new-azure-function.png differ diff --git a/Workshop/GuidedWalkthough/images/13-OrderFulfillment/postman.png b/Workshop/GuidedWalkthough/images/13-OrderFulfillment/postman.png new file mode 100644 index 0000000..c410d29 Binary files /dev/null and b/Workshop/GuidedWalkthough/images/13-OrderFulfillment/postman.png differ diff --git a/Workshop/Solution/ConsoleApp1/ConsoleApp1.csproj b/Workshop/Solution/ConsoleApp1/ConsoleApp1.csproj deleted file mode 100644 index 7101fab..0000000 --- a/Workshop/Solution/ConsoleApp1/ConsoleApp1.csproj +++ /dev/null @@ -1,26 +0,0 @@ - - - - Exe - net6.0 - enable - enable - - - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - - - - - - diff --git a/Workshop/Solution/ConsoleApp1/Program.cs b/Workshop/Solution/ConsoleApp1/Program.cs deleted file mode 100644 index 3751555..0000000 --- a/Workshop/Solution/ConsoleApp1/Program.cs +++ /dev/null @@ -1,2 +0,0 @@ -// See https://aka.ms/new-console-template for more information -Console.WriteLine("Hello, World!"); diff --git a/Workshop/Solution/Core/Core.Azure.Functions/Core.Azure.Functions.csproj b/Workshop/Solution/Core/Core.Azure.Functions/Core.Azure.Functions.csproj deleted file mode 100644 index 5b8d92c..0000000 --- a/Workshop/Solution/Core/Core.Azure.Functions/Core.Azure.Functions.csproj +++ /dev/null @@ -1,14 +0,0 @@ - - - - net6.0 - enable - enable - BuildingBlocks.Azure.Functions - - - - - - - \ No newline at end of file diff --git a/Workshop/Solution/Core/Core.Azure.Functions/HttpRequestDataExtensions.cs b/Workshop/Solution/Core/Core.Azure.Functions/HttpRequestDataExtensions.cs deleted file mode 100644 index 7c7785b..0000000 --- a/Workshop/Solution/Core/Core.Azure.Functions/HttpRequestDataExtensions.cs +++ /dev/null @@ -1,173 +0,0 @@ -using Microsoft.Azure.Functions.Worker.Http; -using System.Collections.Specialized; -using System.Web; -using TaleLearnCode; - -namespace BuildingBlocks.Azure.Functions; - -public static class HttpRequestDataExtensions -{ - - /// - /// Creates a created (HTTP status code 201) response with the Content-Location header value. - /// - /// The for this response. - /// The routing information for the content-location. - /// A with a status of Created (201) and a Content-Location header value. - public static HttpResponseData CreatedResponse(this HttpRequestData httpRequestData, string route) - { - return httpRequestData.CreateCreatedResponse(GetContentLocation(route)); - } - - /// - /// Gets the content location of the specified route. - /// - /// The route of the content location. - /// A string representing the content-location value. - public static string GetContentLocation(this string route) - { - - string httpProtocol; - try - { - string? httpStatus = Environment.GetEnvironmentVariable("HTTPS"); - if (!string.IsNullOrWhiteSpace(httpStatus) && httpStatus.ToLower() == "on") - httpProtocol = "https"; - else - httpProtocol = "http"; - } - catch - { - httpProtocol = "http"; - } - - if (route.StartsWith('/')) - return $"{httpProtocol}://{Environment.GetEnvironmentVariable("WEBSITE_HOSTNAME")}{route}"; - else - return $"{httpProtocol}://{Environment.GetEnvironmentVariable("WEBSITE_HOSTNAME")}/{route}"; - } - - /// - /// Gets the flag of whether to create an object if it does not already exist from the query string. - /// - /// True if the CreateIfNotExists query string value is present and set to true; otherwise, false. - public static bool CreteIfNotExists(this HttpRequestData httpRequestData) - { - try - { - NameValueCollection queryValues = HttpUtility.ParseQueryString(httpRequestData.Url.Query); - if (queryValues.HasKeys() && queryValues.AllKeys.Contains("CreateIfNotExists")) - return (queryValues["CreteIfNotExists"]?.ToLowerInvariant() == "true"); - else - return false; - } - catch (Exception) - { - return false; - } - } - - /// - /// Creates a created (HTTP status code 201) response with appropriate header values. - /// - /// The to use for building the response object. - /// The location of the created object. - /// Name of the type of object that was created. - /// Identifier of the created object. - /// A representing the response to send back after an object has been created. - public static HttpResponseData CreateCreatedResponse(this HttpRequestData httpRequestData, string contentLocation, string objectIdentifierName, string objectIdentifierValue) - { - HttpResponseData response = httpRequestData.CreateCreatedResponse(contentLocation); - response.Headers.Add(objectIdentifierName, objectIdentifierValue); - return response; - } - - /// - /// Gets the value of the specified request query string element. - /// - /// The containing the request information. - /// The key of the query string element to get. - /// A string> representing the value of the specified query string element. - public static string? GetQueryStringValue(this HttpRequestData httpRequestData, string key) - { - if (TryGetQueryStringValue(httpRequestData, key, out var queryStringValue)) - return queryStringValue; - else - return default; - } - - /// - /// Attempts to get the value of a specified request query string element. - /// - /// The containing the request information. - /// The key of the query string element to attempt to get. - /// The resulting value of the attempted query string element. - /// True if the specified query string value is returned. - public static bool TryGetQueryStringValue(this HttpRequestData httpRequestData, string key, out string? value) - { - value = null; - key = key.ToLowerInvariant(); - if (TryGetQueryStringValues(httpRequestData, out Dictionary values) && values.ContainsKey(key)) - value = values[key]; - return value != null; - } - - /// - /// Attempts to get the values from the requests' query string. - /// - /// The containing the request information. - /// The resulting dictionary of query string values. - /// True if the query string values were returned (they were present); otherwise, false. - public static bool TryGetQueryStringValues(this HttpRequestData httpRequestData, out Dictionary queryStringValues) - { - queryStringValues = GetQueryStringValues(httpRequestData); - return queryStringValues != null; - } - - /// - /// Gets the values from the request's query string. - /// - /// The containing the request information. - /// representing the query string values for the value. - public static Dictionary GetQueryStringValues(this HttpRequestData httpRequestData) - { - if (!string.IsNullOrWhiteSpace(httpRequestData.Url.Query)) - { - NameValueCollection queryValues = HttpUtility.ParseQueryString(httpRequestData.Url.Query); - if (queryValues.Count >= 1) - { - Dictionary result = new(); - foreach (string key in queryValues.Keys) - { - string? queryStringValue = queryValues[key]; - if (!string.IsNullOrWhiteSpace(queryStringValue)) - result.TryAdd(key.ToLowerInvariant(), queryStringValue); - } - return result; - } - else - return new(); - } - else - return new(); - } - - public static bool GetBooleanQueryStringValue(this HttpRequestData httpRequestData, string key, bool defaultValue) - { - string? queryStringValue = httpRequestData.GetQueryStringValue(key); - if (queryStringValue is not null) - if (queryStringValue.ToLowerInvariant() == "true") - return true; - else if (queryStringValue.ToLowerInvariant() == "false") - return false; - else - return defaultValue; - else - return defaultValue; - } - - public static int GetInt32QueryStringValue(this HttpRequestData httpRequestData, string key, int defaultValue = 0) - => int.TryParse(httpRequestData.GetQueryStringValue(key), out int result) ? result : defaultValue; - - -} \ No newline at end of file diff --git a/Workshop/Solution/Core/Core/Constants/ConfigurationKeys.cs b/Workshop/Solution/Core/Core/Constants/ConfigurationKeys.cs deleted file mode 100644 index cdea314..0000000 --- a/Workshop/Solution/Core/Core/Constants/ConfigurationKeys.cs +++ /dev/null @@ -1,20 +0,0 @@ -namespace BuildingBlocks.Core.Constants; - -public static class ConfigurationKeys -{ - - public const string CosmosKey = "Cosmos:Key"; - public const string CosmosUri = "Cosmos:Uri"; - - public const string PMCosmosDatabaseId = "ProductManagement:Cosmos:DatabaseId"; - public const string PMMetadataContainerId = "ProductManagement:Cosmos:Metadata:ContainerId"; - public const string PMMetadataPartitionKey = "ProductManagement:Cosmos:Metadata:PartitionKey"; - public const string PMProductsByAvailabilityContainerId = "ProductManagement:Cosmos:ProductsByAvailability:ContainerId"; - public const string PMProductsByAvailabilityPartitionKey = "ProductManagement:Cosmos:ProductsByAvailability:PartitionKey"; - public const string PMProductsByThemeContainerId = "ProductManagement:Cosmos:ProductsByTheme:ContainerId"; - public const string PMProductsByThemePartitionKey = "ProductManagement:Cosmos:ProductsByTheme:PartitionKey"; - - public const string OrderPlacedQueueName = "ServiceBusQueue:OrderPlaced"; - - public const string OrderPlacedEventHub = "EventHubs:OrderPlaced"; -} \ No newline at end of file diff --git a/Workshop/Solution/Core/Core/Constants/EnvironmentVariables.cs b/Workshop/Solution/Core/Core/Constants/EnvironmentVariables.cs deleted file mode 100644 index 51986a5..0000000 --- a/Workshop/Solution/Core/Core/Constants/EnvironmentVariables.cs +++ /dev/null @@ -1,10 +0,0 @@ -namespace BuildingBlocks.Core.Constants; - -public static class EnvironmentVariables -{ - - public const string Base = "OrderProcessingSystem-"; - public const string AppConfig = $"{Base}AppConfig"; - public const string CosmosKey = $"{Base}CosmosKey"; - -} \ No newline at end of file diff --git a/Workshop/Solution/Core/Core/Core.csproj b/Workshop/Solution/Core/Core/Core.csproj deleted file mode 100644 index 909c8f4..0000000 --- a/Workshop/Solution/Core/Core/Core.csproj +++ /dev/null @@ -1,16 +0,0 @@ - - - - net6.0 - enable - enable - BuildingBlocks.Core - - - - - - - - - diff --git a/Workshop/Solution/Core/Core/EventMessage/OrderItemMessage.cs b/Workshop/Solution/Core/Core/EventMessage/OrderItemMessage.cs deleted file mode 100644 index d7c610b..0000000 --- a/Workshop/Solution/Core/Core/EventMessage/OrderItemMessage.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace BuildingBlocks.Core.EventMessage; - -public class OrderItemMessage -{ - public string OrderId { get; set; } = null!; - public int OrderItemId { get; set; } - public string ProductId { get; set; } = null!; - public int Quantity { get; set; } -} \ No newline at end of file diff --git a/Workshop/Solution/Core/Core/EventMessage/OrderPlacedMessage.cs b/Workshop/Solution/Core/Core/EventMessage/OrderPlacedMessage.cs deleted file mode 100644 index d8383bf..0000000 --- a/Workshop/Solution/Core/Core/EventMessage/OrderPlacedMessage.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace BuildingBlocks.Core.EventMessage; - -public class OrderPlacedMessage -{ - public string OrderId { get; init; } = null!; - public int CustomerId { get; init; } - public List Items { get; init; } = new List(); -} \ No newline at end of file diff --git a/Workshop/Solution/Core/Core/Exceptions/BatchSizeTooLargeException.cs b/Workshop/Solution/Core/Core/Exceptions/BatchSizeTooLargeException.cs deleted file mode 100644 index 79717f6..0000000 --- a/Workshop/Solution/Core/Core/Exceptions/BatchSizeTooLargeException.cs +++ /dev/null @@ -1,17 +0,0 @@ -using System.Runtime.Serialization; - -namespace BuildingBlocks.Core.Exceptions; - -[Serializable] -public class BatchSizeTooLargeException : Exception -{ - - public BatchSizeTooLargeException() : base("Message is too large for the batch and cannot be sent.") { } - - public BatchSizeTooLargeException(string message) : base(message) { } - - public BatchSizeTooLargeException(string message, Exception innerException) : base(message, innerException) { } - - public BatchSizeTooLargeException(SerializationInfo info, StreamingContext context) : base(info, context) { } - -} \ No newline at end of file diff --git a/Workshop/Solution/Core/Core/ServicesBase.cs b/Workshop/Solution/Core/Core/ServicesBase.cs deleted file mode 100644 index 8218c69..0000000 --- a/Workshop/Solution/Core/Core/ServicesBase.cs +++ /dev/null @@ -1,50 +0,0 @@ -using Azure.Messaging.EventHubs.Producer; -using Azure.Messaging.ServiceBus; -using BuildingBlocks.Core.Exceptions; -using System.Text; - -namespace BuildingBlocks.Core; - -public abstract class ServicesBase -{ - - protected static async Task SendMessageToEventHubAsync( - string connectionStringEnvironmentVariableName, - string eventHubName, - string message) - { - - await using EventHubProducerClient producerClient = new(Environment.GetEnvironmentVariable(connectionStringEnvironmentVariableName), eventHubName); - - using EventDataBatch eventDataBatch = await producerClient.CreateBatchAsync(); - if (!eventDataBatch.TryAdd(new Azure.Messaging.EventHubs.EventData(Encoding.UTF8.GetBytes(message)))) - throw new BatchSizeTooLargeException(); - - try - { - await producerClient.SendAsync(eventDataBatch); - } - finally - { - await producerClient.CloseAsync(); - } - } - - protected static ServiceBusClient GetServiceBusClient(string connectionStringEnvironmentVariableName) - => new(Environment.GetEnvironmentVariable(connectionStringEnvironmentVariableName)!); - - protected static async Task SendSessionMessageToServiceBusAsync( - ServiceBusClient serviceBusClient, - string queueName, - string message, - string sessionId) - { - ServiceBusSender serviceBusSender = serviceBusClient.CreateSender(queueName); - ServiceBusMessage serviceBusMessage = new(Encoding.UTF8.GetBytes(message)) - { - SessionId = sessionId - }; - await serviceBusSender.SendMessageAsync(serviceBusMessage); - } - -} \ No newline at end of file diff --git a/Workshop/Solution/Inventory Management/IM.Database/IM.Database.sqlproj b/Workshop/Solution/Inventory Management/IM.Database/IM.Database.sqlproj deleted file mode 100644 index 1a668af..0000000 --- a/Workshop/Solution/Inventory Management/IM.Database/IM.Database.sqlproj +++ /dev/null @@ -1,79 +0,0 @@ - - - - Debug - AnyCPU - IM.Database - 2.0 - 4.1 - {f0225ab4-2836-492a-b0e2-6a0b0667e555} - Microsoft.Data.Tools.Schema.Sql.SqlAzureV12DatabaseSchemaProvider - Database - - - IM.Database - IM.Database - 1033, CI - BySchemaAndSchemaType - True - v4.7.2 - CS - Properties - False - True - True - Inventory - - - bin\Release\ - $(MSBuildProjectName).sql - False - pdbonly - true - false - true - prompt - 4 - - - bin\Debug\ - $(MSBuildProjectName).sql - false - true - full - false - true - true - prompt - 4 - True - - - 11.0 - - True - 11.0 - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/Workshop/Solution/Inventory Management/IM.Database/Inventory.sql b/Workshop/Solution/Inventory Management/IM.Database/Inventory.sql deleted file mode 100644 index 8dc3ab9..0000000 --- a/Workshop/Solution/Inventory Management/IM.Database/Inventory.sql +++ /dev/null @@ -1 +0,0 @@ -CREATE SCHEMA Inventory \ No newline at end of file diff --git a/Workshop/Solution/Inventory Management/IM.Database/Scripts/Populate/Populate_Product.sql b/Workshop/Solution/Inventory Management/IM.Database/Scripts/Populate/Populate_Product.sql deleted file mode 100644 index eeffdf1..0000000 --- a/Workshop/Solution/Inventory Management/IM.Database/Scripts/Populate/Populate_Product.sql +++ /dev/null @@ -1,71 +0,0 @@ -MERGE INTO Inventory.Product AS TARGET -USING (VALUES ('10255', 'Assembly Square'), - ('10265', 'Ford Mustang'), - ('10266', 'NASA Apollo 11 Lunar Lander'), - ('10270', 'Bookshop'), - ('10273', 'Hauned House'), - ('10274', 'Ghostbusters™ ECTO-1'), - ('10276', 'Colosseum'), - ('10278', 'Police Station'), - ('10280', 'Flower Bouquet'), - ('10281', 'Bonsai Tree'), - ('10283', 'NASA Space Shuttle Discovery'), - ('10290', 'Pickup Truck'), - ('10292', 'The Friends Apartments'), - ('10293', 'Santa''s Visit'), - ('10294', 'LEGO® Titanic'), - ('10295', 'Porsche 911'), - ('10297', 'Boutique Hotel'), - ('10298', 'Vespa 125'), - ('10299', 'Real Madrid – Santiago Bernabéu Stadium'), - ('10300', 'Back to the Future Time Machine'), - ('10302', 'Optimus Prime'), - ('10303', 'Loop Coaster'), - ('10304', 'Chevrolet Camaro Z28'), - ('10305', 'Bonsai Tree'), - ('10306', 'Atari® 2600'), - ('10307', 'Eiffel tower'), - ('10309', 'Succulents'), - ('10312', 'Jazz Club'), - ('10314', 'Corvette'), - ('10315', 'Tranquil Garden'), - ('10316', 'THE LORD OF THE RINGS: RIVENDELL™'), - ('10317', 'Land Rover Classic Defender 90'), - ('10320', 'Eldorado Fortress'), - ('10321', 'Corvette'), - ('10323', 'PAC-MAN Arcade'), - ('10497', 'Galaxy Explorer'), - ('21028', 'New York City'), - ('21034', 'London'), - ('21042', 'Statue of Liberty'), - ('21044', 'Paris'), - ('21054', 'The White House'), - ('21056', 'Taj Mahal'), - ('21057', 'Statue of Liberty'), - ('21058', 'Great Pyramid of Giza'), - ('21060', 'Himeji Castle'), - ('21318', 'Tree House'), - ('21323', 'Grand Piano'), - ('21325', 'Medieval Blacksmith'), - ('21326', 'Winnie the Pooh'), - ('21327', 'Typewriter'), - ('21330', 'LEGO® Ideas Home Alone'), - ('21332', 'The Globe'), - ('21334', 'Jazz Quartet'), - ('21335', 'Motorized Lighthouse'), - ('21336', 'The Office'), - ('21337', 'Table Football'), - ('21338', 'A-Frame Cabin'), - ('21339', 'BTS Dynamite'), - ('21340', 'Tales of the Space Age'), - ('21341', 'Disney Hocus Pocus: The Sanderson Sisters'' Cottage'), - ('40220', 'London Bus'), - ('40517', 'Vespa'), - ('40518', 'High Speed Train')) -AS SOURCE (ProductId, ProductName) -ON TARGET.ProductId = SOURCE.ProductId -WHEN MATCHED THEN UPDATE SET TARGET.ProductName = SOURCE.ProductName -WHEN NOT MATCHED THEN INSERT (ProductId, - ProductName) - VALUES (SOURCE.ProductId, - SOURCE.ProductName); \ No newline at end of file diff --git a/Workshop/Solution/Inventory Management/IM.Database/Scripts/Populate/Populate_ProductInventory.sql b/Workshop/Solution/Inventory Management/IM.Database/Scripts/Populate/Populate_ProductInventory.sql deleted file mode 100644 index d83b608..0000000 --- a/Workshop/Solution/Inventory Management/IM.Database/Scripts/Populate/Populate_ProductInventory.sql +++ /dev/null @@ -1,84 +0,0 @@ -SET IDENTITY_INSERT Inventory.ProductInventory ON -GO - -MERGE INTO Inventory.ProductInventory AS TARGET -USING (VALUES ( 1, '10255', 5, 20), - ( 2, '10265', 5, 0), - ( 3, '10266', 5, 20), - ( 4, '10270', 5, 20), - ( 5, '10273', 5, 2), - ( 6, '10274', 5, 20), - ( 7, '10276', 5, 20), - ( 8, '10278', 5, 20), - ( 9, '10280', 5, 20), - (10, '10281', 5, 20), - (11, '10283', 5, 20), - (12, '10290', 5, 20), - (13, '10292', 5, 20), - (14, '10293', 5, 20), - (15, '10294', 5, 20), - (16, '10295', 5, 0), - (17, '10297', 5, 20), - (18, '10298', 5, 20), - (19, '10299', 5, 20), - (20, '10300', 5, 20), - (21, '10302', 5, 0), - (22, '10303', 5, 20), - (23, '10304', 5, 20), - (24, '10305', 5, 20), - (25, '10306', 5, 20), - (26, '10307', 5, 20), - (27, '10309', 5, 20), - (28, '10312', 5, 20), - (29, '10314', 5, 20), - (30, '10315', 5, 0), - (31, '10316', 5, 20), - (32, '10317', 5, 20), - (33, '10320', 5, 20), - (34, '10321', 5, 0), - (35, '10323', 5, 20), - (36, '10497', 5, 20), - (37, '21028', 5, 20), - (38, '21034', 5, 20), - (39, '21042', 5, 20), - (40, '21044', 5, 20), - (41, '21054', 5, 20), - (42, '21056', 5, 20), - (43, '21057', 5, 20), - (44, '21058', 5, 20), - (45, '21060', 5, 0), - (46, '21318', 5, 20), - (47, '21323', 5, 20), - (48, '21325', 5, 20), - (49, '21326', 5, 20), - (50, '21327', 5, 20), - (51, '21330', 5, 20), - (52, '21332', 5, 20), - (53, '21334', 5, 20), - (54, '21335', 5, 20), - (55, '21336', 5, 20), - (56, '21337', 5, 20), - (57, '21338', 5, 20), - (58, '21339', 5, 20), - (59, '21340', 5, 20), - (60, '21341', 5, 0), - (61, '40220', 5, 20), - (62, '40517', 5, 20), - (63, '40518', 5, 20)) -AS SOURCE (ProductInventoryId, ProductId, ProductInventoryActionId, InventoryCredit) -ON TARGET.ProductInventoryId = SOURCE.ProductInventoryId -WHEN MATCHED THEN UPDATE SET ProductId = SOURCE.ProductId, - ProductInventoryActionId = SOURCE.ProductInventoryActionId, - InventoryCredit = SOURCE.InventoryCredit -WHEN NOT MATCHED THEN INSERT (ProductInventoryId, - ProductId, - ProductInventoryActionId, - InventoryCredit) - VALUES (SOURCE.ProductInventoryId, - SOURCE.ProductId, - SOURCE.ProductInventoryActionId, - SOURCE.InventoryCredit) -WHEN NOT MATCHED BY SOURCE THEN DELETE; - -SET IDENTITY_INSERT Inventory.ProductInventory OFF -GO \ No newline at end of file diff --git a/Workshop/Solution/Inventory Management/IM.Database/Scripts/Populate/Populate_ProductInventoryAction.sql b/Workshop/Solution/Inventory Management/IM.Database/Scripts/Populate/Populate_ProductInventoryAction.sql deleted file mode 100644 index 0b31f6b..0000000 --- a/Workshop/Solution/Inventory Management/IM.Database/Scripts/Populate/Populate_ProductInventoryAction.sql +++ /dev/null @@ -1,14 +0,0 @@ -MERGE Inventory.ProductInventoryAction AS TARGET -USING (VALUES (1, 'Stock Received'), - (2, 'Reserved for Order'), - (3, 'Order Item Shipped'), - (4, 'Order Item Cancelled'), - (5, 'Inventory Adjustment')) -AS SOURCE (ProductInventoryActionId, ProductInventoryActionName) -ON TARGET.ProductInventoryActionId = SOURCE.ProductInventoryActionId -WHEN MATCHED THEN UPDATE SET TARGET.ProductInventoryActionName = SOURCE.ProductInventoryActionName -WHEN NOT MATCHED THEN INSERT (ProductInventoryActionId, - ProductInventoryActionName) - VALUES (SOURCE.ProductInventoryActionId, - SOURCE.ProductInventoryActionName); -GO \ No newline at end of file diff --git a/Workshop/Solution/Inventory Management/IM.Database/Scripts/Script.PostDeployment.sql b/Workshop/Solution/Inventory Management/IM.Database/Scripts/Script.PostDeployment.sql deleted file mode 100644 index 846ebd4..0000000 --- a/Workshop/Solution/Inventory Management/IM.Database/Scripts/Script.PostDeployment.sql +++ /dev/null @@ -1,3 +0,0 @@ -:r .\Populate\Populate_ProductInventoryAction.sql -:r .\Populate\Populate_Product.sql -:r .\Populate\Populate_ProductInventory.sql \ No newline at end of file diff --git a/Workshop/Solution/Inventory Management/IM.Database/Tables/Product.sql b/Workshop/Solution/Inventory Management/IM.Database/Tables/Product.sql deleted file mode 100644 index 234fe43..0000000 --- a/Workshop/Solution/Inventory Management/IM.Database/Tables/Product.sql +++ /dev/null @@ -1,6 +0,0 @@ -CREATE TABLE Inventory.Product -( - ProductId CHAR(5) NOT NULL, - ProductName NVARCHAR(100) NOT NULL, - CONSTRAINT pkcProduct PRIMARY KEY CLUSTERED (ProductId) -) \ No newline at end of file diff --git a/Workshop/Solution/Inventory Management/IM.Database/Tables/ProductInventory.sql b/Workshop/Solution/Inventory Management/IM.Database/Tables/ProductInventory.sql deleted file mode 100644 index 4c8c7b0..0000000 --- a/Workshop/Solution/Inventory Management/IM.Database/Tables/ProductInventory.sql +++ /dev/null @@ -1,13 +0,0 @@ -CREATE TABLE Inventory.ProductInventory -( - ProductInventoryId INT NOT NULL IDENTITY(1,1), - ProductId CHAR(5) NOT NULL, - ProductInventoryActionId INT NOT NULL, - ActionDateTime DATETIME2 NOT NULL CONSTRAINT dfProductInventory_ActionDateTime DEFAULT(GETUTCDATE()), - InventoryCredit INT NOT NULL CONSTRAINT dfProductInventory_InventoryCredit DEFAULT(0), - InventoryDebit INT NOT NULL CONSTRAINT dfProductInventory_InventoryDebit DEFAULT(0), - OrderNumber VARCHAR(100) NULL, - CONSTRAINT pkcProductInventory PRIMARY KEY CLUSTERED (ProductInventoryId), - CONSTRAINT fkProductInventory_Product FOREIGN KEY (ProductId) REFERENCES Inventory.Product (ProductId), - CONSTRAINT fkProductInventory_ProductInventoryAction FOREIGN KEY (ProductInventoryActionId) REFERENCES Inventory.ProductInventoryAction (ProductInventoryActionId) -) \ No newline at end of file diff --git a/Workshop/Solution/Inventory Management/IM.Database/Tables/ProductInventoryAction.sql b/Workshop/Solution/Inventory Management/IM.Database/Tables/ProductInventoryAction.sql deleted file mode 100644 index f96e8e2..0000000 --- a/Workshop/Solution/Inventory Management/IM.Database/Tables/ProductInventoryAction.sql +++ /dev/null @@ -1,6 +0,0 @@ -CREATE TABLE Inventory.ProductInventoryAction -( - ProductInventoryActionId INT NOT NULL, - ProductInventoryActionName NVARCHAR(100), - CONSTRAINT pkcProductInventoryAction PRIMARY KEY CLUSTERED (ProductInventoryActionId) -) \ No newline at end of file diff --git a/Workshop/Solution/Inventory Management/IM.Services/IM.Services.csproj b/Workshop/Solution/Inventory Management/IM.Services/IM.Services.csproj deleted file mode 100644 index b37fd73..0000000 --- a/Workshop/Solution/Inventory Management/IM.Services/IM.Services.csproj +++ /dev/null @@ -1,14 +0,0 @@ - - - - net6.0 - enable - enable - BuildingBricks.IM.Services - - - - - - - \ No newline at end of file diff --git a/Workshop/Solution/Inventory Management/IM.Services/InventoryManagementContext.cs b/Workshop/Solution/Inventory Management/IM.Services/InventoryManagementContext.cs deleted file mode 100644 index 6a522b5..0000000 --- a/Workshop/Solution/Inventory Management/IM.Services/InventoryManagementContext.cs +++ /dev/null @@ -1,79 +0,0 @@ -using BuildingBricks.IM.Models; -using Microsoft.EntityFrameworkCore; - -namespace BuildingBricks.IM -{ - public partial class InventoryManagementContext : DbContext - { - public InventoryManagementContext() { } - - public InventoryManagementContext(DbContextOptions options) : base(options) { } - - public virtual DbSet Products { get; set; } = null!; - public virtual DbSet ProductInventories { get; set; } = null!; - public virtual DbSet ProductInventoryActions { get; set; } = null!; - - protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) - { - if (!optionsBuilder.IsConfigured) - { - optionsBuilder.UseSqlServer(Environment.GetEnvironmentVariable("OrderProcessingSystem-IM-DatabaseConnectionString")!); - } - } - - protected override void OnModelCreating(ModelBuilder modelBuilder) - { - modelBuilder.Entity(entity => - { - entity.ToTable("Product", "Inventory"); - - entity.Property(e => e.ProductId) - .HasMaxLength(5) - .IsUnicode(false) - .IsFixedLength(); - - entity.Property(e => e.ProductName).HasMaxLength(100); - }); - - modelBuilder.Entity(entity => - { - entity.ToTable("ProductInventory", "Inventory"); - - entity.Property(e => e.ActionDateTime).HasDefaultValueSql("(getutcdate())"); - - entity.Property(e => e.OrderNumber) - .HasMaxLength(100) - .IsUnicode(false); - - entity.Property(e => e.ProductId) - .HasMaxLength(5) - .IsUnicode(false) - .IsFixedLength(); - - entity.HasOne(d => d.Product) - .WithMany(p => p.ProductInventories) - .HasForeignKey(d => d.ProductId) - .OnDelete(DeleteBehavior.ClientSetNull) - .HasConstraintName("fkProductInventory_Product"); - - entity.HasOne(d => d.ProductInventoryAction) - .WithMany(p => p.ProductInventories) - .HasForeignKey(d => d.ProductInventoryActionId) - .OnDelete(DeleteBehavior.ClientSetNull) - .HasConstraintName("fkProductInventory_ProductInventoryAction"); - }); - - modelBuilder.Entity(entity => - { - entity.ToTable("ProductInventoryAction", "Inventory"); - - entity.Property(e => e.ProductInventoryActionId).ValueGeneratedNever(); - - entity.Property(e => e.ProductInventoryActionName).HasMaxLength(100); - }); - - } - - } - -} \ No newline at end of file diff --git a/Workshop/Solution/Inventory Management/IM.Services/Models/Product.cs b/Workshop/Solution/Inventory Management/IM.Services/Models/Product.cs deleted file mode 100644 index 800d173..0000000 --- a/Workshop/Solution/Inventory Management/IM.Services/Models/Product.cs +++ /dev/null @@ -1,18 +0,0 @@ -using System; -using System.Collections.Generic; - -namespace BuildingBricks.IM.Models -{ - public partial class Product - { - public Product() - { - ProductInventories = new HashSet(); - } - - public string ProductId { get; set; } = null!; - public string ProductName { get; set; } = null!; - - public virtual ICollection ProductInventories { get; set; } - } -} diff --git a/Workshop/Solution/Inventory Management/IM.Services/Models/ProductInventory.cs b/Workshop/Solution/Inventory Management/IM.Services/Models/ProductInventory.cs deleted file mode 100644 index 4776b91..0000000 --- a/Workshop/Solution/Inventory Management/IM.Services/Models/ProductInventory.cs +++ /dev/null @@ -1,19 +0,0 @@ -using System; -using System.Collections.Generic; - -namespace BuildingBricks.IM.Models -{ - public partial class ProductInventory - { - public int ProductInventoryId { get; set; } - public string ProductId { get; set; } = null!; - public int ProductInventoryActionId { get; set; } - public DateTime ActionDateTime { get; set; } - public int InventoryCredit { get; set; } - public int InventoryDebit { get; set; } - public string? OrderNumber { get; set; } - - public virtual Product Product { get; set; } = null!; - public virtual ProductInventoryAction ProductInventoryAction { get; set; } = null!; - } -} diff --git a/Workshop/Solution/Inventory Management/IM.Services/Models/ProductInventoryAction.cs b/Workshop/Solution/Inventory Management/IM.Services/Models/ProductInventoryAction.cs deleted file mode 100644 index 4999d49..0000000 --- a/Workshop/Solution/Inventory Management/IM.Services/Models/ProductInventoryAction.cs +++ /dev/null @@ -1,18 +0,0 @@ -using System; -using System.Collections.Generic; - -namespace BuildingBricks.IM.Models -{ - public partial class ProductInventoryAction - { - public ProductInventoryAction() - { - ProductInventories = new HashSet(); - } - - public int ProductInventoryActionId { get; set; } - public string? ProductInventoryActionName { get; set; } - - public virtual ICollection ProductInventories { get; set; } - } -} diff --git a/Workshop/Solution/Notification Management/NM.Database/NM.Database.sqlproj b/Workshop/Solution/Notification Management/NM.Database/NM.Database.sqlproj deleted file mode 100644 index 3832b36..0000000 --- a/Workshop/Solution/Notification Management/NM.Database/NM.Database.sqlproj +++ /dev/null @@ -1,77 +0,0 @@ - - - - Debug - AnyCPU - NM.Database - 2.0 - 4.1 - {cf531b50-3aa2-44f8-a82b-d21f479ca781} - Microsoft.Data.Tools.Schema.Sql.SqlAzureV12DatabaseSchemaProvider - Database - - - NM.Database - NM.Database - 1033, CI - BySchemaAndSchemaType - True - v4.7.2 - CS - Properties - False - True - True - NotificationManagement - - - bin\Release\ - $(MSBuildProjectName).sql - False - pdbonly - true - false - true - prompt - 4 - - - bin\Debug\ - $(MSBuildProjectName).sql - false - true - full - false - true - true - prompt - 4 - True - - - 11.0 - - True - 11.0 - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/Workshop/Solution/Notification Management/NM.Database/NotificationManagement.sql b/Workshop/Solution/Notification Management/NM.Database/NotificationManagement.sql deleted file mode 100644 index b1c7b65..0000000 --- a/Workshop/Solution/Notification Management/NM.Database/NotificationManagement.sql +++ /dev/null @@ -1 +0,0 @@ -CREATE SCHEMA NotificationManagement \ No newline at end of file diff --git a/Workshop/Solution/Notification Management/NM.Database/Scripts/Populate/Populate_NotificationType.sql b/Workshop/Solution/Notification Management/NM.Database/Scripts/Populate/Populate_NotificationType.sql deleted file mode 100644 index 6a2d544..0000000 --- a/Workshop/Solution/Notification Management/NM.Database/Scripts/Populate/Populate_NotificationType.sql +++ /dev/null @@ -1,11 +0,0 @@ -MERGE INTO NotificationManagement.NotificationType AS TARGET -USING (VALUES (1, 'Order Confirmation'), - (2, 'Order Shipped'), - (3, 'Order Received')) -AS SOURCE (NotificationTypeId, NotificationTypeName) -ON TARGET.NotificationTypeId = SOURCE.NotificationTypeId -WHEN MATCHED THEN UPDATE SET TARGET.NotificationTypeName = SOURCE.NotificationTypeName -WHEN NOT MATCHED THEN INSERT (NotificationTypeId, - NotificationTypeName) - VALUES (SOURCE.NotificationTypeId, - SOURCE.NotificationTypeName); \ No newline at end of file diff --git a/Workshop/Solution/Notification Management/NM.Database/Scripts/Script.PostDeployment.sql b/Workshop/Solution/Notification Management/NM.Database/Scripts/Script.PostDeployment.sql deleted file mode 100644 index 8ea7097..0000000 --- a/Workshop/Solution/Notification Management/NM.Database/Scripts/Script.PostDeployment.sql +++ /dev/null @@ -1 +0,0 @@ -:r .\Populate\Populate_NotificationType.sql \ No newline at end of file diff --git a/Workshop/Solution/Notification Management/NM.Database/Tables/Customer.sql b/Workshop/Solution/Notification Management/NM.Database/Tables/Customer.sql deleted file mode 100644 index 3c7771f..0000000 --- a/Workshop/Solution/Notification Management/NM.Database/Tables/Customer.sql +++ /dev/null @@ -1,6 +0,0 @@ -CREATE TABLE NotificationManagement.Customer -( - CustomerId INT NOT NULL, - EmailAddress VARCHAR(255) NOT NULL, - CONSTRAINT pkcCustomer PRIMARY KEY CLUSTERED (CustomerId) -) \ No newline at end of file diff --git a/Workshop/Solution/Notification Management/NM.Database/Tables/NotificationLog.sql b/Workshop/Solution/Notification Management/NM.Database/Tables/NotificationLog.sql deleted file mode 100644 index 0e9ff60..0000000 --- a/Workshop/Solution/Notification Management/NM.Database/Tables/NotificationLog.sql +++ /dev/null @@ -1,10 +0,0 @@ -CREATE TABLE NotificationManagement.NotificationLog -( - NotificationLogId INT NOT NULL IDENTITY(1,1), - NotificationTypeId INT NOT NULL, - CustomerId INT NOT NULL, - SentDateTime DATETIME2 NOT NULL CONSTRAINT dfNotificationLog_SendDateTime DEFAULT(GETUTCDATE()), - CONSTRAINT pkcNotificationLog PRIMARY KEY CLUSTERED (NotificationLogId), - CONSTRAINT fkNotificationLog_NotificationType FOREIGN KEY (NotificationTypeId) REFERENCES NotificationManagement.NotificationType (NotificationTypeId), - CONSTRAINT fkNotificationLog_Customer FOREIGN KEY (CustomerId) REFERENCES NotificationManagement.Customer (CustomerId) -) \ No newline at end of file diff --git a/Workshop/Solution/Notification Management/NM.Database/Tables/NotificationType.sql b/Workshop/Solution/Notification Management/NM.Database/Tables/NotificationType.sql deleted file mode 100644 index b89de02..0000000 --- a/Workshop/Solution/Notification Management/NM.Database/Tables/NotificationType.sql +++ /dev/null @@ -1,6 +0,0 @@ -CREATE TABLE NotificationManagement.NotificationType -( - NotificationTypeId INT NOT NULL, - NotificationTypeName VARCHAR(100) NOT NULL, - CONSTRAINT pkcNotificationType PRIMARY KEY CLUSTERED (NotificationTypeId) -) \ No newline at end of file diff --git a/Workshop/Solution/Notification Management/NM.Services/Models/Customer.cs b/Workshop/Solution/Notification Management/NM.Services/Models/Customer.cs deleted file mode 100644 index 189f381..0000000 --- a/Workshop/Solution/Notification Management/NM.Services/Models/Customer.cs +++ /dev/null @@ -1,18 +0,0 @@ -using System; -using System.Collections.Generic; - -namespace BuildingBricks.NM.Models -{ - public partial class Customer - { - public Customer() - { - NotificationLogs = new HashSet(); - } - - public int CustomerId { get; set; } - public string EmailAddress { get; set; } = null!; - - public virtual ICollection NotificationLogs { get; set; } - } -} diff --git a/Workshop/Solution/Notification Management/NM.Services/Models/NotificationLog.cs b/Workshop/Solution/Notification Management/NM.Services/Models/NotificationLog.cs deleted file mode 100644 index 7ebf4ff..0000000 --- a/Workshop/Solution/Notification Management/NM.Services/Models/NotificationLog.cs +++ /dev/null @@ -1,16 +0,0 @@ -using System; -using System.Collections.Generic; - -namespace BuildingBricks.NM.Models -{ - public partial class NotificationLog - { - public int NotificationLogId { get; set; } - public int NotificationTypeId { get; set; } - public int CustomerId { get; set; } - public DateTime SentDateTime { get; set; } - - public virtual Customer Customer { get; set; } = null!; - public virtual NotificationType NotificationType { get; set; } = null!; - } -} diff --git a/Workshop/Solution/Notification Management/NM.Services/Models/NotificationType.cs b/Workshop/Solution/Notification Management/NM.Services/Models/NotificationType.cs deleted file mode 100644 index 15be91c..0000000 --- a/Workshop/Solution/Notification Management/NM.Services/Models/NotificationType.cs +++ /dev/null @@ -1,18 +0,0 @@ -using System; -using System.Collections.Generic; - -namespace BuildingBricks.NM.Models -{ - public partial class NotificationType - { - public NotificationType() - { - NotificationLogs = new HashSet(); - } - - public int NotificationTypeId { get; set; } - public string NotificationTypeName { get; set; } = null!; - - public virtual ICollection NotificationLogs { get; set; } - } -} diff --git a/Workshop/Solution/Notification Management/NM.Services/NM.Services.csproj b/Workshop/Solution/Notification Management/NM.Services/NM.Services.csproj deleted file mode 100644 index fae2809..0000000 --- a/Workshop/Solution/Notification Management/NM.Services/NM.Services.csproj +++ /dev/null @@ -1,13 +0,0 @@ - - - - net6.0 - enable - enable - - - - - - - diff --git a/Workshop/Solution/Notification Management/NM.Services/NotificationManagementContext.cs b/Workshop/Solution/Notification Management/NM.Services/NotificationManagementContext.cs deleted file mode 100644 index 5dfe242..0000000 --- a/Workshop/Solution/Notification Management/NM.Services/NotificationManagementContext.cs +++ /dev/null @@ -1,76 +0,0 @@ -using BuildingBricks.NM.Models; -using Microsoft.EntityFrameworkCore; - -namespace BuildingBricks.NM -{ - public partial class NotificationManagementContext : DbContext - { - public NotificationManagementContext() - { - } - - public NotificationManagementContext(DbContextOptions options) - : base(options) - { - } - - public virtual DbSet Customers { get; set; } = null!; - public virtual DbSet NotificationLogs { get; set; } = null!; - public virtual DbSet NotificationTypes { get; set; } = null!; - - protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) - { - if (!optionsBuilder.IsConfigured) - { - optionsBuilder.UseSqlServer(Environment.GetEnvironmentVariable("OrderProcessingSystem-NM-DatabaseConnectionString")!); - } - } - - protected override void OnModelCreating(ModelBuilder modelBuilder) - { - modelBuilder.Entity(entity => - { - entity.ToTable("Customer", "NotificationManagement"); - - entity.Property(e => e.CustomerId).ValueGeneratedNever(); - - entity.Property(e => e.EmailAddress) - .HasMaxLength(255) - .IsUnicode(false); - }); - - modelBuilder.Entity(entity => - { - entity.ToTable("NotificationLog", "NotificationManagement"); - - entity.Property(e => e.SentDateTime).HasDefaultValueSql("(getutcdate())"); - - entity.HasOne(d => d.Customer) - .WithMany(p => p.NotificationLogs) - .HasForeignKey(d => d.CustomerId) - .OnDelete(DeleteBehavior.ClientSetNull) - .HasConstraintName("fkNotificationLog_Customer"); - - entity.HasOne(d => d.NotificationType) - .WithMany(p => p.NotificationLogs) - .HasForeignKey(d => d.NotificationTypeId) - .OnDelete(DeleteBehavior.ClientSetNull) - .HasConstraintName("fkNotificationLog_NotificationType"); - }); - - modelBuilder.Entity(entity => - { - entity.ToTable("NotificationType", "NotificationManagement"); - - entity.Property(e => e.NotificationTypeId).ValueGeneratedNever(); - - entity.Property(e => e.NotificationTypeName) - .HasMaxLength(100) - .IsUnicode(false); - }); - - } - - } - -} \ No newline at end of file diff --git a/Workshop/Solution/Order Management/OM.Database/OM.Database.sqlproj b/Workshop/Solution/Order Management/OM.Database/OM.Database.sqlproj deleted file mode 100644 index fbd2d8f..0000000 --- a/Workshop/Solution/Order Management/OM.Database/OM.Database.sqlproj +++ /dev/null @@ -1,81 +0,0 @@ - - - - Debug - AnyCPU - OM.Database - 2.0 - 4.1 - {fe682f58-f6ca-4e1d-8ded-66bc07ba3972} - Microsoft.Data.Tools.Schema.Sql.SqlAzureV12DatabaseSchemaProvider - Database - - - OM.Database - OM.Database - 1033, CI - BySchemaAndSchemaType - True - v4.7.2 - CS - Properties - False - True - True - OrderManagement - - - bin\Release\ - $(MSBuildProjectName).sql - False - pdbonly - true - false - true - prompt - 4 - - - bin\Debug\ - $(MSBuildProjectName).sql - false - true - full - false - true - true - prompt - 4 - True - - - 11.0 - - True - 11.0 - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/Workshop/Solution/Order Management/OM.Database/OrderManagement.sql b/Workshop/Solution/Order Management/OM.Database/OrderManagement.sql deleted file mode 100644 index 0e4559f..0000000 --- a/Workshop/Solution/Order Management/OM.Database/OrderManagement.sql +++ /dev/null @@ -1 +0,0 @@ -CREATE SCHEMA OrderManagement \ No newline at end of file diff --git a/Workshop/Solution/Order Management/OM.Database/Scripts/Populate/Populate_Customer.sql b/Workshop/Solution/Order Management/OM.Database/Scripts/Populate/Populate_Customer.sql deleted file mode 100644 index 42385a7..0000000 --- a/Workshop/Solution/Order Management/OM.Database/Scripts/Populate/Populate_Customer.sql +++ /dev/null @@ -1,44 +0,0 @@ --- emailfake.com - -SET IDENTITY_INSERT OrderManagement.Customer ON -GO - -MERGE INTO OrderManagement.Customer AS TARGET -USING (VALUES (1, 'Julie', 'Hartshorn', 'Julie.Hartshorn@wpgotten.com', '2598 Butternut Lane', 'Ava', 'IL', 'US', '62707'), - (2, 'William', 'Pierce', 'William.Pierce@wpgotten.com', '859 Courtright Street', 'Gardner', 'ND', 'US', '58036'), - (3, 'Christine', 'Brandt', 'Christine.Brandt@wpgotten.com', '3428 Burnside Avenue', 'Monument Valley', 'UT', 'US', '84536'), - (4, 'Patsy', 'Hauser', 'Patsy.Hauser@wpgotten.com', '121 Old Dear Lane', 'Marlboro', 'NY', 'US', '12542'), - (5, 'Carlos', 'Riley', 'Carlos.Riley@wpgotten.com', '2949 White River Way', 'Salt Lake City', 'UT', 'US', '84104'), - (6, 'Edward', 'Tate', 'Edward.Tate@wpgotten.com', '2776 MacLaren Street', 'Ottawa', 'ON', 'CA', 'K1P 5M7'), - (7, 'Christi', 'Miller', 'Christi.Miller@wpgotten.com', '775 Lynden Road', 'Orono', 'ON', 'CA', 'L0B 1M0')) -AS SOURCE (CustomerId, FirstName, LastName, EmailAddress, StreetAddress, City, CountryDivisionCode, CountryCode, PostalCode) -ON TARGET.CustomerId = SOURCE.CustomerId -WHEN MATCHED THEN UPDATE SET TARGET.FirstName = SOURCE.FirstName, - TARGET.LastName = SOURCE.LastName, - TARGET.EmailAddress = SOURCE.EmailAddress, - TARGET.StreetAddress = SOURCE.StreetAddress, - TARGET.City = SOURCE.City, - TARGET.CountryDivisionCode = SOURCE.CountryDivisionCode, - TARGET.CountryCode = SOURCE.CountryCode, - TARGET.PostalCode = SOURCE.PostalCode -WHEN NOT MATCHED THEN INSERT (CustomerId, - FirstName, - LastName, - EmailAddress, - StreetAddress, - City, - CountryDivisionCode, - CountryCode, - PostalCode) - VALUES (SOURCE.CustomerId, - SOURCE.FirstName, - SOURCE.LastName, - SOURCE.EmailAddress, - SOURCE.StreetAddress, - SOURCE.City, - SOURCE.CountryDivisionCode, - SOURCE.CountryCode, - SOURCE.PostalCode); - -SET IDENTITY_INSERT OrderManagement.Customer OFF -GO \ No newline at end of file diff --git a/Workshop/Solution/Order Management/OM.Database/Scripts/Populate/Populate_OrderStatus.sql b/Workshop/Solution/Order Management/OM.Database/Scripts/Populate/Populate_OrderStatus.sql deleted file mode 100644 index 59d7577..0000000 --- a/Workshop/Solution/Order Management/OM.Database/Scripts/Populate/Populate_OrderStatus.sql +++ /dev/null @@ -1,12 +0,0 @@ -MERGE INTO OrderManagement.OrderStatus AS TARGET -USING (VALUES (1, 'Placed'), - (2, 'Reserved'), - (3, 'Shipped'), - (4, 'Complete')) -AS SOURCE (OrderStatusId, OrderStatusName) -ON TARGET.OrderStatusId = SOURCE.OrderStatusId -WHEN MATCHED THEN UPDATE SET TARGET.OrderStatusName = SOURCE.OrderStatusName -WHEN NOT MATCHED THEN INSERT (OrderStatusId, - OrderStatusName) - VALUES (SOURCE.OrderStatusId, - SOURCE.OrderStatusName); \ No newline at end of file diff --git a/Workshop/Solution/Order Management/OM.Database/Scripts/Populate/Populate_Product.sql b/Workshop/Solution/Order Management/OM.Database/Scripts/Populate/Populate_Product.sql deleted file mode 100644 index eb9e655..0000000 --- a/Workshop/Solution/Order Management/OM.Database/Scripts/Populate/Populate_Product.sql +++ /dev/null @@ -1,74 +0,0 @@ -MERGE INTO OrderManagement.Product AS TARGET -USING (VALUES ('10255', 'Assembly Square', 299.99), - ('10265', 'Ford Mustang', 169.99), - ('10266', 'NASA Apollo 11 Lunar Lander', 99.99), - ('10270', 'Bookshop', 199.99), - ('10273', 'Hauned House', 299.99), - ('10274', 'Ghostbusters™ ECTO-1', 239.99), - ('10276', 'Colosseum', 549.99), - ('10278', 'Police Station', 199.99), - ('10280', 'Flower Bouquet', 59.99), - ('10281', 'Bonsai Tree', 49.99), - ('10283', 'NASA Space Shuttle Discovery', 199.99), - ('10290', 'Pickup Truck', 129.99), - ('10292', 'The Friends Apartments', 179.99), - ('10293', 'Santa''s Visit', 99.99), - ('10294', 'LEGO® Titanic', 679.99), - ('10295', 'Porsche 911', 169.99), - ('10297', 'Boutique Hotel', 229.99), - ('10298', 'Vespa 125', 99.99), - ('10299', 'Real Madrid – Santiago Bernabéu Stadium', 399.99), - ('10300', 'Back to the Future Time Machine', 199.99), - ('10302', 'Optimus Prime', 179.99), - ('10303', 'Loop Coaster', 399.99), - ('10304', 'Chevrolet Camaro Z28', 169.99), - ('10305', 'Bonsai Tree', 399.99), - ('10306', 'Atari® 2600', 239.99), - ('10307', 'Eiffel tower', 629.99), - ('10309', 'Succulents', 49.99), - ('10312', 'Jazz Club', 229.99), - ('10314', 'Corvette', 49.99), - ('10315', 'Tranquil Garden', 109.99), - ('10316', 'THE LORD OF THE RINGS: RIVENDELL™', 499.99), - ('10317', 'Land Rover Classic Defender 90', 239.99), - ('10320', 'Eldorado Fortress', 214.99), - ('10321', 'Corvette', 149.99), - ('10323', 'PAC-MAN Arcade', 269.99), - ('10497', 'Galaxy Explorer', 99.99), - ('21028', 'New York City', 59.99), - ('21034', 'London', 39.99), - ('21042', 'Statue of Liberty', 199.99), - ('21044', 'Paris', 49.99), - ('21054', 'The White House', 99.99), - ('21056', 'Taj Mahal', 199.99), - ('21057', 'Statue of Liberty', 59.99), - ('21058', 'Great Pyramid of Giza', 129.99), - ('21060', 'Himeji Castle', 159.99), - ('21318', 'Tree House', 249.99), - ('21323', 'Grand Piano', 399.99), - ('21325', 'Medieval Blacksmith', 179.99), - ('21326', 'Winnie the Pooh', 99.99), - ('21327', 'Typewriter', 249.99), - ('21330', 'LEGO® Ideas Home Alone', 299.99), - ('21332', 'The Globe', 229.99), - ('21334', 'Jazz Quartet', 99.99), - ('21335', 'Motorized Lighthouse', 299.99), - ('21336', 'The Office', 119.99), - ('21337', 'Table Football', 249.99), - ('21338', 'A-Frame Cabin', 179.99), - ('21339', 'BTS Dynamite', 99.99), - ('21340', 'Tales of the Space Age', 49.99), - ('21341', 'Disney Hocus Pocus: The Sanderson Sisters'' Cottage', 229.99), - ('40220', 'London Bus', 9.99), - ('40517', 'Vespa', 9.99), - ('40518', 'High Speed Train', 19.99)) -AS SOURCE (ProductId, ProductName, Price) -ON TARGET.ProductId = SOURCE.ProductId -WHEN MATCHED THEN UPDATE SET TARGET.ProductName = SOURCE.ProductName, - TARGET.Price = SOURCE.Price -WHEN NOT MATCHED THEN INSERT (ProductId, - ProductName, - Price) - VALUES (SOURCE.ProductId, - SOURCE.ProductName, - SOURCE.Price); \ No newline at end of file diff --git a/Workshop/Solution/Order Management/OM.Database/Scripts/Script.PostDeployment.sql b/Workshop/Solution/Order Management/OM.Database/Scripts/Script.PostDeployment.sql deleted file mode 100644 index d83968f..0000000 --- a/Workshop/Solution/Order Management/OM.Database/Scripts/Script.PostDeployment.sql +++ /dev/null @@ -1,3 +0,0 @@ -:r .\Populate\Populate_Customer.sql -:r .\Populate\Populate_OrderStatus.sql -:r .\Populate\Populate_Product.sql \ No newline at end of file diff --git a/Workshop/Solution/Order Management/OM.Database/Tables/Customer.sql b/Workshop/Solution/Order Management/OM.Database/Tables/Customer.sql deleted file mode 100644 index 5d3f8d9..0000000 --- a/Workshop/Solution/Order Management/OM.Database/Tables/Customer.sql +++ /dev/null @@ -1,13 +0,0 @@ -CREATE TABLE OrderManagement.Customer -( - CustomerId INT NOT NULL IDENTITY(1,1), - FirstName NVARCHAR(100) NOT NULL, - LastName NVARCHAR(100) NOT NULL, - StreetAddress NVARCHAR(100) NOT NULL, - City NVARCHAR(100) NOT NULL, - CountryDivisionCode CHAR(2) NULL, - CountryCode CHAR(2) NOT NULL, - PostalCode VARCHAR(20) NULL, - EMailAddress VARCHAR(255) NOT NULL, - CONSTRAINT pkcCustomer PRIMARY KEY CLUSTERED (CustomerId) -) \ No newline at end of file diff --git a/Workshop/Solution/Order Management/OM.Database/Tables/CustomerOrder.sql b/Workshop/Solution/Order Management/OM.Database/Tables/CustomerOrder.sql deleted file mode 100644 index bc0e202..0000000 --- a/Workshop/Solution/Order Management/OM.Database/Tables/CustomerOrder.sql +++ /dev/null @@ -1,11 +0,0 @@ -CREATE TABLE OrderManagement.CustomerOrder -( - CustomerOrderId CHAR(36) NOT NULL, - CustomerId INT NOT NULL, - OrderStatusId INT NOT NULL CONSTRAINT dfCustomerOrder_OrderStatusId DEFAULT(1), - OrderDateTime DATETIME2 NOT NULL CONSTRAINT dfCustomerOrder_OrderDateTime DEFAULT(GETUTCDATE()), - ReserveDateTime DATETIME2 NULL, - ShipDateTime DATETIME2 NULL, - CompleteDateTime DATETIME2 NULL, - CONSTRAINT pkcCustomerOrder PRIMARY KEY CLUSTERED (CustomerOrderId) -) \ No newline at end of file diff --git a/Workshop/Solution/Order Management/OM.Database/Tables/OrderItem.sql b/Workshop/Solution/Order Management/OM.Database/Tables/OrderItem.sql deleted file mode 100644 index cf415f5..0000000 --- a/Workshop/Solution/Order Management/OM.Database/Tables/OrderItem.sql +++ /dev/null @@ -1,14 +0,0 @@ -CREATE TABLE OrderManagement.OrderItem -( - OrderItemId INT NOT NULL IDENTITY(1,1), - CustomerOrderId CHAR(36) NOT NULL, - ProductId CHAR(5) NOT NULL, - Quantity INT NOT NULL, - OrderStatusId INT NOT NULL CONSTRAINT dfOrderItem_OrderStatusId DEFAULT(1), - DateTimeAdded DATETIME2 NOT NULL CONSTRAINT dfOrderItem_DateTimeAdded DEFAULT(GETUTCDATE()), - DateTimeModified DATETIME2 NULL, - CONSTRAINT pkcOrderItme PRIMARY KEY CLUSTERED (OrderItemId), - CONSTRAINT fkOrderItem_CustomerOrder FOREIGN KEY (CustomerOrderId) REFERENCES OrderManagement.CustomerOrder (CustomerOrderId), - CONSTRAINT fkOrderItem_Product FOREIGN KEY (ProductId) REFERENCES OrderManagement.Product (ProductId), - CONSTRAINT fkOrderItem_OrderStatus FOREIGN KEY (OrderStatusId) REFERENCES OrderManagement.OrderStatus (OrderStatusId) -) \ No newline at end of file diff --git a/Workshop/Solution/Order Management/OM.Database/Tables/OrderStatus.sql b/Workshop/Solution/Order Management/OM.Database/Tables/OrderStatus.sql deleted file mode 100644 index 2443a41..0000000 --- a/Workshop/Solution/Order Management/OM.Database/Tables/OrderStatus.sql +++ /dev/null @@ -1,6 +0,0 @@ -CREATE TABLE OrderManagement.OrderStatus -( - OrderStatusId INT NOT NULL, - OrderStatusName VARCHAR(100) NOT NULL, - CONSTRAINT pkcOrderStatus PRIMARY KEY CLUSTERED (OrderStatusId) -) \ No newline at end of file diff --git a/Workshop/Solution/Order Management/OM.Database/Tables/Product.sql b/Workshop/Solution/Order Management/OM.Database/Tables/Product.sql deleted file mode 100644 index 8b6e3ad..0000000 --- a/Workshop/Solution/Order Management/OM.Database/Tables/Product.sql +++ /dev/null @@ -1,7 +0,0 @@ -CREATE TABLE OrderManagement.Product -( - ProductId CHAR(5) NOT NULL, - ProductName NVARCHAR(100) NOT NULL, - Price SMALLMONEY NOT NULL, - CONSTRAINT pkcProduct PRIMARY KEY CLUSTERED (ProductId) -) \ No newline at end of file diff --git a/Workshop/Solution/Order Management/OM.Functions/.gitignore b/Workshop/Solution/Order Management/OM.Functions/.gitignore deleted file mode 100644 index ff5b00c..0000000 --- a/Workshop/Solution/Order Management/OM.Functions/.gitignore +++ /dev/null @@ -1,264 +0,0 @@ -## Ignore Visual Studio temporary files, build results, and -## files generated by popular Visual Studio add-ons. - -# Azure Functions localsettings file -local.settings.json - -# User-specific files -*.suo -*.user -*.userosscache -*.sln.docstates - -# User-specific files (MonoDevelop/Xamarin Studio) -*.userprefs - -# Build results -[Dd]ebug/ -[Dd]ebugPublic/ -[Rr]elease/ -[Rr]eleases/ -x64/ -x86/ -bld/ -[Bb]in/ -[Oo]bj/ -[Ll]og/ - -# Visual Studio 2015 cache/options directory -.vs/ -# Uncomment if you have tasks that create the project's static files in wwwroot -#wwwroot/ - -# MSTest test Results -[Tt]est[Rr]esult*/ -[Bb]uild[Ll]og.* - -# NUNIT -*.VisualState.xml -TestResult.xml - -# Build Results of an ATL Project -[Dd]ebugPS/ -[Rr]eleasePS/ -dlldata.c - -# DNX -project.lock.json -project.fragment.lock.json -artifacts/ - -*_i.c -*_p.c -*_i.h -*.ilk -*.meta -*.obj -*.pch -*.pdb -*.pgc -*.pgd -*.rsp -*.sbr -*.tlb -*.tli -*.tlh -*.tmp -*.tmp_proj -*.log -*.vspscc -*.vssscc -.builds -*.pidb -*.svclog -*.scc - -# Chutzpah Test files -_Chutzpah* - -# Visual C++ cache files -ipch/ -*.aps -*.ncb -*.opendb -*.opensdf -*.sdf -*.cachefile -*.VC.db -*.VC.VC.opendb - -# Visual Studio profiler -*.psess -*.vsp -*.vspx -*.sap - -# TFS 2012 Local Workspace -$tf/ - -# Guidance Automation Toolkit -*.gpState - -# ReSharper is a .NET coding add-in -_ReSharper*/ -*.[Rr]e[Ss]harper -*.DotSettings.user - -# JustCode is a .NET coding add-in -.JustCode - -# TeamCity is a build add-in -_TeamCity* - -# DotCover is a Code Coverage Tool -*.dotCover - -# NCrunch -_NCrunch_* -.*crunch*.local.xml -nCrunchTemp_* - -# MightyMoose -*.mm.* -AutoTest.Net/ - -# Web workbench (sass) -.sass-cache/ - -# Installshield output folder -[Ee]xpress/ - -# DocProject is a documentation generator add-in -DocProject/buildhelp/ -DocProject/Help/*.HxT -DocProject/Help/*.HxC -DocProject/Help/*.hhc -DocProject/Help/*.hhk -DocProject/Help/*.hhp -DocProject/Help/Html2 -DocProject/Help/html - -# Click-Once directory -publish/ - -# Publish Web Output -*.[Pp]ublish.xml -*.azurePubxml -# TODO: Comment the next line if you want to checkin your web deploy settings -# but database connection strings (with potential passwords) will be unencrypted -#*.pubxml -*.publishproj - -# Microsoft Azure Web App publish settings. Comment the next line if you want to -# checkin your Azure Web App publish settings, but sensitive information contained -# in these scripts will be unencrypted -PublishScripts/ - -# NuGet Packages -*.nupkg -# The packages folder can be ignored because of Package Restore -**/packages/* -# except build/, which is used as an MSBuild target. -!**/packages/build/ -# Uncomment if necessary however generally it will be regenerated when needed -#!**/packages/repositories.config -# NuGet v3's project.json files produces more ignoreable files -*.nuget.props -*.nuget.targets - -# Microsoft Azure Build Output -csx/ -*.build.csdef - -# Microsoft Azure Emulator -ecf/ -rcf/ - -# Windows Store app package directories and files -AppPackages/ -BundleArtifacts/ -Package.StoreAssociation.xml -_pkginfo.txt - -# Visual Studio cache files -# files ending in .cache can be ignored -*.[Cc]ache -# but keep track of directories ending in .cache -!*.[Cc]ache/ - -# Others -ClientBin/ -~$* -*~ -*.dbmdl -*.dbproj.schemaview -*.jfm -*.pfx -*.publishsettings -node_modules/ -orleans.codegen.cs - -# Since there are multiple workflows, uncomment next line to ignore bower_components -# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) -#bower_components/ - -# RIA/Silverlight projects -Generated_Code/ - -# Backup & report files from converting an old project file -# to a newer Visual Studio version. Backup files are not needed, -# because we have git ;-) -_UpgradeReport_Files/ -Backup*/ -UpgradeLog*.XML -UpgradeLog*.htm - -# SQL Server files -*.mdf -*.ldf - -# Business Intelligence projects -*.rdl.data -*.bim.layout -*.bim_*.settings - -# Microsoft Fakes -FakesAssemblies/ - -# GhostDoc plugin setting file -*.GhostDoc.xml - -# Node.js Tools for Visual Studio -.ntvs_analysis.dat - -# Visual Studio 6 build log -*.plg - -# Visual Studio 6 workspace options file -*.opt - -# Visual Studio LightSwitch build output -**/*.HTMLClient/GeneratedArtifacts -**/*.DesktopClient/GeneratedArtifacts -**/*.DesktopClient/ModelManifest.xml -**/*.Server/GeneratedArtifacts -**/*.Server/ModelManifest.xml -_Pvt_Extensions - -# Paket dependency manager -.paket/paket.exe -paket-files/ - -# FAKE - F# Make -.fake/ - -# JetBrains Rider -.idea/ -*.sln.iml - -# CodeRush -.cr/ - -# Python Tools for Visual Studio (PTVS) -__pycache__/ -*.pyc \ No newline at end of file diff --git a/Workshop/Solution/Order Management/OM.Functions/Functions/Function.cs b/Workshop/Solution/Order Management/OM.Functions/Functions/Function.cs deleted file mode 100644 index b4bd559..0000000 --- a/Workshop/Solution/Order Management/OM.Functions/Functions/Function.cs +++ /dev/null @@ -1,30 +0,0 @@ -using System.Net; -using Microsoft.Azure.Functions.Worker; -using Microsoft.Azure.Functions.Worker.Http; -using Microsoft.Extensions.Logging; - -namespace OM.Functions.Functions -{ - public class Function - { - private readonly ILogger _logger; - - public Function(ILoggerFactory loggerFactory) - { - _logger = loggerFactory.CreateLogger(); - } - - [Function("Function")] - public HttpResponseData Run([HttpTrigger(AuthorizationLevel.Function, "get", "post")] HttpRequestData req) - { - _logger.LogInformation("C# HTTP trigger function processed a request."); - - var response = req.CreateResponse(HttpStatusCode.OK); - response.Headers.Add("Content-Type", "text/plain; charset=utf-8"); - - response.WriteString("Welcome to Azure Functions!"); - - return response; - } - } -} diff --git a/Workshop/Solution/Order Management/OM.Functions/Functions/GetOrder.cs b/Workshop/Solution/Order Management/OM.Functions/Functions/GetOrder.cs deleted file mode 100644 index 8a88a1a..0000000 --- a/Workshop/Solution/Order Management/OM.Functions/Functions/GetOrder.cs +++ /dev/null @@ -1,45 +0,0 @@ -using BuildingBricks.OM; -using Microsoft.Azure.Functions.Worker; -using Microsoft.Azure.Functions.Worker.Http; -using Microsoft.Extensions.Logging; -using System.Text.Json; -using TaleLearnCode; - -namespace OM.Functions; - -public class GetOrder -{ - - private readonly ILogger _logger; - private readonly JsonSerializerOptions _jsonSerializerOptions; - - public GetOrder( - ILoggerFactory loggerFactory, - JsonSerializerOptions jsonSerializerOptions) - { - _logger = loggerFactory.CreateLogger(); - _jsonSerializerOptions = jsonSerializerOptions; - } - - [Function("GetOrder")] - public async Task RunAsync( - [HttpTrigger(AuthorizationLevel.Function, "get", Route = "orders/{orderId}")] HttpRequestData request, - string orderId) - { - try - { - ArgumentNullException.ThrowIfNull(orderId); - return await request.CreateResponseAsync(await OrderServices.GetOrder(orderId), _jsonSerializerOptions); - } - catch (Exception ex) when (ex is ArgumentNullException) - { - return request.CreateBadRequestResponse(ex); - } - catch (Exception ex) - { - _logger.LogError("Unexpected exception: {ExceptionMessage}", ex.Message); - return request.CreateErrorResponse(ex); - } - } - -} \ No newline at end of file diff --git a/Workshop/Solution/Order Management/OM.Functions/Functions/PlaceOrder.cs b/Workshop/Solution/Order Management/OM.Functions/Functions/PlaceOrder.cs deleted file mode 100644 index 968773f..0000000 --- a/Workshop/Solution/Order Management/OM.Functions/Functions/PlaceOrder.cs +++ /dev/null @@ -1,51 +0,0 @@ -using BuildingBricks.OM; -using BuildingBricks.OM.Requests; -using Microsoft.Azure.Functions.Worker; -using Microsoft.Azure.Functions.Worker.Http; -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.Logging; -using System.Text.Json; -using TaleLearnCode; - -namespace OM.Functions; - -public class PlaceOrder -{ - - private readonly ILogger _logger; - private readonly IConfiguration _configuration; - private readonly JsonSerializerOptions _jsonSerializerOptions; - - public PlaceOrder( - ILoggerFactory loggerFactory, - IConfiguration configuration, - JsonSerializerOptions jsonSerializerOptions) - { - _logger = loggerFactory.CreateLogger(); - _configuration = configuration; - _jsonSerializerOptions = jsonSerializerOptions; - } - - [Function("PlaceOrder")] - public async Task RunAsync( - [HttpTrigger(AuthorizationLevel.Function, "post", Route = "orders")] HttpRequestData request) - { - try - { - PlaceOrderRequest placeOrderRequest = await request.GetRequestParametersAsync(_jsonSerializerOptions); - OrderServices orderServices = new(_configuration); - string orderId = await orderServices.PlaceOrderAsync(placeOrderRequest); - return request.CreateCreatedResponse($"orders/{orderId}"); - } - catch (Exception ex) when (ex is ArgumentNullException) - { - return request.CreateBadRequestResponse(ex); - } - catch (Exception ex) - { - _logger.LogError("Unexpected exception: {ExceptionMessage}", ex.Message); - return request.CreateErrorResponse(ex); - } - } - -} \ No newline at end of file diff --git a/Workshop/Solution/Order Management/OM.Functions/OM.Functions.csproj b/Workshop/Solution/Order Management/OM.Functions/OM.Functions.csproj deleted file mode 100644 index 401c212..0000000 --- a/Workshop/Solution/Order Management/OM.Functions/OM.Functions.csproj +++ /dev/null @@ -1,31 +0,0 @@ - - - net6.0 - v4 - Exe - enable - enable - - - - - - - - - - - - - - PreserveNewest - - - PreserveNewest - Never - - - - - - \ No newline at end of file diff --git a/Workshop/Solution/Order Management/OM.Functions/Program.cs b/Workshop/Solution/Order Management/OM.Functions/Program.cs deleted file mode 100644 index 32263cc..0000000 --- a/Workshop/Solution/Order Management/OM.Functions/Program.cs +++ /dev/null @@ -1,28 +0,0 @@ -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Hosting; -using System.Text.Json; -using System.Text.Json.Serialization; - -IHost host = new HostBuilder() - .ConfigureAppConfiguration(builder => - { - builder.AddAzureAppConfiguration(Environment.GetEnvironmentVariable("AppConfigConnectionString")!); - }) - .ConfigureFunctionsWorkerDefaults() - .ConfigureServices(s => - { - s.AddSingleton((s) => - { - return new JsonSerializerOptions() - { - PropertyNamingPolicy = JsonNamingPolicy.CamelCase, - DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull, - DictionaryKeyPolicy = JsonNamingPolicy.CamelCase, - WriteIndented = true - }; - }); - }) - .Build(); - -host.Run(); \ No newline at end of file diff --git a/Workshop/Solution/Order Management/OM.Functions/Properties/launchSettings.json b/Workshop/Solution/Order Management/OM.Functions/Properties/launchSettings.json deleted file mode 100644 index a0b646a..0000000 --- a/Workshop/Solution/Order Management/OM.Functions/Properties/launchSettings.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "profiles": { - "OM.Functions": { - "commandName": "Project", - "commandLineArgs": "--port 7174", - "launchBrowser": false - } - } -} \ No newline at end of file diff --git a/Workshop/Solution/Order Management/OM.Functions/Properties/serviceDependencies.json b/Workshop/Solution/Order Management/OM.Functions/Properties/serviceDependencies.json deleted file mode 100644 index df4dcc9..0000000 --- a/Workshop/Solution/Order Management/OM.Functions/Properties/serviceDependencies.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "dependencies": { - "appInsights1": { - "type": "appInsights" - }, - "storage1": { - "type": "storage", - "connectionId": "AzureWebJobsStorage" - } - } -} \ No newline at end of file diff --git a/Workshop/Solution/Order Management/OM.Functions/Properties/serviceDependencies.local.json b/Workshop/Solution/Order Management/OM.Functions/Properties/serviceDependencies.local.json deleted file mode 100644 index b804a28..0000000 --- a/Workshop/Solution/Order Management/OM.Functions/Properties/serviceDependencies.local.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "dependencies": { - "appInsights1": { - "type": "appInsights.sdk" - }, - "storage1": { - "type": "storage.emulator", - "connectionId": "AzureWebJobsStorage" - } - } -} \ No newline at end of file diff --git a/Workshop/Solution/Order Management/OM.Functions/host.json b/Workshop/Solution/Order Management/OM.Functions/host.json deleted file mode 100644 index beb2e40..0000000 --- a/Workshop/Solution/Order Management/OM.Functions/host.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "version": "2.0", - "logging": { - "applicationInsights": { - "samplingSettings": { - "isEnabled": true, - "excludedTypes": "Request" - } - } - } -} \ No newline at end of file diff --git a/Workshop/Solution/Order Management/OM.Services/Constants/OrderManagementEnvironmentVariables.cs b/Workshop/Solution/Order Management/OM.Services/Constants/OrderManagementEnvironmentVariables.cs deleted file mode 100644 index fc1d4c7..0000000 --- a/Workshop/Solution/Order Management/OM.Services/Constants/OrderManagementEnvironmentVariables.cs +++ /dev/null @@ -1,9 +0,0 @@ -using BuildingBlocks.Core.Constants; - -namespace BuildingBricks.OM.Constants; - -internal static class OrderManagementEnvironmentVariables -{ - internal const string OrderPlacedQueueConnectionString = $"{EnvironmentVariables.Base}-OM-ServiceBusConnectionString"; - internal const string EventHubConnectionString = $"{EnvironmentVariables.Base}-OM-EventHubConnectionString"; -} \ No newline at end of file diff --git a/Workshop/Solution/Order Management/OM.Services/Extensions/CustomerOrderExtensions.cs b/Workshop/Solution/Order Management/OM.Services/Extensions/CustomerOrderExtensions.cs deleted file mode 100644 index 7bb4d0d..0000000 --- a/Workshop/Solution/Order Management/OM.Services/Extensions/CustomerOrderExtensions.cs +++ /dev/null @@ -1,23 +0,0 @@ -using BuildingBlocks.Core.EventMessage; -using BuildingBricks.OM.Models; - -namespace BuildingBricks.OM.Extensions; - -internal static class CustomerOrderExtensions -{ - - internal static List ToOrderItemMessageList(this CustomerOrder customerOrder) - { - List orderItemMessages = new(); - foreach (OrderItem orderItem in customerOrder.OrderItems) - orderItemMessages.Add(new() - { - OrderId = customerOrder.CustomerOrderId, - OrderItemId = orderItem.OrderItemId, - ProductId = orderItem.ProductId, - Quantity = orderItem.Quantity - }); - return orderItemMessages; - } - -} \ No newline at end of file diff --git a/Workshop/Solution/Order Management/OM.Services/Models/Customer.cs b/Workshop/Solution/Order Management/OM.Services/Models/Customer.cs deleted file mode 100644 index a1d5a9a..0000000 --- a/Workshop/Solution/Order Management/OM.Services/Models/Customer.cs +++ /dev/null @@ -1,18 +0,0 @@ -using System; -using System.Collections.Generic; - -namespace BuildingBricks.OM.Models -{ - public partial class Customer - { - public int CustomerId { get; set; } - public string FirstName { get; set; } = null!; - public string LastName { get; set; } = null!; - public string StreetAddress { get; set; } = null!; - public string City { get; set; } = null!; - public string? CountryDivisionCode { get; set; } - public string CountryCode { get; set; } = null!; - public string? PostalCode { get; set; } - public string EmailAddress { get; set; } = null!; - } -} diff --git a/Workshop/Solution/Order Management/OM.Services/Models/CustomerOrder.cs b/Workshop/Solution/Order Management/OM.Services/Models/CustomerOrder.cs deleted file mode 100644 index 7a24d4d..0000000 --- a/Workshop/Solution/Order Management/OM.Services/Models/CustomerOrder.cs +++ /dev/null @@ -1,23 +0,0 @@ -using System; -using System.Collections.Generic; - -namespace BuildingBricks.OM.Models -{ - public partial class CustomerOrder - { - public CustomerOrder() - { - OrderItems = new HashSet(); - } - - public string CustomerOrderId { get; set; } = null!; - public int CustomerId { get; set; } - public int OrderStatusId { get; set; } - public DateTime OrderDateTime { get; set; } - public DateTime? ReserveDateTime { get; set; } - public DateTime? ShipDateTime { get; set; } - public DateTime? CompleteDateTime { get; set; } - - public virtual ICollection OrderItems { get; set; } - } -} diff --git a/Workshop/Solution/Order Management/OM.Services/Models/OrderItem.cs b/Workshop/Solution/Order Management/OM.Services/Models/OrderItem.cs deleted file mode 100644 index 47036b9..0000000 --- a/Workshop/Solution/Order Management/OM.Services/Models/OrderItem.cs +++ /dev/null @@ -1,20 +0,0 @@ -using System; -using System.Collections.Generic; - -namespace BuildingBricks.OM.Models -{ - public partial class OrderItem - { - public int OrderItemId { get; set; } - public string CustomerOrderId { get; set; } = null!; - public string ProductId { get; set; } = null!; - public int Quantity { get; set; } - public int OrderStatusId { get; set; } - public DateTime DateTimeAdded { get; set; } - public DateTime? DateTimeModified { get; set; } - - public virtual CustomerOrder CustomerOrder { get; set; } = null!; - public virtual OrderStatus OrderStatus { get; set; } = null!; - public virtual Product Product { get; set; } = null!; - } -} diff --git a/Workshop/Solution/Order Management/OM.Services/Models/OrderStatus.cs b/Workshop/Solution/Order Management/OM.Services/Models/OrderStatus.cs deleted file mode 100644 index ea4ef2e..0000000 --- a/Workshop/Solution/Order Management/OM.Services/Models/OrderStatus.cs +++ /dev/null @@ -1,18 +0,0 @@ -using System; -using System.Collections.Generic; - -namespace BuildingBricks.OM.Models -{ - public partial class OrderStatus - { - public OrderStatus() - { - OrderItems = new HashSet(); - } - - public int OrderStatusId { get; set; } - public string OrderStatusName { get; set; } = null!; - - public virtual ICollection OrderItems { get; set; } - } -} diff --git a/Workshop/Solution/Order Management/OM.Services/Models/Product.cs b/Workshop/Solution/Order Management/OM.Services/Models/Product.cs deleted file mode 100644 index 3dcb948..0000000 --- a/Workshop/Solution/Order Management/OM.Services/Models/Product.cs +++ /dev/null @@ -1,19 +0,0 @@ -using System; -using System.Collections.Generic; - -namespace BuildingBricks.OM.Models -{ - public partial class Product - { - public Product() - { - OrderItems = new HashSet(); - } - - public string ProductId { get; set; } = null!; - public string ProductName { get; set; } = null!; - public decimal Price { get; set; } - - public virtual ICollection OrderItems { get; set; } - } -} diff --git a/Workshop/Solution/Order Management/OM.Services/OM.Services.csproj b/Workshop/Solution/Order Management/OM.Services/OM.Services.csproj deleted file mode 100644 index b84833f..0000000 --- a/Workshop/Solution/Order Management/OM.Services/OM.Services.csproj +++ /dev/null @@ -1,18 +0,0 @@ - - - - net6.0 - enable - enable - BuildingBricks.OM - - - - - - - - - - - diff --git a/Workshop/Solution/Order Management/OM.Services/OrderManagementContext.cs b/Workshop/Solution/Order Management/OM.Services/OrderManagementContext.cs deleted file mode 100644 index 8f80bc7..0000000 --- a/Workshop/Solution/Order Management/OM.Services/OrderManagementContext.cs +++ /dev/null @@ -1,146 +0,0 @@ -using BuildingBricks.OM.Models; -using Microsoft.EntityFrameworkCore; - -namespace BuildingBricks.OM -{ - public partial class OrderManagementContext : DbContext - { - - public OrderManagementContext() - { - } - - public OrderManagementContext(DbContextOptions options) - : base(options) - { - } - - public virtual DbSet Customers { get; set; } = null!; - public virtual DbSet CustomerOrders { get; set; } = null!; - public virtual DbSet OrderItems { get; set; } = null!; - public virtual DbSet OrderStatuses { get; set; } = null!; - public virtual DbSet Products { get; set; } = null!; - - protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) - { - if (!optionsBuilder.IsConfigured) - { - optionsBuilder.UseSqlServer(Environment.GetEnvironmentVariable("OrderProcessingSystem-OM-DatabaseConnectionString")!); - } - } - - protected override void OnModelCreating(ModelBuilder modelBuilder) - { - modelBuilder.Entity(entity => - { - entity.ToTable("Customer", "OrderManagement"); - - entity.Property(e => e.City).HasMaxLength(100); - - entity.Property(e => e.CountryCode) - .HasMaxLength(2) - .IsUnicode(false) - .IsFixedLength(); - - entity.Property(e => e.CountryDivisionCode) - .HasMaxLength(2) - .IsUnicode(false) - .IsFixedLength(); - - entity.Property(e => e.EmailAddress) - .HasMaxLength(255) - .IsUnicode(false) - .HasColumnName("EMailAddress"); - - entity.Property(e => e.FirstName).HasMaxLength(100); - - entity.Property(e => e.LastName).HasMaxLength(100); - - entity.Property(e => e.PostalCode) - .HasMaxLength(20) - .IsUnicode(false); - - entity.Property(e => e.StreetAddress).HasMaxLength(100); - }); - - modelBuilder.Entity(entity => - { - entity.ToTable("CustomerOrder", "OrderManagement"); - - entity.Property(e => e.CustomerOrderId) - .HasMaxLength(36) - .IsUnicode(false) - .IsFixedLength(); - - entity.Property(e => e.OrderDateTime).HasDefaultValueSql("(getutcdate())"); - - entity.Property(e => e.OrderStatusId).HasDefaultValueSql("((1))"); - }); - - modelBuilder.Entity(entity => - { - entity.ToTable("OrderItem", "OrderManagement"); - - entity.Property(e => e.CustomerOrderId) - .HasMaxLength(36) - .IsUnicode(false) - .IsFixedLength(); - - entity.Property(e => e.DateTimeAdded).HasDefaultValueSql("(getutcdate())"); - - entity.Property(e => e.OrderStatusId).HasDefaultValueSql("((1))"); - - entity.Property(e => e.ProductId) - .HasMaxLength(5) - .IsUnicode(false) - .IsFixedLength(); - - entity.HasOne(d => d.CustomerOrder) - .WithMany(p => p.OrderItems) - .HasForeignKey(d => d.CustomerOrderId) - .OnDelete(DeleteBehavior.ClientSetNull) - .HasConstraintName("fkOrderItem_CustomerOrder"); - - entity.HasOne(d => d.OrderStatus) - .WithMany(p => p.OrderItems) - .HasForeignKey(d => d.OrderStatusId) - .OnDelete(DeleteBehavior.ClientSetNull) - .HasConstraintName("fkOrderItem_OrderStatus"); - - entity.HasOne(d => d.Product) - .WithMany(p => p.OrderItems) - .HasForeignKey(d => d.ProductId) - .OnDelete(DeleteBehavior.ClientSetNull) - .HasConstraintName("fkOrderItem_Product"); - }); - - modelBuilder.Entity(entity => - { - entity.ToTable("OrderStatus", "OrderManagement"); - - entity.Property(e => e.OrderStatusId).ValueGeneratedNever(); - - entity.Property(e => e.OrderStatusName) - .HasMaxLength(100) - .IsUnicode(false); - }); - - modelBuilder.Entity(entity => - { - entity.ToTable("Product", "OrderManagement"); - - entity.Property(e => e.ProductId) - .HasMaxLength(5) - .IsUnicode(false) - .IsFixedLength(); - - entity.Property(e => e.Price).HasColumnType("smallmoney"); - - entity.Property(e => e.ProductName).HasMaxLength(100); - }); - - } - - } - -} \ No newline at end of file diff --git a/Workshop/Solution/Order Management/OM.Services/OrderServices.cs b/Workshop/Solution/Order Management/OM.Services/OrderServices.cs deleted file mode 100644 index 9ae17e8..0000000 --- a/Workshop/Solution/Order Management/OM.Services/OrderServices.cs +++ /dev/null @@ -1,75 +0,0 @@ -using Azure.Messaging.ServiceBus; -using BuildingBlocks.Core; -using BuildingBlocks.Core.Constants; -using BuildingBlocks.Core.EventMessage; -using BuildingBricks.OM.Constants; -using BuildingBricks.OM.Extensions; -using BuildingBricks.OM.Models; -using BuildingBricks.OM.Requests; -using Microsoft.EntityFrameworkCore; -using Microsoft.Extensions.Configuration; -using System.Text.Json; - -namespace BuildingBricks.OM; - -public class OrderServices : ServicesBase -{ - - private readonly IConfiguration _configuration; - - public OrderServices(IConfiguration configuration) => _configuration = configuration; - - public async Task PlaceOrderAsync(PlaceOrderRequest placeOrderRequest) - { - string orderId = Guid.NewGuid().ToString(); - - using OrderManagementContext context = new(); - CustomerOrder customerOrder = new() - { - CustomerOrderId = Guid.NewGuid().ToString(), - CustomerId = placeOrderRequest.CustomerId, - OrderItems = BuildOrderItemsList(placeOrderRequest, orderId) - }; - await context.CustomerOrders.AddAsync(customerOrder); - await context.SaveChangesAsync(); - - await using ServiceBusClient serviceBusClient = GetServiceBusClient(OrderManagementEnvironmentVariables.OrderPlacedQueueConnectionString); - List orderItemMessages = customerOrder.ToOrderItemMessageList(); - foreach (OrderItemMessage orderItemMessage in orderItemMessages) - { - await SendSessionMessageToServiceBusAsync( - serviceBusClient, - _configuration[ConfigurationKeys.OrderPlacedQueueName], - JsonSerializer.Serialize(orderItemMessage), - orderItemMessage.ProductId); - } - - await SendMessageToEventHubAsync( - OrderManagementEnvironmentVariables.EventHubConnectionString, - _configuration[ConfigurationKeys.OrderPlacedEventHub], - JsonSerializer.Serialize(placeOrderRequest)); - - return orderId; - - } - - private static List BuildOrderItemsList(PlaceOrderRequest placeOrderRequest, string orderId) - { - List orderItems = new(); - foreach (PlaceOrderItem placeOrderItem in placeOrderRequest.Items) - orderItems.Add(new() - { - CustomerOrderId = orderId, - ProductId = placeOrderItem.ProductId, - Quantity = placeOrderItem.Quantity - }); - return orderItems; - } - - public static async Task GetOrder(string orderId) - { - using OrderManagementContext context = new(); - return await context.CustomerOrders.FirstOrDefaultAsync(x => x.CustomerOrderId == orderId); - } - -} \ No newline at end of file diff --git a/Workshop/Solution/Order Management/OM.Services/Requests/PlaceOrderItem.cs b/Workshop/Solution/Order Management/OM.Services/Requests/PlaceOrderItem.cs deleted file mode 100644 index d6835e5..0000000 --- a/Workshop/Solution/Order Management/OM.Services/Requests/PlaceOrderItem.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace BuildingBricks.OM.Requests; - -public class PlaceOrderItem -{ - public string ProductId { get; set; } = null!; - public int Quantity { get; set; } -} \ No newline at end of file diff --git a/Workshop/Solution/Order Management/OM.Services/Requests/PlaceOrderRequest.cs b/Workshop/Solution/Order Management/OM.Services/Requests/PlaceOrderRequest.cs deleted file mode 100644 index 1e2315d..0000000 --- a/Workshop/Solution/Order Management/OM.Services/Requests/PlaceOrderRequest.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace BuildingBricks.OM.Requests; - -public class PlaceOrderRequest -{ - public int CustomerId { get; set; } - public List Items { get; set; } = new(); -} \ No newline at end of file diff --git a/Workshop/Solution/OrderProcessingSystem.sln b/Workshop/Solution/OrderProcessingSystem.sln deleted file mode 100644 index d6a245d..0000000 --- a/Workshop/Solution/OrderProcessingSystem.sln +++ /dev/null @@ -1,138 +0,0 @@ - -Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 17 -VisualStudioVersion = 17.7.33920.267 -MinimumVisualStudioVersion = 10.0.40219.1 -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Product Management", "Product Management", "{301D6075-6E8E-448B-B4E1-2667538C599E}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PM.LoadDatabase", "Product Management\PM.LoadDatabase\PM.LoadDatabase.csproj", "{FFC24FE7-3A60-4EBB-B9AE-467B8B272E51}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Inventory Management", "Inventory Management", "{535EEF89-F922-4C50-B714-184DBDAD6597}" -EndProject -Project("{00D1A9C2-B5F0-4AF3-8072-F6C62B433612}") = "IM.Database", "Inventory Management\IM.Database\IM.Database.sqlproj", "{F0225AB4-2836-492A-B0E2-6A0B0667E555}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Order Management", "Order Management", "{0B879EDA-4DC3-4632-81D3-5F3807E070F3}" -EndProject -Project("{00D1A9C2-B5F0-4AF3-8072-F6C62B433612}") = "OM.Database", "Order Management\OM.Database\OM.Database.sqlproj", "{FE682F58-F6CA-4E1D-8DED-66BC07BA3972}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Shipping Management", "Shipping Management", "{B847CBC3-2B9F-46AE-8082-F37F9EDBAFC0}" -EndProject -Project("{00D1A9C2-B5F0-4AF3-8072-F6C62B433612}") = "SM.Database", "Shipping Management\SM.Database\SM.Database.sqlproj", "{5A4BB707-FB63-40A1-9AF9-A3553CD30C02}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Notification Management", "Notification Management", "{B7FAFCDD-492F-462B-94EA-59602F55033A}" -EndProject -Project("{00D1A9C2-B5F0-4AF3-8072-F6C62B433612}") = "NM.Database", "Notification Management\NM.Database\NM.Database.sqlproj", "{CF531B50-3AA2-44F8-A82B-D21F479CA781}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "IM.Services", "Inventory Management\IM.Services\IM.Services.csproj", "{9B51DEE9-FB4A-439E-81AC-0537378C2835}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NM.Services", "Notification Management\NM.Services\NM.Services.csproj", "{E5586019-E47F-4EAF-A0D8-A403710F1046}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OM.Services", "Order Management\OM.Services\OM.Services.csproj", "{1052C01C-620E-4458-A616-15510F9CBFA2}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PM.Services", "Product Management\PM.Services\PM.Services.csproj", "{DA99F252-4324-45A9-A91D-47C1537F6EC0}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SM.Services", "Shipping Management\SM.Services\SM.Services.csproj", "{47143F4A-308C-45E8-900E-E29BD71CE956}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Core", "Core", "{C249EE2A-3B49-40CF-8FD5-4CA8B512530B}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Core", "Core\Core\Core.csproj", "{9960D716-A872-468B-A472-C89E2493AC25}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ConsoleApp1", "ConsoleApp1\ConsoleApp1.csproj", "{68FC56CE-D97A-4AE9-9F96-C2D628A2A32B}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OM.Functions", "Order Management\OM.Functions\OM.Functions.csproj", "{A52A3658-80E4-465F-857C-A2656E01D4D0}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Core.Azure.Functions", "Core\Core.Azure.Functions\Core.Azure.Functions.csproj", "{2E80F237-FAE1-4BE8-AFD7-031F4AB7B939}" -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Any CPU = Debug|Any CPU - Release|Any CPU = Release|Any CPU - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {FFC24FE7-3A60-4EBB-B9AE-467B8B272E51}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {FFC24FE7-3A60-4EBB-B9AE-467B8B272E51}.Debug|Any CPU.Build.0 = Debug|Any CPU - {FFC24FE7-3A60-4EBB-B9AE-467B8B272E51}.Release|Any CPU.ActiveCfg = Release|Any CPU - {FFC24FE7-3A60-4EBB-B9AE-467B8B272E51}.Release|Any CPU.Build.0 = Release|Any CPU - {F0225AB4-2836-492A-B0E2-6A0B0667E555}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {F0225AB4-2836-492A-B0E2-6A0B0667E555}.Debug|Any CPU.Build.0 = Debug|Any CPU - {F0225AB4-2836-492A-B0E2-6A0B0667E555}.Debug|Any CPU.Deploy.0 = Debug|Any CPU - {F0225AB4-2836-492A-B0E2-6A0B0667E555}.Release|Any CPU.ActiveCfg = Release|Any CPU - {F0225AB4-2836-492A-B0E2-6A0B0667E555}.Release|Any CPU.Build.0 = Release|Any CPU - {F0225AB4-2836-492A-B0E2-6A0B0667E555}.Release|Any CPU.Deploy.0 = Release|Any CPU - {FE682F58-F6CA-4E1D-8DED-66BC07BA3972}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {FE682F58-F6CA-4E1D-8DED-66BC07BA3972}.Debug|Any CPU.Build.0 = Debug|Any CPU - {FE682F58-F6CA-4E1D-8DED-66BC07BA3972}.Debug|Any CPU.Deploy.0 = Debug|Any CPU - {FE682F58-F6CA-4E1D-8DED-66BC07BA3972}.Release|Any CPU.ActiveCfg = Release|Any CPU - {FE682F58-F6CA-4E1D-8DED-66BC07BA3972}.Release|Any CPU.Build.0 = Release|Any CPU - {FE682F58-F6CA-4E1D-8DED-66BC07BA3972}.Release|Any CPU.Deploy.0 = Release|Any CPU - {5A4BB707-FB63-40A1-9AF9-A3553CD30C02}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {5A4BB707-FB63-40A1-9AF9-A3553CD30C02}.Debug|Any CPU.Build.0 = Debug|Any CPU - {5A4BB707-FB63-40A1-9AF9-A3553CD30C02}.Debug|Any CPU.Deploy.0 = Debug|Any CPU - {5A4BB707-FB63-40A1-9AF9-A3553CD30C02}.Release|Any CPU.ActiveCfg = Release|Any CPU - {5A4BB707-FB63-40A1-9AF9-A3553CD30C02}.Release|Any CPU.Build.0 = Release|Any CPU - {5A4BB707-FB63-40A1-9AF9-A3553CD30C02}.Release|Any CPU.Deploy.0 = Release|Any CPU - {CF531B50-3AA2-44F8-A82B-D21F479CA781}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {CF531B50-3AA2-44F8-A82B-D21F479CA781}.Debug|Any CPU.Build.0 = Debug|Any CPU - {CF531B50-3AA2-44F8-A82B-D21F479CA781}.Debug|Any CPU.Deploy.0 = Debug|Any CPU - {CF531B50-3AA2-44F8-A82B-D21F479CA781}.Release|Any CPU.ActiveCfg = Release|Any CPU - {CF531B50-3AA2-44F8-A82B-D21F479CA781}.Release|Any CPU.Build.0 = Release|Any CPU - {CF531B50-3AA2-44F8-A82B-D21F479CA781}.Release|Any CPU.Deploy.0 = Release|Any CPU - {9B51DEE9-FB4A-439E-81AC-0537378C2835}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {9B51DEE9-FB4A-439E-81AC-0537378C2835}.Debug|Any CPU.Build.0 = Debug|Any CPU - {9B51DEE9-FB4A-439E-81AC-0537378C2835}.Release|Any CPU.ActiveCfg = Release|Any CPU - {9B51DEE9-FB4A-439E-81AC-0537378C2835}.Release|Any CPU.Build.0 = Release|Any CPU - {E5586019-E47F-4EAF-A0D8-A403710F1046}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {E5586019-E47F-4EAF-A0D8-A403710F1046}.Debug|Any CPU.Build.0 = Debug|Any CPU - {E5586019-E47F-4EAF-A0D8-A403710F1046}.Release|Any CPU.ActiveCfg = Release|Any CPU - {E5586019-E47F-4EAF-A0D8-A403710F1046}.Release|Any CPU.Build.0 = Release|Any CPU - {1052C01C-620E-4458-A616-15510F9CBFA2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {1052C01C-620E-4458-A616-15510F9CBFA2}.Debug|Any CPU.Build.0 = Debug|Any CPU - {1052C01C-620E-4458-A616-15510F9CBFA2}.Release|Any CPU.ActiveCfg = Release|Any CPU - {1052C01C-620E-4458-A616-15510F9CBFA2}.Release|Any CPU.Build.0 = Release|Any CPU - {DA99F252-4324-45A9-A91D-47C1537F6EC0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {DA99F252-4324-45A9-A91D-47C1537F6EC0}.Debug|Any CPU.Build.0 = Debug|Any CPU - {DA99F252-4324-45A9-A91D-47C1537F6EC0}.Release|Any CPU.ActiveCfg = Release|Any CPU - {DA99F252-4324-45A9-A91D-47C1537F6EC0}.Release|Any CPU.Build.0 = Release|Any CPU - {47143F4A-308C-45E8-900E-E29BD71CE956}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {47143F4A-308C-45E8-900E-E29BD71CE956}.Debug|Any CPU.Build.0 = Debug|Any CPU - {47143F4A-308C-45E8-900E-E29BD71CE956}.Release|Any CPU.ActiveCfg = Release|Any CPU - {47143F4A-308C-45E8-900E-E29BD71CE956}.Release|Any CPU.Build.0 = Release|Any CPU - {9960D716-A872-468B-A472-C89E2493AC25}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {9960D716-A872-468B-A472-C89E2493AC25}.Debug|Any CPU.Build.0 = Debug|Any CPU - {9960D716-A872-468B-A472-C89E2493AC25}.Release|Any CPU.ActiveCfg = Release|Any CPU - {9960D716-A872-468B-A472-C89E2493AC25}.Release|Any CPU.Build.0 = Release|Any CPU - {68FC56CE-D97A-4AE9-9F96-C2D628A2A32B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {68FC56CE-D97A-4AE9-9F96-C2D628A2A32B}.Debug|Any CPU.Build.0 = Debug|Any CPU - {68FC56CE-D97A-4AE9-9F96-C2D628A2A32B}.Release|Any CPU.ActiveCfg = Release|Any CPU - {68FC56CE-D97A-4AE9-9F96-C2D628A2A32B}.Release|Any CPU.Build.0 = Release|Any CPU - {A52A3658-80E4-465F-857C-A2656E01D4D0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {A52A3658-80E4-465F-857C-A2656E01D4D0}.Debug|Any CPU.Build.0 = Debug|Any CPU - {A52A3658-80E4-465F-857C-A2656E01D4D0}.Release|Any CPU.ActiveCfg = Release|Any CPU - {A52A3658-80E4-465F-857C-A2656E01D4D0}.Release|Any CPU.Build.0 = Release|Any CPU - {2E80F237-FAE1-4BE8-AFD7-031F4AB7B939}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {2E80F237-FAE1-4BE8-AFD7-031F4AB7B939}.Debug|Any CPU.Build.0 = Debug|Any CPU - {2E80F237-FAE1-4BE8-AFD7-031F4AB7B939}.Release|Any CPU.ActiveCfg = Release|Any CPU - {2E80F237-FAE1-4BE8-AFD7-031F4AB7B939}.Release|Any CPU.Build.0 = Release|Any CPU - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection - GlobalSection(NestedProjects) = preSolution - {FFC24FE7-3A60-4EBB-B9AE-467B8B272E51} = {301D6075-6E8E-448B-B4E1-2667538C599E} - {F0225AB4-2836-492A-B0E2-6A0B0667E555} = {535EEF89-F922-4C50-B714-184DBDAD6597} - {FE682F58-F6CA-4E1D-8DED-66BC07BA3972} = {0B879EDA-4DC3-4632-81D3-5F3807E070F3} - {5A4BB707-FB63-40A1-9AF9-A3553CD30C02} = {B847CBC3-2B9F-46AE-8082-F37F9EDBAFC0} - {CF531B50-3AA2-44F8-A82B-D21F479CA781} = {B7FAFCDD-492F-462B-94EA-59602F55033A} - {9B51DEE9-FB4A-439E-81AC-0537378C2835} = {535EEF89-F922-4C50-B714-184DBDAD6597} - {E5586019-E47F-4EAF-A0D8-A403710F1046} = {B7FAFCDD-492F-462B-94EA-59602F55033A} - {1052C01C-620E-4458-A616-15510F9CBFA2} = {0B879EDA-4DC3-4632-81D3-5F3807E070F3} - {DA99F252-4324-45A9-A91D-47C1537F6EC0} = {301D6075-6E8E-448B-B4E1-2667538C599E} - {47143F4A-308C-45E8-900E-E29BD71CE956} = {B847CBC3-2B9F-46AE-8082-F37F9EDBAFC0} - {9960D716-A872-468B-A472-C89E2493AC25} = {C249EE2A-3B49-40CF-8FD5-4CA8B512530B} - {A52A3658-80E4-465F-857C-A2656E01D4D0} = {0B879EDA-4DC3-4632-81D3-5F3807E070F3} - {2E80F237-FAE1-4BE8-AFD7-031F4AB7B939} = {C249EE2A-3B49-40CF-8FD5-4CA8B512530B} - EndGlobalSection - GlobalSection(ExtensibilityGlobals) = postSolution - SolutionGuid = {46272F77-407E-4A65-AD1C-3B1479BE63E0} - EndGlobalSection -EndGlobal diff --git a/Workshop/Solution/Product Management/PM.LoadDatabase/Constants/ConfigurationKeys.cs b/Workshop/Solution/Product Management/PM.LoadDatabase/Constants/ConfigurationKeys.cs deleted file mode 100644 index 8921497..0000000 --- a/Workshop/Solution/Product Management/PM.LoadDatabase/Constants/ConfigurationKeys.cs +++ /dev/null @@ -1,14 +0,0 @@ -namespace BuildingBricks.PM.Constants; - -public static class ConfigurationKeys -{ - public const string CosmosKey = "Cosmos:Key"; - public const string CosmosUri = "Cosmos:Uri"; - public const string PMCosmosDatabaseId = "ProductManagement:Cosmos:DatabaseId"; - public const string PMMetadataContainerId = "ProductManagement:Cosmos:Metadata:ContainerId"; - public const string PMMetadataPartitionKey = "ProductManagement:Cosmos:Metadata:PartitionKey"; - public const string PMProductsByAvailabilityContainerId = "ProductManagement:Cosmos:ProductsByAvailability:ContainerId"; - public const string PMProductsByAvailabilityPartitionKey = "ProductManagement:Cosmos:ProductsByAvailability:PartitionKey"; - public const string PMProductsByThemeContainerId = "ProductManagement:Cosmos:ProductsByTheme:ContainerId"; - public const string PMProductsByThemePartitionKey = "ProductManagement:Cosmos:ProductsByTheme:PartitionKey"; -} \ No newline at end of file diff --git a/Workshop/Solution/Product Management/PM.LoadDatabase/Constants/EnvironmentVariables.cs b/Workshop/Solution/Product Management/PM.LoadDatabase/Constants/EnvironmentVariables.cs deleted file mode 100644 index 06c781e..0000000 --- a/Workshop/Solution/Product Management/PM.LoadDatabase/Constants/EnvironmentVariables.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace BuildingBricks.PM.Constants; - -public static class EnvironmentVariables -{ - public const string AppConfig = "OrderProcessingSystem-AppConfig"; - public const string CosmosKey = "OrderProcessingSystem-CosmosKey"; -} \ No newline at end of file diff --git a/Workshop/Solution/Product Management/PM.LoadDatabase/Constants/MetadataTypeConstants.cs b/Workshop/Solution/Product Management/PM.LoadDatabase/Constants/MetadataTypeConstants.cs deleted file mode 100644 index d1c1f27..0000000 --- a/Workshop/Solution/Product Management/PM.LoadDatabase/Constants/MetadataTypeConstants.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace BuildingBricks.PM.Constants; - -public static class MetadataTypeConstants -{ - public const string Availability = "Availability"; - public const string MetadataType = "MetadataType"; - public const string Theme = "Theme"; -} \ No newline at end of file diff --git a/Workshop/Solution/Product Management/PM.LoadDatabase/GlobalUsings.cs b/Workshop/Solution/Product Management/PM.LoadDatabase/GlobalUsings.cs deleted file mode 100644 index ab5ee13..0000000 --- a/Workshop/Solution/Product Management/PM.LoadDatabase/GlobalUsings.cs +++ /dev/null @@ -1 +0,0 @@ -global using Newtonsoft.Json; \ No newline at end of file diff --git a/Workshop/Solution/Product Management/PM.LoadDatabase/Models/Availability.cs b/Workshop/Solution/Product Management/PM.LoadDatabase/Models/Availability.cs deleted file mode 100644 index 66a594f..0000000 --- a/Workshop/Solution/Product Management/PM.LoadDatabase/Models/Availability.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace BuildingBricks.PM.Models; - -public class Availability : Metadata -{ - - [JsonProperty("name")] - public string Name { get; set; } = null!; - -} \ No newline at end of file diff --git a/Workshop/Solution/Product Management/PM.LoadDatabase/Models/Merchandise.cs b/Workshop/Solution/Product Management/PM.LoadDatabase/Models/Merchandise.cs deleted file mode 100644 index 8211571..0000000 --- a/Workshop/Solution/Product Management/PM.LoadDatabase/Models/Merchandise.cs +++ /dev/null @@ -1,63 +0,0 @@ -namespace BuildingBricks.PM.Models; - -public class Merchandise -{ - - [JsonProperty("id")] - public string Id { get; set; } = null!; - - [JsonProperty("name")] - public string Name { get; set; } = null!; - - [JsonProperty("themeId")] - public string ThemeId { get; set; } = null!; - - [JsonProperty("themeName")] - public string ThemeName { get; set; } = null!; - - [JsonProperty("yearReleased")] - public int YearReleased { get; set; } - - [JsonProperty("weight")] - public int Weight { get; set; } - - [JsonProperty("price")] - public decimal Price { get; set; } - - [JsonProperty("pieces")] - public int Pieces { get; set; } - - [JsonProperty("vipPoints")] - public int VIPPoints { get; set; } - - [JsonProperty("hardToFind")] - public bool HardToFind { get; set; } - - [JsonProperty("availabilityId")] - public string AvailabilityId { get; set; } = null!; - - [JsonProperty("availability")] - public string Availability { get; set; } = null!; - - [JsonProperty("description")] - public string Description { get; set; } = null!; - - [JsonProperty("heightCentimeters")] - public int HeightCentimeters { get; set; } - - [JsonProperty("heightInches")] - public decimal HeightInches { get; set; } - - [JsonProperty("widthCentimeters")] - public int WidthCentimeters { get; set; } - - [JsonProperty("widthInches")] - public decimal WidthInches { get; set; } - - [JsonProperty("depthCentimeters")] - public int DepthCentimeters { get; set; } - - [JsonProperty("depthInches")] - public decimal DepthInches { get; set; } - -} \ No newline at end of file diff --git a/Workshop/Solution/Product Management/PM.LoadDatabase/Models/Metadata.cs b/Workshop/Solution/Product Management/PM.LoadDatabase/Models/Metadata.cs deleted file mode 100644 index 3376363..0000000 --- a/Workshop/Solution/Product Management/PM.LoadDatabase/Models/Metadata.cs +++ /dev/null @@ -1,15 +0,0 @@ -namespace BuildingBricks.PM.Models; - -public abstract class Metadata -{ - - [JsonProperty("id")] - public string Id { get; set; } = null!; - - [JsonProperty("legacyId")] - public int LegacyId { get; set; } - - [JsonProperty("metadataType")] - public string MetadataType { get; set; } = null!; - -} \ No newline at end of file diff --git a/Workshop/Solution/Product Management/PM.LoadDatabase/Models/Theme.cs b/Workshop/Solution/Product Management/PM.LoadDatabase/Models/Theme.cs deleted file mode 100644 index 0f09332..0000000 --- a/Workshop/Solution/Product Management/PM.LoadDatabase/Models/Theme.cs +++ /dev/null @@ -1,12 +0,0 @@ -namespace BuildingBricks.PM.Models; - -public class Theme : Metadata -{ - - [JsonProperty("name")] - public string Name { get; set; } = null!; - - [JsonProperty("merchandises")] - public List Merchandises { get; set; } = new(); - -} \ No newline at end of file diff --git a/Workshop/Solution/Product Management/PM.LoadDatabase/Models/ThemeMerchandise.cs b/Workshop/Solution/Product Management/PM.LoadDatabase/Models/ThemeMerchandise.cs deleted file mode 100644 index 5040d7f..0000000 --- a/Workshop/Solution/Product Management/PM.LoadDatabase/Models/ThemeMerchandise.cs +++ /dev/null @@ -1,29 +0,0 @@ -namespace BuildingBricks.PM.Models; - -#nullable disable - -public class ThemeMerchandise -{ - - [JsonProperty("itemNumber")] - public string ItemNumber { get; set; } = null!; - - [JsonProperty("itemName")] - public string ItemName { get; set; } = null!; - - [JsonProperty("pieces")] - public int Pieces { get; set; } - - [JsonProperty("price")] - public decimal Price { get; set; } - - [JsonProperty("hardToFind")] - public bool HardToFind { get; set; } - - [JsonProperty("availabilityId")] - public string AvailabilityId { get; set; } - - [JsonProperty("availability")] - public string Availability { get; set; } = null!; - -} \ No newline at end of file diff --git a/Workshop/Solution/Product Management/PM.LoadDatabase/PM.LoadDatabase.csproj b/Workshop/Solution/Product Management/PM.LoadDatabase/PM.LoadDatabase.csproj deleted file mode 100644 index d9474a1..0000000 --- a/Workshop/Solution/Product Management/PM.LoadDatabase/PM.LoadDatabase.csproj +++ /dev/null @@ -1,17 +0,0 @@ - - - - Exe - net6.0 - enable - enable - BuildingBricks.PM - - - - - - - - - diff --git a/Workshop/Solution/Product Management/PM.LoadDatabase/Program.cs b/Workshop/Solution/Product Management/PM.LoadDatabase/Program.cs deleted file mode 100644 index 032c6ad..0000000 --- a/Workshop/Solution/Product Management/PM.LoadDatabase/Program.cs +++ /dev/null @@ -1,209 +0,0 @@ -using BuildingBricks.PM.Constants; -using BuildingBricks.PM.Models; -using Microsoft.Azure.Cosmos; -using Microsoft.Extensions.Configuration; -using System.Text.Json; -using System.Text.Json.Serialization; -using JsonSerializer = System.Text.Json.JsonSerializer; - -PrintBanner(); - -JsonSerializerOptions jsonSerializerOptions = BuildJsonSerializerOptions(); -IConfiguration config = GetConfiguration(); -string dataPath = GetDataPath(); - -using CosmosClient cosmosClient = new(config[ConfigurationKeys.CosmosUri], Environment.GetEnvironmentVariable(EnvironmentVariables.CosmosKey)!); -Database database = await ConnectToDatabaseAsync(config[ConfigurationKeys.PMMetadataContainerId]); - -Container metadataContainer = await ConnectToContainerAsync(config[ConfigurationKeys.PMMetadataContainerId], config[ConfigurationKeys.PMMetadataPartitionKey]); -await LoadAvailabilitiesAsync(); -await LoadThemesAsync(); - -await LoadMerchandiseAsync(); - -static void PrintBanner() -{ - Console.Clear(); - Console.ForegroundColor = ConsoleColor.Green; - Console.WriteLine(@".____ .___ __________ .___ __ "); - Console.WriteLine(@"| | _________ __| _/ \______ \_______ ____ __| _/_ __ _____/ |_ ______"); - Console.WriteLine(@"| | / _ \__ \ / __ | | ___/\_ __ \/ _ \ / __ | | \_/ ___\ __\/ ___/"); - Console.WriteLine(@"| |__( <_> ) __ \_/ /_/ | | | | | \( <_> ) /_/ | | /\ \___| | \___ \ "); - Console.WriteLine(@"|_______ \____(____ /\____ | |____| |__| \____/\____ |____/ \___ >__| /____ >"); - Console.WriteLine(@" \/ \/ \/ \/ \/ \/ "); - Console.WriteLine(); - Console.WriteLine(); - Console.ResetColor(); -} - -static JsonSerializerOptions BuildJsonSerializerOptions() - => new() - { - PropertyNamingPolicy = JsonNamingPolicy.CamelCase, - DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull, - DictionaryKeyPolicy = JsonNamingPolicy.CamelCase, - WriteIndented = true - }; - -IConfiguration GetConfiguration() -{ - ConfigurationBuilder builder = new(); - builder.AddAzureAppConfiguration(Environment.GetEnvironmentVariable(EnvironmentVariables.AppConfig)!); - return builder.Build(); -} - -static string GetDataPath() -{ - string? response; - do - { - Console.Write("Path to data files: "); - response = Console.ReadLine(); - if (!Directory.Exists(response)) - { - Console.ForegroundColor = ConsoleColor.Red; - Console.WriteLine("Invalid path"); - Console.Beep(); - Console.ResetColor(); - response = null; - } - } while (response is null); - return response; -} - -async Task ConnectToDatabaseAsync(string id) -{ - Database database = await cosmosClient.CreateDatabaseIfNotExistsAsync(id); - Console.WriteLine($"Database Connected:\t{database.Id}"); - return database; -} - -async Task ConnectToContainerAsync(string id, string partitionKeyPath) -{ - Container container = await database.CreateContainerIfNotExistsAsync(id, partitionKeyPath); - Console.WriteLine($"Container Connected:\t{container.Id}"); - return container; -} - -async Task> RetrieveMetadataByLegacyIdAsync(string metadataType) where T : Metadata -{ - Dictionary response = new(); - using FeedIterator feed = metadataContainer.GetItemQueryIterator($"SELECT * FROM metadata"); - while (feed.HasMoreResults) - { - FeedResponse feedResponse = await feed.ReadNextAsync(); - foreach (T item in feedResponse) - { - if (item.MetadataType == metadataType) - response.Add(item.LegacyId, item); - } - } - return response; -} - -IEnumerable GetDirectoryFilePaths(string directoryName) - => Directory.EnumerateFiles($@"{dataPath}\{directoryName}"); - -async Task LoadAvailabilitiesAsync() -{ - Console.WriteLine("Loading availabilities"); - Dictionary existingAvailabilities = await RetrieveMetadataByLegacyIdAsync(MetadataTypeConstants.Availability); - IEnumerable filePaths = GetDirectoryFilePaths(MetadataTypeConstants.Availability); - if (filePaths.Any()) - { - foreach (string filePath in filePaths) - { - Availability? availability = JsonSerializer.Deserialize(File.ReadAllText(filePath), jsonSerializerOptions); - if (availability is not null) - { - availability.MetadataType = MetadataTypeConstants.Availability; - availability.LegacyId = Convert.ToInt32(availability.Id); - availability.Id = Guid.NewGuid().ToString(); - if (!existingAvailabilities.ContainsKey(availability.LegacyId)) - { - Availability createdItem = await metadataContainer.CreateItemAsync(availability, new PartitionKey(MetadataTypeConstants.Availability)); - Console.WriteLine($"\t{createdItem.Id}\t{createdItem.Name}"); - } - } - } - } -} - -async Task LoadThemesAsync() -{ - Console.WriteLine("Loading themes"); - Dictionary availabilities = await RetrieveMetadataByLegacyIdAsync(MetadataTypeConstants.Availability); - Dictionary existingThemes = await RetrieveMetadataByLegacyIdAsync(MetadataTypeConstants.Theme); - foreach (string filePath in GetDirectoryFilePaths(MetadataTypeConstants.Theme)) - { - Theme? theme = JsonSerializer.Deserialize(File.ReadAllText(filePath), jsonSerializerOptions); - if (theme is not null) - { - theme.MetadataType = MetadataTypeConstants.Theme; - theme.LegacyId = Convert.ToInt32(theme.Id); - theme.Id = Guid.NewGuid().ToString(); - foreach (ThemeMerchandise themeMerchandise in theme.Merchandises) - { - if (availabilities.TryGetValue(Convert.ToInt32(themeMerchandise.AvailabilityId), out Availability? availability) && availability is not null) - { - themeMerchandise.AvailabilityId = availability.Id; - themeMerchandise.Availability = availability.Name; - } - } - if (!existingThemes.ContainsKey(theme.LegacyId)) - { - Theme createdItem = await metadataContainer.CreateItemAsync(theme, new PartitionKey(MetadataTypeConstants.Theme)); - Console.WriteLine($"\t{createdItem.Id}\t{createdItem.Name}"); - } - } - } -} - -async Task LoadMerchandiseAsync() -{ - Console.WriteLine("Loading merchandise"); - - Dictionary availabilities = await RetrieveMetadataByLegacyIdAsync(MetadataTypeConstants.Availability); - Dictionary existingThemes = await RetrieveMetadataByLegacyIdAsync(MetadataTypeConstants.Theme); - - Container productsByAvailabilityContainer = await ConnectToContainerAsync(config[ConfigurationKeys.PMProductsByAvailabilityContainerId], config[ConfigurationKeys.PMProductsByAvailabilityPartitionKey]); - List productsByAvailability = await RetrieveMerchandiseListAsync(productsByAvailabilityContainer); - - Container productsByThemeContainer = await ConnectToContainerAsync(config[ConfigurationKeys.PMProductsByThemeContainerId], config[ConfigurationKeys.PMProductsByThemePartitionKey]); - List productsByTheme = await RetrieveMerchandiseListAsync(productsByThemeContainer); - - foreach (string filePath in GetDirectoryFilePaths("Merchandise")) - { - Merchandise? merchandise = JsonSerializer.Deserialize(File.ReadAllText(filePath), jsonSerializerOptions); - if (merchandise is not null - && availabilities.TryGetValue(Convert.ToInt32(merchandise.AvailabilityId), out Availability? availability) && availability is not null - && existingThemes.TryGetValue(Convert.ToInt32(merchandise.ThemeId), out Theme? theme) && theme is not null) - { - merchandise.AvailabilityId = availability.Id; - merchandise.ThemeId = theme.Id; - if (!productsByAvailability.Contains(merchandise.Id)) - { - Merchandise createdItem = await productsByAvailabilityContainer.CreateItemAsync(merchandise, new PartitionKey(merchandise.AvailabilityId)); - Console.WriteLine($"\t(Availability)\t{createdItem.Id}\t{createdItem.Name}"); - } - if (!productsByTheme.Contains(merchandise.Id)) - { - Merchandise createdItem = await productsByThemeContainer.CreateItemAsync(merchandise, new PartitionKey(merchandise.ThemeId)); - Console.WriteLine($"\t(Theme)\t{createdItem.Id}\t{createdItem.Name}"); - } - } - } -} - -async Task> RetrieveMerchandiseListAsync(Container container) -{ - List response = new(); - using FeedIterator feed = container.GetItemQueryIterator($"SELECT * FROM merchandise"); - while (feed.HasMoreResults) - { - FeedResponse feedResponse = await feed.ReadNextAsync(); - foreach (Merchandise item in feedResponse) - response.Add(item.Id); - } - return response; -} diff --git a/Workshop/Solution/Product Management/PM.Services/PM.Services.csproj b/Workshop/Solution/Product Management/PM.Services/PM.Services.csproj deleted file mode 100644 index 132c02c..0000000 --- a/Workshop/Solution/Product Management/PM.Services/PM.Services.csproj +++ /dev/null @@ -1,9 +0,0 @@ - - - - net6.0 - enable - enable - - - diff --git a/Workshop/Solution/Shipping Management/SM.Database/SM.Database.sqlproj b/Workshop/Solution/Shipping Management/SM.Database/SM.Database.sqlproj deleted file mode 100644 index 8328563..0000000 --- a/Workshop/Solution/Shipping Management/SM.Database/SM.Database.sqlproj +++ /dev/null @@ -1,83 +0,0 @@ - - - - Debug - AnyCPU - SM.Database - 2.0 - 4.1 - {5a4bb707-fb63-40a1-9af9-a3553cd30c02} - Microsoft.Data.Tools.Schema.Sql.SqlAzureV12DatabaseSchemaProvider - Database - - - SM.Database - SM.Database - 1033, CI - BySchemaAndSchemaType - True - v4.7.2 - CS - Properties - False - True - True - Shipping - - - bin\Release\ - $(MSBuildProjectName).sql - False - pdbonly - true - false - true - prompt - 4 - - - bin\Debug\ - $(MSBuildProjectName).sql - false - true - full - false - true - true - prompt - 4 - True - - - 11.0 - - True - 11.0 - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/Workshop/Solution/Shipping Management/SM.Database/Scripts/Populate/Populate_Customer.sql b/Workshop/Solution/Shipping Management/SM.Database/Scripts/Populate/Populate_Customer.sql deleted file mode 100644 index 9f8d591..0000000 --- a/Workshop/Solution/Shipping Management/SM.Database/Scripts/Populate/Populate_Customer.sql +++ /dev/null @@ -1,33 +0,0 @@ -MERGE INTO Shipping.Customer AS TARGET -USING (VALUES (1, 'Julie', 'Hartshorn', '2598 Butternut Lane', 'Ava', 'IL', 'US', '62707'), - (2, 'William', 'Pierce', '859 Courtright Street', 'Gardner', 'ND', 'US', '58036'), - (3, 'Christine', 'Brandt', '3428 Burnside Avenue', 'Monument Valley', 'UT', 'US', '84536'), - (4, 'Patsy', 'Hauser', '121 Old Dear Lane', 'Marlboro', 'NY', 'US', '12542'), - (5, 'Carlos', 'Riley', '2949 White River Way', 'Salt Lake City', 'UT', 'US', '84104'), - (6, 'Edward', 'Tate', '2776 MacLaren Street', 'Ottawa', 'ON', 'CA', 'K1P 5M7'), - (7, 'Christi', 'Miller', '775 Lynden Road', 'Orono', 'ON', 'CA', 'L0B 1M0')) -AS SOURCE (CustomerId, FirstName, LastName, StreetAddress, City, CountryDivisionCode, CountryCode, PostalCode) -ON TARGET.CustomerId = SOURCE.CustomerId -WHEN MATCHED THEN UPDATE SET TARGET.FirstName = SOURCE.FirstName, - TARGET.LastName = SOURCE.LastName, - TARGET.StreetAddress = SOURCE.StreetAddress, - TARGET.City = SOURCE.City, - TARGET.CountryDivisionCode = SOURCE.CountryDivisionCode, - TARGET.CountryCode = SOURCE.CountryCode, - TARGET.PostalCode = SOURCE.PostalCode -WHEN NOT MATCHED THEN INSERT (CustomerId, - FirstName, - LastName, - StreetAddress, - City, - CountryDivisionCode, - CountryCode, - PostalCode) - VALUES (SOURCE.CustomerId, - SOURCE.FirstName, - SOURCE.LastName, - SOURCE.StreetAddress, - SOURCE.City, - SOURCE.CountryDivisionCode, - SOURCE.CountryCode, - SOURCE.PostalCode); \ No newline at end of file diff --git a/Workshop/Solution/Shipping Management/SM.Database/Scripts/Populate/Populate_Product.sql b/Workshop/Solution/Shipping Management/SM.Database/Scripts/Populate/Populate_Product.sql deleted file mode 100644 index aed1231..0000000 --- a/Workshop/Solution/Shipping Management/SM.Database/Scripts/Populate/Populate_Product.sql +++ /dev/null @@ -1,74 +0,0 @@ -MERGE INTO Shipping.Product AS TARGET -USING (VALUES ('10255', 4666, 'Assembly Square'), - ('10265', 1820, 'Ford Mustang'), - ('10266', 1349, 'NASA Apollo 11 Lunar Lander'), - ('10270', 2932, 'Bookshop'), - ('10273', 4261, 'Hauned House'), - ('10274', 3145, 'Ghostbusters™ ECTO-1'), - ('10276', 10780, 'Colosseum'), - ('10278', 4155, 'Police Station'), - ('10280', 774, 'Flower Bouquet'), - ('10281', 768, 'Bonsai Tree'), - ('10283', 3249, 'NASA Space Shuttle Discovery'), - ('10290', 1954, 'Pickup Truck'), - ('10292', 2812, 'The Friends Apartments'), - ('10293', 1670, 'Santa''s Visit'), - ('10294', 14000, 'LEGO® Titanic'), - ('10295', 1979, 'Porsche 911'), - ('10297', 3473, 'Boutique Hotel'), - ('10298', 1447, 'Vespa 125'), - ('10299', 6860, 'Real Madrid – Santiago Bernabéu Stadium'), - ('10300', 2142, 'Back to the Future Time Machine'), - ('10302', 1670, 'Optimus Prime'), - ('10303', 5870, 'Loop Coaster'), - ('10304', 1830, 'Chevrolet Camaro Z28'), - ('10305', 6062, 'Bonsai Tree'), - ('10306', 3510, 'Atari® 2600'), - ('10307', 11680, 'Eiffel tower'), - ('10309', 665, 'Succulents'), - ('10312', 3870, 'Jazz Club'), - ('10314', 670, 'Corvette'), - ('10315', 3145, 'Tranquil Garden'), - ('10316', 6890, 'THE LORD OF THE RINGS: RIVENDELL™'), - ('10317', 2850, 'Land Rover Classic Defender 90'), - ('10320', 3310, 'Eldorado Fortress'), - ('10321', 3510, 'Corvette'), - ('10323', 3714, 'PAC-MAN Arcade'), - ('10497', 1758, 'Galaxy Explorer'), - ('21028', 549, 'New York City'), - ('21034', 606, 'London'), - ('21042', 1362, 'Statue of Liberty'), - ('21044', 670, 'Paris'), - ('21054', 1370, 'The White House'), - ('21056', 1780, 'Taj Mahal'), - ('21057', 597, 'Statue of Liberty'), - ('21058', 2471, 'Great Pyramid of Giza'), - ('21060', 2125, 'Himeji Castle'), - ('21318', 3870, 'Tree House'), - ('21323', 5780, 'Grand Piano'), - ('21325', 2415, 'Medieval Blacksmith'), - ('21326', 1268, 'Winnie the Pooh'), - ('21327', 2714, 'Typewriter'), - ('21330', 5390, 'LEGO® Ideas Home Alone'), - ('21332', 2645, 'The Globe'), - ('21334', 1810, 'Jazz Quartet'), - ('21335', 3335, 'Motorized Lighthouse'), - ('21336', 1708, 'The Office'), - ('21337', 3160, 'Table Football'), - ('21338', 2480, 'A-Frame Cabin'), - ('21339', 1348, 'BTS Dynamite'), - ('21340', 987, 'Tales of the Space Age'), - ('21341', 2900, 'Disney Hocus Pocus: The Sanderson Sisters'' Cottage'), - ('40220', 153, 'London Bus'), - ('40517', 142, 'Vespa'), - ('40518', 364, 'High Speed Train')) -AS SOURCE (ProductId, ProductWeight, ProductName) -ON TARGET.ProductId = SOURCE.ProductId -WHEN MATCHED THEN UPDATE SET TARGET.ProductName = SOURCE.ProductName, - TARGET.ProductWeight = SOURCE.ProductWeight -WHEN NOT MATCHED THEN INSERT (ProductId, - ProductName, - ProductWeight) - VALUES (SOURCE.ProductId, - SOURCE.ProductName, - SOURCE.ProductWeight); \ No newline at end of file diff --git a/Workshop/Solution/Shipping Management/SM.Database/Scripts/Populate/Populate_ShipmentStatus.sql b/Workshop/Solution/Shipping Management/SM.Database/Scripts/Populate/Populate_ShipmentStatus.sql deleted file mode 100644 index b290c09..0000000 --- a/Workshop/Solution/Shipping Management/SM.Database/Scripts/Populate/Populate_ShipmentStatus.sql +++ /dev/null @@ -1,12 +0,0 @@ -MERGE INTO Shipping.ShipmentStatus AS TARGET -USING (VALUES (1, 'Picking'), - (2, 'Packed'), - (3, 'Shipped'), - (4, 'Delivered')) -AS SOURCE (ShipmentStatusId, ShipmentStatusName) -ON TARGET.ShipmentStatusId = SOURCE.ShipmentStatusId -WHEN MATCHED THEN UPDATE SET TARGET.ShipmentStatusName = SOURCE.ShipmentStatusName -WHEN NOT MATCHED THEN INSERT (ShipmentStatusId, - ShipmentStatusName) - VALUES (SOURCE.ShipmentStatusId, - SOURCE.ShipmentStatusName); \ No newline at end of file diff --git a/Workshop/Solution/Shipping Management/SM.Database/Scripts/Script.PostDeployment.sql b/Workshop/Solution/Shipping Management/SM.Database/Scripts/Script.PostDeployment.sql deleted file mode 100644 index 79c0985..0000000 --- a/Workshop/Solution/Shipping Management/SM.Database/Scripts/Script.PostDeployment.sql +++ /dev/null @@ -1,3 +0,0 @@ -:r .\Populate\Populate_Product.sql -:r .\Populate\Populate_Customer.sql -:r .\Populate\Populate_ShipmentStatus.sql \ No newline at end of file diff --git a/Workshop/Solution/Shipping Management/SM.Database/Shipping.sql b/Workshop/Solution/Shipping Management/SM.Database/Shipping.sql deleted file mode 100644 index 2ea0725..0000000 --- a/Workshop/Solution/Shipping Management/SM.Database/Shipping.sql +++ /dev/null @@ -1 +0,0 @@ -CREATE SCHEMA Shipping \ No newline at end of file diff --git a/Workshop/Solution/Shipping Management/SM.Database/Tables/Customer.sql b/Workshop/Solution/Shipping Management/SM.Database/Tables/Customer.sql deleted file mode 100644 index d0ee0f5..0000000 --- a/Workshop/Solution/Shipping Management/SM.Database/Tables/Customer.sql +++ /dev/null @@ -1,12 +0,0 @@ -CREATE TABLE Shipping.Customer -( - CustomerId INT NOT NULL, - FirstName NVARCHAR(100) NOT NULL, - LastName NVARCHAR(100) NOT NULL, - StreetAddress NVARCHAR(100) NOT NULL, - City NVARCHAR(100) NOT NULL, - CountryDivisionCode CHAR(2) NULL, - CountryCode CHAR(2) NOT NULL, - PostalCode VARCHAR(20) NULL, - CONSTRAINT pkcCustomer PRIMARY KEY CLUSTERED (CustomerId) -) \ No newline at end of file diff --git a/Workshop/Solution/Shipping Management/SM.Database/Tables/CustomerOrder.sql b/Workshop/Solution/Shipping Management/SM.Database/Tables/CustomerOrder.sql deleted file mode 100644 index 86208e9..0000000 --- a/Workshop/Solution/Shipping Management/SM.Database/Tables/CustomerOrder.sql +++ /dev/null @@ -1,6 +0,0 @@ -CREATE TABLE Shipping.CustomerOrder -( - CustomerOrderId CHAR(36) NOT NULL, - CustomerId INT NOT NULL, - CONSTRAINT pkcCustomerOrder PRIMARY KEY CLUSTERED (CustomerOrderId) -) \ No newline at end of file diff --git a/Workshop/Solution/Shipping Management/SM.Database/Tables/OrderItem.sql b/Workshop/Solution/Shipping Management/SM.Database/Tables/OrderItem.sql deleted file mode 100644 index 02534d4..0000000 --- a/Workshop/Solution/Shipping Management/SM.Database/Tables/OrderItem.sql +++ /dev/null @@ -1,9 +0,0 @@ -CREATE TABLE Shipping.OrderItem -( - OrderItemId INT NOT NULL, - CustomerOrderId CHAR(36) NOT NULL, - ProductId CHAR(5) NOT NULL, - CONSTRAINT pkcOrderItem PRIMARY KEY CLUSTERED (OrderItemId), - CONSTRAINT fkOrderItem_CustomerOrder FOREIGN KEY (CustomerOrderId) REFERENCES Shipping.CustomerOrder(CustomerOrderId), - CONSTRAINT fkOrderItem_Product FOREIGN KEY (ProductId) REFERENCES Shipping.Product(ProductId) -) \ No newline at end of file diff --git a/Workshop/Solution/Shipping Management/SM.Database/Tables/Product.sql b/Workshop/Solution/Shipping Management/SM.Database/Tables/Product.sql deleted file mode 100644 index 9d6a265..0000000 --- a/Workshop/Solution/Shipping Management/SM.Database/Tables/Product.sql +++ /dev/null @@ -1,7 +0,0 @@ -CREATE TABLE Shipping.Product -( - ProductId CHAR(5) NOT NULL, - ProductName NVARCHAR(100) NOT NULL, - ProductWeight INT NOT NULL, - CONSTRAINT pkcProduct PRIMARY KEY CLUSTERED (ProductId) -) \ No newline at end of file diff --git a/Workshop/Solution/Shipping Management/SM.Database/Tables/Shipment.sql b/Workshop/Solution/Shipping Management/SM.Database/Tables/Shipment.sql deleted file mode 100644 index f68131f..0000000 --- a/Workshop/Solution/Shipping Management/SM.Database/Tables/Shipment.sql +++ /dev/null @@ -1,9 +0,0 @@ -CREATE TABLE Shipping.Shipment -( - ShipmentId INT NOT NULL IDENTITY(1,1), - ShipmentStatusId INT NOT NULL, - CustomerOrderId CHAR(36) NOT NULL, - CONSTRAINT pkcShipment PRIMARY KEY CLUSTERED (ShipmentId), - CONSTRAINT fkShipment_ShipmentStatus FOREIGN KEY (ShipmentStatusId) REFERENCES Shipping.ShipmentStatus (ShipmentStatusId), - CONSTRAINT fkShipment_CustomerOrder FOREIGN KEY (CustomerOrderId) REFERENCES Shipping.CustomerOrder (CustomerOrderId) -) \ No newline at end of file diff --git a/Workshop/Solution/Shipping Management/SM.Database/Tables/ShipmentStatus.sql b/Workshop/Solution/Shipping Management/SM.Database/Tables/ShipmentStatus.sql deleted file mode 100644 index 24cff49..0000000 --- a/Workshop/Solution/Shipping Management/SM.Database/Tables/ShipmentStatus.sql +++ /dev/null @@ -1,6 +0,0 @@ -CREATE TABLE Shipping.ShipmentStatus -( - ShipmentStatusId INT NOT NULL, - ShipmentStatusName VARCHAR(100) NOT NULL, - CONSTRAINT pkcShipmentStatus PRIMARY KEY CLUSTERED (ShipmentStatusId) -) \ No newline at end of file diff --git a/Workshop/Solution/Shipping Management/SM.Database/Tables/ShipmentStatusDetail.sql b/Workshop/Solution/Shipping Management/SM.Database/Tables/ShipmentStatusDetail.sql deleted file mode 100644 index 732b796..0000000 --- a/Workshop/Solution/Shipping Management/SM.Database/Tables/ShipmentStatusDetail.sql +++ /dev/null @@ -1,10 +0,0 @@ -CREATE TABLE Shipping.ShipmentStatusDetail -( - ShipmentStatusDetailId INT NOT NULL IDENTITY(1,1), - ShipmentId INT NOT NULL, - ShipmentStatusId INT NOT NULL, - StatusDateTime DATETIME2 NOT NULL CONSTRAINT dfShipmentStatusDetail_StatusDateTime DEFAULT(GETUTCDATE()), - CONSTRAINT pkcShipmentStatusDetail PRIMARY KEY CLUSTERED (ShipmentStatusDetailId), - CONSTRAINT fkShipmentStatusDetail_Shipment FOREIGN KEY (ShipmentId) REFERENCES Shipping.Shipment (ShipmentId), - CONSTRAINT fkShipmentStatusDetails_ShipmentStatus FOREIGN KEY (ShipmentStatusId) REFERENCES Shipping.ShipmentStatus (ShipmentStatusId) -) \ No newline at end of file diff --git a/Workshop/Solution/Shipping Management/SM.Services/Models/Customer.cs b/Workshop/Solution/Shipping Management/SM.Services/Models/Customer.cs deleted file mode 100644 index ce2c631..0000000 --- a/Workshop/Solution/Shipping Management/SM.Services/Models/Customer.cs +++ /dev/null @@ -1,17 +0,0 @@ -using System; -using System.Collections.Generic; - -namespace BuildingBricks.SM.Models -{ - public partial class Customer - { - public int CustomerId { get; set; } - public string FirstName { get; set; } = null!; - public string LastName { get; set; } = null!; - public string StreetAddress { get; set; } = null!; - public string City { get; set; } = null!; - public string? CountryDivisionCode { get; set; } - public string CountryCode { get; set; } = null!; - public string? PostalCode { get; set; } - } -} diff --git a/Workshop/Solution/Shipping Management/SM.Services/Models/CustomerOrder.cs b/Workshop/Solution/Shipping Management/SM.Services/Models/CustomerOrder.cs deleted file mode 100644 index e536c80..0000000 --- a/Workshop/Solution/Shipping Management/SM.Services/Models/CustomerOrder.cs +++ /dev/null @@ -1,20 +0,0 @@ -using System; -using System.Collections.Generic; - -namespace BuildingBricks.SM.Models -{ - public partial class CustomerOrder - { - public CustomerOrder() - { - OrderItems = new HashSet(); - Shipments = new HashSet(); - } - - public string CustomerOrderId { get; set; } = null!; - public int CustomerId { get; set; } - - public virtual ICollection OrderItems { get; set; } - public virtual ICollection Shipments { get; set; } - } -} diff --git a/Workshop/Solution/Shipping Management/SM.Services/Models/OrderItem.cs b/Workshop/Solution/Shipping Management/SM.Services/Models/OrderItem.cs deleted file mode 100644 index 1ffec27..0000000 --- a/Workshop/Solution/Shipping Management/SM.Services/Models/OrderItem.cs +++ /dev/null @@ -1,15 +0,0 @@ -using System; -using System.Collections.Generic; - -namespace BuildingBricks.SM.Models -{ - public partial class OrderItem - { - public int OrderItemId { get; set; } - public string CustomerOrderId { get; set; } = null!; - public string ProductId { get; set; } = null!; - - public virtual CustomerOrder CustomerOrder { get; set; } = null!; - public virtual Product Product { get; set; } = null!; - } -} diff --git a/Workshop/Solution/Shipping Management/SM.Services/Models/Product.cs b/Workshop/Solution/Shipping Management/SM.Services/Models/Product.cs deleted file mode 100644 index 9ffee82..0000000 --- a/Workshop/Solution/Shipping Management/SM.Services/Models/Product.cs +++ /dev/null @@ -1,19 +0,0 @@ -using System; -using System.Collections.Generic; - -namespace BuildingBricks.SM.Models -{ - public partial class Product - { - public Product() - { - OrderItems = new HashSet(); - } - - public string ProductId { get; set; } = null!; - public string ProductName { get; set; } = null!; - public int ProductWeight { get; set; } - - public virtual ICollection OrderItems { get; set; } - } -} diff --git a/Workshop/Solution/Shipping Management/SM.Services/Models/Shipment.cs b/Workshop/Solution/Shipping Management/SM.Services/Models/Shipment.cs deleted file mode 100644 index 2a5411a..0000000 --- a/Workshop/Solution/Shipping Management/SM.Services/Models/Shipment.cs +++ /dev/null @@ -1,21 +0,0 @@ -using System; -using System.Collections.Generic; - -namespace BuildingBricks.SM.Models -{ - public partial class Shipment - { - public Shipment() - { - ShipmentStatusDetails = new HashSet(); - } - - public int ShipmentId { get; set; } - public int ShipmentStatusId { get; set; } - public string CustomerOrderId { get; set; } = null!; - - public virtual CustomerOrder CustomerOrder { get; set; } = null!; - public virtual ShipmentStatus ShipmentStatus { get; set; } = null!; - public virtual ICollection ShipmentStatusDetails { get; set; } - } -} diff --git a/Workshop/Solution/Shipping Management/SM.Services/Models/ShipmentStatus.cs b/Workshop/Solution/Shipping Management/SM.Services/Models/ShipmentStatus.cs deleted file mode 100644 index a6ac010..0000000 --- a/Workshop/Solution/Shipping Management/SM.Services/Models/ShipmentStatus.cs +++ /dev/null @@ -1,20 +0,0 @@ -using System; -using System.Collections.Generic; - -namespace BuildingBricks.SM.Models -{ - public partial class ShipmentStatus - { - public ShipmentStatus() - { - ShipmentStatusDetails = new HashSet(); - Shipments = new HashSet(); - } - - public int ShipmentStatusId { get; set; } - public string ShipmentStatusName { get; set; } = null!; - - public virtual ICollection ShipmentStatusDetails { get; set; } - public virtual ICollection Shipments { get; set; } - } -} diff --git a/Workshop/Solution/Shipping Management/SM.Services/Models/ShipmentStatusDetail.cs b/Workshop/Solution/Shipping Management/SM.Services/Models/ShipmentStatusDetail.cs deleted file mode 100644 index b969f1a..0000000 --- a/Workshop/Solution/Shipping Management/SM.Services/Models/ShipmentStatusDetail.cs +++ /dev/null @@ -1,16 +0,0 @@ -using System; -using System.Collections.Generic; - -namespace BuildingBricks.SM.Models -{ - public partial class ShipmentStatusDetail - { - public int ShipmentStatusDetailId { get; set; } - public int ShipmentId { get; set; } - public int ShipmentStatusId { get; set; } - public DateTime StatusDateTime { get; set; } - - public virtual Shipment Shipment { get; set; } = null!; - public virtual ShipmentStatus ShipmentStatus { get; set; } = null!; - } -} diff --git a/Workshop/Solution/Shipping Management/SM.Services/SM.Services.csproj b/Workshop/Solution/Shipping Management/SM.Services/SM.Services.csproj deleted file mode 100644 index 8631cb0..0000000 --- a/Workshop/Solution/Shipping Management/SM.Services/SM.Services.csproj +++ /dev/null @@ -1,14 +0,0 @@ - - - - net6.0 - enable - enable - BuildingBricks.SM.Services - - - - - - - diff --git a/Workshop/Solution/Shipping Management/SM.Services/ShippingManagementContext.cs b/Workshop/Solution/Shipping Management/SM.Services/ShippingManagementContext.cs deleted file mode 100644 index 3078332..0000000 --- a/Workshop/Solution/Shipping Management/SM.Services/ShippingManagementContext.cs +++ /dev/null @@ -1,172 +0,0 @@ -using BuildingBricks.SM.Models; -using Microsoft.EntityFrameworkCore; - -namespace BuildingBricks.SM -{ - - public partial class ShippingManagementContext : DbContext - { - public ShippingManagementContext() - { - } - - public ShippingManagementContext(DbContextOptions options) - : base(options) - { - } - - public virtual DbSet Customers { get; set; } = null!; - public virtual DbSet CustomerOrders { get; set; } = null!; - public virtual DbSet OrderItems { get; set; } = null!; - public virtual DbSet Products { get; set; } = null!; - public virtual DbSet Shipments { get; set; } = null!; - public virtual DbSet ShipmentStatuses { get; set; } = null!; - public virtual DbSet ShipmentStatusDetails { get; set; } = null!; - - protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) - { - if (!optionsBuilder.IsConfigured) - { - optionsBuilder.UseSqlServer(Environment.GetEnvironmentVariable("OrderProcessingSystem-SM-DatabaseConnectionString")!); - } - } - - protected override void OnModelCreating(ModelBuilder modelBuilder) - { - modelBuilder.Entity(entity => - { - entity.ToTable("Customer", "Shipping"); - - entity.Property(e => e.CustomerId).ValueGeneratedNever(); - - entity.Property(e => e.City).HasMaxLength(100); - - entity.Property(e => e.CountryCode) - .HasMaxLength(2) - .IsUnicode(false) - .IsFixedLength(); - - entity.Property(e => e.CountryDivisionCode) - .HasMaxLength(2) - .IsUnicode(false) - .IsFixedLength(); - - entity.Property(e => e.FirstName).HasMaxLength(100); - - entity.Property(e => e.LastName).HasMaxLength(100); - - entity.Property(e => e.PostalCode) - .HasMaxLength(20) - .IsUnicode(false); - - entity.Property(e => e.StreetAddress).HasMaxLength(100); - }); - - modelBuilder.Entity(entity => - { - entity.ToTable("CustomerOrder", "Shipping"); - - entity.Property(e => e.CustomerOrderId) - .HasMaxLength(36) - .IsUnicode(false) - .IsFixedLength(); - }); - - modelBuilder.Entity(entity => - { - entity.ToTable("OrderItem", "Shipping"); - - entity.Property(e => e.OrderItemId).ValueGeneratedNever(); - - entity.Property(e => e.CustomerOrderId) - .HasMaxLength(36) - .IsUnicode(false) - .IsFixedLength(); - - entity.Property(e => e.ProductId) - .HasMaxLength(5) - .IsUnicode(false) - .IsFixedLength(); - - entity.HasOne(d => d.CustomerOrder) - .WithMany(p => p.OrderItems) - .HasForeignKey(d => d.CustomerOrderId) - .OnDelete(DeleteBehavior.ClientSetNull) - .HasConstraintName("fkOrderItem_CustomerOrder"); - - entity.HasOne(d => d.Product) - .WithMany(p => p.OrderItems) - .HasForeignKey(d => d.ProductId) - .OnDelete(DeleteBehavior.ClientSetNull) - .HasConstraintName("fkOrderItem_Product"); - }); - - modelBuilder.Entity(entity => - { - entity.ToTable("Product", "Shipping"); - - entity.Property(e => e.ProductId) - .HasMaxLength(5) - .IsUnicode(false) - .IsFixedLength(); - - entity.Property(e => e.ProductName).HasMaxLength(100); - }); - - modelBuilder.Entity(entity => - { - entity.ToTable("Shipment", "Shipping"); - - entity.Property(e => e.CustomerOrderId) - .HasMaxLength(36) - .IsUnicode(false) - .IsFixedLength(); - - entity.HasOne(d => d.CustomerOrder) - .WithMany(p => p.Shipments) - .HasForeignKey(d => d.CustomerOrderId) - .OnDelete(DeleteBehavior.ClientSetNull) - .HasConstraintName("fkShipment_CustomerOrder"); - - entity.HasOne(d => d.ShipmentStatus) - .WithMany(p => p.Shipments) - .HasForeignKey(d => d.ShipmentStatusId) - .OnDelete(DeleteBehavior.ClientSetNull) - .HasConstraintName("fkShipment_ShipmentStatus"); - }); - - modelBuilder.Entity(entity => - { - entity.ToTable("ShipmentStatus", "Shipping"); - - entity.Property(e => e.ShipmentStatusId).ValueGeneratedNever(); - - entity.Property(e => e.ShipmentStatusName) - .HasMaxLength(100) - .IsUnicode(false); - }); - - modelBuilder.Entity(entity => - { - entity.ToTable("ShipmentStatusDetail", "Shipping"); - - entity.Property(e => e.StatusDateTime).HasDefaultValueSql("(getutcdate())"); - - entity.HasOne(d => d.Shipment) - .WithMany(p => p.ShipmentStatusDetails) - .HasForeignKey(d => d.ShipmentId) - .OnDelete(DeleteBehavior.ClientSetNull) - .HasConstraintName("fkShipmentStatusDetail_Shipment"); - - entity.HasOne(d => d.ShipmentStatus) - .WithMany(p => p.ShipmentStatusDetails) - .HasForeignKey(d => d.ShipmentStatusId) - .OnDelete(DeleteBehavior.ClientSetNull) - .HasConstraintName("fkShipmentStatusDetails_ShipmentStatus"); - }); - - } - - } - -} \ No newline at end of file diff --git a/Workshop/readme.md b/Workshop/readme.md index 48de4f8..46ea49e 100644 --- a/Workshop/readme.md +++ b/Workshop/readme.md @@ -1,4 +1,4 @@ -![DesignDevelopServerlessEventDrivenMicroserviceSolution](https://github.com/TaleLearnCode/DesignDevelopServerlessEventDrivenMicroserviceSolution/blob/main/Thumbnail.png) +![Design and Develop a Serverless Event-Driven Microservice-Based Solution](https://github.com/TaleLearnCode/DesignDevelopServerlessEventDrivenMicroserviceSolution/blob/main/Thumbnail.png) # Design and Develop a Serverless Event-Driven Microservice-Based Solution Workshop @@ -41,13 +41,14 @@ In this full-day hands-on workshop, attendees will dive into the exciting world ## Technologies and Services This workshop leverages the following technologies and services (subject to change): -- [Azure App Configuration](https://azure.microsoft.com/en-us/products/app-configuration/) +- [Azure SQL](https://azure.microsoft.com/en-us/products/azure-sql/) - [Azure Cosmos DB](https://azure.microsoft.com/en-us/products/cosmos-db/) - [Azure Event Hubs](https://azure.microsoft.com/en-us/products/event-hubs/) +- [Azure Service Bus](https://azure.microsoft.com/en-us/products/service-bus/) - [Azure Functions](https://azure.microsoft.com/en-us/products/functions/) -- [Azure Logic Apps](https://azure.microsoft.com/en-us/products/logic-apps/) +- [Azure Communication Services](https://azure.microsoft.com/en-us/services/communication-services/) +- [Azure App Configuration](https://azure.microsoft.com/en-us/products/app-configuration/) - [Azure KeyVault](https://azure.microsoft.com/en-us/products/key-vault/) -- [Azure SQL](https://azure.microsoft.com/en-us/products/azure-sql/) - [GitHub Actions](https://learn.microsoft.com/en-us/azure/developer/github/github-actions?WT.mc_id=AZ-MVP-5004334) ## Prerequisites: