This repository demonstrates three progressively more structured implementations of a weather API using .NET — from a basic monolith to a clean hexagonal architecture with multiple bounded contexts.
⚙️ Based on the official Microsoft weather API template (
dotnet new webapi
) with slight modifications.
- Exposes a basic
/weatherforecast
endpoint - Uses random temperature generation
- Lacks architectural layering or separation of concerns
- Serves as a baseline for comparison
🛠 Implements hexagonal architecture (a.k.a. Ports & Adapters)
- Domain-focused
WeatherService
handles requests - Uses
IWeatherProvider
interface to decouple the weather data source - Domain entity:
WeatherForecast
(city, temperature) - Adapters:
RandomWeatherForecastAdapter
: returns random tempsWeatherApiForecastAdapter
: fetches real data (e.g. from WeatherApi)
- Exposed via REST API (
app.MapGet("/weatherforecast/{city}"
)
- API controller calls the domain service
- Service calls injected ports:
- Weather provider
- Testable and modular
- Clear separation of concerns
- Swappable adapters without changing the domain
🧩 Illustrates multi-hexagon communication using separate, independent modules with explicit interfaces in the Weather Module
- Weather Module: same logic as
HexagonalWeatherApp
- Exposes
IWeatherForecastQueryNotifier
- Exposes
IWeatherForecastQueryLogger
- Adapters:
RandomWeatherForecastAdapter
: returns random tempsWeatherApiForecastAdapter
: fetches real data (e.g. from WeatherApi)
- Exposes
- Notification Module:
- Exposes
INotificationEmailSender
- Adapter:
EmailWeatherNotifierAdapter
usingINotificationEmailSender
(mocked withFakeEmailSender
)
- Exposes
- History Module:
- Adapter:
EfHistoryQueryLoggerAdapter
stores past queries using Entity Framework
- Adapter:
Hexagon | Role |
---|---|
Weather | Core logic & API |
Notification | Sends alerts (email, etc) |
History | Logs queries to database |
The WeatherService
orchestrates both cross-hexagon calls via explicit interfaces injected through dependency injection.
🧩 Illustrates multi-hexagon communication using separate, independent modules without explicit interfaces in the Weather Module. The orchestration is handled by
GetWeatherForecastUseCase
in a dedicated Use Case module.
- Weather Module: same logic as
HexagonalWeatherApp
- Adapters:
RandomWeatherForecastAdapter
: returns random tempsWeatherApiForecastAdapter
: fetches real data (e.g. from WeatherApi)
- Adapters:
- Notification Module:
- Exposes
INotificationEmailSender
- Exposes
IWeatherForecastQueryNotifier
- Adapter:
EmailWeatherNotifierAdapter
usingINotificationEmailSender
(mocked withFakeEmailSender
)
- Exposes
- History Module:
- Exposes
IWeatherForecastQueryLogger
- Adapter:
EfHistoryQueryLoggerAdapter
stores past queries using Entity Framework
- Exposes
- UseCases Module:
- Orchestrate the Weather, Notification and History modules.
- Exposes
GetWeatherForecastUseCase
The GetWeatherForecastUseCase
use case orchestrates both cross-hexagon calls via injected through dependency injection.
No more side effects interfaces in the Weather Module at the cost of an extra-layer.
Before running the HexagonalWeatherApp
, MultiHexagonalWeatherApp
or MultiHexagonalWithUseCaseWeatherApp
, make sure to configure your API key for the weather provider.
All three hexagonal applications use an external weather API (e.g. WeatherApi). To inject the API key securely:
cd HexagonalWeatherApp
dotnet user-secrets init
dotnet user-secrets set "WeatherApi:ApiKey" "YOUR_KEY_VALUE_HERE"
- .NET 9
- ASP.NET Core
- Entity Framework Core (InMemory)
- Clean Hexagonal Architecture (Ports & Adapters)
- WeatherApi
Big thanks to my cats 🐱 who made sure I never wrote more than three lines of code without a tail on the keyboard, a paw on the screen, or a loud meow demanding attention.
This architecture may be hexagonal, but the inspiration was pure feline.