Skip to content

Commit d8e9985

Browse files
committed
feat: Introduce API client library with generation tooling and paged list infrastructure
1 parent 69aa4a2 commit d8e9985

19 files changed

Lines changed: 2703 additions & 55 deletions

BookStore.slnx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,9 @@
1818
<Project Path="src/ApiService/BookStore.ApiService.Tests/BookStore.ApiService.Tests.csproj" />
1919
<Project Path="src/ApiService/BookStore.ApiService/BookStore.ApiService.csproj" />
2020
</Folder>
21+
<Folder Name="/src/Client/">
22+
<Project Path="src/Client/BookStore.Client/BookStore.Client.csproj" />
23+
</Folder>
2124
<Folder Name="/src/Shared/">
2225
<Project Path="src/Shared/BookStore.Shared.Tests/BookStore.Shared.Tests.csproj" />
2326
<Project Path="src/Shared/BookStore.Shared/BookStore.Shared.csproj" />

README.md

Lines changed: 41 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ The Aspire dashboard opens automatically, providing access to:
4242
- **Real-time Updates** with SignalR notifications
4343
- **Optimistic UI** for instant feedback with eventual consistency
4444
- **Responsive Design** for desktop and mobile
45-
- **Type-safe API Client** with Refit
45+
- **Type-safe API Client** with BookStore.Client library (Refit-based)
4646
- **Resilience** with Polly (retry and circuit breaker)
4747

4848
### Backend API
@@ -83,41 +83,51 @@ See [Analyzer Rules Documentation](docs/analyzer-rules.md) for details.
8383
```
8484
BookStore/
8585
├── src/
86-
│ ├── BookStore.ApiService/ # Backend API with event sourcing
87-
│ │ ├── Aggregates/ # Domain aggregates
88-
│ │ ├── Events/ # Domain events
89-
│ │ ├── Commands/ # Command definitions
90-
│ │ ├── Handlers/ # Wolverine command handlers
91-
│ │ ├── Projections/ # Read model projections
92-
│ │ ├── Endpoints/ # API endpoints
93-
│ │ └── Infrastructure/ # Cross-cutting concerns
86+
│ ├── ApiService/
87+
│ │ ├── BookStore.ApiService/ # Backend API with event sourcing
88+
│ │ │ ├── Aggregates/ # Domain aggregates
89+
│ │ │ ├── Events/ # Domain events
90+
│ │ │ ├── Commands/ # Command definitions
91+
│ │ │ ├── Handlers/ # Wolverine command handlers
92+
│ │ │ ├── Projections/ # Read model projections
93+
│ │ │ ├── Endpoints/ # API endpoints
94+
│ │ │ └── Infrastructure/ # Cross-cutting concerns
95+
│ │ │
96+
│ │ └── BookStore.ApiService.Analyzers/ # Roslyn analyzers
9497
│ │
95-
│ ├── BookStore.Web/ # Blazor frontend
96-
│ │ ├── Components/ # Blazor components
97-
│ │ ├── Services/ # API client (Refit)
98-
│ │ └── Models/ # DTOs and view models
98+
│ ├── Client/
99+
│ │ └── BookStore.Client/ # Reusable API client library
100+
│ │ ├── IBookStoreApi.cs # Refit interface
101+
│ │ ├── BookStoreClientExtensions.cs # DI helpers
102+
│ │ └── README.md # Usage guide
99103
│ │
100-
│ ├── BookStore.AppHost/ # Aspire orchestration
101-
│ │ └── Program.cs # Service configuration
104+
│ ├── Web/
105+
│ │ └── BookStore.Web/ # Blazor frontend
106+
│ │ ├── Components/ # Blazor components
107+
│ │ └── Services/ # Application services
102108
│ │
103-
│ │ └── Extensions.cs # OpenTelemetry, health checks
109+
│ ├── Shared/
110+
│ │ ├── BookStore.Shared/ # Shared domain models & DTOs
111+
│ │ └── BookStore.Shared.Tests/ # Unit tests for shared code
104112
│ │
105-
│ ├── BookStore.Shared/ # Shared domain models & DTOs
106-
│ │ ├── BookStore.Shared/ # Shared library
107-
│ │ └── BookStore.Shared.Tests/# Unit tests for shared code
113+
│ ├── BookStore.AppHost/ # Aspire orchestration
114+
│ │ └── Program.cs # Service configuration
108115
│ │
109-
│ └── BookStore.Tests/ # Integration tests
116+
│ └── BookStore.ServiceDefaults/ # Shared service configuration
117+
│ └── Extensions.cs # OpenTelemetry, health checks
110118
111-
├── docs/ # Documentation
112-
│ ├── getting-started.md # Setup guide
113-
│ ├── architecture.md # System design
114-
│ ├── wolverine-guide.md # Command/handler pattern
115-
│ ├── time-standards.md # JSON and time standards
116-
│ ├── etag-guide.md # ETag usage
117-
│ └── correlation-causation-guide.md
119+
├── docs/ # Documentation
120+
│ ├── getting-started.md # Setup guide
121+
│ ├── architecture.md # System design
122+
│ ├── api-client-generation.md # Client library usage
123+
│ ├── wolverine-guide.md # Command/handler pattern
124+
│ └── ...
118125
119-
├── BookStore.slnx # Solution file (new .slnx format)
120-
└── README.md # This file
126+
├── _tools/ # Development tools
127+
│ └── update-openapi.sh # OpenAPI spec updater
128+
129+
├── BookStore.slnx # Solution file (new .slnx format)
130+
└── README.md # This file
121131
```
122132

123133
## 📖 Documentation
@@ -133,6 +143,7 @@ BookStore/
133143
- **[Testing Guide](docs/testing-guide.md)** - Testing with TUnit, assertions, and best practices
134144
- **[Wolverine Integration](docs/wolverine-guide.md)** - Command/handler pattern with Wolverine
135145
- **[API Conventions](docs/api-conventions-guide.md)** - Time handling and JSON serialization standards
146+
- **[API Client Generation](docs/api-client-generation.md)** - Automated client generation with OpenAPI and Refitter
136147
- **[ETag Support](docs/etag-guide.md)** - Optimistic concurrency and caching
137148
- **[Correlation & Causation IDs](docs/correlation-causation-guide.md)** - Distributed tracing
138149
- **[Caching Guide](docs/caching-guide.md)** - Hybrid caching with Redis and localization support
@@ -144,7 +155,7 @@ BookStore/
144155
### Frontend
145156
- **Blazor Web** - Interactive web UI with Server rendering
146157
- **SignalR Client** - Real-time notifications
147-
- **Refit** - Type-safe HTTP client
158+
- **BookStore.Client** - Reusable API client library (Refit-based)
148159
- **Polly** - Resilience and transient fault handling
149160

150161
### Backend

_tools/generate-client-nswag.sh

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
#!/bin/bash
2+
# Generate API client using NSwag (supports OpenAPI 3.1)
3+
# Alternative to Refitter with better OpenAPI 3.1 support
4+
5+
set -e
6+
7+
echo "🔄 Generating API client with NSwag..."
8+
echo ""
9+
10+
# Check if openapi.json exists
11+
if [ ! -f "openapi.json" ]; then
12+
echo "❌ openapi.json not found"
13+
echo ""
14+
echo "Run ./_tools/update-openapi.sh first to download the spec"
15+
exit 1
16+
fi
17+
18+
# Generate client
19+
echo "📝 Generating C# client..."
20+
nswag openapi2csclient \
21+
/input:openapi.json \
22+
/output:src/Web/BookStore.Web/Services/IBookStoreApi.cs \
23+
/namespace:BookStore.Web.Services \
24+
/className:BookStoreApiClient \
25+
/generateClientInterfaces:true \
26+
/generateDtoTypes:false \
27+
/useBaseUrl:false \
28+
/generateExceptionClasses:false
29+
30+
if [ $? -eq 0 ]; then
31+
echo ""
32+
echo "✅ Client generated successfully!"
33+
echo ""
34+
echo "📝 Next steps:"
35+
echo " 1. Review generated client: src/Web/BookStore.Web/Services/IBookStoreApi.cs"
36+
echo " 2. Build project: dotnet build"
37+
echo " 3. Commit changes: git add src/Web/BookStore.Web/Services/IBookStoreApi.cs"
38+
echo ""
39+
else
40+
echo ""
41+
echo "❌ Client generation failed"
42+
exit 1
43+
fi

_tools/update-openapi.sh

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
#!/bin/bash
2+
# Update OpenAPI specification from running API
3+
# This script works with Aspire's dynamic port assignment
4+
5+
set -e
6+
7+
echo "🔄 Updating OpenAPI specification..."
8+
echo ""
9+
10+
# Function to find API port from Aspire
11+
find_api_port() {
12+
# Try to find the API process
13+
local pid=$(ps aux | grep "BookStore.ApiService" | grep -v grep | awk '{print $2}' | head -1)
14+
15+
if [ -z "$pid" ]; then
16+
return 1
17+
fi
18+
19+
# Get the HTTPS port (usually the second LISTEN port)
20+
local port=$(lsof -p "$pid" 2>/dev/null | grep LISTEN | awk '{print $9}' | cut -d: -f2 | sort -u | tail -1)
21+
22+
if [ -z "$port" ]; then
23+
return 1
24+
fi
25+
26+
echo "$port"
27+
return 0
28+
}
29+
30+
# Check if API is running and find its port
31+
echo "🔍 Looking for running API..."
32+
API_PORT=$(find_api_port)
33+
34+
if [ -z "$API_PORT" ]; then
35+
echo "❌ API is not running"
36+
echo ""
37+
echo "Please start the API first:"
38+
echo " aspire run"
39+
echo ""
40+
echo "Or check the Aspire dashboard for the API URL"
41+
exit 1
42+
fi
43+
44+
echo "✓ Found API on port $API_PORT"
45+
echo ""
46+
47+
# Try HTTPS first, then HTTP
48+
echo "📥 Downloading OpenAPI spec..."
49+
if curl -k -s "https://localhost:$API_PORT/openapi/v1.json" -o openapi.json 2>/dev/null && [ -s openapi.json ]; then
50+
echo "✅ Downloaded from https://localhost:$API_PORT/openapi/v1.json"
51+
elif curl -s "http://localhost:$API_PORT/openapi/v1.json" -o openapi.json 2>/dev/null && [ -s openapi.json ]; then
52+
echo "✅ Downloaded from http://localhost:$API_PORT/openapi/v1.json"
53+
else
54+
echo "❌ Failed to download OpenAPI spec"
55+
echo ""
56+
echo "Try manually from Aspire dashboard:"
57+
echo " 1. Open Aspire dashboard (usually https://localhost:17161)"
58+
echo " 2. Find apiservice endpoint"
59+
echo " 3. Navigate to /openapi/v1.json"
60+
echo " 4. Save to openapi.json in repository root"
61+
exit 1
62+
fi
63+
64+
# Verify the file
65+
if [ ! -s openapi.json ]; then
66+
echo "❌ OpenAPI spec file is empty"
67+
exit 1
68+
fi
69+
70+
echo ""
71+
echo "✅ OpenAPI specification updated successfully!"
72+
echo ""
73+
echo "📝 Next steps:"
74+
echo " 1. Review changes: git diff openapi.json"
75+
echo " 2. Rebuild Web project: dotnet build src/Web/BookStore.Web"
76+
echo " 3. Verify client generated: ls -la src/Web/BookStore.Web/Services/IBookStoreApi.cs"
77+
echo " 4. Commit changes: git add openapi.json && git commit -m 'Update OpenAPI spec'"
78+
echo ""

docs/api-client-generation.md

Lines changed: 156 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,156 @@
1+
# API Client Generation Guide
2+
3+
This guide explains how to use the BookStore API client library.
4+
5+
## Overview
6+
7+
The BookStore API client is provided as a reusable library (`BookStore.Client`) that can be used by any .NET project.
8+
9+
**Technology Stack**:
10+
- **Refit**: Type-safe REST library
11+
- **BookStore.Client**: Standalone client library
12+
- **OpenAPI Spec**: Generated at runtime for documentation
13+
14+
## Using the Client Library
15+
16+
### Installation
17+
18+
Add a project reference to `BookStore.Client`:
19+
20+
```xml
21+
<ProjectReference Include="path/to/BookStore.Client/BookStore.Client.csproj" />
22+
```
23+
24+
### Registration
25+
26+
#### ASP.NET Core / Blazor
27+
28+
```csharp
29+
using BookStore.Client;
30+
31+
var builder = WebApplication.CreateBuilder(args);
32+
33+
// Register the client
34+
builder.Services
35+
.AddBookStoreClient(new Uri("https://api.bookstore.com"))
36+
.AddStandardResilienceHandler(); // Optional: Add Polly resilience
37+
38+
var app = builder.Build();
39+
```
40+
41+
#### Console Application
42+
43+
```csharp
44+
using BookStore.Client;
45+
using Microsoft.Extensions.DependencyInjection;
46+
47+
var services = new ServiceCollection();
48+
services.AddBookStoreClient(new Uri("https://api.bookstore.com"));
49+
50+
var provider = services.BuildServiceProvider();
51+
var client = provider.GetRequiredService<IBookStoreApi>();
52+
53+
// Use the client
54+
var books = await client.GetBooksAsync("clean code");
55+
```
56+
57+
### Usage
58+
59+
Inject `IBookStoreApi` into your services:
60+
61+
```csharp
62+
public class BookService
63+
{
64+
private readonly IBookStoreApi _api;
65+
66+
public BookService(IBookStoreApi api)
67+
{
68+
_api = api;
69+
}
70+
71+
public async Task<PagedListDto<BookDto>> SearchBooksAsync(
72+
string? query = null,
73+
int page = 1,
74+
int pageSize = 20,
75+
CancellationToken cancellationToken = default)
76+
{
77+
return await _api.GetBooksAsync(query, page, pageSize, cancellationToken);
78+
}
79+
}
80+
```
81+
82+
## Available Endpoints
83+
84+
See `IBookStoreApi` for all available methods:
85+
86+
- **Books**: `GetBooksAsync`, `GetBookAsync`
87+
- **Authors**: `GetAuthorsAsync`, `GetAuthorAsync`
88+
- **Categories**: `GetCategoriesAsync`, `GetCategoryAsync`
89+
- **Publishers**: `GetPublishersAsync`, `GetPublisherAsync`
90+
91+
## Resilience (Optional)
92+
93+
Add Polly resilience policies:
94+
95+
```csharp
96+
// Standard resilience (recommended)
97+
builder.Services
98+
.AddBookStoreClient(apiUrl)
99+
.AddStandardResilienceHandler();
100+
101+
// Custom Polly policies
102+
builder.Services
103+
.AddBookStoreClient(apiUrl)
104+
.AddPolicyHandler(retryPolicy)
105+
.AddPolicyHandler(circuitBreakerPolicy);
106+
```
107+
108+
## Maintaining the Client
109+
110+
### When API Changes
111+
112+
1. **Update OpenAPI Spec**:
113+
```bash
114+
./_tools/update-openapi.sh
115+
```
116+
117+
2. **Update `IBookStoreApi.cs`** if endpoints changed:
118+
- Location: `src/Client/BookStore.Client/IBookStoreApi.cs`
119+
- Add/update methods to match API changes
120+
121+
3. **Build and Test**:
122+
```bash
123+
dotnet build
124+
dotnet test
125+
```
126+
127+
4. **Commit Changes**:
128+
```bash
129+
git add openapi.json src/Client/BookStore.Client/IBookStoreApi.cs
130+
git commit -m "Update API client: [description]"
131+
```
132+
133+
### Update Script
134+
135+
The `update-openapi.sh` script:
136+
- Auto-detects Aspire's dynamic ports
137+
- Downloads the OpenAPI spec
138+
- Validates the file
139+
140+
## OpenAPI Specification
141+
142+
**Location**: `openapi.json` (repository root)
143+
144+
**Purpose**:
145+
- API contract documentation
146+
- Scalar UI documentation (`/scalar/v1`)
147+
- Contract validation
148+
- Manual client updates
149+
150+
**Format**: OpenAPI 3.1.1 (generated by .NET 9)
151+
152+
## References
153+
154+
- [Refit Documentation](https://github.com/reactiveui/refit)
155+
- [OpenAPI 3.1 Specification](https://spec.openapis.org/oas/v3.1.0)
156+
- [Client Library README](../src/Client/BookStore.Client/README.md)

0 commit comments

Comments
 (0)