This REST API solution demonstrates how to create a clean, modern API (from my point of view) using Clean Architecture, Minimal API, and various design patterns.
The example API allows users to retrieve current and forecasted weather data by location from Weatherbit via RapidAPI. It also allows users to add favorite locations to an in memory database and retrieve weather data for those stored locations.
- .NET SDK 9.0.x
To install the project using Git Bash:
- Clone the repository:
git clone https://github.com/Gramli/WeatherApi.git
- Navigate to the project directory:
cd WeatherApi/src
- Install the backend dependencies:
dotnet restore
- Register on RapidAPI
- Subscribe Weatherbit (its for free) and go to Endpoints tab
- In API documentation copy (from Code Snippet) X-RapidAPI-Key, X-RapidAPI-Host and put them to appsettings.json file in WeatherAPI project
"Weatherbit": {
"BaseUrl": "https://weatherbit-v1-mashape.p.rapidapi.com",
"XRapidAPIKey": "value from code snippet",
"XRapidAPIHost": "value from code snippet"
}
- Run Weather.API
- Go to Tests/Debug folder and open debug-tests.http file (in VS2022)
- Send request
The main motivation for this project is to create a practical example of Minimal API, to explore its benefits and drawbacks, and to build a REST API using Clean Architecture and various design patterns.
This project follows Clean Architecture. The application layer is split into the Core and Domain projects, where the Core project holds the business rules, and the Domain project contains the business entities.
Since Minimal API allows injecting handlers into endpoint mapping methods, I decided not to use MediatR. Instead, each endpoint has its own request and handler. The solution follows the CQRS pattern, where handlers are separated into commands and queries. Command handlers handle command requests, while query handlers handle query requests. Additionally, repositories (Repository pattern) are also separated into command and query repositories.
Instead of throwing exceptions, the project uses the Result pattern (using the FluentResuls package). Every handler returns data wrapped in an HttpDataResponse object, which also contains a collection of error messages and an HTTP status code.
Each HTTP status code's response is wrapped in a DataResponse class, which holds the result data and any errors. This approach allows, for example, returning error messages alongside an "OK" status code.
An important aspect of any project is testing. When writing tests, we aim for optimal code coverage. I believe every project has its own optimal coverage based on its needs. My rule is: cover your code enough to confidently refactor without worrying about functionality changes.
In this solution, each code project has its own unit test project, and each unit test project mirrors the directory structure of its respective code project. This structure helps with organization in larger projects.
To ensure the REST API works as expected for end users, we write system tests. These tests typically call API endpoints in a specific order defined by business requirements and check the expected results. The solution contains simple System Tests, which call the exposed endpoints and validate the response.
-
WeatherAPI This is the entry point of the application and the top layer, containing:
- Endpoints: Define and expose application routes.
- Middlewares (or Filters): Handle cross-cutting concerns like exception handling and logging.
- API Configuration: Centralized setup for services, routes, and middleware.
-
Weather.Infrastructure This layer handles communication with external resources, such as databases, caches, and web services. It includes:
- Repositories Implementation: Provides access to the database.
- External Services Proxies: Proxy classes for obtaining data from external web services.
- ** Weatherbit.Client** - A standalone project dedicated to communication with RapidAPI/Weatherbit.
- Infrastructure-Specific Services: Services required to interact with external libraries and frameworks.
-
Weather.Core This layer contains the application's business logic, including:
- Request Handlers/Managers: Implement business operations and workflows.
- Abstractions: Define interfaces and contracts, including abstractions for the infrastructure layer (e.g., services, repositories) to ensure their usability in the core layer.
-
Weather.Domain Contains shared components that are used across all projects, such as:
- DTOs: Data Transfer Objects for communication between layers.
- General Extensions: Common utilities and extension methods.