diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 7e3fb64d8f..0f5d182a94 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -289,7 +289,7 @@ jobs:
- 11433:1433
env:
ACCEPT_EULA: Y
- SA_PASSWORD: Password1!
+ SA_PASSWORD: Password123!
steps:
- uses: actions/checkout@v4
- name: Setup dotnet 6
diff --git a/.idea/.idea.Brighter/.idea/httpRequests/http-requests-log.http b/.idea/.idea.Brighter/.idea/httpRequests/http-requests-log.http
index c4ff58a6e2..10e80c8e3b 100644
--- a/.idea/.idea.Brighter/.idea/httpRequests/http-requests-log.http
+++ b/.idea/.idea.Brighter/.idea/httpRequests/http-requests-log.http
@@ -1,491 +1,660 @@
-GET http://localhost:5000/People/Tyrion
+POST http://localhost:5000/Greetings/Tyrion/new
+Content-Type: application/json
+Content-Length: 47
Connection: Keep-Alive
User-Agent: Apache-HttpClient/4.5.14 (Java/17.0.6)
Accept-Encoding: br,deflate,gzip,x-gzip
-###
-
-POST http://localhost:5000/Greetings/Tyrion/new
-Content-Type: application/json
-
{
"Greeting" : "I drink, and I know things"
}
-<> 2022-11-08T211212.200.json
+<> 2023-07-10T160215.200.json
###
POST http://localhost:5000/Greetings/Tyrion/new
Content-Type: application/json
+Content-Length: 47
+Connection: Keep-Alive
+User-Agent: Apache-HttpClient/4.5.14 (Java/17.0.6)
+Accept-Encoding: br,deflate,gzip,x-gzip
{
"Greeting" : "I drink, and I know things"
}
-<> 2022-11-08T211208.200.json
+<> 2023-07-10T160214.200.json
###
POST http://localhost:5000/Greetings/Tyrion/new
Content-Type: application/json
+Content-Length: 47
+Connection: Keep-Alive
+User-Agent: Apache-HttpClient/4.5.14 (Java/17.0.6)
+Accept-Encoding: br,deflate,gzip,x-gzip
{
"Greeting" : "I drink, and I know things"
}
-<> 2022-11-08T211200.200.json
+<> 2023-07-10T160213.200.json
###
-POST http://localhost:5000/People/new
+POST http://localhost:5000/Greetings/Tyrion/new
Content-Type: application/json
+Content-Length: 47
+Connection: Keep-Alive
+User-Agent: Apache-HttpClient/4.5.14 (Java/17.0.6)
+Accept-Encoding: br,deflate,gzip,x-gzip
{
- "Name" : "Tyrion"
+ "Greeting" : "I drink, and I know things"
}
-<> 2022-11-08T211124.200.json
+<> 2023-07-10T160212.200.json
###
-POST http://localhost:5000/People/new
+POST http://localhost:5000/Greetings/Tyrion/new
Content-Type: application/json
+Content-Length: 47
+Connection: Keep-Alive
+User-Agent: Apache-HttpClient/4.5.14 (Java/17.0.6)
+Accept-Encoding: br,deflate,gzip,x-gzip
{
- "Name" : "Tyrion"
+ "Greeting" : "I drink, and I know things"
}
-<> 2022-11-08T200350.200.json
+<> 2023-07-10T160211-1.200.json
###
POST http://localhost:5000/Greetings/Tyrion/new
Content-Type: application/json
+Content-Length: 47
+Connection: Keep-Alive
+User-Agent: Apache-HttpClient/4.5.14 (Java/17.0.6)
+Accept-Encoding: br,deflate,gzip,x-gzip
{
"Greeting" : "I drink, and I know things"
}
-<> 2022-10-21T115924.200.json
+<> 2023-07-10T160211.200.json
###
-GET http://localhost:5000/People/Tyrion
-
-<> 2022-10-21T115848.200.json
-
-###
-
-POST http://localhost:5000/People/new
+POST http://localhost:5000/Greetings/Tyrion/new
Content-Type: application/json
+Content-Length: 47
+Connection: Keep-Alive
+User-Agent: Apache-HttpClient/4.5.14 (Java/17.0.6)
+Accept-Encoding: br,deflate,gzip,x-gzip
{
- "Name" : "Tyrion"
+ "Greeting" : "I drink, and I know things"
}
-<> 2022-10-21T115844.200.json
-
-###
-
-GET http://localhost:5000/People/Tyrion
-
-<> 2022-10-21T115838.500.json
+<> 2023-07-10T160210.200.json
###
-POST http://localhost:5000/People/new
+POST http://localhost:5000/Greetings/Tyrion/new
Content-Type: application/json
+Content-Length: 47
+Connection: Keep-Alive
+User-Agent: Apache-HttpClient/4.5.14 (Java/17.0.6)
+Accept-Encoding: br,deflate,gzip,x-gzip
{
- "Name" : "Tyrion"
+ "Greeting" : "I drink, and I know things"
}
-<> 2022-10-19T215040.200.json
-
-###
-
-GET http://localhost:5000/People/Tyrion
-
-<> 2022-10-19T214755.500.json
+<> 2023-07-10T160209.200.json
###
POST http://localhost:5000/Greetings/Tyrion/new
Content-Type: application/json
+Content-Length: 47
+Connection: Keep-Alive
+User-Agent: Apache-HttpClient/4.5.14 (Java/17.0.6)
+Accept-Encoding: br,deflate,gzip,x-gzip
{
"Greeting" : "I drink, and I know things"
}
-<> 2022-10-19T204831.200.json
+<> 2023-07-10T124554.200.json
###
POST http://localhost:5000/Greetings/Tyrion/new
Content-Type: application/json
+Content-Length: 47
+Connection: Keep-Alive
+User-Agent: Apache-HttpClient/4.5.14 (Java/17.0.6)
+Accept-Encoding: br,deflate,gzip,x-gzip
{
"Greeting" : "I drink, and I know things"
}
-<> 2022-10-19T204823.200.json
+<> 2023-07-10T124553-1.200.json
###
POST http://localhost:5000/Greetings/Tyrion/new
Content-Type: application/json
+Content-Length: 47
+Connection: Keep-Alive
+User-Agent: Apache-HttpClient/4.5.14 (Java/17.0.6)
+Accept-Encoding: br,deflate,gzip,x-gzip
{
"Greeting" : "I drink, and I know things"
}
-<> 2022-10-19T204723.200.json
+<> 2023-07-10T124553.200.json
###
-POST http://localhost:5000/People/new
+POST http://localhost:5000/Greetings/Tyrion/new
Content-Type: application/json
+Content-Length: 47
+Connection: Keep-Alive
+User-Agent: Apache-HttpClient/4.5.14 (Java/17.0.6)
+Accept-Encoding: br,deflate,gzip,x-gzip
{
- "Name" : "Tyrion"
+ "Greeting" : "I drink, and I know things"
}
-<> 2022-10-19T201347.200.json
-
-###
-
-GET http://localhost:5000/People/Tyrion
-
-<> 2022-10-19T201322.500.json
+<> 2023-07-10T124551.200.json
###
-GET http://localhost:5000/People/Tyrion
-
-<> 2022-10-19T195123.200.json
-
-###
-
-GET http://localhost:5000/People/Tyrion
-
-<> 2022-10-19T194030.200.json
-
-###
-
-POST http://localhost:5000/People/new
+POST http://localhost:5000/Greetings/Tyrion/new
Content-Type: application/json
+Content-Length: 47
+Connection: Keep-Alive
+User-Agent: Apache-HttpClient/4.5.14 (Java/17.0.6)
+Accept-Encoding: br,deflate,gzip,x-gzip
{
- "Name" : "Tyrion"
+ "Greeting" : "I drink, and I know things"
}
-<> 2022-10-19T194012.500.json
+<> 2023-07-10T124550.200.json
###
-POST http://localhost:5000/People/new
+POST http://localhost:5000/Greetings/Tyrion/new
Content-Type: application/json
+Content-Length: 47
+Connection: Keep-Alive
+User-Agent: Apache-HttpClient/4.5.14 (Java/17.0.6)
+Accept-Encoding: br,deflate,gzip,x-gzip
{
- "Name" : "Tyrion"
+ "Greeting" : "I drink, and I know things"
}
-<> 2022-10-19T190032.500.json
+<> 2023-07-10T124549.200.json
###
-POST http://localhost:5000/People/new
+POST http://localhost:5000/Greetings/Tyrion/new
Content-Type: application/json
+Content-Length: 47
+Connection: Keep-Alive
+User-Agent: Apache-HttpClient/4.5.14 (Java/17.0.6)
+Accept-Encoding: br,deflate,gzip,x-gzip
{
- "Name" : "Tyrion"
+ "Greeting" : "I drink, and I know things"
}
-<> 2022-10-19T185850.500.json
+<> 2023-07-10T124547.200.json
###
-POST http://localhost:5000/People/new
+POST http://localhost:5000/Greetings/Tyrion/new
Content-Type: application/json
+Content-Length: 47
+Connection: Keep-Alive
+User-Agent: Apache-HttpClient/4.5.14 (Java/17.0.6)
+Accept-Encoding: br,deflate,gzip,x-gzip
{
- "Name" : "Tyrion"
+ "Greeting" : "I drink, and I know things"
}
-<> 2022-10-19T184041.500.json
+<> 2023-07-10T124546.200.json
###
-POST http://localhost:5000/People/new
+POST http://localhost:5000/Greetings/Tyrion/new
Content-Type: application/json
+Content-Length: 47
+Connection: Keep-Alive
+User-Agent: Apache-HttpClient/4.5.14 (Java/17.0.6)
+Accept-Encoding: br,deflate,gzip,x-gzip
{
- "Name" : "Tyrion"
+ "Greeting" : "I drink, and I know things"
}
-<> 2022-10-19T183305.500.json
+<> 2023-07-10T124544.200.json
###
GET http://localhost:5000/People/Tyrion
+Connection: Keep-Alive
+User-Agent: Apache-HttpClient/4.5.14 (Java/17.0.6)
+Accept-Encoding: br,deflate,gzip,x-gzip
-<> 2022-10-19T180348.500.html
+<> 2023-07-10T124540.200.json
###
-POST http://localhost:5000/People/new
+GET http://localhost:5000/Greetings/Tyrion
+Connection: Keep-Alive
+User-Agent: Apache-HttpClient/4.5.14 (Java/17.0.6)
+Accept-Encoding: br,deflate,gzip,x-gzip
+
+<> 2023-07-07T200406.200.json
+
+###
+
+POST http://localhost:5000/Greetings/Tyrion/new
Content-Type: application/json
+Content-Length: 47
+Connection: Keep-Alive
+User-Agent: Apache-HttpClient/4.5.14 (Java/17.0.6)
+Accept-Encoding: br,deflate,gzip,x-gzip
{
- "Name" : "Tyrion"
+ "Greeting" : "I drink, and I know things"
}
-<> 2022-10-19T180248.500.html
+<> 2023-07-07T200403.200.json
###
-DELETE http://localhost:5000/People/Tyrion
+GET http://localhost:5000/People/Tyrion
+Connection: Keep-Alive
+User-Agent: Apache-HttpClient/4.5.14 (Java/17.0.6)
+Accept-Encoding: br,deflate,gzip,x-gzip
-<> 2022-10-19T180240.500.html
+<> 2023-07-07T200359.200.json
###
POST http://localhost:5000/Greetings/Tyrion/new
Content-Type: application/json
+Content-Length: 50
+Connection: Keep-Alive
+User-Agent: Apache-HttpClient/4.5.14 (Java/17.0.6)
+Accept-Encoding: br,deflate,gzip,x-gzip
{
- "Greeting" : "I drink, and I know things"
+ "Greeting" : "I drink, and I know things #1"
}
-<> 2022-06-25T125829.200.json
-
###
-POST http://localhost:5000/People/new
+POST http://localhost:5000/Greetings/Tyrion/new
Content-Type: application/json
+Content-Length: 50
+Connection: Keep-Alive
+User-Agent: Apache-HttpClient/4.5.14 (Java/17.0.6)
+Accept-Encoding: br,deflate,gzip,x-gzip
{
- "Name" : "Tyrion"
+ "Greeting" : "I drink, and I know things #1"
}
-<> 2022-06-25T125806.200.json
+<> 2023-07-07T195459.200.json
###
POST http://localhost:5000/Greetings/Tyrion/new
Content-Type: application/json
+Content-Length: 52
+Connection: Keep-Alive
+User-Agent: Apache-HttpClient/4.5.14 (Java/17.0.6)
+Accept-Encoding: br,deflate,gzip,x-gzip
{
- "Greeting" : "I drink, and I know things"
+ "Greeting" : "I drink, and I know more things"
}
-<> 2022-06-24T234446.200.json
+<> 2023-07-07T192903.200.json
###
-POST http://localhost:5000/People/new
+POST http://localhost:5000/Greetings/Tyrion/new
Content-Type: application/json
+Content-Length: 47
+Connection: Keep-Alive
+User-Agent: Apache-HttpClient/4.5.14 (Java/17.0.6)
+Accept-Encoding: br,deflate,gzip,x-gzip
{
- "Name" : "Tyrion"
+ "Greeting" : "I drink, and I know things"
}
-<> 2022-06-24T224322.200.json
+<> 2023-07-07T190420.200.json
###
-GET http://localhost:5000/People/Tyrion
+GET http://localhost:5000/Greetings/Tyrion
+Connection: Keep-Alive
+User-Agent: Apache-HttpClient/4.5.14 (Java/17.0.6)
+Accept-Encoding: br,deflate,gzip,x-gzip
-<> 2022-06-24T224317.500.json
+<> 2023-07-07T190355.200.json
###
POST http://localhost:5000/Greetings/Tyrion/new
Content-Type: application/json
+Content-Length: 47
+Connection: Keep-Alive
+User-Agent: Apache-HttpClient/4.5.14 (Java/17.0.6)
+Accept-Encoding: br,deflate,gzip,x-gzip
{
"Greeting" : "I drink, and I know things"
}
+<> 2023-07-07T190352.200.json
+
###
-POST http://localhost:5000/Greetings/Tyrion/new
-Content-Type: application/json
+GET http://localhost:5000/People/Tyrion
+Connection: Keep-Alive
+User-Agent: Apache-HttpClient/4.5.14 (Java/17.0.6)
+Accept-Encoding: br,deflate,gzip,x-gzip
-{
- "Greeting" : "I drink, and I know things"
-}
+<> 2023-07-07T190349.200.json
###
-POST http://localhost:5000/Greetings/Tyrion/new
-Content-Type: application/json
+GET http://localhost:5000/Greetings/Tyrion
+Connection: Keep-Alive
+User-Agent: Apache-HttpClient/4.5.14 (Java/17.0.6)
+Accept-Encoding: br,deflate,gzip,x-gzip
-{
- "Greeting" : "I drink, and I know things"
-}
+<> 2023-07-07T165603.200.json
###
POST http://localhost:5000/Greetings/Tyrion/new
Content-Type: application/json
+Content-Length: 47
+Connection: Keep-Alive
+User-Agent: Apache-HttpClient/4.5.14 (Java/17.0.6)
+Accept-Encoding: br,deflate,gzip,x-gzip
{
"Greeting" : "I drink, and I know things"
}
+<> 2023-07-07T165600.200.json
+
###
POST http://localhost:5000/Greetings/Tyrion/new
Content-Type: application/json
+Content-Length: 47
+Connection: Keep-Alive
+User-Agent: Apache-HttpClient/4.5.14 (Java/17.0.6)
+Accept-Encoding: br,deflate,gzip,x-gzip
{
"Greeting" : "I drink, and I know things"
}
-<> 2022-06-20T193453.200.json
+<> 2023-07-07T165559.200.json
###
POST http://localhost:5000/Greetings/Tyrion/new
Content-Type: application/json
+Content-Length: 47
+Connection: Keep-Alive
+User-Agent: Apache-HttpClient/4.5.14 (Java/17.0.6)
+Accept-Encoding: br,deflate,gzip,x-gzip
{
"Greeting" : "I drink, and I know things"
}
-<> 2022-06-20T193452.200.json
+<> 2023-07-07T165557.200.json
###
POST http://localhost:5000/Greetings/Tyrion/new
Content-Type: application/json
+Content-Length: 47
+Connection: Keep-Alive
+User-Agent: Apache-HttpClient/4.5.14 (Java/17.0.6)
+Accept-Encoding: br,deflate,gzip,x-gzip
{
"Greeting" : "I drink, and I know things"
}
-<> 2022-06-20T193451.200.json
+<> 2023-07-07T165555.200.json
###
-POST http://localhost:5000/Greetings/Tyrion/new
-Content-Type: application/json
+GET http://localhost:5000/Greetings/Tyrion
+Connection: Keep-Alive
+User-Agent: Apache-HttpClient/4.5.14 (Java/17.0.6)
+Accept-Encoding: br,deflate,gzip,x-gzip
-{
- "Greeting" : "I drink, and I know things"
-}
+<> 2023-07-07T165552.200.json
+
+###
-<> 2022-06-20T193450.200.json
+GET http://localhost:5000/People/Tyrion
+Connection: Keep-Alive
+User-Agent: Apache-HttpClient/4.5.14 (Java/17.0.6)
+Accept-Encoding: br,deflate,gzip,x-gzip
+
+<> 2023-07-07T165549.200.json
###
-POST http://localhost:5000/Greetings/Tyrion/new
+GET http://localhost:5000/People/Tyrion
+Connection: Keep-Alive
+User-Agent: Apache-HttpClient/4.5.14 (Java/17.0.6)
+Accept-Encoding: br,deflate,gzip,x-gzip
+
+<> 2023-07-07T165406.200.json
+
+###
+
+POST http://localhost:5000/People/new
Content-Type: application/json
+Content-Length: 23
+Connection: Keep-Alive
+User-Agent: Apache-HttpClient/4.5.14 (Java/17.0.6)
+Accept-Encoding: br,deflate,gzip,x-gzip
{
- "Greeting" : "I drink, and I know things"
+ "Name" : "Tyrion"
}
-<> 2022-06-20T193431.200.json
+<> 2023-07-07T165401.200.json
+
+###
+
+GET http://localhost:5000/People/Tyrion
+Connection: Keep-Alive
+User-Agent: Apache-HttpClient/4.5.14 (Java/17.0.6)
+Accept-Encoding: br,deflate,gzip,x-gzip
+
+<> 2023-07-07T165354.500.json
###
POST http://localhost:5000/Greetings/Tyrion/new
Content-Type: application/json
+Content-Length: 47
+Connection: Keep-Alive
+User-Agent: Apache-HttpClient/4.5.14 (Java/17.0.6)
+Accept-Encoding: br,deflate,gzip,x-gzip
{
"Greeting" : "I drink, and I know things"
}
-<> 2022-06-20T193430.200.json
+<> 2023-07-04T212401.200.json
###
POST http://localhost:5000/Greetings/Tyrion/new
Content-Type: application/json
+Content-Length: 47
+Connection: Keep-Alive
+User-Agent: Apache-HttpClient/4.5.14 (Java/17.0.6)
+Accept-Encoding: br,deflate,gzip,x-gzip
{
"Greeting" : "I drink, and I know things"
}
-<> 2022-06-20T193429.200.json
+<> 2023-07-04T212358.200.json
###
POST http://localhost:5000/Greetings/Tyrion/new
Content-Type: application/json
+Content-Length: 47
+Connection: Keep-Alive
+User-Agent: Apache-HttpClient/4.5.14 (Java/17.0.6)
+Accept-Encoding: br,deflate,gzip,x-gzip
{
"Greeting" : "I drink, and I know things"
}
-<> 2022-06-20T193428.200.json
+<> 2023-07-04T212356.200.json
###
POST http://localhost:5000/Greetings/Tyrion/new
Content-Type: application/json
+Content-Length: 47
+Connection: Keep-Alive
+User-Agent: Apache-HttpClient/4.5.14 (Java/17.0.6)
+Accept-Encoding: br,deflate,gzip,x-gzip
{
"Greeting" : "I drink, and I know things"
}
-<> 2022-06-20T193427.200.json
+<> 2023-07-04T211455.200.json
###
POST http://localhost:5000/Greetings/Tyrion/new
Content-Type: application/json
+Content-Length: 47
+Connection: Keep-Alive
+User-Agent: Apache-HttpClient/4.5.14 (Java/17.0.6)
+Accept-Encoding: br,deflate,gzip,x-gzip
{
"Greeting" : "I drink, and I know things"
}
-<> 2022-06-20T193426.200.json
+<> 2023-07-04T202333.200.json
###
POST http://localhost:5000/Greetings/Tyrion/new
Content-Type: application/json
+Content-Length: 47
+Connection: Keep-Alive
+User-Agent: Apache-HttpClient/4.5.14 (Java/17.0.6)
+Accept-Encoding: br,deflate,gzip,x-gzip
{
"Greeting" : "I drink, and I know things"
}
-<> 2022-06-20T193423.200.json
+<> 2023-07-04T202332.200.json
###
POST http://localhost:5000/Greetings/Tyrion/new
Content-Type: application/json
+Content-Length: 47
+Connection: Keep-Alive
+User-Agent: Apache-HttpClient/4.5.14 (Java/17.0.6)
+Accept-Encoding: br,deflate,gzip,x-gzip
{
"Greeting" : "I drink, and I know things"
}
-<> 2022-06-20T193420.200.json
+<> 2023-07-04T202330.200.json
###
-POST http://localhost:5000/Greetings/Tyrion/new
-Content-Type: application/json
+GET http://localhost:5000/People/Tyrion
+Connection: Keep-Alive
+User-Agent: Apache-HttpClient/4.5.14 (Java/17.0.6)
+Accept-Encoding: br,deflate,gzip,x-gzip
-{
- "Greeting" : "I drink, and I know things"
-}
+<> 2023-07-04T202327.200.json
+
+###
+
+GET http://localhost:5000/People/Tyrion
+Connection: Keep-Alive
+User-Agent: Apache-HttpClient/4.5.14 (Java/17.0.6)
+Accept-Encoding: br,deflate,gzip,x-gzip
+
+<> 2023-07-04T201903.200.json
###
POST http://localhost:5000/People/new
Content-Type: application/json
+Content-Length: 23
+Connection: Keep-Alive
+User-Agent: Apache-HttpClient/4.5.14 (Java/17.0.6)
+Accept-Encoding: br,deflate,gzip,x-gzip
{
"Name" : "Tyrion"
}
-<> 2022-06-20T192613.200.json
+<> 2023-07-04T201859.200.json
+
+###
+
+GET http://localhost:5000/People/Tyrion
+Connection: Keep-Alive
+User-Agent: Apache-HttpClient/4.5.14 (Java/17.0.6)
+Accept-Encoding: br,deflate,gzip,x-gzip
+
+###
+
+GET http://localhost:5000/People/Tyrion
+Connection: Keep-Alive
+User-Agent: Apache-HttpClient/4.5.14 (Java/17.0.6)
+Accept-Encoding: br,deflate,gzip,x-gzip
###
diff --git a/Brighter.sln b/Brighter.sln
index 8edb695c60..b060ce5afc 100644
--- a/Brighter.sln
+++ b/Brighter.sln
@@ -251,21 +251,11 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "GreetingsPorts", "samples\W
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "GreetingsEntities", "samples\WebAPI_Dapper\GreetingsEntities\GreetingsEntities.csproj", "{4164912F-F69E-4AD7-A521-6D58253B5ABC}"
EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Paramore.Brighter.Sqlite.Dapper", "src\Paramore.Brighter.Sqlite.Dapper\Paramore.Brighter.Sqlite.Dapper.csproj", "{1DEBF15F-AA1B-4A9C-B1C3-7190E0988C86}"
-EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Paramore.Brighter.MySql.Dapper", "src\Paramore.Brighter.MySql.Dapper\Paramore.Brighter.MySql.Dapper.csproj", "{191A929A-0AE4-4E2A-9608-E47F93FA0004}"
-EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Paramore.Brighter.Dapper", "src\Paramore.Brighter.Dapper\Paramore.Brighter.Dapper.csproj", "{5FDA646C-30DA-4F13-8399-A3C533D2D16E}"
EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Greetings_SqliteMigrations", "samples\WebAPI_Dapper\Greetings_SqliteMigrations\Greetings_SqliteMigrations.csproj", "{026230E1-F388-425A-98CB-6E17C174FE62}"
-EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Salutations_SqliteMigrations", "samples\WebAPI_Dapper\Salutations_SqliteMigrations\Salutations_SqliteMigrations.csproj", "{C601A031-963B-4EA9-82C7-1221B1EE9E51}"
-EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Greetings_MySqlMigrations", "samples\WebAPI_Dapper\Greetings_MySqlMigrations\Greetings_MySqlMigrations.csproj", "{ECD2C752-4E20-4EC5-BB6B-B06731BDE5BD}"
-EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Salutations_mySqlMigrations", "samples\WebAPI_Dapper\Salutations_mySqlMigrations\Salutations_mySqlMigrations.csproj", "{F72FF4C5-4CD8-4EFE-B468-5FAE45D20931}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Salutations_Migrations", "samples\WebAPI_Dapper\Salutations_Migrations\Salutations_Migrations.csproj", "{C601A031-963B-4EA9-82C7-1221B1EE9E51}"
EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Paramore.Brighter.MsSql.Dapper", "src\Paramore.Brighter.MsSql.Dapper\Paramore.Brighter.MsSql.Dapper.csproj", "{1E4A5095-2D49-43EF-9628-BF7CE147CAE9}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Greetings_Migrations", "samples\WebAPI_Dapper\Greetings_Migrations\Greetings_Migrations.csproj", "{ECD2C752-4E20-4EC5-BB6B-B06731BDE5BD}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "WebAPI_Dynamo", "WebAPI_Dynamo", "{11935469-A062-4CFF-9F72-F4F41E14C2B4}"
EndProject
@@ -1506,30 +1496,6 @@ Global
{4164912F-F69E-4AD7-A521-6D58253B5ABC}.Release|Mixed Platforms.Build.0 = Release|Any CPU
{4164912F-F69E-4AD7-A521-6D58253B5ABC}.Release|x86.ActiveCfg = Release|Any CPU
{4164912F-F69E-4AD7-A521-6D58253B5ABC}.Release|x86.Build.0 = Release|Any CPU
- {1DEBF15F-AA1B-4A9C-B1C3-7190E0988C86}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {1DEBF15F-AA1B-4A9C-B1C3-7190E0988C86}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {1DEBF15F-AA1B-4A9C-B1C3-7190E0988C86}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
- {1DEBF15F-AA1B-4A9C-B1C3-7190E0988C86}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
- {1DEBF15F-AA1B-4A9C-B1C3-7190E0988C86}.Debug|x86.ActiveCfg = Debug|Any CPU
- {1DEBF15F-AA1B-4A9C-B1C3-7190E0988C86}.Debug|x86.Build.0 = Debug|Any CPU
- {1DEBF15F-AA1B-4A9C-B1C3-7190E0988C86}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {1DEBF15F-AA1B-4A9C-B1C3-7190E0988C86}.Release|Any CPU.Build.0 = Release|Any CPU
- {1DEBF15F-AA1B-4A9C-B1C3-7190E0988C86}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
- {1DEBF15F-AA1B-4A9C-B1C3-7190E0988C86}.Release|Mixed Platforms.Build.0 = Release|Any CPU
- {1DEBF15F-AA1B-4A9C-B1C3-7190E0988C86}.Release|x86.ActiveCfg = Release|Any CPU
- {1DEBF15F-AA1B-4A9C-B1C3-7190E0988C86}.Release|x86.Build.0 = Release|Any CPU
- {191A929A-0AE4-4E2A-9608-E47F93FA0004}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {191A929A-0AE4-4E2A-9608-E47F93FA0004}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {191A929A-0AE4-4E2A-9608-E47F93FA0004}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
- {191A929A-0AE4-4E2A-9608-E47F93FA0004}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
- {191A929A-0AE4-4E2A-9608-E47F93FA0004}.Debug|x86.ActiveCfg = Debug|Any CPU
- {191A929A-0AE4-4E2A-9608-E47F93FA0004}.Debug|x86.Build.0 = Debug|Any CPU
- {191A929A-0AE4-4E2A-9608-E47F93FA0004}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {191A929A-0AE4-4E2A-9608-E47F93FA0004}.Release|Any CPU.Build.0 = Release|Any CPU
- {191A929A-0AE4-4E2A-9608-E47F93FA0004}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
- {191A929A-0AE4-4E2A-9608-E47F93FA0004}.Release|Mixed Platforms.Build.0 = Release|Any CPU
- {191A929A-0AE4-4E2A-9608-E47F93FA0004}.Release|x86.ActiveCfg = Release|Any CPU
- {191A929A-0AE4-4E2A-9608-E47F93FA0004}.Release|x86.Build.0 = Release|Any CPU
{5FDA646C-30DA-4F13-8399-A3C533D2D16E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{5FDA646C-30DA-4F13-8399-A3C533D2D16E}.Debug|Any CPU.Build.0 = Debug|Any CPU
{5FDA646C-30DA-4F13-8399-A3C533D2D16E}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
@@ -1542,18 +1508,6 @@ Global
{5FDA646C-30DA-4F13-8399-A3C533D2D16E}.Release|Mixed Platforms.Build.0 = Release|Any CPU
{5FDA646C-30DA-4F13-8399-A3C533D2D16E}.Release|x86.ActiveCfg = Release|Any CPU
{5FDA646C-30DA-4F13-8399-A3C533D2D16E}.Release|x86.Build.0 = Release|Any CPU
- {026230E1-F388-425A-98CB-6E17C174FE62}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {026230E1-F388-425A-98CB-6E17C174FE62}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {026230E1-F388-425A-98CB-6E17C174FE62}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
- {026230E1-F388-425A-98CB-6E17C174FE62}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
- {026230E1-F388-425A-98CB-6E17C174FE62}.Debug|x86.ActiveCfg = Debug|Any CPU
- {026230E1-F388-425A-98CB-6E17C174FE62}.Debug|x86.Build.0 = Debug|Any CPU
- {026230E1-F388-425A-98CB-6E17C174FE62}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {026230E1-F388-425A-98CB-6E17C174FE62}.Release|Any CPU.Build.0 = Release|Any CPU
- {026230E1-F388-425A-98CB-6E17C174FE62}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
- {026230E1-F388-425A-98CB-6E17C174FE62}.Release|Mixed Platforms.Build.0 = Release|Any CPU
- {026230E1-F388-425A-98CB-6E17C174FE62}.Release|x86.ActiveCfg = Release|Any CPU
- {026230E1-F388-425A-98CB-6E17C174FE62}.Release|x86.Build.0 = Release|Any CPU
{C601A031-963B-4EA9-82C7-1221B1EE9E51}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{C601A031-963B-4EA9-82C7-1221B1EE9E51}.Debug|Any CPU.Build.0 = Debug|Any CPU
{C601A031-963B-4EA9-82C7-1221B1EE9E51}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
@@ -1578,30 +1532,6 @@ Global
{ECD2C752-4E20-4EC5-BB6B-B06731BDE5BD}.Release|Mixed Platforms.Build.0 = Release|Any CPU
{ECD2C752-4E20-4EC5-BB6B-B06731BDE5BD}.Release|x86.ActiveCfg = Release|Any CPU
{ECD2C752-4E20-4EC5-BB6B-B06731BDE5BD}.Release|x86.Build.0 = Release|Any CPU
- {F72FF4C5-4CD8-4EFE-B468-5FAE45D20931}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {F72FF4C5-4CD8-4EFE-B468-5FAE45D20931}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {F72FF4C5-4CD8-4EFE-B468-5FAE45D20931}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
- {F72FF4C5-4CD8-4EFE-B468-5FAE45D20931}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
- {F72FF4C5-4CD8-4EFE-B468-5FAE45D20931}.Debug|x86.ActiveCfg = Debug|Any CPU
- {F72FF4C5-4CD8-4EFE-B468-5FAE45D20931}.Debug|x86.Build.0 = Debug|Any CPU
- {F72FF4C5-4CD8-4EFE-B468-5FAE45D20931}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {F72FF4C5-4CD8-4EFE-B468-5FAE45D20931}.Release|Any CPU.Build.0 = Release|Any CPU
- {F72FF4C5-4CD8-4EFE-B468-5FAE45D20931}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
- {F72FF4C5-4CD8-4EFE-B468-5FAE45D20931}.Release|Mixed Platforms.Build.0 = Release|Any CPU
- {F72FF4C5-4CD8-4EFE-B468-5FAE45D20931}.Release|x86.ActiveCfg = Release|Any CPU
- {F72FF4C5-4CD8-4EFE-B468-5FAE45D20931}.Release|x86.Build.0 = Release|Any CPU
- {1E4A5095-2D49-43EF-9628-BF7CE147CAE9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {1E4A5095-2D49-43EF-9628-BF7CE147CAE9}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {1E4A5095-2D49-43EF-9628-BF7CE147CAE9}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
- {1E4A5095-2D49-43EF-9628-BF7CE147CAE9}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
- {1E4A5095-2D49-43EF-9628-BF7CE147CAE9}.Debug|x86.ActiveCfg = Debug|Any CPU
- {1E4A5095-2D49-43EF-9628-BF7CE147CAE9}.Debug|x86.Build.0 = Debug|Any CPU
- {1E4A5095-2D49-43EF-9628-BF7CE147CAE9}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {1E4A5095-2D49-43EF-9628-BF7CE147CAE9}.Release|Any CPU.Build.0 = Release|Any CPU
- {1E4A5095-2D49-43EF-9628-BF7CE147CAE9}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
- {1E4A5095-2D49-43EF-9628-BF7CE147CAE9}.Release|Mixed Platforms.Build.0 = Release|Any CPU
- {1E4A5095-2D49-43EF-9628-BF7CE147CAE9}.Release|x86.ActiveCfg = Release|Any CPU
- {1E4A5095-2D49-43EF-9628-BF7CE147CAE9}.Release|x86.Build.0 = Release|Any CPU
{8C5F9810-2158-4479-9C0B-E139F2BC8125}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{8C5F9810-2158-4479-9C0B-E139F2BC8125}.Debug|Any CPU.Build.0 = Debug|Any CPU
{8C5F9810-2158-4479-9C0B-E139F2BC8125}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
@@ -2065,10 +1995,8 @@ Global
{526E4E1A-9E8E-4233-B851-D6172D013AF6} = {202BA107-89D5-4868-AC5A-3527114C0109}
{79A46154-4754-48B1-849F-374AD729C040} = {202BA107-89D5-4868-AC5A-3527114C0109}
{4164912F-F69E-4AD7-A521-6D58253B5ABC} = {202BA107-89D5-4868-AC5A-3527114C0109}
- {026230E1-F388-425A-98CB-6E17C174FE62} = {202BA107-89D5-4868-AC5A-3527114C0109}
{C601A031-963B-4EA9-82C7-1221B1EE9E51} = {202BA107-89D5-4868-AC5A-3527114C0109}
{ECD2C752-4E20-4EC5-BB6B-B06731BDE5BD} = {202BA107-89D5-4868-AC5A-3527114C0109}
- {F72FF4C5-4CD8-4EFE-B468-5FAE45D20931} = {202BA107-89D5-4868-AC5A-3527114C0109}
{11935469-A062-4CFF-9F72-F4F41E14C2B4} = {235DE1F1-E71B-4817-8E27-3B34FF006E4C}
{8C5F9810-2158-4479-9C0B-E139F2BC8125} = {11935469-A062-4CFF-9F72-F4F41E14C2B4}
{C0E3FF76-597A-4449-9DBF-18BC76586685} = {11935469-A062-4CFF-9F72-F4F41E14C2B4}
diff --git a/Docker/dynamodb/shared-local-instance.db b/Docker/dynamodb/shared-local-instance.db
new file mode 100644
index 0000000000..43008023c6
Binary files /dev/null and b/Docker/dynamodb/shared-local-instance.db differ
diff --git a/docker-compose-mssql.yaml b/docker-compose-mssql.yaml
index 25fe426b3f..808a5b59f3 100644
--- a/docker-compose-mssql.yaml
+++ b/docker-compose-mssql.yaml
@@ -6,12 +6,12 @@ services:
sqlserver:
image: mcr.microsoft.com/mssql/server
ports:
- - "1433:1433"
+ - "11433:1433"
volumes:
- sqlserver-data:/var/opt/mssql
environment:
- - ACCEPT_EULA=Y
- - SA_PASSWORD=Password123!
+ - 'ACCEPT_EULA=Y'
+ - 'SA_PASSWORD=Password123!'
volumes:
sqlserver-data:
diff --git a/samples/ASBTaskQueue/Greetings/Greetings.csproj b/samples/ASBTaskQueue/Greetings/Greetings.csproj
index a0657a44a4..c3e286ea2c 100644
--- a/samples/ASBTaskQueue/Greetings/Greetings.csproj
+++ b/samples/ASBTaskQueue/Greetings/Greetings.csproj
@@ -16,7 +16,7 @@
-
+
diff --git a/samples/ASBTaskQueue/GreetingsReceiverConsole/Program.cs b/samples/ASBTaskQueue/GreetingsReceiverConsole/Program.cs
index 9bf4a641b0..5661437160 100644
--- a/samples/ASBTaskQueue/GreetingsReceiverConsole/Program.cs
+++ b/samples/ASBTaskQueue/GreetingsReceiverConsole/Program.cs
@@ -60,7 +60,6 @@ public async static Task Main(string[] args)
options.ChannelFactory = new AzureServiceBusChannelFactory(asbConsumerFactory);
options.UseScoped = false;
})
- .UseInMemoryOutbox()
.AutoFromAssemblies();
services.AddHostedService();
diff --git a/samples/ASBTaskQueue/GreetingsScopedReceiverConsole/Program.cs b/samples/ASBTaskQueue/GreetingsScopedReceiverConsole/Program.cs
index 6472fff9df..33ca33e3dc 100644
--- a/samples/ASBTaskQueue/GreetingsScopedReceiverConsole/Program.cs
+++ b/samples/ASBTaskQueue/GreetingsScopedReceiverConsole/Program.cs
@@ -62,7 +62,6 @@ public static async Task Main(string[] args)
options.ChannelFactory = new AzureServiceBusChannelFactory(asbConsumerFactory);
options.UseScoped = true;
})
- .UseInMemoryOutbox()
.AutoFromAssemblies();
services.AddHostedService();
diff --git a/samples/ASBTaskQueue/GreetingsSender.Web/Program.cs b/samples/ASBTaskQueue/GreetingsSender.Web/Program.cs
index 8363e6b58c..c7063d4dbe 100644
--- a/samples/ASBTaskQueue/GreetingsSender.Web/Program.cs
+++ b/samples/ASBTaskQueue/GreetingsSender.Web/Program.cs
@@ -1,4 +1,5 @@
-using Greetings.Adaptors.Data;
+using System.Data.Common;
+using Greetings.Adaptors.Data;
using Greetings.Adaptors.Services;
using Greetings.Ports.Commands;
using Microsoft.AspNetCore.Builder;
@@ -36,7 +37,18 @@
var asbConnection = new ServiceBusVisualStudioCredentialClientProvider(asbEndpoint);
-var outboxConfig = new MsSqlConfiguration(dbConnString, "BrighterOutbox");
+var outboxConfig = new RelationalDatabaseConfiguration(dbConnString, outBoxTableName: "BrighterOutbox");
+
+var producerRegistry = new AzureServiceBusProducerRegistryFactory(
+ asbConnection,
+ new AzureServiceBusPublication[]
+ {
+ new() { Topic = new RoutingKey("greeting.event") },
+ new() { Topic = new RoutingKey("greeting.addGreetingCommand") },
+ new() { Topic = new RoutingKey("greeting.Asyncevent") }
+ }
+ )
+ .Create();
builder.Services
.AddBrighter(opt =>
@@ -44,25 +56,17 @@
opt.PolicyRegistry = new DefaultPolicy();
opt.CommandProcessorLifetime = ServiceLifetime.Scoped;
})
- .UseExternalBus(
- new AzureServiceBusProducerRegistryFactory(
- asbConnection,
- new AzureServiceBusPublication[]
- {
- new() { Topic = new RoutingKey("greeting.event") },
- new() { Topic = new RoutingKey("greeting.addGreetingCommand") },
- new() { Topic = new RoutingKey("greeting.Asyncevent") }
- }
- )
- .Create()
- )
- .UseMsSqlOutbox(outboxConfig, typeof(MsSqlSqlAuthConnectionProvider))
- .UseMsSqlTransactionConnectionProvider(typeof(MsSqlEntityFrameworkCoreConnectionProvider))
.MapperRegistry(r =>
{
r.Add(typeof(GreetingEvent), typeof(GreetingEventMessageMapper));
r.Add(typeof(GreetingAsyncEvent), typeof(GreetingEventAsyncMessageMapper));
r.Add(typeof(AddGreetingCommand), typeof(AddGreetingMessageMapper));
+ })
+ .UseExternalBus((configure) =>
+ {
+ configure.ProducerRegistry = producerRegistry;
+ configure.Outbox = new MsSqlOutbox(outboxConfig);
+ configure.TransactionProvider = typeof(MsSqlEntityFrameworkCoreConnectionProvider);
});
diff --git a/samples/ASBTaskQueue/GreetingsSender/Program.cs b/samples/ASBTaskQueue/GreetingsSender/Program.cs
index b6d8ff6b97..41d9427e4a 100644
--- a/samples/ASBTaskQueue/GreetingsSender/Program.cs
+++ b/samples/ASBTaskQueue/GreetingsSender/Program.cs
@@ -1,10 +1,13 @@
using System;
+using System.Data.Common;
+using System.Transactions;
using Greetings.Ports.Events;
using Microsoft.Extensions.DependencyInjection;
using Paramore.Brighter;
using Paramore.Brighter.Extensions.DependencyInjection;
using Paramore.Brighter.MessagingGateway.AzureServiceBus;
using Paramore.Brighter.MessagingGateway.AzureServiceBus.ClientProvider;
+using Polly.Caching;
namespace GreetingsSender
{
@@ -19,26 +22,30 @@ static void Main(string[] args)
//TODO: add your ASB qualified name here
var asbClientProvider = new ServiceBusVisualStudioCredentialClientProvider("fim-development-bus.servicebus.windows.net");
- serviceCollection.AddBrighter()
- .UseInMemoryOutbox()
- .UseExternalBus(new AzureServiceBusProducerRegistryFactory(
- asbClientProvider,
- new AzureServiceBusPublication[]
+ var producerRegistry = new AzureServiceBusProducerRegistryFactory(
+ asbClientProvider,
+ new AzureServiceBusPublication[]
+ {
+ new AzureServiceBusPublication
+ {
+ Topic = new RoutingKey("greeting.event")
+ },
+ new AzureServiceBusPublication
{
- new AzureServiceBusPublication
- {
- Topic = new RoutingKey("greeting.event")
- },
- new AzureServiceBusPublication
- {
- Topic = new RoutingKey("greeting.addGreetingCommand")
- },
- new AzureServiceBusPublication
- {
- Topic = new RoutingKey("greeting.Asyncevent")
- }
+ Topic = new RoutingKey("greeting.addGreetingCommand")
+ },
+ new AzureServiceBusPublication
+ {
+ Topic = new RoutingKey("greeting.Asyncevent")
}
- ).Create())
+ }
+ ).Create();
+
+ serviceCollection.AddBrighter()
+ .UseExternalBus((config) =>
+ {
+ config.ProducerRegistry = producerRegistry;
+ })
.AutoFromAssemblies();
var serviceProvider = serviceCollection.BuildServiceProvider();
diff --git a/samples/ASBTaskQueue/GreetingsWorker/Program.cs b/samples/ASBTaskQueue/GreetingsWorker/Program.cs
index d2b8ab4902..ae1741b586 100644
--- a/samples/ASBTaskQueue/GreetingsWorker/Program.cs
+++ b/samples/ASBTaskQueue/GreetingsWorker/Program.cs
@@ -71,7 +71,7 @@
o.UseSqlServer(dbConnString);
});
-var outboxConfig = new MsSqlConfiguration(dbConnString, "BrighterOutbox");
+var outboxConfig = new RelationalDatabaseConfiguration(dbConnString, outBoxTableName: "BrighterOutbox");
//TODO: add your ASB qualified name here
var clientProvider = new ServiceBusVisualStudioCredentialClientProvider(".servicebus.windows.net");
@@ -83,8 +83,7 @@
options.ChannelFactory = new AzureServiceBusChannelFactory(asbConsumerFactory);
options.UseScoped = true;
- }).UseMsSqlOutbox(outboxConfig, typeof(MsSqlSqlAuthConnectionProvider))
- .UseMsSqlTransactionConnectionProvider(typeof(MsSqlEntityFrameworkCoreConnectionProvider))
+ })
.AutoFromAssemblies();
builder.Services.AddHostedService();
diff --git a/samples/AWSTaskQueue/GreetingsPumper/Program.cs b/samples/AWSTaskQueue/GreetingsPumper/Program.cs
index bf634f2ed5..0478b71099 100644
--- a/samples/AWSTaskQueue/GreetingsPumper/Program.cs
+++ b/samples/AWSTaskQueue/GreetingsPumper/Program.cs
@@ -1,6 +1,7 @@
using System;
using System.Threading;
using System.Threading.Tasks;
+using System.Transactions;
using Amazon;
using Amazon.Runtime.CredentialManagement;
using Greetings.Ports.Commands;
@@ -31,19 +32,22 @@ private static async Task Main(string[] args)
{
var awsConnection = new AWSMessagingGatewayConnection(credentials, RegionEndpoint.EUWest1);
- services.AddBrighter()
- .UseInMemoryOutbox()
- .UseExternalBus(new SnsProducerRegistryFactory(
- awsConnection,
- new SnsPublication[]
+ var producerRegistry = new SnsProducerRegistryFactory(
+ awsConnection,
+ new SnsPublication[]
+ {
+ new SnsPublication
{
- new SnsPublication
- {
- Topic = new RoutingKey(typeof(GreetingEvent).FullName.ToValidSNSTopicName())
- }
+ Topic = new RoutingKey(typeof(GreetingEvent).FullName.ToValidSNSTopicName())
}
- ).Create()
- )
+ }
+ ).Create();
+
+ services.AddBrighter()
+ .UseExternalBus((configure) =>
+ {
+ configure.ProducerRegistry = producerRegistry;
+ })
.AutoFromAssemblies(typeof(GreetingEvent).Assembly);
}
diff --git a/samples/AWSTaskQueue/GreetingsReceiverConsole/Program.cs b/samples/AWSTaskQueue/GreetingsReceiverConsole/Program.cs
index 3b8d1c5925..2e8d4cc17a 100644
--- a/samples/AWSTaskQueue/GreetingsReceiverConsole/Program.cs
+++ b/samples/AWSTaskQueue/GreetingsReceiverConsole/Program.cs
@@ -72,7 +72,6 @@ public static async Task Main(string[] args)
options.Subscriptions = subscriptions;
options.ChannelFactory = new ChannelFactory(awsConnection);
})
- .UseInMemoryOutbox()
.AutoFromAssemblies();
}
diff --git a/samples/AWSTaskQueue/GreetingsSender/Program.cs b/samples/AWSTaskQueue/GreetingsSender/Program.cs
index b6f1bac8e7..fcbf665c6d 100644
--- a/samples/AWSTaskQueue/GreetingsSender/Program.cs
+++ b/samples/AWSTaskQueue/GreetingsSender/Program.cs
@@ -22,6 +22,7 @@ THE SOFTWARE. */
#endregion
+using System.Transactions;
using Amazon;
using Amazon.Runtime.CredentialManagement;
using Greetings.Ports.Commands;
@@ -52,19 +53,22 @@ static void Main(string[] args)
{
var awsConnection = new AWSMessagingGatewayConnection(credentials, RegionEndpoint.EUWest1);
- serviceCollection.AddBrighter()
- .UseInMemoryOutbox()
- .UseExternalBus(new SnsProducerRegistryFactory(
- awsConnection,
- new SnsPublication[]
+ var producerRegistry = new SnsProducerRegistryFactory(
+ awsConnection,
+ new SnsPublication[]
+ {
+ new SnsPublication
{
- new SnsPublication
- {
- Topic = new RoutingKey(typeof(GreetingEvent).FullName.ToValidSNSTopicName())
- }
+ Topic = new RoutingKey(typeof(GreetingEvent).FullName.ToValidSNSTopicName())
}
- ).Create()
- )
+ }
+ ).Create();
+
+ serviceCollection.AddBrighter()
+ .UseExternalBus((configure) =>
+ {
+ configure.ProducerRegistry = producerRegistry;
+ })
.AutoFromAssemblies(typeof(GreetingEvent).Assembly);
var serviceProvider = serviceCollection.BuildServiceProvider();
diff --git a/samples/AWSTransfomers/ClaimCheck/GreetingsReceiverConsole/Program.cs b/samples/AWSTransfomers/ClaimCheck/GreetingsReceiverConsole/Program.cs
index 33ed7cfc83..b301913e38 100644
--- a/samples/AWSTransfomers/ClaimCheck/GreetingsReceiverConsole/Program.cs
+++ b/samples/AWSTransfomers/ClaimCheck/GreetingsReceiverConsole/Program.cs
@@ -76,7 +76,6 @@ public static async Task Main(string[] args)
options.Subscriptions = subscriptions;
options.ChannelFactory = new ChannelFactory(awsConnection);
})
- .UseInMemoryOutbox()
.AutoFromAssemblies();
//We need this for the check as to whether an S3 bucket exists
diff --git a/samples/AWSTransfomers/ClaimCheck/GreetingsSender/Program.cs b/samples/AWSTransfomers/ClaimCheck/GreetingsSender/Program.cs
index b3cdfb116d..67213ae9c3 100644
--- a/samples/AWSTransfomers/ClaimCheck/GreetingsSender/Program.cs
+++ b/samples/AWSTransfomers/ClaimCheck/GreetingsSender/Program.cs
@@ -24,6 +24,7 @@ THE SOFTWARE. */
using System;
using System.Linq;
+using System.Transactions;
using Amazon;
using Amazon.Runtime.CredentialManagement;
using Amazon.S3;
@@ -57,22 +58,25 @@ static void Main(string[] args)
var awsConnection = new AWSMessagingGatewayConnection(credentials, RegionEndpoint.EUWest1);
var topic = new RoutingKey(typeof(GreetingEvent).FullName.ToValidSNSTopicName());
-
- serviceCollection.AddBrighter()
- .UseInMemoryOutbox()
- .UseExternalBus(new SnsProducerRegistryFactory(
- awsConnection,
- new SnsPublication[]
+
+ var producerRegistry = new SnsProducerRegistryFactory(
+ awsConnection,
+ new SnsPublication[]
+ {
+ new SnsPublication
{
- new SnsPublication
- {
- Topic = topic,
- FindTopicBy = TopicFindBy.Convention,
- MakeChannels = OnMissingChannel.Create
- }
+ Topic = topic,
+ FindTopicBy = TopicFindBy.Convention,
+ MakeChannels = OnMissingChannel.Create
}
- ).Create()
- )
+ }
+ ).Create();
+
+ serviceCollection.AddBrighter()
+ .UseExternalBus((configure) =>
+ {
+ configure.ProducerRegistry = producerRegistry;
+ })
.AutoFromAssemblies(typeof(GreetingEvent).Assembly);
//We need this for the check as to whether an S3 bucket exists
diff --git a/samples/AWSTransfomers/Compression/GreetingsReceiverConsole/Program.cs b/samples/AWSTransfomers/Compression/GreetingsReceiverConsole/Program.cs
index d429ddd3c8..4907a5e3aa 100644
--- a/samples/AWSTransfomers/Compression/GreetingsReceiverConsole/Program.cs
+++ b/samples/AWSTransfomers/Compression/GreetingsReceiverConsole/Program.cs
@@ -76,7 +76,6 @@ public static async Task Main(string[] args)
options.Subscriptions = subscriptions;
options.ChannelFactory = new ChannelFactory(awsConnection);
})
- .UseInMemoryOutbox()
.AutoFromAssemblies();
}
diff --git a/samples/AWSTransfomers/Compression/GreetingsSender/Program.cs b/samples/AWSTransfomers/Compression/GreetingsSender/Program.cs
index 62b164eef7..6e79ab36e8 100644
--- a/samples/AWSTransfomers/Compression/GreetingsSender/Program.cs
+++ b/samples/AWSTransfomers/Compression/GreetingsSender/Program.cs
@@ -24,6 +24,7 @@ THE SOFTWARE. */
using System;
using System.Linq;
+using System.Transactions;
using Amazon;
using Amazon.Runtime.CredentialManagement;
using Amazon.S3;
@@ -57,22 +58,25 @@ static void Main(string[] args)
var awsConnection = new AWSMessagingGatewayConnection(credentials, RegionEndpoint.EUWest1);
var topic = new RoutingKey(typeof(GreetingEvent).FullName.ToValidSNSTopicName());
-
- serviceCollection.AddBrighter()
- .UseInMemoryOutbox()
- .UseExternalBus(new SnsProducerRegistryFactory(
- awsConnection,
- new SnsPublication[]
+
+ var producerRegistry = new SnsProducerRegistryFactory(
+ awsConnection,
+ new SnsPublication[]
+ {
+ new SnsPublication
{
- new SnsPublication
- {
- Topic = topic,
- FindTopicBy = TopicFindBy.Convention,
- MakeChannels = OnMissingChannel.Create
- }
+ Topic = topic,
+ FindTopicBy = TopicFindBy.Convention,
+ MakeChannels = OnMissingChannel.Create
}
- ).Create()
- )
+ }
+ ).Create();
+
+ serviceCollection.AddBrighter()
+ .UseExternalBus((configure) =>
+ {
+ configure.ProducerRegistry = producerRegistry;
+ })
.AutoFromAssemblies(typeof(GreetingEvent).Assembly);
var serviceProvider = serviceCollection.BuildServiceProvider();
diff --git a/samples/HelloAsyncListeners/Program.cs b/samples/HelloAsyncListeners/Program.cs
index 65228c51f6..29e70c5889 100644
--- a/samples/HelloAsyncListeners/Program.cs
+++ b/samples/HelloAsyncListeners/Program.cs
@@ -1,4 +1,5 @@
using System.Threading.Tasks;
+using System.Transactions;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Paramore.Brighter.Extensions.DependencyInjection;
diff --git a/samples/HelloWorld/Program.cs b/samples/HelloWorld/Program.cs
index 36de3a3172..3fadbd17c7 100644
--- a/samples/HelloWorld/Program.cs
+++ b/samples/HelloWorld/Program.cs
@@ -24,6 +24,7 @@ THE SOFTWARE. */
#endregion
using System;
+using System.Transactions;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Paramore.Brighter;
diff --git a/samples/HelloWorldAsync/Program.cs b/samples/HelloWorldAsync/Program.cs
index 8ac8de44ce..42ecc8fce9 100644
--- a/samples/HelloWorldAsync/Program.cs
+++ b/samples/HelloWorldAsync/Program.cs
@@ -25,6 +25,7 @@ THE SOFTWARE. */
using System.Threading;
using System.Threading.Tasks;
+using System.Transactions;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Paramore.Brighter;
diff --git a/samples/KafkaSchemaRegistry/Greetings/Greetings.csproj b/samples/KafkaSchemaRegistry/Greetings/Greetings.csproj
index 6560755cab..e7b7c36f12 100644
--- a/samples/KafkaSchemaRegistry/Greetings/Greetings.csproj
+++ b/samples/KafkaSchemaRegistry/Greetings/Greetings.csproj
@@ -12,7 +12,7 @@
-
-
+
+
\ No newline at end of file
diff --git a/samples/KafkaSchemaRegistry/GreetingsReceiverConsole/GreetingsReceiverConsole.csproj b/samples/KafkaSchemaRegistry/GreetingsReceiverConsole/GreetingsReceiverConsole.csproj
index d202b0823f..0390f1467c 100644
--- a/samples/KafkaSchemaRegistry/GreetingsReceiverConsole/GreetingsReceiverConsole.csproj
+++ b/samples/KafkaSchemaRegistry/GreetingsReceiverConsole/GreetingsReceiverConsole.csproj
@@ -4,7 +4,7 @@
Exe
-
+
diff --git a/samples/KafkaSchemaRegistry/GreetingsSender/GreetingsSender.csproj b/samples/KafkaSchemaRegistry/GreetingsSender/GreetingsSender.csproj
index 674656d90a..779c7c868c 100644
--- a/samples/KafkaSchemaRegistry/GreetingsSender/GreetingsSender.csproj
+++ b/samples/KafkaSchemaRegistry/GreetingsSender/GreetingsSender.csproj
@@ -13,7 +13,6 @@
-
diff --git a/samples/KafkaSchemaRegistry/GreetingsSender/Program.cs b/samples/KafkaSchemaRegistry/GreetingsSender/Program.cs
index 4a1d522de5..027005bbe3 100644
--- a/samples/KafkaSchemaRegistry/GreetingsSender/Program.cs
+++ b/samples/KafkaSchemaRegistry/GreetingsSender/Program.cs
@@ -27,6 +27,7 @@ THE SOFTWARE. */
using System;
using System.IO;
using System.Threading.Tasks;
+using System.Transactions;
using Confluent.SchemaRegistry;
using Greetings.Ports.Commands;
using Microsoft.Extensions.Configuration;
@@ -91,29 +92,32 @@ static async Task Main(string[] args)
var cachedSchemaRegistryClient = new CachedSchemaRegistryClient(schemaRegistryConfig);
services.AddSingleton(cachedSchemaRegistryClient);
+ var producerRegistry = new KafkaProducerRegistryFactory(
+ new KafkaMessagingGatewayConfiguration
+ {
+ Name = "paramore.brighter.greetingsender",
+ BootStrapServers = new[] {"localhost:9092"}
+ },
+ new KafkaPublication[]
+ {
+ new KafkaPublication
+ {
+ Topic = new RoutingKey("greeting.event"),
+ MessageSendMaxRetries = 3,
+ MessageTimeoutMs = 1000,
+ MaxInFlightRequestsPerConnection = 1
+ }
+ })
+ .Create();
+
services.AddBrighter(options =>
{
options.PolicyRegistry = policyRegistry;
})
- .UseInMemoryOutbox()
- .UseExternalBus(
- new KafkaProducerRegistryFactory(
- new KafkaMessagingGatewayConfiguration
- {
- Name = "paramore.brighter.greetingsender",
- BootStrapServers = new[] {"localhost:9092"}
- },
- new KafkaPublication[]
- {
- new KafkaPublication
- {
- Topic = new RoutingKey("greeting.event"),
- MessageSendMaxRetries = 3,
- MessageTimeoutMs = 1000,
- MaxInFlightRequestsPerConnection = 1
- }
- })
- .Create())
+ .UseExternalBus((configure) =>
+ {
+ configure.ProducerRegistry = producerRegistry;
+ })
.MapperRegistryFromAssemblies(typeof(GreetingEvent).Assembly);
services.AddHostedService();
diff --git a/samples/KafkaTaskQueue/GreetingsSender/Program.cs b/samples/KafkaTaskQueue/GreetingsSender/Program.cs
index b3f71c127e..0d0f5493ee 100644
--- a/samples/KafkaTaskQueue/GreetingsSender/Program.cs
+++ b/samples/KafkaTaskQueue/GreetingsSender/Program.cs
@@ -25,6 +25,7 @@ THE SOFTWARE. */
#endregion
using System;
+using System.Data.Common;
using System.IO;
using System.Threading.Tasks;
using Greetings.Ports.Commands;
@@ -85,30 +86,33 @@ static async Task Main(string[] args)
{CommandProcessor.CIRCUITBREAKERASYNC, circuitBreakerPolicyAsync}
};
+ var producerRegistry = new KafkaProducerRegistryFactory(
+ new KafkaMessagingGatewayConfiguration
+ {
+ Name = "paramore.brighter.greetingsender",
+ BootStrapServers = new[] {"localhost:9092"}
+ },
+ new KafkaPublication[]
+ {
+ new KafkaPublication
+ {
+ Topic = new RoutingKey("greeting.event"),
+ NumPartitions = 3,
+ MessageSendMaxRetries = 3,
+ MessageTimeoutMs = 1000,
+ MaxInFlightRequestsPerConnection = 1
+ }
+ })
+ .Create();
+
services.AddBrighter(options =>
{
options.PolicyRegistry = policyRegistry;
})
- .UseInMemoryOutbox()
- .UseExternalBus(
- new KafkaProducerRegistryFactory(
- new KafkaMessagingGatewayConfiguration
- {
- Name = "paramore.brighter.greetingsender",
- BootStrapServers = new[] {"localhost:9092"}
- },
- new KafkaPublication[]
- {
- new KafkaPublication
- {
- Topic = new RoutingKey("greeting.event"),
- NumPartitions = 3,
- MessageSendMaxRetries = 3,
- MessageTimeoutMs = 1000,
- MaxInFlightRequestsPerConnection = 1
- }
- })
- .Create())
+ .UseExternalBus((configure) =>
+ {
+ configure.ProducerRegistry = producerRegistry;
+ })
.MapperRegistryFromAssemblies(typeof(GreetingEvent).Assembly);
services.AddHostedService();
diff --git a/samples/MsSqlMessagingGateway/CompetingReceiverConsole/Program.cs b/samples/MsSqlMessagingGateway/CompetingReceiverConsole/Program.cs
index e923358e52..5825614e38 100644
--- a/samples/MsSqlMessagingGateway/CompetingReceiverConsole/Program.cs
+++ b/samples/MsSqlMessagingGateway/CompetingReceiverConsole/Program.cs
@@ -36,7 +36,7 @@ private static async Task Main()
timeoutInMilliseconds: 200)
};
- var messagingConfiguration = new MsSqlConfiguration(@"Database=BrighterSqlQueue;Server=.\sqlexpress;Integrated Security=SSPI;", queueStoreTable: "QueueData");
+ var messagingConfiguration = new RelationalDatabaseConfiguration(@"Database=BrighterSqlQueue;Server=.\sqlexpress;Integrated Security=SSPI;", queueStoreTable: "QueueData");
var messageConsumerFactory = new MsSqlMessageConsumerFactory(messagingConfiguration);
services.AddServiceActivator(options =>
@@ -44,10 +44,8 @@ private static async Task Main()
options.Subscriptions = subscriptions;
options.ChannelFactory = new ChannelFactory(messageConsumerFactory);
})
- .UseInMemoryOutbox()
.AutoFromAssemblies();
-
services.AddHostedService();
services.AddHostedService();
diff --git a/samples/MsSqlMessagingGateway/CompetingSender/Program.cs b/samples/MsSqlMessagingGateway/CompetingSender/Program.cs
index 7eb99fbea2..4707c8aab7 100644
--- a/samples/MsSqlMessagingGateway/CompetingSender/Program.cs
+++ b/samples/MsSqlMessagingGateway/CompetingSender/Program.cs
@@ -40,14 +40,18 @@ private static async Task Main(string[] args)
.ConfigureServices((hostContext, services) =>
{
//create the gateway
- var messagingConfiguration = new MsSqlConfiguration(@"Database=BrighterSqlQueue;Server=.\sqlexpress;Integrated Security=SSPI;", queueStoreTable: "QueueData");
+ var messagingConfiguration = new RelationalDatabaseConfiguration(@"Database=BrighterSqlQueue;Server=.\sqlexpress;Integrated Security=SSPI;", queueStoreTable: "QueueData");
- services.AddBrighter()
- .UseInMemoryOutbox()
- .UseExternalBus(new MsSqlProducerRegistryFactory(
+ var producerRegistry = new MsSqlProducerRegistryFactory(
messagingConfiguration,
new Publication[]{new Publication()})
- .Create())
+ .Create();
+
+ services.AddBrighter()
+ .UseExternalBus((configure) =>
+ {
+ configure.ProducerRegistry = producerRegistry;
+ })
.AutoFromAssemblies();
services.AddHostedService(provider => new RunCommandProcessor(provider.GetService(), repeatCount));
diff --git a/samples/MsSqlMessagingGateway/GreetingsReceiverConsole/Program.cs b/samples/MsSqlMessagingGateway/GreetingsReceiverConsole/Program.cs
index 344616544c..ec814790da 100644
--- a/samples/MsSqlMessagingGateway/GreetingsReceiverConsole/Program.cs
+++ b/samples/MsSqlMessagingGateway/GreetingsReceiverConsole/Program.cs
@@ -62,7 +62,7 @@ public static async Task Main(string[] args)
//create the gateway
var messagingConfiguration =
- new MsSqlConfiguration(
+ new RelationalDatabaseConfiguration(
@"Database=BrighterSqlQueue;Server=.\sqlexpress;Integrated Security=SSPI;", queueStoreTable: "QueueData");
var messageConsumerFactory = new MsSqlMessageConsumerFactory(messagingConfiguration);
services.AddServiceActivator(options =>
@@ -70,7 +70,6 @@ public static async Task Main(string[] args)
options.Subscriptions = subscriptions;
options.ChannelFactory = new ChannelFactory(messageConsumerFactory);
})
- .UseInMemoryOutbox()
.AutoFromAssemblies();
diff --git a/samples/MsSqlMessagingGateway/GreetingsSender/Program.cs b/samples/MsSqlMessagingGateway/GreetingsSender/Program.cs
index 834906986e..b561543d80 100644
--- a/samples/MsSqlMessagingGateway/GreetingsSender/Program.cs
+++ b/samples/MsSqlMessagingGateway/GreetingsSender/Program.cs
@@ -1,4 +1,5 @@
-using Events.Ports.Commands;
+using System.Transactions;
+using Events.Ports.Commands;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Paramore.Brighter;
@@ -23,15 +24,19 @@ static void Main()
var serviceCollection = new ServiceCollection();
serviceCollection.AddSingleton(new SerilogLoggerFactory());
- var messagingConfiguration = new MsSqlConfiguration(@"Database=BrighterSqlQueue;Server=.\sqlexpress;Integrated Security=SSPI;", queueStoreTable: "QueueData");
+ var messagingConfiguration = new RelationalDatabaseConfiguration(@"Database=BrighterSqlQueue;Server=.\sqlexpress;Integrated Security=SSPI;", queueStoreTable: "QueueData");
- serviceCollection.AddBrighter()
- .UseInMemoryOutbox()
- .UseExternalBus(new MsSqlProducerRegistryFactory(
+ var producerRegistry = new MsSqlProducerRegistryFactory(
messagingConfiguration,
new Publication[] {new Publication()}
- )
- .Create())
+ )
+ .Create();
+
+ serviceCollection.AddBrighter()
+ .UseExternalBus((configure) =>
+ {
+ configure.ProducerRegistry = producerRegistry;
+ })
.AutoFromAssemblies();
var serviceProvider = serviceCollection.BuildServiceProvider();
diff --git a/samples/OpenTelemetry/Consumer/Program.cs b/samples/OpenTelemetry/Consumer/Program.cs
index f8c6915e4a..00b7733086 100644
--- a/samples/OpenTelemetry/Consumer/Program.cs
+++ b/samples/OpenTelemetry/Consumer/Program.cs
@@ -41,6 +41,8 @@
var rmqMessageConsumerFactory = new RmqMessageConsumerFactory(rmqConnection);
+var producerRegistry = Helpers.GetProducerRegistry(rmqConnection);
+
builder.Services.AddServiceActivator(options =>
{
options.Subscriptions = new Subscription[]
@@ -64,8 +66,6 @@
};
options.ChannelFactory = new ChannelFactory(rmqMessageConsumerFactory);
})
- .UseExternalBus(Helpers.GetProducerRegistry(rmqConnection))
- .UseInMemoryOutbox()
.MapperRegistry(r =>
{
r.Register>();
@@ -78,6 +78,10 @@
r.Register();
r.Register();
})
+ .UseExternalBus((configure) =>
+ {
+ configure.ProducerRegistry = producerRegistry;
+ })
.UseOutboxSweeper(options =>
{
options.TimerInterval = 30;
diff --git a/samples/OpenTelemetry/Producer/Program.cs b/samples/OpenTelemetry/Producer/Program.cs
index 248fbbaf97..34cfe93c16 100644
--- a/samples/OpenTelemetry/Producer/Program.cs
+++ b/samples/OpenTelemetry/Producer/Program.cs
@@ -1,3 +1,4 @@
+using System.Transactions;
using OpenTelemetry;
using OpenTelemetry.Resources;
using OpenTelemetry.Shared.Commands;
@@ -27,17 +28,21 @@
Exchange = new Exchange("paramore.brighter.exchange"),
};
+var producerRegistry = Helpers.GetProducerRegistry(rmqConnection);
+
builder.Services.AddBrighter(options =>
{
options.CommandProcessorLifetime = ServiceLifetime.Scoped;
})
- .UseExternalBus(Helpers.GetProducerRegistry(rmqConnection))
- .UseInMemoryOutbox()
.MapperRegistry(r =>
{
r.Register>();
r.Register>();
r.Register>();
+ })
+ .UseExternalBus((configure) =>
+ {
+ configure.ProducerRegistry = producerRegistry;
});
builder.Services.AddSingleton();
diff --git a/samples/OpenTelemetry/Sweeper/Program.cs b/samples/OpenTelemetry/Sweeper/Program.cs
index 60dcc1bfb6..110c88bdad 100644
--- a/samples/OpenTelemetry/Sweeper/Program.cs
+++ b/samples/OpenTelemetry/Sweeper/Program.cs
@@ -1,5 +1,6 @@
// See https://aka.ms/new-console-template for more information
+using System.Transactions;
using OpenTelemetry;
using OpenTelemetry.Resources;
using OpenTelemetry.Trace;
@@ -23,13 +24,6 @@
using var tracerProvider = Sdk.CreateTracerProviderBuilder()
.SetResourceBuilder(ResourceBuilder.CreateDefault().AddService("Brighter Sweeper Sample"))
.AddSource("Paramore.Brighter")
- // .AddZipkinExporter(o => o.HttpClientFactory = () =>
- // {
- // HttpClient client = new HttpClient();
- // client.DefaultRequestHeaders.Add("X-MyCustomHeader", "value");
- // return client;
- // o.Endpoint = new Uri("http://localhost:9411/api/v2/spans");
- // })
.AddJaegerExporter()
.Build();
@@ -39,8 +33,10 @@
});
builder.Services.AddBrighter()
- .UseExternalBus(producerRegistry)
- .UseInMemoryOutbox()
+ .UseExternalBus((configure) =>
+ {
+ configure.ProducerRegistry = producerRegistry;
+ })
.UseOutboxSweeper(options =>
{
options.TimerInterval = 5;
@@ -49,7 +45,7 @@
var app = builder.Build();
-var outBox = app.Services.GetService>();
+var outBox = app.Services.GetService>();
outBox.Add(new Message(new MessageHeader(Guid.NewGuid(), "Test.Topic", MessageType.MT_COMMAND, DateTime.UtcNow),
new MessageBody("Hello")));
diff --git a/samples/RMQRequestReply/GreetingsClient/Program.cs b/samples/RMQRequestReply/GreetingsClient/Program.cs
index 8770bddfcc..d58ac3765d 100644
--- a/samples/RMQRequestReply/GreetingsClient/Program.cs
+++ b/samples/RMQRequestReply/GreetingsClient/Program.cs
@@ -23,6 +23,7 @@ THE SOFTWARE. */
#endregion
using System;
+using System.Transactions;
using Greetings.Ports.Commands;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
@@ -61,24 +62,25 @@ static void Main(string[] args)
new RmqSubscription(typeof(GreetingReply))
};
+ var producerRegistry = new RmqProducerRegistryFactory(
+ rmqConnection,
+ new RmqPublication[]
+ {
+ new RmqPublication
+ {
+ Topic = new RoutingKey("Greeting.Request")
+ }
+ }).Create();
+
serviceCollection
- .AddBrighter(options =>
+ .AddBrighter()
+ .UseExternalBus((configure) =>
{
- options.ChannelFactory = new ChannelFactory(rmqMessageConsumerFactory);
+ configure.ProducerRegistry = producerRegistry;
+ configure.UseRpc = true;
+ configure.ReplyQueueSubscriptions = replySubscriptions;
+ configure.ResponseChannelFactory = new ChannelFactory(rmqMessageConsumerFactory);
})
- .UseInMemoryOutbox()
- .UseExternalBus(
- new RmqProducerRegistryFactory(
- rmqConnection,
- new RmqPublication[]
- {
- new RmqPublication
- {
- Topic = new RoutingKey("Greeting.Request")
- }
- }).Create(),
- true,
- replySubscriptions)
.AutoFromAssemblies();
var serviceProvider = serviceCollection.BuildServiceProvider();
diff --git a/samples/RMQRequestReply/GreetingsServer/Program.cs b/samples/RMQRequestReply/GreetingsServer/Program.cs
index 16bc4760ea..d7afebae1b 100644
--- a/samples/RMQRequestReply/GreetingsServer/Program.cs
+++ b/samples/RMQRequestReply/GreetingsServer/Program.cs
@@ -70,25 +70,27 @@ public static async Task Main(string[] args)
ChannelFactory amAChannelFactory = new ChannelFactory(rmqMessageConsumerFactory);
var producer = new RmqMessageProducer(rmqConnection);
+ var producerRegistry = new RmqProducerRegistryFactory(
+ rmqConnection,
+ new RmqPublication[]
+ {
+ new()
+ {
+ //TODO: We don't know the reply routing key, but need a topic name, we could make this simpler
+ Topic = new RoutingKey("Reply"),
+ MakeChannels = OnMissingChannel.Assume
+ }
+ }).Create();
+
services.AddServiceActivator(options =>
{
options.Subscriptions = subscriptions;
options.ChannelFactory = amAChannelFactory;
})
- .UseInMemoryOutbox()
- .UseExternalBus(
- new RmqProducerRegistryFactory(
- rmqConnection,
- new RmqPublication[]
- {
- new()
- {
- //TODO: We don't know the reply routing key, but need a topic name, we could make this simpler
- Topic = new RoutingKey("Reply"),
- MakeChannels = OnMissingChannel.Assume
- }
- }).Create(),
- true)
+ .UseExternalBus((configure) =>
+ {
+ configure.ProducerRegistry = producerRegistry;
+ })
.AutoFromAssemblies();
diff --git a/samples/RMQTaskQueue/GreetingsReceiverConsole/Program.cs b/samples/RMQTaskQueue/GreetingsReceiverConsole/Program.cs
index 85959e5b57..135c7faf38 100644
--- a/samples/RMQTaskQueue/GreetingsReceiverConsole/Program.cs
+++ b/samples/RMQTaskQueue/GreetingsReceiverConsole/Program.cs
@@ -83,7 +83,6 @@ public static async Task Main(string[] args)
options.Subscriptions = subscriptions;
options.ChannelFactory = new ChannelFactory(rmqMessageConsumerFactory);
})
- .UseInMemoryOutbox()
.AutoFromAssemblies();
diff --git a/samples/RMQTaskQueue/GreetingsSender/Program.cs b/samples/RMQTaskQueue/GreetingsSender/Program.cs
index 12bb05570f..59fb8d000e 100644
--- a/samples/RMQTaskQueue/GreetingsSender/Program.cs
+++ b/samples/RMQTaskQueue/GreetingsSender/Program.cs
@@ -23,6 +23,7 @@ THE SOFTWARE. */
#endregion
using System;
+using System.Transactions;
using Greetings.Ports.Commands;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
@@ -52,30 +53,34 @@ static void Main(string[] args)
AmpqUri = new AmqpUriSpecification(new Uri("amqp://guest:guest@localhost:5672")),
Exchange = new Exchange("paramore.brighter.exchange"),
};
+
+ var producerRegistry = new RmqProducerRegistryFactory(
+ rmqConnection,
+ new RmqPublication[]
+ {
+ new()
+ {
+ MaxOutStandingMessages = 5,
+ MaxOutStandingCheckIntervalMilliSeconds = 500,
+ WaitForConfirmsTimeOutInMilliseconds = 1000,
+ MakeChannels =OnMissingChannel.Create,
+ Topic = new RoutingKey("greeting.event")
+ },
+ new()
+ {
+ MaxOutStandingMessages = 5,
+ MaxOutStandingCheckIntervalMilliSeconds = 500,
+ WaitForConfirmsTimeOutInMilliseconds = 1000,
+ MakeChannels =OnMissingChannel.Create,
+ Topic = new RoutingKey("farewell.event")
+ }
+ }).Create();
serviceCollection.AddBrighter()
- .UseInMemoryOutbox()
- .UseExternalBus(new RmqProducerRegistryFactory(
- rmqConnection,
- new RmqPublication[]
- {
- new()
- {
- MaxOutStandingMessages = 5,
- MaxOutStandingCheckIntervalMilliSeconds = 500,
- WaitForConfirmsTimeOutInMilliseconds = 1000,
- MakeChannels =OnMissingChannel.Create,
- Topic = new RoutingKey("greeting.event")
- },
- new()
- {
- MaxOutStandingMessages = 5,
- MaxOutStandingCheckIntervalMilliSeconds = 500,
- WaitForConfirmsTimeOutInMilliseconds = 1000,
- MakeChannels =OnMissingChannel.Create,
- Topic = new RoutingKey("farewell.event")
- }
- }).Create())
+ .UseExternalBus((configure) =>
+ {
+ configure.ProducerRegistry = producerRegistry;
+ })
.AutoFromAssemblies();
var serviceProvider = serviceCollection.BuildServiceProvider();
diff --git a/samples/RedisTaskQueue/GreetingsReceiver/Program.cs b/samples/RedisTaskQueue/GreetingsReceiver/Program.cs
index ef4daddcff..2e7288d371 100644
--- a/samples/RedisTaskQueue/GreetingsReceiver/Program.cs
+++ b/samples/RedisTaskQueue/GreetingsReceiver/Program.cs
@@ -49,8 +49,7 @@ public static async Task Main(string[] args)
options.Subscriptions = subscriptions;
options.ChannelFactory = new ChannelFactory(redisConsumerFactory);
})
- .UseInMemoryOutbox()
- .AutoFromAssemblies();
+ .AutoFromAssemblies();
services.AddHostedService();
diff --git a/samples/RedisTaskQueue/GreetingsSender/Program.cs b/samples/RedisTaskQueue/GreetingsSender/Program.cs
index 1a318dc712..30d7cd2b74 100644
--- a/samples/RedisTaskQueue/GreetingsSender/Program.cs
+++ b/samples/RedisTaskQueue/GreetingsSender/Program.cs
@@ -24,6 +24,7 @@ THE SOFTWARE. */
#endregion
using System;
+using System.Transactions;
using Greetings.Ports.Events;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
@@ -90,19 +91,23 @@ private static IHostBuilder CreateHostBuilder(string[] args) =>
MaxPoolSize = 10,
MessageTimeToLive = TimeSpan.FromMinutes(10)
};
+
+ var producerRegistry = new RedisProducerRegistryFactory(
+ redisConnection,
+ new RedisMessagePublication[]
+ {
+ new RedisMessagePublication
+ {
+ Topic = new RoutingKey("greeting.event")
+ }
+ }
+ ).Create();
collection.AddBrighter()
- .UseInMemoryOutbox()
- .UseExternalBus(new RedisProducerRegistryFactory(
- redisConnection,
- new RedisMessagePublication[]
- {
- new RedisMessagePublication
- {
- Topic = new RoutingKey("greeting.event")
- }
- }
- ).Create())
+ .UseExternalBus((configure) =>
+ {
+ configure.ProducerRegistry = producerRegistry;
+ })
.AutoFromAssemblies();
});
}
diff --git a/samples/WebAPI_Dapper/GreetingsEntities/Person.cs b/samples/WebAPI_Dapper/GreetingsEntities/Person.cs
index 10dce975d4..0c5aac0483 100644
--- a/samples/WebAPI_Dapper/GreetingsEntities/Person.cs
+++ b/samples/WebAPI_Dapper/GreetingsEntities/Person.cs
@@ -5,12 +5,12 @@ namespace GreetingsEntities
{
public class Person
{
- public byte[] TimeStamp { get; set; }
- public long Id { get; set; }
+ public DateTime TimeStamp { get; set; }
+ public int Id { get; set; }
public string Name { get; set; }
public IList Greetings { get; set; } = new List();
- public Person(){ /*Required for DapperExtensions*/}
+ public Person(){ /*Required for Dapper*/}
public Person(string name)
{
diff --git a/samples/WebAPI_Dapper/GreetingsPorts/EntityMappers/GreetingsMapper.cs b/samples/WebAPI_Dapper/GreetingsPorts/EntityMappers/GreetingsMapper.cs
deleted file mode 100644
index ede3108fab..0000000000
--- a/samples/WebAPI_Dapper/GreetingsPorts/EntityMappers/GreetingsMapper.cs
+++ /dev/null
@@ -1,18 +0,0 @@
-using System.Data;
-using DapperExtensions.Mapper;
-using GreetingsEntities;
-
-namespace GreetingsPorts.EntityMappers;
-
-public class GreetingsMapper : ClassMapper
-{
- public GreetingsMapper()
- {
- TableName = nameof(Greeting);
- Map(g=> g.Id).Column("Id").Key(KeyType.Identity);
- Map(g => g.Message).Column("Message");
- Map(g => g.RecipientId).Column("Recipient_Id").Key(KeyType.ForeignKey);
- }
-
-}
-
diff --git a/samples/WebAPI_Dapper/GreetingsPorts/EntityMappers/PersonMapper.cs b/samples/WebAPI_Dapper/GreetingsPorts/EntityMappers/PersonMapper.cs
deleted file mode 100644
index 7a29337bbb..0000000000
--- a/samples/WebAPI_Dapper/GreetingsPorts/EntityMappers/PersonMapper.cs
+++ /dev/null
@@ -1,17 +0,0 @@
-using DapperExtensions.Mapper;
-using GreetingsEntities;
-
-namespace GreetingsPorts.EntityMappers;
-
- public class PersonMapper : ClassMapper
- {
- public PersonMapper()
- {
- TableName = nameof(Person);
- Map(p => p.Id).Column("Id").Key(KeyType.Identity);
- Map(p => p.Name).Column("Name");
- Map(p => p.TimeStamp).Column("TimeStamp").Ignore();
- Map(p => p.Greetings).Ignore();
- ReferenceMap(p => p.Greetings).Reference((g, p) => g.RecipientId == p.Id);
- }
- }
diff --git a/samples/WebAPI_Dapper/GreetingsPorts/GreetingsPorts.csproj b/samples/WebAPI_Dapper/GreetingsPorts/GreetingsPorts.csproj
index b013116f90..424a59e904 100644
--- a/samples/WebAPI_Dapper/GreetingsPorts/GreetingsPorts.csproj
+++ b/samples/WebAPI_Dapper/GreetingsPorts/GreetingsPorts.csproj
@@ -4,12 +4,12 @@
net6.0
-
-
-
-
-
-
+
+
+
+
+
+
diff --git a/samples/WebAPI_Dapper/GreetingsPorts/Handlers/AddGreetingHandlerAsync.cs b/samples/WebAPI_Dapper/GreetingsPorts/Handlers/AddGreetingHandlerAsync.cs
index c4f768a767..88e87c9c07 100644
--- a/samples/WebAPI_Dapper/GreetingsPorts/Handlers/AddGreetingHandlerAsync.cs
+++ b/samples/WebAPI_Dapper/GreetingsPorts/Handlers/AddGreetingHandlerAsync.cs
@@ -3,16 +3,13 @@
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
-using DapperExtensions;
-using DapperExtensions.Predicate;
+using Dapper;
using Paramore.Brighter;
-using Paramore.Brighter.Dapper;
using GreetingsEntities;
using GreetingsPorts.Requests;
using Microsoft.Extensions.Logging;
using Paramore.Brighter.Logging.Attributes;
using Paramore.Brighter.Policies.Attributes;
-using Paramore.Brighter.Sqlite.Dapper;
namespace GreetingsPorts.Handlers
{
@@ -20,12 +17,12 @@ public class AddGreetingHandlerAsync: RequestHandlerAsync
{
private readonly IAmACommandProcessor _postBox;
private readonly ILogger _logger;
- private readonly SqliteDapperConnectionProvider _uow;
+ private readonly IAmATransactionConnectionProvider _transactionProvider;
- public AddGreetingHandlerAsync(IAmABoxTransactionConnectionProvider uow, IAmACommandProcessor postBox, ILogger logger)
+ public AddGreetingHandlerAsync(IAmATransactionConnectionProvider transactionProvider, IAmACommandProcessor postBox, ILogger logger)
{
- _uow = (SqliteDapperConnectionProvider)uow; //We want to take the dependency on the same instance that will be used via the Outbox, so use the marker interface
+ _transactionProvider = transactionProvider; //We want to take the dependency on the same instance that will be used via the Outbox, so use the marker interface
_postBox = postBox;
_logger = logger;
}
@@ -39,33 +36,48 @@ public override async Task HandleAsync(AddGreeting addGreeting, Can
//We use the unit of work to grab connection and transaction, because Outbox needs
//to share them 'behind the scenes'
- var conn = await _uow.GetConnectionAsync(cancellationToken);
- await conn.OpenAsync(cancellationToken);
- var tx = _uow.GetTransaction();
+ var conn = await _transactionProvider.GetConnectionAsync(cancellationToken);
+ var tx = await _transactionProvider.GetTransactionAsync(cancellationToken);
try
{
- var searchbyName = Predicates.Field(p => p.Name, Operator.Eq, addGreeting.Name);
- var people = await conn.GetListAsync(searchbyName, transaction: tx);
- var person = people.Single();
-
- var greeting = new Greeting(addGreeting.Greeting, person);
-
- //write the added child entity to the Db
- await conn.InsertAsync(greeting, tx);
+ var people = await conn.QueryAsync(
+ "select * from Person where name = @name",
+ new {name = addGreeting.Name},
+ tx
+ );
+ var person = people.SingleOrDefault();
- //Now write the message we want to send to the Db in the same transaction.
- posts.Add(await _postBox.DepositPostAsync(new GreetingMade(greeting.Greet()), cancellationToken: cancellationToken));
-
- //commit both new greeting and outgoing message
- await tx.CommitAsync(cancellationToken);
+ if (person != null)
+ {
+ var greeting = new Greeting(addGreeting.Greeting, person);
+
+ //write the added child entity to the Db
+ await conn.ExecuteAsync(
+ "insert into Greeting (Message, Recipient_Id) values (@Message, @RecipientId)",
+ new { greeting.Message, RecipientId = greeting.RecipientId },
+ tx);
+
+ //Now write the message we want to send to the Db in the same transaction.
+ posts.Add(await _postBox.DepositPostAsync(
+ new GreetingMade(greeting.Greet()),
+ _transactionProvider,
+ cancellationToken: cancellationToken));
+
+ //commit both new greeting and outgoing message
+ await _transactionProvider.CommitAsync(cancellationToken);
+ }
}
catch (Exception e)
- {
+ {
_logger.LogError(e, "Exception thrown handling Add Greeting request");
//it went wrong, rollback the entity change and the downstream message
- await tx.RollbackAsync(cancellationToken);
+ await _transactionProvider.RollbackAsync(cancellationToken);
return await base.HandleAsync(addGreeting, cancellationToken);
}
+ finally
+ {
+ _transactionProvider.Close();
+ }
//Send this message via a transport. We need the ids to send just the messages here, not all outstanding ones.
//Alternatively, you can let the Sweeper do this, but at the cost of increased latency
diff --git a/samples/WebAPI_Dapper/GreetingsPorts/Handlers/AddPersonHandlerAsync.cs b/samples/WebAPI_Dapper/GreetingsPorts/Handlers/AddPersonHandlerAsync.cs
index 21baf781e5..f5e0d276d9 100644
--- a/samples/WebAPI_Dapper/GreetingsPorts/Handlers/AddPersonHandlerAsync.cs
+++ b/samples/WebAPI_Dapper/GreetingsPorts/Handlers/AddPersonHandlerAsync.cs
@@ -1,10 +1,8 @@
using System.Threading;
using System.Threading.Tasks;
-using DapperExtensions;
-using GreetingsEntities;
+using Dapper;
using GreetingsPorts.Requests;
using Paramore.Brighter;
-using Paramore.Brighter.Dapper;
using Paramore.Brighter.Logging.Attributes;
using Paramore.Brighter.Policies.Attributes;
@@ -12,19 +10,19 @@ namespace GreetingsPorts.Handlers
{
public class AddPersonHandlerAsync : RequestHandlerAsync
{
- private readonly IUnitOfWork _uow;
+ private readonly IAmARelationalDbConnectionProvider _relationalDbConnectionProvider;
- public AddPersonHandlerAsync(IUnitOfWork uow)
+ public AddPersonHandlerAsync(IAmARelationalDbConnectionProvider relationalDbConnectionProvider)
{
- _uow = uow;
+ _relationalDbConnectionProvider = relationalDbConnectionProvider;
}
[RequestLoggingAsync(0, HandlerTiming.Before)]
[UsePolicyAsync(step:1, policy: Policies.Retry.EXPONENTIAL_RETRYPOLICYASYNC)]
public override async Task HandleAsync(AddPerson addPerson, CancellationToken cancellationToken = default)
{
- await _uow.Database.InsertAsync(new Person(addPerson.Name));
-
+ await using var connection = await _relationalDbConnectionProvider.GetConnectionAsync(cancellationToken);
+ await connection.ExecuteAsync("insert into Person (Name) values (@Name)", new {Name = addPerson.Name});
return await base.HandleAsync(addPerson, cancellationToken);
}
}
diff --git a/samples/WebAPI_Dapper/GreetingsPorts/Handlers/DeletePersonHandlerAsync.cs b/samples/WebAPI_Dapper/GreetingsPorts/Handlers/DeletePersonHandlerAsync.cs
index f3b300dcd7..fe2731a534 100644
--- a/samples/WebAPI_Dapper/GreetingsPorts/Handlers/DeletePersonHandlerAsync.cs
+++ b/samples/WebAPI_Dapper/GreetingsPorts/Handlers/DeletePersonHandlerAsync.cs
@@ -2,12 +2,11 @@
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
-using DapperExtensions;
-using DapperExtensions.Predicate;
+using Dapper;
using GreetingsEntities;
using GreetingsPorts.Requests;
+using Microsoft.Extensions.Logging;
using Paramore.Brighter;
-using Paramore.Brighter.Dapper;
using Paramore.Brighter.Logging.Attributes;
using Paramore.Brighter.Policies.Attributes;
@@ -15,36 +14,57 @@ namespace GreetingsPorts.Handlers
{
public class DeletePersonHandlerAsync : RequestHandlerAsync
{
- private readonly IUnitOfWork _uow;
+ private readonly IAmARelationalDbConnectionProvider _relationalDbConnectionProvider;
+ private readonly ILogger _logger;
- public DeletePersonHandlerAsync(IUnitOfWork uow)
+ public DeletePersonHandlerAsync(IAmARelationalDbConnectionProvider relationalDbConnectionProvider, ILogger logger)
{
- _uow = uow;
+ _relationalDbConnectionProvider = relationalDbConnectionProvider;
+ _logger = logger;
}
[RequestLoggingAsync(0, HandlerTiming.Before)]
[UsePolicyAsync(step:1, policy: Policies.Retry.EXPONENTIAL_RETRYPOLICYASYNC)]
- public async override Task HandleAsync(DeletePerson deletePerson, CancellationToken cancellationToken = default)
+ public override async Task HandleAsync(DeletePerson deletePerson, CancellationToken cancellationToken = default)
{
- var tx = await _uow.BeginOrGetTransactionAsync(cancellationToken);
+ var connection = await _relationalDbConnectionProvider.GetConnectionAsync(cancellationToken);
+ var tx = await connection.BeginTransactionAsync(cancellationToken);
try
{
+ var people = await connection.QueryAsync(
+ "select * from Person where name = @name",
+ new {name = deletePerson.Name},
+ tx
+ );
+ var person = people.SingleOrDefault();
- var searchbyName = Predicates.Field(p => p.Name, Operator.Eq, deletePerson.Name);
- var people = await _uow.Database.GetListAsync(searchbyName, transaction: tx);
- var person = people.Single();
+ if (person != null)
+ {
+ await connection.ExecuteAsync(
+ "delete from Greeting where Recipient_Id = @PersonId",
+ new { PersonId = person.Id },
+ tx);
+
+ await connection.ExecuteAsync("delete from Person where Id = @Id",
+ new {Id = person.Id},
+ tx);
- var deleteById = Predicates.Field(g => g.RecipientId, Operator.Eq, person.Id);
- await _uow.Database.DeleteAsync(deleteById, tx);
-
- await tx.CommitAsync(cancellationToken);
+ await tx.CommitAsync(cancellationToken);
+ }
}
- catch (Exception)
+ catch (Exception e)
{
+ _logger.LogError(e, "Exception thrown handling Add Greeting request");
//it went wrong, rollback the entity change and the downstream message
await tx.RollbackAsync(cancellationToken);
return await base.HandleAsync(deletePerson, cancellationToken);
}
+ finally
+ {
+ await connection.DisposeAsync();
+ await tx.DisposeAsync();
+
+ }
return await base.HandleAsync(deletePerson, cancellationToken);
}
diff --git a/samples/WebAPI_Dapper/GreetingsPorts/Handlers/FIndGreetingsForPersonHandlerAsync.cs b/samples/WebAPI_Dapper/GreetingsPorts/Handlers/FIndGreetingsForPersonHandlerAsync.cs
index d908df5c2c..45752cff49 100644
--- a/samples/WebAPI_Dapper/GreetingsPorts/Handlers/FIndGreetingsForPersonHandlerAsync.cs
+++ b/samples/WebAPI_Dapper/GreetingsPorts/Handlers/FIndGreetingsForPersonHandlerAsync.cs
@@ -1,4 +1,5 @@
-using System.Linq;
+using System;
+using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Dapper;
@@ -6,7 +7,7 @@
using GreetingsPorts.Policies;
using GreetingsPorts.Requests;
using GreetingsPorts.Responses;
-using Paramore.Brighter.Dapper;
+using Paramore.Brighter;
using Paramore.Darker;
using Paramore.Darker.Policies;
using Paramore.Darker.QueryLogging;
@@ -15,11 +16,11 @@ namespace GreetingsPorts.Handlers
{
public class FIndGreetingsForPersonHandlerAsync : QueryHandlerAsync
{
- private readonly IUnitOfWork _uow;
+ private readonly IAmARelationalDbConnectionProvider _relationalDbConnectionProvider;
- public FIndGreetingsForPersonHandlerAsync(IUnitOfWork uow)
+ public FIndGreetingsForPersonHandlerAsync(IAmARelationalDbConnectionProvider relationalDbConnectionProvider)
{
- _uow = uow;
+ _relationalDbConnectionProvider = relationalDbConnectionProvider;
}
[QueryLogging(0)]
@@ -33,11 +34,17 @@ public FIndGreetingsForPersonHandlerAsync(IUnitOfWork uow)
var sql = @"select p.Id, p.Name, g.Id, g.Message
from Person p
inner join Greeting g on g.Recipient_Id = p.Id";
- var people = await _uow.Database.QueryAsync(sql, (person, greeting) =>
+ await using var connection = await _relationalDbConnectionProvider.GetConnectionAsync(cancellationToken);
+ var people = await connection.QueryAsync(sql, (person, greeting) =>
{
- person.Greetings.Add(greeting);
- return person;
+ person.Greetings.Add(greeting);
+ return person;
}, splitOn: "Id");
+
+ if (!people.Any())
+ {
+ return new FindPersonsGreetings(){Name = query.Name, Greetings = Array.Empty()};
+ }
var peopleGreetings = people.GroupBy(p => p.Id).Select(grp =>
{
@@ -50,10 +57,8 @@ from Person p
return new FindPersonsGreetings
{
- Name = person.Name,
- Greetings = person.Greetings.Select(g => new Salutation(g.Greet()))
+ Name = person.Name, Greetings = person.Greetings.Select(g => new Salutation(g.Greet()))
};
-
}
}
diff --git a/samples/WebAPI_Dapper/GreetingsPorts/Handlers/FindPersonByNameHandlerAsync.cs b/samples/WebAPI_Dapper/GreetingsPorts/Handlers/FindPersonByNameHandlerAsync.cs
index 1ab5898541..7ada5290dc 100644
--- a/samples/WebAPI_Dapper/GreetingsPorts/Handlers/FindPersonByNameHandlerAsync.cs
+++ b/samples/WebAPI_Dapper/GreetingsPorts/Handlers/FindPersonByNameHandlerAsync.cs
@@ -1,13 +1,12 @@
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
-using DapperExtensions;
-using DapperExtensions.Predicate;
+using Dapper;
using GreetingsEntities;
using GreetingsPorts.Policies;
using GreetingsPorts.Requests;
using GreetingsPorts.Responses;
-using Paramore.Brighter.Dapper;
+using Paramore.Brighter;
using Paramore.Darker;
using Paramore.Darker.Policies;
using Paramore.Darker.QueryLogging;
@@ -16,20 +15,20 @@ namespace GreetingsPorts.Handlers
{
public class FindPersonByNameHandlerAsync : QueryHandlerAsync
{
- private readonly IUnitOfWork _uow;
+ private readonly IAmARelationalDbConnectionProvider _relationalDbConnectionProvider;
- public FindPersonByNameHandlerAsync(IUnitOfWork uow)
+ public FindPersonByNameHandlerAsync(IAmARelationalDbConnectionProvider relationalDbConnectionProvider)
{
- _uow = uow;
+ _relationalDbConnectionProvider = relationalDbConnectionProvider;
}
[QueryLogging(0)]
[RetryableQuery(1, Retry.EXPONENTIAL_RETRYPOLICYASYNC)]
public override async Task ExecuteAsync(FindPersonByName query, CancellationToken cancellationToken = new CancellationToken())
{
- var searchbyName = Predicates.Field(p => p.Name, Operator.Eq, query.Name);
- var people = await _uow.Database.GetListAsync(searchbyName);
- var person = people.Single();
+ await using var connection = await _relationalDbConnectionProvider .GetConnectionAsync(cancellationToken);
+ var people = await connection.QueryAsync("select * from Person where name = @name", new {name = query.Name});
+ var person = people.SingleOrDefault();
return new FindPersonResult(person);
}
diff --git a/samples/WebAPI_Dapper/GreetingsPorts/Responses/FindPersonResult.cs b/samples/WebAPI_Dapper/GreetingsPorts/Responses/FindPersonResult.cs
index ea50242c8c..69529a9803 100644
--- a/samples/WebAPI_Dapper/GreetingsPorts/Responses/FindPersonResult.cs
+++ b/samples/WebAPI_Dapper/GreetingsPorts/Responses/FindPersonResult.cs
@@ -4,10 +4,10 @@ namespace GreetingsPorts.Responses
{
public class FindPersonResult
{
- public string Name { get; private set; }
+ public Person Person { get; private set; }
public FindPersonResult(Person person)
{
- Name = person.Name;
+ Person = person;
}
}
diff --git a/samples/WebAPI_Dapper/GreetingsWeb/Controllers/PeopleController.cs b/samples/WebAPI_Dapper/GreetingsWeb/Controllers/PeopleController.cs
index 0ce06da254..7feab500f7 100644
--- a/samples/WebAPI_Dapper/GreetingsWeb/Controllers/PeopleController.cs
+++ b/samples/WebAPI_Dapper/GreetingsWeb/Controllers/PeopleController.cs
@@ -29,7 +29,7 @@ public async Task> Get(string name)
{
var foundPerson = await _queryProcessor.ExecuteAsync(new FindPersonByName(name));
- if (foundPerson == null) return new NotFoundResult();
+ if (foundPerson.Person == null) return new NotFoundResult();
return Ok(foundPerson);
}
diff --git a/samples/WebAPI_Dapper/GreetingsWeb/Database/OutboxExtensions.cs b/samples/WebAPI_Dapper/GreetingsWeb/Database/OutboxExtensions.cs
index 80f172a2be..ddaae874fe 100644
--- a/samples/WebAPI_Dapper/GreetingsWeb/Database/OutboxExtensions.cs
+++ b/samples/WebAPI_Dapper/GreetingsWeb/Database/OutboxExtensions.cs
@@ -1,63 +1,73 @@
using System;
+using GreetingsEntities;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
+using Npgsql;
+using Npgsql.NameTranslation;
+using Paramore.Brighter;
using Paramore.Brighter.Extensions.DependencyInjection;
using Paramore.Brighter.Extensions.Hosting;
+using Paramore.Brighter.MsSql;
using Paramore.Brighter.MySql;
-using Paramore.Brighter.MySql.Dapper;
+using Paramore.Brighter.Outbox.MsSql;
using Paramore.Brighter.Outbox.MySql;
+using Paramore.Brighter.Outbox.PostgreSql;
using Paramore.Brighter.Outbox.Sqlite;
+using Paramore.Brighter.PostgreSql;
using Paramore.Brighter.Sqlite;
-using Paramore.Brighter.Sqlite.Dapper;
-using UnitOfWork = Paramore.Brighter.MySql.Dapper.UnitOfWork;
-namespace GreetingsWeb.Database;
-
-public static class OutboxExtensions
+namespace GreetingsWeb.Database
{
- public static IBrighterBuilder AddOutbox(this IBrighterBuilder brighterBuilder, IWebHostEnvironment env, DatabaseType databaseType,
- string dbConnectionString, string outBoxTableName)
+
+ public class OutboxExtensions
{
- if (env.IsDevelopment())
- {
- AddSqliteOutBox(brighterBuilder, dbConnectionString, outBoxTableName);
- }
- else
+ public static (IAmAnOutbox, Type, Type) MakeOutbox(
+ IWebHostEnvironment env,
+ DatabaseType databaseType,
+ RelationalDatabaseConfiguration configuration,
+ IServiceCollection services)
{
- switch (databaseType)
+ (IAmAnOutbox, Type, Type) outbox;
+ if (env.IsDevelopment())
+ {
+ outbox = MakeSqliteOutBox(configuration);
+ }
+ else
{
- case DatabaseType.MySql:
- AddMySqlOutbox(brighterBuilder, dbConnectionString, outBoxTableName);
- break;
- default:
- throw new InvalidOperationException("Unknown Db type for Outbox configuration");
+ outbox = databaseType switch
+ {
+ DatabaseType.MySql => MakeMySqlOutbox(configuration),
+ DatabaseType.MsSql => MakeMsSqlOutbox(configuration),
+ DatabaseType.Postgres => MakePostgresSqlOutbox(configuration, services),
+ DatabaseType.Sqlite => MakeSqliteOutBox(configuration),
+ _ => throw new InvalidOperationException("Unknown Db type for Outbox configuration")
+ };
}
+
+ return outbox;
}
- return brighterBuilder;
- }
- private static void AddMySqlOutbox(IBrighterBuilder brighterBuilder, string dbConnectionString, string outBoxTableName)
- {
- brighterBuilder.UseMySqlOutbox(
- new MySqlConfiguration(dbConnectionString, outBoxTableName),
- typeof(MySqlConnectionProvider),
- ServiceLifetime.Singleton)
- .UseMySqTransactionConnectionProvider(typeof(Paramore.Brighter.MySql.Dapper.MySqlDapperConnectionProvider), ServiceLifetime.Scoped)
- .UseOutboxSweeper();
- }
+ private static (IAmAnOutbox, Type, Type) MakePostgresSqlOutbox(
+ RelationalDatabaseConfiguration configuration,
+ IServiceCollection services)
+ {
+ return (new PostgreSqlOutbox(configuration), typeof(PostgreSqlConnectionProvider), typeof(PostgreSqlUnitOfWork));
+ }
- private static void AddSqliteOutBox(IBrighterBuilder brighterBuilder, string dbConnectionString, string outBoxTableName)
- {
- brighterBuilder.UseSqliteOutbox(
- new SqliteConfiguration(dbConnectionString, outBoxTableName),
- typeof(SqliteConnectionProvider),
- ServiceLifetime.Singleton)
- .UseSqliteTransactionConnectionProvider(typeof(Paramore.Brighter.Sqlite.Dapper.SqliteDapperConnectionProvider), ServiceLifetime.Scoped)
- .UseOutboxSweeper(options =>
- {
- options.TimerInterval = 5;
- options.MinimumMessageAge = 5000;
- });
+ private static (IAmAnOutbox, Type, Type) MakeMsSqlOutbox(RelationalDatabaseConfiguration configuration)
+ {
+ return new(new MsSqlOutbox(configuration), typeof(MsSqlConnectionProvider), typeof(MsSqlUnitOfWork));
+ }
+
+ private static (IAmAnOutbox, Type, Type) MakeMySqlOutbox(RelationalDatabaseConfiguration configuration)
+ {
+ return (new MySqlOutbox(configuration), typeof (MySqlConnectionProvider), typeof(MySqlUnitOfWork));
+ }
+
+ private static (IAmAnOutbox, Type, Type) MakeSqliteOutBox(RelationalDatabaseConfiguration configuration)
+ {
+ return (new SqliteOutbox(configuration), typeof(SqliteConnectionProvider), typeof(SqliteUnitOfWork));
+ }
}
}
diff --git a/samples/WebAPI_Dapper/GreetingsWeb/Database/SchemaCreation.cs b/samples/WebAPI_Dapper/GreetingsWeb/Database/SchemaCreation.cs
index d628c0bdb4..639a46424d 100644
--- a/samples/WebAPI_Dapper/GreetingsWeb/Database/SchemaCreation.cs
+++ b/samples/WebAPI_Dapper/GreetingsWeb/Database/SchemaCreation.cs
@@ -2,6 +2,7 @@
using System.Data;
using System.Data.Common;
using FluentMigrator.Runner;
+using GreetingsWeb.Messaging;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Data.SqlClient;
using Microsoft.Data.Sqlite;
@@ -10,7 +11,10 @@
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using MySqlConnector;
+using Npgsql;
+using Paramore.Brighter.Outbox.MsSql;
using Paramore.Brighter.Outbox.MySql;
+using Paramore.Brighter.Outbox.PostgreSql;
using Paramore.Brighter.Outbox.Sqlite;
using Polly;
@@ -27,16 +31,33 @@ public static IHost CheckDbIsUp(this IHost webHost)
var services = scope.ServiceProvider;
var env = services.GetService();
var config = services.GetService();
- string connectionString = DbServerConnectionString(config, env);
+ var (dbType, connectionString) = DbServerConnectionString(config, env);
- //We don't check in development as using Sqlite
+ //We don't check db availability in development as we always use Sqlite which is a file not a server
if (env.IsDevelopment()) return webHost;
- WaitToConnect(connectionString);
- CreateDatabaseIfNotExists(GetDbConnection(GetDatabaseType(config), connectionString));
+ WaitToConnect(dbType, connectionString);
+ CreateDatabaseIfNotExists(dbType, GetDbConnection(dbType, connectionString));
return webHost;
}
+
+ public static IHost CreateOutbox(this IHost webHost, bool hasBinaryPayload)
+ {
+ using var scope = webHost.Services.CreateScope();
+ var services = scope.ServiceProvider;
+ var env = services.GetService();
+ var config = services.GetService();
+
+ CreateOutbox(config, env, hasBinaryPayload);
+
+ return webHost;
+ }
+
+ public static bool HasBinaryMessagePayload(this IHost webHost)
+ {
+ return GetTransportType(Environment.GetEnvironmentVariable("BRIGHTER_TRANSPORT")) == MessagingTransport.Kafka;
+ }
public static IHost MigrateDatabase(this IHost webHost)
{
@@ -61,40 +82,60 @@ public static IHost MigrateDatabase(this IHost webHost)
return webHost;
}
- public static IHost CreateOutbox(this IHost webHost)
- {
- using (var scope = webHost.Services.CreateScope())
- {
- var services = scope.ServiceProvider;
- var env = services.GetService();
- var config = services.GetService();
- CreateOutbox(config, env);
- }
-
- return webHost;
- }
-
- private static void CreateDatabaseIfNotExists(DbConnection conn)
+ private static void CreateDatabaseIfNotExists(DatabaseType databaseType, DbConnection conn)
{
//The migration does not create the Db, so we need to create it sot that it will add it
conn.Open();
using var command = conn.CreateCommand();
- command.CommandText = "CREATE DATABASE IF NOT EXISTS Greetings";
- command.ExecuteScalar();
- }
+ command.CommandText = databaseType switch
+ {
+ DatabaseType.Sqlite => "CREATE DATABASE IF NOT EXISTS Greetings",
+ DatabaseType.MySql => "CREATE DATABASE IF NOT EXISTS Greetings",
+ DatabaseType.Postgres => "CREATE DATABASE Greetings",
+ DatabaseType.MsSql =>
+ "IF NOT EXISTS(SELECT * FROM sys.databases WHERE name = 'Greetings') CREATE DATABASE Greetings",
+ _ => throw new InvalidOperationException("Could not create instance of Greetings for unknown Db type")
+ };
+
+ try
+ {
+ command.ExecuteScalar();
+ }
+ catch (NpgsqlException pe)
+ {
+ //Ignore if the Db already exists - we can't test for this in the SQL for Postgres
+ if (!pe.Message.Contains("already exists"))
+ throw;
+ }
+ catch (System.Exception e)
+ {
+ Console.WriteLine($"Issue with creating Greetings tables, {e.Message}");
+ //Rethrow, if we can't create the Outbox, shut down
+ throw;
+ }
+ }
- private static void CreateOutbox(IConfiguration config, IWebHostEnvironment env)
+ private static void CreateOutbox(IConfiguration config, IWebHostEnvironment env, bool hasBinaryPayload)
{
try
{
var connectionString = DbConnectionString(config, env);
if (env.IsDevelopment())
- CreateOutboxDevelopment(connectionString);
+ CreateOutboxDevelopment(connectionString, hasBinaryPayload);
else
- CreateOutboxProduction(GetDatabaseType(config), connectionString);
+ CreateOutboxProduction(GetDatabaseType(config), connectionString, hasBinaryPayload);
+ }
+ catch (NpgsqlException pe)
+ {
+ //Ignore if the Db already exists - we can't test for this in the SQL for Postgres
+ if (!pe.Message.Contains("already exists"))
+ {
+ Console.WriteLine($"Issue with creating Outbox table, {pe.Message}");
+ throw;
+ }
}
catch (System.Exception e)
{
@@ -104,69 +145,111 @@ private static void CreateOutbox(IConfiguration config, IWebHostEnvironment env)
}
}
- private static void CreateOutboxDevelopment(string connectionString)
+ private static void CreateOutboxDevelopment(string connectionString, bool hasBinaryPayload)
{
- CreateOutboxSqlite(connectionString);
- }
-
- private static void CreateOutboxSqlite(string connectionString)
- {
- using var sqlConnection = new SqliteConnection(connectionString);
- sqlConnection.Open();
-
- using var exists = sqlConnection.CreateCommand();
- exists.CommandText = SqliteOutboxBuilder.GetExistsQuery(OUTBOX_TABLE_NAME);
- using var reader = exists.ExecuteReader(CommandBehavior.SingleRow);
-
- if (reader.HasRows) return;
-
- using var command = sqlConnection.CreateCommand();
- command.CommandText = SqliteOutboxBuilder.GetDDL(OUTBOX_TABLE_NAME);
- command.ExecuteScalar();
+ CreateOutboxSqlite(connectionString, hasBinaryPayload);
}
- private static void CreateOutboxProduction(DatabaseType databaseType, string connectionString)
- {
+ private static void CreateOutboxProduction(DatabaseType databaseType, string connectionString, bool hasBinaryPayload)
+ {
switch (databaseType)
{
case DatabaseType.MySql:
- CreateOutboxMySql(connectionString);
+ CreateOutboxMySql(connectionString, hasBinaryPayload);
+ break;
+ case DatabaseType.MsSql:
+ CreateOutboxMsSql(connectionString, hasBinaryPayload);
+ break;
+ case DatabaseType.Postgres:
+ CreateOutboxPostgres(connectionString, hasBinaryPayload);
+ break;
+ case DatabaseType.Sqlite:
+ CreateOutboxSqlite(connectionString, hasBinaryPayload);
break;
default:
throw new InvalidOperationException("Could not create instance of Outbox for unknown Db type");
}
}
- private static void CreateOutboxMySql(string connectionString)
+ private static void CreateOutboxMsSql(string connectionString, bool hasBinaryPayload)
+ {
+ using var sqlConnection = new SqlConnection(connectionString);
+ sqlConnection.Open();
+
+ using var existsQuery = sqlConnection.CreateCommand();
+ existsQuery.CommandText = SqlOutboxBuilder.GetExistsQuery(OUTBOX_TABLE_NAME);
+ var findOutbox = existsQuery.ExecuteScalar();
+ bool exists = findOutbox is > 0;
+
+ if (exists) return;
+
+ using var command = sqlConnection.CreateCommand();
+ command.CommandText = SqlOutboxBuilder.GetDDL(OUTBOX_TABLE_NAME, hasBinaryPayload);
+ command.ExecuteScalar();
+
+ }
+
+ private static void CreateOutboxMySql(string connectionString, bool hasBinaryPayload)
{
using var sqlConnection = new MySqlConnection(connectionString);
sqlConnection.Open();
using var existsQuery = sqlConnection.CreateCommand();
existsQuery.CommandText = MySqlOutboxBuilder.GetExistsQuery(OUTBOX_TABLE_NAME);
- bool exists = existsQuery.ExecuteScalar() != null;
+ var findOutbox = existsQuery.ExecuteScalar();
+ bool exists = findOutbox is long and > 0;
if (exists) return;
using var command = sqlConnection.CreateCommand();
- command.CommandText = MySqlOutboxBuilder.GetDDL(OUTBOX_TABLE_NAME);
+ command.CommandText = MySqlOutboxBuilder.GetDDL(OUTBOX_TABLE_NAME, hasBinaryPayload);
command.ExecuteScalar();
}
+
+ private static void CreateOutboxPostgres(string connectionString, bool hasBinaryPayload)
+ {
+ using var sqlConnection = new NpgsqlConnection(connectionString);
+ sqlConnection.Open();
+
+ using var existsQuery = sqlConnection.CreateCommand();
+ existsQuery.CommandText = PostgreSqlOutboxBulder.GetExistsQuery(OUTBOX_TABLE_NAME);
+ var findOutbox = existsQuery.ExecuteScalar();
+ bool exists = findOutbox is long and > 0;
+
+ if (exists) return;
+
+ using var command = sqlConnection.CreateCommand();
+ command.CommandText = PostgreSqlOutboxBulder.GetDDL(OUTBOX_TABLE_NAME, hasBinaryPayload);
+ command.ExecuteScalar();
+ }
- private static string DbConnectionString(IConfiguration config, IWebHostEnvironment env)
+ private static void CreateOutboxSqlite(string connectionString, bool hasBinaryPayload)
{
- //NOTE: Sqlite needs to use a shared cache to allow Db writes to the Outbox as well as entities
- return env.IsDevelopment() ? GetDevDbConnectionString() : GetProductionDbConnectionString(config, GetDatabaseType(config));
+ using var sqlConnection = new SqliteConnection(connectionString);
+ sqlConnection.Open();
+
+ using var exists = sqlConnection.CreateCommand();
+ exists.CommandText = SqliteOutboxBuilder.GetExistsQuery(OUTBOX_TABLE_NAME);
+ using var reader = exists.ExecuteReader(CommandBehavior.SingleRow);
+
+ if (reader.HasRows) return;
+
+ using var command = sqlConnection.CreateCommand();
+ command.CommandText = SqliteOutboxBuilder.GetDDL(OUTBOX_TABLE_NAME, hasBinaryPayload);
+ command.ExecuteScalar();
}
- private static string GetDevDbConnectionString()
+ private static string DbConnectionString(IConfiguration config, IWebHostEnvironment env)
{
- return "Filename=Greetings.db;Cache=Shared";
+ //NOTE: Sqlite needs to use a shared cache to allow Db writes to the Outbox as well as entities
+ return env.IsDevelopment() ? GetDevConnectionString() : GetProductionDbConnectionString(config, GetDatabaseType(config));
}
- private static string DbServerConnectionString(IConfiguration config, IWebHostEnvironment env)
+ private static (DatabaseType, string) DbServerConnectionString(IConfiguration config, IWebHostEnvironment env)
{
- return env.IsDevelopment() ? GetDevConnectionString() : GetProductionConnectionString(config, GetDatabaseType(config));
+ var databaseType = GetDatabaseType(config);
+ var connectionString = env.IsDevelopment() ? GetDevConnectionString() : GetProductionConnectionString(config, databaseType);
+ return (databaseType, connectionString);
}
private static string GetDevConnectionString()
@@ -179,6 +262,9 @@ private static DbConnection GetDbConnection(DatabaseType databaseType, string co
return databaseType switch
{
DatabaseType.MySql => new MySqlConnection(connectionString),
+ DatabaseType.MsSql => new SqlConnection(connectionString),
+ DatabaseType.Postgres => new NpgsqlConnection(connectionString),
+ DatabaseType.Sqlite => new SqliteConnection(connectionString),
_ => throw new InvalidOperationException("Could not determine the database type")
};
}
@@ -187,7 +273,10 @@ private static string GetProductionConnectionString(IConfiguration config, Datab
{
return databaseType switch
{
- DatabaseType.MySql => config.GetConnectionString("GreetingsMySql"),
+ DatabaseType.MySql => config.GetConnectionString("MySqlDb"),
+ DatabaseType.MsSql => config.GetConnectionString("MsSqlDb"),
+ DatabaseType.Postgres => config.GetConnectionString("PostgreSqlDb"),
+ DatabaseType.Sqlite => GetDevConnectionString(),
_ => throw new InvalidOperationException("Could not determine the database type")
};
}
@@ -197,6 +286,9 @@ private static string GetProductionDbConnectionString(IConfiguration config, Dat
return databaseType switch
{
DatabaseType.MySql => config.GetConnectionString("GreetingsMySql"),
+ DatabaseType.MsSql => config.GetConnectionString("GreetingsMsSql"),
+ DatabaseType.Postgres => config.GetConnectionString("GreetingsPostgreSql"),
+ DatabaseType.Sqlite => GetDevConnectionString(),
_ => throw new InvalidOperationException("Could not determine the database type")
};
}
@@ -213,9 +305,9 @@ private static DatabaseType GetDatabaseType(IConfiguration config)
};
}
- private static void WaitToConnect(string connectionString)
+ private static void WaitToConnect(DatabaseType dbType, string connectionString)
{
- var policy = Policy.Handle().WaitAndRetryForever(
+ var policy = Policy.Handle().WaitAndRetryForever(
retryAttempt => TimeSpan.FromSeconds(2),
(exception, timespan) =>
{
@@ -224,9 +316,33 @@ private static void WaitToConnect(string connectionString)
policy.Execute(() =>
{
- using var conn = new MySqlConnection(connectionString);
+ using var conn = GetConnection(dbType, connectionString);
conn.Open();
});
}
+
+ private static DbConnection GetConnection(DatabaseType databaseType, string connectionString)
+ {
+ return databaseType switch
+ {
+ DatabaseType.MySql => new MySqlConnection(connectionString),
+ DatabaseType.MsSql => new SqlConnection(connectionString),
+ DatabaseType.Postgres => new NpgsqlConnection(connectionString),
+ DatabaseType.Sqlite => new SqliteConnection(connectionString),
+ _ => throw new ArgumentOutOfRangeException(nameof(databaseType), databaseType, null)
+ };
+ }
+
+ private static MessagingTransport GetTransportType(string brighterTransport)
+ {
+ return brighterTransport switch
+ {
+ MessagingGlobals.RMQ => MessagingTransport.Rmq,
+ MessagingGlobals.KAFKA => MessagingTransport.Kafka,
+ _ => throw new ArgumentOutOfRangeException(nameof(MessagingGlobals.BRIGHTER_TRANSPORT),
+ "Messaging transport is not supported")
+ };
+ }
+
}
}
diff --git a/samples/WebAPI_Dapper/GreetingsWeb/GreetingsWeb.csproj b/samples/WebAPI_Dapper/GreetingsWeb/GreetingsWeb.csproj
index 3d30f65e5d..3bb1ef51f4 100644
--- a/samples/WebAPI_Dapper/GreetingsWeb/GreetingsWeb.csproj
+++ b/samples/WebAPI_Dapper/GreetingsWeb/GreetingsWeb.csproj
@@ -8,6 +8,10 @@
+
+
+
+
@@ -16,14 +20,14 @@
+
-
+
+
-
-
-
+
@@ -51,4 +55,10 @@
<_ContentIncludedByDefault Remove="out\GreetingsAdapters.runtimeconfig.json" />
+
+
+ ..\..\..\libs\Npgsql\net6.0\Npgsql.dll
+
+
+
diff --git a/samples/WebAPI_Dapper/GreetingsWeb/Messaging/MessagingTransport.cs b/samples/WebAPI_Dapper/GreetingsWeb/Messaging/MessagingTransport.cs
new file mode 100644
index 0000000000..c4811dcf1b
--- /dev/null
+++ b/samples/WebAPI_Dapper/GreetingsWeb/Messaging/MessagingTransport.cs
@@ -0,0 +1,21 @@
+namespace GreetingsWeb.Messaging;
+
+public static class MessagingGlobals
+{
+ //environment string key
+ public const string BRIGHTER_TRANSPORT = "BRIGHTER_TRANSPORT";
+
+ public const string RMQ = "RabbitMQ";
+ public const string KAFKA = "Kafka";
+}
+
+
+///
+/// Which messaging transport are you using?
+///
+public enum MessagingTransport
+{
+ Rmq,
+ Kafka
+}
+
diff --git a/samples/WebAPI_Dapper/GreetingsWeb/Program.cs b/samples/WebAPI_Dapper/GreetingsWeb/Program.cs
index 749522914a..8e6dfbe0b6 100644
--- a/samples/WebAPI_Dapper/GreetingsWeb/Program.cs
+++ b/samples/WebAPI_Dapper/GreetingsWeb/Program.cs
@@ -16,7 +16,7 @@ public static void Main(string[] args)
host.CheckDbIsUp();
host.MigrateDatabase();
- host.CreateOutbox();
+ host.CreateOutbox(host.HasBinaryMessagePayload());
host.Run();
}
diff --git a/samples/WebAPI_Dapper/GreetingsWeb/Properties/launchSettings.json b/samples/WebAPI_Dapper/GreetingsWeb/Properties/launchSettings.json
index 1186c52e1f..14641d72fe 100644
--- a/samples/WebAPI_Dapper/GreetingsWeb/Properties/launchSettings.json
+++ b/samples/WebAPI_Dapper/GreetingsWeb/Properties/launchSettings.json
@@ -9,7 +9,7 @@
}
},
"profiles": {
- "Development": {
+ "Development": {
"commandName": "Project",
"dotnetRunMessages": "true",
"launchBrowser": true,
@@ -17,10 +17,11 @@
"applicationUrl": "https://localhost:5001;http://localhost:5000",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development",
- "BRIGHTER_GREETINGS_DATABASE": "Sqlite"
+ "BRIGHTER_GREETINGS_DATABASE": "Sqlite",
+ "BRIGHTER_TRANSPORT": "Kafka"
}
},
- "Production": {
+ "ProductionMySql": {
"commandName": "Project",
"dotnetRunMessages": "true",
"launchBrowser": true,
@@ -28,7 +29,32 @@
"applicationUrl": "https://localhost:5001;http://localhost:5000",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Production",
- "BRIGHTER_GREETINGS_DATABASE": "MySQL"
+ "BRIGHTER_GREETINGS_DATABASE": "MySQL",
+ "BRIGHTER_TRANSPORT": "RabbitMQ"
+ }
+ },
+ "ProductionPostgres": {
+ "commandName": "Project",
+ "dotnetRunMessages": "true",
+ "launchBrowser": true,
+ "launchUrl": "swagger",
+ "applicationUrl": "https://localhost:5001;http://localhost:5000",
+ "environmentVariables": {
+ "ASPNETCORE_ENVIRONMENT": "Production",
+ "BRIGHTER_GREETINGS_DATABASE": "PostgresSQL",
+ "BRIGHTER_TRANSPORT": "RabbitMQ"
+ }
+ },
+ "ProductionMsSql": {
+ "commandName": "Project",
+ "dotnetRunMessages": "true",
+ "launchBrowser": true,
+ "launchUrl": "swagger",
+ "applicationUrl": "https://localhost:5001;http://localhost:5000",
+ "environmentVariables": {
+ "ASPNETCORE_ENVIRONMENT": "Production",
+ "BRIGHTER_GREETINGS_DATABASE": "MsSQL",
+ "BRIGHTER_TRANSPORT": "RabbitMQ"
}
}
}
diff --git a/samples/WebAPI_Dapper/GreetingsWeb/Startup.cs b/samples/WebAPI_Dapper/GreetingsWeb/Startup.cs
index 5a16c8a4a3..b6193994fe 100644
--- a/samples/WebAPI_Dapper/GreetingsWeb/Startup.cs
+++ b/samples/WebAPI_Dapper/GreetingsWeb/Startup.cs
@@ -1,13 +1,11 @@
using System;
-using DapperExtensions;
-using DapperExtensions.Sql;
+using Confluent.SchemaRegistry;
using FluentMigrator.Runner;
using Greetings_MySqlMigrations.Migrations;
-using Greetings_SqliteMigrations.Migrations;
-using GreetingsPorts.EntityMappers;
using GreetingsPorts.Handlers;
using GreetingsPorts.Policies;
using GreetingsWeb.Database;
+using GreetingsWeb.Messaging;
using Hellang.Middleware.ProblemDetails;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
@@ -15,9 +13,12 @@
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.OpenApi.Models;
+using OpenTelemetry.Metrics;
+using OpenTelemetry.Trace;
using Paramore.Brighter;
-using Paramore.Brighter.Dapper;
using Paramore.Brighter.Extensions.DependencyInjection;
+using Paramore.Brighter.Extensions.Hosting;
+using Paramore.Brighter.MessagingGateway.Kafka;
using Paramore.Brighter.MessagingGateway.RMQ;
using Paramore.Darker.AspNetCore;
using Paramore.Darker.Policies;
@@ -27,16 +28,24 @@ namespace GreetingsWeb
{
public class Startup
{
- private const string _outBoxTableName = "Outbox";
- private IWebHostEnvironment _env;
+
+ private readonly IConfiguration _configuration;
+ private readonly IWebHostEnvironment _env;
public Startup(IConfiguration configuration, IWebHostEnvironment env)
{
- Configuration = configuration;
+ _configuration = configuration;
_env = env;
}
- public IConfiguration Configuration { get; }
+ private void AddSchemaRegistryMaybe(IServiceCollection services, MessagingTransport messagingTransport)
+ {
+ if (messagingTransport != MessagingTransport.Kafka) return;
+
+ var schemaRegistryConfig = new SchemaRegistryConfig { Url = "http://localhost:8081" };
+ var cachedSchemaRegistryClient = new CachedSchemaRegistryClient(schemaRegistryConfig);
+ services.AddSingleton(cachedSchemaRegistryClient);
+ }
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
@@ -71,29 +80,26 @@ public void ConfigureServices(IServiceCollection services)
c.SwaggerDoc("v1", new OpenApiInfo { Title = "GreetingsAPI", Version = "v1" });
});
+ services.AddOpenTelemetry()
+ .WithTracing(builder => builder
+ .AddAspNetCoreInstrumentation()
+ .AddConsoleExporter())
+ .WithMetrics(builder => builder
+ .AddAspNetCoreInstrumentation()
+ .AddConsoleExporter());
+
ConfigureMigration(services);
- ConfigureDapper(services);
ConfigureBrighter(services);
ConfigureDarker(services);
}
private void ConfigureMigration(IServiceCollection services)
{
+ //dev is always Sqlite
if (_env.IsDevelopment())
- {
- services
- .AddFluentMigratorCore()
- .ConfigureRunner(c =>
- {
- c.AddSQLite()
- .WithGlobalConnectionString(DbConnectionString())
- .ScanIn(typeof(SqlliteInitialCreate).Assembly).For.Migrations();
- });
- }
+ ConfigureSqlite(services);
else
- {
ConfigureProductionDatabase(GetDatabaseType(), services);
- }
}
private void ConfigureProductionDatabase(DatabaseType databaseType, IServiceCollection services)
@@ -103,64 +109,80 @@ private void ConfigureProductionDatabase(DatabaseType databaseType, IServiceColl
case DatabaseType.MySql:
ConfigureMySql(services);
break;
+ case DatabaseType.MsSql:
+ ConfigureMsSql(services);
+ break;
+ case DatabaseType.Postgres:
+ ConfigurePostgreSql(services);
+ break;
+ case DatabaseType.Sqlite:
+ ConfigureSqlite(services);
+ break;
default:
throw new ArgumentOutOfRangeException(nameof(databaseType), "Database type is not supported");
}
}
- private void ConfigureMySql(IServiceCollection services)
+ private void ConfigureMsSql(IServiceCollection services)
{
services
.AddFluentMigratorCore()
- .ConfigureRunner(c => c.AddMySql5()
+ .ConfigureRunner(c => c.AddSqlServer()
.WithGlobalConnectionString(DbConnectionString())
- .ScanIn(typeof(MySqlInitialCreate).Assembly).For.Migrations()
- );
- }
-
- private void ConfigureDapper(IServiceCollection services)
- {
- services.AddSingleton(new DbConnectionStringProvider(DbConnectionString()));
-
- ConfigureDapperByHost(GetDatabaseType(), services);
-
- DapperExtensions.DapperExtensions.SetMappingAssemblies(new[] { typeof(PersonMapper).Assembly });
- DapperAsyncExtensions.SetMappingAssemblies(new[] { typeof(PersonMapper).Assembly });
+ .ScanIn(typeof(SqlInitialCreate).Assembly).For.Migrations()
+ )
+ .AddSingleton(new MigrationConfiguration(){DbType = DatabaseType.MsSql.ToString()});
}
- private static void ConfigureDapperByHost(DatabaseType databaseType, IServiceCollection services)
+ private void ConfigureMySql(IServiceCollection services)
{
- switch (databaseType)
- {
- case DatabaseType.Sqlite:
- ConfigureDapperSqlite(services);
- break;
- case DatabaseType.MySql:
- ConfigureDapperMySql(services);
- break;
- default:
- throw new ArgumentOutOfRangeException(nameof(databaseType), "Database type is not supported");
- }
+ services
+ .AddFluentMigratorCore()
+ .ConfigureRunner(c => c.AddMySql5()
+ .WithGlobalConnectionString(DbConnectionString())
+ .ScanIn(typeof(SqlInitialCreate).Assembly).For.Migrations()
+ )
+ .AddSingleton(new MigrationConfiguration(){DbType = DatabaseType.MySql.ToString()});
}
- private static void ConfigureDapperSqlite(IServiceCollection services)
+ private void ConfigurePostgreSql(IServiceCollection services)
{
- DapperExtensions.DapperExtensions.SqlDialect = new SqliteDialect();
- DapperAsyncExtensions.SqlDialect = new SqliteDialect();
- services.AddScoped();
+ services
+ .AddFluentMigratorCore()
+ .ConfigureRunner(c => c.AddPostgres()
+ .ConfigureGlobalProcessorOptions(opt => opt.ProviderSwitches = "Force Quote=false")
+ .WithGlobalConnectionString(DbConnectionString())
+ .ScanIn(typeof(SqlInitialCreate).Assembly).For.Migrations()
+ )
+ .AddSingleton(new MigrationConfiguration(){DbType = DatabaseType.Postgres.ToString()});
}
- private static void ConfigureDapperMySql(IServiceCollection services)
+ private void ConfigureSqlite(IServiceCollection services)
{
- DapperExtensions.DapperExtensions.SqlDialect = new MySqlDialect();
- DapperAsyncExtensions.SqlDialect = new MySqlDialect();
- services.AddScoped();
+ services
+ .AddFluentMigratorCore()
+ .ConfigureRunner(c => c.AddSQLite()
+ .WithGlobalConnectionString(DbConnectionString())
+ .ScanIn(typeof(SqlInitialCreate).Assembly).For.Migrations()
+ )
+ .AddSingleton(new MigrationConfiguration(){DbType = DatabaseType.Sqlite.ToString()});
}
-
+
private void ConfigureBrighter(IServiceCollection services)
{
- services.AddSingleton(new DbConnectionStringProvider(DbConnectionString()));
+ var messagingTransport = GetTransportType();
+
+ AddSchemaRegistryMaybe(services, messagingTransport);
+
+ var outboxConfiguration = new RelationalDatabaseConfiguration(
+ DbConnectionString(),
+ binaryMessagePayload: messagingTransport == MessagingTransport.Kafka
+ );
+ services.AddSingleton(outboxConfiguration);
+ (IAmAnOutbox outbox, Type connectionProvider, Type transactionProvider) makeOutbox =
+ OutboxExtensions.MakeOutbox(_env, GetDatabaseType(), outboxConfiguration, services);
+
services.AddBrighter(options =>
{
//we want to use scoped, so make sure everything understands that which needs to
@@ -169,30 +191,17 @@ private void ConfigureBrighter(IServiceCollection services)
options.MapperLifetime = ServiceLifetime.Singleton;
options.PolicyRegistry = new GreetingsPolicy();
})
- .UseExternalBus(new RmqProducerRegistryFactory(
- new RmqMessagingGatewayConnection
- {
- AmpqUri = new AmqpUriSpecification(new Uri("amqp://guest:guest@localhost:5672")),
- Exchange = new Exchange("paramore.brighter.exchange"),
- },
- new RmqPublication[]
- {
- new RmqPublication
- {
- Topic = new RoutingKey("GreetingMade"),
- MaxOutStandingMessages = 5,
- MaxOutStandingCheckIntervalMilliSeconds = 500,
- WaitForConfirmsTimeOutInMilliseconds = 1000,
- MakeChannels = OnMissingChannel.Create
- }
- }
- ).Create()
- )
- //NOTE: The extension method AddOutbox is defined locally to the sample, to allow us to switch between outbox
- //types easily. You may just choose to call the methods directly if you do not need to support multiple
- //db types (which we just need to allow you to see how to configure your outbox type).
- //It's also an example of how you can extend the DSL here easily if you have this kind of variability
- .AddOutbox(_env, GetDatabaseType(), DbConnectionString(), _outBoxTableName)
+ .UseExternalBus((configure) =>
+ {
+ configure.ProducerRegistry = ConfigureProducerRegistry(messagingTransport);
+ configure.Outbox = makeOutbox.outbox;
+ configure.TransactionProvider = makeOutbox.transactionProvider;
+ configure.ConnectionProvider = makeOutbox.connectionProvider;
+ })
+ .UseOutboxSweeper(options => {
+ options.TimerInterval = 5;
+ options.MinimumMessageAge = 5000;
+ })
.AutoFromAssemblies(typeof(AddPersonHandlerAsync).Assembly);
}
@@ -208,6 +217,16 @@ private void ConfigureDarker(IServiceCollection services)
.AddPolicies(new GreetingsPolicy());
}
+ private static IAmAProducerRegistry ConfigureProducerRegistry(MessagingTransport messagingTransport)
+ {
+ return messagingTransport switch
+ {
+ MessagingTransport.Rmq => GetRmqProducerRegistry(),
+ MessagingTransport.Kafka => GetKafkaProducerRegistry(),
+ _ => throw new ArgumentOutOfRangeException(nameof(messagingTransport), "Messaging transport is not supported")
+ };
+ }
+
private string DbConnectionString()
{
//NOTE: Sqlite needs to use a shared cache to allow Db writes to the Outbox as well as entities
@@ -216,13 +235,13 @@ private string DbConnectionString()
private DatabaseType GetDatabaseType()
{
- return Configuration[DatabaseGlobals.DATABASE_TYPE_ENV] switch
+ return _configuration[DatabaseGlobals.DATABASE_TYPE_ENV] switch
{
DatabaseGlobals.MYSQL => DatabaseType.MySql,
DatabaseGlobals.MSSQL => DatabaseType.MsSql,
DatabaseGlobals.POSTGRESSQL => DatabaseType.Postgres,
DatabaseGlobals.SQLITE => DatabaseType.Sqlite,
- _ => throw new InvalidOperationException("Could not determine the database type")
+ _ => throw new ArgumentOutOfRangeException(nameof(DatabaseGlobals.DATABASE_TYPE_ENV), "Database type is not supported")
};
}
@@ -235,9 +254,69 @@ private string GetConnectionString(DatabaseType databaseType)
{
return databaseType switch
{
- DatabaseType.MySql => Configuration.GetConnectionString("GreetingsMySql"),
- _ => throw new InvalidOperationException("Could not determine the database type")
+ DatabaseType.MySql => _configuration.GetConnectionString("GreetingsMySql"),
+ DatabaseType.MsSql => _configuration.GetConnectionString("GreetingsMsSql"),
+ DatabaseType.Postgres => _configuration.GetConnectionString("GreetingsPostgreSql"),
+ DatabaseType.Sqlite => GetDevDbConnectionString(),
+ _ => throw new ArgumentOutOfRangeException(nameof(databaseType), "Database type is not supported")
};
}
+
+ private static IAmAProducerRegistry GetKafkaProducerRegistry()
+ {
+ var producerRegistry = new KafkaProducerRegistryFactory(
+ new KafkaMessagingGatewayConfiguration
+ {
+ Name = "paramore.brighter.greetingsender", BootStrapServers = new[] { "localhost:9092" }
+ },
+ new KafkaPublication[]
+ {
+ new KafkaPublication
+ {
+ Topic = new RoutingKey("GreetingMade"),
+ MessageSendMaxRetries = 3,
+ MessageTimeoutMs = 1000,
+ MaxInFlightRequestsPerConnection = 1,
+ MakeChannels = OnMissingChannel.Create
+ }
+ })
+ .Create();
+
+ return producerRegistry;
+ }
+
+ private static IAmAProducerRegistry GetRmqProducerRegistry()
+ {
+ var producerRegistry = new RmqProducerRegistryFactory(
+ new RmqMessagingGatewayConnection
+ {
+ AmpqUri = new AmqpUriSpecification(new Uri("amqp://guest:guest@localhost:5672")),
+ Exchange = new Exchange("paramore.brighter.exchange"),
+ },
+ new RmqPublication[]
+ {
+ new RmqPublication
+ {
+ Topic = new RoutingKey("GreetingMade"),
+ MaxOutStandingMessages = 5,
+ MaxOutStandingCheckIntervalMilliSeconds = 500,
+ WaitForConfirmsTimeOutInMilliseconds = 1000,
+ MakeChannels = OnMissingChannel.Create
+ }
+ }
+ ).Create();
+ return producerRegistry;
+ }
+
+ private MessagingTransport GetTransportType()
+ {
+ return _configuration[MessagingGlobals.BRIGHTER_TRANSPORT] switch
+ {
+ MessagingGlobals.RMQ => MessagingTransport.Rmq,
+ MessagingGlobals.KAFKA => MessagingTransport.Kafka,
+ _ => throw new ArgumentOutOfRangeException(nameof(MessagingGlobals.BRIGHTER_TRANSPORT),
+ "Messaging transport is not supported")
+ };
+ }
}
}
diff --git a/samples/WebAPI_Dapper/GreetingsWeb/appsettings.Production.json b/samples/WebAPI_Dapper/GreetingsWeb/appsettings.Production.json
index c02f13f346..43ad056184 100644
--- a/samples/WebAPI_Dapper/GreetingsWeb/appsettings.Production.json
+++ b/samples/WebAPI_Dapper/GreetingsWeb/appsettings.Production.json
@@ -7,7 +7,11 @@
}
},
"ConnectionStrings": {
- "GreetingsMySql": "server=localhost; port=3306; uid=root; pwd=root; database=Greetings",
- "GreetingsMySqlDb": "server=localhost; port=3306; uid=root; pwd=root"
+ "GreetingsMySql": "server=localhost; port=3306; uid=root; pwd=root; database=Greetings; Allow User Variables=True",
+ "MySqlDb": "server=localhost; port=3306; uid=root; pwd=root",
+ "GreetingsPostgreSql": "Server=localhost; Port=5432; Database=greetings; Username=postgres; Password=password",
+ "PostgreSqlDb": "Server=localhost; Port=5432; Username=postgres; Password=password",
+ "GreetingsMsSql": "Server=localhost,11433;User Id=sa;Password=Password123!;Database=Greetings;TrustServerCertificate=true;Encrypt=false",
+ "MsSqlDb": "Server=localhost,11433;User Id=sa;Password=Password123!;TrustServerCertificate=true;Encrypt=false"
}
}
\ No newline at end of file
diff --git a/samples/WebAPI_Dapper/Greetings_MySqlMigrations/Greetings_MySqlMigrations.csproj b/samples/WebAPI_Dapper/Greetings_Migrations/Greetings_Migrations.csproj
similarity index 83%
rename from samples/WebAPI_Dapper/Greetings_MySqlMigrations/Greetings_MySqlMigrations.csproj
rename to samples/WebAPI_Dapper/Greetings_Migrations/Greetings_Migrations.csproj
index be526d4af0..d8b786d3e4 100644
--- a/samples/WebAPI_Dapper/Greetings_MySqlMigrations/Greetings_MySqlMigrations.csproj
+++ b/samples/WebAPI_Dapper/Greetings_Migrations/Greetings_Migrations.csproj
@@ -4,6 +4,7 @@
net6.0
enable
disable
+ Greetings_MySqlMigrations
diff --git a/samples/WebAPI_Dapper/Greetings_MySqlMigrations/Migrations/20220527_InitialCreate.cs b/samples/WebAPI_Dapper/Greetings_Migrations/Migrations/20220527_InitialCreate.cs
similarity index 56%
rename from samples/WebAPI_Dapper/Greetings_MySqlMigrations/Migrations/20220527_InitialCreate.cs
rename to samples/WebAPI_Dapper/Greetings_Migrations/Migrations/20220527_InitialCreate.cs
index 9fa274705a..2957c97da3 100644
--- a/samples/WebAPI_Dapper/Greetings_MySqlMigrations/Migrations/20220527_InitialCreate.cs
+++ b/samples/WebAPI_Dapper/Greetings_Migrations/Migrations/20220527_InitialCreate.cs
@@ -3,16 +3,25 @@
namespace Greetings_MySqlMigrations.Migrations;
[Migration(1)]
-public class MySqlInitialCreate : Migration
+public class SqlInitialCreate : Migration
{
+ private readonly IAmAMigrationConfiguration _configuration;
+
+ public SqlInitialCreate(IAmAMigrationConfiguration configuration)
+ {
+ _configuration = configuration;
+ }
+
public override void Up()
{
- Create.Table("Person")
+ var timestampColumn = _configuration.DbType == "Postgres" ? "timeStamp" : "TimeStamp";
+
+ var person = Create.Table("Person")
.WithColumn("Id").AsInt32().NotNullable().PrimaryKey().Identity()
.WithColumn("Name").AsString().Unique()
- .WithColumn("TimeStamp").AsDateTime().Nullable().WithDefault(SystemMethods.CurrentDateTime);
-
- Create.Table("Greeting")
+ .WithColumn(timestampColumn).AsDateTime().Nullable().WithDefault(SystemMethods.CurrentDateTime);
+
+ var greeting = Create.Table("Greeting")
.WithColumn("Id").AsInt32().NotNullable().PrimaryKey().Identity()
.WithColumn("Message").AsString()
.WithColumn("Recipient_Id").AsInt32();
diff --git a/samples/WebAPI_Dapper/Greetings_Migrations/Migrations/MigrationConfiguration.cs b/samples/WebAPI_Dapper/Greetings_Migrations/Migrations/MigrationConfiguration.cs
new file mode 100644
index 0000000000..04c69cd421
--- /dev/null
+++ b/samples/WebAPI_Dapper/Greetings_Migrations/Migrations/MigrationConfiguration.cs
@@ -0,0 +1,11 @@
+namespace Greetings_MySqlMigrations.Migrations;
+
+public interface IAmAMigrationConfiguration
+{
+ string DbType { get; set; }
+}
+
+public class MigrationConfiguration : IAmAMigrationConfiguration
+{
+ public string DbType { get; set; }
+}
diff --git a/samples/WebAPI_Dapper/Greetings_SqliteMigrations/Greetings_SqliteMigrations.csproj b/samples/WebAPI_Dapper/Greetings_SqliteMigrations/Greetings_SqliteMigrations.csproj
deleted file mode 100644
index eb42023016..0000000000
--- a/samples/WebAPI_Dapper/Greetings_SqliteMigrations/Greetings_SqliteMigrations.csproj
+++ /dev/null
@@ -1,13 +0,0 @@
-
-
-
- net6.0
- enable
- disable
-
-
-
-
-
-
-
diff --git a/samples/WebAPI_Dapper/Greetings_SqliteMigrations/Migrations/202204221833_InitialCreate.cs b/samples/WebAPI_Dapper/Greetings_SqliteMigrations/Migrations/202204221833_InitialCreate.cs
deleted file mode 100644
index 1b1edf2cbf..0000000000
--- a/samples/WebAPI_Dapper/Greetings_SqliteMigrations/Migrations/202204221833_InitialCreate.cs
+++ /dev/null
@@ -1,30 +0,0 @@
-using FluentMigrator;
-
-namespace Greetings_SqliteMigrations.Migrations;
-
-[Migration(1)]
-public class SqlliteInitialCreate : Migration
-{
- public override void Up()
- {
- Create.Table("Person")
- .WithColumn("Id").AsInt32().NotNullable().PrimaryKey().Identity()
- .WithColumn("Name").AsString().Unique()
- .WithColumn("TimeStamp").AsBinary().WithDefault(SystemMethods.CurrentDateTime);
-
- Create.Table("Greeting")
- .WithColumn("Id").AsInt32().NotNullable().PrimaryKey().Identity()
- .WithColumn("Message").AsString()
- .WithColumn("Recipient_Id").AsInt32();
-
- Create.ForeignKey()
- .FromTable("Greeting").ForeignColumn("Recipient_Id")
- .ToTable("Person").PrimaryColumn("Id");
- }
-
- public override void Down()
- {
- Delete.Table("Greeting");
- Delete.Table("Person");
- }
-}
diff --git a/samples/WebAPI_Dapper/README.md b/samples/WebAPI_Dapper/README.md
index b78be28dd2..de34cf2c7d 100644
--- a/samples/WebAPI_Dapper/README.md
+++ b/samples/WebAPI_Dapper/README.md
@@ -1,124 +1,104 @@
-# Table of content
+# Table of contents
- [Web API and Dapper Example](#web-api-and-dapper-example)
* [Environments](#environments)
* [Architecture](#architecture)
- + [Outbox](#outbox)
+ [GreetingsAPI](#greetingsapi)
+ [SalutationAnalytics](#salutationanalytics)
- * [Build and Deploy](#build-and-deploy)
- + [Building](#building)
- + [Deploy](#deploy)
- + [Possible issues](#possible-issues)
- - [Sqlite Database Read-Only Errors](#sqlite-database-read-only-errors)
- - [Queue Creation and Dropped Messages](#queue-creation-and-dropped-messages)
- - [Connection issue with the RabbitMQ](#connection-issue-with-the-rabbitmq)
- - [Helpful documentation links](#helpful-documentation-links)
- * [Tests](#tests)
+ * [Acceptance Tests](#tests)
+ * [Possible issues](#possible-issues)
+ +[Sqlite Database Read-Only Errors](#sqlite-database-read-only-errors)
+ + [RabbitMQ Queue Creation and Dropped Messages](#queue-creation-and-dropped-messages)
+ + [Helpful documentation links](#helpful-documentation-links)
+
# Web API and Dapper Example
This sample shows a typical scenario when using WebAPI and Brighter/Darker. It demonstrates both using Brighter and Darker to implement the API endpoints, and using a work queue to handle asynchronous work that results from handling the API call.
## Environments
-*Development* - runs locally on your machine, uses Sqlite as a data store; uses RabbitMQ for messaging, can be launched individually from the docker compose file; it represents a typical setup for development.
+*Development*
+
+- Uses a local Sqlite instance for the data store.
+- We support Docker hosted messaging brokers, either RabbitMQ or Kafka.
+
+*Production*
+- We offer support for a range of common SQL stores (MySQL, PostgreSQL, SQL Server) using Docker.
+- We support Docker hosted messaging brokers, either RabbitMQ or Kafka.
+
+### Configuration
-*Production* - runs in Docker;uses RabbitMQ for messaging; it emulates a possible production environment. We offer support for a range of common SQL stores in this example. We determine which SQL store to use via an environment
-variable. The process is: (1) determine we are running in a non-development environment (2) lookup the type of database we want to support (3) initialise an enum to identify that.
+Configuration is via Environment variables. The following are supported:
-We provide launchSetting.json files for all of these, which allows you to run Production with the appropriate db; you should launch your SQL data store and RabbitMQ from the docker compose file.
+- BRIGHTER_GREETINGS_DATABASE => "Sqlite", "MySql", "Postgres", "MsSQL"
+- BRIGHTER_TRANSPORT => "RabbitMQ", "Kafka"
+
+We provide launchSetting.json files for all of these, which allows you to run Production with the appropriate db; you should launch your SQL data store and broker from the docker compose file.
In case you are using Command Line Interface for running the project, consider adding --launch-profile:
```sh
dotnet run --launch-profile XXXXXX -d
```
+
## Architecture
-### Outbox
-Brighter does have an [Outbox pattern support](https://paramore.readthedocs.io/en/latest/OutboxPattern.html). In case you are new to it, consider reading it before diving deeper.
+
### GreetingsAPI
We follow a _ports and adapters_ architectural style, dividing the app into the following modules:
* **GreetingsAdapters**: The adapters' module, handles the primary adapter of HTTP requests and responses to the app
-* **GreetingsPorts**: the ports' module, handles requests from the primary adapter (HTTP) to the domain, and requests to secondary adapters. In a fuller app, the handlers for the primary adapter would correspond to our use case boundaries. The secondary port of the EntityGateway handles access to the DB via EF Core. We choose to treat EF Core as a port, not an adapter itself, here, as it wraps our underlying adapters for Sqlite or MySql.
+* **GreetingsPorts**: the ports' module, handles requests from the primary adapter (HTTP) to the domain, and requests to secondary adapters.
+In a fuller app, the handlers for the primary adapter would correspond to our use case boundaries. The secondary port uses either an IAmARelationalDbConnectionProvider or an IAmATransactionConnectionProvider.
+Both of these are required for Brighter's Outbox. If you register the former with ServiceCollection, you can use it use it for your own queries; we use Dapper with that connection.
+The latter is used by the Outbox to ensure that the message is sent within the same transaction as your writes to the entity and you should use its transaction support for transactional messaging.
* **GreetingsEntities**: the domain model (or application in ports & adapters). In a fuller app, this would contain the logic that has a dependency on entity state.
We 'depend on inwards' i.e. **GreetingsAdapters -> GreetingsPorts -> GreetingsEntities**
-The assemblies migrations: **Greetings_MySqlMigrations** and **Greetings_SqliteMigrations** hold generated code to configure the Db. Consider this adapter layer code - the use of separate modules allows us to switch migration per environment.
-
-### SalutationAnalytics
-
-This listens for a GreetingMade message and stores it. It demonstrates listening to a queue. It also demonstrates the use of scopes provided by Brighter's ServiceActivator, which work with EFCore. These support writing to an Outbox when this component raises a message in turn.
-
-We don't listen to that message, and without any listeners the RabbitMQ will drop the message we send, as it has no queues to give it to. We don't listen because we would just be repeating what we have shown here. If you want to see the messages produced, use the RMQ Management Console (localhost:15672) to create a queue and then bind it to the paramore.binding.exchange with the routingkey of SalutationReceived.
-
-We also add an Inbox here. The Inbox can be used to de-duplicate messages. In messaging, the guarantee is 'at least once' if you use a technique such as an Outbox to ensure sending. This means we may receive a message twice. We either need, as in this case, to use an Inbox to de-duplicate, or we need to be idempotent such that receiving the message multiple times would result in the same outcome.
-
+The assemblies migrations: **Greetings_Migrations** hold code to configure the Db.
-## Build and Deploy
+GreetingsAPI uses an Outbox for Transactional Messaging - the write to the entity and the message store are within the same transaction and the message is posted from the message store.
-### Building
-
-Use the build.sh file to:
+### SalutationAnalytics
-- Build both GreetingsAdapters and SalutationAnalytics and publish it to the /out directory. The Dockerfile assumes the app will be published here.
-- Build the Docker image from the Dockerfile for each.
+* **SalutationAnalytics** The adapter subscribes to GreetingMade messages. It demonstrates listening to a queue. It also demonstrates the use of scopes provided by Brighter's ServiceActivator, which work with Dapper. These support writing to an Outbox when this component raises a message in turn.
+
+* **SalutationPorts** The ports' module, handles requests from the primary adapter to the domain, and requests to secondary adapters. It writes to the entity store and sends another message. We don't listen to that message. Note that without any listeners RabbitMQ will drop the message we send, as it has no queues to give it to.
+If you want to see the messages produced, use the RMQ Management Console (localhost:15672) or Kafka Console (localhost:9021). (You will need to create a subscribing queue in RabbitMQ)
-(Why not use a multi-stage Docker build? We can't do this as the projects here reference projects not NuGet packages for Brighter libraries and there are not in the Docker build context.)
+* **SalutationEntities** The domain model (or application in ports & adapters). In a fuller app, this would contain the logic that has a dependency on entity state.
-A common error is to change something, forget to run build.sh and use an old Docker image.
+We add an Inbox as well as the Outbox here. The Inbox can be used to de-duplicate messages. In messaging, the guarantee is 'at least once' if you use a technique such as an Outbox to ensure sending. This means we may receive a message twice. We either need, as in this case, to use an Inbox to de-duplicate, or we need to be idempotent such that receiving the message multiple times would result in the same outcome.
-### Deploy
+The assemblies migrations: **Salutations_Migrations** hold code to configure the Db.
-We provide a docker compose file to allow you to run a 'Production' environment or to startup RabbitMQ for production:
-```sh
-docker compose up -d rabbitmq # will just start rabbitmq
-```
+## Acceptance Tests
-```sh
-docker compose up -d mysql # will just start mysql
-```
+We provide a tests.http file (supported by both JetBrains Rider and VS Code with the REST Client plugin) to allow you to test operations on the API.
-and so on.
+## Possible issues
-### Possible issues
#### Sqlite Database Read-Only Errors
A Sqlite database will only have permissions for the process that created it. This can result in you receiving read-only errors between invocations of the sample. You either need to alter the permissions on your Db, or delete it between runs.
Maintainers, please don't check the Sqlite files into source control.
-#### Queue Creation and Dropped Messages
+#### RabbitMQ Queue Creation and Dropped Messages
-Queues are created by consumers. This is because publishers don't know who consumes them, and thus don't create their queues. This means that if you run a producer, such as GreetingsWeb, and use tests.http to push in greetings, although a message will be published to RabbitMQ, it won't have a queue to be delivered to and will be dropped, unless you have first run the SalutationAnalytics worker to create the queue.
+For Rabbit MQ, queues are created by consumers. This is because publishers don't know who consumes them, and thus don't create their queues. This means that if you run a producer, such as GreetingsWeb, and use tests.http to push in greetings, although a message will be published to RabbitMQ, it won't have a queue to be delivered to and will be dropped, unless you have first run the SalutationAnalytics worker to create the queue.
Generally, the rule of thumb is: start the consumer and *then* start the producer.
-You can spot this by looking in the [RabbitMQ Management console](http://localhost:1567) and noting that no queue is bound to the routing key in the exchange.
+You can spot this by looking in the [RabbitMQ Management console](http://localhost:15672) and noting that no queue is bound to the routing key in the exchange.
You can use default credentials for the RabbitMQ Management console:
```sh
user :guest
passowrd: guest
```
-#### Connection issue with the RabbitMQ
-When running RabbitMQ from the docker compose file (without any additional network setup, etc.) your RabbitMQ instance in docker will still be accessible by **localhost** as a host name. Consider this when running your application in the Production environment.
-In Production, the application by default will have:
-```sh
-amqp://guest:guest@rabbitmq:5672
-```
-
-as an Advanced Message Queuing Protocol (AMQP) connection string.
-So one of the options will be replacing AMQP connection string with:
-```sh
-amqp://guest:guest@localhost:5672
-```
-In case you still struggle, consider following these steps: [RabbitMQ Troubleshooting Networking](https://www.rabbitmq.com/troubleshooting-networking.html)
#### Helpful documentation links
* [Brighter technical documentation](https://paramore.readthedocs.io/en/latest/index.html)
* [Rabbit Message Queue (RMQ) documentation](https://www.rabbitmq.com/documentation.html)
+* [Kafka documentation](https://kafka.apache.org/documentation/)
-## Tests
-
-We provide a tests.http file (supported by both JetBrains Rider and VS Code with the REST Client plugin) to allow you to test operations.
\ No newline at end of file
diff --git a/samples/WebAPI_Dapper/SalutationAnalytics/Database/OutboxExtensions.cs b/samples/WebAPI_Dapper/SalutationAnalytics/Database/OutboxExtensions.cs
new file mode 100644
index 0000000000..428c3bfda7
--- /dev/null
+++ b/samples/WebAPI_Dapper/SalutationAnalytics/Database/OutboxExtensions.cs
@@ -0,0 +1,67 @@
+using System;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Hosting;
+using Paramore.Brighter;
+using Paramore.Brighter.MsSql;
+using Paramore.Brighter.MySql;
+using Paramore.Brighter.Outbox.MsSql;
+using Paramore.Brighter.Outbox.MySql;
+using Paramore.Brighter.Outbox.PostgreSql;
+using Paramore.Brighter.Outbox.Sqlite;
+using Paramore.Brighter.PostgreSql;
+using Paramore.Brighter.Sqlite;
+
+namespace SalutationAnalytics.Database
+{
+
+ public class OutboxExtensions
+ {
+ public static (IAmAnOutbox, Type, Type) MakeOutbox(
+ HostBuilderContext hostContext,
+ DatabaseType databaseType,
+ RelationalDatabaseConfiguration configuration,
+ IServiceCollection services)
+ {
+ (IAmAnOutbox, Type, Type) outbox;
+ if (hostContext.HostingEnvironment.IsDevelopment())
+ {
+ outbox = MakeSqliteOutBox(configuration);
+ }
+ else
+ {
+ outbox = databaseType switch
+ {
+ DatabaseType.MySql => MakeMySqlOutbox(configuration),
+ DatabaseType.MsSql => MakeMsSqlOutbox(configuration),
+ DatabaseType.Postgres => MakePostgresSqlOutbox(configuration, services),
+ DatabaseType.Sqlite => MakeSqliteOutBox(configuration),
+ _ => throw new InvalidOperationException("Unknown Db type for Outbox configuration")
+ };
+ }
+
+ return outbox;
+ }
+
+ private static (IAmAnOutbox, Type, Type) MakePostgresSqlOutbox(
+ RelationalDatabaseConfiguration configuration,
+ IServiceCollection services)
+ {
+ return (new PostgreSqlOutbox(configuration), typeof(PostgreSqlConnectionProvider), typeof(PostgreSqlUnitOfWork));
+ }
+
+ private static (IAmAnOutbox, Type, Type) MakeMsSqlOutbox(RelationalDatabaseConfiguration configuration)
+ {
+ return new(new MsSqlOutbox(configuration), typeof(MsSqlConnectionProvider), typeof(MsSqlUnitOfWork));
+ }
+
+ private static (IAmAnOutbox, Type, Type) MakeMySqlOutbox(RelationalDatabaseConfiguration configuration)
+ {
+ return (new MySqlOutbox(configuration), typeof (MySqlConnectionProvider), typeof(MySqlUnitOfWork));
+ }
+
+ private static (IAmAnOutbox, Type, Type) MakeSqliteOutBox(RelationalDatabaseConfiguration configuration)
+ {
+ return (new SqliteOutbox(configuration), typeof(SqliteConnectionProvider), typeof(SqliteUnitOfWork));
+ }
+ }
+}
diff --git a/samples/WebAPI_Dapper/SalutationAnalytics/Database/SchemaCreation.cs b/samples/WebAPI_Dapper/SalutationAnalytics/Database/SchemaCreation.cs
index f727b948a3..9ddeeaf24e 100644
--- a/samples/WebAPI_Dapper/SalutationAnalytics/Database/SchemaCreation.cs
+++ b/samples/WebAPI_Dapper/SalutationAnalytics/Database/SchemaCreation.cs
@@ -1,15 +1,22 @@
using System;
using System.Data;
+using System.Data.Common;
using FluentMigrator.Runner;
+using Microsoft.Data.SqlClient;
using Microsoft.Data.Sqlite;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using MySqlConnector;
+using Npgsql;
+using Paramore.Brighter.Inbox.MsSql;
using Paramore.Brighter.Inbox.MySql;
+using Paramore.Brighter.Inbox.Postgres;
using Paramore.Brighter.Inbox.Sqlite;
+using Paramore.Brighter.Outbox.MsSql;
using Paramore.Brighter.Outbox.MySql;
+using Paramore.Brighter.Outbox.PostgreSql;
using Paramore.Brighter.Outbox.Sqlite;
using Polly;
@@ -27,13 +34,13 @@ public static IHost CheckDbIsUp(this IHost host)
var services = scope.ServiceProvider;
var env = services.GetService();
var config = services.GetService();
- string connectionString = DbServerConnectionString(config, env);
+ var (dbType, connectionString) = DbServerConnectionString(config, env);
- //We don't check in development as using Sqlite
+ //We don't check db availability in development as we always use Sqlite which is a file not a server
if (env.IsDevelopment()) return host;
- WaitToConnect(connectionString);
- CreateDatabaseIfNotExists(connectionString);
+ WaitToConnect(dbType, connectionString);
+ CreateDatabaseIfNotExists(dbType, GetDbConnection(dbType, connectionString));
return host;
}
@@ -51,6 +58,20 @@ public static IHost CreateInbox(this IHost host)
return host;
}
+
+ public static IHost CreateOutbox(this IHost webHost, bool hasBinaryMessagePayload)
+ {
+ using (var scope = webHost.Services.CreateScope())
+ {
+ var services = scope.ServiceProvider;
+ var env = services.GetService();
+ var config = services.GetService();
+
+ CreateOutbox(config, env, hasBinaryMessagePayload);
+ }
+
+ return webHost;
+ }
public static IHost MigrateDatabase(this IHost host)
{
@@ -75,16 +96,39 @@ public static IHost MigrateDatabase(this IHost host)
return host;
}
- private static void CreateDatabaseIfNotExists(string connectionString)
+ private static void CreateDatabaseIfNotExists(DatabaseType databaseType, DbConnection conn)
{
//The migration does not create the Db, so we need to create it sot that it will add it
- using var conn = new MySqlConnection(connectionString);
conn.Open();
using var command = conn.CreateCommand();
- command.CommandText = "CREATE DATABASE IF NOT EXISTS Salutations";
- command.ExecuteScalar();
- }
+ command.CommandText = databaseType switch
+ {
+ DatabaseType.Sqlite => "CREATE DATABASE IF NOT EXISTS Salutations",
+ DatabaseType.MySql => "CREATE DATABASE IF NOT EXISTS Salutations",
+ DatabaseType.Postgres => "CREATE DATABASE Salutations",
+ DatabaseType.MsSql =>
+ "IF NOT EXISTS(SELECT * FROM sys.databases WHERE name = 'Salutations') CREATE DATABASE Salutations",
+ _ => throw new InvalidOperationException("Could not create instance of Salutations for unknown Db type")
+ };
+
+ try
+ {
+ command.ExecuteScalar();
+ }
+ catch (NpgsqlException pe)
+ {
+ //Ignore if the Db already exists - we can't test for this in the SQL for Postgres
+ if (!pe.Message.Contains("already exists"))
+ throw;
+ }
+ catch (System.Exception e)
+ {
+ Console.WriteLine($"Issue with creating Greetings tables, {e.Message}");
+ //Rethrow, if we can't create the Outbox, shut down
+ throw;
+ }
+ }
private static void CreateInbox(IConfiguration config, IHostEnvironment env)
{
try
@@ -94,9 +138,9 @@ private static void CreateInbox(IConfiguration config, IHostEnvironment env)
if (env.IsDevelopment())
CreateInboxDevelopment(connectionString);
else
- CreateInboxProduction(connectionString);
+ CreateInboxProduction(GetDatabaseType(config), connectionString);
}
- catch (System.Exception e)
+ catch (Exception e)
{
Console.WriteLine($"Issue with creating Inbox table, {e.Message}");
throw;
@@ -104,6 +148,32 @@ private static void CreateInbox(IConfiguration config, IHostEnvironment env)
}
private static void CreateInboxDevelopment(string connectionString)
+ {
+ CreateInboxSqlite(connectionString);
+ }
+
+ private static void CreateInboxProduction(DatabaseType databaseType, string connectionString)
+ {
+ switch (databaseType)
+ {
+ case DatabaseType.MySql:
+ CreateInboxMySql(connectionString);
+ break;
+ case DatabaseType.MsSql:
+ CreateInboxMsSql(connectionString);
+ break;
+ case DatabaseType.Postgres:
+ CreateInboxPostgres(connectionString);
+ break;
+ case DatabaseType.Sqlite:
+ CreateInboxSqlite(connectionString);
+ break;
+ default:
+ throw new InvalidOperationException("Could not create instance of Outbox for unknown Db type");
+ }
+ }
+
+ private static void CreateInboxSqlite(string connectionString)
{
using var sqlConnection = new SqliteConnection(connectionString);
sqlConnection.Open();
@@ -119,14 +189,15 @@ private static void CreateInboxDevelopment(string connectionString)
command.ExecuteScalar();
}
- private static void CreateInboxProduction(string connectionString)
+ private static void CreateInboxMySql(string connectionString)
{
using var sqlConnection = new MySqlConnection(connectionString);
sqlConnection.Open();
using var existsQuery = sqlConnection.CreateCommand();
existsQuery.CommandText = MySqlInboxBuilder.GetExistsQuery(INBOX_TABLE_NAME);
- bool exists = existsQuery.ExecuteScalar() != null;
+ var findInbox = existsQuery.ExecuteScalar();
+ bool exists = findInbox is long and > 0;
if (exists) return;
@@ -134,63 +205,124 @@ private static void CreateInboxProduction(string connectionString)
command.CommandText = MySqlInboxBuilder.GetDDL(INBOX_TABLE_NAME);
command.ExecuteScalar();
}
+
+ private static void CreateInboxMsSql(string connectionString)
+ {
+ using var sqlConnection = new SqlConnection(connectionString);
+ sqlConnection.Open();
+
+ using var existsQuery = sqlConnection.CreateCommand();
+ existsQuery.CommandText = SqlInboxBuilder.GetExistsQuery(INBOX_TABLE_NAME);
+ var findInbox = existsQuery.ExecuteScalar();
+ bool exists = findInbox is > 0;
+
+ if (exists) return;
- public static IHost CreateOutbox(this IHost webHost)
+ using var command = sqlConnection.CreateCommand();
+ command.CommandText = SqlInboxBuilder.GetDDL(INBOX_TABLE_NAME);
+ command.ExecuteScalar();
+ }
+
+ private static void CreateInboxPostgres(string connectionString)
{
- using (var scope = webHost.Services.CreateScope())
- {
- var services = scope.ServiceProvider;
- var env = services.GetService();
- var config = services.GetService();
+ using var sqlConnection = new NpgsqlConnection(connectionString);
+ sqlConnection.Open();
- CreateOutbox(config, env);
- }
+ using var existsQuery = sqlConnection.CreateCommand();
+ existsQuery.CommandText = PostgreSqlInboxBuilder.GetExistsQuery(INBOX_TABLE_NAME.ToLower());
+
+ var findInbox = existsQuery.ExecuteScalar();
+ bool exists = findInbox is true;
- return webHost;
+ if (exists) return;
+
+
+ using var command = sqlConnection.CreateCommand();
+ command.CommandText = PostgreSqlInboxBuilder.GetDDL(INBOX_TABLE_NAME);
+ command.ExecuteScalar();
}
- private static void CreateOutbox(IConfiguration config, IHostEnvironment env)
+ private static void CreateOutbox(IConfiguration config, IHostEnvironment env, bool hasBinaryMessagePayload)
{
try
{
var connectionString = DbConnectionString(config, env);
if (env.IsDevelopment())
- CreateOutboxDevelopment(connectionString);
+ CreateOutboxDevelopment(connectionString, hasBinaryMessagePayload);
else
- CreateOutboxProduction(connectionString);
+ CreateOutboxProduction(GetDatabaseType(config), connectionString, hasBinaryMessagePayload);
+ }
+ catch (NpgsqlException pe)
+ {
+ //Ignore if the Db already exists - we can't test for this in the SQL for Postgres
+ if (!pe.Message.Contains("already exists"))
+ {
+ Console.WriteLine($"Issue with creating Outbox table, {pe.Message}");
+ throw;
+ }
}
catch (System.Exception e)
{
Console.WriteLine($"Issue with creating Outbox table, {e.Message}");
+ //Rethrow, if we can't create the Outbox, shut down
throw;
}
}
- private static void CreateOutboxDevelopment(string connectionString)
+ private static void CreateOutboxDevelopment(string connectionString, bool hasBinaryMessagePayload)
{
- using var sqlConnection = new SqliteConnection(connectionString);
+ CreateOutboxSqlite(connectionString, hasBinaryMessagePayload);
+ }
+
+ private static void CreateOutboxProduction(DatabaseType databaseType, string connectionString, bool hasBinaryMessagePayload)
+ {
+ switch (databaseType)
+ {
+ case DatabaseType.MySql:
+ CreateOutboxMySql(connectionString, hasBinaryMessagePayload);
+ break;
+ case DatabaseType.MsSql:
+ CreateOutboxMsSql(connectionString, hasBinaryMessagePayload);
+ break;
+ case DatabaseType.Postgres:
+ CreateOutboxPostgres(connectionString, hasBinaryMessagePayload);
+ break;
+ case DatabaseType.Sqlite:
+ CreateOutboxSqlite(connectionString, hasBinaryMessagePayload);
+ break;
+ default:
+ throw new InvalidOperationException("Could not create instance of Outbox for unknown Db type");
+ }
+ }
+
+ private static void CreateOutboxMsSql(string connectionString, bool hasBinaryMessagePayload)
+ {
+ using var sqlConnection = new SqlConnection(connectionString);
sqlConnection.Open();
- using var exists = sqlConnection.CreateCommand();
- exists.CommandText = SqliteOutboxBuilder.GetExistsQuery(OUTBOX_TABLE_NAME);
- using var reader = exists.ExecuteReader(CommandBehavior.SingleRow);
+ using var existsQuery = sqlConnection.CreateCommand();
+ existsQuery.CommandText = SqlOutboxBuilder.GetExistsQuery(OUTBOX_TABLE_NAME);
+ var findOutbox = existsQuery.ExecuteScalar();
+ bool exists = findOutbox is > 0;
- if (reader.HasRows) return;
+ if (exists) return;
using var command = sqlConnection.CreateCommand();
- command.CommandText = SqliteOutboxBuilder.GetDDL(OUTBOX_TABLE_NAME);
+ command.CommandText = SqlOutboxBuilder.GetDDL(OUTBOX_TABLE_NAME);
command.ExecuteScalar();
+
}
- private static void CreateOutboxProduction(string connectionString)
+ private static void CreateOutboxMySql(string connectionString, bool hasBinaryMessagePayload)
{
using var sqlConnection = new MySqlConnection(connectionString);
sqlConnection.Open();
using var existsQuery = sqlConnection.CreateCommand();
existsQuery.CommandText = MySqlOutboxBuilder.GetExistsQuery(OUTBOX_TABLE_NAME);
- bool exists = existsQuery.ExecuteScalar() != null;
+ var findOutbox = existsQuery.ExecuteScalar();
+ bool exists = findOutbox is long and > 0;
if (exists) return;
@@ -198,16 +330,132 @@ private static void CreateOutboxProduction(string connectionString)
command.CommandText = MySqlOutboxBuilder.GetDDL(OUTBOX_TABLE_NAME);
command.ExecuteScalar();
}
+
+ private static void CreateOutboxPostgres(string connectionString, bool hasBinaryMessagePayload)
+ {
+ using var sqlConnection = new NpgsqlConnection(connectionString);
+ sqlConnection.Open();
+
+ using var existsQuery = sqlConnection.CreateCommand();
+ existsQuery.CommandText = PostgreSqlOutboxBulder.GetExistsQuery(OUTBOX_TABLE_NAME.ToLower());
+ var findOutbox = existsQuery.ExecuteScalar();
+ bool exists = findOutbox is true;
+
+ if (exists) return;
+
+ using var command = sqlConnection.CreateCommand();
+ command.CommandText = PostgreSqlOutboxBulder.GetDDL(OUTBOX_TABLE_NAME);
+ command.ExecuteScalar();
+ }
+
+ private static void CreateOutboxSqlite(string connectionString, bool hasBinaryMessagePayload)
+ {
+ using var sqlConnection = new SqliteConnection(connectionString);
+ sqlConnection.Open();
+
+ using var exists = sqlConnection.CreateCommand();
+ exists.CommandText = SqliteOutboxBuilder.GetExistsQuery(OUTBOX_TABLE_NAME);
+ using var reader = exists.ExecuteReader(CommandBehavior.SingleRow);
+
+ if (reader.HasRows) return;
+
+ using var command = sqlConnection.CreateCommand();
+ command.CommandText = SqliteOutboxBuilder.GetDDL(OUTBOX_TABLE_NAME, hasBinaryMessagePayload);
+ command.ExecuteScalar();
+ }
private static string DbConnectionString(IConfiguration config, IHostEnvironment env)
{
//NOTE: Sqlite needs to use a shared cache to allow Db writes to the Outbox as well as entities
- return env.IsDevelopment() ? "Filename=Salutations.db;Cache=Shared" : config.GetConnectionString("Salutations");
+ return env.IsDevelopment() ? GetDevConnectionString() : GetProductionDbConnectionString(config, GetDatabaseType(config));
+ }
+
+ private static (DatabaseType, string) DbServerConnectionString(IConfiguration config, IHostEnvironment env)
+ {
+ var databaseType = GetDatabaseType(config);
+ var connectionString = env.IsDevelopment() ? GetDevConnectionString() : GetProductionConnectionString(config, databaseType);
+ return (databaseType, connectionString);
}
- private static string DbServerConnectionString(IConfiguration config, IHostEnvironment env)
+ private static string GetDevConnectionString()
{
- return env.IsDevelopment() ? "Filename=Salutations.db;Cache=Shared" : config.GetConnectionString("SalutationsDb");
+ return "Filename=Salutations.db;Cache=Shared";
+ }
+
+ private static DbConnection GetDbConnection(DatabaseType databaseType, string connectionString)
+ {
+ return databaseType switch
+ {
+ DatabaseType.MySql => new MySqlConnection(connectionString),
+ DatabaseType.MsSql => new SqlConnection(connectionString),
+ DatabaseType.Postgres => new NpgsqlConnection(connectionString),
+ DatabaseType.Sqlite => new SqliteConnection(connectionString),
+ _ => throw new InvalidOperationException("Could not determine the database type")
+ };
+ }
+
+ private static string GetProductionConnectionString(IConfiguration config, DatabaseType databaseType)
+ {
+ return databaseType switch
+ {
+ DatabaseType.MySql => config.GetConnectionString("MySqlDb"),
+ DatabaseType.MsSql => config.GetConnectionString("MsSqlDb"),
+ DatabaseType.Postgres => config.GetConnectionString("PostgreSqlDb"),
+ DatabaseType.Sqlite => GetDevConnectionString(),
+ _ => throw new InvalidOperationException("Could not determine the database type")
+ };
+ }
+
+ private static string GetProductionDbConnectionString(IConfiguration config, DatabaseType databaseType)
+ {
+ return databaseType switch
+ {
+ DatabaseType.MySql => config.GetConnectionString("SalutationsMySql"),
+ DatabaseType.MsSql => config.GetConnectionString("SalutationsMsSql"),
+ DatabaseType.Postgres => config.GetConnectionString("SalutationsPostgreSql"),
+ DatabaseType.Sqlite => GetDevConnectionString(),
+ _ => throw new InvalidOperationException("Could not determine the database type")
+ };
+ }
+
+ private static DatabaseType GetDatabaseType(IConfiguration config)
+ {
+ return config[DatabaseGlobals.DATABASE_TYPE_ENV] switch
+ {
+ DatabaseGlobals.MYSQL => DatabaseType.MySql,
+ DatabaseGlobals.MSSQL => DatabaseType.MsSql,
+ DatabaseGlobals.POSTGRESSQL => DatabaseType.Postgres,
+ DatabaseGlobals.SQLITE => DatabaseType.Sqlite,
+ _ => throw new InvalidOperationException("Could not determine the database type")
+ };
+ }
+
+ private static void WaitToConnect(DatabaseType dbType, string connectionString)
+ {
+ var policy = Policy.Handle().WaitAndRetryForever(
+ retryAttempt => TimeSpan.FromSeconds(2),
+ (exception, timespan) =>
+ {
+ Console.WriteLine($"Healthcheck: Waiting for the database {connectionString} to come online - {exception.Message}");
+ });
+
+ policy.Execute(() =>
+ {
+ using var conn = GetConnection(dbType, connectionString);
+ conn.Open();
+ });
+ }
+
+ private static DbConnection GetConnection(DatabaseType databaseType, string connectionString)
+ {
+ return databaseType switch
+ {
+ DatabaseType.MySql => new MySqlConnection(connectionString),
+ DatabaseType.MsSql => new SqlConnection(connectionString),
+ DatabaseType.Postgres => new NpgsqlConnection(connectionString),
+ DatabaseType.Sqlite => new SqliteConnection(connectionString),
+ _ => throw new ArgumentOutOfRangeException(nameof(databaseType), databaseType, null)
+ };
}
private static void WaitToConnect(string connectionString)
diff --git a/samples/WebAPI_Dapper/SalutationAnalytics/Messaging/MessagingTransport.cs b/samples/WebAPI_Dapper/SalutationAnalytics/Messaging/MessagingTransport.cs
new file mode 100644
index 0000000000..45f7af1bde
--- /dev/null
+++ b/samples/WebAPI_Dapper/SalutationAnalytics/Messaging/MessagingTransport.cs
@@ -0,0 +1,21 @@
+namespace SalutationAnalytics.Messaging;
+
+public static class MessagingGlobals
+{
+ //environment string key
+ public const string BRIGHTER_TRANSPORT = "BRIGHTER_TRANSPORT";
+
+ public const string RMQ = "RabbitMQ";
+ public const string KAFKA = "Kafka";
+}
+
+
+///
+/// Which messaging transport are you using?
+///
+public enum MessagingTransport
+{
+ Rmq,
+ Kafka
+}
+
diff --git a/samples/WebAPI_Dapper/SalutationAnalytics/Program.cs b/samples/WebAPI_Dapper/SalutationAnalytics/Program.cs
index a221b2604d..26d7119a90 100644
--- a/samples/WebAPI_Dapper/SalutationAnalytics/Program.cs
+++ b/samples/WebAPI_Dapper/SalutationAnalytics/Program.cs
@@ -1,27 +1,33 @@
using System;
using System.IO;
using System.Threading.Tasks;
-using DapperExtensions;
-using DapperExtensions.Sql;
+using Confluent.Kafka;
+using Confluent.SchemaRegistry;
using FluentMigrator.Runner;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Paramore.Brighter;
-using Paramore.Brighter.Dapper;
using Paramore.Brighter.Extensions.DependencyInjection;
using Paramore.Brighter.Inbox;
+using Paramore.Brighter.Inbox.MsSql;
using Paramore.Brighter.Inbox.MySql;
+using Paramore.Brighter.Inbox.Postgres;
using Paramore.Brighter.Inbox.Sqlite;
+using Paramore.Brighter.MessagingGateway.Kafka;
using Paramore.Brighter.MessagingGateway.RMQ;
+using Paramore.Brighter.MsSql;
+using Paramore.Brighter.MySql;
+using Paramore.Brighter.PostgreSql;
using Paramore.Brighter.ServiceActivator.Extensions.DependencyInjection;
using Paramore.Brighter.ServiceActivator.Extensions.Hosting;
+using Paramore.Brighter.Sqlite;
using SalutationAnalytics.Database;
-using SalutationPorts.EntityMappers;
+using SalutationAnalytics.Messaging;
using SalutationPorts.Policies;
using SalutationPorts.Requests;
-using Salutations_SqliteMigrations.Migrations;
+using ChannelFactory = Paramore.Brighter.MessagingGateway.RMQ.ChannelFactory;
namespace SalutationAnalytics
{
@@ -33,10 +39,19 @@ public static async Task Main(string[] args)
host.CheckDbIsUp();
host.MigrateDatabase();
host.CreateInbox();
- host.CreateOutbox();
+ host.CreateOutbox(HasBinaryMessagePayload());
await host.RunAsync();
}
+ private static void AddSchemaRegistryMaybe(IServiceCollection services, MessagingTransport messagingTransport)
+ {
+ if (messagingTransport != MessagingTransport.Kafka) return;
+
+ var schemaRegistryConfig = new SchemaRegistryConfig { Url = "http://localhost:8081" };
+ var cachedSchemaRegistryClient = new CachedSchemaRegistryClient(schemaRegistryConfig);
+ services.AddSingleton(cachedSchemaRegistryClient);
+ }
+
private static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureHostConfiguration(configurationBuilder =>
@@ -63,100 +78,137 @@ private static IHostBuilder CreateHostBuilder(string[] args) =>
private static void ConfigureBrighter(HostBuilderContext hostContext, IServiceCollection services)
{
- var subscriptions = new Subscription[]
- {
- new RmqSubscription(
- new SubscriptionName("paramore.sample.salutationanalytics"),
- new ChannelName("SalutationAnalytics"),
- new RoutingKey("GreetingMade"),
- runAsync: true,
- timeoutInMilliseconds: 200,
- isDurable: true,
- makeChannels: OnMissingChannel.Create), //change to OnMissingChannel.Validate if you have infrastructure declared elsewhere
- };
-
- var host = hostContext.HostingEnvironment.IsDevelopment() ? "localhost" : "rabbitmq";
+ var messagingTransport = GetTransportType(hostContext.Configuration[MessagingGlobals.BRIGHTER_TRANSPORT]);
+
+ AddSchemaRegistryMaybe(services, messagingTransport);
+
+ Subscription[] subscriptions = GetSubscriptions(messagingTransport);
- var rmqConnection = new RmqMessagingGatewayConnection
- {
- AmpqUri = new AmqpUriSpecification(new Uri($"amqp://guest:guest@{host}:5672")), Exchange = new Exchange("paramore.brighter.exchange")
- };
-
- var rmqMessageConsumerFactory = new RmqMessageConsumerFactory(rmqConnection);
+ var relationalDatabaseConfiguration = new RelationalDatabaseConfiguration(DbConnectionString(hostContext));
+ services.AddSingleton(relationalDatabaseConfiguration);
+
+ var outboxConfiguration = new RelationalDatabaseConfiguration(
+ DbConnectionString(hostContext),
+ binaryMessagePayload: messagingTransport == MessagingTransport.Kafka
+ );
+ services.AddSingleton(outboxConfiguration);
+
+ (IAmAnOutbox outbox, Type connectionProvider, Type transactionProvider) makeOutbox =
+ OutboxExtensions.MakeOutbox(hostContext, GetDatabaseType(hostContext), outboxConfiguration, services);
services.AddServiceActivator(options =>
{
options.Subscriptions = subscriptions;
- options.ChannelFactory = new ChannelFactory(rmqMessageConsumerFactory);
+ options.ChannelFactory = GetChannelFactory(messagingTransport);
options.UseScoped = true;
options.HandlerLifetime = ServiceLifetime.Scoped;
options.MapperLifetime = ServiceLifetime.Singleton;
options.CommandProcessorLifetime = ServiceLifetime.Scoped;
options.PolicyRegistry = new SalutationPolicy();
+ options.InboxConfiguration = new InboxConfiguration(
+ CreateInbox(hostContext, relationalDatabaseConfiguration),
+ scope: InboxScope.Commands,
+ onceOnly: true,
+ actionOnExists: OnceOnlyAction.Throw
+
+ );
})
.ConfigureJsonSerialisation((options) =>
{
//We don't strictly need this, but added as an example
options.PropertyNameCaseInsensitive = true;
})
- .UseExternalBus(new RmqProducerRegistryFactory(
- rmqConnection,
- new RmqPublication[]
- {
- new RmqPublication
- {
- Topic = new RoutingKey("SalutationReceived"),
- MaxOutStandingMessages = 5,
- MaxOutStandingCheckIntervalMilliSeconds = 500,
- WaitForConfirmsTimeOutInMilliseconds = 1000,
- MakeChannels = OnMissingChannel.Create
- }
- }
- ).Create()
- )
- .AutoFromAssemblies()
- .UseExternalInbox(
- ConfigureInbox(hostContext),
- new InboxConfiguration(
- scope: InboxScope.Commands,
- onceOnly: true,
- actionOnExists: OnceOnlyAction.Throw
- )
- );
+ .UseExternalBus((config) =>
+ {
+ config.ProducerRegistry = ConfigureProducerRegistry(messagingTransport);
+ config.Outbox = makeOutbox.outbox;
+ config.ConnectionProvider = makeOutbox.connectionProvider;
+ config.TransactionProvider = makeOutbox.transactionProvider;
+ })
+ .AutoFromAssemblies();
services.AddHostedService();
}
-
+
private static void ConfigureMigration(HostBuilderContext hostBuilderContext, IServiceCollection services)
{
+ //dev is always Sqlite
if (hostBuilderContext.HostingEnvironment.IsDevelopment())
- {
- services
- .AddFluentMigratorCore()
- .ConfigureRunner(c =>
- {
- c.AddSQLite()
- .WithGlobalConnectionString(DbConnectionString(hostBuilderContext))
- .ScanIn(typeof(Salutations_SqliteMigrations.Migrations.SqliteInitialCreate).Assembly).For.Migrations();
- });
- }
+ ConfigureSqlite(hostBuilderContext, services);
else
+ ConfigureProductionDatabase(hostBuilderContext, GetDatabaseType(hostBuilderContext), services);
+ }
+
+ private static void ConfigureProductionDatabase(
+ HostBuilderContext hostBuilderContext,
+ DatabaseType databaseType,
+ IServiceCollection services)
+ {
+ switch (databaseType)
{
- services
- .AddFluentMigratorCore()
- .ConfigureRunner(c => c.AddMySql5()
- .WithGlobalConnectionString(DbConnectionString(hostBuilderContext))
- .ScanIn(typeof(Salutations_mySqlMigrations.Migrations.MySqlInitialCreate).Assembly).For.Migrations()
- );
+ case DatabaseType.MySql:
+ ConfigureMySql(hostBuilderContext, services);
+ break;
+ case DatabaseType.MsSql:
+ ConfigureMsSql(hostBuilderContext, services);
+ break;
+ case DatabaseType.Postgres:
+ ConfigurePostgreSql(hostBuilderContext, services);
+ break;
+ case DatabaseType.Sqlite:
+ ConfigureSqlite(hostBuilderContext, services);
+ break;
+ default:
+ throw new ArgumentOutOfRangeException(nameof(databaseType), "Database type is not supported");
}
}
+
+ private static void ConfigureMySql(HostBuilderContext hostBuilderContext, IServiceCollection services)
+ {
+ services
+ .AddFluentMigratorCore()
+ .ConfigureRunner(c => c.AddMySql5()
+ .WithGlobalConnectionString(DbConnectionString(hostBuilderContext))
+ .ScanIn(typeof(Salutations_Migrations.Migrations.SqlInitialMigrations).Assembly).For.Migrations()
+ );
+ }
+
+ private static void ConfigureMsSql(HostBuilderContext hostBuilderContext, IServiceCollection services)
+ {
+ services
+ .AddFluentMigratorCore()
+ .ConfigureRunner(c => c.AddSqlServer()
+ .WithGlobalConnectionString(DbConnectionString(hostBuilderContext))
+ .ScanIn(typeof(Salutations_Migrations.Migrations.SqlInitialMigrations).Assembly).For.Migrations()
+ );
+ }
+
+ private static void ConfigurePostgreSql(HostBuilderContext hostBuilderContext, IServiceCollection services)
+ {
+ services
+ .AddFluentMigratorCore()
+ .ConfigureRunner(c => c.AddPostgres()
+ .ConfigureGlobalProcessorOptions(opt => opt.ProviderSwitches = "Force Quote=false")
+ .WithGlobalConnectionString(DbConnectionString(hostBuilderContext))
+ .ScanIn(typeof(Salutations_Migrations.Migrations.SqlInitialMigrations).Assembly).For.Migrations()
+ );
+ }
+
+ private static void ConfigureSqlite(HostBuilderContext hostBuilderContext, IServiceCollection services)
+ {
+ services
+ .AddFluentMigratorCore()
+ .ConfigureRunner(c =>
+ {
+ c.AddSQLite()
+ .WithGlobalConnectionString(DbConnectionString(hostBuilderContext))
+ .ScanIn(typeof(Salutations_Migrations.Migrations.SqlInitialMigrations).Assembly).For.Migrations();
+ });
+ }
private static void ConfigureDapper(HostBuilderContext hostBuilderContext, IServiceCollection services)
{
- services.AddSingleton(new DbConnectionStringProvider(DbConnectionString(hostBuilderContext)));
ConfigureDapperByHost(GetDatabaseType(hostBuilderContext), services);
- DapperExtensions.DapperExtensions.SetMappingAssemblies(new[] { typeof(SalutationMapper).Assembly });
- DapperAsyncExtensions.SetMappingAssemblies(new[] { typeof(SalutationMapper).Assembly });
}
private static void ConfigureDapperByHost(DatabaseType databaseType, IServiceCollection services)
@@ -169,6 +221,12 @@ private static void ConfigureDapperByHost(DatabaseType databaseType, IServiceCol
case DatabaseType.MySql:
ConfigureDapperMySql(services);
break;
+ case DatabaseType.MsSql:
+ ConfigureDapperMsSql(services);
+ break;
+ case DatabaseType.Postgres:
+ ConfigureDapperPostgreSql(services);
+ break;
default:
throw new ArgumentOutOfRangeException(nameof(databaseType), "Database type is not supported");
}
@@ -176,41 +234,71 @@ private static void ConfigureDapperByHost(DatabaseType databaseType, IServiceCol
private static void ConfigureDapperSqlite(IServiceCollection services)
{
- DapperExtensions.DapperExtensions.SqlDialect = new SqliteDialect();
- DapperAsyncExtensions.SqlDialect = new SqliteDialect();
- services.AddScoped();
+ services.AddScoped();
+ services.AddScoped();
}
private static void ConfigureDapperMySql(IServiceCollection services)
{
- DapperExtensions.DapperExtensions.SqlDialect = new MySqlDialect();
- DapperAsyncExtensions.SqlDialect = new MySqlDialect();
- services.AddScoped();
+ services.AddScoped();
+ services.AddScoped();
}
+ private static void ConfigureDapperMsSql(IServiceCollection services)
+ {
+ services.AddScoped();
+ services.AddScoped();
+ }
- private static IAmAnInbox ConfigureInbox(HostBuilderContext hostContext)
+ private static void ConfigureDapperPostgreSql(IServiceCollection services)
+ {
+ services.AddScoped();
+ services.AddScoped();
+ }
+
+ private static IAmAnInbox CreateInbox(HostBuilderContext hostContext, IAmARelationalDatabaseConfiguration configuration)
{
if (hostContext.HostingEnvironment.IsDevelopment())
{
- return new SqliteInbox(new SqliteInboxConfiguration(DbConnectionString(hostContext), SchemaCreation.INBOX_TABLE_NAME));
+ return new SqliteInbox(configuration);
}
- return new MySqlInbox(new MySqlInboxConfiguration(DbConnectionString(hostContext), SchemaCreation.INBOX_TABLE_NAME));
+ return CreateProductionInbox(GetDatabaseType(hostContext), configuration);
+ }
+
+ private static IAmAProducerRegistry ConfigureProducerRegistry(MessagingTransport messagingTransport)
+ {
+ return messagingTransport switch
+ {
+ MessagingTransport.Rmq => GetRmqProducerRegistry(),
+ MessagingTransport.Kafka => GetKafkaProducerRegistry(),
+ _ => throw new ArgumentOutOfRangeException(nameof(messagingTransport), "Messaging transport is not supported")
+ };
+ }
+
+ private static IAmAnInbox CreateProductionInbox(DatabaseType databaseType, IAmARelationalDatabaseConfiguration configuration)
+ {
+ return databaseType switch
+ {
+ DatabaseType.Sqlite => new SqliteInbox(configuration),
+ DatabaseType.MySql => new MySqlInbox(configuration),
+ DatabaseType.MsSql => new MsSqlInbox(configuration),
+ DatabaseType.Postgres => new PostgreSqlInbox(configuration),
+ _ => throw new ArgumentOutOfRangeException(nameof(databaseType), "Database type is not supported")
+ };
}
private static string DbConnectionString(HostBuilderContext hostContext)
{
//NOTE: Sqlite needs to use a shared cache to allow Db writes to the Outbox as well as entities
return hostContext.HostingEnvironment.IsDevelopment()
- ? "Filename=Salutations.db;Cache=Shared"
- : hostContext.Configuration.GetConnectionString("Salutations");
+ ? GetDevDbConnectionString()
+ :GetConnectionString(hostContext, GetDatabaseType(hostContext));
}
- private static DatabaseType GetDatabaseType(HostBuilderContext hostContext)
+ private static DatabaseType GetDatabaseType(HostBuilderContext hostContext)
{
return hostContext.Configuration[DatabaseGlobals.DATABASE_TYPE_ENV] switch
-
{
DatabaseGlobals.MYSQL => DatabaseType.MySql,
DatabaseGlobals.MSSQL => DatabaseType.MsSql,
@@ -219,12 +307,167 @@ private static DatabaseType GetDatabaseType(HostBuilderContext hostContext)
_ => throw new InvalidOperationException("Could not determine the database type")
};
}
-
+
+ private static IAmAChannelFactory GetChannelFactory(MessagingTransport messagingTransport)
+ {
+ return messagingTransport switch
+ {
+ MessagingTransport.Rmq => GetRmqChannelFactory(),
+ MessagingTransport.Kafka => GetKafkaChannelFactory(),
+ _ => throw new ArgumentOutOfRangeException(nameof(messagingTransport), "Messaging transport is not supported")
+ };
+ }
private static string GetEnvironment()
{
//NOTE: Hosting Context will always return Production outside of ASPNET_CORE at this point, so grab it directly
return Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT");
}
+
+ private static string GetConnectionString(HostBuilderContext hostContext, DatabaseType databaseType)
+ {
+ return databaseType switch
+ {
+ DatabaseType.MySql => hostContext.Configuration.GetConnectionString("SalutationsMySql"),
+ DatabaseType.MsSql => hostContext.Configuration.GetConnectionString("SalutationsMsSql"),
+ DatabaseType.Postgres => hostContext.Configuration.GetConnectionString("SalutationsPostgreSql"),
+ DatabaseType.Sqlite => GetDevDbConnectionString(),
+ _ => throw new InvalidOperationException("Could not determine the database type")
+ };
+ }
+ private static string GetDevDbConnectionString()
+ {
+ return "Filename=Salutations.db;Cache=Shared";
+ }
+
+ private static IAmAChannelFactory GetKafkaChannelFactory()
+ {
+ return new Paramore.Brighter.MessagingGateway.Kafka.ChannelFactory(
+ new KafkaMessageConsumerFactory(
+ new KafkaMessagingGatewayConfiguration
+ {
+ Name = "paramore.brighter",
+ BootStrapServers = new[] { "localhost:9092" }
+ }
+ )
+ );
+ }
+
+ private static IAmAProducerRegistry GetKafkaProducerRegistry()
+ {
+ var producerRegistry = new KafkaProducerRegistryFactory(
+ new KafkaMessagingGatewayConfiguration
+ {
+ Name = "paramore.brighter.greetingsender", BootStrapServers = new[] { "localhost:9092" }
+ },
+ new KafkaPublication[]
+ {
+ new KafkaPublication
+ {
+ Topic = new RoutingKey("SalutationReceived"),
+ MessageSendMaxRetries = 3,
+ MessageTimeoutMs = 1000,
+ MaxInFlightRequestsPerConnection = 1,
+ MakeChannels = OnMissingChannel.Create
+ }
+ })
+ .Create();
+
+ return producerRegistry;
+ }
+
+ private static IAmAChannelFactory GetRmqChannelFactory()
+ {
+ return new ChannelFactory(new RmqMessageConsumerFactory(new RmqMessagingGatewayConnection
+ {
+ AmpqUri = new AmqpUriSpecification(new Uri($"amqp://guest:guest@localhost:5672")),
+ Exchange = new Exchange("paramore.brighter.exchange")
+ })
+ );
+ }
+
+ private static IAmAProducerRegistry GetRmqProducerRegistry()
+ {
+ var producerRegistry = new RmqProducerRegistryFactory(
+ new RmqMessagingGatewayConnection
+ {
+ AmpqUri = new AmqpUriSpecification(new Uri($"amqp://guest:guest@localhost:5672")),
+ Exchange = new Exchange("paramore.brighter.exchange")
+ },
+ new RmqPublication[]
+ {
+ new RmqPublication
+ {
+ Topic = new RoutingKey("SalutationReceived"),
+ MaxOutStandingMessages = 5,
+ MaxOutStandingCheckIntervalMilliSeconds = 500,
+ WaitForConfirmsTimeOutInMilliseconds = 1000,
+ MakeChannels = OnMissingChannel.Create
+ }
+ }
+ ).Create();
+ return producerRegistry;
+ }
+
+ private static Subscription[] GetRmqSubscriptions()
+ {
+ var subscriptions = new Subscription[]
+ {
+ new RmqSubscription(
+ new SubscriptionName("paramore.sample.salutationanalytics"),
+ new ChannelName("SalutationAnalytics"),
+ new RoutingKey("GreetingMade"),
+ runAsync: false,
+ timeoutInMilliseconds: 200,
+ isDurable: true,
+ makeChannels: OnMissingChannel
+ .Create), //change to OnMissingChannel.Validate if you have infrastructure declared elsewhere
+ };
+ return subscriptions;
+ }
+
+ private static Subscription[] GetSubscriptions(MessagingTransport messagingTransport)
+ {
+ return messagingTransport switch
+ {
+ MessagingTransport.Rmq => GetRmqSubscriptions(),
+ MessagingTransport.Kafka => GetKafkaSubscriptions(),
+ _ => throw new ArgumentOutOfRangeException(nameof(messagingTransport), "Messaging transport is not supported")
+ };
+ }
+
+ private static Subscription[] GetKafkaSubscriptions()
+ {
+ var subscriptions = new KafkaSubscription[]
+ {
+ new KafkaSubscription(
+ new SubscriptionName("paramore.sample.salutationanalytics"),
+ channelName: new ChannelName("SalutationAnalytics"),
+ routingKey: new RoutingKey("GreetingMade"),
+ groupId: "kafka-GreetingsReceiverConsole-Sample",
+ timeoutInMilliseconds: 100,
+ offsetDefault: AutoOffsetReset.Earliest,
+ commitBatchSize: 5,
+ sweepUncommittedOffsetsIntervalMs: 10000,
+ makeChannels: OnMissingChannel.Create)
+ };
+ return subscriptions;
+ }
+
+ private static MessagingTransport GetTransportType(string brighterTransport)
+ {
+ return brighterTransport switch
+ {
+ MessagingGlobals.RMQ => MessagingTransport.Rmq,
+ MessagingGlobals.KAFKA => MessagingTransport.Kafka,
+ _ => throw new ArgumentOutOfRangeException(nameof(MessagingGlobals.BRIGHTER_TRANSPORT),
+ "Messaging transport is not supported")
+ };
+ }
+
+ private static bool HasBinaryMessagePayload()
+ {
+ return GetTransportType(Environment.GetEnvironmentVariable("BRIGHTER_TRANSPORT")) == MessagingTransport.Kafka;
+ }
}
}
diff --git a/samples/WebAPI_Dapper/SalutationAnalytics/Properties/launchSettings.json b/samples/WebAPI_Dapper/SalutationAnalytics/Properties/launchSettings.json
index 4154fbe3ff..639defffb1 100644
--- a/samples/WebAPI_Dapper/SalutationAnalytics/Properties/launchSettings.json
+++ b/samples/WebAPI_Dapper/SalutationAnalytics/Properties/launchSettings.json
@@ -5,15 +5,45 @@
"commandName": "Project",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development",
- "BRIGHTER_GREETINGS_DATABASE" : "Sqlite"
+ "BRIGHTER_GREETINGS_DATABASE": "Sqlite",
+ "BRIGHTER_TRANSPORT": "RabbitMQ"
}
},
- "Production": {
- "commandName": "Project",
- "environmentVariables": {
- "ASPNETCORE_ENVIRONMENT": "Production",
- "BRIGHTER_GREETINGS_DATABASE" : "MySQL"
- }
+ "ProductionMySql": {
+ "commandName": "Project",
+ "dotnetRunMessages": "true",
+ "launchBrowser": true,
+ "launchUrl": "swagger",
+ "applicationUrl": "https://localhost:5001;http://localhost:5000",
+ "environmentVariables": {
+ "ASPNETCORE_ENVIRONMENT": "Production",
+ "BRIGHTER_GREETINGS_DATABASE": "MySQL",
+ "BRIGHTER_TRANSPORT": "RabbitMQ"
+ }
+ },
+ "ProductionPostgres": {
+ "commandName": "Project",
+ "dotnetRunMessages": "true",
+ "launchBrowser": true,
+ "launchUrl": "swagger",
+ "applicationUrl": "https://localhost:5001;http://localhost:5000",
+ "environmentVariables": {
+ "ASPNETCORE_ENVIRONMENT": "Production",
+ "BRIGHTER_GREETINGS_DATABASE": "PostgresSQL",
+ "BRIGHTER_TRANSPORT": "RabbitMQ"
+ }
+ },
+ "ProductionMsSql": {
+ "commandName": "Project",
+ "dotnetRunMessages": "true",
+ "launchBrowser": true,
+ "launchUrl": "swagger",
+ "applicationUrl": "https://localhost:5001;http://localhost:5000",
+ "environmentVariables": {
+ "ASPNETCORE_ENVIRONMENT": "Production",
+ "BRIGHTER_GREETINGS_DATABASE": "MsSQL",
+ "BRIGHTER_TRANSPORT": "RabbitMQ"
+ }
}
}
}
diff --git a/samples/WebAPI_Dapper/SalutationAnalytics/SalutationAnalytics.csproj b/samples/WebAPI_Dapper/SalutationAnalytics/SalutationAnalytics.csproj
index ee5dff6bb8..03427236da 100644
--- a/samples/WebAPI_Dapper/SalutationAnalytics/SalutationAnalytics.csproj
+++ b/samples/WebAPI_Dapper/SalutationAnalytics/SalutationAnalytics.csproj
@@ -6,20 +6,22 @@
+
+
+
-
+
+
-
-
-
+
@@ -41,4 +43,10 @@
+
+
+ ..\..\..\libs\Npgsql\net6.0\Npgsql.dll
+
+
+
diff --git a/samples/WebAPI_Dapper/SalutationAnalytics/appsettings.Production.json b/samples/WebAPI_Dapper/SalutationAnalytics/appsettings.Production.json
index dbbfec462a..546be3c89c 100644
--- a/samples/WebAPI_Dapper/SalutationAnalytics/appsettings.Production.json
+++ b/samples/WebAPI_Dapper/SalutationAnalytics/appsettings.Production.json
@@ -7,7 +7,11 @@
}
},
"ConnectionStrings": {
- "Salutations": "server=localhost; port=3306; uid=root; pwd=root; database=Salutations",
- "SalutationsDb": "server=localhost; port=3306; uid=root; pwd=root"
+ "SalutationsMySql": "server=localhost; port=3306; uid=root; pwd=root; database=Salutations",
+ "MySqlDb": "server=localhost; port=3306; uid=root; pwd=root",
+ "SalutationsPostgreSql": "Server=localhost; Port=5432; Database=salutations; Username=postgres; Password=password",
+ "PostgreSqlDb": "Server=localhost; Port=5432; Username=postgres; Password=password",
+ "SalutationsMsSql": "Server=localhost,11433;User Id=sa;Password=Password123!;Database=Salutations;TrustServerCertificate=true;Encrypt=false",
+ "MsSqlDb": "Server=localhost,11433;User Id=sa;Password=Password123!;TrustServerCertificate=true;Encrypt=false"
}
}
\ No newline at end of file
diff --git a/samples/WebAPI_Dapper/SalutationEntities/Salutation.cs b/samples/WebAPI_Dapper/SalutationEntities/Salutation.cs
index b3c44a995b..7a4c2c2408 100644
--- a/samples/WebAPI_Dapper/SalutationEntities/Salutation.cs
+++ b/samples/WebAPI_Dapper/SalutationEntities/Salutation.cs
@@ -1,9 +1,11 @@
-namespace SalutationEntities
+using System;
+
+namespace SalutationEntities
{
public class Salutation
{
public long Id { get; set; }
- public byte[] TimeStamp { get; set; }
+ public DateTime TimeStamp { get; set; }
public string Greeting { get; set; }
public Salutation() { /* ORM needs to create */ }
diff --git a/samples/WebAPI_Dapper/SalutationPorts/EntityMappers/SalutationMapper.cs b/samples/WebAPI_Dapper/SalutationPorts/EntityMappers/SalutationMapper.cs
deleted file mode 100644
index aacce5f63e..0000000000
--- a/samples/WebAPI_Dapper/SalutationPorts/EntityMappers/SalutationMapper.cs
+++ /dev/null
@@ -1,15 +0,0 @@
-using DapperExtensions.Mapper;
-using SalutationEntities;
-
-namespace SalutationPorts.EntityMappers;
-
-public class SalutationMapper : ClassMapper
-{
- public SalutationMapper()
- {
- TableName = nameof(Salutation);
- Map(s => s.Id).Column("Id").Key(KeyType.Identity);
- Map(s => s.Greeting).Column("Greeting");
- Map(s => s.TimeStamp).Column("TimeStamp").Ignore();
- }
-}
diff --git a/samples/WebAPI_Dapper/SalutationPorts/Handlers/GreetingMadeHandler.cs b/samples/WebAPI_Dapper/SalutationPorts/Handlers/GreetingMadeHandler.cs
index 4d9894b23d..0fca6ec34d 100644
--- a/samples/WebAPI_Dapper/SalutationPorts/Handlers/GreetingMadeHandler.cs
+++ b/samples/WebAPI_Dapper/SalutationPorts/Handlers/GreetingMadeHandler.cs
@@ -1,11 +1,10 @@
using System;
using System.Collections.Generic;
-using System.Threading;
-using System.Threading.Tasks;
-using DapperExtensions;
+using System.Data.Common;
+using Dapper;
using Microsoft.Extensions.Logging;
using Paramore.Brighter;
-using Paramore.Brighter.Dapper;
+using Paramore.Brighter.Inbox.Attributes;
using Paramore.Brighter.Logging.Attributes;
using Paramore.Brighter.Policies.Attributes;
using SalutationEntities;
@@ -13,50 +12,56 @@
namespace SalutationPorts.Handlers
{
- public class GreetingMadeHandlerAsync : RequestHandlerAsync
+ public class GreetingMadeHandler : RequestHandler
{
- private readonly IUnitOfWork _uow;
+ private readonly IAmABoxTransactionProvider _transactionConnectionProvider;
private readonly IAmACommandProcessor _postBox;
- private readonly ILogger _logger;
+ private readonly ILogger _logger;
- public GreetingMadeHandlerAsync(IUnitOfWork uow, IAmACommandProcessor postBox, ILogger logger)
+ public GreetingMadeHandler(IAmABoxTransactionProvider transactionConnectionProvider, IAmACommandProcessor postBox, ILogger logger)
{
- _uow = uow;
+ _transactionConnectionProvider = transactionConnectionProvider;
_postBox = postBox;
_logger = logger;
}
- //[UseInboxAsync(step:0, contextKey: typeof(GreetingMadeHandlerAsync), onceOnly: true )] -- we are using a global inbox, so need to be explicit!!
- [RequestLoggingAsync(step: 1, timing: HandlerTiming.Before)]
- [UsePolicyAsync(step:2, policy: Policies.Retry.EXPONENTIAL_RETRYPOLICYASYNC)]
- public override async Task HandleAsync(GreetingMade @event, CancellationToken cancellationToken = default)
+ [UseInbox(step:0, contextKey: typeof(GreetingMadeHandler), onceOnly: true )]
+ [RequestLogging(step: 1, timing: HandlerTiming.Before)]
+ [UsePolicy(step:2, policy: Policies.Retry.EXPONENTIAL_RETRYPOLICY)]
+ public override GreetingMade Handle(GreetingMade @event)
{
var posts = new List();
- var tx = await _uow.BeginOrGetTransactionAsync(cancellationToken);
+ var tx = _transactionConnectionProvider.GetTransaction();
+ var conn = tx.Connection;
try
{
var salutation = new Salutation(@event.Greeting);
- await _uow.Database.InsertAsync(salutation, tx);
+ conn.Execute(
+ "insert into Salutation (greeting) values (@greeting)",
+ new {greeting = salutation.Greeting},
+ tx);
- posts.Add(await _postBox.DepositPostAsync(new SalutationReceived(DateTimeOffset.Now), cancellationToken: cancellationToken));
+ posts.Add(_postBox.DepositPost(
+ new SalutationReceived(DateTimeOffset.Now),
+ _transactionConnectionProvider));
- await tx.CommitAsync(cancellationToken);
+ _transactionConnectionProvider.Commit();
}
catch (Exception e)
{
_logger.LogError(e, "Could not save salutation");
//if it went wrong rollback entity write and Outbox write
- await tx.RollbackAsync(cancellationToken);
+ _transactionConnectionProvider.Rollback();
- return await base.HandleAsync(@event, cancellationToken);
+ return base.Handle(@event);
}
- await _postBox.ClearOutboxAsync(posts, cancellationToken: cancellationToken);
+ _postBox.ClearOutbox(posts.ToArray());
- return await base.HandleAsync(@event, cancellationToken);
+ return base.Handle(@event);
}
}
}
diff --git a/samples/WebAPI_Dapper/SalutationPorts/Policies/Retry.cs b/samples/WebAPI_Dapper/SalutationPorts/Policies/Retry.cs
index 4db47aa42d..58013a5a3f 100644
--- a/samples/WebAPI_Dapper/SalutationPorts/Policies/Retry.cs
+++ b/samples/WebAPI_Dapper/SalutationPorts/Policies/Retry.cs
@@ -7,21 +7,21 @@ namespace SalutationPorts.Policies
{
public static class Retry
{
- public const string RETRYPOLICYASYNC = "SalutationPorts.Policies.RetryPolicyAsync";
- public const string EXPONENTIAL_RETRYPOLICYASYNC = "SalutationPorts.Policies.ExponenttialRetryPolicyAsync";
+ public const string RETRYPOLICY = "SalutationPorts.Policies.RetryPolicy";
+ public const string EXPONENTIAL_RETRYPOLICY = "SalutationPorts.Policies.ExponenttialRetryPolicy";
- public static AsyncRetryPolicy GetSimpleHandlerRetryPolicy()
+ public static RetryPolicy GetSimpleHandlerRetryPolicy()
{
- return Policy.Handle().WaitAndRetryAsync(new[]
+ return Policy.Handle().WaitAndRetry(new[]
{
TimeSpan.FromMilliseconds(50), TimeSpan.FromMilliseconds(100), TimeSpan.FromMilliseconds(150)
});
}
- public static AsyncRetryPolicy GetExponentialHandlerRetryPolicy()
+ public static RetryPolicy GetExponentialHandlerRetryPolicy()
{
var delay = Backoff.ExponentialBackoff(TimeSpan.FromMilliseconds(100), retryCount: 5, fastFirst: true);
- return Policy.Handle().WaitAndRetryAsync(delay);
+ return Policy.Handle().WaitAndRetry(delay);
}
}
}
diff --git a/samples/WebAPI_Dapper/SalutationPorts/Policies/SalutationPolicy.cs b/samples/WebAPI_Dapper/SalutationPorts/Policies/SalutationPolicy.cs
index ddf21c324f..28024f22a7 100644
--- a/samples/WebAPI_Dapper/SalutationPorts/Policies/SalutationPolicy.cs
+++ b/samples/WebAPI_Dapper/SalutationPorts/Policies/SalutationPolicy.cs
@@ -11,8 +11,8 @@ public SalutationPolicy()
private void AddSalutationPolicies()
{
- Add(Retry.RETRYPOLICYASYNC, Retry.GetSimpleHandlerRetryPolicy());
- Add(Retry.EXPONENTIAL_RETRYPOLICYASYNC, Retry.GetExponentialHandlerRetryPolicy());
+ Add(Retry.RETRYPOLICY, Retry.GetSimpleHandlerRetryPolicy());
+ Add(Retry.EXPONENTIAL_RETRYPOLICY, Retry.GetExponentialHandlerRetryPolicy());
}
}
}
diff --git a/samples/WebAPI_Dapper/SalutationPorts/SalutationPorts.csproj b/samples/WebAPI_Dapper/SalutationPorts/SalutationPorts.csproj
index c513055246..68859628e9 100644
--- a/samples/WebAPI_Dapper/SalutationPorts/SalutationPorts.csproj
+++ b/samples/WebAPI_Dapper/SalutationPorts/SalutationPorts.csproj
@@ -4,11 +4,11 @@
net6.0
-
-
-
-
-
+
+
+
+
+
diff --git a/samples/WebAPI_Dapper/Salutations_SqliteMigrations/Migrations/202205161812_SqliteMigrations.cs b/samples/WebAPI_Dapper/Salutations_Migrations/Migrations/202205161812_SqlInitialMigrations.cs
similarity index 62%
rename from samples/WebAPI_Dapper/Salutations_SqliteMigrations/Migrations/202205161812_SqliteMigrations.cs
rename to samples/WebAPI_Dapper/Salutations_Migrations/Migrations/202205161812_SqlInitialMigrations.cs
index 0afb14f529..a0f91cec2e 100644
--- a/samples/WebAPI_Dapper/Salutations_SqliteMigrations/Migrations/202205161812_SqliteMigrations.cs
+++ b/samples/WebAPI_Dapper/Salutations_Migrations/Migrations/202205161812_SqlInitialMigrations.cs
@@ -1,16 +1,16 @@
using FluentMigrator;
-namespace Salutations_SqliteMigrations.Migrations;
+namespace Salutations_Migrations.Migrations;
[Migration(1)]
-public class SqliteInitialCreate : Migration
+public class SqlInitialMigrations : Migration
{
public override void Up()
{
Create.Table("Salutation")
.WithColumn("Id").AsInt32().NotNullable().PrimaryKey().Identity()
.WithColumn("Greeting").AsString()
- .WithColumn("TimeStamp").AsBinary().WithDefault(SystemMethods.CurrentDateTime);
+ .WithColumn("TimeStamp").AsDateTime().Nullable().WithDefault(SystemMethods.CurrentDateTime);
}
public override void Down()
diff --git a/samples/WebAPI_Dapper/Salutations_SqliteMigrations/Salutations_SqliteMigrations.csproj b/samples/WebAPI_Dapper/Salutations_Migrations/Salutations_Migrations.csproj
similarity index 100%
rename from samples/WebAPI_Dapper/Salutations_SqliteMigrations/Salutations_SqliteMigrations.csproj
rename to samples/WebAPI_Dapper/Salutations_Migrations/Salutations_Migrations.csproj
diff --git a/samples/WebAPI_Dapper/Salutations_mySqlMigrations/Migrations/20220527_MySqlMigrations.cs b/samples/WebAPI_Dapper/Salutations_mySqlMigrations/Migrations/20220527_MySqlMigrations.cs
deleted file mode 100644
index d1f4e4aec2..0000000000
--- a/samples/WebAPI_Dapper/Salutations_mySqlMigrations/Migrations/20220527_MySqlMigrations.cs
+++ /dev/null
@@ -1,20 +0,0 @@
-using FluentMigrator;
-
-namespace Salutations_mySqlMigrations.Migrations;
-
-[Migration(1)]
-public class MySqlInitialCreate : Migration
-{
- public override void Up()
- {
- Create.Table("Salutation")
- .WithColumn("Id").AsInt32().NotNullable().PrimaryKey().Identity()
- .WithColumn("Greeting").AsString()
- .WithColumn("TimeStamp").AsBinary().WithDefault(SystemMethods.CurrentDateTime);
- }
-
- public override void Down()
- {
- Delete.Table("Salutation");
- }
-}
diff --git a/samples/WebAPI_Dapper/Salutations_mySqlMigrations/Salutations_mySqlMigrations.csproj b/samples/WebAPI_Dapper/Salutations_mySqlMigrations/Salutations_mySqlMigrations.csproj
deleted file mode 100644
index 9107f11c61..0000000000
--- a/samples/WebAPI_Dapper/Salutations_mySqlMigrations/Salutations_mySqlMigrations.csproj
+++ /dev/null
@@ -1,13 +0,0 @@
-
-
-
- net6.0
- enable
- enable
-
-
-
-
-
-
-
diff --git a/samples/WebAPI_Dapper/build.sh b/samples/WebAPI_Dapper/build.sh
deleted file mode 100644
index 052b5d5dc6..0000000000
--- a/samples/WebAPI_Dapper/build.sh
+++ /dev/null
@@ -1,15 +0,0 @@
-pushd GreetingsWeb || exit
-rm -rf out
-dotnet restore
-dotnet build
-dotnet publish -c Release -o out
-docker build .
-popd || exit
-pushd SalutationAnalytics || exit
-rm -rf out
-dotnet restore
-dotnet build
-dotnet publish -c Release -o out
-docker build .
-popd || exit
-
diff --git a/samples/WebAPI_Dapper/docker-compose.yml b/samples/WebAPI_Dapper/docker-compose.yml
deleted file mode 100644
index 703b1a2372..0000000000
--- a/samples/WebAPI_Dapper/docker-compose.yml
+++ /dev/null
@@ -1,51 +0,0 @@
-version: '3.1'
-services:
- web:
- build: ./GreetingsAdapters
- hostname: greetingsapi
- ports:
- - "5000:5000"
- environment:
- - BRIGHTER_ConnectionStrings__Greetings=server=greetings_db; port=3306; uid=root; pwd=root; database=Greetings
- - BRIGHTER_ConnectionStrings__GreetingsDb=server=greetings_db; port=3306; uid=root; pwd=root
- - ASPNETCORE_ENVIRONMENT=Production
- links:
- - mysql:greetings_db
- depends_on:
- - mysql
- - rabbitmq
- worker:
- build: ./GreetingsWatcher
- hostname: greetingsworker
- environment:
- - ASPNETCORE_ENVIRONMENT=Production
- depends_on:
- - rabbitmq
- mysql:
- hostname: greetings_db
- image: mysql
- ports:
- - "3306:3306"
- security_opt:
- - seccomp:unconfined
- volumes:
- - my-db:/var/lib/mysql
- environment:
- MYSQL_ROOT_PASSWORD: "root"
- healthcheck:
- test: mysqladmin ping -h localhost -p$$MYSQL_ROOT_PASSWORD && test '0' -eq $$(ps aux | awk '{print $$11}' | grep -c -e '^mysql$$')
- rabbitmq:
- image: brightercommand/rabbitmq:3.8-management-delay
- ports:
- - "5672:5672"
- - "15672:15672"
- volumes:
- - rabbitmq-home:/var/lib/rabbitmq
-
-volumes:
- rabbitmq-home:
- driver: local
- my-db:
- driver: local
-
-
diff --git a/samples/WebAPI_Dynamo/GreetingsEntities/GreetingsEntities.csproj b/samples/WebAPI_Dynamo/GreetingsEntities/GreetingsEntities.csproj
index 8a9988ee26..73b72cb964 100644
--- a/samples/WebAPI_Dynamo/GreetingsEntities/GreetingsEntities.csproj
+++ b/samples/WebAPI_Dynamo/GreetingsEntities/GreetingsEntities.csproj
@@ -5,7 +5,7 @@
-
+
diff --git a/samples/WebAPI_Dynamo/GreetingsPorts/Handlers/AddGreetingHandlerAsync.cs b/samples/WebAPI_Dynamo/GreetingsPorts/Handlers/AddGreetingHandlerAsync.cs
index 00b1511547..38ed398c55 100644
--- a/samples/WebAPI_Dynamo/GreetingsPorts/Handlers/AddGreetingHandlerAsync.cs
+++ b/samples/WebAPI_Dynamo/GreetingsPorts/Handlers/AddGreetingHandlerAsync.cs
@@ -16,14 +16,14 @@ namespace GreetingsPorts.Handlers
{
public class AddGreetingHandlerAsync: RequestHandlerAsync
{
- private readonly DynamoDbUnitOfWork _unitOfWork;
+ private readonly IAmADynamoDbTransactionProvider _transactionProvider;
private readonly IAmACommandProcessor _postBox;
private readonly ILogger _logger;
- public AddGreetingHandlerAsync(IAmABoxTransactionConnectionProvider uow, IAmACommandProcessor postBox, ILogger logger)
+ public AddGreetingHandlerAsync(IAmADynamoDbTransactionProvider transactionProvider, IAmACommandProcessor postBox, ILogger logger)
{
- _unitOfWork = (DynamoDbUnitOfWork)uow;
+ _transactionProvider = transactionProvider;
_postBox = postBox;
_logger = logger;
}
@@ -36,34 +36,44 @@ public override async Task HandleAsync(AddGreeting addGreeting, Can
//We use the unit of work to grab connection and transaction, because Outbox needs
//to share them 'behind the scenes'
- var context = new DynamoDBContext(_unitOfWork.DynamoDb);
- var transaction = _unitOfWork.BeginOrGetTransaction();
+ var context = new DynamoDBContext(_transactionProvider.DynamoDb);
+ var transaction = await _transactionProvider.GetTransactionAsync(cancellationToken);
try
{
var person = await context.LoadAsync(addGreeting.Name, cancellationToken);
-
+
person.Greetings.Add(addGreeting.Greeting);
var document = context.ToDocument(person);
var attributeValues = document.ToAttributeMap();
-
- //write the added child entity to the Db - just replace the whole entity as we grabbed the original
- //in production code, an update expression would be faster
- transaction.TransactItems.Add(new TransactWriteItem{Put = new Put{TableName = "People", Item = attributeValues}});
+
+ //write the added child entity to the Db - just replace the whole entity as we grabbed the original
+ //in production code, an update expression would be faster
+ transaction.TransactItems.Add(new TransactWriteItem
+ {
+ Put = new Put { TableName = "People", Item = attributeValues }
+ });
//Now write the message we want to send to the Db in the same transaction.
- posts.Add(await _postBox.DepositPostAsync(new GreetingMade(addGreeting.Greeting), cancellationToken: cancellationToken));
-
+ posts.Add(await _postBox.DepositPostAsync(
+ new GreetingMade(addGreeting.Greeting),
+ _transactionProvider,
+ cancellationToken: cancellationToken));
+
//commit both new greeting and outgoing message
- await _unitOfWork.CommitAsync(cancellationToken);
+ await _transactionProvider.CommitAsync(cancellationToken);
}
catch (Exception e)
- {
+ {
_logger.LogError(e, "Exception thrown handling Add Greeting request");
//it went wrong, rollback the entity change and the downstream message
- _unitOfWork.Rollback();
+ await _transactionProvider.RollbackAsync(cancellationToken);
return await base.HandleAsync(addGreeting, cancellationToken);
}
+ finally
+ {
+ _transactionProvider.Close();
+ }
//Send this message via a transport. We need the ids to send just the messages here, not all outstanding ones.
//Alternatively, you can let the Sweeper do this, but at the cost of increased latency
diff --git a/samples/WebAPI_Dynamo/GreetingsPorts/Handlers/AddPersonHandlerAsync.cs b/samples/WebAPI_Dynamo/GreetingsPorts/Handlers/AddPersonHandlerAsync.cs
index fdced54ece..90df9aa8ad 100644
--- a/samples/WebAPI_Dynamo/GreetingsPorts/Handlers/AddPersonHandlerAsync.cs
+++ b/samples/WebAPI_Dynamo/GreetingsPorts/Handlers/AddPersonHandlerAsync.cs
@@ -1,6 +1,7 @@
using System.Threading;
using System.Threading.Tasks;
using Amazon.DynamoDBv2.DataModel;
+using Amazon.DynamoDBv2.Model;
using GreetingsEntities;
using GreetingsPorts.Requests;
using Paramore.Brighter;
@@ -12,18 +13,18 @@ namespace GreetingsPorts.Handlers
{
public class AddPersonHandlerAsync : RequestHandlerAsync
{
- private readonly DynamoDbUnitOfWork _dynamoDbUnitOfWork;
+ private readonly IAmADynamoDbConnectionProvider _dynamoDbConnectionProvider;
- public AddPersonHandlerAsync(IAmABoxTransactionConnectionProvider dynamoDbUnitOfWork)
+ public AddPersonHandlerAsync(IAmADynamoDbConnectionProvider dynamoDbConnectionProvider)
{
- _dynamoDbUnitOfWork = (DynamoDbUnitOfWork )dynamoDbUnitOfWork;
+ _dynamoDbConnectionProvider = dynamoDbConnectionProvider;
}
[RequestLoggingAsync(0, HandlerTiming.Before)]
[UsePolicyAsync(step:1, policy: Policies.Retry.EXPONENTIAL_RETRYPOLICYASYNC)]
public override async Task HandleAsync(AddPerson addPerson, CancellationToken cancellationToken = default)
{
- var context = new DynamoDBContext(_dynamoDbUnitOfWork.DynamoDb);
+ var context = new DynamoDBContext(_dynamoDbConnectionProvider.DynamoDb);
await context.SaveAsync(new Person { Name = addPerson.Name }, cancellationToken);
return await base.HandleAsync(addPerson, cancellationToken);
diff --git a/samples/WebAPI_Dynamo/GreetingsPorts/Handlers/DeletePersonHandlerAsync.cs b/samples/WebAPI_Dynamo/GreetingsPorts/Handlers/DeletePersonHandlerAsync.cs
index bc63f0be3e..af1fdbecc2 100644
--- a/samples/WebAPI_Dynamo/GreetingsPorts/Handlers/DeletePersonHandlerAsync.cs
+++ b/samples/WebAPI_Dynamo/GreetingsPorts/Handlers/DeletePersonHandlerAsync.cs
@@ -1,6 +1,7 @@
using System.Threading;
using System.Threading.Tasks;
using Amazon.DynamoDBv2.DataModel;
+using Amazon.DynamoDBv2.Model;
using GreetingsPorts.Requests;
using Paramore.Brighter;
using Paramore.Brighter.DynamoDb;
@@ -11,18 +12,18 @@ namespace GreetingsPorts.Handlers
{
public class DeletePersonHandlerAsync : RequestHandlerAsync
{
- private readonly DynamoDbUnitOfWork _unitOfWork;
+ private readonly IAmADynamoDbConnectionProvider _dynamoDbConnectionProvider;
- public DeletePersonHandlerAsync(IAmABoxTransactionConnectionProvider unitOfWork)
+ public DeletePersonHandlerAsync(IAmADynamoDbConnectionProvider dynamoDbConnectionProvider)
{
- _unitOfWork = (DynamoDbUnitOfWork)unitOfWork;
+ _dynamoDbConnectionProvider = dynamoDbConnectionProvider;
}
[RequestLoggingAsync(0, HandlerTiming.Before)]
[UsePolicyAsync(step:1, policy: Policies.Retry.EXPONENTIAL_RETRYPOLICYASYNC)]
public override async Task HandleAsync(DeletePerson deletePerson, CancellationToken cancellationToken = default)
{
- var context = new DynamoDBContext(_unitOfWork.DynamoDb);
+ var context = new DynamoDBContext(_dynamoDbConnectionProvider.DynamoDb);
await context.DeleteAsync(deletePerson.Name, cancellationToken);
return await base.HandleAsync(deletePerson, cancellationToken);
diff --git a/samples/WebAPI_Dynamo/GreetingsPorts/Handlers/FIndGreetingsForPersonHandlerAsync.cs b/samples/WebAPI_Dynamo/GreetingsPorts/Handlers/FIndGreetingsForPersonHandlerAsync.cs
index aecaae4237..feb9290b80 100644
--- a/samples/WebAPI_Dynamo/GreetingsPorts/Handlers/FIndGreetingsForPersonHandlerAsync.cs
+++ b/samples/WebAPI_Dynamo/GreetingsPorts/Handlers/FIndGreetingsForPersonHandlerAsync.cs
@@ -2,6 +2,7 @@
using System.Threading;
using System.Threading.Tasks;
using Amazon.DynamoDBv2.DataModel;
+using Amazon.DynamoDBv2.Model;
using GreetingsEntities;
using GreetingsPorts.Policies;
using GreetingsPorts.Requests;
@@ -16,18 +17,18 @@ namespace GreetingsPorts.Handlers
{
public class FIndGreetingsForPersonHandlerAsync : QueryHandlerAsync
{
- private readonly DynamoDbUnitOfWork _unitOfWork;
+ private readonly IAmADynamoDbConnectionProvider _dynamoDbConnectionProvider;
- public FIndGreetingsForPersonHandlerAsync(IAmABoxTransactionConnectionProvider unitOfWork)
+ public FIndGreetingsForPersonHandlerAsync(IAmADynamoDbConnectionProvider dynamoDbConnectionProvider)
{
- _unitOfWork = (DynamoDbUnitOfWork ) unitOfWork;
+ _dynamoDbConnectionProvider = dynamoDbConnectionProvider;
}
[QueryLogging(0)]
[RetryableQuery(1, Retry.EXPONENTIAL_RETRYPOLICYASYNC)]
public override async Task ExecuteAsync(FindGreetingsForPerson query, CancellationToken cancellationToken = new CancellationToken())
{
- var context = new DynamoDBContext(_unitOfWork.DynamoDb);
+ var context = new DynamoDBContext(_dynamoDbConnectionProvider.DynamoDb);
var person = await context.LoadAsync(query.Name, cancellationToken);
diff --git a/samples/WebAPI_Dynamo/GreetingsPorts/Handlers/FindPersonByNameHandlerAsync.cs b/samples/WebAPI_Dynamo/GreetingsPorts/Handlers/FindPersonByNameHandlerAsync.cs
index 0a4f5b0e6b..b12ab3abc6 100644
--- a/samples/WebAPI_Dynamo/GreetingsPorts/Handlers/FindPersonByNameHandlerAsync.cs
+++ b/samples/WebAPI_Dynamo/GreetingsPorts/Handlers/FindPersonByNameHandlerAsync.cs
@@ -3,6 +3,7 @@
using System.Threading;
using System.Threading.Tasks;
using Amazon.DynamoDBv2.DataModel;
+using Amazon.DynamoDBv2.Model;
using GreetingsEntities;
using GreetingsPorts.Policies;
using GreetingsPorts.Requests;
@@ -17,18 +18,18 @@ namespace GreetingsPorts.Handlers
{
public class FindPersonByNameHandlerAsync : QueryHandlerAsync
{
- private readonly DynamoDbUnitOfWork _unitOfWork;
+ private readonly IAmADynamoDbConnectionProvider _dynamoDbConnectionProvider;
- public FindPersonByNameHandlerAsync(IAmABoxTransactionConnectionProvider unitOfWork)
+ public FindPersonByNameHandlerAsync(IAmADynamoDbConnectionProvider dynamoDbConnectionProvider)
{
- _unitOfWork = (DynamoDbUnitOfWork ) unitOfWork;
+ _dynamoDbConnectionProvider = dynamoDbConnectionProvider;
}
[QueryLogging(0)]
[RetryableQuery(1, Retry.EXPONENTIAL_RETRYPOLICYASYNC)]
public override async Task ExecuteAsync(FindPersonByName query, CancellationToken cancellationToken = new CancellationToken())
{
- var context = new DynamoDBContext(_unitOfWork.DynamoDb);
+ var context = new DynamoDBContext(_dynamoDbConnectionProvider.DynamoDb);
var person = await context.LoadAsync(query.Name, cancellationToken);
diff --git a/samples/WebAPI_Dynamo/GreetingsWeb/Startup.cs b/samples/WebAPI_Dynamo/GreetingsWeb/Startup.cs
index dbd582526a..5784b301fe 100644
--- a/samples/WebAPI_Dynamo/GreetingsWeb/Startup.cs
+++ b/samples/WebAPI_Dynamo/GreetingsWeb/Startup.cs
@@ -30,7 +30,8 @@ public class Startup
{
private const string _outBoxTableName = "Outbox";
private IWebHostEnvironment _env;
-
+ private IAmazonDynamoDB _client;
+
public Startup(IConfiguration configuration, IWebHostEnvironment env)
{
Configuration = configuration;
@@ -79,9 +80,9 @@ public void ConfigureServices(IServiceCollection services)
private void ConfigureDynamo(IServiceCollection services)
{
- IAmazonDynamoDB client = CreateAndRegisterClient(services);
- CreateEntityStore(client);
- CreateOutbox(client, services);
+ _client = CreateAndRegisterClient(services);
+ CreateEntityStore();
+ CreateOutbox(services);
}
private IAmazonDynamoDB CreateAndRegisterClient(IServiceCollection services)
@@ -106,9 +107,6 @@ private IAmazonDynamoDB CreateAndRegisterLocalClient(IServiceCollection services
var dynamoDb = new AmazonDynamoDBClient(credentials, clientConfig);
services.Add(new ServiceDescriptor(typeof(IAmazonDynamoDB), dynamoDb));
- var dynamoDbConfiguration = new DynamoDbConfiguration();
- services.Add(new ServiceDescriptor(typeof(DynamoDbConfiguration), dynamoDbConfiguration));
-
return dynamoDb;
}
@@ -117,10 +115,10 @@ private IAmazonDynamoDB CreateAndRegisterRemoteClient(IServiceCollection service
throw new NotImplementedException();
}
- private void CreateEntityStore(IAmazonDynamoDB client)
+ private void CreateEntityStore()
{
var tableRequestFactory = new DynamoDbTableFactory();
- var dbTableBuilder = new DynamoDbTableBuilder(client);
+ var dbTableBuilder = new DynamoDbTableBuilder(_client);
CreateTableRequest tableRequest = tableRequestFactory.GenerateCreateTableRequest(
new DynamoDbCreateProvisionedThroughput
@@ -138,10 +136,10 @@ private void CreateEntityStore(IAmazonDynamoDB client)
}
}
- private void CreateOutbox(IAmazonDynamoDB client, IServiceCollection services)
+ private void CreateOutbox(IServiceCollection services)
{
var tableRequestFactory = new DynamoDbTableFactory();
- var dbTableBuilder = new DynamoDbTableBuilder(client);
+ var dbTableBuilder = new DynamoDbTableBuilder(_client);
var createTableRequest = new DynamoDbTableFactory().GenerateCreateTableRequest(
new DynamoDbCreateProvisionedThroughput(
@@ -163,6 +161,24 @@ private void CreateOutbox(IAmazonDynamoDB client, IServiceCollection services)
private void ConfigureBrighter(IServiceCollection services)
{
+ var producerRegistry = new RmqProducerRegistryFactory(
+ new RmqMessagingGatewayConnection
+ {
+ AmpqUri = new AmqpUriSpecification(new Uri("amqp://guest:guest@localhost:5672")),
+ Exchange = new Exchange("paramore.brighter.exchange"),
+ },
+ new RmqPublication[]{
+ new RmqPublication
+ {
+ Topic = new RoutingKey("GreetingMade"),
+ MaxOutStandingMessages = 5,
+ MaxOutStandingCheckIntervalMilliSeconds = 500,
+ WaitForConfirmsTimeOutInMilliseconds = 1000,
+ OutBoxBag = new Dictionary {{"Topic", "GreetingMade"}},
+ MakeChannels = OnMissingChannel.Create
+ }}
+ ).Create();
+
services.AddBrighter(options =>
{
//we want to use scoped, so make sure everything understands that which needs to
@@ -171,26 +187,13 @@ private void ConfigureBrighter(IServiceCollection services)
options.MapperLifetime = ServiceLifetime.Singleton;
options.PolicyRegistry = new GreetingsPolicy();
})
- .UseExternalBus(new RmqProducerRegistryFactory(
- new RmqMessagingGatewayConnection
- {
- AmpqUri = new AmqpUriSpecification(new Uri("amqp://guest:guest@localhost:5672")),
- Exchange = new Exchange("paramore.brighter.exchange"),
- },
- new RmqPublication[]{
- new RmqPublication
- {
- Topic = new RoutingKey("GreetingMade"),
- MaxOutStandingMessages = 5,
- MaxOutStandingCheckIntervalMilliSeconds = 500,
- WaitForConfirmsTimeOutInMilliseconds = 1000,
- OutBoxBag = new Dictionary {{"Topic", "GreetingMade"}},
- MakeChannels = OnMissingChannel.Create
- }}
- ).Create()
- )
- .UseDynamoDbOutbox(ServiceLifetime.Singleton)
- .UseDynamoDbTransactionConnectionProvider(typeof(DynamoDbUnitOfWork), ServiceLifetime.Scoped)
+ .UseExternalBus((configure) =>
+ {
+ configure.ProducerRegistry = producerRegistry;
+ configure.Outbox = new DynamoDbOutbox(_client, new DynamoDbConfiguration());
+ configure.ConnectionProvider = typeof(DynamoDbUnitOfWork);
+ configure.TransactionProvider = typeof(DynamoDbUnitOfWork);
+ })
.UseOutboxSweeper(options => { options.Args.Add("Topic", "GreetingMade"); })
.AutoFromAssemblies(typeof(AddPersonHandlerAsync).Assembly);
}
diff --git a/samples/WebAPI_Dynamo/SalutationAnalytics/Program.cs b/samples/WebAPI_Dynamo/SalutationAnalytics/Program.cs
index 7c7ef23f26..22dec0b091 100644
--- a/samples/WebAPI_Dynamo/SalutationAnalytics/Program.cs
+++ b/samples/WebAPI_Dynamo/SalutationAnalytics/Program.cs
@@ -69,7 +69,7 @@ private static void ConfigureBrighter(
new SubscriptionName("paramore.sample.salutationanalytics"),
new ChannelName("SalutationAnalytics"),
new RoutingKey("GreetingMade"),
- runAsync: true,
+ runAsync: false,
timeoutInMilliseconds: 200,
isDurable: true,
makeChannels: OnMissingChannel.Create), //change to OnMissingChannel.Validate if you have infrastructure declared elsewhere
@@ -84,6 +84,21 @@ private static void ConfigureBrighter(
var rmqMessageConsumerFactory = new RmqMessageConsumerFactory(rmqConnection);
+ var producerRegistry = new RmqProducerRegistryFactory(
+ rmqConnection,
+ new RmqPublication[]
+ {
+ new RmqPublication
+ {
+ Topic = new RoutingKey("SalutationReceived"),
+ MaxOutStandingMessages = 5,
+ MaxOutStandingCheckIntervalMilliSeconds = 500,
+ WaitForConfirmsTimeOutInMilliseconds = 1000,
+ MakeChannels = OnMissingChannel.Create
+ }
+ }
+ ).Create();
+
services.AddServiceActivator(options =>
{
options.Subscriptions = subscriptions;
@@ -93,38 +108,27 @@ private static void ConfigureBrighter(
options.MapperLifetime = ServiceLifetime.Singleton;
options.CommandProcessorLifetime = ServiceLifetime.Scoped;
options.PolicyRegistry = new SalutationPolicy();
+ options.InboxConfiguration = new InboxConfiguration(
+ ConfigureInbox(awsCredentials, dynamoDb),
+ scope: InboxScope.Commands,
+ onceOnly: true,
+ actionOnExists: OnceOnlyAction.Throw
+ );
})
.ConfigureJsonSerialisation((options) =>
{
//We don't strictly need this, but added as an example
options.PropertyNameCaseInsensitive = true;
})
- .UseExternalBus(new RmqProducerRegistryFactory(
- rmqConnection,
- new RmqPublication[]
- {
- new RmqPublication
- {
- Topic = new RoutingKey("SalutationReceived"),
- MaxOutStandingMessages = 5,
- MaxOutStandingCheckIntervalMilliSeconds = 500,
- WaitForConfirmsTimeOutInMilliseconds = 1000,
- MakeChannels = OnMissingChannel.Create
- }
- }
- ).Create()
- )
- .AutoFromAssemblies()
- .UseExternalInbox(
- ConfigureInbox(awsCredentials, dynamoDb),
- new InboxConfiguration(
- scope: InboxScope.Commands,
- onceOnly: true,
- actionOnExists: OnceOnlyAction.Throw
- )
+ .UseExternalBus((configure) =>
+ {
+ configure.ProducerRegistry = producerRegistry;
+ configure.Outbox = ConfigureOutbox(awsCredentials, dynamoDb);
+ configure.ConnectionProvider = typeof(DynamoDbUnitOfWork);
+ configure.TransactionProvider = typeof(DynamoDbUnitOfWork);
+ }
)
- .UseExternalOutbox(ConfigureOutbox(awsCredentials, dynamoDb))
- .UseDynamoDbTransactionConnectionProvider(typeof(DynamoDbUnitOfWork), ServiceLifetime.Scoped);
+ .AutoFromAssemblies();
services.AddHostedService();
}
@@ -240,7 +244,7 @@ private static IAmAnInbox ConfigureInbox(AWSCredentials credentials, IAmazonDyna
return new DynamoDbInbox(dynamoDb, new DynamoDbInboxConfiguration(credentials, RegionEndpoint.EUWest1));
}
- private static IAmAnOutbox ConfigureOutbox(AWSCredentials credentials, IAmazonDynamoDB dynamoDb)
+ private static IAmAnOutbox ConfigureOutbox(AWSCredentials credentials, IAmazonDynamoDB dynamoDb)
{
return new DynamoDbOutbox(dynamoDb, new DynamoDbConfiguration(credentials, RegionEndpoint.EUWest1));
}
diff --git a/samples/WebAPI_Dynamo/SalutationEntities/SalutationEntities.csproj b/samples/WebAPI_Dynamo/SalutationEntities/SalutationEntities.csproj
index 3e0ea19c37..4d137ef3cd 100644
--- a/samples/WebAPI_Dynamo/SalutationEntities/SalutationEntities.csproj
+++ b/samples/WebAPI_Dynamo/SalutationEntities/SalutationEntities.csproj
@@ -5,7 +5,7 @@
-
+
diff --git a/samples/WebAPI_Dynamo/SalutationPorts/Handlers/GreetingMadeHandler.cs b/samples/WebAPI_Dynamo/SalutationPorts/Handlers/GreetingMadeHandler.cs
index 4fd22a6af0..89a91a2802 100644
--- a/samples/WebAPI_Dynamo/SalutationPorts/Handlers/GreetingMadeHandler.cs
+++ b/samples/WebAPI_Dynamo/SalutationPorts/Handlers/GreetingMadeHandler.cs
@@ -7,6 +7,7 @@
using Microsoft.Extensions.Logging;
using Paramore.Brighter;
using Paramore.Brighter.DynamoDb;
+using Paramore.Brighter.Inbox.Attributes;
using Paramore.Brighter.Logging.Attributes;
using Paramore.Brighter.Policies.Attributes;
using SalutationEntities;
@@ -14,49 +15,56 @@
namespace SalutationPorts.Handlers
{
- public class GreetingMadeHandlerAsync : RequestHandlerAsync
+ public class GreetingMadeHandler : RequestHandler
{
- private readonly DynamoDbUnitOfWork _uow;
+ private readonly IAmADynamoDbTransactionProvider _transactionProvider;
private readonly IAmACommandProcessor _postBox;
- private readonly ILogger _logger;
+ private readonly ILogger _logger;
- public GreetingMadeHandlerAsync(IAmABoxTransactionConnectionProvider uow, IAmACommandProcessor postBox, ILogger logger)
+ public GreetingMadeHandler(IAmADynamoDbTransactionProvider transactionProvider, IAmACommandProcessor postBox, ILogger logger)
{
- _uow = (DynamoDbUnitOfWork)uow;
+ _transactionProvider = transactionProvider;
_postBox = postBox;
_logger = logger;
}
- //[UseInboxAsync(step:0, contextKey: typeof(GreetingMadeHandlerAsync), onceOnly: true )] -- we are using a global inbox, so need to be explicit!!
- [RequestLoggingAsync(step: 1, timing: HandlerTiming.Before)]
- [UsePolicyAsync(step:2, policy: Policies.Retry.EXPONENTIAL_RETRYPOLICYASYNC)]
- public override async Task HandleAsync(GreetingMade @event, CancellationToken cancellationToken = default)
+ [UseInbox(step:0, contextKey: typeof(GreetingMadeHandler), onceOnly: true )]
+ [RequestLogging(step: 1, timing: HandlerTiming.Before)]
+ [UsePolicy(step:2, policy: Policies.Retry.EXPONENTIAL_RETRYPOLICY)]
+ public override GreetingMade Handle(GreetingMade @event)
{
var posts = new List();
- var context = new DynamoDBContext(_uow.DynamoDb);
- var tx = _uow.BeginOrGetTransaction();
+ var context = new DynamoDBContext(_transactionProvider.DynamoDb);
+ var tx = _transactionProvider.GetTransaction();
try
{
- var salutation = new Salutation{ Greeting = @event.Greeting};
+ var salutation = new Salutation { Greeting = @event.Greeting };
var attributes = context.ToDocument(salutation).ToAttributeMap();
-
- tx.TransactItems.Add(new TransactWriteItem{Put = new Put{ TableName = "Salutations", Item = attributes}});
-
- posts.Add(await _postBox.DepositPostAsync(new SalutationReceived(DateTimeOffset.Now), cancellationToken: cancellationToken));
-
- await _uow.CommitAsync(cancellationToken);
+
+ tx.TransactItems.Add(new TransactWriteItem
+ {
+ Put = new Put { TableName = "Salutations", Item = attributes }
+ });
+
+ posts.Add(_postBox.DepositPost(new SalutationReceived(DateTimeOffset.Now), _transactionProvider));
+
+ _transactionProvider.Commit();
}
catch (Exception e)
{
_logger.LogError(e, "Could not save salutation");
- _uow.Rollback();
-
- return await base.HandleAsync(@event, cancellationToken);
+ _transactionProvider.Rollback();
+
+ return base.Handle(@event);
+ }
+ finally
+ {
+ _transactionProvider.Close();
}
- await _postBox.ClearOutboxAsync(posts, cancellationToken: cancellationToken);
+ _postBox.ClearOutboxAsync(posts);
- return await base.HandleAsync(@event, cancellationToken);
+ return base.Handle(@event);
}
}
}
diff --git a/samples/WebAPI_Dynamo/SalutationPorts/Policies/Retry.cs b/samples/WebAPI_Dynamo/SalutationPorts/Policies/Retry.cs
index 4db47aa42d..58013a5a3f 100644
--- a/samples/WebAPI_Dynamo/SalutationPorts/Policies/Retry.cs
+++ b/samples/WebAPI_Dynamo/SalutationPorts/Policies/Retry.cs
@@ -7,21 +7,21 @@ namespace SalutationPorts.Policies
{
public static class Retry
{
- public const string RETRYPOLICYASYNC = "SalutationPorts.Policies.RetryPolicyAsync";
- public const string EXPONENTIAL_RETRYPOLICYASYNC = "SalutationPorts.Policies.ExponenttialRetryPolicyAsync";
+ public const string RETRYPOLICY = "SalutationPorts.Policies.RetryPolicy";
+ public const string EXPONENTIAL_RETRYPOLICY = "SalutationPorts.Policies.ExponenttialRetryPolicy";
- public static AsyncRetryPolicy GetSimpleHandlerRetryPolicy()
+ public static RetryPolicy GetSimpleHandlerRetryPolicy()
{
- return Policy.Handle().WaitAndRetryAsync(new[]
+ return Policy.Handle().WaitAndRetry(new[]
{
TimeSpan.FromMilliseconds(50), TimeSpan.FromMilliseconds(100), TimeSpan.FromMilliseconds(150)
});
}
- public static AsyncRetryPolicy GetExponentialHandlerRetryPolicy()
+ public static RetryPolicy GetExponentialHandlerRetryPolicy()
{
var delay = Backoff.ExponentialBackoff(TimeSpan.FromMilliseconds(100), retryCount: 5, fastFirst: true);
- return Policy.Handle().WaitAndRetryAsync(delay);
+ return Policy.Handle().WaitAndRetry(delay);
}
}
}
diff --git a/samples/WebAPI_Dynamo/SalutationPorts/Policies/SalutationPolicy.cs b/samples/WebAPI_Dynamo/SalutationPorts/Policies/SalutationPolicy.cs
index ddf21c324f..28024f22a7 100644
--- a/samples/WebAPI_Dynamo/SalutationPorts/Policies/SalutationPolicy.cs
+++ b/samples/WebAPI_Dynamo/SalutationPorts/Policies/SalutationPolicy.cs
@@ -11,8 +11,8 @@ public SalutationPolicy()
private void AddSalutationPolicies()
{
- Add(Retry.RETRYPOLICYASYNC, Retry.GetSimpleHandlerRetryPolicy());
- Add(Retry.EXPONENTIAL_RETRYPOLICYASYNC, Retry.GetExponentialHandlerRetryPolicy());
+ Add(Retry.RETRYPOLICY, Retry.GetSimpleHandlerRetryPolicy());
+ Add(Retry.EXPONENTIAL_RETRYPOLICY, Retry.GetExponentialHandlerRetryPolicy());
}
}
}
diff --git a/samples/WebAPI_Dynamo/tests.http b/samples/WebAPI_Dynamo/tests.http
index 26adac670e..ac986c633d 100644
--- a/samples/WebAPI_Dynamo/tests.http
+++ b/samples/WebAPI_Dynamo/tests.http
@@ -23,7 +23,7 @@ POST http://localhost:5000/Greetings/Tyrion/new HTTP/1.1
Content-Type: application/json
{
- "Greeting" : "I drink, and I know things"
+ "Greeting" : "I drink, and I know things #1"
}
### And now look up Tyrion's greetings
diff --git a/samples/WebAPI_EFCore/GreetingsEntities/GreetingsEntities.csproj b/samples/WebAPI_EFCore/GreetingsEntities/GreetingsEntities.csproj
index 4f444d8c8b..7d9ee003f9 100644
--- a/samples/WebAPI_EFCore/GreetingsEntities/GreetingsEntities.csproj
+++ b/samples/WebAPI_EFCore/GreetingsEntities/GreetingsEntities.csproj
@@ -1,7 +1,7 @@
- net6.0
+ net7.0
diff --git a/samples/WebAPI_EFCore/GreetingsPorts/GreetingsPorts.csproj b/samples/WebAPI_EFCore/GreetingsPorts/GreetingsPorts.csproj
index 6ad024cb6a..99b918dfa9 100644
--- a/samples/WebAPI_EFCore/GreetingsPorts/GreetingsPorts.csproj
+++ b/samples/WebAPI_EFCore/GreetingsPorts/GreetingsPorts.csproj
@@ -1,7 +1,7 @@
- net6.0
+ net7.0
diff --git a/samples/WebAPI_EFCore/GreetingsPorts/Handlers/AddGreetingHandlerAsync.cs b/samples/WebAPI_EFCore/GreetingsPorts/Handlers/AddGreetingHandlerAsync.cs
index f7d2599ab5..7115622fd5 100644
--- a/samples/WebAPI_EFCore/GreetingsPorts/Handlers/AddGreetingHandlerAsync.cs
+++ b/samples/WebAPI_EFCore/GreetingsPorts/Handlers/AddGreetingHandlerAsync.cs
@@ -17,12 +17,13 @@ public class AddGreetingHandlerAsync: RequestHandlerAsync
{
private readonly GreetingsEntityGateway _uow;
private readonly IAmACommandProcessor _postBox;
-
- public AddGreetingHandlerAsync(GreetingsEntityGateway uow, IAmACommandProcessor postBox)
+ private readonly IAmATransactionConnectionProvider _transactionProvider;
+
+ public AddGreetingHandlerAsync(GreetingsEntityGateway uow, IAmATransactionConnectionProvider provider, IAmACommandProcessor postBox)
{
_uow = uow;
_postBox = postBox;
-
+ _transactionProvider = provider;
}
[RequestLoggingAsync(0, HandlerTiming.Before)]
[UsePolicyAsync(step:1, policy: Policies.Retry.EXPONENTIAL_RETRYPOLICYASYNC)]
@@ -30,33 +31,39 @@ public override async Task HandleAsync(AddGreeting addGreeting, Can
{
var posts = new List();
- //We span a Db outside of EF's control, so start an explicit transactional scope
- var tx = await _uow.Database.BeginTransactionAsync(cancellationToken);
+ await _transactionProvider.GetTransactionAsync(cancellationToken);
try
{
var person = await _uow.People
.Where(p => p.Name == addGreeting.Name)
.SingleAsync(cancellationToken);
-
+
var greeting = new Greeting(addGreeting.Greeting);
-
+
person.AddGreeting(greeting);
-
+
//Now write the message we want to send to the Db in the same transaction.
- posts.Add(await _postBox.DepositPostAsync(new GreetingMade(greeting.Greet()), cancellationToken: cancellationToken));
-
+ posts.Add(await _postBox.DepositPostAsync(
+ new GreetingMade(greeting.Greet()),
+ _transactionProvider,
+ cancellationToken: cancellationToken));
+
//write the changed entity to the Db
await _uow.SaveChangesAsync(cancellationToken);
//write new person and the associated message to the Db
- await tx.CommitAsync(cancellationToken);
+ await _transactionProvider.CommitAsync(cancellationToken);
}
catch (Exception)
{
//it went wrong, rollback the entity change and the downstream message
- await tx.RollbackAsync(cancellationToken);
+ await _transactionProvider.RollbackAsync(cancellationToken);
return await base.HandleAsync(addGreeting, cancellationToken);
}
+ finally
+ {
+ _transactionProvider.Close();
+ }
//Send this message via a transport. We need the ids to send just the messages here, not all outstanding ones.
//Alternatively, you can let the Sweeper do this, but at the cost of increased latency
diff --git a/samples/WebAPI_EFCore/GreetingsWeb/Database/SchemaCreation.cs b/samples/WebAPI_EFCore/GreetingsWeb/Database/SchemaCreation.cs
index d1a493e9f8..fe4bccbfa0 100644
--- a/samples/WebAPI_EFCore/GreetingsWeb/Database/SchemaCreation.cs
+++ b/samples/WebAPI_EFCore/GreetingsWeb/Database/SchemaCreation.cs
@@ -140,7 +140,8 @@ private static void CreateOutboxProduction(string connectionString)
using var existsQuery = sqlConnection.CreateCommand();
existsQuery.CommandText = MySqlOutboxBuilder.GetExistsQuery(OUTBOX_TABLE_NAME);
- bool exists = existsQuery.ExecuteScalar() != null;
+ var findOutbox = existsQuery.ExecuteScalar();
+ bool exists = findOutbox is long and > 0;
if (exists) return;
diff --git a/samples/WebAPI_EFCore/GreetingsWeb/GreetingsWeb.csproj b/samples/WebAPI_EFCore/GreetingsWeb/GreetingsWeb.csproj
index 68d27f0623..b498df8fbe 100644
--- a/samples/WebAPI_EFCore/GreetingsWeb/GreetingsWeb.csproj
+++ b/samples/WebAPI_EFCore/GreetingsWeb/GreetingsWeb.csproj
@@ -1,11 +1,11 @@
- net6.0
+ net7.0
-
+
diff --git a/samples/WebAPI_EFCore/GreetingsWeb/Startup.cs b/samples/WebAPI_EFCore/GreetingsWeb/Startup.cs
index 3028c0341e..60376552e2 100644
--- a/samples/WebAPI_EFCore/GreetingsWeb/Startup.cs
+++ b/samples/WebAPI_EFCore/GreetingsWeb/Startup.cs
@@ -1,13 +1,9 @@
using System;
-using System.Data;
-using System.Data.Common;
using GreetingsPorts.EntityGateway;
using GreetingsPorts.Handlers;
using GreetingsPorts.Policies;
-using Hellang.Middleware.ProblemDetails;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
-using Microsoft.Data.Sqlite;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
@@ -28,7 +24,6 @@
using Paramore.Darker.Policies;
using Paramore.Darker.QueryLogging;
using Polly;
-using Polly.Registry;
namespace GreetingsWeb
{
@@ -36,7 +31,7 @@ public class Startup
{
private const string _outBoxTableName = "Outbox";
private IWebHostEnvironment _env;
-
+
public Startup(IConfiguration configuration, IWebHostEnvironment env)
{
Configuration = configuration;
@@ -48,8 +43,6 @@ public Startup(IConfiguration configuration, IWebHostEnvironment env)
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
- app.UseProblemDetails();
-
if (env.IsDevelopment())
{
app.UseSwagger();
@@ -82,16 +75,17 @@ public void ConfigureServices(IServiceCollection services)
ConfigureBrighter(services);
ConfigureDarker(services);
}
-
+
private void CheckDbIsUp()
{
string connectionString = DbConnectionString();
-
+
var policy = Policy.Handle().WaitAndRetryForever(
retryAttempt => TimeSpan.FromSeconds(2),
(exception, timespan) =>
{
- Console.WriteLine($"Healthcheck: Waiting for the database {connectionString} to come online - {exception.Message}");
+ Console.WriteLine(
+ $"Healthcheck: Waiting for the database {connectionString} to come online - {exception.Message}");
});
policy.Execute(() =>
@@ -109,73 +103,35 @@ private void CheckDbIsUp()
private void ConfigureBrighter(IServiceCollection services)
{
- if (_env.IsDevelopment())
- {
- services.AddBrighter(options =>
- {
- //we want to use scoped, so make sure everything understands that which needs to
- options.HandlerLifetime = ServiceLifetime.Scoped;
- options.CommandProcessorLifetime = ServiceLifetime.Scoped;
- options.MapperLifetime = ServiceLifetime.Singleton;
- options.PolicyRegistry = new GreetingsPolicy();
- })
- .UseExternalBus(new RmqProducerRegistryFactory(
- new RmqMessagingGatewayConnection
- {
- AmpqUri = new AmqpUriSpecification(new Uri("amqp://guest:guest@localhost:5672")),
- Exchange = new Exchange("paramore.brighter.exchange"),
- },
- new RmqPublication[]{
- new RmqPublication
- {
- Topic = new RoutingKey("GreetingMade"),
- MaxOutStandingMessages = 5,
- MaxOutStandingCheckIntervalMilliSeconds = 500,
- WaitForConfirmsTimeOutInMilliseconds = 1000,
- MakeChannels = OnMissingChannel.Create
- }}
- ).Create()
- )
- .UseSqliteOutbox(new SqliteConfiguration(DbConnectionString(), _outBoxTableName), typeof(SqliteConnectionProvider), ServiceLifetime.Singleton)
- .UseSqliteTransactionConnectionProvider(typeof(SqliteEntityFrameworkConnectionProvider), ServiceLifetime.Scoped)
- .UseOutboxSweeper(options =>
- {
- options.TimerInterval = 5;
- options.MinimumMessageAge = 5000;
- })
- .AutoFromAssemblies();
- }
- else
- {
- services.AddBrighter(options =>
- {
- options.HandlerLifetime = ServiceLifetime.Scoped;
- options.MapperLifetime = ServiceLifetime.Singleton;
- options.PolicyRegistry = new GreetingsPolicy();
- })
- .UseExternalBus(new RmqProducerRegistryFactory(
- new RmqMessagingGatewayConnection
- {
- AmpqUri = new AmqpUriSpecification(new Uri("amqp://guest:guest@rabbitmq:5672")),
- Exchange = new Exchange("paramore.brighter.exchange"),
- },
- new RmqPublication[] {
- new RmqPublication
- {
- Topic = new RoutingKey("GreetingMade"),
- MaxOutStandingMessages = 5,
- MaxOutStandingCheckIntervalMilliSeconds = 500,
- WaitForConfirmsTimeOutInMilliseconds = 1000,
- MakeChannels = OnMissingChannel.Create
- }}
- ).Create()
- )
- .UseMySqlOutbox(new MySqlConfiguration(DbConnectionString(), _outBoxTableName), typeof(MySqlConnectionProvider), ServiceLifetime.Singleton)
- .UseMySqTransactionConnectionProvider(typeof(MySqlEntityFrameworkConnectionProvider), ServiceLifetime.Scoped)
- .UseOutboxSweeper()
- .AutoFromAssemblies();
- }
+ (IAmAnOutbox outbox, Type transactionProvider, Type connectionProvider) = MakeOutbox();
+ var outboxConfiguration = new RelationalDatabaseConfiguration(DbConnectionString());
+ services.AddSingleton(outboxConfiguration);
+
+ IAmAProducerRegistry producerRegistry = ConfigureProducerRegistry();
+ services.AddBrighter(options =>
+ {
+ //we want to use scoped, so make sure everything understands that which needs to
+ options.HandlerLifetime = ServiceLifetime.Scoped;
+ options.CommandProcessorLifetime = ServiceLifetime.Scoped;
+ options.MapperLifetime = ServiceLifetime.Singleton;
+ options.PolicyRegistry = new GreetingsPolicy();
+ })
+ .UseExternalBus((configure) =>
+ {
+ configure.ProducerRegistry = producerRegistry;
+ configure.Outbox = outbox;
+ configure.TransactionProvider = transactionProvider;
+ configure.ConnectionProvider = connectionProvider;
+ }
+ )
+ .UseOutboxSweeper(options =>
+ {
+ options.TimerInterval = 5;
+ options.MinimumMessageAge = 5000;
+ })
+ .UseOutboxSweeper()
+ .AutoFromAssemblies();
}
private void ConfigureDarker(IServiceCollection services)
@@ -188,7 +144,6 @@ private void ConfigureDarker(IServiceCollection services)
.AddHandlersFromAssemblies(typeof(FindPersonByNameHandlerAsync).Assembly)
.AddJsonQueryLogging()
.AddPolicies(new GreetingsPolicy());
-
}
private void ConfigureEFCore(IServiceCollection services)
@@ -200,33 +155,79 @@ private void ConfigureEFCore(IServiceCollection services)
services.AddDbContext(
builder =>
{
- builder.UseSqlite(connectionString,
+ builder.UseSqlite(connectionString,
optionsBuilder =>
{
optionsBuilder.MigrationsAssembly("Greetings_SqliteMigrations");
- });
+ })
+ .EnableDetailedErrors()
+ .EnableSensitiveDataLogging();
});
}
else
{
- services.AddDbContextPool(builder =>
- {
- builder
- .UseMySql(connectionString, ServerVersion.AutoDetect(connectionString), optionsBuilder =>
- {
- optionsBuilder.MigrationsAssembly("Greetings_MySqlMigrations");
- })
- .EnableDetailedErrors()
- .EnableSensitiveDataLogging();
- });
+ services.AddDbContextPool(builder =>
+ {
+ builder
+ .UseMySql(connectionString, ServerVersion.AutoDetect(connectionString), optionsBuilder =>
+ {
+ optionsBuilder.MigrationsAssembly("Greetings_MySqlMigrations");
+ })
+ .EnableDetailedErrors()
+ .EnableSensitiveDataLogging();
+ });
}
}
+ private static IAmAProducerRegistry ConfigureProducerRegistry()
+ {
+ var producerRegistry = new RmqProducerRegistryFactory(
+ new RmqMessagingGatewayConnection
+ {
+ AmpqUri = new AmqpUriSpecification(new Uri("amqp://guest:guest@localhost:5672")),
+ Exchange = new Exchange("paramore.brighter.exchange"),
+ },
+ new RmqPublication[]
+ {
+ new RmqPublication
+ {
+ Topic = new RoutingKey("GreetingMade"),
+ MaxOutStandingMessages = 5,
+ MaxOutStandingCheckIntervalMilliSeconds = 500,
+ WaitForConfirmsTimeOutInMilliseconds = 1000,
+ MakeChannels = OnMissingChannel.Create
+ }
+ }
+ ).Create();
+ return producerRegistry;
+ }
private string DbConnectionString()
{
//NOTE: Sqlite needs to use a shared cache to allow Db writes to the Outbox as well as entities
- return _env.IsDevelopment() ? "Filename=Greetings.db;Cache=Shared" : Configuration.GetConnectionString("Greetings");
+ return _env.IsDevelopment()
+ ? "Filename=Greetings.db;Cache=Shared"
+ : Configuration.GetConnectionString("Greetings");
+ }
+
+ private (IAmAnOutbox outbox, Type transactionProvider, Type connectionProvider) MakeOutbox()
+ {
+ if (_env.IsDevelopment())
+ {
+ var outbox = new SqliteOutbox(
+ new RelationalDatabaseConfiguration(DbConnectionString(), _outBoxTableName));
+ var transactionProvider = typeof(SqliteEntityFrameworkConnectionProvider);
+ var connectionProvider = typeof(SqliteConnectionProvider);
+ return (outbox, transactionProvider, connectionProvider);
+ }
+ else
+ {
+ var outbox = new MySqlOutbox(
+ new RelationalDatabaseConfiguration(DbConnectionString(), _outBoxTableName));
+ var transactionProvider = typeof(MySqlEntityFrameworkConnectionProvider);
+ var connectionProvider = typeof(MySqlConnectionProvider);
+ return (outbox, transactionProvider, connectionProvider);
+ }
}
}
}
diff --git a/samples/WebAPI_EFCore/Greetings_MySqlMigrations/Greetings_MySqlMigrations.csproj b/samples/WebAPI_EFCore/Greetings_MySqlMigrations/Greetings_MySqlMigrations.csproj
index fab1b18a4a..cf93cb986b 100644
--- a/samples/WebAPI_EFCore/Greetings_MySqlMigrations/Greetings_MySqlMigrations.csproj
+++ b/samples/WebAPI_EFCore/Greetings_MySqlMigrations/Greetings_MySqlMigrations.csproj
@@ -1,7 +1,7 @@
- net6.0
+ net7.0
diff --git a/samples/WebAPI_EFCore/Greetings_MySqlMigrations/Migrations/20210920201732_InitialCreate.Designer.cs b/samples/WebAPI_EFCore/Greetings_MySqlMigrations/Migrations/20210920201732_InitialCreate.Designer.cs
index 5a1e895969..2652d4af8a 100644
--- a/samples/WebAPI_EFCore/Greetings_MySqlMigrations/Migrations/20210920201732_InitialCreate.Designer.cs
+++ b/samples/WebAPI_EFCore/Greetings_MySqlMigrations/Migrations/20210920201732_InitialCreate.Designer.cs
@@ -10,7 +10,7 @@ namespace Greetings_MySqlMigrations.Migrations
{
[DbContext(typeof(GreetingsEntityGateway))]
[Migration("20210920201732_InitialCreate")]
- partial class InitialCreate
+ partial class MySqlInitialCreate
{
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
diff --git a/samples/WebAPI_EFCore/Greetings_MySqlMigrations/Migrations/20210920201732_InitialCreate.cs b/samples/WebAPI_EFCore/Greetings_MySqlMigrations/Migrations/20210920201732_InitialCreate.cs
index 60611e06ce..ddcd96f016 100644
--- a/samples/WebAPI_EFCore/Greetings_MySqlMigrations/Migrations/20210920201732_InitialCreate.cs
+++ b/samples/WebAPI_EFCore/Greetings_MySqlMigrations/Migrations/20210920201732_InitialCreate.cs
@@ -4,7 +4,7 @@
namespace Greetings_MySqlMigrations.Migrations
{
- public partial class InitialCreate : Migration
+ public partial class MySqlInitialCreate : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
diff --git a/samples/WebAPI_EFCore/Greetings_SqliteMigrations/Greetings_SqliteMigrations.csproj b/samples/WebAPI_EFCore/Greetings_SqliteMigrations/Greetings_SqliteMigrations.csproj
index e347ce3528..14afa7dfbb 100644
--- a/samples/WebAPI_EFCore/Greetings_SqliteMigrations/Greetings_SqliteMigrations.csproj
+++ b/samples/WebAPI_EFCore/Greetings_SqliteMigrations/Greetings_SqliteMigrations.csproj
@@ -1,7 +1,7 @@
- net6.0
+ net7.0
diff --git a/samples/WebAPI_EFCore/Greetings_SqliteMigrations/Migrations/20210902180249_Initial.Designer.cs b/samples/WebAPI_EFCore/Greetings_SqliteMigrations/Migrations/20210902180249_Initial.Designer.cs
index d7e6605b1f..797cdcf0d5 100644
--- a/samples/WebAPI_EFCore/Greetings_SqliteMigrations/Migrations/20210902180249_Initial.Designer.cs
+++ b/samples/WebAPI_EFCore/Greetings_SqliteMigrations/Migrations/20210902180249_Initial.Designer.cs
@@ -9,7 +9,7 @@ namespace Greetings_SqliteMigrations.Migrations
{
[DbContext(typeof(GreetingsEntityGateway))]
[Migration("20210902180249_Initial")]
- partial class Initial
+ partial class SqliteInitialCreate
{
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
diff --git a/samples/WebAPI_EFCore/Greetings_SqliteMigrations/Migrations/20210902180249_Initial.cs b/samples/WebAPI_EFCore/Greetings_SqliteMigrations/Migrations/20210902180249_Initial.cs
index 2fa6db671e..5e882b87fe 100644
--- a/samples/WebAPI_EFCore/Greetings_SqliteMigrations/Migrations/20210902180249_Initial.cs
+++ b/samples/WebAPI_EFCore/Greetings_SqliteMigrations/Migrations/20210902180249_Initial.cs
@@ -2,7 +2,7 @@
namespace Greetings_SqliteMigrations.Migrations
{
- public partial class Initial : Migration
+ public partial class SqliteInitialCreate : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
diff --git a/samples/WebAPI_EFCore/README.md b/samples/WebAPI_EFCore/README.md
index ec61ff1b6d..ca6aee0bce 100644
--- a/samples/WebAPI_EFCore/README.md
+++ b/samples/WebAPI_EFCore/README.md
@@ -2,7 +2,6 @@
- [Web API and EF Core Example](#web-api-and-ef-core-example)
* [Environments](#environments)
* [Architecture](#architecture)
- + [Outbox](#outbox)
+ [GreetingsAPI](#greetingsapi)
+ [SalutationAnalytics](#salutationanalytics)
* [Build and Deploy](#build-and-deploy)
@@ -14,32 +13,43 @@
- [Connection issue with the RabbitMQ](#connection-issue-with-the-rabbitmq)
- [Helpful documentation links](#helpful-documentation-links)
* [Tests](#tests)
+
# Web API and EF Core Example
+
This sample shows a typical scenario when using WebAPI and Brighter/Darker. It demonstrates both using Brighter and Darker to implement the API endpoints, and using a work queue to handle asynchronous work that results from handling the API call.
## Environments
-*Development* - runs locally on your machine, uses Sqlite as a data store; uses RabbitMQ for messaging, can be launched individually from the docker compose file; it represents a typical setup for development.
+*Development*
+
+- Uses a local Sqlite instance for the data store.
+- We support Docker hosted messaging brokers, either RabbitMQ or Kafka.
-*Production* - runs in Docker, uses MySql as a data store; uses RabbitMQ for messaging; it emulates a possible production environment.
+*Production*
+- We offer support for MySQL using Docker.
+- We support Docker hosted messaging brokers, using RabbitMQ.
-We provide launchSetting.json files for both, which allows you to run Production; you should launch MySQl and RabbitMQ from the docker compose file; useful for debugging MySQL operations.
+### Configuration
+
+We provide launchSetting.json files for all of these, which allows you to run Production with the appropriate db; you should launch your SQL data store and broker from the docker compose file.
In case you are using Command Line Interface for running the project, consider adding --launch-profile:
```sh
-dotnet run --launch-profile Production -d
+dotnet run --launch-profile XXXXXX -d
```
+
## Architecture
-### Outbox
- Brighter does have an [Outbox pattern support](https://paramore.readthedocs.io/en/latest/OutboxPattern.html). In case you are new to it, consider reading it before diving deeper.
+
### GreetingsAPI
We follow a _ports and adapters_ architectural style, dividing the app into the following modules:
* **GreetingsAdapters**: The adapters' module, handles the primary adapter of HTTP requests and responses to the app
- * **GreetingsPorts**: the ports' module, handles requests from the primary adapter (HTTP) to the domain, and requests to secondary adapters. In a fuller app, the handlers for the primary adapter would correspond to our use case boundaries. The secondary port of the EntityGateway handles access to the DB via EF Core. We choose to treat EF Core as a port, not an adapter itself, here, as it wraps our underlying adapters for Sqlite or MySql.
+ * **GreetingsPorts**: the ports' module, handles requests from the primary adapter (HTTP) to the domain, and requests to secondary adapters.
+In a fuller app, the handlers for the primary adapter would correspond to our use case boundaries. The secondary port of the EntityGateway handles access to the DB via EF Core.
+We choose to treat EF Core as a port, not an adapter itself, here, as it wraps our underlying adapters for Sqlite or MySql.
* **GreetingsEntities**: the domain model (or application in ports & adapters). In a fuller app, this would contain the logic that has a dependency on entity state.
@@ -49,76 +59,68 @@ The assemblies migrations: **Greetings_MySqlMigrations** and **Greetings_SqliteM
### SalutationAnalytics
-This listens for a GreetingMade message and stores it. It demonstrates listening to a queue. It also demonstrates the use of scopes provided by Brighter's ServiceActivator, which work with EFCore. These support writing to an Outbox when this component raises a message in turn.
+* **SalutationAnalytics** The adapter subscribes to GreetingMade messages. It demonstrates listening to a queue. It also demonstrates the use of scopes provided by Brighter's ServiceActivator, which work with Dapper. These support writing to an Outbox when this component raises a message in turn.
-We don't listen to that message, and without any listeners the RabbitMQ will drop the message we send, as it has no queues to give it to. We don't listen because we would just be repeating what we have shown here. If you want to see the messages produced, use the RMQ Management Console (localhost:15672) to create a queue and then bind it to the paramore.binding.exchange with the routingkey of SalutationReceived.
+* **SalutationPorts** The ports' module, handles requests from the primary adapter to the domain, and requests to secondary adapters. It writes to the entity store and sends another message. We don't listen to that message. Note that without any listeners RabbitMQ will drop the message we send, as it has no queues to give it to.
+ If you want to see the messages produced, use the RMQ Management Console (localhost:15672) or Kafka Console (localhost:9021). (You will need to create a subscribing queue in RabbitMQ)
-We also add an Inbox here. The Inbox can be used to de-duplicate messages. In messaging, the guarantee is 'at least once' if you use a technique such as an Outbox to ensure sending. This means we may receive a message twice. We either need, as in this case, to use an Inbox to de-duplicate, or we need to be idempotent such that receiving the message multiple times would result in the same outcome.
+* **SalutationEntities** The domain model (or application in ports & adapters). In a fuller app, this would contain the logic that has a dependency on entity state.
+We add an Inbox as well as the Outbox here. The Inbox can be used to de-duplicate messages. In messaging, the guarantee is 'at least once' if you use a technique such as an Outbox to ensure sending. This means we may receive a message twice. We either need, as in this case, to use an Inbox to de-duplicate, or we need to be idempotent such that receiving the message multiple times would result in the same outcome.
-## Build and Deploy
+The assemblies migrations: **Salutations_MySqlMigrations** and **Salutations_SqliteMigrations** hold code to configure the Db.
-### Building
+We don't listen to that message, and without any listeners the RabbitMQ will drop the message we send, as it has no queues to give it to. We don't listen because we would just be repeating what we have shown here. If you want to see the messages produced, use the RMQ Management Console (localhost:15672) to create a queue and then bind it to the paramore.binding.exchange with the routingkey of SalutationReceived.
-Use the build.sh file to:
+We also add an Inbox here. The Inbox can be used to de-duplicate messages. In messaging, the guarantee is 'at least once' if you use a technique such as an Outbox to ensure sending. This means we may receive a message twice. We either need, as in this case, to use an Inbox to de-duplicate, or we need to be idempotent such that receiving the message multiple times would result in the same outcome.
-- Build both GreetingsAdapters and SalutationAnalytics and publish it to the /out directory. The Dockerfile assumes the app will be published here.
-- Build the Docker image from the Dockerfile for each.
+### Possible issues
+#### Sqlite Database Read-Only Errors
-(Why not use a multi-stage Docker build? We can't do this as the projects here reference projects not NuGet packages for Brighter libraries and there are not in the Docker build context.)
+A Sqlite database will only have permissions for the process that created it. This can result in you receiving read-only errors between invocations of the sample. You either need to alter the permissions on your Db, or delete it between runs.
-A common error is to change something, forget to run build.sh and use an old Docker image.
+Maintainers, please don't check the Sqlite files into source control.
-### Deploy
+#### Queue Creation and Dropped Messages
-We provide a docker compose file to allow you to run a 'Production' environment or to startup RabbitMQ for production:
-```sh
-docker compose up -d rabbitmq # will just start rabbitmq
-```
+Queues are created by consumers. This is because publishers don't know who consumes them, and thus don't create their queues. This means that if you run a producer, such as GreetingsWeb, and use tests.http to push in greetings, although a message will be published to RabbitMQ, it won't have a queue to be delivered to and will be dropped, unless you have first run the SalutationAnalytics worker to create the queue.
+
+Generally, the rule of thumb is: start the consumer and *then* start the producer.
+
+You can spot this by looking in the [RabbitMQ Management console](http://localhost:1567) and noting that no queue is bound to the routing key in the exchange.
+You can use default credentials for the RabbitMQ Management console:
```sh
-docker compose up -d mysql # will just start mysql
+user :guest
+passowrd: guest
```
+## Acceptance Tests
+We provide a tests.http file (supported by both JetBrains Rider and VS Code with the REST Client plugin) to allow you to test operations on the API.
-and so on.
+## Possible issues
-### Possible issues
#### Sqlite Database Read-Only Errors
A Sqlite database will only have permissions for the process that created it. This can result in you receiving read-only errors between invocations of the sample. You either need to alter the permissions on your Db, or delete it between runs.
Maintainers, please don't check the Sqlite files into source control.
-#### Queue Creation and Dropped Messages
+#### RabbitMQ Queue Creation and Dropped Messages
-Queues are created by consumers. This is because publishers don't know who consumes them, and thus don't create their queues. This means that if you run a producer, such as GreetingsWeb, and use tests.http to push in greetings, although a message will be published to RabbitMQ, it won't have a queue to be delivered to and will be dropped, unless you have first run the SalutationAnalytics worker to create the queue.
+For Rabbit MQ, queues are created by consumers. This is because publishers don't know who consumes them, and thus don't create their queues. This means that if you run a producer, such as GreetingsWeb, and use tests.http to push in greetings, although a message will be published to RabbitMQ, it won't have a queue to be delivered to and will be dropped, unless you have first run the SalutationAnalytics worker to create the queue.
Generally, the rule of thumb is: start the consumer and *then* start the producer.
-You can spot this by looking in the [RabbitMQ Management console](http://localhost:1567) and noting that no queue is bound to the routing key in the exchange.
+You can spot this by looking in the [RabbitMQ Management console](http://localhost:15672) and noting that no queue is bound to the routing key in the exchange.
You can use default credentials for the RabbitMQ Management console:
+
```sh
user :guest
passowrd: guest
```
-#### Connection issue with the RabbitMQ
-When running RabbitMQ from the docker compose file (without any additional network setup, etc.) your RabbitMQ instance in docker will still be accessible by **localhost** as a host name. Consider this when running your application in the Production environment.
-In Production, the application by default will have:
-```sh
-amqp://guest:guest@rabbitmq:5672
-```
-
-as an Advanced Message Queuing Protocol (AMQP) connection string.
-So one of the options will be replacing AMQP connection string with:
-```sh
-amqp://guest:guest@localhost:5672
-```
-In case you still struggle, consider following these steps: [RabbitMQ Troubleshooting Networking](https://www.rabbitmq.com/troubleshooting-networking.html)
#### Helpful documentation links
* [Brighter technical documentation](https://paramore.readthedocs.io/en/latest/index.html)
* [Rabbit Message Queue (RMQ) documentation](https://www.rabbitmq.com/documentation.html)
+* [Kafka documentation](https://kafka.apache.org/documentation/)
-## Tests
-
-We provide a tests.http file (supported by both JetBrains Rider and VS Code with the REST Client plugin) to allow you to test operations.
\ No newline at end of file
diff --git a/samples/WebAPI_EFCore/SalutationAnalytics/Program.cs b/samples/WebAPI_EFCore/SalutationAnalytics/Program.cs
index a1afcb3d0b..c68f8e6ce2 100644
--- a/samples/WebAPI_EFCore/SalutationAnalytics/Program.cs
+++ b/samples/WebAPI_EFCore/SalutationAnalytics/Program.cs
@@ -12,9 +12,14 @@
using Paramore.Brighter.Inbox.MySql;
using Paramore.Brighter.Inbox.Sqlite;
using Paramore.Brighter.MessagingGateway.RMQ;
+using Paramore.Brighter.MySql;
+using Paramore.Brighter.MySql.EntityFrameworkCore;
+using Paramore.Brighter.Outbox.MySql;
+using Paramore.Brighter.Outbox.Sqlite;
using Paramore.Brighter.ServiceActivator.Extensions.DependencyInjection;
using Paramore.Brighter.ServiceActivator.Extensions.Hosting;
-using Polly.Registry;
+using Paramore.Brighter.Sqlite;
+using Paramore.Brighter.Sqlite.EntityFrameworkCore;
using SalutationAnalytics.Database;
using SalutationPorts.EntityGateway;
using SalutationPorts.Policies;
@@ -41,7 +46,9 @@ private static IHostBuilder CreateHostBuilder(string[] args) =>
configurationBuilder.SetBasePath(Directory.GetCurrentDirectory());
configurationBuilder.AddJsonFile("appsettings.json", optional: true);
configurationBuilder.AddJsonFile($"appsettings.{GetEnvironment()}.json", optional: true);
- configurationBuilder.AddEnvironmentVariables(prefix: "ASPNETCORE_"); //NOTE: Although not web, we use this to grab the environment
+ configurationBuilder
+ .AddEnvironmentVariables(
+ prefix: "ASPNETCORE_"); //NOTE: Although not web, we use this to grab the environment
configurationBuilder.AddEnvironmentVariables(prefix: "BRIGHTER_");
configurationBuilder.AddCommandLine(args);
})
@@ -59,27 +66,30 @@ private static IHostBuilder CreateHostBuilder(string[] args) =>
private static void ConfigureBrighter(HostBuilderContext hostContext, IServiceCollection services)
{
+ (IAmAnOutbox outbox, Type transactionProvider, Type connectionProvider) = MakeOutbox(hostContext);
+ var outboxConfiguration = new RelationalDatabaseConfiguration(DbConnectionString(hostContext));
+ services.AddSingleton(outboxConfiguration);
+
+ IAmAProducerRegistry producerRegistry = ConfigureProducerRegistry();
+
var subscriptions = new Subscription[]
{
new RmqSubscription(
new SubscriptionName("paramore.sample.salutationanalytics"),
new ChannelName("SalutationAnalytics"),
new RoutingKey("GreetingMade"),
- runAsync: true,
+ runAsync: false,
timeoutInMilliseconds: 200,
isDurable: true,
- makeChannels: OnMissingChannel.Create), //change to OnMissingChannel.Validate if you have infrastructure declared elsewhere
+ makeChannels: OnMissingChannel
+ .Create), //change to OnMissingChannel.Validate if you have infrastructure declared elsewhere
};
- var host = hostContext.HostingEnvironment.IsDevelopment() ? "localhost" : "rabbitmq";
-
- var rmqConnection = new RmqMessagingGatewayConnection
+ var rmqMessageConsumerFactory = new RmqMessageConsumerFactory(new RmqMessagingGatewayConnection
{
- AmpqUri = new AmqpUriSpecification(new Uri($"amqp://guest:guest@{host}:5672")),
- Exchange = new Exchange("paramore.brighter.exchange")
- };
-
- var rmqMessageConsumerFactory = new RmqMessageConsumerFactory(rmqConnection);
+ AmpqUri = new AmqpUriSpecification(new Uri("amqp://guest:guest@localhost:5672")),
+ Exchange = new Exchange("paramore.brighter.exchange"),
+ });
services.AddServiceActivator(options =>
{
@@ -90,41 +100,32 @@ private static void ConfigureBrighter(HostBuilderContext hostContext, IServiceCo
options.MapperLifetime = ServiceLifetime.Singleton;
options.CommandProcessorLifetime = ServiceLifetime.Scoped;
options.PolicyRegistry = new SalutationPolicy();
- })
- .UseExternalBus(new RmqProducerRegistryFactory(
- rmqConnection,
- new RmqPublication[]
- {
- new RmqPublication
- {
- Topic = new RoutingKey("SalutationReceived"),
- MaxOutStandingMessages = 5,
- MaxOutStandingCheckIntervalMilliSeconds = 500,
- WaitForConfirmsTimeOutInMilliseconds = 1000,
- MakeChannels = OnMissingChannel.Create
- }
- }
- ).Create()
- )
- .AutoFromAssemblies()
- .UseExternalInbox(
- ConfigureInbox(hostContext),
- new InboxConfiguration(
+ options.InboxConfiguration = new InboxConfiguration(
+ ConfigureInbox(hostContext),
scope: InboxScope.Commands,
onceOnly: true,
actionOnExists: OnceOnlyAction.Throw
- )
- );
+ );
+ })
+ .UseExternalBus((configure) =>
+ {
+ configure.ProducerRegistry = producerRegistry;
+ configure.Outbox = outbox;
+ configure.TransactionProvider = transactionProvider;
+ configure.ConnectionProvider = connectionProvider;
+ })
+ .AutoFromAssemblies();
services.AddHostedService();
}
+
private static string GetEnvironment()
{
//NOTE: Hosting Context will always return Production outside of ASPNET_CORE at this point, so grab it directly
return Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT");
}
-
+
private static void ConfigureEFCore(HostBuilderContext hostContext, IServiceCollection services)
{
string connectionString = DbConnectionString(hostContext);
@@ -134,25 +135,25 @@ private static void ConfigureEFCore(HostBuilderContext hostContext, IServiceColl
services.AddDbContext(
builder =>
{
- builder.UseSqlite(connectionString,
+ builder.UseSqlite(connectionString,
optionsBuilder =>
{
- optionsBuilder.MigrationsAssembly("Salutations_SqliteMigrations");
+ optionsBuilder.MigrationsAssembly("Salutations_Migrations");
});
});
}
else
{
- services.AddDbContextPool(builder =>
- {
- builder
- .UseMySql(connectionString, ServerVersion.AutoDetect(connectionString), optionsBuilder =>
- {
- optionsBuilder.MigrationsAssembly("Salutations_MySqlMigrations");
- })
- .EnableDetailedErrors()
- .EnableSensitiveDataLogging();
- });
+ services.AddDbContextPool(builder =>
+ {
+ builder
+ .UseMySql(connectionString, ServerVersion.AutoDetect(connectionString), optionsBuilder =>
+ {
+ optionsBuilder.MigrationsAssembly("Salutations_MySqlMigrations");
+ })
+ .EnableDetailedErrors()
+ .EnableSensitiveDataLogging();
+ });
}
}
@@ -160,17 +161,63 @@ private static IAmAnInbox ConfigureInbox(HostBuilderContext hostContext)
{
if (hostContext.HostingEnvironment.IsDevelopment())
{
- return new SqliteInbox(new SqliteInboxConfiguration(DbConnectionString(hostContext), SchemaCreation.INBOX_TABLE_NAME));
+ return new SqliteInbox(new RelationalDatabaseConfiguration(DbConnectionString(hostContext),
+ SchemaCreation.INBOX_TABLE_NAME));
}
- return new MySqlInbox(new MySqlInboxConfiguration(DbConnectionString(hostContext), SchemaCreation.INBOX_TABLE_NAME));
+ return new MySqlInbox(new RelationalDatabaseConfiguration(DbConnectionString(hostContext),
+ SchemaCreation.INBOX_TABLE_NAME));
}
+ private static IAmAProducerRegistry ConfigureProducerRegistry()
+ {
+ var producerRegistry = new RmqProducerRegistryFactory(
+ new RmqMessagingGatewayConnection
+ {
+ AmpqUri = new AmqpUriSpecification(new Uri("amqp://guest:guest@localhost:5672")),
+ Exchange = new Exchange("paramore.brighter.exchange"),
+ },
+ new RmqPublication[]
+ {
+ new RmqPublication
+ {
+ Topic = new RoutingKey("SalutationReceived"),
+ MaxOutStandingMessages = 5,
+ MaxOutStandingCheckIntervalMilliSeconds = 500,
+ WaitForConfirmsTimeOutInMilliseconds = 1000,
+ MakeChannels = OnMissingChannel.Create
+ }
+ }
+ ).Create();
+
+ return producerRegistry;
+ }
+
+
private static string DbConnectionString(HostBuilderContext hostContext)
{
//NOTE: Sqlite needs to use a shared cache to allow Db writes to the Outbox as well as entities
- return hostContext.HostingEnvironment.IsDevelopment() ? "Filename=Salutations.db;Cache=Shared" : hostContext.Configuration.GetConnectionString("Salutations");
+ return hostContext.HostingEnvironment.IsDevelopment()
+ ? "Filename=Salutations.db;Cache=Shared"
+ : hostContext.Configuration.GetConnectionString("Salutations");
+ }
+
+ private static (IAmAnOutbox outbox, Type transactionProvider, Type connectionProvider) MakeOutbox(HostBuilderContext hostContext)
+ {
+ if (hostContext.HostingEnvironment.IsDevelopment())
+ {
+ var outbox = new SqliteOutbox(new RelationalDatabaseConfiguration(DbConnectionString(hostContext)));
+ var transactionProvider = typeof(SqliteEntityFrameworkConnectionProvider);
+ var connectionProvider = typeof(SqliteConnectionProvider);
+ return (outbox, transactionProvider, connectionProvider);
+ }
+ else
+ {
+ var outbox = new MySqlOutbox(new RelationalDatabaseConfiguration(DbConnectionString(hostContext)));
+ var transactionProvider = typeof(MySqlEntityFrameworkConnectionProvider);
+ var connectionProvider = typeof(MySqlConnectionProvider);
+ return (outbox, transactionProvider, connectionProvider);
+ }
}
-
}
}
diff --git a/samples/WebAPI_EFCore/SalutationAnalytics/SalutationAnalytics.csproj b/samples/WebAPI_EFCore/SalutationAnalytics/SalutationAnalytics.csproj
index 8a911e922d..c2af94ab47 100644
--- a/samples/WebAPI_EFCore/SalutationAnalytics/SalutationAnalytics.csproj
+++ b/samples/WebAPI_EFCore/SalutationAnalytics/SalutationAnalytics.csproj
@@ -2,18 +2,20 @@
Exe
- net6.0
+ net7.0
+
+
diff --git a/samples/WebAPI_EFCore/SalutationEntities/SalutationEntities.csproj b/samples/WebAPI_EFCore/SalutationEntities/SalutationEntities.csproj
index dbc151713b..8268829b64 100644
--- a/samples/WebAPI_EFCore/SalutationEntities/SalutationEntities.csproj
+++ b/samples/WebAPI_EFCore/SalutationEntities/SalutationEntities.csproj
@@ -1,7 +1,7 @@
- net6.0
+ net7.0
diff --git a/samples/WebAPI_EFCore/SalutationPorts/Handlers/GreetingMadeHandler.cs b/samples/WebAPI_EFCore/SalutationPorts/Handlers/GreetingMadeHandler.cs
index 7f50726fda..ecb171a58d 100644
--- a/samples/WebAPI_EFCore/SalutationPorts/Handlers/GreetingMadeHandler.cs
+++ b/samples/WebAPI_EFCore/SalutationPorts/Handlers/GreetingMadeHandler.cs
@@ -12,51 +12,60 @@
namespace SalutationPorts.Handlers
{
- public class GreetingMadeHandlerAsync : RequestHandlerAsync
+ public class GreetingMadeHandler : RequestHandler
{
private readonly SalutationsEntityGateway _uow;
private readonly IAmACommandProcessor _postBox;
+ private readonly IAmATransactionConnectionProvider _transactionProvider;
- public GreetingMadeHandlerAsync(SalutationsEntityGateway uow, IAmACommandProcessor postBox)
+ public GreetingMadeHandler(SalutationsEntityGateway uow, IAmATransactionConnectionProvider provider, IAmACommandProcessor postBox)
{
_uow = uow;
_postBox = postBox;
+ _transactionProvider = provider;
}
- //[UseInboxAsync(step:0, contextKey: typeof(GreetingMadeHandlerAsync), onceOnly: true )] -- we are using a global inbox, so need to be explicit!!
- [RequestLoggingAsync(step: 1, timing: HandlerTiming.Before)]
- [UsePolicyAsync(step:2, policy: Policies.Retry.EXPONENTIAL_RETRYPOLICYASYNC)]
- public override async Task HandleAsync(GreetingMade @event, CancellationToken cancellationToken = default)
+ [UseInbox(step:0, contextKey: typeof(GreetingMadeHandler), onceOnly: true )]
+ [RequestLogging(step: 1, timing: HandlerTiming.Before)]
+ [UsePolicy(step:2, policy: Policies.Retry.EXPONENTIAL_RETRYPOLICY)]
+ public override GreetingMade Handle(GreetingMade @event)
{
var posts = new List();
- var tx = await _uow.Database.BeginTransactionAsync(cancellationToken);
+ var tx =_transactionProvider.GetTransaction();
try
{
var salutation = new Salutation(@event.Greeting);
_uow.Salutations.Add(salutation);
-
- posts.Add(await _postBox.DepositPostAsync(new SalutationReceived(DateTimeOffset.Now), cancellationToken: cancellationToken));
-
- await _uow.SaveChangesAsync(cancellationToken);
- await tx.CommitAsync(cancellationToken);
+ posts.Add(_postBox.DepositPost(
+ new SalutationReceived(DateTimeOffset.Now),
+ _transactionProvider)
+ );
+
+ _uow.SaveChanges();
+
+ _transactionProvider.Commit();
}
catch (Exception e)
{
Console.WriteLine(e);
-
- await tx.RollbackAsync(cancellationToken);
-
+
+ _transactionProvider.Rollback();
+
Console.WriteLine("Salutation analytical record not saved");
throw;
}
+ finally
+ {
+ _transactionProvider.Close();
+ }
- await _postBox.ClearOutboxAsync(posts, cancellationToken: cancellationToken);
+ _postBox.ClearOutbox(posts.ToArray());
- return await base.HandleAsync(@event, cancellationToken);
+ return base.Handle(@event);
}
}
}
diff --git a/samples/WebAPI_EFCore/SalutationPorts/Policies/Retry.cs b/samples/WebAPI_EFCore/SalutationPorts/Policies/Retry.cs
index 4db47aa42d..40ec53c32b 100644
--- a/samples/WebAPI_EFCore/SalutationPorts/Policies/Retry.cs
+++ b/samples/WebAPI_EFCore/SalutationPorts/Policies/Retry.cs
@@ -7,21 +7,21 @@ namespace SalutationPorts.Policies
{
public static class Retry
{
- public const string RETRYPOLICYASYNC = "SalutationPorts.Policies.RetryPolicyAsync";
- public const string EXPONENTIAL_RETRYPOLICYASYNC = "SalutationPorts.Policies.ExponenttialRetryPolicyAsync";
+ public const string RETRYPOLICY = "SalutationPorts.Policies.RetryPolicyAsync";
+ public const string EXPONENTIAL_RETRYPOLICY = "SalutationPorts.Policies.ExponenttialRetryPolicyAsync";
- public static AsyncRetryPolicy GetSimpleHandlerRetryPolicy()
+ public static RetryPolicy GetSimpleHandlerRetryPolicy()
{
- return Policy.Handle().WaitAndRetryAsync(new[]
+ return Policy.Handle().WaitAndRetry(new[]
{
TimeSpan.FromMilliseconds(50), TimeSpan.FromMilliseconds(100), TimeSpan.FromMilliseconds(150)
});
}
- public static AsyncRetryPolicy GetExponentialHandlerRetryPolicy()
+ public static RetryPolicy GetExponentialHandlerRetryPolicy()
{
var delay = Backoff.ExponentialBackoff(TimeSpan.FromMilliseconds(100), retryCount: 5, fastFirst: true);
- return Policy.Handle().WaitAndRetryAsync(delay);
+ return Policy.Handle().WaitAndRetry(delay);
}
}
}
diff --git a/samples/WebAPI_EFCore/SalutationPorts/Policies/SalutationPolicy.cs b/samples/WebAPI_EFCore/SalutationPorts/Policies/SalutationPolicy.cs
index ddf21c324f..28024f22a7 100644
--- a/samples/WebAPI_EFCore/SalutationPorts/Policies/SalutationPolicy.cs
+++ b/samples/WebAPI_EFCore/SalutationPorts/Policies/SalutationPolicy.cs
@@ -11,8 +11,8 @@ public SalutationPolicy()
private void AddSalutationPolicies()
{
- Add(Retry.RETRYPOLICYASYNC, Retry.GetSimpleHandlerRetryPolicy());
- Add(Retry.EXPONENTIAL_RETRYPOLICYASYNC, Retry.GetExponentialHandlerRetryPolicy());
+ Add(Retry.RETRYPOLICY, Retry.GetSimpleHandlerRetryPolicy());
+ Add(Retry.EXPONENTIAL_RETRYPOLICY, Retry.GetExponentialHandlerRetryPolicy());
}
}
}
diff --git a/samples/WebAPI_EFCore/SalutationPorts/SalutationPorts.csproj b/samples/WebAPI_EFCore/SalutationPorts/SalutationPorts.csproj
index 8b83a99c91..5b6e770d6d 100644
--- a/samples/WebAPI_EFCore/SalutationPorts/SalutationPorts.csproj
+++ b/samples/WebAPI_EFCore/SalutationPorts/SalutationPorts.csproj
@@ -1,7 +1,7 @@
- net6.0
+ net7.0
diff --git a/samples/WebAPI_EFCore/Salutations_MySqlMigrations/Salutations_MySqlMigrations.csproj b/samples/WebAPI_EFCore/Salutations_MySqlMigrations/Salutations_MySqlMigrations.csproj
index 9c08384194..dc6d4eb4c4 100644
--- a/samples/WebAPI_EFCore/Salutations_MySqlMigrations/Salutations_MySqlMigrations.csproj
+++ b/samples/WebAPI_EFCore/Salutations_MySqlMigrations/Salutations_MySqlMigrations.csproj
@@ -1,7 +1,7 @@
- net6.0
+ net7.0
diff --git a/samples/WebAPI_EFCore/Salutations_SqliteMigrations/Salutations_SqliteMigrations.csproj b/samples/WebAPI_EFCore/Salutations_SqliteMigrations/Salutations_SqliteMigrations.csproj
index 23e2d9de53..7a766e756a 100644
--- a/samples/WebAPI_EFCore/Salutations_SqliteMigrations/Salutations_SqliteMigrations.csproj
+++ b/samples/WebAPI_EFCore/Salutations_SqliteMigrations/Salutations_SqliteMigrations.csproj
@@ -1,7 +1,7 @@
- net6.0
+ net7.0
diff --git a/samples/WebAPI_EFCore/build.sh b/samples/WebAPI_EFCore/build.sh
deleted file mode 100644
index a9522415f7..0000000000
--- a/samples/WebAPI_EFCore/build.sh
+++ /dev/null
@@ -1,15 +0,0 @@
-pushd GreetingsAdapters || exit
-rm -rf out
-dotnet restore
-dotnet build
-dotnet publish -c Release -o out
-docker build .
-popd || exit
-pushd SalutationAnalytics || exit
-rm -rf out
-dotnet restore
-dotnet build
-dotnet publish -c Release -o out
-docker build .
-popd || exit
-
diff --git a/samples/WebAPI_EFCore/docker-compose.yml b/samples/WebAPI_EFCore/docker-compose.yml
deleted file mode 100644
index 703b1a2372..0000000000
--- a/samples/WebAPI_EFCore/docker-compose.yml
+++ /dev/null
@@ -1,51 +0,0 @@
-version: '3.1'
-services:
- web:
- build: ./GreetingsAdapters
- hostname: greetingsapi
- ports:
- - "5000:5000"
- environment:
- - BRIGHTER_ConnectionStrings__Greetings=server=greetings_db; port=3306; uid=root; pwd=root; database=Greetings
- - BRIGHTER_ConnectionStrings__GreetingsDb=server=greetings_db; port=3306; uid=root; pwd=root
- - ASPNETCORE_ENVIRONMENT=Production
- links:
- - mysql:greetings_db
- depends_on:
- - mysql
- - rabbitmq
- worker:
- build: ./GreetingsWatcher
- hostname: greetingsworker
- environment:
- - ASPNETCORE_ENVIRONMENT=Production
- depends_on:
- - rabbitmq
- mysql:
- hostname: greetings_db
- image: mysql
- ports:
- - "3306:3306"
- security_opt:
- - seccomp:unconfined
- volumes:
- - my-db:/var/lib/mysql
- environment:
- MYSQL_ROOT_PASSWORD: "root"
- healthcheck:
- test: mysqladmin ping -h localhost -p$$MYSQL_ROOT_PASSWORD && test '0' -eq $$(ps aux | awk '{print $$11}' | grep -c -e '^mysql$$')
- rabbitmq:
- image: brightercommand/rabbitmq:3.8-management-delay
- ports:
- - "5672:5672"
- - "15672:15672"
- volumes:
- - rabbitmq-home:/var/lib/rabbitmq
-
-volumes:
- rabbitmq-home:
- driver: local
- my-db:
- driver: local
-
-
diff --git a/samples/WebApiWithWorkerAndSweeper/Orders.API/Program.cs b/samples/WebApiWithWorkerAndSweeper/Orders.API/Program.cs
index a264fb3554..549609d28a 100644
--- a/samples/WebApiWithWorkerAndSweeper/Orders.API/Program.cs
+++ b/samples/WebApiWithWorkerAndSweeper/Orders.API/Program.cs
@@ -1,7 +1,9 @@
using System.ComponentModel;
+using System.Data.Common;
using System.Reflection;
using System.Text.Json;
using System.Text.Json.Serialization;
+using System.Transactions;
using Orders.Data;
using Paramore.Brighter.MessagingGateway.AzureServiceBus;
using Paramore.Brighter.MsSql;
@@ -31,7 +33,13 @@
var asbConnection = new ServiceBusVisualStudioCredentialClientProvider(asbEndpoint);
-var outboxConfig = new MsSqlConfiguration(dbConnString, "BrighterOutbox");
+var outboxConfig = new RelationalDatabaseConfiguration(dbConnString, outBoxTableName: "BrighterOutbox");
+
+var producerRegistry = new AzureServiceBusProducerRegistryFactory(
+ asbConnection,
+ new AzureServiceBusPublication[] { new() { Topic = new RoutingKey(NewOrderVersionEvent.Topic) }, }
+ )
+ .Create();
builder.Services
.AddBrighter(opt =>
@@ -39,18 +47,15 @@
opt.PolicyRegistry = new DefaultPolicy();
opt.CommandProcessorLifetime = ServiceLifetime.Scoped;
})
- .UseExternalBus(
- new AzureServiceBusProducerRegistryFactory(
- asbConnection,
- new AzureServiceBusPublication[] { new() { Topic = new RoutingKey(NewOrderVersionEvent.Topic) }, }
- )
- .Create()
+ .UseExternalBus((configure) =>
+ {
+ configure.ProducerRegistry = producerRegistry;
+ configure.Outbox = new MsSqlOutbox(outboxConfig);
+ configure.TransactionProvider = typeof(MsSqlUnitOfWork);
+ }
)
- .UseMsSqlOutbox(outboxConfig, typeof(MsSqlSqlAuthConnectionProvider))
- .UseMsSqlTransactionConnectionProvider(typeof(SqlConnectionProvider))
.AutoFromAssemblies(Assembly.GetAssembly(typeof(NewOrderVersionEvent)));
-
builder.Services.AddControllersWithViews().AddJsonOptions(options =>
{
options.JsonSerializerOptions.Converters.Add(new JsonStringEnumConverter(allowIntegerValues: false));
diff --git a/samples/WebApiWithWorkerAndSweeper/Orders.Data/SqlConnectionProvider.cs b/samples/WebApiWithWorkerAndSweeper/Orders.Data/SqlConnectionProvider.cs
index eaead8234a..fcaf04521d 100644
--- a/samples/WebApiWithWorkerAndSweeper/Orders.Data/SqlConnectionProvider.cs
+++ b/samples/WebApiWithWorkerAndSweeper/Orders.Data/SqlConnectionProvider.cs
@@ -1,10 +1,10 @@
+using System.Data.Common;
using Microsoft.Data.SqlClient;
-using Orders.Domain;
-using Paramore.Brighter.MsSql;
+using Paramore.Brighter;
namespace Orders.Data;
-public class SqlConnectionProvider : IMsSqlTransactionConnectionProvider
+public class SqlConnectionProvider : RelationalDbConnectionProvider
{
private readonly SqlUnitOfWork _sqlConnection;
@@ -13,21 +13,16 @@ public SqlConnectionProvider(SqlUnitOfWork sqlConnection)
_sqlConnection = sqlConnection;
}
- public SqlConnection GetConnection()
+ public override DbConnection GetConnection()
{
return _sqlConnection.Connection;
}
- public Task GetConnectionAsync(CancellationToken cancellationToken = default)
- {
- return Task.FromResult(_sqlConnection.Connection);
- }
-
- public SqlTransaction? GetTransaction()
+ public override DbTransaction GetTransaction()
{
return _sqlConnection.Transaction;
}
- public bool HasOpenTransaction { get => _sqlConnection.Transaction != null; }
- public bool IsSharedConnection { get => true; }
+ public override bool HasOpenTransaction { get => _sqlConnection.Transaction != null; }
+ public override bool IsSharedConnection { get => true; }
}
diff --git a/samples/WebApiWithWorkerAndSweeper/Orders.Sweeper/Extensions/BrighterExtensions.cs b/samples/WebApiWithWorkerAndSweeper/Orders.Sweeper/Extensions/BrighterExtensions.cs
index 98397646d1..1c2669dd60 100644
--- a/samples/WebApiWithWorkerAndSweeper/Orders.Sweeper/Extensions/BrighterExtensions.cs
+++ b/samples/WebApiWithWorkerAndSweeper/Orders.Sweeper/Extensions/BrighterExtensions.cs
@@ -1,3 +1,5 @@
+using System.Data.Common;
+using System.Transactions;
using Azure.Identity;
using Orders.Sweeper.Settings;
using Paramore.Brighter;
@@ -32,28 +34,32 @@ public static WebApplicationBuilder AddBrighter(this WebApplicationBuilder build
new() {MakeChannels = OnMissingChannel.Validate, Topic = new RoutingKey("default")}
}, boxSettings.BatchChunkSize).Create();
- var outboxSettings = new MsSqlConfiguration(boxSettings.ConnectionString, boxSettings.OutboxTableName);
- Type outboxType;
+ var outboxSettings = new RelationalDatabaseConfiguration(boxSettings.ConnectionString, outBoxTableName: boxSettings.OutboxTableName);
+ Type transactionProviderType;
if (boxSettings.UseMsi)
{
if (environmentName != null && environmentName.Equals(_developmentEnvironemntName, StringComparison.InvariantCultureIgnoreCase))
{
- outboxType = typeof(MsSqlVisualStudioConnectionProvider);
+ transactionProviderType = typeof(MsSqlVisualStudioConnectionProvider);
}
else
{
- outboxType = typeof(MsSqlDefaultAzureConnectionProvider);
+ transactionProviderType = typeof(MsSqlDefaultAzureConnectionProvider);
}
}
else
{
- outboxType = typeof(MsSqlSqlAuthConnectionProvider);
+ transactionProviderType = typeof(MsSqlConnectionProvider);
}
builder.Services.AddBrighter()
- .UseExternalBus(producerRegistry)
- .UseMsSqlOutbox(outboxSettings, outboxType)
+ .UseExternalBus((configure) =>
+ {
+ configure.ProducerRegistry = producerRegistry;
+ configure.Outbox = new MsSqlOutbox(outboxSettings);
+ configure.TransactionProvider = transactionProviderType;
+ })
.UseOutboxSweeper(options =>
{
options.TimerInterval = boxSettings.OutboxSweeperInterval;
diff --git a/samples/WebApiWithWorkerAndSweeper/Orders.Sweeper/Extensions/HealthCheckExtensions.cs b/samples/WebApiWithWorkerAndSweeper/Orders.Sweeper/Extensions/HealthCheckExtensions.cs
index 0ebde0eee8..0f9599f27b 100644
--- a/samples/WebApiWithWorkerAndSweeper/Orders.Sweeper/Extensions/HealthCheckExtensions.cs
+++ b/samples/WebApiWithWorkerAndSweeper/Orders.Sweeper/Extensions/HealthCheckExtensions.cs
@@ -1,5 +1,6 @@
using Microsoft.Extensions.Diagnostics.HealthChecks;
using Orders.Sweeper.HealthChecks;
+using Paramore.Brighter;
using Paramore.Brighter.MsSql;
namespace Orders.Sweeper.Extensions;
@@ -9,7 +10,7 @@ public static class HealthCheckExtensions
public static IHealthChecksBuilder AddBrighterOutbox(this IHealthChecksBuilder builder)
{
return builder.Add(new HealthCheckRegistration("Brighter Outbox",
- sp => new BrighterOutboxConnectionHealthCheck(sp.GetService()),
+ sp => new BrighterOutboxConnectionHealthCheck(sp.GetService()),
HealthStatus.Unhealthy,
null,
TimeSpan.FromSeconds(15)));
diff --git a/samples/WebApiWithWorkerAndSweeper/Orders.Sweeper/HealthChecks/BrighterOutboxConnectionHealthCheck.cs b/samples/WebApiWithWorkerAndSweeper/Orders.Sweeper/HealthChecks/BrighterOutboxConnectionHealthCheck.cs
index afe490604b..813d1fe8dc 100644
--- a/samples/WebApiWithWorkerAndSweeper/Orders.Sweeper/HealthChecks/BrighterOutboxConnectionHealthCheck.cs
+++ b/samples/WebApiWithWorkerAndSweeper/Orders.Sweeper/HealthChecks/BrighterOutboxConnectionHealthCheck.cs
@@ -1,36 +1,30 @@
using System.Data;
using Microsoft.Extensions.Diagnostics.HealthChecks;
+using Paramore.Brighter;
using Paramore.Brighter.MsSql;
namespace Orders.Sweeper.HealthChecks;
public class BrighterOutboxConnectionHealthCheck : IHealthCheck
{
- private readonly IMsSqlConnectionProvider _connectionProvider;
+ private readonly IAmARelationalDbConnectionProvider _connectionProvider;
- public BrighterOutboxConnectionHealthCheck(IMsSqlConnectionProvider connectionProvider)
+ public BrighterOutboxConnectionHealthCheck(IAmARelationalDbConnectionProvider connectionProvider)
{
_connectionProvider = connectionProvider;
}
- public async Task CheckHealthAsync(HealthCheckContext context, CancellationToken cancellationToken = new CancellationToken())
+ public async Task CheckHealthAsync(HealthCheckContext context, CancellationToken cancellationToken = default)
{
try
{
- var connection = await _connectionProvider.GetConnectionAsync(cancellationToken);
+ await using var connection = await _connectionProvider.GetConnectionAsync(cancellationToken);
- await connection.OpenAsync(cancellationToken);
-
- if (connection.State != ConnectionState.Open) await connection.OpenAsync(cancellationToken);
var command = connection.CreateCommand();
- if (_connectionProvider.HasOpenTransaction) command.Transaction = _connectionProvider.GetTransaction();
command.CommandText = "SELECT 1;";
await command.ExecuteScalarAsync(cancellationToken);
- if (!_connectionProvider.IsSharedConnection) connection.Dispose();
- else if (!_connectionProvider.HasOpenTransaction) connection.Close();
-
return HealthCheckResult.Healthy();
}
catch (Exception ex)
diff --git a/samples/WebApiWithWorkerAndSweeper/Orders.Sweeper/Settings/BrighterBoxSettings.cs b/samples/WebApiWithWorkerAndSweeper/Orders.Sweeper/Settings/BrighterBoxSettings.cs
index ce5408effd..bc94eed512 100644
--- a/samples/WebApiWithWorkerAndSweeper/Orders.Sweeper/Settings/BrighterBoxSettings.cs
+++ b/samples/WebApiWithWorkerAndSweeper/Orders.Sweeper/Settings/BrighterBoxSettings.cs
@@ -9,6 +9,7 @@ public class BrighterBoxSettings
public int OutboxSweeperInterval { get; set; } = 5;
public bool UseMsi { get; set; } = true;
+
public string ConnectionString { get; set; }
public int MinimumMessageAge { get; set; } = 5000;
diff --git a/samples/WebApiWithWorkerAndSweeper/Orders.Worker/Program.cs b/samples/WebApiWithWorkerAndSweeper/Orders.Worker/Program.cs
index d546ebc93b..ed42d85cd5 100644
--- a/samples/WebApiWithWorkerAndSweeper/Orders.Worker/Program.cs
+++ b/samples/WebApiWithWorkerAndSweeper/Orders.Worker/Program.cs
@@ -50,7 +50,7 @@
-var outboxConfig = new MsSqlConfiguration(dbConnString, "BrighterOutbox");
+var outboxConfig = new RelationalDatabaseConfiguration(dbConnString, outBoxTableName: "BrighterOutbox");
//TODO: add your ASB qualified name here
var clientProvider = new ServiceBusVisualStudioCredentialClientProvider(".servicebus.windows.net");
@@ -62,8 +62,7 @@
options.ChannelFactory = new AzureServiceBusChannelFactory(asbConsumerFactory);
options.UseScoped = true;
- }).UseMsSqlOutbox(outboxConfig, typeof(MsSqlSqlAuthConnectionProvider))
- .UseMsSqlTransactionConnectionProvider(typeof(SqlConnectionProvider))
+ })
.AutoFromAssemblies(Assembly.GetAssembly(typeof(CreateOrderCommand)));
builder.Services.AddHostedService();
diff --git a/src/Paramore.Brighter.Archive.Azure/Paramore.Brighter.Archive.Azure.csproj b/src/Paramore.Brighter.Archive.Azure/Paramore.Brighter.Archive.Azure.csproj
index fc01f28fa0..e50983bdae 100644
--- a/src/Paramore.Brighter.Archive.Azure/Paramore.Brighter.Archive.Azure.csproj
+++ b/src/Paramore.Brighter.Archive.Azure/Paramore.Brighter.Archive.Azure.csproj
@@ -12,7 +12,7 @@
-
+
all
runtime; build; native; contentfiles; analyzers; buildtransitive
diff --git a/src/Paramore.Brighter.Dapper/IUnitOfWork.cs b/src/Paramore.Brighter.Dapper/IUnitOfWork.cs
deleted file mode 100644
index 642c18fc1f..0000000000
--- a/src/Paramore.Brighter.Dapper/IUnitOfWork.cs
+++ /dev/null
@@ -1,42 +0,0 @@
-using System;
-using System.Data.Common;
-using System.Threading;
-using System.Threading.Tasks;
-
-namespace Paramore.Brighter.Dapper
-{
- ///
- /// Creates a unit of work, so that Brighter can access the active transaction for the Outbox
- ///
- public interface IUnitOfWork : IAmABoxTransactionConnectionProvider, IDisposable
- {
- ///
- /// Begins a new transaction against the database. Will open the connection if it is not already open,
- ///
- /// A transaction
- DbTransaction BeginOrGetTransaction();
-
- ///
- /// Begins a new transaction asynchronously against the database. Will open the connection if it is not already open,
- ///
- ///
- /// A transaction
- Task BeginOrGetTransactionAsync(CancellationToken cancellationToken);
-
- ///
- /// Commits any pending transactions
- ///
- void Commit();
-
- ///
- /// The .NET DbConnection to the Database
- ///
- DbConnection Database { get; }
-
- ///
- /// Is there an extant transaction
- ///
- /// True if a transaction is already open on this unit of work, false otherwise
- bool HasTransaction();
- }
-}
diff --git a/src/Paramore.Brighter.DynamoDb/DynamoDbTableBuilder.cs b/src/Paramore.Brighter.DynamoDb/DynamoDbTableBuilder.cs
index 8010b27aa3..b225b45fc5 100644
--- a/src/Paramore.Brighter.DynamoDb/DynamoDbTableBuilder.cs
+++ b/src/Paramore.Brighter.DynamoDb/DynamoDbTableBuilder.cs
@@ -49,7 +49,7 @@ public async Task EnsureTablesDeleted(IEnumerable tableNames, Cancellati
Dictionary tableResults = null;
do
{
- var tableQuery = new DynampDbTableQuery();
+ var tableQuery = new DynamoDbTableQuery();
tableResults = await tableQuery.HasTables(_client, tableNames, ct: ct);
} while (tableResults.Any(tr => tr.Value));
}
diff --git a/src/Paramore.Brighter.DynamoDb/DynampDbTableQuery.cs b/src/Paramore.Brighter.DynamoDb/DynamoDbTableQuery.cs
similarity index 97%
rename from src/Paramore.Brighter.DynamoDb/DynampDbTableQuery.cs
rename to src/Paramore.Brighter.DynamoDb/DynamoDbTableQuery.cs
index 700043d013..f17ebc4b07 100644
--- a/src/Paramore.Brighter.DynamoDb/DynampDbTableQuery.cs
+++ b/src/Paramore.Brighter.DynamoDb/DynamoDbTableQuery.cs
@@ -7,7 +7,7 @@
namespace Paramore.Brighter.DynamoDb
{
- public class DynampDbTableQuery
+ public class DynamoDbTableQuery
{
public async Task> HasTables(
IAmazonDynamoDB client,
diff --git a/src/Paramore.Brighter.DynamoDb/DynamoDbUnitOfWork.cs b/src/Paramore.Brighter.DynamoDb/DynamoDbUnitOfWork.cs
index 17cd772623..eb56f7cc90 100644
--- a/src/Paramore.Brighter.DynamoDb/DynamoDbUnitOfWork.cs
+++ b/src/Paramore.Brighter.DynamoDb/DynamoDbUnitOfWork.cs
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
+using System.Net;
using System.Threading;
using System.Threading.Tasks;
using Amazon.DynamoDBv2;
@@ -7,7 +8,7 @@
namespace Paramore.Brighter.DynamoDb
{
- public class DynamoDbUnitOfWork : IDynamoDbClientTransactionProvider, IDisposable
+ public class DynamoDbUnitOfWork : IAmADynamoDbTransactionProvider, IDisposable
{
private TransactWriteItemsRequest _tx;
@@ -15,12 +16,56 @@ public class DynamoDbUnitOfWork : IDynamoDbClientTransactionProvider, IDisposabl
/// The AWS client for dynamoDb
///
public IAmazonDynamoDB DynamoDb { get; }
+
+ ///
+ /// The response for the last transaction commit
+ ///
+ public TransactWriteItemsResponse LastResponse { get; set; }
public DynamoDbUnitOfWork(IAmazonDynamoDB dynamoDb)
{
DynamoDb = dynamoDb;
}
+ public void Close()
+ {
+ _tx = null;
+ }
+
+ ///
+ /// Commit a transaction, performing all associated write actions
+ ///
+ public void Commit()
+ {
+ if (!HasOpenTransaction)
+ throw new InvalidOperationException("No transaction to commit");
+
+ LastResponse = DynamoDb.TransactWriteItemsAsync(_tx).GetAwaiter().GetResult();
+
+ }
+
+ ///
+ /// Commit a transaction, performing all associated write actions
+ ///
+ /// A cancellation token
+ ///
+ public async Task CommitAsync(CancellationToken ct = default)
+ {
+ if (!HasOpenTransaction)
+ throw new InvalidOperationException("No transaction to commit");
+ try
+ {
+ LastResponse = await DynamoDb.TransactWriteItemsAsync(_tx, ct);
+ if (LastResponse.HttpStatusCode != HttpStatusCode.OK)
+ throw new InvalidOperationException($"HTTP error writing to DynamoDb {LastResponse.HttpStatusCode}");
+ }
+ catch (AmazonDynamoDBException e)
+ {
+ throw new InvalidOperationException($"HTTP error writing to DynamoDb {e.Message}");
+ }
+ }
+
+
///
/// Begin a transaction if one has not been started, otherwise return the extant transaction
/// We populate the TransactItems member with an empty list, so you do not need to create your own list
@@ -28,52 +73,35 @@ public DynamoDbUnitOfWork(IAmazonDynamoDB dynamoDb)
/// i.e. tx.TransactItems.Add(new TransactWriteItem(...
///
///
- public TransactWriteItemsRequest BeginOrGetTransaction()
+ public TransactWriteItemsRequest GetTransaction()
{
- if (HasTransaction())
- {
- return _tx;
- }
- else
+ if (HasOpenTransaction)
{
- _tx = new TransactWriteItemsRequest();
- _tx.TransactItems = new List();
return _tx;
}
+
+ _tx = new TransactWriteItemsRequest();
+ _tx.TransactItems = new List();
+ return _tx;
}
- ///
- /// Commit a transaction, performing all associated write actions
- ///
- /// A response indicating the status of the transaction
- public TransactWriteItemsResponse Commit()
+ public Task GetTransactionAsync(CancellationToken cancellationToken = default)
{
- if (!HasTransaction())
- throw new InvalidOperationException("No transaction to commit");
-
- return DynamoDb.TransactWriteItemsAsync(_tx).GetAwaiter().GetResult();
+ var tcs = new TaskCompletionSource();
+ tcs.SetResult(GetTransaction());
+ return tcs.Task;
}
- ///
- /// Commit a transaction, performing all associated write actions
+ ///
+ /// Is there an existing transaction?
///
- /// A response indicating the status of the transaction
- public async Task CommitAsync(CancellationToken ct = default)
- {
- if (!HasTransaction())
- throw new InvalidOperationException("No transaction to commit");
-
- return await DynamoDb.TransactWriteItemsAsync(_tx, ct);
- }
+ ///
+ public bool HasOpenTransaction => _tx != null;
///
- /// Is there an existing transaction
+ /// Is there a shared connection, not true, but we do not manage the DynamoDb client
///
- ///
- public bool HasTransaction()
- {
- return _tx != null;
- }
+ public bool IsSharedConnection => false;
///
/// Clear any transaction
@@ -83,12 +111,18 @@ public void Rollback()
_tx = null;
}
+ public Task RollbackAsync(CancellationToken cancellationToken = default)
+ {
+ Rollback();
+ return Task.CompletedTask;
+ }
+
///
/// Clear any transaction. Does not kill any client to DynamoDb as we assume that we don't own it.
///
public void Dispose()
{
- if (HasTransaction())
+ if (HasOpenTransaction)
_tx = null;
}
}
diff --git a/src/Paramore.Brighter.DynamoDb/IAmADynamoDbConnectionProvider.cs b/src/Paramore.Brighter.DynamoDb/IAmADynamoDbConnectionProvider.cs
new file mode 100644
index 0000000000..29117af24b
--- /dev/null
+++ b/src/Paramore.Brighter.DynamoDb/IAmADynamoDbConnectionProvider.cs
@@ -0,0 +1,15 @@
+using Amazon.DynamoDBv2;
+
+namespace Paramore.Brighter.DynamoDb
+{
+ ///
+ /// Provides the dynamo db connection we are using, base of any unit of work
+ ///
+ public interface IAmADynamoDbConnectionProvider
+ {
+ ///
+ /// The AWS client for dynamoDb
+ ///
+ IAmazonDynamoDB DynamoDb { get; }
+ }
+}
diff --git a/src/Paramore.Brighter.DynamoDb/IAmADynamoDbTransactionProvider.cs b/src/Paramore.Brighter.DynamoDb/IAmADynamoDbTransactionProvider.cs
new file mode 100644
index 0000000000..37757444be
--- /dev/null
+++ b/src/Paramore.Brighter.DynamoDb/IAmADynamoDbTransactionProvider.cs
@@ -0,0 +1,10 @@
+using System.Threading;
+using System.Threading.Tasks;
+using Amazon.DynamoDBv2.Model;
+
+namespace Paramore.Brighter.DynamoDb
+{
+ public interface IAmADynamoDbTransactionProvider : IAmADynamoDbConnectionProvider, IAmABoxTransactionProvider
+ {
+ }
+}
diff --git a/src/Paramore.Brighter.DynamoDb/IDynamoDbClientProvider.cs b/src/Paramore.Brighter.DynamoDb/IDynamoDbClientProvider.cs
deleted file mode 100644
index 86b0a887d6..0000000000
--- a/src/Paramore.Brighter.DynamoDb/IDynamoDbClientProvider.cs
+++ /dev/null
@@ -1,46 +0,0 @@
-using System.Threading;
-using System.Threading.Tasks;
-using Amazon.DynamoDBv2;
-using Amazon.DynamoDBv2.Model;
-
-namespace Paramore.Brighter.DynamoDb
-{
- public interface IDynamoDbClientProvider
- {
- ///
- /// The AWS client for dynamoDb
- ///
- IAmazonDynamoDB DynamoDb { get; }
-
- ///
- /// Begin a transaction if one has not been started, otherwise return the extant transaction
- ///
- ///
- TransactWriteItemsRequest BeginOrGetTransaction();
-
- ///
- /// Commit a transaction, performing all associated write actions
- ///
- /// A response indicating the status of the transaction
- TransactWriteItemsResponse Commit();
-
- ///
- /// Commit a transaction, performing all associated write actions
- ///
- /// The cancellation token for the task
- /// A response indicating the status of the transaction
- Task CommitAsync(CancellationToken ct);
-
- ///
- /// Is there an existing transaction
- ///
- ///
- bool HasTransaction();
-
- ///
- /// Clear any transaction
- ///
- void Rollback();
- }
-}
-
diff --git a/src/Paramore.Brighter.DynamoDb/IDynamoDbTransactionProvider.cs b/src/Paramore.Brighter.DynamoDb/IDynamoDbTransactionProvider.cs
deleted file mode 100644
index 90dad7735e..0000000000
--- a/src/Paramore.Brighter.DynamoDb/IDynamoDbTransactionProvider.cs
+++ /dev/null
@@ -1,7 +0,0 @@
-namespace Paramore.Brighter.DynamoDb
-{
- public interface IDynamoDbClientTransactionProvider : IDynamoDbClientProvider, IAmABoxTransactionConnectionProvider
- {
-
- }
-}
diff --git a/src/Paramore.Brighter.DynamoDb/Paramore.Brighter.DynamoDb.csproj b/src/Paramore.Brighter.DynamoDb/Paramore.Brighter.DynamoDb.csproj
index 207ca882f1..1c72030886 100644
--- a/src/Paramore.Brighter.DynamoDb/Paramore.Brighter.DynamoDb.csproj
+++ b/src/Paramore.Brighter.DynamoDb/Paramore.Brighter.DynamoDb.csproj
@@ -5,7 +5,7 @@
-
+
all
runtime; build; native; contentfiles; analyzers; buildtransitive
diff --git a/src/Paramore.Brighter.Extensions.DependencyInjection/BrighterOptions.cs b/src/Paramore.Brighter.Extensions.DependencyInjection/BrighterOptions.cs
index 77d5ec5b02..6b92ee7a2c 100644
--- a/src/Paramore.Brighter.Extensions.DependencyInjection/BrighterOptions.cs
+++ b/src/Paramore.Brighter.Extensions.DependencyInjection/BrighterOptions.cs
@@ -1,4 +1,5 @@
using Microsoft.Extensions.DependencyInjection;
+using Paramore.Brighter.FeatureSwitch;
using Polly.Registry;
namespace Paramore.Brighter.Extensions.DependencyInjection
@@ -6,14 +7,15 @@ namespace Paramore.Brighter.Extensions.DependencyInjection
public class BrighterOptions : IBrighterOptions
{
///
- /// Used to create a channel, an abstraction over a message processing pipeline
+ /// Configures the life time of the Command Processor. Defaults to Transient.
///
- public IAmAChannelFactory ChannelFactory { get; set; }
+ public ServiceLifetime CommandProcessorLifetime { get; set; } = ServiceLifetime.Transient;
///
- /// Configures the life time of the Command Processor. Defaults to Transient.
+ /// Do we support feature switching? In which case please supply an initialized feature switch registry
///
- public ServiceLifetime CommandProcessorLifetime { get; set; } = ServiceLifetime.Transient;
+ ///
+ public IAmAFeatureSwitchRegistry FeatureSwitchRegistry { get; set; } = null;
///
/// Configures the lifetime of the Handlers. Defaults to Scoped.
@@ -26,7 +28,7 @@ public class BrighterOptions : IBrighterOptions
public ServiceLifetime MapperLifetime { get; set; } = ServiceLifetime.Singleton;
///
- /// Configures the polly policy registry.
+ /// Configures the polly policy registry.
///
public IPolicyRegistry PolicyRegistry { get; set; } = new DefaultPolicy();
@@ -39,21 +41,24 @@ public class BrighterOptions : IBrighterOptions
/// Configures the lifetime of any transformers. Defaults to Singleton
///
public ServiceLifetime TransformerLifetime { get; set; } = ServiceLifetime.Singleton;
+
+
}
public interface IBrighterOptions
{
- ///
- /// Used to create a channel, an abstraction over a message processing pipeline
- ///
- IAmAChannelFactory ChannelFactory { get; set; }
-
///
/// Configures the life time of the Command Processor.
///
ServiceLifetime CommandProcessorLifetime { get; set; }
///
+ /// Do we support feature switching? In which case please supply an initialized feature switch registry
+ ///
+ ///
+ IAmAFeatureSwitchRegistry FeatureSwitchRegistry { get; set; }
+
+ ///
/// Configures the lifetime of the Handlers.
///
ServiceLifetime HandlerLifetime { get; set; }
@@ -77,5 +82,6 @@ public interface IBrighterOptions
/// Configures the lifetime of any transformers.
///
ServiceLifetime TransformerLifetime { get; set; }
- }
+
+ }
}
diff --git a/src/Paramore.Brighter.Extensions.DependencyInjection/IBrighterBuilder.cs b/src/Paramore.Brighter.Extensions.DependencyInjection/IBrighterBuilder.cs
index ad016f94fb..c4e2fd64e9 100644
--- a/src/Paramore.Brighter.Extensions.DependencyInjection/IBrighterBuilder.cs
+++ b/src/Paramore.Brighter.Extensions.DependencyInjection/IBrighterBuilder.cs
@@ -22,12 +22,10 @@ THE SOFTWARE. */
#endregion
-
using System;
-using System.Collections;
-using System.Collections.Generic;
using System.Reflection;
using Microsoft.Extensions.DependencyInjection;
+using Polly.Registry;
namespace Paramore.Brighter.Extensions.DependencyInjection
{
@@ -85,7 +83,6 @@ public interface IBrighterBuilder
/// This builder, allows chaining calls
IBrighterBuilder MapperRegistryFromAssemblies(params Assembly[] assemblies);
-
///
/// Scan the assemblies for implementations of IAmAMessageTransformAsync and register them with ServiceCollection
///
@@ -93,9 +90,17 @@ public interface IBrighterBuilder
/// This builder, allows chaining calls
IBrighterBuilder TransformsFromAssemblies(params Assembly[] assemblies);
+ ///
+ /// The policy registry to use for the command processor and the event bus
+ /// It needs to be here as we need to pass it between AddBrighter and UseExternalBus
+ ///
+ IPolicyRegistry PolicyRegistry { get; set; }
+
+
///
/// The IoC container to populate
///
IServiceCollection Services { get; }
+
}
}
diff --git a/src/Paramore.Brighter.Extensions.DependencyInjection/Paramore.Brighter.Extensions.DependencyInjection.csproj b/src/Paramore.Brighter.Extensions.DependencyInjection/Paramore.Brighter.Extensions.DependencyInjection.csproj
index 6649daf21c..5cde23b269 100644
--- a/src/Paramore.Brighter.Extensions.DependencyInjection/Paramore.Brighter.Extensions.DependencyInjection.csproj
+++ b/src/Paramore.Brighter.Extensions.DependencyInjection/Paramore.Brighter.Extensions.DependencyInjection.csproj
@@ -14,6 +14,7 @@
+
diff --git a/src/Paramore.Brighter.Extensions.DependencyInjection/ServiceCollectionBrighterBuilder.cs b/src/Paramore.Brighter.Extensions.DependencyInjection/ServiceCollectionBrighterBuilder.cs
index aa4fd8e706..6e0c371cc6 100644
--- a/src/Paramore.Brighter.Extensions.DependencyInjection/ServiceCollectionBrighterBuilder.cs
+++ b/src/Paramore.Brighter.Extensions.DependencyInjection/ServiceCollectionBrighterBuilder.cs
@@ -23,15 +23,13 @@ THE SOFTWARE. */
#endregion
-
using System;
-using System.Collections;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Reflection;
using Microsoft.Extensions.DependencyInjection;
-using Microsoft.Extensions.DependencyInjection.Extensions;
+using Polly.Registry;
namespace Paramore.Brighter.Extensions.DependencyInjection
{
@@ -40,6 +38,8 @@ public class ServiceCollectionBrighterBuilder : IBrighterBuilder
private readonly ServiceCollectionSubscriberRegistry _serviceCollectionSubscriberRegistry;
private readonly ServiceCollectionMessageMapperRegistry _mapperRegistry;
private readonly ServiceCollectionTransformerRegistry _transformerRegistry;
+
+ public IPolicyRegistry PolicyRegistry { get; set; }
///
/// Registers the components of Brighter pipelines
@@ -48,16 +48,20 @@ public class ServiceCollectionBrighterBuilder : IBrighterBuilder
/// The register for looking up message handlers
/// The register for looking up message mappers
/// The register for transforms
+ /// The list of policies that we require
public ServiceCollectionBrighterBuilder(
- IServiceCollection services,
+ IServiceCollection services,
ServiceCollectionSubscriberRegistry serviceCollectionSubscriberRegistry,
- ServiceCollectionMessageMapperRegistry mapperRegistry,
- ServiceCollectionTransformerRegistry transformerRegistry = null)
+ ServiceCollectionMessageMapperRegistry mapperRegistry,
+ ServiceCollectionTransformerRegistry transformerRegistry = null,
+ IPolicyRegistry policyRegistry = null
+ )
{
Services = services;
_serviceCollectionSubscriberRegistry = serviceCollectionSubscriberRegistry;
_mapperRegistry = mapperRegistry;
_transformerRegistry = transformerRegistry ?? new ServiceCollectionTransformerRegistry(services);
+ PolicyRegistry = policyRegistry;
}
///
@@ -66,9 +70,35 @@ public ServiceCollectionBrighterBuilder(
public IServiceCollection Services { get; }
///
- /// Scan the assemblies provided for implementations of IHandleRequests, IHandleRequestsAsync, IAmAMessageMapper and register them with ServiceCollection
+ /// Scan the assemblies provided for implementations of IHandleRequestsAsync and register them with ServiceCollection
+ ///
+ /// A callback to register handlers
+ /// This builder, allows chaining calls
+ public IBrighterBuilder AsyncHandlers(Action registerHandlers)
+ {
+ if (registerHandlers == null)
+ throw new ArgumentNullException(nameof(registerHandlers));
+
+ registerHandlers(_serviceCollectionSubscriberRegistry);
+
+ return this;
+ }
+
+ ///
+ /// Scan the assemblies provided for implementations of IHandleRequests and register them with ServiceCollection
///
/// The assemblies to scan
+ /// This builder, allows chaining calls
+ public IBrighterBuilder AsyncHandlersFromAssemblies(params Assembly[] assemblies)
+ {
+ RegisterHandlersFromAssembly(typeof(IHandleRequestsAsync<>), assemblies, typeof(IHandleRequestsAsync<>).Assembly);
+ return this;
+ }
+
+ ///
+ /// Scan the assemblies provided for implementations of IHandleRequests, IHandleRequestsAsync, IAmAMessageMapper and register them with ServiceCollection
+ ///
+ /// The assemblies to scan
///
public IBrighterBuilder AutoFromAssemblies(params Assembly[] extraAssemblies)
{
@@ -151,34 +181,6 @@ public IBrighterBuilder HandlersFromAssemblies(params Assembly[] assemblies)
return this;
}
-
- ///
- /// Scan the assemblies provided for implementations of IHandleRequestsAsync and register them with ServiceCollection
- ///
- /// A callback to register handlers
- /// This builder, allows chaining calls
- public IBrighterBuilder AsyncHandlers(Action registerHandlers)
- {
- if (registerHandlers == null)
- throw new ArgumentNullException(nameof(registerHandlers));
-
- registerHandlers(_serviceCollectionSubscriberRegistry);
-
- return this;
- }
-
- ///
- /// Scan the assemblies provided for implementations of IHandleRequests and register them with ServiceCollection
- ///
- /// The assemblies to scan
- /// This builder, allows chaining calls
- public IBrighterBuilder AsyncHandlersFromAssemblies(params Assembly[] assemblies)
- {
- RegisterHandlersFromAssembly(typeof(IHandleRequestsAsync<>), assemblies, typeof(IHandleRequestsAsync<>).Assembly);
- return this;
- }
-
-
///
/// Scan the assemblies for implementations of IAmAMessageTransformAsync and register them with the ServiceCollection
///
@@ -204,7 +206,7 @@ from i in ti.ImplementedInterfaces
return this;
}
-
+
private void RegisterHandlersFromAssembly(Type interfaceType, IEnumerable assemblies, Assembly assembly)
{
assemblies = assemblies.Concat(new[] { assembly });
diff --git a/src/Paramore.Brighter.Extensions.DependencyInjection/ServiceCollectionExtensions.cs b/src/Paramore.Brighter.Extensions.DependencyInjection/ServiceCollectionExtensions.cs
index f9bd4a0a57..bcf76d72b8 100644
--- a/src/Paramore.Brighter.Extensions.DependencyInjection/ServiceCollectionExtensions.cs
+++ b/src/Paramore.Brighter.Extensions.DependencyInjection/ServiceCollectionExtensions.cs
@@ -1,4 +1,5 @@
#region Licence
+
/* The MIT License (MIT)
Copyright © 2022 Ian Cooper
@@ -19,38 +20,41 @@ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE. */
-
-#endregion
+#endregion
using System;
-using System.Collections.Generic;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.Extensions.Logging;
using Paramore.Brighter.FeatureSwitch;
using Paramore.Brighter.Logging;
using System.Text.Json;
+using System.Transactions;
+using Paramore.Brighter.DynamoDb;
+using Polly.Registry;
namespace Paramore.Brighter.Extensions.DependencyInjection
{
public static class ServiceCollectionExtensions
{
- private static int _outboxBulkChunkSize = 100;
-
///
/// Will add Brighter into the .NET IoC Container - ServiceCollection
- /// Registers singletons with the service collection :-
+ /// Registers the following with the service collection :-
/// - BrighterOptions - how should we configure Brighter
+ /// - Feature Switch Registry - optional if features switch support is desired
+ /// - Inbox - defaults to InMemoryInbox if none supplied
/// - SubscriberRegistry - what handlers subscribe to what requests
/// - MapperRegistry - what mappers translate what messages
- /// - InMemoryOutbox - Optional - if an in memory outbox is selected
///
- /// The IoC container to update
+ /// The collection of services that we want to add registrations to
/// A callback that defines what options to set when Brighter is built
- /// A builder that can be used to populate the IoC container with handlers and mappers by inspection - used by built in factory from CommandProcessor
+ /// A builder that can be used to populate the IoC container with handlers and mappers by inspection
+ /// - used by built in factory from CommandProcessor
/// Thrown if we have no IoC provided ServiceCollection
- public static IBrighterBuilder AddBrighter(this IServiceCollection services, Action configure = null)
+ public static IBrighterBuilder AddBrighter(
+ this IServiceCollection services,
+ Action configure = null)
{
if (services == null)
throw new ArgumentNullException(nameof(services));
@@ -61,189 +65,285 @@ public static IBrighterBuilder AddBrighter(this IServiceCollection services, Act
return BrighterHandlerBuilder(services, options);
}
-
+
///
- /// Normally you want to call AddBrighter from client code, and not this method. Public only to support Service Activator extensions
- /// Registers singletons with the service collection :-
+ /// This is public so that we can call it from
+ /// which allows that extension method to be called with a configuration
+ /// that derives from .
+ /// DON'T CALL THIS DIRECTLY
+ /// Registers the following with the service collection :-
+ /// - BrighterOptions - how should we configure Brighter
+ /// - Feature Switch Registry - optional if features switch support is desired
+ /// - Inbox - defaults to InMemoryInbox if none supplied
/// - SubscriberRegistry - what handlers subscribe to what requests
/// - MapperRegistry - what mappers translate what messages
///
- /// The IoC container to update
- /// A callback that defines what options to set when Brighter is built
- /// A builder that can be used to populate the IoC container with handlers and mappers by inspection - used by built in factory from CommandProcessor
+ /// The collection of services that we want to add registrations to
+ ///
+ ///
public static IBrighterBuilder BrighterHandlerBuilder(IServiceCollection services, BrighterOptions options)
{
var subscriberRegistry = new ServiceCollectionSubscriberRegistry(services, options.HandlerLifetime);
- services.TryAddSingleton(subscriberRegistry);
+ services.TryAddSingleton(subscriberRegistry);
var transformRegistry = new ServiceCollectionTransformerRegistry(services, options.TransformerLifetime);
- services.TryAddSingleton(transformRegistry);
-
- services.TryAdd(new ServiceDescriptor(typeof(IAmACommandProcessor), BuildCommandProcessor, options.CommandProcessorLifetime));
+ services.TryAddSingleton(transformRegistry);
var mapperRegistry = new ServiceCollectionMessageMapperRegistry(services, options.MapperLifetime);
- services.TryAddSingleton(mapperRegistry);
+ services.TryAddSingleton(mapperRegistry);
+
+ if (options.FeatureSwitchRegistry != null)
+ services.TryAddSingleton(options.FeatureSwitchRegistry);
- return new ServiceCollectionBrighterBuilder(services, subscriberRegistry, mapperRegistry, transformRegistry);
+ //Add the policy registry
+ IPolicyRegistry policyRegistry;
+ if (options.PolicyRegistry == null) policyRegistry = new DefaultPolicy();
+ else policyRegistry = AddDefaults(options.PolicyRegistry);
+
+ services.TryAdd(new ServiceDescriptor(typeof(IAmACommandProcessor),
+ (serviceProvider) => (IAmACommandProcessor)BuildCommandProcessor(serviceProvider),
+ options.CommandProcessorLifetime));
+
+ return new ServiceCollectionBrighterBuilder(
+ services,
+ subscriberRegistry,
+ mapperRegistry,
+ transformRegistry,
+ policyRegistry
+ );
}
///
- /// Use an external Brighter Outbox to store messages Posted to another process (evicts based on age and size).
- /// Advantages: By using the same Db to store both any state changes for your app, and outgoing messages you can create a transaction that spans both
- /// your state change and writing to an outbox [use DepositPost to store]. Then a sweeper process can look for message not flagged as sent and send them.
- /// For low latency just send after the transaction with ClearOutbox, for higher latency just let the sweeper run in the background.
- /// The outstanding messages dispatched this way can be sent from any producer that runs a sweeper process and so it not tied to the lifetime of the
- /// producer, offering guaranteed, at least once, delivery.
- /// NOTE: there may be a database specific Use*OutBox available. If so, use that in preference to this generic method
- /// If not null, registers singletons with the service collection :-
- /// - IAmAnOutboxSync - what messages have we posted
- /// - ImAnOutboxAsync - what messages have we posted (async pipeline compatible)
+ /// An external bus is the use of Message Oriented Middleware (MoM) to dispatch a message between a producer
+ /// and a consumer. The assumption is that this is being used for inter-process communication, for example the
+ /// work queue pattern for distributing work, or between microservicves
+ /// Registers singletons with the service collection :-
+ /// - An Event Bus - used to send message externally and contains:
+ /// -- Producer Registry - A list of producers we can send middleware messages with
+ /// -- Outbox - stores messages so that they can be written in the same transaction as entity writes
+ /// -- Outbox Transaction Provider - used to provide a transaction that spans the Outbox write and
+ /// your updates to your entities
+ /// -- RelationalDb Connection Provider - if your transaction provider is for a relational db we register this
+ /// interface to access your Db and make it available to your own classes
+ /// -- Transaction Connection Provider - if your transaction provider is also a relational db connection
+ /// provider it will implement this interface which inherits from both
+ /// -- External Bus Configuration - the configuration parameters for an external bus, mainly used internally
+ /// -- UseRpc - do we want to use RPC i.e. a command blocks waiting for a response, over middleware.
///
/// The Brighter builder to add this option to
- /// The outbox provider - if your outbox supports both sync and async options, just provide this and we will register both
- ///
- ///
- public static IBrighterBuilder UseExternalOutbox(this IBrighterBuilder brighterBuilder, IAmAnOutbox outbox = null, int outboxBulkChunkSize = 100)
+ /// A callback that allows you to configure options
+ /// The transaction provider for the outbox, can be null for in-memory default
+ /// of which you must set the generic type to for
+ ///
+ /// The lifetime of the transaction provider
+ /// The Brighter builder to allow chaining of requests
+ public static IBrighterBuilder UseExternalBus(
+ this IBrighterBuilder brighterBuilder,
+ Action configure,
+ ServiceLifetime serviceLifetime = ServiceLifetime.Scoped)
{
- if (outbox is IAmAnOutboxSync)
+ if (brighterBuilder is null)
+ throw new ArgumentNullException($"{nameof(brighterBuilder)} cannot be null.", nameof(brighterBuilder));
+
+ var busConfiguration = new ExternalBusConfiguration();
+ configure?.Invoke(busConfiguration);
+ brighterBuilder.Services.TryAddSingleton(busConfiguration);
+
+ //default to using System Transactions if nothing provided, so we always technically can share the outbox transaction
+ Type transactionProvider = busConfiguration.TransactionProvider ?? typeof(CommittableTransactionProvider);
+
+ //Find the transaction type from the provider
+ Type transactionProviderInterface = typeof(IAmABoxTransactionProvider<>);
+ Type transactionType = null;
+ foreach (Type i in transactionProvider.GetInterfaces())
+ if (i.IsGenericType && i.GetGenericTypeDefinition() == transactionProviderInterface)
+ transactionType = i.GetGenericArguments()[0];
+
+ if (transactionType == null)
+ throw new ConfigurationException(
+ $"Unable to register provider of type {transactionProvider.Name}. It does not implement {typeof(IAmABoxTransactionProvider<>).Name}.");
+
+ //register the generic interface with the transaction type
+ var boxProviderType = transactionProviderInterface.MakeGenericType(transactionType);
+
+ brighterBuilder.Services.Add(new ServiceDescriptor(boxProviderType, transactionProvider, serviceLifetime));
+
+ //NOTE: It is a little unsatisfactory to hard code our types in here
+ RegisterRelationalProviderServicesMaybe(brighterBuilder, busConfiguration.ConnectionProvider, transactionProvider, serviceLifetime);
+ RegisterDynamoProviderServicesMaybe(brighterBuilder, busConfiguration.ConnectionProvider, transactionProvider, serviceLifetime);
+
+ return ExternalBusBuilder(brighterBuilder, busConfiguration, transactionType);
+ }
+
+ private static INeedARequestContext AddEventBus(
+ IServiceProvider provider,
+ INeedMessaging messagingBuilder,
+ IUseRpc useRequestResponse)
+ {
+ var eventBus = provider.GetService();
+ var eventBusConfiguration = provider.GetService();
+ var messageMapperRegistry = MessageMapperRegistry(provider);
+ var messageTransformFactory = TransformFactory(provider);
+
+ INeedARequestContext ret = null;
+ var hasEventBus = eventBus != null;
+ bool useRpc = useRequestResponse != null && useRequestResponse.RPC;
+
+ if (!hasEventBus) ret = messagingBuilder.NoExternalBus();
+
+ if (hasEventBus && !useRpc)
{
- brighterBuilder.Services.TryAdd(new ServiceDescriptor(typeof(IAmAnOutboxSync), _ => outbox, ServiceLifetime.Singleton));
+ ret = messagingBuilder.ExternalBus(
+ ExternalBusType.FireAndForget,
+ eventBus,
+ messageMapperRegistry,
+ messageTransformFactory,
+ eventBusConfiguration.ResponseChannelFactory,
+ eventBusConfiguration.ReplyQueueSubscriptions);
}
-
- if (outbox is IAmAnOutboxAsync)
+
+ if (hasEventBus && useRpc)
{
- brighterBuilder.Services.TryAdd(new ServiceDescriptor(typeof(IAmAnOutboxAsync), _ => outbox, ServiceLifetime.Singleton));
+ ret = messagingBuilder.ExternalBus(
+ ExternalBusType.RPC,
+ eventBus,
+ messageMapperRegistry,
+ messageTransformFactory,
+ eventBusConfiguration.ResponseChannelFactory,
+ eventBusConfiguration.ReplyQueueSubscriptions
+ );
}
- _outboxBulkChunkSize = outboxBulkChunkSize;
-
- return brighterBuilder;
-
+ return ret;
}
- ///
- /// Uses an external Brighter Inbox to record messages received to allow "once only" or diagnostics (how did we get here?)
- /// Advantages: by using an external inbox then you can share "once only" across multiple threads/processes and support a competing consumer
- /// model; an internal inbox is useful for testing but outside of single consumer scenarios won't work as intended
- /// If not null, registers singletons with the service collection :-
- /// - IAmAnInboxSync - what messages have we received
- /// - IAmAnInboxAsync - what messages have we received (async pipeline compatible)
- ///
- /// Extension method to support a fluent interface
- /// The external inbox to use
- /// If this is null, configure by hand, if not, will auto-add inbox to handlers
- ///
- public static IBrighterBuilder UseExternalInbox(
- this IBrighterBuilder brighterBuilder,
- IAmAnInbox inbox, InboxConfiguration inboxConfiguration = null,
- ServiceLifetime serviceLifetime = ServiceLifetime.Scoped)
- {
- if (inbox is IAmAnInboxSync)
- {
- brighterBuilder.Services.TryAdd(new ServiceDescriptor(typeof(IAmAnInboxSync), _ => inbox, serviceLifetime));
- }
-
- if (inbox is IAmAnInboxAsync)
- {
- brighterBuilder.Services.TryAdd(new ServiceDescriptor(typeof(IAmAnInboxAsync), _ => inbox, serviceLifetime));
- }
-
- if (inboxConfiguration != null)
- {
- brighterBuilder.Services.TryAddSingleton(inboxConfiguration);
- }
-
- return brighterBuilder;
- }
-
- ///
- /// Use the Brighter In-Memory Outbox to store messages Posted to another process (evicts based on age and size).
- /// Advantages: fast and no additional infrastructure required
- /// Disadvantages: The Outbox will not survive restarts, so messages not published by shutdown will not be flagged as not posted
- /// Registers singletons with the service collection :-
- /// - InMemoryOutboxSync - what messages have we posted
- /// - InMemoryOutboxAsync - what messages have we posted (async pipeline compatible)
- ///
- /// The builder we are adding this facility to
- /// The Brighter builder to allow chaining of requests
- public static IBrighterBuilder UseInMemoryOutbox(this IBrighterBuilder brighterBuilder)
+ private static IPolicyRegistry AddDefaults(IPolicyRegistry policyRegistry)
{
- brighterBuilder.Services.TryAdd(new ServiceDescriptor(typeof(IAmAnOutboxSync), _ => new InMemoryOutbox(), ServiceLifetime.Singleton));
- brighterBuilder.Services.TryAdd(new ServiceDescriptor(typeof(IAmAnOutboxAsync), _ => new InMemoryOutbox(), ServiceLifetime.Singleton));
+ if (!policyRegistry.ContainsKey(CommandProcessor.RETRYPOLICY))
+ throw new ConfigurationException(
+ "The policy registry is missing the CommandProcessor.RETRYPOLICY policy which is required");
- return brighterBuilder;
+ if (!policyRegistry.ContainsKey(CommandProcessor.CIRCUITBREAKER))
+ throw new ConfigurationException(
+ "The policy registry is missing the CommandProcessor.CIRCUITBREAKER policy which is required");
+
+ return policyRegistry;
}
- ///
- /// Uses the Brighter In-Memory Inbox to store messages received to support once-only messaging and diagnostics
- /// Advantages: Fast and no additional infrastructure required
- /// Disadvantages: The inbox will not survive restarts, so messages will not be de-duped if received after a restart.
- /// The inbox will not work across threads/processes so only works with a single performer/consumer.
- /// Registers singletons with the service collection:
- /// - InMemoryInboxSync - what messages have we received
- /// - InMemoryInboxAsync - what messages have we received (async pipeline compatible)
- ///
- ///
- ///
- public static IBrighterBuilder UseInMemoryInbox(this IBrighterBuilder brighterBuilder)
+ private static object BuildCommandProcessor(IServiceProvider provider)
{
- brighterBuilder.Services.TryAdd(new ServiceDescriptor(typeof(IAmAnInboxSync), _ => new InMemoryInbox(), ServiceLifetime.Singleton));
- brighterBuilder.Services.TryAdd(new ServiceDescriptor(typeof(IAmAnInboxAsync), _ => new InMemoryInbox(), ServiceLifetime.Singleton));
+ var loggerFactory = provider.GetService();
+ ApplicationLogging.LoggerFactory = loggerFactory;
- return brighterBuilder;
+ var options = provider.GetService();
+ var subscriberRegistry = provider.GetService();
+ var useRequestResponse = provider.GetService();
+
+ var handlerFactory = new ServiceProviderHandlerFactory(provider);
+ var handlerConfiguration = new HandlerConfiguration(subscriberRegistry, handlerFactory);
+
+ var needHandlers = CommandProcessorBuilder.With();
+
+ var featureSwitchRegistry = provider.GetService();
+
+ if (featureSwitchRegistry != null)
+ needHandlers = needHandlers.ConfigureFeatureSwitches(featureSwitchRegistry);
+
+ var policyBuilder = needHandlers.Handlers(handlerConfiguration);
+
+ var messagingBuilder = options.PolicyRegistry == null
+ ? policyBuilder.DefaultPolicy()
+ : policyBuilder.Policies(options.PolicyRegistry);
+
+ INeedARequestContext ret = AddEventBus(provider, messagingBuilder, useRequestResponse);
+
+ var commandProcessor = ret
+ .RequestContextFactory(options.RequestContextFactory)
+ .Build();
+
+ return commandProcessor;
}
- ///
- /// An external bus is the use of Message Oriented Middleware (MoM) to dispatch a message between a producer and a consumer. The assumption is that this
- /// is being used for inter-process communication, for example the work queue pattern for distributing work, or between microservicves
- /// Registers singletons with the service collection :-
- /// - Producer - the Gateway wrapping access to Middleware
- /// - UseRpc - do we want to use Rpc i.e. a command blocks waiting for a response, over middleware
- ///
- /// The Brighter builder to add this option to
- /// The collection of producers - clients that connect to a specific transport
- /// Add support for RPC over MoM by using a reply queue
- /// Reply queue subscription
- /// The Brighter builder to allow chaining of requests
- public static IBrighterBuilder UseExternalBus(this IBrighterBuilder brighterBuilder, IAmAProducerRegistry producerRegistry, bool useRequestResponseQueues = false, IEnumerable replyQueueSubscriptions = null)
+ private static IBrighterBuilder ExternalBusBuilder(
+ IBrighterBuilder brighterBuilder,
+ IAmExternalBusConfiguration externalBusConfiguration,
+ Type transactionType)
{
+ if (externalBusConfiguration.ProducerRegistry == null)
+ throw new ConfigurationException("An external bus must have an IAmAProducerRegistry");
+
+ var serviceCollection = brighterBuilder.Services;
+
+ serviceCollection.TryAddSingleton(externalBusConfiguration);
+ serviceCollection.TryAddSingleton(externalBusConfiguration.ProducerRegistry);
+
+ //we always need an outbox in case of producer callbacks
+ var outbox = externalBusConfiguration.Outbox;
+ if (outbox == null)
+ {
+ outbox = new InMemoryOutbox();
+ }
+
+ //we create the outbox from interfaces from the determined transaction type to prevent the need
+ //to pass generic types as we know the transaction provider type
+ var syncOutboxType = typeof(IAmAnOutboxSync<,>).MakeGenericType(typeof(Message), transactionType);
+ var asyncOutboxType = typeof(IAmAnOutboxAsync<,>).MakeGenericType(typeof(Message), transactionType);
+
+ foreach (Type i in outbox.GetType().GetInterfaces())
+ {
+ if (i.IsGenericType && i.GetGenericTypeDefinition() == syncOutboxType)
+ {
+ var outboxDescriptor = new ServiceDescriptor(syncOutboxType, _ => outbox, ServiceLifetime.Singleton);
+ serviceCollection.Add(outboxDescriptor);
+ }
+
+ if (i.IsGenericType && i.GetGenericTypeDefinition() == asyncOutboxType)
+ {
+ var asyncOutboxdescriptor = new ServiceDescriptor(asyncOutboxType, _ => outbox, ServiceLifetime.Singleton);
+ serviceCollection.Add(asyncOutboxdescriptor);
+ }
+ }
+
+ if (externalBusConfiguration.UseRpc)
+ {
+ serviceCollection.TryAddSingleton(new UseRpc(externalBusConfiguration.UseRpc,
+ externalBusConfiguration.ReplyQueueSubscriptions));
+ }
- brighterBuilder.Services.TryAddSingleton(producerRegistry);
-
- brighterBuilder.Services.TryAddSingleton(new UseRpc(useRequestResponseQueues, replyQueueSubscriptions));
+ //Because the bus has specialized types as members, we need to create the bus type dynamically
+ //again to prevent someone configuring Brighter from having to pass generic types
+ var busType = typeof(ExternalBusServices<,>).MakeGenericType(typeof(Message), transactionType);
- return brighterBuilder;
- }
+ IAmAnExternalBusService bus = (IAmAnExternalBusService)Activator.CreateInstance(busType,
+ externalBusConfiguration.ProducerRegistry,
+ brighterBuilder.PolicyRegistry,
+ outbox,
+ externalBusConfiguration.OutboxBulkChunkSize,
+ externalBusConfiguration.OutboxTimeout);
+
+ serviceCollection.TryAddSingleton(bus);
- ///
- /// Configure a Feature Switch registry to control handlers to be feature switched at runtime
- ///
- /// The Brighter builder to add this option to
- /// The registry for handler Feature Switches
- /// The Brighter builder to allow chaining of requests
- public static IBrighterBuilder UseFeatureSwitches(this IBrighterBuilder brighterBuilder, IAmAFeatureSwitchRegistry featureSwitchRegistry)
- {
- brighterBuilder.Services.TryAddSingleton(featureSwitchRegistry);
return brighterBuilder;
}
-
+
///
- /// Config the Json Serialiser that is used inside of Brighter
+ /// Config the Json Serializer that is used inside of Brighter
///
/// The Brighter Builder
/// Action to configure the options
/// Brighter Builder
- public static IBrighterBuilder ConfigureJsonSerialisation(this IBrighterBuilder brighterBuilder, Action configure)
+ public static IBrighterBuilder ConfigureJsonSerialisation(this IBrighterBuilder brighterBuilder,
+ Action configure)
{
var options = new JsonSerializerOptions();
-
+
configure.Invoke(options);
JsonSerialisationOptions.Options = options;
-
+
return brighterBuilder;
}
-
+
///
/// Registers message mappers with the registry. Normally you don't need to call this, it is called by the builder for Brighter or the Service Activator
/// Visibility is required for use from both
@@ -263,129 +363,62 @@ public static MessageMapperRegistry MessageMapperRegistry(IServiceProvider provi
return messageMapperRegistry;
}
-
- ///
- /// Creates transforms. Normally you don't need to call this, it is called by the builder for Brighter or the Service Activator
- /// Visibility is required for use from both
- ///
- /// The IoC container to build the transform factory over
- ///
- public static ServiceProviderTransformerFactory TransformFactory(IServiceProvider provider)
- {
- return new ServiceProviderTransformerFactory(provider);
- }
- private static CommandProcessor BuildCommandProcessor(IServiceProvider provider)
+ private static void RegisterDynamoProviderServicesMaybe(
+ IBrighterBuilder brighterBuilder,
+ Type connectionProvider,
+ Type transactionProvider,
+ ServiceLifetime serviceLifetime)
{
- var loggerFactory = provider.GetService();
- ApplicationLogging.LoggerFactory = loggerFactory;
-
- var options = provider.GetService();
- var subscriberRegistry = provider.GetService();
- var useRequestResponse = provider.GetService();
-
- var handlerFactory = new ServiceProviderHandlerFactory(provider);
- var handlerConfiguration = new HandlerConfiguration(subscriberRegistry, handlerFactory);
-
- var messageMapperRegistry = MessageMapperRegistry(provider);
-
- var transformFactory = TransformFactory(provider);
-
- var outbox = provider.GetService>();
- var asyncOutbox = provider.GetService>();
- var overridingConnectionProvider = provider.GetService();
-
- if (outbox == null) outbox = new InMemoryOutbox();
- if (asyncOutbox == null) asyncOutbox = new InMemoryOutbox();
-
- var inboxConfiguration = provider.GetService();
-
- var producerRegistry = provider.GetService();
-
- var needHandlers = CommandProcessorBuilder.With();
-
- var featureSwitchRegistry = provider.GetService();
-
- if (featureSwitchRegistry != null)
- needHandlers = needHandlers.ConfigureFeatureSwitches(featureSwitchRegistry);
+ //not all box transaction providers are also relational connection providers
+ if (typeof(IAmADynamoDbConnectionProvider).IsAssignableFrom(connectionProvider))
+ {
+ brighterBuilder.Services.Add(new ServiceDescriptor(typeof(IAmADynamoDbConnectionProvider),
+ connectionProvider, serviceLifetime));
+ }
- var policyBuilder = needHandlers.Handlers(handlerConfiguration);
-
- var messagingBuilder = options.PolicyRegistry == null
- ? policyBuilder.DefaultPolicy()
- : policyBuilder.Policies(options.PolicyRegistry);
-
- var commandProcessor = AddExternalBusMaybe(
- options,
- producerRegistry,
- messagingBuilder,
- messageMapperRegistry,
- inboxConfiguration,
- outbox,
- overridingConnectionProvider,
- useRequestResponse,
- _outboxBulkChunkSize,
- transformFactory)
- .RequestContextFactory(options.RequestContextFactory)
- .Build();
-
- return commandProcessor;
+ //not all box transaction providers are also relational connection providers
+ if (typeof(IAmADynamoDbTransactionProvider).IsAssignableFrom(transactionProvider))
+ {
+ //register the combined interface just in case
+ brighterBuilder.Services.Add(new ServiceDescriptor(typeof(IAmADynamoDbTransactionProvider),
+ transactionProvider, serviceLifetime));
+ }
}
-
- private enum ExternalBusType
+ private static void RegisterRelationalProviderServicesMaybe(
+ IBrighterBuilder brighterBuilder,
+ Type connectionProvider,
+ Type transactionProvider,
+ ServiceLifetime serviceLifetime
+ )
{
- None = 0,
- FireAndForget = 1,
- RPC = 2
- }
-
- private static INeedARequestContext AddExternalBusMaybe(
- IBrighterOptions options,
- IAmAProducerRegistry producerRegistry,
- INeedMessaging messagingBuilder,
- MessageMapperRegistry messageMapperRegistry,
- InboxConfiguration inboxConfiguration,
- IAmAnOutboxSync outbox,
- IAmABoxTransactionConnectionProvider overridingConnectionProvider,
- IUseRpc useRequestResponse,
- int outboxBulkChunkSize,
- IAmAMessageTransformerFactory transformerFactory)
- {
- ExternalBusType externalBusType = GetExternalBusType(producerRegistry, useRequestResponse);
-
- if (externalBusType == ExternalBusType.None)
- return messagingBuilder.NoExternalBus();
- else if (externalBusType == ExternalBusType.FireAndForget)
- return messagingBuilder.ExternalBus(
- new ExternalBusConfiguration(
- producerRegistry,
- messageMapperRegistry,
- outboxBulkChunkSize: outboxBulkChunkSize,
- useInbox: inboxConfiguration,
- transformerFactory: transformerFactory),
- outbox,
- overridingConnectionProvider);
- else if (externalBusType == ExternalBusType.RPC)
+ //not all box transaction providers are also relational connection providers
+ if (typeof(IAmARelationalDbConnectionProvider).IsAssignableFrom(connectionProvider))
{
- return messagingBuilder.ExternalRPC(
- new ExternalBusConfiguration(
- producerRegistry,
- messageMapperRegistry,
- responseChannelFactory: options.ChannelFactory,
- useInbox: inboxConfiguration),
- outbox,
- useRequestResponse.ReplyQueueSubscriptions);
+ brighterBuilder.Services.Add(new ServiceDescriptor(typeof(IAmARelationalDbConnectionProvider),
+ connectionProvider, serviceLifetime));
+ }
+
+ //not all box transaction providers are also relational connection providers
+ if (typeof(IAmATransactionConnectionProvider).IsAssignableFrom(transactionProvider))
+ {
+ //register the combined interface just in case
+ brighterBuilder.Services.Add(new ServiceDescriptor(typeof(IAmATransactionConnectionProvider),
+ transactionProvider, serviceLifetime));
}
-
- throw new ArgumentOutOfRangeException("The external bus type requested was not understood");
}
- private static ExternalBusType GetExternalBusType(IAmAProducerRegistry producerRegistry, IUseRpc useRequestResponse)
+ ///
+ /// Creates transforms. Normally you don't need to call this, it is called by the builder for Brighter or
+ /// the Service Activator
+ /// Visibility is required for use from both
+ ///
+ /// The IoC container to build the transform factory over
+ ///
+ public static ServiceProviderTransformerFactory TransformFactory(IServiceProvider provider)
{
- var externalBusType = producerRegistry == null ? ExternalBusType.None : ExternalBusType.FireAndForget;
- if (externalBusType == ExternalBusType.FireAndForget && useRequestResponse.RPC) externalBusType = ExternalBusType.RPC;
- return externalBusType;
+ return new ServiceProviderTransformerFactory(provider);
}
}
}
diff --git a/src/Paramore.Brighter.Extensions.Hosting/HostedServiceCollectionExtensions.cs b/src/Paramore.Brighter.Extensions.Hosting/HostedServiceCollectionExtensions.cs
index a26da51c7d..4e38bce4be 100644
--- a/src/Paramore.Brighter.Extensions.Hosting/HostedServiceCollectionExtensions.cs
+++ b/src/Paramore.Brighter.Extensions.Hosting/HostedServiceCollectionExtensions.cs
@@ -24,7 +24,7 @@ public static IBrighterBuilder UseOutboxSweeper(this IBrighterBuilder brighterBu
return brighterBuilder;
}
- public static IBrighterBuilder UseOutboxArchiver(this IBrighterBuilder brighterBuilder,
+ public static IBrighterBuilder UseOutboxArchiver(this IBrighterBuilder brighterBuilder,
IAmAnArchiveProvider archiveProvider,
Action timedOutboxArchiverOptionsAction = null)
{
@@ -33,7 +33,7 @@ public static IBrighterBuilder UseOutboxArchiver(this IBrighterBuilder brighterB
brighterBuilder.Services.TryAddSingleton(options);
brighterBuilder.Services.AddSingleton(archiveProvider);
- brighterBuilder.Services.AddHostedService