Skip to content

Commit a30a394

Browse files
authored
Kiota-based backend client + complete code cleanup
* Added crosscutting ServiceClients project with Kiota-based clients * Cleaned GlobalUsings + added reference to EcommerceDDD.ServiceClients * Added CancellationToken argument * Fixed ProducesResponseType * Updated debezium/connect to 2.5 * Injected ApiGatewayClient to make API requests
1 parent 121f572 commit a30a394

File tree

168 files changed

+8389
-894
lines changed

Some content is hidden

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

168 files changed

+8389
-894
lines changed

EcommerceDDD.sln

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "EcommerceDDD.QuoteManagemen
6565
EndProject
6666
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "EcommerceDDD.InventoryManagement.Tests", "src\Services\EcommerceDDD.InventoryManagement.Tests\EcommerceDDD.InventoryManagement.Tests.csproj", "{487D7187-2669-4BB8-A3A7-F39031EC818B}"
6767
EndProject
68+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EcommerceDDD.ServiceClients", "src\Crosscutting\EcommerceDDD.ServiceClients\EcommerceDDD.ServiceClients.csproj", "{46F37BDA-92DA-439B-A31D-170D22EE88FE}"
69+
EndProject
6870
Global
6971
GlobalSection(SolutionConfigurationPlatforms) = preSolution
7072
Debug|Any CPU = Debug|Any CPU
@@ -241,6 +243,14 @@ Global
241243
{487D7187-2669-4BB8-A3A7-F39031EC818B}.Release|Any CPU.Build.0 = Release|Any CPU
242244
{487D7187-2669-4BB8-A3A7-F39031EC818B}.Release|x64.ActiveCfg = Release|Any CPU
243245
{487D7187-2669-4BB8-A3A7-F39031EC818B}.Release|x64.Build.0 = Release|Any CPU
246+
{46F37BDA-92DA-439B-A31D-170D22EE88FE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
247+
{46F37BDA-92DA-439B-A31D-170D22EE88FE}.Debug|Any CPU.Build.0 = Debug|Any CPU
248+
{46F37BDA-92DA-439B-A31D-170D22EE88FE}.Debug|x64.ActiveCfg = Debug|Any CPU
249+
{46F37BDA-92DA-439B-A31D-170D22EE88FE}.Debug|x64.Build.0 = Debug|Any CPU
250+
{46F37BDA-92DA-439B-A31D-170D22EE88FE}.Release|Any CPU.ActiveCfg = Release|Any CPU
251+
{46F37BDA-92DA-439B-A31D-170D22EE88FE}.Release|Any CPU.Build.0 = Release|Any CPU
252+
{46F37BDA-92DA-439B-A31D-170D22EE88FE}.Release|x64.ActiveCfg = Release|Any CPU
253+
{46F37BDA-92DA-439B-A31D-170D22EE88FE}.Release|x64.Build.0 = Release|Any CPU
244254
EndGlobalSection
245255
GlobalSection(SolutionProperties) = preSolution
246256
HideSolutionNode = FALSE
@@ -273,6 +283,7 @@ Global
273283
{CA70DD69-D513-4209-8E4A-A47F6773D1DA} = {C90A4F2B-9DDF-40F8-B0EC-C5546622313E}
274284
{FD844762-CFC4-4A49-9831-042A86294BF3} = {7B1F8BA8-D8A4-4604-92F8-8AB9EA0485B8}
275285
{487D7187-2669-4BB8-A3A7-F39031EC818B} = {C90A4F2B-9DDF-40F8-B0EC-C5546622313E}
286+
{46F37BDA-92DA-439B-A31D-170D22EE88FE} = {2EBCF63E-7C14-47A7-B972-C7870434AD32}
276287
EndGlobalSection
277288
GlobalSection(ExtensibilityGlobals) = postSolution
278289
SolutionGuid = {920B9A56-71E3-492F-93BD-FA154EF349D2}

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ This project is an experimental full-stack application I use to combine several
2929
├── Core.Infrastructure
3030
3131
├── Crosscutting
32+
│ ├── ServiceClients
3233
│   ├── ApiGateway
3334
│   └── IdentityServer
3435
@@ -107,6 +108,7 @@ The microservices composing the backend are built to be as simple as possible, s
107108
<li>XUnit 2.9.3</li>
108109
<li>NSubstitute 5.3.0</li>
109110
<li>SwaggerGen/SwaggerUI 8.1.1</li>
111+
<li>Microsoft.Kiota.* 1.17.2</li>
110112
</ul>
111113
</li>
112114
<li>

docker-compose.yml

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,7 @@ services:
103103
condition: service_started
104104

105105
connect:
106-
image: debezium/connect:2.1
106+
image: debezium/connect:2.5
107107
container_name: connect
108108
ports:
109109
- '8083:8083'
@@ -287,11 +287,11 @@ services:
287287
on-failure
288288
depends_on:
289289
postgres:
290-
condition: service_healthy
291-
ecommerceddd-identityserver:
292-
condition: service_started
290+
condition: service_healthy
293291
ecommerceddd-products:
294292
condition: service_started
293+
ecommerceddd-identityserver:
294+
condition: service_started
295295

296296
ecommerceddd-quotes:
297297
image: ecommerceddd-quotes
@@ -317,11 +317,11 @@ services:
317317
on-failure
318318
depends_on:
319319
postgres:
320-
condition: service_healthy
321-
ecommerceddd-identityserver:
322-
condition: service_started
320+
condition: service_healthy
323321
ecommerceddd-products:
324322
condition: service_started
323+
ecommerceddd-identityserver:
324+
condition: service_started
325325

326326
ecommerceddd-orders:
327327
image: ecommerceddd-orders
@@ -350,12 +350,12 @@ services:
350350
condition: service_healthy
351351
kafka:
352352
condition: service_healthy
353+
ecommerceddd-products:
354+
condition: service_started
353355
connect:
354356
condition: service_started
355357
ecommerceddd-identityserver:
356-
condition: service_started
357-
ecommerceddd-products:
358-
condition: service_started
358+
condition: service_started
359359

360360
ecommerceddd-payments:
361361
image: ecommerceddd-payments
@@ -384,12 +384,12 @@ services:
384384
condition: service_healthy
385385
kafka:
386386
condition: service_healthy
387+
ecommerceddd-products:
388+
condition: service_started
387389
connect:
388390
condition: service_started
389391
ecommerceddd-identityserver:
390-
condition: service_started
391-
ecommerceddd-products:
392-
condition: service_started
392+
condition: service_started
393393

394394
ecommerceddd-shipments:
395395
image: ecommerceddd-shipments
@@ -418,12 +418,12 @@ services:
418418
condition: service_healthy
419419
kafka:
420420
condition: service_healthy
421+
ecommerceddd-products:
422+
condition: service_started
421423
connect:
422424
condition: service_started
423425
ecommerceddd-identityserver:
424-
condition: service_started
425-
ecommerceddd-products:
426-
condition: service_started
426+
condition: service_started
427427

428428
networks:
429429
ecommercedddnet:

src/Core/EcommerceDDD.Core.Infrastructure/Marten/MartenRepository.cs

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ public class MartenRepository<TA>(
55
ILogger<MartenRepository<TA>> logger
66
) : IEventStoreRepository<TA> where TA : class, IAggregateRoot<StronglyTypedId<Guid>>
77
{
8-
private readonly IDocumentSession _documentSession = documentSession
8+
private readonly IDocumentSession _documentSession = documentSession
99
?? throw new ArgumentNullException(nameof(documentSession));
1010
private readonly ILogger<MartenRepository<TA>> _logger = logger
1111
?? throw new ArgumentNullException(nameof(logger));
@@ -22,9 +22,9 @@ public async Task<long> AppendEventsAsync(TA aggregate, CancellationToken cancel
2222
var nextVersion = aggregate.Version + events.Length;
2323

2424
aggregate.ClearUncommittedEvents();
25-
_documentSession.Events.Append(aggregate.Id.Value, nextVersion, events);
25+
_documentSession.Events.Append(aggregate.Id.Value, nextVersion, events);
2626

27-
await _documentSession.SaveChangesAsync();
27+
await _documentSession.SaveChangesAsync(cancellationToken);
2828

2929
return nextVersion;
3030
}
@@ -39,8 +39,12 @@ public async Task<long> AppendEventsAsync(TA aggregate, CancellationToken cancel
3939
/// <exception cref="InvalidOperationException"></exception>
4040
public async Task<TA> FetchStreamAsync(Guid id, int? version = null, CancellationToken cancellationToken = default)
4141
{
42-
var aggregate = await _documentSession.Events.AggregateStreamAsync<TA>(id, version ?? 0);
43-
return aggregate ?? throw new InvalidOperationException($"No aggregate found with id {id}.");
42+
var aggregate = await _documentSession.Events.AggregateStreamAsync<TA>(
43+
id, version ?? 0,
44+
token: cancellationToken
45+
);
46+
return aggregate ??
47+
throw new InvalidOperationException($"No aggregate found with id {id}.");
4448
}
4549

4650
/// <summary>
@@ -57,7 +61,7 @@ public void AppendToOutbox(INotification @event)
5761
var integrationEvent = IntegrationEvent
5862
.FromNotification(@event!);
5963

60-
_logger.LogInformation($"Adding integration event {@event} to outbox...", @event);
64+
_logger.LogInformation($"Adding integration event {@event} to outbox...", @event);
6165
_documentSession.Store(integrationEvent!);
6266
}
6367
}

src/Crosscutting/EcommerceDDD.IdentityServer/GlobalUsings.cs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
1-
global using System.Net;
1+
global using Duende.IdentityModel;
22
global using Duende.IdentityServer;
33
global using Duende.IdentityServer.EntityFramework.DbContexts;
44
global using Duende.IdentityServer.EntityFramework.Mappers;
5+
global using Duende.IdentityServer.Extensions;
56
global using Duende.IdentityServer.Models;
67
global using Duende.IdentityServer.Services;
78
global using EcommerceDDD.Core.Infrastructure;
9+
global using EcommerceDDD.Core.Infrastructure.Extensions;
810
global using EcommerceDDD.Core.Infrastructure.Identity;
911
global using EcommerceDDD.Core.Infrastructure.WebApi;
1012
global using EcommerceDDD.IdentityServer.API.Controllers.Requests;
@@ -21,7 +23,6 @@
2123
global using Microsoft.Extensions.Options;
2224
global using Newtonsoft.Json;
2325
global using System.ComponentModel.DataAnnotations;
26+
global using System.Net;
2427
global using System.Reflection;
2528
global using System.Security.Claims;
26-
global using Duende.IdentityServer.Extensions;
27-
global using Duende.IdentityModel;

src/Crosscutting/EcommerceDDD.IdentityServer/Program.cs

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
using EcommerceDDD.Core.Infrastructure.Extensions;
2-
31
var builder = WebApplication.CreateBuilder(args);
42
var services = builder.Services;
53

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
3+
<PropertyGroup>
4+
<TargetFramework>net8.0</TargetFramework>
5+
<ImplicitUsings>enable</ImplicitUsings>
6+
<Nullable>enable</Nullable>
7+
</PropertyGroup>
8+
9+
<ItemGroup>
10+
<PackageReference Include="Microsoft.Kiota.Abstractions" Version="1.17.2" />
11+
<PackageReference Include="Microsoft.Kiota.Serialization.Form" Version="1.17.2" />
12+
<PackageReference Include="Microsoft.Kiota.Serialization.Json" Version="1.17.2" />
13+
<PackageReference Include="Microsoft.Kiota.Serialization.Multipart" Version="1.17.2" />
14+
<PackageReference Include="Microsoft.Kiota.Serialization.Text" Version="1.17.2" />
15+
<PackageReference Include="Microsoft.Kiota.Http.HttpClientLibrary" Version="1.17.2" />
16+
</ItemGroup>
17+
18+
<ItemGroup>
19+
<ProjectReference Include="..\..\Core\EcommerceDDD.Core.Infrastructure\EcommerceDDD.Core.Infrastructure.csproj" />
20+
</ItemGroup>
21+
22+
<ItemGroup>
23+
<None Update="apigateway.yaml">
24+
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
25+
</None>
26+
</ItemGroup>
27+
28+
<!--# Generating API Gateway client with Kiota | Uncomment it to Generate the API Gateway client with Kiota again-->
29+
<!--<Target Name="GenerateKiotaClient" BeforeTargets="Build">
30+
<Exec Command="kiota generate -d ../../../src/Crosscutting/EcommerceDDD.ServiceClients/apigateway.yaml -l CSharp -c ApiGatewayClient -n EcommerceDDD.ServiceClients.ApiGateway -o Kiota" />
31+
</Target>-->
32+
</Project>
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
namespace EcommerceDDD.ServiceClients.Extensions;
2+
3+
public static class ApiGatewayClientExtensions
4+
{
5+
public static IServiceCollection AddApiGatewayClient(this IServiceCollection services, IConfiguration configuration)
6+
{
7+
// Configure HttpClient with Bearer Token Authentication
8+
services.AddHttpClient<IRequestAdapter, HttpClientRequestAdapter>(client =>
9+
{
10+
var baseUrl = configuration["IntegrationHttpSettings:ApiGatewayBaseUrl"];
11+
if (string.IsNullOrEmpty(baseUrl))
12+
throw new InvalidOperationException("The ApiGatewayBaseUrl configuration setting is missing or empty.");
13+
14+
client.BaseAddress = new Uri(baseUrl);
15+
// Optionally configure the client further (timeouts, headers, etc.)
16+
})
17+
.AddTypedClient<IRequestAdapter>((httpClient, serviceProvider) =>
18+
{
19+
var tokenRequester = serviceProvider
20+
.GetRequiredService<ITokenRequester>();
21+
22+
// Create token provider with the required dependency
23+
var tokenProvider = new BearerTokenProvider(tokenRequester);
24+
var authProvider = new BaseBearerTokenAuthenticationProvider(tokenProvider);
25+
return new HttpClientRequestAdapter(authProvider, httpClient: httpClient);
26+
});
27+
28+
// Configure ApiGatewayClient
29+
services.AddSingleton<ApiGatewayClient>(sp =>
30+
{
31+
var adapter = sp.GetRequiredService<IRequestAdapter>();
32+
return new ApiGatewayClient(adapter);
33+
});
34+
35+
return services;
36+
}
37+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
global using EcommerceDDD.Core.Infrastructure.Identity;
2+
global using EcommerceDDD.ServiceClients.ApiGateway;
3+
global using Microsoft.Extensions.Configuration;
4+
global using Microsoft.Extensions.DependencyInjection;
5+
global using Microsoft.Kiota.Abstractions;
6+
global using Microsoft.Kiota.Abstractions.Authentication;
7+
global using Microsoft.Kiota.Http.HttpClientLibrary;
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
// <auto-generated/>
2+
#pragma warning disable CS0618
3+
using EcommerceDDD.ServiceClients.ApiGateway.Api.Accounts.Login;
4+
using EcommerceDDD.ServiceClients.ApiGateway.Api.Accounts.Register;
5+
using Microsoft.Kiota.Abstractions.Extensions;
6+
using Microsoft.Kiota.Abstractions;
7+
using System.Collections.Generic;
8+
using System.IO;
9+
using System.Threading.Tasks;
10+
using System;
11+
namespace EcommerceDDD.ServiceClients.ApiGateway.Api.Accounts
12+
{
13+
/// <summary>
14+
/// Builds and executes requests for operations under \api\accounts
15+
/// </summary>
16+
[global::System.CodeDom.Compiler.GeneratedCode("Kiota", "1.0.0")]
17+
public partial class AccountsRequestBuilder : BaseRequestBuilder
18+
{
19+
/// <summary>The login property</summary>
20+
public global::EcommerceDDD.ServiceClients.ApiGateway.Api.Accounts.Login.LoginRequestBuilder Login
21+
{
22+
get => new global::EcommerceDDD.ServiceClients.ApiGateway.Api.Accounts.Login.LoginRequestBuilder(PathParameters, RequestAdapter);
23+
}
24+
/// <summary>The register property</summary>
25+
public global::EcommerceDDD.ServiceClients.ApiGateway.Api.Accounts.Register.RegisterRequestBuilder Register
26+
{
27+
get => new global::EcommerceDDD.ServiceClients.ApiGateway.Api.Accounts.Register.RegisterRequestBuilder(PathParameters, RequestAdapter);
28+
}
29+
/// <summary>
30+
/// Instantiates a new <see cref="global::EcommerceDDD.ServiceClients.ApiGateway.Api.Accounts.AccountsRequestBuilder"/> and sets the default values.
31+
/// </summary>
32+
/// <param name="pathParameters">Path parameters for the request</param>
33+
/// <param name="requestAdapter">The request adapter to use to execute the requests.</param>
34+
public AccountsRequestBuilder(Dictionary<string, object> pathParameters, IRequestAdapter requestAdapter) : base(requestAdapter, "{+baseurl}/api/accounts", pathParameters)
35+
{
36+
}
37+
/// <summary>
38+
/// Instantiates a new <see cref="global::EcommerceDDD.ServiceClients.ApiGateway.Api.Accounts.AccountsRequestBuilder"/> and sets the default values.
39+
/// </summary>
40+
/// <param name="rawUrl">The raw URL to use for the request builder.</param>
41+
/// <param name="requestAdapter">The request adapter to use to execute the requests.</param>
42+
public AccountsRequestBuilder(string rawUrl, IRequestAdapter requestAdapter) : base(requestAdapter, "{+baseurl}/api/accounts", rawUrl)
43+
{
44+
}
45+
}
46+
}
47+
#pragma warning restore CS0618

0 commit comments

Comments
 (0)