Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add .NET Aspire #4

Merged
merged 2 commits into from
Dec 21, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 11 additions & 35 deletions DevelopmentGuide.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ The following steps illustrate how to create microservices based on the principl
- **EventDriven.CQRS.Extensions**
- **EventDriven.DependencyInjection.URF.Mongo**
- **EventDriven.EventBus.Dapr**
- **EventDriven.EventBus.Dapr.EventCache.Mongo**
- **EventDriven.EventBus.EventCache.Mongo**
- **MongoDB.Driver**
- **URF.Core.Mongo**
2. Add **Domain/CustomerAggregate** folders to the project, then add a `Customer` class that extends `Entity`.
Expand Down Expand Up @@ -336,7 +336,7 @@ The following steps illustrate how to create microservices based on the principl

// Add Dapr event bus
builder.Services.AddDaprEventBus(builder.Configuration, true);
builder.Services.AddDaprMongoEventCache(builder.Configuration);
builder.Services.AddMongoEventCache(builder.Configuration);
```
12. Add configuration entries to **appsettings.json**.
```json
Expand All @@ -348,12 +348,10 @@ The following steps illustrate how to create microservices based on the principl
"DaprEventBusOptions": {
"PubSubName": "pubsub"
},
"DaprEventCacheOptions": {
"DaprStateStoreOptions": {
"StateStoreName": "statestore-mongodb"
}
"MongoEventCacheOptions": {
"AppName": "order-service"
},
"DaprStoreDatabaseSettings": {
"MongoStoreDatabaseSettings": {
"ConnectionString": "mongodb://localhost:27017",
"DatabaseName": "daprStore",
"CollectionName": "daprCollection"
Expand Down Expand Up @@ -390,7 +388,7 @@ The following steps illustrate how to create microservices based on the principl
```csharp
builder.Services.AddSingleton<CustomerAddressUpdatedEventHandler>();
builder.Services.AddDaprEventBus(builder.Configuration, true);
builder.Services.AddDaprMongoEventCache(builder.Configuration);
builder.Services.AddMongoEventCache(builder.Configuration);
```
- Also in `Program` use Cloud Events, map subscribe handlers, and map Dapr Event Bus endpoints.
```csharp
Expand All @@ -406,43 +404,21 @@ The following steps illustrate how to create microservices based on the principl
});
});
```
14. Add configuration entries for `DaprEventCacheOptions` and `DaprStoreDatabaseSettings` to **appsettings.json**.
14. Add configuration entries for `MongoEventCacheOptions` and `MongoStoreDatabaseSettings` to **appsettings.json**.
```json
"DaprEventCacheOptions": {
"DaprStateStoreOptions": {
"StateStoreName": "statestore-mongodb"
}
"MongoEventCacheOptions": {
"AppName": "order-service"
},
"DaprStoreDatabaseSettings": {
"MongoStoreDatabaseSettings": {
"ConnectionString": "mongodb://localhost:27017",
"DatabaseName": "daprStore",
"CollectionName": "daprCollection"
},
}
```
15. Lastly, add a **dapr/components** directory to the **reference-architecture** folder.
- Add the following dapr component yaml files:
- **pubsub.yaml**
- **statestore.yaml**
- **statestore-mongodb.yaml**
- Files not in use should be placed in a separate folder.
- Add a **tye.yaml** file to the **reference-architecture** folder (recommended), including the dapr extension and components path. Optionally specify port bindings which match those in **launchSettings.json**.
```yaml
name: event-driven-ref-arch
extensions:
- name: dapr
log-level: debug
components-path: "dapr/components"
services:
- name: customer-service
project: CustomerService/CustomerService.csproj
bindings:
- port: 5656
- name: order-service
project: OrderService/OrderService.csproj
bindings:
- port: 5757
```
- Run the Customer and Order services using [Tye](https://github.com/dotnet/tye).
- Be sure to install the [Tye CLI](https://github.com/dotnet/tye/blob/main/docs/getting_started.md) tool globally.
- To debug services, set breakpoints and execute `tye run --debug *`. Then attach the debugger to **CustomerService.dll** and/or **OrderService.dll**.

27 changes: 19 additions & 8 deletions EventDriven.ReferenceArchitecture.sln
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,13 @@ VisualStudioVersion = 17.0.32014.148
MinimumVisualStudioVersion = 10.0.40219.1
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{AFFCBFA4-9D64-43AA-AC59-D4CC54BD9C72}"
ProjectSection(SolutionItems) = preProject
ReadMe.md = ReadMe.md
DevelopmentGuide.md = DevelopmentGuide.md
ReadMe.md = ReadMe.md
EndProjectSection
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{269CD137-4093-4100-B33E-808586D335F6}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "reference-architecture", "reference-architecture", "{C4FD0AF1-927A-4860-A634-7CE342807692}"
ProjectSection(SolutionItems) = preProject
reference-architecture\tye.yaml = reference-architecture\tye.yaml
EndProjectSection
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CustomerService", "reference-architecture\CustomerService\CustomerService.csproj", "{48983715-E6DF-462F-AF3C-769C1122794F}"
EndProject
Expand Down Expand Up @@ -42,11 +39,15 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "standby", "standby", "{A045
reference-architecture\dapr\standby\pubsub-snssqs.yaml = reference-architecture\dapr\standby\pubsub-snssqs.yaml
EndProjectSection
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EventDriven.ReferenceArchitecture.Specs", "test\EventDriven.ReferenceArchitecture.Specs\EventDriven.ReferenceArchitecture.Specs.csproj", "{68B613B3-EB4A-41DB-A61E-1E031C5BFA85}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "EventDriven.ReferenceArchitecture.Specs", "test\EventDriven.ReferenceArchitecture.Specs\EventDriven.ReferenceArchitecture.Specs.csproj", "{68B613B3-EB4A-41DB-A61E-1E031C5BFA85}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CustomerService.Tests", "test\CustomerService.Tests\CustomerService.Tests.csproj", "{C5056BF3-987B-466C-962D-77F8E725F210}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CustomerService.Tests", "test\CustomerService.Tests\CustomerService.Tests.csproj", "{C5056BF3-987B-466C-962D-77F8E725F210}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OrderService.Tests", "test\OrderService.Tests\OrderService.Tests.csproj", "{B3A36227-2EB9-4BEC-8FCA-B5DB7A809C06}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OrderService.Tests", "test\OrderService.Tests\OrderService.Tests.csproj", "{B3A36227-2EB9-4BEC-8FCA-B5DB7A809C06}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ReferenceArchitecture.AppHost", "reference-architecture\ReferenceArchitecture.AppHost\ReferenceArchitecture.AppHost.csproj", "{8E145A04-E2D6-4507-86B7-F5E4A3CA8C61}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ReferenceArchitecture.ServiceDefaults", "reference-architecture\ReferenceArchitecture.ServiceDefaults\ReferenceArchitecture.ServiceDefaults.csproj", "{CB1E0EE8-0B80-40EB-B838-D1F568A05B29}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Expand Down Expand Up @@ -78,6 +79,14 @@ Global
{B3A36227-2EB9-4BEC-8FCA-B5DB7A809C06}.Debug|Any CPU.Build.0 = Debug|Any CPU
{B3A36227-2EB9-4BEC-8FCA-B5DB7A809C06}.Release|Any CPU.ActiveCfg = Release|Any CPU
{B3A36227-2EB9-4BEC-8FCA-B5DB7A809C06}.Release|Any CPU.Build.0 = Release|Any CPU
{8E145A04-E2D6-4507-86B7-F5E4A3CA8C61}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{8E145A04-E2D6-4507-86B7-F5E4A3CA8C61}.Debug|Any CPU.Build.0 = Debug|Any CPU
{8E145A04-E2D6-4507-86B7-F5E4A3CA8C61}.Release|Any CPU.ActiveCfg = Release|Any CPU
{8E145A04-E2D6-4507-86B7-F5E4A3CA8C61}.Release|Any CPU.Build.0 = Release|Any CPU
{CB1E0EE8-0B80-40EB-B838-D1F568A05B29}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{CB1E0EE8-0B80-40EB-B838-D1F568A05B29}.Debug|Any CPU.Build.0 = Debug|Any CPU
{CB1E0EE8-0B80-40EB-B838-D1F568A05B29}.Release|Any CPU.ActiveCfg = Release|Any CPU
{CB1E0EE8-0B80-40EB-B838-D1F568A05B29}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand All @@ -87,12 +96,14 @@ Global
{16A5B2CB-8C46-4F3E-B7A1-97C47D9F66E7} = {C4FD0AF1-927A-4860-A634-7CE342807692}
{B11B21E0-7B89-4285-990A-D98793310B02} = {C4FD0AF1-927A-4860-A634-7CE342807692}
{FC04D111-903D-49FF-84A6-8806C71E2168} = {C4FD0AF1-927A-4860-A634-7CE342807692}
{00BA9501-787E-465C-97D0-F51295D97802} = {F0E48E00-7D72-4614-9C13-90A7B015B06F}
{F0E48E00-7D72-4614-9C13-90A7B015B06F} = {C4FD0AF1-927A-4860-A634-7CE342807692}
{00BA9501-787E-465C-97D0-F51295D97802} = {F0E48E00-7D72-4614-9C13-90A7B015B06F}
{A045033E-5283-4A6F-87D3-B02194595788} = {F0E48E00-7D72-4614-9C13-90A7B015B06F}
{68B613B3-EB4A-41DB-A61E-1E031C5BFA85} = {269CD137-4093-4100-B33E-808586D335F6}
{C5056BF3-987B-466C-962D-77F8E725F210} = {269CD137-4093-4100-B33E-808586D335F6}
{B3A36227-2EB9-4BEC-8FCA-B5DB7A809C06} = {269CD137-4093-4100-B33E-808586D335F6}
{8E145A04-E2D6-4507-86B7-F5E4A3CA8C61} = {C4FD0AF1-927A-4860-A634-7CE342807692}
{CB1E0EE8-0B80-40EB-B838-D1F568A05B29} = {C4FD0AF1-927A-4860-A634-7CE342807692}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {427A0D03-63CA-48AE-AA95-D21800101398}
Expand Down
75 changes: 49 additions & 26 deletions ReadMe.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,21 @@
Reference architecture for using **Event Driven .NET** abstractions and libraries for [Domain Driven Design](https://en.wikipedia.org/wiki/Domain-driven_design) (DDD), [Command Query Responsibility Segregation](https://martinfowler.com/bliki/CQRS.html) (CQRS) and [Event Driven Architecture](https://en.wikipedia.org/wiki/Event-driven_architecture) (EDA).

## Prerequisites
- [.NET Core SDK](https://dotnet.microsoft.com/download) (6.0 or greater)
- [.NET Core SDK](https://dotnet.microsoft.com/download) (8.0 or greater)
- [Docker Desktop](https://www.docker.com/products/docker-desktop)
- MongoDB Docker: `docker run --name mongo -d -p 27017:27017 -v /tmp/mongo/data:/data/db mongo`
- [MongoDB Client](https://robomongo.org/download):
- Download Robo 3T only.
- [MongoDB Client](https://studio3t.com/download/):
- Download Studio 3T only.
- Add connection to localhost on port 27017.
- [Dapr](https://dapr.io/) (Distributed Application Runtime)
- [Install Dapr CLI](https://docs.dapr.io/getting-started/install-dapr-cli/)
- [Initialize Dapr](https://docs.dapr.io/getting-started/install-dapr-selfhost/)
- [Microsoft Tye](https://github.com/dotnet/tye/blob/main/docs/getting_started.md) (recommended)
- [.NET Aspire Workload](https://learn.microsoft.com/en-us/dotnet/aspire/fundamentals/setup-tooling?tabs=dotnet-cli#install-net-aspire)
```
dotnet workload update
dotnet workload install aspire
dotnet workload list
```
- [Specflow](https://specflow.org/) IDE Plugin (recommended)
- [Visual Studio](https://docs.specflow.org/projects/getting-started/en/latest/GettingStarted/Step1.html)
- [JetBrains Rider](https://docs.specflow.org/projects/specflow/en/latest/Rider/rider-installation.html)
Expand All @@ -38,58 +43,76 @@ The **Reference Architecture** projects demonstrate how to apply these concepts
<img width="600" src="images/event-driven-ref-arch.png">
</p>

## Running Services with Tye and Dapr
## Running Services with Aspire

> **Note**: As an alternative to Tye, you can run services directly usng the [Dapr CLI](https://docs.dapr.io/getting-started/install-dapr-cli/). This may be useful for troubleshooting Dapr issues after setting `Microsoft.AspNetCore` logging level to `Debug`:
> `dapr run --app-id service-name --app-port #### --components-path ../dapr/components -- dotnet run`

1. Open a terminal at the **reference-architecture** directory and run Tye to launch all services simultaneously.
```
tye run
```
2. Alternatively, run Tye in debug mode.
```
tye run --debug *
```
- Set breakpoints in **OrderService**, **CustomerService**.
- Attach the IDE debugger to **OrderService.dll**, **CustomerService.dll**.
3. Open the Tye dashboard at http://localhost:8000 to inspect service endpoints and view logs.
4. Create some customers.
1. If using an IDE such as Visual Studio or Rider, Using an IDE such as Visual Studio or Rider, run the **http** profile of **ReferenceArchitecture.AppHost**.
2. Open the Aspire dashboard to inspect service endpoints and view logs.
3. Create some customers.
- Open http://localhost:5656/swagger
- Execute posts using contents of **customers.json**.
- Copy post response, modify fields, then execute puts.
- Make sure to copy `etag` value from last response, or you will get a concurrency error.
- Copy `id` and `etag` values to execute deletes.
- Execute gets to retrieve customers.
- View customers database collections using Robo 3T.
5. Create some orders.
4. Create some orders.
- Open http://localhost:5757/swagger
- Execute posts using contents of **orders.json**.
- Copy post response, modify fields, then execute puts.
- Make sure to copy `etag` value from last response, or you will get a concurrency error.
- Copy `id` and `etag` values to execute deletes.
- Execute gets to retrieve orders.
- View orders database collections using Robo 3T.
6. Update the address of a customer who has order.
5. Update the address of a customer who has order.
- Note the address is also updated for the customer's orders.
- Observe log messages in terminal when integration events are published and handled.

## Tests
## Running Services with the Dapr CLI

1. Start the Customer Service using the Dapr CLI from a terminal at the project root.

```
dapr run --app-id customer-service --app-port 5656 --resources-path ../dapr/components -- dotnet run
```

2. Start the Order Service using the Dapr CLI from a terminal at the project root.

```
dapr run --app-id order-service --app-port 5757 --resources-path ../dapr/components -- dotnet run
```

3. Open the Dapr Dashboard at http://localhost:8080


```
dapr dashboard
```

4. Execute steps 3-5 above to use services and pub-sub features.

## Running Tests

### Unit Tests

In the **test** folder you'll find unit tests for both **CustomerService** and **OrderService** projects.
- [xUnit](https://xunit.net/) is used as the unit testing framework.
- [Moq](https://github.com/moq/moq4) is used as the mocking framework.

> **Note**: Because database API's are notoriously [difficult to mock](https://jimmybogard.com/avoid-in-memory-databases-for-tests/), **repositories** are deliberately *excluded* from unit testing. Instead, repositories attain code coverage with **integration / acceptance tests**.
> **Note**: Because database API's are notoriously [difficult to mock](https://jimmybogard.com/avoid-in-memory-databases-for-tests/), **repositories** are deliberately *excluded* from unit testing. Instead, repositories attain code coverage with **acceptance/integration tests**.

### Integration / Acceptance Tests
1. Run unit **CustomerService.Tests** and **OrderService.Tests** from the Test explorer in your IDE.
2. Alternatively, open a terminal at **CustomerService.Tests** and **OrderService.Tests**, then run `dotnet test`

In the **tests** folder you'll find an **EventDriven.ReferenceArchitecture.Specs** project with automated integration / acceptance tests.
### Acceptance (Integration) Tests

In the **tests** folder you'll find an **EventDriven.ReferenceArchitecture.Specs** project with automated acceptance / integration tests.
- [SpecFlow](https://specflow.org/) is used as the acceptance testing framework.
- Feature files use [Gherkin](https://specflow.org/learn/gherkin/) syntax to enable [Behavior Driven Development](https://en.wikipedia.org/wiki/Behavior-driven_development) with scenarios that match **acceptance criteria** in user stories.

1. Using an IDE such as Visual Studio or Rider, run the **specs** profile of **ReferenceArchitecture.AppHost**.
2. Run **EventDriven.ReferenceArchitecture.Specs** from the Test explorer of your IDE.
3. Alternatively, open a terminal at **EventDriven.ReferenceArchitecture.Specs**, then run `dotnet test`

## Development Guide

For step-by-step instructions on how to build microservices with [Event Driven .NET](https://github.com/event-driven-dotnet/Home) using this reference architecture, please see the **Event Driven .NET** [Development Guide](DevelopmentGuide.md).
7 changes: 7 additions & 0 deletions global.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"sdk": {
"version": "8.0.100",
"rollForward": "latestPatch",
"allowPrerelease": true
}
}
4 changes: 2 additions & 2 deletions reference-architecture/Common/Common.csproj
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<TargetFramework>net8.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="EventDriven.CQRS.Extensions" Version="2.0.0" />
<PackageReference Include="EventDriven.EventBus.Abstractions" Version="1.4.0" />
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="7.0.1" />
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="8.0.0" />
</ItemGroup>

</Project>
7 changes: 4 additions & 3 deletions reference-architecture/CustomerService/CustomerService.csproj
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
<Project Sdk="Microsoft.NET.Sdk.Web">

<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>
Expand All @@ -12,14 +13,14 @@
<PackageReference Include="EventDriven.CQRS.Extensions" Version="2.0.0" />
<PackageReference Include="EventDriven.DependencyInjection.URF.Mongo" Version="1.2.2" />
<PackageReference Include="EventDriven.EventBus.Dapr" Version="1.4.0" />
<PackageReference Include="EventDriven.EventBus.Dapr.EventCache.Mongo" Version="1.4.0" />
<PackageReference Include="MongoDB.Driver" Version="2.22.0" />
<PackageReference Include="MongoDB.Driver" Version="2.23.1" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.5.0" />
<PackageReference Include="URF.Core.Mongo" Version="7.0.0" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\Common\Common.csproj" />
<ProjectReference Include="..\ReferenceArchitecture.ServiceDefaults\ReferenceArchitecture.ServiceDefaults.csproj" />
</ItemGroup>

</Project>
5 changes: 4 additions & 1 deletion reference-architecture/CustomerService/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,14 @@

var builder = WebApplication.CreateBuilder(args);

// Add Aspire service defaults
builder.AddServiceDefaults();

// Add services to the container.
builder.Services.AddControllers();
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
builder.Services.AddProblemDetails();

// Add automapper
builder.Services.AddAutoMapper(typeof(Program));
Expand All @@ -28,7 +32,6 @@

// Add Dapr event bus
builder.Services.AddDaprEventBus(builder.Configuration);
builder.Services.AddDaprMongoEventCache(builder.Configuration);

var app = builder.Build();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"profiles": {
"CustomerService": {
"commandName": "Project",
"dotnetRunMessages": "true",
"dotnetRunMessages": true,
"launchBrowser": true,
"launchUrl": "swagger",
"applicationUrl": "http://localhost:5656",
Expand Down
14 changes: 14 additions & 0 deletions reference-architecture/CustomerService/appsettings.Specs.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft": "Warning",
"Microsoft.Hosting.Lifetime": "Information"
}
},
"CustomerDatabaseSettings": {
"ConnectionString": "mongodb://localhost:27017",
"DatabaseName": "CustomersTestDb",
"CollectionName": "Customers"
}
}
Loading